diff --git a/.github/workflows/prune-container-images.yml b/.github/workflows/prune-container-images.yml index f3987a064a9..c1653701c3b 100644 --- a/.github/workflows/prune-container-images.yml +++ b/.github/workflows/prune-container-images.yml @@ -19,4 +19,4 @@ jobs: # remove all snapshot container images that have not been pulled for over a year # --keep-semver makes sure that any image with a x.y.z version scheme is unaffected by this pip install prune-container-repo==0.0.4 - prune-container-repo -u ${CONTAINER_REGISTRY_USER} -r ${CONTAINER_REPO} --days=90 --keep-semver --activate + prune-container-repo -u ${CONTAINER_REGISTRY_USER} -r ${CONTAINER_REPO} --days=365 --keep-semver --activate diff --git a/doc-templates/OsmMapper.md b/doc-templates/OsmMapper.md new file mode 100644 index 00000000000..1690deac627 --- /dev/null +++ b/doc-templates/OsmMapper.md @@ -0,0 +1,30 @@ +# OSM tag mapping + +This page is intended to give an overview of which OpenStreetMap(OSM) tags OTP uses to evaluate its +walking and bicycling instructions. If a tag is not part of the documentation on this page +then this tag mapper (profile) does not use it. + +The exception are access permissions and wheelchair accessibility tags like + +- `access=no` +- `wheelchair=no` +- `oneway=yes` + +These are identical for all mappers and not separately listed on this page. + +### Way properties + +Way properties set a way's permission and optionally influences its walk and bicycle safety factors. + +These factors determine how desirable an OSM way is when routing for cyclists and pedestrians. +Lower safety values make an OSM way more desirable and higher values less desirable. + + + +### Safety mixins + +Mixins are selectors that have only an effect on the bicycle and walk safety factors but not on the +permission of an OSM way. Their safety values are multiplied with the base values from the selected +way properties. Multiple mixins can apply to the same way and their effects compound. + + diff --git a/doc-templates/sandbox/VehicleRentalServiceDirectory.md b/doc-templates/sandbox/VehicleRentalServiceDirectory.md index d9908de1a90..787a17e90e4 100644 --- a/doc-templates/sandbox/VehicleRentalServiceDirectory.md +++ b/doc-templates/sandbox/VehicleRentalServiceDirectory.md @@ -1,10 +1,10 @@ -# Vehicle Rental Service Directory API support. +# Vehicle Rental Service Directory API support -This adds support for the GBFS service directory endpoint component located at -https://github.com/entur/lamassu. OTP uses the service directory to lookup and connect to all GBFS -endpoints registered in the directory. This simplifies the management of the GBFS endpoints, since -multiple services/components like OTP can connect to the directory and get the necessary -configuration from it. +This adds support for the GBFS service directory endpoint component +[Lamassu](https://github.com/entur/lamassu). +OTP uses the service directory to lookup and connects to all GBFS endpoints registered in the +directory. This simplifies the management of the GBFS endpoints, since multiple services/components +like OTP can connect to the directory and get the necessary configuration from it. ## Contact Info @@ -17,6 +17,7 @@ configuration from it. - Initial implementation of bike share updater API support - Make json tag names configurable [#3447](https://github.com/opentripplanner/OpenTripPlanner/pull/3447) - Enable GBFS geofencing with VehicleRentalServiceDirectory [#5324](https://github.com/opentripplanner/OpenTripPlanner/pull/5324) +- Enable `allowKeepingVehicleAtDestination` [#5944](https://github.com/opentripplanner/OpenTripPlanner/pull/5944) ## Configuration diff --git a/docs/Changelog.md b/docs/Changelog.md index 5712915216c..6d84b377de7 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -41,6 +41,11 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Fix NullPointerException in stop transfer priority cost vector generation [#5943](https://github.com/opentripplanner/OpenTripPlanner/pull/5943) - Convert transferSlack configuration to duration [#5897](https://github.com/opentripplanner/OpenTripPlanner/pull/5897) - Expose stop transfer priority in Transmodel API [#5942](https://github.com/opentripplanner/OpenTripPlanner/pull/5942) +- Add rental system to GraphQL API [#5909](https://github.com/opentripplanner/OpenTripPlanner/pull/5909) +- Improve handling of SIRI added trip with unresolvable agency [#5931](https://github.com/opentripplanner/OpenTripPlanner/pull/5931) +- Fix copy-on-write in TimetableSnapshot [#5941](https://github.com/opentripplanner/OpenTripPlanner/pull/5941) +- Generate documentation for OSM tag mappers [#5929](https://github.com/opentripplanner/OpenTripPlanner/pull/5929) +- Disable Legacy REST API by default [#5948](https://github.com/opentripplanner/OpenTripPlanner/pull/5948) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.5.0 (2024-03-13) diff --git a/docs/Configuration.md b/docs/Configuration.md index 05611e23628..a4e8b5100a4 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -241,7 +241,7 @@ Here is a list of all features which can be toggled on/off and their default val | `FaresV2` | Enable import of GTFS-Fares v2 data. | | ✓️ | | `FlexRouting` | Enable FLEX routing. | | ✓️ | | `GoogleCloudStorage` | Enable Google Cloud Storage integration. | | ✓️ | -| `LegacyRestApi` | Enable legacy REST API. This API will be removed in the future. | ✓️ | ✓️ | +| `LegacyRestApi` | Enable legacy REST API. This API will be removed in the future. | | ✓️ | | `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with real-time data | | ✓️ | | `ReportApi` | Enable the report API. | | ✓️ | | `RestAPIPassInDefaultConfigAsJson` | Enable a default RouteRequest to be passed in as JSON on the REST API - FOR DEBUGGING ONLY! | | | diff --git a/docs/osm/Default.md b/docs/osm/Default.md new file mode 100644 index 00000000000..814420b791f --- /dev/null +++ b/docs/osm/Default.md @@ -0,0 +1,214 @@ +# OSM tag mapping + +This page is intended to give an overview of which OpenStreetMap(OSM) tags OTP uses to evaluate its +walking and bicycling instructions. If a tag is not part of the documentation on this page +then this tag mapper (profile) does not use it. + +The exception are access permissions and wheelchair accessibility tags like + +- `access=no` +- `wheelchair=no` +- `oneway=yes` + +These are identical for all mappers and not separately listed on this page. + +### Way properties + +Way properties set a way's permission and optionally influences its walk and bicycle safety factors. + +These factors determine how desirable an OSM way is when routing for cyclists and pedestrians. +Lower safety values make an OSM way more desirable and higher values less desirable. + + + + +| specifier | permission | bike safety | walk safety | +|---------------------------------------------------------|--------------------------|-------------------------------|-------------| +| `mtb:scale=3` | `NONE` | | | +| `mtb:scale=4` | `NONE` | | | +| `mtb:scale=5` | `NONE` | | | +| `mtb:scale=6` | `NONE` | | | +| `highway=corridor` | `PEDESTRIAN` | | | +| `highway=steps` | `PEDESTRIAN` | | | +| `highway=crossing` | `PEDESTRIAN` | | | +| `highway=platform` | `PEDESTRIAN` | | | +| `public_transport=platform` | `PEDESTRIAN` | | | +| `railway=platform` | `PEDESTRIAN` | | | +| `footway=sidewalk; highway=footway` | `PEDESTRIAN` | | | +| `mtb:scale=1` | `PEDESTRIAN` | | | +| `mtb:scale=2` | `PEDESTRIAN` | | | +| `mtb:scale=0` | `PEDESTRIAN_AND_BICYCLE` | | | +| `highway=cycleway` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | | +| `highway=path` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | +| `highway=pedestrian` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | +| `highway=footway` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | +| `highway=bridleway` | `PEDESTRIAN_AND_BICYCLE` | 1.3 | | +| `highway=living_street` | `ALL` | 0.9 | | +| `highway=unclassified` | `ALL` | | | +| `highway=road` | `ALL` | | | +| `highway=byway` | `ALL` | 1.3 | | +| `highway=track` | `ALL` | 1.3 | | +| `highway=service` | `ALL` | 1.1 | | +| `highway=residential` | `ALL` | 0.98 | | +| `highway=residential_link` | `ALL` | 0.98 | | +| `highway=tertiary` | `ALL` | | | +| `highway=tertiary_link` | `ALL` | | | +| `highway=secondary` | `ALL` | 1.5 | | +| `highway=secondary_link` | `ALL` | 1.5 | | +| `highway=primary` | `ALL` | 2.06 | | +| `highway=primary_link` | `ALL` | 2.06 | | +| `highway=trunk_link` | `CAR` | 2.06 | | +| `highway=motorway_link` | `CAR` | 2.06 | | +| `highway=trunk` | `CAR` | 7.47 | | +| `highway=motorway` | `CAR` | 8.0 | | +| `present(highway); cycleway=lane` | `PEDESTRIAN_AND_BICYCLE` | 0.87 | | +| `highway=service; cycleway=lane` | `ALL` | 0.77 | | +| `highway=residential; cycleway=lane` | `ALL` | 0.77 | | +| `highway=residential_link; cycleway=lane` | `ALL` | 0.77 | | +| `highway=tertiary; cycleway=lane` | `ALL` | 0.87 | | +| `highway=tertiary_link; cycleway=lane` | `ALL` | 0.87 | | +| `highway=secondary; cycleway=lane` | `ALL` | 0.96 | | +| `highway=secondary_link; cycleway=lane` | `ALL` | 0.96 | | +| `highway=primary; cycleway=lane` | `ALL` | 1.15 | | +| `highway=primary_link; cycleway=lane` | `ALL` | 1.15 | | +| `highway=trunk; cycleway=lane` | `BICYCLE_AND_CAR` | 1.5 | | +| `highway=trunk_link; cycleway=lane` | `BICYCLE_AND_CAR` | 1.15 | | +| `highway=motorway; cycleway=lane` | `BICYCLE_AND_CAR` | 2.0 | | +| `highway=motorway_link; cycleway=lane` | `BICYCLE_AND_CAR` | 1.15 | | +| `present(highway); cycleway=share_busway` | `PEDESTRIAN_AND_BICYCLE` | 0.92 | | +| `highway=service; cycleway=share_busway` | `ALL` | 0.85 | | +| `highway=residential; cycleway=share_busway` | `ALL` | 0.85 | | +| `highway=residential_link; cycleway=share_busway` | `ALL` | 0.85 | | +| `highway=tertiary; cycleway=share_busway` | `ALL` | 0.92 | | +| `highway=tertiary_link; cycleway=share_busway` | `ALL` | 0.92 | | +| `highway=secondary; cycleway=share_busway` | `ALL` | 0.99 | | +| `highway=secondary_link; cycleway=share_busway` | `ALL` | 0.99 | | +| `highway=primary; cycleway=share_busway` | `ALL` | 1.25 | | +| `highway=primary_link; cycleway=share_busway` | `ALL` | 1.25 | | +| `highway=trunk; cycleway=share_busway` | `BICYCLE_AND_CAR` | 1.75 | | +| `highway=trunk_link; cycleway=share_busway` | `BICYCLE_AND_CAR` | 1.25 | | +| `highway=motorway; cycleway=share_busway` | `BICYCLE_AND_CAR` | 2.5 | | +| `highway=motorway_link; cycleway=share_busway` | `BICYCLE_AND_CAR` | 1.25 | | +| `present(highway); cycleway=opposite_lane` | `PEDESTRIAN_AND_BICYCLE` | forward: 1.0
back: 0.87 | | +| `highway=service; cycleway=opposite_lane` | `ALL` | forward: 1.1
back: 0.77 | | +| `highway=residential; cycleway=opposite_lane` | `ALL` | forward: 0.98
back: 0.77 | | +| `highway=residential_link; cycleway=opposite_lane` | `ALL` | forward: 0.98
back: 0.77 | | +| `highway=tertiary; cycleway=opposite_lane` | `ALL` | forward: 1.0
back: 0.87 | | +| `highway=tertiary_link; cycleway=opposite_lane` | `ALL` | forward: 1.0
back: 0.87 | | +| `highway=secondary; cycleway=opposite_lane` | `ALL` | forward: 1.5
back: 0.96 | | +| `highway=secondary_link; cycleway=opposite_lane` | `ALL` | forward: 1.5
back: 0.96 | | +| `highway=primary; cycleway=opposite_lane` | `ALL` | forward: 2.06
back: 1.15 | | +| `highway=primary_link; cycleway=opposite_lane` | `ALL` | forward: 2.06
back: 1.15 | | +| `highway=trunk; cycleway=opposite_lane` | `BICYCLE_AND_CAR` | forward: 7.47
back: 1.5 | | +| `highway=trunk_link; cycleway=opposite_lane` | `BICYCLE_AND_CAR` | forward: 2.06
back: 1.15 | | +| `present(highway); cycleway=track` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | +| `highway=service; cycleway=track` | `ALL` | 0.65 | | +| `highway=residential; cycleway=track` | `ALL` | 0.65 | | +| `highway=residential_link; cycleway=track` | `ALL` | 0.65 | | +| `highway=tertiary; cycleway=track` | `ALL` | 0.75 | | +| `highway=tertiary_link; cycleway=track` | `ALL` | 0.75 | | +| `highway=secondary; cycleway=track` | `ALL` | 0.8 | | +| `highway=secondary_link; cycleway=track` | `ALL` | 0.8 | | +| `highway=primary; cycleway=track` | `ALL` | 0.85 | | +| `highway=primary_link; cycleway=track` | `ALL` | 0.85 | | +| `highway=trunk; cycleway=track` | `BICYCLE_AND_CAR` | 0.95 | | +| `highway=trunk_link; cycleway=track` | `BICYCLE_AND_CAR` | 0.85 | | +| `present(highway); cycleway=opposite_track` | `PEDESTRIAN_AND_BICYCLE` | forward: 1.0
back: 0.75 | | +| `highway=service; cycleway=opposite_track` | `ALL` | forward: 1.1
back: 0.65 | | +| `highway=residential; cycleway=opposite_track` | `ALL` | forward: 0.98
back: 0.65 | | +| `highway=residential_link; cycleway=opposite_track` | `ALL` | forward: 0.98
back: 0.65 | | +| `highway=tertiary; cycleway=opposite_track` | `ALL` | forward: 1.0
back: 0.75 | | +| `highway=tertiary_link; cycleway=opposite_track` | `ALL` | forward: 1.0
back: 0.75 | | +| `highway=secondary; cycleway=opposite_track` | `ALL` | forward: 1.5
back: 0.8 | | +| `highway=secondary_link; cycleway=opposite_track` | `ALL` | forward: 1.5
back: 0.8 | | +| `highway=primary; cycleway=opposite_track` | `ALL` | forward: 2.06
back: 0.85 | | +| `highway=primary_link; cycleway=opposite_track` | `ALL` | forward: 2.06
back: 0.85 | | +| `highway=trunk; cycleway=opposite_track` | `BICYCLE_AND_CAR` | forward: 7.47
back: 0.95 | | +| `highway=trunk_link; cycleway=opposite_track` | `BICYCLE_AND_CAR` | forward: 2.06
back: 0.85 | | +| `present(highway); cycleway=shared_lane` | `PEDESTRIAN_AND_BICYCLE` | 0.77 | | +| `highway=service; cycleway=shared_lane` | `ALL` | 0.73 | | +| `highway=residential; cycleway=shared_lane` | `ALL` | 0.77 | | +| `highway=residential_link; cycleway=shared_lane` | `ALL` | 0.77 | | +| `highway=tertiary; cycleway=shared_lane` | `ALL` | 0.83 | | +| `highway=tertiary_link; cycleway=shared_lane` | `ALL` | 0.83 | | +| `highway=secondary; cycleway=shared_lane` | `ALL` | 1.25 | | +| `highway=secondary_link; cycleway=shared_lane` | `ALL` | 1.25 | | +| `highway=primary; cycleway=shared_lane` | `ALL` | 1.75 | | +| `highway=primary_link; cycleway=shared_lane` | `ALL` | 1.75 | | +| `present(highway); cycleway=opposite` | `PEDESTRIAN_AND_BICYCLE` | forward: 1.0
back: 1.4 | | +| `highway=service; cycleway=opposite` | `ALL` | 1.1 | | +| `highway=residential; cycleway=opposite` | `ALL` | 0.98 | | +| `highway=residential_link; cycleway=opposite` | `ALL` | 0.98 | | +| `highway=tertiary; cycleway=opposite` | `ALL` | | | +| `highway=tertiary_link; cycleway=opposite` | `ALL` | | | +| `highway=secondary; cycleway=opposite` | `ALL` | forward: 1.5
back: 1.71 | | +| `highway=secondary_link; cycleway=opposite` | `ALL` | forward: 1.5
back: 1.71 | | +| `highway=primary; cycleway=opposite` | `ALL` | forward: 2.06
back: 2.99 | | +| `highway=primary_link; cycleway=opposite` | `ALL` | forward: 2.06
back: 2.99 | | +| `highway=path; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | | +| `highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | +| `highway=footway; bicycle=yes; area=yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | +| `highway=pedestrian; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | +| `footway=sidewalk; highway=footway; bicycle=yes` | `PEDESTRIAN_AND_BICYCLE` | 2.5 | | +| `footway=sidewalk; highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | +| `highway=footway; footway=crossing` | `PEDESTRIAN_AND_BICYCLE` | 2.5 | | +| `highway=footway; footway=crossing; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | +| `highway=track; bicycle=yes` | `PEDESTRIAN_AND_BICYCLE` | 1.18 | | +| `highway=track; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.99 | | +| `highway=track; bicycle=yes; present(surface)` | `PEDESTRIAN_AND_BICYCLE` | 1.18 | | +| `highway=track; bicycle=designated; present(surface)` | `PEDESTRIAN_AND_BICYCLE` | 0.99 | | +| `highway=track; present(surface)` | `PEDESTRIAN_AND_BICYCLE` | 1.3 | | +| `present(highway); bicycle=designated` | `ALL` | 0.97 | | +| `highway=service; bicycle=designated` | `ALL` | 0.84 | | +| `highway=residential; bicycle=designated` | `ALL` | 0.95 | | +| `highway=unclassified; bicycle=designated` | `ALL` | 0.95 | | +| `highway=residential_link; bicycle=designated` | `ALL` | 0.95 | | +| `highway=tertiary; bicycle=designated` | `ALL` | 0.97 | | +| `highway=tertiary_link; bicycle=designated` | `ALL` | 0.97 | | +| `highway=secondary; bicycle=designated` | `ALL` | 1.46 | | +| `highway=secondary_link; bicycle=designated` | `ALL` | 1.46 | | +| `highway=primary; bicycle=designated` | `ALL` | 2.0 | | +| `highway=primary_link; bicycle=designated` | `ALL` | 2.0 | | +| `highway=trunk; bicycle=designated` | `BICYCLE_AND_CAR` | 7.25 | | +| `highway=trunk_link; bicycle=designated` | `BICYCLE_AND_CAR` | 2.0 | | +| `highway=motorway; bicycle=designated` | `BICYCLE_AND_CAR` | 7.76 | | +| `highway=motorway_link; bicycle=designated` | `BICYCLE_AND_CAR` | 2.0 | | + + + +### Safety mixins + +Mixins are selectors that have only an effect on the bicycle and walk safety factors but not on the +permission of an OSM way. Their safety values are multiplied with the base values from the selected +way properties. Multiple mixins can apply to the same way and their effects compound. + + + + +| matcher | bicycle safety | walk safety | +|------------------------------------------------------------|----------------|-------------| +| `lcn=yes¦rcn=yes¦ncn=yes¦bicycle_road=yes¦cyclestreet=yes` | 0.7 | | +| `surface=unpaved` | 1.18 | | +| `surface=compacted` | 1.18 | | +| `surface=wood` | 1.18 | | +| `surface=cobblestone` | 1.3 | | +| `surface=sett` | 1.3 | | +| `surface=unhewn_cobblestone` | 1.5 | | +| `surface=grass_paver` | 1.3 | | +| `surface=pebblestone` | 1.3 | | +| `surface=metal` | 1.3 | | +| `surface=ground` | 1.5 | | +| `surface=dirt` | 1.5 | | +| `surface=earth` | 1.5 | | +| `surface=grass` | 1.5 | | +| `surface=mud` | 1.5 | | +| `surface=woodchip` | 1.5 | | +| `surface=gravel` | 1.5 | | +| `surface=artifical_turf` | 1.5 | | +| `surface=sand` | 100.0 | | +| `foot=discouraged` | | 3.0 | +| `bicycle=discouraged` | 3.0 | | +| `foot=use_sidepath` | | 5.0 | +| `bicycle=use_sidepath` | 5.0 | | + + diff --git a/docs/osm/Finland.md b/docs/osm/Finland.md new file mode 100644 index 00000000000..8a60b5f0b13 --- /dev/null +++ b/docs/osm/Finland.md @@ -0,0 +1,259 @@ +# OSM tag mapping + +This page is intended to give an overview of which OpenStreetMap(OSM) tags OTP uses to evaluate its +walking and bicycling instructions. If a tag is not part of the documentation on this page +then this tag mapper (profile) does not use it. + +The exception are access permissions and wheelchair accessibility tags like + +- `access=no` +- `wheelchair=no` +- `oneway=yes` + +These are identical for all mappers and not separately listed on this page. + +### Way properties + +Way properties set a way's permission and optionally influences its walk and bicycle safety factors. + +These factors determine how desirable an OSM way is when routing for cyclists and pedestrians. +Lower safety values make an OSM way more desirable and higher values less desirable. + + + + +| specifier | permission | bike safety | walk safety | +|---------------------------------------------------------------------------------|--------------------------|-------------------------------|-------------| +| `highway=living_street` | `ALL` | 0.9 | | +| `highway=unclassified` | `ALL` | | | +| `highway=road` | `ALL` | | | +| `highway=byway` | `ALL` | 1.3 | | +| `highway=track` | `ALL` | 1.3 | | +| `highway=service` | `ALL` | 1.1 | | +| `highway=residential` | `ALL` | 0.98 | | +| `highway=residential_link` | `ALL` | 0.98 | | +| `highway=tertiary` | `ALL` | | | +| `highway=tertiary_link` | `ALL` | | | +| `highway=secondary` | `ALL` | 1.5 | | +| `highway=secondary_link` | `ALL` | 1.5 | | +| `highway=primary` | `ALL` | 2.06 | | +| `highway=primary_link` | `ALL` | 2.06 | | +| `highway=trunk_link` | `ALL` | 2.06 | | +| `highway=trunk` | `ALL` | 7.47 | | +| `highway=trunk; tunnel=yes` | `CAR` | 7.47 | | +| `motorroad=yes` | `CAR` | 7.47 | | +| `present(highway); informal=yes` | `NONE` | | | +| `highway=service; access=private` | `NONE` | | | +| `highway=trail` | `NONE` | | | +| `present(highway); seasonal=winter` | `NONE` | | | +| `present(highway); ice_road=yes` | `NONE` | | | +| `present(highway); winter_road=yes` | `NONE` | | | +| `highway=footway` | `PEDESTRIAN` | | | +| `footway=sidewalk; highway=footway` | `PEDESTRIAN` | | | +| `highway=cycleway; segregated=yes` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | 1.1 | +| `highway=footway; bridge=yes` | `PEDESTRIAN` | | | +| `highway=footway; tunnel=yes` | `PEDESTRIAN` | | | +| `highway=cycleway; bridge=yes` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | | +| `highway=cycleway; tunnel=yes` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | | +| `highway=footway; footway=crossing; crossing=traffic_signals` | `PEDESTRIAN` | | 1.1 | +| `highway=footway; footway=crossing` | `PEDESTRIAN` | | 1.2 | +| `highway=cycleway; cycleway=crossing; segregated=yes; crossing=traffic_signals` | `PEDESTRIAN_AND_BICYCLE` | 0.8 | 1.1 | +| `highway=cycleway; footway=crossing; segregated=yes; crossing=traffic_signals` | `PEDESTRIAN` | 0.8 | 1.1 | +| `highway=cycleway; cycleway=crossing; segregated=yes` | `PEDESTRIAN_AND_BICYCLE` | 1.2 | 1.2 | +| `highway=cycleway; footway=crossing; segregated=yes` | `PEDESTRIAN` | 1.2 | 1.2 | +| `highway=cycleway; cycleway=crossing; crossing=traffic_signals` | `PEDESTRIAN_AND_BICYCLE` | 0.8 | 1.15 | +| `highway=cycleway; footway=crossing; crossing=traffic_signals` | `PEDESTRIAN_AND_BICYCLE` | 0.8 | 1.15 | +| `highway=cycleway; cycleway=crossing` | `PEDESTRIAN_AND_BICYCLE` | 1.2 | 1.25 | +| `highway=cycleway; footway=crossing` | `PEDESTRIAN_AND_BICYCLE` | 1.2 | 1.25 | +| `highway=cycleway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | | +| `highway=service; tunnel=yes; access=destination` | `NONE` | | | +| `highway=service; access=destination` | `ALL` | 1.1 | | +| `mtb:scale=3` | `NONE` | | | +| `mtb:scale=4` | `NONE` | | | +| `mtb:scale=5` | `NONE` | | | +| `mtb:scale=6` | `NONE` | | | +| `highway=corridor` | `PEDESTRIAN` | | | +| `highway=steps` | `PEDESTRIAN` | | | +| `highway=crossing` | `PEDESTRIAN` | | | +| `highway=platform` | `PEDESTRIAN` | | | +| `public_transport=platform` | `PEDESTRIAN` | | | +| `railway=platform` | `PEDESTRIAN` | | | +| `footway=sidewalk; highway=footway` | `PEDESTRIAN` | | | +| `mtb:scale=1` | `PEDESTRIAN` | | | +| `mtb:scale=2` | `PEDESTRIAN` | | | +| `mtb:scale=0` | `PEDESTRIAN_AND_BICYCLE` | | | +| `highway=cycleway` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | | +| `highway=path` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | +| `highway=pedestrian` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | +| `highway=footway` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | +| `highway=bridleway` | `PEDESTRIAN_AND_BICYCLE` | 1.3 | | +| `highway=living_street` | `ALL` | 0.9 | | +| `highway=unclassified` | `ALL` | | | +| `highway=road` | `ALL` | | | +| `highway=byway` | `ALL` | 1.3 | | +| `highway=track` | `ALL` | 1.3 | | +| `highway=service` | `ALL` | 1.1 | | +| `highway=residential` | `ALL` | 0.98 | | +| `highway=residential_link` | `ALL` | 0.98 | | +| `highway=tertiary` | `ALL` | | | +| `highway=tertiary_link` | `ALL` | | | +| `highway=secondary` | `ALL` | 1.5 | | +| `highway=secondary_link` | `ALL` | 1.5 | | +| `highway=primary` | `ALL` | 2.06 | | +| `highway=primary_link` | `ALL` | 2.06 | | +| `highway=trunk_link` | `CAR` | 2.06 | | +| `highway=motorway_link` | `CAR` | 2.06 | | +| `highway=trunk` | `CAR` | 7.47 | | +| `highway=motorway` | `CAR` | 8.0 | | +| `present(highway); cycleway=lane` | `PEDESTRIAN_AND_BICYCLE` | 0.87 | | +| `highway=service; cycleway=lane` | `ALL` | 0.77 | | +| `highway=residential; cycleway=lane` | `ALL` | 0.77 | | +| `highway=residential_link; cycleway=lane` | `ALL` | 0.77 | | +| `highway=tertiary; cycleway=lane` | `ALL` | 0.87 | | +| `highway=tertiary_link; cycleway=lane` | `ALL` | 0.87 | | +| `highway=secondary; cycleway=lane` | `ALL` | 0.96 | | +| `highway=secondary_link; cycleway=lane` | `ALL` | 0.96 | | +| `highway=primary; cycleway=lane` | `ALL` | 1.15 | | +| `highway=primary_link; cycleway=lane` | `ALL` | 1.15 | | +| `highway=trunk; cycleway=lane` | `BICYCLE_AND_CAR` | 1.5 | | +| `highway=trunk_link; cycleway=lane` | `BICYCLE_AND_CAR` | 1.15 | | +| `highway=motorway; cycleway=lane` | `BICYCLE_AND_CAR` | 2.0 | | +| `highway=motorway_link; cycleway=lane` | `BICYCLE_AND_CAR` | 1.15 | | +| `present(highway); cycleway=share_busway` | `PEDESTRIAN_AND_BICYCLE` | 0.92 | | +| `highway=service; cycleway=share_busway` | `ALL` | 0.85 | | +| `highway=residential; cycleway=share_busway` | `ALL` | 0.85 | | +| `highway=residential_link; cycleway=share_busway` | `ALL` | 0.85 | | +| `highway=tertiary; cycleway=share_busway` | `ALL` | 0.92 | | +| `highway=tertiary_link; cycleway=share_busway` | `ALL` | 0.92 | | +| `highway=secondary; cycleway=share_busway` | `ALL` | 0.99 | | +| `highway=secondary_link; cycleway=share_busway` | `ALL` | 0.99 | | +| `highway=primary; cycleway=share_busway` | `ALL` | 1.25 | | +| `highway=primary_link; cycleway=share_busway` | `ALL` | 1.25 | | +| `highway=trunk; cycleway=share_busway` | `BICYCLE_AND_CAR` | 1.75 | | +| `highway=trunk_link; cycleway=share_busway` | `BICYCLE_AND_CAR` | 1.25 | | +| `highway=motorway; cycleway=share_busway` | `BICYCLE_AND_CAR` | 2.5 | | +| `highway=motorway_link; cycleway=share_busway` | `BICYCLE_AND_CAR` | 1.25 | | +| `present(highway); cycleway=opposite_lane` | `PEDESTRIAN_AND_BICYCLE` | forward: 1.0
back: 0.87 | | +| `highway=service; cycleway=opposite_lane` | `ALL` | forward: 1.1
back: 0.77 | | +| `highway=residential; cycleway=opposite_lane` | `ALL` | forward: 0.98
back: 0.77 | | +| `highway=residential_link; cycleway=opposite_lane` | `ALL` | forward: 0.98
back: 0.77 | | +| `highway=tertiary; cycleway=opposite_lane` | `ALL` | forward: 1.0
back: 0.87 | | +| `highway=tertiary_link; cycleway=opposite_lane` | `ALL` | forward: 1.0
back: 0.87 | | +| `highway=secondary; cycleway=opposite_lane` | `ALL` | forward: 1.5
back: 0.96 | | +| `highway=secondary_link; cycleway=opposite_lane` | `ALL` | forward: 1.5
back: 0.96 | | +| `highway=primary; cycleway=opposite_lane` | `ALL` | forward: 2.06
back: 1.15 | | +| `highway=primary_link; cycleway=opposite_lane` | `ALL` | forward: 2.06
back: 1.15 | | +| `highway=trunk; cycleway=opposite_lane` | `BICYCLE_AND_CAR` | forward: 7.47
back: 1.5 | | +| `highway=trunk_link; cycleway=opposite_lane` | `BICYCLE_AND_CAR` | forward: 2.06
back: 1.15 | | +| `present(highway); cycleway=track` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | +| `highway=service; cycleway=track` | `ALL` | 0.65 | | +| `highway=residential; cycleway=track` | `ALL` | 0.65 | | +| `highway=residential_link; cycleway=track` | `ALL` | 0.65 | | +| `highway=tertiary; cycleway=track` | `ALL` | 0.75 | | +| `highway=tertiary_link; cycleway=track` | `ALL` | 0.75 | | +| `highway=secondary; cycleway=track` | `ALL` | 0.8 | | +| `highway=secondary_link; cycleway=track` | `ALL` | 0.8 | | +| `highway=primary; cycleway=track` | `ALL` | 0.85 | | +| `highway=primary_link; cycleway=track` | `ALL` | 0.85 | | +| `highway=trunk; cycleway=track` | `BICYCLE_AND_CAR` | 0.95 | | +| `highway=trunk_link; cycleway=track` | `BICYCLE_AND_CAR` | 0.85 | | +| `present(highway); cycleway=opposite_track` | `PEDESTRIAN_AND_BICYCLE` | forward: 1.0
back: 0.75 | | +| `highway=service; cycleway=opposite_track` | `ALL` | forward: 1.1
back: 0.65 | | +| `highway=residential; cycleway=opposite_track` | `ALL` | forward: 0.98
back: 0.65 | | +| `highway=residential_link; cycleway=opposite_track` | `ALL` | forward: 0.98
back: 0.65 | | +| `highway=tertiary; cycleway=opposite_track` | `ALL` | forward: 1.0
back: 0.75 | | +| `highway=tertiary_link; cycleway=opposite_track` | `ALL` | forward: 1.0
back: 0.75 | | +| `highway=secondary; cycleway=opposite_track` | `ALL` | forward: 1.5
back: 0.8 | | +| `highway=secondary_link; cycleway=opposite_track` | `ALL` | forward: 1.5
back: 0.8 | | +| `highway=primary; cycleway=opposite_track` | `ALL` | forward: 2.06
back: 0.85 | | +| `highway=primary_link; cycleway=opposite_track` | `ALL` | forward: 2.06
back: 0.85 | | +| `highway=trunk; cycleway=opposite_track` | `BICYCLE_AND_CAR` | forward: 7.47
back: 0.95 | | +| `highway=trunk_link; cycleway=opposite_track` | `BICYCLE_AND_CAR` | forward: 2.06
back: 0.85 | | +| `present(highway); cycleway=shared_lane` | `PEDESTRIAN_AND_BICYCLE` | 0.77 | | +| `highway=service; cycleway=shared_lane` | `ALL` | 0.73 | | +| `highway=residential; cycleway=shared_lane` | `ALL` | 0.77 | | +| `highway=residential_link; cycleway=shared_lane` | `ALL` | 0.77 | | +| `highway=tertiary; cycleway=shared_lane` | `ALL` | 0.83 | | +| `highway=tertiary_link; cycleway=shared_lane` | `ALL` | 0.83 | | +| `highway=secondary; cycleway=shared_lane` | `ALL` | 1.25 | | +| `highway=secondary_link; cycleway=shared_lane` | `ALL` | 1.25 | | +| `highway=primary; cycleway=shared_lane` | `ALL` | 1.75 | | +| `highway=primary_link; cycleway=shared_lane` | `ALL` | 1.75 | | +| `present(highway); cycleway=opposite` | `PEDESTRIAN_AND_BICYCLE` | forward: 1.0
back: 1.4 | | +| `highway=service; cycleway=opposite` | `ALL` | 1.1 | | +| `highway=residential; cycleway=opposite` | `ALL` | 0.98 | | +| `highway=residential_link; cycleway=opposite` | `ALL` | 0.98 | | +| `highway=tertiary; cycleway=opposite` | `ALL` | | | +| `highway=tertiary_link; cycleway=opposite` | `ALL` | | | +| `highway=secondary; cycleway=opposite` | `ALL` | forward: 1.5
back: 1.71 | | +| `highway=secondary_link; cycleway=opposite` | `ALL` | forward: 1.5
back: 1.71 | | +| `highway=primary; cycleway=opposite` | `ALL` | forward: 2.06
back: 2.99 | | +| `highway=primary_link; cycleway=opposite` | `ALL` | forward: 2.06
back: 2.99 | | +| `highway=path; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | | +| `highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | +| `highway=footway; bicycle=yes; area=yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | +| `highway=pedestrian; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | +| `footway=sidewalk; highway=footway; bicycle=yes` | `PEDESTRIAN_AND_BICYCLE` | 2.5 | | +| `footway=sidewalk; highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | +| `highway=footway; footway=crossing` | `PEDESTRIAN_AND_BICYCLE` | 2.5 | | +| `highway=footway; footway=crossing; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | +| `highway=track; bicycle=yes` | `PEDESTRIAN_AND_BICYCLE` | 1.18 | | +| `highway=track; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.99 | | +| `highway=track; bicycle=yes; present(surface)` | `PEDESTRIAN_AND_BICYCLE` | 1.18 | | +| `highway=track; bicycle=designated; present(surface)` | `PEDESTRIAN_AND_BICYCLE` | 0.99 | | +| `highway=track; present(surface)` | `PEDESTRIAN_AND_BICYCLE` | 1.3 | | +| `present(highway); bicycle=designated` | `ALL` | 0.97 | | +| `highway=service; bicycle=designated` | `ALL` | 0.84 | | +| `highway=residential; bicycle=designated` | `ALL` | 0.95 | | +| `highway=unclassified; bicycle=designated` | `ALL` | 0.95 | | +| `highway=residential_link; bicycle=designated` | `ALL` | 0.95 | | +| `highway=tertiary; bicycle=designated` | `ALL` | 0.97 | | +| `highway=tertiary_link; bicycle=designated` | `ALL` | 0.97 | | +| `highway=secondary; bicycle=designated` | `ALL` | 1.46 | | +| `highway=secondary_link; bicycle=designated` | `ALL` | 1.46 | | +| `highway=primary; bicycle=designated` | `ALL` | 2.0 | | +| `highway=primary_link; bicycle=designated` | `ALL` | 2.0 | | +| `highway=trunk; bicycle=designated` | `BICYCLE_AND_CAR` | 7.25 | | +| `highway=trunk_link; bicycle=designated` | `BICYCLE_AND_CAR` | 2.0 | | +| `highway=motorway; bicycle=designated` | `BICYCLE_AND_CAR` | 7.76 | | +| `highway=motorway_link; bicycle=designated` | `BICYCLE_AND_CAR` | 2.0 | | + + + +### Safety mixins + +Mixins are selectors that have only an effect on the bicycle and walk safety factors but not on the +permission of an OSM way. Their safety values are multiplied with the base values from the selected +way properties. Multiple mixins can apply to the same way and their effects compound. + + + + +| matcher | bicycle safety | walk safety | +|------------------------------------------------------------|----------------|-------------| +| `bicycle=use_sidepath` | | 5.0 | +| `lcn=yes¦rcn=yes¦ncn=yes¦bicycle_road=yes¦cyclestreet=yes` | 0.7 | | +| `surface=unpaved` | 1.18 | | +| `surface=compacted` | 1.18 | | +| `surface=wood` | 1.18 | | +| `surface=cobblestone` | 1.3 | | +| `surface=sett` | 1.3 | | +| `surface=unhewn_cobblestone` | 1.5 | | +| `surface=grass_paver` | 1.3 | | +| `surface=pebblestone` | 1.3 | | +| `surface=metal` | 1.3 | | +| `surface=ground` | 1.5 | | +| `surface=dirt` | 1.5 | | +| `surface=earth` | 1.5 | | +| `surface=grass` | 1.5 | | +| `surface=mud` | 1.5 | | +| `surface=woodchip` | 1.5 | | +| `surface=gravel` | 1.5 | | +| `surface=artifical_turf` | 1.5 | | +| `surface=sand` | 100.0 | | +| `foot=discouraged` | | 3.0 | +| `bicycle=discouraged` | 3.0 | | +| `foot=use_sidepath` | | 5.0 | +| `bicycle=use_sidepath` | 5.0 | | + + diff --git a/docs/osm/Germany.md b/docs/osm/Germany.md new file mode 100644 index 00000000000..922aa3af836 --- /dev/null +++ b/docs/osm/Germany.md @@ -0,0 +1,234 @@ +# OSM tag mapping + +This page is intended to give an overview of which OpenStreetMap(OSM) tags OTP uses to evaluate its +walking and bicycling instructions. If a tag is not part of the documentation on this page +then this tag mapper (profile) does not use it. + +The exception are access permissions and wheelchair accessibility tags like + +- `access=no` +- `wheelchair=no` +- `oneway=yes` + +These are identical for all mappers and not separately listed on this page. + +### Way properties + +Way properties set a way's permission and optionally influences its walk and bicycle safety factors. + +These factors determine how desirable an OSM way is when routing for cyclists and pedestrians. +Lower safety values make an OSM way more desirable and higher values less desirable. + + + + +| specifier | permission | bike safety | walk safety | +|---------------------------------------------------------|--------------------------|-------------------------------|-------------| +| `highway=track` | `PEDESTRIAN_AND_BICYCLE` | | | +| `highway=track; present(surface)` | `PEDESTRIAN_AND_BICYCLE` | | | +| `highway=residential; junction=roundabout` | `ALL` | 0.98 | | +| `present(highway); junction=roundabout` | `BICYCLE_AND_CAR` | | | +| `highway=pedestrian` | `PEDESTRIAN` | | | +| `highway=residential; maxspeed=30` | `ALL` | 0.9 | | +| `highway=footway; bicycle=yes` | `PEDESTRIAN_AND_BICYCLE` | 0.8 | | +| `footway=sidewalk; highway=footway; bicycle=yes` | `PEDESTRIAN_AND_BICYCLE` | 1.2 | | +| `highway=unclassified; cycleway=lane` | `ALL` | 0.87 | | +| `mtb:scale=3` | `NONE` | | | +| `mtb:scale=4` | `NONE` | | | +| `mtb:scale=5` | `NONE` | | | +| `mtb:scale=6` | `NONE` | | | +| `highway=corridor` | `PEDESTRIAN` | | | +| `highway=steps` | `PEDESTRIAN` | | | +| `highway=crossing` | `PEDESTRIAN` | | | +| `highway=platform` | `PEDESTRIAN` | | | +| `public_transport=platform` | `PEDESTRIAN` | | | +| `railway=platform` | `PEDESTRIAN` | | | +| `footway=sidewalk; highway=footway` | `PEDESTRIAN` | | | +| `mtb:scale=1` | `PEDESTRIAN` | | | +| `mtb:scale=2` | `PEDESTRIAN` | | | +| `mtb:scale=0` | `PEDESTRIAN_AND_BICYCLE` | | | +| `highway=cycleway` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | | +| `highway=path` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | +| `highway=pedestrian` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | +| `highway=footway` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | +| `highway=bridleway` | `PEDESTRIAN_AND_BICYCLE` | 1.3 | | +| `highway=living_street` | `ALL` | 0.9 | | +| `highway=unclassified` | `ALL` | | | +| `highway=road` | `ALL` | | | +| `highway=byway` | `ALL` | 1.3 | | +| `highway=track` | `ALL` | 1.3 | | +| `highway=service` | `ALL` | 1.1 | | +| `highway=residential` | `ALL` | 0.98 | | +| `highway=residential_link` | `ALL` | 0.98 | | +| `highway=tertiary` | `ALL` | | | +| `highway=tertiary_link` | `ALL` | | | +| `highway=secondary` | `ALL` | 1.5 | | +| `highway=secondary_link` | `ALL` | 1.5 | | +| `highway=primary` | `ALL` | 2.06 | | +| `highway=primary_link` | `ALL` | 2.06 | | +| `highway=trunk_link` | `CAR` | 2.06 | | +| `highway=motorway_link` | `CAR` | 2.06 | | +| `highway=trunk` | `CAR` | 7.47 | | +| `highway=motorway` | `CAR` | 8.0 | | +| `present(highway); cycleway=lane` | `PEDESTRIAN_AND_BICYCLE` | 0.87 | | +| `highway=service; cycleway=lane` | `ALL` | 0.77 | | +| `highway=residential; cycleway=lane` | `ALL` | 0.77 | | +| `highway=residential_link; cycleway=lane` | `ALL` | 0.77 | | +| `highway=tertiary; cycleway=lane` | `ALL` | 0.87 | | +| `highway=tertiary_link; cycleway=lane` | `ALL` | 0.87 | | +| `highway=secondary; cycleway=lane` | `ALL` | 0.96 | | +| `highway=secondary_link; cycleway=lane` | `ALL` | 0.96 | | +| `highway=primary; cycleway=lane` | `ALL` | 1.15 | | +| `highway=primary_link; cycleway=lane` | `ALL` | 1.15 | | +| `highway=trunk; cycleway=lane` | `BICYCLE_AND_CAR` | 1.5 | | +| `highway=trunk_link; cycleway=lane` | `BICYCLE_AND_CAR` | 1.15 | | +| `highway=motorway; cycleway=lane` | `BICYCLE_AND_CAR` | 2.0 | | +| `highway=motorway_link; cycleway=lane` | `BICYCLE_AND_CAR` | 1.15 | | +| `present(highway); cycleway=share_busway` | `PEDESTRIAN_AND_BICYCLE` | 0.92 | | +| `highway=service; cycleway=share_busway` | `ALL` | 0.85 | | +| `highway=residential; cycleway=share_busway` | `ALL` | 0.85 | | +| `highway=residential_link; cycleway=share_busway` | `ALL` | 0.85 | | +| `highway=tertiary; cycleway=share_busway` | `ALL` | 0.92 | | +| `highway=tertiary_link; cycleway=share_busway` | `ALL` | 0.92 | | +| `highway=secondary; cycleway=share_busway` | `ALL` | 0.99 | | +| `highway=secondary_link; cycleway=share_busway` | `ALL` | 0.99 | | +| `highway=primary; cycleway=share_busway` | `ALL` | 1.25 | | +| `highway=primary_link; cycleway=share_busway` | `ALL` | 1.25 | | +| `highway=trunk; cycleway=share_busway` | `BICYCLE_AND_CAR` | 1.75 | | +| `highway=trunk_link; cycleway=share_busway` | `BICYCLE_AND_CAR` | 1.25 | | +| `highway=motorway; cycleway=share_busway` | `BICYCLE_AND_CAR` | 2.5 | | +| `highway=motorway_link; cycleway=share_busway` | `BICYCLE_AND_CAR` | 1.25 | | +| `present(highway); cycleway=opposite_lane` | `PEDESTRIAN_AND_BICYCLE` | forward: 1.0
back: 0.87 | | +| `highway=service; cycleway=opposite_lane` | `ALL` | forward: 1.1
back: 0.77 | | +| `highway=residential; cycleway=opposite_lane` | `ALL` | forward: 0.98
back: 0.77 | | +| `highway=residential_link; cycleway=opposite_lane` | `ALL` | forward: 0.98
back: 0.77 | | +| `highway=tertiary; cycleway=opposite_lane` | `ALL` | forward: 1.0
back: 0.87 | | +| `highway=tertiary_link; cycleway=opposite_lane` | `ALL` | forward: 1.0
back: 0.87 | | +| `highway=secondary; cycleway=opposite_lane` | `ALL` | forward: 1.5
back: 0.96 | | +| `highway=secondary_link; cycleway=opposite_lane` | `ALL` | forward: 1.5
back: 0.96 | | +| `highway=primary; cycleway=opposite_lane` | `ALL` | forward: 2.06
back: 1.15 | | +| `highway=primary_link; cycleway=opposite_lane` | `ALL` | forward: 2.06
back: 1.15 | | +| `highway=trunk; cycleway=opposite_lane` | `BICYCLE_AND_CAR` | forward: 7.47
back: 1.5 | | +| `highway=trunk_link; cycleway=opposite_lane` | `BICYCLE_AND_CAR` | forward: 2.06
back: 1.15 | | +| `present(highway); cycleway=track` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | +| `highway=service; cycleway=track` | `ALL` | 0.65 | | +| `highway=residential; cycleway=track` | `ALL` | 0.65 | | +| `highway=residential_link; cycleway=track` | `ALL` | 0.65 | | +| `highway=tertiary; cycleway=track` | `ALL` | 0.75 | | +| `highway=tertiary_link; cycleway=track` | `ALL` | 0.75 | | +| `highway=secondary; cycleway=track` | `ALL` | 0.8 | | +| `highway=secondary_link; cycleway=track` | `ALL` | 0.8 | | +| `highway=primary; cycleway=track` | `ALL` | 0.85 | | +| `highway=primary_link; cycleway=track` | `ALL` | 0.85 | | +| `highway=trunk; cycleway=track` | `BICYCLE_AND_CAR` | 0.95 | | +| `highway=trunk_link; cycleway=track` | `BICYCLE_AND_CAR` | 0.85 | | +| `present(highway); cycleway=opposite_track` | `PEDESTRIAN_AND_BICYCLE` | forward: 1.0
back: 0.75 | | +| `highway=service; cycleway=opposite_track` | `ALL` | forward: 1.1
back: 0.65 | | +| `highway=residential; cycleway=opposite_track` | `ALL` | forward: 0.98
back: 0.65 | | +| `highway=residential_link; cycleway=opposite_track` | `ALL` | forward: 0.98
back: 0.65 | | +| `highway=tertiary; cycleway=opposite_track` | `ALL` | forward: 1.0
back: 0.75 | | +| `highway=tertiary_link; cycleway=opposite_track` | `ALL` | forward: 1.0
back: 0.75 | | +| `highway=secondary; cycleway=opposite_track` | `ALL` | forward: 1.5
back: 0.8 | | +| `highway=secondary_link; cycleway=opposite_track` | `ALL` | forward: 1.5
back: 0.8 | | +| `highway=primary; cycleway=opposite_track` | `ALL` | forward: 2.06
back: 0.85 | | +| `highway=primary_link; cycleway=opposite_track` | `ALL` | forward: 2.06
back: 0.85 | | +| `highway=trunk; cycleway=opposite_track` | `BICYCLE_AND_CAR` | forward: 7.47
back: 0.95 | | +| `highway=trunk_link; cycleway=opposite_track` | `BICYCLE_AND_CAR` | forward: 2.06
back: 0.85 | | +| `present(highway); cycleway=shared_lane` | `PEDESTRIAN_AND_BICYCLE` | 0.77 | | +| `highway=service; cycleway=shared_lane` | `ALL` | 0.73 | | +| `highway=residential; cycleway=shared_lane` | `ALL` | 0.77 | | +| `highway=residential_link; cycleway=shared_lane` | `ALL` | 0.77 | | +| `highway=tertiary; cycleway=shared_lane` | `ALL` | 0.83 | | +| `highway=tertiary_link; cycleway=shared_lane` | `ALL` | 0.83 | | +| `highway=secondary; cycleway=shared_lane` | `ALL` | 1.25 | | +| `highway=secondary_link; cycleway=shared_lane` | `ALL` | 1.25 | | +| `highway=primary; cycleway=shared_lane` | `ALL` | 1.75 | | +| `highway=primary_link; cycleway=shared_lane` | `ALL` | 1.75 | | +| `present(highway); cycleway=opposite` | `PEDESTRIAN_AND_BICYCLE` | forward: 1.0
back: 1.4 | | +| `highway=service; cycleway=opposite` | `ALL` | 1.1 | | +| `highway=residential; cycleway=opposite` | `ALL` | 0.98 | | +| `highway=residential_link; cycleway=opposite` | `ALL` | 0.98 | | +| `highway=tertiary; cycleway=opposite` | `ALL` | | | +| `highway=tertiary_link; cycleway=opposite` | `ALL` | | | +| `highway=secondary; cycleway=opposite` | `ALL` | forward: 1.5
back: 1.71 | | +| `highway=secondary_link; cycleway=opposite` | `ALL` | forward: 1.5
back: 1.71 | | +| `highway=primary; cycleway=opposite` | `ALL` | forward: 2.06
back: 2.99 | | +| `highway=primary_link; cycleway=opposite` | `ALL` | forward: 2.06
back: 2.99 | | +| `highway=path; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | | +| `highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | +| `highway=footway; bicycle=yes; area=yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | +| `highway=pedestrian; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | +| `footway=sidewalk; highway=footway; bicycle=yes` | `PEDESTRIAN_AND_BICYCLE` | 2.5 | | +| `footway=sidewalk; highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | +| `highway=footway; footway=crossing` | `PEDESTRIAN_AND_BICYCLE` | 2.5 | | +| `highway=footway; footway=crossing; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | +| `highway=track; bicycle=yes` | `PEDESTRIAN_AND_BICYCLE` | 1.18 | | +| `highway=track; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.99 | | +| `highway=track; bicycle=yes; present(surface)` | `PEDESTRIAN_AND_BICYCLE` | 1.18 | | +| `highway=track; bicycle=designated; present(surface)` | `PEDESTRIAN_AND_BICYCLE` | 0.99 | | +| `highway=track; present(surface)` | `PEDESTRIAN_AND_BICYCLE` | 1.3 | | +| `present(highway); bicycle=designated` | `ALL` | 0.97 | | +| `highway=service; bicycle=designated` | `ALL` | 0.84 | | +| `highway=residential; bicycle=designated` | `ALL` | 0.95 | | +| `highway=unclassified; bicycle=designated` | `ALL` | 0.95 | | +| `highway=residential_link; bicycle=designated` | `ALL` | 0.95 | | +| `highway=tertiary; bicycle=designated` | `ALL` | 0.97 | | +| `highway=tertiary_link; bicycle=designated` | `ALL` | 0.97 | | +| `highway=secondary; bicycle=designated` | `ALL` | 1.46 | | +| `highway=secondary_link; bicycle=designated` | `ALL` | 1.46 | | +| `highway=primary; bicycle=designated` | `ALL` | 2.0 | | +| `highway=primary_link; bicycle=designated` | `ALL` | 2.0 | | +| `highway=trunk; bicycle=designated` | `BICYCLE_AND_CAR` | 7.25 | | +| `highway=trunk_link; bicycle=designated` | `BICYCLE_AND_CAR` | 2.0 | | +| `highway=motorway; bicycle=designated` | `BICYCLE_AND_CAR` | 7.76 | | +| `highway=motorway_link; bicycle=designated` | `BICYCLE_AND_CAR` | 2.0 | | + + + +### Safety mixins + +Mixins are selectors that have only an effect on the bicycle and walk safety factors but not on the +permission of an OSM way. Their safety values are multiplied with the base values from the selected +way properties. Multiple mixins can apply to the same way and their effects compound. + + + + +| matcher | bicycle safety | walk safety | +|------------------------------------------------------------|----------------|-------------| +| `highway=tertiary` | 1.2 | | +| `maxspeed=70` | 1.5 | | +| `maxspeed=80` | 2.0 | | +| `maxspeed=90` | 3.0 | | +| `maxspeed=100` | 5.0 | | +| `tracktype=grade1` | | | +| `tracktype=grade2` | 1.1 | | +| `tracktype=grade3` | 1.15 | | +| `tracktype=grade4` | 1.3 | | +| `tracktype=grade5` | 1.5 | | +| `lit=no` | 1.05 | | +| `lcn=yes¦rcn=yes¦ncn=yes¦bicycle_road=yes¦cyclestreet=yes` | 0.7 | | +| `surface=unpaved` | 1.18 | | +| `surface=compacted` | 1.18 | | +| `surface=wood` | 1.18 | | +| `surface=cobblestone` | 1.3 | | +| `surface=sett` | 1.3 | | +| `surface=unhewn_cobblestone` | 1.5 | | +| `surface=grass_paver` | 1.3 | | +| `surface=pebblestone` | 1.3 | | +| `surface=metal` | 1.3 | | +| `surface=ground` | 1.5 | | +| `surface=dirt` | 1.5 | | +| `surface=earth` | 1.5 | | +| `surface=grass` | 1.5 | | +| `surface=mud` | 1.5 | | +| `surface=woodchip` | 1.5 | | +| `surface=gravel` | 1.5 | | +| `surface=artifical_turf` | 1.5 | | +| `surface=sand` | 100.0 | | +| `foot=discouraged` | | 3.0 | +| `bicycle=discouraged` | 3.0 | | +| `foot=use_sidepath` | | 5.0 | +| `bicycle=use_sidepath` | 5.0 | | + + diff --git a/docs/osm/Norway.md b/docs/osm/Norway.md new file mode 100644 index 00000000000..d38fc4c04df --- /dev/null +++ b/docs/osm/Norway.md @@ -0,0 +1,122 @@ +# OSM tag mapping + +This page is intended to give an overview of which OpenStreetMap(OSM) tags OTP uses to evaluate its +walking and bicycling instructions. If a tag is not part of the documentation on this page +then this tag mapper (profile) does not use it. + +The exception are access permissions and wheelchair accessibility tags like + +- `access=no` +- `wheelchair=no` +- `oneway=yes` + +These are identical for all mappers and not separately listed on this page. + +### Way properties + +Way properties set a way's permission and optionally influences its walk and bicycle safety factors. + +These factors determine how desirable an OSM way is when routing for cyclists and pedestrians. +Lower safety values make an OSM way more desirable and higher values less desirable. + + + + +| specifier | permission | bike safety | walk safety | +|------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------|-------------|-------------| +| `highway one of [motorway, motorway_link]` | `CAR` | | | +| `highway one of [trunk, trunk_link, primary, primary_link]; motorroad=yes` | `CAR` | | | +| `highway one of [trunk, trunk_link, primary, primary_link, secondary, secondary_link, tertiary, tertiary_link, unclassified, residential]` | `ALL` | | | +| `cycleway=track; highway one of [trunk, trunk_link, primary, primary_link, secondary, secondary_link, tertiary, tertiary_link, unclassified, residential]` | `ALL` | | | +| `cycleway=lane; highway one of [trunk, trunk_link, primary, primary_link, secondary, secondary_link, tertiary, tertiary_link]` | `ALL` | 1.27 | | +| `cycleway=lane; maxspeed < 50; highway one of [trunk, trunk_link, primary, primary_link, secondary, secondary_link, tertiary, tertiary_link]` | `ALL` | 1.1 | | +| `cycleway=lane; highway one of [unclassified, residential]` | `ALL` | 1.1 | | +| `highway=service` | `ALL` | | | +| `highway=service; service=parking_aisle` | `ALL` | 2.5 | | +| `highway=service; service=drive-through` | `ALL` | 2.5 | | +| `highway=living_street` | `ALL` | 1.83 | | +| `highway=pedestrian` | `PEDESTRIAN_AND_BICYCLE` | 1.2 | | +| `highway=busway` | `PEDESTRIAN_AND_BICYCLE` | 2.37 | 1.9 | +| `highway=service; bus one of [yes, designated]` | `PEDESTRIAN_AND_BICYCLE` | 2.37 | 1.9 | +| `highway=footway` | `PEDESTRIAN_AND_BICYCLE` | 1.42 | | +| `highway=cycleway` | `PEDESTRIAN_AND_BICYCLE` | 1.05 | 1.4 | +| `highway=cycleway; lanes > 1` | `PEDESTRIAN_AND_BICYCLE` | | 1.4 | +| `highway=cycleway; oneway=yes` | `PEDESTRIAN_AND_BICYCLE` | | 1.4 | +| `highway=cycleway; sidewalk one of [yes, left, right, both]` | `PEDESTRIAN_AND_BICYCLE` | 1.05 | | +| `highway=cycleway; lanes > 1; sidewalk one of [yes, left, right, both]` | `PEDESTRIAN_AND_BICYCLE` | | | +| `highway=cycleway; oneway=yes; sidewalk one of [yes, left, right, both]` | `PEDESTRIAN_AND_BICYCLE` | | | +| `highway=cycleway; foot=designated; segregated=no` | `PEDESTRIAN_AND_BICYCLE` | 1.05 | 1.15 | +| `highway=path; foot=designated; bicycle=designated; segregated=no` | `PEDESTRIAN_AND_BICYCLE` | 1.05 | 1.15 | +| `highway=cycleway; foot=designated; segregated=yes` | `PEDESTRIAN_AND_BICYCLE` | 1.05 | | +| `highway=path; foot=designated; bicycle=designated; segregated=yes` | `PEDESTRIAN_AND_BICYCLE` | 1.05 | | +| `highway=cycleway; foot=designated; segregated=yes; lanes > 1` | `PEDESTRIAN_AND_BICYCLE` | | | +| `highway=cycleway; foot=designated; present(segregated); motor_vehicle=destination` | `PEDESTRIAN_AND_BICYCLE` | 1.57 | | +| `highway=path; foot=designated; bicycle=designated; present(segregated); motor_vehicle=destination` | `PEDESTRIAN_AND_BICYCLE` | 1.57 | | +| `highway=footway; footway=sidewalk` | `PEDESTRIAN_AND_BICYCLE` | 1.93 | 1.1 | +| `highway=footway; footway=crossing` | `PEDESTRIAN_AND_BICYCLE` | 2.33 | 1.35 | +| `highway=cycleway; cycleway=crossing` | `PEDESTRIAN_AND_BICYCLE` | 2.33 | 1.35 | +| `highway=track` | `PEDESTRIAN_AND_BICYCLE` | | | +| `highway=bridleway` | `PEDESTRIAN_AND_BICYCLE` | | | +| `highway=path` | `PEDESTRIAN_AND_BICYCLE` | | | +| `highway=steps` | `PEDESTRIAN` | | | +| `highway=corridor` | `PEDESTRIAN` | | | +| `highway=footway; indoor=yes` | `PEDESTRIAN` | | | +| `highway=platform` | `PEDESTRIAN` | | | +| `public_transport=platform` | `PEDESTRIAN` | | | +| `trail_visibility one of [bad, low, poor, horrible, no]; highway=path` | `NONE` | | | +| `sac_scale one of [demanding_mountain_hiking, alpine_hiking, demanding_alpine_hiking, difficult_alpine_hiking]; highway one of [path, steps]` | `NONE` | | | +| `smoothness one of [horrible, very_horrible]; highway one of [path, bridleway, track]` | `PEDESTRIAN` | | 1.15 | +| `smoothness=impassable; highway one of [path, bridleway, track]` | `NONE` | | | +| `1 > mtb:scale < 2; highway one of [path, bridleway, track]` | `PEDESTRIAN` | | 1.15 | +| `mtb:scale > 2; highway one of [path, bridleway, track]` | `NONE` | | | + + + +### Safety mixins + +Mixins are selectors that have only an effect on the bicycle and walk safety factors but not on the +permission of an OSM way. Their safety values are multiplied with the base values from the selected +way properties. Multiple mixins can apply to the same way and their effects compound. + + + + +| matcher | bicycle safety | walk safety | +|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------|-------------| +| `cycleway=shared_lane; highway one of [trunk, trunk_link, primary, primary_link, secondary, secondary_link, tertiary, tertiary_link, unclassified, residential]` | 0.85 | | +| `lcn=yes¦rcn=yes¦ncn=yes` | 0.85 | | +| `oneway=yes; cycleway not one of [no, none] or absent; highway one of [trunk, trunk_link, primary, primary_link, secondary, secondary_link, tertiary, tertiary_link, unclassified, residential]` | forward: 1.0
back: 1.15 | | +| `embedded_rails one of [tram, light_rail, disused]` | 1.2 | | +| `tunnel=yes; highway one of [trunk, trunk_link, primary, primary_link, secondary, secondary_link, tertiary, tertiary_link, unclassified]` | | 2.0 | +| `bridge=yes; sidewalk not one of [no, separate] or absent; highway one of [trunk, trunk_link, primary, primary_link, secondary, secondary_link, tertiary, tertiary_link, unclassified]¦verge=no; sidewalk not one of [no, separate] or absent; highway one of [trunk, trunk_link, primary, primary_link, secondary, secondary_link, tertiary, tertiary_link, unclassified]` | | 2.0 | +| `junction=roundabout; sidewalk not one of [no, separate] or absent` | | 2.0 | +| `surface=grass_paver` | 1.2 | | +| `surface=sett` | 1.2 | | +| `surface=cobblestone` | 1.2 | | +| `surface=unhewn_cobblestone` | 3.0 | | +| `surface=metal_grid` | 1.2 | | +| `surface=metal` | 1.2 | | +| `smoothness=intermediate; surface one of [asfalt, concrete, paving_stones, paved, wood]` | 1.2 | | +| `smoothness=bad; surface one of [asfalt, concrete, paving_stones, paved, wood]` | 1.4 | 1.6 | +| `surface=unpaved; !tracktype` | 1.8 | 1.6 | +| `surface=compacted` | 1.4 | 1.4 | +| `surface=fine_gravel` | 1.8 | 1.6 | +| `surface=pebblestone` | 1.8 | 1.6 | +| `surface=gravel` | 1.8 | 1.6 | +| `surface=woodchip` | 1.8 | 1.6 | +| `surface=ground` | 2.3 | 2.4 | +| `surface=dirt` | 2.3 | 2.4 | +| `surface=earth` | 2.3 | 2.4 | +| `surface=grass` | 2.3 | 1.8 | +| `surface=mud` | 3.0 | 3.0 | +| `surface=sand` | 3.0 | 1.8 | +| `!tracktype; surface not one of [unpaved] or absent; highway one of [track, bridleway]` | 1.8 | 1.6 | +| `tracktype=grade2; surface not one of [unpaved] or absent; highway one of [track, bridleway, service, unclassified]` | 1.4 | 1.4 | +| `tracktype=grade3; surface not one of [unpaved] or absent; highway one of [track, bridleway, service, unclassified]` | 1.8 | 1.6 | +| `tracktype=grade4; surface not one of [unpaved] or absent; highway one of [track, bridleway, service, unclassified]` | 2.3 | 1.8 | +| `tracktype=grade5; surface not one of [unpaved] or absent; highway one of [track, bridleway, service, unclassified]` | 2.3 | 2.4 | +| `surface not one of [no, none] or absent; highway=path` | 2.3 | 2.4 | +| `sac_scale=mountain_hiking` | | 1.8 | +| `trail_visibility=intermediate` | | 1.8 | + + diff --git a/docs/osm/UK.md b/docs/osm/UK.md new file mode 100644 index 00000000000..4a640caf95c --- /dev/null +++ b/docs/osm/UK.md @@ -0,0 +1,228 @@ +# OSM tag mapping + +This page is intended to give an overview of which OpenStreetMap(OSM) tags OTP uses to evaluate its +walking and bicycling instructions. If a tag is not part of the documentation on this page +then this tag mapper (profile) does not use it. + +The exception are access permissions and wheelchair accessibility tags like + +- `access=no` +- `wheelchair=no` +- `oneway=yes` + +These are identical for all mappers and not separately listed on this page. + +### Way properties + +Way properties set a way's permission and optionally influences its walk and bicycle safety factors. + +These factors determine how desirable an OSM way is when routing for cyclists and pedestrians. +Lower safety values make an OSM way more desirable and higher values less desirable. + + + + +| specifier | permission | bike safety | walk safety | +|---------------------------------------------------------|--------------------------|-------------------------------|-------------| +| `highway=trunk_link` | `ALL` | 2.06 | | +| `highway=trunk` | `ALL` | 7.47 | | +| `highway=trunk; cycleway=lane` | `ALL` | 1.5 | | +| `highway=trunk_link; cycleway=lane` | `ALL` | 1.15 | | +| `highway=trunk; cycleway=share_busway` | `ALL` | 1.75 | | +| `highway=trunk_link; cycleway=share_busway` | `ALL` | 1.25 | | +| `highway=trunk; cycleway=opposite_lane` | `ALL` | forward: 7.47
back: 1.5 | | +| `highway=trunk_link; cycleway=opposite_lane` | `ALL` | forward: 2.06
back: 1.15 | | +| `highway=trunk; cycleway=track` | `ALL` | 0.95 | | +| `highway=trunk_link; cycleway=track` | `ALL` | 0.85 | | +| `highway=trunk; cycleway=opposite_track` | `ALL` | forward: 7.47
back: 0.95 | | +| `highway=trunk_link; cycleway=opposite_track` | `ALL` | forward: 2.06
back: 0.85 | | +| `highway=trunk; bicycle=designated` | `ALL` | 7.25 | | +| `highway=trunk_link; bicycle=designated` | `ALL` | 2.0 | | +| `mtb:scale=3` | `NONE` | | | +| `mtb:scale=4` | `NONE` | | | +| `mtb:scale=5` | `NONE` | | | +| `mtb:scale=6` | `NONE` | | | +| `highway=corridor` | `PEDESTRIAN` | | | +| `highway=steps` | `PEDESTRIAN` | | | +| `highway=crossing` | `PEDESTRIAN` | | | +| `highway=platform` | `PEDESTRIAN` | | | +| `public_transport=platform` | `PEDESTRIAN` | | | +| `railway=platform` | `PEDESTRIAN` | | | +| `footway=sidewalk; highway=footway` | `PEDESTRIAN` | | | +| `mtb:scale=1` | `PEDESTRIAN` | | | +| `mtb:scale=2` | `PEDESTRIAN` | | | +| `mtb:scale=0` | `PEDESTRIAN_AND_BICYCLE` | | | +| `highway=cycleway` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | | +| `highway=path` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | +| `highway=pedestrian` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | +| `highway=footway` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | +| `highway=bridleway` | `PEDESTRIAN_AND_BICYCLE` | 1.3 | | +| `highway=living_street` | `ALL` | 0.9 | | +| `highway=unclassified` | `ALL` | | | +| `highway=road` | `ALL` | | | +| `highway=byway` | `ALL` | 1.3 | | +| `highway=track` | `ALL` | 1.3 | | +| `highway=service` | `ALL` | 1.1 | | +| `highway=residential` | `ALL` | 0.98 | | +| `highway=residential_link` | `ALL` | 0.98 | | +| `highway=tertiary` | `ALL` | | | +| `highway=tertiary_link` | `ALL` | | | +| `highway=secondary` | `ALL` | 1.5 | | +| `highway=secondary_link` | `ALL` | 1.5 | | +| `highway=primary` | `ALL` | 2.06 | | +| `highway=primary_link` | `ALL` | 2.06 | | +| `highway=trunk_link` | `CAR` | 2.06 | | +| `highway=motorway_link` | `CAR` | 2.06 | | +| `highway=trunk` | `CAR` | 7.47 | | +| `highway=motorway` | `CAR` | 8.0 | | +| `present(highway); cycleway=lane` | `PEDESTRIAN_AND_BICYCLE` | 0.87 | | +| `highway=service; cycleway=lane` | `ALL` | 0.77 | | +| `highway=residential; cycleway=lane` | `ALL` | 0.77 | | +| `highway=residential_link; cycleway=lane` | `ALL` | 0.77 | | +| `highway=tertiary; cycleway=lane` | `ALL` | 0.87 | | +| `highway=tertiary_link; cycleway=lane` | `ALL` | 0.87 | | +| `highway=secondary; cycleway=lane` | `ALL` | 0.96 | | +| `highway=secondary_link; cycleway=lane` | `ALL` | 0.96 | | +| `highway=primary; cycleway=lane` | `ALL` | 1.15 | | +| `highway=primary_link; cycleway=lane` | `ALL` | 1.15 | | +| `highway=trunk; cycleway=lane` | `BICYCLE_AND_CAR` | 1.5 | | +| `highway=trunk_link; cycleway=lane` | `BICYCLE_AND_CAR` | 1.15 | | +| `highway=motorway; cycleway=lane` | `BICYCLE_AND_CAR` | 2.0 | | +| `highway=motorway_link; cycleway=lane` | `BICYCLE_AND_CAR` | 1.15 | | +| `present(highway); cycleway=share_busway` | `PEDESTRIAN_AND_BICYCLE` | 0.92 | | +| `highway=service; cycleway=share_busway` | `ALL` | 0.85 | | +| `highway=residential; cycleway=share_busway` | `ALL` | 0.85 | | +| `highway=residential_link; cycleway=share_busway` | `ALL` | 0.85 | | +| `highway=tertiary; cycleway=share_busway` | `ALL` | 0.92 | | +| `highway=tertiary_link; cycleway=share_busway` | `ALL` | 0.92 | | +| `highway=secondary; cycleway=share_busway` | `ALL` | 0.99 | | +| `highway=secondary_link; cycleway=share_busway` | `ALL` | 0.99 | | +| `highway=primary; cycleway=share_busway` | `ALL` | 1.25 | | +| `highway=primary_link; cycleway=share_busway` | `ALL` | 1.25 | | +| `highway=trunk; cycleway=share_busway` | `BICYCLE_AND_CAR` | 1.75 | | +| `highway=trunk_link; cycleway=share_busway` | `BICYCLE_AND_CAR` | 1.25 | | +| `highway=motorway; cycleway=share_busway` | `BICYCLE_AND_CAR` | 2.5 | | +| `highway=motorway_link; cycleway=share_busway` | `BICYCLE_AND_CAR` | 1.25 | | +| `present(highway); cycleway=opposite_lane` | `PEDESTRIAN_AND_BICYCLE` | forward: 1.0
back: 0.87 | | +| `highway=service; cycleway=opposite_lane` | `ALL` | forward: 1.1
back: 0.77 | | +| `highway=residential; cycleway=opposite_lane` | `ALL` | forward: 0.98
back: 0.77 | | +| `highway=residential_link; cycleway=opposite_lane` | `ALL` | forward: 0.98
back: 0.77 | | +| `highway=tertiary; cycleway=opposite_lane` | `ALL` | forward: 1.0
back: 0.87 | | +| `highway=tertiary_link; cycleway=opposite_lane` | `ALL` | forward: 1.0
back: 0.87 | | +| `highway=secondary; cycleway=opposite_lane` | `ALL` | forward: 1.5
back: 0.96 | | +| `highway=secondary_link; cycleway=opposite_lane` | `ALL` | forward: 1.5
back: 0.96 | | +| `highway=primary; cycleway=opposite_lane` | `ALL` | forward: 2.06
back: 1.15 | | +| `highway=primary_link; cycleway=opposite_lane` | `ALL` | forward: 2.06
back: 1.15 | | +| `highway=trunk; cycleway=opposite_lane` | `BICYCLE_AND_CAR` | forward: 7.47
back: 1.5 | | +| `highway=trunk_link; cycleway=opposite_lane` | `BICYCLE_AND_CAR` | forward: 2.06
back: 1.15 | | +| `present(highway); cycleway=track` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | +| `highway=service; cycleway=track` | `ALL` | 0.65 | | +| `highway=residential; cycleway=track` | `ALL` | 0.65 | | +| `highway=residential_link; cycleway=track` | `ALL` | 0.65 | | +| `highway=tertiary; cycleway=track` | `ALL` | 0.75 | | +| `highway=tertiary_link; cycleway=track` | `ALL` | 0.75 | | +| `highway=secondary; cycleway=track` | `ALL` | 0.8 | | +| `highway=secondary_link; cycleway=track` | `ALL` | 0.8 | | +| `highway=primary; cycleway=track` | `ALL` | 0.85 | | +| `highway=primary_link; cycleway=track` | `ALL` | 0.85 | | +| `highway=trunk; cycleway=track` | `BICYCLE_AND_CAR` | 0.95 | | +| `highway=trunk_link; cycleway=track` | `BICYCLE_AND_CAR` | 0.85 | | +| `present(highway); cycleway=opposite_track` | `PEDESTRIAN_AND_BICYCLE` | forward: 1.0
back: 0.75 | | +| `highway=service; cycleway=opposite_track` | `ALL` | forward: 1.1
back: 0.65 | | +| `highway=residential; cycleway=opposite_track` | `ALL` | forward: 0.98
back: 0.65 | | +| `highway=residential_link; cycleway=opposite_track` | `ALL` | forward: 0.98
back: 0.65 | | +| `highway=tertiary; cycleway=opposite_track` | `ALL` | forward: 1.0
back: 0.75 | | +| `highway=tertiary_link; cycleway=opposite_track` | `ALL` | forward: 1.0
back: 0.75 | | +| `highway=secondary; cycleway=opposite_track` | `ALL` | forward: 1.5
back: 0.8 | | +| `highway=secondary_link; cycleway=opposite_track` | `ALL` | forward: 1.5
back: 0.8 | | +| `highway=primary; cycleway=opposite_track` | `ALL` | forward: 2.06
back: 0.85 | | +| `highway=primary_link; cycleway=opposite_track` | `ALL` | forward: 2.06
back: 0.85 | | +| `highway=trunk; cycleway=opposite_track` | `BICYCLE_AND_CAR` | forward: 7.47
back: 0.95 | | +| `highway=trunk_link; cycleway=opposite_track` | `BICYCLE_AND_CAR` | forward: 2.06
back: 0.85 | | +| `present(highway); cycleway=shared_lane` | `PEDESTRIAN_AND_BICYCLE` | 0.77 | | +| `highway=service; cycleway=shared_lane` | `ALL` | 0.73 | | +| `highway=residential; cycleway=shared_lane` | `ALL` | 0.77 | | +| `highway=residential_link; cycleway=shared_lane` | `ALL` | 0.77 | | +| `highway=tertiary; cycleway=shared_lane` | `ALL` | 0.83 | | +| `highway=tertiary_link; cycleway=shared_lane` | `ALL` | 0.83 | | +| `highway=secondary; cycleway=shared_lane` | `ALL` | 1.25 | | +| `highway=secondary_link; cycleway=shared_lane` | `ALL` | 1.25 | | +| `highway=primary; cycleway=shared_lane` | `ALL` | 1.75 | | +| `highway=primary_link; cycleway=shared_lane` | `ALL` | 1.75 | | +| `present(highway); cycleway=opposite` | `PEDESTRIAN_AND_BICYCLE` | forward: 1.0
back: 1.4 | | +| `highway=service; cycleway=opposite` | `ALL` | 1.1 | | +| `highway=residential; cycleway=opposite` | `ALL` | 0.98 | | +| `highway=residential_link; cycleway=opposite` | `ALL` | 0.98 | | +| `highway=tertiary; cycleway=opposite` | `ALL` | | | +| `highway=tertiary_link; cycleway=opposite` | `ALL` | | | +| `highway=secondary; cycleway=opposite` | `ALL` | forward: 1.5
back: 1.71 | | +| `highway=secondary_link; cycleway=opposite` | `ALL` | forward: 1.5
back: 1.71 | | +| `highway=primary; cycleway=opposite` | `ALL` | forward: 2.06
back: 2.99 | | +| `highway=primary_link; cycleway=opposite` | `ALL` | forward: 2.06
back: 2.99 | | +| `highway=path; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | | +| `highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | +| `highway=footway; bicycle=yes; area=yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | +| `highway=pedestrian; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | +| `footway=sidewalk; highway=footway; bicycle=yes` | `PEDESTRIAN_AND_BICYCLE` | 2.5 | | +| `footway=sidewalk; highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | +| `highway=footway; footway=crossing` | `PEDESTRIAN_AND_BICYCLE` | 2.5 | | +| `highway=footway; footway=crossing; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | +| `highway=track; bicycle=yes` | `PEDESTRIAN_AND_BICYCLE` | 1.18 | | +| `highway=track; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.99 | | +| `highway=track; bicycle=yes; present(surface)` | `PEDESTRIAN_AND_BICYCLE` | 1.18 | | +| `highway=track; bicycle=designated; present(surface)` | `PEDESTRIAN_AND_BICYCLE` | 0.99 | | +| `highway=track; present(surface)` | `PEDESTRIAN_AND_BICYCLE` | 1.3 | | +| `present(highway); bicycle=designated` | `ALL` | 0.97 | | +| `highway=service; bicycle=designated` | `ALL` | 0.84 | | +| `highway=residential; bicycle=designated` | `ALL` | 0.95 | | +| `highway=unclassified; bicycle=designated` | `ALL` | 0.95 | | +| `highway=residential_link; bicycle=designated` | `ALL` | 0.95 | | +| `highway=tertiary; bicycle=designated` | `ALL` | 0.97 | | +| `highway=tertiary_link; bicycle=designated` | `ALL` | 0.97 | | +| `highway=secondary; bicycle=designated` | `ALL` | 1.46 | | +| `highway=secondary_link; bicycle=designated` | `ALL` | 1.46 | | +| `highway=primary; bicycle=designated` | `ALL` | 2.0 | | +| `highway=primary_link; bicycle=designated` | `ALL` | 2.0 | | +| `highway=trunk; bicycle=designated` | `BICYCLE_AND_CAR` | 7.25 | | +| `highway=trunk_link; bicycle=designated` | `BICYCLE_AND_CAR` | 2.0 | | +| `highway=motorway; bicycle=designated` | `BICYCLE_AND_CAR` | 7.76 | | +| `highway=motorway_link; bicycle=designated` | `BICYCLE_AND_CAR` | 2.0 | | + + + +### Safety mixins + +Mixins are selectors that have only an effect on the bicycle and walk safety factors but not on the +permission of an OSM way. Their safety values are multiplied with the base values from the selected +way properties. Multiple mixins can apply to the same way and their effects compound. + + + + +| matcher | bicycle safety | walk safety | +|------------------------------------------------------------|----------------|-------------| +| `lcn=yes¦rcn=yes¦ncn=yes¦bicycle_road=yes¦cyclestreet=yes` | 0.7 | | +| `surface=unpaved` | 1.18 | | +| `surface=compacted` | 1.18 | | +| `surface=wood` | 1.18 | | +| `surface=cobblestone` | 1.3 | | +| `surface=sett` | 1.3 | | +| `surface=unhewn_cobblestone` | 1.5 | | +| `surface=grass_paver` | 1.3 | | +| `surface=pebblestone` | 1.3 | | +| `surface=metal` | 1.3 | | +| `surface=ground` | 1.5 | | +| `surface=dirt` | 1.5 | | +| `surface=earth` | 1.5 | | +| `surface=grass` | 1.5 | | +| `surface=mud` | 1.5 | | +| `surface=woodchip` | 1.5 | | +| `surface=gravel` | 1.5 | | +| `surface=artifical_turf` | 1.5 | | +| `surface=sand` | 100.0 | | +| `foot=discouraged` | | 3.0 | +| `bicycle=discouraged` | 3.0 | | +| `foot=use_sidepath` | | 5.0 | +| `bicycle=use_sidepath` | 5.0 | | + + diff --git a/docs/sandbox/ReportApi.md b/docs/sandbox/ReportApi.md index 1a0668d1740..bc219ec2f98 100644 --- a/docs/sandbox/ReportApi.md +++ b/docs/sandbox/ReportApi.md @@ -24,14 +24,6 @@ This module mounts an endpoint for generating reports under `otp/report`. Availa - [/otp/report/transfers.csv](http://localhost:8080/otp/report/transfers.csv) - [/otp/report/graph.json](http://localhost:8080/otp/report/graph.json) Detailed numbers of transit and street entities in the graph -- [/otp/report/bicycle-safety.html](http://localhost:8080/otp/report/bicycle-safety.html): - Interactive viewer of the rules that determine how bicycle safety factors are calculated. -- [/otp/report/bicycle-safety.csv](http://localhost:8080/otp/report/bicycle-safety.csv): Raw CSV - data for the bicycle safety report. - - [Norwegian version](http://localhost:8080/otp/report/bicycle-safety.csv?osmWayPropertySet=norway) - - [German version](http://localhost:8080/otp/report/bicycle-safety.csv?osmWayPropertySet=germany) - - [UK version](http://localhost:8080/otp/report/bicycle-safety.csv?osmWayPropertySet=uk) - - [Finnish version](http://localhost:8080/otp/report/bicycle-safety.csv?osmWayPropertySet=finland) - [/otp/report/transit/group/priorities](http://localhost:8080/otp/report/transit/group/priorities): List all transit groups used for transit-group-priority (Competition neutral planning). diff --git a/docs/sandbox/VehicleRentalServiceDirectory.md b/docs/sandbox/VehicleRentalServiceDirectory.md index 8009a62f912..1be5418e155 100644 --- a/docs/sandbox/VehicleRentalServiceDirectory.md +++ b/docs/sandbox/VehicleRentalServiceDirectory.md @@ -1,10 +1,10 @@ -# Vehicle Rental Service Directory API support. +# Vehicle Rental Service Directory API support -This adds support for the GBFS service directory endpoint component located at -https://github.com/entur/lamassu. OTP uses the service directory to lookup and connect to all GBFS -endpoints registered in the directory. This simplifies the management of the GBFS endpoints, since -multiple services/components like OTP can connect to the directory and get the necessary -configuration from it. +This adds support for the GBFS service directory endpoint component +[Lamassu](https://github.com/entur/lamassu). +OTP uses the service directory to lookup and connects to all GBFS endpoints registered in the +directory. This simplifies the management of the GBFS endpoints, since multiple services/components +like OTP can connect to the directory and get the necessary configuration from it. ## Contact Info @@ -17,6 +17,7 @@ configuration from it. - Initial implementation of bike share updater API support - Make json tag names configurable [#3447](https://github.com/opentripplanner/OpenTripPlanner/pull/3447) - Enable GBFS geofencing with VehicleRentalServiceDirectory [#5324](https://github.com/opentripplanner/OpenTripPlanner/pull/5324) +- Enable `allowKeepingVehicleAtDestination` [#5944](https://github.com/opentripplanner/OpenTripPlanner/pull/5944) ## Configuration @@ -29,17 +30,18 @@ the `router-config.json` -| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | -|-----------------------------------------------------|:---------------:|---------------------------------------------------------------------------------|:----------:|---------------|:-----:| -| language | `string` | Language code. | *Optional* | | 2.1 | -| sourcesName | `string` | Json tag name for updater sources. | *Optional* | `"systems"` | 2.1 | -| updaterNetworkName | `string` | Json tag name for the network name for each source. | *Optional* | `"id"` | 2.1 | -| updaterUrlName | `string` | Json tag name for endpoint urls for each source. | *Optional* | `"url"` | 2.1 | -| url | `uri` | Endpoint for the VehicleRentalServiceDirectory | *Required* | | 2.1 | -| [headers](#vehicleRentalServiceDirectory_headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.1 | -| [networks](#vehicleRentalServiceDirectory_networks) | `object[]` | List all networks to include. Use "network": "default-network" to set defaults. | *Optional* | | 2.4 | -|       geofencingZones | `boolean` | Enables geofencingZones for the given network | *Optional* | `false` | 2.4 | -|       network | `string` | The network name | *Required* | | 2.4 | +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|----------------------------------------------------------------------------------------------------------------------|:---------------:|---------------------------------------------------------------------------------|:----------:|---------------|:-----:| +| language | `string` | Language code. | *Optional* | | 2.1 | +| sourcesName | `string` | Json tag name for updater sources. | *Optional* | `"systems"` | 2.1 | +| updaterNetworkName | `string` | Json tag name for the network name for each source. | *Optional* | `"id"` | 2.1 | +| updaterUrlName | `string` | Json tag name for endpoint urls for each source. | *Optional* | `"url"` | 2.1 | +| url | `uri` | Endpoint for the VehicleRentalServiceDirectory | *Required* | | 2.1 | +| [headers](#vehicleRentalServiceDirectory_headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.1 | +| [networks](#vehicleRentalServiceDirectory_networks) | `object[]` | List all networks to include. Use "network": "default-network" to set defaults. | *Optional* | | 2.4 | +|       [allowKeepingVehicleAtDestination](#vehicleRentalServiceDirectory_networks_0_allowKeepingVehicleAtDestination) | `boolean` | Enables `allowKeepingVehicleAtDestination` for the given network. | *Optional* | `false` | 2.5 | +|       [geofencingZones](#vehicleRentalServiceDirectory_networks_0_geofencingZones) | `boolean` | Enables geofencingZones for the given network | *Optional* | `false` | 2.4 | +|       network | `string` | The network name | *Required* | | 2.4 | @@ -69,6 +71,28 @@ networks are dropped. Note! The values in the "default-network" are not used to missing field values in networks listed. +

allowKeepingVehicleAtDestination

+ +**Since version:** `2.5` ∙ **Type:** `boolean` ∙ **Cardinality:** `Optional` ∙ **Default value:** `false` +**Path:** /vehicleRentalServiceDirectory/networks/[0] + +Enables `allowKeepingVehicleAtDestination` for the given network. + +Configures if a vehicle rented from a station must be returned to another one or can +be kept at the end of the trip. + +See the regular [GBFS documentation](../UpdaterConfig.md#gbfs-vehicle-rental-systems) for more information. + + +

geofencingZones

+ +**Since version:** `2.4` ∙ **Type:** `boolean` ∙ **Cardinality:** `Optional` ∙ **Default value:** `false` +**Path:** /vehicleRentalServiceDirectory/networks/[0] + +Enables geofencingZones for the given network + +See the regular [GBFS documentation](../UpdaterConfig.md#gbfs-vehicle-rental-systems) for more information. + diff --git a/mkdocs.yml b/mkdocs.yml index 7925151ac35..dbb9f86c877 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -73,6 +73,12 @@ nav: - Configuration: - Introduction: 'Configuration.md' - Build: 'BuildConfiguration.md' + - OSM Tag Mapping: + - Default: 'osm/Default.md' + - Finland: 'osm/Finland.md' + - Germany: 'osm/Germany.md' + - Norway: 'osm/Norway.md' + - UK: 'osm/UK.md' - Router: 'RouterConfiguration.md' - "Route Request": 'RouteRequest.md' - "Realtime Updaters": 'UpdaterConfig.md' diff --git a/pom.xml b/pom.xml index b3bb99d0861..586f835c860 100644 --- a/pom.xml +++ b/pom.xml @@ -60,10 +60,10 @@ 31.2 2.51.1 - 2.17.1 + 2.17.2 3.1.7 5.10.3 - 1.13.0 + 1.13.2 5.5.3 1.5.6 9.11.1 @@ -247,7 +247,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.3.0 + 3.3.1 me.fabriciorby diff --git a/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java b/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java index a069f5c6656..7eb44302fb9 100644 --- a/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java @@ -2,21 +2,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import java.util.List; import java.util.Set; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.opentripplanner.model.PickDrop; import org.opentripplanner.transit.model.timetable.RealTimeState; -import org.opentripplanner.transit.model.timetable.Trip; -import org.opentripplanner.transit.model.timetable.TripOnServiceDate; -import org.opentripplanner.transit.model.timetable.TripTimes; -import org.opentripplanner.transit.model.timetable.TripTimesFactory; -import org.opentripplanner.transit.service.DefaultTransitService; -import org.opentripplanner.transit.service.StopModel; -import org.opentripplanner.transit.service.TransitModel; -import org.opentripplanner.transit.service.TransitService; -import org.opentripplanner.updater.TimetableSnapshotSourceParameters; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.spi.UpdateResult; import org.opentripplanner.updater.trip.RealtimeTestEnvironment; @@ -88,6 +77,30 @@ void testAddedJourneyWithInvalidScheduledData() { assertFailure(UpdateError.UpdateErrorType.NEGATIVE_HOP_TIME, result); } + @Test + void testAddedJourneyWithUnresolvableAgency() { + var env = RealtimeTestEnvironment.siri(); + + // Create an extra journey with unknown line and operator + var createExtraJourney = new SiriEtBuilder(env.getDateTimeHelper()) + .withEstimatedVehicleJourneyCode("newJourney") + .withIsExtraJourney(true) + .withOperatorRef("unknown operator") + .withLineRef("unknown line") + .withEstimatedCalls(builder -> + builder + .call(env.stopA1) + .departAimedExpected("10:58", "10:48") + .call(env.stopB1) + .arriveAimedExpected("10:08", "10:58") + ) + .buildEstimatedTimetableDeliveries(); + + var result = env.applyEstimatedTimetable(createExtraJourney); + assertEquals(0, result.successful()); + assertFailure(UpdateError.UpdateErrorType.CANNOT_RESOLVE_AGENCY, result); + } + @Test void testReplaceJourney() { var env = RealtimeTestEnvironment.siri(); diff --git a/src/ext/java/org/opentripplanner/ext/reportapi/model/BicycleSafetyReport.java b/src/ext/java/org/opentripplanner/ext/reportapi/model/BicycleSafetyReport.java deleted file mode 100644 index 8830189107d..00000000000 --- a/src/ext/java/org/opentripplanner/ext/reportapi/model/BicycleSafetyReport.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.opentripplanner.ext.reportapi.model; - -import org.opentripplanner.openstreetmap.tagmapping.OsmTagMapperSource; -import org.opentripplanner.openstreetmap.wayproperty.WayPropertySet; - -public class BicycleSafetyReport { - - public static void main(String[] args) { - System.out.println(makeCsv(OsmTagMapperSource.NORWAY)); - } - - public static String makeCsv(OsmTagMapperSource source) { - var wayPropertySet = new WayPropertySet(); - - source.getInstance().populateProperties(wayPropertySet); - - var buf = new CsvReportBuilder(","); - - buf.addHeader( - "OSM tags for osmWayPropertySet " + source, - "mixin", - "permissions", - "safety penalty there", - "safety penalty back" - ); - - wayPropertySet - .getWayProperties() - .forEach(p -> { - buf.addText(p.specifier().toString()); - buf.addBoolean(false); - buf.addText(p.properties().getPermission().toString()); - - var safetyProps = p.properties().bicycleSafety(); - if (safetyProps != null) { - buf.addNumber(safetyProps.forward()); - buf.addNumber(safetyProps.back()); - } - buf.newLine(); - }); - - wayPropertySet - .getMixins() - .forEach(p -> { - buf.addText(p.specifier().toString()); - buf.addBoolean(true); - buf.addText(""); - - var safetyProps = p.bicycleSafety(); - buf.addNumber(safetyProps.forward()); - buf.addNumber(safetyProps.back()); - buf.newLine(); - }); - - return buf.toString(); - } -} diff --git a/src/ext/java/org/opentripplanner/ext/reportapi/resource/ReportResource.java b/src/ext/java/org/opentripplanner/ext/reportapi/resource/ReportResource.java index a859b4ff78a..e06aec71b53 100644 --- a/src/ext/java/org/opentripplanner/ext/reportapi/resource/ReportResource.java +++ b/src/ext/java/org/opentripplanner/ext/reportapi/resource/ReportResource.java @@ -1,25 +1,18 @@ package org.opentripplanner.ext.reportapi.resource; -import jakarta.ws.rs.BadRequestException; -import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.time.Duration; -import org.opentripplanner.ext.reportapi.model.BicycleSafetyReport; import org.opentripplanner.ext.reportapi.model.CachedValue; import org.opentripplanner.ext.reportapi.model.GraphReportBuilder; import org.opentripplanner.ext.reportapi.model.GraphReportBuilder.GraphStats; import org.opentripplanner.ext.reportapi.model.TransfersReport; import org.opentripplanner.ext.reportapi.model.TransitGroupPriorityReport; import org.opentripplanner.model.transfer.TransferService; -import org.opentripplanner.openstreetmap.tagmapping.OsmTagMapperSource; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.transit.service.TransitService; @@ -51,39 +44,6 @@ public String getTransfersAsCsv() { return TransfersReport.export(transferService.listAll(), transitService); } - @GET - @Path("/bicycle-safety.html") - @Produces(MediaType.TEXT_HTML) - public Response getBicycleSafetyPage() { - try (var is = getClass().getResourceAsStream("/reportapi/report.html")) { - return Response.ok(new String(is.readAllBytes(), StandardCharsets.UTF_8)).build(); - } catch (IOException e) { - return Response.serverError().build(); - } - } - - @GET - @Path("/bicycle-safety.csv") - @Produces("text/csv") - public Response getBicycleSafetyAsCsv( - @DefaultValue("DEFAULT") @QueryParam("osmWayPropertySet") String osmWayPropertySet - ) { - OsmTagMapperSource source; - try { - source = OsmTagMapperSource.valueOf(osmWayPropertySet.toUpperCase()); - } catch (IllegalArgumentException ignore) { - throw new BadRequestException("Unknown osmWayPropertySet: " + osmWayPropertySet); - } - - return Response - .ok(BicycleSafetyReport.makeCsv(source)) - .header( - "Content-Disposition", - "attachment; filename=\"" + osmWayPropertySet + "-bicycle-safety.csv\"" - ) - .build(); - } - @GET @Path("/transit/group/priorities") @Produces(MediaType.TEXT_PLAIN) diff --git a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java index 8eae3be7077..aac485da4eb 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java @@ -2,6 +2,7 @@ import static java.lang.Boolean.TRUE; import static org.opentripplanner.ext.siri.mapper.SiriTransportModeMapper.mapTransitMainMode; +import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.CANNOT_RESOLVE_AGENCY; import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.NO_START_DATE; import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.NO_VALID_STOPS; import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.TOO_FEW_STOPS; @@ -13,6 +14,8 @@ import java.util.List; import java.util.Objects; import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.opentripplanner.ext.siri.mapper.PickDropMapper; import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.framework.time.ServiceDateUtils; @@ -172,7 +175,11 @@ Result build() { Route route = entityResolver.resolveRoute(lineRef); if (route == null) { - route = createRoute(); + Agency agency = resolveAgency(); + if (agency == null) { + return UpdateError.result(tripId, CANNOT_RESOLVE_AGENCY); + } + route = createRoute(agency); LOG.info("Adding route {} to transitModel.", route); transitModel.getTransitModelIndex().addRoutes(route); } @@ -278,24 +285,34 @@ Result build() { /** * Method to create a Route. Commonly used to create a route if a real-time message * refers to a route that is not in the transit model. - * - * We will find the first Route with same Operator, and use the same Authority - * If no operator found, copy the agency from replaced route - * * If no name is given for the route, an empty string will be set as the name. * * @return a new Route */ - private Route createRoute() { + @Nonnull + private Route createRoute(Agency agency) { var routeBuilder = Route.of(entityResolver.resolveId(lineRef)); routeBuilder.withShortName(shortName); routeBuilder.withMode(transitMode); routeBuilder.withNetexSubmode(transitSubMode); routeBuilder.withOperator(operator); + routeBuilder.withAgency(agency); + + return routeBuilder.build(); + } - // TODO - SIRI: Is there a better way to find authority/Agency? - Agency agency = transitModel + /** + * Attempt to find the agency to which this new trip belongs. + * The algorithm retrieves any route operated by the same operator as the one operating this new + * trip and resolves its agency. + * If no route with the same operator can be found, the algorithm falls back to retrieving the + * agency operating the replaced route. + * If none can be found the method returns null. + */ + @Nullable + private Agency resolveAgency() { + return transitModel .getTransitModelIndex() .getAllRoutes() .stream() @@ -303,9 +320,6 @@ private Route createRoute() { .findFirst() .map(Route::getAgency) .orElseGet(() -> replacedRoute != null ? replacedRoute.getAgency() : null); - routeBuilder.withAgency(agency); - - return routeBuilder.build(); } private Trip createTrip(Route route, FeedScopedId calServiceId) { diff --git a/src/ext/java/org/opentripplanner/ext/smoovebikerental/SmooveBikeRentalDataSource.java b/src/ext/java/org/opentripplanner/ext/smoovebikerental/SmooveBikeRentalDataSource.java index 3876ff44651..b65c22b4727 100644 --- a/src/ext/java/org/opentripplanner/ext/smoovebikerental/SmooveBikeRentalDataSource.java +++ b/src/ext/java/org/opentripplanner/ext/smoovebikerental/SmooveBikeRentalDataSource.java @@ -8,6 +8,7 @@ import org.opentripplanner.service.vehiclerental.model.RentalVehicleType; import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStation; +import org.opentripplanner.service.vehiclerental.model.VehicleRentalSystem; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.updater.spi.DataSource; import org.opentripplanner.updater.spi.GenericJsonDataSource; @@ -32,6 +33,7 @@ public class SmooveBikeRentalDataSource private final String networkName; private final RentalVehicleType vehicleType; + private final VehicleRentalSystem system; public SmooveBikeRentalDataSource(SmooveBikeRentalDataSourceParameters config) { this(config, new OtpHttpClientFactory()); @@ -45,6 +47,24 @@ public SmooveBikeRentalDataSource( networkName = config.getNetwork(DEFAULT_NETWORK_NAME); vehicleType = RentalVehicleType.getDefaultType(networkName); overloadingAllowed = config.overloadingAllowed(); + system = + new VehicleRentalSystem( + networkName, + "fi", + "Helsinki/Espoo", + null, + null, + null, + null, + null, + null, + null, + null, + "Europe/Helsinki", + null, + null, + null + ); } /** @@ -94,6 +114,7 @@ protected VehicleRentalStation parseElement(JsonNode node) { station.vehicleTypesAvailable = Map.of(vehicleType, station.vehiclesAvailable); station.vehicleSpacesAvailable = Map.of(vehicleType, station.spacesAvailable); station.overloadingAllowed = overloadingAllowed; + station.system = system; return station; } } diff --git a/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java b/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java index 9b350dec345..03b0acd59cd 100644 --- a/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java +++ b/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java @@ -105,8 +105,7 @@ private static List buildListOfNetworksFr new GbfsVehicleRentalDataSourceParameters( updaterUrl.get(), parameters.getLanguage(), - // allowKeepingRentedVehicleAtDestination - not part of GBFS, not supported here - false, + networkParams.allowKeepingAtDestination(), parameters.getHeaders(), networkName, networkParams.geofencingZones(), diff --git a/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/api/NetworkParameters.java b/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/api/NetworkParameters.java index 25d394f72ec..c8b638edae3 100644 --- a/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/api/NetworkParameters.java +++ b/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/api/NetworkParameters.java @@ -5,15 +5,18 @@ /** * Parameters for a specific network. *

- * The {@link GbfsVehicleRentalDataSourceParameters} support {@code overloadingAllowed} and - * {@code allowKeepingRentedVehicleAtDestination} is not included here since they are not part of - * the GBFS specification. If there is a demand for these, they can be added. + * The {@link GbfsVehicleRentalDataSourceParameters} supports {@code overloadingAllowed} + * which is not included here since it is not part of + * the GBFS specification. If there is a demand for it, it can be added. *

* @param network The network name * @param geofencingZones enable geofencingZones for the given network + * @param allowKeepingAtDestination if a vehicle that was picked up from a station must be returned + * to another one or can be kept at the destination. + * {@link org.opentripplanner.standalone.config.routerconfig.updaters.sources.VehicleRentalSourceFactory#allOwKeepingRentedVehicleAtDestination()} */ -public record NetworkParameters(String network, boolean geofencingZones) { - public NetworkParameters withName(String network) { - return new NetworkParameters(network, geofencingZones); - } -} +public record NetworkParameters( + String network, + boolean geofencingZones, + boolean allowKeepingAtDestination +) {} diff --git a/src/ext/resources/reportapi/report.html b/src/ext/resources/reportapi/report.html deleted file mode 100644 index bfa5af66603..00000000000 --- a/src/ext/resources/reportapi/report.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - OTP Bicycle safety factor report browser - - - - - - - - - - - - - - - - -

- - -
-

OTP Bicycle safety factor report browser

-
- Documentation -
- -
- -
- -
- - - - - - - - - - - - -
OSM tag match expressionMixin?Traversal permissionsSafety factor there1Safety factor back1
-
- -
- 1: Smaller means more cycling friendly. Larger means less cycle friendly. Think of at as a multiplier for the cost of traversing the way. -
-
-
-
- - - - - - - diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index e309346339b..ca059723acd 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -75,6 +75,7 @@ import org.opentripplanner.apis.gtfs.datafetchers.UnknownImpl; import org.opentripplanner.apis.gtfs.datafetchers.VehicleParkingImpl; import org.opentripplanner.apis.gtfs.datafetchers.VehiclePositionImpl; +import org.opentripplanner.apis.gtfs.datafetchers.VehicleRentalNetworkImpl; import org.opentripplanner.apis.gtfs.datafetchers.VehicleRentalStationImpl; import org.opentripplanner.apis.gtfs.datafetchers.debugOutputImpl; import org.opentripplanner.apis.gtfs.datafetchers.elevationProfileComponentImpl; @@ -166,6 +167,7 @@ protected static GraphQLSchema buildSchema() { .type(typeWiring.build(BookingTimeImpl.class)) .type(typeWiring.build(BookingInfoImpl.class)) .type(typeWiring.build(VehicleRentalStationImpl.class)) + .type(typeWiring.build(VehicleRentalNetworkImpl.class)) .type(typeWiring.build(RentalVehicleImpl.class)) .type(typeWiring.build(RentalVehicleTypeImpl.class)) .type(typeWiring.build(StopOnRouteImpl.class)) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index eae4bc2ad33..f187a49d9c7 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -330,6 +330,7 @@ public DataFetcher> nearest() { List filterByPlaceTypes = args.getGraphQLFilterByPlaceTypes() != null ? args.getGraphQLFilterByPlaceTypes().stream().map(GraphQLUtils::toModel).toList() : DEFAULT_PLACE_TYPES; + List filterByNetwork = args.getGraphQLFilterByNetwork(); List places; try { @@ -347,6 +348,7 @@ public DataFetcher> nearest() { filterByStations, filterByRoutes, filterByBikeRentalStations, + filterByNetwork, getTransitService(environment) ) ); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java index 53c3ba1343d..c4fb92c0ef4 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java @@ -6,6 +6,7 @@ import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; import org.opentripplanner.service.vehiclerental.model.RentalVehicleType; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; +import org.opentripplanner.service.vehiclerental.model.VehicleRentalSystem; import org.opentripplanner.service.vehiclerental.model.VehicleRentalVehicle; public class RentalVehicleImpl implements GraphQLDataFetchers.GraphQLRentalVehicle { @@ -61,6 +62,11 @@ public DataFetcher vehicleType() { return environment -> getSource(environment).vehicleType; } + @Override + public DataFetcher rentalNetwork() { + return environment -> getSource(environment).getVehicleRentalSystem(); + } + private VehicleRentalVehicle getSource(DataFetchingEnvironment environment) { return environment.getSource(); } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/VehicleRentalNetworkImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/VehicleRentalNetworkImpl.java new file mode 100644 index 00000000000..75f771eed83 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/VehicleRentalNetworkImpl.java @@ -0,0 +1,23 @@ +package org.opentripplanner.apis.gtfs.datafetchers; + +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; +import org.opentripplanner.service.vehiclerental.model.VehicleRentalSystem; + +public class VehicleRentalNetworkImpl implements GraphQLDataFetchers.GraphQLVehicleRentalNetwork { + + @Override + public DataFetcher networkId() { + return environment -> getSource(environment).systemId; + } + + @Override + public DataFetcher url() { + return environment -> getSource(environment).url; + } + + private VehicleRentalSystem getSource(DataFetchingEnvironment environment) { + return environment.getSource(); + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/VehicleRentalStationImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/VehicleRentalStationImpl.java index dc60a7c76e8..0603d19e412 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/VehicleRentalStationImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/VehicleRentalStationImpl.java @@ -7,6 +7,7 @@ import org.opentripplanner.service.vehiclerental.model.RentalVehicleEntityCounts; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStation; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; +import org.opentripplanner.service.vehiclerental.model.VehicleRentalSystem; public class VehicleRentalStationImpl implements GraphQLDataFetchers.GraphQLVehicleRentalStation { @@ -107,6 +108,11 @@ public DataFetcher availableSpaces() { return environment -> getSource(environment).getVehicleSpaceCounts(); } + @Override + public DataFetcher rentalNetwork() { + return environment -> getSource(environment).getVehicleRentalSystem(); + } + private VehicleRentalStation getSource(DataFetchingEnvironment environment) { return environment.getSource(); } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 6c66d3992f4..67944543580 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -58,6 +58,7 @@ import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStation; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; +import org.opentripplanner.service.vehiclerental.model.VehicleRentalSystem; import org.opentripplanner.service.vehiclerental.model.VehicleRentalVehicle; import org.opentripplanner.transit.model.basic.Money; import org.opentripplanner.transit.model.network.Route; @@ -852,6 +853,8 @@ public interface GraphQLRentalVehicle { public DataFetcher operative(); + public DataFetcher rentalNetwork(); + public DataFetcher rentalUris(); public DataFetcher vehicleId(); @@ -1266,6 +1269,17 @@ public interface GraphQLVehiclePosition { public DataFetcher vehicleId(); } + /** + * Vehicle rental network, which is referred as system in the GBFS terminology. Note, the same operator can operate in multiple + * regions either with the same network/system or with a different one. This can contain information about either the rental brand + * or about the operator. + */ + public interface GraphQLVehicleRentalNetwork { + public DataFetcher networkId(); + + public DataFetcher url(); + } + /** Vehicle rental station represents a location where users can rent bicycles etc. for a fee. */ public interface GraphQLVehicleRentalStation { public DataFetcher allowDropoff(); @@ -1298,6 +1312,8 @@ public interface GraphQLVehicleRentalStation { public DataFetcher realtime(); + public DataFetcher rentalNetwork(); + public DataFetcher rentalUris(); public DataFetcher spacesAvailable(); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index 3c187ca3bbe..541219481ef 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -2409,6 +2409,7 @@ public static class GraphQLQueryTypeNearestArgs { private String before; private GraphQLInputFiltersInput filterByIds; private List filterByModes; + private List filterByNetwork; private List filterByPlaceTypes; private Integer first; private Integer last; @@ -2430,6 +2431,7 @@ public GraphQLQueryTypeNearestArgs(Map args) { .map(GraphQLMode.class::cast) .collect(Collectors.toList()); } + this.filterByNetwork = (List) args.get("filterByNetwork"); if (args.get("filterByPlaceTypes") != null) { this.filterByPlaceTypes = ((List) args.get("filterByPlaceTypes")).stream() @@ -2466,6 +2468,10 @@ public List getGraphQLFilterByModes() { return this.filterByModes; } + public List getGraphQLFilterByNetwork() { + return this.filterByNetwork; + } + public List getGraphQLFilterByPlaceTypes() { return this.filterByPlaceTypes; } @@ -2510,6 +2516,10 @@ public void setGraphQLFilterByModes(List filterByModes) { this.filterByModes = filterByModes; } + public void setGraphQLFilterByNetwork(List filterByNetwork) { + this.filterByNetwork = filterByNetwork; + } + public void setGraphQLFilterByPlaceTypes(List filterByPlaceTypes) { this.filterByPlaceTypes = filterByPlaceTypes; } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml index ff9af6f6aa0..b9ee0ac3e16 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml @@ -47,6 +47,7 @@ config: BikeRentalStation: org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace#VehicleRentalPlace BikeRentalStationUris: org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris#VehicleRentalStationUris VehicleRentalStation: org.opentripplanner.service.vehiclerental.model.VehicleRentalStation#VehicleRentalStation + VehicleRentalNetwork: org.opentripplanner.service.vehiclerental.model.VehicleRentalSystem#VehicleRentalSystem RentalVehicleEntityCounts: org.opentripplanner.service.vehiclerental.model.RentalVehicleEntityCounts#RentalVehicleEntityCounts RentalVehicleTypeCount: org.opentripplanner.service.vehiclerental.model.RentalVehicleTypeCount#RentalVehicleTypeCount RentalVehicle: org.opentripplanner.service.vehiclerental.model.VehicleRentalVehicle#VehicleRentalVehicle diff --git a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java index a88c36ac039..9ad43606420 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java @@ -912,6 +912,7 @@ private GraphQLSchema create() { List filterByBikeRentalStations = null; List filterByBikeParks = null; List filterByCarParks = null; + List filterByNetwork = null; @SuppressWarnings("rawtypes") Map filterByIds = environment.getArgument("filterByIds"); if (filterByIds != null) { @@ -960,6 +961,7 @@ private GraphQLSchema create() { filterByStations, filterByRoutes, filterByBikeRentalStations, + filterByNetwork, GqlUtil.getTransitService(environment) ); diff --git a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 29489de19f2..8ee5d100b94 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -89,7 +89,7 @@ public enum OTPFeature { FaresV2(false, true, "Enable import of GTFS-Fares v2 data."), FlexRouting(false, true, "Enable FLEX routing."), GoogleCloudStorage(false, true, "Enable Google Cloud Storage integration."), - LegacyRestApi(true, true, "Enable legacy REST API. This API will be removed in the future."), + LegacyRestApi(false, true, "Enable legacy REST API. This API will be removed in the future."), RealtimeResolver( false, true, diff --git a/src/main/java/org/opentripplanner/inspector/vector/LayerParameters.java b/src/main/java/org/opentripplanner/inspector/vector/LayerParameters.java index ca4a6a64c76..cb849e0f0ac 100644 --- a/src/main/java/org/opentripplanner/inspector/vector/LayerParameters.java +++ b/src/main/java/org/opentripplanner/inspector/vector/LayerParameters.java @@ -10,7 +10,6 @@ public interface LayerParameters> { int MAX_ZOOM = 20; int CACHE_MAX_SECONDS = -1; double EXPANSION_FACTOR = 0.25d; - /** * User-visible name of the layer */ diff --git a/src/main/java/org/opentripplanner/model/TimetableSnapshot.java b/src/main/java/org/opentripplanner/model/TimetableSnapshot.java index 5c018412572..d7d9385d9b8 100644 --- a/src/main/java/org/opentripplanner/model/TimetableSnapshot.java +++ b/src/main/java/org/opentripplanner/model/TimetableSnapshot.java @@ -1,6 +1,8 @@ package org.opentripplanner.model; import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.SetMultimap; import java.time.LocalDate; import java.util.Collection; @@ -9,6 +11,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; @@ -80,8 +83,7 @@ public class TimetableSnapshot { * include ones from the scheduled GTFS, as well as ones added by realtime messages and * tracked by the TripPatternCache.

* Note that the keys do not include all scheduled TripPatterns, only those for which we have at - * least one update. The type of the field is specifically HashMap (rather than the more general - * Map interface) because we need to efficiently clone it.

+ * least one update.

* The members of the SortedSet (the Timetable for a particular day) are treated as copy-on-write * when we're updating them. If an update will modify the timetable for a particular day, that * timetable is replicated before any modifications are applied to avoid affecting any previous @@ -91,16 +93,15 @@ public class TimetableSnapshot { * The compound key approach better reflects the fact that there should be only one Timetable per * TripPattern and date. */ - private HashMap> timetables = new HashMap(); + private Map> timetables = new HashMap<>(); /** * For cases where the trip pattern (sequence of stops visited) has been changed by a realtime * update, a Map associating the updated trip pattern with a compound key of the feed-scoped - * trip ID and the service date. The type of this field is HashMap rather than the more general - * Map interface because we need to efficiently clone it whenever we start building up a new - * snapshot. TODO RT_AB: clarify if this is an index or the original source of truth. + * trip ID and the service date. + * TODO RT_AB: clarify if this is an index or the original source of truth. */ - private HashMap realtimeAddedTripPattern = new HashMap<>(); + private Map realtimeAddedTripPattern = new HashMap<>(); /** * This is an index of TripPatterns, not the primary collection. It tracks which TripPatterns @@ -186,25 +187,7 @@ public Result update( Timetable tt = resolve(pattern, serviceDate); // we need to perform the copy of Timetable here rather than in Timetable.update() // to avoid repeatedly copying in case several updates are applied to the same timetable - if (!dirtyTimetables.contains(tt)) { - Timetable old = tt; - tt = new Timetable(tt, serviceDate); - SortedSet sortedTimetables = timetables.get(pattern); - if (sortedTimetables == null) { - sortedTimetables = new TreeSet<>(new SortedTimetableComparator()); - } else { - SortedSet temp = new TreeSet<>(new SortedTimetableComparator()); - temp.addAll(sortedTimetables); - sortedTimetables = temp; - } - if (old.getServiceDate() != null) { - sortedTimetables.remove(old); - } - sortedTimetables.add(tt); - timetables.put(pattern, sortedTimetables); - dirtyTimetables.add(tt); - dirty = true; - } + tt = copyTimetable(pattern, serviceDate, tt); // Assume all trips in a pattern are from the same feed, which should be the case. // Find trip index @@ -256,9 +239,8 @@ public TimetableSnapshot commit(TransitLayerUpdater transitLayerUpdater, boolean if (!force && !this.isDirty()) { return null; } - ret.timetables = (HashMap>) this.timetables.clone(); - ret.realtimeAddedTripPattern = - (HashMap) this.realtimeAddedTripPattern.clone(); + ret.timetables = Map.copyOf(timetables); + ret.realtimeAddedTripPattern = Map.copyOf(realtimeAddedTripPattern); if (transitLayerUpdater != null) { transitLayerUpdater.update(dirtyTimetables, timetables); @@ -267,7 +249,7 @@ public TimetableSnapshot commit(TransitLayerUpdater transitLayerUpdater, boolean this.dirtyTimetables.clear(); this.dirty = false; - ret.setPatternsForStop(HashMultimap.create(this.patternsForStop)); + ret.patternsForStop = ImmutableSetMultimap.copyOf(patternsForStop); ret.readOnly = true; // mark the snapshot as henceforth immutable return ret; @@ -304,6 +286,10 @@ public void clear(String feedId) { * message and an attempt was made to re-associate it with its originally scheduled trip pattern. */ public boolean revertTripToScheduledTripPattern(FeedScopedId tripId, LocalDate serviceDate) { + if (readOnly) { + throw new ConcurrentModificationException("This TimetableSnapshot is read-only."); + } + boolean success = false; final TripPattern pattern = getRealtimeAddedTripPattern(tripId, serviceDate); @@ -330,10 +316,10 @@ public boolean revertTripToScheduledTripPattern(FeedScopedId tripId, LocalDate s } if (tripTimesToRemove != null) { - for (Timetable sortedTimetable : sortedTimetables) { - boolean isDirty = sortedTimetable.getTripTimes().remove(tripTimesToRemove); - if (isDirty) { - dirtyTimetables.add(sortedTimetable); + for (Timetable originalTimetable : sortedTimetables) { + if (originalTimetable.getTripTimes().contains(tripTimesToRemove)) { + Timetable updatedTimetable = copyTimetable(pattern, serviceDate, originalTimetable); + updatedTimetable.getTripTimes().remove(tripTimesToRemove); } } } @@ -370,7 +356,7 @@ public boolean purgeExpiredData(LocalDate serviceDate) { if (toKeepTimetables.isEmpty()) { it.remove(); } else { - timetables.put(pattern, toKeepTimetables); + timetables.put(pattern, ImmutableSortedSet.copyOfSorted(toKeepTimetables)); } } @@ -407,10 +393,6 @@ public Collection getPatternsForStop(StopLocation stop) { return patternsForStop.get(stop); } - public void setPatternsForStop(SetMultimap patternsForStop) { - this.patternsForStop = patternsForStop; - } - /** * Does this snapshot contain any realtime data or is it completely empty? */ @@ -424,7 +406,7 @@ public boolean isEmpty() { * @param feedId feed id to clear out * @return true if the timetable changed as a result of the call */ - protected boolean clearTimetable(String feedId) { + private boolean clearTimetable(String feedId) { return timetables.keySet().removeIf(tripPattern -> feedId.equals(tripPattern.getFeedId())); } @@ -434,7 +416,7 @@ protected boolean clearTimetable(String feedId) { * @param feedId feed id to clear out * @return true if the realtimeAddedTripPattern changed as a result of the call */ - protected boolean clearRealtimeAddedTripPattern(String feedId) { + private boolean clearRealtimeAddedTripPattern(String feedId) { return realtimeAddedTripPattern .keySet() .removeIf(realtimeAddedTripPattern -> @@ -455,6 +437,39 @@ private void addPatternToIndex(TripPattern tripPattern) { } } + /** + * Make a copy of the given timetable for a given pattern and service date. + * If the timetable was already copied-on write in this snapshot, the same instance will be + * returned. The SortedSet that holds the collection of Timetables for that pattern + * (sorted by service date) is shared between multiple snapshots and must be copied as well.
+ * Note on performance: if multiple Timetables are modified in a SortedSet, the SortedSet will be + * copied multiple times. The impact on memory/garbage collection is assumed to be minimal + * since the collection is small. + * The SortedSet is made immutable to prevent change after snapshot publication. + */ + private Timetable copyTimetable(TripPattern pattern, LocalDate serviceDate, Timetable tt) { + if (!dirtyTimetables.contains(tt)) { + Timetable old = tt; + tt = new Timetable(tt, serviceDate); + SortedSet sortedTimetables = timetables.get(pattern); + if (sortedTimetables == null) { + sortedTimetables = new TreeSet<>(new SortedTimetableComparator()); + } else { + SortedSet temp = new TreeSet<>(new SortedTimetableComparator()); + temp.addAll(sortedTimetables); + sortedTimetables = temp; + } + if (old.getServiceDate() != null) { + sortedTimetables.remove(old); + } + sortedTimetables.add(tt); + timetables.put(pattern, ImmutableSortedSet.copyOfSorted(sortedTimetables)); + dirtyTimetables.add(tt); + dirty = true; + } + return tt; + } + protected static class SortedTimetableComparator implements Comparator { @Override diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapper.java index 9989c102030..c5d1c0d1582 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapper.java @@ -602,25 +602,6 @@ public void populateProperties(WayPropertySet props) { /* Portland-local mixins */ - /* - * the RLIS/CCGIS:bicycle=designated mixins are coded out as they are no longer neccessary because of of the bicycle=designated block of code - * above. This switch makes our weighting system less reliant on tags that aren't generally used by the OSM community, and prevents the double - * counting that was occuring on streets with both bicycle infrastructure and an RLIS:bicycle=designated tag - */ - - /* - * props.setProperties("RLIS:bicycle=designated", StreetTraversalPermission.ALL, 0.97, 0.97, true); - */ - props.setMixinProperties("RLIS:bicycle=caution_area", ofBicycleSafety(1.45)); - props.setMixinProperties("RLIS:bicycle:right=caution_area", ofBicycleSafety(1.45, 1)); - props.setMixinProperties("RLIS:bicycle:left=caution_area", ofBicycleSafety(1, 1.45)); - /* - * props.setProperties("CCGIS:bicycle=designated", StreetTraversalPermission.ALL, 0.97, 0.97, true); - */ - props.setMixinProperties("CCGIS:bicycle=caution_area", ofBicycleSafety(1.45)); - props.setMixinProperties("CCGIS:bicycle:right=caution_area", ofBicycleSafety(1.45, 1)); - props.setMixinProperties("CCGIS:bicycle:left=caution_area", ofBicycleSafety(1, 1.45)); - props.setMixinProperties("foot=discouraged", ofWalkSafety(3)); props.setMixinProperties("bicycle=discouraged", ofBicycleSafety(3)); diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/NorwayMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/NorwayMapper.java index 9de824a145b..efe7850f08b 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/NorwayMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/NorwayMapper.java @@ -30,7 +30,7 @@ class NorwayMapper implements OsmTagMapper { @Override public void populateProperties(WayPropertySet props) { - var hasSidewalk = new Condition.EqualsAnyIn("sidewalk", "yes", "left", "right", "both"); + var hasSidewalk = new Condition.OneOf("sidewalk", "yes", "left", "right", "both"); var hasPrefixSidewalk = new Condition.Equals("sidewalk", "yes"); // e.g sidewalk:left=yes props.setDefaultWalkSafetyForPermission((permission, speedLimit, way) -> switch (permission) { @@ -74,16 +74,16 @@ else if (speedLimit >= 11.1f) { var cycleSafetyLowTraffic = 1.83; var cycleSafetyVeryLowTraffic = 1.57; - var isTrunkOrPrimary = new Condition.EqualsAnyIn( + var isTrunkOrPrimary = new Condition.OneOf( "highway", "trunk", "trunk_link", "primary", "primary_link" ); - var isSecondaryHighway = new Condition.EqualsAnyIn("highway", "secondary", "secondary_link"); - var isTertiaryHighway = new Condition.EqualsAnyIn("highway", "tertiary", "tertiary_link"); - var isClassifiedRoad = new Condition.EqualsAnyIn( + var isSecondaryHighway = new Condition.OneOf("highway", "secondary", "secondary_link"); + var isTertiaryHighway = new Condition.OneOf("highway", "tertiary", "tertiary_link"); + var isClassifiedRoad = new Condition.OneOf( "highway", "trunk", "trunk_link", @@ -94,7 +94,7 @@ else if (speedLimit >= 11.1f) { "tertiary", "tertiary_link" ); - var isClassifiedOrUnclassifiedRoad = new Condition.EqualsAnyIn( + var isClassifiedOrUnclassifiedRoad = new Condition.OneOf( "highway", "trunk", "trunk_link", @@ -107,7 +107,7 @@ else if (speedLimit >= 11.1f) { "unclassified" ); - var isNormalRoad = new Condition.EqualsAnyIn( + var isNormalRoad = new Condition.OneOf( "highway", "trunk", "trunk_link", @@ -166,7 +166,7 @@ else if (speedLimit >= 11.1f) { ); props.setProperties( - new ExactMatchSpecifier(new Condition.EqualsAnyIn("highway", "motorway", "motorway_link")), + new ExactMatchSpecifier(new Condition.OneOf("highway", "motorway", "motorway_link")), withModes(CAR) ); @@ -205,7 +205,7 @@ else if (speedLimit >= 11.1f) { props.setProperties( new ExactMatchSpecifier( new Condition.Equals("cycleway", "lane"), - new Condition.EqualsAnyIn("highway", "unclassified", "residential") + new Condition.OneOf("highway", "unclassified", "residential") ), cycleLaneInLowTraffic ); @@ -224,7 +224,7 @@ else if (speedLimit >= 11.1f) { props.setMixinProperties( new ExactMatchSpecifier( new Condition.Equals("oneway", "yes"), - new Condition.EqualsAnyInOrAbsent("cycleway"), + new Condition.OneOfOrAbsent("cycleway"), isNormalRoad ), ofBicycleSafety(1, 1.15) @@ -233,7 +233,7 @@ else if (speedLimit >= 11.1f) { // Discourage cycling along tram tracks props.setMixinProperties( new ExactMatchSpecifier( - new Condition.EqualsAnyIn("embedded_rails", "tram", "light_rail", "disused") + new Condition.OneOf("embedded_rails", "tram", "light_rail", "disused") ), ofBicycleSafety(1.2) ); @@ -252,12 +252,12 @@ else if (speedLimit >= 11.1f) { new LogicalOrSpecifier( new ExactMatchSpecifier( new Condition.Equals("bridge", "yes"), - new Condition.EqualsAnyInOrAbsent("sidewalk", "no", "separate"), + new Condition.OneOfOrAbsent("sidewalk", "no", "separate"), isClassifiedOrUnclassifiedRoad ), new ExactMatchSpecifier( new Condition.Equals("verge", "no"), - new Condition.EqualsAnyInOrAbsent("sidewalk", "no", "separate"), + new Condition.OneOfOrAbsent("sidewalk", "no", "separate"), isClassifiedOrUnclassifiedRoad ) ), @@ -268,7 +268,7 @@ else if (speedLimit >= 11.1f) { props.setMixinProperties( new ExactMatchSpecifier( new Condition.Equals("junction", "roundabout"), - new Condition.EqualsAnyInOrAbsent("sidewalk", "no", "separate") + new Condition.OneOfOrAbsent("sidewalk", "no", "separate") ), ofWalkSafety(2.) ); @@ -297,7 +297,7 @@ else if (speedLimit >= 11.1f) { props.setProperties( new ExactMatchSpecifier( new Condition.Equals("highway", "service"), - new Condition.EqualsAnyIn("bus", "yes", "designated") + new Condition.OneOf("bus", "yes", "designated") ), withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(cycleSafetyMediumLowTraffic).walkSafety(1.9) ); @@ -404,49 +404,49 @@ else if (speedLimit >= 11.1f) { props.setProperties( new ExactMatchSpecifier( - new Condition.EqualsAnyIn("trail_visibility", "bad", "low", "poor", "horrible", "no"), + new Condition.OneOf("trail_visibility", "bad", "low", "poor", "horrible", "no"), new Condition.Equals("highway", "path") ), withModes(NONE) ); props.setProperties( new ExactMatchSpecifier( - new Condition.EqualsAnyIn( + new Condition.OneOf( "sac_scale", "demanding_mountain_hiking", "alpine_hiking", "demanding_alpine_hiking", "difficult_alpine_hiking" ), - new Condition.EqualsAnyIn("highway", "path", "steps") + new Condition.OneOf("highway", "path", "steps") ), withModes(NONE) ); props.setProperties( new ExactMatchSpecifier( - new Condition.EqualsAnyIn("smoothness", "horrible", "very_horrible"), - new Condition.EqualsAnyIn("highway", "path", "bridleway", "track") + new Condition.OneOf("smoothness", "horrible", "very_horrible"), + new Condition.OneOf("highway", "path", "bridleway", "track") ), withModes(PEDESTRIAN).walkSafety(1.15) ); props.setProperties( new ExactMatchSpecifier( new Condition.Equals("smoothness", "impassable"), - new Condition.EqualsAnyIn("highway", "path", "bridleway", "track") + new Condition.OneOf("highway", "path", "bridleway", "track") ), withModes(NONE) ); props.setProperties( new ExactMatchSpecifier( new Condition.InclusiveRange("mtb:scale", 2, 1), - new Condition.EqualsAnyIn("highway", "path", "bridleway", "track") + new Condition.OneOf("highway", "path", "bridleway", "track") ), withModes(PEDESTRIAN).walkSafety(1.15) ); props.setProperties( new ExactMatchSpecifier( new Condition.GreaterThan("mtb:scale", 2), - new Condition.EqualsAnyIn("highway", "path", "bridleway", "track") + new Condition.OneOf("highway", "path", "bridleway", "track") ), withModes(NONE) ); @@ -461,7 +461,7 @@ else if (speedLimit >= 11.1f) { props.setMixinProperties("surface=metal_grid", ofBicycleSafety(1.2)); props.setMixinProperties("surface=metal", ofBicycleSafety(1.2)); // Paved but damaged - var isPaved = new Condition.EqualsAnyIn( + var isPaved = new Condition.OneOf( "surface", "asfalt", "concrete", @@ -502,46 +502,46 @@ else if (speedLimit >= 11.1f) { props.setMixinProperties( new ExactMatchSpecifier( new Condition.Absent("tracktype"), - new Condition.EqualsAnyInOrAbsent("surface", "unpaved"), - new Condition.EqualsAnyIn("highway", "track", "bridleway") + new Condition.OneOfOrAbsent("surface", "unpaved"), + new Condition.OneOf("highway", "track", "bridleway") ), ofBicycleSafety(1.8).walkSafety(1.6) ); props.setMixinProperties( new ExactMatchSpecifier( new Condition.Equals("tracktype", "grade2"), - new Condition.EqualsAnyInOrAbsent("surface", "unpaved"), - new Condition.EqualsAnyIn("highway", "track", "bridleway", "service", "unclassified") + new Condition.OneOfOrAbsent("surface", "unpaved"), + new Condition.OneOf("highway", "track", "bridleway", "service", "unclassified") ), ofBicycleSafety(1.4).walkSafety(1.4) ); props.setMixinProperties( new ExactMatchSpecifier( new Condition.Equals("tracktype", "grade3"), - new Condition.EqualsAnyInOrAbsent("surface", "unpaved"), - new Condition.EqualsAnyIn("highway", "track", "bridleway", "service", "unclassified") + new Condition.OneOfOrAbsent("surface", "unpaved"), + new Condition.OneOf("highway", "track", "bridleway", "service", "unclassified") ), ofBicycleSafety(1.8).walkSafety(1.6) ); props.setMixinProperties( new ExactMatchSpecifier( new Condition.Equals("tracktype", "grade4"), - new Condition.EqualsAnyInOrAbsent("surface", "unpaved"), - new Condition.EqualsAnyIn("highway", "track", "bridleway", "service", "unclassified") + new Condition.OneOfOrAbsent("surface", "unpaved"), + new Condition.OneOf("highway", "track", "bridleway", "service", "unclassified") ), ofBicycleSafety(2.3).walkSafety(1.8) ); props.setMixinProperties( new ExactMatchSpecifier( new Condition.Equals("tracktype", "grade5"), - new Condition.EqualsAnyInOrAbsent("surface", "unpaved"), - new Condition.EqualsAnyIn("highway", "track", "bridleway", "service", "unclassified") + new Condition.OneOfOrAbsent("surface", "unpaved"), + new Condition.OneOf("highway", "track", "bridleway", "service", "unclassified") ), ofBicycleSafety(2.3).walkSafety(2.4) ); props.setMixinProperties( new ExactMatchSpecifier( - new Condition.EqualsAnyInOrAbsent("surface"), + new Condition.OneOfOrAbsent("surface"), new Condition.Equals("highway", "path") ), ofBicycleSafety(2.3).walkSafety(2.4) @@ -560,7 +560,7 @@ else if (speedLimit >= 11.1f) { */ props.setCarSpeed( - new ExactMatchSpecifier(new Condition.EqualsAnyIn("highway", "motorway", "motorway_link")), + new ExactMatchSpecifier(new Condition.OneOf("highway", "motorway", "motorway_link")), 30.56f // 110 km/t ); @@ -570,7 +570,7 @@ else if (speedLimit >= 11.1f) { ); props.setCarSpeed( new ExactMatchSpecifier( - new Condition.EqualsAnyIn( + new Condition.OneOf( "highway", "trunk", "trunk_link", @@ -589,8 +589,8 @@ else if (speedLimit >= 11.1f) { ); props.setCarSpeed( new ExactMatchSpecifier( - new Condition.EqualsAnyIn("sidewalk", "yes", "both", "left", "right", "separate"), - new Condition.EqualsAnyIn( + new Condition.OneOf("sidewalk", "yes", "both", "left", "right", "separate"), + new Condition.OneOf( "highway", "trunk", "trunk_link", diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/PortlandMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/PortlandMapper.java index a532d279ba4..1a09b3b6714 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/PortlandMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/PortlandMapper.java @@ -1,5 +1,6 @@ package org.opentripplanner.openstreetmap.tagmapping; +import static org.opentripplanner.openstreetmap.wayproperty.MixinPropertiesBuilder.ofBicycleSafety; import static org.opentripplanner.openstreetmap.wayproperty.MixinPropertiesBuilder.ofWalkSafety; import static org.opentripplanner.openstreetmap.wayproperty.specifier.ExactMatchSpecifier.exact; @@ -41,6 +42,18 @@ public void populateProperties(WayPropertySet props) { props.setMixinProperties(exact("sidewalk=no;maxspeed=35 mph"), ofWalkSafety(2)); props.setMixinProperties(exact("sidewalk=no;maxspeed=30 mph"), ofWalkSafety(1.5)); + // rarely used tags that are specific to counties near Portland + // https://taginfo.openstreetmap.org/keys/RLIS:bicycle#overview + + props.setMixinProperties("RLIS:bicycle=caution_area", ofBicycleSafety(1.45)); + props.setMixinProperties("RLIS:bicycle:right=caution_area", ofBicycleSafety(1.45, 1)); + props.setMixinProperties("RLIS:bicycle:left=caution_area", ofBicycleSafety(1, 1.45)); + + // https://taginfo.openstreetmap.org/keys/CCGIS:bicycle#overview + props.setMixinProperties("CCGIS:bicycle=caution_area", ofBicycleSafety(1.45)); + props.setMixinProperties("CCGIS:bicycle:right=caution_area", ofBicycleSafety(1.45, 1)); + props.setMixinProperties("CCGIS:bicycle:left=caution_area", ofBicycleSafety(1, 1.45)); + // Max speed limit in Oregon is 70 mph ~= 113kmh ~= 31.3m/s props.maxPossibleCarSpeed = 31.4f; diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/SafetyFeatures.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/SafetyFeatures.java index fc166a69927..ab68fd978c2 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/SafetyFeatures.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/SafetyFeatures.java @@ -5,4 +5,18 @@ */ public record SafetyFeatures(double forward, double back) { public static final SafetyFeatures DEFAULT = new SafetyFeatures(1, 1); + + /** + * Does this instance actually modify the safety values? + */ + public boolean modifies() { + return !(forward == 1 && back == 1); + } + + /** + * Does forward and back have the same value? + */ + public boolean isSymmetric() { + return forward == back; + } } diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/BestMatchSpecifier.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/BestMatchSpecifier.java index d9f194aa4e5..293bcf84644 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/BestMatchSpecifier.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/BestMatchSpecifier.java @@ -1,5 +1,7 @@ package org.opentripplanner.openstreetmap.wayproperty.specifier; +import java.util.Arrays; +import java.util.stream.Collectors; import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.openstreetmap.model.OSMWithTags; @@ -72,6 +74,11 @@ public int matchScore(OSMWithTags way) { return score; } + @Override + public String toDocString() { + return Arrays.stream(conditions).map(Object::toString).collect(Collectors.joining("; ")); + } + @Override public String toString() { return ToStringBuilder.of(this.getClass()).addObj("conditions", conditions).toString(); diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/Condition.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/Condition.java index 4d8381963b3..4ad180c16c3 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/Condition.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/Condition.java @@ -107,13 +107,24 @@ enum MatchResult { NONE, } + /** + * Selects tags where a given key/value matches. + */ record Equals(String key, String value) implements Condition { @Override public boolean isExtendedKeyMatch(OSMWithTags way, String exKey) { return way.hasTag(exKey) && way.isTag(exKey, value); } + + @Override + public String toString() { + return "%s=%s".formatted(key, value); + } } + /** + * Selects tags with a given key. + */ record Present(String key) implements Condition { @Override public MatchResult matchType() { @@ -123,31 +134,63 @@ public MatchResult matchType() { public boolean isExtendedKeyMatch(OSMWithTags way, String exKey) { return way.hasTag(exKey); } + + @Override + public String toString() { + return "present(%s)".formatted(key); + } } + /** + * Selects tags where a given tag is absent. + */ record Absent(String key) implements Condition { @Override public boolean isExtendedKeyMatch(OSMWithTags way, String exKey) { return !way.hasTag(exKey); } + + @Override + public String toString() { + return "!%s".formatted(key); + } } + /** + * Selects tags where the integer value is greater than a given number. + */ record GreaterThan(String key, int value) implements Condition { @Override public boolean isExtendedKeyMatch(OSMWithTags way, String exKey) { var maybeInt = way.getTagAsInt(exKey, ignored -> {}); return maybeInt.isPresent() && maybeInt.getAsInt() > value; } + + @Override + public String toString() { + return "%s > %s".formatted(key, value); + } } + /** + * Selects tags where the integer value is less than a given number. + */ record LessThan(String key, int value) implements Condition { @Override public boolean isExtendedKeyMatch(OSMWithTags way, String exKey) { var maybeInt = way.getTagAsInt(exKey, ignored -> {}); return maybeInt.isPresent() && maybeInt.getAsInt() < value; } + + @Override + public String toString() { + return "%s < %s".formatted(key, value); + } } + /** + * Selects integer tag values and checks if they are in between a lower and an upper bound. + */ record InclusiveRange(String key, int upper, int lower) implements Condition { public InclusiveRange { if (upper < lower) { @@ -160,18 +203,36 @@ public boolean isExtendedKeyMatch(OSMWithTags way, String exKey) { var maybeInt = way.getTagAsInt(exKey, ignored -> {}); return maybeInt.isPresent() && maybeInt.getAsInt() >= lower && maybeInt.getAsInt() <= upper; } + + @Override + public String toString() { + return "%s > %s < %s".formatted(lower, key, upper); + } } - record EqualsAnyIn(String key, String... values) implements Condition { + /** + * Selects a tag which has one of a set of given values. + */ + record OneOf(String key, String... values) implements Condition { @Override public boolean isExtendedKeyMatch(OSMWithTags way, String exKey) { return Arrays.stream(values).anyMatch(value -> way.isTag(exKey, value)); } + + @Override + public String toString() { + return "%s one of [%s]".formatted(key, String.join(", ", values)); + } } - record EqualsAnyInOrAbsent(String key, String... values) implements Condition { + /** + * Selects a tag where one of the following conditions is true: + * - one of a set of given values matches + * - the tag is absent + */ + record OneOfOrAbsent(String key, String... values) implements Condition { /* A use case for this is to detect the absence of a sidewalk, cycle lane or verge*/ - public EqualsAnyInOrAbsent(String key) { + public OneOfOrAbsent(String key) { this(key, "no", "none"); } @@ -181,5 +242,10 @@ public boolean isExtendedKeyMatch(OSMWithTags way, String exKey) { !way.hasTag(exKey) || Arrays.stream(values).anyMatch(value -> way.isTag(exKey, value)) ); } + + @Override + public String toString() { + return "%s not one of [%s] or absent".formatted(key, String.join(", ", values)); + } } } diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/ExactMatchSpecifier.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/ExactMatchSpecifier.java index e6eb3f37940..48c3b5edb64 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/ExactMatchSpecifier.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/ExactMatchSpecifier.java @@ -2,6 +2,7 @@ import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; import org.opentripplanner.openstreetmap.model.OSMWithTags; /** @@ -54,6 +55,11 @@ public int matchScore(OSMWithTags way) { } } + @Override + public String toDocString() { + return conditions.stream().map(Object::toString).collect(Collectors.joining("; ")); + } + public boolean allTagsMatch(OSMWithTags way) { return conditions.stream().allMatch(o -> o.isMatch(way)); } diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/LogicalOrSpecifier.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/LogicalOrSpecifier.java index 229b26fa25a..74db280115b 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/LogicalOrSpecifier.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/LogicalOrSpecifier.java @@ -2,6 +2,7 @@ import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; import org.opentripplanner.openstreetmap.model.OSMWithTags; /** @@ -25,10 +26,6 @@ public LogicalOrSpecifier(ExactMatchSpecifier... specifiers) { this.subSpecs = Arrays.asList(specifiers); } - public LogicalOrSpecifier(Condition... conditions) { - this.subSpecs = Arrays.stream(conditions).map(ExactMatchSpecifier::new).toList(); - } - public LogicalOrSpecifier(String... specs) { this.subSpecs = Arrays.stream(specs).map(ExactMatchSpecifier::new).toList(); } @@ -47,4 +44,9 @@ public int matchScore(OSMWithTags way) { return 0; } } + + @Override + public String toDocString() { + return subSpecs.stream().map(ExactMatchSpecifier::toDocString).collect(Collectors.joining("|")); + } } diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/OsmSpecifier.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/OsmSpecifier.java index 1e6c53a25c9..71d629552ff 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/OsmSpecifier.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/OsmSpecifier.java @@ -42,6 +42,12 @@ static Condition[] parseConditions(String spec, String separator) { */ int matchScore(OSMWithTags way); + /** + * Convert this specifier to a human-readable identifier that represents this in (generated) + * documentation. + */ + String toDocString(); + record Scores(int forward, int backward) { public static Scores of(int s) { return new Scores(s, s); diff --git a/src/main/java/org/opentripplanner/routing/graphfinder/DirectGraphFinder.java b/src/main/java/org/opentripplanner/routing/graphfinder/DirectGraphFinder.java index 5ffa7cd2301..c36efc59e5b 100644 --- a/src/main/java/org/opentripplanner/routing/graphfinder/DirectGraphFinder.java +++ b/src/main/java/org/opentripplanner/routing/graphfinder/DirectGraphFinder.java @@ -63,6 +63,7 @@ public List findClosestPlaces( List filterByStations, List filterByRoutes, List filterByBikeRentalStations, + List filterByNetwork, TransitService transitService ) { throw new UnsupportedOperationException("Not implemented"); diff --git a/src/main/java/org/opentripplanner/routing/graphfinder/GraphFinder.java b/src/main/java/org/opentripplanner/routing/graphfinder/GraphFinder.java index 4c0b0c81144..063ed221dd5 100644 --- a/src/main/java/org/opentripplanner/routing/graphfinder/GraphFinder.java +++ b/src/main/java/org/opentripplanner/routing/graphfinder/GraphFinder.java @@ -69,6 +69,7 @@ List findClosestPlaces( List filterByStations, List filterByRoutes, List filterByBikeRentalStations, + List filterByNetwork, TransitService transitService ); } diff --git a/src/main/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitor.java b/src/main/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitor.java index e71504d58f3..16420a0d9eb 100644 --- a/src/main/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitor.java +++ b/src/main/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitor.java @@ -31,6 +31,7 @@ public class PlaceFinderTraverseVisitor implements TraverseVisitor private final Set filterByStops; private final Set filterByStations; private final Set filterByRoutes; + private final Set filterByNetwork; private final Set filterByVehicleRental; private final Set seenPatternAtStops = new HashSet<>(); private final Set seenStops = new HashSet<>(); @@ -69,6 +70,7 @@ public PlaceFinderTraverseVisitor( List filterByStations, List filterByRoutes, List filterByBikeRentalStations, + List filterByNetwork, int maxResults, double radiusMeters ) { @@ -82,6 +84,7 @@ public PlaceFinderTraverseVisitor( this.filterByStations = toSet(filterByStations); this.filterByRoutes = toSet(filterByRoutes); this.filterByVehicleRental = toSet(filterByBikeRentalStations); + this.filterByNetwork = toSet(filterByNetwork); includeStops = shouldInclude(filterByPlaceTypes, PlaceType.STOP); includePatternAtStops = shouldInclude(filterByPlaceTypes, PlaceType.PATTERN_AT_STOP); @@ -264,6 +267,9 @@ private void handleVehicleRental(VehicleRentalPlace station, double distance) { if (seenVehicleRentalPlaces.contains(station.getId())) { return; } + if (!filterByNetwork.isEmpty() && !filterByNetwork.contains(station.getNetwork())) { + return; + } seenVehicleRentalPlaces.add(station.getId()); placesFound.add(new PlaceAtDistance(station, distance)); } diff --git a/src/main/java/org/opentripplanner/routing/graphfinder/StreetGraphFinder.java b/src/main/java/org/opentripplanner/routing/graphfinder/StreetGraphFinder.java index 1b2b1d8f522..71f65209ddf 100644 --- a/src/main/java/org/opentripplanner/routing/graphfinder/StreetGraphFinder.java +++ b/src/main/java/org/opentripplanner/routing/graphfinder/StreetGraphFinder.java @@ -56,6 +56,7 @@ public List findClosestPlaces( List filterByStations, List filterByRoutes, List filterByBikeRentalStations, + List filterByNetwork, TransitService transitService ) { PlaceFinderTraverseVisitor visitor = new PlaceFinderTraverseVisitor( @@ -66,6 +67,7 @@ public List findClosestPlaces( filterByStations, filterByRoutes, filterByBikeRentalStations, + filterByNetwork, maxResults, radiusMeters ); diff --git a/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalPlace.java b/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalPlace.java index 1725e7df2dd..cd806603c9d 100644 --- a/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalPlace.java +++ b/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalPlace.java @@ -80,6 +80,9 @@ public interface VehicleRentalPlace { /** Deep links for this rental station or individual vehicle */ VehicleRentalStationUris getRentalUris(); + /** System information for the vehicle rental provider */ + VehicleRentalSystem getVehicleRentalSystem(); + default boolean networkIsNotAllowed(VehicleRentalPreferences preferences) { if ( getNetwork() == null && diff --git a/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalStation.java b/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalStation.java index a31e5e88a0e..d6e72023a31 100644 --- a/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalStation.java +++ b/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalStation.java @@ -172,6 +172,11 @@ public VehicleRentalStationUris getRentalUris() { return rentalUris; } + @Override + public VehicleRentalSystem getVehicleRentalSystem() { + return system; + } + @Override public String toString() { return String.format( diff --git a/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java b/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java index 7dba5e714b4..042e608c88f 100644 --- a/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java +++ b/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java @@ -128,4 +128,9 @@ public boolean isRealTimeData() { public VehicleRentalStationUris getRentalUris() { return rentalUris; } + + @Override + public VehicleRentalSystem getVehicleRentalSystem() { + return system; + } } diff --git a/src/main/java/org/opentripplanner/standalone/config/sandbox/VehicleRentalServiceDirectoryFetcherConfig.java b/src/main/java/org/opentripplanner/standalone/config/sandbox/VehicleRentalServiceDirectoryFetcherConfig.java index b30fc3d5b3f..f8ed1184c04 100644 --- a/src/main/java/org/opentripplanner/standalone/config/sandbox/VehicleRentalServiceDirectoryFetcherConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/sandbox/VehicleRentalServiceDirectoryFetcherConfig.java @@ -3,6 +3,7 @@ import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_0; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_1; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_4; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_5; import java.util.List; import org.opentripplanner.ext.vehiclerentalservicedirectory.api.NetworkParameters; @@ -79,6 +80,22 @@ private static List mapNetworkParameters( .of("geofencingZones") .since(V2_4) .summary("Enables geofencingZones for the given network") + .description( + "See the regular [GBFS documentation](../UpdaterConfig.md#gbfs-vehicle-rental-systems) for more information." + ) + .asBoolean(false), + c + .of("allowKeepingVehicleAtDestination") + .since(V2_5) + .summary("Enables `allowKeepingVehicleAtDestination` for the given network.") + .description( + """ + Configures if a vehicle rented from a station must be returned to another one or can + be kept at the end of the trip. + + See the regular [GBFS documentation](../UpdaterConfig.md#gbfs-vehicle-rental-systems) for more information. + """ + ) .asBoolean(false) ) ); diff --git a/src/main/java/org/opentripplanner/updater/spi/UpdateError.java b/src/main/java/org/opentripplanner/updater/spi/UpdateError.java index 548ef0210eb..1f568ba99a4 100644 --- a/src/main/java/org/opentripplanner/updater/spi/UpdateError.java +++ b/src/main/java/org/opentripplanner/updater/spi/UpdateError.java @@ -49,6 +49,7 @@ public enum UpdateErrorType { NOT_IMPLEMENTED_UNSCHEDULED, NOT_IMPLEMENTED_DUPLICATED, NOT_MONITORED, + CANNOT_RESOLVE_AGENCY, } public static Result result(FeedScopedId tripId, UpdateErrorType errorType) { diff --git a/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsGeofencingZoneMapper.java b/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsGeofencingZoneMapper.java index caf5d97c0d4..22a4131f338 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsGeofencingZoneMapper.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsGeofencingZoneMapper.java @@ -10,6 +10,7 @@ import org.mobilitydata.gbfs.v2_3.geofencing_zones.GBFSGeofencingZones; import org.opentripplanner.framework.geometry.GeometryUtils; import org.opentripplanner.framework.geometry.UnsupportedGeometryException; +import org.opentripplanner.framework.lang.StringUtils; import org.opentripplanner.service.vehiclerental.model.GeofencingZone; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.slf4j.Logger; @@ -52,6 +53,9 @@ private GeofencingZone toInternalModel(GBFSFeature f) { return null; } var name = Objects.requireNonNullElseGet(f.getProperties().getName(), () -> fallbackId(g)); + if (!StringUtils.hasValue(name)) { + name = fallbackId(g); + } var dropOffBanned = !f.getProperties().getRules().get(0).getRideAllowed(); var passThroughBanned = !f.getProperties().getRules().get(0).getRideThroughAllowed(); return new GeofencingZone( diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 57fe17a55ab..6e1195f5901 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1183,6 +1183,8 @@ type QueryType { nearest places related to bicycling. """ filterByModes: [Mode], + "Only include vehicle rental networks that match one of the given network ids." + filterByNetwork: [String!], "Only return places that are one of these types, e.g. `STOP` or `VEHICLE_RENT`" filterByPlaceTypes: [FilterPlaceType], first: Int, @@ -1739,9 +1741,11 @@ type RentalVehicle implements Node & PlaceInterface { "Name of the vehicle" name: String! "ID of the rental network." - network: String + network: String @deprecated(reason : "Use `networkId` from `rentalNetwork` instead.") "If true, vehicle is not disabled." operative: Boolean + "The vehicle rental network information. This is referred as system in the GBFS terminology." + rentalNetwork: VehicleRentalNetwork! "Platform-specific URLs to begin the vehicle." rentalUris: VehicleRentalUris "ID of the vehicle in the format of network:id" @@ -2413,6 +2417,21 @@ type VehiclePosition { vehicleId: String } +""" +Vehicle rental network, which is referred as system in the GBFS terminology. Note, the same operator can operate in multiple +regions either with the same network/system or with a different one. This can contain information about either the rental brand +or about the operator. +""" +type VehicleRentalNetwork { + """ + ID of the vehicle rental network. In GBFS, this is the `system_id` field from the system information, but it can + be overridden in the configuration to have a different value so this field doesn't necessarily match the source data. + """ + networkId: String! + "The rental vehicle operator's network/system URL. In GBFS, this is the `url` field from the system information." + url: String +} + "Vehicle rental station represents a location where users can rent bicycles etc. for a fee." type VehicleRentalStation implements Node & PlaceInterface { """ @@ -2443,7 +2462,7 @@ type VehicleRentalStation implements Node & PlaceInterface { "Name of the vehicle rental station" name: String! "ID of the rental network." - network: String + network: String @deprecated(reason : "Use `networkId` from `rentalNetwork` instead.") "If true, station is on and in service." operative: Boolean """ @@ -2452,6 +2471,8 @@ type VehicleRentalStation implements Node & PlaceInterface { are always the total capacity divided by two. """ realtime: Boolean + "The vehicle rental network information. This is referred as system in the GBFS terminology." + rentalNetwork: VehicleRentalNetwork! "Platform-specific URLs to begin renting a vehicle from this station." rentalUris: VehicleRentalUris """ diff --git a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java index 614c8778c6b..2fb7a8d9db2 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -73,8 +73,10 @@ import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService; +import org.opentripplanner.service.vehiclerental.model.TestFreeFloatingRentalVehicleBuilder; import org.opentripplanner.service.vehiclerental.model.TestVehicleRentalStationBuilder; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStation; +import org.opentripplanner.service.vehiclerental.model.VehicleRentalVehicle; import org.opentripplanner.standalone.config.framework.json.JsonSupport; import org.opentripplanner.test.support.FilePatternSource; import org.opentripplanner.transit.model._data.TransitModelForTest; @@ -112,6 +114,18 @@ class GraphQLIntegrationTest { .map(p -> (RegularStop) p.stop) .toList(); + private static VehicleRentalStation VEHICLE_RENTAL_STATION = new TestVehicleRentalStationBuilder() + .withVehicles(10) + .withSpaces(10) + .withVehicleTypeBicycle(5, 7) + .withVehicleTypeElectricBicycle(5, 3) + .withSystem("Network-1", "https://foo.bar") + .build(); + + private static VehicleRentalVehicle RENTAL_VEHICLE = new TestFreeFloatingRentalVehicleBuilder() + .withSystem("Network-1", "https://foo.bar") + .build(); + static final Graph GRAPH = new Graph(); static final Instant ALERT_START_TIME = OffsetDateTime @@ -280,13 +294,8 @@ public TransitAlertService getTransitAlertService() { realtimeVehicleService.setRealtimeVehicles(pattern, List.of(occypancyVehicle, positionVehicle)); DefaultVehicleRentalService defaultVehicleRentalService = new DefaultVehicleRentalService(); - VehicleRentalStation vehicleRentalStation = new TestVehicleRentalStationBuilder() - .withVehicles(10) - .withSpaces(10) - .withVehicleTypeBicycle(5, 7) - .withVehicleTypeElectricBicycle(5, 3) - .build(); - defaultVehicleRentalService.addVehicleRentalStation(vehicleRentalStation); + defaultVehicleRentalService.addVehicleRentalStation(VEHICLE_RENTAL_STATION); + defaultVehicleRentalService.addVehicleRentalStation(RENTAL_VEHICLE); context = new GraphQLRequestContext( @@ -448,13 +457,15 @@ public List findClosestPlaces( List filterByStations, List filterByRoutes, List filterByBikeRentalStations, + List filterByNetwork, TransitService transitService ) { - return List - .of(TransitModelForTest.of().stop("A").build()) - .stream() - .map(stop -> new PlaceAtDistance(stop, 0)) - .toList(); + var stop = TransitModelForTest.of().stop("A").build(); + return List.of( + new PlaceAtDistance(stop, 0), + new PlaceAtDistance(VEHICLE_RENTAL_STATION, 30), + new PlaceAtDistance(RENTAL_VEHICLE, 50) + ); } }; } diff --git a/src/test/java/org/opentripplanner/generate/doc/OsmMapperDocTest.java b/src/test/java/org/opentripplanner/generate/doc/OsmMapperDocTest.java new file mode 100644 index 00000000000..3090c696e37 --- /dev/null +++ b/src/test/java/org/opentripplanner/generate/doc/OsmMapperDocTest.java @@ -0,0 +1,114 @@ +package org.opentripplanner.generate.doc; + +import static org.opentripplanner.framework.io.FileUtils.assertFileEquals; +import static org.opentripplanner.framework.io.FileUtils.readFile; +import static org.opentripplanner.framework.io.FileUtils.writeFile; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceSection; +import static org.opentripplanner.openstreetmap.tagmapping.OsmTagMapperSource.ATLANTA; +import static org.opentripplanner.openstreetmap.tagmapping.OsmTagMapperSource.CONSTANT_SPEED_FINLAND; +import static org.opentripplanner.openstreetmap.tagmapping.OsmTagMapperSource.HAMBURG; +import static org.opentripplanner.openstreetmap.tagmapping.OsmTagMapperSource.HOUSTON; +import static org.opentripplanner.openstreetmap.tagmapping.OsmTagMapperSource.PORTLAND; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.framework.text.Table; +import org.opentripplanner.framework.text.TableBuilder; +import org.opentripplanner.generate.doc.framework.GeneratesDocumentation; +import org.opentripplanner.openstreetmap.tagmapping.OsmTagMapper; +import org.opentripplanner.openstreetmap.tagmapping.OsmTagMapperSource; +import org.opentripplanner.openstreetmap.wayproperty.SafetyFeatures; +import org.opentripplanner.openstreetmap.wayproperty.WayPropertySet; + +@GeneratesDocumentation +public class OsmMapperDocTest { + + private static final String FILE_NAME = "OsmMapper.md"; + private static final File TEMPLATE = new File(TEMPLATE_ROOT, FILE_NAME); + private static final Set SKIP_MAPPERS = Set.of( + ATLANTA, + HOUSTON, + PORTLAND, + HAMBURG, + CONSTANT_SPEED_FINLAND + ); + + public static List mappers() { + return Arrays + .stream(OsmTagMapperSource.values()) + .filter(m -> !SKIP_MAPPERS.contains(m)) + .toList(); + } + + @ParameterizedTest + @MethodSource("mappers") + public void updateDocs(OsmTagMapperSource source) { + var mapper = source.getInstance(); + var wps = new WayPropertySet(); + mapper.populateProperties(wps); + + var outFile = outputFile(mapper); + + // Read and close input file (same as output file) + String template = readFile(TEMPLATE); + String original = readFile(outFile); + + var propTable = propTable(wps); + var mixinTable = mixinTable(wps); + + template = replaceSection(template, "props", propTable.toMarkdownTable()); + template = replaceSection(template, "mixins", mixinTable.toMarkdownTable()); + writeFile(outFile, template); + assertFileEquals(original, outFile); + } + + private static File outputFile(OsmTagMapper mapper) { + var name = mapper.getClass().getSimpleName().replaceAll("Mapper", ".md"); + return new File("%s/osm/".formatted(DOCS_ROOT), name); + } + + private static Table propTable(WayPropertySet wps) { + var propTable = new TableBuilder(); + propTable.withHeaders("specifier", "permission", "bike safety", "walk safety"); + + for (var prop : wps.getWayProperties()) { + propTable.addRow( + "`%s`".formatted(prop.specifier().toDocString()), + "`%s`".formatted(prop.properties().getPermission()), + tableValues(prop.properties().bicycleSafety()), + tableValues(prop.properties().walkSafety()) + ); + } + return propTable.build(); + } + + private static Table mixinTable(WayPropertySet wps) { + var propTable = new TableBuilder(); + propTable.withHeaders("matcher", "bicycle safety", "walk safety"); + + for (var prop : wps.getMixins()) { + propTable.addRow( + "`%s`".formatted(prop.specifier().toDocString()), + tableValues(prop.bicycleSafety()), + tableValues(prop.walkSafety()) + ); + } + return propTable.build(); + } + + private static String tableValues(SafetyFeatures safety) { + if (!safety.modifies()) { + return ""; + } else if (safety.isSymmetric()) { + return Double.toString(safety.forward()); + } else { + return "forward: %s
back: %s".formatted(safety.forward(), safety.back()); + } + } +} diff --git a/src/test/java/org/opentripplanner/model/TimetableSnapshotTest.java b/src/test/java/org/opentripplanner/model/TimetableSnapshotTest.java index 26d93f6a848..89c30da79e4 100644 --- a/src/test/java/org/opentripplanner/model/TimetableSnapshotTest.java +++ b/src/test/java/org/opentripplanner/model/TimetableSnapshotTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.transit.realtime.GtfsRealtime.TripDescriptor; @@ -261,6 +262,52 @@ public void testPurge() { assertFalse(resolver.isDirty()); } + @Test + void testCannotUpdateReadOnlyTimetableSnapshot() { + TimetableSnapshot committedSnapshot = createCommittedSnapshot(); + LocalDate today = LocalDate.now(timeZone); + TripPattern pattern = patternIndex.get(new FeedScopedId(feedId, "1.1")); + assertThrows( + ConcurrentModificationException.class, + () -> committedSnapshot.update(pattern, null, today) + ); + } + + @Test + void testCannotCommitReadOnlyTimetableSnapshot() { + TimetableSnapshot committedSnapshot = createCommittedSnapshot(); + assertThrows(ConcurrentModificationException.class, () -> committedSnapshot.commit(null, true)); + } + + @Test + void testCannotClearReadOnlyTimetableSnapshot() { + TimetableSnapshot committedSnapshot = createCommittedSnapshot(); + assertThrows(ConcurrentModificationException.class, () -> committedSnapshot.clear(null)); + } + + @Test + void testCannotPurgeReadOnlyTimetableSnapshot() { + TimetableSnapshot committedSnapshot = createCommittedSnapshot(); + assertThrows( + ConcurrentModificationException.class, + () -> committedSnapshot.purgeExpiredData(null) + ); + } + + @Test + void testCannotRevertReadOnlyTimetableSnapshot() { + TimetableSnapshot committedSnapshot = createCommittedSnapshot(); + assertThrows( + ConcurrentModificationException.class, + () -> committedSnapshot.revertTripToScheduledTripPattern(null, null) + ); + } + + private static TimetableSnapshot createCommittedSnapshot() { + TimetableSnapshot timetableSnapshot = new TimetableSnapshot(); + return timetableSnapshot.commit(null, true); + } + private Result updateResolver( TimetableSnapshot resolver, TripPattern pattern, diff --git a/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/ConditionTest.java b/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/ConditionTest.java index def272652f6..6386aa902e2 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/ConditionTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/ConditionTest.java @@ -29,11 +29,11 @@ import org.opentripplanner.openstreetmap.model.OSMWithTags; import org.opentripplanner.openstreetmap.wayproperty.specifier.Condition.Absent; import org.opentripplanner.openstreetmap.wayproperty.specifier.Condition.Equals; -import org.opentripplanner.openstreetmap.wayproperty.specifier.Condition.EqualsAnyIn; import org.opentripplanner.openstreetmap.wayproperty.specifier.Condition.GreaterThan; import org.opentripplanner.openstreetmap.wayproperty.specifier.Condition.InclusiveRange; import org.opentripplanner.openstreetmap.wayproperty.specifier.Condition.LessThan; import org.opentripplanner.openstreetmap.wayproperty.specifier.Condition.MatchResult; +import org.opentripplanner.openstreetmap.wayproperty.specifier.Condition.OneOf; import org.opentripplanner.openstreetmap.wayproperty.specifier.Condition.Present; class ConditionTest { @@ -46,7 +46,7 @@ class ConditionTest { static Condition moreThanFourLanes = new GreaterThan("lanes", 4); static Condition lessThanFourLanes = new LessThan("lanes", 4); static Condition betweenFiveAndThreeLanes = new InclusiveRange("lanes", 5, 3); - static Condition smoothnessBadAndWorseThanBad = new EqualsAnyIn( + static Condition smoothnessBadAndWorseThanBad = new OneOf( "smoothness", "bad", "very_bad", @@ -54,7 +54,7 @@ class ConditionTest { "very_horrible", "impassable" ); - static Condition noSidewalk = new Condition.EqualsAnyInOrAbsent("sidewalk"); + static Condition noSidewalk = new Condition.OneOfOrAbsent("sidewalk"); static Stream equalsCases() { return Stream.of( diff --git a/src/test/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitorTest.java b/src/test/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitorTest.java index 1ef675c8101..b69a6533334 100644 --- a/src/test/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitorTest.java +++ b/src/test/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitorTest.java @@ -13,6 +13,7 @@ import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.model.StopTime; +import org.opentripplanner.service.vehiclerental.model.TestVehicleRentalStationBuilder; import org.opentripplanner.street.search.state.TestStateBuilder; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.basic.TransitMode; @@ -96,6 +97,7 @@ void stopsOnly() { null, null, null, + null, 1, 500 ); @@ -124,6 +126,7 @@ void stationsOnly() { null, null, null, + null, 1, 500 ); @@ -152,6 +155,7 @@ void stopsAndStations() { null, null, null, + null, 1, 500 ); @@ -183,6 +187,7 @@ void stopsAndStationsWithStationFilter() { List.of(STATION1.getId()), null, null, + null, 1, 500 ); @@ -217,6 +222,7 @@ void stopsAndStationsWithStopFilter() { null, null, null, + null, 1, 500 ); @@ -250,6 +256,7 @@ void stopsAndStationsWithStopAndStationFilter() { List.of(STATION1.getId()), null, null, + null, 1, 500 ); @@ -274,4 +281,74 @@ void stopsAndStationsWithStopAndStationFilter() { visitor.visitVertex(state1); } + + @Test + void rentalStation() { + var visitor = new PlaceFinderTraverseVisitor( + transitService, + null, + List.of(PlaceType.VEHICLE_RENT), + null, + null, + null, + null, + null, + 1, + 500 + ); + var station = new TestVehicleRentalStationBuilder().build(); + assertEquals(List.of(), visitor.placesFound); + var state1 = TestStateBuilder.ofWalking().rentalStation(station).build(); + visitor.visitVertex(state1); + + var res = visitor.placesFound.stream().map(PlaceAtDistance::place).toList(); + + assertEquals(List.of(station), res); + } + + @Test + void rentalStationWithNetworksFilter() { + var visitor = new PlaceFinderTraverseVisitor( + transitService, + null, + List.of(PlaceType.VEHICLE_RENT), + null, + null, + null, + null, + List.of("Network-1"), + 1, + 500 + ); + var station = new TestVehicleRentalStationBuilder().build(); + assertEquals(List.of(), visitor.placesFound); + var state1 = TestStateBuilder.ofWalking().rentalStation(station).build(); + visitor.visitVertex(state1); + + var res = visitor.placesFound.stream().map(PlaceAtDistance::place).toList(); + + assertEquals(List.of(station), res); + + visitor = + new PlaceFinderTraverseVisitor( + transitService, + null, + List.of(PlaceType.VEHICLE_RENT), + null, + null, + null, + null, + List.of("Network-2"), + 1, + 500 + ); + + assertEquals(List.of(), visitor.placesFound); + state1 = TestStateBuilder.ofWalking().rentalStation(station).build(); + visitor.visitVertex(state1); + + res = visitor.placesFound.stream().map(PlaceAtDistance::place).toList(); + + assertEquals(List.of(), res); + } } diff --git a/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java b/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java index 3285e27594c..76f231577dd 100644 --- a/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java +++ b/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java @@ -156,6 +156,7 @@ void findClosestPlacesLimiting() { null, null, null, + null, transitService ) ); @@ -179,6 +180,7 @@ void findClosestPlacesLimiting() { null, null, null, + null, transitService ) ); @@ -196,6 +198,7 @@ void findClosestPlacesLimiting() { null, null, null, + null, transitService ) ); @@ -220,6 +223,7 @@ void findClosestPlacesWithAModeFilter() { null, null, null, + null, transitService ) ); @@ -237,6 +241,7 @@ void findClosestPlacesWithAModeFilter() { null, null, null, + null, transitService ) ); @@ -262,6 +267,7 @@ void findClosestPlacesWithAStopFilter() { null, null, null, + null, transitService ) ); @@ -279,6 +285,7 @@ void findClosestPlacesWithAStopFilter() { null, null, null, + null, transitService ) ); @@ -304,6 +311,7 @@ void findClosestPlacesWithAStopAndRouteFilter() { null, null, null, + null, transitService ) ); @@ -321,6 +329,7 @@ void findClosestPlacesWithAStopAndRouteFilter() { null, List.of(R1.getId()), null, + null, transitService ) ); @@ -347,6 +356,7 @@ void findClosestPlacesWithARouteFilter() { null, null, null, + null, transitService ) ); @@ -364,6 +374,7 @@ void findClosestPlacesWithARouteFilter() { null, List.of(R2.getId()), null, + null, transitService ) ); @@ -387,6 +398,7 @@ void findClosestPlacesWithAVehicleRentalFilter() { null, null, null, + null, transitService ) ); @@ -404,6 +416,7 @@ void findClosestPlacesWithAVehicleRentalFilter() { null, null, List.of("BR2"), + null, transitService ) ); @@ -426,6 +439,7 @@ void findClosestPlacesWithABikeParkFilter() { null, null, null, + null, transitService ) ); @@ -448,6 +462,7 @@ void findClosestPlacesWithACarParkFilter() { null, null, null, + null, transitService ) ); diff --git a/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java b/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java index 4864fab5e43..c3837942426 100644 --- a/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java +++ b/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java @@ -13,6 +13,7 @@ public class TestFreeFloatingRentalVehicleBuilder { private double latitude = DEFAULT_LATITUDE; private double longitude = DEFAULT_LONGITUDE; + private VehicleRentalSystem system = null; private RentalVehicleType vehicleType = RentalVehicleType.getDefaultType(NETWORK_1); @@ -30,6 +31,28 @@ public TestFreeFloatingRentalVehicleBuilder withLongitude(double longitude) { return this; } + public TestFreeFloatingRentalVehicleBuilder withSystem(String id, String url) { + this.system = + new VehicleRentalSystem( + id, + null, + null, + null, + null, + url, + null, + null, + null, + null, + null, + null, + null, + null, + null + ); + return this; + } + public TestFreeFloatingRentalVehicleBuilder withVehicleScooter() { return buildVehicleType(RentalFormFactor.SCOOTER); } @@ -63,6 +86,7 @@ public VehicleRentalVehicle build() { vehicle.latitude = latitude; vehicle.longitude = longitude; vehicle.vehicleType = vehicleType; + vehicle.system = system; return vehicle; } } diff --git a/src/test/java/org/opentripplanner/service/vehiclerental/model/TestVehicleRentalStationBuilder.java b/src/test/java/org/opentripplanner/service/vehiclerental/model/TestVehicleRentalStationBuilder.java index 33f922ff0b9..0fb9f8b620f 100644 --- a/src/test/java/org/opentripplanner/service/vehiclerental/model/TestVehicleRentalStationBuilder.java +++ b/src/test/java/org/opentripplanner/service/vehiclerental/model/TestVehicleRentalStationBuilder.java @@ -19,6 +19,7 @@ public class TestVehicleRentalStationBuilder { private int spaces = 10; private boolean overloadingAllowed = false; private boolean stationOn = false; + private VehicleRentalSystem system = null; private final Map vehicleTypesAvailable = new HashMap<>(); private final Map vehicleSpacesAvailable = new HashMap<>(); @@ -52,6 +53,28 @@ public TestVehicleRentalStationBuilder withStationOn(boolean stationOn) { return this; } + public TestVehicleRentalStationBuilder withSystem(String id, String url) { + this.system = + new VehicleRentalSystem( + id, + null, + null, + null, + null, + url, + null, + null, + null, + null, + null, + null, + null, + null, + null + ); + return this; + } + public TestVehicleRentalStationBuilder withVehicleTypeBicycle(int numAvailable, int numSpaces) { return buildVehicleType( RentalFormFactor.BICYCLE, @@ -127,6 +150,7 @@ public VehicleRentalStation build() { station.isRenting = stationOn; station.isReturning = stationOn; station.realTimeData = true; + station.system = system; return station; } } diff --git a/src/test/java/org/opentripplanner/street/search/state/TestStateBuilder.java b/src/test/java/org/opentripplanner/street/search/state/TestStateBuilder.java index e43c5a769d0..3750c4619b9 100644 --- a/src/test/java/org/opentripplanner/street/search/state/TestStateBuilder.java +++ b/src/test/java/org/opentripplanner/street/search/state/TestStateBuilder.java @@ -16,6 +16,7 @@ import org.opentripplanner.service.vehiclerental.model.TestFreeFloatingRentalVehicleBuilder; import org.opentripplanner.service.vehiclerental.model.TestVehicleRentalStationBuilder; import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace; +import org.opentripplanner.service.vehiclerental.model.VehicleRentalStation; import org.opentripplanner.service.vehiclerental.street.StreetVehicleRentalLink; import org.opentripplanner.service.vehiclerental.street.VehicleRentalEdge; import org.opentripplanner.service.vehiclerental.street.VehicleRentalPlaceVertex; @@ -219,6 +220,19 @@ public TestStateBuilder stop() { return arriveAtStop(testModel.stop("stop", count, count).build()); } + /** + * Add a state that arrives at a rental station. + */ + public TestStateBuilder rentalStation(VehicleRentalStation station) { + count++; + var from = (StreetVertex) currentState.vertex; + var to = new VehicleRentalPlaceVertex(station); + + var link = StreetVehicleRentalLink.createStreetVehicleRentalLink(from, to); + currentState = link.traverse(currentState)[0]; + return this; + } + public TestStateBuilder enterStation(String id) { count++; var from = (StreetVertex) currentState.vertex; diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/nearest.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/nearest.json index 82d929adeb1..b6b5b7ee674 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/nearest.json +++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/nearest.json @@ -10,6 +10,20 @@ "parentStation" : null } } + }, + { + "node" : { + "place" : { + "stationId" : "Network-1:FooStation" + } + } + }, + { + "node" : { + "place" : { + "vehicleId" : "Network-1:free-floating-bicycle" + } + } } ] } diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/rental-vehicle.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/rental-vehicle.json new file mode 100644 index 00000000000..9017fe77a93 --- /dev/null +++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/rental-vehicle.json @@ -0,0 +1,21 @@ +{ + "data": { + "rentalVehicle": { + "vehicleId":"Network-1:free-floating-bicycle", + "name":"free-floating-bicycle", + "allowPickupNow":true, + "lon":19.01, + "lat":47.52, + "rentalUris":null, + "operative":true, + "vehicleType": { + "formFactor":"BICYCLE", + "propulsionType":"HUMAN" + }, + "rentalNetwork": { + "networkId":"Network-1", + "url":"https://foo.bar" + } + } + } +} diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/vehicle-rental-station.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/vehicle-rental-station.json index ef1284c5c5e..ad1ce76d9be 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/vehicle-rental-station.json +++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/vehicle-rental-station.json @@ -47,13 +47,16 @@ "allowPickup" : false, "allowDropoffNow" : false, "allowPickupNow" : false, - "network" : "Network-1", "lon" : 18.99, "lat" : 47.51, "capacity" : null, "allowOverloading" : false, "rentalUris" : null, - "operative" : false + "operative" : false, + "rentalNetwork" : { + "networkId" : "Network-1", + "url" : "https://foo.bar" + } } } } diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/nearest.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/nearest.graphql index c7f8eed4213..469a117bab4 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/nearest.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/nearest.graphql @@ -1,5 +1,11 @@ { - nearest(lat: 60.19915, lon: 24.94089, maxDistance: 500) { + nearest( + lat: 60.19915 + lon: 24.94089 + maxDistance: 500 + filterByPlaceTypes: [STOP, VEHICLE_RENT] + filterByNetwork: ["Network-1"] + ) { edges { node { place { @@ -10,6 +16,12 @@ id } } + ... on RentalVehicle { + vehicleId + } + ... on VehicleRentalStation { + stationId + } } } } diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/rental-vehicle.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/rental-vehicle.graphql new file mode 100644 index 00000000000..9a912781c56 --- /dev/null +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/rental-vehicle.graphql @@ -0,0 +1,23 @@ +{ + rentalVehicle(id: "Network-1:free-floating-bicycle") { + vehicleId + name + allowPickupNow + lon + lat + rentalUris { + android + ios + web + } + operative + vehicleType { + formFactor + propulsionType + } + rentalNetwork { + networkId + url + } + } +} diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/vehicle-rental-station.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/vehicle-rental-station.graphql index 8e555ffdbdd..a2200465912 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/vehicle-rental-station.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/vehicle-rental-station.graphql @@ -28,7 +28,6 @@ allowPickup allowDropoffNow allowPickupNow - network lon lat capacity @@ -39,5 +38,9 @@ web } operative + rentalNetwork { + networkId + url + } } }