diff --git a/.github/workflows/prune-container-images.yml b/.github/workflows/prune-container-images.yml new file mode 100644 index 00000000000..c1653701c3b --- /dev/null +++ b/.github/workflows/prune-container-images.yml @@ -0,0 +1,22 @@ +name: 'Prune container images' + +on: + schedule: + - cron: '0 12 * * 1' + workflow_dispatch: + +jobs: + container-image: + if: github.repository_owner == 'opentripplanner' + runs-on: ubuntu-latest + steps: + - name: Delete unused container images + env: + CONTAINER_REPO: opentripplanner/opentripplanner + CONTAINER_REGISTRY_USER: otpbot + CONTAINER_REGISTRY_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} + run: | + # remove all snapshot container images that have not been pulled for over a year + # --keep-semver makes sure that any image with a x.y.z version scheme is unaffected by this + pip install prune-container-repo==0.0.4 + prune-container-repo -u ${CONTAINER_REGISTRY_USER} -r ${CONTAINER_REPO} --days=365 --keep-semver --activate diff --git a/doc-templates/StopConsolidation.md b/doc-templates/StopConsolidation.md index 690fe0b98e6..c92cab6afe1 100644 --- a/doc-templates/StopConsolidation.md +++ b/doc-templates/StopConsolidation.md @@ -30,7 +30,7 @@ This has the following consequences However, this feature has also severe downsides: - - It makes realtime trip updates referencing a stop id much more complicated and in many cases + - It makes real-time trip updates referencing a stop id much more complicated and in many cases impossible to resolve. You can only reference a stop by its sequence, which only works in GTFS-RT, not Siri. - Fare calculation and transfers are unlikely to work as expected. diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index b32f782aa32..05da4cf4d0a 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -28,7 +28,6 @@ Sections follow that describe particular settings in more depth. | [graph](#graph) | `uri` | URI to the graph object file for reading and writing. | *Optional* | | 2.0 | | [gsCredentials](#gsCredentials) | `string` | Local file system path to Google Cloud Platform service accounts credentials file. | *Optional* | | 2.0 | | [includeEllipsoidToGeoidDifference](#includeEllipsoidToGeoidDifference) | `boolean` | Include the Ellipsoid to Geoid difference in the calculations of every point along every StreetWithElevationEdge. | *Optional* | `false` | 2.0 | -| matchBusRoutesToStreets | `boolean` | Based on GTFS shape data, guess which OSM streets each bus runs on to improve stop linking. | *Optional* | `false` | 1.5 | | maxAreaNodes | `integer` | Visibility calculations for an area will not be done if there are more nodes than this limit. | *Optional* | `150` | 2.1 | | [maxDataImportIssuesPerFile](#maxDataImportIssuesPerFile) | `integer` | When to split the import report. | *Optional* | `1000` | 2.0 | | maxElevationPropagationMeters | `integer` | The maximum distance to propagate elevation to vertices which have no elevation. | *Optional* | `2000` | 1.5 | diff --git a/docs/Changelog.md b/docs/Changelog.md index bccd0c504d5..06beb1d120c 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -40,7 +40,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Interpolate increasing stop times for GTFS-RT cancelled trips [#5348](https://github.com/opentripplanner/OpenTripPlanner/pull/5348) - Remove itineraries outside the search window in arriveBy search [#5433](https://github.com/opentripplanner/OpenTripPlanner/pull/5433) - Add back walk-reluctance in Transmodel API [#5471](https://github.com/opentripplanner/OpenTripPlanner/pull/5471) -- Make `feedId` required for realtime updaters [#5502](https://github.com/opentripplanner/OpenTripPlanner/pull/5502) +- Make `feedId` required for real-time updaters [#5502](https://github.com/opentripplanner/OpenTripPlanner/pull/5502) - Fix serialization of `AtomicInteger` [#5508](https://github.com/opentripplanner/OpenTripPlanner/pull/5508) - Improve linking of fixed stops used by flex trips [#5503](https://github.com/opentripplanner/OpenTripPlanner/pull/5503) - Keep min transfer filter is not local to group-by-filters [#5436](https://github.com/opentripplanner/OpenTripPlanner/pull/5436) @@ -51,6 +51,10 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Transfer cost limit [#5516](https://github.com/opentripplanner/OpenTripPlanner/pull/5516) - Fix missed trip when arrive-by search-window is off by one minute [#5520](https://github.com/opentripplanner/OpenTripPlanner/pull/5520) - Transit group priority - Part 1 [#4999](https://github.com/opentripplanner/OpenTripPlanner/pull/4999) +- Remove `matchBusRoutesToStreets` [#5523](https://github.com/opentripplanner/OpenTripPlanner/pull/5523) +- Rename realtime to real-time in docs [#5535](https://github.com/opentripplanner/OpenTripPlanner/pull/5535) +- Add same submode in alternative legs filter [#5548](https://github.com/opentripplanner/OpenTripPlanner/pull/5548) +- Fix issue where stop points are sometimes added twice to index [#5552](https://github.com/opentripplanner/OpenTripPlanner/pull/5552) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) @@ -88,7 +92,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Graceful timeout error handling [#5130](https://github.com/opentripplanner/OpenTripPlanner/pull/5130) - Log http request headers - like correlationId [#5131](https://github.com/opentripplanner/OpenTripPlanner/pull/5131) - Fix vertex removal race condition [#5141](https://github.com/opentripplanner/OpenTripPlanner/pull/5141) -- Comment out replacing DSJ-ID from planned data with ID from realtime-data [#5140](https://github.com/opentripplanner/OpenTripPlanner/pull/5140) +- Comment out replacing DSJ-ID from planned data with ID from real-time-data [#5140](https://github.com/opentripplanner/OpenTripPlanner/pull/5140) - Remove San Francisco and vehicle rental fare calculators [#5145](https://github.com/opentripplanner/OpenTripPlanner/pull/5145) - Remove batch query from Transmodel API [#5147](https://github.com/opentripplanner/OpenTripPlanner/pull/5147) - Fix nullable absolute direction in GTFS GraphQL API [#5159](https://github.com/opentripplanner/OpenTripPlanner/pull/5159) @@ -186,7 +190,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Remove all edges from stop vertex in island pruning [#4846](https://github.com/opentripplanner/OpenTripPlanner/pull/4846) - Filter functionality for GroupOfLines/GroupOfRoutes in TransmodelAPI [#4812](https://github.com/opentripplanner/OpenTripPlanner/pull/4812) - Mapping for maxAccessEgressDurationPerMode in Transmodel API [#4829](https://github.com/opentripplanner/OpenTripPlanner/pull/4829) -- Use headsign from the original pattern in a realtime added pattern if the stop sequence is unchanged [#4845](https://github.com/opentripplanner/OpenTripPlanner/pull/4845) +- Use headsign from the original pattern in a real-time added pattern if the stop sequence is unchanged [#4845](https://github.com/opentripplanner/OpenTripPlanner/pull/4845) - Remove RouteMatcher [#4821](https://github.com/opentripplanner/OpenTripPlanner/pull/4821) - Improve boarding location linking on platforms [#4852](https://github.com/opentripplanner/OpenTripPlanner/pull/4852) - Always check allowed modes in VehicleRentalEdge [#4810](https://github.com/opentripplanner/OpenTripPlanner/pull/4810) @@ -234,7 +238,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Experimental support for GTFS Fares V2 [#4338](https://github.com/opentripplanner/OpenTripPlanner/pull/4338) - Document JVM configuration options [#4492](https://github.com/opentripplanner/OpenTripPlanner/pull/4492) - Support for HTTPS datasource for Graph Building [#4482](https://github.com/opentripplanner/OpenTripPlanner/pull/4482) -- Metrics for realtime trip updaters [#4471](https://github.com/opentripplanner/OpenTripPlanner/pull/4471) +- Metrics for real-time trip updaters [#4471](https://github.com/opentripplanner/OpenTripPlanner/pull/4471) - Configuration Documentation generated programmatically [#4478](https://github.com/opentripplanner/OpenTripPlanner/pull/4478) @@ -265,7 +269,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Enable overriding maxDirectStreetDuration per mode [#4104](https://github.com/opentripplanner/OpenTripPlanner/pull/4104) - Preserve language in SIRI/GTFS-RT alert messages [#4117](https://github.com/opentripplanner/OpenTripPlanner/pull/4117) - Use board/alight cost only for transits [#4079](https://github.com/opentripplanner/OpenTripPlanner/pull/4079) -- Improve SIRI realtime performance by reducing stopPattern duplicates [#4038](https://github.com/opentripplanner/OpenTripPlanner/pull/4038) +- Improve SIRI real-time performance by reducing stopPattern duplicates [#4038](https://github.com/opentripplanner/OpenTripPlanner/pull/4038) - Siri updaters for Azure ServiceBus [#4106](https://github.com/opentripplanner/OpenTripPlanner/pull/4106) - Fallback to recorded/expected arrival/departure time if other one is missing in SIRI-ET [#4055](https://github.com/opentripplanner/OpenTripPlanner/pull/4055) - Allow overriding GBFS system_id with configuration [#4147](https://github.com/opentripplanner/OpenTripPlanner/pull/4147) @@ -274,7 +278,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Don't indicate stop has been updated when NO_DATA is defined [#3962](https://github.com/opentripplanner/OpenTripPlanner/pull/3962) - Implement nearby searches for car and bicycle parking [#4165](https://github.com/opentripplanner/OpenTripPlanner/pull/4165) - Do not link cars to stop vertices in routing [#4166](https://github.com/opentripplanner/OpenTripPlanner/pull/4166) -- Add Siri realtime occupancy info [#4180](https://github.com/opentripplanner/OpenTripPlanner/pull/4180) +- Add Siri real-time occupancy info [#4180](https://github.com/opentripplanner/OpenTripPlanner/pull/4180) - Add gtfs stop description translations [#4158](https://github.com/opentripplanner/OpenTripPlanner/pull/4158) - Add option to discard min transfer times [#4195](https://github.com/opentripplanner/OpenTripPlanner/pull/4195) - Use negative delay from first stop in a GTFS RT update in previous stop times when required [#4035](https://github.com/opentripplanner/OpenTripPlanner/pull/4035) @@ -303,7 +307,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Account for boarding restrictions when calculating direct transfers [#4421](https://github.com/opentripplanner/OpenTripPlanner/pull/4421) - Configure the import of OSM extracts individually [#4419](https://github.com/opentripplanner/OpenTripPlanner/pull/4419) - Configure the import of elevation data individually [#4423](https://github.com/opentripplanner/OpenTripPlanner/pull/4423) -- Return typed errors from realtime updates, prepare for realtime statistics [#4424](https://github.com/opentripplanner/OpenTripPlanner/pull/4424) +- Return typed errors from real-time updates, prepare for real-time statistics [#4424](https://github.com/opentripplanner/OpenTripPlanner/pull/4424) - Add feature switch for matching ET messages on stops [#4347](https://github.com/opentripplanner/OpenTripPlanner/pull/4347) - Make safety defaults customizable for walking and cycling [#4438](https://github.com/opentripplanner/OpenTripPlanner/pull/4438) - Fix block-based interlining when importing several GTFS feeds [#4468](https://github.com/opentripplanner/OpenTripPlanner/pull/4468) @@ -341,7 +345,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle ### Detailed changes by Pull Request -- Fix NullPointerException when a RealTime update do not match an existing TripPattern [#3284](https://github.com/opentripplanner/OpenTripPlanner/issues/3284) +- Fix NullPointerException when a Real-Time update do not match an existing TripPattern [#3284](https://github.com/opentripplanner/OpenTripPlanner/issues/3284) - Support for versioning the configuration files [#3282](https://github.com/opentripplanner/OpenTripPlanner/issues/3282) - Prioritize "direct" routes over transfers in group-filters [#3309](https://github.com/opentripplanner/OpenTripPlanner/issues/3309) - Remove poor transit results for short trips, when walking is better [#3331](https://github.com/opentripplanner/OpenTripPlanner/issues/3331) @@ -428,7 +432,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Remove build parameter 'useTransfersTxt' [#3791](https://github.com/opentripplanner/OpenTripPlanner/pull/3791) - Add cursor-based paging [#3759](https://github.com/opentripplanner/OpenTripPlanner/pull/3759) - Data overlay sandbox feature [#3760](https://github.com/opentripplanner/OpenTripPlanner/pull/3760) -- Add support for sandboxed realtime vehicle parking updaters [#3796](https://github.com/opentripplanner/OpenTripPlanner/pull/3796) +- Add support for sandboxed real-time vehicle parking updaters [#3796](https://github.com/opentripplanner/OpenTripPlanner/pull/3796) - Add reading and exposing of Netex submodes [#3793](https://github.com/opentripplanner/OpenTripPlanner/pull/3793) - Fix: Account for wait-time in no-wait Raptor strategy [#3798](https://github.com/opentripplanner/OpenTripPlanner/pull/3798) - Read in flex window from Netex feeds [#3800](https://github.com/opentripplanner/OpenTripPlanner/pull/3800) @@ -438,7 +442,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Store stop indices in leg and use them to simplify logic in TripTimeShortHelper [#3820](https://github.com/opentripplanner/OpenTripPlanner/pull/3820) - Include all trips in `stopTimesForStop` [#3817](https://github.com/opentripplanner/OpenTripPlanner/pull/3817) - Store all alerts and add support for route_type and direction_id selectors [#3780](https://github.com/opentripplanner/OpenTripPlanner/pull/3780) -- Remove outdated realtime-update from TimetableSnapshot [#3770](https://github.com/opentripplanner/OpenTripPlanner/pull/3770) +- Remove outdated real-time-update from TimetableSnapshot [#3770](https://github.com/opentripplanner/OpenTripPlanner/pull/3770) - Contributing Guide [#3769](https://github.com/opentripplanner/OpenTripPlanner/pull/3769) - OTP support for NeTEx branding [#3829](https://github.com/opentripplanner/OpenTripPlanner/pull/3829) - Not allowed transfers and support for GTFS transfer points [#3792](https://github.com/opentripplanner/OpenTripPlanner/pull/3792) @@ -465,7 +469,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Optimize RAPTOR trip search by pre-calculating arrival/departure time arrays [#3919](https://github.com/opentripplanner/OpenTripPlanner/pull/3919) - Make turn restrictions faster and thread-safe by moving them into StreetEdge [#3899](https://github.com/opentripplanner/OpenTripPlanner/pull/3899) - Add routing using frequency trips [#3916](https://github.com/opentripplanner/OpenTripPlanner/pull/3916) -- Remove ET realtime override code [#3912](https://github.com/opentripplanner/OpenTripPlanner/pull/3912) +- Remove ET real-time override code [#3912](https://github.com/opentripplanner/OpenTripPlanner/pull/3912) - Allow traversal of pathways without traversal time, distance or steps [#3910](https://github.com/opentripplanner/OpenTripPlanner/pull/3910) @@ -495,8 +499,8 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Support for next/previous paging trip search results [#2941](https://github.com/opentripplanner/OpenTripPlanner/issues/2941) - Fix mismatch in duration for walk legs, resulting in negative wait times [#2955](https://github.com/opentripplanner/OpenTripPlanner/issues/2955) - NeTEx import now supports ServiceLinks [#2951](https://github.com/opentripplanner/OpenTripPlanner/issues/2951) -- Also check TripPatterns added by realtime when showing stoptimes for stop [#2954](https://github.com/opentripplanner/OpenTripPlanner/issues/2954) -- Copy geometries from previous TripPattern when realtime updates result in a TripPattern being replaced [#2987](https://github.com/opentripplanner/OpenTripPlanner/issues/2987) +- Also check TripPatterns added by real-time when showing stoptimes for stop [#2954](https://github.com/opentripplanner/OpenTripPlanner/issues/2954) +- Copy geometries from previous TripPattern when real-time updates result in a TripPattern being replaced [#2987](https://github.com/opentripplanner/OpenTripPlanner/issues/2987) - Support for the Norwegian language. - Update pathways support to official GTFS specification [#2923](https://github.com/opentripplanner/OpenTripPlanner/issues/2923) - Support for XML (de-)serialization is REMOVED from the REST API [#3031](https://github.com/opentripplanner/OpenTripPlanner/issues/3031) @@ -532,7 +536,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Remove the coupling to OneBusAway GTFS within OTP's internal model by creating new classes replacing the external classes [#2494](https://github.com/opentripplanner/OpenTripPlanner/issues/2494) - Allow itineraries in response to be sorted by duration [#2593](https://github.com/opentripplanner/OpenTripPlanner/issues/2593) - Fix reverse optimization bug #2653, #2411 -- increase GTFS-realtime feeds size limit from 64MB to 2G [#2738](https://github.com/opentripplanner/OpenTripPlanner/issues/2738) +- increase GTFS-real-time feeds size limit from 64MB to 2G [#2738](https://github.com/opentripplanner/OpenTripPlanner/issues/2738) - Fix XML response serialization [#2685](https://github.com/opentripplanner/OpenTripPlanner/issues/2685) - Refactor InterleavedBidirectionalHeuristic [#2671](https://github.com/opentripplanner/OpenTripPlanner/issues/2671) - Add "Accept" headers to GTFS-RT HTTP requests [#2796](https://github.com/opentripplanner/OpenTripPlanner/issues/2796) @@ -544,7 +548,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Support OSM highway=razed tag [#2660](https://github.com/opentripplanner/OpenTripPlanner/issues/2660) - Add bicimad bike rental updater [#2503](https://github.com/opentripplanner/OpenTripPlanner/issues/2503) - Add Smoove citybikes updater [#2515](https://github.com/opentripplanner/OpenTripPlanner/issues/2515) -- Allow big GTFS-realtime feeds by increasing protobuf size limit to 2G [#2739](https://github.com/opentripplanner/OpenTripPlanner/issues/2739) +- Allow big GTFS-real-time feeds by increasing protobuf size limit to 2G [#2739](https://github.com/opentripplanner/OpenTripPlanner/issues/2739) - Cannot transfer between stops at exactly the same location [#2371](https://github.com/opentripplanner/OpenTripPlanner/issues/2371) - Improve documentation for `mode` routing parameter [#2809](https://github.com/opentripplanner/OpenTripPlanner/issues/2809) - Switched to single license file, removing all OTP and OBA file license headers @@ -781,7 +785,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Disable some time consuming graph building steps by default - Finnish and Swedish translations - Subway-specific JSON configuration options (street to platform time) -- Realtime fetch / streaming configurable via JSON +- Real-time fetch / streaming configurable via JSON - Stairs reluctance is much higher when carrying a bike - Graph visualizer routing progress animates when a search is triggered via the web API - Assume WGS84 (spherical distance calculations) everywhere @@ -849,7 +853,7 @@ represents a much earlier stage in the development of OTP. ## 0.7.0 (2012-04-29) - Bike rental support (thanks Laurent Grégoire) -- Realtime bike rental availability feed support +- Real-time bike rental availability feed support - Updated to new version of One Bus Away GTFS/CSV, fixing timezone and string interning issues (thanks Brian Ferris) - Bugfixes in area routing, OSM loading, nonexistant NED tiles, route short names - Dutch and French language updates @@ -876,7 +880,7 @@ represents a much earlier stage in the development of OTP. - git commit IDs included in MavenVersion, allowing clearer OTP/Graph version mismatch warnings - fix problems with immediate reboarding and unexpected edges in itinerary builder - favicon (thanks Joel Haasnoot) -- Legs in API response have TripId (for realtime information) +- Legs in API response have TripId (for real-time information) - Polish locale (thanks Łukasz Witkowski) - transfers.txt can define station paths, entry costs for stations - allow loading a base graph into graphbuilder instead of starting from scratch diff --git a/docs/Configuration.md b/docs/Configuration.md index bfd168d5955..998842d3ffb 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -65,29 +65,29 @@ documentation below we will refer to the following types: -| Type | Description | Examples | -|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| -| `boolean` | This is the Boolean JSON type | `true`, `false` | -| `string` | This is the String JSON type. | `"This is a string!"` | -| `double` | A decimal floating point _number_. 64 bit. | `3.15` | -| `integer` | A decimal integer _number_. 32 bit. | `1`, `-7`, `2100` | -| `long` | A decimal integer _number_. 64 bit. | `-1234567890` | -| `enum` | A fixed set of string literals. | `"RAIL"`, `"BUS"` | -| `enum-map` | List of key/value pairs, where the key is a enum and the value can be any given type. | `{ "RAIL: 1.2, "BUS": 2.3 }` | -| `enum-set` | List of enum string values | `[ "RAIL", "TRAM" ]` | -| `locale` | _`Language[\_country[\_variant]]`_. A Locale object represents a specific geographical, political, or cultural region. For more information see the [Java Locale](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Locale.html). | `"en_US"`, `"nn_NO"` | -| `date` | Local date. The format is _YYYY-MM-DD_ (ISO-8601). | `"2020-09-21"` | -| `date-or-period` | A _local date_, or a _period_ relative to today. The local date has the format `YYYY-MM-DD` and the period has the format `PnYnMnD` or `-PnYnMnD` where `n` is a integer number. | `"P1Y"`, `"-P3M2D"`, `"P1D"` | -| `duration` | A _duration_ is a amount of time. The format is `PnDTnHnMnS` or `nDnHnMnS` where `n` is a integer number. The `D`(days), `H`(hours), `M`(minutes) and `S`(seconds) are not case sensitive. | `"3h"`, `"2m"`, `"1d5h2m3s"`, `"-P2dT-1s"` | -| `regexp` | A regular expression pattern used to match a sting. | `"$^"`, `"gtfs"`, `"\w{3})-.*\.xml"` | -| `uri` | An URI path to a resource like a file or a URL. Relative URIs are resolved relative to the OTP base path. | `"http://foo.bar/"`, `"file:///Users/jon/local/file"`, `"graph.obj"` | -| `time-zone` | Time-Zone ID | `"UTC"`, `"Europe/Paris"`, `"-05:00"` | -| `feed-scoped-id` | FeedScopedId | `"NO:1001"`, `"1:101"` | -| `cost-linear-function` | A cost-linear-function used to calculate a cost from another cost or time/duration. Given a function of time: ``` f(t) = a + b * t ``` then `a` is the constant time part, `b` is the time-coefficient, and `t` is the variable. If `a=0s` and `b=0.0`, then the cost is always `0`(zero). Examples: `0s + 2.5t`, `10m + 0t` and `1h5m59s + 9.9t` The `constant` must be 0 or a positive number or duration. The unit is seconds unless specified using the duration format. A duration is automatically converted to a cost. The `coefficient` must be in range: [0.0, 100.0] | | -| `time-penalty` | A time-penalty is used to add a penalty to the duration/arrival-time/depature-time for a path. It will be invisible to the end user, but used during the routing when comparing stop-arrival/paths. Given a function of time: ``` f(t) = a + b * t ``` then `a` is the constant time part, `b` is the time-coefficient, and `t` is the variable. If `a=0s` and `b=0.0`, then the cost is always `0`(zero). Examples: `0s + 2.5t`, `10m + 0 x` and `1h5m59s + 9.9t` The `constant` must be 0 or a positive number(seconds) or a duration. The `coefficient` must be in range: [0.0, 100.0] | | -| `map` | List of key/value pairs, where the key is a string and the value can be any given type. | `{ "one": 1.2, "two": 2.3 }` | -| `object` | Config object containing nested elements | `"walk": { "speed": 1.3, "reluctance": 5 }` | -| `array` | Config object containing an array/list of elements | `"array": [ 1, 2, 3 ]` | +| Type | Description | Examples | +|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| +| `boolean` | This is the Boolean JSON type | `true`, `false` | +| `string` | This is the String JSON type. | `"This is a string!"` | +| `double` | A decimal floating point _number_. 64 bit. | `3.15` | +| `integer` | A decimal integer _number_. 32 bit. | `1`, `-7`, `2100` | +| `long` | A decimal integer _number_. 64 bit. | `-1234567890` | +| `enum` | A fixed set of string literals. | `"RAIL"`, `"BUS"` | +| `enum-map` | List of key/value pairs, where the key is a enum and the value can be any given type. | `{ "RAIL: 1.2, "BUS": 2.3 }` | +| `enum-set` | List of enum string values | `[ "RAIL", "TRAM" ]` | +| `locale` | _`Language[\_country[\_variant]]`_. A Locale object represents a specific geographical, political, or cultural region. For more information see the [Java Locale](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Locale.html). | `"en_US"`, `"nn_NO"` | +| `date` | Local date. The format is _YYYY-MM-DD_ (ISO-8601). | `"2020-09-21"` | +| `date-or-period` | A _local date_, or a _period_ relative to today. The local date has the format `YYYY-MM-DD` and the period has the format `PnYnMnD` or `-PnYnMnD` where `n` is a integer number. | `"P1Y"`, `"-P3M2D"`, `"P1D"` | +| `duration` | A _duration_ is a amount of time. The format is `PnDTnHnMnS` or `nDnHnMnS` where `n` is a integer number. The `D`(days), `H`(hours), `M`(minutes) and `S`(seconds) are not case sensitive. | `"3h"`, `"2m"`, `"1d5h2m3s"`, `"-P2dT-1s"` | +| `regexp` | A regular expression pattern used to match a sting. | `"$^"`, `"gtfs"`, `"\w{3})-.*\.xml"` | +| `uri` | An URI path to a resource like a file or a URL. Relative URIs are resolved relative to the OTP base path. | `"http://foo.bar/"`, `"file:///Users/jon/local/file"`, `"graph.obj"` | +| `time-zone` | Time-Zone ID | `"UTC"`, `"Europe/Paris"`, `"-05:00"` | +| `feed-scoped-id` | FeedScopedId | `"NO:1001"`, `"1:101"` | +| `cost-linear-function` | A cost-linear-function used to calculate a cost from another cost or time/duration. Given a function of time: ``` f(t) = a + b * t ``` then `a` is the constant time part, `b` is the time-coefficient, and `t` is the variable. If `a=0s` and `b=0.0`, then the cost is always `0`(zero). Examples: `0s + 2.5t`, `10m + 0t` and `1h5m59s + 9.9t` The `constant` must be 0 or a positive number or duration. The unit is seconds unless specified using the duration format. A duration is automatically converted to a cost. The `coefficient` must be in range: [0.0, 100.0] | | +| `time-penalty` | A time-penalty is used to add a penalty to the duration/arrival-time/departure-time for a path. It will be invisible to the end user, but used during the routing when comparing stop-arrival/paths. Given a function of time: ``` f(t) = a + b * t ``` then `a` is the constant time part, `b` is the time-coefficient, and `t` is the variable. If `a=0s` and `b=0.0`, then the cost is always `0`(zero). Examples: `0s + 2.5t`, `10m + 0 x` and `1h5m59s + 9.9t` The `constant` must be 0 or a positive number(seconds) or a duration. The `coefficient` must be in range: [0.0, 100.0] | | +| `map` | List of key/value pairs, where the key is a string and the value can be any given type. | `{ "one": 1.2, "two": 2.3 }` | +| `object` | Config object containing nested elements | `"walk": { "speed": 1.3, "reluctance": 5 }` | +| `array` | Config object containing an array/list of elements | `"array": [ 1, 2, 3 ]` | @@ -241,7 +241,7 @@ Here is a list of all features which can be toggled on/off and their default val | `FaresV2` | Enable import of GTFS-Fares v2 data. | | ✓️ | | `FlexRouting` | Enable FLEX routing. | | ✓️ | | `GoogleCloudStorage` | Enable Google Cloud Storage integration. | | ✓️ | -| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with realtime data | | ✓️ | +| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with real-time data | | ✓️ | | `ReportApi` | Enable the report API. | | ✓️ | | `RestAPIPassInDefaultConfigAsJson` | Enable a default RouteRequest to be passed in as JSON on the REST API - FOR DEBUGGING ONLY! | | | | `SandboxAPIGeocoder` | Enable the Geocoder API. | | ✓️ | diff --git a/docs/Data-Sources.md b/docs/Data-Sources.md index 317e0a1f2f7..bf426186f57 100644 --- a/docs/Data-Sources.md +++ b/docs/Data-Sources.md @@ -5,7 +5,7 @@ At the core of OpenTripPlanner is a library of Java code that finds efficient paths through multi-modal transportation networks built from [OpenStreetMap](http://wiki.openstreetmap.org/wiki/Main_Page) -and [GTFS](https://developers.google.com/transit/gtfs/) data. It can also receive GTFS-RT (realtime) +and [GTFS](https://developers.google.com/transit/gtfs/) data. It can also receive GTFS-RT (real-time) data. In addition to GTFS, OTP can also load data in the Nordic Profile of Netex, the EU-standard transit diff --git a/docs/Deployments.md b/docs/Deployments.md index f2946a9f0ba..d1df3984b05 100644 --- a/docs/Deployments.md +++ b/docs/Deployments.md @@ -6,7 +6,7 @@ The following are known deployments of OTP in a government- or agency-sponsored * **Norway (nationwide)** Since November 2017, the national integrated ticketing agency Entur has prodvided a [national journey planner](https://en-tur.no/) which consumes schedule data in the EU - standard NeTEx format with SIRI realtime updates. Entur has contributed greatly to the OTP2 effort + standard NeTEx format with SIRI real-time updates. Entur has contributed greatly to the OTP2 effort and primarily uses OTP2 in production, handling peak loads in excess of 20 requests per second. Most regional agencies in Norway, like **Ruter, Oslo area** uses OTP as a service provided by Entur. * **Finland (nationwide)** The [Helsinki Regional Transport Authority](https://www.reittiopas.fi/), @@ -22,7 +22,7 @@ The following are known deployments of OTP in a government- or agency-sponsored service [Matkahuolto](https://en.wikipedia.org/wiki/Matkahuolto) has [developed a trip planner in partnership with Kyyti](https://www.kyyti.com/matkahuoltos-new-app-brings-real-travel-chains-within-the-reach-of-citizens-in-addition-to-coach-travel-hsl-tickets-are-also-available/). * **Skåne, Sweden**, the JourneyPlanner and mobile app for the regional transit agency [Skånetrafiken](https://www.skanetrafiken.se/) - uses OTP2 with the nordic profile of NeTEx and SIRI for realtime updates. + uses OTP2 with the nordic profile of NeTEx and SIRI for real-time updates. * [**Northern Colorado**](https://discover.rideno.co/) * [**Philadelphia and surrounding areas**](https://plan.septa.org) * **Portland, Oregon** TriMet is the agency that originally started the OpenTripPlanner project. diff --git a/docs/Netex-Norway.md b/docs/Netex-Norway.md index 6157849bedc..0a447237592 100644 --- a/docs/Netex-Norway.md +++ b/docs/Netex-Norway.md @@ -85,11 +85,11 @@ using OTP's built in testing web client. Try some long trips like Oslo to Bergen get long distance trains and flights as alternatives. You might need to increase the walking limit above its very low default value. -## Adding SIRI Realtime Data +## Adding SIRI Real-time Data Another important feature in OTP2 is the ability to -use [SIRI realtime data](https://en.wikipedia.org/wiki/Service_Interface_for_Real_Time_Information). -Within the EU data standards, SIRI is analogous to GTFS-RT: a way to apply realtime updates on top +use [SIRI real-time data](https://en.wikipedia.org/wiki/Service_Interface_for_Real_Time_Information). +Within the EU data standards, SIRI is analogous to GTFS-RT: a way to apply real-time updates on top of schedule data. While technically a distinct specification from Netex, both Netex and SIRI use the Transmodel vocabulary, allowing SIRI messages to reference entities in Netex schedule data. Like GTFS-RT, SIRI is consumed by OTP2 using "graph updaters" which are configured in @@ -143,6 +143,6 @@ Note that between these SIRI updaters and the GTFS-RT Websocket updater, we now and streaming examples of GTFS-RT "incrementality" semantics, so should be able to finalize that part of the specification. -The final updater regularly performs a copy of the realtime data into a format suitable for use by -OTP2's new Raptor router. Without this updater the realtime data will be received and cataloged, but +The final updater regularly performs a copy of the real-time data into a format suitable for use by +OTP2's new Raptor router. Without this updater the real-time data will be received and cataloged, but not visible to the router. diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index 28c49764810..af6dcf00142 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -47,7 +47,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe | elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | | escalatorReluctance | `double` | A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time | *Optional* | `1.5` | 2.4 | | geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | -| ignoreRealtimeUpdates | `boolean` | When true, realtime updates are ignored during this search. | *Optional* | `false` | 2.0 | +| ignoreRealtimeUpdates | `boolean` | When true, real-time updates are ignored during this search. | *Optional* | `false` | 2.0 | | [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | | locale | `locale` | TODO | *Optional* | `"en_US"` | 2.0 | | [maxDirectStreetDuration](#rd_maxDirectStreetDuration) | `duration` | This is the maximum duration for a direct street search for each mode. | *Optional* | `"PT4H"` | 2.1 | diff --git a/docs/RouterConfiguration.md b/docs/RouterConfiguration.md index d0a58384819..34a3db8f6c6 100644 --- a/docs/RouterConfiguration.md +++ b/docs/RouterConfiguration.md @@ -46,7 +46,7 @@ A full list of them can be found in the [RouteRequest](RouteRequest.md). |          [logKey](#server_traceParameters_0_logKey) | `string` | The log event key used. | *Optional* | | 2.4 | | timetableUpdates | `object` | Global configuration for timetable updaters. | *Optional* | | 2.2 | |    [maxSnapshotFrequency](#timetableUpdates_maxSnapshotFrequency) | `duration` | How long a snapshot should be cached. | *Optional* | `"PT1S"` | 2.2 | -|    purgeExpiredData | `boolean` | Should expired realtime data be purged from the graph. Apply to GTFS-RT and Siri updates. | *Optional* | `true` | 2.2 | +|    purgeExpiredData | `boolean` | Should expired real-time data be purged from the graph. Apply to GTFS-RT and Siri updates. | *Optional* | `true` | 2.2 | | [transit](#transit) | `object` | Configuration for transit searches with RAPTOR. | *Optional* | | na | |    [iterationDepartureStepInSeconds](#transit_iterationDepartureStepInSeconds) | `integer` | Step for departure times between each RangeRaptor iterations. | *Optional* | `60` | na | |    [maxNumberOfTransfers](#transit_maxNumberOfTransfers) | `integer` | This parameter is used to allocate enough memory space for Raptor. | *Optional* | `12` | na | diff --git a/docs/SandboxExtension.md b/docs/SandboxExtension.md index 55ee214979a..4a3f500ae80 100644 --- a/docs/SandboxExtension.md +++ b/docs/SandboxExtension.md @@ -15,8 +15,8 @@ provided "as is". - [Transfer analyser](sandbox/transferanalyzer.md) - Module used for analyzing the transfers between nearby stops generated by routing via OSM data. - [Transmodel API](sandbox/TransmodelApi.md) - Enturs GraphQL Transmodel API. -- [SIRI Updater](sandbox/SiriUpdater.md) - Update OTP with realtime information from a Transmodel SIRI data source. -- [SIRI Azure Updater](sandbox/SiriAzureUpdater.md) - fetch SIRI realtime data through *Azure Service Bus* +- [SIRI Updater](sandbox/SiriUpdater.md) - Update OTP with real-time information from a Transmodel SIRI data source. +- [SIRI Azure Updater](sandbox/SiriAzureUpdater.md) - fetch SIRI real-time data through *Azure Service Bus* - [VehicleRentalServiceDirectory](sandbox/VehicleRentalServiceDirectory.md) - GBFS service directory endpoint. - [Smoove Bike Rental Updator Support](sandbox/SmooveBikeRental.md) - Smoove Bike Rental Updator(HSL) - [Mapbox Vector Tiles API](sandbox/MapboxVectorTilesApi.md) - Mapbox Vector Tiles API diff --git a/docs/System-Requirements.md b/docs/System-Requirements.md index 133370b8ede..387bba0cd03 100644 --- a/docs/System-Requirements.md +++ b/docs/System-Requirements.md @@ -10,7 +10,7 @@ OTP is relatively memory-hungry as it includes all the required data in memory. Single thread performance is an important factor for OTP's performance. Additionally, OTP benefits from larger CPU cache as reading from memory can be a bottleneck. -OTP's performance scales with the number of available CPU cores. OTP processes each request in a separate thread and usually one request doesn't utilize more than one thread, but with some requests and configurations, it's possible that multiple threads are used in parallel for a small part of a request or to process multiple queries within one request. How much parallel processing we utilize in requests might change in the future. Realtime updates also run in a separate thread. Therefore, to have a good performance, it makes sense to have multiple cores available. How OTP uses parallel processing also depends on the available cores (<= 2 cores vs >2 cores) in some cases. Therefore, load testing should be done against a machine that doesn't differ too much from production machines. +OTP's performance scales with the number of available CPU cores. OTP processes each request in a separate thread and usually one request doesn't utilize more than one thread, but with some requests and configurations, it's possible that multiple threads are used in parallel for a small part of a request or to process multiple queries within one request. How much parallel processing we utilize in requests might change in the future. Real-time updates also run in a separate thread. Therefore, to have a good performance, it makes sense to have multiple cores available. How OTP uses parallel processing also depends on the available cores (<= 2 cores vs >2 cores) in some cases. Therefore, load testing should be done against a machine that doesn't differ too much from production machines. Entur and the Digitransit project have found that the 3rd generation AMD processors have a slightly better performance for OTP2 than the Intel 3rd generation CPUs (and especially better than the 2nd generation CPUs). diff --git a/docs/examples/entur/build-config.json b/docs/examples/entur/build-config.json index 3829e975f34..e9351882774 100644 --- a/docs/examples/entur/build-config.json +++ b/docs/examples/entur/build-config.json @@ -5,7 +5,6 @@ "embedRouterConfig": true, "areaVisibility": true, "platformEntriesLinking": true, - "matchBusRoutesToStreets": false, "staticParkAndRide": true, "staticBikeParkAndRide": true, "maxDataImportIssuesPerFile": 1000, diff --git a/docs/examples/skanetrafiken/Readme.md b/docs/examples/skanetrafiken/Readme.md index ab7ef5f33a6..fc342f4192b 100644 --- a/docs/examples/skanetrafiken/Readme.md +++ b/docs/examples/skanetrafiken/Readme.md @@ -35,13 +35,13 @@ To reduced graph size, only data for southern part of Sweden is used. OSM data is downloaded from `http://download.geofabrik.de/europe/denmark-latest.osm.pbf`. To reduce graph size, only data for northern part of Denmark is used. -## Realtime +## Real-time -The **Azure Service Bus** is used to propagate SIRI SX and ET realtime messages to OTP. +The **Azure Service Bus** is used to propagate SIRI SX and ET real-time messages to OTP. This is solved through Siri Azure updaters that Skånetrafiken had implemented in OTP. There are separate updaters for SIRI SX and ET. Those updaters are used to provide data for Swedish traffic (NeTEx). Right now, there is no -connection to any realtime source for danish traffic (GTFS data). +connection to any real-time source for danish traffic (GTFS data). Except for receiving messages from **Service Bus** there are two endpoints through which historical ET and SX messages can be downloaded at OTP startup. @@ -54,8 +54,8 @@ the subscription. Once the updaters are done with processing of history messages they will change their status to primed, and the system will start channeling request to this OTP instance. -This ensures that no realtime message is omitted and all OTP instance that ran in the -cluster does have exact same realtime data. +This ensures that no real-time message is omitted and all OTP instance that ran in the +cluster does have exact same real-time data. Thi means that no matter which instance the client is hitting it will always get the same search results. diff --git a/docs/examples/skanetrafiken/router-config.json b/docs/examples/skanetrafiken/router-config.json index 9cc8b659734..ddec543e516 100644 --- a/docs/examples/skanetrafiken/router-config.json +++ b/docs/examples/skanetrafiken/router-config.json @@ -33,7 +33,7 @@ "fuzzyTripMatching": false, "history": { "url": "", - // Get all realtime history for current operating day date + // Get all real-time history for current operating day date "fromDateTime": "-P0D", "timeout": 300000 } diff --git a/docs/sandbox/MapboxVectorTilesApi.md b/docs/sandbox/MapboxVectorTilesApi.md index a503e6a839d..8ef8ee179e7 100644 --- a/docs/sandbox/MapboxVectorTilesApi.md +++ b/docs/sandbox/MapboxVectorTilesApi.md @@ -80,7 +80,7 @@ The feature must be configured in `router-config.json` as follows "minZoom": 14, "cacheMaxSeconds": 600 }, - // Contains just stations and realtime information for them + // Contains just stations and real-time information for them { "name": "realtimeRentalStations", "type": "VehicleRentalStation", @@ -90,7 +90,7 @@ The feature must be configured in `router-config.json` as follows "cacheMaxSeconds": 60 }, // This exists for backwards compatibility. At some point, we might want - // to add a new realtime parking mapper with better translation support + // to add a new real-time parking mapper with better translation support // and less unnecessary fields. { "name": "stadtnaviVehicleParking", @@ -101,7 +101,7 @@ The feature must be configured in `router-config.json` as follows "cacheMaxSeconds": 60, "expansionFactor": 0.25 }, - // no realtime, translatable fields are translated based on accept-language header + // no real-time, translatable fields are translated based on accept-language header // and contains less fields than the Stadtnavi mapper { "name": "vehicleParking", @@ -190,4 +190,4 @@ key, and a function to create the mapper, with a `Graph` object as a parameter, * Translatable fields are now translated based on accept-language header * Added DigitransitRealtime for vehicle rental stations * Changed old vehicle parking mapper to be Stadtnavi - * Added a new Digitransit vehicle parking mapper with no realtime information and less fields + * Added a new Digitransit vehicle parking mapper with no real-time information and less fields diff --git a/docs/sandbox/StopConsolidation.md b/docs/sandbox/StopConsolidation.md index fa3ff822e88..750a873b547 100644 --- a/docs/sandbox/StopConsolidation.md +++ b/docs/sandbox/StopConsolidation.md @@ -30,7 +30,7 @@ This has the following consequences However, this feature has also severe downsides: - - It makes realtime trip updates referencing a stop id much more complicated and in many cases + - It makes real-time trip updates referencing a stop id much more complicated and in many cases impossible to resolve. You can only reference a stop by its sequence, which only works in GTFS-RT, not Siri. - Fare calculation and transfers are unlikely to work as expected. diff --git a/pom.xml b/pom.xml index a47094978fc..5fcbe06a790 100644 --- a/pom.xml +++ b/pom.xml @@ -56,16 +56,16 @@ - 131 + 132 30.1 - 2.48.1 + 2.49 2.16.0 - 3.1.3 + 3.1.4 5.10.1 1.11.5 5.5.3 - 1.4.12 + 1.4.14 9.8.0 2.0.9 2.0.15 @@ -703,7 +703,7 @@ org.entur.gbfs gbfs-java-model - 3.0.13 + 3.0.16 @@ -722,13 +722,13 @@ com.tngtech.archunit archunit - 1.2.0 + 1.2.1 test org.mockito mockito-core - 5.7.0 + 5.8.0 test @@ -853,7 +853,7 @@ org.onebusaway onebusaway-gtfs - 1.4.5 + 1.4.9 org.slf4j @@ -898,7 +898,7 @@ org.apache.httpcomponents.client5 httpclient5 - 5.2.1 + 5.3 commons-cli @@ -966,6 +966,17 @@ prettierSkip + + + + ps + + + 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 43be9c6d735..af415b5e883 100644 --- a/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java @@ -137,7 +137,7 @@ private static TripPattern delay(TripPattern pattern1, int seconds) { } private static TripTimes delay(TripTimes tt, int seconds) { - var delayed = tt.copyOfScheduledTimes(); + var delayed = tt.copyScheduledTimes(); IntStream .range(0, delayed.getNumStops()) .forEach(i -> { diff --git a/src/ext-test/java/org/opentripplanner/ext/siri/TimetableHelperTest.java b/src/ext-test/java/org/opentripplanner/ext/siri/TimetableHelperTest.java index 97bd3be2398..dde2677a952 100644 --- a/src/ext-test/java/org/opentripplanner/ext/siri/TimetableHelperTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/siri/TimetableHelperTest.java @@ -19,8 +19,8 @@ import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.Station; import org.opentripplanner.transit.model.timetable.OccupancyStatus; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.Trip; -import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.opentripplanner.transit.service.StopModel; import uk.org.siri.siri20.OccupancyEnumeration; @@ -46,7 +46,7 @@ public class TimetableHelperTest { ZoneIds.CET ); - private TripTimes tripTimes; + private RealTimeTripTimes tripTimes; @BeforeEach public void setUp() { diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql index 069fefdf54d..2eff7d94020 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/ext/graphql/transmodelapi/schema.graphql @@ -158,7 +158,7 @@ type Contact { "A planned journey on a specific day" type DatedServiceJourney { - "Returns scheduled passingTimes for this dated service journey, updated with realtime-updates (if available). " + "Returns scheduled passingTimes for this dated service journey, updated with real-time-updates (if available). " estimatedCalls: [EstimatedCall] @timingData id: ID! "JourneyPattern for the dated service journey." @@ -211,7 +211,7 @@ type EstimatedCall { aimedDepartureTime: DateTime! "Booking arrangements for this EstimatedCall." bookingArrangements: BookingArrangement - "Whether stop is cancelled. This means that either the ServiceJourney has a planned cancellation, the ServiceJourney has been cancelled by realtime data, or this particular StopPoint has been cancelled. This also means that both boarding and alighting has been cancelled." + "Whether stop is cancelled. This means that either the ServiceJourney has a planned cancellation, the ServiceJourney has been cancelled by real-time data, or this particular StopPoint has been cancelled. This also means that both boarding and alighting has been cancelled." cancellation: Boolean! "The date the estimated call is valid for." date: Date! @@ -315,9 +315,9 @@ type Leg { includes both the start and end coordinate. """ elevationProfile: [ElevationProfileStep]! - "The expected, realtime adjusted date and time this leg ends." + "The expected, real-time adjusted date and time this leg ends." expectedEndTime: DateTime! - "The expected, realtime adjusted date and time this leg starts." + "The expected, real-time adjusted date and time this leg starts." expectedStartTime: DateTime! "EstimatedCall for the quay where the leg originates." fromEstimatedCall: EstimatedCall @timingData @@ -560,7 +560,7 @@ type Quay implements PlaceInterface { estimatedCalls( "Filters results by either departures, arrivals or both. For departures forBoarding has to be true and the departure time has to be within the specified time range. For arrivals, forAlight has to be true and the arrival time has to be within the specified time range. If both are asked for, either the conditions for arrivals or the conditions for departures will have to be true for an EstimatedCall to show." arrivalDeparture: ArrivalDeparture = departures, - "Indicates that realtime-cancelled trips should also be included." + "Indicates that real-time-cancelled trips should also be included." includeCancelledTrips: Boolean = false, "Limit the total number of departures returned." numberOfDepartures: Int = 5, @@ -797,7 +797,7 @@ type QueryType { filters: [TripFilterInput!], "The start location" from: Location!, - "When true, realtime updates are ignored during this search." + "When true, real-time updates are ignored during this search." ignoreRealtimeUpdates: Boolean = false, "When true, service journeys cancelled in scheduled route data will be included during this search." includePlannedCancellations: Boolean = false, @@ -1012,7 +1012,7 @@ type RoutingParameters { elevatorHopTime: Int "Whether to apply the ellipsoid->geoid offset to all elevations in the response." geoIdElevation: Boolean - "When true, realtime updates are ignored during this search." + "When true, real-time updates are ignored during this search." ignoreRealTimeUpdates: Boolean "When true, service journeys cancelled in scheduled route data will be included during this search." includedPlannedCancellations: Boolean @@ -1081,7 +1081,7 @@ type ServiceJourney { "Booking arrangements for flexible services." bookingArrangements: BookingArrangement @deprecated(reason : "BookingArrangements are defined per stop, and can be found under `passingTimes` or `estimatedCalls`") directionType: DirectionType - "Returns scheduled passingTimes for this ServiceJourney for a given date, updated with realtime-updates (if available). NB! This takes a date as argument (default=today) and returns estimatedCalls for that date and should only be used if the date is known when creating the request. For fetching estimatedCalls for a given trip.leg, use leg.serviceJourneyEstimatedCalls instead." + "Returns scheduled passingTimes for this ServiceJourney for a given date, updated with real-time-updates (if available). NB! This takes a date as argument (default=today) and returns estimatedCalls for that date and should only be used if the date is known when creating the request. For fetching estimatedCalls for a given trip.leg, use leg.serviceJourneyEstimatedCalls instead." estimatedCalls( "Date to get estimated calls for. Defaults to today." date: Date @@ -1092,7 +1092,7 @@ type ServiceJourney { line: Line! notices: [Notice!]! operator: Operator - "Returns scheduled passing times only - without realtime-updates, for realtime-data use 'estimatedCalls'" + "Returns scheduled passing times only - without real-time-updates, for realtime-data use 'estimatedCalls'" passingTimes: [TimetabledPassingTime]! @timingData "Detailed path travelled by service journey. Not available for flexible trips." pointsOnLink: PointsOnLink @@ -1122,7 +1122,7 @@ type StopPlace implements PlaceInterface { "List of visits to this stop place as part of vehicle journeys." estimatedCalls( arrivalDeparture: ArrivalDeparture = departures, - "Indicates that realtime-cancelled trips should also be included." + "Indicates that real-time-cancelled trips should also be included." includeCancelledTrips: Boolean = false, "Limit the total number of departures returned." numberOfDepartures: Int = 5, @@ -1280,9 +1280,9 @@ type TripPattern { duration: Long "Time that the trip arrives." endTime: DateTime @deprecated(reason : "Replaced with expectedEndTime") - "The expected, realtime adjusted date and time the trip ends." + "The expected, real-time adjusted date and time the trip ends." expectedEndTime: DateTime! - "The expected, realtime adjusted date and time the trip starts." + "The expected, real-time adjusted date and time the trip starts." expectedStartTime: DateTime! "Generalized cost or weight of the itinerary. Used for debugging." generalizedCost: Int @@ -1405,6 +1405,8 @@ enum AlternativeLegsFilter { sameAuthority sameLine sameMode + "Must match both subMode and mode" + sameSubmode } enum ArrivalDeparture { diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java b/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java index 486362697b3..4f1aca5bf63 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java @@ -395,7 +395,7 @@ protected Optional getRidePrice( * If free transfers are applicable, the most expensive discount fare across all legs is added to * the final cumulative price. *

