diff --git a/README.md b/README.md index 582511ce7f2..4486818745b 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ OpenTripPlanner (OTP) is an open source multi-modal trip planner, focusing on travel by scheduled public transportation in combination with bicycling, walking, and mobility services including bike share and ride hailing. Its server component runs on any platform with a Java virtual machine ( -including Linux, Mac, and Windows). It exposes REST and GraphQL APIs that can be accessed by various +including Linux, Mac, and Windows). It exposes GraphQL APIs that can be accessed by various clients including open source Javascript components and native mobile applications. It builds its representation of the transportation network from open data in open standard file formats (primarily GTFS and OpenStreetMap). It applies real-time updates and alerts with immediate visibility to diff --git a/client-next/package-lock.json b/client-next/package-lock.json index 19909ba109d..11894b5b246 100644 --- a/client-next/package-lock.json +++ b/client-next/package-lock.json @@ -12,7 +12,7 @@ "bootstrap": "5.3.1", "graphql": "16.8.0", "graphql-request": "6.1.0", - "maplibre-gl": "3.3.0", + "maplibre-gl": "3.6.2", "react": "18.2.0", "react-bootstrap": "2.8.0", "react-dom": "18.2.0", @@ -2864,9 +2864,9 @@ } }, "node_modules/@maplibre/maplibre-gl-style-spec": { - "version": "19.3.0", - "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.0.tgz", - "integrity": "sha512-ZbhX9CTV+Z7vHwkRIasDOwTSzr76e8Q6a55RMsAibjyX6+P0ZNL1qAKNzOjjBDP3+aEfNMl7hHo5knuY6pTAUQ==", + "version": "19.3.3", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.3.tgz", + "integrity": "sha512-cOZZOVhDSulgK0meTsTkmNXb1ahVvmTmWmfx9gRBwc6hq98wS9JP35ESIoNq3xqEan+UN+gn8187Z6E4NKhLsw==", "dependencies": { "@mapbox/jsonlint-lines-primitives": "~2.0.2", "@mapbox/unitbezier": "^0.0.1", @@ -3306,9 +3306,9 @@ } }, "node_modules/@types/geojson": { - "version": "7946.0.10", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", - "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==" + "version": "7946.0.13", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz", + "integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==" }, "node_modules/@types/js-yaml": { "version": "4.0.5", @@ -3335,14 +3335,14 @@ "dev": true }, "node_modules/@types/mapbox__point-geometry": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.2.tgz", - "integrity": "sha512-D0lgCq+3VWV85ey1MZVkE8ZveyuvW5VAfuahVTQRpXFQTxw03SuIf1/K4UQ87MMIXVKzpFjXFiFMZzLj2kU+iA==" + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz", + "integrity": "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==" }, "node_modules/@types/mapbox__vector-tile": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.0.tgz", - "integrity": "sha512-kDwVreQO5V4c8yAxzZVQLE5tyWF+IPToAanloQaSnwfXmIcJ7cyOrv8z4Ft4y7PsLYmhWXmON8MBV8RX0Rgr8g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.4.tgz", + "integrity": "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==", "dependencies": { "@types/geojson": "*", "@types/mapbox__point-geometry": "*", @@ -3364,9 +3364,9 @@ "dev": true }, "node_modules/@types/pbf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.2.tgz", - "integrity": "sha512-EDrLIPaPXOZqDjrkzxxbX7UlJSeQVgah3i0aA4pOSzmK9zq3BIh7/MZIQxED7slJByvKM4Gc6Hypyu2lJzh3SQ==" + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.5.tgz", + "integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==" }, "node_modules/@types/prop-types": { "version": "15.7.5", @@ -3412,9 +3412,9 @@ "dev": true }, "node_modules/@types/supercluster": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.0.tgz", - "integrity": "sha512-6JapQ2GmEkH66r23BK49I+u6zczVDGTtiJEVvKDYZVSm/vepWaJuTq6BXzJ6I4agG5s8vA1KM7m/gXWDg03O4Q==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz", + "integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==", "dependencies": { "@types/geojson": "*" } @@ -7379,9 +7379,9 @@ } }, "node_modules/maplibre-gl": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-3.3.0.tgz", - "integrity": "sha512-LDia3b8u2S8qtl50n8TYJM0IPLzfc01KDc71LNuydvDiEXAGBI5togty+juVtUipRZZjs4dAW6xhgrabc6lIgw==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-3.6.2.tgz", + "integrity": "sha512-krg2KFIdOpLPngONDhP6ixCoWl5kbdMINP0moMSJFVX7wX1Clm2M9hlNKXS8vBGlVWwR5R3ZfI6IPrYz7c+aCQ==", "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", "@mapbox/jsonlint-lines-primitives": "^2.0.2", @@ -7390,12 +7390,12 @@ "@mapbox/unitbezier": "^0.0.1", "@mapbox/vector-tile": "^1.3.1", "@mapbox/whoots-js": "^3.1.0", - "@maplibre/maplibre-gl-style-spec": "^19.3.0", - "@types/geojson": "^7946.0.10", - "@types/mapbox__point-geometry": "^0.1.2", - "@types/mapbox__vector-tile": "^1.3.0", - "@types/pbf": "^3.0.2", - "@types/supercluster": "^7.1.0", + "@maplibre/maplibre-gl-style-spec": "^19.3.3", + "@types/geojson": "^7946.0.13", + "@types/mapbox__point-geometry": "^0.1.4", + "@types/mapbox__vector-tile": "^1.3.4", + "@types/pbf": "^3.0.5", + "@types/supercluster": "^7.1.3", "earcut": "^2.2.4", "geojson-vt": "^3.2.1", "gl-matrix": "^3.4.3", diff --git a/client-next/package.json b/client-next/package.json index 79d2c7942f1..6388122befc 100644 --- a/client-next/package.json +++ b/client-next/package.json @@ -19,7 +19,7 @@ "bootstrap": "5.3.1", "graphql": "16.8.0", "graphql-request": "6.1.0", - "maplibre-gl": "3.3.0", + "maplibre-gl": "3.6.2", "react": "18.2.0", "react-bootstrap": "2.8.0", "react-dom": "18.2.0", diff --git a/client-next/src/components/MapView/ContextMenuPopup.tsx b/client-next/src/components/MapView/ContextMenuPopup.tsx index 52dd3858298..7d51a4c21ad 100644 --- a/client-next/src/components/MapView/ContextMenuPopup.tsx +++ b/client-next/src/components/MapView/ContextMenuPopup.tsx @@ -1,5 +1,5 @@ import { TripQueryVariables } from '../../gql/graphql.ts'; -import { LngLat, Popup } from 'react-map-gl'; +import { LngLat, Popup } from 'react-map-gl/maplibre'; import { Button, ButtonGroup } from 'react-bootstrap'; export function ContextMenuPopup({ diff --git a/client-next/src/components/MapView/GeometryPropertyPopup.tsx b/client-next/src/components/MapView/GeometryPropertyPopup.tsx index d2b55689270..3ecedb2c67d 100644 --- a/client-next/src/components/MapView/GeometryPropertyPopup.tsx +++ b/client-next/src/components/MapView/GeometryPropertyPopup.tsx @@ -1,4 +1,4 @@ -import { LngLat, Popup } from 'react-map-gl'; +import { LngLat, Popup } from 'react-map-gl/maplibre'; import { Table } from 'react-bootstrap'; export function GeometryPropertyPopup({ @@ -11,7 +11,13 @@ export function GeometryPropertyPopup({ onClose: () => void; }) { return ( - onClose()}> + onClose()} + maxWidth="350px" + > {Object.entries(properties).map(([key, value]) => ( diff --git a/client-next/src/components/MapView/LayerControl.tsx b/client-next/src/components/MapView/LayerControl.tsx new file mode 100644 index 00000000000..237ea8ab150 --- /dev/null +++ b/client-next/src/components/MapView/LayerControl.tsx @@ -0,0 +1,82 @@ +import type { ControlPosition } from 'react-map-gl'; +import { useControl } from 'react-map-gl'; +import { IControl, Map } from 'maplibre-gl'; + +type LayerControlProps = { + position: ControlPosition; +}; + +/** + * A maplibre control that allows you to switch vector tile layers on and off. + * + * It appears that you cannot use React elements but have to drop down to raw DOM. Please correct + * me if I'm wrong. + */ +class LayerControl implements IControl { + private readonly container: HTMLDivElement = document.createElement('div'); + + onAdd(map: Map) { + this.container.className = 'maplibregl-ctrl maplibregl-ctrl-group layer-select'; + + map.on('load', () => { + // clean on + while (this.container.firstChild) { + this.container.removeChild(this.container.firstChild); + } + + const title = document.createElement('h6'); + title.textContent = 'Debug layers'; + this.container.appendChild(title); + + map + .getLayersOrder() + .map((l) => map.getLayer(l)) + .filter((s) => s?.type !== 'raster') + .reverse() + .forEach((layer) => { + if (layer) { + const div = document.createElement('div'); + const input = document.createElement('input'); + input.type = 'checkbox'; + input.value = layer.id; + input.id = layer.id; + input.onchange = (e) => { + e.preventDefault(); + e.stopPropagation(); + + if (this.layerVisible(map, layer)) { + map.setLayoutProperty(layer.id, 'visibility', 'none'); + } else { + map.setLayoutProperty(layer.id, 'visibility', 'visible'); + } + }; + input.checked = this.layerVisible(map, layer); + const label = document.createElement('label'); + label.textContent = layer.id; + label.htmlFor = layer.id; + div.appendChild(input); + div.appendChild(label); + this.container.appendChild(div); + } + }); + }); + + return this.container; + } + + private layerVisible(map: Map, layer: { id: string }) { + return map.getLayoutProperty(layer.id, 'visibility') !== 'none'; + } + + onRemove() { + this.container.parentNode?.removeChild(this.container); + } +} + +export default function DebugLayerControl(props: LayerControlProps) { + useControl(() => new LayerControl(), { + position: props.position, + }); + + return null; +} diff --git a/client-next/src/components/MapView/MapView.tsx b/client-next/src/components/MapView/MapView.tsx index 5b6223a5dee..51cb39068f8 100644 --- a/client-next/src/components/MapView/MapView.tsx +++ b/client-next/src/components/MapView/MapView.tsx @@ -1,4 +1,12 @@ -import { LngLat, Map, MapboxGeoJSONFeature, NavigationControl } from 'react-map-gl'; +import { + LngLat, + Map, + MapEvent, + MapGeoJSONFeature, + MapMouseEvent, + NavigationControl, + VectorTileSource, +} from 'react-map-gl/maplibre'; import 'maplibre-gl/dist/maplibre-gl.css'; import { TripPattern, TripQuery, TripQueryVariables } from '../../gql/graphql.ts'; import { NavigationMarkers } from './NavigationMarkers.tsx'; @@ -7,17 +15,11 @@ import { useMapDoubleClick } from './useMapDoubleClick.ts'; import { useState } from 'react'; import { ContextMenuPopup } from './ContextMenuPopup.tsx'; import { GeometryPropertyPopup } from './GeometryPropertyPopup.tsx'; - -// TODO: this should be configurable -const initialViewState = { - latitude: 60.7554885, - longitude: 10.2332855, - zoom: 4, -}; +import DebugLayerControl from './LayerControl.tsx'; const styleUrl = import.meta.env.VITE_DEBUG_STYLE_URL; -type PopupData = { coordinates: LngLat; feature: MapboxGeoJSONFeature }; +type PopupData = { coordinates: LngLat; feature: MapGeoJSONFeature }; export function MapView({ tripQueryVariables, @@ -36,8 +38,8 @@ export function MapView({ const [showContextPopup, setShowContextPopup] = useState(null); const [showPropsPopup, setShowPropsPopup] = useState(null); const showFeaturePropPopup = ( - e: mapboxgl.MapMouseEvent & { - features?: mapboxgl.MapboxGeoJSONFeature[] | undefined; + e: MapMouseEvent & { + features?: MapGeoJSONFeature[] | undefined; }, ) => { if (e.features) { @@ -48,6 +50,17 @@ export function MapView({ setShowPropsPopup({ coordinates: e.lngLat, feature: feature }); } }; + const panToWorldEnvelopeIfRequired = (e: MapEvent) => { + const map = e.target; + // if we are really far zoomed out and show the entire world it means that we are not starting + // in a location selected from the URL hash. + // in such a case we pan to the area that is specified in the tile bounds, which is + // provided by the WorldEnvelopeService + if (map.getZoom() < 2) { + const source = map.getSource('stops') as VectorTileSource; + map.fitBounds(source.bounds, { maxDuration: 50, linear: true }); + } + }; return (
@@ -56,18 +69,20 @@ export function MapView({ mapLib={import('maplibre-gl')} // @ts-ignore mapStyle={styleUrl} - initialViewState={initialViewState} onDblClick={onMapDoubleClick} onContextMenu={(e) => { setShowContextPopup(e.lngLat); }} - interactiveLayerIds={['regular-stop']} + // it's unfortunate that you have to list these layers here. + // maybe there is a way around it: https://github.com/visgl/react-map-gl/discussions/2343 + interactiveLayerIds={['regular-stop', 'vertex', 'edge', 'link']} onClick={showFeaturePropPopup} // put lat/long in URL and pan to it on page reload hash={true} // disable pitching and rotating the map touchPitch={false} dragRotate={false} + onLoad={panToWorldEnvelopeIfRequired} > + {tripQueryResult?.trip.tripPatterns.length && ( )} diff --git a/client-next/src/components/MapView/useMapDoubleClick.ts b/client-next/src/components/MapView/useMapDoubleClick.ts index 35cb1d76a62..eb9217aa167 100644 --- a/client-next/src/components/MapView/useMapDoubleClick.ts +++ b/client-next/src/components/MapView/useMapDoubleClick.ts @@ -1,6 +1,6 @@ import { useCallback } from 'react'; import { TripQueryVariables } from '../../gql/graphql.ts'; -import { LngLat, MapLayerMouseEvent } from 'react-map-gl'; +import { LngLat, MapLayerMouseEvent } from 'react-map-gl/maplibre'; const setCoordinates = (tripQueryVariables: TripQueryVariables, lngLat: LngLat, key: 'from' | 'to') => ({ ...tripQueryVariables, diff --git a/client-next/src/style.css b/client-next/src/style.css index b7661779991..9a538686043 100644 --- a/client-next/src/style.css +++ b/client-next/src/style.css @@ -119,3 +119,12 @@ font-size: 14px; padding-left: 2px; } + +/* debug layer selector */ + +.maplibregl-ctrl-group.layer-select { + padding: 10px; +} +.maplibregl-ctrl-group.layer-select label { + margin-left: 6px; +} diff --git a/doc-templates/UpdaterConfig.md b/doc-templates/UpdaterConfig.md index a239317f244..57152671ec7 100644 --- a/doc-templates/UpdaterConfig.md +++ b/doc-templates/UpdaterConfig.md @@ -8,8 +8,8 @@ # Updater configuration -This section covers all options that can be set in the *router-config.json* in the -[updaters](RouterConfiguration.md) section. +This section covers options that can be set in the updaters section of `router-config.json`. +See the parameter summary and examples in the router configuration documentation Real-time data are those that are not added to OTP during the graph build phase but during runtime. @@ -44,6 +44,15 @@ The information is downloaded in a single HTTP request and polled regularly. +### Streaming TripUpdates via MQTT + +This updater connects to an MQTT broker and processes TripUpdates in a streaming fashion. This means +that they will be applied individually in near-realtime rather than in batches at a certain interval. + +This system powers the realtime updates in Helsinki and more information can be found +[on Github](https://github.com/HSLdevcom/transitdata). + + ### Vehicle Positions diff --git a/docs/Changelog.md b/docs/Changelog.md index 50846c3fce7..6f27f60d947 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -74,6 +74,10 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Fix high walk reluctance leading to zero egress results for rental searches [#5605](https://github.com/opentripplanner/OpenTripPlanner/pull/5605) - Remove GTFS-RT websocket updater [#5604](https://github.com/opentripplanner/OpenTripPlanner/pull/5604) - Add stop layer to new Debug UI [#5602](https://github.com/opentripplanner/OpenTripPlanner/pull/5602) +- Use fallback timezone if no transit data is loaded [#4652](https://github.com/opentripplanner/OpenTripPlanner/pull/4652) +- Add new path for GTFS GraphQL API, remove batch feature [#5581](https://github.com/opentripplanner/OpenTripPlanner/pull/5581) +- Restructure walk/bicycle/car preferences in router-config.json [#5582](https://github.com/opentripplanner/OpenTripPlanner/pull/5582) +- Revert REST API spelling change of real-time [#5629](https://github.com/opentripplanner/OpenTripPlanner/pull/5629) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index 9e891cf732d..199eda8a332 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -17,35 +17,12 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe |--------------------------------------------------------------------------------------------------------------|:----------------------:|------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|------------------|:-----:| | [alightSlack](#rd_alightSlack) | `duration` | The minimum extra time after exiting a public transport vehicle. | *Optional* | `"PT0S"` | 2.0 | | arriveBy | `boolean` | Whether the trip should depart or arrive at the specified date and time. | *Optional* | `false` | 2.0 | -| [bikeBoardCost](#rd_bikeBoardCost) | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. | *Optional* | `600` | 2.0 | -| bikeParkCost | `integer` | Cost to park a bike. | *Optional* | `120` | 2.0 | -| bikeParkTime | `duration` | Time to park a bike. | *Optional* | `"PT1M"` | 2.0 | -| bikeReluctance | `double` | A multiplier for how bad biking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -| bikeSpeed | `double` | Max bike speed along streets, in meters per second | *Optional* | `5.0` | 2.0 | -| bikeStairsReluctance | `double` | How bad is it to walk the bicycle up/down a flight of stairs compared to taking a detour. | *Optional* | `10.0` | 2.3 | -| bikeSwitchCost | `integer` | The cost of the user fetching their bike and parking it again. | *Optional* | `0` | 2.0 | -| bikeSwitchTime | `integer` | The time it takes the user to fetch their bike and park it again in seconds. | *Optional* | `0` | 2.0 | -| bikeTriangleSafetyFactor | `double` | For bike triangle routing, how much safety matters (range 0-1). | *Optional* | `0.0` | 2.0 | -| bikeTriangleSlopeFactor | `double` | For bike triangle routing, how much slope matters (range 0-1). | *Optional* | `0.0` | 2.0 | -| bikeTriangleTimeFactor | `double` | For bike triangle routing, how much time matters (range 0-1). | *Optional* | `0.0` | 2.0 | -| bikeWalkingReluctance | `double` | A multiplier for how bad walking with a bike is, compared to being in transit for equal lengths of time. | *Optional* | `5.0` | 2.1 | -| bikeWalkingSpeed | `double` | The user's bike walking speed in meters/second. Defaults to approximately 3 MPH. | *Optional* | `1.33` | 2.1 | | [boardSlack](#rd_boardSlack) | `duration` | The boardSlack is the minimum extra time to board a public transport vehicle. | *Optional* | `"PT0S"` | 2.0 | -| carAccelerationSpeed | `double` | The acceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | -| carDecelerationSpeed | `double` | The deceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | -| carDropoffTime | `integer` | Time to park a car in a park and ride, w/o taking into account driving and walking cost. | *Optional* | `120` | 2.0 | -| carParkCost | `integer` | Cost of parking a car. | *Optional* | `120` | 2.1 | -| carParkTime | `duration` | Time to park a car | *Optional* | `"PT1M"` | 2.1 | -| carPickupCost | `integer` | Add a cost for car pickup changes when a pickup or drop off takes place | *Optional* | `120` | 2.1 | -| carPickupTime | `integer` | Add a time for car pickup changes when a pickup or drop off takes place | *Optional* | `60` | 2.1 | -| carReluctance | `double` | A multiplier for how bad driving is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -| carSpeed | `double` | Max car speed along streets, in meters per second | *Optional* | `40.0` | 2.0 | | [drivingDirection](#rd_drivingDirection) | `enum` | The driving direction to use in the intersection traversal calculation | *Optional* | `"right"` | 2.2 | | elevatorBoardCost | `integer` | What is the cost of boarding a elevator? | *Optional* | `90` | 2.0 | | elevatorBoardTime | `integer` | How long does it take to get on an elevator, on average. | *Optional* | `90` | 2.0 | | elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 | | elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | -| escalatorReluctance | `double` | A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time | *Optional* | `1.5` | 2.4 | | geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | | ignoreRealtimeUpdates | `boolean` | When true, real-time updates are ignored during this search. | *Optional* | `false` | 2.0 | | [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | @@ -55,24 +32,16 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe | modes | `string` | The set of access/egress/direct/transit modes to be used for the route search. | *Optional* | `"TRANSIT,WALK"` | 2.0 | | nonpreferredTransferPenalty | `integer` | Penalty (in seconds) for using a non-preferred transfer. | *Optional* | `180` | 2.0 | | numItineraries | `integer` | The maximum number of itineraries to return. | *Optional* | `50` | 2.0 | -| [optimize](#rd_optimize) | `enum` | The set of characteristics that the user wants to optimize for. | *Optional* | `"safe"` | 2.0 | | [otherThanPreferredRoutesPenalty](#rd_otherThanPreferredRoutesPenalty) | `integer` | Penalty added for using every route that is not preferred if user set any route as preferred. | *Optional* | `300` | 2.0 | -| [relaxTransitPriorityGroup](#rd_relaxTransitPriorityGroup) | `string` | The relax function for transit-priority-groups | *Optional* | `"0s + 1.00 t"` | 2.5 | +| [relaxTransitGroupPriority](#rd_relaxTransitGroupPriority) | `string` | The relax function for transit-group-priority | *Optional* | `"0s + 1.00 t"` | 2.5 | | [relaxTransitSearchGeneralizedCostAtDestination](#rd_relaxTransitSearchGeneralizedCostAtDestination) | `double` | Whether non-optimal transit paths at the destination should be returned | *Optional* | | 2.3 | | [searchWindow](#rd_searchWindow) | `duration` | The duration of the search-window. | *Optional* | | 2.0 | -| stairsReluctance | `double` | Used instead of walkReluctance for stairs. | *Optional* | `2.0` | 2.0 | -| [stairsTimeFactor](#rd_stairsTimeFactor) | `double` | How much more time does it take to walk a flight of stairs compared to walking a similar horizontal length. | *Optional* | `3.0` | 2.1 | | [streetRoutingTimeout](#rd_streetRoutingTimeout) | `duration` | The maximum time a street routing request is allowed to take before returning the results. | *Optional* | `"PT5S"` | 2.2 | | [transferPenalty](#rd_transferPenalty) | `integer` | An additional penalty added to boardings after the first. | *Optional* | `0` | 2.0 | | [transferSlack](#rd_transferSlack) | `integer` | The extra time needed to make a safe transfer in seconds. | *Optional* | `120` | 2.0 | | turnReluctance | `double` | Multiplicative factor on expected turning time. | *Optional* | `1.0` | 2.0 | | [unpreferredCost](#rd_unpreferredCost) | `cost-linear-function` | A cost function used to calculate penalty for an unpreferred route. | *Optional* | `"0s + 1.00 t"` | 2.2 | -| [unpreferredVehicleParkingTagCost](#rd_unpreferredVehicleParkingTagCost) | `integer` | What cost to add if a parking facility doesn't contain a preferred tag. | *Optional* | `300` | 2.3 | | waitReluctance | `double` | How much worse is waiting for a transit vehicle than being on a transit vehicle, as a multiplier. | *Optional* | `1.0` | 2.0 | -| walkBoardCost | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. This is the cost that is used when boarding while walking. | *Optional* | `600` | 2.0 | -| [walkReluctance](#rd_walkReluctance) | `double` | A multiplier for how bad walking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -| [walkSafetyFactor](#rd_walkSafetyFactor) | `double` | Factor for how much the walk safety is considered in routing. | *Optional* | `1.0` | 2.2 | -| walkSpeed | `double` | The user's walking speed in meters/second. | *Optional* | `1.33` | 2.0 | | accessEgress | `object` | Parameters for access and egress routing. | *Optional* | | 2.4 | |    [maxDuration](#rd_accessEgress_maxDuration) | `duration` | This is the maximum duration for access/egress for street searches. | *Optional* | `"PT45M"` | 2.1 | |    [maxStopCount](#rd_accessEgress_maxStopCount) | `integer` | Maximal number of stops collected in access/egress routing | *Optional* | `500` | 2.4 | @@ -82,8 +51,63 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe |          costFactor | `double` | A factor multiplied with the time-penalty to get the cost-penalty. | *Optional* | `0.0` | 2.4 | |          timePenalty | `time-penalty` | Penalty added to the time of a path/leg. | *Optional* | `"0s + 0.00 t"` | 2.4 | | [alightSlackForMode](#rd_alightSlackForMode) | `enum map of duration` | How much extra time should be given when alighting a vehicle for each given mode. | *Optional* | | 2.0 | -| [bannedVehicleParkingTags](#rd_bannedVehicleParkingTags) | `string[]` | Tags with which a vehicle parking will not be used. If empty, no tags are banned. | *Optional* | | 2.1 | +| bicycle | `object` | Bicycle preferences. | *Optional* | | 2.5 | +|    [boardCost](#rd_bicycle_boardCost) | `integer` | Prevents unnecessary transfers by adding a cost for boarding a transit vehicle. | *Optional* | `600` | 2.0 | +|    [optimization](#rd_bicycle_optimization) | `enum` | The set of characteristics that the user wants to optimize for. | *Optional* | `"safe-streets"` | 2.0 | +|    reluctance | `double` | A multiplier for how bad cycling is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | +|    speed | `double` | Max bicycle speed along streets, in meters per second | *Optional* | `5.0` | 2.0 | +|    parking | `object` | Preferences for parking a vehicle. | *Optional* | | 2.5 | +|       cost | `integer` | Cost to park a vehicle. | *Optional* | `120` | 2.0 | +|       time | `duration` | Time to park a vehicle. | *Optional* | `"PT1M"` | 2.0 | +|       [unpreferredVehicleParkingTagCost](#rd_bicycle_parking_unpreferredVehicleParkingTagCost) | `integer` | What cost to add if a parking facility doesn't contain a preferred tag. | *Optional* | `300` | 2.3 | +|       [bannedVehicleParkingTags](#rd_bicycle_parking_bannedVehicleParkingTags) | `string[]` | Tags with which a vehicle parking will not be used. If empty, no tags are banned. | *Optional* | | 2.1 | +|       [preferredVehicleParkingTags](#rd_bicycle_parking_preferredVehicleParkingTags) | `string[]` | Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised. | *Optional* | | 2.3 | +|       [requiredVehicleParkingTags](#rd_bicycle_parking_requiredVehicleParkingTags) | `string[]` | Tags without which a vehicle parking will not be used. If empty, no tags are required. | *Optional* | | 2.1 | +|    rental | `object` | Vehicle rental options | *Optional* | | 2.3 | +|       allowKeepingAtDestination | `boolean` | If a vehicle should be allowed to be kept at the end of a station-based rental. | *Optional* | `false` | 2.2 | +|       dropOffCost | `integer` | Cost to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | +|       dropOffTime | `duration` | Time to drop-off a rented vehicle. | *Optional* | `"PT30S"` | 2.0 | +|       keepingAtDestinationCost | `integer` | The cost of arriving at the destination with the rented vehicle, to discourage doing so. | *Optional* | `0` | 2.2 | +|       pickupCost | `integer` | Cost to rent a vehicle. | *Optional* | `120` | 2.0 | +|       pickupTime | `duration` | Time to rent a vehicle. | *Optional* | `"PT1M"` | 2.0 | +|       useAvailabilityInformation | `boolean` | Whether or not vehicle rental availability information will be used to plan vehicle rental trips. | *Optional* | `false` | 2.0 | +|       [allowedNetworks](#rd_bicycle_rental_allowedNetworks) | `string[]` | The vehicle rental networks which may be used. If empty all networks may be used. | *Optional* | | 2.1 | +|       [bannedNetworks](#rd_bicycle_rental_bannedNetworks) | `string[]` | The vehicle rental networks which may not be used. If empty, no networks are banned. | *Optional* | | 2.1 | +|    [triangle](#rd_bicycle_triangle) | `object` | Triangle optimization criteria. | *Optional* | | 2.5 | +|       flatness | `double` | Relative importance of flat terrain (range 0-1). | *Optional* | `0.0` | 2.0 | +|       [safety](#rd_bicycle_triangle_safety) | `double` | Relative importance of safety (range 0-1). | *Optional* | `0.0` | 2.0 | +|       time | `double` | Relative importance of duration of travel (range 0-1). | *Optional* | `0.0` | 2.0 | +|    walk | `object` | Preferences for walking a vehicle. | *Optional* | | 2.5 | +|       [hopCost](#rd_bicycle_walk_hopCost) | `integer` | The cost of hopping on or off a vehicle. | *Optional* | `0` | 2.0 | +|       [hopTime](#rd_bicycle_walk_hopTime) | `duration` | The time it takes the user to hop on or off a vehicle. | *Optional* | `"PT0S"` | 2.0 | +|       reluctance | `double` | A multiplier for how bad walking with a vehicle is, compared to being in transit for equal lengths of time. | *Optional* | `5.0` | 2.1 | +|       speed | `double` | The user's vehicle walking speed in meters/second. Defaults to approximately 3 MPH. | *Optional* | `1.33` | 2.1 | +|       stairsReluctance | `double` | How bad is it to walk the vehicle up/down a flight of stairs compared to taking a detour. | *Optional* | `10.0` | 2.3 | | [boardSlackForMode](#rd_boardSlackForMode) | `enum map of duration` | How much extra time should be given when boarding a vehicle for each given mode. | *Optional* | | 2.0 | +| car | `object` | Car preferences. | *Optional* | | 2.5 | +|    accelerationSpeed | `double` | The acceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | +|    decelerationSpeed | `double` | The deceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | +|    pickupCost | `integer` | Add a cost for car pickup changes when a pickup or drop off takes place | *Optional* | `120` | 2.1 | +|    pickupTime | `duration` | Add a time for car pickup changes when a pickup or drop off takes place | *Optional* | `"PT1M"` | 2.1 | +|    reluctance | `double` | A multiplier for how bad driving is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | +|    speed | `double` | Max car speed along streets, in meters per second | *Optional* | `40.0` | 2.0 | +|    parking | `object` | Preferences for parking a vehicle. | *Optional* | | 2.5 | +|       cost | `integer` | Cost to park a vehicle. | *Optional* | `120` | 2.0 | +|       time | `duration` | Time to park a vehicle. | *Optional* | `"PT1M"` | 2.0 | +|       [unpreferredVehicleParkingTagCost](#rd_car_parking_unpreferredVehicleParkingTagCost) | `integer` | What cost to add if a parking facility doesn't contain a preferred tag. | *Optional* | `300` | 2.3 | +|       [bannedVehicleParkingTags](#rd_car_parking_bannedVehicleParkingTags) | `string[]` | Tags with which a vehicle parking will not be used. If empty, no tags are banned. | *Optional* | | 2.1 | +|       [preferredVehicleParkingTags](#rd_car_parking_preferredVehicleParkingTags) | `string[]` | Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised. | *Optional* | | 2.3 | +|       [requiredVehicleParkingTags](#rd_car_parking_requiredVehicleParkingTags) | `string[]` | Tags without which a vehicle parking will not be used. If empty, no tags are required. | *Optional* | | 2.1 | +|    rental | `object` | Vehicle rental options | *Optional* | | 2.3 | +|       allowKeepingAtDestination | `boolean` | If a vehicle should be allowed to be kept at the end of a station-based rental. | *Optional* | `false` | 2.2 | +|       dropOffCost | `integer` | Cost to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | +|       dropOffTime | `duration` | Time to drop-off a rented vehicle. | *Optional* | `"PT30S"` | 2.0 | +|       keepingAtDestinationCost | `integer` | The cost of arriving at the destination with the rented vehicle, to discourage doing so. | *Optional* | `0` | 2.2 | +|       pickupCost | `integer` | Cost to rent a vehicle. | *Optional* | `120` | 2.0 | +|       pickupTime | `duration` | Time to rent a vehicle. | *Optional* | `"PT1M"` | 2.0 | +|       useAvailabilityInformation | `boolean` | Whether or not vehicle rental availability information will be used to plan vehicle rental trips. | *Optional* | `false` | 2.0 | +|       [allowedNetworks](#rd_car_rental_allowedNetworks) | `string[]` | The vehicle rental networks which may be used. If empty all networks may be used. | *Optional* | | 2.1 | +|       [bannedNetworks](#rd_car_rental_bannedNetworks) | `string[]` | The vehicle rental networks which may not be used. If empty, no networks are banned. | *Optional* | | 2.1 | | [itineraryFilters](#rd_itineraryFilters) | `object` | Configure itinerary filters that may modify itineraries, sort them, and filter away less preferable results. | *Optional* | | 2.0 | |    [accessibilityScore](#rd_if_accessibilityScore) | `boolean` | An experimental feature contributed by IBI which adds a sandbox accessibility *score* between 0 and 1 for each leg and itinerary. | *Optional* | `false` | 2.2 | |    [bikeRentalDistanceRatio](#rd_if_bikeRentalDistanceRatio) | `double` | Filter routes that consist of bike-rental and walking by the minimum fraction of the bike-rental leg using _distance_. | *Optional* | `0.0` | 2.1 | @@ -101,28 +125,24 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe |       [costLimitFunction](#rd_if_transitGeneralizedCostLimit_costLimitFunction) | `cost-linear-function` | The base function used by the filter. | *Optional* | `"15m + 1.50 t"` | 2.2 | |       [intervalRelaxFactor](#rd_if_transitGeneralizedCostLimit_intervalRelaxFactor) | `double` | How much the filter should be relaxed for itineraries that do not overlap in time. | *Optional* | `0.4` | 2.2 | | [maxDirectStreetDurationForMode](#rd_maxDirectStreetDurationForMode) | `enum map of duration` | Limit direct route duration per street mode. | *Optional* | | 2.2 | -| [preferredVehicleParkingTags](#rd_preferredVehicleParkingTags) | `string[]` | Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised. | *Optional* | | 2.3 | -| [requiredVehicleParkingTags](#rd_requiredVehicleParkingTags) | `string[]` | Tags without which a vehicle parking will not be used. If empty, no tags are required. | *Optional* | | 2.1 | | [transferOptimization](#rd_transferOptimization) | `object` | Optimize where a transfer between to trip happens. | *Optional* | | 2.1 | |    [backTravelWaitTimeFactor](#rd_to_backTravelWaitTimeFactor) | `double` | To reduce back-travel we favor waiting, this reduces the cost of waiting. | *Optional* | `1.0` | 2.1 | |    [extraStopBoardAlightCostsFactor](#rd_to_extraStopBoardAlightCostsFactor) | `double` | Add an extra board- and alight-cost for prioritized stops. | *Optional* | `0.0` | 2.1 | |    [minSafeWaitTimeFactor](#rd_to_minSafeWaitTimeFactor) | `double` | Used to set a maximum wait-time cost, base on min-safe-transfer-time. | *Optional* | `5.0` | 2.1 | |    [optimizeTransferWaitTime](#rd_to_optimizeTransferWaitTime) | `boolean` | This enables the transfer wait time optimization. | *Optional* | `true` | 2.1 | -| [transitPriorityGroups](#rd_transitPriorityGroups) | `object` | Transit priority groups configuration | *Optional* | | 2.5 | +| [transitGroupPriority](#rd_transitGroupPriority) | `object` | Group transit patterns and give each group a mutual advantage in the Raptor search. | *Optional* | | 2.5 | | [transitReluctanceForMode](#rd_transitReluctanceForMode) | `enum map of double` | Transit reluctance for a given transport mode | *Optional* | | 2.1 | | [unpreferred](#rd_unpreferred) | `object` | Parameters listing authorities or lines that preferably should not be used in trip patters. | *Optional* | | 2.2 | |    [agencies](#rd_unpreferred_agencies) | `feed-scoped-id[]` | The ids of the agencies that incur an extra cost when being used. Format: `FeedId:AgencyId` | *Optional* | | 2.2 | |    [routes](#rd_unpreferred_routes) | `feed-scoped-id[]` | The ids of the routes that incur an extra cost when being used. Format: `FeedId:RouteId` | *Optional* | | 2.2 | -| vehicleRental | `object` | Vehicle rental options | *Optional* | | 2.3 | -|    allowKeepingAtDestination | `boolean` | If a vehicle should be allowed to be kept at the end of a station-based rental. | *Optional* | `false` | 2.2 | -|    dropOffCost | `integer` | Cost to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | -|    dropOffTime | `integer` | Time to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | -|    keepingAtDestinationCost | `double` | The cost of arriving at the destination with the rented vehicle, to discourage doing so. | *Optional* | `0.0` | 2.2 | -|    pickupCost | `integer` | Cost to rent a vehicle. | *Optional* | `120` | 2.0 | -|    pickupTime | `integer` | Time to rent a vehicle. | *Optional* | `60` | 2.0 | -|    useAvailabilityInformation | `boolean` | Whether or not vehicle rental availability information will be used to plan vehicle rental trips. | *Optional* | `false` | 2.0 | -|    [allowedNetworks](#rd_vehicleRental_allowedNetworks) | `string[]` | The vehicle rental networks which may be used. If empty all networks may be used. | *Optional* | | 2.1 | -|    [bannedNetworks](#rd_vehicleRental_bannedNetworks) | `string[]` | The vehicle rental networks which may not be used. If empty, no networks are banned. | *Optional* | | 2.1 | +| walk | `object` | Walking preferences. | *Optional* | | 2.5 | +|    boardCost | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. This is the cost that is used when boarding while walking. | *Optional* | `600` | 2.0 | +|    escalatorReluctance | `double` | A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time | *Optional* | `1.5` | 2.4 | +|    [reluctance](#rd_walk_reluctance) | `double` | A multiplier for how bad walking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | +|    [safetyFactor](#rd_walk_safetyFactor) | `double` | Factor for how much the walk safety is considered in routing. | *Optional* | `1.0` | 2.2 | +|    speed | `double` | The user's walking speed in meters/second. | *Optional* | `1.33` | 2.0 | +|    stairsReluctance | `double` | Used instead of walkReluctance for stairs. | *Optional* | `2.0` | 2.0 | +|    [stairsTimeFactor](#rd_walk_stairsTimeFactor) | `double` | How much more time does it take to walk a flight of stairs compared to walking a similar horizontal length. | *Optional* | `3.0` | 2.1 | | wheelchairAccessibility | `object` | See [Wheelchair Accessibility](Accessibility.md) | *Optional* | | 2.2 | |    enabled | `boolean` | Enable wheelchair accessibility. | *Optional* | `false` | 2.0 | |    inaccessibleStreetReluctance | `double` | The factor to multiply the cost of traversing a street edge that is not wheelchair-accessible. | *Optional* | `25.0` | 2.2 | @@ -159,15 +179,6 @@ The minimum extra time after exiting a public transport vehicle. The slack is added to the time when going from the transit vehicle to the stop. -

bikeBoardCost

- -**Since version:** `2.0` ∙ **Type:** `integer` ∙ **Cardinality:** `Optional` ∙ **Default value:** `600` -**Path:** /routingDefaults - -Prevents unnecessary transfers by adding a cost for boarding a vehicle. - -This is the cost that is used when boarding while cycling.This is usually higher that walkBoardCost. -

boardSlack

**Since version:** `2.0` ∙ **Type:** `duration` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"PT0S"` @@ -230,14 +241,6 @@ search, hence, making it a bit slower. Recommended values would be from 12 hours 1 day (region) to 2 days (country like Norway)." -

optimize

- -**Since version:** `2.0` ∙ **Type:** `enum` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"safe"` -**Path:** /routingDefaults -**Enum values:** `quick` | `safe` | `flat` | `greenways` | `triangle` - -The set of characteristics that the user wants to optimize for. -

otherThanPreferredRoutesPenalty

**Since version:** `2.0` ∙ **Type:** `integer` ∙ **Cardinality:** `Optional` ∙ **Default value:** `300` @@ -247,16 +250,16 @@ Penalty added for using every route that is not preferred if user set any route We return number of seconds that we are willing to wait for preferred route. -

relaxTransitPriorityGroup

+

relaxTransitGroupPriority

**Since version:** `2.5` ∙ **Type:** `string` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"0s + 1.00 t"` **Path:** /routingDefaults -The relax function for transit-priority-groups +The relax function for transit-group-priority -A path is considered optimal if the generalized-cost is less than the -generalized-cost of another path. If this parameter is set, the comparison is relaxed -further if they belong to different transit-priority-groups. +A path is considered optimal if the generalized-cost is less than the generalized-cost of +another path. If this parameter is set, the comparison is relaxed further if they belong +to different transit groups.

relaxTransitSearchGeneralizedCostAtDestination

@@ -303,17 +306,6 @@ There is no need to set this when going to the next/previous page. The OTP Serve increase/decrease the search-window when paging to match the requested number of itineraries. -

stairsTimeFactor

- -**Since version:** `2.1` ∙ **Type:** `double` ∙ **Cardinality:** `Optional` ∙ **Default value:** `3.0` -**Path:** /routingDefaults - -How much more time does it take to walk a flight of stairs compared to walking a similar horizontal length. - -Default value is based on: Fujiyama, T., & Tyler, N. (2010). Predicting the walking -speed of pedestrians on stairs. Transportation Planning and Technology, 33(2), 177–202. - -

streetRoutingTimeout

**Since version:** `2.2` ∙ **Type:** `duration` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"PT5S"` @@ -364,38 +356,6 @@ Function should return number of seconds that we are willing to wait for preferr or for an unpreferred agency's departure. For example: `5m + 2.0 t` -

unpreferredVehicleParkingTagCost

- -**Since version:** `2.3` ∙ **Type:** `integer` ∙ **Cardinality:** `Optional` ∙ **Default value:** `300` -**Path:** /routingDefaults - -What cost to add if a parking facility doesn't contain a preferred tag. - -See `preferredVehicleParkingTags`. - -

walkReluctance

- -**Since version:** `2.0` ∙ **Type:** `double` ∙ **Cardinality:** `Optional` ∙ **Default value:** `2.0` -**Path:** /routingDefaults - -A multiplier for how bad walking is, compared to being in transit for equal lengths of time. - -Empirically, values between 2 and 4 seem to correspond well to the concept of not wanting to walk -too much without asking for totally ridiculous itineraries, but this observation should in no way -be taken as scientific or definitive. Your mileage may vary. -See https://github.com/opentripplanner/OpenTripPlanner/issues/4090 for impact on performance with -high values. - - -

walkSafetyFactor

- -**Since version:** `2.2` ∙ **Type:** `double` ∙ **Cardinality:** `Optional` ∙ **Default value:** `1.0` -**Path:** /routingDefaults - -Factor for how much the walk safety is considered in routing. - -Value should be between 0 and 1. If the value is set to be 0, safety is ignored. -

maxDuration

**Since version:** `2.1` ∙ **Type:** `duration` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"PT45M"` @@ -473,16 +433,120 @@ How much extra time should be given when alighting a vehicle for each given mode Sometimes there is a need to configure a longer alighting times for specific modes, such as airplanes or ferries. -

bannedVehicleParkingTags

+

boardCost

+ +**Since version:** `2.0` ∙ **Type:** `integer` ∙ **Cardinality:** `Optional` ∙ **Default value:** `600` +**Path:** /routingDefaults/bicycle + +Prevents unnecessary transfers by adding a cost for boarding a transit vehicle. + +This is the cost that is used when boarding while cycling. This is usually higher that walkBoardCost. + +

optimization

+ +**Since version:** `2.0` ∙ **Type:** `enum` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"safe-streets"` +**Path:** /routingDefaults/bicycle +**Enum values:** `shortest-duration` | `safe-streets` | `flat-streets` | `safest-streets` | `triangle` + +The set of characteristics that the user wants to optimize for. + +If the triangle optimization is used, it's enough to just define the triangle parameters + +

unpreferredVehicleParkingTagCost

+ +**Since version:** `2.3` ∙ **Type:** `integer` ∙ **Cardinality:** `Optional` ∙ **Default value:** `300` +**Path:** /routingDefaults/bicycle/parking + +What cost to add if a parking facility doesn't contain a preferred tag. + +See `preferredVehicleParkingTags`. + +

bannedVehicleParkingTags

**Since version:** `2.1` ∙ **Type:** `string[]` ∙ **Cardinality:** `Optional` -**Path:** /routingDefaults +**Path:** /routingDefaults/bicycle/parking Tags with which a vehicle parking will not be used. If empty, no tags are banned. Vehicle parking tags can originate from different places depending on the origin of the parking(OSM or RT feed). +

preferredVehicleParkingTags

+ +**Since version:** `2.3` ∙ **Type:** `string[]` ∙ **Cardinality:** `Optional` +**Path:** /routingDefaults/bicycle/parking + +Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised. + +Vehicle parking tags can originate from different places depending on the origin of the parking(OSM or RT feed). + + +

requiredVehicleParkingTags

+ +**Since version:** `2.1` ∙ **Type:** `string[]` ∙ **Cardinality:** `Optional` +**Path:** /routingDefaults/bicycle/parking + +Tags without which a vehicle parking will not be used. If empty, no tags are required. + +Vehicle parking tags can originate from different places depending on the origin of the parking(OSM or RT feed). + + +

allowedNetworks

+ +**Since version:** `2.1` ∙ **Type:** `string[]` ∙ **Cardinality:** `Optional` +**Path:** /routingDefaults/bicycle/rental + +The vehicle rental networks which may be used. If empty all networks may be used. + +

bannedNetworks

+ +**Since version:** `2.1` ∙ **Type:** `string[]` ∙ **Cardinality:** `Optional` +**Path:** /routingDefaults/bicycle/rental + +The vehicle rental networks which may not be used. If empty, no networks are banned. + +

triangle

+ +**Since version:** `2.5` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` +**Path:** /routingDefaults/bicycle + +Triangle optimization criteria. + +Optimization type doesn't need to be defined if these values are defined. + +

safety

+ +**Since version:** `2.0` ∙ **Type:** `double` ∙ **Cardinality:** `Optional` ∙ **Default value:** `0.0` +**Path:** /routingDefaults/bicycle/triangle + +Relative importance of safety (range 0-1). + +This factor can also include other concerns such as convenience and general cyclist +preferences by taking into account road surface etc. + + +

hopCost

+ +**Since version:** `2.0` ∙ **Type:** `integer` ∙ **Cardinality:** `Optional` ∙ **Default value:** `0` +**Path:** /routingDefaults/bicycle/walk + +The cost of hopping on or off a vehicle. + +There are different parameters for the cost of renting or parking a vehicle and this is +not meant for controlling the cost of those events. + + +

hopTime

+ +**Since version:** `2.0` ∙ **Type:** `duration` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"PT0S"` +**Path:** /routingDefaults/bicycle/walk + +The time it takes the user to hop on or off a vehicle. + +Time it takes to rent or park a vehicle have their own parameters and this is not meant +for controlling the duration of those events. + +

boardSlackForMode

**Since version:** `2.0` ∙ **Type:** `enum map of duration` ∙ **Cardinality:** `Optional` @@ -495,6 +559,59 @@ Sometimes there is a need to configure a board times for specific modes, such as ferries, where the check-in process needs to be done in good time before ride. +

unpreferredVehicleParkingTagCost

+ +**Since version:** `2.3` ∙ **Type:** `integer` ∙ **Cardinality:** `Optional` ∙ **Default value:** `300` +**Path:** /routingDefaults/car/parking + +What cost to add if a parking facility doesn't contain a preferred tag. + +See `preferredVehicleParkingTags`. + +

bannedVehicleParkingTags

+ +**Since version:** `2.1` ∙ **Type:** `string[]` ∙ **Cardinality:** `Optional` +**Path:** /routingDefaults/car/parking + +Tags with which a vehicle parking will not be used. If empty, no tags are banned. + +Vehicle parking tags can originate from different places depending on the origin of the parking(OSM or RT feed). + + +

preferredVehicleParkingTags

+ +**Since version:** `2.3` ∙ **Type:** `string[]` ∙ **Cardinality:** `Optional` +**Path:** /routingDefaults/car/parking + +Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised. + +Vehicle parking tags can originate from different places depending on the origin of the parking(OSM or RT feed). + + +

requiredVehicleParkingTags

+ +**Since version:** `2.1` ∙ **Type:** `string[]` ∙ **Cardinality:** `Optional` +**Path:** /routingDefaults/car/parking + +Tags without which a vehicle parking will not be used. If empty, no tags are required. + +Vehicle parking tags can originate from different places depending on the origin of the parking(OSM or RT feed). + + +

allowedNetworks

+ +**Since version:** `2.1` ∙ **Type:** `string[]` ∙ **Cardinality:** `Optional` +**Path:** /routingDefaults/car/rental + +The vehicle rental networks which may be used. If empty all networks may be used. + +

bannedNetworks

+ +**Since version:** `2.1` ∙ **Type:** `string[]` ∙ **Cardinality:** `Optional` +**Path:** /routingDefaults/car/rental + +The vehicle rental networks which may not be used. If empty, no networks are banned. +

itineraryFilters

**Since version:** `2.0` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` @@ -710,26 +827,6 @@ Override the settings in `maxDirectStreetDuration` for specific street modes. Th done because some street modes searches are much more resource intensive than others. -

preferredVehicleParkingTags

- -**Since version:** `2.3` ∙ **Type:** `string[]` ∙ **Cardinality:** `Optional` -**Path:** /routingDefaults - -Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised. - -Vehicle parking tags can originate from different places depending on the origin of the parking(OSM or RT feed). - - -

requiredVehicleParkingTags

- -**Since version:** `2.1` ∙ **Type:** `string[]` ∙ **Cardinality:** `Optional` -**Path:** /routingDefaults - -Tags without which a vehicle parking will not be used. If empty, no tags are required. - -Vehicle parking tags can originate from different places depending on the origin of the parking(OSM or RT feed). - -

transferOptimization

**Since version:** `2.1` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` @@ -812,22 +909,20 @@ This enables the transfer wait time optimization. If not enabled generalizedCost function is used to pick the optimal transfer point. -

transitPriorityGroups

+

transitGroupPriority

**Since version:** `2.5` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` **Path:** /routingDefaults -Transit priority groups configuration +Group transit patterns and give each group a mutual advantage in the Raptor search. Use this to separate transit patterns into groups. Each group will be given a group-id. A path (multiple legs) will then have a set of group-ids based on the group-id from each leg. Hence, two paths with a different set of group-ids will BOTH be optimal unless the cost is -worse than the relaxation specified in the `relaxTransitPriorityGroup` parameter. This is +worse than the relaxation specified in the `relaxTransitGroupPriority` parameter. This is only available in the TransmodelAPI for now. -Unmatched patterns are put in the BASE priority-group (group id: 0). This group is special. -If a path only have legs in the base group, then that path dominates other paths, but other -paths must be better to make it. +Unmatched patterns are put in the BASE priority-group. **THIS IS STILL AN EXPERIMENTAL FEATURE - IT MAY CHANGE WITHOUT ANY NOTICE!** @@ -873,19 +968,39 @@ The ids of the routes that incur an extra cost when being used. Format: `FeedId: How much cost is added is configured in `unpreferredCost`. -

allowedNetworks

+

reluctance

-**Since version:** `2.1` ∙ **Type:** `string[]` ∙ **Cardinality:** `Optional` -**Path:** /routingDefaults/vehicleRental +**Since version:** `2.0` ∙ **Type:** `double` ∙ **Cardinality:** `Optional` ∙ **Default value:** `2.0` +**Path:** /routingDefaults/walk -The vehicle rental networks which may be used. If empty all networks may be used. +A multiplier for how bad walking is, compared to being in transit for equal lengths of time. + +Empirically, values between 2 and 4 seem to correspond well to the concept of not wanting to walk +too much without asking for totally ridiculous itineraries, but this observation should in no way +be taken as scientific or definitive. Your mileage may vary. +See https://github.com/opentripplanner/OpenTripPlanner/issues/4090 for impact on performance with +high values. -

bannedNetworks

-**Since version:** `2.1` ∙ **Type:** `string[]` ∙ **Cardinality:** `Optional` -**Path:** /routingDefaults/vehicleRental +

safetyFactor

+ +**Since version:** `2.2` ∙ **Type:** `double` ∙ **Cardinality:** `Optional` ∙ **Default value:** `1.0` +**Path:** /routingDefaults/walk + +Factor for how much the walk safety is considered in routing. + +Value should be between 0 and 1. If the value is set to be 0, safety is ignored. + +

stairsTimeFactor

+ +**Since version:** `2.1` ∙ **Type:** `double` ∙ **Cardinality:** `Optional` ∙ **Default value:** `3.0` +**Path:** /routingDefaults/walk + +How much more time does it take to walk a flight of stairs compared to walking a similar horizontal length. + +Default value is based on: Fujiyama, T., & Tyler, N. (2010). Predicting the walking +speed of pedestrians on stairs. Transportation Planning and Technology, 33(2), 177–202. -The vehicle rental networks which may not be used. If empty, no networks are banned.

maxSlope

@@ -933,34 +1048,59 @@ include stairs as a last result. // router-config.json { "routingDefaults" : { - "walkSpeed" : 1.3, - "bikeSpeed" : 5, - "carSpeed" : 40, "numItineraries" : 12, "transferPenalty" : 0, - "walkReluctance" : 4.0, - "bikeReluctance" : 5.0, - "bikeWalkingReluctance" : 10.0, - "bikeStairsReluctance" : 150.0, - "carReluctance" : 10.0, - "stairsReluctance" : 1.65, "turnReluctance" : 1.0, "elevatorBoardTime" : 90, "elevatorBoardCost" : 90, "elevatorHopTime" : 20, "elevatorHopCost" : 20, - "escalatorReluctance" : 1.5, - "vehicleRental" : { - "pickupCost" : 120, - "dropOffTime" : 30, - "dropOffCost" : 30 + "bicycle" : { + "speed" : 5, + "reluctance" : 5.0, + "boardCost" : 600, + "walk" : { + "reluctance" : 10.0, + "stairsReluctance" : 150.0 + }, + "rental" : { + "pickupCost" : 120, + "dropOffTime" : "30s", + "dropOffCost" : 30 + }, + "parking" : { + "time" : "1m", + "cost" : 120 + }, + "triangle" : { + "safety" : 0.4, + "flatness" : 0.3, + "time" : 0.3 + } + }, + "car" : { + "speed" : 40, + "reluctance" : 10, + "decelerationSpeed" : 2.9, + "accelerationSpeed" : 2.9, + "rental" : { + "pickupCost" : 120, + "dropOffTime" : "30s", + "dropOffCost" : 30 + }, + "parking" : { + "time" : "5m", + "cost" : 600 + } + }, + "walk" : { + "speed" : 1.3, + "reluctance" : 4.0, + "stairsReluctance" : 1.65, + "boardCost" : 600, + "escalatorReluctance" : 1.5 }, - "bikeParkTime" : "1m", - "bikeParkCost" : 120, - "carDropoffTime" : 120, "waitReluctance" : 1.0, - "walkBoardCost" : 600, - "bikeBoardCost" : 600, "otherThanPreferredRoutesPenalty" : 300, "transferSlack" : 120, "boardSlackForMode" : { @@ -997,8 +1137,6 @@ include stairs as a last result. "minBikeParkingDistance" : 300, "debug" : "limit-to-search-window" }, - "carDecelerationSpeed" : 2.9, - "carAccelerationSpeed" : 2.9, "ignoreRealtimeUpdates" : false, "geoidElevation" : false, "maxJourneyDuration" : "36h", diff --git a/docs/RouterConfiguration.md b/docs/RouterConfiguration.md index 30e423f29b1..23e2e3763d4 100644 --- a/docs/RouterConfiguration.md +++ b/docs/RouterConfiguration.md @@ -451,34 +451,59 @@ Used to group requests when monitoring OTP. ] }, "routingDefaults" : { - "walkSpeed" : 1.3, - "bikeSpeed" : 5, - "carSpeed" : 40, "numItineraries" : 12, "transferPenalty" : 0, - "walkReluctance" : 4.0, - "bikeReluctance" : 5.0, - "bikeWalkingReluctance" : 10.0, - "bikeStairsReluctance" : 150.0, - "carReluctance" : 10.0, - "stairsReluctance" : 1.65, "turnReluctance" : 1.0, "elevatorBoardTime" : 90, "elevatorBoardCost" : 90, "elevatorHopTime" : 20, "elevatorHopCost" : 20, - "escalatorReluctance" : 1.5, - "vehicleRental" : { - "pickupCost" : 120, - "dropOffTime" : 30, - "dropOffCost" : 30 + "bicycle" : { + "speed" : 5, + "reluctance" : 5.0, + "boardCost" : 600, + "walk" : { + "reluctance" : 10.0, + "stairsReluctance" : 150.0 + }, + "rental" : { + "pickupCost" : 120, + "dropOffTime" : "30s", + "dropOffCost" : 30 + }, + "parking" : { + "time" : "1m", + "cost" : 120 + }, + "triangle" : { + "safety" : 0.4, + "flatness" : 0.3, + "time" : 0.3 + } + }, + "car" : { + "speed" : 40, + "reluctance" : 10, + "decelerationSpeed" : 2.9, + "accelerationSpeed" : 2.9, + "rental" : { + "pickupCost" : 120, + "dropOffTime" : "30s", + "dropOffCost" : 30 + }, + "parking" : { + "time" : "5m", + "cost" : 600 + } + }, + "walk" : { + "speed" : 1.3, + "reluctance" : 4.0, + "stairsReluctance" : 1.65, + "boardCost" : 600, + "escalatorReluctance" : 1.5 }, - "bikeParkTime" : "1m", - "bikeParkCost" : 120, - "carDropoffTime" : 120, "waitReluctance" : 1.0, - "walkBoardCost" : 600, - "bikeBoardCost" : 600, "otherThanPreferredRoutesPenalty" : 300, "transferSlack" : 120, "boardSlackForMode" : { @@ -515,8 +540,6 @@ Used to group requests when monitoring OTP. "minBikeParkingDistance" : 300, "debug" : "limit-to-search-window" }, - "carDecelerationSpeed" : 2.9, - "carAccelerationSpeed" : 2.9, "ignoreRealtimeUpdates" : false, "geoidElevation" : false, "maxJourneyDuration" : "36h", @@ -727,6 +750,13 @@ Used to group requests when monitoring OTP. "Authorization" : "A-Token" } }, + { + "type" : "mqtt-gtfs-rt-updater", + "url" : "tcp://pred.rt.hsl.fi", + "topic" : "gtfsrt/v2/fi/hsl/tu", + "feedId" : "HSL", + "fuzzyTripMatching" : true + }, { "type" : "vehicle-positions", "url" : "https://s3.amazonaws.com/kcm-alerts-realtime-prod/vehiclepositions.pb", diff --git a/docs/UpdaterConfig.md b/docs/UpdaterConfig.md index 6c219d07ddf..a819a898240 100644 --- a/docs/UpdaterConfig.md +++ b/docs/UpdaterConfig.md @@ -8,8 +8,8 @@ # Updater configuration -This section covers all options that can be set in the *router-config.json* in the -[updaters](RouterConfiguration.md) section. +This section covers options that can be set in the updaters section of `router-config.json`. +See the parameter summary and examples in the router configuration documentation Real-time data are those that are not added to OTP during the graph build phase but during runtime. @@ -164,6 +164,72 @@ HTTP headers to add to the request. Any header key, value can be inserted. +### Streaming TripUpdates via MQTT + +This updater connects to an MQTT broker and processes TripUpdates in a streaming fashion. This means +that they will be applied individually in near-realtime rather than in batches at a certain interval. + +This system powers the realtime updates in Helsinki and more information can be found +[on Github](https://github.com/HSLdevcom/transitdata). + + + + +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|-----------------------------------------------------------------------|:---------:|----------------------------------------------|:----------:|----------------------|:-----:| +| type = "mqtt-gtfs-rt-updater" | `enum` | The type of the updater. | *Required* | | 1.5 | +| [backwardsDelayPropagationType](#u__6__backwardsDelayPropagationType) | `enum` | How backwards propagation should be handled. | *Optional* | `"required-no-data"` | 2.2 | +| feedId | `string` | The feed id to apply the updates to. | *Required* | | 2.0 | +| fuzzyTripMatching | `boolean` | Whether to match trips fuzzily. | *Optional* | `false` | 2.0 | +| qos | `integer` | QOS level. | *Optional* | `0` | 2.0 | +| topic | `string` | The topic to subscribe to. | *Required* | | 2.0 | +| url | `string` | URL of the MQTT broker. | *Required* | | 2.0 | + + +##### Parameter details + +

backwardsDelayPropagationType

+ +**Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"required-no-data"` +**Path:** /updaters/[6] +**Enum values:** `required-no-data` | `required` | `always` + +How backwards propagation should be handled. + + REQUIRED_NO_DATA: + Default value. Only propagates delays backwards when it is required to ensure that the times + are increasing, and it sets the NO_DATA flag on the stops so these automatically updated times + are not exposed through APIs. + + REQUIRED: + Only propagates delays backwards when it is required to ensure that the times are increasing. + The updated times are exposed through APIs. + + ALWAYS: + Propagates delays backwards on stops with no estimates regardless if it's required or not. + The updated times are exposed through APIs. + + + + +##### Example configuration + +```JSON +// router-config.json +{ + "updaters" : [ + { + "type" : "mqtt-gtfs-rt-updater", + "url" : "tcp://pred.rt.hsl.fi", + "topic" : "gtfsrt/v2/fi/hsl/tu", + "feedId" : "HSL", + "fuzzyTripMatching" : true + } + ] +} +``` + + ### Vehicle Positions @@ -181,24 +247,24 @@ The information is downloaded in a single HTTP request and polled regularly. | frequency | `duration` | How often the positions should be updated. | *Optional* | `"PT1M"` | 2.2 | | fuzzyTripMatching | `boolean` | Whether to match trips fuzzily. | *Optional* | `false` | 2.5 | | url | `uri` | The URL of GTFS-RT protobuf HTTP resource to download the positions from. | *Required* | | 2.2 | -| [features](#u__6__features) | `enum set` | Which features of GTFS RT vehicle positions should be loaded into OTP. | *Optional* | | 2.5 | -| [headers](#u__6__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.3 | +| [features](#u__7__features) | `enum set` | Which features of GTFS RT vehicle positions should be loaded into OTP. | *Optional* | | 2.5 | +| [headers](#u__7__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.3 | ##### Parameter details -

features

+

features

**Since version:** `2.5` ∙ **Type:** `enum set` ∙ **Cardinality:** `Optional` -**Path:** /updaters/[6] +**Path:** /updaters/[7] **Enum values:** `position` | `stop-position` | `occupancy` Which features of GTFS RT vehicle positions should be loaded into OTP. -

headers

+

headers

**Since version:** `2.3` ∙ **Type:** `map of string` ∙ **Cardinality:** `Optional` -**Path:** /updaters/[6] +**Path:** /updaters/[7] HTTP headers to add to the request. Any header key, value can be inserted. diff --git a/docs/apis/GTFS-GraphQL-API.md b/docs/apis/GTFS-GraphQL-API.md index a9937afc6b2..b67b2882ec9 100644 --- a/docs/apis/GTFS-GraphQL-API.md +++ b/docs/apis/GTFS-GraphQL-API.md @@ -6,7 +6,7 @@ used heavily by [digitransit-ui](https://github.com/HSLdevcom/digitransit-ui). [otp-react-redux](https://github.com/opentripplanner/otp-react-redux) has also migrated to this API in 2023. ## URLs - - GraphQL endpoint: [`http://localhost:8080/otp/routers/default/index/graphql`](http://localhost:8080/otp/routers/default/index/graphql) + - GraphQL endpoint: [`http://localhost:8080/otp/gtfs/v1`](http://localhost:8080/otp/gtfs/v1) - HTML schema documentation: [https://docs.opentripplanner.org/api/dev-2.x/graphql-gtfs/](https://docs.opentripplanner.org/api/dev-2.x/graphql-gtfs/) - Built-in visual GraphQL client: [http://localhost:8080/graphiql](http://localhost:8080/graphiql) @@ -22,7 +22,7 @@ A complete example that fetches the list of all stops from OTP is: ``` curl --request POST \ - --url http://localhost:8080/otp/routers/default/index/graphql \ + --url http://localhost:8080/otp/gtfs/v1 \ --header 'Content-Type: application/json' \ --header 'OTPTimeout: 180000' \ --data '{"query":"query stops {\n stops {\n gtfsId\n name\n }\n}\n","operationName":"stops"}' diff --git a/docs/examples/entur/router-config.json b/docs/examples/entur/router-config.json index 0023dab130a..ffdec9ded79 100644 --- a/docs/examples/entur/router-config.json +++ b/docs/examples/entur/router-config.json @@ -1,32 +1,48 @@ { "configVersion" : "{{ Entur CI config build number inserted here }}", "routingDefaults": { - "walkSpeed": 1.3, - "bikeSpeed": 5, - "carSpeed": 40, "numItineraries": 12, "transferPenalty": 0, - "walkReluctance": 4.0, - "bikeReluctance": 5.0, - "bikeWalkingReluctance": 10.0, - "carReluctance": 10.0, - "stairsReluctance": 1.65, "turnReluctance": 1.0, "elevatorBoardTime": 90, "elevatorBoardCost": 90, "elevatorHopTime": 20, "elevatorHopCost": 20, - "vehicleRental": { - "pickupCost": 120, - "dropOffTime": 30, - "dropOffCost": 30 + "bicycle": { + "speed": 5, + "reluctance": 5.0, + "boardCost": 600, + "walk": { + "reluctance": 10.0 + }, + "rental": { + "pickupCost": 120, + "dropOffTime": "30s", + "dropOffCost": 30 + }, + "parking": { + "time": "1m", + "cost": 120 + } + }, + "car": { + "speed": 40, + "reluctance": 4.0, + "decelerationSpeed": 2.9, + "accelerationSpeed": 2.9, + "rental": { + "pickupCost": 120, + "dropOffTime": "30s", + "dropOffCost": 30 + } + }, + "walk": { + "speed": 1.3, + "reluctance": 4.0, + "stairsReluctance": 1.65, + "boardCost": 600 }, - "bikeParkTime": "1m", - "bikeParkCost": 120, - "carDropoffTime": 120, "waitReluctance": 1.0, - "walkBoardCost": 600, - "bikeBoardCost": 600, "otherThanPreferredRoutesPenalty": 300, "transferSlack": 120, // Default slack for any mode is 0 (zero) @@ -41,15 +57,13 @@ }, "accessEgress": { "maxDurationForMode": { - "BIKE_RENTAL": "20m" + "BIKE_RENTAL": "20m" } }, "itineraryFilters" : { "transitGeneralizedCostLimit" : "1h + 2.5 x", "bikeRentalDistanceRatio": 0.3 }, - "carDecelerationSpeed": 2.9, - "carAccelerationSpeed": 2.9, "ignoreRealtimeUpdates": false, "geoidElevation": false, "maxJourneyDuration": "36h", diff --git a/docs/examples/ibi/atlanta/otp-config.json b/docs/examples/ibi/atlanta/otp-config.json index b1c4b530cf2..2269e35d6ec 100644 --- a/docs/examples/ibi/atlanta/otp-config.json +++ b/docs/examples/ibi/atlanta/otp-config.json @@ -2,7 +2,6 @@ "otpFeatures" : { "FlexRouting": true, "GtfsGraphQlApi": true, - "VehicleToStopHeuristics": true, "ParallelRouting": true } } diff --git a/docs/examples/ibi/atlanta/router-config.json b/docs/examples/ibi/atlanta/router-config.json index 5202fe227c1..909c164ec43 100644 --- a/docs/examples/ibi/atlanta/router-config.json +++ b/docs/examples/ibi/atlanta/router-config.json @@ -1,18 +1,28 @@ { "routingDefaults": { - "bikeTriangleSafetyFactor": 0.4, - "bikeTriangleSlopeFactor": 0.3, - "bikeTriangleTimeFactor": 0.3, + "bicycle": { + "triangle": { + "time": 0.3, + "flatness": 0.3, + "safety": 0.4 + }, + "rental": { + "pickupTime": "3m", + "pickupCost": 850 + } + }, + "car": { + "rental": { + "pickupTime": "3m", + "pickupCost": 850 + } + }, "itineraryFilters": { // only show non-transit (ie. walking) when it's at least as good as the transit option "nonTransitGeneralizedCostLimit": "0 + 1.0 x", // add IBI accessibility score between 0 and 1 "accessibilityScore": true }, - "vehicleRental": { - "pickupTime": 180, - "pickupCost": 850 - }, // use stop and trip with unknown wheelchair accessibility during routing "wheelchairAccessibility": { "trip": { diff --git a/docs/examples/ibi/portland/otp-config.json b/docs/examples/ibi/portland/otp-config.json index 3227d4d90ca..2f4b1f586cc 100644 --- a/docs/examples/ibi/portland/otp-config.json +++ b/docs/examples/ibi/portland/otp-config.json @@ -1,6 +1,5 @@ { "otpFeatures" : { - "GtfsGraphQlApi": true, - "VehicleToStopHeuristics": true + "GtfsGraphQlApi": true } } diff --git a/docs/examples/ibi/portland/router-config.json b/docs/examples/ibi/portland/router-config.json index 55a487fcfdc..445738762a1 100644 --- a/docs/examples/ibi/portland/router-config.json +++ b/docs/examples/ibi/portland/router-config.json @@ -5,8 +5,10 @@ "alightSlack": "0s", "transferSlack": 180, "waitReluctance": 0.9, - "walkReluctance": 1.75, - "stairsReluctance": 1.65, + "walk": { + "reluctance": 1.75, + "stairsReluctance": 1.65 + }, "numItineraries": 3, "geoidElevation": true, "streetRoutingTimeout": "7s" diff --git a/docs/examples/skanetrafiken/router-config.json b/docs/examples/skanetrafiken/router-config.json index ddec543e516..d65604aaa00 100644 --- a/docs/examples/skanetrafiken/router-config.json +++ b/docs/examples/skanetrafiken/router-config.json @@ -5,7 +5,9 @@ }, "transferSlack": 180, "waitReluctance": 0.175, - "walkReluctance": 5, + "walk": { + "reluctance": 5 + }, "maxDirectStreetDuration": "3700s" }, "transit": { diff --git a/docs/sandbox/SiriUpdater.md b/docs/sandbox/SiriUpdater.md index 7730df67d12..f6c4c3f999f 100644 --- a/docs/sandbox/SiriUpdater.md +++ b/docs/sandbox/SiriUpdater.md @@ -37,16 +37,16 @@ To enable the SIRI updater you need to add it to the updaters section of the `ro | previewInterval | `duration` | TODO | *Optional* | | 2.0 | | requestorRef | `string` | The requester reference. | *Optional* | | 2.0 | | timeout | `duration` | The HTTP timeout to download the updates. | *Optional* | `"PT15S"` | 2.0 | -| [url](#u__7__url) | `string` | The URL to send the HTTP requests to. | *Required* | | 2.0 | -| [headers](#u__7__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.3 | +| [url](#u__8__url) | `string` | The URL to send the HTTP requests to. | *Required* | | 2.0 | +| [headers](#u__8__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.3 | ##### Parameter details -

url

+

url

**Since version:** `2.0` ∙ **Type:** `string` ∙ **Cardinality:** `Required` -**Path:** /updaters/[7] +**Path:** /updaters/[8] The URL to send the HTTP requests to. @@ -58,10 +58,10 @@ renamed by the loader when processed: -

headers

+

headers

**Since version:** `2.3` ∙ **Type:** `map of string` ∙ **Cardinality:** `Optional` -**Path:** /updaters/[7] +**Path:** /updaters/[8] HTTP headers to add to the request. Any header key, value can be inserted. @@ -97,21 +97,21 @@ HTTP headers to add to the request. Any header key, value can be inserted. |---------------------------------|:---------------:|--------------------------------------------------------------------------------------------------------|:----------:|---------------|:-----:| | type = "siri-sx-updater" | `enum` | The type of the updater. | *Required* | | 1.5 | | blockReadinessUntilInitialized | `boolean` | Whether catching up with the updates should block the readiness check from returning a 'ready' result. | *Optional* | `false` | 2.0 | -| [earlyStart](#u__8__earlyStart) | `duration` | This value is subtracted from the actual validity defined in the message. | *Optional* | `"PT0S"` | 2.0 | +| [earlyStart](#u__9__earlyStart) | `duration` | This value is subtracted from the actual validity defined in the message. | *Optional* | `"PT0S"` | 2.0 | | feedId | `string` | The ID of the feed to apply the updates to. | *Required* | | 2.0 | | frequency | `duration` | How often the updates should be retrieved. | *Optional* | `"PT1M"` | 2.0 | | requestorRef | `string` | The requester reference. | *Optional* | | 2.0 | | timeout | `duration` | The HTTP timeout to download the updates. | *Optional* | `"PT15S"` | 2.0 | -| [url](#u__8__url) | `string` | The URL to send the HTTP requests to. Supports http/https and file protocol. | *Required* | | 2.0 | -| [headers](#u__8__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.3 | +| [url](#u__9__url) | `string` | The URL to send the HTTP requests to. Supports http/https and file protocol. | *Required* | | 2.0 | +| [headers](#u__9__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.3 | ##### Parameter details -

earlyStart

+

earlyStart

**Since version:** `2.0` ∙ **Type:** `duration` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"PT0S"` -**Path:** /updaters/[8] +**Path:** /updaters/[9] This value is subtracted from the actual validity defined in the message. @@ -119,10 +119,10 @@ Normally the planned departure time is used, so setting this to 10s will cause t SX-message to be included in trip-results 10 seconds before the the planned departure time. -

url

+

url

**Since version:** `2.0` ∙ **Type:** `string` ∙ **Cardinality:** `Required` -**Path:** /updaters/[8] +**Path:** /updaters/[9] The URL to send the HTTP requests to. Supports http/https and file protocol. @@ -135,10 +135,10 @@ renamed by the loader when processed: -

headers

+

headers

**Since version:** `2.3` ∙ **Type:** `map of string` ∙ **Cardinality:** `Optional` -**Path:** /updaters/[8] +**Path:** /updaters/[9] HTTP headers to add to the request. Any header key, value can be inserted. diff --git a/magidoc.mjs b/magidoc.mjs index 44997e3fc9d..a02976f4bcc 100644 --- a/magidoc.mjs +++ b/magidoc.mjs @@ -17,7 +17,7 @@ export default { This is the static documentation of the OTP GraphQL GTFS API. The GraphQL endpoint of your instance, which you should point your tooling to, is -\`http://localhost:8080/otp/routers/default/index/graphql\` +\`http://localhost:8080/otp/gtfs/v1\` Please also check out the interactive API explorer built into every instance and available at http://localhost:8080/graphiql diff --git a/pom.xml b/pom.xml index abd70a60749..34f4365b580 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 136 + 137 30.1 2.50 @@ -78,7 +78,7 @@ When running `mvn jacoco:prepare-agent test` argLine is replaced with the one activating the agent. --> - 0.21 + 0.22 write @@ -688,7 +688,7 @@ org.entur.gbfs gbfs-java-model - 3.0.16 + 3.0.20 @@ -713,7 +713,7 @@ org.mockito mockito-core - 5.8.0 + 5.9.0 test diff --git a/src/client/debug-client-preview/index.html b/src/client/debug-client-preview/index.html index 5f77418dbe6..e9443e240c4 100644 --- a/src/client/debug-client-preview/index.html +++ b/src/client/debug-client-preview/index.html @@ -5,8 +5,8 @@ OTP Debug Client - - + +
diff --git a/src/client/graphiql/index.html b/src/client/graphiql/index.html index 44016557a92..d96f736b287 100644 --- a/src/client/graphiql/index.html +++ b/src/client/graphiql/index.html @@ -152,7 +152,7 @@ let apiFlavor = parameters.flavor || "gtfs"; let urls = { - gtfs: '/otp/routers/default/index/graphql', + gtfs: '/otp/gtfs/v1', transmodel: '/otp/routers/default/transmodel/index/graphql' } diff --git a/src/ext-test/java/org/opentripplanner/ext/accessibilityscore/AccessibilityScoreFilterTest.java b/src/ext-test/java/org/opentripplanner/ext/accessibilityscore/AccessibilityScoreFilterTest.java deleted file mode 100644 index 3239a7a73df..00000000000 --- a/src/ext-test/java/org/opentripplanner/ext/accessibilityscore/AccessibilityScoreFilterTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.opentripplanner.ext.accessibilityscore; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; - -import java.util.List; -import org.junit.jupiter.api.Test; -import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.model.plan.Place; -import org.opentripplanner.model.plan.PlanTestConstants; -import org.opentripplanner.routing.api.request.preference.WheelchairPreferences; - -public class AccessibilityScoreFilterTest implements PlanTestConstants { - - @Test - public void shouldAddAccessibilityScore() { - final int ID = 1; - - Itinerary i1 = newItinerary(A, 0) - .walk(20, Place.forStop(TEST_MODEL.stop("1:stop", 1d, 1d).build())) - .bus(ID, 0, 50, B) - .bus(ID, 52, 100, C) - .build(); - Itinerary i2 = newItinerary(A, 0) - .walk(20, Place.forStop(TEST_MODEL.stop("1:stop", 1d, 1d).build())) - .bus(ID, 0, 50, B) - .bus(ID, 52, 100, C) - .build(); - Itinerary i3 = newItinerary(A, 0) - .walk(20, Place.forStop(TEST_MODEL.stop("1:stop", 1d, 1d).build())) - .bus(ID, 0, 50, B) - .bus(ID, 52, 100, C) - .build(); - - List input = List.of(i1, i2, i3); - - input.forEach(i -> assertNull(i.getAccessibilityScore())); - - var filter = new AccessibilityScoreFilter(WheelchairPreferences.DEFAULT.maxSlope()); - var filtered = filter.filter(input); - - filtered.forEach(i -> { - assertNotNull(i.getAccessibilityScore()); - i - .getLegs() - .forEach(l -> { - assertNotNull(l.accessibilityScore()); - }); - }); - } -} diff --git a/src/ext-test/java/org/opentripplanner/ext/accessibilityscore/DecorateWithAccessibilityScoreTest.java b/src/ext-test/java/org/opentripplanner/ext/accessibilityscore/DecorateWithAccessibilityScoreTest.java new file mode 100644 index 00000000000..d70b9f32f62 --- /dev/null +++ b/src/ext-test/java/org/opentripplanner/ext/accessibilityscore/DecorateWithAccessibilityScoreTest.java @@ -0,0 +1,64 @@ +package org.opentripplanner.ext.accessibilityscore; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; + +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.Place; +import org.opentripplanner.model.plan.PlanTestConstants; +import org.opentripplanner.routing.api.request.preference.WheelchairPreferences; + +public class DecorateWithAccessibilityScoreTest implements PlanTestConstants { + + private static final int ID = 1; + + static List accessibilityScoreTestCase() { + return List.of( + Arguments.of( + newItinerary(A, 0) + .walk(20, Place.forStop(TEST_MODEL.stop("1:stop", 1d, 1d).build())) + .bus(ID, 0, 50, B) + .bus(ID, 52, 100, C) + .build(), + 0.5f + ), + Arguments.of( + newItinerary(A, 0) + .walk(20, Place.forStop(TEST_MODEL.stop("1:stop", 1d, 1d).build())) + .bus(ID, 0, 50, B) + .bus(ID, 52, 100, C) + .build(), + 0.5f + ), + Arguments.of( + newItinerary(A, 0) + .walk(20, Place.forStop(TEST_MODEL.stop("1:stop", 1d, 1d).build())) + .bus(ID, 0, 50, B) + .bus(ID, 52, 100, C) + .build(), + 0.5f + ) + ); + } + + @ParameterizedTest + @MethodSource("accessibilityScoreTestCase") + public void accessibilityScoreTest(Itinerary itinerary, float expectedAccessibilityScore) { + var filter = new DecorateWithAccessibilityScore(WheelchairPreferences.DEFAULT.maxSlope()); + + filter.decorate(itinerary); + + assertEquals(expectedAccessibilityScore, itinerary.getAccessibilityScore()); + + itinerary + .getLegs() + .forEach(l -> { + assertNotNull(l.accessibilityScore()); + }); + } +} diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java index 82058567c38..ff8a65ab494 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java @@ -31,7 +31,7 @@ class EmissionsTest { private static DefaultEmissionsService eService; - private static EmissionsFilter emissionsFilter; + private static DecorateWithEmission decorateWithEmission; static final ZonedDateTime TIME = OffsetDateTime .parse("2023-07-20T17:49:06+03:00") @@ -58,49 +58,42 @@ static void SetUp() { emissions.put(new FeedScopedId("F", "2"), 0.0); EmissionsDataModel emissionsDataModel = new EmissionsDataModel(emissions, 0.131); eService = new DefaultEmissionsService(emissionsDataModel); - emissionsFilter = new EmissionsFilter(eService); + decorateWithEmission = new DecorateWithEmission(eService); } @Test void testGetEmissionsForItinerary() { Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITH_EMISSIONS))); - assertEquals( - new Grams(2223.902), - emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson().getCo2() - ); + decorateWithEmission.decorate(i); + assertEquals(new Grams(2223.902), i.getEmissionsPerPerson().getCo2()); } @Test void testGetEmissionsForCarRoute() { Itinerary i = new Itinerary(List.of(STREET_LEG)); - assertEquals( - new Grams(28.0864), - emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson().getCo2() - ); + decorateWithEmission.decorate(i); + assertEquals(new Grams(28.0864), i.getEmissionsPerPerson().getCo2()); } @Test void testNoEmissionsForFeedWithoutEmissionsConfigured() { Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITHOUT_EMISSIONS_CONFIGURED))); - assertNull(emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson()); + decorateWithEmission.decorate(i); + assertNull(i.getEmissionsPerPerson()); } @Test void testZeroEmissionsForItineraryWithZeroEmissions() { Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITH_ZERO_EMISSIONS))); - assertEquals( - new Grams(0.0), - emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson().getCo2() - ); + decorateWithEmission.decorate(i); + assertEquals(new Grams(0.0), i.getEmissionsPerPerson().getCo2()); } @Test void testGetEmissionsForCombinedRoute() { Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITH_EMISSIONS), STREET_LEG)); - assertEquals( - new Grams(2251.9884), - emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson().getCo2() - ); + decorateWithEmission.decorate(i); + assertEquals(new Grams(2251.9884), i.getEmissionsPerPerson().getCo2()); } @Test @@ -108,8 +101,9 @@ void testNoEmissionsForCombinedRouteWithoutTransitEmissions() { Itinerary i = new Itinerary( List.of(createTransitLeg(ROUTE_WITHOUT_EMISSIONS_CONFIGURED), STREET_LEG) ); - var emissionsResult = emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson() != null - ? emissionsFilter.filter(List.of(i)).get(0).getEmissionsPerPerson().getCo2() + decorateWithEmission.decorate(i); + var emissionsResult = i.getEmissionsPerPerson() != null + ? i.getEmissionsPerPerson().getCo2() : null; assertNull(emissionsResult); } diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/FaresFilterTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/FaresFilterTest.java index 7fe315cb97b..90468b091f9 100644 --- a/src/ext-test/java/org/opentripplanner/ext/fares/FaresFilterTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/fares/FaresFilterTest.java @@ -31,9 +31,7 @@ void shouldAddFare() { .bus(ID, 52, 100, C) .build(); - List input = List.of(i1, i1, i1); - - input.forEach(i -> assertEquals(ItineraryFares.empty(), i.getFares())); + assertEquals(ItineraryFares.empty(), i1.getFares()); var fares = new ItineraryFares(); fares.addFare(FareType.regular, Money.euros(2.80f)); @@ -42,12 +40,13 @@ void shouldAddFare() { var fp = new FareProduct(id("fp"), "fare product", Money.euros(10.00f), null, null, null); fares.addFareProduct(leg, fp); - var filter = new FaresFilter((FareService) itinerary -> fares); - var filtered = filter.filter(input); + var filter = new DecorateWithFare((FareService) itinerary -> fares); + + filter.decorate(i1); - filtered.forEach(i -> assertEquals(fares, i.getFares())); + assertEquals(fares, i1.getFares()); - var busLeg = filtered.get(0).getTransitLeg(1); + var busLeg = i1.getTransitLeg(1); assertEquals( List.of(new FareProductUse("c1a04702-1fb6-32d4-ba02-483bf68111ed", fp)), diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java index 3fb77a29adf..3523a6e5c5e 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java @@ -18,7 +18,7 @@ import org.opentripplanner.TestOtpModel; import org.opentripplanner.TestServerContext; import org.opentripplanner._support.time.ZoneIds; -import org.opentripplanner.ext.fares.FaresFilter; +import org.opentripplanner.ext.fares.DecorateWithFare; import org.opentripplanner.ext.flex.FlexRouter; import org.opentripplanner.ext.flex.FlexTest; import org.opentripplanner.framework.application.OTPFeature; @@ -144,9 +144,9 @@ void calculateDirectFare() { List.of(to) ); - var filter = new FaresFilter(graph.getFareService()); + var filter = new DecorateWithFare(graph.getFareService()); - var itineraries = filter.filter(router.createFlexOnlyItineraries().stream().toList()); + var itineraries = router.createFlexOnlyItineraries().stream().peek(filter::decorate).toList(); var itinerary = itineraries.iterator().next(); assertFalse(itinerary.getFares().getFareTypes().isEmpty()); diff --git a/src/ext-test/java/org/opentripplanner/ext/restapi/parameter/ApiRequestModeTest.java b/src/ext-test/java/org/opentripplanner/ext/restapi/parameter/ApiRequestModeTest.java index 13cfc43c59c..29cd6b5715c 100644 --- a/src/ext-test/java/org/opentripplanner/ext/restapi/parameter/ApiRequestModeTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/restapi/parameter/ApiRequestModeTest.java @@ -4,7 +4,6 @@ import static org.opentripplanner.transit.model.basic.TransitMode.CARPOOL; import java.util.List; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.opentripplanner.api.parameter.ApiRequestMode; diff --git a/src/ext-test/java/org/opentripplanner/ext/ridehailing/RideHailingFilterTest.java b/src/ext-test/java/org/opentripplanner/ext/ridehailing/DecorateWithRideHailingTest.java similarity index 87% rename from src/ext-test/java/org/opentripplanner/ext/ridehailing/RideHailingFilterTest.java rename to src/ext-test/java/org/opentripplanner/ext/ridehailing/DecorateWithRideHailingTest.java index ec7932b1d3d..58eb6315dd9 100644 --- a/src/ext-test/java/org/opentripplanner/ext/ridehailing/RideHailingFilterTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/ridehailing/DecorateWithRideHailingTest.java @@ -16,7 +16,7 @@ import org.opentripplanner.model.plan.TestItineraryBuilder; import org.opentripplanner.transit.model.basic.Money; -class RideHailingFilterTest implements PlanTestConstants { +class DecorateWithRideHailingTest implements PlanTestConstants { public static final RideEstimate RIDE_ESTIMATE = new RideEstimate( UBER, @@ -39,7 +39,7 @@ class RideHailingFilterTest implements PlanTestConstants { @Test void noServices() { - var filter = new RideHailingFilter(List.of(), false); + var filter = new DecorateWithRideHailing(List.of(), false); var filtered = filter.filter(List.of(i)); @@ -48,7 +48,7 @@ void noServices() { @Test void addRideHailingInformation() { - var filter = new RideHailingFilter(List.of(mockService), false); + var filter = new DecorateWithRideHailing(List.of(mockService), false); var filtered = filter.filter(List.of(i)); @@ -63,7 +63,7 @@ void addRideHailingInformation() { @Test void failingService() { - var filter = new RideHailingFilter(List.of(failingService), false); + var filter = new DecorateWithRideHailing(List.of(failingService), false); var filtered = filter.filter(List.of(i)); diff --git a/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/ConsolidatedStopNameFilterTest.java b/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNamesTest.java similarity index 84% rename from src/ext-test/java/org/opentripplanner/ext/stopconsolidation/ConsolidatedStopNameFilterTest.java rename to src/ext-test/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNamesTest.java index d88e8a5879e..38af8ea7187 100644 --- a/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/ConsolidatedStopNameFilterTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNamesTest.java @@ -1,7 +1,6 @@ package org.opentripplanner.ext.stopconsolidation; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.opentripplanner.ext.stopconsolidation.TestStopConsolidationModel.STOP_C; import static org.opentripplanner.ext.stopconsolidation.TestStopConsolidationModel.STOP_D; import static org.opentripplanner.model.plan.PlanTestConstants.T11_05; @@ -16,7 +15,7 @@ import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.model.plan.TestItineraryBuilder; -class ConsolidatedStopNameFilterTest { +class DecorateConsolidatedStopNamesTest { @Test void changeNames() { @@ -27,7 +26,7 @@ void changeNames() { repo.addGroups(groups); var service = new DefaultStopConsolidationService(repo, transitModel); - var filter = new ConsolidatedStopNameFilter(service); + var filter = new DecorateConsolidatedStopNames(service); var itinerary = TestItineraryBuilder .newItinerary(Place.forStop(STOP_C)) @@ -36,10 +35,9 @@ void changeNames() { .bus(1, T11_05, T11_12, PlanTestConstants.F) .build(); - var filtered = filter.filter(List.of(itinerary)); - assertFalse(filtered.isEmpty()); + filter.decorate(itinerary); - var updatedLeg = filtered.get(0).getLegs().get(0); + var updatedLeg = itinerary.getLegs().get(0); assertEquals(STOP_D.getName(), updatedLeg.getFrom().name); assertEquals(STOP_D.getName(), updatedLeg.getTo().name); } diff --git a/src/ext/java/org/opentripplanner/ext/accessibilityscore/AccessibilityScoreFilter.java b/src/ext/java/org/opentripplanner/ext/accessibilityscore/DecorateWithAccessibilityScore.java similarity index 90% rename from src/ext/java/org/opentripplanner/ext/accessibilityscore/AccessibilityScoreFilter.java rename to src/ext/java/org/opentripplanner/ext/accessibilityscore/DecorateWithAccessibilityScore.java index 46862b020d5..51a138b9f9c 100644 --- a/src/ext/java/org/opentripplanner/ext/accessibilityscore/AccessibilityScoreFilter.java +++ b/src/ext/java/org/opentripplanner/ext/accessibilityscore/DecorateWithAccessibilityScore.java @@ -8,7 +8,7 @@ import org.opentripplanner.model.plan.ScheduledTransitLeg; import org.opentripplanner.model.plan.StreetLeg; import org.opentripplanner.model.plan.WalkStep; -import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilter; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryDecorator; import org.opentripplanner.street.model.edge.StreetEdge; import org.opentripplanner.street.model.edge.WheelchairTraversalInformation; import org.opentripplanner.transit.model.basic.Accessibility; @@ -25,7 +25,8 @@ * calculating them on the backend makes life a little easier and changes are automatically applied * to all frontends. */ -public record AccessibilityScoreFilter(double wheelchairMaxSlope) implements ItineraryListFilter { +public record DecorateWithAccessibilityScore(double wheelchairMaxSlope) + implements ItineraryDecorator { public static Float compute(List legs) { return legs .stream() @@ -37,20 +38,20 @@ public static Float compute(List legs) { public static float compute(ScheduledTransitLeg leg) { var fromStop = leg.getFrom().stop.getWheelchairAccessibility(); - var toStop = leg.getFrom().stop.getWheelchairAccessibility(); + var toStop = leg.getTo().stop.getWheelchairAccessibility(); var trip = leg.getTripWheelchairAccessibility(); var values = List.of(trip, fromStop, toStop); var sum = (float) values .stream() - .mapToDouble(AccessibilityScoreFilter::accessibilityScore) + .mapToDouble(DecorateWithAccessibilityScore::accessibilityScore) .sum(); return sum / values.size(); } @Override - public List filter(List itineraries) { - return itineraries.stream().map(this::addAccessibilityScore).toList(); + public void decorate(Itinerary itinerary) { + addAccessibilityScore(itinerary); } private static double accessibilityScore(Accessibility wheelchair) { diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java b/src/ext/java/org/opentripplanner/ext/emissions/DecorateWithEmission.java similarity index 59% rename from src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java rename to src/ext/java/org/opentripplanner/ext/emissions/DecorateWithEmission.java index 19a27d248af..1f6e79fc4df 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsFilter.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/DecorateWithEmission.java @@ -10,7 +10,7 @@ import org.opentripplanner.model.plan.ScheduledTransitLeg; import org.opentripplanner.model.plan.StreetLeg; import org.opentripplanner.model.plan.TransitLeg; -import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilter; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryDecorator; import org.opentripplanner.street.search.TraverseMode; import org.opentripplanner.transit.model.framework.FeedScopedId; @@ -19,42 +19,40 @@ * @param emissionsService */ @Sandbox -public record EmissionsFilter(EmissionsService emissionsService) implements ItineraryListFilter { +public record DecorateWithEmission(EmissionsService emissionsService) + implements ItineraryDecorator { @Override - public List filter(List itineraries) { - for (Itinerary itinerary : itineraries) { - List transitLegs = itinerary - .getLegs() - .stream() - .filter(l -> l instanceof ScheduledTransitLeg || l instanceof FlexibleTransitLeg) - .map(TransitLeg.class::cast) - .toList(); + public void decorate(Itinerary itinerary) { + List transitLegs = itinerary + .getLegs() + .stream() + .filter(l -> l instanceof ScheduledTransitLeg || l instanceof FlexibleTransitLeg) + .map(TransitLeg.class::cast) + .toList(); - Optional co2ForTransit = calculateCo2EmissionsForTransit(transitLegs); + Optional co2ForTransit = calculateCo2EmissionsForTransit(transitLegs); - if (!transitLegs.isEmpty() && co2ForTransit.isEmpty()) { - continue; - } + if (!transitLegs.isEmpty() && co2ForTransit.isEmpty()) { + return; + } - List carLegs = itinerary - .getLegs() - .stream() - .filter(l -> l instanceof StreetLeg) - .map(StreetLeg.class::cast) - .filter(leg -> leg.getMode() == TraverseMode.CAR) - .toList(); + List carLegs = itinerary + .getLegs() + .stream() + .filter(l -> l instanceof StreetLeg) + .map(StreetLeg.class::cast) + .filter(leg -> leg.getMode() == TraverseMode.CAR) + .toList(); - Optional co2ForCar = calculateCo2EmissionsForCar(carLegs); + Optional co2ForCar = calculateCo2EmissionsForCar(carLegs); - if (co2ForTransit.isPresent() && co2ForCar.isPresent()) { - itinerary.setEmissionsPerPerson(new Emissions(co2ForTransit.get().plus(co2ForCar.get()))); - } else if (co2ForTransit.isPresent()) { - itinerary.setEmissionsPerPerson(new Emissions(co2ForTransit.get())); - } else if (co2ForCar.isPresent()) { - itinerary.setEmissionsPerPerson(new Emissions(co2ForCar.get())); - } + if (co2ForTransit.isPresent() && co2ForCar.isPresent()) { + itinerary.setEmissionsPerPerson(new Emissions(co2ForTransit.get().plus(co2ForCar.get()))); + } else if (co2ForTransit.isPresent()) { + itinerary.setEmissionsPerPerson(new Emissions(co2ForTransit.get())); + } else if (co2ForCar.isPresent()) { + itinerary.setEmissionsPerPerson(new Emissions(co2ForCar.get())); } - return itineraries; } private Optional calculateCo2EmissionsForTransit(List transitLegs) { diff --git a/src/ext/java/org/opentripplanner/ext/fares/DecorateWithFare.java b/src/ext/java/org/opentripplanner/ext/fares/DecorateWithFare.java new file mode 100644 index 00000000000..a47392a3fbf --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/fares/DecorateWithFare.java @@ -0,0 +1,21 @@ +package org.opentripplanner.ext.fares; + +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryDecorator; +import org.opentripplanner.routing.fares.FareService; + +/** + * Computes the fares of an itinerary and adds them. + *

+ * TODO: Convert to a class - exposing a service in a DTO is a risk. + */ +public record DecorateWithFare(FareService fareService) implements ItineraryDecorator { + @Override + public void decorate(Itinerary itinerary) { + var fare = fareService.calculateFares(itinerary); + if (fare != null) { + itinerary.setFare(fare); + FaresToItineraryMapper.addFaresToLegs(fare, itinerary); + } + } +} diff --git a/src/ext/java/org/opentripplanner/ext/fares/FaresFilter.java b/src/ext/java/org/opentripplanner/ext/fares/FaresFilter.java deleted file mode 100644 index 8d344db8eb2..00000000000 --- a/src/ext/java/org/opentripplanner/ext/fares/FaresFilter.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.opentripplanner.ext.fares; - -import java.util.List; -import java.util.Objects; -import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilter; -import org.opentripplanner.routing.fares.FareService; - -/** - * Computes the fares of an itinerary and adds them. - */ -public record FaresFilter(FareService fareService) implements ItineraryListFilter { - @Override - public List filter(List itineraries) { - return itineraries - .stream() - .peek(i -> { - var fare = fareService.calculateFares(i); - if (Objects.nonNull(fare)) { - i.setFare(fare); - FaresToItineraryMapper.addFaresToLegs(fare, i); - } - }) - .toList(); - } -} diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/InteractiveOtpMain.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/InteractiveOtpMain.java index 1f9227b3be6..43061ee2b62 100644 --- a/src/ext/java/org/opentripplanner/ext/interactivelauncher/InteractiveOtpMain.java +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/InteractiveOtpMain.java @@ -1,19 +1,20 @@ package org.opentripplanner.ext.interactivelauncher; -import static org.opentripplanner.ext.interactivelauncher.DebugLoggingSupport.configureDebugLogging; - -import org.opentripplanner.ext.interactivelauncher.views.MainView; +import org.opentripplanner.ext.interactivelauncher.configuration.InteractiveLauncherModule; +import org.opentripplanner.ext.interactivelauncher.debug.OtpDebugController; +import org.opentripplanner.ext.interactivelauncher.startup.MainView; import org.opentripplanner.standalone.OTPMain; /** - * This class provide a main method to start a GUI which can start OTPMain. + * This class provides a main method to start a GUI which can start OTPMain. *

- * The UI allow the user to select a OTP configuration data set. The list of data location is - * created by searching the a root data source directory. + * The UI allows the user to select the OTP configuration dataset. The list of data locations is + * created by searching the root data source directory. *

- * The user then select what he/she want OTP to do. The settings are stored in the - * .interactive_otp_main.json file in the folder InteractiveOtpMain is started. The - * settings from the last run is loaded next time InteractiveOtpMain is started. + * The user then selects what he/she wants OTP to do. + * The settings are stored in the + * .interactive_otp_main.json file in the folder InteractiveOtpMain is started. + * The settings from the last run are loaded the next time InteractiveOtpMain is started. */ public class InteractiveOtpMain { @@ -25,16 +26,19 @@ public static void main(String[] args) { private void run() { this.model = Model.load(); - MainView frame = new MainView(new Thread(this::startOtp)::start, model); + MainView frame = new MainView(new Thread(this::startOtp)::start, model.getStartupModel()); frame.start(); } private void startOtp() { - model.save(); - - configureDebugLogging(model.getDebugLogging()); + startDebugControllerAndSetupRequestInterceptor(); System.out.println("Start OTP: " + model + "\n"); - OTPMain.main(model.asOtpArgs()); + OTPMain.main(model.getStartupModel().asOtpArgs()); + } + + private void startDebugControllerAndSetupRequestInterceptor() { + new OtpDebugController(model).start(); + InteractiveLauncherModule.setRequestInterceptor(model.getRaptorDebugModel()); } } diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/Model.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/Model.java index ee2dde3e684..59682082b90 100644 --- a/src/ext/java/org/opentripplanner/ext/interactivelauncher/Model.java +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/Model.java @@ -1,245 +1,81 @@ package org.opentripplanner.ext.interactivelauncher; -import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import java.io.File; import java.io.IOException; import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.function.Consumer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.opentripplanner.ext.interactivelauncher.debug.logging.LogModel; +import org.opentripplanner.ext.interactivelauncher.debug.raptor.RaptorDebugModel; +import org.opentripplanner.ext.interactivelauncher.startup.StartupModel; public class Model implements Serializable { - private static final Logger LOG = LoggerFactory.getLogger(Model.class); - private static final File MODEL_FILE = new File("interactive_otp_main.json"); - private final Map debugLogging = new HashMap<>(); - - @JsonIgnore - private transient Consumer commandLineChange; + private StartupModel startupModel; + private LogModel logModel; + private RaptorDebugModel raptorDebugModel; - private String rootDirectory = null; - private String dataSource = null; - private boolean buildStreet = false; - private boolean buildTransit = true; - private boolean saveGraph = false; - private boolean serveGraph = true; - private boolean visualizer = false; - - public Model() { - setupListOfDebugLoggers(); - } + public Model() {} public static Model load() { - return MODEL_FILE.exists() ? readFromFile() : new Model(); - } - - public void subscribeCmdLineUpdates(Consumer commandLineChange) { - this.commandLineChange = commandLineChange; - } - - @SuppressWarnings("AccessOfSystemProperties") - public String getRootDirectory() { - return rootDirectory == null ? System.getProperty("user.dir") : rootDirectory; - } - - public void setRootDirectory(String rootDirectory) { - // If the persisted JSON do not contain the rootDirectory, then avoid setting it - if (rootDirectory != null) { - this.rootDirectory = rootDirectory; - } - notifyChangeListener(); - } - - public String getDataSource() { - return dataSource; - } - - public void setDataSource(String dataSource) { - this.dataSource = dataSource; - notifyChangeListener(); - } - - @JsonIgnore - public List getDataSourceOptions() { - List dataSourceOptions = new ArrayList<>(); - File rootDir = new File(getRootDirectory()); - List dirs = SearchForOtpConfig.search(rootDir); - // Add 1 char for the path-separator-character - int length = rootDir.getAbsolutePath().length() + 1; - - for (File dir : dirs) { - var path = dir.getAbsolutePath(); - if (path.length() <= length) { - LOG.warn( - "The root directory contains a config file, choose " + - "the parent directory or delete the config file." - ); - continue; - } - dataSourceOptions.add(path.substring(length)); - } - return dataSourceOptions; - } - - public boolean isBuildStreet() { - return buildStreet; - } - - public void setBuildStreet(boolean buildStreet) { - this.buildStreet = buildStreet; - notifyChangeListener(); - } - - public boolean isBuildTransit() { - return buildTransit; - } - - public void setBuildTransit(boolean buildTransit) { - this.buildTransit = buildTransit; - notifyChangeListener(); - } - - public boolean isSaveGraph() { - return saveGraph; - } - - public void setSaveGraph(boolean saveGraph) { - this.saveGraph = saveGraph; - notifyChangeListener(); - } - - public boolean isServeGraph() { - return serveGraph; - } - - public void setServeGraph(boolean serveGraph) { - this.serveGraph = serveGraph; - notifyChangeListener(); - } - - public boolean isVisualizer() { - return visualizer; + return MODEL_FILE.exists() ? readFromFile() : createNew(); } - public void setVisualizer(boolean visualizer) { - this.visualizer = visualizer; - notifyChangeListener(); + public StartupModel getStartupModel() { + return startupModel; } - public Map getDebugLogging() { - return debugLogging; + public LogModel getLogModel() { + return logModel; } - public void setDebugLogging(Map map) { - for (Entry e : map.entrySet()) { - // Only keep entries that exist in the log config - if (debugLogging.containsKey(e.getKey())) { - debugLogging.put(e.getKey(), e.getValue()); - } - } - } - - @Override - public String toString() { - return ( - "(" + - "data-source-dir: " + - getDataSourceDirectory() + - (buildStreet ? ", buildStreet" : "") + - (buildTransit ? ", buildTransit" : "") + - (saveGraph ? ", saveGraph" : "") + - (serveGraph ? ", serveGraph" : "") + - (visualizer ? ", visualizer" : "") + - ')' - ); - } - - public String toCliString() { - return String.join(" ", asOtpArgs()); - } - - public void save() { - try { - new ObjectMapper().writeValue(MODEL_FILE, this); - } catch (IOException e) { - throw new RuntimeException(e.getMessage(), e); - } - } - - @JsonIgnore - String getDataSourceDirectory() { - if (dataSource == null) { - return "DATA_SOURCE_NOT_SET"; - } - return rootDirectory + File.separatorChar + dataSource; + public RaptorDebugModel getRaptorDebugModel() { + return raptorDebugModel; } - String[] asOtpArgs() { - List args = new ArrayList<>(); - - if (buildAll()) { - args.add("--build"); - } else if (buildStreet) { - args.add("--buildStreet"); - } else if (buildTransit) { - args.add("--loadStreet"); - } else { - args.add("--load"); - } - - if (saveGraph && (buildTransit || buildStreet)) { - args.add("--save"); - } - if (serveGraph && !buildStreetOnly()) { - args.add("--serve"); - } - if (serveGraph && !buildStreetOnly() && visualizer) { - args.add("--visualize"); - } - - args.add(getDataSourceDirectory()); - - return args.toArray(new String[0]); + private static Model createNew() { + return new Model().initSubModels(); } private static Model readFromFile() { try { - return new ObjectMapper().readValue(MODEL_FILE, Model.class); + var mapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return mapper.readValue(MODEL_FILE, Model.class).initSubModels(); } catch (IOException e) { System.err.println( "Unable to read the InteractiveOtpMain state cache. If the model changed this " + "is expected, and it will work next time. Cause: " + e.getMessage() ); - return new Model(); + return createNew(); } } - private void notifyChangeListener() { - if (commandLineChange != null) { - commandLineChange.accept(toCliString()); + private void save() { + try { + var mapper = new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true); + mapper.writeValue(MODEL_FILE, this); + } catch (IOException e) { + throw new RuntimeException(e.getMessage(), e); } } - private boolean buildAll() { - return buildStreet && buildTransit; - } - - private boolean buildStreetOnly() { - return buildStreet && !buildTransit; - } - - private void setupListOfDebugLoggers() { - for (String log : DebugLoggingSupport.getLogs()) { - debugLogging.put(log, Boolean.FALSE); + private Model initSubModels() { + if (startupModel == null) { + startupModel = new StartupModel(); + } + if (logModel == null) { + logModel = LogModel.createFromConfig(); + } + if (raptorDebugModel == null) { + raptorDebugModel = new RaptorDebugModel(); } + logModel.init(this::save); + raptorDebugModel.init(this::save); + return this; } } diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/SetupResult.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/SetupResult.java deleted file mode 100644 index a6ecf5e7229..00000000000 --- a/src/ext/java/org/opentripplanner/ext/interactivelauncher/SetupResult.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.opentripplanner.ext.interactivelauncher; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -public class SetupResult { - - private final File configDataDir; - private final boolean buildStreet; - private final boolean buildTransit; - private final boolean saveGraph; - private final boolean serveGraph; - - public SetupResult( - File configDataDir, - boolean buildStreet, - boolean buildTransit, - boolean saveGraph, - boolean serveGraph - ) { - this.configDataDir = configDataDir; - this.buildStreet = buildStreet; - this.buildTransit = buildTransit; - this.saveGraph = saveGraph; - this.serveGraph = serveGraph; - } - - @Override - public String toString() { - return ( - "SetupResult{" + - "configDataDir=" + - configDataDir.getAbsolutePath() + - (buildStreet ? ", buildStreet" : "") + - (buildTransit ? ", buildTransit" : "") + - (saveGraph ? ", saveGraph" : "") + - (serveGraph ? ", serveGraph" : "") + - '}' - ); - } - - public String toCliString() { - return String.join(" ", asOtpArgs()); - } - - File configDataDir() { - return configDataDir; - } - - boolean buildStreet() { - return buildStreet; - } - - boolean buildTransit() { - return buildTransit; - } - - boolean buildAll() { - return buildStreet && buildTransit; - } - - boolean buildStreetOnly() { - return buildStreet && !buildTransit; - } - - boolean saveGraph() { - return saveGraph; - } - - boolean serveGraph() { - return serveGraph; - } - - String[] asOtpArgs() { - List args = new ArrayList<>(); - - if (buildAll()) { - args.add("--build"); - } else if (buildStreet) { - args.add("--buildStreet"); - } else if (buildTransit) { - args.add("--loadStreet"); - } else { - args.add("--load"); - } - - if (saveGraph && (buildTransit || buildStreet)) { - args.add("--save"); - } - if (serveGraph && !buildStreetOnly()) { - args.add("--serve"); - } - - args.add(configDataDir.getAbsolutePath()); - - return args.toArray(new String[0]); - } -} diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/api/LauncherRequestDecorator.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/api/LauncherRequestDecorator.java new file mode 100644 index 00000000000..99c28bad260 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/api/LauncherRequestDecorator.java @@ -0,0 +1,15 @@ +package org.opentripplanner.ext.interactivelauncher.api; + +import org.opentripplanner.routing.api.request.RouteRequest; + +/** + * Allow the interactive launcher intercept planing requests. + */ +public interface LauncherRequestDecorator { + /** + * The launcher may use this method to change the default plan request. Note! It is the DEFAULT + * request witch is passed in here, then the request-specific values are applied on top + * of that. + */ + RouteRequest intercept(RouteRequest defaultRequest); +} diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/configuration/InteractiveLauncherModule.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/configuration/InteractiveLauncherModule.java new file mode 100644 index 00000000000..9acd0298122 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/configuration/InteractiveLauncherModule.java @@ -0,0 +1,20 @@ +package org.opentripplanner.ext.interactivelauncher.configuration; + +import dagger.Module; +import dagger.Provides; +import org.opentripplanner.ext.interactivelauncher.api.LauncherRequestDecorator; + +@Module +public class InteractiveLauncherModule { + + static LauncherRequestDecorator decorator = request -> request; + + public static void setRequestInterceptor(LauncherRequestDecorator decorator) { + InteractiveLauncherModule.decorator = decorator; + } + + @Provides + LauncherRequestDecorator requestDecorator() { + return decorator; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/OtpDebugController.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/OtpDebugController.java new file mode 100644 index 00000000000..9e41fe3412d --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/OtpDebugController.java @@ -0,0 +1,37 @@ +package org.opentripplanner.ext.interactivelauncher.debug; + +import static org.opentripplanner.ext.interactivelauncher.support.ViewUtils.BACKGROUND; + +import javax.swing.JFrame; +import javax.swing.JTabbedPane; +import org.opentripplanner.ext.interactivelauncher.Model; +import org.opentripplanner.ext.interactivelauncher.debug.logging.LogView; +import org.opentripplanner.ext.interactivelauncher.debug.raptor.RaptorDebugView; + +/** + * This controller/UI allows changing the debug loggers and setting the raptor + * debug parameters for incoming rute requests. + */ +public class OtpDebugController { + + private final JFrame debugFrame = new JFrame("OTP Debug Controller"); + + public OtpDebugController(Model model) { + debugFrame.add(createTabbedPane(model)); + debugFrame.getContentPane().setBackground(BACKGROUND); + } + + public void start() { + debugFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + debugFrame.pack(); + debugFrame.setLocationRelativeTo(null); + debugFrame.setVisible(true); + } + + private static JTabbedPane createTabbedPane(Model model) { + var tabPanel = new JTabbedPane(); + tabPanel.addTab("Logging", new LogView(model.getLogModel()).panel()); + tabPanel.addTab("Raptor", new RaptorDebugView(model.getRaptorDebugModel()).panel()); + return tabPanel; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/logging/DebugLoggers.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/logging/DebugLoggers.java new file mode 100644 index 00000000000..48f87abf2ab --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/logging/DebugLoggers.java @@ -0,0 +1,26 @@ +package org.opentripplanner.ext.interactivelauncher.debug.logging; + +import java.util.List; + +class DebugLoggers { + + static List list() { + return List.of( + of("Data import issues", "DATA_IMPORT_ISSUES"), + of("All OTP debuggers", "org.opentripplanner"), + of("OTP request/response", "org.opentripplanner.routing.service.DefaultRoutingService"), + of("Raptor request/response", "org.opentripplanner.raptor.RaptorService"), + of("Transfer Optimization", "org.opentripplanner.routing.algorithm.transferoptimization") + ); + } + + static List listLoggers() { + return list().stream().map(Entry::logger).toList(); + } + + private static Entry of(String label, String logger) { + return new Entry(label, logger); + } + + record Entry(String label, String logger) {} +} diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/DebugLoggingSupport.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/logging/DebugLoggingSupport.java similarity index 60% rename from src/ext/java/org/opentripplanner/ext/interactivelauncher/DebugLoggingSupport.java rename to src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/logging/DebugLoggingSupport.java index e0b07a8c79e..09eddcfdf78 100644 --- a/src/ext/java/org/opentripplanner/ext/interactivelauncher/DebugLoggingSupport.java +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/logging/DebugLoggingSupport.java @@ -1,22 +1,21 @@ -package org.opentripplanner.ext.interactivelauncher; +package org.opentripplanner.ext.interactivelauncher.debug.logging; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.regex.Pattern; import org.slf4j.LoggerFactory; /** - * Responsible for integration with the OTP Debug log configuraton, reading loggers from the slf4j + * Responsible for integration with the OTP Debug log configuration, reading loggers from the slf4j * context and setting DEBUG level on selected loggers back. *

- * The log names are transformed to be more human readable: + * The log names are transformed to be more human-readable: *

org.opentripplanner.routing.algorithm  -->  o.o.routing.algorithm
*/ -public class DebugLoggingSupport { +class DebugLoggingSupport { private static final String OTP = Pattern.quote("org.opentripplanner.") + ".*"; private static final String GRAPHQL = Pattern.quote("fea"); @@ -26,29 +25,26 @@ public class DebugLoggingSupport { "(" + OTP + "|" + GRAPHQL + "|" + NAMED_LOGGERS + ")" ); - public static List getLogs() { + static List listConfiguredDebugLoggers() { List result = new ArrayList<>(); LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); for (Logger log : context.getLoggerList()) { var name = log.getName(); - if (!name.equals("ROOT") && LOG_MATCHER_PATTERN.matcher(name).matches()) { - result.add(logDisplayName(name)); + if (name.equals("ROOT") || log.getLevel() == null) { + continue; + } + if (log.getLevel().toInt() <= Level.DEBUG.toInt()) { + if (LOG_MATCHER_PATTERN.matcher(name).matches()) { + result.add(name); + } } } return result; } - public static void configureDebugLogging(Map loggers) { + static void configureDebugLogging(String logger, boolean debug) { LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); - for (Logger log : context.getLoggerList()) { - var name = logDisplayName(log.getName()); - if (loggers.getOrDefault(name, false)) { - log.setLevel(Level.DEBUG); - } - } - } - - private static String logDisplayName(String name) { - return name.replace("org.opentripplanner.", "o.o."); + var log = context.getLogger(logger); + log.setLevel(debug ? Level.DEBUG : Level.INFO); } } diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/logging/LogModel.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/logging/LogModel.java new file mode 100644 index 00000000000..df59ccaa968 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/logging/LogModel.java @@ -0,0 +1,82 @@ +package org.opentripplanner.ext.interactivelauncher.debug.logging; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import java.io.Serializable; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Responsible for storing the selected loggers to debug. This is + * serialized to store the user preferences between runs. + */ +public class LogModel implements Serializable { + + private final Set activeLoggers = new HashSet<>(); + + @JsonIgnore + private Runnable saveCallback; + + public LogModel() {} + + public static LogModel createFromConfig() { + var model = new LogModel(); + model.initFromConfig(); + return model; + } + + /** Need to set this manually to support JSON serialization. */ + public void init(Runnable saveCallback) { + this.saveCallback = saveCallback; + } + + /** Used by JSON serialization. */ + public Collection getActiveLoggers() { + return List.copyOf(activeLoggers); + } + + /** Used by JSON deserialization. */ + public void setActiveLoggers(Collection loggers) { + this.activeLoggers.clear(); + this.activeLoggers.addAll(loggers); + for (var logger : activeLoggers) { + DebugLoggingSupport.configureDebugLogging(logger, true); + } + } + + boolean isLoggerEnabled(String name) { + return activeLoggers.contains(name); + } + + void turnLoggerOnOff(String name, boolean enable) { + if (enable) { + if (!activeLoggers.contains(name)) { + activeLoggers.add(name); + DebugLoggingSupport.configureDebugLogging(name, enable); + save(); + } + } else { + if (activeLoggers.contains(name)) { + activeLoggers.remove(name); + DebugLoggingSupport.configureDebugLogging(name, enable); + save(); + } + } + } + + private void initFromConfig() { + var debugLoggers = DebugLoggers.listLoggers(); + for (var logger : DebugLoggingSupport.listConfiguredDebugLoggers()) { + if (debugLoggers.contains(logger)) { + activeLoggers.add(logger); + } + } + } + + private void save() { + if (saveCallback != null) { + saveCallback.run(); + } + } +} diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/logging/LogView.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/logging/LogView.java new file mode 100644 index 00000000000..ae8be59b07d --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/logging/LogView.java @@ -0,0 +1,35 @@ +package org.opentripplanner.ext.interactivelauncher.debug.logging; + +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JComponent; + +/** + * Display a list of loggers to turn on/off. + */ +public class LogView { + + private final Box panel = Box.createVerticalBox(); + private final LogModel model; + + public LogView(LogModel model) { + this.model = model; + DebugLoggers.list().forEach(this::add); + } + + public JComponent panel() { + return panel; + } + + private void add(DebugLoggers.Entry entry) { + var box = new JCheckBox(entry.label()); + box.setToolTipText("Logger: " + entry.logger()); + box.setSelected(model.isLoggerEnabled(entry.logger())); + box.addActionListener(e -> selectLogger(entry.logger(), box.isSelected())); + panel.add(box); + } + + private void selectLogger(String logger, boolean selected) { + model.turnLoggerOnOff(logger, selected); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/raptor/RaptorDebugModel.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/raptor/RaptorDebugModel.java new file mode 100644 index 00000000000..41d6ed6be1a --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/raptor/RaptorDebugModel.java @@ -0,0 +1,97 @@ +package org.opentripplanner.ext.interactivelauncher.debug.raptor; + +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Objects; +import java.util.Set; +import javax.annotation.Nullable; +import org.opentripplanner.ext.interactivelauncher.api.LauncherRequestDecorator; +import org.opentripplanner.framework.lang.StringUtils; +import org.opentripplanner.routing.api.request.DebugEventType; +import org.opentripplanner.routing.api.request.RouteRequest; + +public class RaptorDebugModel implements LauncherRequestDecorator { + + private final Set eventTypes = EnumSet.noneOf(DebugEventType.class); + private String stops = null; + private String path = null; + private Runnable saveCallback; + + public RaptorDebugModel() {} + + public void init(Runnable saveCallback) { + this.saveCallback = saveCallback; + } + + /** Used by JSON serialization */ + public Set getEventTypes() { + return Collections.unmodifiableSet(eventTypes); + } + + /** Used by JSON serialization */ + public void setEventTypes(Collection eventTypes) { + this.eventTypes.clear(); + this.eventTypes.addAll(eventTypes); + } + + public void enableEventTypes(DebugEventType eventType, boolean enable) { + if (enable) { + if (!isEventTypeSet(eventType)) { + this.eventTypes.add(eventType); + save(); + } + } else { + if (isEventTypeSet(eventType)) { + this.eventTypes.remove(eventType); + save(); + } + } + } + + @Nullable + public String getStops() { + return stops; + } + + public void setStops(@Nullable String stops) { + stops = StringUtils.hasValue(stops) ? stops : null; + if (!Objects.equals(this.stops, stops)) { + this.stops = stops; + save(); + } + } + + @Nullable + public String getPath() { + return path; + } + + public void setPath(@Nullable String path) { + path = StringUtils.hasValue(path) ? path : null; + if (!Objects.equals(this.path, path)) { + this.path = path; + save(); + } + } + + public boolean isEventTypeSet(DebugEventType eventType) { + return eventTypes.contains(eventType); + } + + @Override + public RouteRequest intercept(RouteRequest defaultRequest) { + var newRequest = defaultRequest.clone(); + var debug = newRequest.journey().transit().raptorDebugging(); + debug.withEventTypes(eventTypes); + debug.withStops(stops); + debug.withPath(path); + return newRequest; + } + + private void save() { + if (saveCallback != null) { + saveCallback.run(); + } + } +} diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/raptor/RaptorDebugView.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/raptor/RaptorDebugView.java new file mode 100644 index 00000000000..186b8d14837 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/raptor/RaptorDebugView.java @@ -0,0 +1,87 @@ +package org.opentripplanner.ext.interactivelauncher.debug.raptor; + +import static org.opentripplanner.ext.interactivelauncher.support.ViewUtils.addComp; +import static org.opentripplanner.ext.interactivelauncher.support.ViewUtils.addLabel; +import static org.opentripplanner.ext.interactivelauncher.support.ViewUtils.addVerticalSectionSpace; +import static org.opentripplanner.routing.api.request.DebugEventType.DESTINATION_ARRIVALS; +import static org.opentripplanner.routing.api.request.DebugEventType.PATTERN_RIDES; +import static org.opentripplanner.routing.api.request.DebugEventType.STOP_ARRIVALS; + +import java.awt.Component; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.util.function.Consumer; +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JTextField; +import org.opentripplanner.routing.api.request.DebugEventType; + +/** + * This UI is used to set Raptor debug parameters, instrument the Raptor + * search, and log event at decision points during routing. + */ +public class RaptorDebugView { + + private final RaptorDebugModel model; + private final Box panel = Box.createVerticalBox(); + private final JCheckBox logStopArrivalsChk = new JCheckBox("Stop arrivals"); + private final JCheckBox logPatternRidesChk = new JCheckBox("Pattern rides"); + private final JCheckBox logDestinationArrivalsChk = new JCheckBox("Destination arrivals"); + private final JTextField stopsTxt = new JTextField(40); + private final JTextField pathTxt = new JTextField(40); + + public RaptorDebugView(RaptorDebugModel model) { + this.model = model; + + addLabel("Log Raptor events for", panel); + addComp(logStopArrivalsChk, panel); + addComp(logPatternRidesChk, panel); + addComp(logDestinationArrivalsChk, panel); + addVerticalSectionSpace(panel); + + addLabel("A list of stops to debug", panel); + addComp(stopsTxt, panel); + addVerticalSectionSpace(panel); + addLabel("A a path (as a list of stops) to debug", panel); + addComp(pathTxt, panel); + addVerticalSectionSpace(panel); + + initValues(); + setupActionListeners(); + } + + private void initValues() { + logStopArrivalsChk.setSelected(model.isEventTypeSet(STOP_ARRIVALS)); + logPatternRidesChk.setSelected(model.isEventTypeSet(PATTERN_RIDES)); + logDestinationArrivalsChk.setSelected(model.isEventTypeSet(DESTINATION_ARRIVALS)); + stopsTxt.setText(model.getStops()); + pathTxt.setText(model.getPath()); + } + + private void setupActionListeners() { + setupActionListenerChkBox(logStopArrivalsChk, STOP_ARRIVALS); + setupActionListenerChkBox(logPatternRidesChk, PATTERN_RIDES); + setupActionListenerChkBox(logDestinationArrivalsChk, DESTINATION_ARRIVALS); + setupActionListenerTextField(stopsTxt, model::setStops); + setupActionListenerTextField(pathTxt, model::setPath); + } + + public Component panel() { + return panel; + } + + private void setupActionListenerChkBox(JCheckBox box, DebugEventType type) { + box.addActionListener(l -> model.enableEventTypes(type, box.isSelected())); + } + + private static void setupActionListenerTextField(JTextField txtField, Consumer model) { + txtField.addFocusListener( + new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + model.accept(txtField.getText()); + } + } + ); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/views/SearchDirectoryView.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/DataSourceRootView.java similarity index 80% rename from src/ext/java/org/opentripplanner/ext/interactivelauncher/views/SearchDirectoryView.java rename to src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/DataSourceRootView.java index ef054e2c879..e5ba9136e9d 100644 --- a/src/ext/java/org/opentripplanner/ext/interactivelauncher/views/SearchDirectoryView.java +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/DataSourceRootView.java @@ -1,7 +1,7 @@ -package org.opentripplanner.ext.interactivelauncher.views; +package org.opentripplanner.ext.interactivelauncher.startup; -import static org.opentripplanner.ext.interactivelauncher.views.ViewUtils.BG_STATUS_BAR; -import static org.opentripplanner.ext.interactivelauncher.views.ViewUtils.FG_STATUS_BAR; +import static org.opentripplanner.ext.interactivelauncher.support.ViewUtils.BG_STATUS_BAR; +import static org.opentripplanner.ext.interactivelauncher.support.ViewUtils.FG_STATUS_BAR; import java.awt.Component; import java.awt.Dimension; @@ -9,18 +9,20 @@ import java.util.function.Consumer; import javax.swing.Box; import javax.swing.JButton; +import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JTextField; +import org.opentripplanner.ext.interactivelauncher.support.ViewUtils; -public class SearchDirectoryView { +class DataSourceRootView { private final Box panel; private final JTextField fileTxt = new JTextField(); private final JButton searchBtn = new JButton("Open"); private final Consumer rootDirChangedListener; - public SearchDirectoryView(String dir, Consumer rootDirChangedListener) { + DataSourceRootView(String dir, Consumer rootDirChangedListener) { this.fileTxt.setText(dir); this.rootDirChangedListener = rootDirChangedListener; @@ -35,9 +37,6 @@ public SearchDirectoryView(String dir, Consumer rootDirChangedListener) fileTxt.setEditable(false); fileTxt.setBackground(BG_STATUS_BAR); fileTxt.setForeground(FG_STATUS_BAR); - //var d = minWidth(fileTxt.getPreferredSize(), 460); - //fileTxt.setMinimumSize(d); - //fileTxt.setPreferredSize(d); // Add text field and open button Box box = Box.createHorizontalBox(); @@ -48,7 +47,7 @@ public SearchDirectoryView(String dir, Consumer rootDirChangedListener) panel.add(box); } - public Box panel() { + public JComponent panel() { return panel; } diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/DataSourcesView.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/DataSourcesView.java new file mode 100644 index 00000000000..e7b1814faa2 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/DataSourcesView.java @@ -0,0 +1,131 @@ +package org.opentripplanner.ext.interactivelauncher.startup; + +import static org.opentripplanner.ext.interactivelauncher.support.ViewUtils.addComp; +import static org.opentripplanner.ext.interactivelauncher.support.ViewUtils.addHorizontalGlue; +import static org.opentripplanner.ext.interactivelauncher.support.ViewUtils.addLabel; +import static org.opentripplanner.ext.interactivelauncher.support.ViewUtils.addVerticalSectionSpace; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.util.List; +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JRadioButton; +import org.opentripplanner.ext.interactivelauncher.support.ViewUtils; + +class DataSourcesView { + + /* + |-----------------------------------------------| + | Label | + |-----------------------------------------------| + | ( ) List 1 | ( ) List 2 | ( ) List 3 | + | ( ) List 1 | ( ) List 2 | ( ) List 3 | + |-----------------------------------------------| + */ + + private final Box mainPanel = Box.createVerticalBox(); + private final Box listPanel = Box.createHorizontalBox(); + private final StartupModel model; + + public DataSourcesView(StartupModel model) { + this.model = model; + setupDataSources(); + + addLabel("Select data source", mainPanel); + addVerticalSectionSpace(mainPanel); + + listPanel.setAlignmentX(Component.LEFT_ALIGNMENT); + addComp(listPanel, mainPanel); + } + + public JComponent panel() { + return mainPanel; + } + + public void onRootDirChange() { + model.setDataSource(null); + listPanel.removeAll(); + setupDataSources(); + listPanel.repaint(); + } + + public void onDataSourceChange(ActionEvent e) { + model.setDataSource(e.getActionCommand()); + } + + private void setupDataSources() { + final List values = model.getDataSourceOptions(); + + if (values.isEmpty()) { + model.setDataSource(null); + var label = new JLabel(""); + label.setBackground(ViewUtils.BG_STATUS_BAR); + label.setForeground(ViewUtils.FG_STATUS_BAR); + addComp(label, listPanel); + return; + } + + String selectedValue = model.getDataSource(); + + if (selectedValue == null) { + selectedValue = values.get(0); + model.setDataSource(selectedValue); + } + + ButtonGroup selectDataSourceRadioGroup = new ButtonGroup(); + + List valuesSorted = values.stream().sorted().toList(); + int size = valuesSorted.size(); + + // Split the list of configuration in one, two or three columns depending on the + // number of configurations found. + if (size <= 10) { + addListPanel(valuesSorted, selectedValue, selectDataSourceRadioGroup); + } else if (size <= 20) { + int half = size / 2; + addListPanel(valuesSorted.subList(0, half), selectedValue, selectDataSourceRadioGroup); + addHorizontalGlue(listPanel); + addListPanel(valuesSorted.subList(half, size), selectedValue, selectDataSourceRadioGroup); + } else { + int third = size / 3; + addListPanel(valuesSorted.subList(0, third), selectedValue, selectDataSourceRadioGroup); + addHorizontalGlue(listPanel); + addListPanel( + valuesSorted.subList(third, third * 2), + selectedValue, + selectDataSourceRadioGroup + ); + addHorizontalGlue(listPanel); + addListPanel( + valuesSorted.subList(third * 2, size), + selectedValue, + selectDataSourceRadioGroup + ); + } + } + + private void addListPanel( + List values, + String selectedValue, + ButtonGroup selectDataSourceRadioGroup + ) { + Box column = Box.createVerticalBox(); + + for (String name : values) { + boolean selected = selectedValue.equals(name); + JRadioButton radioBtn = newRadioBtn(selectDataSourceRadioGroup, name, selected); + radioBtn.addActionListener(this::onDataSourceChange); + addComp(radioBtn, column); + } + addComp(column, listPanel); + } + + private static JRadioButton newRadioBtn(ButtonGroup group, String name, boolean selected) { + JRadioButton radioButton = new JRadioButton(name, selected); + group.add(radioButton); + return radioButton; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/views/MainView.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/MainView.java similarity index 51% rename from src/ext/java/org/opentripplanner/ext/interactivelauncher/views/MainView.java rename to src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/MainView.java index 44ede02e3eb..6db2508196c 100644 --- a/src/ext/java/org/opentripplanner/ext/interactivelauncher/views/MainView.java +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/MainView.java @@ -1,11 +1,9 @@ -package org.opentripplanner.ext.interactivelauncher.views; +package org.opentripplanner.ext.interactivelauncher.startup; -import static java.awt.GridBagConstraints.BOTH; import static java.awt.GridBagConstraints.CENTER; -import static java.awt.GridBagConstraints.NONE; -import static java.awt.GridBagConstraints.NORTH; -import static org.opentripplanner.ext.interactivelauncher.views.ViewUtils.BACKGROUND; -import static org.opentripplanner.ext.interactivelauncher.views.ViewUtils.debugLayout; +import static java.awt.GridBagConstraints.HORIZONTAL; +import static org.opentripplanner.ext.interactivelauncher.support.ViewUtils.BACKGROUND; +import static org.opentripplanner.ext.interactivelauncher.support.ViewUtils.debugLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; @@ -14,105 +12,35 @@ import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; -import org.opentripplanner.ext.interactivelauncher.Model; public class MainView { - /** Margins between components (IN) */ private static final int M_IN = 10; - - /** Margins around frame boarder (OUT) */ private static final int M_OUT = 2 * M_IN; + private static final Insets DEFAULT_INSETS = new Insets(M_OUT, M_OUT, M_IN, M_OUT); + private static final Insets SMALL_INSETS = new Insets(M_OUT, M_OUT, M_IN, M_OUT); + private static int Y = 0; /* - The application have the following 4 panels: + The application have the following panels: + +-----------------------------------+ + | Root dir [Open] | + +-----------------------------------+ + | Config Dirs Panel | + +-----------------------------------+ + | Options Panel | +-----------------------------------+ - | Root dir [Open] | - +-------------------+---------------+ - | | | - | Config Dirs Panel | Options Panel | - | | | - +-------------------+---------------+ - | Start OTP Main Panel | + | [ Start OTP ] | +-----------------------------------+ | Status Bar | +-----------------------------------+ */ - // Root dir view - private static final GridBagConstraints CONFIG_SOURCE_DIR_PANEL_CONSTRAINTS = new GridBagConstraints( - 0, - 0, - 2, - 1, - 1.0, - 0.0, - NORTH, - BOTH, - new Insets(M_OUT, M_OUT, M_IN, M_IN), - 0, - 0 - ); - - // List of locations - private static final GridBagConstraints CONFIG_DIRS_PANEL_CONSTRAINTS = new GridBagConstraints( - 0, - 1, - 1, - 1, - 1.0, - 1.0, - NORTH, - NONE, - new Insets(M_OUT, M_OUT, M_IN, M_IN), - 0, - 0 - ); - - // Options panel - private static final GridBagConstraints OPTIONS_PANEL_CONSTRAINTS = new GridBagConstraints( - 1, - 1, - 1, - 1, - 1.0, - 1.0, - NORTH, - NONE, - new Insets(M_OUT, M_IN, M_IN, M_OUT), - 0, - 0 - ); - - // Run btn and status - private static final GridBagConstraints START_OTP_BUTTON_PANEL_CONSTRAINTS = new GridBagConstraints( - 0, - 2, - 2, - 1, - 1.0, - 1.0, - CENTER, - BOTH, - new Insets(M_IN, M_OUT, M_IN, M_OUT), - 0, - 0 - ); - - // Run btn and status - private static final GridBagConstraints STATUS_BAR_CONSTRAINTS = new GridBagConstraints( - 0, - 3, - 2, - 1, - 1.0, - 0.0, - CENTER, - BOTH, - new Insets(M_IN, 0, 0, 0), - 40, - 0 - ); + private static final GridBagConstraints DATA_SOURCE_ROOT_PANEL_CONSTRAINTS = gbc(0f); + private static final GridBagConstraints DATA_SOURCE_LIST_PANEL_CONSTRAINTS = gbc(1f); + private static final GridBagConstraints OPTIONS_PANEL_CONSTRAINTS = gbc(1f); + private static final GridBagConstraints START_BUTTON_PANEL_CONSTRAINTS = gbc(0f); + private static final GridBagConstraints STATUS_BAR_CONSTRAINTS = gbc(0f, SMALL_INSETS, 40); private final JFrame mainFrame = new JFrame("Setup and Run OTP Main"); @@ -120,9 +48,9 @@ public class MainView { private final OptionsView optionsView; private final StartOtpButtonView startOtpButtonView; private final Runnable otpStarter; - private final Model model; + private final StartupModel model; - public MainView(Runnable otpStarter, Model model) throws HeadlessException { + public MainView(Runnable otpStarter, StartupModel model) throws HeadlessException { var innerPanel = new JPanel(); var statusBarTxt = new StatusBar(); @@ -134,7 +62,7 @@ public MainView(Runnable otpStarter, Model model) throws HeadlessException { innerPanel.setLayout(layout); innerPanel.setBackground(BACKGROUND); - var sourceDirectoryView = new SearchDirectoryView( + var sourceDirectoryView = new DataSourceRootView( model.getRootDirectory(), this::onRootDirChanged ); @@ -142,16 +70,17 @@ public MainView(Runnable otpStarter, Model model) throws HeadlessException { this.optionsView = new OptionsView(model); this.startOtpButtonView = new StartOtpButtonView(); - innerPanel.add(sourceDirectoryView.panel(), CONFIG_SOURCE_DIR_PANEL_CONSTRAINTS); - innerPanel.add(dataSourcesView.panel(), CONFIG_DIRS_PANEL_CONSTRAINTS); + innerPanel.add(sourceDirectoryView.panel(), DATA_SOURCE_ROOT_PANEL_CONSTRAINTS); + innerPanel.add(dataSourcesView.panel(), DATA_SOURCE_LIST_PANEL_CONSTRAINTS); innerPanel.add(optionsView.panel(), OPTIONS_PANEL_CONSTRAINTS); - innerPanel.add(startOtpButtonView.panel(), START_OTP_BUTTON_PANEL_CONSTRAINTS); + innerPanel.add(startOtpButtonView.panel(), START_BUTTON_PANEL_CONSTRAINTS); innerPanel.add(statusBarTxt, STATUS_BAR_CONSTRAINTS); // Setup action listeners startOtpButtonView.addActionListener(e -> startOtp()); debugLayout( + sourceDirectoryView.panel(), dataSourcesView.panel(), optionsView.panel(), startOtpButtonView.panel(), @@ -186,4 +115,12 @@ private void startOtp() { mainFrame.dispose(); otpStarter.run(); } + + private static GridBagConstraints gbc(float weighty) { + return gbc(weighty, DEFAULT_INSETS, 0); + } + + private static GridBagConstraints gbc(float weighty, Insets insets, int ipadx) { + return new GridBagConstraints(0, Y++, 1, 1, 1.0, weighty, CENTER, HORIZONTAL, insets, ipadx, 0); + } } diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/views/OptionsView.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/OptionsView.java similarity index 59% rename from src/ext/java/org/opentripplanner/ext/interactivelauncher/views/OptionsView.java rename to src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/OptionsView.java index 08923f3af88..08907346a76 100644 --- a/src/ext/java/org/opentripplanner/ext/interactivelauncher/views/OptionsView.java +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/OptionsView.java @@ -1,28 +1,25 @@ -package org.opentripplanner.ext.interactivelauncher.views; +package org.opentripplanner.ext.interactivelauncher.startup; -import static org.opentripplanner.ext.interactivelauncher.views.ViewUtils.addComp; -import static org.opentripplanner.ext.interactivelauncher.views.ViewUtils.addSectionDoubleSpace; -import static org.opentripplanner.ext.interactivelauncher.views.ViewUtils.addSectionSpace; +import static org.opentripplanner.ext.interactivelauncher.support.ViewUtils.addComp; +import static org.opentripplanner.ext.interactivelauncher.support.ViewUtils.addLabel; +import static org.opentripplanner.ext.interactivelauncher.support.ViewUtils.addVerticalSectionSpace; -import java.util.List; import java.util.function.Consumer; -import java.util.stream.Collectors; import javax.swing.Box; import javax.swing.JCheckBox; -import javax.swing.JLabel; -import org.opentripplanner.ext.interactivelauncher.Model; +import javax.swing.JComponent; class OptionsView { - private final Box panel = Box.createVerticalBox(); + private final Box panel = Box.createHorizontalBox(); private final JCheckBox buildStreetGraphChk; private final JCheckBox buildTransitGraphChk; private final JCheckBox saveGraphChk; private final JCheckBox startOptServerChk; private final JCheckBox startOptVisualizerChk; - private final Model model; + private final StartupModel model; - OptionsView(Model model) { + OptionsView(StartupModel model) { this.model = model; this.buildStreetGraphChk = new JCheckBox("Street graph", model.isBuildStreet()); this.buildTransitGraphChk = new JCheckBox("Transit graph", model.isBuildTransit()); @@ -30,28 +27,41 @@ class OptionsView { this.startOptServerChk = new JCheckBox("Serve graph", model.isServeGraph()); this.startOptVisualizerChk = new JCheckBox("Visualizer", model.isVisualizer()); - addComp(new JLabel("Build graph"), panel); - addSectionSpace(panel); - addComp(buildStreetGraphChk, panel); - addComp(buildTransitGraphChk, panel); - addSectionDoubleSpace(panel); + panel.add(Box.createGlue()); + addComp(createBuildBox(), panel); + panel.add(Box.createGlue()); + addComp(createActionBox(), panel); + panel.add(Box.createGlue()); // Toggle [ ] save on/off buildStreetGraphChk.addActionListener(e -> onBuildGraphChkChanged()); buildTransitGraphChk.addActionListener(e -> onBuildGraphChkChanged()); startOptServerChk.addActionListener(e -> onStartOptServerChkChanged()); - addComp(new JLabel("Actions"), panel); - addSectionSpace(panel); - addComp(saveGraphChk, panel); - addComp(startOptServerChk, panel); - addComp(startOptVisualizerChk, panel); - - addDebugCheckBoxes(model); - addSectionDoubleSpace(panel); + //addSectionDoubleSpace(panel); bindCheckBoxesToModel(); } + private JComponent createBuildBox() { + var buildBox = Box.createVerticalBox(); + addLabel("Build graph", buildBox); + addVerticalSectionSpace(buildBox); + addComp(buildStreetGraphChk, buildBox); + addComp(buildTransitGraphChk, buildBox); + buildBox.add(Box.createVerticalGlue()); + return buildBox; + } + + private JComponent createActionBox() { + var actionBox = Box.createVerticalBox(); + addLabel("Actions", actionBox); + addVerticalSectionSpace(actionBox); + addComp(saveGraphChk, actionBox); + addComp(startOptServerChk, actionBox); + addComp(startOptVisualizerChk, actionBox); + return actionBox; + } + Box panel() { return panel; } @@ -64,19 +74,6 @@ void bind(JCheckBox box, Consumer modelUpdate) { box.addActionListener(l -> modelUpdate.accept(box.isSelected() && box.isEnabled())); } - private void addDebugCheckBoxes(Model model) { - addSectionSpace(panel); - addComp(new JLabel("Debug logging"), panel); - addSectionSpace(panel); - var entries = model.getDebugLogging(); - List keys = entries.keySet().stream().sorted().collect(Collectors.toList()); - for (String name : keys) { - JCheckBox box = new JCheckBox(name, entries.get(name)); - box.addActionListener(l -> model.getDebugLogging().put(name, box.isSelected())); - addComp(box, panel); - } - } - private void bindCheckBoxesToModel() { bind(buildStreetGraphChk, model::setBuildStreet); bind(buildTransitGraphChk, model::setBuildTransit); diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/views/StartOtpButtonView.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/StartOtpButtonView.java similarity index 82% rename from src/ext/java/org/opentripplanner/ext/interactivelauncher/views/StartOtpButtonView.java rename to src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/StartOtpButtonView.java index 5c0c1e7a621..3050b7c5c62 100644 --- a/src/ext/java/org/opentripplanner/ext/interactivelauncher/views/StartOtpButtonView.java +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/StartOtpButtonView.java @@ -1,6 +1,6 @@ -package org.opentripplanner.ext.interactivelauncher.views; +package org.opentripplanner.ext.interactivelauncher.startup; -import static org.opentripplanner.ext.interactivelauncher.views.ViewUtils.adjustSize; +import static org.opentripplanner.ext.interactivelauncher.support.ViewUtils.adjustSize; import java.awt.event.ActionListener; import javax.swing.Box; diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/StartupModel.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/StartupModel.java new file mode 100644 index 00000000000..5b803b2318c --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/StartupModel.java @@ -0,0 +1,178 @@ +package org.opentripplanner.ext.interactivelauncher.startup; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import org.opentripplanner.ext.interactivelauncher.support.SearchForOtpConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class StartupModel { + + private static final Logger LOG = LoggerFactory.getLogger(StartupModel.class); + + @JsonIgnore + private transient Consumer commandLineChange; + + private String rootDirectory = null; + private String dataSource = null; + private boolean buildStreet = false; + private boolean buildTransit = true; + private boolean saveGraph = false; + private boolean serveGraph = true; + private boolean visualizer = false; + + public void subscribeCmdLineUpdates(Consumer commandLineChange) { + this.commandLineChange = commandLineChange; + } + + @SuppressWarnings("AccessOfSystemProperties") + public String getRootDirectory() { + return rootDirectory == null ? System.getProperty("user.dir") : rootDirectory; + } + + public void setRootDirectory(String rootDirectory) { + // If the persisted JSON do not contain the rootDirectory, then avoid setting it + if (rootDirectory != null) { + this.rootDirectory = rootDirectory; + } + notifyChangeListener(); + } + + public String getDataSource() { + return dataSource; + } + + public void setDataSource(String dataSource) { + this.dataSource = dataSource; + notifyChangeListener(); + } + + @JsonIgnore + public List getDataSourceOptions() { + List dataSourceOptions = new ArrayList<>(); + File rootDir = new File(getRootDirectory()); + List dirs = SearchForOtpConfig.search(rootDir); + // Add 1 char for the path-separator-character + int length = rootDir.getAbsolutePath().length() + 1; + + for (File dir : dirs) { + var path = dir.getAbsolutePath(); + if (path.length() <= length) { + LOG.warn( + "The root directory contains a config file, choose " + + "the parent directory or delete the config file." + ); + continue; + } + dataSourceOptions.add(path.substring(length)); + } + return dataSourceOptions; + } + + public boolean isBuildStreet() { + return buildStreet; + } + + public void setBuildStreet(boolean buildStreet) { + this.buildStreet = buildStreet; + notifyChangeListener(); + } + + public boolean isBuildTransit() { + return buildTransit; + } + + public void setBuildTransit(boolean buildTransit) { + this.buildTransit = buildTransit; + notifyChangeListener(); + } + + public boolean isSaveGraph() { + return saveGraph; + } + + public void setSaveGraph(boolean saveGraph) { + this.saveGraph = saveGraph; + notifyChangeListener(); + } + + public boolean isServeGraph() { + return serveGraph; + } + + public void setServeGraph(boolean serveGraph) { + this.serveGraph = serveGraph; + notifyChangeListener(); + } + + public boolean isVisualizer() { + return visualizer; + } + + public void setVisualizer(boolean visualizer) { + this.visualizer = visualizer; + notifyChangeListener(); + } + + @Override + public String toString() { + return String.join("", asOtpArgs()); + } + + public String toCliString() { + return String.join(" ", asOtpArgs()); + } + + private void notifyChangeListener() { + if (commandLineChange != null) { + commandLineChange.accept(toCliString()); + } + } + + @JsonIgnore + String getDataSourceDirectory() { + if (dataSource == null) { + return "DATA_SOURCE_NOT_SET"; + } + return getRootDirectory() + File.separatorChar + dataSource; + } + + public String[] asOtpArgs() { + List args = new ArrayList<>(); + + if (buildAll()) { + args.add("--build"); + } else if (buildStreet) { + args.add("--buildStreet"); + } else if (buildTransit) { + args.add("--loadStreet"); + } else { + args.add("--load"); + } + + if (saveGraph && (buildTransit || buildStreet)) { + args.add("--save"); + } + if (serveGraph && !buildStreetOnly()) { + args.add("--serve"); + } + if (serveGraph && !buildStreetOnly() && visualizer) { + args.add("--visualize"); + } + + args.add(getDataSourceDirectory()); + + return args.toArray(new String[0]); + } + + private boolean buildAll() { + return buildStreet && buildTransit; + } + + private boolean buildStreetOnly() { + return buildStreet && !buildTransit; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/StatusBar.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/StatusBar.java new file mode 100644 index 00000000000..f88c77c9f75 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/StatusBar.java @@ -0,0 +1,15 @@ +package org.opentripplanner.ext.interactivelauncher.startup; + +import static org.opentripplanner.ext.interactivelauncher.support.ViewUtils.BG_STATUS_BAR; +import static org.opentripplanner.ext.interactivelauncher.support.ViewUtils.FG_STATUS_BAR; + +import javax.swing.JTextField; + +class StatusBar extends JTextField { + + public StatusBar() { + setEditable(false); + setBackground(BG_STATUS_BAR); + setForeground(FG_STATUS_BAR); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/SearchForOtpConfig.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/support/SearchForOtpConfig.java similarity index 85% rename from src/ext/java/org/opentripplanner/ext/interactivelauncher/SearchForOtpConfig.java rename to src/ext/java/org/opentripplanner/ext/interactivelauncher/support/SearchForOtpConfig.java index ccf6be72e20..6b0574f0fdf 100644 --- a/src/ext/java/org/opentripplanner/ext/interactivelauncher/SearchForOtpConfig.java +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/support/SearchForOtpConfig.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.interactivelauncher; +package org.opentripplanner.ext.interactivelauncher.support; import java.io.File; import java.util.Arrays; @@ -10,17 +10,17 @@ /** * Search for directories containing OTP configuration files. The search is recursive and searches - * sub-directories 10 levels deep. + * subdirectories 10 levels deep. */ -class SearchForOtpConfig { +public class SearchForOtpConfig { private static final int DEPTH_LIMIT = 10; private static final Pattern EXCLUDE_DIR = Pattern.compile( "(otp1|archive|\\..*|te?mp|target|docs?|src|source|resource)" ); - static List search(File rootDir) { - return recursiveSearch(rootDir, DEPTH_LIMIT).collect(Collectors.toUnmodifiableList()); + public static List search(File rootDir) { + return recursiveSearch(rootDir, DEPTH_LIMIT).toList(); } @SuppressWarnings("ConstantConditions") diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/support/ViewUtils.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/support/ViewUtils.java new file mode 100644 index 00000000000..f906f063058 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/interactivelauncher/support/ViewUtils.java @@ -0,0 +1,53 @@ +package org.opentripplanner.ext.interactivelauncher.support; + +import java.awt.Color; +import java.awt.Container; +import java.awt.Dimension; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JComponent; +import javax.swing.JLabel; + +public final class ViewUtils { + + private static final boolean DEBUG_LAYOUT = false; + static final int SECTION_SPACE = 10; + public static final Color BACKGROUND = new Color(0xe0, 0xf0, 0xff); + public static final Color BG_STATUS_BAR = new Color(0xd0, 0xe0, 0xf0); + public static final Color FG_STATUS_BAR = new Color(0, 0, 0x80); + + public static void addVerticalSectionSpace(Box panel) { + panel.add(Box.createVerticalStrut(SECTION_SPACE)); + } + + public static void addHorizontalGlue(Box box) { + box.add(Box.createHorizontalGlue()); + } + + public static void addLabel(String label, Container panel) { + addComp(new JLabel(label), panel); + } + + public static void addComp(JComponent c, Container panel) { + if (DEBUG_LAYOUT) { + c.setBorder(BorderFactory.createLineBorder(Color.green)); + } + panel.add(c); + } + + public static void debugLayout(JComponent... components) { + if (DEBUG_LAYOUT) { + for (JComponent c : components) { + c.setBorder(BorderFactory.createLineBorder(Color.red)); + } + } + } + + public static void adjustSize(JComponent c, int dWidth, int dHeight) { + Dimension d0 = c.getPreferredSize(); + Dimension d = new Dimension(d0.width + dWidth, d0.height + dHeight); + c.setMinimumSize(d); + c.setPreferredSize(d); + c.setMaximumSize(d); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/views/DataSourcesView.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/views/DataSourcesView.java deleted file mode 100644 index c7faa43779d..00000000000 --- a/src/ext/java/org/opentripplanner/ext/interactivelauncher/views/DataSourcesView.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.opentripplanner.ext.interactivelauncher.views; - -import static org.opentripplanner.ext.interactivelauncher.views.ViewUtils.addComp; -import static org.opentripplanner.ext.interactivelauncher.views.ViewUtils.addSectionDoubleSpace; -import static org.opentripplanner.ext.interactivelauncher.views.ViewUtils.addSectionSpace; - -import java.awt.event.ActionEvent; -import java.util.List; -import java.util.stream.Collectors; -import javax.swing.Box; -import javax.swing.ButtonGroup; -import javax.swing.JLabel; -import javax.swing.JRadioButton; -import org.opentripplanner.ext.interactivelauncher.Model; - -class DataSourcesView { - - private final Box panel = Box.createVerticalBox(); - private final Box dataSourceSelectionPanel = Box.createVerticalBox(); - private final Model model; - - public DataSourcesView(Model model) { - this.model = model; - - setupDataSources(); - - addComp(new JLabel("Select data source"), panel); - addSectionSpace(panel); - addComp(dataSourceSelectionPanel, panel); - addSectionDoubleSpace(panel); - } - - public Box panel() { - return panel; - } - - public void onRootDirChange() { - model.setDataSource(null); - dataSourceSelectionPanel.removeAll(); - setupDataSources(); - panel.repaint(); - } - - public void onDataSourceChange(ActionEvent e) { - model.setDataSource(e.getActionCommand()); - } - - private static JRadioButton newRadioBtn(ButtonGroup group, String name, boolean selected) { - JRadioButton radioButton = new JRadioButton(name, selected); - group.add(radioButton); - return radioButton; - } - - private void setupDataSources() { - final List values = model.getDataSourceOptions(); - - if (values.isEmpty()) { - model.setDataSource(null); - JLabel label = new JLabel(""); - label.setBackground(ViewUtils.BG_STATUS_BAR); - label.setForeground(ViewUtils.FG_STATUS_BAR); - addComp(label, dataSourceSelectionPanel); - return; - } - - String selectedValue = model.getDataSource(); - - if (selectedValue == null) { - selectedValue = values.get(0); - model.setDataSource(selectedValue); - } - - ButtonGroup selectDataSourceRadioGroup = new ButtonGroup(); - - List valuesSorted = values.stream().sorted().collect(Collectors.toList()); - - for (String name : valuesSorted) { - boolean selected = selectedValue.equals(name); - JRadioButton radioBtn = newRadioBtn(selectDataSourceRadioGroup, name, selected); - radioBtn.addActionListener(this::onDataSourceChange); - addComp(radioBtn, dataSourceSelectionPanel); - } - } -} diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/views/StatusBar.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/views/StatusBar.java deleted file mode 100644 index faffa9d87d2..00000000000 --- a/src/ext/java/org/opentripplanner/ext/interactivelauncher/views/StatusBar.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.opentripplanner.ext.interactivelauncher.views; - -import static org.opentripplanner.ext.interactivelauncher.views.ViewUtils.BG_STATUS_BAR; -import static org.opentripplanner.ext.interactivelauncher.views.ViewUtils.FG_STATUS_BAR; - -import javax.swing.JTextField; - -public class StatusBar extends JTextField { - - public StatusBar() { - setEditable(false); - setBackground(BG_STATUS_BAR); - setForeground(FG_STATUS_BAR); - } -} diff --git a/src/ext/java/org/opentripplanner/ext/interactivelauncher/views/ViewUtils.java b/src/ext/java/org/opentripplanner/ext/interactivelauncher/views/ViewUtils.java deleted file mode 100644 index 14f6f589109..00000000000 --- a/src/ext/java/org/opentripplanner/ext/interactivelauncher/views/ViewUtils.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.opentripplanner.ext.interactivelauncher.views; - -import java.awt.Color; -import java.awt.Dimension; -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.JComponent; - -final class ViewUtils { - - private static final boolean DEBUG_LAYOUT = false; - static final int SECTION_SPACE = 10; - static final Color BACKGROUND = new Color(0xe0, 0xf0, 0xff); - static final Color BG_STATUS_BAR = new Color(0xd0, 0xe0, 0xf0); - static final Color FG_STATUS_BAR = new Color(0, 0, 0x80); - - static void addSectionSpace(Box panel) { - panel.add(Box.createVerticalStrut(SECTION_SPACE)); - } - - static void addSectionDoubleSpace(Box panel) { - panel.add(Box.createVerticalStrut(2 * SECTION_SPACE)); - } - - static void addComp(JComponent c, Box panel) { - if (DEBUG_LAYOUT) { - c.setBorder(BorderFactory.createLineBorder(Color.green)); - } - panel.add(c); - } - - static void debugLayout(JComponent... components) { - if (DEBUG_LAYOUT) { - for (JComponent c : components) { - c.setBorder(BorderFactory.createLineBorder(Color.red)); - } - } - } - - static void adjustSize(JComponent c, int dWidth, int dHeight) { - Dimension d0 = c.getPreferredSize(); - Dimension d = new Dimension(d0.width + dWidth, d0.height + dHeight); - c.setMinimumSize(d); - c.setPreferredSize(d); - c.setMaximumSize(d); - } -} diff --git a/src/ext/java/org/opentripplanner/ext/restapi/mapping/LegacyBicycleOptimizeType.java b/src/ext/java/org/opentripplanner/ext/restapi/mapping/LegacyBicycleOptimizeType.java new file mode 100644 index 00000000000..ff50a03534b --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/restapi/mapping/LegacyBicycleOptimizeType.java @@ -0,0 +1,24 @@ +package org.opentripplanner.ext.restapi.mapping; + +import org.opentripplanner.routing.core.BicycleOptimizeType; + +/** + * Bicycle optimization types that are only meant to be used by the REST API. Related to {@link org.opentripplanner.routing.core.BicycleOptimizeType} + */ +public enum LegacyBicycleOptimizeType { + QUICK, + SAFE, + FLAT, + GREENWAYS, + TRIANGLE; + + public static BicycleOptimizeType map(LegacyBicycleOptimizeType type) { + return switch (type) { + case QUICK -> BicycleOptimizeType.SHORTEST_DURATION; + case FLAT -> BicycleOptimizeType.FLAT_STREETS; + case SAFE -> BicycleOptimizeType.SAFE_STREETS; + case GREENWAYS -> BicycleOptimizeType.SAFEST_STREETS; + case TRIANGLE -> BicycleOptimizeType.TRIANGLE; + }; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/restapi/mapping/TripTimeMapper.java b/src/ext/java/org/opentripplanner/ext/restapi/mapping/TripTimeMapper.java index f1f37e6d8b4..46607cccb6a 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/mapping/TripTimeMapper.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/mapping/TripTimeMapper.java @@ -29,13 +29,13 @@ public static ApiTripTimeShort mapToApi(TripTimeOnDate domain) { api.stopCount = domain.getStopCount(); api.scheduledArrival = domain.getScheduledArrival(); api.scheduledDeparture = domain.getScheduledDeparture(); - api.realTimeArrival = domain.getRealtimeArrival(); - api.realTimeDeparture = domain.getRealtimeDeparture(); + api.realimeArrival = domain.getRealtimeArrival(); + api.realtimeDeparture = domain.getRealtimeDeparture(); api.arrivalDelay = domain.getArrivalDelay(); api.departureDelay = domain.getDepartureDelay(); api.timepoint = domain.isTimepoint(); - api.realTime = domain.isRealtime(); - api.realTimeState = ApiRealTimeState.RealTimeState(domain.getRealTimeState()); + api.realtime = domain.isRealtime(); + api.realtimeState = ApiRealTimeState.RealTimeState(domain.getRealTimeState()); api.blockId = domain.getBlockId(); api.headsign = I18NStringMapper.mapToApi(domain.getHeadsign(), null); api.tripId = FeedScopedIdMapper.mapToApi(domain.getTrip().getId()); diff --git a/src/ext/java/org/opentripplanner/ext/restapi/model/ApiTripTimeShort.java b/src/ext/java/org/opentripplanner/ext/restapi/model/ApiTripTimeShort.java index 21c6010c5a2..c0337ba18d4 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/model/ApiTripTimeShort.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/model/ApiTripTimeShort.java @@ -11,13 +11,13 @@ public class ApiTripTimeShort implements Serializable { public int stopCount; public int scheduledArrival = UNDEFINED; public int scheduledDeparture = UNDEFINED; - public int realTimeArrival = UNDEFINED; - public int realTimeDeparture = UNDEFINED; + public int realimeArrival = UNDEFINED; + public int realtimeDeparture = UNDEFINED; public int arrivalDelay = UNDEFINED; public int departureDelay = UNDEFINED; public boolean timepoint = false; - public boolean realTime = false; - public ApiRealTimeState realTimeState = ApiRealTimeState.SCHEDULED; + public boolean realtime = false; + public ApiRealTimeState realtimeState = ApiRealTimeState.SCHEDULED; public long serviceDay; public String tripId; public String blockId; diff --git a/src/ext/java/org/opentripplanner/ext/restapi/model/ApiVehicleParkingWithEntrance.java b/src/ext/java/org/opentripplanner/ext/restapi/model/ApiVehicleParkingWithEntrance.java index 68a9f37dfe4..79c5decbaeb 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/model/ApiVehicleParkingWithEntrance.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/model/ApiVehicleParkingWithEntrance.java @@ -81,7 +81,7 @@ public class ApiVehicleParkingWithEntrance { /** * True if real-time information is used for checking availability. */ - public final boolean realTime; + public final boolean realtime; ApiVehicleParkingWithEntrance( String id, @@ -114,7 +114,7 @@ public class ApiVehicleParkingWithEntrance { this.hasWheelchairAccessibleCarPlaces = hasWheelchairAccessibleCarPlaces; this.capacity = capacity; this.availability = availability; - this.realTime = realTime; + this.realtime = realTime; } public static ApiVehicleParkingWithEntranceBuilder builder() { diff --git a/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java b/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java index fee01e63d66..bfac498ff8c 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java @@ -2,6 +2,7 @@ import jakarta.validation.constraints.NotNull; import java.util.function.Consumer; +import org.opentripplanner.ext.restapi.mapping.LegacyBicycleOptimizeType; import org.opentripplanner.framework.lang.ObjectUtils; import org.opentripplanner.routing.algorithm.filterchain.api.TransitGeneralizedCostFilterParams; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; @@ -10,7 +11,6 @@ import org.opentripplanner.routing.api.request.preference.RoutingPreferences; import org.opentripplanner.routing.api.request.preference.VehicleParkingPreferences; import org.opentripplanner.routing.api.request.preference.VehicleRentalPreferences; -import org.opentripplanner.routing.core.BicycleOptimizeType; class RequestToPreferencesMapper { @@ -46,8 +46,8 @@ private void mapCar() { setIfNotNull(req.carReluctance, car::withReluctance); car.withParking(parking -> { mapParking(parking); - setIfNotNull(req.carParkCost, parking::withParkCost); - setIfNotNull(req.carParkTime, parking::withParkTime); + setIfNotNull(req.carParkCost, parking::withCost); + setIfNotNull(req.carParkTime, parking::withTime); }); car.withRental(this::mapRental); }); @@ -67,13 +67,12 @@ private void mapBike() { setIfNotNull(req.bikeSpeed, bike::withSpeed); setIfNotNull(req.bikeReluctance, bike::withReluctance); setIfNotNull(req.bikeBoardCost, bike::withBoardCost); - setIfNotNull(req.bikeWalkingSpeed, bike::withWalkingSpeed); - setIfNotNull(req.bikeWalkingReluctance, bike::withWalkingReluctance); - setIfNotNull(req.bikeSwitchTime, bike::withSwitchTime); - setIfNotNull(req.bikeSwitchCost, bike::withSwitchCost); - setIfNotNull(req.bikeOptimizeType, bike::withOptimizeType); + setIfNotNull( + req.bikeOptimizeType, + optimizeType -> bike.withOptimizeType(LegacyBicycleOptimizeType.map(optimizeType)) + ); - if (req.bikeOptimizeType == BicycleOptimizeType.TRIANGLE) { + if (req.bikeOptimizeType == LegacyBicycleOptimizeType.TRIANGLE) { bike.withOptimizeTriangle(triangle -> { setIfNotNull(req.triangleTimeFactor, triangle::withTime); setIfNotNull(req.triangleSlopeFactor, triangle::withSlope); @@ -83,10 +82,16 @@ private void mapBike() { bike.withParking(parking -> { mapParking(parking); - setIfNotNull(req.bikeParkCost, parking::withParkCost); - setIfNotNull(req.bikeParkTime, parking::withParkTime); + setIfNotNull(req.bikeParkCost, parking::withCost); + setIfNotNull(req.bikeParkTime, parking::withTime); }); bike.withRental(this::mapRental); + bike.withWalking(walk -> { + setIfNotNull(req.bikeWalkingSpeed, walk::withSpeed); + setIfNotNull(req.bikeWalkingReluctance, walk::withReluctance); + setIfNotNull(req.bikeSwitchTime, walk::withHopTime); + setIfNotNull(req.bikeSwitchCost, walk::withHopCost); + }); }); } @@ -97,10 +102,8 @@ private BoardAndAlightSlack mapTransit() { setIfNotNull(req.otherThanPreferredRoutesPenalty, tr::setOtherThanPreferredRoutesPenalty); setIfNotNull(req.ignoreRealtimeUpdates, tr::setIgnoreRealtimeUpdates); - if (req.relaxTransitPriorityGroup != null) { - tr.withTransitGroupPriorityGeneralizedCostSlack( - CostLinearFunction.of(req.relaxTransitPriorityGroup) - ); + if (req.relaxTransitGroupPriority != null) { + tr.withRelaxTransitGroupPriority(CostLinearFunction.of(req.relaxTransitGroupPriority)); } else { setIfNotNull( req.relaxTransitSearchGeneralizedCostAtDestination, @@ -145,7 +148,7 @@ private void mapRental(VehicleRentalPreferences.Builder rental) { setIfNotNull( req.keepingRentedBicycleAtDestinationCost, - rental::withArrivingInRentalVehicleAtDestinationCost + cost -> rental.withArrivingInRentalVehicleAtDestinationCost((int) Math.round(cost)) ); rental.withUseAvailabilityInformation(isPlannedForNow); } diff --git a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java index c53e35c63ff..1c234e3ff9e 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java @@ -20,14 +20,15 @@ import javax.xml.datatype.XMLGregorianCalendar; import org.opentripplanner.api.parameter.QualifiedModeSet; import org.opentripplanner.ext.dataoverlay.api.DataOverlayParameters; +import org.opentripplanner.ext.restapi.mapping.LegacyBicycleOptimizeType; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.lang.StringUtils; import org.opentripplanner.framework.time.DurationUtils; +import org.opentripplanner.framework.time.ZoneIdFallback; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.preference.ItineraryFilterDebugProfile; import org.opentripplanner.routing.api.request.request.filter.SelectRequest; import org.opentripplanner.routing.api.request.request.filter.TransitFilterRequest; -import org.opentripplanner.routing.core.BicycleOptimizeType; import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.standalone.config.framework.file.ConfigFileLoader; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; @@ -248,7 +249,7 @@ public abstract class RoutingResource { */ @Deprecated @QueryParam("optimize") - protected BicycleOptimizeType bikeOptimizeType; + protected LegacyBicycleOptimizeType bikeOptimizeType; /** * The set of modes that a user is willing to use, with qualifiers stating whether vehicles should @@ -658,8 +659,8 @@ public abstract class RoutingResource { @QueryParam("useVehicleParkingAvailabilityInformation") protected Boolean useVehicleParkingAvailabilityInformation; - @QueryParam("relaxTransitPriorityGroup") - protected String relaxTransitPriorityGroup; + @QueryParam("relaxTransitGroupPriority") + protected String relaxTransitGroupPriority; /** * Whether non-optimal transit paths at the destination should be returned. @@ -711,7 +712,7 @@ protected RouteRequest buildRequest(MultivaluedMap queryParamete { //FIXME: move into setter method on routing request - ZoneId tz = serverContext.transitService().getTimeZone(); + ZoneId tz = ZoneIdFallback.zoneId(serverContext.transitService().getTimeZone()); if (date == null && time != null) { // Time was provided but not date LOG.debug("parsing ISO datetime {}", time); try { diff --git a/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingFilter.java b/src/ext/java/org/opentripplanner/ext/ridehailing/DecorateWithRideHailing.java similarity index 87% rename from src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingFilter.java rename to src/ext/java/org/opentripplanner/ext/ridehailing/DecorateWithRideHailing.java index 730275d5952..cd076b7e2a5 100644 --- a/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingFilter.java +++ b/src/ext/java/org/opentripplanner/ext/ridehailing/DecorateWithRideHailing.java @@ -7,23 +7,23 @@ import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; import org.opentripplanner.model.plan.StreetLeg; -import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilter; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryListFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This filter decorates car dropoff/pickup legs with information from ride hailing services and - * adds information about price and arrival time of the vehicle. + * adds information about the price and arrival time of the vehicle. */ -public class RideHailingFilter implements ItineraryListFilter { +public class DecorateWithRideHailing implements ItineraryListFilter { - private static final Logger LOG = LoggerFactory.getLogger(RideHailingFilter.class); + private static final Logger LOG = LoggerFactory.getLogger(DecorateWithRideHailing.class); public static final String NO_RIDE_HAILING_AVAILABLE = "no-ride-hailing-available"; private final List rideHailingServices; private final boolean wheelchairAccessible; - public RideHailingFilter( + public DecorateWithRideHailing( List rideHailingServices, boolean wheelchairAccessible ) { diff --git a/src/ext/java/org/opentripplanner/ext/stopconsolidation/ConsolidatedStopNameFilter.java b/src/ext/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNames.java similarity index 75% rename from src/ext/java/org/opentripplanner/ext/stopconsolidation/ConsolidatedStopNameFilter.java rename to src/ext/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNames.java index c28d1de91b6..8fcba82a357 100644 --- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/ConsolidatedStopNameFilter.java +++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNames.java @@ -1,28 +1,27 @@ package org.opentripplanner.ext.stopconsolidation; -import java.util.List; import java.util.Objects; import org.opentripplanner.ext.stopconsolidation.model.ConsolidatedStopLeg; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.ScheduledTransitLeg; -import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilter; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryDecorator; /** * A decorating filter that checks if a transit leg contains any primary stops and if it does, * then replaces it with the secondary, agency-specific stop name. This is so that the in-vehicle * display matches what OTP returns as a board/alight stop name. */ -public class ConsolidatedStopNameFilter implements ItineraryListFilter { +public class DecorateConsolidatedStopNames implements ItineraryDecorator { private final StopConsolidationService service; - public ConsolidatedStopNameFilter(StopConsolidationService service) { + public DecorateConsolidatedStopNames(StopConsolidationService service) { this.service = Objects.requireNonNull(service); } @Override - public List filter(List itineraries) { - return itineraries.stream().map(this::replacePrimaryNamesWithSecondary).toList(); + public void decorate(Itinerary itinerary) { + replacePrimaryNamesWithSecondary(itinerary); } /** @@ -31,8 +30,8 @@ public List filter(List itineraries) { * operating the route, so that the name in the result matches the name in the in-vehicle * display. */ - private Itinerary replacePrimaryNamesWithSecondary(Itinerary i) { - return i.transformTransitLegs(leg -> { + private void replacePrimaryNamesWithSecondary(Itinerary i) { + i.transformTransitLegs(leg -> { if (leg instanceof ScheduledTransitLeg stl && needsToRenameStops(stl)) { var agency = leg.getAgency(); return new ConsolidatedStopLeg( diff --git a/src/main/java/org/opentripplanner/apis/APIEndpoints.java b/src/main/java/org/opentripplanner/apis/APIEndpoints.java index 32d893618cc..68f6bda3d3a 100644 --- a/src/main/java/org/opentripplanner/apis/APIEndpoints.java +++ b/src/main/java/org/opentripplanner/apis/APIEndpoints.java @@ -51,6 +51,8 @@ private APIEndpoints() { addIfEnabled(DebugUi, GraphInspectorTileResource.class); addIfEnabled(DebugUi, GraphInspectorVectorTileResource.class); addIfEnabled(GtfsGraphQlApi, GtfsGraphQLAPI.class); + // scheduled to be removed and only here for backwards compatibility + addIfEnabled(GtfsGraphQlApi, GtfsGraphQLAPI.GtfsGraphQLAPIOldPath.class); addIfEnabled(TransmodelGraphQlApi, TransmodelAPI.class); // Sandbox extension APIs diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLAPI.java b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLAPI.java index b9b93190816..fd135d05544 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLAPI.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLAPI.java @@ -1,7 +1,6 @@ package org.opentripplanner.apis.gtfs; import com.fasterxml.jackson.databind.ObjectMapper; -import graphql.ExecutionResult; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.HeaderParam; @@ -14,39 +13,40 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.Future; -import org.opentripplanner.framework.graphql.GraphQLResponseSerializer; import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@Path("/routers/{ignoreRouterId}/index/graphql") -@Produces(MediaType.APPLICATION_JSON) // One @Produces annotation for all endpoints. +@Path("/gtfs/v1/") +@Produces(MediaType.APPLICATION_JSON) public class GtfsGraphQLAPI { - @SuppressWarnings("unused") private static final Logger LOG = LoggerFactory.getLogger(GtfsGraphQLAPI.class); private final OtpServerRequestContext serverContext; private final ObjectMapper deserializer = new ObjectMapper(); - public GtfsGraphQLAPI( - @Context OtpServerRequestContext serverContext, - /** - * @deprecated The support for multiple routers are removed from OTP2. - * See https://github.com/opentripplanner/OpenTripPlanner/issues/2760 - */ - @Deprecated @PathParam("ignoreRouterId") String ignoreRouterId - ) { + public GtfsGraphQLAPI(@Context OtpServerRequestContext serverContext) { this.serverContext = serverContext; } + /** + * This class is only here for backwards-compatibility. It will be removed in the future. + */ + @Path("/routers/{ignoreRouterId}/index/graphql") + public static class GtfsGraphQLAPIOldPath extends GtfsGraphQLAPI { + + public GtfsGraphQLAPIOldPath( + @Context OtpServerRequestContext serverContext, + @PathParam("ignoreRouterId") String ignore + ) { + super(serverContext); + } + } + @POST @Consumes(MediaType.APPLICATION_JSON) public Response getGraphQL( @@ -120,64 +120,4 @@ public Response getGraphQL( GraphQLRequestContext.ofServerContext(serverContext) ); } - - @POST - @Path("/batch") - @Consumes(MediaType.APPLICATION_JSON) - public Response getGraphQLBatch( - List> queries, - @HeaderParam("OTPTimeout") @DefaultValue("30000") int timeout, - @HeaderParam("OTPMaxResolves") @DefaultValue("1000000") int maxResolves, - @Context HttpHeaders headers - ) { - List> futures = new ArrayList<>(); - Locale locale = headers.getAcceptableLanguages().size() > 0 - ? headers.getAcceptableLanguages().get(0) - : serverContext.defaultLocale(); - - for (HashMap query : queries) { - Map variables; - if (query.get("variables") instanceof Map) { - variables = (Map) query.get("variables"); - } else if ( - query.get("variables") instanceof String && ((String) query.get("variables")).length() > 0 - ) { - try { - variables = deserializer.readValue((String) query.get("variables"), Map.class); - } catch (IOException e) { - return Response - .status(Response.Status.BAD_REQUEST) - .type(MediaType.TEXT_PLAIN_TYPE) - .entity("Variables must be a valid json object") - .build(); - } - } else { - variables = null; - } - String operationName = (String) query.getOrDefault("operationName", null); - - futures.add(() -> - GtfsGraphQLIndex.getGraphQLExecutionResult( - (String) query.get("query"), - variables, - operationName, - maxResolves, - timeout, - locale, - GraphQLRequestContext.ofServerContext(serverContext) - ) - ); - } - - try { - List> results = GtfsGraphQLIndex.threadPool.invokeAll(futures); - return Response - .status(Response.Status.OK) - .entity(GraphQLResponseSerializer.serializeBatch(queries, results)) - .build(); - } catch (InterruptedException e) { - LOG.error("Batch query interrupted", e); - throw new RuntimeException(e); - } - } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/OptimizationTypeMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/OptimizationTypeMapper.java new file mode 100644 index 00000000000..1f53652f7b7 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/OptimizationTypeMapper.java @@ -0,0 +1,19 @@ +package org.opentripplanner.apis.gtfs.mapping; + +import javax.annotation.Nonnull; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.routing.core.BicycleOptimizeType; + +public final class OptimizationTypeMapper { + + @Nonnull + public static BicycleOptimizeType map(GraphQLTypes.GraphQLOptimizeType optimizeType) { + return switch (optimizeType) { + case QUICK -> BicycleOptimizeType.SHORTEST_DURATION; + case FLAT -> BicycleOptimizeType.FLAT_STREETS; + case SAFE -> BicycleOptimizeType.SAFE_STREETS; + case GREENWAYS -> BicycleOptimizeType.SAFEST_STREETS; + case TRIANGLE -> BicycleOptimizeType.TRIANGLE; + }; + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java index e7a2e7c1d1b..070eaa5d86d 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java @@ -16,13 +16,16 @@ import org.opentripplanner.api.parameter.QualifiedMode; import org.opentripplanner.api.parameter.QualifiedModeSet; import org.opentripplanner.apis.gtfs.GraphQLRequestContext; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.framework.graphql.GraphQLUtils; +import org.opentripplanner.framework.time.ZoneIdFallback; import org.opentripplanner.model.GenericLocation; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.routing.api.request.preference.ItineraryFilterDebugProfile; import org.opentripplanner.routing.api.request.preference.VehicleParkingPreferences; import org.opentripplanner.routing.api.request.preference.VehicleRentalPreferences; +import org.opentripplanner.routing.api.request.preference.VehicleWalkingPreferences; import org.opentripplanner.routing.api.request.request.filter.SelectRequest; import org.opentripplanner.routing.api.request.request.filter.TransitFilterRequest; import org.opentripplanner.routing.core.BicycleOptimizeType; @@ -55,7 +58,7 @@ public static RouteRequest toRouteRequest( request.setDateTime( environment.getArgument("date"), environment.getArgument("time"), - context.transitService().getTimeZone() + ZoneIdFallback.zoneId(context.transitService().getTimeZone()) ); callWith.argument("wheelchair", request::setWheelchair); @@ -66,15 +69,15 @@ public static RouteRequest toRouteRequest( request.withPreferences(preferences -> { preferences.withBike(bike -> { callWith.argument("bikeReluctance", bike::withReluctance); - callWith.argument("bikeWalkingReluctance", bike::withWalkingReluctance); - callWith.argument("bikeWalkingSpeed", bike::withWalkingSpeed); callWith.argument("bikeSpeed", bike::withSpeed); - callWith.argument("bikeSwitchTime", bike::withSwitchTime); - callWith.argument("bikeSwitchCost", bike::withSwitchCost); callWith.argument("bikeBoardCost", bike::withBoardCost); if (environment.getArgument("optimize") != null) { - bike.withOptimizeType(BicycleOptimizeType.valueOf(environment.getArgument("optimize"))); + bike.withOptimizeType( + OptimizationTypeMapper.map( + GraphQLTypes.GraphQLOptimizeType.valueOf(environment.getArgument("optimize")) + ) + ); } if (bike.optimizeType() == BicycleOptimizeType.TRIANGLE) { bike.withOptimizeTriangle(triangle -> { @@ -86,6 +89,7 @@ public static RouteRequest toRouteRequest( bike.withParking(parking -> setParkingPreferences(callWith, parking)); bike.withRental(rental -> setRentalPreferences(callWith, request, rental)); + bike.withWalking(walking -> setVehicleWalkingPreferences(callWith, walking)); }); preferences.withCar(car -> { @@ -331,6 +335,16 @@ private static void setRentalPreferences( ); } + private static void setVehicleWalkingPreferences( + CallerWithEnvironment callWith, + VehicleWalkingPreferences.Builder walking + ) { + callWith.argument("bikeWalkingReluctance", walking::withReluctance); + callWith.argument("bikeWalkingSpeed", walking::withSpeed); + callWith.argument("bikeSwitchTime", time -> walking.withHopTime((int) time)); + callWith.argument("bikeSwitchCost", cost -> walking.withHopCost((int) cost)); + } + private static class CallerWithEnvironment { private final DataFetchingEnvironment environment; diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapper.java index 3ccdd9007a4..1179c034e93 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapper.java @@ -27,7 +27,7 @@ public static void mapBikePreferences( "walkReluctance", r -> { bike.withReluctance((double) r); - bike.withWalkingReluctance(WALK_BIKE_RELATIVE_RELUCTANCE * (double) r); + bike.withWalking(w -> w.withReluctance(WALK_BIKE_RELATIVE_RELUCTANCE * (double) r)); } ); diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/TransitPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/TransitPreferencesMapper.java index caa8ebf7715..28643bf8199 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/TransitPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/TransitPreferencesMapper.java @@ -1,8 +1,11 @@ package org.opentripplanner.apis.transmodel.mapping.preferences; import graphql.schema.DataFetchingEnvironment; +import java.util.Map; import org.opentripplanner.apis.transmodel.model.TransportModeSlack; +import org.opentripplanner.apis.transmodel.model.plan.RelaxCostType; import org.opentripplanner.apis.transmodel.support.DataFetcherDecorator; +import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.routing.api.request.preference.TransitPreferences; public class TransitPreferencesMapper { @@ -34,8 +37,11 @@ public static void mapTransitPreferences( callWith.argument("includePlannedCancellations", transit::setIncludePlannedCancellations); callWith.argument("includeRealtimeCancellations", transit::setIncludeRealtimeCancellations); callWith.argument( - "relaxTransitPriorityGroup", - transit::withTransitGroupPriorityGeneralizedCostSlack + "relaxTransitGroupPriority", + it -> + transit.withRelaxTransitGroupPriority( + RelaxCostType.mapToDomain((Map) it, CostLinearFunction.NORMAL) + ) ); callWith.argument( "relaxTransitSearchGeneralizedCostAtDestination", diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/DefaultRouteRequestType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/DefaultRouteRequestType.java index 5e4dca2bfc2..80b6418c314 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/DefaultRouteRequestType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/DefaultRouteRequestType.java @@ -184,7 +184,7 @@ private GraphQLObjectType createGraphQLType() { .name("bikeRentalPickupTime") .description("Time to rent a bike.") .type(Scalars.GraphQLInt) - .dataFetcher(env -> preferences.bike().rental().pickupTime()) + .dataFetcher(env -> (int) preferences.bike().rental().pickupTime().toSeconds()) .build() ) .field( @@ -193,7 +193,7 @@ private GraphQLObjectType createGraphQLType() { .name("bikeRentalPickupCost") .description("Cost to rent a bike.") .type(Scalars.GraphQLInt) - .dataFetcher(env -> preferences.bike().rental().pickupCost()) + .dataFetcher(env -> preferences.bike().rental().pickupCost().toSeconds()) .build() ) .field( @@ -202,7 +202,7 @@ private GraphQLObjectType createGraphQLType() { .name("bikeRentalDropOffTime") .description("Time to drop-off a rented bike.") .type(Scalars.GraphQLInt) - .dataFetcher(env -> preferences.bike().rental().dropoffTime()) + .dataFetcher(env -> (int) preferences.bike().rental().dropOffTime().toSeconds()) .build() ) .field( @@ -211,7 +211,7 @@ private GraphQLObjectType createGraphQLType() { .name("bikeRentalDropOffCost") .description("Cost to drop-off a rented bike.") .type(Scalars.GraphQLInt) - .dataFetcher(env -> preferences.bike().rental().dropoffCost()) + .dataFetcher(env -> preferences.bike().rental().dropOffCost().toSeconds()) .build() ) .field( @@ -220,7 +220,7 @@ private GraphQLObjectType createGraphQLType() { .name("bikeParkTime") .description("Time to park a bike.") .type(Scalars.GraphQLInt) - .dataFetcher(env -> (int) preferences.bike().parking().parkTime().toSeconds()) + .dataFetcher(env -> (int) preferences.bike().parking().time().toSeconds()) .build() ) .field( @@ -229,7 +229,7 @@ private GraphQLObjectType createGraphQLType() { .name("bikeParkCost") .description("Cost to park a bike.") .type(Scalars.GraphQLInt) - .dataFetcher(env -> preferences.bike().parking().parkCost().toSeconds()) + .dataFetcher(env -> preferences.bike().parking().cost().toSeconds()) .build() ) .field( @@ -240,7 +240,7 @@ private GraphQLObjectType createGraphQLType() { "Time to park a car in a park and ride, w/o taking into account driving and walking cost." ) .type(Scalars.GraphQLInt) - .dataFetcher(env -> preferences.car().dropoffTime()) + .dataFetcher(env -> 0) .build() ) .field( diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java b/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java index 12aa19fbaea..e2897d868c9 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java @@ -64,10 +64,10 @@ public class EnumTypes { public static final GraphQLEnumType BICYCLE_OPTIMISATION_METHOD = GraphQLEnumType .newEnum() .name("BicycleOptimisationMethod") - .value("quick", BicycleOptimizeType.QUICK) - .value("safe", BicycleOptimizeType.SAFE) - .value("flat", BicycleOptimizeType.FLAT) - .value("greenways", BicycleOptimizeType.GREENWAYS) + .value("quick", BicycleOptimizeType.SHORTEST_DURATION) + .value("safe", BicycleOptimizeType.SAFE_STREETS) + .value("flat", BicycleOptimizeType.FLAT_STREETS) + .value("greenways", BicycleOptimizeType.SAFEST_STREETS) .value("triangle", BicycleOptimizeType.TRIANGLE) .build(); diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/RelaxCostType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/RelaxCostType.java index 3bd3ed129ef..41435c83a2f 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/RelaxCostType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/RelaxCostType.java @@ -2,13 +2,15 @@ import graphql.Scalars; import graphql.language.FloatValue; -import graphql.language.IntValue; import graphql.language.ObjectField; import graphql.language.ObjectValue; +import graphql.language.StringValue; import graphql.schema.GraphQLInputObjectField; import graphql.schema.GraphQLInputObjectType; -import graphql.schema.GraphQLList; -import graphql.schema.GraphQLNonNull; +import java.util.Map; +import org.opentripplanner.framework.graphql.scalar.CostScalarFactory; +import org.opentripplanner.framework.model.Cost; +import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; public class RelaxCostType { @@ -26,8 +28,8 @@ public class RelaxCostType { with twice as high cost as another one, is accepted. A `constant=$300` means a "fixed" constant is added to the limit. A `{ratio=1.0, constant=0}` is said to be the NORMAL relaxed cost - the limit is the same as the cost used to calculate the limit. The NORMAL is usually - the default. We can express the RelaxCost as a function `f(x) = constant + ratio * x`. - `f(x)=x` is the NORMAL function. + the default. We can express the RelaxCost as a function `f(t) = constant + ratio * t`. + `f(t)=t` is the NORMAL function. """ ) .field( @@ -44,11 +46,12 @@ public class RelaxCostType { .newInputObjectField() .name(CONSTANT) .description( - "The constant value to add to the limit. Must be a positive number. The unit" + - " is cost-seconds." + "The constant value to add to the limit. Must be a positive number. The value is " + + "equivalent to transit-cost-seconds. Integers are treated as seconds, but you may use " + + "the duration format. Example: '3665 = 'DT1h1m5s' = '1h1m5s'." ) - .defaultValueLiteral(IntValue.of(0)) - .type(new GraphQLList(new GraphQLNonNull(Scalars.GraphQLID))) + .defaultValueProgrammatic("0s") + .type(CostScalarFactory.costScalar()) .build() ) .build(); @@ -63,9 +66,31 @@ public static ObjectValue valueOf(CostLinearFunction value) { ObjectField .newObjectField() .name(CONSTANT) - .value(IntValue.of(value.constant().toSeconds())) + // We only use this to display the default value (this is an input type), so using + // the lenient OTP version of duration is ok - it is slightly more readable. + .value(StringValue.of(DurationUtils.durationToStr(value.constant().asDuration()))) .build() ) .build(); } + + public static CostLinearFunction mapToDomain( + Map input, + CostLinearFunction defaultValue + ) { + if (input == null || input.isEmpty()) { + return defaultValue; + } + + double ratio = 1.0; + Cost constant = Cost.ZERO; + + if (input.containsKey(RATIO)) { + ratio = (Double) input.get(RATIO); + } + if (input.containsKey(CONSTANT)) { + constant = (Cost) input.get(CONSTANT); + } + return CostLinearFunction.of(constant, ratio); + } } diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java index 243ebdc1692..abeeb187342 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java @@ -277,30 +277,30 @@ public static GraphQLFieldDefinition create( .argument( GraphQLArgument .newArgument() - .name("relaxTransitPriorityGroup") + .name("relaxTransitGroupPriority") .description( """ Relax generalized-cost when comparing trips with a different set of - transit-priority-groups. The groups are set server side for service-journey and + transit-group-priorities. The groups are set server side for service-journey and can not be configured in the API. This mainly helps to return competition neutral - services. Long distance authorities are put in different transit-priority-groups. + services. Long distance authorities are put in different transit-groups. This relaxes the comparison inside the routing engine for each stop-arrival. If two - paths have a different set of transit-priority-groups, then the generalized-cost + paths have a different set of transit-group-priorities, then the generalized-cost comparison is relaxed. The final set of paths are filtered through the normal itinerary-filters. - The `ratio` must be greater or equal to 1.0 and less then 1.2. - - The `slack` must be greater or equal to 0 and less then 3600. + - The `constant` must be greater or equal to '0s' and less then '1h'. THIS IS STILL AN EXPERIMENTAL FEATURE - IT MAY CHANGE WITHOUT ANY NOTICE! """.stripIndent() ) .type(RelaxCostType.INPUT_TYPE) .defaultValueLiteral( - preferences.transit().relaxTransitPriorityGroup().isNormal() + preferences.transit().relaxTransitGroupPriority().isNormal() ? NullValue.of() - : RelaxCostType.valueOf(preferences.transit().relaxTransitPriorityGroup()) + : RelaxCostType.valueOf(preferences.transit().relaxTransitGroupPriority()) ) .build() ) @@ -523,7 +523,7 @@ public static GraphQLFieldDefinition create( GraphQLArgument .newArgument() .name("relaxTransitSearchGeneralizedCostAtDestination") - .deprecate("This is replaced by 'relaxTransitPriorityGroup'.") + .deprecate("This is replaced by 'relaxTransitGroupPriority'.") .description( """ Whether non-optimal transit paths at the destination should be returned. Let c be the diff --git a/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java b/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java index ff933901cf8..f7ca564b149 100644 --- a/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java +++ b/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java @@ -1,11 +1,27 @@ package org.opentripplanner.apis.vectortiles; import java.util.List; -import org.opentripplanner.apis.vectortiles.model.LayerStyleBuilder; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.opentripplanner.apis.vectortiles.model.StyleBuilder; import org.opentripplanner.apis.vectortiles.model.StyleSpec; import org.opentripplanner.apis.vectortiles.model.TileSource; import org.opentripplanner.apis.vectortiles.model.TileSource.RasterSource; -import org.opentripplanner.apis.vectortiles.model.TileSource.VectorSource; +import org.opentripplanner.apis.vectortiles.model.VectorSourceLayer; +import org.opentripplanner.apis.vectortiles.model.ZoomDependentNumber; +import org.opentripplanner.apis.vectortiles.model.ZoomDependentNumber.ZoomStop; +import org.opentripplanner.service.vehiclerental.street.StreetVehicleRentalLink; +import org.opentripplanner.street.model.edge.AreaEdge; +import org.opentripplanner.street.model.edge.BoardingLocationToStopLink; +import org.opentripplanner.street.model.edge.ElevatorHopEdge; +import org.opentripplanner.street.model.edge.EscalatorEdge; +import org.opentripplanner.street.model.edge.PathwayEdge; +import org.opentripplanner.street.model.edge.StreetEdge; +import org.opentripplanner.street.model.edge.StreetTransitEntranceLink; +import org.opentripplanner.street.model.edge.StreetTransitStopLink; +import org.opentripplanner.street.model.edge.StreetVehicleParkingLink; +import org.opentripplanner.street.model.edge.TemporaryFreeEdge; +import org.opentripplanner.street.model.edge.TemporaryPartialStreetEdge; /** * A Mapbox/Mapblibre style specification for rendering debug information about transit and @@ -13,35 +29,100 @@ */ public class DebugStyleSpec { - private static final RasterSource BACKGROUND_SOURCE = new RasterSource( + private static final TileSource BACKGROUND_SOURCE = new RasterSource( "background", List.of("https://a.tile.openstreetmap.org/{z}/{x}/{y}.png"), + 19, 256, "© OpenStreetMap Contributors" ); + private static final String MAGENTA = "#f21d52"; + private static final String GREEN = "#22DD9E"; + private static final String PURPLE = "#BC55F2"; + private static final String BLACK = "#140d0e"; + private static final int MAX_ZOOM = 23; + private static final ZoomDependentNumber LINE_WIDTH = new ZoomDependentNumber( + 1.3f, + List.of(new ZoomStop(13, 0.5f), new ZoomStop(MAX_ZOOM, 10)) + ); + private static final ZoomDependentNumber CIRCLE_STROKE = new ZoomDependentNumber( + 1, + List.of(new ZoomStop(15, 0.2f), new ZoomStop(MAX_ZOOM, 3)) + ); - public record VectorSourceLayer(VectorSource vectorSource, String vectorLayer) {} - - static StyleSpec build(VectorSource debugSource, VectorSourceLayer regularStops) { - List sources = List.of(BACKGROUND_SOURCE, debugSource); + static StyleSpec build( + VectorSourceLayer regularStops, + VectorSourceLayer edges, + VectorSourceLayer vertices + ) { + var vectorSources = Stream + .of(regularStops, edges, vertices) + .map(VectorSourceLayer::vectorSource); + var allSources = Stream + .concat(Stream.of(BACKGROUND_SOURCE), vectorSources) + .collect(Collectors.toSet()); return new StyleSpec( "OTP Debug Tiles", - sources, + allSources, List.of( - LayerStyleBuilder - .ofId("background") - .typeRaster() - .source(BACKGROUND_SOURCE) - .minZoom(0) - .maxZoom(22), - LayerStyleBuilder + StyleBuilder.ofId("background").typeRaster().source(BACKGROUND_SOURCE).minZoom(0), + StyleBuilder + .ofId("edge") + .typeLine() + .vectorSourceLayer(edges) + .lineColor(MAGENTA) + .edgeFilter( + StreetEdge.class, + AreaEdge.class, + EscalatorEdge.class, + PathwayEdge.class, + ElevatorHopEdge.class, + TemporaryPartialStreetEdge.class, + TemporaryFreeEdge.class + ) + .lineWidth(LINE_WIDTH) + .minZoom(13) + .maxZoom(MAX_ZOOM) + .intiallyHidden(), + StyleBuilder + .ofId("link") + .typeLine() + .vectorSourceLayer(edges) + .lineColor(GREEN) + .edgeFilter( + StreetTransitStopLink.class, + StreetTransitEntranceLink.class, + BoardingLocationToStopLink.class, + StreetVehicleRentalLink.class, + StreetVehicleParkingLink.class + ) + .lineWidth(LINE_WIDTH) + .minZoom(13) + .maxZoom(MAX_ZOOM) + .intiallyHidden(), + StyleBuilder + .ofId("vertex") + .typeCircle() + .vectorSourceLayer(vertices) + .circleStroke(BLACK, CIRCLE_STROKE) + .circleRadius( + new ZoomDependentNumber(1, List.of(new ZoomStop(15, 1), new ZoomStop(MAX_ZOOM, 7))) + ) + .circleColor(PURPLE) + .minZoom(15) + .maxZoom(MAX_ZOOM) + .intiallyHidden(), + StyleBuilder .ofId("regular-stop") .typeCircle() .vectorSourceLayer(regularStops) - .circleStroke("#140d0e", 2) + .circleStroke(BLACK, 2) + .circleRadius( + new ZoomDependentNumber(1, List.of(new ZoomStop(11, 1), new ZoomStop(MAX_ZOOM, 10))) + ) .circleColor("#fcf9fa") - .minZoom(13) - .maxZoom(22) + .minZoom(10) + .maxZoom(MAX_ZOOM) ) ); } diff --git a/src/main/java/org/opentripplanner/apis/vectortiles/GraphInspectorVectorTileResource.java b/src/main/java/org/opentripplanner/apis/vectortiles/GraphInspectorVectorTileResource.java index 67f92f01ee3..5d0778eae6d 100644 --- a/src/main/java/org/opentripplanner/apis/vectortiles/GraphInspectorVectorTileResource.java +++ b/src/main/java/org/opentripplanner/apis/vectortiles/GraphInspectorVectorTileResource.java @@ -1,5 +1,9 @@ package org.opentripplanner.apis.vectortiles; +import static org.opentripplanner.apis.vectortiles.model.LayerType.Edge; +import static org.opentripplanner.apis.vectortiles.model.LayerType.GeofencingZones; +import static org.opentripplanner.apis.vectortiles.model.LayerType.RegularStop; +import static org.opentripplanner.apis.vectortiles.model.LayerType.Vertex; import static org.opentripplanner.framework.io.HttpUtils.APPLICATION_X_PROTOBUF; import jakarta.ws.rs.GET; @@ -28,8 +32,10 @@ import org.opentripplanner.inspector.vector.LayerBuilder; import org.opentripplanner.inspector.vector.LayerParameters; import org.opentripplanner.inspector.vector.VectorTileResponseFactory; +import org.opentripplanner.inspector.vector.edge.EdgeLayerBuilder; import org.opentripplanner.inspector.vector.geofencing.GeofencingZonesLayerBuilder; import org.opentripplanner.inspector.vector.stop.StopLayerBuilder; +import org.opentripplanner.inspector.vector.vertex.VertexLayerBuilder; import org.opentripplanner.model.FeedInfo; import org.opentripplanner.standalone.api.OtpServerRequestContext; @@ -40,19 +46,20 @@ @Path("/routers/{ignoreRouterId}/inspector/vectortile") public class GraphInspectorVectorTileResource { - private static final LayerParams REGULAR_STOPS = new LayerParams( - "regularStops", - LayerType.RegularStop - ); + private static final LayerParams REGULAR_STOPS = new LayerParams("regularStops", RegularStop); private static final LayerParams AREA_STOPS = new LayerParams("areaStops", LayerType.AreaStop); private static final LayerParams GEOFENCING_ZONES = new LayerParams( "geofencingZones", - LayerType.GeofencingZones + GeofencingZones ); + private static final LayerParams EDGES = new LayerParams("edges", Edge); + private static final LayerParams VERTICES = new LayerParams("vertices", Vertex); private static final List> DEBUG_LAYERS = List.of( REGULAR_STOPS, AREA_STOPS, - GEOFENCING_ZONES + GEOFENCING_ZONES, + EDGES, + VERTICES ); private final OtpServerRequestContext serverContext; @@ -119,19 +126,35 @@ public TileJson getTileJson( @Produces(MediaType.APPLICATION_JSON) public StyleSpec getTileJson(@Context UriInfo uri, @Context HttpHeaders headers) { var base = HttpUtils.getBaseAddress(uri, headers); - final String allLayers = DEBUG_LAYERS + + // these two could also be loaded together but are put into separate sources because + // the stops are fast and the edges are relatively slow + var stopsSource = new VectorSource( + "stops", + tileJsonUrl(base, List.of(REGULAR_STOPS, AREA_STOPS)) + ); + var streetSource = new VectorSource( + "street", + tileJsonUrl(base, List.of(EDGES, GEOFENCING_ZONES, VERTICES)) + ); + + return DebugStyleSpec.build( + REGULAR_STOPS.toVectorSourceLayer(stopsSource), + EDGES.toVectorSourceLayer(streetSource), + VERTICES.toVectorSourceLayer(streetSource) + ); + } + + private String tileJsonUrl(String base, List> layers) { + final String allLayers = layers .stream() .map(LayerParameters::name) .collect(Collectors.joining(",")); - var url = - "%s/otp/routers/%s/inspector/vectortile/%s/tilejson.json".formatted( - base, - ignoreRouterId, - allLayers - ); - - var vectorSource = new VectorSource("debug", url); - return DebugStyleSpec.build(vectorSource, REGULAR_STOPS.toVectorSourceLayer(vectorSource)); + return "%s/otp/routers/%s/inspector/vectortile/%s/tilejson.json".formatted( + base, + ignoreRouterId, + allLayers + ); } @Nonnull @@ -162,6 +185,8 @@ private static LayerBuilder createLayerBuilder( e -> context.transitService().findAreaStops(e) ); case GeofencingZones -> new GeofencingZonesLayerBuilder(context.graph(), layerParameters); + case Edge -> new EdgeLayerBuilder(context.graph(), layerParameters); + case Vertex -> new VertexLayerBuilder(context.graph(), layerParameters); }; } } diff --git a/src/main/java/org/opentripplanner/apis/vectortiles/model/LayerParams.java b/src/main/java/org/opentripplanner/apis/vectortiles/model/LayerParams.java index 7365e8972da..4ae7691db23 100644 --- a/src/main/java/org/opentripplanner/apis/vectortiles/model/LayerParams.java +++ b/src/main/java/org/opentripplanner/apis/vectortiles/model/LayerParams.java @@ -1,6 +1,5 @@ package org.opentripplanner.apis.vectortiles.model; -import org.opentripplanner.apis.vectortiles.DebugStyleSpec.VectorSourceLayer; import org.opentripplanner.apis.vectortiles.model.TileSource.VectorSource; import org.opentripplanner.inspector.vector.LayerParameters; diff --git a/src/main/java/org/opentripplanner/apis/vectortiles/model/LayerStyleBuilder.java b/src/main/java/org/opentripplanner/apis/vectortiles/model/LayerStyleBuilder.java deleted file mode 100644 index 41144611f92..00000000000 --- a/src/main/java/org/opentripplanner/apis/vectortiles/model/LayerStyleBuilder.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.opentripplanner.apis.vectortiles.model; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Stream; -import org.opentripplanner.apis.vectortiles.DebugStyleSpec.VectorSourceLayer; -import org.opentripplanner.framework.json.ObjectMappers; - -/** - * Builds a Maplibre/Mapbox vector tile - * layer style. - */ -public class LayerStyleBuilder { - - private static final ObjectMapper OBJECT_MAPPER = ObjectMappers.ignoringExtraFields(); - private static final String TYPE = "type"; - private static final String SOURCE_LAYER = "source-layer"; - private final Map props = new HashMap<>(); - private final Map paint = new HashMap<>(); - - public static LayerStyleBuilder ofId(String id) { - return new LayerStyleBuilder(id); - } - - public LayerStyleBuilder vectorSourceLayer(VectorSourceLayer source) { - source(source.vectorSource()); - return sourceLayer(source.vectorLayer()); - } - - public enum LayerType { - Circle, - Raster, - } - - private LayerStyleBuilder(String id) { - props.put("id", id); - } - - public LayerStyleBuilder minZoom(int i) { - props.put("minzoom", i); - return this; - } - - public LayerStyleBuilder maxZoom(int i) { - props.put("maxzoom", i); - return this; - } - - /** - * Which vector tile source this should apply to. - */ - public LayerStyleBuilder source(TileSource source) { - props.put("source", source.id()); - return this; - } - - /** - * For vector tile sources, specify which source layer in the tile the styles should apply to. - * There is an unfortunate collision in the name "layer" as it can both refer to a styling layer - * and the layer inside the vector tile. - */ - public LayerStyleBuilder sourceLayer(String source) { - props.put(SOURCE_LAYER, source); - return this; - } - - public LayerStyleBuilder typeRaster() { - return type(LayerType.Raster); - } - - public LayerStyleBuilder typeCircle() { - return type(LayerType.Circle); - } - - private LayerStyleBuilder type(LayerType type) { - props.put(TYPE, type.name().toLowerCase()); - return this; - } - - public LayerStyleBuilder circleColor(String color) { - paint.put("circle-color", validateColor(color)); - return this; - } - - public LayerStyleBuilder circleStroke(String color, int width) { - paint.put("circle-stroke-color", validateColor(color)); - paint.put("circle-stroke-width", width); - return this; - } - - public JsonNode toJson() { - validate(); - - var copy = new HashMap<>(props); - if (!paint.isEmpty()) { - copy.put("paint", paint); - } - return OBJECT_MAPPER.valueToTree(copy); - } - - private String validateColor(String color) { - if (!color.startsWith("#")) { - throw new IllegalArgumentException("Colors must start with '#'"); - } - return color; - } - - private void validate() { - Stream - .of(TYPE) - .forEach(p -> Objects.requireNonNull(props.get(p), "%s must be set".formatted(p))); - } -} diff --git a/src/main/java/org/opentripplanner/apis/vectortiles/model/LayerType.java b/src/main/java/org/opentripplanner/apis/vectortiles/model/LayerType.java index f4cb7a636fa..ece75f29b60 100644 --- a/src/main/java/org/opentripplanner/apis/vectortiles/model/LayerType.java +++ b/src/main/java/org/opentripplanner/apis/vectortiles/model/LayerType.java @@ -4,4 +4,6 @@ public enum LayerType { RegularStop, AreaStop, GeofencingZones, + Edge, + Vertex, } diff --git a/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java b/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java new file mode 100644 index 00000000000..3b2f36d3156 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java @@ -0,0 +1,184 @@ +package org.opentripplanner.apis.vectortiles.model; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; +import org.opentripplanner.framework.collection.ListUtils; +import org.opentripplanner.framework.json.ObjectMappers; +import org.opentripplanner.street.model.edge.Edge; + +/** + * Builds a Maplibre/Mapbox vector tile + * layer style. + */ +public class StyleBuilder { + + private static final ObjectMapper OBJECT_MAPPER = ObjectMappers.ignoringExtraFields(); + private static final String TYPE = "type"; + private static final String SOURCE_LAYER = "source-layer"; + private final Map props = new LinkedHashMap<>(); + private final Map paint = new LinkedHashMap<>(); + private final Map layout = new LinkedHashMap<>(); + private final Map line = new LinkedHashMap<>(); + private List filter = List.of(); + + public static StyleBuilder ofId(String id) { + return new StyleBuilder(id); + } + + public StyleBuilder vectorSourceLayer(VectorSourceLayer source) { + source(source.vectorSource()); + return sourceLayer(source.vectorLayer()); + } + + public enum LayerType { + Circle, + Line, + Raster, + } + + private StyleBuilder(String id) { + props.put("id", id); + } + + public StyleBuilder minZoom(int i) { + props.put("minzoom", i); + return this; + } + + public StyleBuilder maxZoom(int i) { + props.put("maxzoom", i); + return this; + } + + /** + * Which vector tile source this should apply to. + */ + public StyleBuilder source(TileSource source) { + props.put("source", source.id()); + return this; + } + + /** + * For vector tile sources, specify which source layer in the tile the styles should apply to. + * There is an unfortunate collision in the name "layer" as it can both refer to a styling layer + * and the layer inside the vector tile. + */ + public StyleBuilder sourceLayer(String source) { + props.put(SOURCE_LAYER, source); + return this; + } + + public StyleBuilder typeRaster() { + return type(LayerType.Raster); + } + + public StyleBuilder typeCircle() { + return type(LayerType.Circle); + } + + public StyleBuilder typeLine() { + type(LayerType.Line); + layout.put("line-cap", "round"); + return this; + } + + private StyleBuilder type(LayerType type) { + props.put(TYPE, type.name().toLowerCase()); + return this; + } + + public StyleBuilder circleColor(String color) { + paint.put("circle-color", validateColor(color)); + return this; + } + + public StyleBuilder circleStroke(String color, int width) { + paint.put("circle-stroke-color", validateColor(color)); + paint.put("circle-stroke-width", width); + return this; + } + + public StyleBuilder circleStroke(String color, ZoomDependentNumber width) { + paint.put("circle-stroke-color", validateColor(color)); + paint.put("circle-stroke-width", width.toJson()); + return this; + } + + public StyleBuilder circleRadius(ZoomDependentNumber radius) { + paint.put("circle-radius", radius.toJson()); + return this; + } + + // Line styling + + public StyleBuilder lineColor(String color) { + paint.put("line-color", validateColor(color)); + return this; + } + + public StyleBuilder lineWidth(float width) { + paint.put("line-width", width); + return this; + } + + public StyleBuilder lineWidth(ZoomDependentNumber zoomStops) { + paint.put("line-width", zoomStops.toJson()); + return this; + } + + /** + * Hide this layer when the debug client starts. It can be made visible in the UI later. + */ + public StyleBuilder intiallyHidden() { + layout.put("visibility", "none"); + return this; + } + + /** + * Only apply the style to the given edges. + */ + @SafeVarargs + public final StyleBuilder edgeFilter(Class... classToFilter) { + var clazzes = Arrays.stream(classToFilter).map(Class::getSimpleName).toList(); + filter = ListUtils.combine(List.of("in", "class"), clazzes); + return this; + } + + public JsonNode toJson() { + validate(); + + var copy = new LinkedHashMap<>(props); + if (!paint.isEmpty()) { + copy.put("paint", paint); + } + if (!filter.isEmpty()) { + copy.put("filter", filter); + } + if (!layout.isEmpty()) { + copy.put("layout", layout); + } + if (!line.isEmpty()) { + copy.put("line", line); + } + return OBJECT_MAPPER.valueToTree(copy); + } + + private String validateColor(String color) { + if (!color.startsWith("#")) { + throw new IllegalArgumentException("Colors must start with '#'"); + } + return color; + } + + private void validate() { + Stream + .of(TYPE) + .forEach(p -> Objects.requireNonNull(props.get(p), "%s must be set".formatted(p))); + } +} diff --git a/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleSpec.java b/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleSpec.java index 84e19f25364..64f680ed202 100644 --- a/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleSpec.java +++ b/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleSpec.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -15,13 +16,13 @@ public final class StyleSpec { private final String name; - private final List sources; + private final Collection sources; private final List layers; - public StyleSpec(String name, List sources, List layers) { + public StyleSpec(String name, Collection sources, List layers) { this.name = name; this.sources = sources; - this.layers = layers.stream().map(LayerStyleBuilder::toJson).toList(); + this.layers = layers.stream().map(StyleBuilder::toJson).toList(); } @JsonSerialize diff --git a/src/main/java/org/opentripplanner/apis/vectortiles/model/TileSource.java b/src/main/java/org/opentripplanner/apis/vectortiles/model/TileSource.java index 06af294a4f0..088ce63d10a 100644 --- a/src/main/java/org/opentripplanner/apis/vectortiles/model/TileSource.java +++ b/src/main/java/org/opentripplanner/apis/vectortiles/model/TileSource.java @@ -26,7 +26,7 @@ public String type() { * Represents a raster-based source for map tiles. These are used mainly for background * map layers with vector data being rendered on top of it. */ - record RasterSource(String id, List tiles, int tileSize, String attribution) + record RasterSource(String id, List tiles, int maxzoom, int tileSize, String attribution) implements TileSource { @Override public String type() { diff --git a/src/main/java/org/opentripplanner/apis/vectortiles/model/VectorSourceLayer.java b/src/main/java/org/opentripplanner/apis/vectortiles/model/VectorSourceLayer.java new file mode 100644 index 00000000000..61f1b852d5a --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/vectortiles/model/VectorSourceLayer.java @@ -0,0 +1,8 @@ +package org.opentripplanner.apis.vectortiles.model; + +/** + * A vector source layer for use in a Maplibre style spec. It contains both the name of the layer + * inside the tile and a reference to the source (which in turn has the URL where to fetch the + * data). + */ +public record VectorSourceLayer(TileSource.VectorSource vectorSource, String vectorLayer) {} diff --git a/src/main/java/org/opentripplanner/apis/vectortiles/model/ZoomDependentNumber.java b/src/main/java/org/opentripplanner/apis/vectortiles/model/ZoomDependentNumber.java new file mode 100644 index 00000000000..d83c4b63495 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/vectortiles/model/ZoomDependentNumber.java @@ -0,0 +1,31 @@ +package org.opentripplanner.apis.vectortiles.model; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.LinkedHashMap; +import java.util.List; +import org.opentripplanner.framework.json.ObjectMappers; + +/** + * A style parameter that allows you to specify a number that changes dependent on the zoom level. + */ +public record ZoomDependentNumber(float base, List stops) { + private static final ObjectMapper OBJECT_MAPPER = ObjectMappers.ignoringExtraFields(); + public JsonNode toJson() { + var props = new LinkedHashMap<>(); + props.put("base", base); + var vals = stops.stream().map(ZoomStop::toList).toList(); + props.put("stops", vals); + return OBJECT_MAPPER.valueToTree(props); + } + + /** + * @param zoom The zoom level. + * @param value What the value should be at the specified zoom. + */ + public record ZoomStop(int zoom, float value) { + public List toList() { + return List.of(zoom, value); + } + } +} diff --git a/src/main/java/org/opentripplanner/framework/graphql/GraphQLResponseSerializer.java b/src/main/java/org/opentripplanner/framework/graphql/GraphQLResponseSerializer.java index cb3b146a113..1497f63285d 100644 --- a/src/main/java/org/opentripplanner/framework/graphql/GraphQLResponseSerializer.java +++ b/src/main/java/org/opentripplanner/framework/graphql/GraphQLResponseSerializer.java @@ -33,30 +33,4 @@ public static String serialize(ExecutionResult executionResult) { throw new RuntimeException(e); } } - - public static String serializeBatch( - List> queries, - List> futures - ) { - List> responses = new LinkedList<>(); - for (int i = 0; i < queries.size(); i++) { - ExecutionResult executionResult; - // Try each request separately, returning both completed and failed responses is ok - try { - executionResult = futures.get(i).get(); - } catch (InterruptedException | ExecutionException e) { - executionResult = new AbortExecutionException(e).toExecutionResult(); - } - responses.add( - Map.of("id", queries.get(i).get("id"), "payload", executionResult.toSpecification()) - ); - } - - try { - return objectMapper.writeValueAsString(responses); - } catch (JsonProcessingException e) { - LOG.error("Unable to serialize response", e); - throw new RuntimeException(e); - } - } } diff --git a/src/main/java/org/opentripplanner/framework/graphql/scalar/CostScalarFactory.java b/src/main/java/org/opentripplanner/framework/graphql/scalar/CostScalarFactory.java new file mode 100644 index 00000000000..6f710328174 --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/graphql/scalar/CostScalarFactory.java @@ -0,0 +1,83 @@ +package org.opentripplanner.framework.graphql.scalar; + +import graphql.GraphQLContext; +import graphql.execution.CoercedVariables; +import graphql.language.StringValue; +import graphql.language.Value; +import graphql.schema.Coercing; +import graphql.schema.CoercingParseLiteralException; +import graphql.schema.CoercingParseValueException; +import graphql.schema.GraphQLScalarType; +import java.util.Locale; +import java.util.NoSuchElementException; +import javax.annotation.Nonnull; +import org.opentripplanner.framework.model.Cost; +import org.opentripplanner.framework.time.DurationUtils; + +public class CostScalarFactory { + + private static final String TYPENAME = "Cost"; + + private static final String DOCUMENTATION = + "A cost value, normally a value of 1 is equivalent to riding transit for 1 second, " + + "but it might not depending on the use-case. Format: 3665 = DT1h1m5s = 1h1m5s"; + + private static final GraphQLScalarType SCALAR_INSTANCE = createCostScalar(); + + private CostScalarFactory() {} + + public static GraphQLScalarType costScalar() { + return SCALAR_INSTANCE; + } + + private static GraphQLScalarType createCostScalar() { + return GraphQLScalarType + .newScalar() + .name(TYPENAME) + .description(DOCUMENTATION) + .coercing(createCoercing()) + .build(); + } + + private static String serializeCost(Cost cost) { + return cost.asDuration().toString(); + } + + private static Cost parseCost(String input) throws CoercingParseValueException { + try { + return Cost.fromDuration(DurationUtils.parseSecondsOrDuration(input).orElseThrow()); + } catch (IllegalArgumentException | NoSuchElementException e) { + throw new CoercingParseValueException(e.getMessage(), e); + } + } + + private static Coercing createCoercing() { + return new Coercing<>() { + @Override + public String serialize(@Nonnull Object result, GraphQLContext c, Locale l) { + return serializeCost((Cost) result); + } + + @Override + public Cost parseValue(Object input, GraphQLContext c, Locale l) + throws CoercingParseValueException { + return parseCost((String) input); + } + + @Override + public Cost parseLiteral(Value input, CoercedVariables v, GraphQLContext c, Locale l) + throws CoercingParseLiteralException { + if (input instanceof StringValue stringValue) { + return parseCost(stringValue.getValue()); + } + return null; + } + + @Override + @Nonnull + public Value valueToLiteral(Object input, GraphQLContext c, Locale l) { + return StringValue.of((String) input); + } + }; + } +} diff --git a/src/main/java/org/opentripplanner/framework/logging/Throttle.java b/src/main/java/org/opentripplanner/framework/logging/Throttle.java index 47417aa974a..631d59a2697 100644 --- a/src/main/java/org/opentripplanner/framework/logging/Throttle.java +++ b/src/main/java/org/opentripplanner/framework/logging/Throttle.java @@ -35,6 +35,10 @@ public static Throttle ofOneSecond() { return new Throttle(1000); } + public static Throttle ofOneMinute() { + return new Throttle(1000 * 60); + } + public String setupInfo() { return setupInfo; } diff --git a/src/main/java/org/opentripplanner/framework/time/ZoneIdFallback.java b/src/main/java/org/opentripplanner/framework/time/ZoneIdFallback.java new file mode 100644 index 00000000000..2d43216e4b2 --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/time/ZoneIdFallback.java @@ -0,0 +1,39 @@ +package org.opentripplanner.framework.time; + +import java.time.ZoneId; +import javax.annotation.Nullable; +import org.opentripplanner.framework.logging.Throttle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class provides a fallback mechanism for retrieving a zone id (=time zone). + * If a ZoneId is not provided, it returns a default fallback ZoneId (UTC). + *

+ * This situation happens when you don't load any transit data into the graph but want to route + * anyway, perhaps only on the street network. + */ +public class ZoneIdFallback { + + private static final Logger LOG = LoggerFactory.getLogger(ZoneIdFallback.class); + private static final ZoneId FALLBACK = ZoneId.of("UTC"); + private static final Throttle THROTTLE = Throttle.ofOneMinute(); + + /** + * Accepts a nullable zone id (time zone) and returns UTC as the fallback. + */ + public static ZoneId zoneId(@Nullable ZoneId id) { + if (id == null) { + THROTTLE.throttle(() -> { + LOG.warn( + "Your instance doesn't contain a time zone (which is usually derived from transit data). Assuming {}.", + FALLBACK + ); + LOG.warn("Please double-check that transit data was correctly loaded."); + }); + return FALLBACK; + } else { + return id; + } + } +} diff --git a/src/main/java/org/opentripplanner/inspector/vector/edge/EdgeLayerBuilder.java b/src/main/java/org/opentripplanner/inspector/vector/edge/EdgeLayerBuilder.java new file mode 100644 index 00000000000..3823f91f039 --- /dev/null +++ b/src/main/java/org/opentripplanner/inspector/vector/edge/EdgeLayerBuilder.java @@ -0,0 +1,37 @@ +package org.opentripplanner.inspector.vector.edge; + +import java.util.List; +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.geom.Geometry; +import org.opentripplanner.inspector.vector.LayerBuilder; +import org.opentripplanner.inspector.vector.LayerParameters; +import org.opentripplanner.routing.graph.Graph; +import org.opentripplanner.routing.graph.index.StreetIndex; +import org.opentripplanner.street.model.edge.Edge; + +/** + * Selects all edges to be displayed for debugging. + */ +public class EdgeLayerBuilder extends LayerBuilder { + + private final StreetIndex streetIndex; + + public EdgeLayerBuilder(Graph graph, LayerParameters layerParameters) { + super(new EdgePropertyMapper(), layerParameters.name(), layerParameters.expansionFactor()); + this.streetIndex = graph.getStreetIndex(); + } + + @Override + protected List getGeometries(Envelope query) { + return streetIndex + .getEdgesForEnvelope(query) + .stream() + .filter(e -> e.getGeometry() != null) + .map(edge -> { + Geometry geometry = edge.getGeometry(); + geometry.setUserData(edge); + return geometry; + }) + .toList(); + } +} diff --git a/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java b/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java new file mode 100644 index 00000000000..fb65d0b5d3b --- /dev/null +++ b/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java @@ -0,0 +1,31 @@ +package org.opentripplanner.inspector.vector.edge; + +import static org.opentripplanner.framework.lang.DoubleUtils.roundTo2Decimals; +import static org.opentripplanner.inspector.vector.KeyValue.kv; + +import java.util.Collection; +import java.util.List; +import org.opentripplanner.apis.support.mapping.PropertyMapper; +import org.opentripplanner.framework.collection.ListUtils; +import org.opentripplanner.inspector.vector.KeyValue; +import org.opentripplanner.street.model.edge.Edge; +import org.opentripplanner.street.model.edge.EscalatorEdge; +import org.opentripplanner.street.model.edge.StreetEdge; + +public class EdgePropertyMapper extends PropertyMapper { + + @Override + protected Collection map(Edge input) { + List baseProps = List.of(kv("class", input.getClass().getSimpleName())); + List properties = + switch (input) { + case StreetEdge e -> List.of( + kv("permission", e.getPermission().toString()), + kv("bicycleSafetyFactor", roundTo2Decimals(e.getBicycleSafetyFactor())) + ); + case EscalatorEdge e -> List.of(kv("distance", e.getDistanceMeters())); + default -> List.of(); + }; + return ListUtils.combine(baseProps, properties); + } +} diff --git a/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexLayerBuilder.java b/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexLayerBuilder.java new file mode 100644 index 00000000000..0b3bb79b72e --- /dev/null +++ b/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexLayerBuilder.java @@ -0,0 +1,37 @@ +package org.opentripplanner.inspector.vector.vertex; + +import java.util.List; +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.geom.Geometry; +import org.opentripplanner.framework.geometry.GeometryUtils; +import org.opentripplanner.inspector.vector.LayerBuilder; +import org.opentripplanner.inspector.vector.LayerParameters; +import org.opentripplanner.routing.graph.Graph; +import org.opentripplanner.routing.graph.index.StreetIndex; +import org.opentripplanner.street.model.vertex.Vertex; + +/** + * Selects all vertices to be displayed for debugging. + */ +public class VertexLayerBuilder extends LayerBuilder { + + private final StreetIndex streetIndex; + + public VertexLayerBuilder(Graph graph, LayerParameters layerParameters) { + super(new VertexPropertyMapper(), layerParameters.name(), layerParameters.expansionFactor()); + this.streetIndex = graph.getStreetIndex(); + } + + @Override + protected List getGeometries(Envelope query) { + return streetIndex + .getVerticesForEnvelope(query) + .stream() + .map(vertex -> { + Geometry geometry = GeometryUtils.getGeometryFactory().createPoint(vertex.getCoordinate()); + geometry.setUserData(vertex); + return geometry; + }) + .toList(); + } +} diff --git a/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexPropertyMapper.java b/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexPropertyMapper.java new file mode 100644 index 00000000000..a493269cc3b --- /dev/null +++ b/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexPropertyMapper.java @@ -0,0 +1,30 @@ +package org.opentripplanner.inspector.vector.vertex; + +import static org.opentripplanner.inspector.vector.KeyValue.kv; + +import java.util.Collection; +import java.util.List; +import org.opentripplanner.apis.support.mapping.PropertyMapper; +import org.opentripplanner.framework.collection.ListUtils; +import org.opentripplanner.inspector.vector.KeyValue; +import org.opentripplanner.service.vehiclerental.street.VehicleRentalPlaceVertex; +import org.opentripplanner.street.model.vertex.BarrierVertex; +import org.opentripplanner.street.model.vertex.Vertex; + +public class VertexPropertyMapper extends PropertyMapper { + + @Override + protected Collection map(Vertex input) { + List baseProps = List.of( + kv("class", input.getClass().getSimpleName()), + kv("label", input.getLabel().toString()) + ); + List properties = + switch (input) { + case BarrierVertex v -> List.of(kv("permission", v.getBarrierPermissions().toString())); + case VehicleRentalPlaceVertex v -> List.of(kv("rentalId", v.getStation().getId())); + default -> List.of(); + }; + return ListUtils.combine(baseProps, properties); + } +} diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index d1252c45f2d..903e04c8b0c 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -388,7 +388,7 @@ public List getLegs() { *

* NOTE: The itinerary is mutable so the transformation is done in-place! */ - public Itinerary transformTransitLegs(Function mapper) { + public void transformTransitLegs(Function mapper) { legs = legs .stream() @@ -400,7 +400,6 @@ public Itinerary transformTransitLegs(Function mapper) { } }) .toList(); - return this; } public Stream getStreetLegs() { diff --git a/src/main/java/org/opentripplanner/model/plan/ItinerarySortKey.java b/src/main/java/org/opentripplanner/model/plan/ItinerarySortKey.java index b9165270444..0ad62927d9a 100644 --- a/src/main/java/org/opentripplanner/model/plan/ItinerarySortKey.java +++ b/src/main/java/org/opentripplanner/model/plan/ItinerarySortKey.java @@ -2,13 +2,13 @@ import java.time.Instant; import org.opentripplanner.framework.tostring.ValueObjectToStringBuilder; -import org.opentripplanner.routing.algorithm.filterchain.comparator.SortOrderComparator; -import org.opentripplanner.routing.algorithm.filterchain.filter.SortingFilter; /** * This interface is used to sort itineraries and other instances that we might want to sort among - * itineraries. It is used in the {@link SortingFilter} as defined by the - * {@link SortOrderComparator}. + * itineraries. It is used in the + * {@link org.opentripplanner.routing.algorithm.filterchain.framework.filter.SortingFilter} + * as defined by the + * {@link org.opentripplanner.routing.algorithm.filterchain.framework.sort.SortOrderComparator}. *

* The methods in this interface are NOT documented here, but in the Itinerary class. To keep it simple, this * interface should be kept in sync with method names in the itinerary. diff --git a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java index 8fc30ae166a..30bd04eea73 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java @@ -11,7 +11,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.model.StopTime; import org.opentripplanner.model.calendar.ServiceCalendar; diff --git a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java index f0d06b8871d..765a9b5c47f 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java @@ -26,7 +26,6 @@ import org.opentripplanner.transit.model.framework.ImmutableEntityById; import org.opentripplanner.transit.model.network.StopPattern; import org.opentripplanner.transit.model.network.TripPattern; -import org.opentripplanner.transit.model.network.TripPatternBuilder; import org.opentripplanner.transit.model.organization.Operator; import org.opentripplanner.transit.model.site.AreaStop; import org.opentripplanner.transit.model.site.GroupStop; diff --git a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapperResult.java b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapperResult.java index f2f75cd2561..370c24a7a41 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapperResult.java +++ b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapperResult.java @@ -1,13 +1,10 @@ package org.opentripplanner.netex.mapping; import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import org.opentripplanner.model.StopTime; -import org.opentripplanner.transit.model.network.StopPattern; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripOnServiceDate; diff --git a/src/main/java/org/opentripplanner/raptor/api/model/RaptorTripPattern.java b/src/main/java/org/opentripplanner/raptor/api/model/RaptorTripPattern.java index 98a2533486b..e5be1cab0d5 100644 --- a/src/main/java/org/opentripplanner/raptor/api/model/RaptorTripPattern.java +++ b/src/main/java/org/opentripplanner/raptor/api/model/RaptorTripPattern.java @@ -46,7 +46,7 @@ public interface RaptorTripPattern { int slackIndex(); /** - * A pattern may belong to a transit-priority-group. Each group is given an advantage during + * A pattern may belong to a transit-group-priority. Each group is given an advantage during * the multi-criteria search, so the best alternative for each group is found. */ int priorityGroupId(); diff --git a/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java b/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java index 64cd9dae1a5..368b4660922 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java @@ -18,7 +18,7 @@ public class MultiCriteriaRequest { private final RelaxFunction relaxC1; @Nullable - private final RaptorTransitPriorityGroupCalculator transitPriorityCalculator; + private final RaptorTransitGroupCalculator transitPriorityCalculator; private final List passThroughPoints; @@ -63,7 +63,7 @@ public RelaxFunction relaxC1() { return relaxC1; } - public Optional transitPriorityCalculator() { + public Optional transitPriorityCalculator() { return Optional.ofNullable(transitPriorityCalculator); } @@ -140,7 +140,7 @@ public static class Builder { private final MultiCriteriaRequest original; private RelaxFunction relaxC1; - private RaptorTransitPriorityGroupCalculator transitPriorityCalculator; + private RaptorTransitGroupCalculator transitPriorityCalculator; private List passThroughPoints; private Double relaxCostAtDestination; @@ -163,11 +163,11 @@ public Builder withRelaxC1(RelaxFunction relaxC1) { } @Nullable - public RaptorTransitPriorityGroupCalculator transitPriorityCalculator() { + public RaptorTransitGroupCalculator transitPriorityCalculator() { return transitPriorityCalculator; } - public Builder withTransitPriorityCalculator(RaptorTransitPriorityGroupCalculator value) { + public Builder withTransitPriorityCalculator(RaptorTransitGroupCalculator value) { transitPriorityCalculator = value; return this; } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/RaptorTransitPriorityGroupCalculator.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorTransitGroupCalculator.java similarity index 71% rename from src/main/java/org/opentripplanner/raptor/api/request/RaptorTransitPriorityGroupCalculator.java rename to src/main/java/org/opentripplanner/raptor/api/request/RaptorTransitGroupCalculator.java index c3890fa47b3..b5f0598415e 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/RaptorTransitPriorityGroupCalculator.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorTransitGroupCalculator.java @@ -2,19 +2,19 @@ import org.opentripplanner.raptor.api.model.DominanceFunction; -public interface RaptorTransitPriorityGroupCalculator { +public interface RaptorTransitGroupCalculator { /** - * Merge in the trip transit priority group id with an existing set. Note! Both the set + * Merge in the transit group id with an existing set. Note! Both the set * and the group id type is {@code int}. * * @param currentGroupIds the set of groupIds for all legs in a path. * @param boardingGroupId the transit group id to add to the given set. * @return the new computed set of groupIds */ - int mergeTransitPriorityGroupIds(int currentGroupIds, int boardingGroupId); + int mergeGroupIds(int currentGroupIds, int boardingGroupId); /** - * This is the dominance function to use for comparing transit-priority-groupIds. + * This is the dominance function to use for comparing transit-groups. * It is critical that the implementation is "static" so it can be inlined, since it * is run in the innermost loop of Raptor. */ diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java index 060d3a2e018..951721f81a7 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java @@ -43,14 +43,14 @@ public class SystemErrDebugLogger implements DebugLogger { private final Table arrivalTable = Table .of() .withAlights(Center, Center, Right, Right, Right, Right, Left, Left) - .withHeaders("ARRIVAL", "LEG", "RND", "STOP", "ARRIVE", "COST", "TRIP", "DETAILS") + .withHeaders("ARRIVAL", "LEG", "RND", "STOP", "ARRIVE", "C₁", "TRIP", "DETAILS") .withMinWidths(9, 7, 3, 5, 8, 9, 24, 0) .build(); private final Table pathTable = Table .of() .withAlights(Center, Center, Right, Right, Right, Right, Right, Right, Left) - .withHeaders(">>> PATH", "TR", "FROM", "TO", "START", "END", "DURATION", "COST", "DETAILS") - .withMinWidths(9, 2, 5, 5, 8, 8, 8, 6, 0) + .withHeaders(">>> PATH", "TR", "FROM", "TO", "START", "END", "DURATION", "C₁", "DETAILS") + .withMinWidths(9, 2, 5, 5, 8, 8, 8, 9, 0) .build(); private boolean forwardSearch = true; private int lastIterationTime = NOT_SET; @@ -112,6 +112,7 @@ public void pathFilteringListener(DebugEvent> e) { RaptorPath p = e.element(); var aLeg = p.accessLeg(); var eLeg = p.egressLeg(); + println( pathTable.rowAsText( e.action().toString(), diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/PassThroughPointsService.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/PassThroughPointsService.java index 69899b55688..4932d9c46fe 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/PassThroughPointsService.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/PassThroughPointsService.java @@ -54,7 +54,7 @@ default boolean isNoop() { void updateC2Value(int currentPathC2, IntConsumer update); /** - * This is the dominance function to use for comparing transit-priority-groupIds. + * This is the dominance function to use for comparing transit-group-priorityIds. * It is critical that the implementation is "static" so it can be inlined, since it * is run in the innermost loop of Raptor. */ diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java index e7aa07fb914..8eef90950dd 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java @@ -6,7 +6,7 @@ import org.opentripplanner.raptor.api.model.DominanceFunction; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.request.MultiCriteriaRequest; -import org.opentripplanner.raptor.api.request.RaptorTransitPriorityGroupCalculator; +import org.opentripplanner.raptor.api.request.RaptorTransitGroupCalculator; import org.opentripplanner.raptor.rangeraptor.context.SearchContext; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetCost; @@ -29,7 +29,7 @@ import org.opentripplanner.raptor.rangeraptor.multicriteria.ride.c1.PatternRideC1; import org.opentripplanner.raptor.rangeraptor.multicriteria.ride.c2.PassThroughRideFactory; import org.opentripplanner.raptor.rangeraptor.multicriteria.ride.c2.PatternRideC2; -import org.opentripplanner.raptor.rangeraptor.multicriteria.ride.c2.TransitPriorityGroupRideFactory; +import org.opentripplanner.raptor.rangeraptor.multicriteria.ride.c2.TransitGroupPriorityRideFactory; import org.opentripplanner.raptor.rangeraptor.path.DestinationArrivalPaths; import org.opentripplanner.raptor.rangeraptor.path.configure.PathConfig; import org.opentripplanner.raptor.util.paretoset.ParetoComparator; @@ -173,7 +173,8 @@ private MultiCriteriaRequest mcRequest() { } /** - * Currently "transit-priority-groups" is the only feature using two multi-criteria(c2). + * Use c2 in the search, this is use-case specific. For example the pass-through or + * transit-group-priority features uses the c2 value. */ private boolean includeC2() { return mcRequest().includeC2(); @@ -184,7 +185,7 @@ private PatternRideFactory> createPatternRideC2Factory() { return new PassThroughRideFactory<>(passThroughPointsService); } if (isTransitPriority()) { - return new TransitPriorityGroupRideFactory<>(getTransitPriorityGroupCalculator()); + return new TransitGroupPriorityRideFactory<>(getTransitGroupPriorityCalculator()); } throw new IllegalStateException("Only pass-through and transit-priority uses c2."); } @@ -195,12 +196,12 @@ private DominanceFunction dominanceFunctionC2() { return passThroughPointsService.dominanceFunction(); } if (isTransitPriority()) { - return getTransitPriorityGroupCalculator().dominanceFunction(); + return getTransitGroupPriorityCalculator().dominanceFunction(); } return null; } - private RaptorTransitPriorityGroupCalculator getTransitPriorityGroupCalculator() { + private RaptorTransitGroupCalculator getTransitGroupPriorityCalculator() { return mcRequest().transitPriorityCalculator().orElseThrow(); } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c2/TransitPriorityGroupRideFactory.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c2/TransitGroupPriorityRideFactory.java similarity index 67% rename from src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c2/TransitPriorityGroupRideFactory.java rename to src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c2/TransitGroupPriorityRideFactory.java index eca049233b9..5d65c40d021 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c2/TransitPriorityGroupRideFactory.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c2/TransitGroupPriorityRideFactory.java @@ -2,25 +2,25 @@ import org.opentripplanner.raptor.api.model.RaptorTripPattern; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; -import org.opentripplanner.raptor.api.request.RaptorTransitPriorityGroupCalculator; +import org.opentripplanner.raptor.api.request.RaptorTransitGroupCalculator; import org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.McStopArrival; import org.opentripplanner.raptor.rangeraptor.multicriteria.ride.PatternRide; import org.opentripplanner.raptor.rangeraptor.multicriteria.ride.PatternRideFactory; /** - * This factory creates new {@link PatternRide}s and merge in transit-priority-group ids + * This factory creates new {@link PatternRide}s and merge in transit-group-priority ids * into c2. */ -public class TransitPriorityGroupRideFactory +public class TransitGroupPriorityRideFactory implements PatternRideFactory> { private int currentPatternGroupPriority; - private final RaptorTransitPriorityGroupCalculator transitPriorityGroupCalculator; + private final RaptorTransitGroupCalculator transitGroupPriorityCalculator; - public TransitPriorityGroupRideFactory( - RaptorTransitPriorityGroupCalculator transitPriorityGroupCalculator + public TransitGroupPriorityRideFactory( + RaptorTransitGroupCalculator transitGroupPriorityCalculator ) { - this.transitPriorityGroupCalculator = transitPriorityGroupCalculator; + this.transitGroupPriorityCalculator = transitGroupPriorityCalculator; } @Override @@ -52,12 +52,9 @@ public void prepareForTransitWith(RaptorTripPattern pattern) { } /** - * Currently transit-priority-group is the only usage of c2 + * Currently transit-group-priority is the only usage of c2 */ private int calculateC2(int c2) { - return transitPriorityGroupCalculator.mergeTransitPriorityGroupIds( - c2, - currentPatternGroupPriority - ); + return transitGroupPriorityCalculator.mergeGroupIds(c2, currentPatternGroupPriority); } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessEgressFunctions.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessEgressFunctions.java index 5059be84312..1ab737ae907 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessEgressFunctions.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessEgressFunctions.java @@ -5,12 +5,15 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.function.Predicate; import java.util.function.ToIntFunction; import java.util.stream.Collectors; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.util.paretoset.ParetoComparator; import org.opentripplanner.raptor.util.paretoset.ParetoSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This class contains functions used by the {@link AccessPaths} and {@link EgressPaths} classes. @@ -23,8 +26,10 @@ */ public final class AccessEgressFunctions { + private static final Logger LOG = LoggerFactory.getLogger(AccessEgressFunctions.class); + /** - * Filter standard(not multi-criteria) Raptor access and egress paths. A path is pareto optimal + * Filter standard (not multi-criteria) Raptor access and egress paths. A path is pareto optimal * for a given stop if *

    *
  1. @@ -47,32 +52,57 @@ public final class AccessEgressFunctions { *
*/ private static final ParetoComparator STANDARD_COMPARATOR = (l, r) -> - (l.stopReachedOnBoard() && !r.stopReachedOnBoard()) || - r.hasOpeningHours() || - (l.numberOfRides() < r.numberOfRides()) || - (l.durationInSeconds() < r.durationInSeconds()); + ( + (l.stopReachedOnBoard() && !r.stopReachedOnBoard()) || + r.hasOpeningHours() || + l.numberOfRides() < r.numberOfRides() || + l.durationInSeconds() < r.durationInSeconds() + ); + + /** + * Filter Multi-criteria Raptor access and egress paths. This can be used to wash + * access/egress paths - paths that are not optimal using this should not be passed into + * Raptor - it is a bug. + */ + private static final ParetoComparator MC_COMPARATOR = (l, r) -> + STANDARD_COMPARATOR.leftDominanceExist(l, r) || l.c1() < r.c1(); /** private constructor to prevent instantiation of utils class. */ private AccessEgressFunctions() {} - static Collection removeNoneOptimalPathsForStandardRaptor( + /** + * Filter non-optimal paths away for the standard search. This method does not + * look at the c1 value. + */ + static Collection removeNonOptimalPathsForStandardRaptor( Collection paths ) { - // To avoid too many items in the pareto set we first group the paths by stop, - // for each stop we filter it down to the optimal pareto set. We could do this - // for multi-criteria as well, but it is likely not so important. The focus for - // the mc-set should be that the list of access/egress created in OTP should not - // contain to many non-optimal paths. - var mapByStop = groupByStop(paths); - var set = new ParetoSet<>(STANDARD_COMPARATOR); - Collection result = new ArrayList<>(); + return removeNonOptimalPaths(paths, STANDARD_COMPARATOR); + } - mapByStop.forEachValue(list -> { - set.clear(); - set.addAll(list); - result.addAll(set); - return true; - }); + /** + * Filter non-optimal paths away for the multi-criteria search. This method should in theory + * not remove any paths since the caller should not pass in duplicates, but it turns out that + * this happens, so we do it. + */ + static Collection removeNonOptimalPathsForMcRaptor( + Collection paths + ) { + var result = removeNonOptimalPaths(paths, MC_COMPARATOR); + if (LOG.isDebugEnabled() && result.size() < paths.size()) { + var duplicates = new ArrayList<>(paths); + duplicates.removeAll(result); + // Note! This does not provide enough information to solve/debug this problem, but this is + // not a problem in Raptor, so we do not want to add more specific logging here - this does + // however document that the problem exist. Turn on debug logging and move the start/end + // coordinate around until you see this message. + // + // See https://github.com/opentripplanner/OpenTripPlanner/issues/5601 + LOG.warn( + "Duplicate access/egress paths passed into raptor:\n\t" + + duplicates.stream().map(Objects::toString).collect(Collectors.joining("\n\t")) + ); + } return result; } @@ -96,6 +126,27 @@ static TIntObjectMap> groupByStop(Collection removeNonOptimalPaths( + Collection paths, + ParetoComparator comparator + ) { + var mapByStop = groupByStop(paths); + var set = new ParetoSet<>(comparator); + var result = new ArrayList(); + + for (int stop : mapByStop.keys()) { + var list = mapByStop.get(stop); + set.clear(); + set.addAll(list); + result.addAll(set); + } + return result; + } + private static List getOrCreate( int key, TIntObjectMap> map diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java index 940937f82db..5c26a10db23 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java @@ -1,7 +1,8 @@ package org.opentripplanner.raptor.rangeraptor.transit; import static org.opentripplanner.raptor.rangeraptor.transit.AccessEgressFunctions.groupByRound; -import static org.opentripplanner.raptor.rangeraptor.transit.AccessEgressFunctions.removeNoneOptimalPathsForStandardRaptor; +import static org.opentripplanner.raptor.rangeraptor.transit.AccessEgressFunctions.removeNonOptimalPathsForMcRaptor; +import static org.opentripplanner.raptor.rangeraptor.transit.AccessEgressFunctions.removeNonOptimalPathsForStandardRaptor; import gnu.trove.map.TIntObjectMap; import java.util.Arrays; @@ -57,8 +58,10 @@ public int calculateMaxNumberOfRides() { * This method is static and package local to enable unit-testing. */ public static AccessPaths create(Collection paths, RaptorProfile profile) { - if (!profile.is(RaptorProfile.MULTI_CRITERIA)) { - paths = removeNoneOptimalPathsForStandardRaptor(paths); + if (profile.is(RaptorProfile.MULTI_CRITERIA)) { + paths = removeNonOptimalPathsForMcRaptor(paths); + } else { + paths = removeNonOptimalPathsForStandardRaptor(paths); } return new AccessPaths( groupByRound(paths, RaptorAccessEgress::stopReachedByWalking), diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java index 2038ab543df..374fc050782 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java @@ -2,7 +2,8 @@ import static org.opentripplanner.raptor.api.request.RaptorProfile.MULTI_CRITERIA; import static org.opentripplanner.raptor.rangeraptor.transit.AccessEgressFunctions.groupByStop; -import static org.opentripplanner.raptor.rangeraptor.transit.AccessEgressFunctions.removeNoneOptimalPathsForStandardRaptor; +import static org.opentripplanner.raptor.rangeraptor.transit.AccessEgressFunctions.removeNonOptimalPathsForMcRaptor; +import static org.opentripplanner.raptor.rangeraptor.transit.AccessEgressFunctions.removeNonOptimalPathsForStandardRaptor; import gnu.trove.map.TIntObjectMap; import java.util.Collection; @@ -31,8 +32,10 @@ private EgressPaths(TIntObjectMap> pathsByStop) { * This method is static and package local to enable unit-testing. */ public static EgressPaths create(Collection paths, RaptorProfile profile) { - if (!MULTI_CRITERIA.is(profile)) { - paths = removeNoneOptimalPathsForStandardRaptor(paths); + if (MULTI_CRITERIA.is(profile)) { + paths = removeNonOptimalPathsForMcRaptor(paths); + } else { + paths = removeNonOptimalPathsForStandardRaptor(paths); } return new EgressPaths(groupByStop(paths)); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java b/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java index ad2c4c92639..06ceaeebeb2 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java @@ -16,10 +16,10 @@ import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.framework.time.ServiceDateUtils; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.paging.cursor.PageCursorInput; import org.opentripplanner.raptor.api.request.RaptorTuningParameters; import org.opentripplanner.raptor.api.request.SearchParams; import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChain; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NumItinerariesFilterResults; import org.opentripplanner.routing.algorithm.mapping.PagingServiceFactory; import org.opentripplanner.routing.algorithm.mapping.RouteRequestToFilterChainMapper; import org.opentripplanner.routing.algorithm.mapping.RoutingResponseMapper; @@ -65,7 +65,7 @@ public class RoutingWorker { private final ZonedDateTime transitSearchTimeZero; private final AdditionalSearchDays additionalSearchDays; private SearchParams raptorSearchParamsUsed = null; - private NumItinerariesFilterResults numItinerariesFilterResults = null; + private PageCursorInput pageCursorInput = null; public RoutingWorker(OtpServerRequestContext serverContext, RouteRequest request, ZoneId zoneId) { request.applyPageCursor(); @@ -137,7 +137,7 @@ public RoutingResponse route() { searchWindowUsed(), emptyDirectModeHandler.removeWalkAllTheWayResults() || removeWalkAllTheWayResultsFromDirectFlex, - it -> numItinerariesFilterResults = it + it -> pageCursorInput = it ); filteredItineraries = filterChain.filter(itineraries); @@ -283,7 +283,7 @@ private PagingService createPagingService(List itineraries) { serverContext.raptorTuningParameters(), request, raptorSearchParamsUsed, - numItinerariesFilterResults, + pageCursorInput, itineraries ); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChain.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChain.java index 2d3a65e3bbd..cbea827e52b 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChain.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChain.java @@ -3,6 +3,9 @@ import java.util.ArrayList; import java.util.List; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.routing.algorithm.filterchain.framework.filterchain.DeleteResultHandler; +import org.opentripplanner.routing.algorithm.filterchain.framework.filterchain.RoutingErrorsAttacher; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryListFilter; import org.opentripplanner.routing.api.response.RoutingError; public class ItineraryListFilterChain { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChain.svg b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChain.svg deleted file mode 100644 index 06f65809fab..00000000000 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChain.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - Comparator<Itinerary>ItineraryListFilterChain*GroupByFilter<GroupId>nestedFilters*ItinararyListFilterDecoratingFilterDeletionFlaggingFilterSortingFilterItineraryDeletionFlagger-------predicate()getFlaggedItineraries(List)skipAlreadyFlaggedItineraries() \ No newline at end of file diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java index 872bad6b4ae..e8b8ed43c1c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java @@ -1,6 +1,6 @@ package org.opentripplanner.routing.algorithm.filterchain; -import static org.opentripplanner.routing.algorithm.filterchain.comparator.SortOrderComparator.generalizedCostComparator; +import static org.opentripplanner.routing.algorithm.filterchain.framework.sort.SortOrderComparator.generalizedCostComparator; import java.time.Duration; import java.time.Instant; @@ -11,38 +11,43 @@ import java.util.function.Consumer; import java.util.function.Function; import javax.annotation.Nullable; -import org.opentripplanner.ext.accessibilityscore.AccessibilityScoreFilter; +import org.opentripplanner.ext.accessibilityscore.DecorateWithAccessibilityScore; import org.opentripplanner.framework.collection.ListSection; import org.opentripplanner.framework.lang.Sandbox; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.ItinerarySortKey; import org.opentripplanner.model.plan.SortOrder; +import org.opentripplanner.model.plan.paging.cursor.PageCursorInput; +import org.opentripplanner.routing.algorithm.filterchain.api.GroupBySimilarity; import org.opentripplanner.routing.algorithm.filterchain.api.TransitGeneralizedCostFilterParams; -import org.opentripplanner.routing.algorithm.filterchain.comparator.SortOrderComparator; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.ItineraryDeletionFlagger; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.MaxLimitFilter; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NonTransitGeneralizedCostFilter; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NumItinerariesFilter; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NumItinerariesFilterResults; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.OtherThanSameLegsMaxGeneralizedCostFilter; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.OutsideSearchWindowFilter; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.PagingFilter; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveBikerentalWithMostlyWalkingFilter; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveItinerariesWithShortStreetLeg; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveParkAndRideWithMostlyWalkingFilter; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveTransitIfStreetOnlyIsBetterFilter; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveTransitIfWalkingIsBetterFilter; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveWalkOnlyFilter; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.TransitGeneralizedCostFilter; -import org.opentripplanner.routing.algorithm.filterchain.filter.DeletionFlaggingFilter; -import org.opentripplanner.routing.algorithm.filterchain.filter.GroupByFilter; -import org.opentripplanner.routing.algorithm.filterchain.filter.RemoveDeletionFlagForLeastTransfersItinerary; -import org.opentripplanner.routing.algorithm.filterchain.filter.SameFirstOrLastTripFilter; -import org.opentripplanner.routing.algorithm.filterchain.filter.SortingFilter; -import org.opentripplanner.routing.algorithm.filterchain.filter.TransitAlertFilter; -import org.opentripplanner.routing.algorithm.filterchain.groupids.GroupByAllSameStations; -import org.opentripplanner.routing.algorithm.filterchain.groupids.GroupByDistance; -import org.opentripplanner.routing.algorithm.filterchain.groupids.GroupBySameRoutesAndStops; +import org.opentripplanner.routing.algorithm.filterchain.filters.street.RemoveBikeRentalWithMostlyWalking; +import org.opentripplanner.routing.algorithm.filterchain.filters.street.RemoveNonTransitItinerariesBasedOnGeneralizedCost; +import org.opentripplanner.routing.algorithm.filterchain.filters.street.RemoveParkAndRideWithMostlyWalkingFilter; +import org.opentripplanner.routing.algorithm.filterchain.filters.street.RemoveWalkOnlyFilter; +import org.opentripplanner.routing.algorithm.filterchain.filters.system.NumItinerariesFilter; +import org.opentripplanner.routing.algorithm.filterchain.filters.system.OutsideSearchWindowFilter; +import org.opentripplanner.routing.algorithm.filterchain.filters.system.PagingFilter; +import org.opentripplanner.routing.algorithm.filterchain.filters.transit.DecorateTransitAlert; +import org.opentripplanner.routing.algorithm.filterchain.filters.transit.KeepItinerariesWithFewestTransfers; +import org.opentripplanner.routing.algorithm.filterchain.filters.transit.RemoveItinerariesWithShortStreetLeg; +import org.opentripplanner.routing.algorithm.filterchain.filters.transit.RemoveTransitIfStreetOnlyIsBetter; +import org.opentripplanner.routing.algorithm.filterchain.filters.transit.RemoveTransitIfWalkingIsBetter; +import org.opentripplanner.routing.algorithm.filterchain.filters.transit.TransitGeneralizedCostFilter; +import org.opentripplanner.routing.algorithm.filterchain.filters.transit.group.RemoveIfFirstOrLastTripIsTheSame; +import org.opentripplanner.routing.algorithm.filterchain.filters.transit.group.RemoveOtherThanSameLegsMaxGeneralizedCost; +import org.opentripplanner.routing.algorithm.filterchain.framework.filter.DecorateFilter; +import org.opentripplanner.routing.algorithm.filterchain.framework.filter.GroupByFilter; +import org.opentripplanner.routing.algorithm.filterchain.framework.filter.MaxLimit; +import org.opentripplanner.routing.algorithm.filterchain.framework.filter.RemoveFilter; +import org.opentripplanner.routing.algorithm.filterchain.framework.filter.SortingFilter; +import org.opentripplanner.routing.algorithm.filterchain.framework.filterchain.DeleteResultHandler; +import org.opentripplanner.routing.algorithm.filterchain.framework.groupids.GroupByAllSameStations; +import org.opentripplanner.routing.algorithm.filterchain.framework.groupids.GroupByDistance; +import org.opentripplanner.routing.algorithm.filterchain.framework.groupids.GroupBySameRoutesAndStops; +import org.opentripplanner.routing.algorithm.filterchain.framework.sort.SortOrderComparator; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryDecorator; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryListFilter; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.routing.api.request.preference.ItineraryFilterDebugProfile; import org.opentripplanner.routing.services.TransitAlertService; @@ -53,6 +58,7 @@ /** * Create a filter chain based on the given config. */ +@SuppressWarnings("UnusedReturnValue") public class ItineraryListFilterChainBuilder { private static final int NOT_SET = -1; @@ -69,7 +75,7 @@ public class ItineraryListFilterChainBuilder { private double bikeRentalDistanceRatio; private double parkAndRideDurationRatio; private CostLinearFunction nonTransitGeneralizedCostLimit; - private Consumer numItinerariesFilterResultsConsumer; + private Consumer pageCursorInputSubscriber; private Instant earliestDepartureTime = null; private Duration searchWindow = null; private boolean accessibilityScore; @@ -86,16 +92,16 @@ public class ItineraryListFilterChainBuilder { */ @Sandbox - private ItineraryListFilter emissionsFilter; + private ItineraryDecorator emissionDecorator; @Sandbox - private ItineraryListFilter faresFilter; + private ItineraryDecorator fareDecorator; @Sandbox - private ItineraryListFilter rideHailingFilter; + private ItineraryListFilter rideHailingDecorator; @Sandbox - private ItineraryListFilter stopConsolidationFilter; + private ItineraryDecorator stopConsolidationDecorator; public ItineraryListFilterChainBuilder(SortOrder sortOrder) { this.sortOrder = sortOrder; @@ -261,18 +267,15 @@ public ItineraryListFilterChainBuilder withSearchWindow( } /** - * If the maximum number of itineraries is exceeded, then the excess itineraries are removed. To - * get notified about this a consumer can be added. The 'maxLimit' check is the last thing - * happening in the filter-chain after the final sort. So, if another filter removes an itinerary, - * the itinerary is not considered with respect to the {@link #withMaxNumberOfItineraries(int)} - * limit. - * - * @param numItinerariesFilterResultsConsumer the consumer to notify when elements are removed. + * If the maximum number of itineraries is exceeded, then the excess itineraries are removed. + * The paging service needs this information to adjust the paging cursor. The 'maxLimit' check is + * the last thing* happening in the filter-chain after the final sort. So, if another filter + * removes an itinerary, the itinerary is not considered with respect to this limit. */ - public ItineraryListFilterChainBuilder withNumItinerariesFilterResultsConsumer( - Consumer numItinerariesFilterResultsConsumer + public ItineraryListFilterChainBuilder withPageCursorInputSubscriber( + Consumer pageCursorInputSubscriber ) { - this.numItinerariesFilterResultsConsumer = numItinerariesFilterResultsConsumer; + this.pageCursorInputSubscriber = pageCursorInputSubscriber; return this; } @@ -317,13 +320,13 @@ public ItineraryListFilterChainBuilder withAccessibilityScore( return this; } - public ItineraryListFilterChainBuilder withFaresFilter(ItineraryListFilter filter) { - this.faresFilter = filter; + public ItineraryListFilterChainBuilder withFareDecorator(ItineraryDecorator decorator) { + this.fareDecorator = decorator; return this; } - public ItineraryListFilterChainBuilder withEmissions(ItineraryListFilter emissionsFilter) { - this.emissionsFilter = emissionsFilter; + public ItineraryListFilterChainBuilder withEmissions(ItineraryDecorator emissionDecorator) { + this.emissionDecorator = emissionDecorator; return this; } @@ -339,57 +342,55 @@ public ItineraryListFilterChainBuilder withRemoveTimeshiftedItinerariesWithSameR return this; } - public ItineraryListFilterChainBuilder withRideHailingFilter(ItineraryListFilter filter) { - this.rideHailingFilter = filter; + public ItineraryListFilterChainBuilder withRideHailingDecoratingFilter( + ItineraryListFilter decoratorFilter + ) { + this.rideHailingDecorator = decoratorFilter; return this; } - public ItineraryListFilterChainBuilder withStopConsolidationFilter( - @Nullable ItineraryListFilter filter + public ItineraryListFilterChainBuilder withConsolidatedStopNamesDecorator( + @Nullable ItineraryDecorator decorator ) { - this.stopConsolidationFilter = filter; + this.stopConsolidationDecorator = decorator; return this; } + public ItineraryListFilterChainBuilder withTransitAlerts( + TransitAlertService transitAlertService, + Function getMultiModalStation + ) { + this.transitAlertService = transitAlertService; + this.getMultiModalStation = getMultiModalStation; + + return this; + } + + @SuppressWarnings("CollectionAddAllCanBeReplacedWithConstructor") public ItineraryListFilterChain build() { List filters = new ArrayList<>(); filters.addAll(buildGroupByTripIdAndDistanceFilters()); if (removeItinerariesWithSameRoutesAndStops) { - filters.addAll(buildGroupBySameRoutesAndStopsFilter()); + filters.add(buildGroupBySameRoutesAndStopsFilter()); } if (sameFirstOrLastTripFilter) { - filters.add(new SortingFilter(generalizedCostComparator())); - addRmFilter(filters, new SameFirstOrLastTripFilter()); + addSort(filters, generalizedCostComparator()); + addRemoveFilter(filters, new RemoveIfFirstOrLastTripIsTheSame()); } if (minBikeParkingDistance > 0) { - filters.add( + addRemoveFilter( + filters, new RemoveItinerariesWithShortStreetLeg(minBikeParkingDistance, TraverseMode.BICYCLE) ); } - if (accessibilityScore) { - filters.add(new AccessibilityScoreFilter(wheelchairMaxSlope)); - } - - if (faresFilter != null) { - filters.add(faresFilter); - } - - if (emissionsFilter != null) { - filters.add(emissionsFilter); - } - - if (transitAlertService != null) { - filters.add(new TransitAlertFilter(transitAlertService, getMultiModalStation)); - } - // Filter transit itineraries on generalized-cost if (transitGeneralizedCostFilterParams != null) { - addRmFilter( + addRemoveFilter( filters, new TransitGeneralizedCostFilter( transitGeneralizedCostFilterParams.costLimitFunction(), @@ -400,7 +401,10 @@ public ItineraryListFilterChain build() { // Filter non-transit itineraries on generalized-cost if (nonTransitGeneralizedCostLimit != null) { - addRmFilter(filters, new NonTransitGeneralizedCostFilter(nonTransitGeneralizedCostLimit)); + addRemoveFilter( + filters, + new RemoveNonTransitItinerariesBasedOnGeneralizedCost(nonTransitGeneralizedCostLimit) + ); } // Apply all absolute filters AFTER the groupBy filters. Absolute filters are filters that @@ -415,28 +419,26 @@ public ItineraryListFilterChain build() { { // Filter transit itineraries by comparing against non-transit using generalized-cost if (removeTransitWithHigherCostThanBestOnStreetOnly != null) { - addRmFilter( + addRemoveFilter( filters, - new RemoveTransitIfStreetOnlyIsBetterFilter( - removeTransitWithHigherCostThanBestOnStreetOnly - ) + new RemoveTransitIfStreetOnlyIsBetter(removeTransitWithHigherCostThanBestOnStreetOnly) ); } if (removeTransitIfWalkingIsBetter) { - addRmFilter(filters, new RemoveTransitIfWalkingIsBetterFilter()); + addRemoveFilter(filters, new RemoveTransitIfWalkingIsBetter()); } if (removeWalkAllTheWayResults) { - addRmFilter(filters, new RemoveWalkOnlyFilter()); + addRemoveFilter(filters, new RemoveWalkOnlyFilter()); } if (bikeRentalDistanceRatio > 0) { - addRmFilter(filters, new RemoveBikerentalWithMostlyWalkingFilter(bikeRentalDistanceRatio)); + addRemoveFilter(filters, new RemoveBikeRentalWithMostlyWalking(bikeRentalDistanceRatio)); } if (parkAndRideDurationRatio > 0) { - addRmFilter( + addRemoveFilter( filters, new RemoveParkAndRideWithMostlyWalkingFilter(parkAndRideDurationRatio) ); @@ -444,49 +446,74 @@ public ItineraryListFilterChain build() { } // Paging related filters - these filters are run after group-by filters to allow a result - // outside the page to also take effect inside the window. This is debatable but lead to less - // noise, however it is not deterministic because the result depends on the size of the search-window and - // where the "cut" between each page is located. + // outside the page to also take effect inside the window. This is debatable, but leads to less + // noise; However, it is not deterministic because the result depends on the size of the + // search-window and where the "cut" between each page is located. { // Limit to search-window if (earliestDepartureTime != null) { - addRmFilter(filters, new OutsideSearchWindowFilter(earliestDepartureTime, searchWindow)); + addRemoveFilter( + filters, + new OutsideSearchWindowFilter(earliestDepartureTime, searchWindow) + ); } // Remove itineraries present in the page retrieved before this page/search. if (itineraryPageCut != null) { - addRmFilter(filters, new PagingFilter(sortOrder, deduplicateSection(), itineraryPageCut)); + addRemoveFilter( + filters, + new PagingFilter(sortOrder, deduplicateSection(), itineraryPageCut) + ); } // Remove itineraries if max limit is set if (maxNumberOfItineraries > 0) { - filters.add(new SortingFilter(SortOrderComparator.comparator(sortOrder))); - addRmFilter( + addSort(filters, SortOrderComparator.comparator(sortOrder)); + addRemoveFilter( filters, new NumItinerariesFilter( maxNumberOfItineraries, maxNumberOfItinerariesCropSection, - numItinerariesFilterResultsConsumer + pageCursorInputSubscriber ) ); } } // Do the final itineraries sort - filters.add(new SortingFilter(SortOrderComparator.comparator(sortOrder))); + addSort(filters, SortOrderComparator.comparator(sortOrder)); + + // Decorate itineraries + { + if (transitAlertService != null) { + addDecorateFilter( + filters, + new DecorateTransitAlert(transitAlertService, getMultiModalStation) + ); + } - // Sandbox filters to decorate itineraries + // Sandbox filters to decorate itineraries - if (faresFilter != null) { - filters.add(faresFilter); - } + if (accessibilityScore) { + // TODO: This should be injected to avoid circular dependencies (dep. on sandbox here) + addDecorateFilter(filters, new DecorateWithAccessibilityScore(wheelchairMaxSlope)); + } - if (rideHailingFilter != null) { - filters.add(rideHailingFilter); - } + if (emissionDecorator != null) { + addDecorateFilter(filters, emissionDecorator); + } - if (stopConsolidationFilter != null) { - filters.add(stopConsolidationFilter); + if (fareDecorator != null) { + addDecorateFilter(filters, fareDecorator); + } + + if (rideHailingDecorator != null) { + filters.add(rideHailingDecorator); + } + + if (stopConsolidationDecorator != null) { + addDecorateFilter(filters, stopConsolidationDecorator); + } } var debugHandler = new DeleteResultHandler(debug, maxNumberOfItineraries); @@ -494,37 +521,25 @@ public ItineraryListFilterChain build() { return new ItineraryListFilterChain(filters, debugHandler); } - public ItineraryListFilterChainBuilder withTransitAlerts( - TransitAlertService transitAlertService, - Function getMultiModalStation - ) { - this.transitAlertService = transitAlertService; - this.getMultiModalStation = getMultiModalStation; - - return this; - } - /** * If enabled, this adds the filter to remove itineraries which have the same stops and routes. * These are sometimes called "time-shifted duplicates" but since those terms have so many * meanings we chose to use a long, but descriptive name instead. */ - private List buildGroupBySameRoutesAndStopsFilter() { - return List.of( - new GroupByFilter<>( - GroupBySameRoutesAndStops::new, - List.of( - new SortingFilter(SortOrderComparator.comparator(sortOrder)), - new DeletionFlaggingFilter(new MaxLimitFilter(GroupBySameRoutesAndStops.TAG, 1)) - ) + private ItineraryListFilter buildGroupBySameRoutesAndStopsFilter() { + return new GroupByFilter<>( + GroupBySameRoutesAndStops::new, + List.of( + new SortingFilter(SortOrderComparator.comparator(sortOrder)), + new RemoveFilter(new MaxLimit(GroupBySameRoutesAndStops.TAG, 1)) ) ); } /** * These filters will group the itineraries by the main-legs and reduce the number of itineraries - * in each group. The main legs is the legs that together constitute more than a given percentage - * of the total travel distance. + * in each group. The main legs are the legs that together constitute more than a given + * percentage of the total travel distance. *

* Each group is filtered using generalized-cost, keeping only the itineraries with the lowest * cost. If there is a tie, the filter look at the number-of-transfers as a tiebreaker. @@ -559,23 +574,22 @@ private List buildGroupByTripIdAndDistanceFilters() { GroupByAllSameStations::new, List.of( new SortingFilter(generalizedCostComparator()), - new DeletionFlaggingFilter(new MaxLimitFilter(innerGroupName, 1)) + new RemoveFilter(new MaxLimit(innerGroupName, 1)) ) ) ); } if (group.maxCostOtherLegsFactor > 1.0) { - var flagger = new OtherThanSameLegsMaxGeneralizedCostFilter(group.maxCostOtherLegsFactor); + var flagger = new RemoveOtherThanSameLegsMaxGeneralizedCost(group.maxCostOtherLegsFactor); sysTags.add(flagger.name()); - addRmFilter(nested, flagger); + addRemoveFilter(nested, flagger); } - nested.add(new SortingFilter(generalizedCostComparator())); + addSort(nested, generalizedCostComparator()); + addRemoveFilter(nested, new MaxLimit(tag, group.maxNumOfItinerariesPerGroup)); - addRmFilter(nested, new MaxLimitFilter(tag, group.maxNumOfItinerariesPerGroup)); - - nested.add(new RemoveDeletionFlagForLeastTransfersItinerary(sysTags)); + nested.add(new KeepItinerariesWithFewestTransfers(sysTags)); groupByFilters.add( new GroupByFilter<>(it -> new GroupByDistance(it, group.groupByP), nested) @@ -589,10 +603,21 @@ private ListSection deduplicateSection() { return maxNumberOfItinerariesCropSection.invert(); } - private static void addRmFilter( + private static void addSort(List filters, SortOrderComparator comparator) { + filters.add(new SortingFilter(comparator)); + } + + private static void addRemoveFilter( + List filters, + RemoveItineraryFlagger removeFilter + ) { + filters.add(new RemoveFilter(removeFilter)); + } + + private static void addDecorateFilter( List filters, - ItineraryDeletionFlagger removeFilter + ItineraryDecorator decorator ) { - filters.add(new DeletionFlaggingFilter(removeFilter)); + filters.add(new DecorateFilter(decorator)); } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/GroupBySimilarity.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/api/GroupBySimilarity.java similarity index 96% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/GroupBySimilarity.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/api/GroupBySimilarity.java index e98587d9f3f..0f671524a06 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/GroupBySimilarity.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/api/GroupBySimilarity.java @@ -1,6 +1,7 @@ -package org.opentripplanner.routing.algorithm.filterchain; +package org.opentripplanner.routing.algorithm.filterchain.api; import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChainBuilder; /** * Group itineraries by similarity and reduce the number of itineraries down to an given maximum diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/api/TransitGeneralizedCostFilterParams.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/api/TransitGeneralizedCostFilterParams.java index 5a460e6fa82..a173f6b454b 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/api/TransitGeneralizedCostFilterParams.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/api/TransitGeneralizedCostFilterParams.java @@ -1,11 +1,11 @@ package org.opentripplanner.routing.algorithm.filterchain.api; import org.opentripplanner.framework.lang.DoubleUtils; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.TransitGeneralizedCostFilter; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; /** - * Input parameters for {@link TransitGeneralizedCostFilter} + * Input parameters for + * {@link org.opentripplanner.routing.algorithm.filterchain.filters.transit.TransitGeneralizedCostFilter} * * @param costLimitFunction Describes the function to calculate the limit for an itinerary based * on the generalized cost diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveItinerariesWithShortStreetLeg.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveItinerariesWithShortStreetLeg.java deleted file mode 100644 index 2d45c46140b..00000000000 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveItinerariesWithShortStreetLeg.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; - -import java.util.List; -import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.model.plan.Leg; -import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilter; -import org.opentripplanner.street.search.TraverseMode; - -/** - * This filter is useful if you want to remove those results where there is a short bicycle - * leg followed by parking the bike and taking transit. In such a case you would not need a bike - * could just walk to the stop instead. - */ -public class RemoveItinerariesWithShortStreetLeg implements ItineraryListFilter { - - private final double minDistance; - private final TraverseMode traverseMode; - - public RemoveItinerariesWithShortStreetLeg(double minDistance, TraverseMode traverseMode) { - this.minDistance = minDistance; - this.traverseMode = traverseMode; - } - - @Override - public List filter(List itineraries) { - return itineraries.stream().filter(this::filterItinerariesWithShortStreetLeg).toList(); - } - - private boolean filterItinerariesWithShortStreetLeg(Itinerary itinerary) { - var hasLegsOfMode = itinerary.getStreetLegs().anyMatch(l -> l.getMode().equals(traverseMode)); - if (hasLegsOfMode && itinerary.hasTransit()) { - var distance = itinerary - .getStreetLegs() - .filter(l -> l.getMode().equals(traverseMode)) - .mapToDouble(Leg::getDistanceMeters) - .sum(); - - return distance > minDistance; - } else { - return true; - } - } -} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveBikerentalWithMostlyWalkingFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveBikeRentalWithMostlyWalking.java similarity index 66% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveBikerentalWithMostlyWalkingFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveBikeRentalWithMostlyWalking.java index 74ce88bc379..21cadf413ae 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveBikerentalWithMostlyWalkingFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveBikeRentalWithMostlyWalking.java @@ -1,8 +1,9 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.street; import java.util.function.Predicate; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; /** * This is used to filter out bike rental itineraries that contain mostly walking. The value @@ -11,11 +12,11 @@ *

* This filter is turned off by default (bikeRentalDistanceRatio == 0) */ -public class RemoveBikerentalWithMostlyWalkingFilter implements ItineraryDeletionFlagger { +public class RemoveBikeRentalWithMostlyWalking implements RemoveItineraryFlagger { private final double bikeRentalDistanceRatio; - public RemoveBikerentalWithMostlyWalkingFilter(double bikeRentalDistanceRatio) { + public RemoveBikeRentalWithMostlyWalking(double bikeRentalDistanceRatio) { this.bikeRentalDistanceRatio = bikeRentalDistanceRatio; } @@ -27,10 +28,9 @@ public String name() { @Override public Predicate shouldBeFlaggedForRemoval() { return itinerary -> { - var containsTransit = itinerary - .getLegs() - .stream() - .anyMatch(l -> l != null && l.isTransitLeg()); + if (itinerary.hasTransit()) { + return false; + } double bikeRentalDistance = itinerary .getLegs() @@ -38,12 +38,10 @@ public Predicate shouldBeFlaggedForRemoval() { .filter(l -> l.getRentedVehicle() != null && l.getRentedVehicle()) .mapToDouble(Leg::getDistanceMeters) .sum(); - double totalDistance = itinerary.distanceMeters(); + double totalDistance = itinerary.distanceMeters(); return ( - bikeRentalDistance != 0 && - !containsTransit && - (bikeRentalDistance / totalDistance) <= bikeRentalDistanceRatio + bikeRentalDistance != 0 && (bikeRentalDistance / totalDistance) <= bikeRentalDistanceRatio ); }; } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NonTransitGeneralizedCostFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveNonTransitItinerariesBasedOnGeneralizedCost.java similarity index 66% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NonTransitGeneralizedCostFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveNonTransitItinerariesBasedOnGeneralizedCost.java index da46b99f203..427084a55f6 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NonTransitGeneralizedCostFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveNonTransitItinerariesBasedOnGeneralizedCost.java @@ -1,29 +1,35 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.street; import java.util.List; import java.util.OptionalInt; import java.util.stream.Collectors; import org.opentripplanner.framework.model.Cost; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.routing.algorithm.filterchain.filters.transit.TransitGeneralizedCostFilter; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.routing.api.request.preference.ItineraryFilterPreferences; /** + * This filter remove none-transit itineraries with generalized-cost higher than the max-limit. + * The max-limit is computed based on the overall min-generalized-cost using the provided cost + * function. + *

* This filter is similar to {@link TransitGeneralizedCostFilter}. There are some important * differences, however. It will only remove non-transit results, but ALL results can be used as a * basis for computing the cost limit. *

- * This is needed so that we do not for example get walk legs that last several hours, when transit - * can take you to the destination much quicker. + * This will, for example, remove walk legs which last several hours, when transit can take you to + * the destination much quicker. *

* * @see ItineraryFilterPreferences#nonTransitGeneralizedCostLimit() */ -public class NonTransitGeneralizedCostFilter implements ItineraryDeletionFlagger { +public class RemoveNonTransitItinerariesBasedOnGeneralizedCost implements RemoveItineraryFlagger { private final CostLinearFunction costLimitFunction; - public NonTransitGeneralizedCostFilter(CostLinearFunction costLimitFunction) { + public RemoveNonTransitItinerariesBasedOnGeneralizedCost(CostLinearFunction costLimitFunction) { this.costLimitFunction = costLimitFunction; } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveParkAndRideWithMostlyWalkingFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveParkAndRideWithMostlyWalkingFilter.java similarity index 80% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveParkAndRideWithMostlyWalkingFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveParkAndRideWithMostlyWalkingFilter.java index ad8c08ce7ec..2a9cefd0657 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveParkAndRideWithMostlyWalkingFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveParkAndRideWithMostlyWalkingFilter.java @@ -1,10 +1,11 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.street; import java.util.List; import java.util.function.Predicate; import java.util.stream.Collectors; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.StreetLeg; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; import org.opentripplanner.street.search.TraverseMode; /** @@ -14,7 +15,7 @@ *

* This filter is turned off by default (parkAndRideDurationRatio == 0) */ -public class RemoveParkAndRideWithMostlyWalkingFilter implements ItineraryDeletionFlagger { +public class RemoveParkAndRideWithMostlyWalkingFilter implements RemoveItineraryFlagger { private final double parkAndRideDurationRatio; @@ -30,10 +31,9 @@ public String name() { @Override public Predicate shouldBeFlaggedForRemoval() { return itinerary -> { - var containsTransit = itinerary - .getLegs() - .stream() - .anyMatch(l -> l != null && l.isTransitLeg()); + if (itinerary.hasTransit()) { + return false; + } double carDuration = itinerary .getLegs() @@ -45,11 +45,7 @@ public Predicate shouldBeFlaggedForRemoval() { .sum(); double totalDuration = itinerary.getDuration().toSeconds(); - return ( - !containsTransit && - carDuration != 0 && - (carDuration / totalDuration) <= parkAndRideDurationRatio - ); + return (carDuration != 0 && (carDuration / totalDuration) <= parkAndRideDurationRatio); }; } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveWalkOnlyFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveWalkOnlyFilter.java similarity index 61% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveWalkOnlyFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveWalkOnlyFilter.java index abac48563f8..1a53b898931 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveWalkOnlyFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveWalkOnlyFilter.java @@ -1,12 +1,13 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.street; import java.util.function.Predicate; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; /** * Filter itineraries and remove all itineraries where all legs are walking. */ -public class RemoveWalkOnlyFilter implements ItineraryDeletionFlagger { +public class RemoveWalkOnlyFilter implements RemoveItineraryFlagger { @Override public String name() { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/NumItinerariesFilter.java similarity index 70% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/NumItinerariesFilter.java index b939dd8ca68..aed5eeb4f95 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/NumItinerariesFilter.java @@ -1,9 +1,11 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.system; import java.util.List; import java.util.function.Consumer; import org.opentripplanner.framework.collection.ListSection; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.paging.cursor.PageCursorInput; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; /** * Flag all itineraries after the provided limit. This flags the itineraries at the end of the list @@ -11,27 +13,25 @@ *

* This filter reports information about the removed itineraries in the results consumer. */ -public class NumItinerariesFilter implements ItineraryDeletionFlagger { +public class NumItinerariesFilter implements RemoveItineraryFlagger { public static final String TAG = "number-of-itineraries-filter"; - private static final Consumer IGNORE_SUBSCRIBER = i -> {}; + private static final Consumer IGNORE_SUBSCRIBER = i -> {}; private final int maxLimit; private final ListSection cropSection; - private final Consumer numItinerariesFilterResultsConsumer; + private final Consumer pageCursorInputSubscriber; public NumItinerariesFilter( int maxLimit, ListSection cropSection, - Consumer numItinerariesFilterResultsConsumer + Consumer pageCursorInputSubscriber ) { this.maxLimit = maxLimit; this.cropSection = cropSection; - this.numItinerariesFilterResultsConsumer = - numItinerariesFilterResultsConsumer == null - ? IGNORE_SUBSCRIBER - : numItinerariesFilterResultsConsumer; + this.pageCursorInputSubscriber = + pageCursorInputSubscriber == null ? IGNORE_SUBSCRIBER : pageCursorInputSubscriber; } @Override @@ -58,7 +58,7 @@ public List flagForRemoval(List itineraries) { itinerariesToKeep = itineraries.subList(0, maxLimit); } - numItinerariesFilterResultsConsumer.accept( + pageCursorInputSubscriber.accept( new NumItinerariesFilterResults(itinerariesToKeep, itinerariesToRemove, cropSection) ); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilterResults.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/NumItinerariesFilterResults.java similarity index 71% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilterResults.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/NumItinerariesFilterResults.java index 7226c19535b..8b11be099f7 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilterResults.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/NumItinerariesFilterResults.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.system; import java.time.Instant; import java.util.List; @@ -9,20 +9,19 @@ import org.opentripplanner.model.plan.ItinerarySortKey; import org.opentripplanner.model.plan.paging.cursor.PageCursorInput; -public class NumItinerariesFilterResults implements PageCursorInput { +/** + * The NumItinerariesFilter removes itineraries from a list of itineraries based on the number to + * keep and whether it should crop at the head or the tail of the list. The results class keeps + * the extreme endpoints of the sets of itineraries that were kept and removed, as well as more + * details about the first itinerary removed (bottom of the head, or top of the tail) and whether + * itineraries were cropped at the head or the tail. + */ +class NumItinerariesFilterResults implements PageCursorInput { private final Instant earliestRemovedDeparture; private final Instant latestRemovedDeparture; private final ItinerarySortKey pageCut; - private final ListSection cropSection; - /** - * The NumItinerariesFilter removes itineraries from a list of itineraries based on the number to - * keep and whether it should crop at the head or the tail of the list. The results class keeps - * the extreme endpoints of the sets of itineraries that were kept and removed, as well as more - * details about the first itinerary removed (bottom of the head, or top of the tail) and whether - * itineraries were cropped at the head or the tail. - */ public NumItinerariesFilterResults( List keptItineraries, List removedItineraries, @@ -40,7 +39,6 @@ public NumItinerariesFilterResults( } else { pageCut = ListUtils.last(keptItineraries); } - this.cropSection = cropSection; } @Override @@ -65,7 +63,6 @@ public String toString() { .addDateTime("earliestRemovedDeparture", earliestRemovedDeparture) .addDateTime("latestRemovedDeparture", latestRemovedDeparture) .addObjOp("pageCut", pageCut, ItinerarySortKey::keyAsString) - .addEnum("cropSection", cropSection) .toString(); } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OutsideSearchWindowFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilter.java similarity index 87% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OutsideSearchWindowFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilter.java index dc189efbc33..ba3ef4b04f3 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OutsideSearchWindowFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilter.java @@ -1,9 +1,10 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.system; import java.time.Duration; import java.time.Instant; import java.util.function.Predicate; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; /** * This filter will remove all itineraries that are outside the search-window. In some @@ -14,7 +15,7 @@ * Itineraries matching the start(earliest-departure-time) are included and itineraries matching * the end(latest-departure-time) are not. The filter is {@code [inclusive, exclusive]}. */ -public class OutsideSearchWindowFilter implements ItineraryDeletionFlagger { +public class OutsideSearchWindowFilter implements RemoveItineraryFlagger { public static final String TAG = "outside-search-window"; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/PagingFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/PagingFilter.java similarity index 86% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/PagingFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/PagingFilter.java index 893a239b46b..1ef109525f6 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/PagingFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/PagingFilter.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.system; import java.util.Comparator; import java.util.List; @@ -7,7 +7,8 @@ import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.ItinerarySortKey; import org.opentripplanner.model.plan.SortOrder; -import org.opentripplanner.routing.algorithm.filterchain.comparator.SortOrderComparator; +import org.opentripplanner.routing.algorithm.filterchain.framework.sort.SortOrderComparator; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; /** * This class is used to enforce the cut/limit between two pages. It removes potential duplicates @@ -19,7 +20,7 @@ * potential duplicates will appear at the bottom of the list. If the previous results were cropped * at the bottom, then the potential duplicates will appear at the top of the list. */ -public class PagingFilter implements ItineraryDeletionFlagger { +public class PagingFilter implements RemoveItineraryFlagger { public static final String TAG = "paging-filter"; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filter/TransitAlertFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/DecorateTransitAlert.java similarity index 57% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/filter/TransitAlertFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/DecorateTransitAlert.java index 06fb28716e6..ccc2c9b26f0 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filter/TransitAlertFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/DecorateTransitAlert.java @@ -1,20 +1,19 @@ -package org.opentripplanner.routing.algorithm.filterchain.filter; +package org.opentripplanner.routing.algorithm.filterchain.filters.transit; -import java.util.List; import java.util.function.Function; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; -import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilter; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryDecorator; import org.opentripplanner.routing.algorithm.mapping.AlertToLegMapper; import org.opentripplanner.routing.services.TransitAlertService; import org.opentripplanner.transit.model.site.MultiModalStation; import org.opentripplanner.transit.model.site.Station; -public class TransitAlertFilter implements ItineraryListFilter { +public class DecorateTransitAlert implements ItineraryDecorator { private final AlertToLegMapper alertToLegMapper; - public TransitAlertFilter( + public DecorateTransitAlert( TransitAlertService transitAlertService, Function getMultiModalStation ) { @@ -22,17 +21,13 @@ public TransitAlertFilter( } @Override - public List filter(List itineraries) { - for (Itinerary itinerary : itineraries) { - boolean firstLeg = true; - for (Leg leg : itinerary.getLegs()) { - if (leg.isTransitLeg()) { - alertToLegMapper.addTransitAlertsToLeg(leg, firstLeg); - firstLeg = false; - } + public void decorate(Itinerary itinerary) { + boolean firstLeg = true; + for (Leg leg : itinerary.getLegs()) { + if (leg.isTransitLeg()) { + alertToLegMapper.addTransitAlertsToLeg(leg, firstLeg); + firstLeg = false; } } - - return itineraries; } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filter/RemoveDeletionFlagForLeastTransfersItinerary.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/KeepItinerariesWithFewestTransfers.java similarity index 53% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/filter/RemoveDeletionFlagForLeastTransfersItinerary.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/KeepItinerariesWithFewestTransfers.java index 42630fdf934..3d1e6301786 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filter/RemoveDeletionFlagForLeastTransfersItinerary.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/KeepItinerariesWithFewestTransfers.java @@ -1,25 +1,25 @@ -package org.opentripplanner.routing.algorithm.filterchain.filter; +package org.opentripplanner.routing.algorithm.filterchain.filters.transit; -import static org.opentripplanner.routing.algorithm.filterchain.comparator.SortOrderComparator.numberOfTransfersComparator; +import static org.opentripplanner.routing.algorithm.filterchain.framework.sort.SortOrderComparator.numberOfTransfersComparator; import java.util.HashSet; import java.util.List; import java.util.Set; import org.opentripplanner.model.SystemNotice; import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilter; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryListFilter; /** - * This filter makes sure that the itinerary with the least amount of transfers is not marked for - * deletion. It iterates over the itineraries and removes the SystemNotice if it contains - * the provided set of {@code filterKeys}. The itinerary must match all {@code filterKeys}, and - * if so the given keys are removed. Other system-notices are ignored. + * This filter makes sure that the itinerary with the fewest transfers is not removed. + * It iterates over the itineraries and removes the SystemNotice if it contains the provided set + * of {@code filterKeys}. The itinerary must match all {@code filterKeys}, and if so the given + * keys are removed. Itineraries with other system notices are ignored. */ -public class RemoveDeletionFlagForLeastTransfersItinerary implements ItineraryListFilter { +public class KeepItinerariesWithFewestTransfers implements ItineraryListFilter { private final Set filterKeys; - public RemoveDeletionFlagForLeastTransfersItinerary(List filterKeys) { + public KeepItinerariesWithFewestTransfers(List filterKeys) { this.filterKeys = new HashSet<>(filterKeys); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/RemoveItinerariesWithShortStreetLeg.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/RemoveItinerariesWithShortStreetLeg.java new file mode 100644 index 00000000000..713775911b3 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/RemoveItinerariesWithShortStreetLeg.java @@ -0,0 +1,58 @@ +package org.opentripplanner.routing.algorithm.filterchain.filters.transit; + +import java.util.function.Predicate; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.Leg; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; +import org.opentripplanner.street.search.TraverseMode; + +/** + * This filter is useful if you want to remove those results where there is a short bicycle + * leg followed by parking the bike and taking transit. In such a case you would not need a bike + * could just walk to the stop instead. + *

+ * This filter does not follow the regular filter framework as it is intended only for use cases where + * several queries are combined in the frontend. + *

+ * Example: you have two queries for bike+transit and walk+transit each. Both give you very short legs + * to reach a train station. A user would not expect to see a bike+transit shorter than 200m leg when it's + * presented right next to a walk+transit leg of the same length. + *

+ * In other words, this offloads the comparison part of the filter chain to a system outside of OTP and + * that is the reason for this non-standard approach. + */ +public class RemoveItinerariesWithShortStreetLeg implements RemoveItineraryFlagger { + + private final double minDistance; + private final TraverseMode traverseMode; + + public RemoveItinerariesWithShortStreetLeg(double minDistance, TraverseMode traverseMode) { + this.minDistance = minDistance; + this.traverseMode = traverseMode; + } + + @Override + public String name() { + return "remove-itineraries-with-short-street-leg"; + } + + @Override + public Predicate shouldBeFlaggedForRemoval() { + return this::removeItineraryWithShortStreetLeg; + } + + private boolean removeItineraryWithShortStreetLeg(Itinerary itinerary) { + var hasLegsOfMode = itinerary.getStreetLegs().anyMatch(l -> l.getMode().equals(traverseMode)); + if (hasLegsOfMode && itinerary.hasTransit()) { + var distance = itinerary + .getStreetLegs() + .filter(l -> l.getMode().equals(traverseMode)) + .mapToDouble(Leg::getDistanceMeters) + .sum(); + + return distance <= minDistance; + } else { + return false; + } + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/RemoveTransitIfStreetOnlyIsBetter.java similarity index 70% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/RemoveTransitIfStreetOnlyIsBetter.java index 08307c16ade..bddf5238f23 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/RemoveTransitIfStreetOnlyIsBetter.java @@ -1,25 +1,23 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.transit; -import java.util.Comparator; import java.util.List; -import java.util.OptionalDouble; import java.util.OptionalInt; import java.util.stream.Collectors; import org.opentripplanner.framework.model.Cost; import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.model.plan.Leg; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; /** - * Filter itineraries based on generalizedCost, compared with a on-street-all-the-way itinerary(if - * it exist). If an itinerary cost exceeds the limit computed from the best all-the-way-on-street itinerary, then the - * transit itinerary is removed. + * Filter itineraries based on generalizedCost, compared with an on-street-all-the-way itinerary + * (if it exists). If an itinerary cost exceeds the limit computed from the best + * all-the-way-on-street itinerary, then the transit itinerary is removed. */ -public class RemoveTransitIfStreetOnlyIsBetterFilter implements ItineraryDeletionFlagger { +public class RemoveTransitIfStreetOnlyIsBetter implements RemoveItineraryFlagger { private final CostLinearFunction costLimitFunction; - public RemoveTransitIfStreetOnlyIsBetterFilter(CostLinearFunction costLimitFunction) { + public RemoveTransitIfStreetOnlyIsBetter(CostLinearFunction costLimitFunction) { this.costLimitFunction = costLimitFunction; } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/RemoveTransitIfWalkingIsBetter.java similarity index 72% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/RemoveTransitIfWalkingIsBetter.java index 4b645e45a79..cf4d102c11a 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/RemoveTransitIfWalkingIsBetter.java @@ -1,19 +1,15 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.transit; -import java.util.Comparator; import java.util.List; -import java.util.OptionalDouble; import java.util.OptionalInt; import java.util.stream.Collectors; -import org.opentripplanner.framework.model.Cost; import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.model.plan.Leg; -import org.opentripplanner.routing.api.request.framework.CostLinearFunction; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; /** - * Filter itineraries which have a higher generalized-cost than a pure walk itinerary. + * Filter itineraries which have a higher generalized-cost than the walk-only itinerary (if exist). */ -public class RemoveTransitIfWalkingIsBetterFilter implements ItineraryDeletionFlagger { +public class RemoveTransitIfWalkingIsBetter implements RemoveItineraryFlagger { /** * Required for {@link org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChain}, diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/TransitGeneralizedCostFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/TransitGeneralizedCostFilter.java similarity index 79% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/TransitGeneralizedCostFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/TransitGeneralizedCostFilter.java index f296ca0f7c1..4d7ffffa13a 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/TransitGeneralizedCostFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/TransitGeneralizedCostFilter.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.transit; import java.time.temporal.ChronoUnit; import java.util.Comparator; @@ -7,14 +7,15 @@ import org.opentripplanner.framework.lang.IntUtils; import org.opentripplanner.framework.model.Cost; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; /** - * This filter remove all transit results which have a generalized-cost higher than the max-limit + * This filter removes all transit results that have a generalized-cost higher than the max-limit * computed by the {@link #costLimitFunction} plus the wait cost given by * {@link TransitGeneralizedCostFilter#getWaitTimeCost}. */ -public class TransitGeneralizedCostFilter implements ItineraryDeletionFlagger { +public class TransitGeneralizedCostFilter implements RemoveItineraryFlagger { private final CostLinearFunction costLimitFunction; @@ -40,11 +41,11 @@ public List flagForRemoval(List itineraries) { return transitItineraries .stream() - .filter(it -> transitItineraries.stream().anyMatch(t -> acceptGeneralizedCost(it, t))) + .filter(it -> transitItineraries.stream().anyMatch(t -> generalizedCostExceedsLimit(it, t))) .collect(Collectors.toList()); } - private boolean acceptGeneralizedCost(Itinerary subject, Itinerary transitItinerary) { + private boolean generalizedCostExceedsLimit(Itinerary subject, Itinerary transitItinerary) { return subject.getGeneralizedCost() > calculateLimit(subject, transitItinerary); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filter/SameFirstOrLastTripFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/group/RemoveIfFirstOrLastTripIsTheSame.java similarity index 58% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/filter/SameFirstOrLastTripFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/group/RemoveIfFirstOrLastTripIsTheSame.java index a556530cb36..bc72aada043 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filter/SameFirstOrLastTripFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/group/RemoveIfFirstOrLastTripIsTheSame.java @@ -1,23 +1,21 @@ -package org.opentripplanner.routing.algorithm.filterchain.filter; +package org.opentripplanner.routing.algorithm.filterchain.filters.transit.group; import java.util.ArrayList; import java.util.List; import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.ItineraryDeletionFlagger; -import org.opentripplanner.routing.algorithm.filterchain.groupids.GroupBySameFirstOrLastTrip; +import org.opentripplanner.routing.algorithm.filterchain.framework.groupids.GroupBySameFirstOrLastTrip; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; /** - * This filter ensures that no more than one itinerary begins or ends with same trip. - * It loops through itineraries from top to bottom. If itinerary matches with any other itinerary - * from above, it is removed from list. - * Uses {@link org.opentripplanner.routing.algorithm.filterchain.groupids.GroupBySameFirstOrLastTrip}. - * for matching itineraries. + * This filter ensures that no more than one itinerary begins or ends with the same trip. + * It loops through itineraries from top to bottom. If an itinerary matches another itinerary, then + * it is removed from the list. Uses {@link GroupBySameFirstOrLastTrip}. */ -public class SameFirstOrLastTripFilter implements ItineraryDeletionFlagger { +public class RemoveIfFirstOrLastTripIsTheSame implements RemoveItineraryFlagger { @Override public String name() { - return "SameFirstOrLastTripFilter"; + return "same-first-or-last-trip-filter"; } @Override diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OtherThanSameLegsMaxGeneralizedCostFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/group/RemoveOtherThanSameLegsMaxGeneralizedCost.java similarity index 73% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OtherThanSameLegsMaxGeneralizedCostFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/group/RemoveOtherThanSameLegsMaxGeneralizedCost.java index b8ed8e90de2..0194cfa2bbc 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OtherThanSameLegsMaxGeneralizedCostFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/group/RemoveOtherThanSameLegsMaxGeneralizedCost.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.transit.group; import java.util.List; import java.util.OptionalInt; @@ -7,23 +7,24 @@ import java.util.stream.Collectors; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; -import org.opentripplanner.routing.algorithm.filterchain.filter.GroupByFilter; +import org.opentripplanner.routing.algorithm.filterchain.framework.filter.GroupByFilter; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; import org.opentripplanner.transit.model.timetable.Trip; /** - * This filter marks itineraries, which use same trips for most of their legs, but where some - * itineraries have a much higher cost for the other legs, for deletion. This is similar to {@link - * TransitGeneralizedCostFilter}, but is used together with {@link GroupByFilter} to filter within - * the groups. + * This filter remove itineraries, which use the same trips for most of their legs, but where some + * itineraries have a much higher cost for the other legs. This is similar to {@link + * org.opentripplanner.routing.algorithm.filterchain.filters.transit.TransitGeneralizedCostFilter}, + * but is used together with {@link GroupByFilter} to filter within the groups. */ -public class OtherThanSameLegsMaxGeneralizedCostFilter implements ItineraryDeletionFlagger { +public class RemoveOtherThanSameLegsMaxGeneralizedCost implements RemoveItineraryFlagger { /** - * How much higher cost do we allow for the non-shared legs before we filter out the itinerary. + * How much higher cost do we allow for the non-shared legs before we filter out the itinerary? */ private final double maxCostOtherLegsFactor; - public OtherThanSameLegsMaxGeneralizedCostFilter(double maxCostOtherLegsFactor) { + public RemoveOtherThanSameLegsMaxGeneralizedCost(double maxCostOtherLegsFactor) { this.maxCostOtherLegsFactor = maxCostOtherLegsFactor; } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/DecorateFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/DecorateFilter.java new file mode 100644 index 00000000000..f0d44872db8 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/DecorateFilter.java @@ -0,0 +1,27 @@ +package org.opentripplanner.routing.algorithm.filterchain.framework.filter; + +import java.util.List; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryDecorator; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryListFilter; + +/** + * This is the decorator filter implementation. To add a decorator, you should implement + * the {@link ItineraryDecorator}. + */ +public final class DecorateFilter implements ItineraryListFilter { + + private final ItineraryDecorator decorator; + + public DecorateFilter(ItineraryDecorator decorator) { + this.decorator = decorator; + } + + @Override + public List filter(List itineraries) { + for (var it : itineraries) { + decorator.decorate(it); + } + return itineraries; + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filter/GroupByFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/GroupByFilter.java similarity index 90% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/filter/GroupByFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/GroupByFilter.java index 8084f4ced19..73f84625750 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filter/GroupByFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/GroupByFilter.java @@ -1,11 +1,11 @@ -package org.opentripplanner.routing.algorithm.filterchain.filter; +package org.opentripplanner.routing.algorithm.filterchain.framework.filter; import java.util.ArrayList; import java.util.List; import java.util.function.Function; import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilter; -import org.opentripplanner.routing.algorithm.filterchain.groupids.GroupId; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.GroupId; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryListFilter; /** * This filter groups the itineraries using a group-id and filter each group by the given {@code diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/MaxLimitFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/MaxLimit.java similarity index 69% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/MaxLimitFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/MaxLimit.java index 3a9d1422d34..5ab1e88f730 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/MaxLimitFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/MaxLimit.java @@ -1,18 +1,19 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.framework.filter; import java.util.List; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; /** * Flags the itineraries at the end of the list for removal. The list should be sorted on the * desired key before this filter is applied. */ -public class MaxLimitFilter implements ItineraryDeletionFlagger { +public class MaxLimit implements RemoveItineraryFlagger { private final String name; private final int maxLimit; - public MaxLimitFilter(String name, int maxLimit) { + public MaxLimit(String name, int maxLimit) { this.name = name; this.maxLimit = maxLimit; } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filter/DeletionFlaggingFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/RemoveFilter.java similarity index 67% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/filter/DeletionFlaggingFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/RemoveFilter.java index 43009b5012e..e83aa237cab 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filter/DeletionFlaggingFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/RemoveFilter.java @@ -1,23 +1,23 @@ -package org.opentripplanner.routing.algorithm.filterchain.filter; +package org.opentripplanner.routing.algorithm.filterchain.framework.filter; import java.util.List; import java.util.function.Predicate; import java.util.stream.Collectors; import org.opentripplanner.model.SystemNotice; import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilter; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.ItineraryDeletionFlagger; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryListFilter; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; /** * This class is responsible for flagging itineraries for deletion based on a predicate in the - * supplied ItineraryDeletionFlagger. The itineraries are not actually deleted at this point, just + * supplied RemoveItineraryFlagger. The itineraries are not actually deleted at this point, just * flagged. They are typically deleted later if debug mode is disabled. */ -public class DeletionFlaggingFilter implements ItineraryListFilter { +public class RemoveFilter implements ItineraryListFilter { - private final ItineraryDeletionFlagger flagger; + private final RemoveItineraryFlagger flagger; - public DeletionFlaggingFilter(ItineraryDeletionFlagger flagger) { + public RemoveFilter(RemoveItineraryFlagger flagger) { this.flagger = flagger; } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filter/SortingFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/SortingFilter.java similarity index 84% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/filter/SortingFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/SortingFilter.java index 3f1bae9049e..0b2664ccf26 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filter/SortingFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/SortingFilter.java @@ -1,11 +1,11 @@ -package org.opentripplanner.routing.algorithm.filterchain.filter; +package org.opentripplanner.routing.algorithm.filterchain.framework.filter; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.ItinerarySortKey; -import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilter; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryListFilter; /** * This is a filter to sort itineraries. To create a filter, provide a comparator as a constructor diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/DeleteResultHandler.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filterchain/DeleteResultHandler.java similarity index 84% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/DeleteResultHandler.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filterchain/DeleteResultHandler.java index c09af943564..c713ed7ac46 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/DeleteResultHandler.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filterchain/DeleteResultHandler.java @@ -1,17 +1,17 @@ -package org.opentripplanner.routing.algorithm.filterchain; +package org.opentripplanner.routing.algorithm.filterchain.framework.filterchain; import java.util.List; import java.util.function.Predicate; import java.util.stream.Collectors; import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.OutsideSearchWindowFilter; +import org.opentripplanner.routing.algorithm.filterchain.filters.system.OutsideSearchWindowFilter; import org.opentripplanner.routing.api.request.preference.ItineraryFilterDebugProfile; /** * This class will remove itineraries from the list which are flagged for deletion by the * filters. */ -class DeleteResultHandler { +public class DeleteResultHandler { private final ItineraryFilterDebugProfile debug; private final int numOfItineraries; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/RoutingErrorsAttacher.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filterchain/RoutingErrorsAttacher.java similarity index 84% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/RoutingErrorsAttacher.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filterchain/RoutingErrorsAttacher.java index 63191fdb88e..6af4f240a10 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/RoutingErrorsAttacher.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filterchain/RoutingErrorsAttacher.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain; +package org.opentripplanner.routing.algorithm.filterchain.framework.filterchain; import static org.opentripplanner.routing.api.response.InputField.DATE_TIME; import static org.opentripplanner.routing.api.response.RoutingErrorCode.NO_TRANSIT_CONNECTION_IN_SEARCH_WINDOW; @@ -9,16 +9,16 @@ import java.util.function.Predicate; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.StreetLeg; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.OutsideSearchWindowFilter; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveTransitIfStreetOnlyIsBetterFilter; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveTransitIfWalkingIsBetterFilter; +import org.opentripplanner.routing.algorithm.filterchain.filters.system.OutsideSearchWindowFilter; +import org.opentripplanner.routing.algorithm.filterchain.filters.transit.RemoveTransitIfStreetOnlyIsBetter; +import org.opentripplanner.routing.algorithm.filterchain.filters.transit.RemoveTransitIfWalkingIsBetter; import org.opentripplanner.routing.api.response.RoutingError; /** * Computes {@link org.opentripplanner.routing.api.response.RoutingError} instances from itinerary * before and after they have been through the filter chain. */ -class RoutingErrorsAttacher { +public class RoutingErrorsAttacher { /** * Computes error codes from the itineraries. @@ -28,7 +28,7 @@ class RoutingErrorsAttacher { * have the {@link org.opentripplanner.model.SystemNotice}s to look up * the error from. */ - static List computeErrors( + public static List computeErrors( List originalItineraries, List filteredItineraries ) { @@ -50,12 +50,12 @@ static List computeErrors( it .getSystemNotices() .stream() - .anyMatch(notice -> notice.tag().equals(RemoveTransitIfStreetOnlyIsBetterFilter.TAG)); + .anyMatch(notice -> notice.tag().equals(RemoveTransitIfStreetOnlyIsBetter.TAG)); Predicate isWorseThanWalking = it -> it .getSystemNotices() .stream() - .anyMatch(notice -> notice.tag().equals(RemoveTransitIfWalkingIsBetterFilter.TAG)); + .anyMatch(notice -> notice.tag().equals(RemoveTransitIfWalkingIsBetter.TAG)); if ( filteredItineraries .stream() diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupByAllSameStations.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupByAllSameStations.java similarity index 91% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupByAllSameStations.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupByAllSameStations.java index f5199a597cb..83a5e15c802 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupByAllSameStations.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupByAllSameStations.java @@ -1,9 +1,10 @@ -package org.opentripplanner.routing.algorithm.filterchain.groupids; +package org.opentripplanner.routing.algorithm.filterchain.framework.groupids; import java.util.List; import java.util.stream.Collectors; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.GroupId; import org.opentripplanner.transit.model.framework.FeedScopedId; /** diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupByDistance.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupByDistance.java similarity index 97% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupByDistance.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupByDistance.java index bbc9b764d15..7ab605fb792 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupByDistance.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupByDistance.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.groupids; +package org.opentripplanner.routing.algorithm.filterchain.framework.groupids; import java.util.Comparator; import java.util.List; @@ -7,6 +7,7 @@ import org.opentripplanner.model.plan.Leg; import org.opentripplanner.model.plan.StreetLeg; import org.opentripplanner.model.plan.TransitLeg; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.GroupId; /** * This class create a group identifier for an itinerary based on the longest legs which together diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupBySameFirstOrLastTrip.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupBySameFirstOrLastTrip.java similarity index 92% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupBySameFirstOrLastTrip.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupBySameFirstOrLastTrip.java index c8b942f1387..cd8c776ef66 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupBySameFirstOrLastTrip.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupBySameFirstOrLastTrip.java @@ -1,10 +1,11 @@ -package org.opentripplanner.routing.algorithm.filterchain.groupids; +package org.opentripplanner.routing.algorithm.filterchain.framework.groupids; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.GroupId; import org.opentripplanner.transit.model.framework.FeedScopedId; /** diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupBySameRoutesAndStops.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupBySameRoutesAndStops.java similarity index 90% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupBySameRoutesAndStops.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupBySameRoutesAndStops.java index 338ec98b1ff..ac95ae87db7 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupBySameRoutesAndStops.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupBySameRoutesAndStops.java @@ -1,9 +1,10 @@ -package org.opentripplanner.routing.algorithm.filterchain.groupids; +package org.opentripplanner.routing.algorithm.filterchain.framework.groupids; import java.util.List; import java.util.stream.Stream; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.GroupId; import org.opentripplanner.transit.model.framework.FeedScopedId; /** diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOnGeneralizedCost.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/sort/SortOnGeneralizedCost.java similarity index 87% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOnGeneralizedCost.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/sort/SortOnGeneralizedCost.java index 364f9fddfff..10c10362c46 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOnGeneralizedCost.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/sort/SortOnGeneralizedCost.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.comparator; +package org.opentripplanner.routing.algorithm.filterchain.framework.sort; import org.opentripplanner.framework.collection.CompositeComparator; import org.opentripplanner.model.plan.ItinerarySortKey; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparator.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/sort/SortOrderComparator.java similarity index 97% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparator.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/sort/SortOrderComparator.java index e52e53ab1c7..475309eeae4 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/sort/SortOrderComparator.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.comparator; +package org.opentripplanner.routing.algorithm.filterchain.framework.sort; import static java.util.Comparator.comparing; import static java.util.Comparator.comparingInt; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupId.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/spi/GroupId.java similarity index 98% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupId.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/spi/GroupId.java index c83d3f4a9a7..cb2b72f93ee 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupId.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/spi/GroupId.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.groupids; +package org.opentripplanner.routing.algorithm.filterchain.framework.spi; /** * A group-id identify a group of elements(itineraries). Group-ids can be arranged in a hierarchy diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/spi/ItineraryDecorator.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/spi/ItineraryDecorator.java new file mode 100644 index 00000000000..4df14c7f5ab --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/spi/ItineraryDecorator.java @@ -0,0 +1,13 @@ +package org.opentripplanner.routing.algorithm.filterchain.framework.spi; + +import org.opentripplanner.model.plan.Itinerary; + +/** + * Use this interface to decorate itineraries with more information. + */ +public interface ItineraryDecorator { + /** + * Implement this to decorate each itinerary in the result. + */ + void decorate(Itinerary itinerary); +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/spi/ItineraryListFilter.java similarity index 71% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/spi/ItineraryListFilter.java index 883e6c48339..7d795e6c7fb 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/spi/ItineraryListFilter.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain; +package org.opentripplanner.routing.algorithm.filterchain.framework.spi; import java.util.List; import org.opentripplanner.model.plan.Itinerary; @@ -8,20 +8,20 @@ * List. It should treat the list as immutable. Do not change the list passed into the filter, * instead make a copy, change it and return the copy. It is allowed to return the list unchanged. *

- * A filter should do only one thing! For example do not change the itineraries and delete elements + * A filter should do only one thing! For example, do not change the itineraries and delete elements * in the same filter. Instead, create two filters and insert them after each other in the filter * chain. *

- * This allows decoration of each filter and make it easier to reuse logic. Like the {@link - * org.opentripplanner.routing.algorithm.filterchain.deletionflagger.MaxLimitFilter} is reused in - * several places. + * This allows decoration of each filter and makes it easier to reuse logic. Like the + * {@link org.opentripplanner.routing.algorithm.filterchain.framework.filter.MaxLimit} is reused + * in several places. */ public interface ItineraryListFilter { /** * Process the given itineraries returning the result. *

* This function should not change the List instance passed in, but may change the elements. It - * must return a List with all the elements passed in (and possibly new elements). Note! that the + * must return a List with all the elements passed in (and possibly new elements). Note! The * list passed into the filter might be immutable. *

* This can be achieved using streams. Example: diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/ItineraryDeletionFlagger.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/spi/RemoveItineraryFlagger.java similarity index 91% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/ItineraryDeletionFlagger.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/spi/RemoveItineraryFlagger.java index c6db9c14cf6..4918f440d1f 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/ItineraryDeletionFlagger.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/spi/RemoveItineraryFlagger.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.framework.spi; import java.util.ArrayList; import java.util.List; @@ -16,7 +16,7 @@ *

  • {@link #flagForRemoval(List)}}) - If you need more than one itinerary to decide which to delete.
  • * */ -public interface ItineraryDeletionFlagger { +public interface RemoveItineraryFlagger { /** * A name used for debugging the itinerary list filter chain. *

    @@ -47,7 +47,7 @@ default List flagForRemoval(List itineraries) { /** * Should itineraries already marked for deletion by previous deletionflagger be removed from the - * list passed to {@link ItineraryDeletionFlagger#flagForRemoval(List)}. The default value + * list passed to {@link RemoveItineraryFlagger#flagForRemoval(List)}. The default value * is true, as usually the already removed itineraries are not needed further in the filter * chain. */ diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChain.excalidraw b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/images/ItineraryListFilterChain.excalidraw similarity index 63% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChain.excalidraw rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/images/ItineraryListFilterChain.excalidraw index 3c1701f2dba..37e3f8de7b2 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChain.excalidraw +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/images/ItineraryListFilterChain.excalidraw @@ -5,8 +5,8 @@ "elements": [ { "type": "arrow", - "version": 2390, - "versionNonce": 369632805, + "version": 2483, + "versionNonce": 773348709, "isDeleted": false, "id": "uTKhXBXl80XXPjGsftdJa", "fillStyle": "hachure", @@ -15,16 +15,22 @@ "roughness": 1, "opacity": 100, "angle": 0, - "x": 566.848335090324, + "x": 564.2990304950548, "y": 564.1295357290845, "strokeColor": "#000000", "backgroundColor": "transparent", - "width": 3.2043051907536437, - "height": 74.45005932341121, + "width": 0.35168042093721397, + "height": 74.3498281379907, "seed": 1822339590, "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1705593960293, + "link": null, + "locked": false, "startBinding": { "elementId": "M76RD0PeJrkYP9LuS_Kxu", "gap": 6.688129479084512, @@ -44,15 +50,15 @@ 0 ], [ - 3.2043051907536437, - 74.45005932341121 + -0.35168042093721397, + 74.3498281379907 ] ] }, { "type": "ellipse", - "version": 919, - "versionNonce": 1250982571, + "version": 968, + "versionNonce": 607278955, "isDeleted": false, "id": "oV4YIjz-w-0HS_dvr7ZoB", "fillStyle": "hachure", @@ -61,23 +67,30 @@ "roughness": 1, "opacity": 100, "angle": 0, - "x": 438.08984375, + "x": 411.91796875, "y": 643.015625, "strokeColor": "#000000", "backgroundColor": "transparent", - "width": 274.64453125, - "height": 282.50781250000006, + "width": 300.81640625000006, + "height": 317.625, "seed": 1296092422, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "uTKhXBXl80XXPjGsftdJa" - ] + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "arrow", + "id": "uTKhXBXl80XXPjGsftdJa" + } + ], + "updated": 1705593960293, + "link": null, + "locked": false }, { "type": "ellipse", - "version": 138, - "versionNonce": 1712254071, + "version": 143, + "versionNonce": 810641605, "isDeleted": false, "id": "iXHhu1-MD9tXNfoZZ8Rcq", "fillStyle": "hachure", @@ -94,15 +107,22 @@ "height": 170.08984375, "seed": 1752922886, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "i6hMn3CPVbRxyPcniV7yb" - ] + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "arrow", + "id": "i6hMn3CPVbRxyPcniV7yb" + } + ], + "updated": 1705593960293, + "link": null, + "locked": false }, { "type": "arrow", - "version": 362, - "versionNonce": 1362745241, + "version": 367, + "versionNonce": 1181364747, "isDeleted": false, "id": "i6hMn3CPVbRxyPcniV7yb", "fillStyle": "hachure", @@ -119,8 +139,14 @@ "height": 79.85129297487731, "seed": 398792602, "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1705593960293, + "link": null, + "locked": false, "startBinding": { "elementId": "NLnBzR8OLk5gbeRQGwI9f", "focus": -0.18252063102968885, @@ -147,8 +173,8 @@ }, { "type": "text", - "version": 91, - "versionNonce": 839585881, + "version": 114, + "versionNonce": 1376913445, "isDeleted": false, "id": "nJRU39yv_4LYhT-kVxv_t", "fillStyle": "hachure", @@ -157,27 +183,34 @@ "roughness": 1, "opacity": 100, "angle": 0, - "x": 760.2421875, - "y": 705.248046875, + "x": 754.1171875, + "y": 708.740234375, "strokeColor": "#000000", "backgroundColor": "transparent", - "width": 113, + "width": 112.73989868164062, "height": 50, "seed": 1641529222, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1705593960293, + "link": null, + "locked": false, "fontSize": 20, "fontFamily": 1, "text": "Comparator\n", - "baseline": 43, "textAlign": "center", - "verticalAlign": "middle" + "verticalAlign": "middle", + "containerId": null, + "originalText": "Comparator\n", + "lineHeight": 1.25, + "baseline": 43 }, { "type": "text", - "version": 259, - "versionNonce": 505668230, + "version": 267, + "versionNonce": 369997995, "isDeleted": false, "id": "D8AYLA3h4ksepFAEqkC7G", "fillStyle": "hachure", @@ -186,27 +219,34 @@ "roughness": 1, "opacity": 100, "angle": 0, - "x": 592.552734375, + "x": 592.4027938842773, "y": 214.466796875, "strokeColor": "#000000", "backgroundColor": "transparent", - "width": 125, + "width": 125.29988098144531, "height": 50, "seed": 1478178009, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1705593960293, + "link": null, + "locked": false, "fontSize": 20, "fontFamily": 1, "text": "ItineraryList\nFilterChain", - "baseline": 43, "textAlign": "center", - "verticalAlign": "top" + "verticalAlign": "top", + "containerId": null, + "originalText": "ItineraryList\nFilterChain", + "lineHeight": 1.25, + "baseline": 43 }, { "type": "arrow", - "version": 589, - "versionNonce": 1147854854, + "version": 594, + "versionNonce": 1699199877, "isDeleted": false, "id": "abNnvFw7GnSsRXaqnRUYs", "fillStyle": "hachure", @@ -223,8 +263,14 @@ "height": 6.933497082758379, "seed": 273649111, "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1705593960293, + "link": null, + "locked": false, "startBinding": { "elementId": "bA4uoIjQQ5b7VTjNXnYCH", "focus": 0.083136294577077, @@ -251,8 +297,8 @@ }, { "type": "rectangle", - "version": 179, - "versionNonce": 1070362266, + "version": 184, + "versionNonce": 1086810955, "isDeleted": false, "id": "bA4uoIjQQ5b7VTjNXnYCH", "fillStyle": "hachure", @@ -269,15 +315,22 @@ "height": 88.37109375, "seed": 1943497081, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "abNnvFw7GnSsRXaqnRUYs" - ] + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "arrow", + "id": "abNnvFw7GnSsRXaqnRUYs" + } + ], + "updated": 1705593960293, + "link": null, + "locked": false }, { "type": "text", - "version": 138, - "versionNonce": 463485399, + "version": 146, + "versionNonce": 573571813, "isDeleted": false, "id": "A4ZjgRaXH3lARCAtxxyGH", "fillStyle": "hachure", @@ -290,23 +343,30 @@ "y": 200.08984375, "strokeColor": "#000000", "backgroundColor": "transparent", - "width": 10, + "width": 10.319992065429688, "height": 25, "seed": 770881047, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1705593960293, + "link": null, + "locked": false, "fontSize": 20, "fontFamily": 1, "text": "*", - "baseline": 18, "textAlign": "left", - "verticalAlign": "top" + "verticalAlign": "top", + "containerId": null, + "originalText": "*", + "lineHeight": 1.25, + "baseline": 18 }, { "type": "line", - "version": 130, - "versionNonce": 1350932407, + "version": 135, + "versionNonce": 1525795307, "isDeleted": false, "id": "xPF7fs9PPAG3FCbgCedlx", "fillStyle": "hachure", @@ -323,8 +383,14 @@ "height": 54.202357687428616, "seed": 1466164567, "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1705593960293, + "link": null, + "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, @@ -343,8 +409,8 @@ }, { "type": "freedraw", - "version": 165, - "versionNonce": 164708486, + "version": 170, + "versionNonce": 1566072389, "isDeleted": false, "id": "v7Du5WLNb590wL5Uam6Ta", "fillStyle": "hachure", @@ -361,8 +427,14 @@ "height": 19.62890625, "seed": 571266967, "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1705593960293, + "link": null, + "locked": false, "points": [ [ 0, @@ -608,8 +680,8 @@ }, { "type": "rectangle", - "version": 213, - "versionNonce": 363355223, + "version": 218, + "versionNonce": 160912523, "isDeleted": false, "id": "bXMUStvEb709SPfYF07CW", "fillStyle": "hachure", @@ -626,21 +698,26 @@ "height": 136.52343750000003, "seed": 1747224151, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "abNnvFw7GnSsRXaqnRUYs", - "DNUdRQjUxwIlQOGRroKjy", - "pRACykcqHnF03bgQc5Tc7", - "sBj3XDz6N2sSDfDg1hlEA", - "5o3WS1bjYB_SRMLHGEwV1", - "Qt7CHy0OYZwkEloLT2tU-", - "hhODwDUSnKDNHxeWgdBRh" - ] + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "arrow", + "id": "abNnvFw7GnSsRXaqnRUYs" + }, + { + "type": "arrow", + "id": "hhODwDUSnKDNHxeWgdBRh" + } + ], + "updated": 1705593960293, + "link": null, + "locked": false }, { "type": "arrow", - "version": 638, - "versionNonce": 1491667819, + "version": 643, + "versionNonce": 2107526565, "isDeleted": false, "id": "hhODwDUSnKDNHxeWgdBRh", "fillStyle": "hachure", @@ -657,8 +734,14 @@ "height": 236.4110879594133, "seed": 413129015, "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1705593960293, + "link": null, + "locked": false, "startBinding": { "elementId": "gL_hDnXZ2lRjaViYaG2rV", "gap": 4.0451591623761765, @@ -693,8 +776,8 @@ }, { "type": "text", - "version": 119, - "versionNonce": 992742379, + "version": 127, + "versionNonce": 1819446059, "isDeleted": false, "id": "svSoLS4U9UhcZVccLd8Kz", "fillStyle": "hachure", @@ -703,30 +786,39 @@ "roughness": 1, "opacity": 100, "angle": 0, - "x": 954.3359375, + "x": 954.5559997558594, "y": 483.53125, "strokeColor": "#000000", "backgroundColor": "transparent", - "width": 133, + "width": 132.55987548828125, "height": 75, "seed": 736680902, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "PigWa1tRUEcK9ZGLifHF3", - "hhODwDUSnKDNHxeWgdBRh" + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "arrow", + "id": "hhODwDUSnKDNHxeWgdBRh" + } ], + "updated": 1705593960293, + "link": null, + "locked": false, "fontSize": 20, "fontFamily": 1, "text": "GroupByFilter\n\n", - "baseline": 68, "textAlign": "center", - "verticalAlign": "top" + "verticalAlign": "top", + "containerId": null, + "originalText": "GroupByFilter\n\n", + "lineHeight": 1.25, + "baseline": 68 }, { "type": "ellipse", - "version": 233, - "versionNonce": 461103578, + "version": 238, + "versionNonce": 841961733, "isDeleted": false, "id": "1H3J1TVatGqsTmeb03PzM", "fillStyle": "hachure", @@ -743,13 +835,17 @@ "height": 21.5234375, "seed": 145370007, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [] + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1705593960293, + "link": null, + "locked": false }, { "type": "text", - "version": 541, - "versionNonce": 1246878711, + "version": 549, + "versionNonce": 1206775243, "isDeleted": false, "id": "eMUtrGSf4_tdjT-SgVl6r", "fillStyle": "hachure", @@ -762,23 +858,30 @@ "y": 216.8515625, "strokeColor": "#000000", "backgroundColor": "transparent", - "width": 128, + "width": 127.57986450195312, "height": 25, "seed": 276931526, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1705593960293, + "link": null, + "locked": false, "fontSize": 20, "fontFamily": 1, "text": "nestedFilters", - "baseline": 18, "textAlign": "left", - "verticalAlign": "top" + "verticalAlign": "top", + "containerId": null, + "originalText": "nestedFilters", + "lineHeight": 1.25, + "baseline": 18 }, { "type": "text", - "version": 199, - "versionNonce": 1740659001, + "version": 207, + "versionNonce": 1753802853, "isDeleted": false, "id": "I00FPxjJzkybs748xvxYQ", "fillStyle": "hachure", @@ -791,25 +894,30 @@ "y": 210.4375, "strokeColor": "#000000", "backgroundColor": "transparent", - "width": 10, + "width": 10.319992065429688, "height": 25, "seed": 654279366, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "Qt7CHy0OYZwkEloLT2tU-" - ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1705593960293, + "link": null, + "locked": false, "fontSize": 20, "fontFamily": 1, "text": "*", - "baseline": 18, "textAlign": "left", - "verticalAlign": "top" + "verticalAlign": "top", + "containerId": null, + "originalText": "*", + "lineHeight": 1.25, + "baseline": 18 }, { "type": "text", - "version": 115, - "versionNonce": 1382233690, + "version": 125, + "versionNonce": 247892555, "isDeleted": false, "id": "MNcWXxi6VbZaulu3K670q", "fillStyle": "hachure", @@ -818,27 +926,34 @@ "roughness": 1, "opacity": 100, "angle": 0, - "x": 901.361328125, + "x": 902.5613327026367, "y": 215.78125, "strokeColor": "#000000", "backgroundColor": "transparent", - "width": 89, + "width": 86.77992248535156, "height": 75, "seed": 1687297177, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1705598138238, + "link": null, + "locked": false, "fontSize": 20, "fontFamily": 1, - "text": "Itinarary\nList\nFilter", - "baseline": 68, + "text": "Itinerary\nList\nFilter", "textAlign": "center", - "verticalAlign": "middle" + "verticalAlign": "middle", + "containerId": null, + "originalText": "Itinerary\nList\nFilter", + "lineHeight": 1.25, + "baseline": 68 }, { "type": "rectangle", - "version": 495, - "versionNonce": 1318057157, + "version": 500, + "versionNonce": 41456581, "isDeleted": false, "id": "gL_hDnXZ2lRjaViYaG2rV", "fillStyle": "hachure", @@ -855,19 +970,22 @@ "height": 98.89843749999999, "seed": 1515944921, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "pRACykcqHnF03bgQc5Tc7", - "sBj3XDz6N2sSDfDg1hlEA", - "5o3WS1bjYB_SRMLHGEwV1", - "Qt7CHy0OYZwkEloLT2tU-", - "hhODwDUSnKDNHxeWgdBRh" - ] + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "arrow", + "id": "hhODwDUSnKDNHxeWgdBRh" + } + ], + "updated": 1705593960293, + "link": null, + "locked": false }, { "type": "line", - "version": 119, - "versionNonce": 311957830, + "version": 124, + "versionNonce": 1843345163, "isDeleted": false, "id": "gF_BvGrwKBQV_s0LLt7II", "fillStyle": "hachure", @@ -884,8 +1002,14 @@ "height": 65.39966988721864, "seed": 810609625, "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1705593960293, + "link": null, + "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, @@ -904,8 +1028,8 @@ }, { "type": "line", - "version": 89, - "versionNonce": 9693367, + "version": 94, + "versionNonce": 1276891941, "isDeleted": false, "id": "6V31n7Wzh9bRD4MWf72QE", "fillStyle": "hachure", @@ -922,8 +1046,14 @@ "height": 79.91170934028924, "seed": 641892473, "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1705593960293, + "link": null, + "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, @@ -942,8 +1072,8 @@ }, { "type": "line", - "version": 151, - "versionNonce": 651803289, + "version": 156, + "versionNonce": 1922702763, "isDeleted": false, "id": "jItuiRV-cqbnZuN1BUg5k", "fillStyle": "hachure", @@ -960,8 +1090,14 @@ "height": 66.58413111448289, "seed": 583293079, "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1705593960293, + "link": null, + "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, @@ -980,8 +1116,8 @@ }, { "type": "line", - "version": 122, - "versionNonce": 312292279, + "version": 127, + "versionNonce": 836773509, "isDeleted": false, "id": "YXFr7rhGIICmJAWKZ5eUK", "fillStyle": "hachure", @@ -998,8 +1134,14 @@ "height": 60.49037211317568, "seed": 2016803833, "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1705593960293, + "link": null, + "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, @@ -1018,8 +1160,8 @@ }, { "type": "rectangle", - "version": 355, - "versionNonce": 2063763525, + "version": 360, + "versionNonce": 2021601355, "isDeleted": false, "id": "M76RD0PeJrkYP9LuS_Kxu", "fillStyle": "hachure", @@ -1036,16 +1178,22 @@ "height": 95.359375, "seed": 1486134679, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "DNUdRQjUxwIlQOGRroKjy", - "uTKhXBXl80XXPjGsftdJa" - ] + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "arrow", + "id": "uTKhXBXl80XXPjGsftdJa" + } + ], + "updated": 1705593960293, + "link": null, + "locked": false }, { "type": "rectangle", - "version": 132, - "versionNonce": 662378169, + "version": 137, + "versionNonce": 914558437, "isDeleted": false, "id": "NLnBzR8OLk5gbeRQGwI9f", "fillStyle": "hachure", @@ -1062,16 +1210,22 @@ "height": 95.359375, "seed": 1267074617, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "ZiK53Ibs5EcfnOH3MWUmX", - "i6hMn3CPVbRxyPcniV7yb" - ] + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "arrow", + "id": "i6hMn3CPVbRxyPcniV7yb" + } + ], + "updated": 1705593960293, + "link": null, + "locked": false }, { "type": "text", - "version": 76, - "versionNonce": 206050586, + "version": 86, + "versionNonce": 828960491, "isDeleted": false, "id": "QxjDVUdnC9uReskEwKjsd", "fillStyle": "hachure", @@ -1080,27 +1234,39 @@ "roughness": 1, "opacity": 100, "angle": 0, - "x": 1183.578125, + "x": 1189.9781188964844, "y": 506.0703125, "strokeColor": "#000000", "backgroundColor": "#868e96", - "width": 157, + "width": 143.79986572265625, "height": 25, "seed": 2048669126, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "3FvznihDEqVpPp0WjjZ26", + "type": "arrow" + } + ], + "updated": 1705593960293, + "link": null, + "locked": false, "fontSize": 20, "fontFamily": 1, - "text": "DecoratingFilter", - "baseline": 18, + "text": "DecorateFilter", "textAlign": "center", - "verticalAlign": "middle" + "verticalAlign": "middle", + "containerId": null, + "originalText": "DecorateFilter", + "lineHeight": 1.25, + "baseline": 18 }, { "type": "text", - "version": 77, - "versionNonce": 1299176613, + "version": 86, + "versionNonce": 689047877, "isDeleted": false, "id": "zcbS0wgQbYVDRmrAUTDaV", "fillStyle": "hachure", @@ -1109,27 +1275,34 @@ "roughness": 1, "opacity": 100, "angle": 0, - "x": 467.625, + "x": 509.32505798339844, "y": 500.16796875, "strokeColor": "#000000", "backgroundColor": "transparent", - "width": 205, + "width": 121.59988403320312, "height": 25, "seed": 1535284294, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1705593960293, + "link": null, + "locked": false, "fontSize": 20, "fontFamily": 1, - "text": "DeletionFlaggingFilter", - "baseline": 18, + "text": "RemoveFilter", "textAlign": "center", - "verticalAlign": "top" + "verticalAlign": "top", + "containerId": null, + "originalText": "RemoveFilter", + "lineHeight": 1.25, + "baseline": 18 }, { "type": "rectangle", - "version": 224, - "versionNonce": 1434841862, + "version": 230, + "versionNonce": 803330443, "isDeleted": false, "id": "sVoxOi_goA0Vz9JvN_EuG", "fillStyle": "hachure", @@ -1146,13 +1319,22 @@ "height": 95.359375, "seed": 1742969881, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [] + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "3FvznihDEqVpPp0WjjZ26", + "type": "arrow" + } + ], + "updated": 1705593960294, + "link": null, + "locked": false }, { "type": "text", - "version": 71, - "versionNonce": 617977913, + "version": 79, + "versionNonce": 451582117, "isDeleted": false, "id": "5bXiMRSAbkBVDNzuMHYjg", "fillStyle": "hachure", @@ -1165,23 +1347,30 @@ "y": 500.25, "strokeColor": "#000000", "backgroundColor": "transparent", - "width": 119, + "width": 118.83987426757812, "height": 25, "seed": 926646726, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1705593960294, + "link": null, + "locked": false, "fontSize": 20, "fontFamily": 1, "text": "SortingFilter", - "baseline": 18, "textAlign": "center", - "verticalAlign": "middle" + "verticalAlign": "middle", + "containerId": null, + "originalText": "SortingFilter", + "lineHeight": 1.25, + "baseline": 18 }, { "type": "line", - "version": 154, - "versionNonce": 182849495, + "version": 159, + "versionNonce": 1361342507, "isDeleted": false, "id": "Ugnzj7K6hXFkke4frMlUY", "fillStyle": "hachure", @@ -1198,8 +1387,14 @@ "height": 3.082508331146073, "seed": 787790041, "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1705593960294, + "link": null, + "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, @@ -1218,8 +1413,8 @@ }, { "type": "text", - "version": 451, - "versionNonce": 768155173, + "version": 687, + "versionNonce": 364526597, "isDeleted": false, "id": "5pmOYWhziuLQ_DZANfOzI", "fillStyle": "hachure", @@ -1228,26 +1423,154 @@ "roughness": 1, "opacity": 100, "angle": 0, - "x": 487.65625, - "y": 677.744140625, + "x": 418.52866278754334, + "y": 718.146484375, "strokeColor": "#000000", "backgroundColor": "transparent", - "width": 179, - "height": 225, + "width": 289.0015563964844, + "height": 145.5729166666666, "seed": 2016628486, "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1705593960294, + "link": null, + "locked": false, + "fontSize": 19.409722222222214, + "fontFamily": 1, + "text": "RemoveItineraryFlagger\n---\nname()\nshouldBeFlaggedForRemoval()\nskipAlreadyFlaggedItineraries()\n", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "RemoveItineraryFlagger\n---\nname()\nshouldBeFlaggedForRemoval()\nskipAlreadyFlaggedItineraries()\n", + "lineHeight": 1.25, + "baseline": 138 + }, + { + "type": "ellipse", + "version": 381, + "versionNonce": 1729835723, + "isDeleted": false, + "id": "-Wb1113k5o16q_0IwIAB8", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1156.7592311487206, + "y": 671.1135670590127, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 208.52734375, + "height": 199.65234375000003, + "seed": 1166357119, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "arrow", + "id": "3FvznihDEqVpPp0WjjZ26" + } + ], + "updated": 1705593960294, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 911, + "versionNonce": 293499749, + "isDeleted": false, + "id": "3FvznihDEqVpPp0WjjZ26", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1258.7430659828142, + "y": 573.1253432215196, + "strokeColor": "#000000", + "backgroundColor": "#868e96", + "width": 1.9333557957434095, + "height": 93.76169170168737, + "seed": 1750633119, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1705593960294, + "link": null, + "locked": false, + "startBinding": { + "elementId": "sVoxOi_goA0Vz9JvN_EuG", + "gap": 4.390968221519643, + "focus": 0.06426443161706548 + }, + "endBinding": { + "elementId": "-Wb1113k5o16q_0IwIAB8", + "gap": 4.227062776875528, + "focus": 0.017251680890168403 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 1.9333557957434095, + 93.76169170168737 + ] + ] + }, + { + "type": "text", + "version": 402, + "versionNonce": 576758123, + "isDeleted": false, + "id": "mO-wquP1JTkteitq9b6D4", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1165.4773936853417, + "y": 728.7756764340127, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 189.45982360839844, + "height": 75, + "seed": 1665916607, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1705593960294, + "link": null, + "locked": false, "fontSize": 20, "fontFamily": 1, - "text": "Itinerary\nDeletionFlagger\n-------\npredicate()\ngetFlagged\nItineraries(List)\nskipAlreadyFlagged\nItineraries()\n", - "baseline": 218, + "text": "ItineraryDecorator\n---\ndecorate(Itinerary)", "textAlign": "center", - "verticalAlign": "top" + "verticalAlign": "middle", + "containerId": null, + "originalText": "ItineraryDecorator\n---\ndecorate(Itinerary)", + "lineHeight": 1.25, + "baseline": 68 } ], "appState": { "gridSize": null, "viewBackgroundColor": "#ffffff" - } + }, + "files": {} } \ No newline at end of file diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/images/ItineraryListFilterChain.svg b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/images/ItineraryListFilterChain.svg new file mode 100644 index 00000000000..6cf23afb7a3 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/images/ItineraryListFilterChain.svg @@ -0,0 +1,21 @@ + + + + + + + + Comparator<Itinerary>ItineraryListFilterChain*GroupByFilter<GroupId>nestedFilters*ItineraryListFilterDecorateFilterRemoveFilterSortingFilterRemoveItineraryFlagger---name()shouldBeFlaggedForRemoval()skipAlreadyFlaggedItineraries()ItineraryDecorator---decorate(Itinerary) \ No newline at end of file diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/package.md b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/package.md index 7630f96b2df..3ea8ceb54c8 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/package.md +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/package.md @@ -1,17 +1,17 @@ # ItineraryListFilterChain -ItineraryListFilterChain is a mechanism for post-processing itinerary results. It contains a list of -Each of which contains different business logic for a different operation. They not only remove -itineraries, but can be also used for sorting, decorating or even adding new itineraries. The filter -chain is also responsible for creating routing errors, if no itineraries remain after filtering. -Itineraries are flagged for deletion, but not actually deleted when debugging is turned on. When -debugging is off, the chain actually removes those itineraries that were flagged for deletion. +ItineraryListFilterChain is a mechanism for post-processing itinerary results. There are filters +for removing itineraries, sorting them and for decorating itineraries with more information like +fares. The filter chain is also responsible for creating routing errors, if no itineraries remain +after filtering. Itineraries are flagged for deletion, but not deleted when debugging is turned on. +When debugging is off, the chain removes those itineraries after all filters are complete. -![Architecture diagram](ItineraryListFilterChain.svg) +![Architecture diagram](images/ItineraryListFilterChain.svg) There are four types of filters, which can be included in the filter chain. The same type of filter can appear multiple times. + ## DeletionFlaggingFilter DeletionFlaggingFilter is responsible for flagging itineraries for deletion. It does not remove any @@ -22,19 +22,46 @@ selecting if the filter should skip already flagged itineraries to the flagger. disable, in case already removed itineraries are useful in comparing whether other itineraries should be flagged for removal. + ## SortingFilter SortingFilter is responsible for sorting the itineraries. It does this by having a Comparator, which is used for sorting. + ## GroupByFilter GroupByFilter is used to group itineraries together using a `GroupId`, and running a set of filters on that subset of itineraries. This is used eg. to remove almost similar search results and to sort them, so that only the best are shown to the user. + ## DecoratingFilter DecorationgFilter can be used to decorate the itineraries. This can be used eg to add information about ticketing and fares for each itinerary, and refining the routing cost of the itinerary, which -might affect the sorting order of the itineraries, depending on the order of the filters. \ No newline at end of file +might affect the sorting order of the itineraries, depending on the order of the filters. + + +## Package structure + +We try to separate use-case specific code (`filters`) and the generic filter chain implementation. +Here is an overview of the packages and their responsibilities. + +``` +filterchain +├── api Request parameters passed into the filter chain +├── filters Concrete filter implementations +│ ├── street For decorating/filtering street itineraries +│ ├── system Mainly support for otp features like paging and search-window crop +│ └── transit For decorating/filtering itineraries with transit +│ └── group Transit group filters +├── framework Generic filter chain implementation +│ ├── filter Filter implementation +│ ├── filterchain Domain logic used by the `FilterChain` (aggregate root). +│ ├── groupids Generic groupId implementations. These can be used as lego to build specific group-by filters. +│ ├── sort Sorting implementation +│ └── spi The interfaces to extend to plug into the filter-chain +└── images Images used in the doc + +``` diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java index 9858ccfee50..11302098f25 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java @@ -18,6 +18,7 @@ import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.geometry.GeometryUtils; import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.framework.time.ZoneIdFallback; import org.opentripplanner.model.plan.ElevationProfile; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; @@ -58,7 +59,7 @@ public GraphPathToItineraryMapper( StreetNotesService streetNotesService, double ellipsoidToGeoidDifference ) { - this.timeZone = timeZone; + this.timeZone = ZoneIdFallback.zoneId(timeZone); this.streetNotesService = streetNotesService; this.ellipsoidToGeoidDifference = ellipsoidToGeoidDifference; } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/PagingServiceFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/PagingServiceFactory.java index 8b122e6cf24..ec58d444914 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/PagingServiceFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/PagingServiceFactory.java @@ -4,9 +4,9 @@ import java.time.Instant; import java.util.List; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.paging.cursor.PageCursorInput; import org.opentripplanner.raptor.api.request.RaptorTuningParameters; import org.opentripplanner.raptor.api.request.SearchParams; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NumItinerariesFilterResults; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitTuningParameters; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.service.paging.PagingService; @@ -19,7 +19,7 @@ public static PagingService createPagingService( RaptorTuningParameters raptorTuningParameters, RouteRequest request, SearchParams raptorSearchParamsUsed, - NumItinerariesFilterResults numItinerariesFilterResults, + PageCursorInput pageCursorInput, List itineraries ) { return new PagingService( @@ -33,7 +33,7 @@ public static PagingService createPagingService( request.arriveBy(), request.numItineraries(), request.pageCursor(), - numItinerariesFilterResults, + pageCursorInput, itineraries ); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java index b379480bc33..651d94b4eac 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java @@ -4,15 +4,15 @@ import java.time.Instant; import java.util.List; import java.util.function.Consumer; -import org.opentripplanner.ext.emissions.EmissionsFilter; -import org.opentripplanner.ext.fares.FaresFilter; -import org.opentripplanner.ext.ridehailing.RideHailingFilter; -import org.opentripplanner.ext.stopconsolidation.ConsolidatedStopNameFilter; +import org.opentripplanner.ext.emissions.DecorateWithEmission; +import org.opentripplanner.ext.fares.DecorateWithFare; +import org.opentripplanner.ext.ridehailing.DecorateWithRideHailing; +import org.opentripplanner.ext.stopconsolidation.DecorateConsolidatedStopNames; import org.opentripplanner.framework.application.OTPFeature; -import org.opentripplanner.routing.algorithm.filterchain.GroupBySimilarity; +import org.opentripplanner.model.plan.paging.cursor.PageCursorInput; import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChain; import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChainBuilder; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NumItinerariesFilterResults; +import org.opentripplanner.routing.algorithm.filterchain.api.GroupBySimilarity; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.api.request.preference.ItineraryFilterPreferences; @@ -32,7 +32,7 @@ public static ItineraryListFilterChain createFilterChain( Instant earliestDepartureTimeUsed, Duration searchWindowUsed, boolean removeWalkAllTheWayResults, - Consumer maxLimitFilterResultsSubscriber + Consumer pageCursorInputSubscriber ) { var builder = new ItineraryListFilterChainBuilder(request.itinerariesSortOrder()); @@ -89,29 +89,29 @@ public static ItineraryListFilterChain createFilterChain( context.transitService()::getMultiModalStationForStation ) .withSearchWindow(earliestDepartureTimeUsed, searchWindowUsed) - .withNumItinerariesFilterResultsConsumer(maxLimitFilterResultsSubscriber) + .withPageCursorInputSubscriber(pageCursorInputSubscriber) .withRemoveWalkAllTheWayResults(removeWalkAllTheWayResults) .withRemoveTransitIfWalkingIsBetter(true) .withDebugEnabled(params.debug()); var fareService = context.graph().getFareService(); if (fareService != null) { - builder.withFaresFilter(new FaresFilter(fareService)); + builder.withFareDecorator(new DecorateWithFare(fareService)); } if (!context.rideHailingServices().isEmpty()) { - builder.withRideHailingFilter( - new RideHailingFilter(context.rideHailingServices(), request.wheelchair()) + builder.withRideHailingDecoratingFilter( + new DecorateWithRideHailing(context.rideHailingServices(), request.wheelchair()) ); } if (OTPFeature.Co2Emissions.isOn() && context.emissionsService() != null) { - builder.withEmissions(new EmissionsFilter(context.emissionsService())); + builder.withEmissions(new DecorateWithEmission(context.emissionsService())); } if (context.stopConsolidationService() != null) { - builder.withStopConsolidationFilter( - new ConsolidatedStopNameFilter(context.stopConsolidationService()) + builder.withConsolidatedStopNamesDecorator( + new DecorateConsolidatedStopNames(context.stopConsolidationService()) ); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitGroupPriority32n.java similarity index 76% rename from src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java rename to src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitGroupPriority32n.java index 9b744932b8b..feb3f6f7b3a 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitGroupPriority32n.java @@ -1,33 +1,33 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority; import org.opentripplanner.raptor.api.model.DominanceFunction; -import org.opentripplanner.raptor.api.request.RaptorTransitPriorityGroupCalculator; +import org.opentripplanner.raptor.api.request.RaptorTransitGroupCalculator; /** * This is a "BitSet" implementation for groupId. It can store upto 32 groups, * a set with few elements does NOT dominate a set with more elements. */ -public class TransitPriorityGroup32n { +public class TransitGroupPriority32n { private static final int GROUP_ZERO = 0; private static final int MIN_SEQ_NO = 0; private static final int MAX_SEQ_NO = 32; - public static RaptorTransitPriorityGroupCalculator priorityCalculator() { - return new RaptorTransitPriorityGroupCalculator() { + public static RaptorTransitGroupCalculator priorityCalculator() { + return new RaptorTransitGroupCalculator() { @Override - public int mergeTransitPriorityGroupIds(int currentGroupIds, int boardingGroupId) { + public int mergeGroupIds(int currentGroupIds, int boardingGroupId) { return mergeInGroupId(currentGroupIds, boardingGroupId); } @Override public DominanceFunction dominanceFunction() { - return TransitPriorityGroup32n::dominate; + return TransitGroupPriority32n::dominate; } @Override public String toString() { - return "TransitPriorityGroup32nCalculator{}"; + return "TransitGroupPriority32nCalculator{}"; } }; } @@ -42,7 +42,7 @@ public static boolean dominate(int left, int right) { @Override public String toString() { - return "TransitPriorityGroup32n{}"; + return "TransitGroupPriority32n{}"; } /** @@ -64,12 +64,12 @@ public static int mergeInGroupId(final int currentSetOfGroupIds, final int newGr private static void assertValidGroupSeqNo(int priorityGroupIndex) { if (priorityGroupIndex < MIN_SEQ_NO) { throw new IllegalArgumentException( - "Transit priority group can not be a negative number: " + priorityGroupIndex + "Transit group priority can not be a negative number: " + priorityGroupIndex ); } if (priorityGroupIndex > MAX_SEQ_NO) { throw new IllegalArgumentException( - "Transit priority group exceeds max number of groups: " + + "Transit group priority exceeds max number of groups: " + priorityGroupIndex + " (MAX=" + MAX_SEQ_NO + diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index 75c1bcf7214..5f3b4b13746 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -13,6 +13,7 @@ import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RelaxFunction; +import org.opentripplanner.raptor.api.request.DebugRequestBuilder; import org.opentripplanner.raptor.api.request.Optimization; import org.opentripplanner.raptor.api.request.PassThroughPoint; import org.opentripplanner.raptor.api.request.RaptorRequest; @@ -21,7 +22,8 @@ import org.opentripplanner.routing.algorithm.raptoradapter.router.performance.PerformanceTimersForRaptor; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter; -import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitPriorityGroup32n; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitGroupPriority32n; +import org.opentripplanner.routing.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.transit.model.site.StopLocation; @@ -117,9 +119,9 @@ private RaptorRequest doMap() { builder.withMultiCriteria(mcBuilder -> { var pt = preferences.transit(); var r = pt.raptor(); - if (!pt.relaxTransitPriorityGroup().isNormal()) { - mcBuilder.withTransitPriorityCalculator(TransitPriorityGroup32n.priorityCalculator()); - mcBuilder.withRelaxC1(mapRelaxCost(pt.relaxTransitPriorityGroup())); + if (!pt.relaxTransitGroupPriority().isNormal()) { + mcBuilder.withTransitPriorityCalculator(TransitGroupPriority32n.priorityCalculator()); + mcBuilder.withRelaxC1(mapRelaxCost(pt.relaxTransitGroupPriority())); } else { mcBuilder.withPassThroughPoints(mapPassThroughPoints()); r.relaxGeneralizedCostAtDestination().ifPresent(mcBuilder::withRelaxCostAtDestination); @@ -156,10 +158,11 @@ private RaptorRequest doMap() { .addStops(raptorDebugging.stops()) .setPath(raptorDebugging.path()) .debugPathFromStopIndex(raptorDebugging.debugPathFromStopIndex()) - .stopArrivalListener(debugLogger::stopArrivalLister) - .patternRideDebugListener(debugLogger::patternRideLister) - .pathFilteringListener(debugLogger::pathFilteringListener) .logger(debugLogger); + + for (var type : raptorDebugging.eventTypes()) { + addLogListenerForEachEventTypeRequested(debug, type, debugLogger); + } } if (!request.timetableView() && request.arriveBy()) { @@ -209,4 +212,16 @@ private int relativeTime(Instant time) { } return (int) (time.getEpochSecond() - transitSearchTimeZeroEpocSecond); } + + private static void addLogListenerForEachEventTypeRequested( + DebugRequestBuilder target, + DebugEventType type, + SystemErrDebugLogger logger + ) { + switch (type) { + case STOP_ARRIVALS -> target.stopArrivalListener(logger::stopArrivalLister); + case PATTERN_RIDES -> target.patternRideDebugListener(logger::patternRideLister); + case DESTINATION_ARRIVALS -> target.pathFilteringListener(logger::pathFilteringListener); + } + } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java index 25006af49d8..826b9c09a13 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java @@ -8,14 +8,14 @@ import java.util.List; import java.util.stream.Stream; import org.opentripplanner.framework.lang.ArrayUtils; -import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitPriorityGroup32n; -import org.opentripplanner.routing.api.request.request.filter.TransitPriorityGroupSelect; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitGroupPriority32n; +import org.opentripplanner.routing.api.request.request.filter.TransitGroupSelect; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.RoutingTripPattern; /** * This class dynamically builds an index of transit-group-ids from the - * provided {@link TransitPriorityGroupSelect}s while serving the caller with + * provided {@link TransitGroupSelect}s while serving the caller with * group-ids for each requested pattern. It is made for optimal * performance, since it is used in request scope. *

    @@ -44,7 +44,7 @@ public class PriorityGroupConfigurator { */ private static final int GROUP_INDEX_COUNTER_START = 1; - private final int baseGroupId = TransitPriorityGroup32n.groupId(GROUP_INDEX_COUNTER_START); + private final int baseGroupId = TransitGroupPriority32n.groupId(GROUP_INDEX_COUNTER_START); private int groupIndexCounter = GROUP_INDEX_COUNTER_START; private final boolean enabled; private final PriorityGroupMatcher[] agencyMatchers; @@ -63,8 +63,8 @@ private PriorityGroupConfigurator() { } private PriorityGroupConfigurator( - Collection byAgency, - Collection global + Collection byAgency, + Collection global ) { this.agencyMatchers = PriorityGroupMatcher.of(byAgency); this.globalMatchers = PriorityGroupMatcher.of(global); @@ -80,8 +80,8 @@ public static PriorityGroupConfigurator empty() { } public static PriorityGroupConfigurator of( - Collection byAgency, - Collection global + Collection byAgency, + Collection global ) { if (Stream.of(byAgency, global).allMatch(Collection::isEmpty)) { return empty(); @@ -94,7 +94,7 @@ public static PriorityGroupConfigurator of( *

    * @throws IllegalArgumentException if more than 32 group-ids are requested. */ - public int lookupTransitPriorityGroupId(RoutingTripPattern tripPattern) { + public int lookupTransitGroupPriorityId(RoutingTripPattern tripPattern) { if (!enabled || tripPattern == null) { return baseGroupId; } @@ -128,7 +128,7 @@ public int baseGroupId() { } private int nextGroupId() { - return TransitPriorityGroup32n.groupId(++groupIndexCounter); + return TransitGroupPriority32n.groupId(++groupIndexCounter); } /** Pair of matcher and groupId. Used only inside this class. */ diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java index 4d8a0475239..c017f2862ab 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java @@ -15,13 +15,13 @@ import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; -import org.opentripplanner.routing.api.request.request.filter.TransitPriorityGroupSelect; +import org.opentripplanner.routing.api.request.request.filter.TransitGroupSelect; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.TripPattern; /** - * This class turns a {@link TransitPriorityGroupSelect} into a matcher. + * This class turns a {@link TransitGroupSelect} into a matcher. *

    * Design: It uses the composite design pattern. A matcher is created for each * value in the "select", then the list of non-empty matchers is merged into @@ -42,7 +42,7 @@ boolean isEmpty() { } }; - public static PriorityGroupMatcher of(TransitPriorityGroupSelect select) { + public static PriorityGroupMatcher of(TransitGroupSelect select) { if (select.isEmpty()) { return NOOP; } @@ -65,7 +65,7 @@ public static PriorityGroupMatcher of(TransitPriorityGroupSelect select) { return andOf(list); } - static PriorityGroupMatcher[] of(Collection selectors) { + static PriorityGroupMatcher[] of(Collection selectors) { return selectors .stream() .map(PriorityGroupMatcher::of) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java index f1212b2f545..4338593d59d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java @@ -92,7 +92,7 @@ public RaptorRoutingRequestTransitData( additionalPastSearchDays, additionalFutureSearchDays, filter, - createTransitPriorityGroupConfigurator(request) + createTransitGroupPriorityConfigurator(request) ); this.patternIndex = transitDataCreator.createPatternIndex(tripPatterns); this.activeTripPatternsPerStop = transitDataCreator.createTripPatternsPerStop(tripPatterns); @@ -243,8 +243,8 @@ public RaptorConstrainedBoardingSearch transferConstraintsReverseS return new ConstrainedBoardingSearch(false, toStopTransfers, fromStopTransfers); } - private PriorityGroupConfigurator createTransitPriorityGroupConfigurator(RouteRequest request) { - if (request.preferences().transit().relaxTransitPriorityGroup().isNormal()) { + private PriorityGroupConfigurator createTransitGroupPriorityConfigurator(RouteRequest request) { + if (request.preferences().transit().relaxTransitGroupPriority().isNormal()) { return PriorityGroupConfigurator.empty(); } var transitRequest = request.journey().transit(); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java index 0cb155facd5..b8f915d6eb4 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java @@ -147,7 +147,7 @@ static List merge( tripPattern.getAlightingPossible(), BoardAlight.ALIGHT ), - priorityGroupConfigurator.lookupTransitPriorityGroupId(tripPattern) + priorityGroupConfigurator.lookupTransitGroupPriorityId(tripPattern) ) ); } diff --git a/src/main/java/org/opentripplanner/routing/api/request/DebugEventType.java b/src/main/java/org/opentripplanner/routing/api/request/DebugEventType.java new file mode 100644 index 00000000000..09f9dbbe732 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/api/request/DebugEventType.java @@ -0,0 +1,11 @@ +package org.opentripplanner.routing.api.request; + +/** + * Raptor check paths in 3 different places. The debugger can print events + * for each of these. + */ +public enum DebugEventType { + STOP_ARRIVALS, + PATTERN_RIDES, + DESTINATION_ARRIVALS, +} diff --git a/src/main/java/org/opentripplanner/routing/api/request/DebugRaptor.java b/src/main/java/org/opentripplanner/routing/api/request/DebugRaptor.java index 7d6c1472eee..0b9ff4f0c30 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/DebugRaptor.java +++ b/src/main/java/org/opentripplanner/routing/api/request/DebugRaptor.java @@ -3,7 +3,11 @@ import java.io.Serial; import java.io.Serializable; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; import java.util.List; +import java.util.Set; import java.util.regex.Pattern; import org.opentripplanner.framework.tostring.ToStringBuilder; import org.slf4j.Logger; @@ -46,14 +50,16 @@ public class DebugRaptor implements Serializable { private List stops = List.of(); private List path = List.of(); private int debugPathFromStopIndex = 0; + private Set eventTypes = EnumSet.noneOf(DebugEventType.class); public DebugRaptor() {} - /** Avoid using clone(), use copy-constructor instead(Josh Bloch). */ + /** Avoid using clone(), use copy-constructor instead (Josh Bloch). */ public DebugRaptor(DebugRaptor other) { this.stops = List.copyOf(other.stops); this.path = List.copyOf(other.path); this.debugPathFromStopIndex = other.debugPathFromStopIndex; + this.eventTypes = EnumSet.copyOf(other.eventTypes); } public boolean isEnabled() { @@ -90,12 +96,23 @@ public int debugPathFromStopIndex() { return debugPathFromStopIndex; } + public Set eventTypes() { + return Collections.unmodifiableSet(eventTypes); + } + + public DebugRaptor withEventTypes(Collection eventTypes) { + this.eventTypes.clear(); + this.eventTypes.addAll(eventTypes); + return this; + } + @Override public String toString() { return ToStringBuilder .of(DebugRaptor.class) .addObj("stops", toString(stops, FIRST_STOP_INDEX)) .addObj("path", toString(path, debugPathFromStopIndex)) + .addCol("eventType", eventTypes) .toString(); } diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/BikePreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/BikePreferences.java index 5717876bbfc..26da1476b38 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/BikePreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/BikePreferences.java @@ -2,6 +2,8 @@ import static org.opentripplanner.framework.lang.DoubleUtils.doubleEquals; import static org.opentripplanner.framework.lang.ObjectUtils.ifNotNull; +import static org.opentripplanner.routing.core.BicycleOptimizeType.SAFE_STREETS; +import static org.opentripplanner.routing.core.BicycleOptimizeType.TRIANGLE; import java.io.Serializable; import java.util.Objects; @@ -25,45 +27,32 @@ public final class BikePreferences implements Serializable { private final double speed; private final double reluctance; private final Cost boardCost; - private final double walkingSpeed; - private final double walkingReluctance; - private final int switchTime; - private final Cost switchCost; private final VehicleParkingPreferences parking; private final VehicleRentalPreferences rental; - private final double stairsReluctance; private final BicycleOptimizeType optimizeType; private final TimeSlopeSafetyTriangle optimizeTriangle; + private final VehicleWalkingPreferences walking; private BikePreferences() { this.speed = 5; this.reluctance = 2.0; this.boardCost = Cost.costOfMinutes(10); - this.walkingSpeed = 1.33; - this.walkingReluctance = 5.0; - this.switchTime = 0; - this.switchCost = Cost.ZERO; this.parking = VehicleParkingPreferences.DEFAULT; this.rental = VehicleRentalPreferences.DEFAULT; - this.optimizeType = BicycleOptimizeType.SAFE; + this.optimizeType = SAFE_STREETS; this.optimizeTriangle = TimeSlopeSafetyTriangle.DEFAULT; - // very high reluctance to carry the bike up/down a flight of stairs - this.stairsReluctance = 10; + this.walking = VehicleWalkingPreferences.DEFAULT; } private BikePreferences(Builder builder) { this.speed = Units.speed(builder.speed); this.reluctance = Units.reluctance(builder.reluctance); this.boardCost = builder.boardCost; - this.walkingSpeed = Units.speed(builder.walkingSpeed); - this.walkingReluctance = Units.reluctance(builder.walkingReluctance); - this.switchTime = Units.duration(builder.switchTime); - this.switchCost = builder.switchCost; this.parking = builder.parking; this.rental = builder.rental; this.optimizeType = Objects.requireNonNull(builder.optimizeType); this.optimizeTriangle = Objects.requireNonNull(builder.optimizeTriangle); - this.stairsReluctance = Units.reluctance(builder.stairsReluctance); + this.walking = builder.walking; } public static BikePreferences.Builder of() { @@ -94,36 +83,6 @@ public int boardCost() { return boardCost.toSeconds(); } - /** - * The walking speed when walking a bike. Default: 1.33 m/s ~ Same as walkSpeed - */ - public double walkingSpeed() { - return walkingSpeed; - } - - /** - * A multiplier for how bad walking is, compared to being in transit for equal - * lengths of time. Empirically, values between 2 and 4 seem to correspond - * well to the concept of not wanting to walk too much without asking for - * totally ridiculous itineraries, but this observation should in no way be - * taken as scientific or definitive. Your mileage may vary. See - * https://github.com/opentripplanner/OpenTripPlanner/issues/4090 for impact on - * performance with high values. Default value: 2.0 - */ - public double walkingReluctance() { - return walkingReluctance; - } - - /** Time to get on and off your own bike */ - public int switchTime() { - return switchTime; - } - - /** Cost of getting on and off your own bike */ - public int switchCost() { - return switchCost.toSeconds(); - } - /** Parking preferences that can be different per request */ public VehicleParkingPreferences parking() { return parking; @@ -135,7 +94,7 @@ public VehicleRentalPreferences rental() { } /** - * The set of characteristics that the user wants to optimize for -- defaults to SAFE. + * The set of characteristics that the user wants to optimize for -- defaults to SAFE_STREETS. */ public BicycleOptimizeType optimizeType() { return optimizeType; @@ -145,8 +104,9 @@ public TimeSlopeSafetyTriangle optimizeTriangle() { return optimizeTriangle; } - public double stairsReluctance() { - return stairsReluctance; + /** Bike walking preferences that can be different per request */ + public VehicleWalkingPreferences walking() { + return walking; } @Override @@ -158,15 +118,11 @@ public boolean equals(Object o) { doubleEquals(that.speed, speed) && doubleEquals(that.reluctance, reluctance) && boardCost.equals(that.boardCost) && - doubleEquals(that.walkingSpeed, walkingSpeed) && - doubleEquals(that.walkingReluctance, walkingReluctance) && - switchTime == that.switchTime && - switchCost.equals(that.switchCost) && - parking.equals(that.parking) && - rental.equals(that.rental) && + Objects.equals(parking, that.parking) && + Objects.equals(rental, that.rental) && optimizeType == that.optimizeType && optimizeTriangle.equals(that.optimizeTriangle) && - doubleEquals(stairsReluctance, that.stairsReluctance) + Objects.equals(walking, that.walking) ); } @@ -176,15 +132,11 @@ public int hashCode() { speed, reluctance, boardCost, - walkingSpeed, - walkingReluctance, - switchTime, - switchCost, parking, rental, optimizeType, optimizeTriangle, - stairsReluctance + walking ); } @@ -195,15 +147,11 @@ public String toString() { .addNum("speed", speed, DEFAULT.speed) .addNum("reluctance", reluctance, DEFAULT.reluctance) .addObj("boardCost", boardCost, DEFAULT.boardCost) - .addNum("walkingSpeed", walkingSpeed, DEFAULT.walkingSpeed) - .addNum("walkingReluctance", walkingReluctance, DEFAULT.walkingReluctance) - .addDurationSec("switchTime", switchTime, DEFAULT.switchTime) - .addObj("switchCost", switchCost, DEFAULT.switchCost) .addObj("parking", parking, DEFAULT.parking) .addObj("rental", rental, DEFAULT.rental) .addEnum("optimizeType", optimizeType, DEFAULT.optimizeType) .addObj("optimizeTriangle", optimizeTriangle, DEFAULT.optimizeTriangle) - .addNum("stairsReluctance", stairsReluctance, DEFAULT.stairsReluctance) + .addObj("walking", walking, DEFAULT.walking) .toString(); } @@ -214,31 +162,22 @@ public static class Builder { private double speed; private double reluctance; private Cost boardCost; - private double walkingSpeed; - private double walkingReluctance; - private int switchTime; - private Cost switchCost; private VehicleParkingPreferences parking; private VehicleRentalPreferences rental; private BicycleOptimizeType optimizeType; private TimeSlopeSafetyTriangle optimizeTriangle; - - public double stairsReluctance; + private VehicleWalkingPreferences walking; public Builder(BikePreferences original) { this.original = original; this.speed = original.speed; this.reluctance = original.reluctance; this.boardCost = original.boardCost; - this.walkingSpeed = original.walkingSpeed; - this.walkingReluctance = original.walkingReluctance; - this.switchTime = original.switchTime; - this.switchCost = original.switchCost; this.parking = original.parking; this.rental = original.rental; this.optimizeType = original.optimizeType; this.optimizeTriangle = original.optimizeTriangle; - this.stairsReluctance = original.stairsReluctance; + this.walking = original.walking; } public BikePreferences original() { @@ -272,42 +211,6 @@ public Builder withBoardCost(int boardCost) { return this; } - public double walkingSpeed() { - return walkingSpeed; - } - - public Builder withWalkingSpeed(double walkingSpeed) { - this.walkingSpeed = walkingSpeed; - return this; - } - - public double walkingReluctance() { - return walkingReluctance; - } - - public Builder withWalkingReluctance(double walkingReluctance) { - this.walkingReluctance = walkingReluctance; - return this; - } - - public int switchTime() { - return switchTime; - } - - public Builder withSwitchTime(int switchTime) { - this.switchTime = switchTime; - return this; - } - - public Cost switchCost() { - return switchCost; - } - - public Builder withSwitchCost(int switchCost) { - this.switchCost = Cost.costOfSeconds(switchCost); - return this; - } - public Builder withParking(Consumer body) { this.parking = ifNotNull(this.parking, original.parking).copyOf().apply(body).build(); return this; @@ -331,6 +234,17 @@ public TimeSlopeSafetyTriangle optimizeTriangle() { return optimizeTriangle; } + /** This also sets the optimization type as TRIANGLE if triangle parameters are defined */ + public Builder withForcedOptimizeTriangle(Consumer body) { + var builder = TimeSlopeSafetyTriangle.of(); + body.accept(builder); + this.optimizeTriangle = builder.buildOrDefault(this.optimizeTriangle); + if (!builder.isEmpty()) { + this.optimizeType = TRIANGLE; + } + return this; + } + public Builder withOptimizeTriangle(Consumer body) { var builder = TimeSlopeSafetyTriangle.of(); body.accept(builder); @@ -338,8 +252,8 @@ public Builder withOptimizeTriangle(Consumer bo return this; } - public Builder withStairsReluctance(double value) { - this.stairsReluctance = value; + public Builder withWalking(Consumer body) { + this.walking = ifNotNull(this.walking, original.walking).copyOf().apply(body).build(); return this; } diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/CarPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/CarPreferences.java index 6b103e8f3b7..b2f226cf307 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/CarPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/CarPreferences.java @@ -3,6 +3,7 @@ import static org.opentripplanner.framework.lang.ObjectUtils.ifNotNull; import java.io.Serializable; +import java.time.Duration; import java.util.Objects; import java.util.function.Consumer; import org.opentripplanner.framework.lang.DoubleUtils; @@ -25,9 +26,8 @@ public final class CarPreferences implements Serializable { private final double reluctance; private final VehicleParkingPreferences parking; private final VehicleRentalPreferences rental; - private final int pickupTime; + private final Duration pickupTime; private final Cost pickupCost; - private final int dropoffTime; private final double accelerationSpeed; private final double decelerationSpeed; @@ -37,9 +37,8 @@ private CarPreferences() { this.reluctance = 2.0; this.parking = VehicleParkingPreferences.DEFAULT; this.rental = VehicleRentalPreferences.DEFAULT; - this.pickupTime = 60; + this.pickupTime = Duration.ofMinutes(1); this.pickupCost = Cost.costOfMinutes(2); - this.dropoffTime = 120; this.accelerationSpeed = 2.9; this.decelerationSpeed = 2.9; } @@ -49,9 +48,8 @@ private CarPreferences(Builder builder) { this.reluctance = Units.reluctance(builder.reluctance); this.parking = builder.parking; this.rental = builder.rental; - this.pickupTime = Units.duration(builder.pickupTime); + this.pickupTime = Duration.ofSeconds(Units.duration(builder.pickupTime)); this.pickupCost = builder.pickupCost; - this.dropoffTime = Units.duration(builder.dropoffTime); this.accelerationSpeed = Units.acceleration(builder.accelerationSpeed); this.decelerationSpeed = Units.acceleration(builder.decelerationSpeed); } @@ -88,21 +86,13 @@ public VehicleRentalPreferences rental() { } /** Time of getting in/out of a carPickup (taxi) */ - public int pickupTime() { + public Duration pickupTime() { return pickupTime; } /** Cost of getting in/out of a carPickup (taxi) */ - public int pickupCost() { - return pickupCost.toSeconds(); - } - - /** - * Time to park a car in a park and ride, w/o taking into account driving and walking cost (time - * to park, switch off, pick your stuff, lock the car, etc...) - */ - public int dropoffTime() { - return dropoffTime; + public Cost pickupCost() { + return pickupCost; } /** @@ -131,9 +121,8 @@ public boolean equals(Object o) { DoubleUtils.doubleEquals(that.reluctance, reluctance) && parking.equals(that.parking) && rental.equals(that.rental) && - pickupTime == that.pickupTime && + Objects.equals(pickupTime, that.pickupTime) && pickupCost.equals(that.pickupCost) && - dropoffTime == that.dropoffTime && DoubleUtils.doubleEquals(that.accelerationSpeed, accelerationSpeed) && DoubleUtils.doubleEquals(that.decelerationSpeed, decelerationSpeed) ); @@ -148,7 +137,6 @@ public int hashCode() { rental, pickupTime, pickupCost, - dropoffTime, accelerationSpeed, decelerationSpeed ); @@ -162,9 +150,8 @@ public String toString() { .addNum("reluctance", reluctance, DEFAULT.reluctance) .addObj("parking", parking, DEFAULT.parking) .addObj("rental", rental, DEFAULT.rental) - .addNum("pickupTime", pickupTime, DEFAULT.pickupTime) + .addObj("pickupTime", pickupTime, DEFAULT.pickupTime) .addObj("pickupCost", pickupCost, DEFAULT.pickupCost) - .addNum("dropoffTime", dropoffTime, DEFAULT.dropoffTime) .addNum("accelerationSpeed", accelerationSpeed, DEFAULT.accelerationSpeed) .addNum("decelerationSpeed", decelerationSpeed, DEFAULT.decelerationSpeed) .toString(); @@ -180,7 +167,6 @@ public static class Builder { private VehicleRentalPreferences rental; private int pickupTime; private Cost pickupCost; - private int dropoffTime; private double accelerationSpeed; private double decelerationSpeed; @@ -190,9 +176,8 @@ public Builder(CarPreferences original) { this.reluctance = original.reluctance; this.parking = original.parking; this.rental = original.rental; - this.pickupTime = original.pickupTime; + this.pickupTime = (int) original.pickupTime.toSeconds(); this.pickupCost = original.pickupCost; - this.dropoffTime = original.dropoffTime; this.accelerationSpeed = original.accelerationSpeed; this.decelerationSpeed = original.decelerationSpeed; } @@ -221,8 +206,8 @@ public Builder withRental(Consumer body) { return this; } - public Builder withPickupTime(int pickupTime) { - this.pickupTime = pickupTime; + public Builder withPickupTime(Duration pickupTime) { + this.pickupTime = (int) pickupTime.toSeconds(); return this; } @@ -231,11 +216,6 @@ public Builder withPickupCost(int pickupCost) { return this; } - public Builder withDropoffTime(int dropoffTime) { - this.dropoffTime = dropoffTime; - return this; - } - public Builder withAccelerationSpeed(double accelerationSpeed) { this.accelerationSpeed = accelerationSpeed; return this; diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java index 3230bbf5968..d3d22160935 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java @@ -129,7 +129,7 @@ public SystemPreferences system() { */ public double getSpeed(TraverseMode mode, boolean walkingBike) { return switch (mode) { - case WALK -> walkingBike ? bike.walkingSpeed() : walk.speed(); + case WALK -> walkingBike ? bike.walking().speed() : walk.speed(); case BICYCLE -> bike.speed(); case CAR -> car.speed(); default -> throw new IllegalArgumentException("getSpeed(): Invalid mode " + mode); diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangle.java b/src/main/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangle.java index b901d738213..99925a441fd 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangle.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangle.java @@ -1,5 +1,6 @@ package org.opentripplanner.routing.api.request.preference; +import static org.opentripplanner.framework.lang.DoubleUtils.doubleEquals; import static org.opentripplanner.framework.lang.DoubleUtils.roundTo2Decimals; /** @@ -109,6 +110,13 @@ public Builder withSafety(double safety) { return this; } + /** + * Returns true if none of the values are set (i.e. all values are zero). + */ + public boolean isEmpty() { + return doubleEquals(time, ZERO) && doubleEquals(slope, ZERO) && doubleEquals(safety, ZERO); + } + public TimeSlopeSafetyTriangle build() { return new TimeSlopeSafetyTriangle(time, slope, safety); } diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java index 1ad6e8ddacd..78f30277e72 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java @@ -26,7 +26,7 @@ public final class TransitPreferences implements Serializable { private final Map reluctanceForMode; private final Cost otherThanPreferredRoutesPenalty; private final CostLinearFunction unpreferredCost; - private final CostLinearFunction relaxTransitPriorityGroup; + private final CostLinearFunction relaxTransitGroupPriority; private final boolean ignoreRealtimeUpdates; private final boolean includePlannedCancellations; private final boolean includeRealtimeCancellations; @@ -37,7 +37,7 @@ private TransitPreferences() { this.reluctanceForMode = Map.of(); this.otherThanPreferredRoutesPenalty = Cost.costOfMinutes(5); this.unpreferredCost = CostLinearFunction.NORMAL; - this.relaxTransitPriorityGroup = CostLinearFunction.NORMAL; + this.relaxTransitGroupPriority = CostLinearFunction.NORMAL; this.ignoreRealtimeUpdates = false; this.includePlannedCancellations = false; this.includeRealtimeCancellations = false; @@ -50,7 +50,7 @@ private TransitPreferences(Builder builder) { this.reluctanceForMode = Map.copyOf(requireNonNull(builder.reluctanceForMode)); this.otherThanPreferredRoutesPenalty = builder.otherThanPreferredRoutesPenalty; this.unpreferredCost = requireNonNull(builder.unpreferredCost); - this.relaxTransitPriorityGroup = Objects.requireNonNull(builder.relaxTransitPriorityGroup); + this.relaxTransitGroupPriority = Objects.requireNonNull(builder.relaxTransitGroupPriority); this.ignoreRealtimeUpdates = builder.ignoreRealtimeUpdates; this.includePlannedCancellations = builder.includePlannedCancellations; this.includeRealtimeCancellations = builder.includeRealtimeCancellations; @@ -128,13 +128,13 @@ public CostLinearFunction unpreferredCost() { } /** - * This is used to relax the cost when comparing transit-priority-groups. The default is the - * NORMAL function({@code f(x) = x}. This is the same as not using priority-groups. The + * This is used to relax the cost when comparing transit-groups. The default is the + * NORMAL function({@code f(t) = t}. This is the same as not using priority-groups. The * coefficient must be in range {@code [1.0 to 4.0]} and the constant must be in range * {@code [$0 to $1440(4h)]}. */ - public CostLinearFunction relaxTransitPriorityGroup() { - return relaxTransitPriorityGroup; + public CostLinearFunction relaxTransitGroupPriority() { + return relaxTransitGroupPriority; } /** @@ -176,7 +176,7 @@ public boolean equals(Object o) { reluctanceForMode.equals(that.reluctanceForMode) && Objects.equals(otherThanPreferredRoutesPenalty, that.otherThanPreferredRoutesPenalty) && unpreferredCost.equals(that.unpreferredCost) && - Objects.equals(relaxTransitPriorityGroup, that.relaxTransitPriorityGroup) && + Objects.equals(relaxTransitGroupPriority, that.relaxTransitGroupPriority) && ignoreRealtimeUpdates == that.ignoreRealtimeUpdates && includePlannedCancellations == that.includePlannedCancellations && includeRealtimeCancellations == that.includeRealtimeCancellations && @@ -192,7 +192,7 @@ public int hashCode() { reluctanceForMode, otherThanPreferredRoutesPenalty, unpreferredCost, - relaxTransitPriorityGroup, + relaxTransitGroupPriority, ignoreRealtimeUpdates, includePlannedCancellations, includeRealtimeCancellations, @@ -213,7 +213,7 @@ public String toString() { DEFAULT.otherThanPreferredRoutesPenalty ) .addObj("unpreferredCost", unpreferredCost, DEFAULT.unpreferredCost) - .addObj("relaxTransitPriorityGroup", relaxTransitPriorityGroup, CostLinearFunction.NORMAL) + .addObj("relaxTransitGroupPriority", relaxTransitGroupPriority, CostLinearFunction.NORMAL) .addBoolIfTrue( "ignoreRealtimeUpdates", ignoreRealtimeUpdates != DEFAULT.ignoreRealtimeUpdates @@ -240,7 +240,7 @@ public static class Builder { private Map reluctanceForMode; private Cost otherThanPreferredRoutesPenalty; private CostLinearFunction unpreferredCost; - private CostLinearFunction relaxTransitPriorityGroup; + private CostLinearFunction relaxTransitGroupPriority; private boolean ignoreRealtimeUpdates; private boolean includePlannedCancellations; private boolean includeRealtimeCancellations; @@ -253,7 +253,7 @@ public Builder(TransitPreferences original) { this.reluctanceForMode = original.reluctanceForMode; this.otherThanPreferredRoutesPenalty = original.otherThanPreferredRoutesPenalty; this.unpreferredCost = original.unpreferredCost; - this.relaxTransitPriorityGroup = original.relaxTransitPriorityGroup; + this.relaxTransitGroupPriority = original.relaxTransitGroupPriority; this.ignoreRealtimeUpdates = original.ignoreRealtimeUpdates; this.includePlannedCancellations = original.includePlannedCancellations; this.includeRealtimeCancellations = original.includeRealtimeCancellations; @@ -302,8 +302,8 @@ public Builder setUnpreferredCostString(String constFunction) { return setUnpreferredCost(CostLinearFunction.of(constFunction)); } - public Builder withTransitGroupPriorityGeneralizedCostSlack(CostLinearFunction value) { - this.relaxTransitPriorityGroup = value; + public Builder withRelaxTransitGroupPriority(CostLinearFunction value) { + this.relaxTransitGroupPriority = value; return this; } diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleParkingPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleParkingPreferences.java index c02862c4d79..f7183812c3a 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleParkingPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleParkingPreferences.java @@ -23,16 +23,16 @@ public final class VehicleParkingPreferences implements Serializable { private final Cost unpreferredVehicleParkingTagCost; private final VehicleParkingFilter filter; private final VehicleParkingFilter preferred; - private final Duration parkTime; - private final Cost parkCost; + private final Duration time; + private final Cost cost; /** Create a new instance with default values. */ private VehicleParkingPreferences() { this.unpreferredVehicleParkingTagCost = Cost.costOfMinutes(5); this.filter = VehicleParkingFilter.empty(); this.preferred = VehicleParkingFilter.empty(); - this.parkTime = Duration.ofMinutes(1); - this.parkCost = Cost.costOfMinutes(2); + this.time = Duration.ofMinutes(1); + this.cost = Cost.costOfMinutes(2); } private VehicleParkingPreferences(Builder builder) { @@ -47,8 +47,8 @@ private VehicleParkingPreferences(Builder builder) { builder.notPreferredVehicleParkingTags, builder.preferredVehicleParkingTags ); - this.parkTime = builder.parkTime; - this.parkCost = builder.parkCost; + this.time = builder.time; + this.cost = builder.cost; } public static VehicleParkingPreferences.Builder of() { @@ -85,13 +85,13 @@ public VehicleParkingFilter preferred() { } /** Time to park a vehicle */ - public Duration parkTime() { - return parkTime; + public Duration time() { + return time; } /** Cost of parking a bike. */ - public Cost parkCost() { - return parkCost; + public Cost cost() { + return cost; } @Override @@ -103,14 +103,14 @@ public boolean equals(Object o) { Objects.equals(unpreferredVehicleParkingTagCost, that.unpreferredVehicleParkingTagCost) && Objects.equals(filter, that.filter) && Objects.equals(preferred, that.preferred) && - Objects.equals(parkCost, that.parkCost) && - Objects.equals(parkTime, that.parkTime) + Objects.equals(cost, that.cost) && + Objects.equals(time, that.time) ); } @Override public int hashCode() { - return Objects.hash(unpreferredVehicleParkingTagCost, filter, preferred, parkCost, parkTime); + return Objects.hash(unpreferredVehicleParkingTagCost, filter, preferred, cost, time); } @Override @@ -124,8 +124,8 @@ public String toString() { ) .addObj("filter", filter, DEFAULT.filter) .addObj("preferred", preferred, DEFAULT.preferred) - .addObj("parkCost", parkCost, DEFAULT.parkCost) - .addObj("parkTime", parkTime, DEFAULT.parkTime) + .addObj("cost", cost, DEFAULT.cost) + .addObj("time", time, DEFAULT.time) .toString(); } @@ -137,8 +137,8 @@ public static class Builder { private List requiredVehicleParkingTags; private List preferredVehicleParkingTags; private List notPreferredVehicleParkingTags; - private Cost parkCost; - private Duration parkTime; + private Cost cost; + private Duration time; private Builder(VehicleParkingPreferences original) { this.original = original; @@ -147,8 +147,8 @@ private Builder(VehicleParkingPreferences original) { this.requiredVehicleParkingTags = original.filter.select(); this.preferredVehicleParkingTags = original.preferred.select(); this.notPreferredVehicleParkingTags = original.preferred.not(); - this.parkCost = original.parkCost; - this.parkTime = original.parkTime; + this.cost = original.cost; + this.time = original.time; } public Builder withUnpreferredVehicleParkingTagCost(int cost) { @@ -180,18 +180,18 @@ public Builder withNotPreferredVehicleParkingTags(Set notPreferredVehicl return this; } - public Builder withParkCost(int cost) { - this.parkCost = Cost.costOfSeconds(cost); + public Builder withCost(int cost) { + this.cost = Cost.costOfSeconds(cost); return this; } - public Builder withParkTime(int seconds) { - this.parkTime = Duration.ofSeconds(seconds); + public Builder withTime(int seconds) { + this.time = Duration.ofSeconds(seconds); return this; } - public Builder withParkTime(Duration duration) { - this.parkTime = duration; + public Builder withTime(Duration duration) { + this.time = duration; return this; } diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleRentalPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleRentalPreferences.java index 8e18d2a81d5..6c9bbfea875 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleRentalPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleRentalPreferences.java @@ -1,10 +1,10 @@ package org.opentripplanner.routing.api.request.preference; import java.io.Serializable; +import java.time.Duration; import java.util.Objects; import java.util.Set; import java.util.function.Consumer; -import org.opentripplanner.framework.lang.DoubleUtils; import org.opentripplanner.framework.model.Cost; import org.opentripplanner.framework.model.Units; import org.opentripplanner.framework.tostring.ToStringBuilder; @@ -17,38 +17,38 @@ public final class VehicleRentalPreferences implements Serializable { public static final VehicleRentalPreferences DEFAULT = new VehicleRentalPreferences(); - private final int pickupTime; + private final Duration pickupTime; private final Cost pickupCost; - private final int dropoffTime; - private final Cost dropoffCost; + private final Duration dropOffTime; + private final Cost dropOffCost; private final boolean useAvailabilityInformation; - private final double arrivingInRentalVehicleAtDestinationCost; + private final Cost arrivingInRentalVehicleAtDestinationCost; private final boolean allowArrivingInRentedVehicleAtDestination; private final Set allowedNetworks; private final Set bannedNetworks; private VehicleRentalPreferences() { - this.pickupTime = 60; + this.pickupTime = Duration.ofMinutes(1); this.pickupCost = Cost.costOfMinutes(2); - this.dropoffTime = 30; - this.dropoffCost = Cost.costOfSeconds(30); + this.dropOffTime = Duration.ofSeconds(30); + this.dropOffCost = Cost.costOfSeconds(30); this.useAvailabilityInformation = false; - this.arrivingInRentalVehicleAtDestinationCost = 0; + this.arrivingInRentalVehicleAtDestinationCost = Cost.costOfSeconds(0); this.allowArrivingInRentedVehicleAtDestination = false; this.allowedNetworks = Set.of(); this.bannedNetworks = Set.of(); } private VehicleRentalPreferences(Builder builder) { - this.pickupTime = builder.pickupTime; + this.pickupTime = Duration.ofSeconds(Units.duration(builder.pickupTime)); this.pickupCost = builder.pickupCost; - this.dropoffTime = builder.dropoffTime; - this.dropoffCost = builder.dropoffCost; + this.dropOffTime = Duration.ofSeconds(Units.duration(builder.dropOffTime)); + this.dropOffCost = builder.dropOffCost; this.useAvailabilityInformation = builder.useAvailabilityInformation; this.arrivingInRentalVehicleAtDestinationCost = - DoubleUtils.roundTo1Decimal(builder.arrivingInRentalVehicleAtDestinationCost); + builder.arrivingInRentalVehicleAtDestinationCost; this.allowArrivingInRentedVehicleAtDestination = builder.allowArrivingInRentedVehicleAtDestination; this.allowedNetworks = builder.allowedNetworks; @@ -64,7 +64,7 @@ public Builder copyOf() { } /** Time to rent a vehicle */ - public int pickupTime() { + public Duration pickupTime() { return pickupTime; } @@ -72,18 +72,18 @@ public int pickupTime() { * Cost of renting a vehicle. The cost is a bit more than actual time to model the associated cost * and trouble. */ - public int pickupCost() { - return pickupCost.toSeconds(); + public Cost pickupCost() { + return pickupCost; } /** Time to drop-off a rented vehicle */ - public int dropoffTime() { - return dropoffTime; + public Duration dropOffTime() { + return dropOffTime; } /** Cost of dropping-off a rented vehicle */ - public int dropoffCost() { - return dropoffCost.toSeconds(); + public Cost dropOffCost() { + return dropOffCost; } /** @@ -97,7 +97,7 @@ public boolean useAvailabilityInformation() { /** * The cost of arriving at the destination with the rented vehicle, to discourage doing so. */ - public double arrivingInRentalVehicleAtDestinationCost() { + public Cost arrivingInRentalVehicleAtDestinationCost() { return arrivingInRentalVehicleAtDestinationCost; } @@ -127,16 +127,15 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; VehicleRentalPreferences that = (VehicleRentalPreferences) o; return ( - pickupTime == that.pickupTime && + Objects.equals(pickupTime, that.pickupTime) && Objects.equals(pickupCost, that.pickupCost) && - dropoffTime == that.dropoffTime && - Objects.equals(dropoffCost, that.dropoffCost) && + Objects.equals(dropOffTime, that.dropOffTime) && + Objects.equals(dropOffCost, that.dropOffCost) && useAvailabilityInformation == that.useAvailabilityInformation && - Double.compare( + Objects.equals( that.arrivingInRentalVehicleAtDestinationCost, arrivingInRentalVehicleAtDestinationCost - ) == - 0 && + ) && allowArrivingInRentedVehicleAtDestination == that.allowArrivingInRentedVehicleAtDestination && allowedNetworks.equals(that.allowedNetworks) && bannedNetworks.equals(that.bannedNetworks) @@ -148,8 +147,8 @@ public int hashCode() { return Objects.hash( pickupTime, pickupCost, - dropoffTime, - dropoffCost, + dropOffTime, + dropOffCost, useAvailabilityInformation, arrivingInRentalVehicleAtDestinationCost, allowArrivingInRentedVehicleAtDestination, @@ -162,12 +161,12 @@ public int hashCode() { public String toString() { return ToStringBuilder .of(VehicleRentalPreferences.class) - .addDurationSec("pickupTime", pickupTime, DEFAULT.pickupTime) + .addDuration("pickupTime", pickupTime, DEFAULT.pickupTime) .addObj("pickupCost", pickupCost, DEFAULT.pickupCost) - .addDurationSec("dropoffTime", dropoffTime, DEFAULT.dropoffTime) - .addObj("dropoffCost", dropoffCost, DEFAULT.dropoffCost) + .addDuration("dropOffTime", dropOffTime, DEFAULT.dropOffTime) + .addObj("dropOffCost", dropOffCost, DEFAULT.dropOffCost) .addBoolIfTrue("useAvailabilityInformation", useAvailabilityInformation) - .addNum( + .addObj( "arrivingInRentalVehicleAtDestinationCost", arrivingInRentalVehicleAtDestinationCost, DEFAULT.arrivingInRentalVehicleAtDestinationCost @@ -186,20 +185,20 @@ public static class Builder { private final VehicleRentalPreferences original; private int pickupTime; private Cost pickupCost; - private int dropoffTime; - private Cost dropoffCost; + private int dropOffTime; + private Cost dropOffCost; private boolean useAvailabilityInformation; - private double arrivingInRentalVehicleAtDestinationCost; + private Cost arrivingInRentalVehicleAtDestinationCost; private boolean allowArrivingInRentedVehicleAtDestination; private Set allowedNetworks; private Set bannedNetworks; private Builder(VehicleRentalPreferences original) { this.original = original; - this.pickupTime = Units.duration(original.pickupTime); + this.pickupTime = (int) original.pickupTime.toSeconds(); this.pickupCost = original.pickupCost; - this.dropoffTime = Units.duration(original.dropoffTime); - this.dropoffCost = original.dropoffCost; + this.dropOffTime = (int) original.dropOffTime.toSeconds(); + this.dropOffCost = original.dropOffCost; this.useAvailabilityInformation = original.useAvailabilityInformation; this.arrivingInRentalVehicleAtDestinationCost = original.arrivingInRentalVehicleAtDestinationCost; @@ -218,18 +217,28 @@ public Builder withPickupTime(int pickupTime) { return this; } + public Builder withPickupTime(Duration pickupTime) { + this.pickupTime = (int) pickupTime.toSeconds(); + return this; + } + public Builder withPickupCost(int pickupCost) { this.pickupCost = Cost.costOfSeconds(pickupCost); return this; } - public Builder withDropoffTime(int dropoffTime) { - this.dropoffTime = dropoffTime; + public Builder withDropOffTime(int dropOffTime) { + this.dropOffTime = dropOffTime; return this; } - public Builder withDropoffCost(int dropoffCost) { - this.dropoffCost = Cost.costOfSeconds(dropoffCost); + public Builder withDropOffTime(Duration dropOffTime) { + this.dropOffTime = (int) dropOffTime.toSeconds(); + return this; + } + + public Builder withDropOffCost(int dropOffCost) { + this.dropOffCost = Cost.costOfSeconds(dropOffCost); return this; } @@ -239,9 +248,10 @@ public Builder withUseAvailabilityInformation(boolean useAvailabilityInformation } public Builder withArrivingInRentalVehicleAtDestinationCost( - double arrivingInRentalVehicleAtDestinationCost + int arrivingInRentalVehicleAtDestinationCost ) { - this.arrivingInRentalVehicleAtDestinationCost = arrivingInRentalVehicleAtDestinationCost; + this.arrivingInRentalVehicleAtDestinationCost = + Cost.costOfSeconds(arrivingInRentalVehicleAtDestinationCost); return this; } diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferences.java new file mode 100644 index 00000000000..aa3631e1c6b --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferences.java @@ -0,0 +1,185 @@ +package org.opentripplanner.routing.api.request.preference; + +import java.io.Serializable; +import java.time.Duration; +import java.util.Objects; +import java.util.function.Consumer; +import org.opentripplanner.framework.model.Cost; +import org.opentripplanner.framework.model.Units; +import org.opentripplanner.framework.tostring.ToStringBuilder; + +/** + * Preferences for walking a vehicle. + *

    + * THIS CLASS IS IMMUTABLE AND THREAD-SAFE. + */ +public class VehicleWalkingPreferences implements Serializable { + + public static final VehicleWalkingPreferences DEFAULT = new VehicleWalkingPreferences(); + + private final double speed; + private final double reluctance; + private final Duration hopTime; + private final Cost hopCost; + private final double stairsReluctance; + + private VehicleWalkingPreferences() { + this.speed = 1.33; + this.reluctance = 5.0; + this.hopTime = Duration.ZERO; + this.hopCost = Cost.ZERO; + // very high reluctance to carry the bike up/down a flight of stairs + this.stairsReluctance = 10; + } + + /** + * Sets the vehicle walking preferences, does some input value validation and rounds + * reluctances and speed to not have too many decimals. + */ + private VehicleWalkingPreferences(Builder builder) { + this.speed = Units.speed(builder.speed); + this.reluctance = Units.reluctance(builder.reluctance); + this.hopTime = Duration.ofSeconds(Units.duration(builder.hopTime)); + this.hopCost = Cost.costOfSeconds(builder.hopCost); + this.stairsReluctance = Units.reluctance(builder.stairsReluctance); + } + + public static VehicleWalkingPreferences.Builder of() { + return new VehicleWalkingPreferences.Builder(DEFAULT); + } + + public VehicleWalkingPreferences.Builder copyOf() { + return new VehicleWalkingPreferences.Builder(this); + } + + /** + * The walking speed when walking a vehicle. Default: 1.33 m/s ~ Same as walkSpeed. + */ + public double speed() { + return speed; + } + + /** + * A multiplier for how bad walking is, compared to being in transit for equal + * lengths of time. Empirically, values between 2 and 4 seem to correspond + * well to the concept of not wanting to walk too much without asking for + * totally ridiculous itineraries, but this observation should in no way be + * taken as scientific or definitive. Your mileage may vary. See + * https://github.com/opentripplanner/OpenTripPlanner/issues/4090 for impact on + * performance with high values. Default value: 2.0 + */ + public double reluctance() { + return reluctance; + } + + /** Time to get on and off your own vehicle. */ + public Duration hopTime() { + return hopTime; + } + + /** Cost of getting on and off your own vehicle. */ + public Cost hopCost() { + return hopCost; + } + + /** Reluctance of walking carrying a vehicle up a flight of stairs. */ + public double stairsReluctance() { + return stairsReluctance; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + VehicleWalkingPreferences that = (VehicleWalkingPreferences) o; + return ( + speed == that.speed && + reluctance == that.reluctance && + Objects.equals(hopTime, that.hopTime) && + Objects.equals(hopCost, that.hopCost) && + stairsReluctance == that.stairsReluctance + ); + } + + @Override + public int hashCode() { + return Objects.hash(speed, reluctance, hopTime, hopCost, stairsReluctance); + } + + @Override + public String toString() { + return ToStringBuilder + .of(VehicleWalkingPreferences.class) + .addNum("speed", speed, DEFAULT.speed) + .addNum("reluctance", reluctance, DEFAULT.reluctance) + .addObj("hopTime", hopTime, DEFAULT.hopTime) + .addObj("hopCost", hopCost, DEFAULT.hopCost) + .addNum("stairsReluctance", stairsReluctance, DEFAULT.stairsReluctance) + .toString(); + } + + public static class Builder { + + private final VehicleWalkingPreferences original; + private double speed; + private double reluctance; + private int hopTime; + private int hopCost; + private double stairsReluctance; + + private Builder(VehicleWalkingPreferences original) { + this.original = original; + this.speed = original.speed; + this.reluctance = original.reluctance; + this.hopTime = (int) original.hopTime.toSeconds(); + this.hopCost = original.hopCost.toSeconds(); + this.stairsReluctance = original.stairsReluctance; + } + + public VehicleWalkingPreferences.Builder withSpeed(double speed) { + this.speed = speed; + return this; + } + + public VehicleWalkingPreferences.Builder withReluctance(double reluctance) { + this.reluctance = reluctance; + return this; + } + + public VehicleWalkingPreferences.Builder withHopTime(Duration hopTime) { + this.hopTime = (int) hopTime.toSeconds(); + return this; + } + + public VehicleWalkingPreferences.Builder withHopTime(int hopTime) { + this.hopTime = hopTime; + return this; + } + + public VehicleWalkingPreferences.Builder withHopCost(int hopCost) { + this.hopCost = hopCost; + return this; + } + + public VehicleWalkingPreferences.Builder withStairsReluctance(double stairsReluctance) { + this.stairsReluctance = stairsReluctance; + return this; + } + + public VehicleWalkingPreferences original() { + return original; + } + + public VehicleWalkingPreferences.Builder apply( + Consumer body + ) { + body.accept(this); + return this; + } + + public VehicleWalkingPreferences build() { + var newObj = new VehicleWalkingPreferences(this); + return original.equals(newObj) ? original : newObj; + } + } +} diff --git a/src/main/java/org/opentripplanner/routing/api/request/request/TransitRequest.java b/src/main/java/org/opentripplanner/routing/api/request/request/TransitRequest.java index 67a56249328..b5626c479a0 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/request/TransitRequest.java +++ b/src/main/java/org/opentripplanner/routing/api/request/request/TransitRequest.java @@ -8,7 +8,7 @@ import org.opentripplanner.routing.api.request.DebugRaptor; import org.opentripplanner.routing.api.request.request.filter.AllowAllTransitFilter; import org.opentripplanner.routing.api.request.request.filter.TransitFilter; -import org.opentripplanner.routing.api.request.request.filter.TransitPriorityGroupSelect; +import org.opentripplanner.routing.api.request.request.filter.TransitGroupSelect; import org.opentripplanner.transit.model.framework.FeedScopedId; // TODO VIA: Javadoc @@ -31,8 +31,8 @@ public class TransitRequest implements Cloneable, Serializable { private List unpreferredRoutes = List.of(); - private List priorityGroupsByAgency = new ArrayList<>(); - private List priorityGroupsGlobal = new ArrayList<>(); + private List priorityGroupsByAgency = new ArrayList<>(); + private List priorityGroupsGlobal = new ArrayList<>(); private DebugRaptor raptorDebugging = new DebugRaptor(); public void setBannedTripsFromString(String ids) { @@ -64,16 +64,14 @@ public void setFilters(List filters) { *

    * Note! Entities that are not matched are put in the BASE-GROUP with id 0. */ - public List priorityGroupsByAgency() { + public List priorityGroupsByAgency() { return priorityGroupsByAgency; } /** * All patterns matching the same select will be assigned the same group-id. */ - public void addPriorityGroupsByAgency( - Collection priorityGroupsByAgency - ) { + public void addPriorityGroupsByAgency(Collection priorityGroupsByAgency) { this.priorityGroupsByAgency.addAll(priorityGroupsByAgency); } @@ -82,11 +80,11 @@ public void addPriorityGroupsByAgency( *

    * Note! Entities that are not matched are put in the BASE-GROUP with id 0. */ - public List priorityGroupsGlobal() { + public List priorityGroupsGlobal() { return priorityGroupsGlobal; } - public void addPriorityGroupsGlobal(Collection priorityGroupsGlobal) { + public void addPriorityGroupsGlobal(Collection priorityGroupsGlobal) { this.priorityGroupsGlobal.addAll(priorityGroupsGlobal); } diff --git a/src/main/java/org/opentripplanner/routing/api/request/request/filter/TransitPriorityGroupSelect.java b/src/main/java/org/opentripplanner/routing/api/request/request/filter/TransitGroupSelect.java similarity index 87% rename from src/main/java/org/opentripplanner/routing/api/request/request/filter/TransitPriorityGroupSelect.java rename to src/main/java/org/opentripplanner/routing/api/request/request/filter/TransitGroupSelect.java index 6d763e9c3bc..dfa5daa0e31 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/request/filter/TransitPriorityGroupSelect.java +++ b/src/main/java/org/opentripplanner/routing/api/request/request/filter/TransitGroupSelect.java @@ -21,23 +21,23 @@ *

  • {@code Entity(mode:SUBWAY, agency:A3)}
  • * */ -public class TransitPriorityGroupSelect { +public class TransitGroupSelect { - private static final TransitPriorityGroupSelect DEFAULT = new TransitPriorityGroupSelect(); + private static final TransitGroupSelect DEFAULT = new TransitGroupSelect(); private final List modes; private final List subModeRegexp; private final List agencyIds; private final List routeIds; - public TransitPriorityGroupSelect() { + public TransitGroupSelect() { this.modes = List.of(); this.subModeRegexp = List.of(); this.agencyIds = List.of(); this.routeIds = List.of(); } - private TransitPriorityGroupSelect(Builder builder) { + private TransitGroupSelect(Builder builder) { // Sort and keep only unique entries, this make this // implementation consistent for eq/hc/toString. this.modes = @@ -77,7 +77,7 @@ public boolean isEmpty() { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - TransitPriorityGroupSelect that = (TransitPriorityGroupSelect) o; + TransitGroupSelect that = (TransitGroupSelect) o; return ( Objects.equals(modes, that.modes) && Objects.equals(subModeRegexp, that.subModeRegexp) && @@ -96,7 +96,7 @@ public String toString() { return isEmpty() ? "TransitGroupSelect{ EMPTY }" : ToStringBuilder - .of(TransitPriorityGroupSelect.class) + .of(TransitGroupSelect.class) .addCol("modes", modes) .addCol("subModeRegexp", subModeRegexp) .addCol("agencyIds", agencyIds) @@ -106,13 +106,13 @@ public String toString() { public static class Builder { - private final TransitPriorityGroupSelect original; + private final TransitGroupSelect original; private final List modes; private final List subModeRegexp; private final List agencyIds; private final List routeIds; - public Builder(TransitPriorityGroupSelect original) { + public Builder(TransitGroupSelect original) { this.original = original; this.modes = new ArrayList<>(original.modes); this.subModeRegexp = new ArrayList<>(original.subModeRegexp); @@ -140,8 +140,8 @@ public Builder addRouteIds(Collection routeIds) { return this; } - public TransitPriorityGroupSelect build() { - var obj = new TransitPriorityGroupSelect(this); + public TransitGroupSelect build() { + var obj = new TransitGroupSelect(this); return original.equals(obj) ? original : obj; } } diff --git a/src/main/java/org/opentripplanner/routing/core/BicycleOptimizeType.java b/src/main/java/org/opentripplanner/routing/core/BicycleOptimizeType.java index b5a47af82de..1d639e0af8b 100644 --- a/src/main/java/org/opentripplanner/routing/core/BicycleOptimizeType.java +++ b/src/main/java/org/opentripplanner/routing/core/BicycleOptimizeType.java @@ -9,10 +9,14 @@ * combined presets of routing parameters, except for triangle. */ public enum BicycleOptimizeType { - QUICK,/* the fastest trip */ - SAFE, - FLAT,/* needs a rewrite */ - GREENWAYS, + /** This was previously called QUICK */ + SHORTEST_DURATION, + /** This was previously called SAFE */ + SAFE_STREETS, + /** This was previously called FLAT. Needs a rewrite. */ + FLAT_STREETS, + /** This was previously called GREENWAYS. */ + SAFEST_STREETS, TRIANGLE; private static final Set NON_TRIANGLE_VALUES = Collections.unmodifiableSet( diff --git a/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java b/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java index 9879b52c9c1..01736aac80e 100644 --- a/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java +++ b/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java @@ -2,6 +2,7 @@ import java.time.ZoneId; import org.opentripplanner.framework.application.OTPRequestTimeoutException; +import org.opentripplanner.framework.time.ZoneIdFallback; import org.opentripplanner.framework.tostring.MultiLineToStringBuilder; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.routing.algorithm.RoutingWorker; @@ -30,7 +31,7 @@ public class DefaultRoutingService implements RoutingService { public DefaultRoutingService(OtpServerRequestContext serverContext) { this.serverContext = serverContext; - this.timeZone = serverContext.transitService().getTimeZone(); + this.timeZone = ZoneIdFallback.zoneId(serverContext.transitService().getTimeZone()); } @Override diff --git a/src/main/java/org/opentripplanner/service/paging/PagingService.java b/src/main/java/org/opentripplanner/service/paging/PagingService.java index e8f09acf4d4..3aa8033105a 100644 --- a/src/main/java/org/opentripplanner/service/paging/PagingService.java +++ b/src/main/java/org/opentripplanner/service/paging/PagingService.java @@ -11,8 +11,8 @@ import org.opentripplanner.model.plan.paging.PagingSearchWindowAdjuster; import org.opentripplanner.model.plan.paging.cursor.PageCursor; import org.opentripplanner.model.plan.paging.cursor.PageCursorFactory; +import org.opentripplanner.model.plan.paging.cursor.PageCursorInput; import org.opentripplanner.model.plan.paging.cursor.PageType; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NumItinerariesFilterResults; import org.opentripplanner.routing.api.response.TripSearchMetadata; public class PagingService { @@ -24,7 +24,7 @@ public class PagingService { private final boolean arriveBy; private final int numberOfItineraries; private final PageCursor pageCursor; - private final NumItinerariesFilterResults numItinerariesFilterResults; + private final PageCursorInput pageCursorInput; private final PagingSearchWindowAdjuster searchWindowAdjuster; private final List itineraries; @@ -42,7 +42,7 @@ public PagingService( boolean arriveBy, int numberOfItineraries, @Nullable PageCursor pageCursor, - NumItinerariesFilterResults numItinerariesFilterResults, + PageCursorInput pageCursorInput, List itineraries ) { this.searchWindowUsed = searchWindowUsed; @@ -53,7 +53,7 @@ public PagingService( this.numberOfItineraries = numberOfItineraries; this.pageCursor = pageCursor; - this.numItinerariesFilterResults = numItinerariesFilterResults; + this.pageCursorInput = pageCursorInput; this.itineraries = Objects.requireNonNull(itineraries); this.searchWindowAdjuster = createSearchWindowAdjuster( @@ -98,9 +98,9 @@ private Duration calculateSearchWindowNextSearch() { } // SearchWindow cropped -> decrease search-window - if (numItinerariesFilterResults != null) { + if (pageCursorInput != null) { boolean cropSWHead = doCropSearchWindowAtTail(); - Instant rmItineraryStartTime = numItinerariesFilterResults.pageCut().startTimeAsInstant(); + Instant rmItineraryStartTime = pageCursorInput.pageCut().startTimeAsInstant(); return searchWindowAdjuster.decreaseSearchWindow( searchWindowUsed, @@ -125,15 +125,11 @@ private Duration calculateSearchWindowNextSearch() { } private Instant lastKeptDepartureTime() { - return numItinerariesFilterResults == null - ? null - : numItinerariesFilterResults.pageCut().startTimeAsInstant(); + return pageCursorInput == null ? null : pageCursorInput.pageCut().startTimeAsInstant(); } private Instant firstKeptDepartureTime() { - return numItinerariesFilterResults == null - ? null - : numItinerariesFilterResults.pageCut().startTimeAsInstant(); + return pageCursorInput == null ? null : pageCursorInput.pageCut().startTimeAsInstant(); } private PagingSearchWindowAdjuster createSearchWindowAdjuster( @@ -189,8 +185,8 @@ private PageCursorFactory mapIntoPageCursorFactory(@Nullable PageType currentPag searchWindowUsed ); - if (numItinerariesFilterResults != null) { - factory = factory.withRemovedItineraries(numItinerariesFilterResults); + if (pageCursorInput != null) { + factory = factory.withRemovedItineraries(pageCursorInput); } return factory; } diff --git a/src/main/java/org/opentripplanner/service/vehiclerental/street/VehicleRentalEdge.java b/src/main/java/org/opentripplanner/service/vehiclerental/street/VehicleRentalEdge.java index 66665f73b54..dc9fed8b9a3 100644 --- a/src/main/java/org/opentripplanner/service/vehiclerental/street/VehicleRentalEdge.java +++ b/src/main/java/org/opentripplanner/service/vehiclerental/street/VehicleRentalEdge.java @@ -161,8 +161,14 @@ public State[] traverse(State s0) { } } - s1.incrementWeight(pickedUp ? preferences.pickupCost() : preferences.dropoffCost()); - s1.incrementTimeInSeconds(pickedUp ? preferences.pickupTime() : preferences.dropoffTime()); + s1.incrementWeight( + pickedUp ? preferences.pickupCost().toSeconds() : preferences.dropOffCost().toSeconds() + ); + s1.incrementTimeInSeconds( + pickedUp + ? (int) preferences.pickupTime().toSeconds() + : (int) preferences.dropOffTime().toSeconds() + ); s1.setBackMode(null); return s1.makeStateArray(); } diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/MqttGtfsRealtimeUpdaterConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/MqttGtfsRealtimeUpdaterConfig.java index 372bd36227e..ccc4ca5e80f 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/MqttGtfsRealtimeUpdaterConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/MqttGtfsRealtimeUpdaterConfig.java @@ -1,6 +1,6 @@ package org.opentripplanner.standalone.config.routerconfig.updaters; -import static org.opentripplanner.standalone.config.framework.json.OtpVersion.NA; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_0; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; @@ -12,13 +12,13 @@ public class MqttGtfsRealtimeUpdaterConfig { public static MqttGtfsRealtimeUpdaterParameters create(String configRef, NodeAdapter c) { return new MqttGtfsRealtimeUpdaterParameters( configRef, - c.of("feedId").since(NA).summary("The feed id to apply the updates to.").asString(), - c.of("url").since(NA).summary("URL of the MQTT broker.").asString(), - c.of("topic").since(NA).summary("The topic to subscribe to.").asString(), - c.of("qos").since(NA).summary("QOS level.").asInt(0), + c.of("feedId").since(V2_0).summary("The feed id to apply the updates to.").asString(), + c.of("url").since(V2_0).summary("URL of the MQTT broker.").asString(), + c.of("topic").since(V2_0).summary("The topic to subscribe to.").asString(), + c.of("qos").since(V2_0).summary("QOS level.").asInt(0), c .of("fuzzyTripMatching") - .since(NA) + .since(V2_0) .summary("Whether to match trips fuzzily.") .asBoolean(false), c diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index e600f1f8f12..4538f6de84d 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -8,11 +8,13 @@ import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_5; import static org.opentripplanner.standalone.config.routerequest.ItineraryFiltersConfig.mapItineraryFilterParams; import static org.opentripplanner.standalone.config.routerequest.TransferConfig.mapTransferPreferences; -import static org.opentripplanner.standalone.config.routerequest.VehicleRentalConfig.setVehicleRental; +import static org.opentripplanner.standalone.config.routerequest.TriangleOptimizationConfig.mapOptimizationTriangle; +import static org.opentripplanner.standalone.config.routerequest.VehicleParkingConfig.mapParking; +import static org.opentripplanner.standalone.config.routerequest.VehicleRentalConfig.mapRental; +import static org.opentripplanner.standalone.config.routerequest.VehicleWalkingConfig.mapVehicleWalking; import static org.opentripplanner.standalone.config.routerequest.WheelchairConfig.mapWheelchairPreferences; import java.time.Duration; -import java.util.List; import org.opentripplanner.api.parameter.QualifiedModeSet; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.routing.api.request.RequestModes; @@ -25,7 +27,6 @@ import org.opentripplanner.routing.api.request.preference.StreetPreferences; import org.opentripplanner.routing.api.request.preference.SystemPreferences; import org.opentripplanner.routing.api.request.preference.TransitPreferences; -import org.opentripplanner.routing.api.request.preference.VehicleParkingPreferences; import org.opentripplanner.routing.api.request.preference.WalkPreferences; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; import org.opentripplanner.standalone.config.sandbox.DataOverlayParametersMapper; @@ -167,7 +168,7 @@ cost function. The cost function (`unpreferredCost`) is defined as a linear func .asFeedScopedIds(request.journey().transit().unpreferredAgencies()) ); - TransitPriorityGroupConfig.mapTransitRequest(c, request.journey().transit()); + TransitGroupPriorityConfig.mapTransitRequest(c, request.journey().transit()); // Map preferences request.withPreferences(preferences -> mapPreferences(c, preferences)); @@ -292,25 +293,24 @@ The board time is added to the time when going from the stop (offboard) to onboa .asCostLinearFunction(dft.unpreferredCost()) ); - String relaxTransitPriorityGroupValue = c - .of("relaxTransitPriorityGroup") + String relaxTransitGroupPriorityValue = c + .of("relaxTransitGroupPriority") .since(V2_5) - .summary("The relax function for transit-priority-groups") + .summary("The relax function for transit-group-priority") .description( """ - A path is considered optimal if the generalized-cost is less than the - generalized-cost of another path. If this parameter is set, the comparison is relaxed - further if they belong to different transit-priority-groups. + A path is considered optimal if the generalized-cost is less than the generalized-cost of + another path. If this parameter is set, the comparison is relaxed further if they belong + to different transit groups. """ ) - .asString(dft.relaxTransitPriorityGroup().toString()); + .asString(dft.relaxTransitGroupPriority().toString()); - if (relaxTransitPriorityGroupValue != null) { - builder.withTransitGroupPriorityGeneralizedCostSlack( - CostLinearFunction.of(relaxTransitPriorityGroupValue) - ); + if (relaxTransitGroupPriorityValue != null) { + builder.withRelaxTransitGroupPriority(CostLinearFunction.of(relaxTransitGroupPriorityValue)); } + // TODO REMOVE THIS builder.withRaptor(it -> c .of("relaxTransitSearchGeneralizedCostAtDestination") @@ -333,178 +333,54 @@ The board time is added to the time when going from the stop (offboard) to onboa ); } - private static void mapBikePreferences(NodeAdapter c, BikePreferences.Builder builder) { + private static void mapBikePreferences(NodeAdapter root, BikePreferences.Builder builder) { var dft = builder.original(); + NodeAdapter c = root.of("bicycle").since(V2_5).summary("Bicycle preferences.").asObject(); builder .withSpeed( c - .of("bikeSpeed") + .of("speed") .since(V2_0) - .summary("Max bike speed along streets, in meters per second") + .summary("Max bicycle speed along streets, in meters per second") .asDouble(dft.speed()) ) .withReluctance( c - .of("bikeReluctance") + .of("reluctance") .since(V2_0) .summary( - "A multiplier for how bad biking is, compared to being in transit for equal lengths of time." + "A multiplier for how bad cycling is, compared to being in transit for equal lengths of time." ) .asDouble(dft.reluctance()) ) .withBoardCost( c - .of("bikeBoardCost") + .of("boardCost") .since(V2_0) - .summary("Prevents unnecessary transfers by adding a cost for boarding a vehicle.") + .summary( + "Prevents unnecessary transfers by adding a cost for boarding a transit vehicle." + ) .description( - "This is the cost that is used when boarding while cycling." + + "This is the cost that is used when boarding while cycling. " + "This is usually higher that walkBoardCost." ) .asInt(dft.boardCost()) ) - .withWalkingSpeed( - c - .of("bikeWalkingSpeed") - .since(V2_1) - .summary( - "The user's bike walking speed in meters/second. Defaults to approximately 3 MPH." - ) - .asDouble(dft.walkingSpeed()) - ) - .withWalkingReluctance( - c - .of("bikeWalkingReluctance") - .since(V2_1) - .summary( - "A multiplier for how bad walking with a bike is, compared to being in transit for equal lengths of time." - ) - .asDouble(dft.walkingReluctance()) - ) - .withSwitchTime( - c - .of("bikeSwitchTime") - .since(V2_0) - .summary("The time it takes the user to fetch their bike and park it again in seconds.") - .asInt(dft.switchTime()) - ) - .withSwitchCost( - c - .of("bikeSwitchCost") - .since(V2_0) - .summary("The cost of the user fetching their bike and parking it again.") - .asInt(dft.switchCost()) - ) .withOptimizeType( c - .of("optimize") + .of("optimization") .since(V2_0) .summary("The set of characteristics that the user wants to optimize for.") - .asEnum(dft.optimizeType()) - ) - .withOptimizeTriangle(it -> - it - .withTime( - c - .of("bikeTriangleTimeFactor") - .since(V2_0) - .summary("For bike triangle routing, how much time matters (range 0-1).") - .asDouble(it.time()) - ) - .withSlope( - c - .of("bikeTriangleSlopeFactor") - .since(V2_0) - .summary("For bike triangle routing, how much slope matters (range 0-1).") - .asDouble(it.slope()) - ) - .withSafety( - c - .of("bikeTriangleSafetyFactor") - .since(V2_0) - .summary("For bike triangle routing, how much safety matters (range 0-1).") - .asDouble(it.safety()) - ) - ) - .withStairsReluctance( - c - .of("bikeStairsReluctance") - .since(V2_3) - .summary( - "How bad is it to walk the bicycle up/down a flight of stairs compared to taking a detour." - ) - .asDouble(dft.stairsReluctance()) - ) - .withParking(it -> - it - .withUnpreferredVehicleParkingTagCost( - c - .of("unpreferredVehicleParkingTagCost") - .since(V2_3) - .summary("What cost to add if a parking facility doesn't contain a preferred tag.") - .description("See `preferredVehicleParkingTags`.") - .asInt( - VehicleParkingPreferences.DEFAULT.unpreferredVehicleParkingTagCost().toSeconds() - ) - ) - .withBannedVehicleParkingTags( - c - .of("bannedVehicleParkingTags") - .since(V2_1) - .summary( - "Tags with which a vehicle parking will not be used. If empty, no tags are banned." - ) - .description( - """ - Vehicle parking tags can originate from different places depending on the origin of the parking(OSM or RT feed). - """ - ) - .asStringSet(List.of()) - ) - .withRequiredVehicleParkingTags( - c - .of("requiredVehicleParkingTags") - .since(V2_1) - .summary( - "Tags without which a vehicle parking will not be used. If empty, no tags are required." - ) - .description( - """ - Vehicle parking tags can originate from different places depending on the origin of the parking(OSM or RT feed). - """ - ) - .asStringSet(List.of()) - ) - .withParkTime( - c - .of("bikeParkTime") - .since(V2_0) - .summary("Time to park a bike.") - .asDuration(VehicleParkingPreferences.DEFAULT.parkTime()) - ) - .withParkCost( - c - .of("bikeParkCost") - .since(V2_0) - .summary("Cost to park a bike.") - .asInt(VehicleParkingPreferences.DEFAULT.parkCost().toSeconds()) - ) - .withPreferredVehicleParkingTags( - c - .of("preferredVehicleParkingTags") - .since(V2_3) - .summary( - "Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised." - ) - .description( - """ - Vehicle parking tags can originate from different places depending on the origin of the parking(OSM or RT feed). - """ - ) - .asStringSet(List.of()) + .description( + "If the triangle optimization is used, it's enough to just define the triangle parameters" ) + .asEnum(dft.optimizeType()) ) - .withRental(it -> setVehicleRental(c, it)); + // triangle overrides the optimization type if defined + .withForcedOptimizeTriangle(it -> mapOptimizationTriangle(c, it)) + .withWalking(it -> mapVehicleWalking(c, it)) + .withParking(it -> mapParking(c, it)) + .withRental(it -> mapRental(c, it)); } private static void mapStreetPreferences(NodeAdapter c, StreetPreferences.Builder builder) { @@ -693,132 +569,56 @@ The street search(AStar) aborts after this duration and any paths found are retu ); } - private static void mapCarPreferences(NodeAdapter c, CarPreferences.Builder builder) { + private static void mapCarPreferences(NodeAdapter root, CarPreferences.Builder builder) { var dft = builder.original(); + NodeAdapter c = root.of("car").since(V2_5).summary("Car preferences.").asObject(); builder .withSpeed( c - .of("carSpeed") + .of("speed") .since(V2_0) .summary("Max car speed along streets, in meters per second") .asDouble(dft.speed()) ) .withReluctance( c - .of("carReluctance") + .of("reluctance") .since(V2_0) .summary( "A multiplier for how bad driving is, compared to being in transit for equal lengths of time." ) .asDouble(dft.reluctance()) ) - .withDropoffTime( - c - .of("carDropoffTime") - .since(V2_0) - .summary( - "Time to park a car in a park and ride, w/o taking into account driving and walking cost." - ) - .asInt(dft.dropoffTime()) - ) .withPickupCost( c - .of("carPickupCost") + .of("pickupCost") .since(V2_1) .summary("Add a cost for car pickup changes when a pickup or drop off takes place") - .asInt(dft.pickupCost()) + .asInt(dft.pickupCost().toSeconds()) ) .withPickupTime( c - .of("carPickupTime") + .of("pickupTime") .since(V2_1) .summary("Add a time for car pickup changes when a pickup or drop off takes place") - .asInt(dft.pickupTime()) + .asDuration(dft.pickupTime()) ) .withAccelerationSpeed( c - .of("carAccelerationSpeed") + .of("accelerationSpeed") .since(V2_0) .summary("The acceleration speed of an automobile, in meters per second per second.") .asDouble(dft.accelerationSpeed()) ) .withDecelerationSpeed( c - .of("carDecelerationSpeed") + .of("decelerationSpeed") .since(V2_0) .summary("The deceleration speed of an automobile, in meters per second per second.") .asDouble(dft.decelerationSpeed()) ) - .withParking(it -> - it - .withUnpreferredVehicleParkingTagCost( - c - .of("unpreferredVehicleParkingTagCost") - .since(V2_3) - .summary("What cost to add if a parking facility doesn't contain a preferred tag.") - .description("See `preferredVehicleParkingTags`.") - .asInt( - VehicleParkingPreferences.DEFAULT.unpreferredVehicleParkingTagCost().toSeconds() - ) - ) - .withBannedVehicleParkingTags( - c - .of("bannedVehicleParkingTags") - .since(V2_1) - .summary( - "Tags with which a vehicle parking will not be used. If empty, no tags are banned." - ) - .description( - """ - Vehicle parking tags can originate from different places depending on the origin of the parking(OSM or RT feed). - """ - ) - .asStringSet(List.of()) - ) - .withRequiredVehicleParkingTags( - c - .of("requiredVehicleParkingTags") - .since(V2_1) - .summary( - "Tags without which a vehicle parking will not be used. If empty, no tags are required." - ) - .description( - """ - Vehicle parking tags can originate from different places depending on the origin of the parking(OSM or RT feed). - """ - ) - .asStringSet(List.of()) - ) - .withParkCost( - c - .of("carParkCost") - .since(V2_1) - .summary("Cost of parking a car.") - .asInt(VehicleParkingPreferences.DEFAULT.parkCost().toSeconds()) - ) - .withParkTime( - c - .of("carParkTime") - .since(V2_1) - .summary("Time to park a car") - .asDuration(VehicleParkingPreferences.DEFAULT.parkTime()) - ) - .withPreferredVehicleParkingTags( - c - .of("preferredVehicleParkingTags") - .since(V2_3) - .summary( - "Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised." - ) - .description( - """ - Vehicle parking tags can originate from different places depending on the origin of the parking(OSM or RT feed). - """ - ) - .asStringSet(List.of()) - ) - ) - .withRental(it -> setVehicleRental(c, it)); + .withParking(it -> mapParking(c, it)) + .withRental(it -> mapRental(c, it)); } private static void mapSystemPreferences(NodeAdapter c, SystemPreferences.Builder builder) { @@ -869,19 +669,20 @@ private static void mapSystemPreferences(NodeAdapter c, SystemPreferences.Builde } } - private static void mapWalkPreferences(NodeAdapter c, WalkPreferences.Builder walk) { + private static void mapWalkPreferences(NodeAdapter root, WalkPreferences.Builder walk) { var dft = walk.original(); + NodeAdapter c = root.of("walk").since(V2_5).summary("Walking preferences.").asObject(); walk .withSpeed( c - .of("walkSpeed") + .of("speed") .since(V2_0) .summary("The user's walking speed in meters/second.") .asDouble(dft.speed()) ) .withReluctance( c - .of("walkReluctance") + .of("reluctance") .since(V2_0) .summary( "A multiplier for how bad walking is, compared to being in transit for equal lengths of time." @@ -899,7 +700,7 @@ private static void mapWalkPreferences(NodeAdapter c, WalkPreferences.Builder wa ) .withBoardCost( c - .of("walkBoardCost") + .of("boardCost") .since(V2_0) .summary( """ @@ -933,7 +734,7 @@ private static void mapWalkPreferences(NodeAdapter c, WalkPreferences.Builder wa ) .withSafetyFactor( c - .of("walkSafetyFactor") + .of("safetyFactor") .since(V2_2) .summary("Factor for how much the walk safety is considered in routing.") .description( diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitGroupPriorityConfig.java similarity index 76% rename from src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java rename to src/main/java/org/opentripplanner/standalone/config/routerequest/TransitGroupPriorityConfig.java index 51faafc7cbf..e5f1ccd784a 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitGroupPriorityConfig.java @@ -6,36 +6,36 @@ import java.util.Collection; import java.util.List; import org.opentripplanner.routing.api.request.request.TransitRequest; -import org.opentripplanner.routing.api.request.request.filter.TransitPriorityGroupSelect; +import org.opentripplanner.routing.api.request.request.filter.TransitGroupSelect; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; import org.opentripplanner.standalone.config.framework.json.OtpVersion; import org.opentripplanner.transit.model.basic.TransitMode; -public class TransitPriorityGroupConfig { +public class TransitGroupPriorityConfig { public static void mapTransitRequest(NodeAdapter root, TransitRequest transit) { var c = root - .of("transitPriorityGroups") + .of("transitGroupPriority") .since(OtpVersion.V2_5) - .summary("Transit priority groups configuration") + .summary( + "Group transit patterns and give each group a mutual advantage in the Raptor search." + ) .description( """ Use this to separate transit patterns into groups. Each group will be given a group-id. A path (multiple legs) will then have a set of group-ids based on the group-id from each leg. Hence, two paths with a different set of group-ids will BOTH be optimal unless the cost is - worse than the relaxation specified in the `relaxTransitPriorityGroup` parameter. This is + worse than the relaxation specified in the `relaxTransitGroupPriority` parameter. This is only available in the TransmodelAPI for now. - Unmatched patterns are put in the BASE priority-group (group id: 0). This group is special. - If a path only have legs in the base group, then that path dominates other paths, but other - paths must be better to make it. + Unmatched patterns are put in the BASE priority-group. """ ) .experimentalFeature() .asObject(); transit.addPriorityGroupsByAgency( - TransitPriorityGroupConfig.mapList( + TransitGroupPriorityConfig.mapList( c, "byAgency", "All groups here are split by agency. For example if you list mode " + @@ -44,7 +44,7 @@ public static void mapTransitRequest(NodeAdapter root, TransitRequest transit) { ) ); transit.addPriorityGroupsGlobal( - TransitPriorityGroupConfig.mapList( + TransitGroupPriorityConfig.mapList( c, "global", "All services matching a 'global' group will get the same group-id. Use this " + @@ -53,7 +53,7 @@ public static void mapTransitRequest(NodeAdapter root, TransitRequest transit) { ); } - private static Collection mapList( + private static Collection mapList( NodeAdapter root, String parameterName, String description @@ -61,13 +61,13 @@ private static Collection mapList( return root .of(parameterName) .since(V2_5) - .summary("Configuration for transit priority groups.") + .summary("List of transit groups.") .description(description + " The max total number of group-ids are 32, so be careful.") - .asObjects(TransitPriorityGroupConfig::mapTransitGroupSelect); + .asObjects(TransitGroupPriorityConfig::mapTransitGroupSelect); } - private static TransitPriorityGroupSelect mapTransitGroupSelect(NodeAdapter c) { - return TransitPriorityGroupSelect + private static TransitGroupSelect mapTransitGroupSelect(NodeAdapter c) { + return TransitGroupSelect .of() .addModes( c diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/TriangleOptimizationConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/TriangleOptimizationConfig.java new file mode 100644 index 00000000000..e6da735a4ac --- /dev/null +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/TriangleOptimizationConfig.java @@ -0,0 +1,54 @@ +package org.opentripplanner.standalone.config.routerequest; + +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_0; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_5; + +import org.opentripplanner.routing.api.request.preference.TimeSlopeSafetyTriangle; +import org.opentripplanner.standalone.config.framework.json.NodeAdapter; + +public class TriangleOptimizationConfig { + + static void mapOptimizationTriangle(NodeAdapter c, TimeSlopeSafetyTriangle.Builder preferences) { + var optimizationTriangle = c + .of("triangle") + .since(V2_5) + .summary("Triangle optimization criteria.") + .description("Optimization type doesn't need to be defined if these values are defined.") + .asObject(); + mapTriangleParameters(optimizationTriangle, preferences); + } + + private static void mapTriangleParameters( + NodeAdapter c, + TimeSlopeSafetyTriangle.Builder builder + ) { + builder + .withTime( + c + .of("time") + .since(V2_0) + .summary("Relative importance of duration of travel (range 0-1).") + .asDouble(builder.time()) + ) + .withSlope( + c + .of("flatness") + .since(V2_0) + .summary("Relative importance of flat terrain (range 0-1).") + .asDouble(builder.slope()) + ) + .withSafety( + c + .of("safety") + .since(V2_0) + .summary("Relative importance of safety (range 0-1).") + .description( + """ + This factor can also include other concerns such as convenience and general cyclist + preferences by taking into account road surface etc. + """ + ) + .asDouble(builder.safety()) + ); + } +} diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleParkingConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleParkingConfig.java new file mode 100644 index 00000000000..218ac3b33df --- /dev/null +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleParkingConfig.java @@ -0,0 +1,93 @@ +package org.opentripplanner.standalone.config.routerequest; + +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_3; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_5; + +import java.util.List; +import org.opentripplanner.routing.api.request.preference.VehicleParkingPreferences; +import org.opentripplanner.standalone.config.framework.json.NodeAdapter; + +public class VehicleParkingConfig { + + static void mapParking(NodeAdapter c, VehicleParkingPreferences.Builder preferences) { + var vehicleParking = c + .of("parking") + .since(V2_5) + .summary("Preferences for parking a vehicle.") + .asObject(); + mapParkingPreferences(vehicleParking, preferences); + } + + private static void mapParkingPreferences( + NodeAdapter c, + VehicleParkingPreferences.Builder builder + ) { + builder + .withUnpreferredVehicleParkingTagCost( + c + .of("unpreferredVehicleParkingTagCost") + .since(V2_3) + .summary("What cost to add if a parking facility doesn't contain a preferred tag.") + .description("See `preferredVehicleParkingTags`.") + .asInt(VehicleParkingPreferences.DEFAULT.unpreferredVehicleParkingTagCost().toSeconds()) + ) + .withBannedVehicleParkingTags( + c + .of("bannedVehicleParkingTags") + .since(V2_1) + .summary( + "Tags with which a vehicle parking will not be used. If empty, no tags are banned." + ) + .description( + """ + Vehicle parking tags can originate from different places depending on the origin of the parking(OSM or RT feed). + """ + ) + .asStringSet(List.of()) + ) + .withRequiredVehicleParkingTags( + c + .of("requiredVehicleParkingTags") + .since(V2_1) + .summary( + "Tags without which a vehicle parking will not be used. If empty, no tags are required." + ) + .description( + """ + Vehicle parking tags can originate from different places depending on the origin of the parking(OSM or RT feed). + """ + ) + .asStringSet(List.of()) + ) + .withTime( + c + .of("time") + .since(V2_0) + .summary("Time to park a vehicle.") + .asDuration(VehicleParkingPreferences.DEFAULT.time()) + ) + .withCost( + c + .of("cost") + .since(V2_0) + .summary("Cost to park a vehicle.") + .asInt(VehicleParkingPreferences.DEFAULT.cost().toSeconds()) + ) + .withPreferredVehicleParkingTags( + c + .of("preferredVehicleParkingTags") + .since(V2_3) + .summary( + "Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised." + ) + .description( + """ + Vehicle parking tags can originate from different places depending on the origin of the parking(OSM or RT feed). + """ + ) + .asStringSet(List.of()) + ); + } +} diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleRentalConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleRentalConfig.java index 057daa7001c..401d977762d 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleRentalConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleRentalConfig.java @@ -10,28 +10,44 @@ public class VehicleRentalConfig { - static void mapRentalPreferences(NodeAdapter c, VehicleRentalPreferences.Builder builder) { + static void mapRental(NodeAdapter c, VehicleRentalPreferences.Builder preferences) { + var vehicleRental = c.of("rental").since(V2_3).summary("Vehicle rental options").asObject(); + mapRentalPreferences(vehicleRental, preferences); + } + + private static void mapRentalPreferences( + NodeAdapter c, + VehicleRentalPreferences.Builder builder + ) { var dft = builder.original(); builder - .withDropoffCost( + .withDropOffCost( c .of("dropOffCost") .since(V2_0) .summary("Cost to drop-off a rented vehicle.") - .asInt(dft.dropoffCost()) + .asInt(dft.dropOffCost().toSeconds()) ) - .withDropoffTime( + .withDropOffTime( c .of("dropOffTime") .since(V2_0) .summary("Time to drop-off a rented vehicle.") - .asInt(dft.dropoffTime()) + .asDuration(dft.dropOffTime()) ) .withPickupCost( - c.of("pickupCost").since(V2_0).summary("Cost to rent a vehicle.").asInt(dft.pickupCost()) + c + .of("pickupCost") + .since(V2_0) + .summary("Cost to rent a vehicle.") + .asInt(dft.pickupCost().toSeconds()) ) .withPickupTime( - c.of("pickupTime").since(V2_0).summary("Time to rent a vehicle.").asInt(dft.pickupTime()) + c + .of("pickupTime") + .since(V2_0) + .summary("Time to rent a vehicle.") + .asDuration(dft.pickupTime()) ) .withUseAvailabilityInformation( c @@ -49,7 +65,7 @@ static void mapRentalPreferences(NodeAdapter c, VehicleRentalPreferences.Builder .summary( "The cost of arriving at the destination with the rented vehicle, to discourage doing so." ) - .asDouble(dft.arrivingInRentalVehicleAtDestinationCost()) + .asInt(dft.arrivingInRentalVehicleAtDestinationCost().toSeconds()) ) .withAllowArrivingInRentedVehicleAtDestination( c @@ -79,13 +95,4 @@ static void mapRentalPreferences(NodeAdapter c, VehicleRentalPreferences.Builder .asStringSet(dft.bannedNetworks()) ); } - - static void setVehicleRental(NodeAdapter c, VehicleRentalPreferences.Builder preferences) { - var vehicleRental = c - .of("vehicleRental") - .since(V2_3) - .summary("Vehicle rental options") - .asObject(); - mapRentalPreferences(vehicleRental, preferences); - } } diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleWalkingConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleWalkingConfig.java new file mode 100644 index 00000000000..f2ba922c8e3 --- /dev/null +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleWalkingConfig.java @@ -0,0 +1,82 @@ +package org.opentripplanner.standalone.config.routerequest; + +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_3; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_5; + +import org.opentripplanner.routing.api.request.preference.VehicleWalkingPreferences; +import org.opentripplanner.standalone.config.framework.json.NodeAdapter; + +public class VehicleWalkingConfig { + + static void mapVehicleWalking(NodeAdapter c, VehicleWalkingPreferences.Builder preferences) { + var vehicleWalking = c + .of("walk") + .since(V2_5) + .summary("Preferences for walking a vehicle.") + .asObject(); + mapVehicleWalkingPreferences(vehicleWalking, preferences); + } + + private static void mapVehicleWalkingPreferences( + NodeAdapter c, + VehicleWalkingPreferences.Builder builder + ) { + var dft = builder.original(); + builder + .withSpeed( + c + .of("speed") + .since(V2_1) + .summary( + "The user's vehicle walking speed in meters/second. Defaults to approximately 3 MPH." + ) + .asDouble(dft.speed()) + ) + .withReluctance( + c + .of("reluctance") + .since(V2_1) + .summary( + "A multiplier for how bad walking with a vehicle is, compared to being in transit for equal lengths of time." + ) + .asDouble(dft.reluctance()) + ) + .withHopTime( + c + .of("hopTime") + .since(V2_0) + .summary("The time it takes the user to hop on or off a vehicle.") + .description( + """ + Time it takes to rent or park a vehicle have their own parameters and this is not meant + for controlling the duration of those events. + """ + ) + .asDuration(dft.hopTime()) + ) + .withHopCost( + c + .of("hopCost") + .since(V2_0) + .summary("The cost of hopping on or off a vehicle.") + .description( + """ + There are different parameters for the cost of renting or parking a vehicle and this is + not meant for controlling the cost of those events. + """ + ) + .asInt(dft.hopCost().toSeconds()) + ) + .withStairsReluctance( + c + .of("stairsReluctance") + .since(V2_3) + .summary( + "How bad is it to walk the vehicle up/down a flight of stairs compared to taking a detour." + ) + .asDouble(dft.stairsReluctance()) + ); + } +} diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java index 097fcfb4f7a..a4f0ee49652 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java @@ -6,6 +6,7 @@ import javax.annotation.Nullable; import org.opentripplanner.ext.emissions.EmissionsDataModel; import org.opentripplanner.ext.emissions.EmissionsServiceModule; +import org.opentripplanner.ext.interactivelauncher.configuration.InteractiveLauncherModule; import org.opentripplanner.ext.ridehailing.configure.RideHailingServicesModule; import org.opentripplanner.ext.stopconsolidation.StopConsolidationRepository; import org.opentripplanner.ext.stopconsolidation.configure.StopConsolidationServiceModule; @@ -50,6 +51,7 @@ RideHailingServicesModule.class, EmissionsServiceModule.class, StopConsolidationServiceModule.class, + InteractiveLauncherModule.class, } ) public interface ConstructApplicationFactory { diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java index 2ff6de29b3d..c9d7253b0be 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java @@ -7,6 +7,7 @@ import javax.annotation.Nullable; import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.ext.emissions.EmissionsService; +import org.opentripplanner.ext.interactivelauncher.api.LauncherRequestDecorator; import org.opentripplanner.ext.ridehailing.RideHailingService; import org.opentripplanner.ext.stopconsolidation.StopConsolidationService; import org.opentripplanner.raptor.configure.RaptorConfig; @@ -36,11 +37,14 @@ OtpServerRequestContext providesServerContext( List rideHailingServices, @Nullable StopConsolidationService stopConsolidationService, @Nullable TraverseVisitor traverseVisitor, - EmissionsService emissionsService + EmissionsService emissionsService, + LauncherRequestDecorator launcherRequestDecorator ) { + var defaultRequest = launcherRequestDecorator.intercept(routerConfig.routingRequestDefaults()); + return DefaultServerRequestContext.create( routerConfig.transitTuningConfig(), - routerConfig.routingRequestDefaults(), + defaultRequest, raptorConfig, graph, transitService, diff --git a/src/main/java/org/opentripplanner/street/model/edge/BikeWalkableEdge.java b/src/main/java/org/opentripplanner/street/model/edge/BikeWalkableEdge.java index eb17c5d0900..799a5b006e6 100644 --- a/src/main/java/org/opentripplanner/street/model/edge/BikeWalkableEdge.java +++ b/src/main/java/org/opentripplanner/street/model/edge/BikeWalkableEdge.java @@ -17,8 +17,8 @@ default void switchToWalkingBike(RoutingPreferences preferences, StateEditor edi editor.setBackWalkingBike(true); if (shouldIncludeCost) { - editor.incrementWeight(preferences.bike().switchCost()); - editor.incrementTimeInSeconds(preferences.bike().switchTime()); + editor.incrementWeight(preferences.bike().walking().hopCost().toSeconds()); + editor.incrementTimeInSeconds((int) preferences.bike().walking().hopTime().toSeconds()); } } @@ -28,8 +28,8 @@ default void switchToBiking(RoutingPreferences preferences, StateEditor editor) editor.setBackWalkingBike(false); if (shouldIncludeCost) { - editor.incrementWeight(preferences.bike().switchCost()); - editor.incrementTimeInSeconds(preferences.bike().switchTime()); + editor.incrementWeight(preferences.bike().walking().hopCost().toSeconds()); + editor.incrementTimeInSeconds((int) preferences.bike().walking().hopTime().toSeconds()); } } diff --git a/src/main/java/org/opentripplanner/street/model/edge/CarPickupableEdge.java b/src/main/java/org/opentripplanner/street/model/edge/CarPickupableEdge.java index ec50e3b60ca..7ea1678bbe7 100644 --- a/src/main/java/org/opentripplanner/street/model/edge/CarPickupableEdge.java +++ b/src/main/java/org/opentripplanner/street/model/edge/CarPickupableEdge.java @@ -30,13 +30,13 @@ default void dropOffAfterDriving(State state, StateEditor editor) { ? CarPickupState.WALK_TO_PICKUP : CarPickupState.WALK_FROM_DROP_OFF ); - editor.incrementTimeInSeconds(state.getPreferences().car().pickupTime()); - editor.incrementWeight(state.getPreferences().car().pickupCost()); + editor.incrementTimeInSeconds((int) state.getPreferences().car().pickupTime().toSeconds()); + editor.incrementWeight(state.getPreferences().car().pickupCost().toSeconds()); } default void driveAfterPickup(State state, StateEditor editor) { editor.setCarPickupState(CarPickupState.IN_CAR); - editor.incrementTimeInSeconds(state.getPreferences().car().pickupTime()); - editor.incrementWeight(state.getPreferences().car().pickupCost()); + editor.incrementTimeInSeconds((int) state.getPreferences().car().pickupTime().toSeconds()); + editor.incrementWeight(state.getPreferences().car().pickupCost().toSeconds()); } } diff --git a/src/main/java/org/opentripplanner/street/model/edge/StreetEdge.java b/src/main/java/org/opentripplanner/street/model/edge/StreetEdge.java index d655c3634f3..eb984a9e501 100644 --- a/src/main/java/org/opentripplanner/street/model/edge/StreetEdge.java +++ b/src/main/java/org/opentripplanner/street/model/edge/StreetEdge.java @@ -54,7 +54,7 @@ public class StreetEdge private static final Logger LOG = LoggerFactory.getLogger(StreetEdge.class); - private static final double GREENWAY_SAFETY_FACTOR = 0.1; + private static final double SAFEST_STREETS_SAFETY_FACTOR = 0.1; /** If you have more than 16 flags, increase flags to short or int */ static final int BACK_FLAG_INDEX = 0; @@ -237,7 +237,9 @@ public double calculateSpeed( final double speed = switch (traverseMode) { - case WALK -> walkingBike ? preferences.bike().walkingSpeed() : preferences.walk().speed(); + case WALK -> walkingBike + ? preferences.bike().walking().speed() + : preferences.walk().speed(); case BICYCLE, SCOOTER -> preferences.bike().speed(); case CAR -> getCarSpeed(); case FLEX -> throw new IllegalArgumentException("getSpeed(): Invalid mode " + traverseMode); @@ -1222,17 +1224,17 @@ private TraversalCosts bicycleTraversalCost(RoutingPreferences pref, double spee double time = getEffectiveBikeDistance() / speed; double weight; switch (pref.bike().optimizeType()) { - case GREENWAYS -> { + case SAFEST_STREETS -> { weight = bicycleSafetyFactor * getDistanceMeters() / speed; - if (bicycleSafetyFactor <= GREENWAY_SAFETY_FACTOR) { - // greenways are treated as even safer than they really are + if (bicycleSafetyFactor <= SAFEST_STREETS_SAFETY_FACTOR) { + // safest streets are treated as even safer than they really are weight *= 0.66; } } - case SAFE -> weight = getEffectiveBicycleSafetyDistance() / speed; - case FLAT -> /* see notes in StreetVertex on speed overhead */weight = + case SAFE_STREETS -> weight = getEffectiveBicycleSafetyDistance() / speed; + case FLAT_STREETS -> /* see notes in StreetVertex on speed overhead */weight = getEffectiveBikeDistanceForWorkCost() / speed; - case QUICK -> weight = getEffectiveBikeDistance() / speed; + case SHORTEST_DURATION -> weight = getEffectiveBikeDistance() / speed; case TRIANGLE -> { double quick = getEffectiveBikeDistance(); double safety = getEffectiveBicycleSafetyDistance(); @@ -1284,7 +1286,7 @@ private TraversalCosts walkingTraversalCosts( time = weight = (getEffectiveBikeDistance() / speed); if (isStairs()) { // we do allow walking the bike across a stairs but there is a very high default penalty - weight *= preferences.bike().stairsReluctance(); + weight *= preferences.bike().walking().stairsReluctance(); } } else { // take slopes into account when walking diff --git a/src/main/java/org/opentripplanner/street/model/edge/StreetEdgeReluctanceCalculator.java b/src/main/java/org/opentripplanner/street/model/edge/StreetEdgeReluctanceCalculator.java index 669adc2489f..ee2f3d833ee 100644 --- a/src/main/java/org/opentripplanner/street/model/edge/StreetEdgeReluctanceCalculator.java +++ b/src/main/java/org/opentripplanner/street/model/edge/StreetEdgeReluctanceCalculator.java @@ -10,7 +10,7 @@ private StreetEdgeReluctanceCalculator() {} /** * Compute reluctance for a regular street section. Note! This does not apply if in a wheelchair, - * see {@link #computeWheelchairReluctance(RouteRequest, double, boolean, boolean)}. + * see {@link #computeWheelchairReluctance(RoutingPreferences, double, boolean, boolean)}. */ static double computeReluctance( RoutingPreferences pref, @@ -22,7 +22,7 @@ static double computeReluctance( return pref.walk().stairsReluctance(); } else { return switch (traverseMode) { - case WALK -> walkingBike ? pref.bike().walkingReluctance() : pref.walk().reluctance(); + case WALK -> walkingBike ? pref.bike().walking().reluctance() : pref.walk().reluctance(); case BICYCLE -> pref.bike().reluctance(); case CAR -> pref.car().reluctance(); default -> throw new IllegalArgumentException( diff --git a/src/main/java/org/opentripplanner/street/model/edge/StreetTransitEntityLink.java b/src/main/java/org/opentripplanner/street/model/edge/StreetTransitEntityLink.java index 926eebc31d9..df5b24a5928 100644 --- a/src/main/java/org/opentripplanner/street/model/edge/StreetTransitEntityLink.java +++ b/src/main/java/org/opentripplanner/street/model/edge/StreetTransitEntityLink.java @@ -143,7 +143,7 @@ private State[] buildState(State s0, StateEditor s1, RoutingPreferences pref) { s0.mayKeepRentedVehicleAtDestination() && rentalPreferences.allowArrivingInRentedVehicleAtDestination() ) { - s1.incrementWeight(rentalPreferences.arrivingInRentalVehicleAtDestinationCost()); + s1.incrementWeight(rentalPreferences.arrivingInRentalVehicleAtDestinationCost().toSeconds()); } s1.setBackMode(null); diff --git a/src/main/java/org/opentripplanner/street/model/edge/TemporaryFreeEdge.java b/src/main/java/org/opentripplanner/street/model/edge/TemporaryFreeEdge.java index 011d0fec7f1..d825a8fcbea 100644 --- a/src/main/java/org/opentripplanner/street/model/edge/TemporaryFreeEdge.java +++ b/src/main/java/org/opentripplanner/street/model/edge/TemporaryFreeEdge.java @@ -48,7 +48,7 @@ public State[] traverse(State s0) { s0.mayKeepRentedVehicleAtDestination() && rentalPreferences.allowArrivingInRentedVehicleAtDestination() ) { - s1.incrementWeight(rentalPreferences.arrivingInRentalVehicleAtDestinationCost()); + s1.incrementWeight(rentalPreferences.arrivingInRentalVehicleAtDestinationCost().toSeconds()); } return s1.makeStateArray(); diff --git a/src/main/java/org/opentripplanner/street/model/edge/VehicleParkingEdge.java b/src/main/java/org/opentripplanner/street/model/edge/VehicleParkingEdge.java index be909c1dc4c..d32f13d6ea4 100644 --- a/src/main/java/org/opentripplanner/street/model/edge/VehicleParkingEdge.java +++ b/src/main/java/org/opentripplanner/street/model/edge/VehicleParkingEdge.java @@ -94,20 +94,10 @@ protected State[] traverseUnPark(State s0) { if (streetMode.includesBiking()) { final BikePreferences bike = s0.getPreferences().bike(); - return traverseUnPark( - s0, - bike.parking().parkCost(), - bike.parking().parkTime(), - TraverseMode.BICYCLE - ); + return traverseUnPark(s0, bike.parking().cost(), bike.parking().time(), TraverseMode.BICYCLE); } else if (streetMode.includesDriving()) { final CarPreferences car = s0.getPreferences().car(); - return traverseUnPark( - s0, - car.parking().parkCost(), - car.parking().parkTime(), - TraverseMode.CAR - ); + return traverseUnPark(s0, car.parking().cost(), car.parking().time(), TraverseMode.CAR); } else { return State.empty(); } @@ -151,14 +141,14 @@ private State[] traversePark(State s0) { return traversePark( s0, - preferences.bike().parking().parkCost(), - preferences.bike().parking().parkTime() + preferences.bike().parking().cost(), + preferences.bike().parking().time() ); } else if (streetMode.includesDriving()) { return traversePark( s0, - preferences.car().parking().parkCost(), - preferences.car().parking().parkTime() + preferences.car().parking().cost(), + preferences.car().parking().time() ); } else { return State.empty(); diff --git a/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java b/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java index 73516b0348f..f15aff9048b 100644 --- a/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java +++ b/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java @@ -157,6 +157,10 @@ public DisplayVertex getElementAt(int index) { * TransitStops only, and allows a user to select stops, examine incoming and outgoing edges, and * examine trip patterns. It's meant mainly for debugging, so it's totally OK if it develops (say) a * bunch of weird buttons designed to debug specific cases. + *

    + * 2024-01-26: We talked about the visualizer in the developer meeting and while the code is a bit + * dusty, we decided that we want to keep the option open to build make the visualization of routing + * steps work again in the future and won't delete it. */ public class GraphVisualizer extends JFrame implements VertexSelectionListener { @@ -545,18 +549,18 @@ protected void route(String from, String to) { BicycleOptimizeType getSelectedOptimizeType() { if (opQuick.isSelected()) { - return BicycleOptimizeType.QUICK; + return BicycleOptimizeType.SHORTEST_DURATION; } if (opSafe.isSelected()) { - return BicycleOptimizeType.SAFE; + return BicycleOptimizeType.SAFE_STREETS; } if (opFlat.isSelected()) { - return BicycleOptimizeType.FLAT; + return BicycleOptimizeType.FLAT_STREETS; } if (opGreenways.isSelected()) { - return BicycleOptimizeType.GREENWAYS; + return BicycleOptimizeType.SAFEST_STREETS; } - return BicycleOptimizeType.QUICK; + return BicycleOptimizeType.SHORTEST_DURATION; } private Container makeDiffTab() { diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index 07067acdb28..9b49283c180 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -827,21 +827,21 @@ type QueryType { passThroughPoints: [PassThroughPoint!], """ Relax generalized-cost when comparing trips with a different set of - transit-priority-groups. The groups are set server side for service-journey and + transit-group-priorities. The groups are set server side for service-journey and can not be configured in the API. This mainly helps to return competition neutral - services. Long distance authorities are put in different transit-priority-groups. + services. Long distance authorities are put in different transit-groups. This relaxes the comparison inside the routing engine for each stop-arrival. If two - paths have a different set of transit-priority-groups, then the generalized-cost + paths have a different set of transit-group-priorities, then the generalized-cost comparison is relaxed. The final set of paths are filtered through the normal itinerary-filters. - The `ratio` must be greater or equal to 1.0 and less then 1.2. - - The `slack` must be greater or equal to 0 and less then 3600. + - The `constant` must be greater or equal to '0s' and less then '1h'. THIS IS STILL AN EXPERIMENTAL FEATURE - IT MAY CHANGE WITHOUT ANY NOTICE! """ - relaxTransitPriorityGroup: RelaxCostInput = null, + relaxTransitGroupPriority: RelaxCostInput = null, """ Whether non-optimal transit paths at the destination should be returned. Let c be the existing minimum pareto optimal generalized-cost to beat. Then a trip with cost c' is @@ -854,7 +854,7 @@ type QueryType { Values less than 1.0 is not allowed, and values greater than 2.0 are not supported, due to performance reasons. """ - relaxTransitSearchGeneralizedCostAtDestination: Float = null @deprecated(reason : "This is replaced by 'relaxTransitPriorityGroup'."), + relaxTransitSearchGeneralizedCostAtDestination: Float = null @deprecated(reason : "This is replaced by 'relaxTransitGroupPriority'."), """ The length of the search-window in minutes. This parameter is optional. @@ -1869,6 +1869,9 @@ enum WheelchairBoarding { "List of coordinates like: [[60.89, 11.12], [62.56, 12.10]]" scalar Coordinates +"A cost value, normally a value of 1 is equivalent to riding transit for 1 second, but it might not depending on the use-case. Format: 3665 = DT1h1m5s = 1h1m5s" +scalar Cost + "Local date using the ISO 8601 format: `YYYY-MM-DD`. Example: `2020-05-17`." scalar Date @@ -2030,12 +2033,12 @@ This is used to include more results into the result. A `ratio=2.0` means a path with twice as high cost as another one, is accepted. A `constant=$300` means a "fixed" constant is added to the limit. A `{ratio=1.0, constant=0}` is said to be the NORMAL relaxed cost - the limit is the same as the cost used to calculate the limit. The NORMAL is usually -the default. We can express the RelaxCost as a function `f(x) = constant + ratio * x`. -`f(x)=x` is the NORMAL function. +the default. We can express the RelaxCost as a function `f(t) = constant + ratio * t`. +`f(t)=t` is the NORMAL function. """ input RelaxCostInput { - "The constant value to add to the limit. Must be a positive number. The unit is cost-seconds." - constant: [ID!] = 0 + "The constant value to add to the limit. Must be a positive number. The value is equivalent to transit-cost-seconds. Integers are treated as seconds, but you may use the duration format. Example: '3665 = 'DT1h1m5s' = '1h1m5s'." + constant: Cost = "0s" "The factor to multiply with the 'other cost'. Minimum value is 1.0." ratio: Float = 1.0 } diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java index fba953cd93a..b05dd77e2a9 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java @@ -2,15 +2,17 @@ import static graphql.execution.ExecutionContextBuilder.newExecutionContextBuilder; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.params.provider.Arguments.of; -import static org.opentripplanner.routing.core.BicycleOptimizeType.SAFE; +import static org.opentripplanner.routing.core.BicycleOptimizeType.SAFE_STREETS; import static org.opentripplanner.routing.core.BicycleOptimizeType.TRIANGLE; import graphql.ExecutionInput; import graphql.execution.ExecutionId; import graphql.schema.DataFetchingEnvironment; import graphql.schema.DataFetchingEnvironmentImpl; +import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Map; @@ -21,12 +23,12 @@ import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.TestRoutingService; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.ext.fares.impl.DefaultFareService; import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.preference.TimeSlopeSafetyTriangle; import org.opentripplanner.routing.api.request.preference.VehicleParkingPreferences; -import org.opentripplanner.routing.core.BicycleOptimizeType; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graphfinder.GraphFinder; import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; @@ -149,7 +151,7 @@ private static Map mode(String mode) { void defaultBikeOptimize() { Map arguments = Map.of(); var routeRequest = RouteRequestMapper.toRouteRequest(executionContext(arguments), context); - assertEquals(SAFE, routeRequest.preferences().bike().optimizeType()); + assertEquals(SAFE_STREETS, routeRequest.preferences().bike().optimizeType()); } @Test @@ -170,14 +172,14 @@ void bikeTriangle() { ); } - static Stream noTriangleCases = BicycleOptimizeType - .nonTriangleValues() - .stream() + static Stream noTriangleCases = Arrays + .stream(GraphQLTypes.GraphQLOptimizeType.values()) + .filter(value -> value != GraphQLTypes.GraphQLOptimizeType.TRIANGLE) .map(Arguments::of); @ParameterizedTest @VariableSource("noTriangleCases") - void noTriangle(BicycleOptimizeType bot) { + void noTriangle(GraphQLTypes.GraphQLOptimizeType bot) { Map arguments = Map.of( "optimize", bot.name(), @@ -187,13 +189,25 @@ void noTriangle(BicycleOptimizeType bot) { var routeRequest = RouteRequestMapper.toRouteRequest(executionContext(arguments), context); - assertEquals(bot, routeRequest.preferences().bike().optimizeType()); + assertEquals(OptimizationTypeMapper.map(bot), routeRequest.preferences().bike().optimizeType()); assertEquals( TimeSlopeSafetyTriangle.DEFAULT, routeRequest.preferences().bike().optimizeTriangle() ); } + @Test + void walkReluctance() { + var reluctance = 119d; + Map arguments = Map.of("walkReluctance", reluctance); + + var routeRequest = RouteRequestMapper.toRouteRequest(executionContext(arguments), context); + assertEquals(reluctance, routeRequest.preferences().walk().reluctance()); + + var noParamsRequest = RouteRequestMapper.toRouteRequest(executionContext(Map.of()), context); + assertNotEquals(reluctance, noParamsRequest.preferences().walk().reluctance()); + } + private DataFetchingEnvironment executionContext(Map arguments) { ExecutionInput executionInput = ExecutionInput .newExecutionInput() diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java index 3058f622281..9a01a36cbcb 100644 --- a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java @@ -272,7 +272,7 @@ public void testBikeTriangleFactors() { @Test void testDefaultTriangleFactors() { var req2 = TripRequestMapper.createRequest(executionContext(Map.of())); - assertEquals(BicycleOptimizeType.SAFE, req2.preferences().bike().optimizeType()); + assertEquals(BicycleOptimizeType.SAFE_STREETS, req2.preferences().bike().optimizeType()); assertEquals(TimeSlopeSafetyTriangle.DEFAULT, req2.preferences().bike().optimizeTriangle()); } diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapperTest.java index 378571eeea9..a72fd25cbfa 100644 --- a/src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapperTest.java @@ -18,7 +18,7 @@ static List mapBikePreferencesTestCases() { Arguments.of( "walkReluctance", 10.0, - "BikePreferences{reluctance: 10.0, walkingReluctance: 27.0}" + "BikePreferences{reluctance: 10.0, walking: VehicleWalkingPreferences{reluctance: 27.0}}" ), Arguments.of("bikeSpeed", 10.0, "BikePreferences{speed: 10.0}"), Arguments.of( diff --git a/src/test/java/org/opentripplanner/apis/transmodel/model/plan/RelaxCostTypeTest.java b/src/test/java/org/opentripplanner/apis/transmodel/model/plan/RelaxCostTypeTest.java new file mode 100644 index 00000000000..6fa235cdf0b --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/transmodel/model/plan/RelaxCostTypeTest.java @@ -0,0 +1,71 @@ +package org.opentripplanner.apis.transmodel.model.plan; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.apis.transmodel.model.plan.RelaxCostType.CONSTANT; +import static org.opentripplanner.apis.transmodel.model.plan.RelaxCostType.RATIO; + +import graphql.language.FloatValue; +import graphql.language.ObjectField; +import graphql.language.ObjectValue; +import graphql.language.StringValue; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.model.Cost; +import org.opentripplanner.routing.api.request.framework.CostLinearFunction; + +class RelaxCostTypeTest { + + @Test + void valueOf() { + assertEquals( + ObjectValue + .newObjectValue() + .objectField(ObjectField.newObjectField().name(RATIO).value(FloatValue.of(1.0)).build()) + .objectField( + ObjectField.newObjectField().name(CONSTANT).value(StringValue.of("0s")).build() + ) + .build() + .toString(), + RelaxCostType.valueOf(CostLinearFunction.NORMAL).toString() + ); + assertEquals( + ObjectValue + .newObjectValue() + .objectField(ObjectField.newObjectField().name(RATIO).value(FloatValue.of(1.3)).build()) + .objectField( + ObjectField.newObjectField().name(CONSTANT).value(StringValue.of("1m7s")).build() + ) + .build() + .toString(), + RelaxCostType.valueOf(CostLinearFunction.of(Cost.costOfSeconds(67), 1.3)).toString() + ); + } + + @Test + void mapToDomain() { + Map input; + + input = Map.of(RATIO, 1.0, CONSTANT, Cost.ZERO); + assertEquals( + CostLinearFunction.NORMAL, + RelaxCostType.mapToDomain(input, CostLinearFunction.ZERO) + ); + + input = Map.of(RATIO, 0.0, CONSTANT, Cost.ZERO); + assertEquals( + CostLinearFunction.ZERO, + RelaxCostType.mapToDomain(input, CostLinearFunction.ZERO) + ); + + input = Map.of(RATIO, 1.7, CONSTANT, Cost.costOfSeconds(3600 + 3 * 60 + 7)); + assertEquals( + CostLinearFunction.of("1h3m7s + 1.7t"), + RelaxCostType.mapToDomain(input, CostLinearFunction.ZERO) + ); + assertEquals( + CostLinearFunction.NORMAL, + RelaxCostType.mapToDomain(null, CostLinearFunction.NORMAL) + ); + assertEquals(CostLinearFunction.ZERO, RelaxCostType.mapToDomain(null, CostLinearFunction.ZERO)); + } +} diff --git a/src/test/java/org/opentripplanner/apis/vectortiles/DebugStyleSpecTest.java b/src/test/java/org/opentripplanner/apis/vectortiles/DebugStyleSpecTest.java index d685e07a2f2..befd34a3b38 100644 --- a/src/test/java/org/opentripplanner/apis/vectortiles/DebugStyleSpecTest.java +++ b/src/test/java/org/opentripplanner/apis/vectortiles/DebugStyleSpecTest.java @@ -3,23 +3,25 @@ import static org.opentripplanner.test.support.JsonAssertions.assertEqualJson; import org.junit.jupiter.api.Test; -import org.opentripplanner.apis.vectortiles.DebugStyleSpec.VectorSourceLayer; import org.opentripplanner.apis.vectortiles.model.TileSource.VectorSource; +import org.opentripplanner.apis.vectortiles.model.VectorSourceLayer; import org.opentripplanner.framework.json.ObjectMappers; import org.opentripplanner.test.support.ResourceLoader; class DebugStyleSpecTest { - private final ResourceLoader RES = ResourceLoader.of(this); + private final ResourceLoader RESOURCES = ResourceLoader.of(this); @Test void spec() { var vectorSource = new VectorSource("vectorSource", "https://example.com"); - var regularStops = new VectorSourceLayer(vectorSource, "regularStops"); - var spec = DebugStyleSpec.build(vectorSource, regularStops); + var stops = new VectorSourceLayer(vectorSource, "stops"); + var edges = new VectorSourceLayer(vectorSource, "edges"); + var vertices = new VectorSourceLayer(vectorSource, "vertices"); + var spec = DebugStyleSpec.build(stops, edges, vertices); var json = ObjectMappers.ignoringExtraFields().valueToTree(spec); - var expectation = RES.fileToString("style.json"); + var expectation = RESOURCES.fileToString("style.json"); assertEqualJson(expectation, json); } } diff --git a/src/test/java/org/opentripplanner/framework/FrameworkArchitectureTest.java b/src/test/java/org/opentripplanner/framework/FrameworkArchitectureTest.java index e1e00076a73..9e44dd05dda 100644 --- a/src/test/java/org/opentripplanner/framework/FrameworkArchitectureTest.java +++ b/src/test/java/org/opentripplanner/framework/FrameworkArchitectureTest.java @@ -97,7 +97,7 @@ void enforceTextPackageDependencies() { @Test void enforceTimePackageDependencies() { - TIME.verify(); + TIME.dependsOn(LOGGING).verify(); } @Test diff --git a/src/test/java/org/opentripplanner/framework/time/ZoneIdFallbackTest.java b/src/test/java/org/opentripplanner/framework/time/ZoneIdFallbackTest.java new file mode 100644 index 00000000000..b69e3ee2ff1 --- /dev/null +++ b/src/test/java/org/opentripplanner/framework/time/ZoneIdFallbackTest.java @@ -0,0 +1,19 @@ +package org.opentripplanner.framework.time; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.opentripplanner._support.time.ZoneIds; + +class ZoneIdFallbackTest { + + @Test + void fallback() { + assertEquals(ZoneIds.UTC, ZoneIdFallback.zoneId(null)); + } + + @Test + void keepOriginal() { + assertEquals(ZoneIds.BERLIN, ZoneIdFallback.zoneId(ZoneIds.BERLIN)); + } +} diff --git a/src/test/java/org/opentripplanner/inspector/vector/stop/AreaStopLayerBuilderTest.java b/src/test/java/org/opentripplanner/inspector/vector/stop/AreaStopLayerBuilderTest.java index 231f3ddce59..a1c01ca2e99 100644 --- a/src/test/java/org/opentripplanner/inspector/vector/stop/AreaStopLayerBuilderTest.java +++ b/src/test/java/org/opentripplanner/inspector/vector/stop/AreaStopLayerBuilderTest.java @@ -6,7 +6,6 @@ import org.junit.jupiter.api.Test; import org.opentripplanner._support.geometry.Polygons; import org.opentripplanner.framework.i18n.I18NString; -import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.inspector.vector.KeyValue; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.AreaStop; diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/K01_TransitPriorityTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/K01_TransitPriorityTest.java index c1db11be779..00b1d528e7e 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/K01_TransitPriorityTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/K01_TransitPriorityTest.java @@ -1,8 +1,6 @@ package org.opentripplanner.raptor.moduletests; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_B; import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_C; import static org.opentripplanner.raptor._data.RaptorTestConstants.T00_00; @@ -12,6 +10,9 @@ import static org.opentripplanner.raptor._data.transit.TestRoute.route; import static org.opentripplanner.raptor._data.transit.TestTripPattern.pattern; import static org.opentripplanner.raptor._data.transit.TestTripSchedule.schedule; +import static org.opentripplanner.raptor.moduletests.support.TestGroupPriorityCalculator.GROUP_A; +import static org.opentripplanner.raptor.moduletests.support.TestGroupPriorityCalculator.GROUP_B; +import static org.opentripplanner.raptor.moduletests.support.TestGroupPriorityCalculator.GROUP_C; import java.time.Duration; import org.junit.jupiter.api.BeforeEach; @@ -19,11 +20,11 @@ import org.opentripplanner.raptor.RaptorService; import org.opentripplanner.raptor._data.transit.TestTransitData; import org.opentripplanner.raptor._data.transit.TestTripSchedule; -import org.opentripplanner.raptor.api.model.DominanceFunction; import org.opentripplanner.raptor.api.request.RaptorProfile; import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; -import org.opentripplanner.raptor.api.request.RaptorTransitPriorityGroupCalculator; +import org.opentripplanner.raptor.api.request.RaptorTransitGroupCalculator; import org.opentripplanner.raptor.configure.RaptorConfig; +import org.opentripplanner.raptor.moduletests.support.TestGroupPriorityCalculator; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter; /** @@ -33,32 +34,8 @@ */ public class K01_TransitPriorityTest { - private static final RaptorTransitPriorityGroupCalculator PRIORITY_GROUP_CALCULATOR = new RaptorTransitPriorityGroupCalculator() { - @Override - public int mergeTransitPriorityGroupIds(int currentGroupIds, int boardingGroupId) { - return currentGroupIds | boardingGroupId; - } - - /** - * Left dominate right, if right has at least one priority group not in left. - */ - @Override - public DominanceFunction dominanceFunction() { - return (l, r) -> ((l ^ r) & r) != 0; - } - }; - - private static final int GROUP_A = 0x01; - private static final int GROUP_B = 0x02; - private static final int GROUP_C = 0x04; - private static final int GROUP_AB = PRIORITY_GROUP_CALCULATOR.mergeTransitPriorityGroupIds( - GROUP_A, - GROUP_B - ); - private static final int GROUP_AC = PRIORITY_GROUP_CALCULATOR.mergeTransitPriorityGroupIds( - GROUP_A, - GROUP_C - ); + private static final RaptorTransitGroupCalculator PRIORITY_GROUP_CALCULATOR = + TestGroupPriorityCalculator.PRIORITY_CALCULATOR; private static final int C1_SLACK_90s = RaptorCostConverter.toRaptorCost(90); private final TestTransitData data = new TestTransitData(); @@ -108,7 +85,6 @@ private void prepareRequest() { ); // Add 1 second access/egress paths requestBuilder.searchParams().addAccessPaths(walk(STOP_B, 1)).addEgressPaths(walk(STOP_C, 1)); - assetGroupCalculatorIsSetupCorrect(); } @Test @@ -122,21 +98,4 @@ public void transitPriority() { pathsToString(raptorService.route(requestBuilder.build(), data)) ); } - - /** - * Make sure the calculator and group setup is done correct. - */ - void assetGroupCalculatorIsSetupCorrect() { - var d = PRIORITY_GROUP_CALCULATOR.dominanceFunction(); - - assertTrue(d.leftDominateRight(GROUP_A, GROUP_B)); - assertTrue(d.leftDominateRight(GROUP_B, GROUP_A)); - assertFalse(d.leftDominateRight(GROUP_A, GROUP_A)); - // 3 = 1&2, 5 = 1&4 - assertTrue(d.leftDominateRight(GROUP_A, GROUP_AB)); - assertFalse(d.leftDominateRight(GROUP_AB, GROUP_A)); - assertFalse(d.leftDominateRight(GROUP_AB, GROUP_AB)); - assertTrue(d.leftDominateRight(GROUP_AB, GROUP_AC)); - assertTrue(d.leftDominateRight(GROUP_AC, GROUP_AB)); - } } diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/K02_TransitPriorityDestinationTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/K02_TransitPriorityDestinationTest.java index c7b64cb5b9e..c6ab8e337ea 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/K02_TransitPriorityDestinationTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/K02_TransitPriorityDestinationTest.java @@ -1,14 +1,11 @@ package org.opentripplanner.raptor.moduletests; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_B; import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_C; import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_D; import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_E; import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_F; -import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_G; import static org.opentripplanner.raptor._data.RaptorTestConstants.T00_00; import static org.opentripplanner.raptor._data.RaptorTestConstants.T01_00; import static org.opentripplanner.raptor._data.api.PathUtils.pathsToString; @@ -16,6 +13,9 @@ import static org.opentripplanner.raptor._data.transit.TestRoute.route; import static org.opentripplanner.raptor._data.transit.TestTripPattern.pattern; import static org.opentripplanner.raptor._data.transit.TestTripSchedule.schedule; +import static org.opentripplanner.raptor.moduletests.support.TestGroupPriorityCalculator.GROUP_A; +import static org.opentripplanner.raptor.moduletests.support.TestGroupPriorityCalculator.GROUP_B; +import static org.opentripplanner.raptor.moduletests.support.TestGroupPriorityCalculator.GROUP_C; import java.time.Duration; import org.junit.jupiter.api.BeforeEach; @@ -23,11 +23,11 @@ import org.opentripplanner.raptor.RaptorService; import org.opentripplanner.raptor._data.transit.TestTransitData; import org.opentripplanner.raptor._data.transit.TestTripSchedule; -import org.opentripplanner.raptor.api.model.DominanceFunction; import org.opentripplanner.raptor.api.request.RaptorProfile; import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; -import org.opentripplanner.raptor.api.request.RaptorTransitPriorityGroupCalculator; +import org.opentripplanner.raptor.api.request.RaptorTransitGroupCalculator; import org.opentripplanner.raptor.configure.RaptorConfig; +import org.opentripplanner.raptor.moduletests.support.TestGroupPriorityCalculator; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter; /** @@ -37,32 +37,8 @@ */ public class K02_TransitPriorityDestinationTest { - private static final RaptorTransitPriorityGroupCalculator PRIORITY_GROUP_CALCULATOR = new RaptorTransitPriorityGroupCalculator() { - @Override - public int mergeTransitPriorityGroupIds(int currentGroupIds, int boardingGroupId) { - return currentGroupIds | boardingGroupId; - } - - /** - * Left dominate right, if right has at least one priority group not in left. - */ - @Override - public DominanceFunction dominanceFunction() { - return (l, r) -> ((l ^ r) & r) != 0; - } - }; - - private static final int GROUP_A = 0x01; - private static final int GROUP_B = 0x02; - private static final int GROUP_C = 0x04; - private static final int GROUP_AB = PRIORITY_GROUP_CALCULATOR.mergeTransitPriorityGroupIds( - GROUP_A, - GROUP_B - ); - private static final int GROUP_AC = PRIORITY_GROUP_CALCULATOR.mergeTransitPriorityGroupIds( - GROUP_A, - GROUP_C - ); + private static final RaptorTransitGroupCalculator PRIORITY_GROUP_CALCULATOR = + TestGroupPriorityCalculator.PRIORITY_CALCULATOR; private static final int C1_SLACK_90s = RaptorCostConverter.toRaptorCost(90); private final TestTransitData data = new TestTransitData(); @@ -117,7 +93,6 @@ private void prepareRequest() { .addEgressPaths(walk(STOP_D, 1)) .addEgressPaths(walk(STOP_E, 1)) .addEgressPaths(walk(STOP_F, 1)); - assetGroupCalculatorIsSetupCorrect(); } @Test @@ -131,21 +106,4 @@ public void transitPriority() { pathsToString(raptorService.route(requestBuilder.build(), data)) ); } - - /** - * Make sure the calculator and group setup is done correct. - */ - void assetGroupCalculatorIsSetupCorrect() { - var d = PRIORITY_GROUP_CALCULATOR.dominanceFunction(); - - assertTrue(d.leftDominateRight(GROUP_A, GROUP_B)); - assertTrue(d.leftDominateRight(GROUP_B, GROUP_A)); - assertFalse(d.leftDominateRight(GROUP_A, GROUP_A)); - // 3 = 1&2, 5 = 1&4 - assertTrue(d.leftDominateRight(GROUP_A, GROUP_AB)); - assertFalse(d.leftDominateRight(GROUP_AB, GROUP_A)); - assertFalse(d.leftDominateRight(GROUP_AB, GROUP_AB)); - assertTrue(d.leftDominateRight(GROUP_AB, GROUP_AC)); - assertTrue(d.leftDominateRight(GROUP_AC, GROUP_AB)); - } } diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/support/TestGroupPriorityCalculator.java b/src/test/java/org/opentripplanner/raptor/moduletests/support/TestGroupPriorityCalculator.java new file mode 100644 index 00000000000..cdbe82f18a6 --- /dev/null +++ b/src/test/java/org/opentripplanner/raptor/moduletests/support/TestGroupPriorityCalculator.java @@ -0,0 +1,51 @@ +package org.opentripplanner.raptor.moduletests.support; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.raptor.api.model.DominanceFunction; +import org.opentripplanner.raptor.api.request.RaptorTransitGroupCalculator; + +public class TestGroupPriorityCalculator implements RaptorTransitGroupCalculator { + + public static final RaptorTransitGroupCalculator PRIORITY_CALCULATOR = new TestGroupPriorityCalculator(); + + public static final int GROUP_A = 0x01; + public static final int GROUP_B = 0x02; + public static final int GROUP_C = 0x04; + + private static final int GROUP_AB = PRIORITY_CALCULATOR.mergeGroupIds(GROUP_A, GROUP_B); + private static final int GROUP_AC = PRIORITY_CALCULATOR.mergeGroupIds(GROUP_A, GROUP_C); + + @Override + public int mergeGroupIds(int currentGroupIds, int boardingGroupId) { + return currentGroupIds | boardingGroupId; + } + + /** + * Left dominate right, if right has at least one priority group not in left. + */ + @Override + public DominanceFunction dominanceFunction() { + return (l, r) -> ((l ^ r) & r) != 0; + } + + /** + * Make sure the calculator and group setup is done correct. + */ + @Test + void assetGroupCalculatorIsSetupCorrect() { + var d = PRIORITY_CALCULATOR.dominanceFunction(); + + assertTrue(d.leftDominateRight(GROUP_A, GROUP_B)); + assertTrue(d.leftDominateRight(GROUP_B, GROUP_A)); + assertFalse(d.leftDominateRight(GROUP_A, GROUP_A)); + // 3 = 1&2, 5 = 1&4 + assertTrue(d.leftDominateRight(GROUP_A, GROUP_AB)); + assertFalse(d.leftDominateRight(GROUP_AB, GROUP_A)); + assertFalse(d.leftDominateRight(GROUP_AB, GROUP_AB)); + assertTrue(d.leftDominateRight(GROUP_AB, GROUP_AC)); + assertTrue(d.leftDominateRight(GROUP_AC, GROUP_AB)); + } +} diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessEgressFunctionsTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessEgressFunctionsTest.java index eab9ee418cf..1d4f90557b0 100644 --- a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessEgressFunctionsTest.java +++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessEgressFunctionsTest.java @@ -6,7 +6,8 @@ import static org.opentripplanner.raptor._data.transit.TestAccessEgress.flexAndWalk; import static org.opentripplanner.raptor.rangeraptor.transit.AccessEgressFunctions.groupByRound; import static org.opentripplanner.raptor.rangeraptor.transit.AccessEgressFunctions.groupByStop; -import static org.opentripplanner.raptor.rangeraptor.transit.AccessEgressFunctions.removeNoneOptimalPathsForStandardRaptor; +import static org.opentripplanner.raptor.rangeraptor.transit.AccessEgressFunctions.removeNonOptimalPathsForMcRaptor; +import static org.opentripplanner.raptor.rangeraptor.transit.AccessEgressFunctions.removeNonOptimalPathsForStandardRaptor; import java.util.Arrays; import java.util.Collection; @@ -24,78 +25,150 @@ class AccessEgressFunctionsTest implements RaptorTestConstants { public static final int TRANSFER_SLACK = D1m; private static final int STOP = 8; - - private static final RaptorAccessEgress WALK_10m = TestAccessEgress.walk(STOP, D10m); - private static final RaptorAccessEgress WALK_8m = TestAccessEgress.walk(STOP, D8m); - private static final RaptorAccessEgress FLEX_1x_10m = flex(STOP, D10m, 1); - private static final RaptorAccessEgress FLEX_1x_8m = flex(STOP, D8m, 1); - private static final RaptorAccessEgress FLEX_2x_8m = flex(STOP, D8m, 2); - private static final RaptorAccessEgress FLEX_AND_WALK_1x_8m = flexAndWalk(STOP, D8m, 1); + private static final int C1 = 1000; + private static final int C1_LOW = 999; + + private static final RaptorAccessEgress WALK_10m = TestAccessEgress.walk(STOP, D10m, C1); + private static final RaptorAccessEgress WALK_10m_C1_LOW = TestAccessEgress.walk( + STOP, + D10m, + C1_LOW + ); + private static final RaptorAccessEgress WALK_8m = TestAccessEgress.walk(STOP, D8m, C1); + private static final RaptorAccessEgress FLEX_1x_10m = flex(STOP, D10m, 1, C1); + private static final RaptorAccessEgress FLEX_1x_8m = flex(STOP, D8m, 1, C1); + private static final RaptorAccessEgress FLEX_2x_8m = flex(STOP, D8m, 2, C1); + private static final RaptorAccessEgress FLEX_AND_WALK_1x_8m = flexAndWalk(STOP, D8m, 1, C1); private static final RaptorAccessEgress WALK_W_OPENING_HOURS_8m = TestAccessEgress - .walk(STOP, D8m) + .walk(STOP, D8m, C1) .openingHours(T00_00, T01_00); private static final RaptorAccessEgress WALK_W_OPENING_HOURS_8m_OTHER = TestAccessEgress - .walk(STOP, D8m) + .walk(STOP, D8m, C1) .openingHours(T00_10, T01_00); @Test - void removeNoneOptimalPathsForStandardRaptorTest() { + void removeNonOptimalPathsForStandardRaptorTest() { // Empty set - assertElements(List.of(), removeNoneOptimalPathsForStandardRaptor(List.of())); + assertElements(List.of(), removeNonOptimalPathsForStandardRaptor(List.of())); // One element - assertElements(List.of(WALK_8m), removeNoneOptimalPathsForStandardRaptor(List.of(WALK_8m))); + assertElements(List.of(WALK_8m), removeNonOptimalPathsForStandardRaptor(List.of(WALK_8m))); // Shortest duration assertElements( List.of(WALK_8m), - removeNoneOptimalPathsForStandardRaptor(List.of(WALK_8m, WALK_10m)) + removeNonOptimalPathsForStandardRaptor(List.of(WALK_8m, WALK_10m)) + ); + + // Fewest rides + assertElements( + List.of(FLEX_1x_8m), + removeNonOptimalPathsForStandardRaptor(List.of(FLEX_1x_8m, FLEX_2x_8m)) + ); + + // Arriving at the stop on-board, and by-foot. + // OnBoard is better because we can do a transfer walk to nearby stops. + assertElements( + List.of(FLEX_1x_8m), + removeNonOptimalPathsForStandardRaptor(List.of(FLEX_AND_WALK_1x_8m, FLEX_1x_8m)) + ); + + // Flex+walk is faster, flex arrive on-board, both is optimal + assertElements( + List.of(FLEX_AND_WALK_1x_8m, FLEX_1x_10m), + removeNonOptimalPathsForStandardRaptor(List.of(FLEX_AND_WALK_1x_8m, FLEX_1x_10m)) + ); + + // Walk has few rides, and Flex is faster - both is optimal + assertElements( + List.of(WALK_10m, FLEX_1x_8m), + removeNonOptimalPathsForStandardRaptor(List.of(WALK_10m, FLEX_1x_8m)) + ); + + // Walk without opening hours is better than with, because it can be time-shifted without + // any constraints + assertElements( + List.of(WALK_8m), + removeNonOptimalPathsForStandardRaptor(List.of(WALK_8m, WALK_W_OPENING_HOURS_8m)) + ); + + // Walk with opening hours can NOT dominate another access/egress without - even if it is + // faster. The reason is that it may not be allowed to time-shift it to the desired time. + assertElements( + List.of(WALK_10m, WALK_W_OPENING_HOURS_8m), + removeNonOptimalPathsForStandardRaptor(List.of(WALK_10m, WALK_W_OPENING_HOURS_8m)) + ); + + // If two paths both have opening hours, both should be accepted. + assertElements( + List.of(WALK_W_OPENING_HOURS_8m, WALK_W_OPENING_HOURS_8m_OTHER), + removeNonOptimalPathsForStandardRaptor( + List.of(WALK_W_OPENING_HOURS_8m, WALK_W_OPENING_HOURS_8m_OTHER) + ) ); + } + + @Test + void removeNonOptimalPathsForMcRaptorTest() { + // Empty set + assertElements(List.of(), removeNonOptimalPathsForMcRaptor(List.of())); + + // One element + assertElements(List.of(WALK_8m), removeNonOptimalPathsForMcRaptor(List.of(WALK_8m))); + + // Lowest cost + assertElements( + List.of(WALK_10m_C1_LOW), + removeNonOptimalPathsForMcRaptor(List.of(WALK_10m, WALK_10m_C1_LOW)) + ); + + // Shortest duration + assertElements(List.of(WALK_8m), removeNonOptimalPathsForMcRaptor(List.of(WALK_8m, WALK_10m))); // Fewest rides assertElements( List.of(FLEX_1x_8m), - removeNoneOptimalPathsForStandardRaptor(List.of(FLEX_1x_8m, FLEX_2x_8m)) + removeNonOptimalPathsForMcRaptor(List.of(FLEX_1x_8m, FLEX_2x_8m)) ); // Arriving at the stop on-board, and by-foot. // OnBoard is better because we can do a transfer walk to nearby stops. assertElements( List.of(FLEX_1x_8m), - removeNoneOptimalPathsForStandardRaptor(List.of(FLEX_AND_WALK_1x_8m, FLEX_1x_8m)) + removeNonOptimalPathsForMcRaptor(List.of(FLEX_AND_WALK_1x_8m, FLEX_1x_8m)) ); // Flex+walk is faster, flex arrive on-board, both is optimal assertElements( List.of(FLEX_AND_WALK_1x_8m, FLEX_1x_10m), - removeNoneOptimalPathsForStandardRaptor(List.of(FLEX_AND_WALK_1x_8m, FLEX_1x_10m)) + removeNonOptimalPathsForStandardRaptor(List.of(FLEX_AND_WALK_1x_8m, FLEX_1x_10m)) ); // Walk has few rides, and Flex is faster - both is optimal assertElements( List.of(WALK_10m, FLEX_1x_8m), - removeNoneOptimalPathsForStandardRaptor(List.of(WALK_10m, FLEX_1x_8m)) + removeNonOptimalPathsForMcRaptor(List.of(WALK_10m, FLEX_1x_8m)) ); // Walk without opening hours is better than with, because it can be time-shifted without // any constraints assertElements( List.of(WALK_8m), - removeNoneOptimalPathsForStandardRaptor(List.of(WALK_8m, WALK_W_OPENING_HOURS_8m)) + removeNonOptimalPathsForMcRaptor(List.of(WALK_8m, WALK_W_OPENING_HOURS_8m)) ); // Walk with opening hours can NOT dominate another access/egress without - even if it is // faster. The reason is that it may not be allowed to time-shift it to the desired time. assertElements( List.of(WALK_10m, WALK_W_OPENING_HOURS_8m), - removeNoneOptimalPathsForStandardRaptor(List.of(WALK_10m, WALK_W_OPENING_HOURS_8m)) + removeNonOptimalPathsForMcRaptor(List.of(WALK_10m, WALK_W_OPENING_HOURS_8m)) ); // If two paths both have opening hours, both should be accepted. assertElements( List.of(WALK_W_OPENING_HOURS_8m, WALK_W_OPENING_HOURS_8m_OTHER), - removeNoneOptimalPathsForStandardRaptor( + removeNonOptimalPathsForMcRaptor( List.of(WALK_W_OPENING_HOURS_8m, WALK_W_OPENING_HOURS_8m_OTHER) ) ); diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPathsTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPathsTest.java index 6d3b25c1d31..144904fa3e6 100644 --- a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPathsTest.java +++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPathsTest.java @@ -38,7 +38,7 @@ class EgressPathsTest { // Number of rides, smallest is better flex(STOP_D, D1m, 2), flex(STOP_D, D1m, 3), - // Opening Hours dominate each other(no check on overlapping) + // Opening Hours dominate each other (no check on overlapping) walk(STOP_E, D2m), walk(STOP_E, D1m).openingHours("10:00", "11:45"), walk(STOP_E, D1m).openingHours("11:30", "12:30"), @@ -83,18 +83,15 @@ void listAll() { """.strip(), subjectStd.listAll().stream().map(Object::toString).sorted().collect(Collectors.joining("\n")) ); + assertEquals( """ Flex 1m C₁120 1x ~ 3 Flex 1m C₁120 1x ~ 4 Flex 1m C₁120 2x ~ 5 - Flex 1m C₁120 3x ~ 5 - Flex 2m C₁240 1x ~ 3 - Flex+Walk 1m C₁120 1x ~ 4 Walk 1m C₁120 Open(10:00 11:45) ~ 6 Walk 1m C₁120 Open(11:30 12:30) ~ 6 Walk 1m C₁120 ~ 2 - Walk 2m C₁240 Open(14:00 14:00) ~ 6 Walk 2m C₁240 ~ 6 """.strip(), subjectMc.listAll().stream().map(Object::toString).sorted().collect(Collectors.joining("\n")) @@ -107,8 +104,10 @@ void walkToDestinationEgressStops() { toString(new int[] { STOP_A, STOP_E }), toString(subjectStd.egressesWitchStartByWalking()) ); + + //[2, 6] assertEquals( - toString(new int[] { STOP_A, STOP_C, STOP_E }), + toString(new int[] { STOP_A, STOP_E }), toString(subjectMc.egressesWitchStartByWalking()) ); } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java index 1ce1aec36bd..d897c948e6d 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java @@ -20,9 +20,9 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import org.opentripplanner.ext.emissions.DecorateWithEmission; import org.opentripplanner.ext.emissions.DefaultEmissionsService; import org.opentripplanner.ext.emissions.EmissionsDataModel; -import org.opentripplanner.ext.emissions.EmissionsFilter; import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.model.SystemNotice; import org.opentripplanner.model.plan.Itinerary; @@ -30,6 +30,7 @@ import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.model.plan.TestItineraryBuilder; import org.opentripplanner.routing.alertpatch.StopCondition; +import org.opentripplanner.routing.algorithm.filterchain.api.GroupBySimilarity; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.routing.api.response.RoutingError; import org.opentripplanner.routing.api.response.RoutingErrorCode; @@ -364,7 +365,9 @@ public void setUpItineraries() { @Test public void emissionsTest() { - ItineraryListFilterChain chain = builder.withEmissions(new EmissionsFilter(eService)).build(); + ItineraryListFilterChain chain = builder + .withEmissions(new DecorateWithEmission(eService)) + .build(); List itineraries = chain.filter(List.of(bus, car)); assertFalse(itineraries.stream().anyMatch(i -> i.getEmissionsPerPerson().getCo2() == null)); } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveBikeRentalWithMostlyWalkingTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveBikeRentalWithMostlyWalkingTest.java similarity index 89% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveBikeRentalWithMostlyWalkingTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveBikeRentalWithMostlyWalkingTest.java index 5fe2bce098b..4e47306601c 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveBikeRentalWithMostlyWalkingTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveBikeRentalWithMostlyWalkingTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.street; import static org.opentripplanner.model.plan.TestItineraryBuilder.A; import static org.opentripplanner.model.plan.TestItineraryBuilder.B; @@ -20,7 +20,7 @@ public class RemoveBikeRentalWithMostlyWalkingTest { private static final int T10_10 = TimeUtils.hm2time(10, 10); private static final int T10_20 = TimeUtils.hm2time(10, 20); - private final RemoveBikerentalWithMostlyWalkingFilter subject = new RemoveBikerentalWithMostlyWalkingFilter( + private final RemoveBikeRentalWithMostlyWalking subject = new RemoveBikeRentalWithMostlyWalking( 0.3 ); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveParkAndRideWithMostlyWalkingTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveParkAndRideWithMostlyWalkingTest.java similarity index 95% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveParkAndRideWithMostlyWalkingTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveParkAndRideWithMostlyWalkingTest.java index 2c65f882473..a4c90d1346f 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveParkAndRideWithMostlyWalkingTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveParkAndRideWithMostlyWalkingTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.street; import static org.opentripplanner.model.plan.TestItineraryBuilder.A; import static org.opentripplanner.model.plan.TestItineraryBuilder.B; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveWalkOnlyFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveWalkOnlyFilterTest.java similarity index 95% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveWalkOnlyFilterTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveWalkOnlyFilterTest.java index 62029ca389f..359423a842b 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveWalkOnlyFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/street/RemoveWalkOnlyFilterTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.street; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.model.plan.TestItineraryBuilder.A; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/NumItinerariesFilterTest.java similarity index 93% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilterTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/NumItinerariesFilterTest.java index 879edd784b0..622482fe4b3 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/NumItinerariesFilterTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.system; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.model.plan.Itinerary.toStr; @@ -10,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.framework.collection.ListSection; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.paging.cursor.PageCursorInput; public class NumItinerariesFilterTest { @@ -17,7 +18,7 @@ public class NumItinerariesFilterTest { private static final Itinerary i2 = newItinerary(A).bicycle(6, 8, B).build(); private static final Itinerary i3 = newItinerary(A).bus(21, 7, 9, B).build(); - private NumItinerariesFilterResults subscribeResult = null; + private PageCursorInput subscribeResult = null; @Test public void name() { diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OutsideSearchWindowFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilterTest.java similarity index 97% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OutsideSearchWindowFilterTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilterTest.java index 207210092b7..9752004560e 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OutsideSearchWindowFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilterTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.system; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/PagingFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/PagingFilterTest.java similarity index 98% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/PagingFilterTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/PagingFilterTest.java index 965889bdefc..751dcc7ad3c 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/PagingFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/PagingFilterTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.system; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.framework.collection.ListUtils.first; @@ -19,7 +19,7 @@ import org.opentripplanner.model.plan.Place; import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.model.plan.SortOrder; -import org.opentripplanner.routing.algorithm.filterchain.comparator.SortOrderComparator; +import org.opentripplanner.routing.algorithm.filterchain.framework.sort.SortOrderComparator; import org.opentripplanner.transit.model._data.TransitModelForTest; public class PagingFilterTest implements PlanTestConstants { diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveItinerariesWithShortStreetLegTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/RemoveItinerariesWithShortStreetLegTest.java similarity index 65% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveItinerariesWithShortStreetLegTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/RemoveItinerariesWithShortStreetLegTest.java index 9b6df6bb1aa..92a11485055 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveItinerariesWithShortStreetLegTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/RemoveItinerariesWithShortStreetLegTest.java @@ -1,16 +1,17 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.transit; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; import static org.opentripplanner.street.search.TraverseMode.BICYCLE; -import java.util.List; import org.junit.jupiter.api.Test; import org.opentripplanner.model.plan.PlanTestConstants; class RemoveItinerariesWithShortStreetLegTest implements PlanTestConstants { - RemoveItinerariesWithShortStreetLeg filter = new RemoveItinerariesWithShortStreetLeg( + private final RemoveItinerariesWithShortStreetLeg subject = new RemoveItinerariesWithShortStreetLeg( 500, BICYCLE ); @@ -19,16 +20,15 @@ class RemoveItinerariesWithShortStreetLegTest implements PlanTestConstants { void noBikeDoesNothing() { var regularTransit = newItinerary(A).bus(30, T11_16, T11_20, C).build(); - var result = filter.filter(List.of(regularTransit, regularTransit, regularTransit)); - assertEquals(List.of(regularTransit, regularTransit, regularTransit), result); + assertFalse(subject.shouldBeFlaggedForRemoval().test(regularTransit), regularTransit.toStr()); } @Test void justBikeDoesNothing() { var itin = newItinerary(A).bicycle(T11_05, T11_06, B).build(); assertEquals(300, itin.getLegs().get(0).getDistanceMeters()); - var result = filter.filter(List.of(itin)); - assertEquals(List.of(itin), result); + + assertFalse(subject.shouldBeFlaggedForRemoval().test(itin), itin.toStr()); } @Test @@ -36,23 +36,23 @@ void zeroMinDoesNothing() { var filter = new RemoveItinerariesWithShortStreetLeg(0, BICYCLE); var itin = newItinerary(A).bicycle(T11_05, T11_06, B).rail(30, T11_16, T11_20, C).build(); assertEquals(300, itin.getLegs().get(0).getDistanceMeters()); - var result = filter.filter(List.of(itin)); - assertEquals(List.of(itin), result); + + assertFalse(filter.shouldBeFlaggedForRemoval().test(itin), itin.toStr()); } @Test void shortBike() { var itin = newItinerary(A).bicycle(T11_05, T11_06, B).rail(30, T11_16, T11_20, C).build(); assertEquals(300, itin.getLegs().get(0).getDistanceMeters()); - var result = filter.filter(List.of(itin, itin, itin)); - assertEquals(List.of(), result); + + assertTrue(subject.shouldBeFlaggedForRemoval().test(itin), itin.toStr()); } @Test void longBike() { var itin = newItinerary(A).bicycle(T11_05, T11_30, B).rail(30, T11_33, T11_50, C).build(); assertEquals(7500, itin.getLegs().get(0).getDistanceMeters()); - var result = filter.filter(List.of(itin, itin)); - assertEquals(List.of(itin, itin), result); + + assertFalse(subject.shouldBeFlaggedForRemoval().test(itin), itin.toStr()); } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/RemoveTransitIfStreetOnlyIsBetterTest.java similarity index 82% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilterTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/RemoveTransitIfStreetOnlyIsBetterTest.java index 9cbba961e71..df45bcf4538 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/RemoveTransitIfStreetOnlyIsBetterTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.transit; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.model.plan.Itinerary.toStr; @@ -9,9 +9,10 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.PlanTestConstants; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; -public class RemoveTransitIfStreetOnlyIsBetterFilterTest implements PlanTestConstants { +public class RemoveTransitIfStreetOnlyIsBetterTest implements PlanTestConstants { @Test public void filterAwayNothingIfNoWalking() { @@ -20,7 +21,7 @@ public void filterAwayNothingIfNoWalking() { Itinerary i2 = newItinerary(A).rail(110, 6, 9, E).build(); // When: - ItineraryDeletionFlagger flagger = new RemoveTransitIfStreetOnlyIsBetterFilter( + RemoveItineraryFlagger flagger = new RemoveTransitIfStreetOnlyIsBetter( CostLinearFunction.of(Duration.ofSeconds(200), 1.2) ); List result = flagger.removeMatchesForTest(List.of(i1, i2)); @@ -48,7 +49,7 @@ public void filterAwayLongTravelTimeWithoutWaitTime() { i2.setGeneralizedCost(360); // When: - ItineraryDeletionFlagger flagger = new RemoveTransitIfStreetOnlyIsBetterFilter( + RemoveItineraryFlagger flagger = new RemoveTransitIfStreetOnlyIsBetter( CostLinearFunction.of(Duration.ofSeconds(60), 1.2) ); List result = flagger.removeMatchesForTest(List.of(i2, bicycle, walk, i1)); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/RemoveTransitIfWalkingIsBetterTest.java similarity index 87% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/RemoveTransitIfWalkingIsBetterTest.java index 9b008993ba3..55efcf68856 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/RemoveTransitIfWalkingIsBetterTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.transit; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.model.plan.Itinerary.toStr; @@ -18,7 +18,7 @@ public void filterAwayNothingIfNoWalking() { Itinerary i2 = newItinerary(A).rail(110, 6, 9, E).build(); // When: - List result = new RemoveTransitIfWalkingIsBetterFilter() + List result = new RemoveTransitIfWalkingIsBetter() .removeMatchesForTest(List.of(i1, i2)); // Then: @@ -39,7 +39,7 @@ public void filterAwayTransitWithLongerWalk() { // transit which has less walking than plain walk should be kept Itinerary i2 = newItinerary(A, 6).walk(D1m, B).bus(2, 7, 10, E).build(); - List result = new RemoveTransitIfWalkingIsBetterFilter() + List result = new RemoveTransitIfWalkingIsBetter() .removeMatchesForTest(List.of(i1, i2, bicycle, walk)); assertEquals(toStr(List.of(i2, bicycle, walk)), toStr(result)); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filter/TransitAlertFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/TransitAlertFilterTest.java similarity index 66% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/filter/TransitAlertFilterTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/TransitAlertFilterTest.java index aae59e8e4b6..379eb207155 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filter/TransitAlertFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/TransitAlertFilterTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.filter; +package org.opentripplanner.routing.algorithm.filterchain.filters.transit; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.model.plan.TestItineraryBuilder.BUS_ROUTE; @@ -6,7 +6,6 @@ import java.util.List; import org.junit.jupiter.api.Test; -import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.routing.alertpatch.EntitySelector; import org.opentripplanner.routing.alertpatch.TimePeriod; @@ -32,23 +31,19 @@ void testFilter() { ) ); - TransitAlertFilter filter = new TransitAlertFilter(transitAlertService, ignore -> null); - - // Expect filter to no fail on an empty list - assertEquals(List.of(), filter.filter(List.of())); + var decorator = new DecorateTransitAlert(transitAlertService, ignore -> null); // Given a list with one itinerary - List list = List.of( - newItinerary(A).bus(31, 0, 30, E).build(), - newItinerary(B).rail(21, 0, 30, E).build() - ); - - list = filter.filter(list); + var i1 = newItinerary(A).bus(31, 0, 30, E).build(); + decorator.decorate(i1); // Then: expect correct alerts to be added - Itinerary first = list.get(0); - assertEquals(1, first.getLegs().get(0).getTransitAlerts().size()); - assertEquals(ID, first.getLegs().get(0).getTransitAlerts().iterator().next().getId()); - assertEquals(0, list.get(1).getLegs().get(0).getTransitAlerts().size()); + assertEquals(1, i1.getLegs().get(0).getTransitAlerts().size()); + assertEquals(ID, i1.getLegs().get(0).getTransitAlerts().iterator().next().getId()); + + var i2 = newItinerary(B).rail(21, 0, 30, E).build(); + decorator.decorate(i2); + + assertEquals(0, i2.getLegs().get(0).getTransitAlerts().size()); } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/TransitGeneralizedCostFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/TransitGeneralizedCostFilterTest.java similarity index 98% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/TransitGeneralizedCostFilterTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/TransitGeneralizedCostFilterTest.java index 37faa5f2feb..6f5f01a9728 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/TransitGeneralizedCostFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/TransitGeneralizedCostFilterTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.transit; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.model.plan.Itinerary.toStr; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filter/SameFirstOrLastTripFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/group/RemoveIfFirstOrLastTripIsTheSameTest.java similarity index 81% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/filter/SameFirstOrLastTripFilterTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/group/RemoveIfFirstOrLastTripIsTheSameTest.java index 3161d23a065..0e125207935 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filter/SameFirstOrLastTripFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/group/RemoveIfFirstOrLastTripIsTheSameTest.java @@ -1,7 +1,7 @@ -package org.opentripplanner.routing.algorithm.filterchain.filter; +package org.opentripplanner.routing.algorithm.filterchain.filters.transit.group; +import static org.ejml.UtilEjml.assertTrue; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; import java.util.List; @@ -9,10 +9,11 @@ import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.PlanTestConstants; -public class SameFirstOrLastTripFilterTest implements PlanTestConstants { +public class RemoveIfFirstOrLastTripIsTheSameTest implements PlanTestConstants { /** - * This test ensures that filter work as intended regarding comparison order and exclusion logic + * This test ensures that the filter works as intended regarding comparison order and exclusion + * logic. */ @Test public void testMatchOrderOnFirstStation() { @@ -34,7 +35,7 @@ public void testMatchOrderOnFirstStation() { List input = List.of(i1, i2, i3, i4, i5); - final SameFirstOrLastTripFilter filter = new SameFirstOrLastTripFilter(); + final RemoveIfFirstOrLastTripIsTheSame filter = new RemoveIfFirstOrLastTripIsTheSame(); var flagged = filter.flagForRemoval(input); // First journey should always be included diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OtherThanSameLegsMaxGeneralizedCostFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/group/RemoveOtherThanSameLegsMaxGeneralizedCostTest.java similarity index 76% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OtherThanSameLegsMaxGeneralizedCostFilterTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/group/RemoveOtherThanSameLegsMaxGeneralizedCostTest.java index e955b0ae04e..c5017c55ae3 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OtherThanSameLegsMaxGeneralizedCostFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/group/RemoveOtherThanSameLegsMaxGeneralizedCostTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.filters.transit.group; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; @@ -8,7 +8,7 @@ import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.PlanTestConstants; -class OtherThanSameLegsMaxGeneralizedCostFilterTest implements PlanTestConstants { +class RemoveOtherThanSameLegsMaxGeneralizedCostTest implements PlanTestConstants { @Test public void testFilter() { @@ -19,7 +19,7 @@ public void testFilter() { Itinerary second = newItinerary(A).rail(20, T11_05, T11_14, B).walk(D10m, C).build(); - var subject = new OtherThanSameLegsMaxGeneralizedCostFilter(2.0); + var subject = new RemoveOtherThanSameLegsMaxGeneralizedCost(2.0); assertEquals(List.of(second), subject.flagForRemoval(List.of(first, second))); } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/DecorateFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/DecorateFilterTest.java new file mode 100644 index 00000000000..7439acd6d12 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/DecorateFilterTest.java @@ -0,0 +1,60 @@ +package org.opentripplanner.routing.algorithm.filterchain.framework.filter; + +import static org.junit.jupiter.api.Assertions.*; +import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; + +import java.util.Iterator; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.PlanTestConstants; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryDecorator; + +class DecorateFilterTest implements ItineraryDecorator, PlanTestConstants { + + private static final Itinerary i1 = newItinerary(A, 6).walk(1, B).build(); + private static final Itinerary i2 = newItinerary(A).bicycle(6, 8, B).build(); + + private Iterator expectedQueue; + + @Override + public void decorate(Itinerary itinerary) { + if (expectedQueue == null) { + fail("The expected queue is null, this method should not be called."); + } + + if (!expectedQueue.hasNext()) { + fail("No itineraries in list of expected, but filter is still processing: " + itinerary); + } + + var current = expectedQueue.next(); + assertEquals(current, itinerary); + } + + @Test + void filterEmptyList() { + // Make sure the filter does nothing and does not crash on empty lists + new DecorateFilter(this).filter(List.of()); + } + + @Test + void filterOneElement() { + var input = List.of(i1); + expectedQueue = input.iterator(); + new DecorateFilter(this).filter(input); + assertTrue(!expectedQueue.hasNext(), "All elements are processed"); + } + + @Test + void filterTwoElements() { + var input = List.of(i1, i2); + expectedQueue = input.iterator(); + new DecorateFilter(this).filter(input); + assertTrue(!expectedQueue.hasNext(), "All elements are processed"); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filter/GroupByFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/GroupByFilterTest.java similarity index 89% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/filter/GroupByFilterTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/GroupByFilterTest.java index 447e9f0ab8e..adecedce669 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filter/GroupByFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/GroupByFilterTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.filter; +package org.opentripplanner.routing.algorithm.filterchain.framework.filter; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -9,9 +9,8 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.PlanTestConstants; -import org.opentripplanner.routing.algorithm.filterchain.comparator.SortOrderComparator; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.MaxLimitFilter; -import org.opentripplanner.routing.algorithm.filterchain.groupids.GroupId; +import org.opentripplanner.routing.algorithm.filterchain.framework.sort.SortOrderComparator; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.GroupId; public class GroupByFilterTest implements PlanTestConstants { @@ -38,7 +37,7 @@ public void aSimpleTestGroupByMatchingTripIdsNoMerge() { assertFalse(i2a.isFlaggedForDeletion()); assertTrue(i2b.isFlaggedForDeletion()); - // Remove an none existing set of tags + // Remove a none existing set of tags i2b.removeDeletionFlags(Set.of("ANY_TAG")); assertTrue(i2b.isFlaggedForDeletion()); @@ -98,9 +97,7 @@ private GroupByFilter createFilter(int maxNumberOfItinerariesPrGroup) i -> new AGroupId(i.firstLeg().getTrip().getId().getId()), List.of( new SortingFilter(SortOrderComparator.defaultComparatorDepartAfter()), - new DeletionFlaggingFilter( - new MaxLimitFilter(TEST_FILTER_TAG, maxNumberOfItinerariesPrGroup) - ) + new RemoveFilter(new MaxLimit(TEST_FILTER_TAG, maxNumberOfItinerariesPrGroup)) ) ); } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/MaxLimitFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/MaxLimitTest.java similarity index 77% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/MaxLimitFilterTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/MaxLimitTest.java index b4418de6c68..25d9188524d 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/MaxLimitFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/MaxLimitTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +package org.opentripplanner.routing.algorithm.filterchain.framework.filter; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.model.plan.Itinerary.toStr; @@ -9,7 +9,7 @@ import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.PlanTestConstants; -public class MaxLimitFilterTest implements PlanTestConstants { +public class MaxLimitTest implements PlanTestConstants { private static final Itinerary i1 = newItinerary(A, 6).walk(1, B).build(); private static final Itinerary i2 = newItinerary(A).bicycle(6, 8, B).build(); @@ -17,27 +17,27 @@ public class MaxLimitFilterTest implements PlanTestConstants { @Test public void name() { - MaxLimitFilter subject = new MaxLimitFilter("Test", 3); + MaxLimit subject = new MaxLimit("Test", 3); assertEquals("Test", subject.name()); } @Test public void testNormalFilterMaxLimit3() { - MaxLimitFilter subject = new MaxLimitFilter("Test", 3); + MaxLimit subject = new MaxLimit("Test", 3); List itineraries = List.of(i1, i2, i3); assertEquals(toStr(itineraries), toStr(subject.removeMatchesForTest(itineraries))); } @Test public void testNormalFilterMaxLimit1() { - MaxLimitFilter subject = new MaxLimitFilter("Test", 1); + MaxLimit subject = new MaxLimit("Test", 1); List itineraries = List.of(i1, i2, i3); assertEquals(toStr(List.of(i1)), toStr(subject.removeMatchesForTest(itineraries))); } @Test public void testNormalFilterMaxLimit0() { - MaxLimitFilter subject = new MaxLimitFilter("Test", 0); + MaxLimit subject = new MaxLimit("Test", 0); List itineraries = List.of(i1, i2, i3); var result = subject.removeMatchesForTest(itineraries); assertEquals(toStr(List.of()), toStr(result)); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filter/SortingFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/SortingFilterTest.java similarity index 87% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/filter/SortingFilterTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/SortingFilterTest.java index 1d5f12aba98..9e03b5a2ace 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filter/SortingFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/SortingFilterTest.java @@ -1,9 +1,9 @@ -package org.opentripplanner.routing.algorithm.filterchain.filter; +package org.opentripplanner.routing.algorithm.filterchain.framework.filter; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.model.plan.Itinerary.toStr; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.routing.algorithm.filterchain.comparator.SortOrderComparator.generalizedCostComparator; +import static org.opentripplanner.routing.algorithm.filterchain.framework.sort.SortOrderComparator.generalizedCostComparator; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/DeleteResultHandlerTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/filterchain/DeleteResultHandlerTest.java similarity index 95% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/DeleteResultHandlerTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/filterchain/DeleteResultHandlerTest.java index beca0632114..b7d91f734d2 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/DeleteResultHandlerTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/filterchain/DeleteResultHandlerTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain; +package org.opentripplanner.routing.algorithm.filterchain.framework.filterchain; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.model.plan.PlanTestConstants.A; @@ -21,8 +21,8 @@ import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.model.SystemNotice; import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NumItinerariesFilter; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.OutsideSearchWindowFilter; +import org.opentripplanner.routing.algorithm.filterchain.filters.system.NumItinerariesFilter; +import org.opentripplanner.routing.algorithm.filterchain.filters.system.OutsideSearchWindowFilter; class DeleteResultHandlerTest { diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/RoutingErrorsAttacherTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/filterchain/RoutingErrorsAttacherTest.java similarity index 86% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/RoutingErrorsAttacherTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/filterchain/RoutingErrorsAttacherTest.java index 238604a66ad..98ea33cea88 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/RoutingErrorsAttacherTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/filterchain/RoutingErrorsAttacherTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain; +package org.opentripplanner.routing.algorithm.filterchain.framework.filterchain; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; @@ -8,7 +8,7 @@ import org.opentripplanner.model.SystemNotice; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.PlanTestConstants; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveTransitIfStreetOnlyIsBetterFilter; +import org.opentripplanner.routing.algorithm.filterchain.filters.transit.RemoveTransitIfStreetOnlyIsBetter; import org.opentripplanner.routing.api.response.RoutingErrorCode; class RoutingErrorsAttacherTest implements PlanTestConstants { @@ -38,7 +38,7 @@ public static List flagAll(List itineraries) { itineraries.forEach(i -> i.flagForDeletion( new SystemNotice( - RemoveTransitIfStreetOnlyIsBetterFilter.TAG, + RemoveTransitIfStreetOnlyIsBetter.TAG, "This itinerary is marked as deleted." ) ) diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupByAllSameStationsTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupByAllSameStationsTest.java similarity index 97% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupByAllSameStationsTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupByAllSameStationsTest.java index 280fe43f3c8..692f3a61ca7 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupByAllSameStationsTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupByAllSameStationsTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.groupids; +package org.opentripplanner.routing.algorithm.filterchain.framework.groupids; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupByDistanceTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupByDistanceTest.java similarity index 97% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupByDistanceTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupByDistanceTest.java index 76d3b34fb5d..cc17305af6d 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupByDistanceTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupByDistanceTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.groupids; +package org.opentripplanner.routing.algorithm.filterchain.framework.groupids; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -6,8 +6,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.routing.algorithm.filterchain.groupids.GroupByDistance.calculateTotalDistance; -import static org.opentripplanner.routing.algorithm.filterchain.groupids.GroupByDistance.createKeySetOfLegsByLimit; +import static org.opentripplanner.routing.algorithm.filterchain.framework.groupids.GroupByDistance.calculateTotalDistance; +import static org.opentripplanner.routing.algorithm.filterchain.framework.groupids.GroupByDistance.createKeySetOfLegsByLimit; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupBySameFirstOrLastTripTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupBySameFirstOrLastTripTest.java similarity index 98% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupBySameFirstOrLastTripTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupBySameFirstOrLastTripTest.java index 6e19fff5bbc..436c1568852 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupBySameFirstOrLastTripTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupBySameFirstOrLastTripTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.groupids; +package org.opentripplanner.routing.algorithm.filterchain.framework.groupids; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupBySameRoutesAndStopsTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupBySameRoutesAndStopsTest.java similarity index 97% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupBySameRoutesAndStopsTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupBySameRoutesAndStopsTest.java index ea9b9c5d38b..26d3ca1d04b 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/groupids/GroupBySameRoutesAndStopsTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupBySameRoutesAndStopsTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain.groupids; +package org.opentripplanner.routing.algorithm.filterchain.framework.groupids; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertSame; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOnGeneralizedCostTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/sort/SortOnGeneralizedCostTest.java similarity index 94% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOnGeneralizedCostTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/sort/SortOnGeneralizedCostTest.java index ac5ec5128c4..c0e53de3381 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOnGeneralizedCostTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/sort/SortOnGeneralizedCostTest.java @@ -1,9 +1,9 @@ -package org.opentripplanner.routing.algorithm.filterchain.comparator; +package org.opentripplanner.routing.algorithm.filterchain.framework.sort; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.model.plan.Itinerary.toStr; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.routing.algorithm.filterchain.comparator.SortOrderComparator.generalizedCostComparator; +import static org.opentripplanner.routing.algorithm.filterchain.framework.sort.SortOrderComparator.generalizedCostComparator; import java.util.List; import java.util.stream.Collectors; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOnNumberOfTransfersTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/sort/SortOnNumberOfTransfersTest.java similarity index 90% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOnNumberOfTransfersTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/sort/SortOnNumberOfTransfersTest.java index 8a91c918c4b..40b7550b1a0 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOnNumberOfTransfersTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/sort/SortOnNumberOfTransfersTest.java @@ -1,9 +1,9 @@ -package org.opentripplanner.routing.algorithm.filterchain.comparator; +package org.opentripplanner.routing.algorithm.filterchain.framework.sort; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.model.plan.Itinerary.toStr; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.routing.algorithm.filterchain.comparator.SortOrderComparator.numberOfTransfersComparator; +import static org.opentripplanner.routing.algorithm.filterchain.framework.sort.SortOrderComparator.numberOfTransfersComparator; import java.util.List; import java.util.stream.Collectors; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparatorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/sort/SortOrderComparatorTest.java similarity index 96% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparatorTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/sort/SortOrderComparatorTest.java index d69bc119c76..c96ddede578 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparatorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/framework/sort/SortOrderComparatorTest.java @@ -1,9 +1,9 @@ -package org.opentripplanner.routing.algorithm.filterchain.comparator; +package org.opentripplanner.routing.algorithm.filterchain.framework.sort; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.routing.algorithm.filterchain.comparator.SortOrderComparator.defaultComparatorArriveBy; -import static org.opentripplanner.routing.algorithm.filterchain.comparator.SortOrderComparator.defaultComparatorDepartAfter; +import static org.opentripplanner.routing.algorithm.filterchain.framework.sort.SortOrderComparator.defaultComparatorArriveBy; +import static org.opentripplanner.routing.algorithm.filterchain.framework.sort.SortOrderComparator.defaultComparatorDepartAfter; import java.util.Arrays; import java.util.List; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap b/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap index 31d59483807..8f527b15bf7 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap +++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap @@ -106,7 +106,7 @@ org.opentripplanner.routing.algorithm.mapping.CarSnapshotTest.directCarPark=[ "hasWheelchairAccessibleCarPlaces" : false, "id" : "OSM:OSMWay/-102488", "name" : "P+R", - "realTime" : false, + "realtime" : false, "tags" : [ "osm:amenity=parking" ] @@ -137,7 +137,7 @@ org.opentripplanner.routing.algorithm.mapping.CarSnapshotTest.directCarPark=[ "hasWheelchairAccessibleCarPlaces" : false, "id" : "OSM:OSMWay/-102488", "name" : "P+R", - "realTime" : false, + "realtime" : false, "tags" : [ "osm:amenity=parking" ] diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32nTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitGroupPriority32nTest.java similarity index 51% rename from src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32nTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitGroupPriority32nTest.java index 85083a3ee6a..2713a190dbf 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32nTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitGroupPriority32nTest.java @@ -6,9 +6,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.raptor.api.request.RaptorTransitPriorityGroupCalculator; +import org.opentripplanner.raptor.api.request.RaptorTransitGroupCalculator; -class TransitPriorityGroup32nTest { +class TransitGroupPriority32nTest { private static final int GROUP_INDEX_0 = 0; private static final int GROUP_INDEX_1 = 1; @@ -16,35 +16,35 @@ class TransitPriorityGroup32nTest { private static final int GROUP_INDEX_30 = 30; private static final int GROUP_INDEX_31 = 31; - private static final int GROUP_0 = TransitPriorityGroup32n.groupId(GROUP_INDEX_0); - private static final int GROUP_1 = TransitPriorityGroup32n.groupId(GROUP_INDEX_1); - private static final int GROUP_2 = TransitPriorityGroup32n.groupId(GROUP_INDEX_2); - private static final int GROUP_30 = TransitPriorityGroup32n.groupId(GROUP_INDEX_30); - private static final int GROUP_31 = TransitPriorityGroup32n.groupId(GROUP_INDEX_31); - private static final RaptorTransitPriorityGroupCalculator subjct = TransitPriorityGroup32n.priorityCalculator(); + private static final int GROUP_0 = TransitGroupPriority32n.groupId(GROUP_INDEX_0); + private static final int GROUP_1 = TransitGroupPriority32n.groupId(GROUP_INDEX_1); + private static final int GROUP_2 = TransitGroupPriority32n.groupId(GROUP_INDEX_2); + private static final int GROUP_30 = TransitGroupPriority32n.groupId(GROUP_INDEX_30); + private static final int GROUP_31 = TransitGroupPriority32n.groupId(GROUP_INDEX_31); + private static final RaptorTransitGroupCalculator subjct = TransitGroupPriority32n.priorityCalculator(); @Test void groupId() { - assertEqualsHex(0x00_00_00_00, TransitPriorityGroup32n.groupId(0)); - assertEqualsHex(0x00_00_00_01, TransitPriorityGroup32n.groupId(1)); - assertEqualsHex(0x00_00_00_02, TransitPriorityGroup32n.groupId(2)); - assertEqualsHex(0x00_00_00_04, TransitPriorityGroup32n.groupId(3)); - assertEqualsHex(0x40_00_00_00, TransitPriorityGroup32n.groupId(31)); - assertEqualsHex(0x80_00_00_00, TransitPriorityGroup32n.groupId(32)); + assertEqualsHex(0x00_00_00_00, TransitGroupPriority32n.groupId(0)); + assertEqualsHex(0x00_00_00_01, TransitGroupPriority32n.groupId(1)); + assertEqualsHex(0x00_00_00_02, TransitGroupPriority32n.groupId(2)); + assertEqualsHex(0x00_00_00_04, TransitGroupPriority32n.groupId(3)); + assertEqualsHex(0x40_00_00_00, TransitGroupPriority32n.groupId(31)); + assertEqualsHex(0x80_00_00_00, TransitGroupPriority32n.groupId(32)); - assertThrows(IllegalArgumentException.class, () -> TransitPriorityGroup32n.groupId(-1)); - assertThrows(IllegalArgumentException.class, () -> TransitPriorityGroup32n.groupId(33)); + assertThrows(IllegalArgumentException.class, () -> TransitGroupPriority32n.groupId(-1)); + assertThrows(IllegalArgumentException.class, () -> TransitGroupPriority32n.groupId(33)); } @Test - void mergeTransitPriorityGroupIds() { - assertEqualsHex(GROUP_0, subjct.mergeTransitPriorityGroupIds(GROUP_0, GROUP_0)); - assertEqualsHex(GROUP_1, subjct.mergeTransitPriorityGroupIds(GROUP_1, GROUP_1)); - assertEqualsHex(GROUP_0 | GROUP_1, subjct.mergeTransitPriorityGroupIds(GROUP_0, GROUP_1)); - assertEqualsHex(GROUP_30 | GROUP_31, subjct.mergeTransitPriorityGroupIds(GROUP_30, GROUP_31)); + void mergeTransitGroupPriorityIds() { + assertEqualsHex(GROUP_0, subjct.mergeGroupIds(GROUP_0, GROUP_0)); + assertEqualsHex(GROUP_1, subjct.mergeGroupIds(GROUP_1, GROUP_1)); + assertEqualsHex(GROUP_0 | GROUP_1, subjct.mergeGroupIds(GROUP_0, GROUP_1)); + assertEqualsHex(GROUP_30 | GROUP_31, subjct.mergeGroupIds(GROUP_30, GROUP_31)); assertEqualsHex( GROUP_0 | GROUP_1 | GROUP_2 | GROUP_30 | GROUP_31, - subjct.mergeTransitPriorityGroupIds(GROUP_0 | GROUP_1 | GROUP_2 | GROUP_30, GROUP_31) + subjct.mergeGroupIds(GROUP_0 | GROUP_1 | GROUP_2 | GROUP_30, GROUP_31) ); } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfiguratorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfiguratorTest.java index 777aee5352c..cc4bb09f01e 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfiguratorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfiguratorTest.java @@ -7,7 +7,7 @@ import java.util.List; import org.junit.jupiter.api.Test; -import org.opentripplanner.routing.api.request.request.filter.TransitPriorityGroupSelect; +import org.opentripplanner.routing.api.request.request.filter.TransitGroupSelect; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.network.RoutingTripPattern; import org.opentripplanner.transit.model.site.RegularStop; @@ -69,14 +69,14 @@ class PriorityGroupConfiguratorTest { @Test void emptyConfigurationShouldReturnGroupZero() { var subject = PriorityGroupConfigurator.of(List.of(), List.of()); - assertEquals(subject.baseGroupId(), subject.lookupTransitPriorityGroupId(railR1)); - assertEquals(subject.baseGroupId(), subject.lookupTransitPriorityGroupId(busB2)); - assertEquals(subject.baseGroupId(), subject.lookupTransitPriorityGroupId(null)); + assertEquals(subject.baseGroupId(), subject.lookupTransitGroupPriorityId(railR1)); + assertEquals(subject.baseGroupId(), subject.lookupTransitGroupPriorityId(busB2)); + assertEquals(subject.baseGroupId(), subject.lookupTransitGroupPriorityId(null)); } @Test - void lookupTransitPriorityGroupIdByAgency() { - var select = TransitPriorityGroupSelect + void lookupTransitGroupIdByAgency() { + var select = TransitGroupSelect .of() .addModes(List.of(TransitMode.BUS, TransitMode.RAIL)) .build(); @@ -85,12 +85,12 @@ void lookupTransitPriorityGroupIdByAgency() { var subject = PriorityGroupConfigurator.of(List.of(select), List.of()); // Agency groups are indexed (group-id set) at request time - assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitPriorityGroupId(null)); - assertEquals(EXP_GROUP_1, subject.lookupTransitPriorityGroupId(busB2)); - assertEquals(EXP_GROUP_2, subject.lookupTransitPriorityGroupId(railR3)); - assertEquals(EXP_GROUP_3, subject.lookupTransitPriorityGroupId(railR1)); - assertEquals(EXP_GROUP_2, subject.lookupTransitPriorityGroupId(busB3)); - assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitPriorityGroupId(ferryF3)); + assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitGroupPriorityId(null)); + assertEquals(EXP_GROUP_1, subject.lookupTransitGroupPriorityId(busB2)); + assertEquals(EXP_GROUP_2, subject.lookupTransitGroupPriorityId(railR3)); + assertEquals(EXP_GROUP_3, subject.lookupTransitGroupPriorityId(railR1)); + assertEquals(EXP_GROUP_2, subject.lookupTransitGroupPriorityId(busB3)); + assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitGroupPriorityId(ferryF3)); } @Test @@ -99,17 +99,17 @@ void lookupTransitPriorityGroupIdByGlobalMode() { var subject = PriorityGroupConfigurator.of( List.of(), List.of( - TransitPriorityGroupSelect.of().addModes(List.of(TransitMode.BUS)).build(), - TransitPriorityGroupSelect.of().addModes(List.of(TransitMode.RAIL)).build() + TransitGroupSelect.of().addModes(List.of(TransitMode.BUS)).build(), + TransitGroupSelect.of().addModes(List.of(TransitMode.RAIL)).build() ) ); - assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitPriorityGroupId(null)); - assertEquals(EXP_GROUP_2, subject.lookupTransitPriorityGroupId(railR1)); - assertEquals(EXP_GROUP_1, subject.lookupTransitPriorityGroupId(busB2)); - assertEquals(EXP_GROUP_2, subject.lookupTransitPriorityGroupId(railR3)); - assertEquals(EXP_GROUP_1, subject.lookupTransitPriorityGroupId(busB3)); - assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitPriorityGroupId(ferryF3)); + assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitGroupPriorityId(null)); + assertEquals(EXP_GROUP_2, subject.lookupTransitGroupPriorityId(railR1)); + assertEquals(EXP_GROUP_1, subject.lookupTransitGroupPriorityId(busB2)); + assertEquals(EXP_GROUP_2, subject.lookupTransitGroupPriorityId(railR3)); + assertEquals(EXP_GROUP_1, subject.lookupTransitGroupPriorityId(busB3)); + assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitGroupPriorityId(ferryF3)); } private static TestRouteData route( diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcherTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcherTest.java index 880d8bc9b45..91d0142f9ef 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcherTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcherTest.java @@ -6,7 +6,7 @@ import java.util.List; import org.junit.jupiter.api.Test; -import org.opentripplanner.routing.api.request.request.filter.TransitPriorityGroupSelect; +import org.opentripplanner.routing.api.request.request.filter.TransitGroupSelect; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.TripPattern; @@ -35,7 +35,7 @@ class PriorityGroupMatcherTest { @Test void testMode() { var m = PriorityGroupMatcher.of( - TransitPriorityGroupSelect.of().addModes(List.of(TransitMode.BUS, TransitMode.TRAM)).build() + TransitGroupSelect.of().addModes(List.of(TransitMode.BUS, TransitMode.TRAM)).build() ); assertEquals("Mode(BUS | TRAM)", m.toString()); assertFalse(m.isEmpty()); @@ -47,10 +47,10 @@ void testMode() { @Test void testAgencyIds() { var m1 = PriorityGroupMatcher.of( - TransitPriorityGroupSelect.of().addAgencyIds(List.of(r1agencyId)).build() + TransitGroupSelect.of().addAgencyIds(List.of(r1agencyId)).build() ); var m2 = PriorityGroupMatcher.of( - TransitPriorityGroupSelect.of().addAgencyIds(List.of(r1agencyId, anyId)).build() + TransitGroupSelect.of().addAgencyIds(List.of(r1agencyId, anyId)).build() ); var matchers = List.of(m1, m2); @@ -68,10 +68,10 @@ void testAgencyIds() { @Test void routeIds() { var m1 = PriorityGroupMatcher.of( - TransitPriorityGroupSelect.of().addRouteIds(List.of(r1routeId)).build() + TransitGroupSelect.of().addRouteIds(List.of(r1routeId)).build() ); var m2 = PriorityGroupMatcher.of( - TransitPriorityGroupSelect.of().addRouteIds(List.of(r1routeId, anyId)).build() + TransitGroupSelect.of().addRouteIds(List.of(r1routeId, anyId)).build() ); var matchers = List.of(m1, m2); @@ -89,7 +89,7 @@ void routeIds() { @Test void testSubMode() { var subject = PriorityGroupMatcher.of( - TransitPriorityGroupSelect.of().addSubModeRegexp(List.of(".*local.*")).build() + TransitGroupSelect.of().addSubModeRegexp(List.of(".*local.*")).build() ); assertEquals("SubModeRegexp(.*local.*)", subject.toString()); @@ -103,7 +103,7 @@ void testSubMode() { @Test void testAnd() { var subject = PriorityGroupMatcher.of( - TransitPriorityGroupSelect + TransitGroupSelect .of() .addSubModeRegexp(List.of("express")) .addRouteIds(List.of(r1routeId)) @@ -125,7 +125,7 @@ void testAnd() { @Test void testToString() { var subject = PriorityGroupMatcher.of( - TransitPriorityGroupSelect + TransitGroupSelect .of() .addModes(List.of(TransitMode.BUS, TransitMode.TRAM)) .addAgencyIds(List.of(anyId, r1agencyId)) diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/BikePreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/BikePreferencesTest.java index 9da7852cc2c..8a6ac3f4f45 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/preference/BikePreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/BikePreferencesTest.java @@ -11,11 +11,7 @@ class BikePreferencesTest { public static final double SPEED = 2.0; public static final double RELUCTANCE = 1.2; - public static final double WALKING_SPEED = 1.15; public static final int BOARD_COST = 660; - public static final double WALKING_RELUCTANCE = 1.45; - public static final int SWITCH_TIME = 200; - public static final int SWITCH_COST = 450; public static final TimeSlopeSafetyTriangle TRIANGLE = TimeSlopeSafetyTriangle .of() .withSlope(1) @@ -29,13 +25,9 @@ class BikePreferencesTest { .withSpeed(SPEED) .withReluctance(RELUCTANCE) .withBoardCost(BOARD_COST) - .withWalkingSpeed(WALKING_SPEED) - .withWalkingReluctance(WALKING_RELUCTANCE) - .withSwitchTime(SWITCH_TIME) - .withSwitchCost(SWITCH_COST) .withOptimizeType(OPTIMIZE_TYPE) .withRental(rental -> rental.withPickupTime(RENTAL_PICKUP_TIME).build()) - .withParking(parking -> parking.withParkCost(PARK_COST).build()) + .withParking(parking -> parking.withCost(PARK_COST).build()) .withOptimizeTriangle(it -> it.withSlope(1).build()) .build(); @@ -54,26 +46,6 @@ void boardCost() { assertEquals(BOARD_COST, subject.boardCost()); } - @Test - void walkingSpeed() { - assertEquals(WALKING_SPEED, subject.walkingSpeed()); - } - - @Test - void walkingReluctance() { - assertEquals(WALKING_RELUCTANCE, subject.walkingReluctance()); - } - - @Test - void switchTime() { - assertEquals(SWITCH_TIME, subject.switchTime()); - } - - @Test - void switchCost() { - assertEquals(SWITCH_COST, subject.switchCost()); - } - @Test void optimizeType() { assertEquals(OPTIMIZE_TYPE, subject.optimizeType()); @@ -92,7 +64,7 @@ void rental() { @Test void parking() { - var vehicleParking = VehicleParkingPreferences.of().withParkCost(PARK_COST).build(); + var vehicleParking = VehicleParkingPreferences.of().withCost(PARK_COST).build(); assertEquals(vehicleParking, subject.parking()); } @@ -119,11 +91,7 @@ void testToString() { "speed: 2.0, " + "reluctance: 1.2, " + "boardCost: $660, " + - "walkingSpeed: 1.15, " + - "walkingReluctance: 1.45, " + - "switchTime: 3m20s, " + - "switchCost: $450, " + - "parking: VehicleParkingPreferences{parkCost: $30}, " + + "parking: VehicleParkingPreferences{cost: $30}, " + "rental: VehicleRentalPreferences{pickupTime: 30s}, " + "optimizeType: TRIANGLE, " + "optimizeTriangle: TimeSlopeSafetyTriangle[time=0.0, slope=1.0, safety=0.0]" + @@ -131,4 +99,27 @@ void testToString() { subject.toString() ); } + + @Test + void testForcedTriangleOptimization() { + var trianglePreferences = BikePreferences + .of() + .withForcedOptimizeTriangle(it -> it.withSlope(1).build()) + .build(); + assertEquals(BicycleOptimizeType.TRIANGLE, trianglePreferences.optimizeType()); + + var conflictingPreferences = BikePreferences + .of() + .withOptimizeType(BicycleOptimizeType.SAFE_STREETS) + .withForcedOptimizeTriangle(it -> it.withSlope(1).build()) + .build(); + assertEquals(BicycleOptimizeType.TRIANGLE, conflictingPreferences.optimizeType()); + + var emptyTrianglePreferences = BikePreferences + .of() + .withOptimizeType(BicycleOptimizeType.SAFE_STREETS) + .withForcedOptimizeTriangle(it -> it.build()) + .build(); + assertEquals(BicycleOptimizeType.SAFE_STREETS, emptyTrianglePreferences.optimizeType()); + } } diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/CarPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/CarPreferencesTest.java index c81cb41d883..4359ed9b33d 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/preference/CarPreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/CarPreferencesTest.java @@ -4,7 +4,9 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.opentripplanner.routing.api.request.preference.ImmutablePreferencesAsserts.assertEqualsAndHashCode; +import java.time.Duration; import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.model.Cost; class CarPreferencesTest { @@ -16,7 +18,6 @@ class CarPreferencesTest { private static final int PICKUP_COST = 500; private static final double ACCELERATION_SPEED = 3.1; private static final double DECELERATION_SPEED = 3.5; - public static final int DROPOFF_TIME = 450; public static final int RENTAL_PICKUP_TIME = 30; public static final int PARK_COST = 30; @@ -24,13 +25,12 @@ class CarPreferencesTest { .of() .withSpeed(SPEED) .withReluctance(RELUCTANCE) - .withPickupTime(PICKUP_TIME) + .withPickupTime(Duration.ofSeconds(PICKUP_TIME)) .withPickupCost(PICKUP_COST) - .withDropoffTime(DROPOFF_TIME) .withAccelerationSpeed(ACCELERATION_SPEED) .withDecelerationSpeed(DECELERATION_SPEED) .withRental(rental -> rental.withPickupTime(RENTAL_PICKUP_TIME).build()) - .withParking(parking -> parking.withParkCost(PARK_COST).build()) + .withParking(parking -> parking.withCost(PARK_COST).build()) .build(); @Test @@ -45,17 +45,12 @@ void reluctance() { @Test void pickupTime() { - assertEquals(PICKUP_TIME, subject.pickupTime()); + assertEquals(Duration.ofSeconds(PICKUP_TIME), subject.pickupTime()); } @Test void pickupCost() { - assertEquals(PICKUP_COST, subject.pickupCost()); - } - - @Test - void dropoffTime() { - assertEquals(DROPOFF_TIME, subject.dropoffTime()); + assertEquals(Cost.costOfSeconds(PICKUP_COST), subject.pickupCost()); } @Test @@ -76,7 +71,7 @@ void rental() { @Test void parking() { - var vehicleParking = VehicleParkingPreferences.of().withParkCost(PARK_COST).build(); + var vehicleParking = VehicleParkingPreferences.of().withCost(PARK_COST).build(); assertEquals(vehicleParking, subject.parking()); } @@ -99,11 +94,10 @@ void testToString() { "CarPreferences{" + "speed: 20.0, " + "reluctance: 5.1, " + - "parking: VehicleParkingPreferences{parkCost: $30}, " + + "parking: VehicleParkingPreferences{cost: $30}, " + "rental: VehicleRentalPreferences{pickupTime: 30s}, " + - "pickupTime: 600, " + + "pickupTime: PT10M, " + "pickupCost: $500, " + - "dropoffTime: 450, " + "accelerationSpeed: 3.1, decelerationSpeed: 3.5" + "}", subject.toString() diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java index 811c4a70b29..b77a7d9085b 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java @@ -42,7 +42,7 @@ class TransitPreferencesTest { .setUnpreferredCost(UNPREFERRED_COST) .withBoardSlack(b -> b.withDefault(D45s).with(TransitMode.AIRPLANE, D35m)) .withAlightSlack(b -> b.withDefault(D15s).with(TransitMode.AIRPLANE, D25m)) - .withTransitGroupPriorityGeneralizedCostSlack(TRANSIT_GROUP_PRIORITY_RELAX) + .withRelaxTransitGroupPriority(TRANSIT_GROUP_PRIORITY_RELAX) .setIgnoreRealtimeUpdates(IGNORE_REALTIME_UPDATES) .setIncludePlannedCancellations(INCLUDE_PLANNED_CANCELLATIONS) .setIncludeRealtimeCancellations(INCLUDE_REALTIME_CANCELLATIONS) @@ -77,8 +77,8 @@ void unpreferredCost() { } @Test - void relaxTransitPriorityGroup() { - assertEquals(TRANSIT_GROUP_PRIORITY_RELAX, subject.relaxTransitPriorityGroup()); + void relaxTransitGroupPriority() { + assertEquals(TRANSIT_GROUP_PRIORITY_RELAX, subject.relaxTransitGroupPriority()); } @Test @@ -125,7 +125,7 @@ void testToString() { "reluctanceForMode: {AIRPLANE=2.1}, " + "otherThanPreferredRoutesPenalty: $350, " + "unpreferredCost: 5m + 1.15 t, " + - "relaxTransitPriorityGroup: 5m + 1.50 t, " + + "relaxTransitGroupPriority: 5m + 1.50 t, " + "ignoreRealtimeUpdates, " + "includePlannedCancellations, " + "includeRealtimeCancellations, " + diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleParkingPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleParkingPreferencesTest.java index 35666b53f9f..ff43a7ddba6 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleParkingPreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleParkingPreferencesTest.java @@ -38,20 +38,20 @@ void unpreferredCost() { } @Test - void parkCost() { - assertEquals(PARKING_COST, subject.parkCost()); + void cost() { + assertEquals(PARKING_COST, subject.cost()); } @Test - void parkTime() { - assertEquals(PARKING_TIME, subject.parkTime()); + void time() { + assertEquals(PARKING_TIME, subject.time()); } @Test void testCopyOfEqualsAndHashCode() { // Create a copy, make a change and set it back again to force creating a new object - var other = subject.copyOf().withParkCost(10).build(); - var same = other.copyOf().withParkCost(PARKING_COST.toSeconds()).build(); + var other = subject.copyOf().withCost(10).build(); + var same = other.copyOf().withCost(PARKING_COST.toSeconds()).build(); assertEqualsAndHashCode(subject, other, same); } @@ -63,8 +63,8 @@ void testToString() { "unpreferredVehicleParkingTagCost: $360, " + "filter: VehicleParkingFilter{not: [tags=[not]], select: [tags=[bar]]}, " + "preferred: VehicleParkingFilter{not: [tags=[bar]], select: [tags=[foo]]}, " + - "parkCost: $240, " + - "parkTime: PT2M}", + "cost: $240, " + + "time: PT2M}", subject.toString() ); } @@ -81,8 +81,8 @@ private VehicleParkingPreferences createPreferences() { .withUnpreferredVehicleParkingTagCost(UNPREFERRED_COST) .withRequiredVehicleParkingTags(REQUIRED_TAGS) .withBannedVehicleParkingTags(BANNED_TAGS) - .withParkCost(PARKING_COST.toSeconds()) - .withParkTime(PARKING_TIME) + .withCost(PARKING_COST.toSeconds()) + .withTime(PARKING_TIME) .build(); } } diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleRentalPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleRentalPreferencesTest.java index 6092c8139bc..1b7068d0cd4 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleRentalPreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleRentalPreferencesTest.java @@ -4,8 +4,10 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.opentripplanner.routing.api.request.preference.ImmutablePreferencesAsserts.assertEqualsAndHashCode; +import java.time.Duration; import java.util.Set; import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.model.Cost; class VehicleRentalPreferencesTest { @@ -23,8 +25,8 @@ class VehicleRentalPreferencesTest { .of() .withPickupTime(PICKUP_TIME) .withPickupCost(PICKUP_COST) - .withDropoffTime(DROPOFF_TIME) - .withDropoffCost(DROPOFF_COST) + .withDropOffTime(DROPOFF_TIME) + .withDropOffCost(DROPOFF_COST) .withArrivingInRentalVehicleAtDestinationCost(ARRIVE_IN_RENTAL_COST) .withUseAvailabilityInformation(USE_AVAILABILITY_INFORMATION) .withAllowArrivingInRentedVehicleAtDestination(ALLOW_ARRIVING_IN_RENTED_VEHICLE) @@ -34,22 +36,22 @@ class VehicleRentalPreferencesTest { @Test void pickupTime() { - assertEquals(PICKUP_TIME, subject.pickupTime()); + assertEquals(Duration.ofSeconds(PICKUP_TIME), subject.pickupTime()); } @Test void pickupCost() { - assertEquals(PICKUP_COST, subject.pickupCost()); + assertEquals(Cost.costOfSeconds(PICKUP_COST), subject.pickupCost()); } @Test void dropoffTime() { - assertEquals(DROPOFF_TIME, subject.dropoffTime()); + assertEquals(Duration.ofSeconds(DROPOFF_TIME), subject.dropOffTime()); } @Test void dropoffCost() { - assertEquals(DROPOFF_COST, subject.dropoffCost()); + assertEquals(Cost.costOfSeconds(DROPOFF_COST), subject.dropOffCost()); } @Test @@ -59,7 +61,10 @@ void useAvailabilityInformation() { @Test void arrivingInRentalVehicleAtDestinationCost() { - assertEquals(ARRIVE_IN_RENTAL_COST, subject.arrivingInRentalVehicleAtDestinationCost()); + assertEquals( + Cost.costOfSeconds(ARRIVE_IN_RENTAL_COST), + subject.arrivingInRentalVehicleAtDestinationCost() + ); } @Test @@ -102,10 +107,10 @@ void testToString() { "VehicleRentalPreferences{" + "pickupTime: 25s, " + "pickupCost: $250, " + - "dropoffTime: 45s, " + - "dropoffCost: $450, " + + "dropOffTime: 45s, " + + "dropOffCost: $450, " + "useAvailabilityInformation, " + - "arrivingInRentalVehicleAtDestinationCost: 500.0, " + + "arrivingInRentalVehicleAtDestinationCost: $500, " + "allowArrivingInRentedVehicleAtDestination, " + "allowedNetworks: [foo], " + "bannedNetworks: [bar]" + diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferencesTest.java new file mode 100644 index 00000000000..9571eee8cc5 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferencesTest.java @@ -0,0 +1,77 @@ +package org.opentripplanner.routing.api.request.preference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.routing.api.request.preference.ImmutablePreferencesAsserts.assertEqualsAndHashCode; + +import java.time.Duration; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.model.Cost; + +class VehicleWalkingPreferencesTest { + + private static final double SPEED = 1.45; + private static final double RELUCTANCE = 5.5; + private static final Duration HOP_TIME = Duration.ofSeconds(15); + private static final Cost HOP_COST = Cost.costOfSeconds(20); + private static final double STAIRS_RELUCTANCE = 11; + + private final VehicleWalkingPreferences subject = createPreferences(); + + @Test + void speed() { + assertEquals(SPEED, subject.speed()); + } + + @Test + void reluctance() { + assertEquals(RELUCTANCE, subject.reluctance()); + } + + @Test + void hopTime() { + assertEquals(HOP_TIME, subject.hopTime()); + } + + @Test + void hopCost() { + assertEquals(HOP_COST, subject.hopCost()); + } + + @Test + void stairsReluctance() { + assertEquals(STAIRS_RELUCTANCE, subject.stairsReluctance()); + } + + @Test + void testCopyOfEqualsAndHashCode() { + // Create a copy, make a change and set it back again to force creating a new object + var other = subject.copyOf().withSpeed(5.4).build(); + var same = other.copyOf().withSpeed(SPEED).build(); + assertEqualsAndHashCode(subject, other, same); + } + + @Test + void testToString() { + assertEquals("VehicleWalkingPreferences{}", VehicleWalkingPreferences.DEFAULT.toString()); + assertEquals( + "VehicleWalkingPreferences{" + + "speed: 1.45, " + + "reluctance: 5.5, " + + "hopTime: PT15S, " + + "hopCost: $20, " + + "stairsReluctance: 11.0}", + subject.toString() + ); + } + + private VehicleWalkingPreferences createPreferences() { + return VehicleWalkingPreferences + .of() + .withSpeed(SPEED) + .withReluctance(RELUCTANCE) + .withHopTime(HOP_TIME) + .withHopCost(HOP_COST.toSeconds()) + .withStairsReluctance(STAIRS_RELUCTANCE) + .build(); + } +} diff --git a/src/test/java/org/opentripplanner/service/paging/TestDriver.java b/src/test/java/org/opentripplanner/service/paging/TestDriver.java index 0b40b140139..928fbaaf01d 100644 --- a/src/test/java/org/opentripplanner/service/paging/TestDriver.java +++ b/src/test/java/org/opentripplanner/service/paging/TestDriver.java @@ -11,10 +11,10 @@ import org.opentripplanner.model.plan.ItinerarySortKey; import org.opentripplanner.model.plan.SortOrder; import org.opentripplanner.model.plan.paging.cursor.PageCursor; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NumItinerariesFilter; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NumItinerariesFilterResults; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.OutsideSearchWindowFilter; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.PagingFilter; +import org.opentripplanner.model.plan.paging.cursor.PageCursorInput; +import org.opentripplanner.routing.algorithm.filterchain.filters.system.NumItinerariesFilter; +import org.opentripplanner.routing.algorithm.filterchain.filters.system.OutsideSearchWindowFilter; +import org.opentripplanner.routing.algorithm.filterchain.filters.system.PagingFilter; /** * This class simulate/mock the context the paging is operating in. @@ -29,7 +29,7 @@ final class TestDriver { private final Instant lat; private final SortOrder sortOrder; private final ListSection cropSection; - private final NumItinerariesFilterResults results; + private final PageCursorInput results; public TestDriver( int nResults, @@ -40,7 +40,7 @@ public TestDriver( Instant lat, SortOrder sortOrder, ListSection cropSection, - NumItinerariesFilterResults results + PageCursorInput results ) { this.nResults = nResults; this.searchWindow = searchWindow; @@ -106,7 +106,7 @@ boolean arrivedBy() { return !sortOrder.isSortedByAscendingArrivalTime(); } - NumItinerariesFilterResults filterResults() { + PageCursorInput filterResults() { return results; } @@ -163,7 +163,7 @@ private static TestDriver createNewDriver( } // Filter nResults - var filterResultBox = new Box(); + var filterResultBox = new Box(); var maxNumFilter = new NumItinerariesFilter(nResults, cropItineraries, filterResultBox::set); kept = maxNumFilter.removeMatchesForTest(kept); diff --git a/src/test/java/org/opentripplanner/service/paging/TestPagingModel.java b/src/test/java/org/opentripplanner/service/paging/TestPagingModel.java index f47e3444264..938a201576f 100644 --- a/src/test/java/org/opentripplanner/service/paging/TestPagingModel.java +++ b/src/test/java/org/opentripplanner/service/paging/TestPagingModel.java @@ -13,7 +13,7 @@ import org.opentripplanner.model.plan.SortOrder; import org.opentripplanner.model.plan.TestItineraryBuilder; import org.opentripplanner.model.plan.paging.cursor.PageCursor; -import org.opentripplanner.routing.algorithm.filterchain.comparator.SortOrderComparator; +import org.opentripplanner.routing.algorithm.filterchain.framework.sort.SortOrderComparator; import org.opentripplanner.transit.model._data.TransitModelForTest; class TestPagingModel { diff --git a/src/test/java/org/opentripplanner/street/integration/BarrierRoutingTest.java b/src/test/java/org/opentripplanner/street/integration/BarrierRoutingTest.java index e9b979e3b5b..d1ea3c8e59d 100644 --- a/src/test/java/org/opentripplanner/street/integration/BarrierRoutingTest.java +++ b/src/test/java/org/opentripplanner/street/integration/BarrierRoutingTest.java @@ -66,7 +66,10 @@ public void shouldWalkForBarriers() { from, to, BIKE, - rr -> rr.withPreferences(p -> p.withBike(it -> it.withWalkingReluctance(1d))), + rr -> + rr.withPreferences(p -> + p.withBike(it -> it.withWalking(walking -> walking.withReluctance(1d))) + ), itineraries -> itineraries .stream() diff --git a/src/test/java/org/opentripplanner/street/integration/BicycleRoutingTest.java b/src/test/java/org/opentripplanner/street/integration/BicycleRoutingTest.java index 300615269a1..d5b7733f3e8 100644 --- a/src/test/java/org/opentripplanner/street/integration/BicycleRoutingTest.java +++ b/src/test/java/org/opentripplanner/street/integration/BicycleRoutingTest.java @@ -75,7 +75,9 @@ private static String computePolyline(Graph graph, GenericLocation from, Generic request.setDateTime(dateTime); request.setFrom(from); request.setTo(to); - request.withPreferences(p -> p.withBike(it -> it.withOptimizeType(BicycleOptimizeType.QUICK))); + request.withPreferences(p -> + p.withBike(it -> it.withOptimizeType(BicycleOptimizeType.SHORTEST_DURATION)) + ); request.journey().direct().setMode(StreetMode.BIKE); var temporaryVertices = new TemporaryVerticesContainer( diff --git a/src/test/java/org/opentripplanner/street/integration/BikeRentalTest.java b/src/test/java/org/opentripplanner/street/integration/BikeRentalTest.java index 0694a991069..8dc1e240392 100644 --- a/src/test/java/org/opentripplanner/street/integration/BikeRentalTest.java +++ b/src/test/java/org/opentripplanner/street/integration/BikeRentalTest.java @@ -620,7 +620,7 @@ private List runStreetSearchAndCreateDescriptor( request.withPreferences(preferences -> preferences.withBike(bike -> bike.withRental(rental -> - rental.withPickupTime(42).withPickupCost(62).withDropoffCost(33).withDropoffTime(15) + rental.withPickupTime(42).withPickupCost(62).withDropOffCost(33).withDropOffTime(15) ) ) ); diff --git a/src/test/java/org/opentripplanner/street/integration/BikeWalkingTest.java b/src/test/java/org/opentripplanner/street/integration/BikeWalkingTest.java index 66086c6d168..417f1b87347 100644 --- a/src/test/java/org/opentripplanner/street/integration/BikeWalkingTest.java +++ b/src/test/java/org/opentripplanner/street/integration/BikeWalkingTest.java @@ -375,7 +375,7 @@ private List runStreetSearchAndCreateDescriptor( preferences .withWalk(w -> w.withSpeed(10)) .withBike(it -> - it.withSpeed(20d).withWalkingSpeed(5d).withSwitchTime(100).withSwitchCost(1000) + it.withSpeed(20d).withWalking(w -> w.withSpeed(5d).withHopTime(100).withHopCost(1000)) ) ); request.setArriveBy(arriveBy); diff --git a/src/test/java/org/opentripplanner/street/integration/ParkAndRideTest.java b/src/test/java/org/opentripplanner/street/integration/ParkAndRideTest.java index 6dd53ae0c6e..c4f7f52f4ec 100644 --- a/src/test/java/org/opentripplanner/street/integration/ParkAndRideTest.java +++ b/src/test/java/org/opentripplanner/street/integration/ParkAndRideTest.java @@ -142,16 +142,16 @@ protected List runStreetSearchAndCreateDescriptor( b.withParking(parking -> { parking.withRequiredVehicleParkingTags(requiredTags); parking.withBannedVehicleParkingTags(bannedTags); - parking.withParkCost(120); - parking.withParkTime(60); + parking.withCost(120); + parking.withTime(60); }) ) .withCar(c -> c.withParking(parking -> { parking.withRequiredVehicleParkingTags(requiredTags); parking.withBannedVehicleParkingTags(bannedTags); - parking.withParkCost(240); - parking.withParkTime(180); + parking.withCost(240); + parking.withTime(180); }) ) ); diff --git a/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeCostTest.java b/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeCostTest.java index 4712841a8d9..2ecfa51822a 100644 --- a/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeCostTest.java +++ b/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeCostTest.java @@ -159,7 +159,9 @@ public void bikeStairsReluctance(double stairsReluctance, long expectedCost) { .buildAndConnect(); var req = StreetSearchRequest.of(); - req.withPreferences(p -> p.withBike(b -> b.withStairsReluctance(stairsReluctance))); + req.withPreferences(p -> + p.withBike(b -> b.withWalking(w -> w.withStairsReluctance(stairsReluctance))) + ); req.withMode(StreetMode.BIKE); var result = traverse(stairsEdge, req.build()); assertEquals(expectedCost, (long) result.weight); diff --git a/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeTest.java b/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeTest.java index e131bae1a7a..42d841b9fa3 100644 --- a/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeTest.java +++ b/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeTest.java @@ -55,7 +55,9 @@ public void before() { references .withStreet(s -> s.withTurnReluctance(1.0)) .withWalk(it -> it.withSpeed(1.0).withReluctance(1.0).withStairsReluctance(1.0)) - .withBike(it -> it.withSpeed(5.0f).withReluctance(1.0).withWalkingSpeed(0.8)) + .withBike(it -> + it.withSpeed(5.0f).withReluctance(1.0).withWalking(w -> w.withSpeed(0.8)) + ) .withCar(c -> c.withSpeed(15.0f).withReluctance(1.0)) ) .build(); @@ -218,7 +220,9 @@ public void testBikeSwitch() { StreetEdge e2 = streetEdge(v2, v0, 0.0, StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE); StreetSearchRequestBuilder noPenalty = StreetSearchRequest.copyOf(proto); - noPenalty.withPreferences(p -> p.withBike(it -> it.withSwitchTime(0).withSwitchCost(0))); + noPenalty.withPreferences(p -> + p.withBike(it -> it.withWalking(w -> w.withHopTime(0).withHopCost(0))) + ); State s0 = new State(v0, noPenalty.withMode(StreetMode.BIKE).build()); State s1 = e0.traverse(s0)[0]; @@ -226,7 +230,9 @@ public void testBikeSwitch() { State s3 = e2.traverse(s2)[0]; StreetSearchRequestBuilder withPenalty = StreetSearchRequest.copyOf(proto); - withPenalty.withPreferences(p -> p.withBike(it -> it.withSwitchTime(42).withSwitchCost(23))); + withPenalty.withPreferences(p -> + p.withBike(it -> it.withWalking(w -> w.withHopTime(42).withHopCost(23))) + ); State s4 = new State(v0, withPenalty.withMode(StreetMode.BIKE).build()); State s5 = e0.traverse(s4)[0]; diff --git a/src/test/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLinkTest.java b/src/test/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLinkTest.java index a9932381b40..a2a052d3b5c 100644 --- a/src/test/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLinkTest.java +++ b/src/test/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLinkTest.java @@ -64,7 +64,7 @@ void foo(Set parkingTags, Set not, Set select, boolean s bike.withParking(parkingPreferences -> { parkingPreferences.withRequiredVehicleParkingTags(select); parkingPreferences.withBannedVehicleParkingTags(not); - parkingPreferences.withParkCost(0); + parkingPreferences.withCost(0); }); }) ); diff --git a/src/test/java/org/opentripplanner/street/model/edge/VehicleParkingPreferredTagsTest.java b/src/test/java/org/opentripplanner/street/model/edge/VehicleParkingPreferredTagsTest.java index 41ecbe162bb..5969121b6d6 100644 --- a/src/test/java/org/opentripplanner/street/model/edge/VehicleParkingPreferredTagsTest.java +++ b/src/test/java/org/opentripplanner/street/model/edge/VehicleParkingPreferredTagsTest.java @@ -88,7 +88,7 @@ private void runTest( bike.withParking(parkingPreferences -> { parkingPreferences.withUnpreferredVehicleParkingTagCost(EXTRA_COST); parkingPreferences.withPreferredVehicleParkingTags(preferredTags); - parkingPreferences.withParkCost(0); + parkingPreferences.withCost(0); }); }) ); diff --git a/src/test/resources/org/opentripplanner/apis/vectortiles/style.json b/src/test/resources/org/opentripplanner/apis/vectortiles/style.json index f5bb18f6f6a..9555f32c1e5 100644 --- a/src/test/resources/org/opentripplanner/apis/vectortiles/style.json +++ b/src/test/resources/org/opentripplanner/apis/vectortiles/style.json @@ -1,42 +1,172 @@ { - "name": "OTP Debug Tiles", - "sources": { - "background": { - "id": "background", - "tiles": [ + "name" : "OTP Debug Tiles", + "sources" : { + "background" : { + "id" : "background", + "tiles" : [ "https://a.tile.openstreetmap.org/{z}/{x}/{y}.png" ], - "tileSize": 256, + "maxzoom" : 19, + "tileSize" : 256, "attribution" : "© OpenStreetMap Contributors", - "type": "raster" + "type" : "raster" }, - "vectorSource": { - "id": "vectorSource", - "url": "https://example.com", - "type": "vector" + "vectorSource" : { + "id" : "vectorSource", + "url" : "https://example.com", + "type" : "vector" } }, - "layers": [ + "layers" : [ { - "id": "background", - "source": "background", - "type": "raster", - "maxzoom": 22, - "minzoom": 0 + "id" : "background", + "type" : "raster", + "source" : "background", + "minzoom" : 0 }, { - "maxzoom": 22, - "paint": { - "circle-stroke-width": 2, - "circle-color": "#fcf9fa", - "circle-stroke-color": "#140d0e" + "id" : "edge", + "type" : "line", + "source" : "vectorSource", + "source-layer" : "edges", + "minzoom" : 13, + "maxzoom" : 23, + "paint" : { + "line-color" : "#f21d52", + "line-width" : { + "base" : 1.3, + "stops" : [ + [ + 13, + 0.5 + ], + [ + 23, + 10.0 + ] + ] + } }, - "id": "regular-stop", - "source": "vectorSource", - "source-layer": "regularStops", - "type": "circle", - "minzoom": 13 + "filter" : [ + "in", + "class", + "StreetEdge", + "AreaEdge", + "EscalatorEdge", + "PathwayEdge", + "ElevatorHopEdge", + "TemporaryPartialStreetEdge", + "TemporaryFreeEdge" + ], + "layout" : { + "line-cap" : "round", + "visibility" : "none" + } + }, + { + "id" : "link", + "type" : "line", + "source" : "vectorSource", + "source-layer" : "edges", + "minzoom" : 13, + "maxzoom" : 23, + "paint" : { + "line-color" : "#22DD9E", + "line-width" : { + "base" : 1.3, + "stops" : [ + [ + 13, + 0.5 + ], + [ + 23, + 10.0 + ] + ] + } + }, + "filter" : [ + "in", + "class", + "StreetTransitStopLink", + "StreetTransitEntranceLink", + "BoardingLocationToStopLink", + "StreetVehicleRentalLink", + "StreetVehicleParkingLink" + ], + "layout" : { + "line-cap" : "round", + "visibility" : "none" + } + }, + { + "id" : "vertex", + "type" : "circle", + "source" : "vectorSource", + "source-layer" : "vertices", + "minzoom" : 15, + "maxzoom" : 23, + "paint" : { + "circle-stroke-color" : "#140d0e", + "circle-stroke-width" : { + "base" : 1.0, + "stops" : [ + [ + 15, + 0.2 + ], + [ + 23, + 3.0 + ] + ] + }, + "circle-radius" : { + "base" : 1.0, + "stops" : [ + [ + 15, + 1.0 + ], + [ + 23, + 7.0 + ] + ] + }, + "circle-color" : "#BC55F2" + }, + "layout" : { + "visibility" : "none" + } + }, + { + "id" : "regular-stop", + "type" : "circle", + "source" : "vectorSource", + "source-layer" : "stops", + "minzoom" : 10, + "maxzoom" : 23, + "paint" : { + "circle-stroke-color" : "#140d0e", + "circle-stroke-width" : 2, + "circle-radius" : { + "base" : 1.0, + "stops" : [ + [ + 11, + 1.0 + ], + [ + 23, + 10.0 + ] + ] + }, + "circle-color" : "#fcf9fa" + } } ], - "version": 8 + "version" : 8 } diff --git a/src/test/resources/standalone/config/router-config.json b/src/test/resources/standalone/config/router-config.json index 1a5eec22a28..863b9bec279 100644 --- a/src/test/resources/standalone/config/router-config.json +++ b/src/test/resources/standalone/config/router-config.json @@ -12,34 +12,59 @@ ] }, "routingDefaults": { - "walkSpeed": 1.3, - "bikeSpeed": 5, - "carSpeed": 40, "numItineraries": 12, "transferPenalty": 0, - "walkReluctance": 4.0, - "bikeReluctance": 5.0, - "bikeWalkingReluctance": 10.0, - "bikeStairsReluctance": 150.0, - "carReluctance": 10.0, - "stairsReluctance": 1.65, "turnReluctance": 1.0, "elevatorBoardTime": 90, "elevatorBoardCost": 90, "elevatorHopTime": 20, "elevatorHopCost": 20, - "escalatorReluctance": 1.5, - "vehicleRental": { - "pickupCost": 120, - "dropOffTime": 30, - "dropOffCost": 30 + "bicycle": { + "speed": 5, + "reluctance": 5.0, + "boardCost": 600, + "walk": { + "reluctance": 10.0, + "stairsReluctance": 150.0 + }, + "rental": { + "pickupCost": 120, + "dropOffTime": "30s", + "dropOffCost": 30 + }, + "parking": { + "time": "1m", + "cost": 120 + }, + "triangle": { + "safety": 0.4, + "flatness": 0.3, + "time": 0.3 + } + }, + "car": { + "speed": 40, + "reluctance": 10, + "decelerationSpeed": 2.9, + "accelerationSpeed": 2.9, + "rental": { + "pickupCost": 120, + "dropOffTime": "30s", + "dropOffCost": 30 + }, + "parking": { + "time": "5m", + "cost": 600 + } + }, + "walk": { + "speed": 1.3, + "reluctance": 4.0, + "stairsReluctance": 1.65, + "boardCost": 600, + "escalatorReluctance": 1.5 }, - "bikeParkTime": "1m", - "bikeParkCost": 120, - "carDropoffTime": 120, "waitReluctance": 1.0, - "walkBoardCost": 600, - "bikeBoardCost": 600, "otherThanPreferredRoutesPenalty": 300, "transferSlack": 120, // Default slack for any mode is 0 (zero) @@ -74,8 +99,6 @@ "minBikeParkingDistance": 300, "debug": "limit-to-search-window" }, - "carDecelerationSpeed": 2.9, - "carAccelerationSpeed": 2.9, "ignoreRealtimeUpdates": false, "geoidElevation": false, "maxJourneyDuration": "36h", @@ -283,6 +306,14 @@ "Authorization": "A-Token" } }, + // Streaming GTFS-RT TripUpdates through an MQTT broker + { + "type": "mqtt-gtfs-rt-updater", + "url": "tcp://pred.rt.hsl.fi", + "topic": "gtfsrt/v2/fi/hsl/tu", + "feedId": "HSL", + "fuzzyTripMatching": true + }, // Polling for GTFS-RT Vehicle Positions - output can be fetched via trip pattern GraphQL API { "type": "vehicle-positions", diff --git a/test/performance/norway/speed-test-config.json b/test/performance/norway/speed-test-config.json index e673155eb4f..e4ca99d7fe5 100644 --- a/test/performance/norway/speed-test-config.json +++ b/test/performance/norway/speed-test-config.json @@ -39,13 +39,17 @@ } }, "routingDefaults": { - // Default is 1.4 m/s = ~ 5.0 km/t - "walkSpeed": 1.4, - // Should not be used - a high cost indicate an error - "bikeBoardCost": 222000, - "walkBoardCost": 600, + "bicycle": { + // Should not be used - a high cost indicate an error + "boardCost": 222000 + }, + "walk": { + // Default is 1.4 m/s = ~ 5.0 km/t + "speed": 1.4, + "boardCost": 600, + "reluctance": 4.0 + }, "transferPenalty": 0, - "walkReluctance": 4.0, "waitReluctance": 1.0, "boardSlack": "30s", "alightSlack": "15s", diff --git a/test/performance/skanetrafiken/speed-test-config.json b/test/performance/skanetrafiken/speed-test-config.json index a43cc97095d..66184ab124f 100644 --- a/test/performance/skanetrafiken/speed-test-config.json +++ b/test/performance/skanetrafiken/speed-test-config.json @@ -15,10 +15,12 @@ } }, "routingDefaults": { - "walkSpeed": 1.38, + "walk": { + "speed": 1.38, + "reluctance": 5 + }, "transferSlack": 180, "waitReluctance": 0.175, - "walkReluctance": 5, "maxDirectStreetDuration": "1h1m", "boardSlackForMode": { "RAIL": "12m" diff --git a/test/performance/switzerland/speed-test-config.json b/test/performance/switzerland/speed-test-config.json index f37c8497ffa..19ac7a1358a 100644 --- a/test/performance/switzerland/speed-test-config.json +++ b/test/performance/switzerland/speed-test-config.json @@ -39,13 +39,17 @@ } }, "routingDefaults": { - // Default is 1.4 m/s = ~ 5.0 km/t - "walkSpeed": 1.4, - // Should not be used - a high cost indicate an error - "bikeBoardCost": 222000, - "walkBoardCost": 600, + "bicycle": { + // Should not be used - a high cost indicate an error + "boardCost": 222000 + }, + "walk": { + // Default is 1.4 m/s = ~ 5.0 km/t + "speed": 1.4, + "boardCost": 600, + "reluctance": 4.0 + }, "transferPenalty": 0, - "walkReluctance": 4.0, "waitReluctance": 1.0, "boardSlack": "30s", "alightSlack": "15s",