diff --git a/.github/workflows/cibuild.yml b/.github/workflows/cibuild.yml
index 0c085b12678..5cae15849ef 100644
--- a/.github/workflows/cibuild.yml
+++ b/.github/workflows/cibuild.yml
@@ -166,9 +166,9 @@ jobs:
if [ ${{ github.ref }} = 'refs/heads/master' ];
then
- mike deploy --branch $LOCAL_BRANCH --prefix en --title=$MASTER_BRANCH_VERSION --update-aliases v$MASTER_BRANCH_VERSION latest
+ mike deploy --branch $LOCAL_BRANCH --deploy-prefix en --title=$MASTER_BRANCH_VERSION --update-aliases v$MASTER_BRANCH_VERSION latest
else
- mike deploy --branch $LOCAL_BRANCH --prefix en dev-2.x
+ mike deploy --branch $LOCAL_BRANCH --deploy-prefix en dev-2.x
fi
# commit and push the GraphQL documentation if the schema file is newer than the
@@ -218,7 +218,7 @@ jobs:
run: mvn --batch-mode compile -DskipTests -P prettierSkip
container-image:
- if: github.repository_owner == 'opentripplanner' && github.event_name == 'push' && github.ref == 'refs/heads/dev-2.x'
+ if: github.repository_owner == 'opentripplanner' && github.event_name == 'push' && (github.ref == 'refs/heads/dev-2.x' || github.ref == 'refs/heads/master')
runs-on: ubuntu-latest
needs:
- build-linux
@@ -244,7 +244,14 @@ jobs:
version_with_snapshot=`mvn -q help:evaluate -Dexpression=project.version -q -DforceStdout`
version=${version_with_snapshot/-SNAPSHOT/}
- image_date=`date +%Y-%m-%dT%H-%M`
- image_version="${version}_${image_date}"
+
+ image_version=${version}
+
+ ## if the Maven version contains SNAPSHOT, then add date to tag
+ if [[ $version_with_snapshot == *"SNAPSHOT"* ]]; then
+ image_date=`date +%Y-%m-%dT%H-%M`
+ image_version="${version}_${image_date}"
+ echo "Maven version ${version_with_snapshot} contains SNAPSHOT, adding date to container image tag"
+ fi
mvn --batch-mode -P prettierSkip compile com.google.cloud.tools:jib-maven-plugin:build -Djib.to.tags=latest,$image_version
diff --git a/.gitignore b/.gitignore
index 3c2c29d911e..b98d0263a70 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,6 +44,8 @@ src/ext/resources/reportapi/report.csv
streetGraph.obj
graph.obj
+# IntelliJ creates these pid files when you attach the debugger to tests
+.attach_pid*
smoke-tests/*.jar
smoke-tests/**/*.obj
diff --git a/doc-templates/Configuration.md b/doc-templates/Configuration.md
index ebbe5501b61..003fe28d794 100644
--- a/doc-templates/Configuration.md
+++ b/doc-templates/Configuration.md
@@ -146,7 +146,7 @@ text inserted is valid JSON (starts with `{` and ends with `}`).
Variable substitution is performed on configuration file after the include file directive; Hence
variable substitution is also performed on the text in the injected file.
-Here is an example including variable substitution, assuming version 2.3.0 of OTP:
+Here is an example including variable substitution, assuming version 2.4.0 of OTP:
```JSON
// build-config.json
@@ -170,7 +170,7 @@ The result will look like this:
{
"transitFeeds": [
{
- "source": "netex-v2.3.0.obj"
+ "source": "netex-v2.4.0.obj"
}
]
}
diff --git a/docs/Basic-Tutorial.md b/docs/Basic-Tutorial.md
index 33473c5d526..788c85aaad0 100644
--- a/docs/Basic-Tutorial.md
+++ b/docs/Basic-Tutorial.md
@@ -18,9 +18,9 @@ JAR containing all other libraries needed for OTP to work, and is available from
repository. You will be able to go
to [the OTP directory at Maven Central](https://repo1.maven.org/maven2/org/opentripplanner/otp/),
navigate to
-the [directory of releases](https://repo1.maven.org/maven2/org/opentripplanner/otp/2.3.0/),
+the [directory of releases](https://repo1.maven.org/maven2/org/opentripplanner/otp/2.4.0/),
and download
-the [file with `shaded.jar` suffix](https://repo1.maven.org/maven2/org/opentripplanner/otp/2.3.0/otp-2.3.0-shaded.jar)
+the [file with `shaded.jar` suffix](https://repo1.maven.org/maven2/org/opentripplanner/otp/2.4.0/otp-2.4.0-shaded.jar)
.
You may also want to get your own copy of the OTP source code
@@ -127,7 +127,7 @@ below and in other tutorials.
The simplest way to use OTP is to build a graph in a single step and start a server immediately,
without saving it to disk. The command to do so is:
- $ java -Xmx2G -jar otp-2.3.0-shaded.jar --build --serve /home/username/otp
+ $ java -Xmx2G -jar otp-2.4.0-shaded.jar --build --serve /home/username/otp
where `/home/username/otp` should be the directory where you put your configuration and input files.
@@ -151,13 +151,13 @@ build a graph from street and transit data then save it to a file using the `--b
command line parameters together. If for example your current working directory (`.`) contains the
input files and the OTP JAR file, you can use this command:
- $ java -Xmx2G -jar otp-2.3.0-shaded.jar --build --save .
+ $ java -Xmx2G -jar otp-2.4.0-shaded.jar --build --save .
This will produce a file called `graph.obj` in the same directory as the inputs. The server can then
be started later using the `--load` parameter, and will read this file instead of building the graph
from scratch:
- $ java -Xmx2G -jar otp-2.3.0-shaded.jar --load .
+ $ java -Xmx2G -jar otp-2.4.0-shaded.jar --load .
Another reason to perform these two phases separately is that the building process loads the entire
GTFS and OSM data sets into memory, so can require significantly more memory than just running a
@@ -174,16 +174,16 @@ graph once, and then layer transit data on top of the streets to make the final
Again assuming the input files and OTP JAR file are in the current working directory, you can build
a street graph with OSM and elevation data only (ignoring transit input files) with this command:
- $ java -Xmx2G -jar otp-2.3.0-shaded.jar --buildStreet .
+ $ java -Xmx2G -jar otp-2.4.0-shaded.jar --buildStreet .
Then, to build a graph layering transit data on top of the saved street graph (built using the
previous command):
- $ java -Xmx2G -jar otp-2.3.0-shaded.jar --loadStreet --save .
+ $ java -Xmx2G -jar otp-2.4.0-shaded.jar --loadStreet --save .
Finally, the server can be started using the `--load` parameter:
- $ java -Xmx2G -jar otp-2.3.0-shaded.jar --load .
+ $ java -Xmx2G -jar otp-2.4.0-shaded.jar --load .
## Command Line Switches
diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md
index 9c8eb5ad5c0..d382d07f673 100644
--- a/docs/BuildConfiguration.md
+++ b/docs/BuildConfiguration.md
@@ -20,8 +20,6 @@ Sections follow that describe particular settings in more depth.
| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since |
|--------------------------------------------------------------------------|:-----------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|-----------------------------------|:-----:|
| [areaVisibility](#areaVisibility) | `boolean` | Perform visibility calculations. | *Optional* | `false` | 1.5 |
-| banDiscouragedBiking | `boolean` | Should biking be allowed on OSM ways tagged with `bicycle=discouraged` | *Optional* | `false` | 2.0 |
-| banDiscouragedWalking | `boolean` | Should walking be allowed on OSM ways tagged with `foot=discouraged` | *Optional* | `false` | 2.0 |
| [buildReportDir](#buildReportDir) | `uri` | URI to the directory where the graph build report should be written to. | *Optional* | | 2.0 |
| [configVersion](#configVersion) | `string` | Deployment version of the *build-config.json*. | *Optional* | | 2.1 |
| [dataImportReport](#dataImportReport) | `boolean` | Generate nice HTML report of Graph errors/warnings | *Optional* | `false` | 2.0 |
diff --git a/docs/Changelog.md b/docs/Changelog.md
index 9b91d562dea..5240802ac9e 100644
--- a/docs/Changelog.md
+++ b/docs/Changelog.md
@@ -3,28 +3,45 @@
The changelog lists most feature changes between each release. The list is automatically created
based on merged pull requests. Search GitHub issues and pull requests for smaller issues.
-## 2.4.0 (in progress)
+## 2.5.0 (under development)
+
+- Gracefully handle nullable fields in TransitAlert [#5349](https://github.com/opentripplanner/OpenTripPlanner/pull/5349)
+- Remove transit with higher cost than best on-street itinerary filter [#5222](https://github.com/opentripplanner/OpenTripPlanner/pull/5222)
+- Remove banDiscouragedCycling and banDiscouragedWalking [#5341](https://github.com/opentripplanner/OpenTripPlanner/pull/5341)
+- Fix rental scooter access [#5361](https://github.com/opentripplanner/OpenTripPlanner/pull/5361)
+- De-duplicate stops returned by `stopsByRadius` [#5366](https://github.com/opentripplanner/OpenTripPlanner/pull/5366)
+- Fix value mapping for bikesAllowed in GTFS GraphQL API [#5368](https://github.com/opentripplanner/OpenTripPlanner/pull/5368)
+[](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE)
+
+## 2.4.0 (2023-09-13)
+
+### Notable Changes
+
+- Improved support for Fares V2 [#4917](https://github.com/opentripplanner/OpenTripPlanner/pull/4917) [#5227](https://github.com/opentripplanner/OpenTripPlanner/pull/5227)
+- Improved error and timeout handling [#5047](https://github.com/opentripplanner/OpenTripPlanner/pull/5047) [#5092](https://github.com/opentripplanner/OpenTripPlanner/pull/5092) [#5121](https://github.com/opentripplanner/OpenTripPlanner/pull/5121) [#5114](https://github.com/opentripplanner/OpenTripPlanner/pull/5114) [#5133](https://github.com/opentripplanner/OpenTripPlanner/pull/5133) [#5130](https://github.com/opentripplanner/OpenTripPlanner/pull/5130) [#5192](https://github.com/opentripplanner/OpenTripPlanner/pull/5192)
+- Enable GTFS GraphQL API by default, remove the word "legacy" from its name and documentation [#5202](https://github.com/opentripplanner/OpenTripPlanner/pull/5202)
+- Access and egress penalty on time and cost [#5180](https://github.com/opentripplanner/OpenTripPlanner/pull/5180)
+- Improve support for GBFS geofencing zones [#5201](https://github.com/opentripplanner/OpenTripPlanner/pull/5201)
+- Reduce memory consumption by 5-8% [#5223](https://github.com/opentripplanner/OpenTripPlanner/pull/5223)
+- Stop count limit for access/egress routing and new accessEgress configuration object [#5214](https://github.com/opentripplanner/OpenTripPlanner/pull/5214)
+
+### Detailed changes by Pull Request
- Generate static documentation for GTFS GraphQL API [#5069](https://github.com/opentripplanner/OpenTripPlanner/pull/5069)
- Create a valid area even when it has too many vertices [#5019](https://github.com/opentripplanner/OpenTripPlanner/pull/5019)
- Constant speed street routing [#5057](https://github.com/opentripplanner/OpenTripPlanner/pull/5057)
- Remove dead build configuration parameter (extraEdgesStopPlatformLink) [#5080](https://github.com/opentripplanner/OpenTripPlanner/pull/5080)
- Configure HTTP WebServer Transaction timeouts [#5047](https://github.com/opentripplanner/OpenTripPlanner/pull/5047)
-- Add Fares v2 to GraphQL API [#4917](https://github.com/opentripplanner/OpenTripPlanner/pull/4917)
- Improve Graph updaters shutdown [#5092](https://github.com/opentripplanner/OpenTripPlanner/pull/5092)
-- Add deduplicated "stop clusters" to geocoding sandbox API [#5084](https://github.com/opentripplanner/OpenTripPlanner/pull/5084)
- Fix flex timeshift [#5063](https://github.com/opentripplanner/OpenTripPlanner/pull/5063)
- Merge norway traversal calculator into default [#5106](https://github.com/opentripplanner/OpenTripPlanner/pull/5106)
- Use correct GTFS sequence number in vehicle position matcher [#5090](https://github.com/opentripplanner/OpenTripPlanner/pull/5090)
-- Fix SIRI ET PubSub updater shutdown [#5104](https://github.com/opentripplanner/OpenTripPlanner/pull/5104)
- OSM: Break out of processing a malformed level map [#5096](https://github.com/opentripplanner/OpenTripPlanner/pull/5096)
-- Create unique SIRI-ET PubSub subscription [#5118](https://github.com/opentripplanner/OpenTripPlanner/pull/5118)
- Handle JsonParseException [#5121](https://github.com/opentripplanner/OpenTripPlanner/pull/5121)
-- Add modes to geocoding [#5115](https://github.com/opentripplanner/OpenTripPlanner/pull/5115)
- Do not enforce API processing timeout for parallel routing [#5114](https://github.com/opentripplanner/OpenTripPlanner/pull/5114)
- Rename bikeRental options to vehicleRental [#5089](https://github.com/opentripplanner/OpenTripPlanner/pull/5089)
- Fare sandbox cleanup, remove MultipleFareService [#5100](https://github.com/opentripplanner/OpenTripPlanner/pull/5100)
-- Add validation of NeTEX timetabled passing times [#5081](https://github.com/opentripplanner/OpenTripPlanner/pull/5081)
+- Add validation of NeTEx timetabled passing times [#5081](https://github.com/opentripplanner/OpenTripPlanner/pull/5081)
- Return WALKING_BETTER_THAN_TRANSIT only on a fully walking leg [#5091](https://github.com/opentripplanner/OpenTripPlanner/pull/5091)
- Handle CoercingParseValueException [#5133](https://github.com/opentripplanner/OpenTripPlanner/pull/5133)
- Remove broken Jersey tracing configuration [#5142](https://github.com/opentripplanner/OpenTripPlanner/pull/5142)
@@ -33,58 +50,40 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle
- 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)
- Remove San Francisco and vehicle rental fare calculators [#5145](https://github.com/opentripplanner/OpenTripPlanner/pull/5145)
-- Make update frequency a Duration [#5152](https://github.com/opentripplanner/OpenTripPlanner/pull/5152)
- 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)
- Fix error in flex validation [#5161](https://github.com/opentripplanner/OpenTripPlanner/pull/5161)
- Check service dates instead of service ids for block id transfers [#5162](https://github.com/opentripplanner/OpenTripPlanner/pull/5162)
-- Improve updater log messages [#5168](https://github.com/opentripplanner/OpenTripPlanner/pull/5168)
- Add support for mapping NeTEx operating day in operating period [#5167](https://github.com/opentripplanner/OpenTripPlanner/pull/5167)
-- Relax validity check on flex trip with null duration [#5169](https://github.com/opentripplanner/OpenTripPlanner/pull/5169)
-- Log unexpected vehicle type in GBFS update [#5175](https://github.com/opentripplanner/OpenTripPlanner/pull/5175)
- Validate to/from in routing request [#5164](https://github.com/opentripplanner/OpenTripPlanner/pull/5164)
- Changing default value for earlyStartSec [#5165](https://github.com/opentripplanner/OpenTripPlanner/pull/5165)
- Add GTFS stop sequence to GTFS GraphQL API [#5153](https://github.com/opentripplanner/OpenTripPlanner/pull/5153)
- Remove walk leg in a stay seated transfer [#5135](https://github.com/opentripplanner/OpenTripPlanner/pull/5135)
- Validate distinct from/to temporary vertices [#5181](https://github.com/opentripplanner/OpenTripPlanner/pull/5181)
-- Add support for taxi mode [#5183](https://github.com/opentripplanner/OpenTripPlanner/pull/5183)
+- Add support for NeTEx taxi mode [#5183](https://github.com/opentripplanner/OpenTripPlanner/pull/5183)
- Fix bike triangle in Transmodel API [#5179](https://github.com/opentripplanner/OpenTripPlanner/pull/5179)
- Bug fixes in stop area relation processing [#5166](https://github.com/opentripplanner/OpenTripPlanner/pull/5166)
-- Enable GTFS GraphQL API by default, remove the word "legacy" from its documentation [#5202](https://github.com/opentripplanner/OpenTripPlanner/pull/5202)
- Allow underscores in GTFS feed IDs [#5191](https://github.com/opentripplanner/OpenTripPlanner/pull/5191)
- Area vertex linking improvements [#5209](https://github.com/opentripplanner/OpenTripPlanner/pull/5209)
- Allow multiple FlexibleAreas in a FlexibleStopPlace [#4922](https://github.com/opentripplanner/OpenTripPlanner/pull/4922)
-- Preemptively make a GraphQL FareProduct an interface [#5207](https://github.com/opentripplanner/OpenTripPlanner/pull/5207)
-- Access and egress penalty on time and cost [#5180](https://github.com/opentripplanner/OpenTripPlanner/pull/5180)
- Prevent NPE in vehicle position matching [#5212](https://github.com/opentripplanner/OpenTripPlanner/pull/5212)
- Empty stop_headsign will fall back to trip_headsign [#5205](https://github.com/opentripplanner/OpenTripPlanner/pull/5205)
-- Refactor HTTP client [#5213](https://github.com/opentripplanner/OpenTripPlanner/pull/5213)
-- Add escalator reluctance parameter [#5046](https://github.com/opentripplanner/OpenTripPlanner/pull/5046)
-- Handle reverse search when starting in no-drop-off zone [#5201](https://github.com/opentripplanner/OpenTripPlanner/pull/5201)
-- Refactor edge constructors [#5221](https://github.com/opentripplanner/OpenTripPlanner/pull/5221)
-- Upgrade to Apache HTTP Client 5 [#5228](https://github.com/opentripplanner/OpenTripPlanner/pull/5228)
-- Deduplicate vertex labels to save memory [#5223](https://github.com/opentripplanner/OpenTripPlanner/pull/5223)
-- Fix duplicate publishing of speed test data [#5237](https://github.com/opentripplanner/OpenTripPlanner/pull/5237)
+- Treat escalator differently from stairs, add escalator reluctance [#5046](https://github.com/opentripplanner/OpenTripPlanner/pull/5046)
- Flex build time and memory optimization for large zones [#5233](https://github.com/opentripplanner/OpenTripPlanner/pull/5233)
- Fix pathway traversal time calculation when none is supplied [#5242](https://github.com/opentripplanner/OpenTripPlanner/pull/5242)
- Add check for null value of serviceCodesRunning in TripPatternForDateMapper [#5239](https://github.com/opentripplanner/OpenTripPlanner/pull/5239)
- Improve error handling in TransmodelGraph [#5192](https://github.com/opentripplanner/OpenTripPlanner/pull/5192)
-- Fix SIRI SX retry logic [#5262](https://github.com/opentripplanner/OpenTripPlanner/pull/5262)
- Fix filtering by submode [#5261](https://github.com/opentripplanner/OpenTripPlanner/pull/5261)
- Add leg.headsign to GTFS GraphQL API [#5290](https://github.com/opentripplanner/OpenTripPlanner/pull/5290)
- Return client error for invalid Transmodel query JSON format [#5277](https://github.com/opentripplanner/OpenTripPlanner/pull/5277)
- Validate missing intermediate location in via requests [#5253](https://github.com/opentripplanner/OpenTripPlanner/pull/5253)
- Support Fares v2 FareMedium and update spec implementation [#5227](https://github.com/opentripplanner/OpenTripPlanner/pull/5227)
-- Replace DoubleFunction with linear function [#5267](https://github.com/opentripplanner/OpenTripPlanner/pull/5267)
- Improve walk step narrative for entering/exiting stations and signposted pathways [#5285](https://github.com/opentripplanner/OpenTripPlanner/pull/5285)
- Fix walk board cost comparison and add escalatorReluctance to hash [#5310](https://github.com/opentripplanner/OpenTripPlanner/pull/5310)
-- Stop count limit for access/egress routing and new accessEgress configuration object [#5214](https://github.com/opentripplanner/OpenTripPlanner/pull/5214)
- Remove pathway id from REST API [#5303](https://github.com/opentripplanner/OpenTripPlanner/pull/5303)
- Remove Winkki street note updater [#5305](https://github.com/opentripplanner/OpenTripPlanner/pull/5305)
- Extend stop area relation linking to include bus stop and platform nodes [#5319](https://github.com/opentripplanner/OpenTripPlanner/pull/5319)
-- Document in-station navigation [#5321](https://github.com/opentripplanner/OpenTripPlanner/pull/5321)
- Add access/egress penalty transmodel api [#5268](https://github.com/opentripplanner/OpenTripPlanner/pull/5268)
-[](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE)
## 2.3.0 (2023-04-24)
diff --git a/docs/Configuration.md b/docs/Configuration.md
index 4a36c764c19..5a33c70fd43 100644
--- a/docs/Configuration.md
+++ b/docs/Configuration.md
@@ -173,7 +173,7 @@ text inserted is valid JSON (starts with `{` and ends with `}`).
Variable substitution is performed on configuration file after the include file directive; Hence
variable substitution is also performed on the text in the injected file.
-Here is an example including variable substitution, assuming version 2.3.0 of OTP:
+Here is an example including variable substitution, assuming version 2.4.0 of OTP:
```JSON
// build-config.json
@@ -197,7 +197,7 @@ The result will look like this:
{
"transitFeeds": [
{
- "source": "netex-v2.3.0.obj"
+ "source": "netex-v2.4.0.obj"
}
]
}
diff --git a/docs/Deployments.md b/docs/Deployments.md
index 12ce8cdefad..f2946a9f0ba 100644
--- a/docs/Deployments.md
+++ b/docs/Deployments.md
@@ -44,6 +44,7 @@ The following are known deployments of OTP in a government- or agency-sponsored
the [OneBusAway native apps](http://onebusaway.org/) in the Puget Sound region. Technical details
are [here](https://github.com/OneBusAway/onebusaway-android/blob/master/SYSTEM_ARCHITECTURE.md#add-trip-planning-andor-bike-share-optional)
.
+* [**Ride Metro Houston**](https://planyourtrip.ridemetro.org/)
* **Tampa, Florida** Hillsoborough Area Regional Transit uses an OpenTripPlanner server to power the
trip planning feature of the [OneBusAway native apps](http://onebusaway.org/) in their region.
Technical details
diff --git a/docs/Getting-OTP.md b/docs/Getting-OTP.md
index 35aeef48f65..bafb5fc61c0 100644
--- a/docs/Getting-OTP.md
+++ b/docs/Getting-OTP.md
@@ -9,8 +9,8 @@ the [release pages on GitHub](https://github.com/opentripplanner/OpenTripPlanner
or [the OTP directory at Maven Central](https://repo1.maven.org/maven2/org/opentripplanner/otp/),
navigate to the highest version number, and download the file whose name ends with `shaded.jar`.
-Note that version numbers like `v2.1.0-rc1` or `v2.3.0-SNAPSHOT` refer to development builds _
-before_ the release version `v2.3.0`. The existence of a build `vX.Y.Z-SNAPSHOT` does not mean
+Note that version numbers like `v2.1.0-rc1` or `v2.4.0-SNAPSHOT` refer to development builds _
+before_ the release version `v2.4.0`. The existence of a build `vX.Y.Z-SNAPSHOT` does not mean
that `vX.Y.Z` has been released yet.
We use the [Github Actions CI system](https://github.com/opentripplanner/OpenTripPlanner/actions) to
@@ -87,7 +87,7 @@ For example, you could do the following:
```bash
cd OpenTripPlanner
-git checkout v2.3.0
+git checkout v2.4.0
git clean -df
mvn clean package -DskipTests
```
@@ -110,8 +110,8 @@ file) to the Maven repository, from which it can be automatically included in ot
This repository is machine-readable (by Maven or other build systems) and also provides human
readable directory listings via HTTP. You can fetch an OTP JAR from this repository by constructing
-the proper URL for the release you want. For example, release 2.3.0 will be found
-at `https://repo1.maven.org/maven2/org/opentripplanner/otp/2.3.0/otp-2.3.0-shaded.jar`.
+the proper URL for the release you want. For example, release 2.4.0 will be found
+at `https://repo1.maven.org/maven2/org/opentripplanner/otp/2.4.0/otp-2.4.0-shaded.jar`.
To make use of OTP in another Maven project, you must specify it as a dependency in that
project's `pom.xml`:
@@ -120,6 +120,6 @@ project's `pom.xml`:
org.opentripplannerotp
- 2.3.0
+ 2.4.0
```
diff --git a/docs/GraphQL-Tutorial.md b/docs/GraphQL-Tutorial.md
index 73fba7d743e..e70294fcebd 100644
--- a/docs/GraphQL-Tutorial.md
+++ b/docs/GraphQL-Tutorial.md
@@ -41,6 +41,7 @@ GraphQL query in the left hand panel of the page:
name
}
mode
+ bikesAllowed
}
}
```
diff --git a/docs/ReleaseChecklist.md b/docs/ReleaseChecklist.md
index 372222d104f..6a46c0074cb 100644
--- a/docs/ReleaseChecklist.md
+++ b/docs/ReleaseChecklist.md
@@ -5,16 +5,16 @@ the actions taken by the Maven release plugin. Based on past experience, the Mav
can fail at various points in the process leaving the repo in a confusing state. Taking each action
manually is more tedious, but keeps eyes on each step and is less prone to failure.
-* Make sure the documentation is up to date
- * Check all links and references to the release and update to the target release version. Search
- all files for with a regular expression: `2\.[012]\.0` and replace if appropriate with the new
- version.
* Check that your local copy of the dev branch is up to date with no uncommitted changes
* `git status`
* `git checkout dev-2.x`
* `git clean -df`
* `git pull`
-* Verify that all dependencies in the POM are non-SNAPSHOT versions (e.g. with `grep`)
+* Make sure the documentation is up to date
+ * Check all links and references to the release and update to the target release version. Search
+ all files for with a regular expression: `2\.[012]\.0` and replace if appropriate with the new
+ version.
+ * In `docs/index.md` replace what is the latest version and add a new line for the previous one
* Update `docs/Changelog.md`
* Lines should have been added or updated as each pull request was merged
* If you suspect any changes are not reflected in the Changelog, review the commit log and add
@@ -27,8 +27,8 @@ manually is more tedious, but keeps eyes on each step and is less prone to failu
* This tells the GH Action that pushes the documentation on master what the name of the
current version is.
For version 2.3.0 Leonard has already done it: [Example commit](https://github.com/opentripplanner/OpenTripPlanner/commit/3cb061ab1e4253c3977a5d08fa5abab1b0baefd7)
-* Check [on GH Actions](https://github.com/opentripplanner/OpenTripPlanner/actions/workflows/) that
- the build is currently passing
+* Verify that all dependencies in the POM are non-SNAPSHOT versions (e.g. with `grep`)
+* Check [on GH Actions](https://github.com/opentripplanner/OpenTripPlanner/actions/workflows/) that the build is currently passing
* Switch to the HEAD of master branch, and ensure it's up to date with no uncommitted changes
* `git checkout master`
* `git status`
@@ -48,18 +48,6 @@ manually is more tedious, but keeps eyes on each step and is less prone to failu
* You can also use the `package` goal instead of the `install` goal to avoid signing if you
don't have the GPG certificate installed.
* All tests should pass
- * Run `MAVEN_OPTS="--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED" mvn com.webcohesion.enunciate:enunciate-maven-plugin:docs`
- * This build will also create Enunciate API docs and Javadoc with the correct non-snapshot
- version number
-* Deploy the documentation to AWS S3
- * You have to do this right after the test release build to ensure the right version number in
- the docs
- * You will need AWSCLI tools (`sudo pip install -U awscli`)
- * You will need AWS credentials with write access to the bucket `s3://dev.opentripplanner.org`
- * `aws s3 cp --recursive target/site/apidocs s3://dev.opentripplanner.org/javadoc/x.y.z --acl public-read`
- * `aws s3 cp --recursive target/site/enunciate/apidocs s3://dev.opentripplanner.org/apidoc/x.y.z --acl public-read`
- * Check that docs are readable and show the correct version via
- the [development documentation landing page](http://dev.opentripplanner.org).
* Finally, if everything looks good, tag and push this release to make it official
* `git tag -a vX.Y.Z -m "release X.Y.Z"`
* `git push origin vX.Y.Z`
diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md
index 83f6d2ddad8..f814753df4a 100644
--- a/docs/RouteRequest.md
+++ b/docs/RouteRequest.md
@@ -13,131 +13,132 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe
-| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since |
-|------------------------------------------------------------------------------------------------------|:----------------------:|------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|------------------|:-----:|
-| [alightSlack](#rd_alightSlack) | `duration` | The minimum extra time after exiting a public transport vehicle. | *Optional* | `"PT0S"` | 2.0 |
-| arriveBy | `boolean` | Whether the trip should depart or arrive at the specified date and time. | *Optional* | `false` | 2.0 |
-| [bikeBoardCost](#rd_bikeBoardCost) | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. | *Optional* | `600` | 2.0 |
-| bikeParkCost | `integer` | Cost to park a bike. | *Optional* | `120` | 2.0 |
-| bikeParkTime | `integer` | Time to park a bike. | *Optional* | `60` | 2.0 |
-| bikeReluctance | `double` | A multiplier for how bad biking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 |
-| bikeSpeed | `double` | Max bike speed along streets, in meters per second | *Optional* | `5.0` | 2.0 |
-| bikeStairsReluctance | `double` | How bad is it to walk the bicycle up/down a flight of stairs compared to taking a detour. | *Optional* | `10.0` | 2.3 |
-| bikeSwitchCost | `integer` | The cost of the user fetching their bike and parking it again. | *Optional* | `0` | 2.0 |
-| bikeSwitchTime | `integer` | The time it takes the user to fetch their bike and park it again in seconds. | *Optional* | `0` | 2.0 |
-| bikeTriangleSafetyFactor | `double` | For bike triangle routing, how much safety matters (range 0-1). | *Optional* | `0.0` | 2.0 |
-| bikeTriangleSlopeFactor | `double` | For bike triangle routing, how much slope matters (range 0-1). | *Optional* | `0.0` | 2.0 |
-| bikeTriangleTimeFactor | `double` | For bike triangle routing, how much time matters (range 0-1). | *Optional* | `0.0` | 2.0 |
-| bikeWalkingReluctance | `double` | A multiplier for how bad walking with a bike is, compared to being in transit for equal lengths of time. | *Optional* | `5.0` | 2.1 |
-| bikeWalkingSpeed | `double` | The user's bike walking speed in meters/second. Defaults to approximately 3 MPH. | *Optional* | `1.33` | 2.1 |
-| [boardSlack](#rd_boardSlack) | `duration` | The boardSlack is the minimum extra time to board a public transport vehicle. | *Optional* | `"PT0S"` | 2.0 |
-| carAccelerationSpeed | `double` | The acceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 |
-| carDecelerationSpeed | `double` | The deceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 |
-| carDropoffTime | `integer` | Time to park a car in a park and ride, w/o taking into account driving and walking cost. | *Optional* | `120` | 2.0 |
-| carParkCost | `integer` | Cost of parking a car. | *Optional* | `120` | 2.1 |
-| carParkTime | `integer` | Time to park a car | *Optional* | `60` | 2.1 |
-| carPickupCost | `integer` | Add a cost for car pickup changes when a pickup or drop off takes place | *Optional* | `120` | 2.1 |
-| carPickupTime | `integer` | Add a time for car pickup changes when a pickup or drop off takes place | *Optional* | `60` | 2.1 |
-| carReluctance | `double` | A multiplier for how bad driving is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 |
-| carSpeed | `double` | Max car speed along streets, in meters per second | *Optional* | `40.0` | 2.0 |
-| [drivingDirection](#rd_drivingDirection) | `enum` | The driving direction to use in the intersection traversal calculation | *Optional* | `"right"` | 2.2 |
-| elevatorBoardCost | `integer` | What is the cost of boarding a elevator? | *Optional* | `90` | 2.0 |
-| elevatorBoardTime | `integer` | How long does it take to get on an elevator, on average. | *Optional* | `90` | 2.0 |
-| elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 |
-| elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 |
-| 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 |
-| [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 |
-| [maxJourneyDuration](#rd_maxJourneyDuration) | `duration` | The expected maximum time a journey can last across all possible journeys for the current deployment. | *Optional* | `"PT24H"` | 2.1 |
-| modes | `string` | The set of access/egress/direct/transit modes to be used for the route search. | *Optional* | `"TRANSIT,WALK"` | 2.0 |
-| nonpreferredTransferPenalty | `integer` | Penalty (in seconds) for using a non-preferred transfer. | *Optional* | `180` | 2.0 |
-| numItineraries | `integer` | The maximum number of itineraries to return. | *Optional* | `50` | 2.0 |
-| [optimize](#rd_optimize) | `enum` | The set of characteristics that the user wants to optimize for. | *Optional* | `"safe"` | 2.0 |
-| [otherThanPreferredRoutesPenalty](#rd_otherThanPreferredRoutesPenalty) | `integer` | Penalty added for using every route that is not preferred if user set any route as preferred. | *Optional* | `300` | 2.0 |
-| [relaxTransitSearchGeneralizedCostAtDestination](#rd_relaxTransitSearchGeneralizedCostAtDestination) | `double` | Whether non-optimal transit paths at the destination should be returned | *Optional* | | 2.3 |
-| [searchWindow](#rd_searchWindow) | `duration` | The duration of the search-window. | *Optional* | | 2.0 |
-| stairsReluctance | `double` | Used instead of walkReluctance for stairs. | *Optional* | `2.0` | 2.0 |
-| [stairsTimeFactor](#rd_stairsTimeFactor) | `double` | How much more time does it take to walk a flight of stairs compared to walking a similar horizontal length. | *Optional* | `3.0` | 2.1 |
-| [streetRoutingTimeout](#rd_streetRoutingTimeout) | `duration` | The maximum time a street routing request is allowed to take before returning the results. | *Optional* | `"PT5S"` | 2.2 |
-| [transferPenalty](#rd_transferPenalty) | `integer` | An additional penalty added to boardings after the first. | *Optional* | `0` | 2.0 |
-| [transferSlack](#rd_transferSlack) | `integer` | The extra time needed to make a safe transfer in seconds. | *Optional* | `120` | 2.0 |
-| turnReluctance | `double` | Multiplicative factor on expected turning time. | *Optional* | `1.0` | 2.0 |
-| [unpreferredCost](#rd_unpreferredCost) | `cost-linear-function` | A cost function used to calculate penalty for an unpreferred route. | *Optional* | `"0s + 1.00 t"` | 2.2 |
-| [unpreferredVehicleParkingTagCost](#rd_unpreferredVehicleParkingTagCost) | `integer` | What cost to add if a parking facility doesn't contain a preferred tag. | *Optional* | `300` | 2.3 |
-| waitReluctance | `double` | How much worse is waiting for a transit vehicle than being on a transit vehicle, as a multiplier. | *Optional* | `1.0` | 2.0 |
-| walkBoardCost | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. This is the cost that is used when boarding while walking. | *Optional* | `600` | 2.0 |
-| [walkReluctance](#rd_walkReluctance) | `double` | A multiplier for how bad walking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 |
-| [walkSafetyFactor](#rd_walkSafetyFactor) | `double` | Factor for how much the walk safety is considered in routing. | *Optional* | `1.0` | 2.2 |
-| walkSpeed | `double` | The user's walking speed in meters/second. | *Optional* | `1.33` | 2.0 |
-| accessEgress | `object` | Parameters for access and egress routing. | *Optional* | | 2.4 |
-| [maxDuration](#rd_accessEgress_maxDuration) | `duration` | This is the maximum duration for access/egress for street searches. | *Optional* | `"PT45M"` | 2.1 |
-| [maxStopCount](#rd_accessEgress_maxStopCount) | `integer` | Maximal number of stops collected in access/egress routing | *Optional* | `500` | 2.4 |
-| [maxDurationForMode](#rd_accessEgress_maxDurationForMode) | `enum map of duration` | Limit access/egress per street mode. | *Optional* | | 2.1 |
-| [penalty](#rd_accessEgress_penalty) | `enum map of object` | Penalty for access/egress by street mode. | *Optional* | | 2.4 |
-| FLEXIBLE | `object` | NA | *Optional* | | 2.4 |
-| costFactor | `double` | A factor multiplied with the time-penalty to get the cost-penalty. | *Optional* | `0.0` | 2.4 |
-| timePenalty | `time-penalty` | Penalty added to the time of a path/leg. | *Optional* | `"0s + 0.00 t"` | 2.4 |
-| [alightSlackForMode](#rd_alightSlackForMode) | `enum map of duration` | How much extra time should be given when alighting a vehicle for each given mode. | *Optional* | | 2.0 |
-| [bannedVehicleParkingTags](#rd_bannedVehicleParkingTags) | `string[]` | Tags with which a vehicle parking will not be used. If empty, no tags are banned. | *Optional* | | 2.1 |
-| [boardSlackForMode](#rd_boardSlackForMode) | `enum map of duration` | How much extra time should be given when boarding a vehicle for each given mode. | *Optional* | | 2.0 |
-| [itineraryFilters](#rd_itineraryFilters) | `object` | Configure itinerary filters that may modify itineraries, sort them, and filter away less preferable results. | *Optional* | | 2.0 |
-| [accessibilityScore](#rd_if_accessibilityScore) | `boolean` | An experimental feature contributed by IBI which adds a sandbox accessibility *score* between 0 and 1 for each leg and itinerary. | *Optional* | `false` | 2.2 |
-| [bikeRentalDistanceRatio](#rd_if_bikeRentalDistanceRatio) | `double` | Filter routes that consist of bike-rental and walking by the minimum fraction of the bike-rental leg using _distance_. | *Optional* | `0.0` | 2.1 |
-| [debug](#rd_if_debug) | `enum` | Enable this to attach a system notice to itineraries instead of removing them. This is very convenient when tuning the itinerary-filter-chain. | *Optional* | `"off"` | 2.0 |
-| [filterItinerariesWithSameFirstOrLastTrip](#rd_if_filterItinerariesWithSameFirstOrLastTrip) | `boolean` | If more than one itinerary begins or ends with same trip, filter out one of those itineraries so that only one remains. | *Optional* | `false` | 2.2 |
-| groupSimilarityKeepOne | `double` | Pick ONE itinerary from each group after putting itineraries that are 85% similar together. | *Optional* | `0.85` | 2.1 |
-| groupSimilarityKeepThree | `double` | Reduce the number of itineraries to three itineraries by reducing each group of itineraries grouped by 68% similarity. | *Optional* | `0.68` | 2.1 |
-| [groupedOtherThanSameLegsMaxCostMultiplier](#rd_if_groupedOtherThanSameLegsMaxCostMultiplier) | `double` | Filter grouped itineraries, where the non-grouped legs are more expensive than in the lowest cost one. | *Optional* | `2.0` | 2.1 |
-| [minBikeParkingDistance](#rd_if_minBikeParkingDistance) | `double` | Filter out bike park+ride results that have fewer meters of cycling than this value. | *Optional* | `0.0` | 2.3 |
-| [nonTransitGeneralizedCostLimit](#rd_if_nonTransitGeneralizedCostLimit) | `cost-linear-function` | The function define a max-limit for generalized-cost for non-transit itineraries. | *Optional* | `"1h + 2.0 t"` | 2.1 |
-| [parkAndRideDurationRatio](#rd_if_parkAndRideDurationRatio) | `double` | Filter P+R routes that consist of driving and walking by the minimum fraction of the driving using of _time_. | *Optional* | `0.0` | 2.1 |
-| [removeItinerariesWithSameRoutesAndStops](#rd_if_removeItinerariesWithSameRoutesAndStops) | `boolean` | Set to true if you want to list only the first itinerary which goes through the same stops and routes. | *Optional* | `false` | 2.2 |
-| [transitGeneralizedCostLimit](#rd_if_transitGeneralizedCostLimit) | `object` | A relative limit for the generalized-cost for transit itineraries. | *Optional* | | 2.1 |
-| [costLimitFunction](#rd_if_transitGeneralizedCostLimit_costLimitFunction) | `cost-linear-function` | The base function used by the filter. | *Optional* | `"15m + 1.50 t"` | 2.2 |
-| [intervalRelaxFactor](#rd_if_transitGeneralizedCostLimit_intervalRelaxFactor) | `double` | How much the filter should be relaxed for itineraries that do not overlap in time. | *Optional* | `0.4` | 2.2 |
-| [maxDirectStreetDurationForMode](#rd_maxDirectStreetDurationForMode) | `enum map of duration` | Limit direct route duration per street mode. | *Optional* | | 2.2 |
-| [preferredVehicleParkingTags](#rd_preferredVehicleParkingTags) | `string[]` | Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised. | *Optional* | | 2.3 |
-| [requiredVehicleParkingTags](#rd_requiredVehicleParkingTags) | `string[]` | Tags without which a vehicle parking will not be used. If empty, no tags are required. | *Optional* | | 2.1 |
-| [transferOptimization](#rd_transferOptimization) | `object` | Optimize where a transfer between to trip happens. | *Optional* | | 2.1 |
-| [backTravelWaitTimeFactor](#rd_to_backTravelWaitTimeFactor) | `double` | To reduce back-travel we favor waiting, this reduces the cost of waiting. | *Optional* | `1.0` | 2.1 |
-| [extraStopBoardAlightCostsFactor](#rd_to_extraStopBoardAlightCostsFactor) | `double` | Add an extra board- and alight-cost for prioritized stops. | *Optional* | `0.0` | 2.1 |
-| [minSafeWaitTimeFactor](#rd_to_minSafeWaitTimeFactor) | `double` | Used to set a maximum wait-time cost, base on min-safe-transfer-time. | *Optional* | `5.0` | 2.1 |
-| [optimizeTransferWaitTime](#rd_to_optimizeTransferWaitTime) | `boolean` | This enables the transfer wait time optimization. | *Optional* | `true` | 2.1 |
-| [transitReluctanceForMode](#rd_transitReluctanceForMode) | `enum map of double` | Transit reluctance for a given transport mode | *Optional* | | 2.1 |
-| [unpreferred](#rd_unpreferred) | `object` | Parameters listing authorities or lines that preferably should not be used in trip patters. | *Optional* | | 2.2 |
-| [agencies](#rd_unpreferred_agencies) | `feed-scoped-id[]` | The ids of the agencies that incur an extra cost when being used. Format: `FeedId:AgencyId` | *Optional* | | 2.2 |
-| [routes](#rd_unpreferred_routes) | `feed-scoped-id[]` | The ids of the routes that incur an extra cost when being used. Format: `FeedId:RouteId` | *Optional* | | 2.2 |
-| vehicleRental | `object` | Vehicle rental options | *Optional* | | 2.3 |
-| allowKeepingAtDestination | `boolean` | If a vehicle should be allowed to be kept at the end of a station-based rental. | *Optional* | `false` | 2.2 |
-| dropOffCost | `integer` | Cost to drop-off a rented vehicle. | *Optional* | `30` | 2.0 |
-| dropOffTime | `integer` | Time to drop-off a rented vehicle. | *Optional* | `30` | 2.0 |
-| keepingAtDestinationCost | `double` | The cost of arriving at the destination with the rented vehicle, to discourage doing so. | *Optional* | `0.0` | 2.2 |
-| pickupCost | `integer` | Cost to rent a vehicle. | *Optional* | `120` | 2.0 |
-| pickupTime | `integer` | Time to rent a vehicle. | *Optional* | `60` | 2.0 |
-| useAvailabilityInformation | `boolean` | Whether or not vehicle rental availability information will be used to plan vehicle rental trips. | *Optional* | `false` | 2.0 |
-| [allowedNetworks](#rd_vehicleRental_allowedNetworks) | `string[]` | The vehicle rental networks which may be used. If empty all networks may be used. | *Optional* | | 2.1 |
-| [bannedNetworks](#rd_vehicleRental_bannedNetworks) | `string[]` | The vehicle rental networks which may not be used. If empty, no networks are banned. | *Optional* | | 2.1 |
-| wheelchairAccessibility | `object` | See [Wheelchair Accessibility](Accessibility.md) | *Optional* | | 2.2 |
-| enabled | `boolean` | Enable wheelchair accessibility. | *Optional* | `false` | 2.0 |
-| inaccessibleStreetReluctance | `double` | The factor to multiply the cost of traversing a street edge that is not wheelchair-accessible. | *Optional* | `25.0` | 2.2 |
-| [maxSlope](#rd_wheelchairAccessibility_maxSlope) | `double` | The maximum slope as a fraction of 1. | *Optional* | `0.083` | 2.0 |
-| [slopeExceededReluctance](#rd_wheelchairAccessibility_slopeExceededReluctance) | `double` | How much streets with high slope should be avoided. | *Optional* | `1.0` | 2.2 |
-| [stairsReluctance](#rd_wheelchairAccessibility_stairsReluctance) | `double` | How much stairs should be avoided. | *Optional* | `100.0` | 2.2 |
-| elevator | `object` | Configuration for when to use inaccessible elevators. | *Optional* | | 2.2 |
-| inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 |
-| onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `false` | 2.2 |
-| unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `20` | 2.2 |
-| stop | `object` | Configuration for when to use inaccessible stops. | *Optional* | | 2.2 |
-| inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 |
-| onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 |
-| unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 |
-| trip | `object` | Configuration for when to use inaccessible trips. | *Optional* | | 2.2 |
-| inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 |
-| onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 |
-| unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 |
+| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since |
+|--------------------------------------------------------------------------------------------------------------|:----------------------:|------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|------------------|:-----:|
+| [alightSlack](#rd_alightSlack) | `duration` | The minimum extra time after exiting a public transport vehicle. | *Optional* | `"PT0S"` | 2.0 |
+| arriveBy | `boolean` | Whether the trip should depart or arrive at the specified date and time. | *Optional* | `false` | 2.0 |
+| [bikeBoardCost](#rd_bikeBoardCost) | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. | *Optional* | `600` | 2.0 |
+| bikeParkCost | `integer` | Cost to park a bike. | *Optional* | `120` | 2.0 |
+| bikeParkTime | `integer` | Time to park a bike. | *Optional* | `60` | 2.0 |
+| bikeReluctance | `double` | A multiplier for how bad biking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 |
+| bikeSpeed | `double` | Max bike speed along streets, in meters per second | *Optional* | `5.0` | 2.0 |
+| bikeStairsReluctance | `double` | How bad is it to walk the bicycle up/down a flight of stairs compared to taking a detour. | *Optional* | `10.0` | 2.3 |
+| bikeSwitchCost | `integer` | The cost of the user fetching their bike and parking it again. | *Optional* | `0` | 2.0 |
+| bikeSwitchTime | `integer` | The time it takes the user to fetch their bike and park it again in seconds. | *Optional* | `0` | 2.0 |
+| bikeTriangleSafetyFactor | `double` | For bike triangle routing, how much safety matters (range 0-1). | *Optional* | `0.0` | 2.0 |
+| bikeTriangleSlopeFactor | `double` | For bike triangle routing, how much slope matters (range 0-1). | *Optional* | `0.0` | 2.0 |
+| bikeTriangleTimeFactor | `double` | For bike triangle routing, how much time matters (range 0-1). | *Optional* | `0.0` | 2.0 |
+| bikeWalkingReluctance | `double` | A multiplier for how bad walking with a bike is, compared to being in transit for equal lengths of time. | *Optional* | `5.0` | 2.1 |
+| bikeWalkingSpeed | `double` | The user's bike walking speed in meters/second. Defaults to approximately 3 MPH. | *Optional* | `1.33` | 2.1 |
+| [boardSlack](#rd_boardSlack) | `duration` | The boardSlack is the minimum extra time to board a public transport vehicle. | *Optional* | `"PT0S"` | 2.0 |
+| carAccelerationSpeed | `double` | The acceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 |
+| carDecelerationSpeed | `double` | The deceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 |
+| carDropoffTime | `integer` | Time to park a car in a park and ride, w/o taking into account driving and walking cost. | *Optional* | `120` | 2.0 |
+| carParkCost | `integer` | Cost of parking a car. | *Optional* | `120` | 2.1 |
+| carParkTime | `integer` | Time to park a car | *Optional* | `60` | 2.1 |
+| carPickupCost | `integer` | Add a cost for car pickup changes when a pickup or drop off takes place | *Optional* | `120` | 2.1 |
+| carPickupTime | `integer` | Add a time for car pickup changes when a pickup or drop off takes place | *Optional* | `60` | 2.1 |
+| carReluctance | `double` | A multiplier for how bad driving is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 |
+| carSpeed | `double` | Max car speed along streets, in meters per second | *Optional* | `40.0` | 2.0 |
+| [drivingDirection](#rd_drivingDirection) | `enum` | The driving direction to use in the intersection traversal calculation | *Optional* | `"right"` | 2.2 |
+| elevatorBoardCost | `integer` | What is the cost of boarding a elevator? | *Optional* | `90` | 2.0 |
+| elevatorBoardTime | `integer` | How long does it take to get on an elevator, on average. | *Optional* | `90` | 2.0 |
+| elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 |
+| elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 |
+| 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 |
+| [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 |
+| [maxJourneyDuration](#rd_maxJourneyDuration) | `duration` | The expected maximum time a journey can last across all possible journeys for the current deployment. | *Optional* | `"PT24H"` | 2.1 |
+| modes | `string` | The set of access/egress/direct/transit modes to be used for the route search. | *Optional* | `"TRANSIT,WALK"` | 2.0 |
+| nonpreferredTransferPenalty | `integer` | Penalty (in seconds) for using a non-preferred transfer. | *Optional* | `180` | 2.0 |
+| numItineraries | `integer` | The maximum number of itineraries to return. | *Optional* | `50` | 2.0 |
+| [optimize](#rd_optimize) | `enum` | The set of characteristics that the user wants to optimize for. | *Optional* | `"safe"` | 2.0 |
+| [otherThanPreferredRoutesPenalty](#rd_otherThanPreferredRoutesPenalty) | `integer` | Penalty added for using every route that is not preferred if user set any route as preferred. | *Optional* | `300` | 2.0 |
+| [relaxTransitSearchGeneralizedCostAtDestination](#rd_relaxTransitSearchGeneralizedCostAtDestination) | `double` | Whether non-optimal transit paths at the destination should be returned | *Optional* | | 2.3 |
+| [searchWindow](#rd_searchWindow) | `duration` | The duration of the search-window. | *Optional* | | 2.0 |
+| stairsReluctance | `double` | Used instead of walkReluctance for stairs. | *Optional* | `2.0` | 2.0 |
+| [stairsTimeFactor](#rd_stairsTimeFactor) | `double` | How much more time does it take to walk a flight of stairs compared to walking a similar horizontal length. | *Optional* | `3.0` | 2.1 |
+| [streetRoutingTimeout](#rd_streetRoutingTimeout) | `duration` | The maximum time a street routing request is allowed to take before returning the results. | *Optional* | `"PT5S"` | 2.2 |
+| [transferPenalty](#rd_transferPenalty) | `integer` | An additional penalty added to boardings after the first. | *Optional* | `0` | 2.0 |
+| [transferSlack](#rd_transferSlack) | `integer` | The extra time needed to make a safe transfer in seconds. | *Optional* | `120` | 2.0 |
+| turnReluctance | `double` | Multiplicative factor on expected turning time. | *Optional* | `1.0` | 2.0 |
+| [unpreferredCost](#rd_unpreferredCost) | `cost-linear-function` | A cost function used to calculate penalty for an unpreferred route. | *Optional* | `"0s + 1.00 t"` | 2.2 |
+| [unpreferredVehicleParkingTagCost](#rd_unpreferredVehicleParkingTagCost) | `integer` | What cost to add if a parking facility doesn't contain a preferred tag. | *Optional* | `300` | 2.3 |
+| waitReluctance | `double` | How much worse is waiting for a transit vehicle than being on a transit vehicle, as a multiplier. | *Optional* | `1.0` | 2.0 |
+| walkBoardCost | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. This is the cost that is used when boarding while walking. | *Optional* | `600` | 2.0 |
+| [walkReluctance](#rd_walkReluctance) | `double` | A multiplier for how bad walking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 |
+| [walkSafetyFactor](#rd_walkSafetyFactor) | `double` | Factor for how much the walk safety is considered in routing. | *Optional* | `1.0` | 2.2 |
+| walkSpeed | `double` | The user's walking speed in meters/second. | *Optional* | `1.33` | 2.0 |
+| accessEgress | `object` | Parameters for access and egress routing. | *Optional* | | 2.4 |
+| [maxDuration](#rd_accessEgress_maxDuration) | `duration` | This is the maximum duration for access/egress for street searches. | *Optional* | `"PT45M"` | 2.1 |
+| [maxStopCount](#rd_accessEgress_maxStopCount) | `integer` | Maximal number of stops collected in access/egress routing | *Optional* | `500` | 2.4 |
+| [maxDurationForMode](#rd_accessEgress_maxDurationForMode) | `enum map of duration` | Limit access/egress per street mode. | *Optional* | | 2.1 |
+| [penalty](#rd_accessEgress_penalty) | `enum map of object` | Penalty for access/egress by street mode. | *Optional* | | 2.4 |
+| FLEXIBLE | `object` | NA | *Optional* | | 2.4 |
+| costFactor | `double` | A factor multiplied with the time-penalty to get the cost-penalty. | *Optional* | `0.0` | 2.4 |
+| timePenalty | `time-penalty` | Penalty added to the time of a path/leg. | *Optional* | `"0s + 0.00 t"` | 2.4 |
+| [alightSlackForMode](#rd_alightSlackForMode) | `enum map of duration` | How much extra time should be given when alighting a vehicle for each given mode. | *Optional* | | 2.0 |
+| [bannedVehicleParkingTags](#rd_bannedVehicleParkingTags) | `string[]` | Tags with which a vehicle parking will not be used. If empty, no tags are banned. | *Optional* | | 2.1 |
+| [boardSlackForMode](#rd_boardSlackForMode) | `enum map of duration` | How much extra time should be given when boarding a vehicle for each given mode. | *Optional* | | 2.0 |
+| [itineraryFilters](#rd_itineraryFilters) | `object` | Configure itinerary filters that may modify itineraries, sort them, and filter away less preferable results. | *Optional* | | 2.0 |
+| [accessibilityScore](#rd_if_accessibilityScore) | `boolean` | An experimental feature contributed by IBI which adds a sandbox accessibility *score* between 0 and 1 for each leg and itinerary. | *Optional* | `false` | 2.2 |
+| [bikeRentalDistanceRatio](#rd_if_bikeRentalDistanceRatio) | `double` | Filter routes that consist of bike-rental and walking by the minimum fraction of the bike-rental leg using _distance_. | *Optional* | `0.0` | 2.1 |
+| [debug](#rd_if_debug) | `enum` | Enable this to attach a system notice to itineraries instead of removing them. This is very convenient when tuning the itinerary-filter-chain. | *Optional* | `"off"` | 2.0 |
+| [filterItinerariesWithSameFirstOrLastTrip](#rd_if_filterItinerariesWithSameFirstOrLastTrip) | `boolean` | If more than one itinerary begins or ends with same trip, filter out one of those itineraries so that only one remains. | *Optional* | `false` | 2.2 |
+| groupSimilarityKeepOne | `double` | Pick ONE itinerary from each group after putting itineraries that are 85% similar together. | *Optional* | `0.85` | 2.1 |
+| groupSimilarityKeepThree | `double` | Reduce the number of itineraries to three itineraries by reducing each group of itineraries grouped by 68% similarity. | *Optional* | `0.68` | 2.1 |
+| [groupedOtherThanSameLegsMaxCostMultiplier](#rd_if_groupedOtherThanSameLegsMaxCostMultiplier) | `double` | Filter grouped itineraries, where the non-grouped legs are more expensive than in the lowest cost one. | *Optional* | `2.0` | 2.1 |
+| [minBikeParkingDistance](#rd_if_minBikeParkingDistance) | `double` | Filter out bike park+ride results that have fewer meters of cycling than this value. | *Optional* | `0.0` | 2.3 |
+| [nonTransitGeneralizedCostLimit](#rd_if_nonTransitGeneralizedCostLimit) | `cost-linear-function` | The function define a max-limit for generalized-cost for non-transit itineraries. | *Optional* | `"1h + 2.0 t"` | 2.1 |
+| [parkAndRideDurationRatio](#rd_if_parkAndRideDurationRatio) | `double` | Filter P+R routes that consist of driving and walking by the minimum fraction of the driving using of _time_. | *Optional* | `0.0` | 2.1 |
+| [removeItinerariesWithSameRoutesAndStops](#rd_if_removeItinerariesWithSameRoutesAndStops) | `boolean` | Set to true if you want to list only the first itinerary which goes through the same stops and routes. | *Optional* | `false` | 2.2 |
+| [removeTransitWithHigherCostThanBestOnStreetOnly](#rd_if_removeTransitWithHigherCostThanBestOnStreetOnly) | `cost-linear-function` | Limit function for generalized-cost computed from street-only itineries applied to transit itineraries. | *Optional* | `"1m + 1.30 t"` | 2.4 |
+| [transitGeneralizedCostLimit](#rd_if_transitGeneralizedCostLimit) | `object` | A relative limit for the generalized-cost for transit itineraries. | *Optional* | | 2.1 |
+| [costLimitFunction](#rd_if_transitGeneralizedCostLimit_costLimitFunction) | `cost-linear-function` | The base function used by the filter. | *Optional* | `"15m + 1.50 t"` | 2.2 |
+| [intervalRelaxFactor](#rd_if_transitGeneralizedCostLimit_intervalRelaxFactor) | `double` | How much the filter should be relaxed for itineraries that do not overlap in time. | *Optional* | `0.4` | 2.2 |
+| [maxDirectStreetDurationForMode](#rd_maxDirectStreetDurationForMode) | `enum map of duration` | Limit direct route duration per street mode. | *Optional* | | 2.2 |
+| [preferredVehicleParkingTags](#rd_preferredVehicleParkingTags) | `string[]` | Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised. | *Optional* | | 2.3 |
+| [requiredVehicleParkingTags](#rd_requiredVehicleParkingTags) | `string[]` | Tags without which a vehicle parking will not be used. If empty, no tags are required. | *Optional* | | 2.1 |
+| [transferOptimization](#rd_transferOptimization) | `object` | Optimize where a transfer between to trip happens. | *Optional* | | 2.1 |
+| [backTravelWaitTimeFactor](#rd_to_backTravelWaitTimeFactor) | `double` | To reduce back-travel we favor waiting, this reduces the cost of waiting. | *Optional* | `1.0` | 2.1 |
+| [extraStopBoardAlightCostsFactor](#rd_to_extraStopBoardAlightCostsFactor) | `double` | Add an extra board- and alight-cost for prioritized stops. | *Optional* | `0.0` | 2.1 |
+| [minSafeWaitTimeFactor](#rd_to_minSafeWaitTimeFactor) | `double` | Used to set a maximum wait-time cost, base on min-safe-transfer-time. | *Optional* | `5.0` | 2.1 |
+| [optimizeTransferWaitTime](#rd_to_optimizeTransferWaitTime) | `boolean` | This enables the transfer wait time optimization. | *Optional* | `true` | 2.1 |
+| [transitReluctanceForMode](#rd_transitReluctanceForMode) | `enum map of double` | Transit reluctance for a given transport mode | *Optional* | | 2.1 |
+| [unpreferred](#rd_unpreferred) | `object` | Parameters listing authorities or lines that preferably should not be used in trip patters. | *Optional* | | 2.2 |
+| [agencies](#rd_unpreferred_agencies) | `feed-scoped-id[]` | The ids of the agencies that incur an extra cost when being used. Format: `FeedId:AgencyId` | *Optional* | | 2.2 |
+| [routes](#rd_unpreferred_routes) | `feed-scoped-id[]` | The ids of the routes that incur an extra cost when being used. Format: `FeedId:RouteId` | *Optional* | | 2.2 |
+| vehicleRental | `object` | Vehicle rental options | *Optional* | | 2.3 |
+| allowKeepingAtDestination | `boolean` | If a vehicle should be allowed to be kept at the end of a station-based rental. | *Optional* | `false` | 2.2 |
+| dropOffCost | `integer` | Cost to drop-off a rented vehicle. | *Optional* | `30` | 2.0 |
+| dropOffTime | `integer` | Time to drop-off a rented vehicle. | *Optional* | `30` | 2.0 |
+| keepingAtDestinationCost | `double` | The cost of arriving at the destination with the rented vehicle, to discourage doing so. | *Optional* | `0.0` | 2.2 |
+| pickupCost | `integer` | Cost to rent a vehicle. | *Optional* | `120` | 2.0 |
+| pickupTime | `integer` | Time to rent a vehicle. | *Optional* | `60` | 2.0 |
+| useAvailabilityInformation | `boolean` | Whether or not vehicle rental availability information will be used to plan vehicle rental trips. | *Optional* | `false` | 2.0 |
+| [allowedNetworks](#rd_vehicleRental_allowedNetworks) | `string[]` | The vehicle rental networks which may be used. If empty all networks may be used. | *Optional* | | 2.1 |
+| [bannedNetworks](#rd_vehicleRental_bannedNetworks) | `string[]` | The vehicle rental networks which may not be used. If empty, no networks are banned. | *Optional* | | 2.1 |
+| wheelchairAccessibility | `object` | See [Wheelchair Accessibility](Accessibility.md) | *Optional* | | 2.2 |
+| enabled | `boolean` | Enable wheelchair accessibility. | *Optional* | `false` | 2.0 |
+| inaccessibleStreetReluctance | `double` | The factor to multiply the cost of traversing a street edge that is not wheelchair-accessible. | *Optional* | `25.0` | 2.2 |
+| [maxSlope](#rd_wheelchairAccessibility_maxSlope) | `double` | The maximum slope as a fraction of 1. | *Optional* | `0.083` | 2.0 |
+| [slopeExceededReluctance](#rd_wheelchairAccessibility_slopeExceededReluctance) | `double` | How much streets with high slope should be avoided. | *Optional* | `1.0` | 2.2 |
+| [stairsReluctance](#rd_wheelchairAccessibility_stairsReluctance) | `double` | How much stairs should be avoided. | *Optional* | `100.0` | 2.2 |
+| elevator | `object` | Configuration for when to use inaccessible elevators. | *Optional* | | 2.2 |
+| inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 |
+| onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `false` | 2.2 |
+| unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `20` | 2.2 |
+| stop | `object` | Configuration for when to use inaccessible stops. | *Optional* | | 2.2 |
+| inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 |
+| onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 |
+| unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 |
+| trip | `object` | Configuration for when to use inaccessible trips. | *Optional* | | 2.2 |
+| inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 |
+| onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 |
+| unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 |
@@ -628,6 +629,22 @@ Set to true if you want to list only the first itinerary which goes through the
Itineraries visiting the same set of stops and riding the exact same routes, departing later are removed from the result.
+
removeTransitWithHigherCostThanBestOnStreetOnly
+
+**Since version:** `2.4` ∙ **Type:** `cost-linear-function` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"1m + 1.30 t"`
+**Path:** /routingDefaults/itineraryFilters
+
+Limit function for generalized-cost computed from street-only itineries applied to transit itineraries.
+
+The max-limit is applied to itineraries with transit *legs*, and only itineraries
+without transit legs are considered when calculating the minimum cost. The smallest
+generalized-cost value is used as input to the function. The function is used to calculate a
+*max-limit*. The max-limit is then used to filter *transit* itineraries by
+*generalized-cost*. Itineraries with a cost higher than the max-limit are dropped from the result
+set. Walking is handled with a different logic: if a transit itinerary has higher cost than
+a plain walk itinerary, it will be removed even if the cost limit function would keep it.
+
+
transitGeneralizedCostLimit
**Since version:** `2.1` ∙ **Type:** `object` ∙ **Cardinality:** `Optional`
@@ -930,6 +947,8 @@ include stairs as a last result.
"costLimitFunction" : "15m + 1.5 x",
"intervalRelaxFactor" : 0.4
},
+ "nonTransitGeneralizedCostLimit" : "400 + 1.5x",
+ "removeTransitWithHigherCostThanBestOnStreetOnly" : "60 + 1.3x",
"bikeRentalDistanceRatio" : 0.3,
"accessibilityScore" : true,
"minBikeParkingDistance" : 300,
diff --git a/docs/RouterConfiguration.md b/docs/RouterConfiguration.md
index 709988d042c..1e77d3513a2 100644
--- a/docs/RouterConfiguration.md
+++ b/docs/RouterConfiguration.md
@@ -434,7 +434,7 @@ HTTP headers to add to the request. Any header key, value can be inserted.
```JSON
// router-config.json
{
- "configVersion" : "v2.3.0-EN000121",
+ "configVersion" : "v2.4.0-EN000121",
"server" : {
"apiProcessingTimeout" : "7s",
"traceParameters" : [
@@ -504,6 +504,8 @@ HTTP headers to add to the request. Any header key, value can be inserted.
"costLimitFunction" : "15m + 1.5 x",
"intervalRelaxFactor" : 0.4
},
+ "nonTransitGeneralizedCostLimit" : "400 + 1.5x",
+ "removeTransitWithHigherCostThanBestOnStreetOnly" : "60 + 1.3x",
"bikeRentalDistanceRatio" : 0.3,
"accessibilityScore" : true,
"minBikeParkingDistance" : 300,
diff --git a/docs/RoutingModes.md b/docs/RoutingModes.md
index 9b96e65e97d..88148d370e5 100644
--- a/docs/RoutingModes.md
+++ b/docs/RoutingModes.md
@@ -17,7 +17,7 @@ Cycling for the entirety of the route or taking a bicycle onto the public transp
Taking a rented, shared-mobility bike for part or the entirety of the route.
-_Prerequisite:_ Vehicle or station location need to be added to OTP from dynamic data feeds.
+_Prerequisite:_ Vehicle or station locations need to be added to OTP from dynamic data feeds.
See [Configuring GBFS](UpdaterConfig.md#gbfs-vehicle-rental-systems) on how to add one.
@@ -25,6 +25,7 @@ See [Configuring GBFS](UpdaterConfig.md#gbfs-vehicle-rental-systems) on how to a
Leaving the bicycle at the departure station and walking from the arrival station to the destination.
This mode needs to be combined with at least one transit mode otherwise it behaves like an ordinary bicycle journey.
+
_Prerequisite:_ Bicycle parking stations present in the OSM file and visible to OTP by enabling the property `staticBikeParkAndRide` during graph build.
@@ -51,7 +52,7 @@ Walking to a pickup point along the road, driving to a drop-off point along the
Walk to a car rental point, drive to a car rental drop-off point and walk the rest of the way.
This can include car rental at fixed locations or free-floating services.
-_Prerequisite:_ Vehicle or station location need to be added to OTP from dynamic data feeds.
+_Prerequisite:_ Vehicle or station locations need to be added to OTP from dynamic data feeds.
See [Configuring GBFS](UpdaterConfig.md#gbfs-vehicle-rental-systems) on how to add one.
@@ -64,14 +65,14 @@ _Prerequisite:_ Park-and-ride areas near the stations need to be present in the
FLEXIBLE
-Encompasses all types of on-demand and flexible transportation for example GTFS Flex or NeTex Flexible Stop Places.
+Encompasses all types of on-demand and flexible transportation for example GTFS Flex or NeTEx Flexible Stop Places.
SCOOTER_RENTAL
Walking to a scooter rental point, riding a scooter to a scooter rental drop-off point, and walking the rest of the way.
This can include scooter rental at fixed locations or free-floating services.
-_Prerequisite:_ Vehicle or station location need to be added to OTP from dynamic data feeds.
+_Prerequisite:_ Vehicle or station locations need to be added to OTP from dynamic data feeds.
See [Configuring GBFS](UpdaterConfig.md#gbfs-vehicle-rental-systems) on how to add one.
diff --git a/docs/examples/entur/build-config.json b/docs/examples/entur/build-config.json
index 5102107c7d6..3829e975f34 100644
--- a/docs/examples/entur/build-config.json
+++ b/docs/examples/entur/build-config.json
@@ -13,8 +13,6 @@
"islandWithoutStopsMaxSize": 5,
"islandWithStopsMaxSize": 5
},
- "banDiscouragedWalking": false,
- "banDiscouragedBiking": false,
"maxTransferDuration": "30m",
"distanceBetweenElevationSamples": 25,
"multiThreadElevationCalculations": true,
diff --git a/docs/index.md b/docs/index.md
index 49f28850f68..b70c99e4d52 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -26,7 +26,8 @@ the selector in the upper left of the published documentation.
**Releases**
-- [Latest](http://docs.opentripplanner.org/en/latest) - Version 2.3 (the git master branch)
+- [Latest](http://docs.opentripplanner.org/en/latest) - Version 2.4.0 (the git master branch)
+- [v2.3.0](http://docs.opentripplanner.org/en/v2.3.0) - Version 2.3
- [v2.2.0](http://docs.opentripplanner.org/en/v2.2.0) - Version 2.2
- [v2.1.0](http://docs.opentripplanner.org/en/v2.1.0) - Version 2.1
- [v2.0.0](http://docs.opentripplanner.org/en/v2.0.0) - Version 2.0
diff --git a/docs/requirements.txt b/docs/requirements.txt
index e8863d98147..5cd0396ae85 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,6 +1,4 @@
mkdocs==1.5.2
mkdocs-material==9.1.17
-mike==1.1.2
+mike@git+https://github.com/jimporter/mike.git@f0522f245e64687dd18384fbd86b721175711474
mkdocs-no-sitemap-plugin==0.0.1
-# remove this after OTP 2.4.0 has been released
-mkdocs-redirects==1.2.1
diff --git a/enunciate.xml b/enunciate.xml
deleted file mode 100644
index 5b5a0cbfaeb..00000000000
--- a/enunciate.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/mkdocs.yml b/mkdocs.yml
index 417e81c6c8d..27d0a6b2b1e 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -13,10 +13,6 @@ strict: true
plugins:
- "no-sitemap"
- search
- # remove this after OTP 2.4.0 has been released
- - redirects:
- redirect_maps:
- 'sandbox/LegacyGraphQLApi.md': 'sandbox/GtfsGraphQlApi.md'
theme:
name: material
diff --git a/pom.xml b/pom.xml
index 33c275041ba..afe0af5a35d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
https://opentripplanner.orgorg.opentripplannerotp
- 2.4.0-SNAPSHOT
+ 2.5.0-SNAPSHOTjar
@@ -56,18 +56,18 @@
- 119
+ 12029.22.482.15.23.1.35.10.0
- 1.11.3
+ 1.11.45.5.31.4.119.7.0
- 2.0.7
+ 2.0.92.0.141.223.0.2
@@ -187,23 +187,6 @@
-
- com.webcohesion.enunciate
- enunciate-maven-plugin
- 2.15.1
-
-
-
- site
-
- docs
-
-
-
-
- ${project.build.directory}/site/enunciate
-
- org.apache.maven.pluginsmaven-source-plugin
@@ -261,13 +244,6 @@
-
-
- org.apache.maven.plugins
- maven-site-plugin
- 3.12.1
-
-
org.apache.maven.pluginsmaven-surefire-plugin
@@ -395,7 +371,7 @@
properly if some input files are missing a terminating newline) -->
org.apache.maven.pluginsmaven-shade-plugin
- 3.5.0
+ 3.5.1package
@@ -745,7 +721,7 @@
org.entur.gbfsgbfs-java-model
- 3.0.1
+ 3.0.9
@@ -902,7 +878,13 @@
org.onebusawayonebusaway-gtfs
- 1.4.4
+ 1.4.5
+
+
+ org.slf4j
+ slf4j-simple
+
+
@@ -931,7 +913,7 @@
com.graphql-javagraphql-java
- 21.0
+ 21.1com.graphql-java
@@ -962,7 +944,7 @@
io.github.ci-cmgmapbox-vector-tile
- 4.0.5
+ 4.0.6net.objecthunter
@@ -999,7 +981,7 @@
org.apache.commonscommons-compress
- 1.23.0
+ 1.24.0test
diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/impl/DefaultFareServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/impl/DefaultFareServiceTest.java
index d93acc8de74..779ea7bd4b7 100644
--- a/src/ext-test/java/org/opentripplanner/ext/fares/impl/DefaultFareServiceTest.java
+++ b/src/ext-test/java/org/opentripplanner/ext/fares/impl/DefaultFareServiceTest.java
@@ -8,6 +8,10 @@
import static org.opentripplanner.ext.fares.impl.FareModelForTest.CITY_CENTER_A_STOP;
import static org.opentripplanner.ext.fares.impl.FareModelForTest.CITY_CENTER_B_STOP;
import static org.opentripplanner.ext.fares.impl.FareModelForTest.INSIDE_CITY_CENTER_SET;
+import static org.opentripplanner.ext.fares.impl.FareModelForTest.OTHER_FEED_ATTRIBUTE;
+import static org.opentripplanner.ext.fares.impl.FareModelForTest.OTHER_FEED_ROUTE;
+import static org.opentripplanner.ext.fares.impl.FareModelForTest.OTHER_FEED_SET;
+import static org.opentripplanner.ext.fares.impl.FareModelForTest.OTHER_FEED_STOP;
import static org.opentripplanner.ext.fares.impl.FareModelForTest.SUBURB_STOP;
import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary;
@@ -98,4 +102,79 @@ void unknownLeg() {
var firstBusLeg = itin.firstTransitLeg().get();
assertEquals(List.of(firstBusLeg), component.legs());
}
+
+ @Test
+ void multipleFeeds() {
+ var service = new DefaultFareService();
+ service.addFareRules(FareType.regular, List.of(AIRPORT_TO_CITY_CENTER_SET, OTHER_FEED_SET));
+ var itin = newItinerary(Place.forStop(AIRPORT_STOP))
+ .bus(1, T11_00, T11_05, Place.forStop(CITY_CENTER_A_STOP))
+ .walk(10, Place.forStop(OTHER_FEED_STOP))
+ .bus(OTHER_FEED_ROUTE, 2, T11_20, T11_32, Place.forStop(OTHER_FEED_STOP))
+ .build();
+ var result = service.calculateFares(itin);
+
+ var resultComponents = result
+ .getComponents(FareType.regular)
+ .stream()
+ .map(r -> r.fareId())
+ .toList();
+
+ var resultPrice = result.getFare(FareType.regular);
+
+ assertEquals(
+ List.of(AIRPORT_TO_CITY_CENTER_SET.getFareAttribute().getId(), OTHER_FEED_ATTRIBUTE.getId()),
+ resultComponents
+ );
+
+ assertEquals(Money.usDollars(20), resultPrice);
+ }
+
+ @Test
+ void multipleFeedsWithTransfersWithinFeed() {
+ var service = new DefaultFareService();
+ service.addFareRules(FareType.regular, List.of(INSIDE_CITY_CENTER_SET, OTHER_FEED_SET));
+ var itin = newItinerary(Place.forStop(OTHER_FEED_STOP))
+ .bus(OTHER_FEED_ROUTE, 2, T11_00, T11_05, Place.forStop(OTHER_FEED_STOP))
+ .walk(10, Place.forStop(CITY_CENTER_A_STOP))
+ .bus(1, T11_00, T11_05, Place.forStop(CITY_CENTER_A_STOP))
+ .walk(10, Place.forStop(OTHER_FEED_STOP))
+ .bus(OTHER_FEED_ROUTE, 2, T11_20, T11_32, Place.forStop(OTHER_FEED_STOP))
+ .build();
+ var result = service.calculateFares(itin);
+
+ var resultComponents = result
+ .getComponents(FareType.regular)
+ .stream()
+ .map(r -> r.fareId())
+ .toList();
+
+ var resultPrice = result.getFare(FareType.regular);
+ assertEquals(
+ List.of(INSIDE_CITY_CENTER_SET.getFareAttribute().getId(), OTHER_FEED_ATTRIBUTE.getId()),
+ resultComponents
+ );
+
+ assertEquals(Money.usDollars(20), resultPrice);
+ }
+
+ @Test
+ void multipleFeedsWithUnknownFareLegs() {
+ var service = new DefaultFareService();
+ service.addFareRules(FareType.regular, List.of(AIRPORT_TO_CITY_CENTER_SET, OTHER_FEED_SET));
+ var itin = newItinerary(Place.forStop(AIRPORT_STOP))
+ .bus(1, T11_00, T11_05, Place.forStop(OTHER_FEED_STOP))
+ .walk(10, Place.forStop(OTHER_FEED_STOP))
+ .bus(OTHER_FEED_ROUTE, 2, T11_20, T11_32, Place.forStop(OTHER_FEED_STOP))
+ .build();
+ var result = service.calculateFares(itin);
+ var resultComponents = result
+ .getComponents(FareType.regular)
+ .stream()
+ .map(r -> r.fareId())
+ .toList();
+ var resultPrice = result.getFare(FareType.regular);
+ assertEquals(List.of(OTHER_FEED_ATTRIBUTE.getId()), resultComponents);
+ assertEquals(Money.usDollars(-0.01f), resultPrice);
+ }
}
diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/impl/FareModelForTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/impl/FareModelForTest.java
index 74c9baa5422..5c8b52548e7 100644
--- a/src/ext-test/java/org/opentripplanner/ext/fares/impl/FareModelForTest.java
+++ b/src/ext-test/java/org/opentripplanner/ext/fares/impl/FareModelForTest.java
@@ -1,11 +1,17 @@
package org.opentripplanner.ext.fares.impl;
+import static org.opentripplanner.transit.model._data.TransitModelForTest.FEED_ID;
+import static org.opentripplanner.transit.model._data.TransitModelForTest.OTHER_AGENCY;
+import static org.opentripplanner.transit.model._data.TransitModelForTest.OTHER_FEED_AGENCY;
import static org.opentripplanner.transit.model._data.TransitModelForTest.id;
import org.opentripplanner.ext.fares.model.FareAttribute;
import org.opentripplanner.ext.fares.model.FareRuleSet;
import org.opentripplanner.framework.geometry.WgsCoordinate;
import org.opentripplanner.framework.i18n.NonLocalizedString;
+import org.opentripplanner.transit.model.basic.TransitMode;
+import org.opentripplanner.transit.model.framework.FeedScopedId;
+import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.site.FareZone;
import org.opentripplanner.transit.model.site.RegularStop;
@@ -14,6 +20,10 @@ public class FareModelForTest {
public static final FareZone AIRPORT_ZONE = FareZone.of(id("airport-zone")).build();
public static final FareZone CITY_CENTER_ZONE = FareZone.of(id("city-center")).build();
+ public static final FareZone OTHER_FEED_ZONE = FareZone
+ .of(FeedScopedId.ofNullable("F2", "other-feed-zone"))
+ .build();
+
static RegularStop AIRPORT_STOP = RegularStop
.of(id("airport"))
.withCoordinate(new WgsCoordinate(1, 1))
@@ -39,16 +49,33 @@ public class FareModelForTest {
.withName(new NonLocalizedString("Suburb"))
.build();
+ static RegularStop OTHER_FEED_STOP = RegularStop
+ .of(FeedScopedId.ofNullable("F2", "other-feed-stop"))
+ .withCoordinate(new WgsCoordinate(1, 5))
+ .withName(new NonLocalizedString("Other feed stop"))
+ .addFareZones(OTHER_FEED_ZONE)
+ .build();
static FareAttribute TEN_DOLLARS = FareAttribute
.of(id("airport-to-city-center"))
.setCurrencyType("USD")
.setPrice(10)
.setTransfers(0)
.build();
+
+ static FareAttribute OTHER_FEED_ATTRIBUTE = FareAttribute
+ .of(FeedScopedId.ofNullable("F2", "other-feed-attribute"))
+ .setCurrencyType("USD")
+ .setPrice(10)
+ .setTransfers(1)
+ .setAgency(OTHER_FEED_AGENCY.getId())
+ .build();
+
// Fare rule sets
static FareRuleSet AIRPORT_TO_CITY_CENTER_SET = new FareRuleSet(TEN_DOLLARS);
static FareRuleSet INSIDE_CITY_CENTER_SET = new FareRuleSet(TEN_DOLLARS);
+ static FareRuleSet OTHER_FEED_SET = new FareRuleSet(OTHER_FEED_ATTRIBUTE);
+
static {
AIRPORT_TO_CITY_CENTER_SET.addOriginDestination(
AIRPORT_ZONE.getId().getId(),
@@ -58,5 +85,16 @@ public class FareModelForTest {
CITY_CENTER_ZONE.getId().getId(),
CITY_CENTER_ZONE.getId().getId()
);
+ OTHER_FEED_SET.addOriginDestination(
+ OTHER_FEED_ZONE.getId().getId(),
+ OTHER_FEED_ZONE.getId().getId()
+ );
}
+
+ static Route OTHER_FEED_ROUTE = Route
+ .of(new FeedScopedId("F2", "other-feed-route"))
+ .withAgency(OTHER_FEED_AGENCY)
+ .withLongName(new NonLocalizedString("other-feed-route"))
+ .withMode(TransitMode.BUS)
+ .build();
}
diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/impl/HSLFareServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/impl/HSLFareServiceTest.java
index 8960128ff0e..d590f43ebe6 100644
--- a/src/ext-test/java/org/opentripplanner/ext/fares/impl/HSLFareServiceTest.java
+++ b/src/ext-test/java/org/opentripplanner/ext/fares/impl/HSLFareServiceTest.java
@@ -61,6 +61,12 @@ private static List createTestCases() {
.withTimezone("Europe/Helsinki")
.build();
+ Agency agency3 = Agency
+ .of(new FeedScopedId("FEED2", "AG3"))
+ .withName("Agency 3")
+ .withTimezone("Europe/Helsinki")
+ .build();
+
FareZone A = FareZone.of(new FeedScopedId(FEED_ID, "A")).build();
FareZone B = FareZone.of(new FeedScopedId(FEED_ID, "B")).build();
FareZone C = FareZone.of(new FeedScopedId(FEED_ID, "C")).build();
@@ -147,6 +153,12 @@ private static List createTestCases() {
.setAgency(agency2.getId())
.build();
+ FareAttribute fareAttributeAgency3 = FareAttribute
+ .of(new FeedScopedId("FEED2", "attribute"))
+ .setCurrencyType("EUR")
+ .setAgency(agency3.getId())
+ .build();
+
// Fare rule sets
FareRuleSet ruleSetAB = new FareRuleSet(fareAttributeAB);
ruleSetAB.addContains("A");
@@ -183,6 +195,9 @@ private static List createTestCases() {
ruleSetD2.addContains("D");
ruleSetD2.setAgency(agency2.getId());
+ FareRuleSet ruleSetAgency3 = new FareRuleSet(fareAttributeAgency3);
+ ruleSetAgency3.addContains("B");
+
hslFareService.addFareRules(
FareType.regular,
List.of(
@@ -193,7 +208,8 @@ private static List createTestCases() {
ruleSetBCD,
ruleSetABCD,
ruleSetD,
- ruleSetD2
+ ruleSetD2,
+ ruleSetAgency3
)
);
@@ -211,6 +227,13 @@ private static List createTestCases() {
.withMode(TransitMode.BUS)
.build();
+ Route routeAgency3 = Route
+ .of(new FeedScopedId("FEED2", "R3"))
+ .withAgency(agency3)
+ .withLongName(new NonLocalizedString("Route agency 3"))
+ .withMode(TransitMode.BUS)
+ .build();
+
// Itineraries within zone A
Itinerary A1_A2 = newItinerary(A1, T11_06).bus(1, T11_06, T11_12, A2).build();
@@ -366,6 +389,49 @@ private static List createTestCases() {
)
);
+ // Multifeed case
+ Itinerary A1_A2_2 = newItinerary(A1, T11_06)
+ .bus(routeAgency3, 1, T11_06, T11_14, A2)
+ .bus(routeAgency1, 2, T11_30, T11_50, A1)
+ .build();
+
+ args.add(
+ Arguments.of(
+ "Bus ride within zone A with two legs using different agencies from different feeds ",
+ hslFareService,
+ A1_A2_2,
+ List.of(fareAttributeAB.getId())
+ )
+ );
+
+ Itinerary i = newItinerary(D1, T11_06)
+ .bus(routeAgency1, 1, T11_06, T11_10, D2)
+ .walk(10, D1)
+ .bus(routeAgency2, 2, T11_20, T11_30, D2)
+ .build();
+
+ args.add(
+ Arguments.of(
+ "Multi-agency itinerary",
+ hslFareService,
+ i,
+ List.of(fareAttributeD.getId(), fareAttributeD2.getId())
+ )
+ );
+
+ Itinerary i2 = newItinerary(B1)
+ .bus(routeAgency1, 1, T11_06, T11_12, B1)
+ .bus(routeAgency3, 1, T11_14, T11_15, B2)
+ .build();
+
+ args.add(
+ Arguments.of(
+ "",
+ hslFareService,
+ i2,
+ List.of(fareAttributeAB.getId(), fareAttributeAgency3.getId())
+ )
+ );
return args;
}
diff --git a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java
index 1d358e17a71..c57a05735c0 100644
--- a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java
+++ b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java
@@ -31,13 +31,16 @@
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
+import java.util.stream.Stream;
import javax.annotation.Nonnull;
import org.glassfish.jersey.message.internal.OutboundJaxrsResponse;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
+import org.opentripplanner._support.text.I18NStrings;
import org.opentripplanner._support.time.ZoneIds;
import org.opentripplanner.ext.fares.FaresToItineraryMapper;
import org.opentripplanner.ext.fares.impl.DefaultFareService;
+import org.opentripplanner.framework.collection.ListUtils;
import org.opentripplanner.framework.geometry.WgsCoordinate;
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.framework.i18n.NonLocalizedString;
@@ -62,6 +65,8 @@
import org.opentripplanner.routing.core.FareType;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graphfinder.GraphFinder;
+import org.opentripplanner.routing.impl.TransitAlertServiceImpl;
+import org.opentripplanner.routing.services.TransitAlertService;
import org.opentripplanner.routing.vehicle_parking.VehicleParking;
import org.opentripplanner.service.vehiclepositions.internal.DefaultVehiclePositionService;
import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService;
@@ -70,6 +75,7 @@
import org.opentripplanner.transit.model._data.TransitModelForTest;
import org.opentripplanner.transit.model.basic.Money;
import org.opentripplanner.transit.model.basic.TransitMode;
+import org.opentripplanner.transit.model.framework.AbstractBuilder;
import org.opentripplanner.transit.model.framework.Deduplicator;
import org.opentripplanner.transit.model.network.TripPattern;
import org.opentripplanner.transit.model.site.RegularStop;
@@ -109,7 +115,8 @@ static void setup() {
var stopModel = StopModel.of();
PlanTestConstants.listStops().forEach(sl -> stopModel.withRegularStop((RegularStop) sl));
- var transitModel = new TransitModel(stopModel.build(), DEDUPLICATOR);
+ var model = stopModel.build();
+ var transitModel = new TransitModel(model, DEDUPLICATOR);
final TripPattern pattern = TransitModelForTest.pattern(BUS).build();
var trip = TransitModelForTest.trip("123").withHeadsign(I18NString.of("Trip Headsign")).build();
@@ -128,7 +135,7 @@ static void setup() {
TransitModelForTest
.route(m.name())
.withMode(m)
- .withLongName(new NonLocalizedString("Long name for %s".formatted(m)))
+ .withLongName(I18NString.of("Long name for %s".formatted(m)))
.build()
)
.toList();
@@ -175,27 +182,40 @@ static void setup() {
railLeg.withAccessibilityScore(.3f);
+ var entitySelector = new EntitySelector.Stop(A.stop.getId());
var alert = TransitAlert
.of(id("an-alert"))
- .withHeaderText(new NonLocalizedString("A header"))
- .withDescriptionText(new NonLocalizedString("A description"))
- .withUrl(new NonLocalizedString("https://example.com"))
+ .withHeaderText(I18NString.of("A header"))
+ .withDescriptionText(I18NString.of("A description"))
+ .withUrl(I18NString.of("https://example.com"))
.withCause(AlertCause.MAINTENANCE)
.withEffect(AlertEffect.REDUCED_SERVICE)
.withSeverity(AlertSeverity.VERY_SEVERE)
- .addEntity(new EntitySelector.Stop(A.stop.getId()))
+ .addEntity(entitySelector)
.addTimePeriod(
new TimePeriod(ALERT_START_TIME.getEpochSecond(), ALERT_END_TIME.getEpochSecond())
)
.build();
+
railLeg.addAlert(alert);
var transitService = new DefaultTransitService(transitModel) {
+ private final TransitAlertService alertService = new TransitAlertServiceImpl(transitModel);
+
@Override
public List getModesOfStopLocation(StopLocation stop) {
return List.of(BUS, FERRY);
}
+
+ @Override
+ public TransitAlertService getTransitAlertService() {
+ return alertService;
+ }
};
+
+ var alerts = ListUtils.combine(List.of(alert), getTransitAlert(entitySelector));
+ transitService.getTransitAlertService().setAlerts(alerts);
+
context =
new GraphQLRequestContext(
new TestRoutingService(List.of(i1)),
@@ -222,10 +242,10 @@ void graphQL(Path path) throws IOException {
Locale.ENGLISH,
context
);
- var actualJson = extracted(response);
+ var actualJson = responseBody(response);
assertEquals(200, response.getStatus());
- Path expectationFile = getPath(path);
+ Path expectationFile = getExpectation(path);
if (!expectationFile.toFile().exists()) {
Files.writeString(
@@ -242,11 +262,32 @@ void graphQL(Path path) throws IOException {
assertEqualJson(expectedJson, actualJson);
}
+ @Nonnull
+ private static List getTransitAlert(EntitySelector.Stop entitySelector) {
+ var alertWithoutDescription = TransitAlert
+ .of(id("no-description"))
+ .withHeaderText(I18NStrings.TRANSLATED_STRING_1)
+ .addEntity(entitySelector);
+
+ var alertWithoutHeader = TransitAlert
+ .of(id("no-header"))
+ .withDescriptionText(I18NStrings.TRANSLATED_STRING_2)
+ .addEntity(entitySelector);
+ var alertWithNothing = TransitAlert
+ .of(id("neither-header-nor-description"))
+ .addEntity(entitySelector);
+
+ return Stream
+ .of(alertWithoutDescription, alertWithoutHeader, alertWithNothing)
+ .map(AbstractBuilder::build)
+ .toList();
+ }
+
@Nonnull
private static WalkStepBuilder walkStep(String name) {
return WalkStep
.builder()
- .withDirectionText(new NonLocalizedString(name))
+ .withDirectionText(I18NString.of(name))
.withStartLocation(WgsCoordinate.GREENWICH)
.withAngle(10);
}
@@ -268,7 +309,7 @@ private static FareProduct fareProduct(String name) {
* subdirectories are expected to be in the same directory.
*/
@Nonnull
- private static Path getPath(Path path) {
+ private static Path getExpectation(Path path) {
return path
.getParent()
.getParent()
@@ -276,7 +317,7 @@ private static Path getPath(Path path) {
.resolve(path.getFileName().toString().replace(".graphql", ".json"));
}
- private static String extracted(Response response) {
+ private static String responseBody(Response response) {
if (response instanceof OutboundJaxrsResponse outbound) {
return (String) outbound.getContext().getEntity();
}
diff --git a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/BikesAllowedMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/BikesAllowedMapperTest.java
new file mode 100644
index 00000000000..ba1b6a2a5f8
--- /dev/null
+++ b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/BikesAllowedMapperTest.java
@@ -0,0 +1,21 @@
+package org.opentripplanner.ext.gtfsgraphqlapi.mapping;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Arrays;
+import org.junit.jupiter.api.Test;
+import org.opentripplanner.transit.model.network.BikeAccess;
+
+class BikesAllowedMapperTest {
+
+ @Test
+ void mapping() {
+ Arrays
+ .stream(BikeAccess.values())
+ .filter(ba -> ba != BikeAccess.UNKNOWN)
+ .forEach(d -> {
+ var mapped = BikesAllowedMapper.map(d);
+ assertEquals(d.toString(), mapped.toString());
+ });
+ }
+}
diff --git a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/StreetNoteMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/StreetNoteMapperTest.java
index 976db86fbe5..5c7cd8be008 100644
--- a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/StreetNoteMapperTest.java
+++ b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/StreetNoteMapperTest.java
@@ -1,13 +1,13 @@
package org.opentripplanner.ext.gtfsgraphqlapi.mapping;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.Date;
import java.util.Locale;
+import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.opentripplanner.routing.alertpatch.TransitAlert;
import org.opentripplanner.street.model.note.StreetNote;
@@ -25,9 +25,9 @@ class StreetNoteMapperTest {
void mapRegularAlert() {
var note = note();
TransitAlert alert = StreetNoteMapper.mapStreetNoteToAlert(note);
- assertEquals(TEST_STREET_NOTE_HEADER, alert.headerText().toString(Locale.ROOT));
- assertEquals(TEST_STREET_NOTE_DESCRIPTION, alert.descriptionText().toString(Locale.ROOT));
- assertEquals(TEST_STREET_NOTE_URL, alert.url().toString(Locale.ROOT));
+ assertEquals(TEST_STREET_NOTE_HEADER, alert.headerText().get().toString(Locale.ROOT));
+ assertEquals(TEST_STREET_NOTE_DESCRIPTION, alert.descriptionText().get().toString(Locale.ROOT));
+ assertEquals(TEST_STREET_NOTE_URL, alert.url().get().toString(Locale.ROOT));
assertEquals(START_INSTANCE, alert.getEffectiveStartDate());
assertEquals(END_INSTANCE, alert.getEffectiveEndDate());
}
@@ -37,7 +37,7 @@ void mapNullUrl() {
var note = note();
note.url = null;
TransitAlert alert = StreetNoteMapper.mapStreetNoteToAlert(note);
- assertNull(alert.url());
+ assertEquals(Optional.empty(), alert.url());
}
@Test
diff --git a/src/ext-test/resources/gtfsgraphqlapi/expectations/alerts.json b/src/ext-test/resources/gtfsgraphqlapi/expectations/alerts.json
new file mode 100644
index 00000000000..eb7b3d24154
--- /dev/null
+++ b/src/ext-test/resources/gtfsgraphqlapi/expectations/alerts.json
@@ -0,0 +1,64 @@
+{
+ "data" : {
+ "alerts" : [
+ {
+ "id" : "QWxlcnQ6RjpuZWl0aGVyLWhlYWRlci1ub3ItZGVzY3JpcHRpb24",
+ "alertHeaderText" : "",
+ "alertDescriptionText" : "",
+ "alertUrl" : null,
+ "alertDescriptionTextTranslations" : [ ],
+ "alertHeaderTextTranslations" : [ ]
+ },
+ {
+ "id" : "QWxlcnQ6Rjpuby1oZWFkZXI",
+ "alertHeaderText" : "Second string",
+ "alertDescriptionText" : "Second string",
+ "alertUrl" : null,
+ "alertDescriptionTextTranslations" : [
+ {
+ "language" : null,
+ "text" : "Second string"
+ },
+ {
+ "language" : "de",
+ "text" : "Zweite Zeichenabfolge"
+ },
+ {
+ "language" : "fi",
+ "text" : "Etkö ole varma, mitä tämä tarkoittaa"
+ }
+ ],
+ "alertHeaderTextTranslations" : [ ]
+ },
+ {
+ "id" : "QWxlcnQ6Rjphbi1hbGVydA",
+ "alertHeaderText" : "A header",
+ "alertDescriptionText" : "A description",
+ "alertUrl" : "https://example.com",
+ "alertDescriptionTextTranslations" : [ ],
+ "alertHeaderTextTranslations" : [ ]
+ },
+ {
+ "id" : "QWxlcnQ6Rjpuby1kZXNjcmlwdGlvbg",
+ "alertHeaderText" : "First string",
+ "alertDescriptionText" : "First string",
+ "alertUrl" : null,
+ "alertDescriptionTextTranslations" : [ ],
+ "alertHeaderTextTranslations" : [
+ {
+ "text" : "First string",
+ "language" : null
+ },
+ {
+ "text" : "Erste Zeichenabfolge",
+ "language" : "de"
+ },
+ {
+ "text" : "Minulla ei ole aavistustakaan kuinka puhua suomea",
+ "language" : "fi"
+ }
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/src/ext-test/resources/gtfsgraphqlapi/expectations/routes.json b/src/ext-test/resources/gtfsgraphqlapi/expectations/routes.json
index e2bdb289c32..d6cb1b481ed 100644
--- a/src/ext-test/resources/gtfsgraphqlapi/expectations/routes.json
+++ b/src/ext-test/resources/gtfsgraphqlapi/expectations/routes.json
@@ -9,7 +9,8 @@
"gtfsId" : "F:A1",
"name" : "Agency Test"
},
- "mode" : "CARPOOL"
+ "mode" : "CARPOOL",
+ "bikesAllowed" : "NO_INFORMATION"
},
{
"longName" : "Long name for SUBWAY",
@@ -19,7 +20,8 @@
"gtfsId" : "F:A1",
"name" : "Agency Test"
},
- "mode" : "SUBWAY"
+ "mode" : "SUBWAY",
+ "bikesAllowed" : "NO_INFORMATION"
},
{
"longName" : "Long name for BUS",
@@ -29,7 +31,8 @@
"gtfsId" : "F:A1",
"name" : "Agency Test"
},
- "mode" : "BUS"
+ "mode" : "BUS",
+ "bikesAllowed" : "NO_INFORMATION"
},
{
"longName" : "Long name for FERRY",
@@ -39,7 +42,8 @@
"gtfsId" : "F:A1",
"name" : "Agency Test"
},
- "mode" : "FERRY"
+ "mode" : "FERRY",
+ "bikesAllowed" : "NO_INFORMATION"
},
{
"longName" : "Long name for COACH",
@@ -49,7 +53,8 @@
"gtfsId" : "F:A1",
"name" : "Agency Test"
},
- "mode" : "COACH"
+ "mode" : "COACH",
+ "bikesAllowed" : "NO_INFORMATION"
},
{
"longName" : "Long name for TRAM",
@@ -59,7 +64,8 @@
"gtfsId" : "F:A1",
"name" : "Agency Test"
},
- "mode" : "TRAM"
+ "mode" : "TRAM",
+ "bikesAllowed" : "NO_INFORMATION"
},
{
"longName" : "Long name for CABLE_CAR",
@@ -69,7 +75,8 @@
"gtfsId" : "F:A1",
"name" : "Agency Test"
},
- "mode" : "CABLE_CAR"
+ "mode" : "CABLE_CAR",
+ "bikesAllowed" : "NO_INFORMATION"
},
{
"longName" : "Long name for FUNICULAR",
@@ -79,7 +86,8 @@
"gtfsId" : "F:A1",
"name" : "Agency Test"
},
- "mode" : "FUNICULAR"
+ "mode" : "FUNICULAR",
+ "bikesAllowed" : "NO_INFORMATION"
},
{
"longName" : "Long name for RAIL",
@@ -89,7 +97,8 @@
"gtfsId" : "F:A1",
"name" : "Agency Test"
},
- "mode" : "RAIL"
+ "mode" : "RAIL",
+ "bikesAllowed" : "NO_INFORMATION"
},
{
"longName" : "Long name for MONORAIL",
@@ -99,7 +108,8 @@
"gtfsId" : "F:A1",
"name" : "Agency Test"
},
- "mode" : "MONORAIL"
+ "mode" : "MONORAIL",
+ "bikesAllowed" : "NO_INFORMATION"
},
{
"longName" : "Long name for GONDOLA",
@@ -109,7 +119,8 @@
"gtfsId" : "F:A1",
"name" : "Agency Test"
},
- "mode" : "GONDOLA"
+ "mode" : "GONDOLA",
+ "bikesAllowed" : "NO_INFORMATION"
},
{
"longName" : "Long name for TROLLEYBUS",
@@ -119,7 +130,8 @@
"gtfsId" : "F:A1",
"name" : "Agency Test"
},
- "mode" : "TROLLEYBUS"
+ "mode" : "TROLLEYBUS",
+ "bikesAllowed" : "NO_INFORMATION"
},
{
"longName" : "Long name for AIRPLANE",
@@ -129,7 +141,8 @@
"gtfsId" : "F:A1",
"name" : "Agency Test"
},
- "mode" : "AIRPLANE"
+ "mode" : "AIRPLANE",
+ "bikesAllowed" : "NO_INFORMATION"
},
{
"longName" : "Long name for TAXI",
@@ -139,7 +152,8 @@
"gtfsId" : "F:A1",
"name" : "Agency Test"
},
- "mode" : "TAXI"
+ "mode" : "TAXI",
+ "bikesAllowed" : "NO_INFORMATION"
}
]
}
diff --git a/src/ext-test/resources/gtfsgraphqlapi/queries/alerts.graphql b/src/ext-test/resources/gtfsgraphqlapi/queries/alerts.graphql
new file mode 100644
index 00000000000..923d9f027cb
--- /dev/null
+++ b/src/ext-test/resources/gtfsgraphqlapi/queries/alerts.graphql
@@ -0,0 +1,18 @@
+{
+ alerts {
+ id
+ alertHeaderText
+ alertDescriptionText
+ alertUrl
+ # these translations are a bit questionable, the above fields are already translated into the
+ # language selected in the request
+ alertDescriptionTextTranslations {
+ language
+ text
+ }
+ alertHeaderTextTranslations {
+ text
+ language
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ext-test/resources/gtfsgraphqlapi/queries/routes.graphql b/src/ext-test/resources/gtfsgraphqlapi/queries/routes.graphql
index 66d7f370c3f..38585d6a836 100644
--- a/src/ext-test/resources/gtfsgraphqlapi/queries/routes.graphql
+++ b/src/ext-test/resources/gtfsgraphqlapi/queries/routes.graphql
@@ -8,5 +8,6 @@
name
}
mode
+ bikesAllowed
}
}
\ No newline at end of file
diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java b/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java
index 50871355951..744782f7b56 100644
--- a/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java
+++ b/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java
@@ -12,6 +12,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.opentripplanner.ext.fares.model.FareAttribute;
import org.opentripplanner.ext.fares.model.FareRuleSet;
@@ -60,11 +61,10 @@ class FareSearch {
record FareAndId(Money fare, FeedScopedId fareId) {}
/**
- * This fare service module handles the cases that GTFS handles within a single feed. It cannot
- * necessarily handle multi-feed graphs, because a rule-less fare attribute might be applied to
- * rides on routes in another feed, for example. For more interesting fare structures like New
- * York's MTA, or cities with multiple feeds and inter-feed transfer rules, you get to implement
- * your own FareService. See this thread on gtfs-changes explaining the proper interpretation of
+ * This fare service module handles GTFS fares in multiple feeds separately so that each fare attribute
+ * is only applicable for legs that operated by an agency within the same feed. Interfeed transfer rules
+ * are not considered in this fare service and for those situations you get to implement your own Fare Service
+ * See this thread on gtfs-changes explaining the proper interpretation of
* fares.txt:
* http://groups.google.com/group/gtfs-changes/browse_thread/thread/8a4a48ae1e742517/4f81b826cb732f3b
*/
@@ -72,6 +72,8 @@ public class DefaultFareService implements FareService {
private static final Logger LOG = LoggerFactory.getLogger(DefaultFareService.class);
+ private final float UNKNOWN_FARE_PRICE = -0.01f;
+
/** For each fare type (regular, student, etc...) the collection of rules that apply. */
protected Map> fareRulesPerType;
@@ -102,18 +104,89 @@ public ItineraryFares calculateFares(Itinerary itinerary) {
if (fareLegs.isEmpty()) {
return null;
}
+ var fareLegsByFeed = fareLegs
+ .stream()
+ .collect(Collectors.groupingBy(leg -> leg.getAgency().getId().getFeedId()));
+ var fareRulesByTypeAndFeed = fareRulesPerType
+ .entrySet()
+ .stream()
+ .collect(
+ Collectors.toMap(
+ Map.Entry::getKey,
+ rules ->
+ rules
+ .getValue()
+ .stream()
+ .collect(Collectors.groupingBy(rule -> rule.getFareAttribute().getId().getFeedId()))
+ )
+ );
ItineraryFares fare = ItineraryFares.empty();
boolean hasFare = false;
- for (Map.Entry> kv : fareRulesPerType.entrySet()) {
- FareType fareType = kv.getKey();
- Collection fareRules = kv.getValue();
- // Get the currency from the first fareAttribute, assuming that all tickets use the same currency.
- if (fareRules.size() > 0) {
- Currency currency = Currency.getInstance(
- fareRules.iterator().next().getFareAttribute().getCurrencyType()
+ for (FareType fareType : fareRulesPerType.keySet()) {
+ List components = new ArrayList<>();
+ List fares = new ArrayList<>();
+ ItineraryFares currentFare = ItineraryFares.empty();
+ boolean legWithoutRulesFound = false;
+ boolean legsWithoutMatchingRulesFound = false;
+ boolean fareTypeHasFare = false;
+ for (String feedId : fareLegsByFeed.keySet()) {
+ var fareRules = fareRulesByTypeAndFeed.get(fareType).get(feedId);
+
+ // Get the currency from the first fareAttribute, assuming that all tickets use the same currency.
+ if (fareRules != null && fareRules.size() > 0) {
+ Currency currency = Currency.getInstance(
+ fareRules.iterator().next().getFareAttribute().getCurrencyType()
+ );
+ boolean feedHasFare = populateFare(
+ currentFare,
+ currency,
+ fareType,
+ fareLegsByFeed.get(feedId),
+ fareRules
+ );
+
+ if (!feedHasFare) {
+ legsWithoutMatchingRulesFound = true;
+ }
+ hasFare = feedHasFare || hasFare; // Other feeds might still have fare for some legs
+
+ components.addAll(currentFare.getComponents(fareType));
+ fare.addFare(fareType, currentFare.getFare(fareType));
+ fares.add(currentFare.getFare(fareType));
+
+ // If all the legs are from one feed, consider itinerary products
+ if (fareLegs.equals(fareLegsByFeed.get(feedId))) {
+ fare.addItineraryProducts(currentFare.getItineraryProducts());
+ }
+ } else {
+ legWithoutRulesFound = true;
+ }
+ }
+
+ fare.addFareComponent(fareType, components);
+
+ // No fares will be discovered after this point
+ if (!hasFare) {
+ continue;
+ }
+
+ // Accumulate the final price of the fare or indicate that no final fare could be found
+ if (legWithoutRulesFound || legsWithoutMatchingRulesFound) {
+ fare.addFare(
+ fareType,
+ Money.ofFractionalAmount(fares.get(0).currency(), UNKNOWN_FARE_PRICE)
+ );
+ } else {
+ fare.addFare(
+ fareType,
+ fares
+ .stream()
+ .reduce(
+ Money.ofFractionalAmount(fare.getFare(fareType).currency(), 0),
+ (r1, r2) -> r1.plus(r2)
+ )
);
- hasFare = populateFare(fare, currency, fareType, fareLegs, fareRules);
}
}
return hasFare ? fare : null;
@@ -202,11 +275,11 @@ protected Optional getBestFareAndId(
String startZone = firstRide.getFrom().stop.getFirstZoneAsString();
String endZone = null;
// stops don't really have an agency id, they have the per-feed default id
- String feedId = firstRide.getTrip().getId().getFeedId();
+ String feedId = firstRide.getAgency().getId().getFeedId();
ZonedDateTime lastRideStartTime = null;
ZonedDateTime lastRideEndTime = null;
for (var leg : legs) {
- if (!leg.getTrip().getId().getFeedId().equals(feedId)) {
+ if (!leg.getAgency().getId().getFeedId().equals(feedId)) {
LOG.debug("skipped multi-feed ride sequence {}", legs);
return Optional.empty();
}
diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java b/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java
index e8ec215b149..ba62e899f70 100644
--- a/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java
+++ b/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java
@@ -1,17 +1,24 @@
package org.opentripplanner.ext.fares.impl;
+import com.google.common.collect.Sets;
import java.time.Duration;
import java.time.ZonedDateTime;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Currency;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
import org.opentripplanner.ext.fares.model.FareAttribute;
import org.opentripplanner.ext.fares.model.FareRuleSet;
import org.opentripplanner.ext.fares.model.RouteOriginDestination;
+import org.opentripplanner.model.fare.ItineraryFares;
+import org.opentripplanner.model.plan.Itinerary;
import org.opentripplanner.model.plan.Leg;
import org.opentripplanner.model.plan.ScheduledTransitLeg;
+import org.opentripplanner.routing.core.FareComponent;
import org.opentripplanner.routing.core.FareType;
import org.opentripplanner.transit.model.basic.Money;
import org.slf4j.Logger;
@@ -51,6 +58,19 @@ protected Optional getBestFareAndId(
String agency = null;
boolean singleAgency = true;
+ // Do not consider fares for legs that do not have fare rules in the same feed
+ Set fareRuleFeedIds = fareRules
+ .stream()
+ .map(fr -> fr.getFareAttribute().getId().getFeedId())
+ .collect(Collectors.toSet());
+ Set legFeedIds = legs
+ .stream()
+ .map(leg -> leg.getAgency().getId().getFeedId())
+ .collect(Collectors.toSet());
+ if (!Sets.difference(legFeedIds, fareRuleFeedIds).isEmpty()) {
+ return Optional.empty();
+ }
+
for (Leg leg : legs) {
lastRideStartTime = leg.getStartTime();
if (agency == null) {
@@ -58,11 +78,12 @@ protected Optional getBestFareAndId(
} else if (agency != leg.getAgency().getId().getId().toString()) {
singleAgency = false;
}
+
/* HSL specific logic: all exception routes start and end from the defined zone set,
but visit temporarily (maybe 1 stop only) an 'external' zone */
Money bestSpecialFare = MAX_PRICE;
- Set ruleZones = null;
+ Set ruleZones = null;
for (FareRuleSet ruleSet : fareRules) {
if (
ruleSet.hasAgencyDefined() &&
@@ -114,6 +135,7 @@ but visit temporarily (maybe 1 stop only) an 'external' zone */
}
}
}
+
if (ruleZones != null) { // the special case
// evaluate boolean ride.zones AND rule.zones
Set zoneIntersection = new HashSet(
diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java b/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java
index 775a64323e0..f375b24fefa 100644
--- a/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java
+++ b/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java
@@ -2,13 +2,10 @@
import java.util.Collection;
import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Function;
-import java.util.function.Predicate;
import java.util.stream.Stream;
import org.opentripplanner.framework.geometry.WgsCoordinate;
import org.opentripplanner.framework.i18n.I18NString;
+import org.opentripplanner.framework.lang.PredicateUtils;
import org.opentripplanner.transit.model.site.StopLocation;
import org.opentripplanner.transit.model.site.StopLocationsGroup;
import org.opentripplanner.transit.service.TransitService;
@@ -44,7 +41,7 @@ Stream generateStopClusters(
.filter(sl -> sl.getName() != null)
// if they are very close to each other and have the same name, only one is chosen (at random)
.filter(
- distinctByKey(sl ->
+ PredicateUtils.distinctByKey(sl ->
new DeduplicationKey(sl.getName(), sl.getCoordinate().roundToApproximate10m())
)
)
@@ -84,10 +81,5 @@ private static StopCluster.Coordinate toCoordinate(WgsCoordinate c) {
return new StopCluster.Coordinate(c.latitude(), c.longitude());
}
- private static Predicate distinctByKey(Function super T, ?> keyExtractor) {
- Set