- * The computed fare for Orca card users takes into account realtime trip updates where available, + * The computed fare for Orca card users takes into account real-time trip updates where available, * so that, for instance, when a leg on a long itinerary is delayed to begin after the initial two * hour window has expired, the calculated fare for that trip will be two one-way fares instead of * one. diff --git a/src/ext/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolver.java b/src/ext/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolver.java index a59d9fc589a..d03ba1a2fc5 100644 --- a/src/ext/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolver.java +++ b/src/ext/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolver.java @@ -11,7 +11,7 @@ public class RealtimeResolver { /** - * Loop through all itineraries and populate legs with realtime data using legReference from the original leg + * Loop through all itineraries and populate legs with real-time data using legReference from the original leg */ public static void populateLegsWithRealtime( List itineraries, @@ -35,10 +35,10 @@ public static void populateLegsWithRealtime( return leg; } - var realtimeLeg = ref.getLeg(transitService); - if (realtimeLeg != null) { + var realTimeLeg = ref.getLeg(transitService); + if (realTimeLeg != null) { return combineReferenceWithOriginal( - realtimeLeg.asScheduledTransitLeg(), + realTimeLeg.asScheduledTransitLeg(), leg.asScheduledTransitLeg() ); } diff --git a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java index 257639d7873..7f0e51fab18 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java @@ -18,6 +18,7 @@ import org.opentripplanner.framework.time.ServiceDateUtils; import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.network.Route; @@ -27,13 +28,13 @@ import org.opentripplanner.transit.model.organization.Operator; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.timetable.RealTimeState; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripIdAndServiceDate; import org.opentripplanner.transit.model.timetable.TripOnServiceDate; -import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.opentripplanner.transit.service.TransitModel; -import org.opentripplanner.updater.spi.TripTimesValidationMapper; +import org.opentripplanner.updater.spi.DataValidationExceptionMapper; import org.opentripplanner.updater.spi.UpdateError; import org.rutebanken.netex.model.BusSubmodeEnumeration; import org.rutebanken.netex.model.RailSubmodeEnumeration; @@ -210,14 +211,14 @@ Result build() { .withStopPattern(stopPattern) .build(); - TripTimes tripTimes = TripTimesFactory.tripTimes( + RealTimeTripTimes tripTimes = TripTimesFactory.tripTimes( trip, aimedStopTimes, transitModel.getDeduplicator() ); tripTimes.setServiceCode(transitModel.getServiceCodes().get(trip.getServiceId())); pattern.add(tripTimes); - TripTimes updatedTripTimes = tripTimes.copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimes = tripTimes.copyScheduledTimes(); // Loop through calls again and apply updates for (int stopSequence = 0; stopSequence < calls.size(); stopSequence++) { @@ -239,9 +240,10 @@ Result build() { } /* Validate */ - var validityResult = updatedTripTimes.validateNonIncreasingTimes(); - if (validityResult.isPresent()) { - return TripTimesValidationMapper.toResult(tripId, validityResult.get()); + try { + updatedTripTimes.validateNonIncreasingTimes(); + } catch (DataValidationException e) { + return DataValidationExceptionMapper.toResult(e); } var tripOnServiceDate = TripOnServiceDate @@ -269,7 +271,7 @@ Result build() { } /** - * Method to create a Route. Commonly used to create a route if a realtime message + * Method to create a Route. Commonly used to create a route if a real-time message * refers to a route that is not in the transit model. * * We will find the first Route with same Operator, and use the same Authority diff --git a/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java b/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java index f56d3c6a75f..df4509eb2d6 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java @@ -12,15 +12,16 @@ import java.util.Set; import org.opentripplanner.ext.siri.mapper.PickDropMapper; import org.opentripplanner.framework.time.ServiceDateUtils; -import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.network.StopPattern; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.RealTimeState; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.TripTimes; -import org.opentripplanner.updater.spi.TripTimesValidationMapper; +import org.opentripplanner.updater.spi.DataValidationExceptionMapper; import org.opentripplanner.updater.spi.UpdateError; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +34,8 @@ */ public class ModifiedTripBuilder { - private static final Logger LOG = LoggerFactory.getLogger(TimetableHelper.class); + private static final Logger LOG = LoggerFactory.getLogger(ModifiedTripBuilder.class); + private final TripTimes existingTripTimes; private final TripPattern pattern; private final LocalDate serviceDate; @@ -94,7 +96,7 @@ public ModifiedTripBuilder( * in form the SIRI-ET update. */ public Result build() { - TripTimes newTimes = existingTripTimes.copyOfScheduledTimes(); + RealTimeTripTimes newTimes = existingTripTimes.copyScheduledTimes(); StopPattern stopPattern = createStopPattern(pattern, calls, entityResolver); @@ -114,16 +116,16 @@ public Result build() { newTimes.setRealTimeState(RealTimeState.MODIFIED); } - var error = newTimes.validateNonIncreasingTimes(); - final FeedScopedId id = newTimes.getTrip().getId(); - if (error.isPresent()) { - var updateError = error.get(); + // TODO - Handle DataValidationException at the outemost level(pr trip) + try { + newTimes.validateNonIncreasingTimes(); + } catch (DataValidationException e) { LOG.info( - "Invalid SIRI-ET data for trip {} - TripTimes are non-increasing after applying SIRI delay propagation at stop index {}", - id, - updateError.stopIndex() + "Invalid SIRI-ET data for trip {} - TripTimes failed to validate after applying SIRI delay propagation. {}", + newTimes.getTrip().getId(), + e.getMessage() ); - return TripTimesValidationMapper.toResult(id, updateError); + return DataValidationExceptionMapper.toResult(e); } int numStopsInUpdate = newTimes.getNumStops(); @@ -131,7 +133,7 @@ public Result build() { if (numStopsInUpdate != numStopsInPattern) { LOG.info( "Invalid SIRI-ET data for trip {} - Inconsistent number of updated stops ({}) and stops in pattern ({})", - id, + newTimes.getTrip().getId(), numStopsInUpdate, numStopsInPattern ); @@ -145,7 +147,7 @@ public Result build() { /** * Applies real-time updates from the calls into newTimes. */ - private void applyUpdates(TripTimes newTimes) { + private void applyUpdates(RealTimeTripTimes newTimes) { ZonedDateTime startOfService = ServiceDateUtils.asStartOfService(serviceDate, zoneId); Set alreadyVisited = new HashSet<>(); diff --git a/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java b/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java index 006329a404b..2f2f113fc79 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java +++ b/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java @@ -297,9 +297,9 @@ private TripAndPattern getTripAndPatternForJourney( continue; } - var realtimeAddedTripPattern = getRealtimeAddedTripPattern.apply(trip.getId(), serviceDate); - TripPattern tripPattern = realtimeAddedTripPattern != null - ? realtimeAddedTripPattern + var realTimeAddedTripPattern = getRealtimeAddedTripPattern.apply(trip.getId(), serviceDate); + TripPattern tripPattern = realTimeAddedTripPattern != null + ? realTimeAddedTripPattern : transitService.getPatternForTrip(trip); var firstStop = tripPattern.firstStop(); diff --git a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java index 0cedc491f79..eab0496c4f2 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java +++ b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java @@ -18,14 +18,17 @@ import org.opentripplanner.model.TimetableSnapshot; import org.opentripplanner.model.TimetableSnapshotProvider; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.TransitLayerUpdater; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.TimetableSnapshotSourceParameters; +import org.opentripplanner.updater.spi.DataValidationExceptionMapper; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.spi.UpdateResult; import org.opentripplanner.updater.spi.UpdateSuccess; @@ -35,8 +38,8 @@ import uk.org.siri.siri20.EstimatedVehicleJourney; /** - * This class should be used to create snapshots of lookup tables of realtime data. This is - * necessary to provide planning threads a consistent constant view of a graph with realtime data at + * This class should be used to create snapshots of lookup tables of real-time data. This is + * necessary to provide planning threads a consistent constant view of a graph with real-time data at * a specific point in time. */ public class SiriTimetableSnapshotSource implements TimetableSnapshotProvider { @@ -59,7 +62,7 @@ public class SiriTimetableSnapshotSource implements TimetableSnapshotProvider { */ private final SiriTripPatternIdGenerator tripPatternIdGenerator = new SiriTripPatternIdGenerator(); /** - * A synchronized cache of trip patterns that are added to the graph due to GTFS-realtime + * A synchronized cache of trip patterns that are added to the graph due to GTFS-real-time * messages. */ private final SiriTripPatternCache tripPatternCache; @@ -81,7 +84,7 @@ public class SiriTimetableSnapshotSource implements TimetableSnapshotProvider { */ private volatile TimetableSnapshot snapshot = null; - /** Should expired realtime data be purged from the graph. */ + /** Should expired real-time data be purged from the graph. */ private final boolean purgeExpiredData; protected LocalDate lastPurgeDate = null; @@ -215,6 +218,8 @@ private Result apply( /* commit */ return addTripToGraphAndBuffer(result.successValue()); + } catch (DataValidationException e) { + return DataValidationExceptionMapper.toResult(e); } catch (Exception e) { LOG.warn( "{} EstimatedJourney {} failed.", @@ -371,7 +376,7 @@ private Result addTripToGraphAndBuffer(TripUpdate tr // Add new trip times to the buffer and return success var result = buffer.update(pattern, tripUpdate.tripTimes(), serviceDate); - LOG.debug("Applied realtime data for trip {} on {}", trip, serviceDate); + LOG.debug("Applied real-time data for trip {} on {}", trip, serviceDate); return result; } @@ -395,7 +400,7 @@ private boolean markScheduledTripAsDeleted(Trip trip, final LocalDate serviceDat if (tripTimes == null) { LOG.warn("Could not mark scheduled trip as deleted {}", trip.getId()); } else { - final TripTimes newTripTimes = tripTimes.copyOfScheduledTimes(); + final RealTimeTripTimes newTripTimes = tripTimes.copyScheduledTimes(); newTripTimes.deleteTrip(); buffer.update(pattern, newTripTimes, serviceDate); success = true; @@ -416,10 +421,8 @@ private boolean removePreviousRealtimeUpdate(final Trip trip, final LocalDate se final TripPattern pattern = buffer.getRealtimeAddedTripPattern(trip.getId(), serviceDate); if (pattern != null) { - /* - Remove the previous realtime-added TripPattern from buffer. - Only one version of the realtime-update should exist - */ + // Remove the previous real-time-added TripPattern from buffer. + // Only one version of the real-time-update should exist buffer.removeLastAddedTripPattern(trip.getId(), serviceDate); buffer.removeRealtimeUpdatedTripTimes(pattern, trip.getId(), serviceDate); success = true; @@ -436,7 +439,7 @@ private boolean purgeExpiredData() { return false; } - LOG.debug("purging expired realtime data"); + LOG.debug("purging expired real-time data"); lastPurgeDate = previously; diff --git a/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java b/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java index ade320db298..fea8bda686f 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java +++ b/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java @@ -7,7 +7,7 @@ import org.opentripplanner.ext.siri.mapper.OccupancyMapper; import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.framework.time.ServiceDateUtils; -import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import uk.org.siri.siri20.NaturalLanguageStringStructure; import uk.org.siri.siri20.OccupancyEnumeration; @@ -52,7 +52,7 @@ private static int handleMissingRealtime(int... times) { public static void applyUpdates( ZonedDateTime departureDate, - TripTimes tripTimes, + RealTimeTripTimes tripTimes, int index, boolean isLastStop, boolean isJourneyPredictionInaccurate, @@ -75,29 +75,29 @@ public static void applyUpdates( } int scheduledArrivalTime = tripTimes.getArrivalTime(index); - int realtimeArrivalTime = getAvailableTime( + int realTimeArrivalTime = getAvailableTime( departureDate, call::getActualArrivalTime, call::getExpectedArrivalTime ); int scheduledDepartureTime = tripTimes.getDepartureTime(index); - int realtimeDepartureTime = getAvailableTime( + int realTimeDepartureTime = getAvailableTime( departureDate, call::getActualDepartureTime, call::getExpectedDepartureTime ); int[] possibleArrivalTimes = index == 0 - ? new int[] { realtimeArrivalTime, realtimeDepartureTime, scheduledArrivalTime } - : new int[] { realtimeArrivalTime, scheduledArrivalTime }; + ? new int[] { realTimeArrivalTime, realTimeDepartureTime, scheduledArrivalTime } + : new int[] { realTimeArrivalTime, scheduledArrivalTime }; var arrivalTime = handleMissingRealtime(possibleArrivalTimes); int arrivalDelay = arrivalTime - scheduledArrivalTime; tripTimes.updateArrivalDelay(index, arrivalDelay); int[] possibleDepartureTimes = isLastStop - ? new int[] { realtimeDepartureTime, realtimeArrivalTime, scheduledDepartureTime } - : new int[] { realtimeDepartureTime, scheduledDepartureTime }; + ? new int[] { realTimeDepartureTime, realTimeArrivalTime, scheduledDepartureTime } + : new int[] { realTimeDepartureTime, scheduledDepartureTime }; var departureTime = handleMissingRealtime(possibleDepartureTimes); int departureDelay = departureTime - scheduledDepartureTime; tripTimes.updateDepartureDelay(index, departureDelay); diff --git a/src/ext/java/org/opentripplanner/ext/siri/TripTimesAndStopPattern.java b/src/ext/java/org/opentripplanner/ext/siri/TripTimesAndStopPattern.java deleted file mode 100644 index 70d9e034188..00000000000 --- a/src/ext/java/org/opentripplanner/ext/siri/TripTimesAndStopPattern.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.opentripplanner.ext.siri; - -import org.opentripplanner.transit.model.network.StopPattern; -import org.opentripplanner.transit.model.timetable.TripTimes; - -public record TripTimesAndStopPattern(TripTimes times, StopPattern pattern) {} diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETGooglePubsubUpdater.java b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETGooglePubsubUpdater.java index cb664632443..29c0fa0c7a2 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETGooglePubsubUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETGooglePubsubUpdater.java @@ -126,10 +126,10 @@ public SiriETGooglePubsubUpdater( SiriTimetableSnapshotSource timetableSnapshot ) { this.configRef = config.configRef(); - /* - URL that responds to HTTP GET which returns all initial data in protobuf-format. - Will be called once to initialize realtime-data. All updates will be received from Google Cloud Pubsub - */ + + // URL that responds to HTTP GET which returns all initial data in protobuf-format. Will be + // called once to initialize real-time-data. All updates will be received from Google Cloud + // Pubsub this.dataInitializationUrl = URI.create(config.dataInitializationUrl()); this.feedId = config.feedId(); this.reconnectPeriod = config.reconnectPeriod(); diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETHttpTripUpdateSource.java b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETHttpTripUpdateSource.java index 3f1de760701..9dab72446ea 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETHttpTripUpdateSource.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETHttpTripUpdateSource.java @@ -88,7 +88,7 @@ public String toString() { } private static SiriLoader createLoader(String url, Parameters parameters) { - // Load realtime updates from a file. + // Load real-time updates from a file. if (SiriFileLoader.matchesUrl(url)) { return new SiriFileLoader(url); } diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETUpdater.java b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETUpdater.java index b4cf5f143e3..c8ccd2c533b 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETUpdater.java @@ -39,7 +39,7 @@ public class SiriETUpdater extends PollingGraphUpdater { protected WriteToGraphCallback saveResultOnGraph; /** - * The place where we'll record the incoming realtime timetables to make them available to the + * The place where we'll record the incoming real-time timetables to make them available to the * router in a thread safe way. */ private final SiriTimetableSnapshotSource snapshotSource; diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriFileLoader.java b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriFileLoader.java index 306deeca484..84da542ef47 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriFileLoader.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriFileLoader.java @@ -72,7 +72,7 @@ private Optional fetchFeed() { if (!matchFilename(file)) { continue; } - LOG.info("Process realtime input file: " + file.getAbsolutePath()); + LOG.info("Process real-time input file: " + file.getAbsolutePath()); var inProgressFile = newFile(file, SUFFIX_IN_PROGRESS); try { file.renameTo(inProgressFile); diff --git a/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationModule.java b/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationModule.java index c70e91fca52..91cbe5e1856 100644 --- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationModule.java +++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationModule.java @@ -20,7 +20,7 @@ * that represent the same stop place) and swaps the "secondary" stops in patterns with their * "primary" equivalent. *

- * NOTE: This will make realtime trip updates for a modified pattern a lot harder. For Arcadis' + * NOTE: This will make real-time trip updates for a modified pattern a lot harder. For Arcadis' * initial implementation this is acceptable and will serve as encouragement for the data producers to * produce a consolidated transit feed rather than relying on this feature. */ diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/DefaultRouteRequestType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/DefaultRouteRequestType.java index 0d52fb280d9..fd9af09822e 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/DefaultRouteRequestType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/DefaultRouteRequestType.java @@ -394,7 +394,7 @@ private GraphQLObjectType createGraphQLType() { GraphQLFieldDefinition .newFieldDefinition() .name("ignoreRealTimeUpdates") - .description("When true, realtime updates are ignored during this search.") + .description("When true, real-time updates are ignored during this search.") .type(Scalars.GraphQLBoolean) .dataFetcher(env -> preferences.transit().ignoreRealtimeUpdates()) .build() diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java index 2b6942d7b8a..e585ad55c57 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java @@ -49,6 +49,7 @@ public class EnumTypes { .value("noFilter", AlternativeLegsFilter.NO_FILTER) .value("sameAuthority", AlternativeLegsFilter.SAME_AGENCY) .value("sameMode", AlternativeLegsFilter.SAME_MODE) + .value("sameSubmode", AlternativeLegsFilter.SAME_SUBMODE, "Must match both subMode and mode") .value("sameLine", AlternativeLegsFilter.SAME_ROUTE) .build(); diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/LegType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/LegType.java index 74c84c79a87..0b9aa00bde0 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/LegType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/LegType.java @@ -75,7 +75,8 @@ public static GraphQLObjectType create( .name("aimedStartTime") .description("The aimed date and time this leg starts.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) - .dataFetcher(env -> // startTime is already adjusted for realtime - need to subtract delay to get aimed time + .dataFetcher(env -> + // startTime is already adjusted for real-time - need to subtract delay to get aimed time leg(env) .getStartTime() .minusSeconds(leg(env).getDepartureDelay()) @@ -88,7 +89,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("expectedStartTime") - .description("The expected, realtime adjusted date and time this leg starts.") + .description("The expected, real-time adjusted date and time this leg starts.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) .dataFetcher(env -> leg(env).getStartTime().toInstant().toEpochMilli()) .build() @@ -99,7 +100,7 @@ public static GraphQLObjectType create( .name("aimedEndTime") .description("The aimed date and time this leg ends.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) - .dataFetcher(env -> // endTime is already adjusted for realtime - need to subtract delay to get aimed time + .dataFetcher(env -> // endTime is already adjusted for real-time - need to subtract delay to get aimed time leg(env) .getEndTime() .minusSeconds(leg(env).getArrivalDelay()) @@ -112,7 +113,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("expectedEndTime") - .description("The expected, realtime adjusted date and time this leg ends.") + .description("The expected, real-time adjusted date and time this leg ends.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) .dataFetcher(env -> leg(env).getEndTime().toInstant().toEpochMilli()) .build() diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripPatternType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripPatternType.java index 4381557d472..de90b89b38f 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripPatternType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripPatternType.java @@ -50,7 +50,8 @@ public static GraphQLObjectType create( .name("aimedStartTime") .description("The aimed date and time the trip starts.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) - .dataFetcher(env -> // startTime is already adjusted for realtime - need to subtract delay to get aimed time + .dataFetcher(env -> + // startTime is already adjusted for real-time - need to subtract delay to get aimed time itinerary(env) .startTime() .minusSeconds(itinerary(env).departureDelay()) @@ -63,7 +64,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("expectedStartTime") - .description("The expected, realtime adjusted date and time the trip starts.") + .description("The expected, real-time adjusted date and time the trip starts.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) .dataFetcher(env -> itinerary(env).startTime().toInstant().toEpochMilli()) .build() @@ -74,7 +75,8 @@ public static GraphQLObjectType create( .name("aimedEndTime") .description("The aimed date and time the trip ends.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) - .dataFetcher(env -> // endTime is already adjusted for realtime - need to subtract delay to get aimed time + .dataFetcher(env -> + // endTime is already adjusted for real-time - need to subtract delay to get aimed time itinerary(env) .endTime() .minusSeconds(itinerary(env).arrivalDelay()) @@ -87,7 +89,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("expectedEndTime") - .description("The expected, realtime adjusted date and time the trip ends.") + .description("The expected, real-time adjusted date and time the trip ends.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) .dataFetcher(env -> itinerary(env).endTime().toInstant().toEpochMilli()) .build() diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java index cf56e2ef34d..4ac182f514f 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java @@ -188,7 +188,7 @@ public static GraphQLFieldDefinition create( GraphQLArgument .newArgument() .name("ignoreRealtimeUpdates") - .description("When true, realtime updates are ignored during this search.") + .description("When true, real-time updates are ignored during this search.") .type(Scalars.GraphQLBoolean) .defaultValue(preferences.transit().ignoreRealtimeUpdates()) .build() diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/et/EstimatedCallType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/et/EstimatedCallType.java index 7a39aee6cca..3111f17b698 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/et/EstimatedCallType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/et/EstimatedCallType.java @@ -194,7 +194,7 @@ public static GraphQLObjectType create( .newFieldDefinition() .name("realtimeState") .type(new GraphQLNonNull(EnumTypes.REALTIME_STATE)) - .dataFetcher(environment -> ((TripTimeOnDate) environment.getSource()).getRealtimeState()) + .dataFetcher(environment -> ((TripTimeOnDate) environment.getSource()).getRealTimeState()) .build() ) .field( @@ -258,7 +258,7 @@ public static GraphQLObjectType create( .description( "Whether stop is cancelled. This means that either the " + "ServiceJourney has a planned cancellation, the ServiceJourney has been " + - "cancelled by realtime data, or this particular StopPoint has been " + + "cancelled by real-time data, or this particular StopPoint has been " + "cancelled. This also means that both boarding and alighting has been " + "cancelled." ) diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java index 203efb10452..8b9296b9137 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java @@ -299,7 +299,7 @@ public static GraphQLObjectType create( GraphQLArgument .newArgument() .name("includeCancelledTrips") - .description("Indicates that realtime-cancelled trips should also be included.") + .description("Indicates that real-time-cancelled trips should also be included.") .type(Scalars.GraphQLBoolean) .defaultValue(false) .build() diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/StopPlaceType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/StopPlaceType.java index 5a803138eb1..a60ee5df20f 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/StopPlaceType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/StopPlaceType.java @@ -20,7 +20,6 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -34,7 +33,6 @@ import org.opentripplanner.ext.transmodelapi.model.plan.JourneyWhiteListed; import org.opentripplanner.ext.transmodelapi.support.GqlUtil; import org.opentripplanner.framework.graphql.GraphQLUtils; -import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.model.StopTimesInPattern; import org.opentripplanner.model.TripTimeOnDate; import org.opentripplanner.routing.stoptimes.ArrivalDeparture; @@ -348,7 +346,7 @@ public static GraphQLObjectType create( GraphQLArgument .newArgument() .name("includeCancelledTrips") - .description("Indicates that realtime-cancelled trips should also be included.") + .description("Indicates that real-time-cancelled trips should also be included.") .type(Scalars.GraphQLBoolean) .defaultValue(false) .build() diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/DatedServiceJourneyType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/DatedServiceJourneyType.java index 6634ee4e4d3..e9cb689036b 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/DatedServiceJourneyType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/DatedServiceJourneyType.java @@ -146,7 +146,7 @@ public static GraphQLObjectType create( .withDirective(gqlUtil.timingData) .description( "Returns scheduled passingTimes for this dated service journey, " + - "updated with realtime-updates (if available). " + "updated with real-time-updates (if available). " ) .dataFetcher(environment -> { TripOnServiceDate tripOnServiceDate = tripOnServiceDate(environment); diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/ServiceJourneyType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/ServiceJourneyType.java index 3603db05120..f03dad44fc1 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/ServiceJourneyType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/ServiceJourneyType.java @@ -240,7 +240,7 @@ public static GraphQLObjectType create( .type(new GraphQLNonNull(new GraphQLList(timetabledPassingTimeType))) .withDirective(gqlUtil.timingData) .description( - "Returns scheduled passing times only - without realtime-updates, for realtime-data use 'estimatedCalls'" + "Returns scheduled passing times only - without real-time-updates, for realtime-data use 'estimatedCalls'" ) .dataFetcher(env -> { Trip trip = trip(env); @@ -259,7 +259,7 @@ public static GraphQLObjectType create( .type(new GraphQLList(estimatedCallType)) .withDirective(gqlUtil.timingData) .description( - "Returns scheduled passingTimes for this ServiceJourney for a given date, updated with realtime-updates (if available). " + + "Returns scheduled passingTimes for this ServiceJourney for a given date, updated with real-time-updates (if available). " + "NB! This takes a date as argument (default=today) and returns estimatedCalls for that date and should only be used if the date is " + "known when creating the request. For fetching estimatedCalls for a given trip.leg, use leg.serviceJourneyEstimatedCalls instead." ) diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdater.java index a921836d907..e5086630941 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdater.java @@ -67,7 +67,7 @@ public HslParkUpdater( /** * Update the data from the sources. It first fetches parks from the facilities URL and park - * groups from hubs URL and then realtime updates from utilizations URL. If facilitiesFrequencySec + * groups from hubs URL and then real-time updates from utilizations URL. If facilitiesFrequencySec * is configured to be over 0, it also occasionally retches the parks as new parks might have been * added or the state of the old parks might have changed. * diff --git a/src/main/java/org/opentripplanner/api/mapping/PlaceMapper.java b/src/main/java/org/opentripplanner/api/mapping/PlaceMapper.java index f6b0defd709..e806c5615d2 100644 --- a/src/main/java/org/opentripplanner/api/mapping/PlaceMapper.java +++ b/src/main/java/org/opentripplanner/api/mapping/PlaceMapper.java @@ -130,7 +130,7 @@ private ApiVehicleParkingWithEntrance mapVehicleParking( .hasWheelchairAccessibleCarPlaces(vp.hasWheelchairAccessibleCarPlaces()) .availability(mapVehicleParkingSpaces(vp.getAvailability())) .capacity(mapVehicleParkingSpaces(vp.getCapacity())) - .realtime(vehicleParkingWithEntrance.isRealtime()) + .realTime(vehicleParkingWithEntrance.isRealtime()) .build(); } } diff --git a/src/main/java/org/opentripplanner/api/mapping/TripPlanMapper.java b/src/main/java/org/opentripplanner/api/mapping/TripPlanMapper.java index 3247b675b65..8c5459af8ed 100644 --- a/src/main/java/org/opentripplanner/api/mapping/TripPlanMapper.java +++ b/src/main/java/org/opentripplanner/api/mapping/TripPlanMapper.java @@ -21,7 +21,7 @@ public ApiTripPlan mapTripPlan(TripPlan domain) { } ApiTripPlan api = new ApiTripPlan(); api.date = Date.from(domain.date); - // The origin/destination do not have arrival/depature times; Hence {@code null} is used. + // The origin/destination do not have arrival/departure times; Hence {@code null} is used. api.from = placeMapper.mapPlace(domain.from, null, null, null, null); api.to = placeMapper.mapPlace(domain.to, null, null, null, null); api.itineraries = itineraryMapper.mapItineraries(domain.itineraries); diff --git a/src/main/java/org/opentripplanner/api/mapping/TripTimeMapper.java b/src/main/java/org/opentripplanner/api/mapping/TripTimeMapper.java index c5303c744cf..2f5bfe91026 100644 --- a/src/main/java/org/opentripplanner/api/mapping/TripTimeMapper.java +++ b/src/main/java/org/opentripplanner/api/mapping/TripTimeMapper.java @@ -28,13 +28,13 @@ public static ApiTripTimeShort mapToApi(TripTimeOnDate domain) { api.stopCount = domain.getStopCount(); api.scheduledArrival = domain.getScheduledArrival(); api.scheduledDeparture = domain.getScheduledDeparture(); - api.realtimeArrival = domain.getRealtimeArrival(); - api.realtimeDeparture = domain.getRealtimeDeparture(); + api.realTimeArrival = domain.getRealtimeArrival(); + api.realTimeDeparture = domain.getRealtimeDeparture(); api.arrivalDelay = domain.getArrivalDelay(); api.departureDelay = domain.getDepartureDelay(); api.timepoint = domain.isTimepoint(); - api.realtime = domain.isRealtime(); - api.realtimeState = ApiRealTimeState.RealTimeState(domain.getRealtimeState()); + api.realTime = domain.isRealtime(); + api.realTimeState = ApiRealTimeState.RealTimeState(domain.getRealTimeState()); api.blockId = domain.getBlockId(); api.headsign = I18NStringMapper.mapToApi(domain.getHeadsign(), null); api.tripId = FeedScopedIdMapper.mapToApi(domain.getTrip().getId()); diff --git a/src/main/java/org/opentripplanner/api/model/ApiTripSearchMetadata.java b/src/main/java/org/opentripplanner/api/model/ApiTripSearchMetadata.java index 49c98d6e5b6..486118883f9 100644 --- a/src/main/java/org/opentripplanner/api/model/ApiTripSearchMetadata.java +++ b/src/main/java/org/opentripplanner/api/model/ApiTripSearchMetadata.java @@ -22,7 +22,7 @@ public class ApiTripSearchMetadata { * This is the suggested search time for the "next page" or time-window. Insert it together with * the {@link #searchWindowUsed} in the request to get a new set of trips following in the * time-window AFTER the current search. No duplicate trips should be returned, unless a trip is - * delayed and new realtime-data is available. + * delayed and new real-time-data is available. *

* Be careful to use paging/scrolling with the {@code numOfItineraries} parameter set. It is safe * to scroll forward when the {@code arriveBy=false}, but not if {@code arriveBy=true}. If you @@ -42,7 +42,7 @@ public class ApiTripSearchMetadata { * This is the suggested search time for the "previous page" or time window. Insert it together * with the {@link #searchWindowUsed} in the request to get a new set of trips preceding in the * time-window BEFORE the current search. No duplicate trips should be returned, unless a trip is - * delayed and new realtime-data is available. + * delayed and new real-time-data is available. *

* Be careful to use paging/scrolling with the {@code numOfItineraries} parameter set. It is safe * to scroll backward when the {@code arriveBy=true}, but not if {@code arriveBy=false}. If you diff --git a/src/main/java/org/opentripplanner/api/model/ApiTripTimeShort.java b/src/main/java/org/opentripplanner/api/model/ApiTripTimeShort.java index 92a78f4aade..0a340d4a812 100644 --- a/src/main/java/org/opentripplanner/api/model/ApiTripTimeShort.java +++ b/src/main/java/org/opentripplanner/api/model/ApiTripTimeShort.java @@ -11,13 +11,13 @@ public class ApiTripTimeShort implements Serializable { public int stopCount; public int scheduledArrival = UNDEFINED; public int scheduledDeparture = UNDEFINED; - public int realtimeArrival = UNDEFINED; - public int realtimeDeparture = UNDEFINED; + public int realTimeArrival = UNDEFINED; + public int realTimeDeparture = UNDEFINED; public int arrivalDelay = UNDEFINED; public int departureDelay = UNDEFINED; public boolean timepoint = false; - public boolean realtime = false; - public ApiRealTimeState realtimeState = ApiRealTimeState.SCHEDULED; + public boolean realTime = false; + public ApiRealTimeState realTimeState = ApiRealTimeState.SCHEDULED; public long serviceDay; public String tripId; public String blockId; diff --git a/src/main/java/org/opentripplanner/api/model/ApiVehicleParkingWithEntrance.java b/src/main/java/org/opentripplanner/api/model/ApiVehicleParkingWithEntrance.java index 56e3e0c3710..8cac78f6c9e 100644 --- a/src/main/java/org/opentripplanner/api/model/ApiVehicleParkingWithEntrance.java +++ b/src/main/java/org/opentripplanner/api/model/ApiVehicleParkingWithEntrance.java @@ -73,15 +73,15 @@ public class ApiVehicleParkingWithEntrance { public final ApiVehicleParkingSpaces capacity; /** - * The number of available spaces. Only present if there is a realtime updater present. Maybe + * The number of available spaces. Only present if there is a real-time updater present. Maybe * {@code null} if unknown. */ public final ApiVehicleParkingSpaces availability; /** - * True if realtime information is used for checking availability. + * True if real-time information is used for checking availability. */ - public final boolean realtime; + public final boolean realTime; ApiVehicleParkingWithEntrance( String id, @@ -98,7 +98,7 @@ public class ApiVehicleParkingWithEntrance { boolean hasWheelchairAccessibleCarPlaces, ApiVehicleParkingSpaces capacity, ApiVehicleParkingSpaces availability, - boolean realtime + boolean realTime ) { this.id = id; this.name = name; @@ -114,7 +114,7 @@ public class ApiVehicleParkingWithEntrance { this.hasWheelchairAccessibleCarPlaces = hasWheelchairAccessibleCarPlaces; this.capacity = capacity; this.availability = availability; - this.realtime = realtime; + this.realTime = realTime; } public static ApiVehicleParkingWithEntranceBuilder builder() { @@ -137,7 +137,7 @@ public static class ApiVehicleParkingWithEntranceBuilder { private boolean hasWheelchairAccessibleCarPlaces; private ApiVehicleParkingSpaces capacity; private ApiVehicleParkingSpaces availability; - private boolean realtime; + private boolean realTime; ApiVehicleParkingWithEntranceBuilder() {} @@ -213,8 +213,8 @@ public ApiVehicleParkingWithEntranceBuilder availability(ApiVehicleParkingSpaces return this; } - public ApiVehicleParkingWithEntranceBuilder realtime(boolean realtime) { - this.realtime = realtime; + public ApiVehicleParkingWithEntranceBuilder realTime(boolean realTime) { + this.realTime = realTime; return this; } @@ -234,7 +234,7 @@ public ApiVehicleParkingWithEntrance build() { hasWheelchairAccessibleCarPlaces, capacity, availability, - realtime + realTime ); } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java index d7229b84b1f..dfc3e2d75f8 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java @@ -17,7 +17,7 @@ public record GraphQLRequestContext( FareService fareService, VehicleParkingService vehicleParkingService, VehicleRentalService vehicleRentalService, - RealtimeVehicleService realtimeVehicleService, + RealtimeVehicleService realTimeVehicleService, GraphFinder graphFinder, RouteRequest defaultRouteRequest ) { diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PatternImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PatternImpl.java index 18ab5c5d5e9..241434c664f 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PatternImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PatternImpl.java @@ -249,7 +249,7 @@ private List getTrips(DataFetchingEnvironment environment) { } private RealtimeVehicleService getRealtimeVehiclesService(DataFetchingEnvironment environment) { - return environment.getContext().realtimeVehicleService(); + return environment.getContext().realTimeVehicleService(); } private TransitService getTransitService(DataFetchingEnvironment environment) { diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java index 9353027439b..0730d2fbc91 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java @@ -538,7 +538,7 @@ private List getTripTimeOnDatesForPatternAtStopIncludingTripsWit } /** - * Get a stream of {@link TripPattern} that were created realtime based of the provided pattern. + * Get a stream of {@link TripPattern} that were created real-time based of the provided pattern. * Only patterns that don't have removed (stops can still be skipped) or added stops are included. */ private Stream getRealtimeAddedPatternsAsStream( diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StoptimeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StoptimeImpl.java index a74d4f9461d..faf59ef9d6e 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StoptimeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StoptimeImpl.java @@ -71,7 +71,7 @@ public DataFetcher realtimeState() { return environment -> getSource(environment).isCanceledEffectively() ? RealTimeState.CANCELED.name() - : getSource(environment).getRealtimeState().name(); + : getSource(environment).getRealTimeState().name(); } @Override diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java index 78255ce7787..2502d2b9539 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java @@ -405,7 +405,7 @@ private TransitService getTransitService(DataFetchingEnvironment environment) { } private RealtimeVehicleService getRealtimeVehiclesService(DataFetchingEnvironment environment) { - return environment.getContext().realtimeVehicleService(); + return environment.getContext().realTimeVehicleService(); } private Trip getSource(DataFetchingEnvironment environment) { diff --git a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 9c730b94216..5f39872eed5 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -84,7 +84,7 @@ public enum OTPFeature { RealtimeResolver( false, true, - "When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with realtime data" + "When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with real-time data" ), ReportApi(false, true, "Enable the report API."), RestAPIPassInDefaultConfigAsJson( diff --git a/src/main/java/org/opentripplanner/framework/error/OtpError.java b/src/main/java/org/opentripplanner/framework/error/OtpError.java new file mode 100644 index 00000000000..6b2ff37945e --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/error/OtpError.java @@ -0,0 +1,37 @@ +package org.opentripplanner.framework.error; + +import java.util.Locale; + +/** + * A generic representation of an error. The error should have a code used to group the same + * type of errors together. To avoid filling up memory with error strings during graph build + * we store errors in memory "decomposed". The {@link #messageTemplate()} and + * {@link #messageArguments()} is used to construct the message. Use the {@link Locale#ROOT} + * when constructing the message - we only support english with SI formatting. + */ +public interface OtpError { + /** + * An error code used to identify the error type. This is NOT an enum, but feel free + * to use an inum in the implementation, then use the enum NAME as the code or + * enum TYPE:NAME. Then name should be unique within OTP. + */ + String errorCode(); + + /** + * The string template used to create a human-readable error message. Use the + * {@link String#format(Locale, String, Object...)} format. + */ + String messageTemplate(); + + /** + * The arguments to inject into the message. + */ + Object[] messageArguments(); + + /** + * Construct a message. + */ + default String message() { + return String.format(Locale.ROOT, messageTemplate(), messageArguments()); + } +} diff --git a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java index e6aba29278b..3a2bb777f04 100644 --- a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java @@ -121,9 +121,6 @@ public static GraphBuilder create( } if (hasTransitData && (hasOsm || graphBuilder.graph.hasStreets)) { - if (config.matchBusRoutesToStreets) { - graphBuilder.addModule(factory.busRouteStreetMatcher()); - } graphBuilder.addModule(factory.osmBoardingLocationsModule()); } diff --git a/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssueStore.java b/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssueStore.java index 1e28cec72f4..bd158436747 100644 --- a/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssueStore.java +++ b/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssueStore.java @@ -1,6 +1,7 @@ package org.opentripplanner.graph_builder.issue.api; import java.util.List; +import org.opentripplanner.framework.error.OtpError; /** * This service is used to store issued during data import. When the import is complete @@ -19,6 +20,9 @@ public interface DataImportIssueStore { /** Add an issue to the issue report. */ void add(DataImportIssue issue); + /** Add an issue to the issue report. */ + void add(OtpError issue); + /** Add an issue to the issue report without the need of creating an issue class. */ void add(String type, String message); diff --git a/src/main/java/org/opentripplanner/graph_builder/issue/api/NoopDataImportIssueStore.java b/src/main/java/org/opentripplanner/graph_builder/issue/api/NoopDataImportIssueStore.java index 504980d74a5..a3adfe7127c 100644 --- a/src/main/java/org/opentripplanner/graph_builder/issue/api/NoopDataImportIssueStore.java +++ b/src/main/java/org/opentripplanner/graph_builder/issue/api/NoopDataImportIssueStore.java @@ -1,6 +1,7 @@ package org.opentripplanner.graph_builder.issue.api; import java.util.List; +import org.opentripplanner.framework.error.OtpError; /** * A no-op implementation of the issue store, convenient for unit testing. No issues are @@ -11,6 +12,9 @@ class NoopDataImportIssueStore implements DataImportIssueStore { @Override public void add(DataImportIssue issue) {} + @Override + public void add(OtpError issue) {} + @Override public void add(String type, String message) {} diff --git a/src/main/java/org/opentripplanner/graph_builder/issue/service/DefaultDataImportIssueStore.java b/src/main/java/org/opentripplanner/graph_builder/issue/service/DefaultDataImportIssueStore.java index 53f8c65d10c..3cdcf9d0db6 100644 --- a/src/main/java/org/opentripplanner/graph_builder/issue/service/DefaultDataImportIssueStore.java +++ b/src/main/java/org/opentripplanner/graph_builder/issue/service/DefaultDataImportIssueStore.java @@ -3,6 +3,7 @@ import jakarta.inject.Singleton; import java.util.ArrayList; import java.util.List; +import org.opentripplanner.framework.error.OtpError; import org.opentripplanner.graph_builder.issue.api.DataImportIssue; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.issue.api.Issue; @@ -30,6 +31,11 @@ public void add(DataImportIssue issue) { } } + @Override + public void add(OtpError issue) { + add(issue.errorCode(), issue.messageTemplate(), issue.messageArguments()); + } + @Override public void add(String type, String message) { add(Issue.issue(type, message)); diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java index 1f50c7a6327..9fd99d35ea0 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java @@ -24,7 +24,6 @@ import org.opentripplanner.graph_builder.module.TripPatternNamer; import org.opentripplanner.graph_builder.module.geometry.CalculateWorldEnvelopeModule; import org.opentripplanner.graph_builder.module.islandpruning.PruneIslands; -import org.opentripplanner.graph_builder.module.map.BusRouteStreetMatcher; import org.opentripplanner.graph_builder.module.ned.ElevationModule; import org.opentripplanner.graph_builder.module.osm.OsmModule; import org.opentripplanner.gtfs.graphbuilder.GtfsModule; @@ -45,7 +44,6 @@ public interface GraphBuilderFactory { NetexModule netexModule(); TimeZoneAdjusterModule timeZoneAdjusterModule(); TripPatternNamer tripPatternNamer(); - BusRouteStreetMatcher busRouteStreetMatcher(); OsmBoardingLocationsModule osmBoardingLocationsModule(); StreetLinkerModule streetLinkerModule(); PruneIslands pruneIslands(); diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/BusRouteStreetMatcher.java b/src/main/java/org/opentripplanner/graph_builder/module/map/BusRouteStreetMatcher.java deleted file mode 100644 index 8fc77bb7662..00000000000 --- a/src/main/java/org/opentripplanner/graph_builder/module/map/BusRouteStreetMatcher.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.opentripplanner.graph_builder.module.map; - -import jakarta.inject.Inject; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.LineString; -import org.opentripplanner.framework.geometry.GeometryUtils; -import org.opentripplanner.framework.logging.ProgressTracker; -import org.opentripplanner.graph_builder.model.GraphBuilderModule; -import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.transit.model.network.Route; -import org.opentripplanner.transit.model.network.TripPattern; -import org.opentripplanner.transit.service.TransitModel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Uses the shapes from GTFS to determine which streets buses drive on. This is used to improve the - * quality of the shapes shown for the user. - *

- * GTFS provides a mapping from trips→shapes. This module provides a mapping from stops→trips and - * shapes→edges. Then transitively we get a mapping from stop→edges. - */ -public class BusRouteStreetMatcher implements GraphBuilderModule { - - private static final Logger log = LoggerFactory.getLogger(BusRouteStreetMatcher.class); - - private final Graph graph; - private final TransitModel transitModel; - - @Inject - public BusRouteStreetMatcher(Graph graph, TransitModel transitModel) { - this.graph = graph; - this.transitModel = transitModel; - } - - public void buildGraph() { - // Mapbuilder needs transit index - transitModel.index(); - graph.index(transitModel.getStopModel()); - - StreetMatcher matcher = new StreetMatcher(graph); - log.info("Finding corresponding street edges for trip patterns..."); - // Why do we need to iterate over the routes? Why not just patterns? - Collection allRoutes = transitModel.getTransitModelIndex().getAllRoutes(); - - // Track progress - ProgressTracker progress = ProgressTracker.track( - "Match route to street edges", - 10, - allRoutes.size() - ); - log.info(progress.startMessage()); - - for (Route route : allRoutes) { - for (TripPattern pattern : transitModel - .getTransitModelIndex() - .getPatternsForRoute() - .get(route)) { - if (pattern.getMode().onStreet()) { - /* we can only match geometry to streets on bus routes */ - log.debug("Matching {}", pattern); - //If there are no shapes in GTFS pattern geometry is generated - //generated geometry is useless for street matching - //that is why pattern.geometry is null in that case - if (pattern.getGeometry() == null) { - continue; - } - - for (int i = 0; i < pattern.numHopGeometries(); i++) { - LineString hopGeometry = pattern.getHopGeometry(i); - - List edges = matcher.match(hopGeometry); - if (edges == null || edges.isEmpty()) { - log.warn("Could not match to street network: {}", pattern); - continue; - } - List coordinates = new ArrayList<>(); - for (Edge e : edges) { - coordinates.addAll(Arrays.asList(e.getGeometry().getCoordinates())); - } - Coordinate[] coordinateArray = new Coordinate[coordinates.size()]; - LineString ls = GeometryUtils - .getGeometryFactory() - .createLineString(coordinates.toArray(coordinateArray)); - // Replace the hop's geometry from GTFS with that of the equivalent OSM edges. - pattern.setHopGeometry(i, ls); - } - } - } - progress.step(log::info); - } - log.info(progress.completeMessage()); - } -} diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/EndMatchState.java b/src/main/java/org/opentripplanner/graph_builder/module/map/EndMatchState.java deleted file mode 100644 index ccb90cf3b00..00000000000 --- a/src/main/java/org/opentripplanner/graph_builder/module/map/EndMatchState.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.opentripplanner.graph_builder.module.map; - -import java.util.List; - -/** - * The end of a route's geometry, meaning that the search can quit - * - * @author novalis - */ -public class EndMatchState extends MatchState { - - public EndMatchState(MatchState parent, double error, double distance) { - super(parent, null, distance); - this.currentError = error; - } - - @Override - public List getNextStates() { - return null; - } -} diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/LinearIterator.java b/src/main/java/org/opentripplanner/graph_builder/module/map/LinearIterator.java deleted file mode 100644 index 562e65e4757..00000000000 --- a/src/main/java/org/opentripplanner/graph_builder/module/map/LinearIterator.java +++ /dev/null @@ -1,229 +0,0 @@ -package org.opentripplanner.graph_builder.module.map; - -import java.util.Iterator; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.geom.LineString; -import org.locationtech.jts.geom.Lineal; -import org.locationtech.jts.linearref.LinearLocation; - -/** - * I copied this class from JTS but made a few changes. - *

- * The JTS version of this class has several design decisions that don't work for me. In particular, - * hasNext() in the original should be "isValid", and if we start mid-segment, we should continue at - * the end of this segment rather than the end of the next segment. - */ -public class LinearIterator implements Iterable { - - private final Geometry linearGeom; - - private final int numLines; - - /** - * Invariant: currentLine <> null if the iterator is pointing at a valid coordinate - * - * @throws IllegalArgumentException if linearGeom is not lineal - */ - private LineString currentLine; - - private int componentIndex = 0; - - private int vertexIndex = 0; - - private double segmentFraction; - - /** - * Creates an iterator initialized to the start of a linear {@link Geometry} - * - * @param linear the linear geometry to iterate over - * @throws IllegalArgumentException if linearGeom is not lineal - */ - public LinearIterator(Geometry linear) { - this(linear, 0, 0); - } - - /** - * Creates an iterator starting at a {@link LinearLocation} on a linear {@link Geometry} - * - * @param linear the linear geometry to iterate over - * @param start the location to start at - * @throws IllegalArgumentException if linearGeom is not lineal - */ - public LinearIterator(Geometry linear, LinearLocation start) { - this(linear, start.getComponentIndex(), start.getSegmentIndex()); - this.segmentFraction = start.getSegmentFraction(); - } - - /** - * Creates an iterator starting at a specified component and vertex in a linear {@link Geometry} - * - * @param linearGeom the linear geometry to iterate over - * @param componentIndex the component to start at - * @param vertexIndex the vertex to start at - * @throws IllegalArgumentException if linearGeom is not lineal - */ - public LinearIterator(Geometry linearGeom, int componentIndex, int vertexIndex) { - if (!(linearGeom instanceof Lineal)) throw new IllegalArgumentException( - "Lineal geometry is required" - ); - this.linearGeom = linearGeom; - numLines = linearGeom.getNumGeometries(); - this.componentIndex = componentIndex; - this.vertexIndex = vertexIndex; - loadCurrentLine(); - } - - public static LinearLocation getEndLocation(Geometry linear) { - //the version in LinearLocation is broken - - int lastComponentIndex = linear.getNumGeometries() - 1; - LineString lastLine = (LineString) linear.getGeometryN(lastComponentIndex); - int lastSegmentIndex = lastLine.getNumPoints() - 1; - return new LinearLocation(lastComponentIndex, lastSegmentIndex, 0.0); - } - - /** - * Tests whether there are any vertices left to iterator over. - * - * @return true if there are more vertices to scan - */ - public boolean hasNext() { - if (componentIndex >= numLines) { - return false; - } - if (componentIndex == numLines - 1 && vertexIndex >= currentLine.getNumPoints() - 1) { - return false; - } - return true; - } - - public boolean isValidIndex() { - if (componentIndex >= numLines) { - return false; - } - if (componentIndex == numLines - 1 && vertexIndex >= currentLine.getNumPoints()) { - return false; - } - return true; - } - - /** - * Moves the iterator ahead to the next vertex and (possibly) linear component. - */ - public void next() { - if (!hasNext()) return; - segmentFraction = 0.0; - vertexIndex++; - if (vertexIndex >= currentLine.getNumPoints()) { - componentIndex++; - if (componentIndex < linearGeom.getNumGeometries() - 1) { - loadCurrentLine(); - vertexIndex = 0; - } - } - } - - /** - * Checks whether the iterator cursor is pointing to the endpoint of a linestring. - * - * @return true if the iterator is at an endpoint - */ - public boolean isEndOfLine() { - if (componentIndex >= numLines) { - return false; - } - // LineString currentLine = (LineString) linear.getGeometryN(componentIndex); - if (vertexIndex < currentLine.getNumPoints() - 1) { - return false; - } - return true; - } - - /** - * The component index of the vertex the iterator is currently at. - * - * @return the current component index - */ - public int getComponentIndex() { - return componentIndex; - } - - /** - * The vertex index of the vertex the iterator is currently at. - * - * @return the current vertex index - */ - public int getVertexIndex() { - return vertexIndex; - } - - /** - * Gets the {@link LineString} component the iterator is current at. - * - * @return a linestring - */ - public LineString getLine() { - return currentLine; - } - - /** - * Gets the first {@link Coordinate} of the current segment. (the coordinate of the current - * vertex). - * - * @return a {@link Coordinate} - */ - public Coordinate getSegmentStart() { - return currentLine.getCoordinateN(vertexIndex); - } - - /** - * Gets the second {@link Coordinate} of the current segment. (the coordinate of the next vertex). - * If the iterator is at the end of a line, null is returned. - * - * @return a {@link Coordinate} or null - */ - public Coordinate getSegmentEnd() { - if (vertexIndex < getLine().getNumPoints() - 1) { - return currentLine.getCoordinateN(vertexIndex + 1); - } - return null; - } - - public LinearLocation getLocation() { - return new LinearLocation(componentIndex, vertexIndex, segmentFraction); - } - - @Override - public Iterator iterator() { - return new LinearIteratorIterator(); - } - - private void loadCurrentLine() { - if (componentIndex >= numLines) { - currentLine = null; - return; - } - currentLine = (LineString) linearGeom.getGeometryN(componentIndex); - } - - class LinearIteratorIterator implements Iterator { - - @Override - public boolean hasNext() { - return LinearIterator.this.hasNext(); - } - - @Override - public LinearLocation next() { - LinearLocation result = getLocation(); - LinearIterator.this.next(); - return result; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } -} diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/MatchState.java b/src/main/java/org/opentripplanner/graph_builder/module/map/MatchState.java deleted file mode 100644 index 424655eb377..00000000000 --- a/src/main/java/org/opentripplanner/graph_builder/module/map/MatchState.java +++ /dev/null @@ -1,119 +0,0 @@ -package org.opentripplanner.graph_builder.module.map; - -import java.util.ArrayList; -import java.util.List; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.linearref.LinearLocation; -import org.opentripplanner.framework.geometry.SphericalDistanceLibrary; -import org.opentripplanner.routing.api.request.StreetMode; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.edge.StreetEdge; -import org.opentripplanner.street.model.vertex.Vertex; -import org.opentripplanner.street.search.request.StreetSearchRequest; -import org.opentripplanner.street.search.state.State; - -public abstract class MatchState { - - private static final StreetSearchRequest REQUEST = StreetSearchRequest - .of() - .withMode(StreetMode.CAR) - .build(); - - protected static final double NEW_SEGMENT_PENALTY = 0.1; - - protected static final double NO_TRAVERSE_PENALTY = 20; - - public double currentError; - - public double accumulatedError; - - public MatchState parent; - - protected Edge edge; - - private double distanceAlongRoute = 0; - - public MatchState(MatchState parent, Edge edge, double distanceAlongRoute) { - this.distanceAlongRoute = distanceAlongRoute; - this.parent = parent; - this.edge = edge; - if (parent != null) { - this.accumulatedError = parent.accumulatedError + parent.currentError; - this.distanceAlongRoute += parent.distanceAlongRoute; - } - } - - public abstract List getNextStates(); - - public Edge getEdge() { - return edge; - } - - public double getTotalError() { - return accumulatedError + currentError; - } - - public double getDistanceAlongRoute() { - return distanceAlongRoute; - } - - /* computes the distance, in meters, along a geometry */ - protected static double distanceAlongGeometry( - Geometry geometry, - LinearLocation startIndex, - LinearLocation endIndex - ) { - if (endIndex == null) { - endIndex = LinearLocation.getEndLocation(geometry); - } - double total = 0; - LinearIterator it = new LinearIterator(geometry, startIndex); - LinearLocation index = startIndex; - Coordinate previousCoordinate = startIndex.getCoordinate(geometry); - - it.next(); - index = it.getLocation(); - while (index.compareTo(endIndex) < 0) { - Coordinate thisCoordinate = index.getCoordinate(geometry); - double distance = SphericalDistanceLibrary.fastDistance(previousCoordinate, thisCoordinate); - total += distance; - previousCoordinate = thisCoordinate; - if (!it.hasNext()) { - break; - } - it.next(); - index = it.getLocation(); - } - //now, last bit of last segment - Coordinate finalCoordinate = endIndex.getCoordinate(geometry); - total += SphericalDistanceLibrary.distance(previousCoordinate, finalCoordinate); - - return total; - } - - protected static double distance(Coordinate from, Coordinate to) { - return SphericalDistanceLibrary.fastDistance(from, to); - } - - protected boolean carsCanTraverse(Edge edge) { - // should be done with a method on edge (canTraverse already exists on turnEdge) - State s0 = new State(edge.getFromVertex(), REQUEST); - var states = edge.traverse(s0); - return !State.isEmpty(states); - } - - protected List getOutgoingMatchableEdges(Vertex vertex) { - List edges = new ArrayList<>(); - for (Edge e : vertex.getOutgoing()) { - if (!(e instanceof StreetEdge)) { - continue; - } - if (e.getGeometry() == null) { - continue; - } - edges.add(e); - } - return edges; - } -} diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/MidblockMatchState.java b/src/main/java/org/opentripplanner/graph_builder/module/map/MidblockMatchState.java deleted file mode 100644 index 111817e5ed8..00000000000 --- a/src/main/java/org/opentripplanner/graph_builder/module/map/MidblockMatchState.java +++ /dev/null @@ -1,265 +0,0 @@ -package org.opentripplanner.graph_builder.module.map; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.linearref.LinearLocation; -import org.locationtech.jts.linearref.LocationIndexedLine; -import org.locationtech.jts.util.AssertionFailedException; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.vertex.Vertex; - -public class MidblockMatchState extends MatchState { - - private static final double MAX_ERROR = 1000; - - private final LinearLocation edgeIndex; - private final Geometry edgeGeometry; - private final LocationIndexedLine indexedEdge; - public LinearLocation routeIndex; - Geometry routeGeometry; - - public MidblockMatchState( - MatchState parent, - Geometry routeGeometry, - Edge edge, - LinearLocation routeIndex, - LinearLocation edgeIndex, - double error, - double distanceAlongRoute - ) { - super(parent, edge, distanceAlongRoute); - this.routeGeometry = routeGeometry; - this.routeIndex = routeIndex; - this.edgeIndex = edgeIndex; - - edgeGeometry = edge.getGeometry(); - indexedEdge = new LocationIndexedLine(edgeGeometry); - currentError = error; - } - - @Override - public List getNextStates() { - ArrayList nextStates = new ArrayList<>(); - if (routeIndex.getSegmentIndex() == routeGeometry.getNumPoints() - 1) { - // this has either hit the end, or gone off the end. It's not real clear which. - // for now, let's assume it means that the ending is somewhere along this edge, - // so we return an end state - Coordinate pt = routeIndex.getCoordinate(routeGeometry); - double error = distance(pt, edgeIndex.getCoordinate(edgeGeometry)); - nextStates.add(new EndMatchState(this, error, 0)); - return nextStates; - } - - LinearIterator it = new LinearIterator(routeGeometry, routeIndex); - if (it.hasNext()) { - it.next(); - LinearLocation routeSuccessor = it.getLocation(); - - // now we want to see where this new point is in terms of the edge's geometry - Coordinate newRouteCoord = routeSuccessor.getCoordinate(routeGeometry); - LinearLocation newEdgeIndex = indexedEdge.project(newRouteCoord); - - Coordinate edgeCoord = newEdgeIndex.getCoordinate(edgeGeometry); - if (newEdgeIndex.compareTo(edgeIndex) <= 0) { - // we must make forward progress along the edge... or go to the next edge - /* this should not require the try/catch, but there is a bug in JTS */ - try { - LinearLocation projected2 = indexedEdge.indexOfAfter(edgeCoord, edgeIndex); - //another bug in JTS - if (Double.isNaN(projected2.getSegmentFraction())) { - // we are probably moving backwards - return Collections.emptyList(); - } else { - newEdgeIndex = projected2; - if (newEdgeIndex.equals(edgeIndex)) { - return Collections.emptyList(); - } - } - edgeCoord = newEdgeIndex.getCoordinate(edgeGeometry); - } catch (AssertionFailedException e) { - // we are not making progress, so just return an empty list - return Collections.emptyList(); - } - } - - if (newEdgeIndex.getSegmentIndex() == edgeGeometry.getNumPoints() - 1) { - // we might choose to continue from the end of the edge and a point mid-way - // along this route segment - - // find nearest point that makes progress along the route - Vertex toVertex = edge.getToVertex(); - Coordinate endCoord = toVertex.getCoordinate(); - LocationIndexedLine indexedRoute = new LocationIndexedLine(routeGeometry); - - // FIXME: it would be better to do this project/indexOfAfter in one step - // as the two-step version could snap to a bad place and be unable to escape. - - LinearLocation routeProjectedEndIndex = indexedRoute.project(endCoord); - Coordinate routeProjectedEndCoord = routeProjectedEndIndex.getCoordinate(routeGeometry); - - if (routeProjectedEndIndex.compareTo(routeIndex) <= 0) { - try { - routeProjectedEndIndex = indexedRoute.indexOfAfter(routeProjectedEndCoord, routeIndex); - if (Double.isNaN(routeProjectedEndIndex.getSegmentFraction())) { - // can't go forward - routeProjectedEndIndex = routeIndex; // this is bad, but not terrible - // since we are advancing along the edge - } - } catch (AssertionFailedException e) { - routeProjectedEndIndex = routeIndex; - } - routeProjectedEndCoord = routeProjectedEndIndex.getCoordinate(routeGeometry); - } - - double positionError = distance(routeProjectedEndCoord, endCoord); - double travelAlongRoute = distanceAlongGeometry( - routeGeometry, - routeIndex, - routeProjectedEndIndex - ); - double travelAlongEdge = distanceAlongGeometry(edgeGeometry, edgeIndex, newEdgeIndex); - double travelError = Math.abs(travelAlongEdge - travelAlongRoute); - - double error = positionError + travelError; - - if (error > MAX_ERROR) { - // we're not going to bother with states which are - // totally wrong - return nextStates; - } - - for (Edge e : getOutgoingMatchableEdges(toVertex)) { - double cost = error + NEW_SEGMENT_PENALTY; - if (!carsCanTraverse(e)) { - cost += NO_TRAVERSE_PENALTY; - } - MatchState nextState = new MidblockMatchState( - this, - routeGeometry, - e, - routeProjectedEndIndex, - new LinearLocation(), - cost, - travelAlongRoute - ); - nextStates.add(nextState); - } - } else { - double travelAlongEdge = distanceAlongGeometry(edgeGeometry, edgeIndex, newEdgeIndex); - double travelAlongRoute = distanceAlongGeometry(routeGeometry, routeIndex, routeSuccessor); - double travelError = Math.abs(travelAlongRoute - travelAlongEdge); - - double positionError = distance(edgeCoord, newRouteCoord); - - double error = travelError + positionError; - - MatchState nextState = new MidblockMatchState( - this, - routeGeometry, - edge, - routeSuccessor, - newEdgeIndex, - error, - travelAlongRoute - ); - nextStates.add(nextState); - - // it's also possible that, although we have not yet reached the end of this edge, - // we are going to turn, because the route turns earlier than the edge. In that - // case, we jump to the corner, and our error is the distance from the route point - // and the corner - - Vertex toVertex = edge.getToVertex(); - double travelAlongOldEdge = distanceAlongGeometry(edgeGeometry, edgeIndex, null); - - for (Edge e : getOutgoingMatchableEdges(toVertex)) { - Geometry newEdgeGeometry = e.getGeometry(); - LocationIndexedLine newIndexedEdge = new LocationIndexedLine(newEdgeGeometry); - newEdgeIndex = newIndexedEdge.project(newRouteCoord); - Coordinate newEdgeCoord = newEdgeIndex.getCoordinate(newEdgeGeometry); - positionError = distance(newEdgeCoord, newRouteCoord); - travelAlongEdge = - travelAlongOldEdge + - distanceAlongGeometry(newEdgeGeometry, new LinearLocation(), newEdgeIndex); - travelError = Math.abs(travelAlongRoute - travelAlongEdge); - - error = travelError + positionError; - - if (error > MAX_ERROR) { - // we're not going to bother with states which are - // totally wrong - return nextStates; - } - - double cost = error + NEW_SEGMENT_PENALTY; - if (!carsCanTraverse(e)) { - cost += NO_TRAVERSE_PENALTY; - } - - nextState = - new MidblockMatchState( - this, - routeGeometry, - e, - routeSuccessor, - new LinearLocation(), - cost, - travelAlongRoute - ); - nextStates.add(nextState); - } - } - return nextStates; - } else { - Coordinate routeCoord = routeIndex.getCoordinate(routeGeometry); - LinearLocation projected = indexedEdge.project(routeCoord); - double locationError = distance(projected.getCoordinate(edgeGeometry), routeCoord); - - MatchState end = new EndMatchState(this, locationError, 0); - return Arrays.asList(end); - } - } - - public int hashCode() { - return (edge.hashCode() * 1337 + hashCode(edgeIndex)) * 1337 + hashCode(routeIndex); - } - - public boolean equals(Object o) { - if (!(o instanceof MidblockMatchState)) { - return false; - } - MidblockMatchState other = (MidblockMatchState) o; - return ( - other.edge == edge && - other.edgeIndex.compareTo(edgeIndex) == 0 && - other.routeIndex.compareTo(routeIndex) == 0 - ); - } - - public String toString() { - return ( - "MidblockMatchState(" + - edge + - ", " + - edgeIndex.getSegmentIndex() + - ", " + - edgeIndex.getSegmentFraction() + - ") - " + - currentError - ); - } - - private int hashCode(LinearLocation location) { - return ( - location.getComponentIndex() * - 1000000 + - location.getSegmentIndex() * - 37 + - Double.valueOf(location.getSegmentFraction()).hashCode() - ); - } -} diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/StreetMatcher.java b/src/main/java/org/opentripplanner/graph_builder/module/map/StreetMatcher.java deleted file mode 100644 index e8bc92cc46f..00000000000 --- a/src/main/java/org/opentripplanner/graph_builder/module/map/StreetMatcher.java +++ /dev/null @@ -1,168 +0,0 @@ -package org.opentripplanner.graph_builder.module.map; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.Envelope; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.index.strtree.STRtree; -import org.locationtech.jts.linearref.LinearLocation; -import org.locationtech.jts.linearref.LocationIndexedLine; -import org.locationtech.jts.simplify.DouglasPeuckerSimplifier; -import org.opentripplanner.astar.model.BinHeap; -import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.edge.StreetEdge; -import org.opentripplanner.street.model.vertex.Vertex; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * This Performs most of the work for the MapBuilder graph builder module. It determines which - * sequence of graph edges a GTFS shape probably corresponds to. Note that GTFS shapes are not in - * any way constrained to OSM edges or even roads. - */ -public class StreetMatcher { - - private static final Logger log = LoggerFactory.getLogger(StreetMatcher.class); - private static final double DISTANCE_THRESHOLD = 0.0002; - private final STRtree index; - Graph graph; - - public StreetMatcher(Graph graph) { - this.graph = graph; - index = createIndex(); - index.build(); - } - - @SuppressWarnings("unchecked") - public List match(Geometry routeGeometry) { - routeGeometry = removeDuplicatePoints(routeGeometry); - - if (routeGeometry == null) { - return null; - } - - routeGeometry = DouglasPeuckerSimplifier.simplify(routeGeometry, 0.00001); - - // initial state: start midway along a block. - LocationIndexedLine indexedLine = new LocationIndexedLine(routeGeometry); - - LinearLocation startIndex = indexedLine.getStartIndex(); - - Coordinate routeStartCoordinate = startIndex.getCoordinate(routeGeometry); - Envelope envelope = new Envelope(routeStartCoordinate); - double distanceThreshold = DISTANCE_THRESHOLD; - envelope.expandBy(distanceThreshold); - - BinHeap states = new BinHeap<>(); - List nearbyEdges = index.query(envelope); - while (nearbyEdges.isEmpty()) { - envelope.expandBy(distanceThreshold); - distanceThreshold *= 2; - nearbyEdges = index.query(envelope); - } - - // compute initial states - for (Edge initialEdge : nearbyEdges) { - Geometry edgeGeometry = initialEdge.getGeometry(); - - LocationIndexedLine indexedEdge = new LocationIndexedLine(edgeGeometry); - LinearLocation initialLocation = indexedEdge.project(routeStartCoordinate); - - double error = MatchState.distance( - initialLocation.getCoordinate(edgeGeometry), - routeStartCoordinate - ); - MidblockMatchState state = new MidblockMatchState( - null, - routeGeometry, - initialEdge, - startIndex, - initialLocation, - error, - 0.01 - ); - states.insert(state, 0); //make sure all initial states are visited by inserting them at 0 - } - - // search for best-matching path - int seen_count = 0, total = 0; - HashSet seen = new HashSet<>(); - while (!states.empty()) { - double k = states.peek_min_key(); - MatchState state = states.extract_min(); - if (++total % 50000 == 0) { - log.debug("seen / total: {} / {}", seen_count, total); - } - if (seen.contains(state)) { - ++seen_count; - continue; - } else { - if (k != 0) { - //but do not mark states as closed if we start at them - seen.add(state); - } - } - if (state instanceof EndMatchState) { - return toEdgeList(state); - } - for (MatchState next : state.getNextStates()) { - if (seen.contains(next)) { - continue; - } - states.insert(next, next.getTotalError() - next.getDistanceAlongRoute()); - } - } - return null; - } - - STRtree createIndex() { - STRtree edgeIndex = new STRtree(); - for (Vertex v : graph.getVertices()) { - for (Edge e : v.getOutgoing()) { - if (e instanceof StreetEdge) { - Envelope envelope; - Geometry geometry = e.getGeometry(); - envelope = geometry.getEnvelopeInternal(); - edgeIndex.insert(envelope, e); - } - } - } - log.debug("Created index"); - return edgeIndex; - } - - private Geometry removeDuplicatePoints(Geometry routeGeometry) { - List coords = new ArrayList<>(); - Coordinate last = null; - for (Coordinate c : routeGeometry.getCoordinates()) { - if (!c.equals(last)) { - last = c; - coords.add(c); - } - } - if (coords.size() < 2) { - return null; - } - Coordinate[] coordArray = new Coordinate[coords.size()]; - return routeGeometry.getFactory().createLineString(coords.toArray(coordArray)); - } - - private List toEdgeList(MatchState next) { - ArrayList edges = new ArrayList<>(); - Edge lastEdge = null; - while (next != null) { - Edge edge = next.getEdge(); - if (edge != lastEdge) { - edges.add(edge); - lastEdge = edge; - } - next = next.parent; - } - Collections.reverse(edges); - return edges; - } -} diff --git a/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java b/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java index 1cb3c4c0186..bf26f9c6d7b 100644 --- a/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java +++ b/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java @@ -9,6 +9,7 @@ import java.util.Map; import java.util.Set; import org.opentripplanner.ext.flex.trip.FlexTrip; +import org.opentripplanner.framework.logging.ProgressTracker; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.issues.TripDegenerate; import org.opentripplanner.graph_builder.issues.TripUndefinedService; @@ -16,6 +17,7 @@ import org.opentripplanner.model.Frequency; import org.opentripplanner.model.StopTime; import org.opentripplanner.model.impl.OtpTransitServiceBuilder; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.Route; @@ -47,7 +49,6 @@ public class GenerateTripPatternsOperation { private final Multimap tripPatterns; private final ListMultimap frequenciesForTrip = ArrayListMultimap.create(); - private int tripCount = 0; private int freqCount = 0; private int scheduledCount = 0; @@ -70,17 +71,21 @@ public void run() { collectFrequencyByTrip(); final Collection trips = transitDaoBuilder.getTripsById().values(); - final int tripsSize = trips.size(); + var progressLogger = ProgressTracker.track("build trip patterns", 50_000, trips.size()); + LOG.info(progressLogger.startMessage()); /* Loop over all trips, handling each one as a frequency-based or scheduled trip. */ for (Trip trip : trips) { - if (++tripCount % 100000 == 0) { - LOG.debug("build trip patterns {}/{}", tripCount, tripsSize); + try { + buildTripPatternForTrip(trip); + //noinspection Convert2MethodRef + progressLogger.step(m -> LOG.info(m)); + } catch (DataValidationException e) { + issueStore.add(e.error()); } - - buildTripPatternForTrip(trip); } + LOG.info(progressLogger.completeMessage()); LOG.info( "Added {} frequency-based and {} single-trip timetable entries.", freqCount, diff --git a/src/main/java/org/opentripplanner/model/Timetable.java b/src/main/java/org/opentripplanner/model/Timetable.java index 170ed49092d..a8f0f1bbf44 100644 --- a/src/main/java/org/opentripplanner/model/Timetable.java +++ b/src/main/java/org/opentripplanner/model/Timetable.java @@ -25,15 +25,17 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.opentripplanner.framework.time.ServiceDateUtils; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.Direction; import org.opentripplanner.transit.model.timetable.FrequencyEntry; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.updater.GtfsRealtimeMapper; -import org.opentripplanner.updater.spi.TripTimesValidationMapper; +import org.opentripplanner.updater.spi.DataValidationExceptionMapper; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.trip.BackwardsDelayPropagationType; import org.slf4j.Logger; @@ -203,7 +205,7 @@ public Result createUpdatedTripTimesFromGTFSRT( LOG.trace("tripId {} found at index {} in timetable.", tripId, tripIndex); } - TripTimes newTimes = getTripTimes(tripIndex).copyOfScheduledTimes(); + RealTimeTripTimes newTimes = getTripTimes(tripIndex).copyScheduledTimes(); List skippedStopIndices = new ArrayList<>(); // The GTFS-RT reference specifies that StopTimeUpdates are sorted by stop_sequence. @@ -360,14 +362,10 @@ public Result createUpdatedTripTimesFromGTFSRT( } // Validate for non-increasing times. Log error if present. - var error = newTimes.validateNonIncreasingTimes(); - if (error.isPresent()) { - LOG.debug( - "TripTimes are non-increasing after applying GTFS-RT delay propagation to trip {} after stop index {}.", - tripId, - error.get().stopIndex() - ); - return TripTimesValidationMapper.toResult(newTimes.getTrip().getId(), error.get()); + try { + newTimes.validateNonIncreasingTimes(); + } catch (DataValidationException e) { + return DataValidationExceptionMapper.toResult(e); } if (tripUpdate.hasVehicle()) { @@ -432,12 +430,12 @@ public boolean isValidFor(LocalDate serviceDate) { // TODO maybe put this is a more appropriate place public void setServiceCodes(Map serviceCodes) { for (TripTimes tt : this.tripTimes) { - tt.setServiceCode(serviceCodes.get(tt.getTrip().getServiceId())); + ((RealTimeTripTimes) tt).setServiceCode(serviceCodes.get(tt.getTrip().getServiceId())); } // Repeated code... bad sign... for (FrequencyEntry freq : this.frequencyEntries) { TripTimes tt = freq.tripTimes; - tt.setServiceCode(serviceCodes.get(tt.getTrip().getServiceId())); + ((RealTimeTripTimes) tt).setServiceCode(serviceCodes.get(tt.getTrip().getServiceId())); } } diff --git a/src/main/java/org/opentripplanner/model/TripTimeOnDate.java b/src/main/java/org/opentripplanner/model/TripTimeOnDate.java index 5cbdc682a1e..9ba85d6b532 100644 --- a/src/main/java/org/opentripplanner/model/TripTimeOnDate.java +++ b/src/main/java/org/opentripplanner/model/TripTimeOnDate.java @@ -189,7 +189,7 @@ public boolean isNoDataStop() { return tripTimes.isNoDataStop(stopIndex); } - public RealTimeState getRealtimeState() { + public RealTimeState getRealTimeState() { return tripTimes.isNoDataStop(stopIndex) ? RealTimeState.SCHEDULED : tripTimes.getRealTimeState(); diff --git a/src/main/java/org/opentripplanner/model/TripTimesPatch.java b/src/main/java/org/opentripplanner/model/TripTimesPatch.java index f804a502d51..25afaf81eae 100644 --- a/src/main/java/org/opentripplanner/model/TripTimesPatch.java +++ b/src/main/java/org/opentripplanner/model/TripTimesPatch.java @@ -1,6 +1,7 @@ package org.opentripplanner.model; import java.util.List; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.TripTimes; /** @@ -9,15 +10,15 @@ */ public class TripTimesPatch { - private final TripTimes tripTimes; + private final RealTimeTripTimes tripTimes; private final List skippedStopIndices; - public TripTimesPatch(TripTimes tripTimes, List skippedStopIndices) { + public TripTimesPatch(RealTimeTripTimes tripTimes, List skippedStopIndices) { this.tripTimes = tripTimes; this.skippedStopIndices = skippedStopIndices; } - public TripTimes getTripTimes() { + public RealTimeTripTimes getTripTimes() { return this.tripTimes; } diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/readme.md b/src/main/java/org/opentripplanner/model/plan/pagecursor/readme.md index d0707a93a0b..e98071cfa62 100644 --- a/src/main/java/org/opentripplanner/model/plan/pagecursor/readme.md +++ b/src/main/java/org/opentripplanner/model/plan/pagecursor/readme.md @@ -18,7 +18,7 @@ moving on to the next. request to the next page. **sw'** is the search window for the new next/previous page. The search window may change between requests, so we need to account for it when computing the next/previous page cursors. -- **earliest-departure-time (edt)** The search-window start with the earliest-depature-time, which +- **earliest-departure-time (edt)** The search-window start with the earliest-departure-time, which is the first possible time any itinerary may start. **edt'** is the calculated value for the new cursor. - **latest-arrival-time (lat)** The latest time an itinerary can arrive to get accepted. The diff --git a/src/main/java/org/opentripplanner/netex/mapping/GroupNetexMapper.java b/src/main/java/org/opentripplanner/netex/mapping/GroupNetexMapper.java index 3428f0f8abd..7abb05854f8 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/GroupNetexMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/GroupNetexMapper.java @@ -3,7 +3,9 @@ import com.google.common.collect.ArrayListMultimap; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.model.impl.OtpTransitServiceBuilder; import org.opentripplanner.model.transfer.ConstrainedTransfer; @@ -25,7 +27,7 @@ class GroupNetexMapper { /** * A map from trip/serviceJourney id to an ordered list of scheduled stop point ids. */ - final ArrayListMultimap scheduledStopPointsIndex = ArrayListMultimap.create(); + final Map> scheduledStopPointsIndex = new HashMap<>(); GroupNetexMapper( FeedScopedIdFactory idFactory, diff --git a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java index 4c20eee2ce6..26016947c09 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java @@ -1,6 +1,7 @@ package org.opentripplanner.netex.mapping; import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; import jakarta.xml.bind.JAXBElement; import java.time.LocalDateTime; import java.time.ZoneId; @@ -469,7 +470,7 @@ private void mapTripPatterns(Map serviceIds) { transitBuilder.getTripPatterns().put(it.getKey(), it.getValue()); } currentMapperIndexes.addStopTimesByNetexId(result.stopTimeByNetexId); - groupMapper.scheduledStopPointsIndex.putAll(result.scheduledStopPointsIndex); + groupMapper.scheduledStopPointsIndex.putAll(Multimaps.asMap(result.scheduledStopPointsIndex)); transitBuilder.getTripOnServiceDates().addAll(result.tripOnServiceDates); } } diff --git a/src/main/java/org/opentripplanner/netex/mapping/TransferMapper.java b/src/main/java/org/opentripplanner/netex/mapping/TransferMapper.java index b5b8f42ba36..cb908922160 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/TransferMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/TransferMapper.java @@ -1,6 +1,7 @@ package org.opentripplanner.netex.mapping; -import com.google.common.collect.ArrayListMultimap; +import java.util.List; +import java.util.Map; import javax.annotation.Nullable; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.model.transfer.ConstrainedTransfer; @@ -23,13 +24,13 @@ public class TransferMapper { private final FeedScopedIdFactory idFactory; private final DataImportIssueStore issueStore; - private final ArrayListMultimap scheduledStopPointsIndex; + private final Map> scheduledStopPointsIndex; private final EntityById trips; public TransferMapper( FeedScopedIdFactory idFactory, DataImportIssueStore issueStore, - ArrayListMultimap scheduledStopPointsIndex, + Map> scheduledStopPointsIndex, EntityById trips ) { this.idFactory = idFactory; @@ -139,32 +140,34 @@ private int findStopPosition( ScheduledStopPointRefStructure scheduledStopPointRef ) { String sspId = scheduledStopPointRef.getRef(); + var scheduledStopPoints = scheduledStopPointsIndex.get(sjId); + String errorMessage; + + if (scheduledStopPoints != null) { + var index = + switch (label) { + case Label.TO -> scheduledStopPoints.indexOf(sspId); + case Label.FROM -> scheduledStopPoints.lastIndexOf(sspId); + }; + if (index >= 0) { + return index; + } - int index = -1; - if (label == Label.TO) { - index = scheduledStopPointsIndex.get(sjId).indexOf(sspId); - } else if (label == Label.FROM) { - index = scheduledStopPointsIndex.get(sjId).lastIndexOf(sspId); - } - - if (index >= 0) { - return index; + errorMessage = "Scheduled-stop-point-ref not found"; + } else { + errorMessage = "Service-journey not found"; } - String detailedMsg = scheduledStopPointsIndex.containsKey(sjId) - ? "Scheduled-stop-point-ref not found" - : "Service-journey not found"; - issueStore.add( new InterchangePointMappingFailed( - detailedMsg, + errorMessage, interchangeId, label.label(fieldName), sjId, sspId ) ); - return index; + return -1; } private FeedScopedId createId(String id) { diff --git a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java index 675b9eab5c9..6fa000c1049 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java @@ -11,11 +11,13 @@ import java.util.Objects; import java.util.stream.Collectors; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; +import org.opentripplanner.model.StopTime; import org.opentripplanner.netex.index.api.ReadOnlyHierarchicalMap; import org.opentripplanner.netex.index.api.ReadOnlyHierarchicalMapById; import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory; import org.opentripplanner.transit.model.basic.SubMode; import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.EntityById; import org.opentripplanner.transit.model.framework.FeedScopedId; @@ -360,19 +362,20 @@ private org.opentripplanner.transit.model.network.Route lookupRoute( private void createTripTimes(List trips, TripPattern tripPattern) { for (Trip trip : trips) { - if (result.tripStopTimes.get(trip).size() == 0) { + List stopTimes = result.tripStopTimes.get(trip); + if (stopTimes.isEmpty()) { issueStore.add( "TripWithoutTripTimes", "Trip %s does not contain any trip times.", trip.getId() ); } else { - TripTimes tripTimes = TripTimesFactory.tripTimes( - trip, - result.tripStopTimes.get(trip), - deduplicator - ); - tripPattern.add(tripTimes); + try { + TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, deduplicator); + tripPattern.add(tripTimes); + } catch (DataValidationException e) { + issueStore.add(e.error()); + } } } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java index b05b0aeb5b0..649737bc42f 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java @@ -57,7 +57,7 @@ public McRangeRaptorConfig( /** * The PassThroughPointsService is injected into the transit-calculator, so it needs to be - * created before the context(witch create the calculator).So, to be able to do this, this + * created before the context(which create the calculator).So, to be able to do this, this * factory is static, and the service is passed back in when this config is instantiated. */ public static PassThroughPointsService passThroughPointsService( diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java index 5c105f4804a..e86249f6fc8 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java @@ -4,9 +4,11 @@ import java.time.Instant; import java.util.List; import java.util.function.Consumer; +import org.opentripplanner.ext.emissions.EmissionsFilter; import org.opentripplanner.ext.fares.FaresFilter; import org.opentripplanner.ext.ridehailing.RideHailingFilter; import org.opentripplanner.ext.stopconsolidation.ConsolidatedStopNameFilter; +import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.routing.algorithm.filterchain.GroupBySimilarity; import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChain; import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChainBuilder; @@ -107,6 +109,10 @@ public static ItineraryListFilterChain createFilterChain( ); } + if (OTPFeature.Co2Emissions.isOn() && context.emissionsService() != null) { + builder.withEmissions(new EmissionsFilter(context.emissionsService())); + } + if (context.stopConsolidationService() != null) { builder.withStopConsolidationFilter( new ConsolidatedStopNameFilter(context.stopConsolidationService()) diff --git a/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegsFilter.java b/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegsFilter.java index 5154f8cd40a..9f883603e77 100644 --- a/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegsFilter.java +++ b/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegsFilter.java @@ -14,6 +14,10 @@ public enum AlternativeLegsFilter { ), SAME_MODE((Leg leg) -> (TripPattern tripPattern) -> leg.getTrip().getMode().equals(tripPattern.getMode()) + ), + SAME_SUBMODE((Leg leg) -> + (TripPattern tripPattern) -> + leg.getTrip().getNetexSubMode().equals(tripPattern.getNetexSubmode()) ); public final Function> predicateGenerator; diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java index ff72bf3efb5..da5c4aad60d 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java @@ -138,7 +138,7 @@ public CostLinearFunction relaxTransitPriorityGroup() { } /** - * When true, realtime updates are ignored during this search. + * When true, real-time updates are ignored during this search. */ public boolean ignoreRealtimeUpdates() { return ignoreRealtimeUpdates; diff --git a/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java b/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java index 4a56a0722e4..d62aa1bf41f 100644 --- a/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java @@ -114,8 +114,6 @@ public class BuildConfig implements OtpDataStoreConfig { public final boolean platformEntriesLinking; - public final boolean matchBusRoutesToStreets; - /** See {@link S3BucketConfig}. */ public final S3BucketConfig elevationBucket; @@ -271,14 +269,6 @@ When set to true (it is false by default), the elevation module will include the islandPruning = IslandPruningConfig.fromConfig(root); - matchBusRoutesToStreets = - root - .of("matchBusRoutesToStreets") - .since(V1_5) - .summary( - "Based on GTFS shape data, guess which OSM streets each bus runs on to improve stop linking." - ) - .asBoolean(false); maxDataImportIssuesPerFile = root .of("maxDataImportIssuesPerFile") diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/ConfigType.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/ConfigType.java index 18de8ab6e6c..f62ec56dbfc 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/ConfigType.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/ConfigType.java @@ -94,7 +94,7 @@ public enum ConfigType { TIME_PENALTY( JsonType.string, """ - A time-penalty is used to add a penalty to the duration/arrival-time/depature-time for + A time-penalty is used to add a penalty to the duration/arrival-time/departure-time for a path. It will be invisible to the end user, but used during the routing when comparing stop-arrival/paths. diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/UpdatersConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/UpdatersConfig.java index 7d8b0f7b33f..0f61fa4f7a2 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/UpdatersConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/UpdatersConfig.java @@ -125,7 +125,7 @@ private TimetableSnapshotSourceParameters timetableUpdates(NodeAdapter c) { .of("purgeExpiredData") .since(V2_2) .summary( - "Should expired realtime data be purged from the graph. Apply to GTFS-RT and Siri updates." + "Should expired real-time data be purged from the graph. Apply to GTFS-RT and Siri updates." ) .asBoolean(dflt.purgeExpiredData()) ); diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index 66f92da04eb..118dca43318 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -302,7 +302,7 @@ The board time is added to the time when going from the stop (offboard) to onboa c .of("ignoreRealtimeUpdates") .since(V2_0) - .summary("When true, realtime updates are ignored during this search.") + .summary("When true, real-time updates are ignored during this search.") .asBoolean(dft.ignoreRealtimeUpdates()) ) .setOtherThanPreferredRoutesPenalty( diff --git a/src/main/java/org/opentripplanner/transit/model/framework/DataValidationException.java b/src/main/java/org/opentripplanner/transit/model/framework/DataValidationException.java new file mode 100644 index 00000000000..20848ebecc1 --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/framework/DataValidationException.java @@ -0,0 +1,35 @@ +package org.opentripplanner.transit.model.framework; + +import org.opentripplanner.framework.error.OtpError; + +/** + * This class is used to throw a data validation exception. It holds an error which can be + * inserted into build issue store, into the updater logs or returned to the APIs. The framework + * to catch and handle this is NOT IN PLACE, see + * Error code design #5070. + *

+ * MORE WORK ON THIS IS NEEDED! + */ +public class DataValidationException extends RuntimeException { + + private final OtpError error; + + public DataValidationException(OtpError error) { + super(); + this.error = error; + } + + public OtpError error() { + return error; + } + + @Override + public String getMessage() { + return error.message(); + } + + @Override + public String toString() { + return getMessage(); + } +} diff --git a/src/main/java/org/opentripplanner/transit/model/framework/Result.java b/src/main/java/org/opentripplanner/transit/model/framework/Result.java index e23bdca83ca..9df1c41f190 100644 --- a/src/main/java/org/opentripplanner/transit/model/framework/Result.java +++ b/src/main/java/org/opentripplanner/transit/model/framework/Result.java @@ -9,7 +9,22 @@ * A type for containing either a success or a failure type as the result of a computation. *

* It's very similar to the Either or Validation type found in functional programming languages. + * + * @deprecated This not possible to use inside a constructor - can only return one thing. Which, + * then makes encapsulation harder and leaves the door open to forget. Also, having to create + * error codes, mapping and handling code for hundreds of different possible validation error + * types make changes harder, and cleanup impossible. This is a nice way to design APIs, but + * faced with hundreds or even thousands of different validation error types and the same + * amount of code branches this breaks. + *

+ * I will use the {@link DataValidationException} for now, but we need to make an error + * handling strategy which take all use-cases and goals into account, also pragmatic goals + * like maintainability. The {@link DataValidationException} is not a solution, but it + * at least it allows me to omit returning all possible error on every method ... + *

+ * See https://github.com/opentripplanner/OpenTripPlanner/issues/5070 */ +@Deprecated public abstract sealed class Result { private Result() {} diff --git a/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java b/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java index e89ea1940f6..7057d9fd56e 100644 --- a/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java +++ b/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java @@ -159,11 +159,6 @@ public StopPattern getStopPattern() { return stopPattern; } - // TODO OTP2 this method modifies the state, it will be refactored in a subsequent step - public void setHopGeometry(int i, LineString hopGeometry) { - this.hopGeometries[i] = CompactLineStringUtils.compactLineString(hopGeometry, false); - } - public LineString getGeometry() { if (hopGeometries == null || hopGeometries.length == 0) { return null; @@ -176,10 +171,6 @@ public LineString getGeometry() { return GeometryUtils.concatenateLineStrings(lineStrings); } - public int numHopGeometries() { - return hopGeometries.length; - } - public int numberOfStops() { return stopPattern.getSize(); } diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/FrequencyEntry.java b/src/main/java/org/opentripplanner/transit/model/timetable/FrequencyEntry.java index fc34b08e0b2..61658f6b3e0 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/FrequencyEntry.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/FrequencyEntry.java @@ -6,6 +6,8 @@ /** * Uses a TripTimes to represent multiple trips following the same template at regular intervals. * (see GTFS frequencies.txt) + *

+ * Refactor this to inherit {@link TripTimes} */ public class FrequencyEntry implements Serializable { @@ -116,11 +118,13 @@ public int prevArrivalTime(int stop, int t) { * Returns a disposable TripTimes for this frequency entry in which the vehicle passes the given * stop index (not stop sequence number) at the given time. This allows us to separate the * departure/arrival search process from actually instantiating a TripTimes, to avoid making too - * many short-lived clones. This delegation is a sign that maybe FrequencyEntry should subclass + * many short-lived clones. This delegation is a sign that maybe FrequencyEntry should implement * TripTimes. */ public TripTimes materialize(int stop, int time, boolean depart) { - return tripTimes.timeShift(stop, time, depart); + // TODO: The cast should be changed, the internal type should be scheduledTripTimes and the + // shift method should partially be inlined here + return ((RealTimeTripTimes) tripTimes).timeShift(stop, time, depart); } /** Used in debugging / dumping times. */ diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimes.java new file mode 100644 index 00000000000..a62da95e79a --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimes.java @@ -0,0 +1,584 @@ +package org.opentripplanner.transit.model.timetable; + +import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_DWELL_TIME; +import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_HOP_TIME; + +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.OptionalInt; +import java.util.function.IntUnaryOperator; +import javax.annotation.Nullable; +import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.model.BookingInfo; +import org.opentripplanner.transit.model.basic.Accessibility; +import org.opentripplanner.transit.model.framework.DataValidationException; + +/** + * A TripTimes represents the arrival and departure times for a single trip in an Timetable. It is + * carried along by States when routing to ensure that they have a consistent, fast view of the trip + * when realtime updates have been applied. All times are expressed as seconds since midnight (as in + * GTFS). + */ +public final class RealTimeTripTimes implements TripTimes { + + private ScheduledTripTimes scheduledTripTimes; + + private int[] arrivalTimes; + private int[] departureTimes; + private RealTimeState realTimeState; + private StopRealTimeState[] stopRealTimeStates; + private I18NString[] headsigns; + private OccupancyStatus[] occupancyStatus; + private Accessibility wheelchairAccessibility; + + RealTimeTripTimes(ScheduledTripTimes scheduledTripTimes) { + this( + scheduledTripTimes, + scheduledTripTimes.getRealTimeState(), + null, + null, + null, + scheduledTripTimes.getWheelchairAccessibility() + ); + } + + private RealTimeTripTimes(RealTimeTripTimes original, ScheduledTripTimes scheduledTripTimes) { + this( + scheduledTripTimes, + original.realTimeState, + original.stopRealTimeStates, + original.headsigns, + original.occupancyStatus, + original.wheelchairAccessibility + ); + } + + private RealTimeTripTimes( + ScheduledTripTimes scheduledTripTimes, + RealTimeState realTimeState, + StopRealTimeState[] stopRealTimeStates, + I18NString[] headsigns, + OccupancyStatus[] occupancyStatus, + Accessibility wheelchairAccessibility + ) { + this.scheduledTripTimes = scheduledTripTimes; + this.realTimeState = realTimeState; + this.stopRealTimeStates = stopRealTimeStates; + this.headsigns = headsigns; + this.occupancyStatus = occupancyStatus; + this.wheelchairAccessibility = wheelchairAccessibility; + + // We set these to null to indicate that this is a non-updated/scheduled TripTimes. + // We cannot point to the scheduled times because we do not want to make an unnecessary copy. + this.arrivalTimes = null; + this.departureTimes = null; + } + + public static RealTimeTripTimes of(ScheduledTripTimes scheduledTripTimes) { + return new RealTimeTripTimes(scheduledTripTimes); + } + + @Override + public RealTimeTripTimes copyScheduledTimes() { + return new RealTimeTripTimes(this, scheduledTripTimes); + } + + /** + * Both trip_headsign and stop_headsign (per stop on a particular trip) are optional GTFS fields. + * A trip may not have a headsign, in which case we should fall back on a Timetable or + * Pattern-level headsign. Such a string will be available when we give TripPatterns or + * StopPatterns unique human readable route variant names, but a TripTimes currently does not have + * a pointer to its enclosing timetable or pattern. + */ + @Nullable + public I18NString getHeadsign(final int stop) { + return (headsigns != null && headsigns[stop] != null) + ? headsigns[stop] + : scheduledTripTimes.getHeadsign(stop); + } + + /** + * Return list of via names per particular stop. This field provides info about intermediate stops + * between current stop and final trip destination. Mapped from NeTEx DestinationDisplay.vias. No + * GTFS mapping at the moment. + * + * @return Empty list if there are no vias registered for a stop. + */ + @Override + public List getHeadsignVias(final int stop) { + return scheduledTripTimes.getHeadsignVias(stop); + } + + /** + * @return the whole trip's headsign. Individual stops can have different headsigns. + */ + @Override + public I18NString getTripHeadsign() { + return scheduledTripTimes.getTripHeadsign(); + } + + /** + * The time in seconds after midnight at which the vehicle should arrive at the given stop + * according to the original schedule. + */ + @Override + public int getScheduledArrivalTime(final int stop) { + return scheduledTripTimes.getScheduledArrivalTime(stop); + } + + /** + * The time in seconds after midnight at which the vehicle should leave the given stop according + * to the original schedule. + */ + @Override + public int getScheduledDepartureTime(final int stop) { + return scheduledTripTimes.getScheduledDepartureTime(stop); + } + + /** + * The time in seconds after midnight at which the vehicle arrives at each stop, accounting for + * any real-time updates. + */ + @Override + public int getArrivalTime(final int stop) { + return getOrElse(stop, arrivalTimes, scheduledTripTimes::getScheduledArrivalTime); + } + + /** + * The time in seconds after midnight at which the vehicle leaves each stop, accounting for any + * real-time updates. + */ + @Override + public int getDepartureTime(final int stop) { + return getOrElse(stop, departureTimes, scheduledTripTimes::getScheduledDepartureTime); + } + + /** @return the difference between the scheduled and actual arrival times at this stop. */ + @Override + public int getArrivalDelay(final int stop) { + return getArrivalTime(stop) - scheduledTripTimes.getScheduledArrivalTime(stop); + } + + /** @return the difference between the scheduled and actual departure times at this stop. */ + @Override + public int getDepartureDelay(final int stop) { + return getDepartureTime(stop) - scheduledTripTimes.getScheduledDepartureTime(stop); + } + + public void setRecorded(int stop) { + setStopRealTimeStates(stop, StopRealTimeState.RECORDED); + } + + public void setCancelled(int stop) { + setStopRealTimeStates(stop, StopRealTimeState.CANCELLED); + } + + public void setNoData(int stop) { + setStopRealTimeStates(stop, StopRealTimeState.NO_DATA); + } + + public void setPredictionInaccurate(int stop) { + setStopRealTimeStates(stop, StopRealTimeState.INACCURATE_PREDICTIONS); + } + + public boolean isCancelledStop(int stop) { + return isStopRealTimeStates(stop, StopRealTimeState.CANCELLED); + } + + public boolean isRecordedStop(int stop) { + return isStopRealTimeStates(stop, StopRealTimeState.RECORDED); + } + + public boolean isNoDataStop(int stop) { + return isStopRealTimeStates(stop, StopRealTimeState.NO_DATA); + } + + public boolean isPredictionInaccurate(int stop) { + return isStopRealTimeStates(stop, StopRealTimeState.INACCURATE_PREDICTIONS); + } + + public void setOccupancyStatus(int stop, OccupancyStatus occupancyStatus) { + prepareForRealTimeUpdates(); + this.occupancyStatus[stop] = occupancyStatus; + } + + /** + * This is only for API-purposes (does not affect routing). + */ + @Override + public OccupancyStatus getOccupancyStatus(int stop) { + if (this.occupancyStatus == null) { + return OccupancyStatus.NO_DATA_AVAILABLE; + } + return this.occupancyStatus[stop]; + } + + @Override + public BookingInfo getDropOffBookingInfo(int stop) { + return scheduledTripTimes.getDropOffBookingInfo(stop); + } + + @Override + public BookingInfo getPickupBookingInfo(int stop) { + return scheduledTripTimes.getPickupBookingInfo(stop); + } + + @Override + public boolean isScheduled() { + return realTimeState == RealTimeState.SCHEDULED; + } + + @Override + public boolean isCanceledOrDeleted() { + return isCanceled() || isDeleted(); + } + + @Override + public boolean isCanceled() { + return realTimeState == RealTimeState.CANCELED; + } + + @Override + public boolean isDeleted() { + return realTimeState == RealTimeState.DELETED; + } + + @Override + public RealTimeState getRealTimeState() { + return realTimeState; + } + + public void setRealTimeState(final RealTimeState realTimeState) { + this.realTimeState = realTimeState; + } + + /** + * When creating a scheduled TripTimes or wrapping it in updates, we could potentially imply + * negative running or dwell times. We really don't want those being used in routing. This method + * checks that all internal times are increasing. Thus, this check should be used at the end of + * updating trip times, after any propagating or interpolating delay operations. + * + * @throws org.opentripplanner.transit.model.framework.DataValidationException of the first error + * found. + */ + public void validateNonIncreasingTimes() { + final int nStops = arrivalTimes.length; + int prevDep = -9_999_999; + for (int s = 0; s < nStops; s++) { + final int arr = getArrivalTime(s); + final int dep = getDepartureTime(s); + + if (dep < arr) { + throw new DataValidationException( + new TimetableValidationError(NEGATIVE_DWELL_TIME, s, getTrip()) + ); + } + if (prevDep > arr) { + throw new DataValidationException( + new TimetableValidationError(NEGATIVE_HOP_TIME, s, getTrip()) + ); + } + prevDep = dep; + } + } + + /** Cancel this entire trip */ + public void cancelTrip() { + realTimeState = RealTimeState.CANCELED; + } + + /** Soft delete the entire trip */ + public void deleteTrip() { + realTimeState = RealTimeState.DELETED; + } + + public void updateDepartureTime(final int stop, final int time) { + prepareForRealTimeUpdates(); + departureTimes[stop] = time; + } + + public void updateDepartureDelay(final int stop, final int delay) { + prepareForRealTimeUpdates(); + departureTimes[stop] = scheduledTripTimes.getScheduledDepartureTime(stop) + delay; + } + + public void updateArrivalTime(final int stop, final int time) { + prepareForRealTimeUpdates(); + arrivalTimes[stop] = time; + } + + public void updateArrivalDelay(final int stop, final int delay) { + prepareForRealTimeUpdates(); + arrivalTimes[stop] = scheduledTripTimes.getScheduledArrivalTime(stop) + delay; + } + + @Nullable + public Accessibility getWheelchairAccessibility() { + // No need to fall back to scheduled state, since it is copied over in the constructor + return wheelchairAccessibility; + } + + public void updateWheelchairAccessibility(Accessibility wheelchairAccessibility) { + this.wheelchairAccessibility = wheelchairAccessibility; + } + + public int getNumStops() { + return scheduledTripTimes.getNumStops(); + } + + /** + * Returns a time-shifted copy of this TripTimes in which the vehicle passes the given stop index + * (not stop sequence number) at the given time. We only have a mechanism to shift the scheduled + * stoptimes, not the real-time stoptimes. Therefore, this only works on trips without updates for + * now (frequency trips don't have updates). + */ + public TripTimes timeShift(final int stop, final int time, final boolean depart) { + if (arrivalTimes != null || departureTimes != null) { + return null; + } + // Adjust 0-based times to match desired stoptime. + final int shift = time - (depart ? getDepartureTime(stop) : getArrivalTime(stop)); + + return new RealTimeTripTimes( + this, + scheduledTripTimes.copyOfNoDuplication().plusTimeShift(shift).build() + ); + } + + /** + * Time-shift all times on this trip. This is used when updating the time zone for the trip. + */ + @Override + public TripTimes adjustTimesToGraphTimeZone(Duration shiftDelta) { + return new RealTimeTripTimes( + this, + scheduledTripTimes.copyOfNoDuplication().plusTimeShift((int) shiftDelta.toSeconds()).build() + ); + } + + @Override + public int gtfsSequenceOfStopIndex(final int stop) { + return scheduledTripTimes.gtfsSequenceOfStopIndex(stop); + } + + @Override + public OptionalInt stopIndexOfGtfsSequence(int stopSequence) { + return scheduledTripTimes.stopIndexOfGtfsSequence(stopSequence); + } + + @Override + public boolean isTimepoint(final int stopIndex) { + return scheduledTripTimes.isTimepoint(stopIndex); + } + + @Override + public int getServiceCode() { + return scheduledTripTimes.getServiceCode(); + } + + public void setServiceCode(int serviceCode) { + this.scheduledTripTimes = + scheduledTripTimes.copyOfNoDuplication().withServiceCode(serviceCode).build(); + } + + @Override + public Trip getTrip() { + return scheduledTripTimes.getTrip(); + } + + /** + * Note: This method only applies for GTFS, not SIRI! + * This method interpolates the times for SKIPPED stops in between regular stops since GTFS-RT + * does not require arrival and departure times for these stops. This method ensures the internal + * time representations in OTP for SKIPPED stops are between the regular stop times immediately + * before and after the cancellation in GTFS-RT. This is to meet the OTP requirement that stop + * times should be increasing and to support the trip search flag `includeRealtimeCancellations`. + * Terminal stop cancellations can be handled by backward and forward propagations, and are + * outside the scope of this method. + * + * @return true if there is interpolated times, false if there is no interpolation. + */ + public boolean interpolateMissingTimes() { + boolean hasInterpolatedTimes = false; + final int numStops = getNumStops(); + boolean startInterpolate = false; + boolean hasPrevTimes = false; + int prevDeparture = 0; + int prevScheduledDeparture = 0; + int prevStopIndex = -1; + + // Loop through all stops + for (int s = 0; s < numStops; s++) { + final boolean isCancelledStop = isCancelledStop(s); + final int scheduledArrival = getScheduledArrivalTime(s); + final int scheduledDeparture = getScheduledDepartureTime(s); + final int arrival = getArrivalTime(s); + final int departure = getDepartureTime(s); + + if (!isCancelledStop && !startInterpolate) { + // Regular stop, could be used for interpolation for future cancellation, keep track. + prevDeparture = departure; + prevScheduledDeparture = scheduledDeparture; + prevStopIndex = s; + hasPrevTimes = true; + } else if (isCancelledStop && !startInterpolate && hasPrevTimes) { + // First cancelled stop, keep track. + startInterpolate = true; + } else if (!isCancelledStop && startInterpolate && hasPrevTimes) { + // First regular stop after cancelled stops, interpolate. + // Calculate necessary info for interpolation. + int numCancelledStops = s - prevStopIndex - 1; + int scheduledTravelTime = scheduledArrival - prevScheduledDeparture; + int realTimeTravelTime = arrival - prevDeparture; + double travelTimeRatio = (double) realTimeTravelTime / scheduledTravelTime; + + // Fill out interpolated time for cancelled stops, using the calculated ratio. + for (int cancelledIndex = prevStopIndex + 1; cancelledIndex < s; cancelledIndex++) { + final int scheduledArrivalCancelled = getScheduledArrivalTime(cancelledIndex); + final int scheduledDepartureCancelled = getScheduledDepartureTime(cancelledIndex); + + // Interpolate + int scheduledArrivalDiff = scheduledArrivalCancelled - prevScheduledDeparture; + double interpolatedArrival = prevDeparture + travelTimeRatio * scheduledArrivalDiff; + int scheduledDepartureDiff = scheduledDepartureCancelled - prevScheduledDeparture; + double interpolatedDeparture = prevDeparture + travelTimeRatio * scheduledDepartureDiff; + + // Set Interpolated Times + updateArrivalTime(cancelledIndex, (int) interpolatedArrival); + updateDepartureTime(cancelledIndex, (int) interpolatedDeparture); + } + + // Set tracking variables + prevDeparture = departure; + prevScheduledDeparture = scheduledDeparture; + prevStopIndex = s; + startInterpolate = false; + hasPrevTimes = true; + + // Set return variable + hasInterpolatedTimes = true; + } + } + + return hasInterpolatedTimes; + } + + /** + * Adjusts arrival time for the stop at the firstUpdatedIndex if no update was given for it and + * arrival/departure times for the stops before that stop. Returns {@code true} if times have been + * adjusted. + */ + public boolean adjustTimesBeforeAlways(int firstUpdatedIndex) { + boolean hasAdjustedTimes = false; + int delay = getDepartureDelay(firstUpdatedIndex); + if (getArrivalDelay(firstUpdatedIndex) == 0) { + updateArrivalDelay(firstUpdatedIndex, delay); + hasAdjustedTimes = true; + } + delay = getArrivalDelay(firstUpdatedIndex); + if (delay == 0) { + return false; + } + for (int i = firstUpdatedIndex - 1; i >= 0; i--) { + hasAdjustedTimes = true; + updateDepartureDelay(i, delay); + updateArrivalDelay(i, delay); + } + return hasAdjustedTimes; + } + + /** + * Adjusts arrival and departure times for the stops before the stop at firstUpdatedIndex when + * required to ensure that the times are increasing. Can set NO_DATA flag on the updated previous + * stops. Returns {@code true} if times have been adjusted. + */ + public boolean adjustTimesBeforeWhenRequired(int firstUpdatedIndex, boolean setNoData) { + if (getArrivalTime(firstUpdatedIndex) > getDepartureTime(firstUpdatedIndex)) { + // The given trip update has arrival time after departure time for the first updated stop. + // This method doesn't try to fix issues in the given data, only for the missing part + return false; + } + int nextStopArrivalTime = getArrivalTime(firstUpdatedIndex); + int delay = getArrivalDelay(firstUpdatedIndex); + boolean hasAdjustedTimes = false; + boolean adjustTimes = true; + for (int i = firstUpdatedIndex - 1; i >= 0; i--) { + if (setNoData && !isCancelledStop(i)) { + setNoData(i); + } + if (adjustTimes) { + if (getDepartureTime(i) < nextStopArrivalTime) { + adjustTimes = false; + continue; + } else { + hasAdjustedTimes = true; + updateDepartureDelay(i, delay); + } + if (getArrivalTime(i) < getDepartureTime(i)) { + adjustTimes = false; + } else { + updateArrivalDelay(i, delay); + nextStopArrivalTime = getArrivalTime(i); + } + } + } + return hasAdjustedTimes; + } + + /* private member methods */ + + private void setStopRealTimeStates(int stop, StopRealTimeState state) { + prepareForRealTimeUpdates(); + this.stopRealTimeStates[stop] = state; + } + + /** + * The real-time states for a given stops. If the state is DEFAULT for a stop, + * the {@link #getRealTimeState()} should determine the realtime state of the stop. + *

+ * This is only for API-purposes (does not affect routing). + */ + private boolean isStopRealTimeStates(int stop, StopRealTimeState state) { + return stopRealTimeStates != null && stopRealTimeStates[stop] == state; + } + + public void setHeadsign(int index, I18NString headsign) { + if (headsigns == null) { + if (headsign.equals(getTrip().getHeadsign())) { + return; + } + this.headsigns = scheduledTripTimes.copyHeadsigns(() -> new I18NString[getNumStops()]); + this.headsigns[index] = headsign; + return; + } + + prepareForRealTimeUpdates(); + headsigns[index] = headsign; + } + + private static int getOrElse(int index, int[] array, IntUnaryOperator defaultValue) { + return array != null ? array[index] : defaultValue.applyAsInt(index); + } + + /** + * If they don't already exist, create arrays for updated arrival and departure times that are + * just time-shifted copies of the zero-based scheduled departure times. + *

+ * Also sets the realtime state to UPDATED. + */ + private void prepareForRealTimeUpdates() { + if (arrivalTimes == null) { + this.arrivalTimes = scheduledTripTimes.copyArrivalTimes(); + this.departureTimes = scheduledTripTimes.copyDepartureTimes(); + // Update the real-time state + this.realTimeState = RealTimeState.UPDATED; + this.stopRealTimeStates = new StopRealTimeState[arrivalTimes.length]; + Arrays.fill(stopRealTimeStates, StopRealTimeState.DEFAULT); + this.headsigns = scheduledTripTimes.copyHeadsigns(() -> null); + this.occupancyStatus = new OccupancyStatus[arrivalTimes.length]; + Arrays.fill(occupancyStatus, OccupancyStatus.NO_DATA_AVAILABLE); + // skip immutable types: scheduledTripTimes & wheelchairAccessibility + } + } +} diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java index c5a5d8f097c..f502a220073 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java @@ -1,23 +1,41 @@ package org.opentripplanner.transit.model.timetable; -import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_DWELL_TIME; -import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_HOP_TIME; +import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_DWELL_TIME; +import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_HOP_TIME; -import java.io.Serializable; +import java.time.Duration; import java.util.Arrays; import java.util.BitSet; import java.util.List; -import java.util.Optional; +import java.util.Objects; import java.util.OptionalInt; import java.util.function.Supplier; import javax.annotation.Nullable; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.lang.IntUtils; +import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.model.BookingInfo; import org.opentripplanner.transit.model.basic.Accessibility; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Deduplicator; +import org.opentripplanner.transit.model.framework.DeduplicatorService; -final class ScheduledTripTimes implements Serializable, Comparable { +/** + * Regular/planed/scheduled read-only version of {@link TripTimes}. The set of static + * trip-times are build during graph-build and can not be changed using real-time updates. + * + * @see RealTimeTripTimes for real-time version + */ +public final class ScheduledTripTimes implements TripTimes { + + /** + * When time-shifting from one time-zone to another negative times may occur. + */ + private static final int MIN_TIME = DurationUtils.durationInSeconds("-12h"); + /** + * We allow a trip to last for maximum 20 days. In Norway the longest trip is 6 days. + */ + private static final int MAX_TIME = DurationUtils.durationInSeconds("20d"); /** * Implementation notes: This allows re-using the same scheduled arrival and departure time @@ -48,31 +66,34 @@ final class ScheduledTripTimes implements Serializable, Comparable * Always provide a deduplicator when building the graph. No deduplication is ok when changing - * simple fields like {@code timeShift} and {@code serviceCode} or even the prefered way in a + * simple fields like {@code timeShift} and {@code serviceCode} or even the prefered way in an * unittest. */ - public ScheduledTripTimesBuilder copyOf(@Nullable Deduplicator deduplicator) { + public static ScheduledTripTimesBuilder of() { + return new ScheduledTripTimesBuilder(null); + } + + public static ScheduledTripTimesBuilder of(DeduplicatorService deduplicator) { + return new ScheduledTripTimesBuilder(deduplicator); + } + + public ScheduledTripTimesBuilder copyOf(Deduplicator deduplicator) { return new ScheduledTripTimesBuilder( timeShift, serviceCode, @@ -96,132 +117,127 @@ public ScheduledTripTimesBuilder copyOfNoDuplication() { return copyOf(null); } - /** The code for the service on which this trip runs. For departure search optimizations. */ + @Override + public RealTimeTripTimes copyScheduledTimes() { + return RealTimeTripTimes.of(this); + } + + @Override + public TripTimes adjustTimesToGraphTimeZone(Duration shiftDelta) { + return copyOfNoDuplication().plusTimeShift((int) shiftDelta.toSeconds()).build(); + } + + @Override public int getServiceCode() { return serviceCode; } - /** - * The time in seconds after midnight at which the vehicle should arrive at the given stop - * according to the original schedule. - */ + @Override public int getScheduledArrivalTime(final int stop) { return arrivalTimes[stop] + timeShift; } - /** - * The time in seconds after midnight at which the vehicle arrives at each stop, accounting for - * any real-time updates. - */ + @Override public int getArrivalTime(final int stop) { return getScheduledArrivalTime(stop); } - /** @return the difference between the scheduled and actual arrival times at this stop. */ + @Override public int getArrivalDelay(final int stop) { return getArrivalTime(stop) - (arrivalTimes[stop] + timeShift); } - /** - * The time in seconds after midnight at which the vehicle should leave the given stop according - * to the original schedule. - */ + @Override public int getScheduledDepartureTime(final int stop) { return departureTimes[stop] + timeShift; } - /** - * The time in seconds after midnight at which the vehicle leaves each stop, accounting for any - * real-time updates. - */ + @Override public int getDepartureTime(final int stop) { return getScheduledDepartureTime(stop); } - /** @return the difference between the scheduled and actual departure times at this stop. */ + @Override public int getDepartureDelay(final int stop) { return getDepartureTime(stop) - (departureTimes[stop] + timeShift); } - /** - * Whether or not stopIndex is considered a GTFS timepoint. - */ + @Override public boolean isTimepoint(final int stopIndex) { return timepoints.get(stopIndex); } - /** The trips whose arrivals and departures are represented by this class */ + @Override public Trip getTrip() { return trip; } - /** - * Return an integer which can be used to sort TripTimes in order of departure/arrivals. - *

- * This sorted trip times is used to search for trips. OTP assume one trip do NOT pass another - * trip down the line. - */ + @Override public int sortIndex() { return getDepartureTime(0); } + @Override public BookingInfo getDropOffBookingInfo(int stop) { return dropOffBookingInfos.get(stop); } + @Override public BookingInfo getPickupBookingInfo(int stop) { return pickupBookingInfos.get(stop); } - /** - * Return {@code true} if the trip is unmodified, a scheduled trip from a published timetable. - * Return {@code false} if the trip is an updated, cancelled, or otherwise modified one. This - * method differs from {@link #getRealTimeState()} in that it checks whether real-time - * information is actually available. - */ + @Override public boolean isScheduled() { return true; } - /** - * Return {@code true} if canceled or soft-deleted - */ + @Override public boolean isCanceledOrDeleted() { return false; } - /** - * Return {@code true} if canceled - */ + @Override public boolean isCanceled() { return false; } - /** - * Return true if trip is soft-deleted, and should not be visible to the user - */ + @Override public boolean isDeleted() { return false; } + @Override public RealTimeState getRealTimeState() { return RealTimeState.SCHEDULED; } - /** - * @return the whole trip's headsign. Individual stops can have different headsigns. - */ + @Override + public boolean isCancelledStop(int stop) { + return false; + } + + @Override + public boolean isRecordedStop(int stop) { + return false; + } + + @Override + public boolean isNoDataStop(int stop) { + return false; + } + + @Override + public boolean isPredictionInaccurate(int stop) { + return false; + } + + @Override public I18NString getTripHeadsign() { return trip.getHeadsign(); } - /** - * Both trip_headsign and stop_headsign (per stop on a particular trip) are optional GTFS fields. - * A trip may not have a headsign, in which case we should fall back on a Timetable or - * Pattern-level headsign. Such a string will be available when we give TripPatterns or - * StopPatterns unique human-readable route variant names, but a ScheduledTripTimes currently - * does not have a pointer to its enclosing timetable or pattern. - */ + @Override @Nullable public I18NString getHeadsign(final int stop) { return (headsigns != null && headsigns[stop] != null) @@ -229,13 +245,7 @@ public I18NString getHeadsign(final int stop) { : getTrip().getHeadsign(); } - /** - * Return list of via names per particular stop. This field provides info about intermediate stops - * between current stop and final trip destination. Mapped from NeTEx DestinationDisplay.vias. No - * GTFS mapping at the moment. - * - * @return Empty list if there are no vias registered for a stop. - */ + @Override public List getHeadsignVias(final int stop) { if (headsignVias == null || headsignVias[stop] == null) { return List.of(); @@ -243,70 +253,27 @@ public List getHeadsignVias(final int stop) { return List.of(headsignVias[stop]); } + @Override public int getNumStops() { return arrivalTimes.length; } + @Override public Accessibility getWheelchairAccessibility() { return trip.getWheelchairBoarding(); } - /** - * This is only for API-purposes (does not affect routing). - */ - public OccupancyStatus getOccupancyStatus(int stop) { + @Override + public OccupancyStatus getOccupancyStatus(int ignore) { return OccupancyStatus.NO_DATA_AVAILABLE; } - /** - * When creating ScheduledTripTimes or wrapping it in updates, we could potentially imply - * negative running or dwell times. We really don't want those being used in routing. This method - * checks that all times are increasing. - * - * @return empty if times were found to be increasing, the first validation error otherwise - */ - public Optional validateNonIncreasingTimes() { - final int nStops = arrivalTimes.length; - int prevDep = -9_999_999; - for (int s = 0; s < nStops; s++) { - final int arr = getArrivalTime(s); - final int dep = getDepartureTime(s); - - if (dep < arr) { - return Optional.of(new ValidationError(NEGATIVE_DWELL_TIME, s)); - } - if (prevDep > arr) { - return Optional.of(new ValidationError(NEGATIVE_HOP_TIME, s)); - } - prevDep = dep; - } - return Optional.empty(); - } - - /** Sort trips based on first departure time. */ @Override - public int compareTo(final ScheduledTripTimes other) { - return this.getDepartureTime(0) - other.getDepartureTime(0); - } - - /** - * Returns the GTFS sequence number of the given 0-based stop position. - * - * These are the GTFS stop sequence numbers, which show the order in which the vehicle visits the - * stops. Despite the fact that the StopPattern or TripPattern enclosing this class provides - * an ordered list of Stops, the original stop sequence numbers may still be needed for matching - * with GTFS-RT update messages. Unfortunately, each individual trip can have totally different - * sequence numbers for the same stops, so we need to store them at the individual trip level. An - * effort is made to re-use the sequence number arrays when they are the same across different - * trips in the same pattern. - */ public int gtfsSequenceOfStopIndex(final int stop) { return originalGtfsStopSequence[stop]; } - /** - * Returns the 0-based stop index of the given GTFS sequence number. - */ + @Override public OptionalInt stopIndexOfGtfsSequence(int stopSequence) { if (originalGtfsStopSequence == null) { return OptionalInt.empty(); @@ -343,4 +310,50 @@ int[] copyDepartureTimes() { I18NString[] copyHeadsigns(Supplier defaultValue) { return headsigns == null ? defaultValue.get() : Arrays.copyOf(headsigns, headsigns.length); } + + /* private methods */ + + private void validate() { + int lastStop = departureTimes.length - 1; + IntUtils.requireInRange(departureTimes[0], MIN_TIME, MAX_TIME); + IntUtils.requireInRange(arrivalTimes[lastStop], MIN_TIME, MAX_TIME); + // TODO: This class is used by FLEX, so we can not validate increasing TripTimes + // validateNonIncreasingTimes(); + } + + /** + * When creating scheduled trip times we could potentially imply negative running or dwell times. + * We really don't want those being used in routing. This method checks that all times are + * increasing. The first stop arrival time and the last stops departure time is NOT checked - + * these should be ignored by raptor. + */ + private void validateNonIncreasingTimes() { + final int lastStop = arrivalTimes.length - 1; + + // This check is currently used since Flex trips may have only one stop. This class should + // not be used to represent FLEX, so remove this check and create new data classes for FLEX + // trips. + if (lastStop < 1) { + return; + } + int prevDep = getDepartureTime(0); + + for (int i = 1; true; ++i) { + final int arr = getArrivalTime(i); + final int dep = getDepartureTime(i); + + if (prevDep > arr) { + throw new DataValidationException(new TimetableValidationError(NEGATIVE_HOP_TIME, i, trip)); + } + if (i == lastStop) { + return; + } + if (dep < arr) { + throw new DataValidationException( + new TimetableValidationError(NEGATIVE_DWELL_TIME, i, trip) + ); + } + prevDep = dep; + } + } } diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java index d6515321f74..0eee5d3e183 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java @@ -4,29 +4,30 @@ import java.util.List; import javax.annotation.Nullable; import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.model.BookingInfo; -import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.DeduplicatorService; public class ScheduledTripTimesBuilder { - private final int NOT_SET = -1; - - int timeShift; - int serviceCode = NOT_SET; - int[] arrivalTimes; - int[] departureTimes; - BitSet timepoints; - Trip trip; - List dropOffBookingInfos; - List pickupBookingInfos; - I18NString[] headsigns; - String[][] headsignVias; - int[] originalGtfsStopSequence; + private static final int NOT_SET = -1; + private static final BitSet EMPTY_BIT_SET = new BitSet(0); + + private int timeShift; + private int serviceCode; + private int[] arrivalTimes; + private int[] departureTimes; + private BitSet timepoints; + private Trip trip; + private List dropOffBookingInfos; + private List pickupBookingInfos; + private I18NString[] headsigns; + private String[][] headsignVias; + private int[] originalGtfsStopSequence; private final DeduplicatorService deduplicator; ScheduledTripTimesBuilder(@Nullable DeduplicatorService deduplicator) { - this.deduplicator = deduplicator == null ? DeduplicatorService.NOOP : deduplicator; + this(0, NOT_SET, null, null, null, null, null, null, null, null, null, deduplicator); } ScheduledTripTimesBuilder( @@ -41,9 +42,8 @@ public class ScheduledTripTimesBuilder { I18NString[] headsigns, String[][] headsignVias, int[] originalGtfsStopSequence, - Deduplicator deduplicator + DeduplicatorService deduplicator ) { - this(deduplicator); this.timeShift = timeShift; this.serviceCode = serviceCode; this.arrivalTimes = arrivalTimes; @@ -55,6 +55,11 @@ public class ScheduledTripTimesBuilder { this.headsigns = headsigns; this.headsignVias = headsignVias; this.originalGtfsStopSequence = originalGtfsStopSequence; + this.deduplicator = deduplicator == null ? DeduplicatorService.NOOP : deduplicator; + } + + public int timeShift() { + return timeShift; } public ScheduledTripTimesBuilder withTimeShift(int timeShift) { @@ -71,59 +76,143 @@ public ScheduledTripTimesBuilder plusTimeShift(int delta) { return this; } + public int serviceCode() { + return serviceCode; + } + public ScheduledTripTimesBuilder withServiceCode(int serviceCode) { this.serviceCode = serviceCode; return this; } + public int[] arrivalTimes() { + return arrivalTimes; + } + public ScheduledTripTimesBuilder withArrivalTimes(int[] arrivalTimes) { this.arrivalTimes = deduplicator.deduplicateIntArray(arrivalTimes); return this; } + /** For unit testing, uses {@link TimeUtils#time(java.lang.String)}. */ + public ScheduledTripTimesBuilder withArrivalTimes(String arrivalTimes) { + return withArrivalTimes(TimeUtils.times(arrivalTimes)); + } + + public int[] departureTimes() { + return departureTimes; + } + public ScheduledTripTimesBuilder withDepartureTimes(int[] departureTimes) { this.departureTimes = deduplicator.deduplicateIntArray(departureTimes); return this; } + /** For unit testing, uses {@link TimeUtils#time(java.lang.String)}. */ + public ScheduledTripTimesBuilder withDepartureTimes(String departureTimes) { + return withDepartureTimes(TimeUtils.times(departureTimes)); + } + + public BitSet timepoints() { + return timepoints == null ? EMPTY_BIT_SET : timepoints; + } + public ScheduledTripTimesBuilder withTimepoints(BitSet timepoints) { this.timepoints = deduplicator.deduplicateBitSet(timepoints); return this; } + public Trip trip() { + return trip; + } + public ScheduledTripTimesBuilder withTrip(Trip trip) { this.trip = trip; return this; } + public List dropOffBookingInfos() { + return dropOffBookingInfos == null ? List.of() : dropOffBookingInfos; + } + public ScheduledTripTimesBuilder withDropOffBookingInfos(List dropOffBookingInfos) { this.dropOffBookingInfos = deduplicator.deduplicateImmutableList(BookingInfo.class, dropOffBookingInfos); return this; } + public List pickupBookingInfos() { + return pickupBookingInfos == null ? List.of() : pickupBookingInfos; + } + public ScheduledTripTimesBuilder withPickupBookingInfos(List pickupBookingInfos) { this.pickupBookingInfos = deduplicator.deduplicateImmutableList(BookingInfo.class, pickupBookingInfos); return this; } + public I18NString[] headsigns() { + return headsigns; + } + public ScheduledTripTimesBuilder withHeadsigns(I18NString[] headsigns) { this.headsigns = deduplicator.deduplicateObjectArray(I18NString.class, headsigns); return this; } + public String[][] headsignVias() { + return headsignVias; + } + public ScheduledTripTimesBuilder withHeadsignVias(String[][] headsignVias) { this.headsignVias = deduplicator.deduplicateString2DArray(headsignVias); return this; } + public int[] originalGtfsStopSequence() { + return originalGtfsStopSequence; + } + public ScheduledTripTimesBuilder withOriginalGtfsStopSequence(int[] originalGtfsStopSequence) { this.originalGtfsStopSequence = deduplicator.deduplicateIntArray(originalGtfsStopSequence); return this; } public ScheduledTripTimes build() { + normalizeTimes(); return new ScheduledTripTimes(this); } + + /** + * Times are always shifted to zero based on the first departure time. This is essential for + * frequencies and deduplication. + */ + private void normalizeTimes() { + if (departureTimes == null) { + this.departureTimes = arrivalTimes; + } + if (arrivalTimes == null) { + this.arrivalTimes = departureTimes; + } + + int shift = departureTimes[0]; + if (shift == 0) { + return; + } + this.departureTimes = timeShift(departureTimes, shift); + if (arrivalTimes != departureTimes) { + this.arrivalTimes = timeShift(arrivalTimes, shift); + } + this.timeShift += shift; + } + + int[] timeShift(int[] a, int shift) { + if (shift == 0) { + return a; + } + for (int i = 0; i < a.length; i++) { + a[i] -= shift; + } + return a; + } } diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java b/src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java index bf98c6e77b8..bd21905f7d1 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java @@ -7,7 +7,7 @@ import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.model.BookingInfo; import org.opentripplanner.model.StopTime; -import org.opentripplanner.transit.model.framework.Deduplicator; +import org.opentripplanner.transit.model.framework.DeduplicatorService; class StopTimeToScheduledTripTimesMapper { @@ -16,7 +16,7 @@ class StopTimeToScheduledTripTimesMapper { private static final String[] EMPTY_STRING_ARRAY = new String[0]; - private StopTimeToScheduledTripTimesMapper(Trip trip, Deduplicator deduplicator) { + private StopTimeToScheduledTripTimesMapper(Trip trip, DeduplicatorService deduplicator) { this.trip = trip; this.builder = ScheduledTripTimes.of(deduplicator).withTrip(trip); } @@ -29,7 +29,7 @@ private StopTimeToScheduledTripTimesMapper(Trip trip, Deduplicator deduplicator) public static ScheduledTripTimes map( Trip trip, Collection stopTimes, - Deduplicator deduplicator + DeduplicatorService deduplicator ) { return new StopTimeToScheduledTripTimesMapper(trip, deduplicator).doMap(stopTimes); } @@ -40,16 +40,13 @@ private ScheduledTripTimes doMap(Collection stopTimes) { final int[] arrivals = new int[nStops]; final int[] sequences = new int[nStops]; final BitSet timepoints = new BitSet(nStops); - // Times are always shifted to zero. This is essential for frequencies and deduplication. - int timeShift = stopTimes.iterator().next().getArrivalTime(); - builder.withTimeShift(timeShift); final List dropOffBookingInfos = new ArrayList<>(); final List pickupBookingInfos = new ArrayList<>(); int s = 0; for (final StopTime st : stopTimes) { - departures[s] = st.getDepartureTime() - timeShift; - arrivals[s] = st.getArrivalTime() - timeShift; + departures[s] = st.getDepartureTime(); + arrivals[s] = st.getArrivalTime(); sequences[s] = st.getStopSequence(); timepoints.set(s, st.getTimepoint() == 1); diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TimetableValidationError.java b/src/main/java/org/opentripplanner/transit/model/timetable/TimetableValidationError.java new file mode 100644 index 00000000000..a69fd87e93b --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TimetableValidationError.java @@ -0,0 +1,29 @@ +package org.opentripplanner.transit.model.timetable; + +import org.opentripplanner.framework.error.OtpError; + +/** + * Details about why a {@link TripTimes} instance is invalid. + */ +public record TimetableValidationError(ErrorCode code, int stopIndex, Trip trip) + implements OtpError { + @Override + public String errorCode() { + return code.name(); + } + + @Override + public String messageTemplate() { + return "%s for stop position %d in trip %s."; + } + + @Override + public Object[] messageArguments() { + return new Object[] { code, stopIndex, trip }; + } + + public enum ErrorCode { + NEGATIVE_DWELL_TIME, + NEGATIVE_HOP_TIME, + } +} diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java index b4ca4850a18..7bf1a826c1c 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java @@ -1,618 +1,181 @@ package org.opentripplanner.transit.model.timetable; -import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_DWELL_TIME; -import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_HOP_TIME; - import java.io.Serializable; import java.time.Duration; -import java.util.Arrays; +import java.util.Comparator; import java.util.List; -import java.util.Optional; import java.util.OptionalInt; -import java.util.function.IntUnaryOperator; import javax.annotation.Nullable; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.model.BookingInfo; import org.opentripplanner.transit.model.basic.Accessibility; /** - * A TripTimes represents the arrival and departure times for a single trip in an Timetable. It is - * carried along by States when routing to ensure that they have a consistent, fast view of the trip - * when realtime updates have been applied. All times are expressed as seconds since midnight (as in + * A TripTimes represents the arrival and departure times for a single trip in a timetable. It is + * one of the core class used for transit routing. This interface allow different kind of trip + * to implement their own trip times. Scheduled/planned trips should be immutable, real-time + * trip times should allow updates and more info, frequency-based trips can use a more compact + * implementation, and Flex may expose part of the trip as a "scheduled/regular" stop-to-stop + * trip using this interface. All times are expressed as seconds since midnight (as in * GTFS). */ -public final class TripTimes implements Serializable, Comparable { - - private ScheduledTripTimes scheduledTripTimes; - - private int[] arrivalTimes; - private int[] departureTimes; - private RealTimeState realTimeState; - private StopRealTimeState[] stopRealTimeStates; - private I18NString[] headsigns; - private OccupancyStatus[] occupancyStatus; - private Accessibility wheelchairAccessibility; - - private TripTimes(final TripTimes original, int timeShiftDelta) { - this( - original, - original.scheduledTripTimes.copyOfNoDuplication().plusTimeShift(timeShiftDelta).build() - ); - } - - TripTimes(ScheduledTripTimes scheduledTripTimes) { - this( - scheduledTripTimes, - scheduledTripTimes.getRealTimeState(), - null, - null, - null, - scheduledTripTimes.getWheelchairAccessibility() - ); - } - - private TripTimes(TripTimes original, ScheduledTripTimes scheduledTripTimes) { - this( - scheduledTripTimes, - original.realTimeState, - original.stopRealTimeStates, - original.headsigns, - original.occupancyStatus, - original.wheelchairAccessibility - ); - } - - private TripTimes( - ScheduledTripTimes scheduledTripTimes, - RealTimeState realTimeState, - StopRealTimeState[] stopRealTimeStates, - I18NString[] headsigns, - OccupancyStatus[] occupancyStatus, - Accessibility wheelchairAccessibility - ) { - this.scheduledTripTimes = scheduledTripTimes; - this.realTimeState = realTimeState; - this.stopRealTimeStates = stopRealTimeStates; - this.headsigns = headsigns; - this.occupancyStatus = occupancyStatus; - this.wheelchairAccessibility = wheelchairAccessibility; - - // We set these to null to indicate that this is a non-updated/scheduled TripTimes. - // We cannot point to the scheduled times because we do not want to make an unnecessary copy. - this.arrivalTimes = null; - this.departureTimes = null; - } - - public static TripTimes of(ScheduledTripTimes scheduledTripTimes) { - return new TripTimes(scheduledTripTimes); - } - +public interface TripTimes extends Serializable, Comparable { /** * Copy scheduled times, but not the actual times. */ - public TripTimes copyOfScheduledTimes() { - return new TripTimes(this, scheduledTripTimes); - } + RealTimeTripTimes copyScheduledTimes(); - /** - * Both trip_headsign and stop_headsign (per stop on a particular trip) are optional GTFS fields. - * A trip may not have a headsign, in which case we should fall back on a Timetable or - * Pattern-level headsign. Such a string will be available when we give TripPatterns or - * StopPatterns unique human readable route variant names, but a TripTimes currently does not have - * a pointer to its enclosing timetable or pattern. - */ - @Nullable - public I18NString getHeadsign(final int stop) { - return (headsigns != null && headsigns[stop] != null) - ? headsigns[stop] - : scheduledTripTimes.getHeadsign(stop); - } - - /** - * Return list of via names per particular stop. This field provides info about intermediate stops - * between current stop and final trip destination. Mapped from NeTEx DestinationDisplay.vias. No - * GTFS mapping at the moment. - * - * @return Empty list if there are no vias registered for a stop. - */ - public List getHeadsignVias(final int stop) { - return scheduledTripTimes.getHeadsignVias(stop); - } - - /** - * @return the whole trip's headsign. Individual stops can have different headsigns. - */ - public I18NString getTripHeadsign() { - return scheduledTripTimes.getTripHeadsign(); - } + /** The code for the service on which this trip runs. For departure search optimizations. */ + int getServiceCode(); /** * The time in seconds after midnight at which the vehicle should arrive at the given stop * according to the original schedule. */ - public int getScheduledArrivalTime(final int stop) { - return scheduledTripTimes.getScheduledArrivalTime(stop); - } + int getScheduledArrivalTime(int stop); /** - * The time in seconds after midnight at which the vehicle should leave the given stop according - * to the original schedule. + * The time in seconds after midnight at which the vehicle arrives at each stop, accounting for + * any real-time updates. */ - public int getScheduledDepartureTime(final int stop) { - return scheduledTripTimes.getScheduledDepartureTime(stop); - } + int getArrivalTime(int stop); - /** - * Return an integer which can be used to sort TripTimes in order of departure/arrivals. - *

- * This sorted trip times is used to search for trips. OTP assume one trip do NOT pass another - * trip down the line. - */ - public int sortIndex() { - return getDepartureTime(0); - } + /** @return the difference between the scheduled and actual arrival times at this stop. */ + int getArrivalDelay(int stop); /** - * The time in seconds after midnight at which the vehicle arrives at each stop, accounting for - * any real-time updates. + * The time in seconds after midnight at which the vehicle should leave the given stop according + * to the original schedule. */ - public int getArrivalTime(final int stop) { - return getOrElse(stop, arrivalTimes, scheduledTripTimes::getScheduledArrivalTime); - } + int getScheduledDepartureTime(int stop); /** * The time in seconds after midnight at which the vehicle leaves each stop, accounting for any * real-time updates. */ - public int getDepartureTime(final int stop) { - return getOrElse(stop, departureTimes, scheduledTripTimes::getScheduledDepartureTime); - } - - /** @return the difference between the scheduled and actual arrival times at this stop. */ - public int getArrivalDelay(final int stop) { - return getArrivalTime(stop) - scheduledTripTimes.getScheduledArrivalTime(stop); - } + int getDepartureTime(int stop); /** @return the difference between the scheduled and actual departure times at this stop. */ - public int getDepartureDelay(final int stop) { - return getDepartureTime(stop) - scheduledTripTimes.getScheduledDepartureTime(stop); - } - - public void setRecorded(int stop) { - setStopRealTimeStates(stop, StopRealTimeState.RECORDED); - } - - public void setCancelled(int stop) { - setStopRealTimeStates(stop, StopRealTimeState.CANCELLED); - } - - public void setNoData(int stop) { - setStopRealTimeStates(stop, StopRealTimeState.NO_DATA); - } - - public void setPredictionInaccurate(int stop) { - setStopRealTimeStates(stop, StopRealTimeState.INACCURATE_PREDICTIONS); - } + int getDepartureDelay(int stop); - public boolean isCancelledStop(int stop) { - return isStopRealTimeStates(stop, StopRealTimeState.CANCELLED); - } - - public boolean isRecordedStop(int stop) { - return isStopRealTimeStates(stop, StopRealTimeState.RECORDED); - } - - public boolean isNoDataStop(int stop) { - return isStopRealTimeStates(stop, StopRealTimeState.NO_DATA); - } - - public boolean isPredictionInaccurate(int stop) { - return isStopRealTimeStates(stop, StopRealTimeState.INACCURATE_PREDICTIONS); - } + /** + * Whether stopIndex is considered a GTFS timepoint. + */ + boolean isTimepoint(int stopIndex); - public void setOccupancyStatus(int stop, OccupancyStatus occupancyStatus) { - prepareForRealTimeUpdates(); - this.occupancyStatus[stop] = occupancyStatus; - } + /** The trips whose arrivals and departures are represented by this class */ + Trip getTrip(); /** - * This is only for API-purposes (does not affect routing). + * Return an integer which can be used to sort TripTimes in order of departure/arrivals. + *

+ * This sorted trip times is used to search for trips. OTP assume one trip do NOT pass another + * trip down the line. */ - public OccupancyStatus getOccupancyStatus(int stop) { - if (this.occupancyStatus == null) { - return OccupancyStatus.NO_DATA_AVAILABLE; - } - return this.occupancyStatus[stop]; + default int sortIndex() { + return getDepartureTime(0); } - public BookingInfo getDropOffBookingInfo(int stop) { - return scheduledTripTimes.getDropOffBookingInfo(stop); + /** Sort trips based on first departure time. */ + default Comparator compare() { + return Comparator.comparingInt(TripTimes::sortIndex); } - public BookingInfo getPickupBookingInfo(int stop) { - return scheduledTripTimes.getPickupBookingInfo(stop); + /** Sort trips based on first departure time. */ + default int compareTo(TripTimes other) { + return sortIndex() - other.sortIndex(); } + BookingInfo getDropOffBookingInfo(int stop); + + BookingInfo getPickupBookingInfo(int stop); + /** * Return {@code true} if the trip is unmodified, a scheduled trip from a published timetable. * Return {@code false} if the trip is an updated, cancelled, or otherwise modified one. This - * method differs from {@link #getRealTimeState()} in that it checks whether real-time - * information is actually available. + * method differs from {@link #getRealTimeState()} in that it checks whether real-time information + * is actually available. */ - public boolean isScheduled() { - return realTimeState == RealTimeState.SCHEDULED; - } + boolean isScheduled(); /** * Return {@code true} if canceled or soft-deleted */ - public boolean isCanceledOrDeleted() { - return isCanceled() || isDeleted(); - } + boolean isCanceledOrDeleted(); /** * Return {@code true} if canceled */ - public boolean isCanceled() { - return realTimeState == RealTimeState.CANCELED; - } + boolean isCanceled(); /** * Return true if trip is soft-deleted, and should not be visible to the user */ - public boolean isDeleted() { - return realTimeState == RealTimeState.DELETED; - } + boolean isDeleted(); - public RealTimeState getRealTimeState() { - return realTimeState; - } + RealTimeState getRealTimeState(); - public void setRealTimeState(final RealTimeState realTimeState) { - this.realTimeState = realTimeState; - } + boolean isCancelledStop(int stop); + + boolean isRecordedStop(int stop); + + boolean isNoDataStop(int stop); + + boolean isPredictionInaccurate(int stop); /** - * When creating a scheduled TripTimes or wrapping it in updates, we could potentially imply - * negative running or dwell times. We really don't want those being used in routing. This method - * checks that all internal times are increasing. Thus, this check should be used at the end of - * updating trip times, after any propagating or interpolating delay operations. - * - * @return empty if times were found to be increasing, the first validation error otherwise + * @return the whole trip's headsign. Individual stops can have different headsigns. */ - public Optional validateNonIncreasingTimes() { - final int nStops = arrivalTimes.length; - int prevDep = -9_999_999; - for (int s = 0; s < nStops; s++) { - final int arr = getArrivalTime(s); - final int dep = getDepartureTime(s); - - if (dep < arr) { - return Optional.of(new ValidationError(NEGATIVE_DWELL_TIME, s)); - } - if (prevDep > arr) { - return Optional.of(new ValidationError(NEGATIVE_HOP_TIME, s)); - } - prevDep = dep; - } - return Optional.empty(); - } + I18NString getTripHeadsign(); /** - * Note: This method only applies for GTFS, not SIRI! - * This method interpolates the times for SKIPPED stops in between regular stops since GTFS-RT - * does not require arrival and departure times for these stops. This method ensures the internal - * time representations in OTP for SKIPPED stops are between the regular stop times immediately - * before and after the cancellation in GTFS-RT. This is to meet the OTP requirement that stop - * times should be increasing and to support the trip search flag `includeRealtimeCancellations`. - * Terminal stop cancellations can be handled by backward and forward propagations, and are - * outside the scope of this method. - * - * @return true if there is interpolated times, false if there is no interpolation. + * Both trip_headsign and stop_headsign (per stop on a particular trip) are optional GTFS fields. + * A trip may not have a headsign, in which case we should fall back on a Timetable or + * Pattern-level headsign. Such a string will be available when we give TripPatterns or + * StopPatterns unique human-readable route variant names, but a ScheduledTripTimes currently does + * not have a pointer to its enclosing timetable or pattern. */ - public boolean interpolateMissingTimes() { - boolean hasInterpolatedTimes = false; - final int numStops = getNumStops(); - boolean startInterpolate = false; - boolean hasPrevTimes = false; - int prevDeparture = 0; - int prevScheduledDeparture = 0; - int prevStopIndex = -1; - - // Loop through all stops - for (int s = 0; s < numStops; s++) { - final boolean isCancelledStop = isCancelledStop(s); - final int scheduledArrival = getScheduledArrivalTime(s); - final int scheduledDeparture = getScheduledDepartureTime(s); - final int arrival = getArrivalTime(s); - final int departure = getDepartureTime(s); - - if (!isCancelledStop && !startInterpolate) { - // Regular stop, could be used for interpolation for future cancellation, keep track. - prevDeparture = departure; - prevScheduledDeparture = scheduledDeparture; - prevStopIndex = s; - hasPrevTimes = true; - } else if (isCancelledStop && !startInterpolate && hasPrevTimes) { - // First cancelled stop, keep track. - startInterpolate = true; - } else if (!isCancelledStop && startInterpolate && hasPrevTimes) { - // First regular stop after cancelled stops, interpolate. - // Calculate necessary info for interpolation. - int numCancelledStops = s - prevStopIndex - 1; - int scheduledTravelTime = scheduledArrival - prevScheduledDeparture; - int realTimeTravelTime = arrival - prevDeparture; - double travelTimeRatio = (double) realTimeTravelTime / scheduledTravelTime; - - // Fill out interpolated time for cancelled stops, using the calculated ratio. - for (int cancelledIndex = prevStopIndex + 1; cancelledIndex < s; cancelledIndex++) { - final int scheduledArrivalCancelled = getScheduledArrivalTime(cancelledIndex); - final int scheduledDepartureCancelled = getScheduledDepartureTime(cancelledIndex); - - // Interpolate - int scheduledArrivalDiff = scheduledArrivalCancelled - prevScheduledDeparture; - double interpolatedArrival = prevDeparture + travelTimeRatio * scheduledArrivalDiff; - int scheduledDepartureDiff = scheduledDepartureCancelled - prevScheduledDeparture; - double interpolatedDeparture = prevDeparture + travelTimeRatio * scheduledDepartureDiff; - - // Set Interpolated Times - updateArrivalTime(cancelledIndex, (int) interpolatedArrival); - updateDepartureTime(cancelledIndex, (int) interpolatedDeparture); - } - - // Set tracking variables - prevDeparture = departure; - prevScheduledDeparture = scheduledDeparture; - prevStopIndex = s; - startInterpolate = false; - hasPrevTimes = true; - - // Set return variable - hasInterpolatedTimes = true; - } - } - - return hasInterpolatedTimes; - } - - /** Cancel this entire trip */ - public void cancelTrip() { - realTimeState = RealTimeState.CANCELED; - } - - /** Soft delete the entire trip */ - public void deleteTrip() { - realTimeState = RealTimeState.DELETED; - } - - public void updateDepartureTime(final int stop, final int time) { - prepareForRealTimeUpdates(); - departureTimes[stop] = time; - } - - public void updateDepartureDelay(final int stop, final int delay) { - prepareForRealTimeUpdates(); - departureTimes[stop] = scheduledTripTimes.getScheduledDepartureTime(stop) + delay; - } - - public void updateArrivalTime(final int stop, final int time) { - prepareForRealTimeUpdates(); - arrivalTimes[stop] = time; - } - - public void updateArrivalDelay(final int stop, final int delay) { - prepareForRealTimeUpdates(); - arrivalTimes[stop] = scheduledTripTimes.getScheduledArrivalTime(stop) + delay; - } - @Nullable - public Accessibility getWheelchairAccessibility() { - // No need to fall back to scheduled state, since it is copied over in the constructor - return wheelchairAccessibility; - } - - public void updateWheelchairAccessibility(Accessibility wheelchairAccessibility) { - this.wheelchairAccessibility = wheelchairAccessibility; - } - - public int getNumStops() { - return scheduledTripTimes.getNumStops(); - } - - /** Sort trips based on first departure time. */ - @Override - public int compareTo(final TripTimes other) { - return this.getDepartureTime(0) - other.getDepartureTime(0); - } + I18NString getHeadsign(int stop); /** - * Returns a time-shifted copy of this TripTimes in which the vehicle passes the given stop index - * (not stop sequence number) at the given time. We only have a mechanism to shift the scheduled - * stoptimes, not the real-time stoptimes. Therefore, this only works on trips without updates for - * now (frequency trips don't have updates). + * Return list of via names per particular stop. This field provides info about intermediate stops + * between current stop and final trip destination. Mapped from NeTEx DestinationDisplay.vias. No + * GTFS mapping at the moment. + * + * @return Empty list if there are no vias registered for a stop. */ - public TripTimes timeShift(final int stop, final int time, final boolean depart) { - if (arrivalTimes != null || departureTimes != null) { - return null; - } - // Adjust 0-based times to match desired stoptime. - final int shift = time - (depart ? getDepartureTime(stop) : getArrivalTime(stop)); - - return new TripTimes( - this, - scheduledTripTimes.copyOfNoDuplication().plusTimeShift(shift).build() - ); - } + List getHeadsignVias(int stop); + + int getNumStops(); + + Accessibility getWheelchairAccessibility(); /** - * Time-shift all times on this trip. This is used when updating the time zone for the trip. + * This is only for API-purposes (does not affect routing). */ - public TripTimes adjustTimesToGraphTimeZone(Duration shiftDelta) { - return new TripTimes( - this, - scheduledTripTimes.copyOfNoDuplication().plusTimeShift((int) shiftDelta.toSeconds()).build() - ); - } + OccupancyStatus getOccupancyStatus(int stop); /** * Returns the GTFS sequence number of the given 0-based stop position. - * + *

* These are the GTFS stop sequence numbers, which show the order in which the vehicle visits the - * stops. Despite the fact that the StopPattern or TripPattern enclosing this TripTimes provides - * an ordered list of Stops, the original stop sequence numbers may still be needed for matching + * stops. Despite the fact that the StopPattern or TripPattern enclosing this class provides an + * ordered list of Stops, the original stop sequence numbers may still be needed for matching * with GTFS-RT update messages. Unfortunately, each individual trip can have totally different * sequence numbers for the same stops, so we need to store them at the individual trip level. An * effort is made to re-use the sequence number arrays when they are the same across different * trips in the same pattern. */ - public int gtfsSequenceOfStopIndex(final int stop) { - return scheduledTripTimes.gtfsSequenceOfStopIndex(stop); - } + int gtfsSequenceOfStopIndex(int stop); /** * Returns the 0-based stop index of the given GTFS sequence number. */ - public OptionalInt stopIndexOfGtfsSequence(int stopSequence) { - return scheduledTripTimes.stopIndexOfGtfsSequence(stopSequence); - } - - /** - * Whether or not stopIndex is considered a GTFS timepoint. - */ - public boolean isTimepoint(final int stopIndex) { - return scheduledTripTimes.isTimepoint(stopIndex); - } - - /** The code for the service on which this trip runs. For departure search optimizations. */ - public int getServiceCode() { - return scheduledTripTimes.getServiceCode(); - } - - public void setServiceCode(int serviceCode) { - this.scheduledTripTimes = - scheduledTripTimes.copyOfNoDuplication().withServiceCode(serviceCode).build(); - } - - /** The trips whose arrivals and departures are represented by this class */ - public Trip getTrip() { - return scheduledTripTimes.getTrip(); - } - - /** - * Adjusts arrival time for the stop at the firstUpdatedIndex if no update was given for it and - * arrival/departure times for the stops before that stop. Returns {@code true} if times have been - * adjusted. - */ - public boolean adjustTimesBeforeAlways(int firstUpdatedIndex) { - boolean hasAdjustedTimes = false; - int delay = getDepartureDelay(firstUpdatedIndex); - if (getArrivalDelay(firstUpdatedIndex) == 0) { - updateArrivalDelay(firstUpdatedIndex, delay); - hasAdjustedTimes = true; - } - delay = getArrivalDelay(firstUpdatedIndex); - if (delay == 0) { - return false; - } - for (int i = firstUpdatedIndex - 1; i >= 0; i--) { - hasAdjustedTimes = true; - updateDepartureDelay(i, delay); - updateArrivalDelay(i, delay); - } - return hasAdjustedTimes; - } - - /** - * Adjusts arrival and departure times for the stops before the stop at firstUpdatedIndex when - * required to ensure that the times are increasing. Can set NO_DATA flag on the updated previous - * stops. Returns {@code true} if times have been adjusted. - */ - public boolean adjustTimesBeforeWhenRequired(int firstUpdatedIndex, boolean setNoData) { - if (getArrivalTime(firstUpdatedIndex) > getDepartureTime(firstUpdatedIndex)) { - // The given trip update has arrival time after departure time for the first updated stop. - // This method doesn't try to fix issues in the given data, only for the missing part - return false; - } - int nextStopArrivalTime = getArrivalTime(firstUpdatedIndex); - int delay = getArrivalDelay(firstUpdatedIndex); - boolean hasAdjustedTimes = false; - boolean adjustTimes = true; - for (int i = firstUpdatedIndex - 1; i >= 0; i--) { - if (setNoData && !isCancelledStop(i)) { - setNoData(i); - } - if (adjustTimes) { - if (getDepartureTime(i) < nextStopArrivalTime) { - adjustTimes = false; - continue; - } else { - hasAdjustedTimes = true; - updateDepartureDelay(i, delay); - } - if (getArrivalTime(i) < getDepartureTime(i)) { - adjustTimes = false; - } else { - updateArrivalDelay(i, delay); - nextStopArrivalTime = getArrivalTime(i); - } - } - } - return hasAdjustedTimes; - } - - /* private member methods */ - - private void setStopRealTimeStates(int stop, StopRealTimeState state) { - prepareForRealTimeUpdates(); - this.stopRealTimeStates[stop] = state; - } - - /** - * The real-time states for a given stops. If the state is DEFAULT for a stop, - * the {@link #getRealTimeState()} should determine the realtime state of the stop. - *

- * This is only for API-purposes (does not affect routing). - */ - private boolean isStopRealTimeStates(int stop, StopRealTimeState state) { - return stopRealTimeStates != null && stopRealTimeStates[stop] == state; - } - - public void setHeadsign(int index, I18NString headsign) { - if (headsigns == null) { - if (headsign.equals(getTrip().getHeadsign())) { - return; - } - this.headsigns = scheduledTripTimes.copyHeadsigns(() -> new I18NString[getNumStops()]); - this.headsigns[index] = headsign; - return; - } - - prepareForRealTimeUpdates(); - headsigns[index] = headsign; - } - - private static int getOrElse(int index, int[] array, IntUnaryOperator defaultValue) { - return array != null ? array[index] : defaultValue.applyAsInt(index); - } + OptionalInt stopIndexOfGtfsSequence(int stopSequence); /** - * If they don't already exist, create arrays for updated arrival and departure times that are - * just time-shifted copies of the zero-based scheduled departure times. - *

- * Also sets the realtime state to UPDATED. + * Time-shift all times on this trip. This is used when updating the time zone for the trip. */ - private void prepareForRealTimeUpdates() { - if (arrivalTimes == null) { - this.arrivalTimes = scheduledTripTimes.copyArrivalTimes(); - this.departureTimes = scheduledTripTimes.copyDepartureTimes(); - // Update the real-time state - this.realTimeState = RealTimeState.UPDATED; - this.stopRealTimeStates = new StopRealTimeState[arrivalTimes.length]; - Arrays.fill(stopRealTimeStates, StopRealTimeState.DEFAULT); - this.headsigns = scheduledTripTimes.copyHeadsigns(() -> null); - this.occupancyStatus = new OccupancyStatus[arrivalTimes.length]; - Arrays.fill(occupancyStatus, OccupancyStatus.NO_DATA_AVAILABLE); - // skip immutable types: scheduledTripTimes & wheelchairAccessibility - } - } + TripTimes adjustTimesToGraphTimeZone(Duration shiftDelta); } diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java index 1bb953b81c5..611974904f0 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java @@ -1,6 +1,6 @@ package org.opentripplanner.transit.model.timetable; -import java.util.Collection; +import java.util.List; import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model.framework.Deduplicator; @@ -18,11 +18,13 @@ public class TripTimesFactory { * non-interpolated stoptimes should already be marked at timepoints by a previous filtering * step. */ - public static TripTimes tripTimes( + public static RealTimeTripTimes tripTimes( Trip trip, - Collection stopTimes, + List stopTimes, Deduplicator deduplicator ) { - return new TripTimes(StopTimeToScheduledTripTimesMapper.map(trip, stopTimes, deduplicator)); + return new RealTimeTripTimes( + StopTimeToScheduledTripTimesMapper.map(trip, stopTimes, deduplicator) + ); } } diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ValidationError.java b/src/main/java/org/opentripplanner/transit/model/timetable/ValidationError.java deleted file mode 100644 index 1e150218dc5..00000000000 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ValidationError.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.opentripplanner.transit.model.timetable; - -/** - * Details about why a {@link TripTimes} instance is invalid. - */ -public record ValidationError(ErrorCode code, int stopIndex) { - public enum ErrorCode { - NEGATIVE_DWELL_TIME, - NEGATIVE_HOP_TIME, - } -} diff --git a/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java b/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java new file mode 100644 index 00000000000..dee103bc385 --- /dev/null +++ b/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java @@ -0,0 +1,36 @@ +package org.opentripplanner.updater.spi; + +import org.opentripplanner.transit.model.framework.DataValidationException; +import org.opentripplanner.transit.model.framework.Result; +import org.opentripplanner.transit.model.timetable.TimetableValidationError; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Converts a {@link TimetableValidationError} to the model of the updater ready to be consumed + * by the metrics APIS and logs. + */ +public class DataValidationExceptionMapper { + + private static final Logger LOG = LoggerFactory.getLogger(DataValidationExceptionMapper.class); + + public static Result toResult(DataValidationException error) { + if (error.error() instanceof TimetableValidationError tt) { + return Result.failure( + new UpdateError(tt.trip().getId(), mapTimeTableError(tt.code()), tt.stopIndex()) + ); + } + // The mapper should handle all possible errors + LOG.error("Unhandled error: " + error.getMessage(), error); + return Result.failure(UpdateError.noTripId(UpdateError.UpdateErrorType.UNKNOWN)); + } + + private static UpdateError.UpdateErrorType mapTimeTableError( + TimetableValidationError.ErrorCode code + ) { + return switch (code) { + case NEGATIVE_DWELL_TIME -> UpdateError.UpdateErrorType.NEGATIVE_DWELL_TIME; + case NEGATIVE_HOP_TIME -> UpdateError.UpdateErrorType.NEGATIVE_HOP_TIME; + }; + } +} diff --git a/src/main/java/org/opentripplanner/updater/spi/TripTimesValidationMapper.java b/src/main/java/org/opentripplanner/updater/spi/TripTimesValidationMapper.java deleted file mode 100644 index 127eddeabeb..00000000000 --- a/src/main/java/org/opentripplanner/updater/spi/TripTimesValidationMapper.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.opentripplanner.updater.spi; - -import org.opentripplanner.transit.model.framework.FeedScopedId; -import org.opentripplanner.transit.model.framework.Result; -import org.opentripplanner.transit.model.timetable.ValidationError; - -/** - * Converts a {@link ValidationError} to the model of the updater ready to be consumed - * by the metrics APIS and logs. - */ -public class TripTimesValidationMapper { - - public static Result toResult( - FeedScopedId tripId, - ValidationError validationError - ) { - var type = - switch (validationError.code()) { - case NEGATIVE_DWELL_TIME -> UpdateError.UpdateErrorType.NEGATIVE_DWELL_TIME; - case NEGATIVE_HOP_TIME -> UpdateError.UpdateErrorType.NEGATIVE_HOP_TIME; - }; - - return Result.failure(new UpdateError(tripId, type, validationError.stopIndex())); - } -} diff --git a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index 6cda95eb983..19db2c7e309 100644 --- a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -44,6 +44,7 @@ import org.opentripplanner.model.TimetableSnapshotProvider; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.TransitLayerUpdater; import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.framework.Result; @@ -53,8 +54,8 @@ import org.opentripplanner.transit.model.organization.Agency; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.RealTimeState; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.Trip; -import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitEditorService; @@ -62,6 +63,7 @@ import org.opentripplanner.updater.GtfsRealtimeFuzzyTripMatcher; import org.opentripplanner.updater.GtfsRealtimeMapper; import org.opentripplanner.updater.TimetableSnapshotSourceParameters; +import org.opentripplanner.updater.spi.DataValidationExceptionMapper; import org.opentripplanner.updater.spi.ResultLogger; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.spi.UpdateResult; @@ -119,7 +121,7 @@ public class TimetableSnapshotSource implements TimetableSnapshotProvider { */ private volatile TimetableSnapshot snapshot = null; - /** Should expired realtime data be purged from the graph. */ + /** Should expired real-time data be purged from the graph. */ private final boolean purgeExpiredData; protected LocalDate lastPurgeDate = null; @@ -279,31 +281,36 @@ public UpdateResult applyTripUpdates( tripDescriptor ); - Result result = - switch (tripScheduleRelationship) { - case SCHEDULED -> handleScheduledTrip( - tripUpdate, - tripId, - serviceDate, - backwardsDelayPropagationType - ); - case ADDED -> validateAndHandleAddedTrip( - tripUpdate, - tripDescriptor, - tripId, - serviceDate - ); - case CANCELED -> handleCanceledTrip(tripId, serviceDate, CancelationType.CANCEL); - case DELETED -> handleCanceledTrip(tripId, serviceDate, CancelationType.DELETE); - case REPLACEMENT -> validateAndHandleModifiedTrip( - tripUpdate, - tripDescriptor, - tripId, - serviceDate - ); - case UNSCHEDULED -> UpdateError.result(tripId, NOT_IMPLEMENTED_UNSCHEDULED); - case DUPLICATED -> UpdateError.result(tripId, NOT_IMPLEMENTED_DUPLICATED); - }; + Result result; + try { + result = + switch (tripScheduleRelationship) { + case SCHEDULED -> handleScheduledTrip( + tripUpdate, + tripId, + serviceDate, + backwardsDelayPropagationType + ); + case ADDED -> validateAndHandleAddedTrip( + tripUpdate, + tripDescriptor, + tripId, + serviceDate + ); + case CANCELED -> handleCanceledTrip(tripId, serviceDate, CancelationType.CANCEL); + case DELETED -> handleCanceledTrip(tripId, serviceDate, CancelationType.DELETE); + case REPLACEMENT -> validateAndHandleModifiedTrip( + tripUpdate, + tripDescriptor, + tripId, + serviceDate + ); + case UNSCHEDULED -> UpdateError.result(tripId, NOT_IMPLEMENTED_UNSCHEDULED); + case DUPLICATED -> UpdateError.result(tripId, NOT_IMPLEMENTED_DUPLICATED); + }; + } catch (DataValidationException e) { + result = DataValidationExceptionMapper.toResult(e); + } results.add(result); if (result.isFailure()) { @@ -708,7 +715,6 @@ private Result handleAddedTrip( // Just use first service id of set tripBuilder.withServiceId(serviceIds.iterator().next()); } - return addTripToGraphAndBuffer( tripBuilder.build(), tripUpdate.getVehicle(), @@ -886,7 +892,11 @@ private Result addTripToGraphAndBuffer( ); // Create new trip times - final TripTimes newTripTimes = TripTimesFactory.tripTimes(trip, stopTimes, deduplicator); + final RealTimeTripTimes newTripTimes = TripTimesFactory.tripTimes( + trip, + stopTimes, + deduplicator + ); // Update all times to mark trip times as realtime // TODO: should we incorporate the delay field if present? @@ -946,7 +956,9 @@ private boolean cancelScheduledTrip( if (tripIndex == -1) { debug(tripId, "Could not cancel scheduled trip because it's not in the timetable"); } else { - final TripTimes newTripTimes = timetable.getTripTimes(tripIndex).copyOfScheduledTimes(); + final RealTimeTripTimes newTripTimes = timetable + .getTripTimes(tripIndex) + .copyScheduledTimes(); switch (cancelationType) { case CANCEL -> newTripTimes.cancelTrip(); case DELETE -> newTripTimes.deleteTrip(); @@ -986,7 +998,9 @@ private boolean cancelPreviouslyAddedTrip( if (tripIndex == -1) { debug(tripId, "Could not cancel previously added trip on {}", serviceDate); } else { - final TripTimes newTripTimes = timetable.getTripTimes(tripIndex).copyOfScheduledTimes(); + final RealTimeTripTimes newTripTimes = timetable + .getTripTimes(tripIndex) + .copyScheduledTimes(); switch (cancelationType) { case CANCEL -> newTripTimes.cancelTrip(); case DELETE -> newTripTimes.deleteTrip(); diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 9dba2c90a5e..d6cc164e3c1 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -3098,7 +3098,7 @@ type QueryType { omitCanceled: Boolean = true """ - When true, realtime updates are ignored during this search. Default value: false + When true, real-time updates are ignored during this search. Default value: false """ ignoreRealtimeUpdates: Boolean diff --git a/src/test/java/org/opentripplanner/OtpArchitectureModules.java b/src/test/java/org/opentripplanner/OtpArchitectureModules.java index e4d08427c71..72520acaa1c 100644 --- a/src/test/java/org/opentripplanner/OtpArchitectureModules.java +++ b/src/test/java/org/opentripplanner/OtpArchitectureModules.java @@ -39,6 +39,7 @@ public interface OtpArchitectureModules { */ Module FRAMEWORK_UTILS = Module.of( FRAMEWORK.subPackage("application"), + FRAMEWORK.subPackage("error"), FRAMEWORK.subPackage("i18n"), FRAMEWORK.subPackage("lang"), FRAMEWORK.subPackage("logging"), diff --git a/src/test/java/org/opentripplanner/graph_builder/module/map/StreetMatcherTest.java b/src/test/java/org/opentripplanner/graph_builder/module/map/StreetMatcherTest.java deleted file mode 100644 index 92001fbf8a7..00000000000 --- a/src/test/java/org/opentripplanner/graph_builder/module/map/StreetMatcherTest.java +++ /dev/null @@ -1,157 +0,0 @@ -package org.opentripplanner.graph_builder.module.map; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.GeometryFactory; -import org.locationtech.jts.geom.LineString; -import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.street.model._data.StreetModelForTest; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.vertex.SimpleVertex; -import org.opentripplanner.street.model.vertex.StreetVertex; -import org.opentripplanner.street.model.vertex.VertexLabel; - -public class StreetMatcherTest { - - static GeometryFactory gf = new GeometryFactory(); - - private Graph graph; - - @BeforeEach - public void before() { - graph = new Graph(); - - vertex("56th_24th", 47.669457, -122.387577); - vertex("56th_22nd", 47.669462, -122.384739); - vertex("56th_20th", 47.669457, -122.382106); - - vertex("market_24th", 47.668690, -122.387577); - vertex("market_ballard", 47.668683, -122.386096); - vertex("market_22nd", 47.668686, -122.384749); - vertex("market_leary", 47.668669, -122.384392); - vertex("market_russell", 47.668655, -122.382997); - vertex("market_20th", 47.668684, -122.382117); - - vertex("shilshole_24th", 47.668419, -122.387534); - vertex("shilshole_22nd", 47.666519, -122.384744); - vertex("shilshole_vernon", 47.665938, -122.384048); - vertex("shilshole_20th", 47.664356, -122.382192); - - vertex("ballard_turn", 47.668509, -122.386069); - vertex("ballard_22nd", 47.667624, -122.384744); - vertex("ballard_vernon", 47.666422, -122.383158); - vertex("ballard_20th", 47.665476, -122.382128); - - vertex("leary_vernon", 47.666863, -122.382353); - vertex("leary_20th", 47.666682, -122.382160); - - vertex("russell_20th", 47.667846, -122.382128); - - edges("56th_24th", "56th_22nd", "56th_20th"); - - edges("56th_24th", "market_24th"); - edges("56th_22nd", "market_22nd"); - edges("56th_20th", "market_20th"); - - edges( - "market_24th", - "market_ballard", - "market_22nd", - "market_leary", - "market_russell", - "market_20th" - ); - edges("market_24th", "shilshole_24th", "shilshole_22nd", "shilshole_vernon", "shilshole_20th"); - edges("market_ballard", "ballard_turn", "ballard_22nd", "ballard_vernon", "ballard_20th"); - edges("market_leary", "leary_vernon", "leary_20th"); - edges("market_russell", "russell_20th"); - - edges("market_22nd", "ballard_22nd", "shilshole_22nd"); - edges("leary_vernon", "ballard_vernon", "shilshole_vernon"); - edges("market_20th", "russell_20th", "leary_20th", "ballard_20th", "shilshole_20th"); - } - - @Test - public void testStreetMatcher() { - LineString geometry = geometry(-122.385689, 47.669484, -122.387384, 47.669470); - - StreetMatcher matcher = new StreetMatcher(graph); - - List match = matcher.match(geometry); - assertNotNull(match); - assertEquals(1, match.size()); - assertEquals("56th_24th", match.get(0).getToVertex().getLabelString()); - - geometry = geometry(-122.385689, 47.669484, -122.387384, 47.669470, -122.387588, 47.669325); - - match = matcher.match(geometry); - assertNotNull(match); - assertEquals(2, match.size()); - - geometry = - geometry( - -122.385689, - 47.669484, - -122.387384, - 47.669470, - -122.387588, - 47.669325, - -122.387255, - 47.668675 - ); - - match = matcher.match(geometry); - assertNotNull(match); - assertEquals(3, match.size()); - - geometry = - geometry( - -122.384756, - 47.669260, - -122.384777, - 47.667454, - -122.383554, - 47.666789, - -122.3825, - 47.666 - ); - match = matcher.match(geometry); - assertNotNull(match); - assertEquals(4, match.size()); - assertEquals("ballard_20th", match.get(3).getToVertex().getLabelString()); - } - - private LineString geometry(double... ordinates) { - Coordinate[] coords = new Coordinate[ordinates.length / 2]; - - for (int i = 0; i < ordinates.length; i += 2) { - coords[i / 2] = new Coordinate(ordinates[i], ordinates[i + 1]); - } - return gf.createLineString(coords); - } - - /**** - * Private Methods - ****/ - - private SimpleVertex vertex(String label, double lat, double lon) { - var v = new SimpleVertex(label, lat, lon); - graph.addVertex(v); - return v; - } - - private void edges(String... vLabels) { - for (int i = 0; i < vLabels.length - 1; i++) { - StreetVertex vA = (StreetVertex) graph.getVertex(VertexLabel.string(vLabels[i])); - StreetVertex vB = (StreetVertex) graph.getVertex(VertexLabel.string(vLabels[i + 1])); - - StreetModelForTest.streetEdge(vA, vB); - StreetModelForTest.streetEdge(vB, vA); - } - } -} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap b/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap index 8f527b15bf7..31d59483807 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap +++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap @@ -106,7 +106,7 @@ org.opentripplanner.routing.algorithm.mapping.CarSnapshotTest.directCarPark=[ "hasWheelchairAccessibleCarPlaces" : false, "id" : "OSM:OSMWay/-102488", "name" : "P+R", - "realtime" : false, + "realTime" : false, "tags" : [ "osm:amenity=parking" ] @@ -137,7 +137,7 @@ org.opentripplanner.routing.algorithm.mapping.CarSnapshotTest.directCarPark=[ "hasWheelchairAccessibleCarPlaces" : false, "id" : "OSM:OSMWay/-102488", "name" : "P+R", - "realtime" : false, + "realTime" : false, "tags" : [ "osm:amenity=parking" ] diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java index 857cd2eb834..ed71b3de400 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java @@ -6,7 +6,6 @@ import java.time.LocalDate; import java.time.ZonedDateTime; import java.util.ArrayList; -import java.util.Arrays; import java.util.BitSet; import java.util.List; import org.junit.jupiter.api.Test; @@ -16,13 +15,12 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripPatternForDate; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.basic.TransitMode; -import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.RoutingTripPattern; import org.opentripplanner.transit.model.network.StopPattern; import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.timetable.ScheduledTripTimes; import org.opentripplanner.transit.model.timetable.TripTimes; -import org.opentripplanner.transit.model.timetable.TripTimesFactory; public class RaptorRoutingRequestTransitDataCreatorTest { @@ -99,21 +97,15 @@ private static TripPatternForDates findTripPatternForDate( } private TripTimes createTripTimesForTest() { - StopTime stopTime1 = new StopTime(); - StopTime stopTime2 = new StopTime(); - - stopTime1.setDepartureTime(0); - stopTime2.setArrivalTime(7200); - - return TripTimesFactory.tripTimes( - TransitModelForTest.trip("Test").build(), - Arrays.asList(stopTime1, stopTime2), - new Deduplicator() - ); + return ScheduledTripTimes + .of() + .withTrip(TransitModelForTest.trip("Test").build()) + .withDepartureTimes("00:00 02:00") + .build(); } /** - * Utility function to create bare minimum of valid StopTime with no interesting attributes + * Utility function to create bare minimum of valid StopTime * * @return StopTime instance */ diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java index 86d5f9404ed..a45c3f2b7a6 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java @@ -40,6 +40,7 @@ import org.opentripplanner.transit.model.network.StopPattern; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.site.RegularStop; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripAlteration; import org.opentripplanner.transit.model.timetable.TripBuilder; @@ -513,7 +514,7 @@ void keepAccessibleTrip() { @Test void keepRealTimeAccessibleTrip() { - TripTimes realTimeWheelchairAccessibleTrip = createTestTripTimes( + RealTimeTripTimes realTimeWheelchairAccessibleTrip = createTestTripTimes( TRIP_ID, ROUTE, BikeAccess.NOT_ALLOWED, @@ -616,7 +617,7 @@ void includeRealtimeCancellationsTest() { TripAlteration.PLANNED ); - TripTimes tripTimesWithCancellation = createTestTripTimes( + RealTimeTripTimes tripTimesWithCancellation = createTestTripTimes( TRIP_ID, ROUTE, BikeAccess.NOT_ALLOWED, @@ -860,7 +861,7 @@ private List combinedFilterForModesAndBannedAgencies( ); } - private TripTimes createTestTripTimes( + private RealTimeTripTimes createTestTripTimes( FeedScopedId tripId, Route route, BikeAccess bikeAccess, diff --git a/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java b/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java index 54194696e71..1432c68fd49 100644 --- a/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java +++ b/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java @@ -38,7 +38,9 @@ public static void setUp() throws Exception { transitService.getTripForId(new FeedScopedId(feedId, "5.1")) ); var tt = transitService.getTimetableForTripPattern(pattern, LocalDate.now()); - tt.getTripTimes(0).cancelTrip(); + var newTripTimes = tt.getTripTimes(0).copyScheduledTimes(); + newTripTimes.cancelTrip(); + tt.setTripTimes(0, newTripTimes); } /** diff --git a/src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java b/src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java similarity index 65% rename from src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java rename to src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java index 4c2cc41cef2..5b3a8f76052 100644 --- a/src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java +++ b/src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java @@ -3,12 +3,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; -import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_DWELL_TIME; -import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_HOP_TIME; +import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_DWELL_TIME; +import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_HOP_TIME; -import java.util.Collection; import java.util.LinkedList; import java.util.List; import org.junit.jupiter.api.Nested; @@ -17,17 +17,18 @@ import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.RegularStop; -class TripTimesTest { +class RealTimeTripTimesTest { private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of(); private static final String TRIP_ID = "testTripId"; - private static final List stops = List.of( + private static final List stopIds = List.of( id("A"), id("B"), id("C"), @@ -43,10 +44,10 @@ static TripTimes createInitialTripTimes() { List stopTimes = new LinkedList<>(); - for (int i = 0; i < stops.size(); ++i) { + for (int i = 0; i < stopIds.size(); ++i) { StopTime stopTime = new StopTime(); - RegularStop stop = TEST_MODEL.stop(stops.get(i).getId(), 0.0, 0.0).build(); + RegularStop stop = TEST_MODEL.stop(stopIds.get(i).getId(), 0.0, 0.0).build(); stopTime.setStop(stop); stopTime.setArrivalTime(i * 60); stopTime.setDepartureTime(i * 60); @@ -69,7 +70,7 @@ class Headsign { @Test void shouldHandleBothNullScenario() { Trip trip = TransitModelForTest.trip("TRIP").build(); - Collection stopTimes = List.of(EMPTY_STOPPOINT, EMPTY_STOPPOINT, EMPTY_STOPPOINT); + List stopTimes = List.of(EMPTY_STOPPOINT, EMPTY_STOPPOINT, EMPTY_STOPPOINT); TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); @@ -80,7 +81,7 @@ void shouldHandleBothNullScenario() { @Test void shouldHandleTripOnlyHeadSignScenario() { Trip trip = TransitModelForTest.trip("TRIP").withHeadsign(DIRECTION).build(); - Collection stopTimes = List.of(EMPTY_STOPPOINT, EMPTY_STOPPOINT, EMPTY_STOPPOINT); + List stopTimes = List.of(EMPTY_STOPPOINT, EMPTY_STOPPOINT, EMPTY_STOPPOINT); TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); @@ -93,11 +94,7 @@ void shouldHandleStopsOnlyHeadSignScenario() { Trip trip = TransitModelForTest.trip("TRIP").build(); StopTime stopWithHeadsign = new StopTime(); stopWithHeadsign.setStopHeadsign(STOP_TEST_DIRECTION); - Collection stopTimes = List.of( - stopWithHeadsign, - stopWithHeadsign, - stopWithHeadsign - ); + List stopTimes = List.of(stopWithHeadsign, stopWithHeadsign, stopWithHeadsign); TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); @@ -110,11 +107,7 @@ void shouldHandleStopsEqualToTripHeadSignScenario() { Trip trip = TransitModelForTest.trip("TRIP").withHeadsign(DIRECTION).build(); StopTime stopWithHeadsign = new StopTime(); stopWithHeadsign.setStopHeadsign(DIRECTION); - Collection stopTimes = List.of( - stopWithHeadsign, - stopWithHeadsign, - stopWithHeadsign - ); + List stopTimes = List.of(stopWithHeadsign, stopWithHeadsign, stopWithHeadsign); TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); @@ -127,7 +120,7 @@ void shouldHandleDifferingTripAndStopHeadSignScenario() { Trip trip = TransitModelForTest.trip("TRIP").withHeadsign(DIRECTION).build(); StopTime stopWithHeadsign = new StopTime(); stopWithHeadsign.setStopHeadsign(STOP_TEST_DIRECTION); - Collection stopTimes = List.of(stopWithHeadsign, EMPTY_STOPPOINT, EMPTY_STOPPOINT); + List stopTimes = List.of(stopWithHeadsign, EMPTY_STOPPOINT, EMPTY_STOPPOINT); TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); @@ -141,7 +134,7 @@ void shouldHandleDifferingTripAndStopHeadSignScenario() { @Test public void testStopUpdate() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimesA = createInitialTripTimes().copyScheduledTimes(); updatedTripTimesA.updateArrivalTime(3, 190); updatedTripTimesA.updateDepartureTime(3, 190); @@ -156,7 +149,7 @@ public void testStopUpdate() { @Test public void testPassedUpdate() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimesA = createInitialTripTimes().copyScheduledTimes(); updatedTripTimesA.updateDepartureTime(0, 30); @@ -164,32 +157,6 @@ public void testPassedUpdate() { assertEquals(60, updatedTripTimesA.getArrivalTime(1)); } - @Test - public void testNegativeDwellTime() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); - - updatedTripTimesA.updateArrivalTime(1, 60); - updatedTripTimesA.updateDepartureTime(1, 59); - - var error = updatedTripTimesA.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(1, error.get().stopIndex()); - assertEquals(NEGATIVE_DWELL_TIME, error.get().code()); - } - - @Test - public void testNegativeHopTime() { - TripTimes updatedTripTimesB = createInitialTripTimes().copyOfScheduledTimes(); - - updatedTripTimesB.updateDepartureTime(6, 421); - updatedTripTimesB.updateArrivalTime(7, 420); - - var error = updatedTripTimesB.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(7, error.get().stopIndex()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); - } - /** * Test negative hop time with stop cancellations. * Scheduled: 5 at 300, 6 at 360, 7 at 420 @@ -200,7 +167,7 @@ public void testNegativeHopTime() { */ @Test public void testNegativeHopTimeWithStopCancellations() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.updateDepartureTime(5, 421); updatedTripTimes.updateArrivalTime(6, 481); @@ -208,14 +175,14 @@ public void testNegativeHopTimeWithStopCancellations() { updatedTripTimes.setCancelled(6); updatedTripTimes.updateArrivalTime(7, 420); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); - - assertTrue(updatedTripTimes.interpolateMissingTimes()); - error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + var error = assertThrows( + DataValidationException.class, + updatedTripTimes::validateNonIncreasingTimes + ); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 7 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); } /** @@ -227,7 +194,7 @@ public void testNegativeHopTimeWithStopCancellations() { */ @Test public void testPositiveHopTimeWithStopCancellationsLate() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.updateDepartureTime(5, 400); updatedTripTimes.updateArrivalTime(6, 460); @@ -235,13 +202,18 @@ public void testPositiveHopTimeWithStopCancellationsLate() { updatedTripTimes.setCancelled(6); updatedTripTimes.updateArrivalTime(7, 420); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + var error = assertThrows( + DataValidationException.class, + updatedTripTimes::validateNonIncreasingTimes + ); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 7 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); assertTrue(updatedTripTimes.interpolateMissingTimes()); - error = updatedTripTimes.validateNonIncreasingTimes(); - assertFalse(error.isPresent()); + + updatedTripTimes.validateNonIncreasingTimes(); } /** @@ -253,19 +225,23 @@ public void testPositiveHopTimeWithStopCancellationsLate() { */ @Test public void testPositiveHopTimeWithStopCancellationsEarly() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.updateDepartureTime(5, 300); updatedTripTimes.setCancelled(6); updatedTripTimes.updateArrivalTime(7, 320); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + var error = assertThrows( + DataValidationException.class, + updatedTripTimes::validateNonIncreasingTimes + ); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 7 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); assertTrue(updatedTripTimes.interpolateMissingTimes()); - error = updatedTripTimes.validateNonIncreasingTimes(); - assertFalse(error.isPresent()); + updatedTripTimes.validateNonIncreasingTimes(); } /** @@ -278,21 +254,29 @@ public void testPositiveHopTimeWithStopCancellationsEarly() { */ @Test public void testPositiveHopTimeWithTerminalCancellation() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.setCancelled(0); updatedTripTimes.setCancelled(1); updatedTripTimes.updateArrivalTime(2, 0); updatedTripTimes.updateDepartureTime(2, 10); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + var error = assertThrows( + DataValidationException.class, + updatedTripTimes::validateNonIncreasingTimes + ); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 2 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); assertFalse(updatedTripTimes.interpolateMissingTimes()); - error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + error = + assertThrows(DataValidationException.class, updatedTripTimes::validateNonIncreasingTimes); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 2 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); } /** @@ -304,14 +288,14 @@ public void testPositiveHopTimeWithTerminalCancellation() { */ @Test public void testInterpolationWithTerminalCancellation() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.setCancelled(6); updatedTripTimes.setCancelled(7); assertFalse(updatedTripTimes.interpolateMissingTimes()); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertFalse(error.isPresent()); + + updatedTripTimes.validateNonIncreasingTimes(); } /** @@ -323,7 +307,7 @@ public void testInterpolationWithTerminalCancellation() { */ @Test public void testInterpolationWithMultipleStopCancellations() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.setCancelled(1); updatedTripTimes.setCancelled(2); @@ -334,13 +318,18 @@ public void testInterpolationWithMultipleStopCancellations() { updatedTripTimes.updateArrivalTime(7, 350); updatedTripTimes.updateDepartureTime(7, 350); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + var error = assertThrows( + DataValidationException.class, + updatedTripTimes::validateNonIncreasingTimes + ); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 7 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); assertTrue(updatedTripTimes.interpolateMissingTimes()); - error = updatedTripTimes.validateNonIncreasingTimes(); - assertFalse(error.isPresent()); + + updatedTripTimes.validateNonIncreasingTimes(); } /** @@ -352,7 +341,7 @@ public void testInterpolationWithMultipleStopCancellations() { */ @Test public void testInterpolationWithMultipleStopCancellations2() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.setCancelled(1); updatedTripTimes.setCancelled(2); @@ -364,28 +353,32 @@ public void testInterpolationWithMultipleStopCancellations2() { updatedTripTimes.updateArrivalTime(7, 240); updatedTripTimes.updateDepartureTime(7, 240); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + var error = assertThrows( + DataValidationException.class, + updatedTripTimes::validateNonIncreasingTimes + ); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 3 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); assertTrue(updatedTripTimes.interpolateMissingTimes()); - error = updatedTripTimes.validateNonIncreasingTimes(); - assertFalse(error.isPresent()); + updatedTripTimes.validateNonIncreasingTimes(); } @Test public void testNonIncreasingUpdateCrossingMidnight() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimesA = createInitialTripTimes().copyScheduledTimes(); updatedTripTimesA.updateArrivalTime(0, -300); //"Yesterday" updatedTripTimesA.updateDepartureTime(0, 50); - assertTrue(updatedTripTimesA.validateNonIncreasingTimes().isEmpty()); + updatedTripTimesA.validateNonIncreasingTimes(); } @Test public void testDelay() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimesA = createInitialTripTimes().copyScheduledTimes(); updatedTripTimesA.updateDepartureDelay(0, 10); updatedTripTimesA.updateArrivalDelay(6, 13); @@ -395,14 +388,14 @@ public void testDelay() { @Test public void testCancel() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimesA = createInitialTripTimes().copyScheduledTimes(); updatedTripTimesA.cancelTrip(); assertEquals(RealTimeState.CANCELED, updatedTripTimesA.getRealTimeState()); } @Test public void testNoData() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimesA = createInitialTripTimes().copyScheduledTimes(); updatedTripTimesA.setNoData(1); assertFalse(updatedTripTimesA.isNoDataStop(0)); assertTrue(updatedTripTimesA.isNoDataStop(1)); @@ -433,46 +426,38 @@ void unknownGtfsSequence() { } @Test - public void testApply() { - Trip trip = TransitModelForTest.trip(TRIP_ID).build(); - - List stopTimes = new LinkedList<>(); - - StopTime stopTime0 = new StopTime(); - StopTime stopTime1 = new StopTime(); - StopTime stopTime2 = new StopTime(); + public void validateNegativeDwellTime() { + var expMsg = "NEGATIVE_DWELL_TIME for stop position 3 in trip Trip{F:testTripId RRtestTripId}."; + var tt = createInitialTripTimes(); + var updatedTt = tt.copyScheduledTimes(); - RegularStop stop0 = TEST_MODEL.stop(stops.get(0).getId(), 0.0, 0.0).build(); - RegularStop stop1 = TEST_MODEL.stop(stops.get(1).getId(), 0.0, 0.0).build(); - RegularStop stop2 = TEST_MODEL.stop(stops.get(2).getId(), 0.0, 0.0).build(); + updatedTt.updateArrivalTime(3, 69); + updatedTt.updateDepartureTime(3, 68); - stopTime0.setStop(stop0); - stopTime0.setDepartureTime(0); - stopTime0.setStopSequence(0); + var ex = assertThrows(DataValidationException.class, updatedTt::validateNonIncreasingTimes); + var error = (TimetableValidationError) ex.error(); - stopTime1.setStop(stop1); - stopTime1.setArrivalTime(30); - stopTime1.setDepartureTime(60); - stopTime1.setStopSequence(1); - - stopTime2.setStop(stop2); - stopTime2.setArrivalTime(90); - stopTime2.setStopSequence(2); - - stopTimes.add(stopTime0); - stopTimes.add(stopTime1); - stopTimes.add(stopTime2); + assertEquals(3, error.stopIndex()); + assertEquals(NEGATIVE_DWELL_TIME, error.code()); + assertEquals(expMsg, error.message()); + assertEquals(expMsg, ex.getMessage()); + } - TripTimes differingTripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); + @Test + public void validateNegativeHopTime() { + var expMsg = "NEGATIVE_HOP_TIME for stop position 2 in trip Trip{F:testTripId RRtestTripId}."; + var tt = createInitialTripTimes(); + var updatedTt = tt.copyScheduledTimes(); - TripTimes updatedTripTimesA = differingTripTimes.copyOfScheduledTimes(); + updatedTt.updateDepartureTime(1, 100); + updatedTt.updateArrivalTime(2, 99); - updatedTripTimesA.updateArrivalTime(1, 89); - updatedTripTimesA.updateDepartureTime(1, 98); + var ex = assertThrows(DataValidationException.class, updatedTt::validateNonIncreasingTimes); + var error = (TimetableValidationError) ex.error(); - var validationResult = updatedTripTimesA.validateNonIncreasingTimes(); - assertTrue(validationResult.isPresent()); - assertEquals(2, validationResult.get().stopIndex()); - assertEquals(NEGATIVE_DWELL_TIME, validationResult.get().code()); + assertEquals(2, error.stopIndex()); + assertEquals(NEGATIVE_HOP_TIME, error.code()); + assertEquals(expMsg, error.message()); + assertEquals(expMsg, ex.getMessage()); } } diff --git a/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java b/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java new file mode 100644 index 00000000000..ed6c5c70f92 --- /dev/null +++ b/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java @@ -0,0 +1,176 @@ +package org.opentripplanner.transit.model.timetable; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.transit.model._data.TransitModelForTest.id; + +import java.util.BitSet; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.time.TimeUtils; +import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.basic.Accessibility; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +class ScheduledTripTimesTest { + + private static final Trip TRIP = TransitModelForTest.trip("Trip-1").build(); + + private static final List STOP_IDS = List.of(id("A"), id("B"), id("C")); + private static final int SERVICE_CODE = 5; + private static final BitSet TIMEPOINTS = new BitSet(3); + private static final int T10_00 = TimeUtils.time("10:00"); + private static final int T10_01 = T10_00 + 60; + private static final int T11_00 = TimeUtils.time("11:00"); + private static final int T11_02 = T11_00 + 120; + private static final int T12_00 = TimeUtils.time("12:00"); + private static final int T12_03 = T12_00 + 180; + public static final int STOP_POS_0 = 0; + public static final int STOP_POS_1 = 1; + public static final int STOP_POS_2 = 2; + + static { + TIMEPOINTS.set(1); + } + + private final ScheduledTripTimes subject = ScheduledTripTimes + .of() + .withArrivalTimes("10:00 11:00 12:00") + .withDepartureTimes("10:01 11:02 12:03") + .withServiceCode(SERVICE_CODE) + .withTrip(TRIP) + .withTimepoints(TIMEPOINTS) + .build(); + + @Test + void getServiceCode() { + assertEquals(SERVICE_CODE, subject.getServiceCode()); + } + + @Test + void getScheduledArrivalTime() { + assertEquals(T10_00, subject.getScheduledArrivalTime(STOP_POS_0)); + assertEquals(T11_00, subject.getScheduledArrivalTime(STOP_POS_1)); + assertEquals(T12_00, subject.getScheduledArrivalTime(STOP_POS_2)); + } + + @Test + void getArrivalTime() { + assertEquals(T10_00, subject.getArrivalTime(STOP_POS_0)); + assertEquals(T11_00, subject.getArrivalTime(STOP_POS_1)); + assertEquals(T12_00, subject.getArrivalTime(STOP_POS_2)); + } + + @Test + void getArrivalDelay() { + assertEquals(0, subject.getArrivalDelay(STOP_POS_0)); + assertEquals(0, subject.getArrivalDelay(STOP_POS_1)); + assertEquals(0, subject.getArrivalDelay(STOP_POS_2)); + } + + @Test + void getScheduledDepartureTime() { + assertEquals(T10_01, subject.getScheduledDepartureTime(STOP_POS_0)); + assertEquals(T11_02, subject.getScheduledDepartureTime(STOP_POS_1)); + assertEquals(T12_03, subject.getScheduledDepartureTime(STOP_POS_2)); + } + + @Test + void getDepartureTime() { + assertEquals(T10_01, subject.getDepartureTime(STOP_POS_0)); + assertEquals(T11_02, subject.getDepartureTime(STOP_POS_1)); + assertEquals(T12_03, subject.getDepartureTime(STOP_POS_2)); + } + + @Test + void getDepartureDelay() { + assertEquals(0, subject.getDepartureDelay(STOP_POS_0)); + assertEquals(0, subject.getDepartureDelay(STOP_POS_1)); + assertEquals(0, subject.getDepartureDelay(STOP_POS_2)); + } + + @Test + void isTimepoint() { + assertFalse(subject.isTimepoint(STOP_POS_0)); + assertTrue(subject.isTimepoint(STOP_POS_1)); + assertFalse(subject.isTimepoint(STOP_POS_2)); + } + + @Test + void validateLastArrivalTimeIsNotMoreThan20DaysAfterFirstDepartureTime() { + var ex = assertThrows( + IllegalArgumentException.class, + () -> + ScheduledTripTimes + .of() + .withDepartureTimes("10:00 12:00 10:00:01+20d") + .withServiceCode(SERVICE_CODE) + .withTrip(TRIP) + .build() + ); + assertEquals("The value is not in range[-43200, 1728000]: 1728001", ex.getMessage()); + } + + @Test + void getTrip() { + assertEquals(TRIP, subject.getTrip()); + } + + @Test + void sortIndex() { + assertEquals(T10_01, subject.sortIndex()); + } + + @Test + void isScheduled() { + assertTrue(subject.isScheduled()); + } + + @Test + void isCanceledOrDeleted() { + assertFalse(subject.isCanceledOrDeleted()); + } + + @Test + void isCanceled() { + assertFalse(subject.isCanceled()); + } + + @Test + void isDeleted() { + assertFalse(subject.isDeleted()); + } + + @Test + void getRealTimeState() { + assertEquals(RealTimeState.SCHEDULED, subject.getRealTimeState()); + } + + @Test + void getNumStops() { + assertEquals(3, subject.getNumStops()); + } + + @Test + void getWheelchairAccessibility() { + assertEquals(Accessibility.NO_INFORMATION, subject.getWheelchairAccessibility()); + } + + @Test + void getOccupancyStatus() { + assertEquals(OccupancyStatus.NO_DATA_AVAILABLE, subject.getOccupancyStatus(0)); + } + + @Test + void copyArrivalTimes() { + assertArrayEquals(new int[] { T10_00, T11_00, T12_00 }, subject.copyArrivalTimes()); + } + + @Test + void copyDepartureTimes() { + assertArrayEquals(new int[] { T10_01, T11_02, T12_03 }, subject.copyDepartureTimes()); + } +} diff --git a/src/test/java/org/opentripplanner/transit/model/timetable/TimetableValidationErrorTest.java b/src/test/java/org/opentripplanner/transit/model/timetable/TimetableValidationErrorTest.java new file mode 100644 index 00000000000..7dd67665e92 --- /dev/null +++ b/src/test/java/org/opentripplanner/transit/model/timetable/TimetableValidationErrorTest.java @@ -0,0 +1,24 @@ +package org.opentripplanner.transit.model.timetable; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.basic.TransitMode; + +class TimetableValidationErrorTest { + + private TimetableValidationError subject = new TimetableValidationError( + TimetableValidationError.ErrorCode.NEGATIVE_HOP_TIME, + 3, + TransitModelForTest.trip("A").withMode(TransitMode.BUS).withShortName("Line A").build() + ); + + @Test + void message() { + assertEquals( + "NEGATIVE_HOP_TIME for stop position 3 in trip Trip{F:A Line A}.", + subject.message() + ); + } +} diff --git a/test/performance/norway/build-config.json b/test/performance/norway/build-config.json index 5fa7f03ed58..83de34a027b 100644 --- a/test/performance/norway/build-config.json +++ b/test/performance/norway/build-config.json @@ -6,7 +6,6 @@ "embedRouterConfig": true, "areaVisibility": true, "platformEntriesLinking": true, - "matchBusRoutesToStreets": false, "staticParkAndRide": true, "staticBikeParkAndRide": true, "maxDataImportIssuesPerFile": 1000,