diff --git a/.github/workflows/cibuild.yml b/.github/workflows/cibuild.yml index befce81de3e..aa458f7d408 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -33,6 +33,12 @@ jobs: distribution: temurin cache: maven + - name: Build debug client + working-directory: ./client-next + run: | + npm install + npm run build + - name: Prepare coverage agent, build and test # these are split into two steps because otherwise maven keeps long-running HTTP connections # to Maven Central open which then hang during the package phase because the Azure (Github Actions) @@ -250,6 +256,14 @@ jobs: java-version: 21 distribution: temurin cache: maven + - uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Build debug client + working-directory: ./client-next + run: | + npm install + npm run build - name: Build container image with Jib, push to Dockerhub env: CONTAINER_REPO: docker.io/opentripplanner/opentripplanner diff --git a/.github/workflows/prune-container-images.yml b/.github/workflows/prune-container-images.yml new file mode 100644 index 00000000000..c1653701c3b --- /dev/null +++ b/.github/workflows/prune-container-images.yml @@ -0,0 +1,22 @@ +name: 'Prune container images' + +on: + schedule: + - cron: '0 12 * * 1' + workflow_dispatch: + +jobs: + container-image: + if: github.repository_owner == 'opentripplanner' + runs-on: ubuntu-latest + steps: + - name: Delete unused container images + env: + CONTAINER_REPO: opentripplanner/opentripplanner + CONTAINER_REGISTRY_USER: otpbot + CONTAINER_REGISTRY_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} + run: | + # remove all snapshot container images that have not been pulled for over a year + # --keep-semver makes sure that any image with a x.y.z version scheme is unaffected by this + pip install prune-container-repo==0.0.4 + prune-container-repo -u ${CONTAINER_REGISTRY_USER} -r ${CONTAINER_REPO} --days=365 --keep-semver --activate diff --git a/.gitignore b/.gitignore index b98d0263a70..ec589dcbd0a 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ gen-py/ node_modules/ target/ /graphs +/src/client/debug-client-preview/ # for local dev only /src/test/resources/speedtest/travelSearch-results-*.csv diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index dd0f986e518..acd90b787e6 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -24,7 +24,7 @@ examples. The Transit model is more complex than the VehiclePosition model. a use-case or set of features. It may have an api with request/response classes. These are usually stateless; Hence the `Use Case Service` does normally not have a model. The implementing class has the same name as the interface with prefix `Default`. - - `Domain Model` A model witch encapsulate a business area. In the drawing two examples are shown, + - `Domain Model` A model which encapsulate a business area. In the drawing two examples are shown, the `transit` and `vhicleposition` domain model. The transit model is more complex so the implementation have a separate `Service` and `Repository`. Almost all http endpoints are , read-only so the `Service` can focus on serving the http API endpoints, while the repository diff --git a/CODE_CONVENTIONS.md b/CODE_CONVENTIONS.md index a9fd73a0497..49b92fbe2f8 100644 --- a/CODE_CONVENTIONS.md +++ b/CODE_CONVENTIONS.md @@ -30,6 +30,12 @@ Builder initStop(Stop stop) { ## Naming Conventions +In general, we use American English. We use the GTFS terminology inside OTP as the transit domain +specific language. In cases where GTFS does not provide an alternative we use NeTEx. The naming +should follow the Java standard naming conventions. For example a "real-time updater" class +is named `RealTimeUpdater`. If in doubt check the Oxford Dictionary(American). + + ### Packages Try to arrange code by domain functionality, not technology. The main structure of a package should @@ -38,7 +44,7 @@ be `org.opentripplanner...`. | Package | Description | | ------------------------------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `o.o.` | At the top level we should divide OTP into "domain"s like `apis`, `framework`, `transit`, `street`, `astar`, `raptor`, `feeds`, `updaters`, and `application`. | -| `component` and `sub-component` | A group of packages/classes witch naturally belong together, think aggregate as in Domain Driven Design. | +| `component` and `sub-component` | A group of packages/classes which naturally belong together, think aggregate as in Domain Driven Design. | | `component.api` | Used for components to define the programing interface for the component. If present, (see Raptor) all outside dependencies to the component should be through the `api`. | | `component.model` | Used to create a model of a Entites, ValueObjects, ++. If exposed outside the component you should include an entry point like `xyz.model.XyzModel` and/or a Service (in api or component root package). | | `component.service` | Implementation of the service like `DefaultTransitService`, may also contain use-case specific code. Note, the Service interface goes into the component root or `api`, not in the service package. | diff --git a/client-next/.env b/client-next/.env new file mode 100644 index 00000000000..003970b4e1f --- /dev/null +++ b/client-next/.env @@ -0,0 +1 @@ +VITE_API_URL=/otp/routers/default/transmodel/index/graphql \ No newline at end of file diff --git a/client-next/.env.development b/client-next/.env.development new file mode 100644 index 00000000000..e11b45c4411 --- /dev/null +++ b/client-next/.env.development @@ -0,0 +1 @@ +VITE_API_URL=http://localhost:8080/otp/routers/default/transmodel/index/graphql \ No newline at end of file diff --git a/client-next/.eslintrc.cjs b/client-next/.eslintrc.cjs new file mode 100644 index 00000000000..516e639be09 --- /dev/null +++ b/client-next/.eslintrc.cjs @@ -0,0 +1,42 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:react-hooks/recommended', + 'plugin:import/recommended', + 'plugin:jsx-a11y/recommended', + 'plugin:@typescript-eslint/recommended', + 'eslint-config-prettier', + ], + ignorePatterns: ['node_modules', 'dist', '.prettierrc.js', '.eslintrc.cjs', 'src/gql/**/*'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + settings: { + react: { + // Tells eslint-plugin-react to automatically detect the version of React to use. + version: 'detect', + }, + // Tells eslint how to resolve imports + 'import/resolver': { + node: { + paths: ['src'], + extensions: ['.js', '.jsx', '.ts', '.tsx'], + }, + }, + }, + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + "react/jsx-uses-react": "off", + "react/react-in-jsx-scope": "off", + '@typescript-eslint/ban-ts-comment': "off", + + // TODO: this is a temporary fix for + // https://github.com/typescript-eslint/typescript-eslint/issues/154 + "import/named": "off" + }, +} diff --git a/client-next/.gitignore b/client-next/.gitignore new file mode 100644 index 00000000000..049d26604a9 --- /dev/null +++ b/client-next/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# generated code +src/gql/ + +# Vite cache folder +.vite/ \ No newline at end of file diff --git a/client-next/.prettierignore b/client-next/.prettierignore new file mode 100644 index 00000000000..c0aafcabe5f --- /dev/null +++ b/client-next/.prettierignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +src/gql/ diff --git a/client-next/.prettierrc.cjs b/client-next/.prettierrc.cjs new file mode 100644 index 00000000000..6111ad4264d --- /dev/null +++ b/client-next/.prettierrc.cjs @@ -0,0 +1,8 @@ +module.exports = { + "trailingComma": "all", + "tabWidth": 2, + "semi": true, + "singleQuote": true, + "printWidth": 120, + "bracketSpacing": true +} \ No newline at end of file diff --git a/client-next/README-vite.md b/client-next/README-vite.md new file mode 100644 index 00000000000..1ebe379f5f4 --- /dev/null +++ b/client-next/README-vite.md @@ -0,0 +1,27 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + }, +``` + +- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` +- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/client-next/README.md b/client-next/README.md new file mode 100644 index 00000000000..a6c8e76cfad --- /dev/null +++ b/client-next/README.md @@ -0,0 +1,65 @@ +# OTP debug client (next) + +This is the next version of the debug client, intended to replace `../src/client`. + +It is designed to work with the Transmodel GraphQL API. + +## Stack notes + +This is a true Single Page Application (SPA) written in TypeScript on the React framework. It uses `vite` to run +(locally) and for building static assets. It requires no server runtime, and can be served from a CDN and run entirely +in the browser. + +The design framework is Bootstrap with React support from `react-bootstrap`. + +The map framework is MapLibre, using `MapLibre GL JS` with React support from `react-map-gl`. + +GraphQL integration is provided by `graphql-request`, with type support added with `@graphql-codegen`. Types are +generated during build and are not checked into the repository. + +## Prerequisites + +Use latest LTS version of Node/npm (currently v18). Recommend using a version manager such as `nvm`. + +The dev and production builds require graphql schema to be present at +`../src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql`. + +## Getting started (development) + +Change directory to `client-next` (current) if you haven't already. + + npm install + +Then + + npm run dev + +The debug client will now be available at `http://localhost:5173/debug-client-preview`. It has +hot reloading enabled, so you don't have to restart it when you save files. + +If you change graphql code during development you can issue the following command: + + npm run codegen + +You don't have to restart the development server for the changes to take effect. + +## Build for production + +Change directory to `client-next` (current) if you haven't already. + + npm install + +Then + + npm run build + +## Which OTP instance do I use? + +In development mode, the debug client by default assumes you are running an OTP instance locally at +port 8080 (see `.env.development`). If you wish to point to a different OTP instance +(like a remote server), use the environment variable`VITE_API_URL`, either at the command line, +or add it to a new `.env.development.local` file (this file will be ignored by git). + +In production mode, the default is to access OTP via the same origin as the client (see `.env`). +This behavior can also be modified by changing the previously mentioned environment variable at +build-time. diff --git a/client-next/codegen.ts b/client-next/codegen.ts new file mode 100644 index 00000000000..b5b68a0650a --- /dev/null +++ b/client-next/codegen.ts @@ -0,0 +1,15 @@ +import type { CodegenConfig } from '@graphql-codegen/cli'; + +const config: CodegenConfig = { + overwrite: true, + schema: '../src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql', + documents: 'src/**/*.{ts,tsx}', + generates: { + 'src/gql/': { + preset: 'client', + plugins: [], + }, + }, +}; + +export default config; diff --git a/client-next/index.html b/client-next/index.html new file mode 100644 index 00000000000..77eb289e595 --- /dev/null +++ b/client-next/index.html @@ -0,0 +1,13 @@ + + + + + + + OTP Debug Client + + +
+ + + diff --git a/client-next/package-lock.json b/client-next/package-lock.json new file mode 100644 index 00000000000..85a29784489 --- /dev/null +++ b/client-next/package-lock.json @@ -0,0 +1,9842 @@ +{ + "name": "otp-debug-client-next", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "otp-debug-client-next", + "version": "0.0.0", + "dependencies": { + "@googlemaps/polyline-codec": "^1.0.28", + "bootstrap": "^5.3.1", + "graphql": "^16.8.0", + "graphql-request": "^6.1.0", + "maplibre-gl": "^3.3.0", + "react": "^18.2.0", + "react-bootstrap": "^2.8.0", + "react-dom": "^18.2.0", + "react-map-gl": "^7.1.5" + }, + "devDependencies": { + "@graphql-codegen/cli": "5.0.0", + "@graphql-codegen/client-preset": "4.1.0", + "@graphql-codegen/introspection": "4.0.0", + "@parcel/watcher": "^2.3.0", + "@types/react": "^18.2.15", + "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "@vitejs/plugin-react": "^4.0.3", + "eslint": "^8.45.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.3", + "prettier": "^3.0.3", + "typescript": "^5.2.2", + "vite": "^4.4.5" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@ardatan/relay-compiler": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@ardatan/relay-compiler/-/relay-compiler-12.0.0.tgz", + "integrity": "sha512-9anThAaj1dQr6IGmzBMcfzOQKTa5artjuPmw8NYK/fiGEMjADbSguBY2FMDykt+QhilR3wc9VA/3yVju7JHg7Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.14.0", + "@babel/generator": "^7.14.0", + "@babel/parser": "^7.14.0", + "@babel/runtime": "^7.0.0", + "@babel/traverse": "^7.14.0", + "@babel/types": "^7.0.0", + "babel-preset-fbjs": "^3.4.0", + "chalk": "^4.0.0", + "fb-watchman": "^2.0.0", + "fbjs": "^3.0.0", + "glob": "^7.1.1", + "immutable": "~3.7.6", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "relay-runtime": "12.0.0", + "signedsource": "^1.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "relay-compiler": "bin/relay-compiler" + }, + "peerDependencies": { + "graphql": "*" + } + }, + "node_modules/@ardatan/relay-compiler/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@ardatan/relay-compiler/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@ardatan/relay-compiler/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/@ardatan/relay-compiler/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@ardatan/relay-compiler/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@ardatan/relay-compiler/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@ardatan/relay-compiler/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@ardatan/relay-compiler/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@ardatan/relay-compiler/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ardatan/relay-compiler/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@ardatan/relay-compiler/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@ardatan/relay-compiler/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/@ardatan/relay-compiler/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@ardatan/relay-compiler/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@ardatan/sync-fetch": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@ardatan/sync-fetch/-/sync-fetch-0.0.1.tgz", + "integrity": "sha512-xhlTqH0m31mnsG0tIP4ETgfSB6gXDaYYsUWTrlUV93fFQPI9dd8hE0Ot6MHLCtqgB32hwJAC3YZMWlXZw7AleA==", + "dev": true, + "dependencies": { + "node-fetch": "^2.6.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", + "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.11.tgz", + "integrity": "sha512-lh7RJrtPdhibbxndr6/xx0w8+CVlY5FJZiaSz908Fpy+G0xkBFTvwLcKJFF4PJxVfGhVWNebikpWGnOoC71juQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.10", + "@babel/generator": "^7.22.10", + "@babel/helper-compilation-targets": "^7.22.10", + "@babel/helper-module-transforms": "^7.22.9", + "@babel/helpers": "^7.22.11", + "@babel/parser": "^7.22.11", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.11", + "@babel/types": "^7.22.11", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz", + "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.10", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz", + "integrity": "sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.5", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.11.tgz", + "integrity": "sha512-y1grdYL4WzmUDBRGK0pDbIoFd7UZKoDurDzWEoNMYoj1EL+foGRQNyPWDcC+YyegN5y1DUsFFmzjGijB3nSVAQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", + "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", + "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz", + "integrity": "sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.11.tgz", + "integrity": "sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.11", + "@babel/types": "^7.22.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz", + "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.13.tgz", + "integrity": "sha512-3l6+4YOvc9wx7VlCSw4yQfcBo01ECA8TicQfbnCPuCEpRQrf+gTUyGdxNw+pyTUyywp6JRD1w0YQs9TpBXYlkw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.22.5.tgz", + "integrity": "sha512-9RdCl0i+q0QExayk2nOS7853w08yLucnnPML6EN9S8fgMPVtdLDCdx/cOQ/i44Lb9UeQX9A35yaqBBOMMZxPxQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", + "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", + "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", + "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.10.tgz", + "integrity": "sha512-1+kVpGAOOI1Albt6Vse7c8pHzcZQdQKW+wJH+g8mCaszOdDVwRXa/slHPqIw+oJAJANTKDMuM2cBdV0Dg618Vg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz", + "integrity": "sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", + "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.10.tgz", + "integrity": "sha512-dPJrL0VOyxqLM9sritNbMSGx/teueHF/htMKrPT7DNxccXxRDPYqlgPFFdr8u+F+qUZOkZoXue/6rL5O5GduEw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.22.5.tgz", + "integrity": "sha512-tujNbZdxdG0/54g/oua8ISToaXTFBf8EnSb5PgQSciIXWOWKX3S4+JR7ZE9ol8FZwf9kxitzkGQ+QWeov/mCiA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-flow": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz", + "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", + "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", + "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", + "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.11.tgz", + "integrity": "sha512-o2+bg7GDS60cJMgz9jWqRUsWkMzLCxp+jFDeDUT5sjRlAxcJWZ2ylNdI7QQ2+CH5hWu7OnN+Cv3htt7AkSf96g==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.22.9", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", + "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz", + "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", + "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz", + "integrity": "sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.5.tgz", + "integrity": "sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.22.5.tgz", + "integrity": "sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.22.5.tgz", + "integrity": "sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", + "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", + "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", + "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", + "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.11.tgz", + "integrity": "sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.10", + "@babel/generator": "^7.22.10", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.22.11", + "@babel/types": "^7.22.11", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz", + "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.0.tgz", + "integrity": "sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.48.0.tgz", + "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@googlemaps/polyline-codec": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@googlemaps/polyline-codec/-/polyline-codec-1.0.28.tgz", + "integrity": "sha512-m7rh8sbxlrHvebXEweBHX8r1uPtToPRYxWDD6p6k2YG8hyhBe0Wi6xRUVFpxpEseMNgF+OBotFQC5senj8K7TQ==" + }, + "node_modules/@graphql-codegen/add": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/add/-/add-5.0.0.tgz", + "integrity": "sha512-ynWDOsK2yxtFHwcJTB9shoSkUd7YXd6ZE57f0nk7W5cu/nAgxZZpEsnTPEpZB/Mjf14YRGe2uJHQ7AfElHjqUQ==", + "dev": true, + "dependencies": { + "@graphql-codegen/plugin-helpers": "^5.0.0", + "tslib": "~2.5.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/add/node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", + "dev": true + }, + "node_modules/@graphql-codegen/cli": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-5.0.0.tgz", + "integrity": "sha512-A7J7+be/a6e+/ul2KI5sfJlpoqeqwX8EzktaKCeduyVKgOLA6W5t+NUGf6QumBDXU8PEOqXk3o3F+RAwCWOiqA==", + "dev": true, + "dependencies": { + "@babel/generator": "^7.18.13", + "@babel/template": "^7.18.10", + "@babel/types": "^7.18.13", + "@graphql-codegen/core": "^4.0.0", + "@graphql-codegen/plugin-helpers": "^5.0.1", + "@graphql-tools/apollo-engine-loader": "^8.0.0", + "@graphql-tools/code-file-loader": "^8.0.0", + "@graphql-tools/git-loader": "^8.0.0", + "@graphql-tools/github-loader": "^8.0.0", + "@graphql-tools/graphql-file-loader": "^8.0.0", + "@graphql-tools/json-file-loader": "^8.0.0", + "@graphql-tools/load": "^8.0.0", + "@graphql-tools/prisma-loader": "^8.0.0", + "@graphql-tools/url-loader": "^8.0.0", + "@graphql-tools/utils": "^10.0.0", + "@whatwg-node/fetch": "^0.8.0", + "chalk": "^4.1.0", + "cosmiconfig": "^8.1.3", + "debounce": "^1.2.0", + "detect-indent": "^6.0.0", + "graphql-config": "^5.0.2", + "inquirer": "^8.0.0", + "is-glob": "^4.0.1", + "jiti": "^1.17.1", + "json-to-pretty-yaml": "^1.2.2", + "listr2": "^4.0.5", + "log-symbols": "^4.0.0", + "micromatch": "^4.0.5", + "shell-quote": "^1.7.3", + "string-env-interpolation": "^1.0.1", + "ts-log": "^2.2.3", + "tslib": "^2.4.0", + "yaml": "^2.3.1", + "yargs": "^17.0.0" + }, + "bin": { + "gql-gen": "cjs/bin.js", + "graphql-code-generator": "cjs/bin.js", + "graphql-codegen": "cjs/bin.js", + "graphql-codegen-esm": "esm/bin.js" + }, + "peerDependencies": { + "@parcel/watcher": "^2.1.0", + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + }, + "peerDependenciesMeta": { + "@parcel/watcher": { + "optional": true + } + } + }, + "node_modules/@graphql-codegen/cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@graphql-codegen/cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@graphql-codegen/cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@graphql-codegen/cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@graphql-codegen/cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@graphql-codegen/cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@graphql-codegen/client-preset": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/client-preset/-/client-preset-4.1.0.tgz", + "integrity": "sha512-/3Ymb/fjxIF1+HGmaI1YwSZbWsrZAWMSQjh3dU425eBjctjsVQ6gzGRr+l/gE5F1mtmCf+vlbTAT03heAc/QIw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/template": "^7.20.7", + "@graphql-codegen/add": "^5.0.0", + "@graphql-codegen/gql-tag-operations": "4.0.1", + "@graphql-codegen/plugin-helpers": "^5.0.1", + "@graphql-codegen/typed-document-node": "^5.0.1", + "@graphql-codegen/typescript": "^4.0.1", + "@graphql-codegen/typescript-operations": "^4.0.1", + "@graphql-codegen/visitor-plugin-common": "^4.0.1", + "@graphql-tools/documents": "^1.0.0", + "@graphql-tools/utils": "^10.0.0", + "@graphql-typed-document-node/core": "3.2.0", + "tslib": "~2.5.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/client-preset/node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", + "dev": true + }, + "node_modules/@graphql-codegen/core": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/core/-/core-4.0.0.tgz", + "integrity": "sha512-JAGRn49lEtSsZVxeIlFVIRxts2lWObR+OQo7V2LHDJ7ohYYw3ilv7nJ8pf8P4GTg/w6ptcYdSdVVdkI8kUHB/Q==", + "dev": true, + "dependencies": { + "@graphql-codegen/plugin-helpers": "^5.0.0", + "@graphql-tools/schema": "^10.0.0", + "@graphql-tools/utils": "^10.0.0", + "tslib": "~2.5.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/core/node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", + "dev": true + }, + "node_modules/@graphql-codegen/gql-tag-operations": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/gql-tag-operations/-/gql-tag-operations-4.0.1.tgz", + "integrity": "sha512-qF6wIbBzW8BNT+wiVsBxrYOs2oYcsxQ7mRvCpfEI3HnNZMAST/uX76W8MqFEJvj4mw7NIDv7xYJAcAZIWM5LWw==", + "dev": true, + "dependencies": { + "@graphql-codegen/plugin-helpers": "^5.0.0", + "@graphql-codegen/visitor-plugin-common": "4.0.1", + "@graphql-tools/utils": "^10.0.0", + "auto-bind": "~4.0.0", + "tslib": "~2.5.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/gql-tag-operations/node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", + "dev": true + }, + "node_modules/@graphql-codegen/introspection": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/introspection/-/introspection-4.0.0.tgz", + "integrity": "sha512-t9g3AkK99dfHblMWtG4ynUM9+A7JrWq5110zSpNV2wlSnv0+bRKagDW8gozwgXfR5i1IIG8QDjJZ6VgXQVqCZw==", + "dev": true, + "dependencies": { + "@graphql-codegen/plugin-helpers": "^5.0.0", + "@graphql-codegen/visitor-plugin-common": "^4.0.0", + "tslib": "~2.5.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/introspection/node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", + "dev": true + }, + "node_modules/@graphql-codegen/plugin-helpers": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-5.0.1.tgz", + "integrity": "sha512-6L5sb9D8wptZhnhLLBcheSPU7Tg//DGWgc5tQBWX46KYTOTQHGqDpv50FxAJJOyFVJrveN9otWk9UT9/yfY4ww==", + "dev": true, + "dependencies": { + "@graphql-tools/utils": "^10.0.0", + "change-case-all": "1.0.15", + "common-tags": "1.8.2", + "import-from": "4.0.0", + "lodash": "~4.17.0", + "tslib": "~2.5.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/plugin-helpers/node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", + "dev": true + }, + "node_modules/@graphql-codegen/schema-ast": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/schema-ast/-/schema-ast-4.0.0.tgz", + "integrity": "sha512-WIzkJFa9Gz28FITAPILbt+7A8+yzOyd1NxgwFh7ie+EmO9a5zQK6UQ3U/BviirguXCYnn+AR4dXsoDrSrtRA1g==", + "dev": true, + "dependencies": { + "@graphql-codegen/plugin-helpers": "^5.0.0", + "@graphql-tools/utils": "^10.0.0", + "tslib": "~2.5.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/schema-ast/node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", + "dev": true + }, + "node_modules/@graphql-codegen/typed-document-node": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typed-document-node/-/typed-document-node-5.0.1.tgz", + "integrity": "sha512-VFkhCuJnkgtbbgzoCAwTdJe2G1H6sd3LfCrDqWUrQe53y2ukfSb5Ov1PhAIkCBStKCMQBUY9YgGz9GKR40qQ8g==", + "dev": true, + "dependencies": { + "@graphql-codegen/plugin-helpers": "^5.0.0", + "@graphql-codegen/visitor-plugin-common": "4.0.1", + "auto-bind": "~4.0.0", + "change-case-all": "1.0.15", + "tslib": "~2.5.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/typed-document-node/node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", + "dev": true + }, + "node_modules/@graphql-codegen/typescript": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-4.0.1.tgz", + "integrity": "sha512-3YziQ21dCVdnHb+Us1uDb3pA6eG5Chjv0uTK+bt9dXeMlwYBU8MbtzvQTo4qvzWVC1AxSOKj0rgfNu1xCXqJyA==", + "dev": true, + "dependencies": { + "@graphql-codegen/plugin-helpers": "^5.0.0", + "@graphql-codegen/schema-ast": "^4.0.0", + "@graphql-codegen/visitor-plugin-common": "4.0.1", + "auto-bind": "~4.0.0", + "tslib": "~2.5.0" + }, + "peerDependencies": { + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/typescript-operations": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-4.0.1.tgz", + "integrity": "sha512-GpUWWdBVUec/Zqo23aFLBMrXYxN2irypHqDcKjN78JclDPdreasAEPcIpMfqf4MClvpmvDLy4ql+djVAwmkjbw==", + "dev": true, + "dependencies": { + "@graphql-codegen/plugin-helpers": "^5.0.0", + "@graphql-codegen/typescript": "^4.0.1", + "@graphql-codegen/visitor-plugin-common": "4.0.1", + "auto-bind": "~4.0.0", + "tslib": "~2.5.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/typescript-operations/node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", + "dev": true + }, + "node_modules/@graphql-codegen/typescript/node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", + "dev": true + }, + "node_modules/@graphql-codegen/visitor-plugin-common": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-4.0.1.tgz", + "integrity": "sha512-Bi/1z0nHg4QMsAqAJhds+ForyLtk7A3HQOlkrZNm3xEkY7lcBzPtiOTLBtvziwopBsXUxqeSwVjOOFPLS5Yw1Q==", + "dev": true, + "dependencies": { + "@graphql-codegen/plugin-helpers": "^5.0.0", + "@graphql-tools/optimize": "^2.0.0", + "@graphql-tools/relay-operation-optimizer": "^7.0.0", + "@graphql-tools/utils": "^10.0.0", + "auto-bind": "~4.0.0", + "change-case-all": "1.0.15", + "dependency-graph": "^0.11.0", + "graphql-tag": "^2.11.0", + "parse-filepath": "^1.0.2", + "tslib": "~2.5.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/visitor-plugin-common/node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", + "dev": true + }, + "node_modules/@graphql-tools/apollo-engine-loader": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/apollo-engine-loader/-/apollo-engine-loader-8.0.0.tgz", + "integrity": "sha512-axQTbN5+Yxs1rJ6cWQBOfw3AEeC+fvIuZSfJLPLLvFJLj4pUm9fhxey/g6oQZAAQJqKPfw+tLDUQvnfvRK8Kmg==", + "dev": true, + "dependencies": { + "@ardatan/sync-fetch": "^0.0.1", + "@graphql-tools/utils": "^10.0.0", + "@whatwg-node/fetch": "^0.9.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/apollo-engine-loader/node_modules/@whatwg-node/events": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", + "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-tools/apollo-engine-loader/node_modules/@whatwg-node/fetch": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.9.tgz", + "integrity": "sha512-OTVoDm039CNyAWSRc2WBimMl/N9J4Fk2le21Xzcf+3OiWPNNSIbMnpWKBUyraPh2d9SAEgoBdQxTfVNihXgiUw==", + "dev": true, + "dependencies": { + "@whatwg-node/node-fetch": "^0.4.8", + "urlpattern-polyfill": "^9.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-tools/apollo-engine-loader/node_modules/@whatwg-node/node-fetch": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.4.14.tgz", + "integrity": "sha512-ii/eZz2PcjLGj9D6WfsmfzlTzZV1Kz6MxYpq2Vc5P21J8vkKfENWC9B2ISsFCKovxElLukIwPg8HTrHFsLNflg==", + "dev": true, + "dependencies": { + "@whatwg-node/events": "^0.1.0", + "busboy": "^1.6.0", + "fast-querystring": "^1.1.1", + "fast-url-parser": "^1.1.3", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-tools/apollo-engine-loader/node_modules/urlpattern-polyfill": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-9.0.0.tgz", + "integrity": "sha512-WHN8KDQblxd32odxeIgo83rdVDE2bvdkb86it7bMhYZwWKJz0+O0RK/eZiHYnM+zgt/U7hAHOlCQGfjjvSkw2g==", + "dev": true + }, + "node_modules/@graphql-tools/batch-execute": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/batch-execute/-/batch-execute-9.0.2.tgz", + "integrity": "sha512-Y2uwdZI6ZnatopD/SYfZ1eGuQFI7OU2KGZ2/B/7G9ISmgMl5K+ZZWz/PfIEXeiHirIDhyk54s4uka5rj2xwKqQ==", + "dev": true, + "dependencies": { + "@graphql-tools/utils": "^10.0.5", + "dataloader": "^2.2.2", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/code-file-loader": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-8.0.2.tgz", + "integrity": "sha512-AKNpkElUL2cWocYpC4DzNEpo6qJw8Lp+L3bKQ/mIfmbsQxgLz5uve6zHBMhDaFPdlwfIox41N3iUSvi77t9e8A==", + "dev": true, + "dependencies": { + "@graphql-tools/graphql-tag-pluck": "8.0.2", + "@graphql-tools/utils": "^10.0.0", + "globby": "^11.0.3", + "tslib": "^2.4.0", + "unixify": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/delegate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-10.0.2.tgz", + "integrity": "sha512-ZU7VnR2xFgHrGnsuw6+nRJkcvSucn7w5ooxb/lTKlVfrNJfTwJevNcNKMnbtPUSajG3+CaFym/nU6v44GXCmNw==", + "dev": true, + "dependencies": { + "@graphql-tools/batch-execute": "^9.0.1", + "@graphql-tools/executor": "^1.0.0", + "@graphql-tools/schema": "^10.0.0", + "@graphql-tools/utils": "^10.0.5", + "dataloader": "^2.2.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/documents": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/documents/-/documents-1.0.0.tgz", + "integrity": "sha512-rHGjX1vg/nZ2DKqRGfDPNC55CWZBMldEVcH+91BThRa6JeT80NqXknffLLEZLRUxyikCfkwMsk6xR3UNMqG0Rg==", + "dev": true, + "dependencies": { + "lodash.sortby": "^4.7.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/executor": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-1.2.0.tgz", + "integrity": "sha512-SKlIcMA71Dha5JnEWlw4XxcaJ+YupuXg0QCZgl2TOLFz4SkGCwU/geAsJvUJFwK2RbVLpQv/UMq67lOaBuwDtg==", + "dev": true, + "dependencies": { + "@graphql-tools/utils": "^10.0.0", + "@graphql-typed-document-node/core": "3.2.0", + "@repeaterjs/repeater": "^3.0.4", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/executor-graphql-ws": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-graphql-ws/-/executor-graphql-ws-1.1.0.tgz", + "integrity": "sha512-yM67SzwE8rYRpm4z4AuGtABlOp9mXXVy6sxXnTJRoYIdZrmDbKVfIY+CpZUJCqS0FX3xf2+GoHlsj7Qswaxgcg==", + "dev": true, + "dependencies": { + "@graphql-tools/utils": "^10.0.2", + "@types/ws": "^8.0.0", + "graphql-ws": "^5.14.0", + "isomorphic-ws": "^5.0.0", + "tslib": "^2.4.0", + "ws": "^8.13.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/executor-http": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-http/-/executor-http-1.0.2.tgz", + "integrity": "sha512-JKTB4E3kdQM2/1NEcyrVPyQ8057ZVthCV5dFJiKktqY9IdmF00M8gupFcW3jlbM/Udn78ickeUBsUzA3EouqpA==", + "dev": true, + "dependencies": { + "@graphql-tools/utils": "^10.0.2", + "@repeaterjs/repeater": "^3.0.4", + "@whatwg-node/fetch": "^0.9.0", + "extract-files": "^11.0.0", + "meros": "^1.2.1", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/executor-http/node_modules/@whatwg-node/events": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", + "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-tools/executor-http/node_modules/@whatwg-node/fetch": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.9.tgz", + "integrity": "sha512-OTVoDm039CNyAWSRc2WBimMl/N9J4Fk2le21Xzcf+3OiWPNNSIbMnpWKBUyraPh2d9SAEgoBdQxTfVNihXgiUw==", + "dev": true, + "dependencies": { + "@whatwg-node/node-fetch": "^0.4.8", + "urlpattern-polyfill": "^9.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-tools/executor-http/node_modules/@whatwg-node/node-fetch": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.4.14.tgz", + "integrity": "sha512-ii/eZz2PcjLGj9D6WfsmfzlTzZV1Kz6MxYpq2Vc5P21J8vkKfENWC9B2ISsFCKovxElLukIwPg8HTrHFsLNflg==", + "dev": true, + "dependencies": { + "@whatwg-node/events": "^0.1.0", + "busboy": "^1.6.0", + "fast-querystring": "^1.1.1", + "fast-url-parser": "^1.1.3", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-tools/executor-http/node_modules/urlpattern-polyfill": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-9.0.0.tgz", + "integrity": "sha512-WHN8KDQblxd32odxeIgo83rdVDE2bvdkb86it7bMhYZwWKJz0+O0RK/eZiHYnM+zgt/U7hAHOlCQGfjjvSkw2g==", + "dev": true + }, + "node_modules/@graphql-tools/executor-legacy-ws": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-legacy-ws/-/executor-legacy-ws-1.0.1.tgz", + "integrity": "sha512-PQrTJ+ncHMEQspBARc2lhwiQFfRAX/z/CsOdZTFjIljOHgRWGAA1DAx7pEN0j6PflbLCfZ3NensNq2jCBwF46w==", + "dev": true, + "dependencies": { + "@graphql-tools/utils": "^10.0.0", + "@types/ws": "^8.0.0", + "isomorphic-ws": "5.0.0", + "tslib": "^2.4.0", + "ws": "8.13.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/git-loader": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/git-loader/-/git-loader-8.0.2.tgz", + "integrity": "sha512-AuCB0nlPvsHh8u42zRZdlD/ZMaWP9A44yAkQUVCZir1E/LG63fsZ9svTWJ+CbusW3Hd0ZP9qpxEhlHxnd4Tlsg==", + "dev": true, + "dependencies": { + "@graphql-tools/graphql-tag-pluck": "8.0.2", + "@graphql-tools/utils": "^10.0.0", + "is-glob": "4.0.3", + "micromatch": "^4.0.4", + "tslib": "^2.4.0", + "unixify": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/github-loader": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/github-loader/-/github-loader-8.0.0.tgz", + "integrity": "sha512-VuroArWKcG4yaOWzV0r19ElVIV6iH6UKDQn1MXemND0xu5TzrFme0kf3U9o0YwNo0kUYEk9CyFM0BYg4he17FA==", + "dev": true, + "dependencies": { + "@ardatan/sync-fetch": "^0.0.1", + "@graphql-tools/executor-http": "^1.0.0", + "@graphql-tools/graphql-tag-pluck": "^8.0.0", + "@graphql-tools/utils": "^10.0.0", + "@whatwg-node/fetch": "^0.9.0", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/github-loader/node_modules/@whatwg-node/events": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", + "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-tools/github-loader/node_modules/@whatwg-node/fetch": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.9.tgz", + "integrity": "sha512-OTVoDm039CNyAWSRc2WBimMl/N9J4Fk2le21Xzcf+3OiWPNNSIbMnpWKBUyraPh2d9SAEgoBdQxTfVNihXgiUw==", + "dev": true, + "dependencies": { + "@whatwg-node/node-fetch": "^0.4.8", + "urlpattern-polyfill": "^9.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-tools/github-loader/node_modules/@whatwg-node/node-fetch": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.4.14.tgz", + "integrity": "sha512-ii/eZz2PcjLGj9D6WfsmfzlTzZV1Kz6MxYpq2Vc5P21J8vkKfENWC9B2ISsFCKovxElLukIwPg8HTrHFsLNflg==", + "dev": true, + "dependencies": { + "@whatwg-node/events": "^0.1.0", + "busboy": "^1.6.0", + "fast-querystring": "^1.1.1", + "fast-url-parser": "^1.1.3", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-tools/github-loader/node_modules/urlpattern-polyfill": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-9.0.0.tgz", + "integrity": "sha512-WHN8KDQblxd32odxeIgo83rdVDE2bvdkb86it7bMhYZwWKJz0+O0RK/eZiHYnM+zgt/U7hAHOlCQGfjjvSkw2g==", + "dev": true + }, + "node_modules/@graphql-tools/graphql-file-loader": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-8.0.0.tgz", + "integrity": "sha512-wRXj9Z1IFL3+zJG1HWEY0S4TXal7+s1vVhbZva96MSp0kbb/3JBF7j0cnJ44Eq0ClccMgGCDFqPFXty4JlpaPg==", + "dev": true, + "dependencies": { + "@graphql-tools/import": "7.0.0", + "@graphql-tools/utils": "^10.0.0", + "globby": "^11.0.3", + "tslib": "^2.4.0", + "unixify": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/graphql-tag-pluck": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.0.2.tgz", + "integrity": "sha512-U6fE4yEHxuk/nqmPixHpw1WhqdS6aYuaV60m1bEmUmGJNbpAhaMBy01JncpvpF15yZR5LZ0UjkHg+A3Lhoc8YQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.22.9", + "@babel/parser": "^7.16.8", + "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/traverse": "^7.16.8", + "@babel/types": "^7.16.8", + "@graphql-tools/utils": "^10.0.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/import": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/import/-/import-7.0.0.tgz", + "integrity": "sha512-NVZiTO8o1GZs6OXzNfjB+5CtQtqsZZpQOq+Uu0w57kdUkT4RlQKlwhT8T81arEsbV55KpzkpFsOZP7J1wdmhBw==", + "dev": true, + "dependencies": { + "@graphql-tools/utils": "^10.0.0", + "resolve-from": "5.0.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/import/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@graphql-tools/json-file-loader": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-8.0.0.tgz", + "integrity": "sha512-ki6EF/mobBWJjAAC84xNrFMhNfnUFD6Y0rQMGXekrUgY0NdeYXHU0ZUgHzC9O5+55FslqUmAUHABePDHTyZsLg==", + "dev": true, + "dependencies": { + "@graphql-tools/utils": "^10.0.0", + "globby": "^11.0.3", + "tslib": "^2.4.0", + "unixify": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/load": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-8.0.0.tgz", + "integrity": "sha512-Cy874bQJH0FP2Az7ELPM49iDzOljQmK1PPH6IuxsWzLSTxwTqd8dXA09dcVZrI7/LsN26heTY2R8q2aiiv0GxQ==", + "dev": true, + "dependencies": { + "@graphql-tools/schema": "^10.0.0", + "@graphql-tools/utils": "^10.0.0", + "p-limit": "3.1.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/merge": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.0.0.tgz", + "integrity": "sha512-J7/xqjkGTTwOJmaJQJ2C+VDBDOWJL3lKrHJN4yMaRLAJH3PosB7GiPRaSDZdErs0+F77sH2MKs2haMMkywzx7Q==", + "dev": true, + "dependencies": { + "@graphql-tools/utils": "^10.0.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/optimize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/optimize/-/optimize-2.0.0.tgz", + "integrity": "sha512-nhdT+CRGDZ+bk68ic+Jw1OZ99YCDIKYA5AlVAnBHJvMawSx9YQqQAIj4refNc1/LRieGiuWvhbG3jvPVYho0Dg==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/prisma-loader": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/prisma-loader/-/prisma-loader-8.0.1.tgz", + "integrity": "sha512-bl6e5sAYe35Z6fEbgKXNrqRhXlCJYeWKBkarohgYA338/SD9eEhXtg3Cedj7fut3WyRLoQFpHzfiwxKs7XrgXg==", + "dev": true, + "dependencies": { + "@graphql-tools/url-loader": "^8.0.0", + "@graphql-tools/utils": "^10.0.0", + "@types/js-yaml": "^4.0.0", + "@types/json-stable-stringify": "^1.0.32", + "@whatwg-node/fetch": "^0.9.0", + "chalk": "^4.1.0", + "debug": "^4.3.1", + "dotenv": "^16.0.0", + "graphql-request": "^6.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "jose": "^4.11.4", + "js-yaml": "^4.0.0", + "json-stable-stringify": "^1.0.1", + "lodash": "^4.17.20", + "scuid": "^1.1.0", + "tslib": "^2.4.0", + "yaml-ast-parser": "^0.0.43" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/prisma-loader/node_modules/@whatwg-node/events": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", + "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-tools/prisma-loader/node_modules/@whatwg-node/fetch": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.9.tgz", + "integrity": "sha512-OTVoDm039CNyAWSRc2WBimMl/N9J4Fk2le21Xzcf+3OiWPNNSIbMnpWKBUyraPh2d9SAEgoBdQxTfVNihXgiUw==", + "dev": true, + "dependencies": { + "@whatwg-node/node-fetch": "^0.4.8", + "urlpattern-polyfill": "^9.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-tools/prisma-loader/node_modules/@whatwg-node/node-fetch": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.4.14.tgz", + "integrity": "sha512-ii/eZz2PcjLGj9D6WfsmfzlTzZV1Kz6MxYpq2Vc5P21J8vkKfENWC9B2ISsFCKovxElLukIwPg8HTrHFsLNflg==", + "dev": true, + "dependencies": { + "@whatwg-node/events": "^0.1.0", + "busboy": "^1.6.0", + "fast-querystring": "^1.1.1", + "fast-url-parser": "^1.1.3", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-tools/prisma-loader/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@graphql-tools/prisma-loader/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@graphql-tools/prisma-loader/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@graphql-tools/prisma-loader/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@graphql-tools/prisma-loader/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@graphql-tools/prisma-loader/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@graphql-tools/prisma-loader/node_modules/urlpattern-polyfill": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-9.0.0.tgz", + "integrity": "sha512-WHN8KDQblxd32odxeIgo83rdVDE2bvdkb86it7bMhYZwWKJz0+O0RK/eZiHYnM+zgt/U7hAHOlCQGfjjvSkw2g==", + "dev": true + }, + "node_modules/@graphql-tools/relay-operation-optimizer": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-7.0.0.tgz", + "integrity": "sha512-UNlJi5y3JylhVWU4MBpL0Hun4Q7IoJwv9xYtmAz+CgRa066szzY7dcuPfxrA7cIGgG/Q6TVsKsYaiF4OHPs1Fw==", + "dev": true, + "dependencies": { + "@ardatan/relay-compiler": "12.0.0", + "@graphql-tools/utils": "^10.0.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/schema": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.0.tgz", + "integrity": "sha512-kf3qOXMFcMs2f/S8Y3A8fm/2w+GaHAkfr3Gnhh2LOug/JgpY/ywgFVxO3jOeSpSEdoYcDKLcXVjMigNbY4AdQg==", + "dev": true, + "dependencies": { + "@graphql-tools/merge": "^9.0.0", + "@graphql-tools/utils": "^10.0.0", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/url-loader": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-8.0.0.tgz", + "integrity": "sha512-rPc9oDzMnycvz+X+wrN3PLrhMBQkG4+sd8EzaFN6dypcssiefgWKToXtRKI8HHK68n2xEq1PyrOpkjHFJB+GwA==", + "dev": true, + "dependencies": { + "@ardatan/sync-fetch": "^0.0.1", + "@graphql-tools/delegate": "^10.0.0", + "@graphql-tools/executor-graphql-ws": "^1.0.0", + "@graphql-tools/executor-http": "^1.0.0", + "@graphql-tools/executor-legacy-ws": "^1.0.0", + "@graphql-tools/utils": "^10.0.0", + "@graphql-tools/wrap": "^10.0.0", + "@types/ws": "^8.0.0", + "@whatwg-node/fetch": "^0.9.0", + "isomorphic-ws": "^5.0.0", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.11", + "ws": "^8.12.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/url-loader/node_modules/@whatwg-node/events": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", + "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-tools/url-loader/node_modules/@whatwg-node/fetch": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.9.tgz", + "integrity": "sha512-OTVoDm039CNyAWSRc2WBimMl/N9J4Fk2le21Xzcf+3OiWPNNSIbMnpWKBUyraPh2d9SAEgoBdQxTfVNihXgiUw==", + "dev": true, + "dependencies": { + "@whatwg-node/node-fetch": "^0.4.8", + "urlpattern-polyfill": "^9.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-tools/url-loader/node_modules/@whatwg-node/node-fetch": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.4.14.tgz", + "integrity": "sha512-ii/eZz2PcjLGj9D6WfsmfzlTzZV1Kz6MxYpq2Vc5P21J8vkKfENWC9B2ISsFCKovxElLukIwPg8HTrHFsLNflg==", + "dev": true, + "dependencies": { + "@whatwg-node/events": "^0.1.0", + "busboy": "^1.6.0", + "fast-querystring": "^1.1.1", + "fast-url-parser": "^1.1.3", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-tools/url-loader/node_modules/urlpattern-polyfill": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-9.0.0.tgz", + "integrity": "sha512-WHN8KDQblxd32odxeIgo83rdVDE2bvdkb86it7bMhYZwWKJz0+O0RK/eZiHYnM+zgt/U7hAHOlCQGfjjvSkw2g==", + "dev": true + }, + "node_modules/@graphql-tools/utils": { + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.0.5.tgz", + "integrity": "sha512-ZTioQqg9z9eCG3j+KDy54k1gp6wRIsLqkx5yi163KVvXVkfjsrdErCyZjrEug21QnKE9piP4tyxMpMMOT1RuRw==", + "dev": true, + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "dset": "^3.1.2", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/wrap": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-10.0.0.tgz", + "integrity": "sha512-HDOeUUh6UhpiH0WPJUQl44ODt1x5pnMUbOJZ7GjTdGQ7LK0AgVt3ftaAQ9duxLkiAtYJmu5YkULirfZGj4HzDg==", + "dev": true, + "dependencies": { + "@graphql-tools/delegate": "^10.0.0", + "@graphql-tools/schema": "^10.0.0", + "@graphql-tools/utils": "^10.0.0", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mapbox/geojson-rewind": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", + "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==", + "dependencies": { + "get-stream": "^6.0.1", + "minimist": "^1.2.6" + }, + "bin": { + "geojson-rewind": "geojson-rewind" + } + }, + "node_modules/@mapbox/jsonlint-lines-primitives": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mapbox/point-geometry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", + "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==" + }, + "node_modules/@mapbox/tiny-sdf": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz", + "integrity": "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==" + }, + "node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==" + }, + "node_modules/@mapbox/vector-tile": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", + "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", + "dependencies": { + "@mapbox/point-geometry": "~0.1.0" + } + }, + "node_modules/@mapbox/whoots-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", + "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", + "engines": { + "node": ">=6.0.0" + } + }, + "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==", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^3.0.0", + "minimist": "^1.2.8", + "rw": "^1.3.3", + "sort-object": "^3.0.3" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.3.0.tgz", + "integrity": "sha512-pW7QaFiL11O0BphO+bq3MgqeX/INAk9jgBldVDYjlQPO4VddoZnF22TcF9onMhnLVHuNqBJeRf+Fj7eezi/+rQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.3.0", + "@parcel/watcher-darwin-arm64": "2.3.0", + "@parcel/watcher-darwin-x64": "2.3.0", + "@parcel/watcher-freebsd-x64": "2.3.0", + "@parcel/watcher-linux-arm-glibc": "2.3.0", + "@parcel/watcher-linux-arm64-glibc": "2.3.0", + "@parcel/watcher-linux-arm64-musl": "2.3.0", + "@parcel/watcher-linux-x64-glibc": "2.3.0", + "@parcel/watcher-linux-x64-musl": "2.3.0", + "@parcel/watcher-win32-arm64": "2.3.0", + "@parcel/watcher-win32-ia32": "2.3.0", + "@parcel/watcher-win32-x64": "2.3.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.3.0.tgz", + "integrity": "sha512-f4o9eA3dgk0XRT3XhB0UWpWpLnKgrh1IwNJKJ7UJek7eTYccQ8LR7XUWFKqw6aEq5KUNlCcGvSzKqSX/vtWVVA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.3.0.tgz", + "integrity": "sha512-mKY+oijI4ahBMc/GygVGvEdOq0L4DxhYgwQqYAz/7yPzuGi79oXrZG52WdpGA1wLBPrYb0T8uBaGFo7I6rvSKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.3.0.tgz", + "integrity": "sha512-20oBj8LcEOnLE3mgpy6zuOq8AplPu9NcSSSfyVKgfOhNAc4eF4ob3ldj0xWjGGbOF7Dcy1Tvm6ytvgdjlfUeow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.3.0.tgz", + "integrity": "sha512-7LftKlaHunueAEiojhCn+Ef2CTXWsLgTl4hq0pkhkTBFI3ssj2bJXmH2L67mKpiAD5dz66JYk4zS66qzdnIOgw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.3.0.tgz", + "integrity": "sha512-1apPw5cD2xBv1XIHPUlq0cO6iAaEUQ3BcY0ysSyD9Kuyw4MoWm1DV+W9mneWI+1g6OeP6dhikiFE6BlU+AToTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.3.0.tgz", + "integrity": "sha512-mQ0gBSQEiq1k/MMkgcSB0Ic47UORZBmWoAWlMrTW6nbAGoLZP+h7AtUM7H3oDu34TBFFvjy4JCGP43JlylkTQA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.3.0.tgz", + "integrity": "sha512-LXZAExpepJew0Gp8ZkJ+xDZaTQjLHv48h0p0Vw2VMFQ8A+RKrAvpFuPVCVwKJCr5SE+zvaG+Etg56qXvTDIedw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.3.0.tgz", + "integrity": "sha512-P7Wo91lKSeSgMTtG7CnBS6WrA5otr1K7shhSjKHNePVmfBHDoAOHYRXgUmhiNfbcGk0uMCHVcdbfxtuiZCHVow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.3.0.tgz", + "integrity": "sha512-+kiRE1JIq8QdxzwoYY+wzBs9YbJ34guBweTK8nlzLKimn5EQ2b2FSC+tAOpq302BuIMjyuUGvBiUhEcLIGMQ5g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.3.0.tgz", + "integrity": "sha512-35gXCnaz1AqIXpG42evcoP2+sNL62gZTMZne3IackM+6QlfMcJLy3DrjuL6Iks7Czpd3j4xRBzez3ADCj1l7Aw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.3.0.tgz", + "integrity": "sha512-FJS/IBQHhRpZ6PiCjFt1UAcPr0YmCLHRbTc00IBTrelEjlmmgIVLeOx4MSXzx2HFEy5Jo5YdhGpxCuqCyDJ5ow==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.3.0.tgz", + "integrity": "sha512-dLx+0XRdMnVI62kU3wbXvbIRhLck4aE28bIGKbRGS7BJNt54IIj9+c/Dkqb+7DJEbHUZAX1bwaoM8PqVlHJmCA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.6.tgz", + "integrity": "sha512-izNRxPoaeJeg/AyH8hER6s+H7p4itk+03QCa4sbxI3lNdseQYCuxzgsuNK8bTXChtLTjpJz6NmXKA73qLa3rCA==", + "dev": true, + "dependencies": { + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "dev": true, + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.4.3.tgz", + "integrity": "sha512-VtaY4spKTdN5LjJ04im/d/joXuvLbQdgy5Z4DXF4MFZhQ+MTrejbNMkfZBp1Bs3O5+bFqnJgyGdPuZQflvIa5A==", + "dev": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.6", + "@peculiar/json-schema": "^1.1.12", + "pvtsutils": "^1.3.2", + "tslib": "^2.5.0", + "webcrypto-core": "^1.7.7" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.7.1.tgz", + "integrity": "sha512-ovVPSD1WlRpZHt7GI9DqJrWG3OIYS+NXQ9y5HIewMJpSe+jPQmMQfyRmgX4EnvmxSlp0u04Wg/7oItcoSIb/RA==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@repeaterjs/repeater": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.4.tgz", + "integrity": "sha512-AW8PKd6iX3vAZ0vA43nOUOnbq/X5ihgU+mSXXqunMkeQADGiqw/PY0JNeYtD5sr0PAy51YPgAPbDoeapv9r8WA==", + "dev": true + }, + "node_modules/@restart/hooks": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.11.tgz", + "integrity": "sha512-Ft/ncTULZN6ldGHiF/k5qt72O8JyRMOeg0tApvCni8LkoiEahO+z3TNxfXIVGy890YtWVDvJAl662dVJSJXvMw==", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.6.6.tgz", + "integrity": "sha512-eC3puKuWE1SRYbojWHXnvCNHGgf3uzHCb6JOhnF4OXPibOIPEkR1sqDSkL643ydigxwh+ruCa1CmYHlzk7ikKA==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@popperjs/core": "^2.11.6", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.4.9", + "@types/warning": "^3.0.0", + "dequal": "^2.0.3", + "dom-helpers": "^5.2.0", + "uncontrollable": "^8.0.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, + "node_modules/@restart/ui/node_modules/uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "peerDependencies": { + "react": ">=16.14.0" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz", + "integrity": "sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.10", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==" + }, + "node_modules/@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "node_modules/@types/json-stable-stringify": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/@types/json-stable-stringify/-/json-stable-stringify-1.0.34.tgz", + "integrity": "sha512-s2cfwagOQAS8o06TcwKfr9Wx11dNGbH2E9vJz1cqV+a/LOyhWNLUNd6JSRYNzvB4d29UuJX2M0Dj9vE1T8fRXw==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "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==" + }, + "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==", + "dependencies": { + "@types/geojson": "*", + "@types/mapbox__point-geometry": "*", + "@types/pbf": "*" + } + }, + "node_modules/@types/mapbox-gl": { + "version": "2.7.13", + "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-2.7.13.tgz", + "integrity": "sha512-qNffhTdYkeFl8QG9Q1zPPJmcs8PvHgmLa1PcwP1rxvcfMsIgcFr/FnrCttG0ZnH7Kzdd7xfECSRNTWSr4jC3PQ==", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/node": { + "version": "20.5.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", + "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", + "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==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "node_modules/@types/react": { + "version": "18.2.21", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz", + "integrity": "sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", + "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", + "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" + }, + "node_modules/@types/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", + "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==", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", + "integrity": "sha512-t/Tvs5qR47OLOr+4E9ckN8AmP2Tf16gWq+/qA4iUGS/OOyHVO8wv2vjJuX8SNOUTJyWb+2t7wJm6cXILFnOROA==" + }, + "node_modules/@types/ws": { + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", + "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.5.0.tgz", + "integrity": "sha512-2pktILyjvMaScU6iK3925uvGU87E+N9rh372uGZgiMYwafaw9SXq86U04XPq3UH6tzRvNgBsub6x2DacHc33lw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.5.0", + "@typescript-eslint/type-utils": "6.5.0", + "@typescript-eslint/utils": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.5.0.tgz", + "integrity": "sha512-LMAVtR5GN8nY0G0BadkG0XIe4AcNMeyEy3DyhKGAh9k4pLSMBO7rF29JvDBpZGCmp5Pgz5RLHP6eCpSYZJQDuQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.5.0", + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/typescript-estree": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.5.0.tgz", + "integrity": "sha512-A8hZ7OlxURricpycp5kdPTH3XnjG85UpJS6Fn4VzeoH4T388gQJ/PGP4ole5NfKt4WDVhmLaQ/dBLNDC4Xl/Kw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.5.0.tgz", + "integrity": "sha512-f7OcZOkRivtujIBQ4yrJNIuwyCQO1OjocVqntl9dgSIZAdKqicj3xFDqDOzHDlGCZX990LqhLQXWRnQvsapq8A==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.5.0", + "@typescript-eslint/utils": "6.5.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.5.0.tgz", + "integrity": "sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.5.0.tgz", + "integrity": "sha512-q0rGwSe9e5Kk/XzliB9h2LBc9tmXX25G0833r7kffbl5437FPWb2tbpIV9wAATebC/018pGa9fwPDuvGN+LxWQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.5.0.tgz", + "integrity": "sha512-9nqtjkNykFzeVtt9Pj6lyR9WEdd8npPhhIPM992FWVkZuS6tmxHfGVnlUcjpUP2hv8r4w35nT33mlxd+Be1ACQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.5.0", + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/typescript-estree": "6.5.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.5.0.tgz", + "integrity": "sha512-yCB/2wkbv3hPsh02ZS8dFQnij9VVQXJMN/gbQsaaY+zxALkZnxa/wagvLEFsAWMPv7d7lxQmNsIzGU1w/T/WyA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.5.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.4.tgz", + "integrity": "sha512-7wU921ABnNYkETiMaZy7XqpueMnpu5VxvVps13MjmCo+utBdD79sZzrApHawHtVX66cCJQQTXFcjH0y9dSUK8g==", + "dev": true, + "dependencies": { + "@babel/core": "^7.22.9", + "@babel/plugin-transform-react-jsx-self": "^7.22.5", + "@babel/plugin-transform-react-jsx-source": "^7.22.5", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0" + } + }, + "node_modules/@whatwg-node/events": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.0.3.tgz", + "integrity": "sha512-IqnKIDWfXBJkvy/k6tzskWTc2NK3LcqHlb+KHGCrjOCH4jfQckRX0NAiIcC/vIqQkzLYw2r2CTSwAxcrtcD6lA==", + "dev": true + }, + "node_modules/@whatwg-node/fetch": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.8.8.tgz", + "integrity": "sha512-CdcjGC2vdKhc13KKxgsc6/616BQ7ooDIgPeTuAiE8qfCnS0mGzcfCOoZXypQSz73nxI+GWc7ZReIAVhxoE1KCg==", + "dev": true, + "dependencies": { + "@peculiar/webcrypto": "^1.4.0", + "@whatwg-node/node-fetch": "^0.3.6", + "busboy": "^1.6.0", + "urlpattern-polyfill": "^8.0.0", + "web-streams-polyfill": "^3.2.1" + } + }, + "node_modules/@whatwg-node/node-fetch": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.3.6.tgz", + "integrity": "sha512-w9wKgDO4C95qnXZRwZTfCmLWqyRnooGjcIwG0wADWjw9/HN0p7dtvtgSvItZtUyNteEvgTrd8QojNEqV6DAGTA==", + "dev": true, + "dependencies": { + "@whatwg-node/events": "^0.0.3", + "busboy": "^1.6.0", + "fast-querystring": "^1.1.1", + "fast-url-parser": "^1.1.3", + "tslib": "^2.3.1" + } + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", + "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", + "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", + "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "node_modules/asn1js": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", + "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "dev": true, + "dependencies": { + "pvtsutils": "^1.3.2", + "pvutils": "^1.1.3", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", + "dev": true + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asynciterator.prototype": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", + "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + } + }, + "node_modules/auto-bind": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-4.0.0.tgz", + "integrity": "sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz", + "integrity": "sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", + "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/babel-plugin-syntax-trailing-function-commas": { + "version": "7.0.0-beta.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz", + "integrity": "sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==", + "dev": true + }, + "node_modules/babel-preset-fbjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz", + "integrity": "sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow==", + "dev": true, + "dependencies": { + "@babel/plugin-proposal-class-properties": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/plugin-syntax-class-properties": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-block-scoped-functions": "^7.0.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.0.0", + "@babel/plugin-transform-flow-strip-types": "^7.0.0", + "@babel/plugin-transform-for-of": "^7.0.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-member-expression-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-object-super": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-property-literals": "^7.0.0", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-template-literals": "^7.0.0", + "babel-plugin-syntax-trailing-function-commas": "^7.0.0-beta.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bootstrap": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.1.tgz", + "integrity": "sha512-jzwza3Yagduci2x0rr9MeFSORjcHpt0lRZukZPZQJT1Dth5qzV7XcgGqYzi39KGAVYR8QEDVoO0ubFKOxzMG+g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.10", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", + "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001517", + "electron-to-chromium": "^1.4.477", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.11" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dev": true, + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytewise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bytewise/-/bytewise-1.1.0.tgz", + "integrity": "sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==", + "dependencies": { + "bytewise-core": "^1.2.2", + "typewise": "^1.0.3" + } + }, + "node_modules/bytewise-core": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bytewise-core/-/bytewise-core-1.2.3.tgz", + "integrity": "sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==", + "dependencies": { + "typewise-core": "^1.2" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001524", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001524.tgz", + "integrity": "sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/change-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", + "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "dev": true, + "dependencies": { + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/change-case-all": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/change-case-all/-/change-case-all-1.0.15.tgz", + "integrity": "sha512-3+GIFhk3sNuvFAJKU46o26OdzudQlPNBCu1ZQi3cMeMHhty1bhDxu2WrEilVNYaGvqUtR1VSigFcJOiS13dRhQ==", + "dev": true, + "dependencies": { + "change-case": "^4.1.2", + "is-lower-case": "^2.0.2", + "is-upper-case": "^2.0.2", + "lower-case": "^2.0.2", + "lower-case-first": "^2.0.2", + "sponge-case": "^1.0.1", + "swap-case": "^2.0.2", + "title-case": "^3.0.3", + "upper-case": "^2.0.2", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.0.tgz", + "integrity": "sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cliui/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/constant-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", + "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", + "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", + "dev": true, + "dependencies": { + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + } + }, + "node_modules/cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, + "node_modules/dataloader": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.2.tgz", + "integrity": "sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==", + "dev": true + }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/dset": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz", + "integrity": "sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.504", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.504.tgz", + "integrity": "sha512-cSMwIAd8yUh54VwitVRVvHK66QqHWE39C3DRj8SWiXitEpVSY3wNPD9y1pxQtLIi4w3UdzF9klLsmuPshz09DQ==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", + "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.1", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.1", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.0", + "safe-array-concat": "^1.0.0", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.14.tgz", + "integrity": "sha512-JgtVnwiuoRuzLvqelrvN3Xu7H9bu2ap/kQ2CrM62iidP8SKuD99rWU3CJy++s7IVL2qb/AjXPGR/E7i9ngd/Cw==", + "dev": true, + "dependencies": { + "asynciterator.prototype": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-set-tostringtag": "^2.0.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.2.1", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "iterator.prototype": "^1.1.0", + "safe-array-concat": "^1.0.0" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz", + "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.48.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", + "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.28.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz", + "integrity": "sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.findlastindex": "^1.2.2", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.8.0", + "has": "^1.0.3", + "is-core-module": "^2.13.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.6", + "object.groupby": "^1.0.0", + "object.values": "^1.1.6", + "semver": "^6.3.1", + "tsconfig-paths": "^3.14.2" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz", + "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.20.7", + "aria-query": "^5.1.3", + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.6.2", + "axobject-query": "^3.1.1", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.3", + "language-tags": "=1.0.5", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.33.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", + "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.12", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.4", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.8" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.3.tgz", + "integrity": "sha512-Hh0wv8bUNY877+sI0BlCUlsS0TYYQqvzEwJsJJPM2WF4RnTStSnSR3zdJYa2nPOJgg3UghXi54lVyMSmpCalzA==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extract-files": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/extract-files/-/extract-files-11.0.0.tgz", + "integrity": "sha512-FuoE1qtbJ4bBVvv94CC7s0oTnKUGvQs+Rjf1L2SJFfS+HTVVjhPFtehPdQ0JiGPqVNfSSZvL5yzHHQq2Z4WNhQ==", + "dev": true, + "engines": { + "node": "^12.20 || >= 14.13" + }, + "funding": { + "url": "https://github.com/sponsors/jaydenseric" + } + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "dev": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "dev": true, + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-url-parser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", + "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", + "dev": true, + "dependencies": { + "punycode": "^1.3.2" + } + }, + "node_modules/fast-url-parser/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fbjs": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz", + "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==", + "dev": true, + "dependencies": { + "cross-fetch": "^3.1.5", + "fbjs-css-vars": "^1.0.0", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^1.0.35" + } + }, + "node_modules/fbjs-css-vars": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", + "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==", + "dev": true + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", + "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "dev": true, + "dependencies": { + "flatted": "^3.2.7", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/geojson-vt": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", + "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==" + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gl-matrix": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", + "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/graphql": { + "version": "16.8.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.0.tgz", + "integrity": "sha512-0oKGaR+y3qcS5mCu1vb7KG+a89vjn06C7Ihq/dDl3jA+A8B3TKomvi3CiEcVLJQGalbu8F52LxkOym7U5sSfbg==", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/graphql-config": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/graphql-config/-/graphql-config-5.0.2.tgz", + "integrity": "sha512-7TPxOrlbiG0JplSZYCyxn2XQtqVhXomEjXUmWJVSS5ET1nPhOJSsIb/WTwqWhcYX6G0RlHXSj9PLtGTKmxLNGg==", + "dev": true, + "dependencies": { + "@graphql-tools/graphql-file-loader": "^8.0.0", + "@graphql-tools/json-file-loader": "^8.0.0", + "@graphql-tools/load": "^8.0.0", + "@graphql-tools/merge": "^9.0.0", + "@graphql-tools/url-loader": "^8.0.0", + "@graphql-tools/utils": "^10.0.0", + "cosmiconfig": "^8.1.0", + "jiti": "^1.18.2", + "minimatch": "^4.2.3", + "string-env-interpolation": "^1.0.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "cosmiconfig-toml-loader": "^1.0.0", + "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + }, + "peerDependenciesMeta": { + "cosmiconfig-toml-loader": { + "optional": true + } + } + }, + "node_modules/graphql-config/node_modules/minimatch": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.3.tgz", + "integrity": "sha512-lIUdtK5hdofgCTu3aT0sOaHsYR37viUuIc0rwnnDXImbwFRcumyLMeZaM0t0I/fgxS6s6JMfu0rLD1Wz9pv1ng==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/graphql-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-6.1.0.tgz", + "integrity": "sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw==", + "dependencies": { + "@graphql-typed-document-node/core": "^3.2.0", + "cross-fetch": "^3.1.5" + }, + "peerDependencies": { + "graphql": "14 - 16" + } + }, + "node_modules/graphql-tag": { + "version": "2.12.6", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", + "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/graphql-ws": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.14.0.tgz", + "integrity": "sha512-itrUTQZP/TgswR4GSSYuwWUzrE/w5GhbwM2GX3ic2U7aw33jgEsayfIlvaj7/GcIvZgNMzsPTrE5hqPuFUiE5g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "graphql": ">=0.11 <=16" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/header-case": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", + "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", + "dev": true, + "dependencies": { + "capital-case": "^1.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz", + "integrity": "sha512-Eun8zV0kcYS1g19r78osiQLEFIRspRUDd9tIfBCTBPBeMieF/EsJNL8VI3xOIdYRDEkjQnqOYPsZ2DsWsVsFwQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "3.7.6", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz", + "integrity": "sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-4.0.0.tgz", + "integrity": "sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ==", + "dev": true, + "engines": { + "node": ">=12.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/inquirer": { + "version": "8.2.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", + "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^6.0.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/inquirer/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/inquirer/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/inquirer/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-2.0.2.tgz", + "integrity": "sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-2.0.2.tgz", + "integrity": "sha512-44pxmxAvnnAOwBg4tHPnkfvgjPwbc5QIsSstNU+YcJ1ovxVzCWpSGosPJOZh/a1tdl81fbgnLc9LLv+x2ywbPQ==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "dev": true, + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.0.tgz", + "integrity": "sha512-rjuhAk1AJ1fssphHD0IFV6TWL40CwRZ53FrztKx43yk2v6rguBYsY4Bj1VU4HmoMmKwZUlx7mfnhDf9cOp4YTw==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.4", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "has-tostringtag": "^1.0.0", + "reflect.getprototypeof": "^1.0.3" + } + }, + "node_modules/jiti": { + "version": "1.19.3", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.3.tgz", + "integrity": "sha512-5eEbBDQT/jF1xg6l36P+mWGGoH9Spuy0PCdSr2dtWRDGC6ph/w9ZCL4lmESW8f8F7MwT3XKescfP0wnZWAKL9w==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/jose": { + "version": "4.14.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", + "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.2.tgz", + "integrity": "sha512-eunSSaEnxV12z+Z73y/j5N37/In40GK4GmsSy+tEHJMxknvqnA7/djeYtAgW0GsWHUfg+847WJjKaEylk2y09g==", + "dev": true, + "dependencies": { + "jsonify": "^0.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json-stringify-pretty-compact": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz", + "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==" + }, + "node_modules/json-to-pretty-yaml": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/json-to-pretty-yaml/-/json-to-pretty-yaml-1.2.2.tgz", + "integrity": "sha512-rvm6hunfCcqegwYaG5T4yKJWxc9FXFgBVrcTZ4XfSVRwa5HA/Xs+vB/Eo9treYYHCeNM0nrSUr82V/M31Urc7A==", + "dev": true, + "dependencies": { + "remedial": "^1.0.7", + "remove-trailing-spaces": "^1.0.6" + }, + "engines": { + "node": ">= 0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/kdbush": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", + "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==" + }, + "node_modules/keyv": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", + "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "dev": true, + "dependencies": { + "language-subtag-registry": "~0.3.2" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/listr2": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.5.tgz", + "integrity": "sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==", + "dev": true, + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.5.5", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/listr2/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-update/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lower-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case-first/-/lower-case-first-2.0.2.tgz", + "integrity": "sha512-EVm/rR94FJTZi3zefZ82fLWab+GX14LJN4HrWBcuo6Evmsl9hEfnqxgcHCKb9q+mNf6EVdsjx/qucYFIIB84pg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^2.0.6", + "@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", + "earcut": "^2.2.4", + "geojson-vt": "^3.2.1", + "gl-matrix": "^3.4.3", + "global-prefix": "^3.0.0", + "kdbush": "^4.0.2", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^2.0.0", + "quickselect": "^2.0.0", + "supercluster": "^8.0.1", + "tinyqueue": "^2.0.3", + "vt-pbf": "^3.1.3" + }, + "engines": { + "node": ">=16.14.0", + "npm": ">=8.1.0" + }, + "funding": { + "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/meros": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/meros/-/meros-1.3.0.tgz", + "integrity": "sha512-2BNGOimxEz5hmjUG2FwoxCt5HN7BXdaWyFqEwxPTrJzVdABtrL4TiHTcsWSFAxPQ/tOnEaQEJh3qWq71QRMY+w==", + "dev": true, + "engines": { + "node": ">=13" + }, + "peerDependencies": { + "@types/node": ">=13" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==" + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-addon-api": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", + "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==", + "dev": true + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "dev": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", + "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", + "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/object.hasown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", + "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ora/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ora/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", + "dev": true, + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", + "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", + "dev": true, + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pbf": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz", + "integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==", + "dependencies": { + "ieee754": "^1.1.12", + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz", + "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/potpack": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz", + "integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "dependencies": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "peerDependencies": { + "react": ">=0.14.0" + } + }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", + "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==" + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", + "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", + "dev": true, + "dependencies": { + "tslib": "^2.6.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-bootstrap": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.8.0.tgz", + "integrity": "sha512-e/aNtxl0Z2ozrIaR82jr6Zz7ss9GSoaXpQaxmvtDUsTZIq/XalkduR/ZXP6vbQHz2T4syvjA+4FbtwELxxmpww==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.6.3", + "@types/react-transition-group": "^4.4.5", + "classnames": "^2.3.2", + "dom-helpers": "^5.2.1", + "invariant": "^2.2.4", + "prop-types": "^15.8.1", + "prop-types-extra": "^1.1.0", + "react-transition-group": "^4.4.5", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "@types/react": ">=16.14.8", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-map-gl": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-7.1.5.tgz", + "integrity": "sha512-YS2u2cSLlZVGjfa394f0snO6f6ZwZVTKqQwjbq/jj0w7fHU01Mn+Xqvm/Qr7nZChoT3OG7kh8JluDcXeBrDG/g==", + "dependencies": { + "@maplibre/maplibre-gl-style-spec": "^19.2.1", + "@types/mapbox-gl": ">=1.0.0" + }, + "peerDependencies": { + "mapbox-gl": ">=1.13.0", + "maplibre-gl": ">=1.13.0", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + }, + "peerDependenciesMeta": { + "mapbox-gl": { + "optional": true + }, + "maplibre-gl": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.3.tgz", + "integrity": "sha512-TTAOZpkJ2YLxl7mVHWrNo3iDMEkYlva/kgFcXndqMgbo/AZUmmavEkdXV+hXtE4P8xdyEKRzalaFqZVuwIk/Nw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.1", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/relay-runtime": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/relay-runtime/-/relay-runtime-12.0.0.tgz", + "integrity": "sha512-QU6JKr1tMsry22DXNy9Whsq5rmvwr3LSZiiWV/9+DFpuTWvp+WFhobWMc8TC4OjKFfNhEZy7mOiqUAn5atQtug==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.0.0", + "fbjs": "^3.0.0", + "invariant": "^2.2.4" + } + }, + "node_modules/remedial": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/remedial/-/remedial-1.0.8.tgz", + "integrity": "sha512-/62tYiOe6DzS5BqVsNpH/nkGlX45C/Sp6V+NtiN6JQNS1Viay7cWkazmRkrQrdFj2eshDe96SIQNIoMxqhzBOg==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "dev": true + }, + "node_modules/remove-trailing-spaces": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/remove-trailing-spaces/-/remove-trailing-spaces-1.0.8.tgz", + "integrity": "sha512-O3vsMYfWighyFbTd8hk8VaSj9UAGENxAtX+//ugIst2RMk5e03h6RoIS+0ylsFxY1gvmPuAY/PO4It+gPEeySA==", + "dev": true + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", + "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "3.28.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz", + "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-array-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", + "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/scuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/scuid/-/scuid-1.1.0.tgz", + "integrity": "sha512-MuCAyrGZcTLfQoH2XoBlQ8C6bzwN88XT/0slOGz0pn8+gIP85BOAfYa44ZXQUTOwRwPU0QvgU+V+OSajl/59Xg==", + "dev": true + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/sentence-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/signedsource": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/signedsource/-/signedsource-1.0.0.tgz", + "integrity": "sha512-6+eerH9fEnNmi/hyM1DXcRK3pWdoMQtlkQ+ns0ntzunjKqp5i3sKCc80ym8Fib3iaYhdJUOPdhlJWj1tvge2Ww==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/sort-asc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.2.0.tgz", + "integrity": "sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-desc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.2.0.tgz", + "integrity": "sha512-NqZqyvL4VPW+RAxxXnB8gvE1kyikh8+pR+T+CXLksVRN9eiQqkQlPwqWYU0mF9Jm7UnctShlxLyAt1CaBOTL1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-object": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sort-object/-/sort-object-3.0.3.tgz", + "integrity": "sha512-nK7WOY8jik6zaG9CRwZTaD5O7ETWDLZYMM12pqY8htll+7dYeqGfEUPcUBHOpSJg2vJOrvFIY2Dl5cX2ih1hAQ==", + "dependencies": { + "bytewise": "^1.1.0", + "get-value": "^2.0.2", + "is-extendable": "^0.1.1", + "sort-asc": "^0.2.0", + "sort-desc": "^0.2.0", + "union-value": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sponge-case": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sponge-case/-/sponge-case-1.0.1.tgz", + "integrity": "sha512-dblb9Et4DAtiZ5YSUZHLl4XhH4uK80GhAZrVXdN4O2P4gQ40Wa5UIOPUHlA/nFd2PLblBZWUioLMMAVrgpoYcA==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-env-interpolation": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string-env-interpolation/-/string-env-interpolation-1.0.1.tgz", + "integrity": "sha512-78lwMoCcn0nNu8LszbP1UA7g55OeE4v7rCeWnM5B453rnNr4aq+5it3FEYtZrSEiMvHZOZ9Jlqb0OD0M2VInqg==", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.9.tgz", + "integrity": "sha512-6i5hL3MqG/K2G43mWXWgP+qizFW/QH/7kCNN13JrJS5q48FN5IKksLDscexKP3dnmB6cdm9jlNgAsWNLpSykmA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "regexp.prototype.flags": "^1.5.0", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supercluster": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", + "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", + "dependencies": { + "kdbush": "^4.0.2" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swap-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/swap-case/-/swap-case-2.0.2.tgz", + "integrity": "sha512-kc6S2YS/2yXbtkSMunBtKdah4VFETZ8Oh6ONSmSd9bRxhqTrtARUCBUiWXH3xVPpvR7tz2CSnkuXVE42EcGnMw==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/tinyqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", + "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" + }, + "node_modules/title-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", + "integrity": "sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/ts-api-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.2.tgz", + "integrity": "sha512-Cbu4nIqnEdd+THNEsBdkolnOXhg0I8XteoHaEKgvsxpsbWda4IsUut2c187HxywQCvveojow0Dgw/amxtSKVkQ==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-log": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/ts-log/-/ts-log-2.2.5.tgz", + "integrity": "sha512-PGcnJoTBnVGy6yYNFxWVNkdcAuAMstvutN9MgDJIV6L0oG8fB+ZNNy1T+wJzah8RPGor1mZuPQkVfXNDpy9eHA==", + "dev": true + }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typewise": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typewise/-/typewise-1.0.3.tgz", + "integrity": "sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==", + "dependencies": { + "typewise-core": "^1.2.0" + } + }, + "node_modules/typewise-core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz", + "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==" + }, + "node_modules/ua-parser-js": { + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz", + "integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "dependencies": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unixify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unixify/-/unixify-1.0.0.tgz", + "integrity": "sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg==", + "dev": true, + "dependencies": { + "normalize-path": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", + "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz", + "integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/value-or-promise": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.12.tgz", + "integrity": "sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", + "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", + "dev": true, + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vt-pbf": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", + "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", + "dependencies": { + "@mapbox/point-geometry": "0.1.0", + "@mapbox/vector-tile": "^1.3.1", + "pbf": "^3.2.1" + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/webcrypto-core": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.7.7.tgz", + "integrity": "sha512-7FjigXNsBfopEj+5DV2nhNpfic2vumtjjgPmeDKk45z+MJwXKKfhPB7118Pfzrmh4jqOMST6Ch37iPAHoImg5g==", + "dev": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.6", + "@peculiar/json-schema": "^1.1.12", + "asn1js": "^3.0.1", + "pvtsutils": "^1.3.2", + "tslib": "^2.4.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dev": true, + "dependencies": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true + }, + "node_modules/which-typed-array": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", + "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yaml-ast-parser": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", + "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/client-next/package.json b/client-next/package.json new file mode 100644 index 00000000000..4d453ed37ca --- /dev/null +++ b/client-next/package.json @@ -0,0 +1,49 @@ +{ + "name": "otp-debug-client-next", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --report-unused-disable-directives --max-warnings 0", + "check-format": "prettier --check \"**/*.{js,jsx,ts,tsx,json,css,scss,md}\"", + "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,scss,md}\"", + "preview": "vite preview", + "prebuild": "npm run codegen && npm run lint && npm run check-format", + "predev": "npm run codegen", + "codegen": "graphql-codegen --config codegen.ts" + }, + "dependencies": { + "@googlemaps/polyline-codec": "^1.0.28", + "bootstrap": "^5.3.1", + "graphql": "^16.8.0", + "graphql-request": "^6.1.0", + "maplibre-gl": "^3.3.0", + "react": "^18.2.0", + "react-bootstrap": "^2.8.0", + "react-dom": "^18.2.0", + "react-map-gl": "^7.1.5" + }, + "devDependencies": { + "@graphql-codegen/cli": "5.0.0", + "@graphql-codegen/client-preset": "4.1.0", + "@graphql-codegen/introspection": "4.0.0", + "@parcel/watcher": "^2.3.0", + "@types/react": "^18.2.15", + "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "@vitejs/plugin-react": "^4.0.3", + "eslint": "^8.45.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.3", + "prettier": "^3.0.3", + "typescript": "^5.2.2", + "vite": "^4.4.5" + } +} diff --git a/client-next/public/img/marker-flag-end-shadowed.png b/client-next/public/img/marker-flag-end-shadowed.png new file mode 100644 index 00000000000..ed8177fc2cf Binary files /dev/null and b/client-next/public/img/marker-flag-end-shadowed.png differ diff --git a/client-next/public/img/marker-flag-start-shadowed.png b/client-next/public/img/marker-flag-start-shadowed.png new file mode 100644 index 00000000000..7e565d59cf9 Binary files /dev/null and b/client-next/public/img/marker-flag-start-shadowed.png differ diff --git a/client-next/public/img/mode/air.png b/client-next/public/img/mode/air.png new file mode 100644 index 00000000000..69ea123a099 Binary files /dev/null and b/client-next/public/img/mode/air.png differ diff --git a/client-next/public/img/mode/bicycle.png b/client-next/public/img/mode/bicycle.png new file mode 100644 index 00000000000..c5a366b0e01 Binary files /dev/null and b/client-next/public/img/mode/bicycle.png differ diff --git a/client-next/public/img/mode/bus.png b/client-next/public/img/mode/bus.png new file mode 100644 index 00000000000..36fd81e008b Binary files /dev/null and b/client-next/public/img/mode/bus.png differ diff --git a/client-next/public/img/mode/cableway.png b/client-next/public/img/mode/cableway.png new file mode 100644 index 00000000000..f0b9d8469bd Binary files /dev/null and b/client-next/public/img/mode/cableway.png differ diff --git a/client-next/public/img/mode/car.png b/client-next/public/img/mode/car.png new file mode 100644 index 00000000000..e2f09986591 Binary files /dev/null and b/client-next/public/img/mode/car.png differ diff --git a/client-next/public/img/mode/coach.png b/client-next/public/img/mode/coach.png new file mode 100644 index 00000000000..36fd81e008b Binary files /dev/null and b/client-next/public/img/mode/coach.png differ diff --git a/client-next/public/img/mode/foot.png b/client-next/public/img/mode/foot.png new file mode 100644 index 00000000000..68025c60543 Binary files /dev/null and b/client-next/public/img/mode/foot.png differ diff --git a/client-next/public/img/mode/funicular.png b/client-next/public/img/mode/funicular.png new file mode 100644 index 00000000000..f0b9d8469bd Binary files /dev/null and b/client-next/public/img/mode/funicular.png differ diff --git a/client-next/public/img/mode/metro.png b/client-next/public/img/mode/metro.png new file mode 100644 index 00000000000..850f387481a Binary files /dev/null and b/client-next/public/img/mode/metro.png differ diff --git a/client-next/public/img/mode/monorail.png b/client-next/public/img/mode/monorail.png new file mode 100644 index 00000000000..2b720907fbb Binary files /dev/null and b/client-next/public/img/mode/monorail.png differ diff --git a/client-next/public/img/mode/rail.png b/client-next/public/img/mode/rail.png new file mode 100644 index 00000000000..f0b9d8469bd Binary files /dev/null and b/client-next/public/img/mode/rail.png differ diff --git a/client-next/public/img/mode/taxi.png b/client-next/public/img/mode/taxi.png new file mode 100644 index 00000000000..e2f09986591 Binary files /dev/null and b/client-next/public/img/mode/taxi.png differ diff --git a/client-next/public/img/mode/tram.png b/client-next/public/img/mode/tram.png new file mode 100644 index 00000000000..19a62327b2e Binary files /dev/null and b/client-next/public/img/mode/tram.png differ diff --git a/client-next/public/img/mode/trolleybus.png b/client-next/public/img/mode/trolleybus.png new file mode 100644 index 00000000000..b6d5bcd209a Binary files /dev/null and b/client-next/public/img/mode/trolleybus.png differ diff --git a/client-next/public/img/mode/water.png b/client-next/public/img/mode/water.png new file mode 100644 index 00000000000..abf995c8f15 Binary files /dev/null and b/client-next/public/img/mode/water.png differ diff --git a/client-next/public/img/otp-logo.svg b/client-next/public/img/otp-logo.svg new file mode 100644 index 00000000000..1ed23d0be8e --- /dev/null +++ b/client-next/public/img/otp-logo.svg @@ -0,0 +1,13 @@ + + + + + diff --git a/client-next/src/components/ItineraryList/ItineraryDetails.tsx b/client-next/src/components/ItineraryList/ItineraryDetails.tsx new file mode 100644 index 00000000000..11fe14c73fb --- /dev/null +++ b/client-next/src/components/ItineraryList/ItineraryDetails.tsx @@ -0,0 +1,15 @@ +import { TripPattern } from '../../gql/graphql.ts'; +import { ItineraryLegDetails } from './ItineraryLegDetails.tsx'; + +export function ItineraryDetails({ tripPattern }: { tripPattern: TripPattern }) { + return ( +
+ {tripPattern.systemNotices.length > 0 && ( +

System tags: {tripPattern.systemNotices.map((systemNotice) => systemNotice.tag).join(', ')}

+ )} + {tripPattern.legs.map((leg, i) => ( + + ))} +
+ ); +} diff --git a/client-next/src/components/ItineraryList/ItineraryHeaderContent.tsx b/client-next/src/components/ItineraryList/ItineraryHeaderContent.tsx new file mode 100644 index 00000000000..419b7a2ebb9 --- /dev/null +++ b/client-next/src/components/ItineraryList/ItineraryHeaderContent.tsx @@ -0,0 +1,77 @@ +import { TripPattern } from '../../gql/graphql.ts'; +import { TIME_BOX_WIDTH, useHeaderContentStyleCalculations } from './useHeaderContentStyleCalculations.ts'; +import { ItineraryHeaderLegContent } from './ItineraryHeaderLegContent.tsx'; +import { useMemo } from 'react'; +import { formatTime } from '../../util/formatTime.ts'; + +export function ItineraryHeaderContent({ + tripPattern, + itineraryIndex, + containerWidth, + earliestStartTime, + latestEndTime, +}: { + tripPattern: TripPattern; + itineraryIndex: number; + containerWidth: number; + earliestStartTime: string | null; + latestEndTime: string | null; +}) { + const { maxSpan, pxSpan, startPx, widthPx, leftPx } = useHeaderContentStyleCalculations( + tripPattern, + containerWidth, + earliestStartTime, + latestEndTime, + ); + + const formattedStartTime = useMemo( + () => formatTime(tripPattern.expectedStartTime, 'short'), + [tripPattern.expectedStartTime], + ); + + const formattedEndTime = useMemo( + () => formatTime(tripPattern.expectedEndTime, 'short'), + [tripPattern.expectedEndTime], + ); + + return ( +
+
{itineraryIndex + 1}.
+
+
+ {formattedStartTime} +
+ + {tripPattern.legs.map((leg, i) => ( + + ))} + +
+ {formattedEndTime} +
+
+ ); +} diff --git a/client-next/src/components/ItineraryList/ItineraryHeaderLegContent.tsx b/client-next/src/components/ItineraryList/ItineraryHeaderLegContent.tsx new file mode 100644 index 00000000000..93ee400fa2a --- /dev/null +++ b/client-next/src/components/ItineraryList/ItineraryHeaderLegContent.tsx @@ -0,0 +1,46 @@ +import { Leg } from '../../gql/graphql.ts'; +import { useHeaderLegContentStyleCalculations } from './useHeaderLegContentStyleCalculations.ts'; + +export function ItineraryHeaderLegContent({ + leg, + earliestStartTime, + maxSpan, + startPx, + pxSpan, +}: { + leg: Leg; + earliestStartTime: string | null; + maxSpan: number; + startPx: number; + pxSpan: number; +}) { + const { widthPx, leftPx, legTextColor, modeColor, showPublicCode } = useHeaderLegContentStyleCalculations( + leg, + earliestStartTime, + maxSpan, + startPx, + pxSpan, + ); + + return ( +
+
+ {showPublicCode && {leg.line?.publicCode}} +
+ ); +} diff --git a/client-next/src/components/ItineraryList/ItineraryLegDetails.tsx b/client-next/src/components/ItineraryList/ItineraryLegDetails.tsx new file mode 100644 index 00000000000..f6df37c2d1d --- /dev/null +++ b/client-next/src/components/ItineraryList/ItineraryLegDetails.tsx @@ -0,0 +1,24 @@ +import { Leg, Mode } from '../../gql/graphql.ts'; +import { LegTime } from './LegTime.tsx'; +import { formatDistance } from '../../util/formatDistance.ts'; +import { formatDuration } from '../../util/formatDuration.ts'; + +export function ItineraryLegDetails({ leg, isLast }: { leg: Leg; isLast: boolean }) { + return ( +
+ -{' '} + {' '} + {leg.mode}{' '} + {leg.line && ( + <> + + {leg.line.publicCode} {leg.toEstimatedCall?.destinationDisplay?.frontText} + + , {leg.authority?.name} + + )}{' '} + {formatDistance(leg.distance)}, {formatDuration(leg.duration)} + {leg.mode !== Mode.Foot && from {leg.fromPlace.name}} {!isLast && to {leg.toPlace.name}} +
+ ); +} diff --git a/client-next/src/components/ItineraryList/ItineraryListContainer.tsx b/client-next/src/components/ItineraryList/ItineraryListContainer.tsx new file mode 100644 index 00000000000..feaf29aa514 --- /dev/null +++ b/client-next/src/components/ItineraryList/ItineraryListContainer.tsx @@ -0,0 +1,61 @@ +import { QueryType } from '../../gql/graphql.ts'; +import { Accordion } from 'react-bootstrap'; +import { useContainerWidth } from './useContainerWidth.ts'; +import { ItineraryHeaderContent } from './ItineraryHeaderContent.tsx'; +import { useEarliestAndLatestTimes } from './useEarliestAndLatestTimes.ts'; +import { ItineraryDetails } from './ItineraryDetails.tsx'; +import { ItineraryPaginationControl } from './ItineraryPaginationControl.tsx'; + +export function ItineraryListContainer({ + tripQueryResult, + selectedTripPatternIndex, + setSelectedTripPatternIndex, + pageResults, + loading, +}: { + tripQueryResult: QueryType | null; + selectedTripPatternIndex: number; + setSelectedTripPatternIndex: (selectedTripPatterIndex: number) => void; + pageResults: (cursor: string) => void; + loading: boolean; +}) { + const [earliestStartTime, latestEndTime] = useEarliestAndLatestTimes(tripQueryResult); + const { containerRef, containerWidth } = useContainerWidth(); + + return ( +
+ + setSelectedTripPatternIndex(parseInt(eventKey as string))} + > + {tripQueryResult && + tripQueryResult.trip.tripPatterns.map((tripPattern, itineraryIndex) => ( + + + + + + + + + ))} + +
+ ); +} diff --git a/client-next/src/components/ItineraryList/ItineraryPaginationControl.tsx b/client-next/src/components/ItineraryList/ItineraryPaginationControl.tsx new file mode 100644 index 00000000000..ecc1ffd45db --- /dev/null +++ b/client-next/src/components/ItineraryList/ItineraryPaginationControl.tsx @@ -0,0 +1,34 @@ +import { Button } from 'react-bootstrap'; + +export function ItineraryPaginationControl({ + previousPageCursor, + nextPageCursor, + onPagination, + loading, +}: { + previousPageCursor?: string | null; + nextPageCursor?: string | null; + onPagination: (cursor: string) => void; + loading: boolean; +}) { + return ( +
+ {' '} + +
+ ); +} diff --git a/client-next/src/components/ItineraryList/LegTime.tsx b/client-next/src/components/ItineraryList/LegTime.tsx new file mode 100644 index 00000000000..5bd4aa56b1a --- /dev/null +++ b/client-next/src/components/ItineraryList/LegTime.tsx @@ -0,0 +1,23 @@ +import { formatTime } from '../../util/formatTime.ts'; + +export function LegTime({ + aimedTime, + expectedTime, + hasRealtime, +}: { + aimedTime: string; + expectedTime: string; + hasRealtime: boolean; +}) { + return aimedTime !== expectedTime ? ( + <> + {formatTime(expectedTime)} + {formatTime(aimedTime)} + + ) : ( + + {formatTime(expectedTime)} + {hasRealtime && (on time)} + + ); +} diff --git a/client-next/src/components/ItineraryList/useContainerWidth.ts b/client-next/src/components/ItineraryList/useContainerWidth.ts new file mode 100644 index 00000000000..f3ee97c9dcc --- /dev/null +++ b/client-next/src/components/ItineraryList/useContainerWidth.ts @@ -0,0 +1,28 @@ +import { useEffect, useLayoutEffect, useRef, useState } from 'react'; + +export function useContainerWidth() { + const [containerWidth, setContainerWidth] = useState(0); + const containerRef = useRef(null); + + useLayoutEffect(() => { + if (containerRef.current) { + setContainerWidth(containerRef.current.getBoundingClientRect().width); + } + }, []); + + useEffect(() => { + const listener = () => { + if (containerRef.current) { + setContainerWidth(containerRef.current.getBoundingClientRect().width); + } + }; + + window.addEventListener('resize', listener); + + return () => { + window.removeEventListener('resize', listener); + }; + }, []); + + return { containerRef, containerWidth }; +} diff --git a/client-next/src/components/ItineraryList/useEarliestAndLatestTimes.ts b/client-next/src/components/ItineraryList/useEarliestAndLatestTimes.ts new file mode 100644 index 00000000000..49d548f8787 --- /dev/null +++ b/client-next/src/components/ItineraryList/useEarliestAndLatestTimes.ts @@ -0,0 +1,30 @@ +import { useMemo } from 'react'; +import { TripQuery } from '../../gql/graphql.ts'; + +export function useEarliestAndLatestTimes(tripQueryResult: TripQuery | null) { + const earliestStartTime = useMemo(() => { + return ( + tripQueryResult?.trip.tripPatterns.reduce((acc, current) => { + if (acc === null) { + return current?.expectedStartTime; + } else { + return new Date(current?.expectedStartTime) < new Date(acc) ? current.expectedStartTime : acc; + } + }, null) || null + ); + }, [tripQueryResult?.trip]); + + const latestEndTime = useMemo(() => { + return ( + tripQueryResult?.trip.tripPatterns.reduce((acc, current) => { + if (acc === null) { + return current?.expectedEndTime; + } else { + return new Date(current?.expectedEndTime) > new Date(acc) ? current.expectedEndTime : acc; + } + }, null) || null + ); + }, [tripQueryResult?.trip]); + + return [earliestStartTime, latestEndTime]; +} diff --git a/client-next/src/components/ItineraryList/useHeaderContentStyleCalculations.ts b/client-next/src/components/ItineraryList/useHeaderContentStyleCalculations.ts new file mode 100644 index 00000000000..91695f6ae6e --- /dev/null +++ b/client-next/src/components/ItineraryList/useHeaderContentStyleCalculations.ts @@ -0,0 +1,44 @@ +import { useMemo } from 'react'; +import { TripPattern } from '../../gql/graphql.ts'; + +const CONTAINER_WIDTH_PADDING = 70; +const START_PX_PADDING = 20; + +// Width of time box +export const TIME_BOX_WIDTH = 40; + +export function useHeaderContentStyleCalculations( + tripPattern: TripPattern, + containerWidth: number, + earliestStartTime: string | null, + latestEndTime: string | null, +) { + const maxSpan = useMemo( + () => new Date(latestEndTime!).getTime() - new Date(earliestStartTime!).getTime(), + [earliestStartTime, latestEndTime], + ); + + const startPct = useMemo( + () => (new Date(tripPattern.expectedStartTime).getTime() - new Date(earliestStartTime!).getTime()) / maxSpan, + [tripPattern.expectedStartTime, earliestStartTime, maxSpan], + ); + + const itinSpan = useMemo( + () => new Date(tripPattern.expectedEndTime).getTime() - new Date(tripPattern.expectedStartTime).getTime(), + [tripPattern.expectedStartTime, tripPattern.expectedEndTime], + ); + + const startPx = START_PX_PADDING + TIME_BOX_WIDTH; + const endPx = containerWidth - CONTAINER_WIDTH_PADDING - TIME_BOX_WIDTH; + const pxSpan = endPx - startPx; + const leftPx = startPx + startPct * pxSpan; + const widthPx = pxSpan * (itinSpan / maxSpan); + + return { + maxSpan, + startPx, + pxSpan, + widthPx, + leftPx, + }; +} diff --git a/client-next/src/components/ItineraryList/useHeaderLegContentStyleCalculations.ts b/client-next/src/components/ItineraryList/useHeaderLegContentStyleCalculations.ts new file mode 100644 index 00000000000..642a71320b7 --- /dev/null +++ b/client-next/src/components/ItineraryList/useHeaderLegContentStyleCalculations.ts @@ -0,0 +1,34 @@ +import { useMemo } from 'react'; +import { isTransitMode } from '../../util/isTransitMode.ts'; +import { getColorForMode } from '../../util/getColorForMode.ts'; +import { generateTextColor } from '../../util/generateTextColor.ts'; +import { Leg } from '../../gql/graphql.ts'; + +export function useHeaderLegContentStyleCalculations( + leg: Leg, + earliestStartTime: string | null, + maxSpan: number, + startPx: number, + pxSpan: number, +) { + const startPct = useMemo( + () => (new Date(leg.expectedStartTime).getTime() - new Date(earliestStartTime!).getTime()) / maxSpan, + [leg.expectedStartTime, earliestStartTime, maxSpan], + ); + + const widthPx = useMemo( + () => + (pxSpan * (new Date(leg.expectedEndTime).getTime() - new Date(leg.expectedStartTime).getTime())) / maxSpan - 1, + [pxSpan, leg, maxSpan], + ); + + const leftPx = startPx + startPct * pxSpan + 1; + + const showPublicCode = + widthPx > 40 && isTransitMode(leg.mode) && leg.line?.publicCode && leg.line.publicCode.length <= 6; + + const modeColor = getColorForMode(leg.mode); + const legTextColor = useMemo(() => generateTextColor(modeColor), [modeColor]); + + return { widthPx, leftPx, legTextColor, modeColor, showPublicCode }; +} diff --git a/client-next/src/components/MapView/ContextMenuPopup.tsx b/client-next/src/components/MapView/ContextMenuPopup.tsx new file mode 100644 index 00000000000..52dd3858298 --- /dev/null +++ b/client-next/src/components/MapView/ContextMenuPopup.tsx @@ -0,0 +1,54 @@ +import { TripQueryVariables } from '../../gql/graphql.ts'; +import { LngLat, Popup } from 'react-map-gl'; +import { Button, ButtonGroup } from 'react-bootstrap'; + +export function ContextMenuPopup({ + tripQueryVariables, + setTripQueryVariables, + coordinates, + onClose, +}: { + tripQueryVariables: TripQueryVariables; + setTripQueryVariables: (variables: TripQueryVariables) => void; + coordinates: LngLat; + onClose: () => void; +}) { + return ( + + + + + + + ); +} diff --git a/client-next/src/components/MapView/LegLines.tsx b/client-next/src/components/MapView/LegLines.tsx new file mode 100644 index 00000000000..41b02ece83a --- /dev/null +++ b/client-next/src/components/MapView/LegLines.tsx @@ -0,0 +1,40 @@ +import { TripPattern } from '../../gql/graphql.ts'; +import { Layer, Source } from 'react-map-gl'; +import { decode } from '@googlemaps/polyline-codec'; +import { getColorForMode } from '../../util/getColorForMode.ts'; + +export function LegLines({ tripPattern }: { tripPattern?: TripPattern }) { + return ( + <> + {tripPattern?.legs.map( + (leg, i) => + leg.pointsOnLink && ( + value.reverse()), + }, + }} + > + + + ), + )} + + ); +} diff --git a/client-next/src/components/MapView/MapView.tsx b/client-next/src/components/MapView/MapView.tsx new file mode 100644 index 00000000000..011d9408148 --- /dev/null +++ b/client-next/src/components/MapView/MapView.tsx @@ -0,0 +1,67 @@ +import { LngLat, Map, NavigationControl } from 'react-map-gl'; +import 'maplibre-gl/dist/maplibre-gl.css'; +import { TripPattern, TripQuery, TripQueryVariables } from '../../gql/graphql.ts'; +import { NavigationMarkers } from './NavigationMarkers.tsx'; +import { LegLines } from './LegLines.tsx'; +import { useMapDoubleClick } from './useMapDoubleClick.ts'; +import { mapStyle } from './mapStyle.ts'; +import { useState } from 'react'; +import { ContextMenuPopup } from './ContextMenuPopup.tsx'; + +// TODO: this should be configurable +const initialViewState = { + latitude: 60.7554885, + longitude: 10.2332855, + zoom: 4, +}; + +export function MapView({ + tripQueryVariables, + setTripQueryVariables, + tripQueryResult, + selectedTripPatternIndex, + loading, +}: { + tripQueryVariables: TripQueryVariables; + setTripQueryVariables: (variables: TripQueryVariables) => void; + tripQueryResult: TripQuery | null; + selectedTripPatternIndex: number; + loading: boolean; +}) { + const onMapDoubleClick = useMapDoubleClick({ tripQueryVariables, setTripQueryVariables }); + const [showPopup, setShowPopup] = useState(null); + + return ( +
+ { + setShowPopup(e.lngLat); + }} + > + + + {tripQueryResult?.trip.tripPatterns.length && ( + + )} + {showPopup && ( + setShowPopup(null)} + /> + )} + +
+ ); +} diff --git a/client-next/src/components/MapView/NavigationMarkers.tsx b/client-next/src/components/MapView/NavigationMarkers.tsx new file mode 100644 index 00000000000..a99590bd068 --- /dev/null +++ b/client-next/src/components/MapView/NavigationMarkers.tsx @@ -0,0 +1,53 @@ +import { TripQueryVariables } from '../../gql/graphql.ts'; +import { Marker } from 'react-map-gl'; + +export function NavigationMarkers({ + tripQueryVariables, + setTripQueryVariables, + loading, +}: { + tripQueryVariables: TripQueryVariables; + setTripQueryVariables: (variables: TripQueryVariables) => void; + loading: boolean; +}) { + return ( + <> + {tripQueryVariables.from.coordinates && ( + { + if (!loading) { + setTripQueryVariables({ + ...tripQueryVariables, + from: { coordinates: { latitude: e.lngLat.lat, longitude: e.lngLat.lng } }, + }); + } + }} + anchor="bottom-right" + > + + + )} + {tripQueryVariables.to.coordinates && ( + { + if (!loading) { + setTripQueryVariables({ + ...tripQueryVariables, + to: { coordinates: { latitude: e.lngLat.lat, longitude: e.lngLat.lng } }, + }); + } + }} + anchor="bottom-right" + > + + + )} + + ); +} diff --git a/client-next/src/components/MapView/mapStyle.ts b/client-next/src/components/MapView/mapStyle.ts new file mode 100644 index 00000000000..ecaa88c0354 --- /dev/null +++ b/client-next/src/components/MapView/mapStyle.ts @@ -0,0 +1,19 @@ +export const mapStyle = { + version: 8, + sources: { + osm: { + type: 'raster', + tiles: ['https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'], + tileSize: 256, + attribution: '© OpenStreetMap Contributors', + maxzoom: 19, + }, + }, + layers: [ + { + id: 'osm', + type: 'raster', + source: 'osm', // This must match the source key above + }, + ], +}; diff --git a/client-next/src/components/MapView/useMapDoubleClick.ts b/client-next/src/components/MapView/useMapDoubleClick.ts new file mode 100644 index 00000000000..35cb1d76a62 --- /dev/null +++ b/client-next/src/components/MapView/useMapDoubleClick.ts @@ -0,0 +1,39 @@ +import { useCallback } from 'react'; +import { TripQueryVariables } from '../../gql/graphql.ts'; +import { LngLat, MapLayerMouseEvent } from 'react-map-gl'; + +const setCoordinates = (tripQueryVariables: TripQueryVariables, lngLat: LngLat, key: 'from' | 'to') => ({ + ...tripQueryVariables, + [key]: { + coordinates: { + latitude: lngLat.lat, + longitude: lngLat.lng, + }, + }, +}); + +const setFromCoordinates = (tripQueryVariables: TripQueryVariables, lngLat: LngLat) => + setCoordinates(tripQueryVariables, lngLat, 'from'); + +const setToCoordinates = (tripQueryVariables: TripQueryVariables, lngLat: LngLat) => + setCoordinates(tripQueryVariables, lngLat, 'to'); + +export function useMapDoubleClick({ + tripQueryVariables, + setTripQueryVariables, +}: { + tripQueryVariables: TripQueryVariables; + setTripQueryVariables: (variables: TripQueryVariables) => void; +}) { + return useCallback( + (event: MapLayerMouseEvent) => { + event.preventDefault(); + if (!tripQueryVariables.from.coordinates) { + setTripQueryVariables(setFromCoordinates(tripQueryVariables, event.lngLat)); + } else { + setTripQueryVariables(setToCoordinates(tripQueryVariables, event.lngLat)); + } + }, + [tripQueryVariables, setTripQueryVariables], + ); +} diff --git a/client-next/src/components/SearchBar/AccessSelect.tsx b/client-next/src/components/SearchBar/AccessSelect.tsx new file mode 100644 index 00000000000..d76ea59207a --- /dev/null +++ b/client-next/src/components/SearchBar/AccessSelect.tsx @@ -0,0 +1,52 @@ +import { Form } from 'react-bootstrap'; +import { StreetMode, TripQueryVariables } from '../../gql/graphql.ts'; + +export function AccessSelect({ + tripQueryVariables, + setTripQueryVariables, +}: { + tripQueryVariables: TripQueryVariables; + setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void; +}) { + return ( + + + Access + + { + if (e.target.value !== 'not_selected') { + setTripQueryVariables({ + ...tripQueryVariables, + modes: { + ...tripQueryVariables.modes, + accessMode: e.target.value as StreetMode, + }, + }); + } else { + setTripQueryVariables({ + ...tripQueryVariables, + modes: + tripQueryVariables.modes?.directMode || tripQueryVariables.modes?.transportModes + ? { + ...tripQueryVariables.modes, + accessMode: undefined, + } + : undefined, + }); + } + }} + value={tripQueryVariables.modes?.accessMode || 'not_selected'} + > + + {Object.values(StreetMode).map((mode) => ( + + ))} + + + ); +} diff --git a/client-next/src/components/SearchBar/DateInputField.tsx b/client-next/src/components/SearchBar/DateInputField.tsx new file mode 100644 index 00000000000..0b0eca38869 --- /dev/null +++ b/client-next/src/components/SearchBar/DateInputField.tsx @@ -0,0 +1,40 @@ +import { Form } from 'react-bootstrap'; +import { TripQueryVariables } from '../../gql/graphql.ts'; +import { ChangeEvent, useCallback, useMemo } from 'react'; + +export function DateInputField({ + tripQueryVariables, + setTripQueryVariables, +}: { + tripQueryVariables: TripQueryVariables; + setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void; +}) { + const current = useMemo( + () => new Date(tripQueryVariables.dateTime).toISOString().split('T')[0], + [tripQueryVariables.dateTime], + ); + + const onChange = useCallback( + (event: ChangeEvent) => { + const oldDate = new Date(tripQueryVariables.dateTime); + const newDate = new Date(event.target.value); + + newDate.setHours(oldDate.getHours(), oldDate.getMinutes(), oldDate.getSeconds()); + + setTripQueryVariables({ + ...tripQueryVariables, + dateTime: newDate.toISOString(), + }); + }, + [tripQueryVariables, setTripQueryVariables], + ); + + return ( + + + Date + + + + ); +} diff --git a/client-next/src/components/SearchBar/DepartureArrivalSelect.tsx b/client-next/src/components/SearchBar/DepartureArrivalSelect.tsx new file mode 100644 index 00000000000..b6a92cdd495 --- /dev/null +++ b/client-next/src/components/SearchBar/DepartureArrivalSelect.tsx @@ -0,0 +1,33 @@ +import { Form } from 'react-bootstrap'; +import { TripQueryVariables } from '../../gql/graphql.ts'; + +export function DepartureArrivalSelect({ + tripQueryVariables, + setTripQueryVariables, +}: { + tripQueryVariables: TripQueryVariables; + setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void; +}) { + const onChange = (arriveBy: boolean) => { + setTripQueryVariables({ + ...tripQueryVariables, + arriveBy, + }); + }; + + return ( + + + Departure/Arrival + + (e.target.value === 'arrival' ? onChange(true) : onChange(false))} + value={tripQueryVariables.arriveBy ? 'arrival' : 'departure'} + > + + + + + ); +} diff --git a/client-next/src/components/SearchBar/DirectModeSelect.tsx b/client-next/src/components/SearchBar/DirectModeSelect.tsx new file mode 100644 index 00000000000..459cb2e4d47 --- /dev/null +++ b/client-next/src/components/SearchBar/DirectModeSelect.tsx @@ -0,0 +1,54 @@ +import { Form } from 'react-bootstrap'; +import { StreetMode, TripQueryVariables } from '../../gql/graphql.ts'; + +export function DirectModeSelect({ + tripQueryVariables, + setTripQueryVariables, +}: { + tripQueryVariables: TripQueryVariables; + setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void; +}) { + return ( + + + Direct mode + + { + if (e.target.value !== 'not_selected') { + setTripQueryVariables({ + ...tripQueryVariables, + modes: { + ...tripQueryVariables.modes, + directMode: e.target.value as StreetMode, + }, + }); + } else { + setTripQueryVariables({ + ...tripQueryVariables, + modes: + tripQueryVariables.modes?.accessMode || + tripQueryVariables.modes?.egressMode || + tripQueryVariables.modes?.transportModes + ? { + ...tripQueryVariables.modes, + directMode: undefined, + } + : undefined, + }); + } + }} + value={tripQueryVariables.modes?.directMode || 'not_selected'} + > + + {Object.values(StreetMode).map((mode) => ( + + ))} + + + ); +} diff --git a/client-next/src/components/SearchBar/EgressSelect.tsx b/client-next/src/components/SearchBar/EgressSelect.tsx new file mode 100644 index 00000000000..916588d08e9 --- /dev/null +++ b/client-next/src/components/SearchBar/EgressSelect.tsx @@ -0,0 +1,52 @@ +import { Form } from 'react-bootstrap'; +import { StreetMode, TripQueryVariables } from '../../gql/graphql.ts'; + +export function EgressSelect({ + tripQueryVariables, + setTripQueryVariables, +}: { + tripQueryVariables: TripQueryVariables; + setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void; +}) { + return ( + + + Egress + + { + if (e.target.value !== 'not_selected') { + setTripQueryVariables({ + ...tripQueryVariables, + modes: { + ...tripQueryVariables.modes, + egressMode: e.target.value as StreetMode, + }, + }); + } else { + setTripQueryVariables({ + ...tripQueryVariables, + modes: + tripQueryVariables.modes?.directMode || tripQueryVariables.modes?.transportModes + ? { + ...tripQueryVariables.modes, + egressMode: undefined, + } + : undefined, + }); + } + }} + value={tripQueryVariables.modes?.egressMode || 'not_selected'} + > + + {Object.values(StreetMode).map((mode) => ( + + ))} + + + ); +} diff --git a/client-next/src/components/SearchBar/ItineraryFilterDebugSelect.tsx b/client-next/src/components/SearchBar/ItineraryFilterDebugSelect.tsx new file mode 100644 index 00000000000..636ba551541 --- /dev/null +++ b/client-next/src/components/SearchBar/ItineraryFilterDebugSelect.tsx @@ -0,0 +1,36 @@ +import { Form } from 'react-bootstrap'; +import { ItineraryFilterDebugProfile, TripQueryVariables } from '../../gql/graphql.ts'; + +export function ItineraryFilterDebugSelect({ + tripQueryVariables, + setTripQueryVariables, +}: { + tripQueryVariables: TripQueryVariables; + setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void; +}) { + return ( + + + Itinerary filter debug + + { + setTripQueryVariables({ + ...tripQueryVariables, + itineraryFiltersDebug: e.target.value as ItineraryFilterDebugProfile, + }); + }} + value={tripQueryVariables.itineraryFiltersDebug || 'not_selected'} + > + + {Object.values(ItineraryFilterDebugProfile).map((debugProfile) => ( + + ))} + + + ); +} diff --git a/client-next/src/components/SearchBar/LocationInputField.tsx b/client-next/src/components/SearchBar/LocationInputField.tsx new file mode 100644 index 00000000000..ffa66702e81 --- /dev/null +++ b/client-next/src/components/SearchBar/LocationInputField.tsx @@ -0,0 +1,29 @@ +import { Form } from 'react-bootstrap'; +import { COORDINATE_PRECISION } from './constants.ts'; +import { Location } from '../../gql/graphql.ts'; + +export function LocationInputField({ location, id, label }: { location: Location; id: string; label: string }) { + return ( + + + {label} + + {}} + value={ + location.coordinates + ? `${location.coordinates?.latitude.toPrecision( + COORDINATE_PRECISION, + )} ${location.coordinates?.longitude.toPrecision(COORDINATE_PRECISION)}` + : '' + } + /> + + ); +} diff --git a/client-next/src/components/SearchBar/MultiSelectDropdown.tsx b/client-next/src/components/SearchBar/MultiSelectDropdown.tsx new file mode 100644 index 00000000000..fc20e6822ac --- /dev/null +++ b/client-next/src/components/SearchBar/MultiSelectDropdown.tsx @@ -0,0 +1,65 @@ +import { ChangeEvent, useState } from 'react'; +import { Form } from 'react-bootstrap'; + +type MultiSelectDropdownOption = { + id: T; + label: string; +}; + +type MultiSelectDropdownProps = { + label: string; + options: MultiSelectDropdownOption[]; + values: T[]; + onChange: (value: T[]) => void; +}; + +const MultiSelectDropdown = ({ label, options, values, onChange }: MultiSelectDropdownProps) => { + const [isOpen, setIsOpen] = useState(false); + + const toggleDropdown = () => { + setIsOpen(!isOpen); + }; + + const handleOptionChange = (event: ChangeEvent) => { + const optionId = event.target.value as T; + const isChecked = event.target.checked; + + if (isChecked) { + onChange([...values, optionId]); + } else { + onChange(values.filter((id) => id !== optionId)); + } + }; + + return ( +
+ + {label} + + 0 ? values.join(', ') : 'Not selected'} + onClick={toggleDropdown} + onChange={() => {}} + /> +
+ {options.map((option) => ( + + ))} +
+
+ ); +}; + +export default MultiSelectDropdown; diff --git a/client-next/src/components/SearchBar/NumTripPatternsInput.tsx b/client-next/src/components/SearchBar/NumTripPatternsInput.tsx new file mode 100644 index 00000000000..b77e70adb81 --- /dev/null +++ b/client-next/src/components/SearchBar/NumTripPatternsInput.tsx @@ -0,0 +1,32 @@ +import { Form } from 'react-bootstrap'; +import { TripQueryVariables } from '../../gql/graphql.ts'; + +export function NumTripPatternsInput({ + tripQueryVariables, + setTripQueryVariables, +}: { + tripQueryVariables: TripQueryVariables; + setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void; +}) { + return ( + + + Number of trip patterns + + + setTripQueryVariables({ + ...tripQueryVariables, + numTripPatterns: Number(event.target.value) > 0 ? Number(event.target.value) : undefined, + }) + } + /> + + ); +} diff --git a/client-next/src/components/SearchBar/SearchBar.tsx b/client-next/src/components/SearchBar/SearchBar.tsx new file mode 100644 index 00000000000..ea1492fbe9b --- /dev/null +++ b/client-next/src/components/SearchBar/SearchBar.tsx @@ -0,0 +1,72 @@ +import { Button, Spinner } from 'react-bootstrap'; +import { ServerInfo, TripQueryVariables } from '../../gql/graphql.ts'; +import { LocationInputField } from './LocationInputField.tsx'; +import { DepartureArrivalSelect } from './DepartureArrivalSelect.tsx'; +import { TimeInputField } from './TimeInputField.tsx'; +import { DateInputField } from './DateInputField.tsx'; +import { SearchWindowInput } from './SearchWindowInput.tsx'; +import { AccessSelect } from './AccessSelect.tsx'; +import { EgressSelect } from './EgressSelect.tsx'; +import { DirectModeSelect } from './DirectModeSelect.tsx'; +import { TransitModeSelect } from './TransitModeSelect.tsx'; +import { NumTripPatternsInput } from './NumTripPatternsInput.tsx'; +import { ItineraryFilterDebugSelect } from './ItineraryFilterDebugSelect.tsx'; +import Navbar from 'react-bootstrap/Navbar'; +import { ServerInfoTooltip } from './ServerInfoTooltip.tsx'; +import { useRef, useState } from 'react'; + +type SearchBarProps = { + onRoute: () => void; + tripQueryVariables: TripQueryVariables; + setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void; + serverInfo?: ServerInfo; + loading: boolean; +}; + +export function SearchBar({ onRoute, tripQueryVariables, setTripQueryVariables, serverInfo, loading }: SearchBarProps) { + const [showServerInfo, setShowServerInfo] = useState(false); + const target = useRef(null); + + return ( +
+ setShowServerInfo((v) => !v)}> +
+ {' '} + OTP Debug Client + {showServerInfo && } +
+
+ + + + + + + + + + + + +
+ +
+
+ ); +} diff --git a/client-next/src/components/SearchBar/SearchWindowInput.tsx b/client-next/src/components/SearchBar/SearchWindowInput.tsx new file mode 100644 index 00000000000..5442784de8e --- /dev/null +++ b/client-next/src/components/SearchBar/SearchWindowInput.tsx @@ -0,0 +1,32 @@ +import { Form } from 'react-bootstrap'; +import { TripQueryVariables } from '../../gql/graphql.ts'; + +export function SearchWindowInput({ + tripQueryVariables, + setTripQueryVariables, +}: { + tripQueryVariables: TripQueryVariables; + setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void; +}) { + return ( + + + Search window + + + setTripQueryVariables({ + ...tripQueryVariables, + searchWindow: Number(event.target.value) > 0 ? Number(event.target.value) : undefined, + }) + } + /> + + ); +} diff --git a/client-next/src/components/SearchBar/ServerInfoTooltip.tsx b/client-next/src/components/SearchBar/ServerInfoTooltip.tsx new file mode 100644 index 00000000000..a1aaed16f9c --- /dev/null +++ b/client-next/src/components/SearchBar/ServerInfoTooltip.tsx @@ -0,0 +1,21 @@ +import { ServerInfo } from '../../gql/graphql.ts'; +import { Overlay } from 'react-bootstrap'; +import { MutableRefObject } from 'react'; + +export function ServerInfoTooltip({ target, serverInfo }: { target: MutableRefObject; serverInfo?: ServerInfo }) { + return ( + +
+
{JSON.stringify(serverInfo, null, 2)}
+
+
+ ); +} diff --git a/client-next/src/components/SearchBar/TimeInputField.tsx b/client-next/src/components/SearchBar/TimeInputField.tsx new file mode 100644 index 00000000000..71bb7325340 --- /dev/null +++ b/client-next/src/components/SearchBar/TimeInputField.tsx @@ -0,0 +1,39 @@ +import { Form } from 'react-bootstrap'; +import { TripQueryVariables } from '../../gql/graphql.ts'; +import { ChangeEvent, useCallback, useMemo } from 'react'; + +export function TimeInputField({ + tripQueryVariables, + setTripQueryVariables, +}: { + tripQueryVariables: TripQueryVariables; + setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void; +}) { + const current = useMemo( + () => new Date(tripQueryVariables.dateTime).toTimeString().split(' ')[0], + [tripQueryVariables.dateTime], + ); + + const onChange = useCallback( + (event: ChangeEvent) => { + const timeComponents = event.target.value.split(':'); + const newDate = new Date(tripQueryVariables.dateTime); + newDate.setHours(Number(timeComponents[0]), Number(timeComponents[1]), Number(timeComponents[2])); + + setTripQueryVariables({ + ...tripQueryVariables, + dateTime: newDate.toISOString(), + }); + }, + [tripQueryVariables, setTripQueryVariables], + ); + + return ( + + + Time + + + + ); +} diff --git a/client-next/src/components/SearchBar/TransitModeSelect.tsx b/client-next/src/components/SearchBar/TransitModeSelect.tsx new file mode 100644 index 00000000000..a5db4793991 --- /dev/null +++ b/client-next/src/components/SearchBar/TransitModeSelect.tsx @@ -0,0 +1,60 @@ +import { TransportMode, TripQueryVariables } from '../../gql/graphql.ts'; +import MultiSelectDropdown from './MultiSelectDropdown.tsx'; +import { useCallback, useMemo } from 'react'; + +export function TransitModeSelect({ + tripQueryVariables, + setTripQueryVariables, +}: { + tripQueryVariables: TripQueryVariables; + setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void; +}) { + const values = useMemo(() => { + return ( + tripQueryVariables?.modes?.transportModes + ?.map((transportMode) => transportMode?.transportMode) + .filter((v) => !!v) || [] + ); + }, [tripQueryVariables.modes?.transportModes]); + + const onChange = useCallback( + (values: (TransportMode | null | undefined)[]) => { + const newTransportModes = values.map((v) => ({ + transportMode: v, + })); + + if (newTransportModes.length === 0) { + setTripQueryVariables({ + ...tripQueryVariables, + modes: + tripQueryVariables.modes?.directMode || + tripQueryVariables.modes?.accessMode || + tripQueryVariables.modes?.egressMode + ? { ...tripQueryVariables.modes } + : undefined, + }); + } else { + setTripQueryVariables({ + ...tripQueryVariables, + modes: { + ...tripQueryVariables.modes, + transportModes: newTransportModes.length > 0 ? newTransportModes : undefined, + }, + }); + } + }, + [tripQueryVariables, setTripQueryVariables], + ); + + return ( + ({ + id: mode, + label: mode.toString(), + }))} + values={values} + onChange={onChange} + /> + ); +} diff --git a/client-next/src/components/SearchBar/constants.ts b/client-next/src/components/SearchBar/constants.ts new file mode 100644 index 00000000000..705f207de2d --- /dev/null +++ b/client-next/src/components/SearchBar/constants.ts @@ -0,0 +1 @@ +export const COORDINATE_PRECISION = 7; diff --git a/client-next/src/hooks/useServerInfo.ts b/client-next/src/hooks/useServerInfo.ts new file mode 100644 index 00000000000..aff463d571a --- /dev/null +++ b/client-next/src/hooks/useServerInfo.ts @@ -0,0 +1,31 @@ +import { useEffect, useState } from 'react'; +import { graphql } from '../gql'; +import request from 'graphql-request'; +import { QueryType } from '../gql/graphql.ts'; + +const endpoint = import.meta.env.VITE_API_URL; + +const query = graphql(` + query serverInfo { + serverInfo { + version + otpSerializationVersionId + buildConfigVersion + routerConfigVersion + gitCommit + gitBranch + } + } +`); + +export const useServerInfo = () => { + const [data, setData] = useState(null); + useEffect(() => { + const fetchData = async () => { + setData((await request(endpoint, query)) as QueryType); + }; + fetchData(); + }, []); + + return data?.serverInfo; +}; diff --git a/client-next/src/hooks/useTripQuery.ts b/client-next/src/hooks/useTripQuery.ts new file mode 100644 index 00000000000..3720e2c450a --- /dev/null +++ b/client-next/src/hooks/useTripQuery.ts @@ -0,0 +1,120 @@ +import { useCallback, useEffect, useState } from 'react'; +import { graphql } from '../gql'; +import request from 'graphql-request'; +import { QueryType, TripQueryVariables } from '../gql/graphql.ts'; + +const endpoint = import.meta.env.VITE_API_URL; + +/** + General purpose trip query document for debugging trip searches + TODO: should live in a separate file, and split into fragments for readability + */ +const query = graphql(` + query trip( + $from: Location! + $to: Location! + $arriveBy: Boolean + $dateTime: DateTime + $numTripPatterns: Int + $searchWindow: Int + $modes: Modes + $itineraryFiltersDebug: ItineraryFilterDebugProfile + $pageCursor: String + ) { + trip( + from: $from + to: $to + arriveBy: $arriveBy + dateTime: $dateTime + numTripPatterns: $numTripPatterns + searchWindow: $searchWindow + modes: $modes + itineraryFilters: { debug: $itineraryFiltersDebug } + pageCursor: $pageCursor + ) { + previousPageCursor + nextPageCursor + tripPatterns { + aimedStartTime + aimedEndTime + expectedEndTime + expectedStartTime + duration + distance + legs { + id + mode + aimedStartTime + aimedEndTime + expectedEndTime + expectedStartTime + realtime + distance + duration + fromPlace { + name + } + toPlace { + name + } + toEstimatedCall { + destinationDisplay { + frontText + } + } + line { + publicCode + name + } + authority { + name + } + pointsOnLink { + points + } + } + systemNotices { + tag + } + } + } + } +`); + +type TripQueryHook = ( + variables?: TripQueryVariables, +) => [QueryType | null, boolean, (pageCursor?: string) => Promise]; + +export const useTripQuery: TripQueryHook = (variables) => { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + const callback = useCallback( + async (pageCursor?: string) => { + if (loading) { + console.warn('Wait for previous search to finish'); + } else { + if (variables) { + setLoading(true); + if (pageCursor) { + setData((await request(endpoint, query, { ...variables, pageCursor })) as QueryType); + } else { + setData((await request(endpoint, query, variables)) as QueryType); + } + setLoading(false); + } else { + console.warn("Can't search without variables"); + } + } + }, + [setData, variables, loading], + ); + + useEffect(() => { + if (variables?.from.coordinates && variables?.to.coordinates) { + callback(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [variables?.from, variables?.to]); + + return [data, loading, callback]; +}; diff --git a/client-next/src/main.tsx b/client-next/src/main.tsx new file mode 100644 index 00000000000..b5de8eedd8f --- /dev/null +++ b/client-next/src/main.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import 'bootstrap/dist/css/bootstrap.min.css'; +import { App } from './screens/App.tsx'; +import './style.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/client-next/src/screens/App.tsx b/client-next/src/screens/App.tsx new file mode 100644 index 00000000000..df17bb713bf --- /dev/null +++ b/client-next/src/screens/App.tsx @@ -0,0 +1,50 @@ +import { Stack } from 'react-bootstrap'; +import { MapView } from '../components/MapView/MapView.tsx'; +import { SearchBar } from '../components/SearchBar/SearchBar.tsx'; +import { ItineraryListContainer } from '../components/ItineraryList/ItineraryListContainer.tsx'; +import { useState } from 'react'; +import { TripQueryVariables } from '../gql/graphql.ts'; +import { useTripQuery } from '../hooks/useTripQuery.ts'; +import { useServerInfo } from '../hooks/useServerInfo.ts'; + +const INITIAL_VARIABLES: TripQueryVariables = { + from: {}, + to: {}, + dateTime: new Date().toISOString(), +}; + +export function App() { + const [tripQueryVariables, setTripQueryVariables] = useState(INITIAL_VARIABLES); + const [tripQueryResult, loading, callback] = useTripQuery(tripQueryVariables); + const serverInfo = useServerInfo(); + const [selectedTripPatternIndex, setSelectedTripPatternIndex] = useState(0); + + return ( +
+ + + + + + +
+ ); +} diff --git a/client-next/src/style.css b/client-next/src/style.css new file mode 100644 index 00000000000..b7661779991 --- /dev/null +++ b/client-next/src/style.css @@ -0,0 +1,121 @@ +.app { + min-width: 810px; +} +.navbar-brand { + color: #4078bc; + margin-top: 20px; + margin-right: 14px; +} + +@media (min-width: 2160px) { + .top-content { + height: 75px; + } + + .below-content { + height: calc(100vh - 75px); + } +} + +@media (max-width: 2159px) { + .top-content { + height: 150px; + } + + .below-content { + height: calc(100vh - 150px); + } +} + +@media (max-width: 1120px) { + .top-content { + height: 200px; + } + + .below-content { + height: calc(100vh - 200px); + } +} + +.map-container { + width: 100%; +} + +.search-bar { + padding-left: 1rem; + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; + margin-right: 1rem; +} + +.search-bar-route-button-wrapper { + height: 5rem; + padding-top: 25px; +} + +.itinerary-list-container { + width: 36rem; + overflow-y: auto; +} + +.itinerary-header-wrapper { + position: relative; + background: #0a53be; +} + +.accordion-item-filtered { + margin-bottom: 0; + background-color: lightpink; + --bs-accordion-btn-bg: lightpink; + --bs-accordion-active-bg: pink; +} + +.itinerary-header-itinerary-number { + position: absolute; +} + +.itinerary-header-itinerary-line { + position: absolute; + height: 2px; + top: 9px; + background: #000; +} + +.itinerary-header-itinerary-time { + position: absolute; + background: #000; + color: #fff; + font-size: 12px; + width: 38px; + height: 15px; + text-align: center; + top: 2px; + padding: 1px; +} + +.itinerary-header-leg-wrapper { + position: absolute; + height: 22px; + font-weight: bold; + text-shadow: none; + margin-top: -1px; + padding-top: 3px; + text-align: center; + overflow: hidden; +} + +.itinerary-header-leg-icon { + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + width: 17px; + height: 17px; + display: inline-block; +} + +.itinerary-header-leg-public-code { + vertical-align: top; + font-size: 14px; + padding-left: 2px; +} diff --git a/client-next/src/util/formatDistance.ts b/client-next/src/util/formatDistance.ts new file mode 100644 index 00000000000..0aa67a030a4 --- /dev/null +++ b/client-next/src/util/formatDistance.ts @@ -0,0 +1,18 @@ +/** + * Format distance + * + * Adapted from src/client/js/otp/util/Geo.js#distanceStringMetric + */ +export function formatDistance(meters: number) { + const kilometers = meters / 1000; + if (kilometers > 100) { + //100 km => 999999999 km + return `${kilometers.toFixed(0)} km`; + } else if (kilometers > 1) { + //1.1 km => 99.9 km + return `${kilometers.toFixed(1)} km`; + } else { + //1m => 999m + return `${meters.toFixed(0)} m`; + } +} diff --git a/client-next/src/util/formatDuration.ts b/client-next/src/util/formatDuration.ts new file mode 100644 index 00000000000..94e51ad9d47 --- /dev/null +++ b/client-next/src/util/formatDuration.ts @@ -0,0 +1,32 @@ +/** + * Format duration in seconds + * + * Adapted from src/client/js/otp/util/Time.js#secsToHrMinSec + */ +export function formatDuration(seconds: number) { + const hrs = Math.floor(seconds / 3600); + const mins = Math.floor(seconds / 60) % 60; + const secs = seconds % 60; + + let formatted = ''; + + if (hrs === 1) { + formatted = `${formatted}${hrs} hr `; + } else if (hrs > 1) { + formatted = `${formatted}${hrs} hrs `; + } + + if (mins === 1) { + formatted = `${formatted}${mins} min `; + } else if (mins > 1) { + formatted = `${formatted}${mins} mins `; + } + + if (secs === 1) { + formatted = `${formatted}${secs} sec `; + } else if (secs > 1) { + formatted = `${formatted}${secs} secs `; + } + + return formatted; +} diff --git a/client-next/src/util/formatTime.ts b/client-next/src/util/formatTime.ts new file mode 100644 index 00000000000..1849640fe3f --- /dev/null +++ b/client-next/src/util/formatTime.ts @@ -0,0 +1,13 @@ +/** + * Format departure and arrival times from scalar dateTime strings + * + * If style argument is provided formatted with ('medium') or without ('short') seconds, + * otherwise seconds are shown if not 0. + */ +export function formatTime(dateTime: string, style?: 'short' | 'medium') { + const parsed = new Date(dateTime); + return parsed.toLocaleTimeString('en-US', { + timeStyle: style ? style : parsed.getSeconds() === 0 ? 'short' : 'medium', + hourCycle: 'h24', + }); +} diff --git a/client-next/src/util/generateTextColor.ts b/client-next/src/util/generateTextColor.ts new file mode 100644 index 00000000000..824dc87f3a2 --- /dev/null +++ b/client-next/src/util/generateTextColor.ts @@ -0,0 +1,40 @@ +/** + * textColor can be black or white. White for dark colors and black for light colors. + * Calculated based on luminance formula: + * sqrt( 0.299*Red^2 + 0.587*Green^2 + 0.114*Blue^2 ) + */ +export function generateTextColor(hexColor: string) { + const color = decodeColor(hexColor); + + //Calculates luminance based on https://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color + const newRed = 0.299 * Math.pow(color[0] / 255.0, 2.0); + const newGreen = 0.587 * Math.pow(color[1] / 255.0, 2.0); + const newBlue = 0.114 * Math.pow(color[2] / 255.0, 2.0); + const luminance = Math.sqrt(newRed + newGreen + newBlue); + + if (luminance > 0.66) { + return '#000'; + } else { + return '#fff'; + } +} + +function decodeColor(hex: string): number[] { + return hex2rgb(hex); +} + +function hex2rgb(hex: string) { + if (hex.length === 4) { + return fullHex(hex); + } + + return [parseInt(hex.slice(1, 3), 16), parseInt(hex.slice(3, 5), 16), parseInt(hex.slice(5, 7), 16)]; +} + +function fullHex(hex: string) { + const r = hex.slice(1, 2); + const g = hex.slice(2, 3); + const b = hex.slice(3, 4); + + return [parseInt(r + r, 16), parseInt(g + g, 16), parseInt(b + b, 16)]; +} diff --git a/client-next/src/util/getColorForMode.ts b/client-next/src/util/getColorForMode.ts new file mode 100644 index 00000000000..0276a1bce52 --- /dev/null +++ b/client-next/src/util/getColorForMode.ts @@ -0,0 +1,21 @@ +import { Mode } from '../gql/graphql.ts'; + +export const getColorForMode = function (mode: Mode) { + if (mode === Mode.Foot) return '#444'; + if (mode === Mode.Bicycle) return '#44f'; + if (mode === Mode.Scooter) return '#88f'; + if (mode === Mode.Car) return '#444'; + if (mode === Mode.Rail) return '#b00'; + if (mode === Mode.Coach) return '#0f0'; + if (mode === Mode.Metro) return '#f00'; + if (mode === Mode.Bus) return '#0f0'; + if (mode === Mode.Tram) return '#f00'; + if (mode === Mode.Trolleybus) return '#0f0'; + if (mode === Mode.Water) return '#f0f'; + if (mode === Mode.Air) return '#f0f'; + if (mode === Mode.Cableway) return '#f0f'; + if (mode === Mode.Funicular) return '#f0f'; + if (mode === Mode.Monorail) return '#f0f'; + if (mode === Mode.Taxi) return '#f0f'; + return '#aaa'; +}; diff --git a/client-next/src/util/isTransitMode.ts b/client-next/src/util/isTransitMode.ts new file mode 100644 index 00000000000..17fa0279bbc --- /dev/null +++ b/client-next/src/util/isTransitMode.ts @@ -0,0 +1,18 @@ +import { Mode } from '../gql/graphql.ts'; + +export function isTransitMode(mode: Mode) { + return ( + mode === Mode.Rail || + mode === Mode.Coach || + mode === Mode.Metro || + mode === Mode.Bus || + mode === Mode.Tram || + mode === Mode.Water || + mode === Mode.Air || + mode === Mode.Cableway || + mode === Mode.Funicular || + mode === Mode.Trolleybus || + mode === Mode.Monorail || + mode === Mode.Taxi + ); +} diff --git a/client-next/src/vite-env.d.ts b/client-next/src/vite-env.d.ts new file mode 100644 index 00000000000..11f02fe2a00 --- /dev/null +++ b/client-next/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/client-next/tsconfig.json b/client-next/tsconfig.json new file mode 100644 index 00000000000..a7fc6fbf23d --- /dev/null +++ b/client-next/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/client-next/tsconfig.node.json b/client-next/tsconfig.node.json new file mode 100644 index 00000000000..42872c59f5b --- /dev/null +++ b/client-next/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/client-next/vite.config.ts b/client-next/vite.config.ts new file mode 100644 index 00000000000..f5fa0ab82ee --- /dev/null +++ b/client-next/vite.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + base: '/debug-client-preview/', + build: { + outDir: '../src/client/debug-client-preview', + emptyOutDir: true, + }, +}); diff --git a/doc-templates/StopConsolidation.md b/doc-templates/StopConsolidation.md index 690fe0b98e6..c92cab6afe1 100644 --- a/doc-templates/StopConsolidation.md +++ b/doc-templates/StopConsolidation.md @@ -30,7 +30,7 @@ This has the following consequences However, this feature has also severe downsides: - - It makes realtime trip updates referencing a stop id much more complicated and in many cases + - It makes real-time trip updates referencing a stop id much more complicated and in many cases impossible to resolve. You can only reference a stop by its sequence, which only works in GTFS-RT, not Siri. - Fare calculation and transfers are unlikely to work as expected. diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index b32f782aa32..05da4cf4d0a 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -28,7 +28,6 @@ Sections follow that describe particular settings in more depth. | [graph](#graph) | `uri` | URI to the graph object file for reading and writing. | *Optional* | | 2.0 | | [gsCredentials](#gsCredentials) | `string` | Local file system path to Google Cloud Platform service accounts credentials file. | *Optional* | | 2.0 | | [includeEllipsoidToGeoidDifference](#includeEllipsoidToGeoidDifference) | `boolean` | Include the Ellipsoid to Geoid difference in the calculations of every point along every StreetWithElevationEdge. | *Optional* | `false` | 2.0 | -| matchBusRoutesToStreets | `boolean` | Based on GTFS shape data, guess which OSM streets each bus runs on to improve stop linking. | *Optional* | `false` | 1.5 | | maxAreaNodes | `integer` | Visibility calculations for an area will not be done if there are more nodes than this limit. | *Optional* | `150` | 2.1 | | [maxDataImportIssuesPerFile](#maxDataImportIssuesPerFile) | `integer` | When to split the import report. | *Optional* | `1000` | 2.0 | | maxElevationPropagationMeters | `integer` | The maximum distance to propagate elevation to vertices which have no elevation. | *Optional* | `2000` | 1.5 | diff --git a/docs/Changelog.md b/docs/Changelog.md index a2eece5d664..cffe55c0f6f 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -40,7 +40,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Interpolate increasing stop times for GTFS-RT cancelled trips [#5348](https://github.com/opentripplanner/OpenTripPlanner/pull/5348) - Remove itineraries outside the search window in arriveBy search [#5433](https://github.com/opentripplanner/OpenTripPlanner/pull/5433) - Add back walk-reluctance in Transmodel API [#5471](https://github.com/opentripplanner/OpenTripPlanner/pull/5471) -- Make `feedId` required for realtime updaters [#5502](https://github.com/opentripplanner/OpenTripPlanner/pull/5502) +- Make `feedId` required for real-time updaters [#5502](https://github.com/opentripplanner/OpenTripPlanner/pull/5502) - Fix serialization of `AtomicInteger` [#5508](https://github.com/opentripplanner/OpenTripPlanner/pull/5508) - Improve linking of fixed stops used by flex trips [#5503](https://github.com/opentripplanner/OpenTripPlanner/pull/5503) - Keep min transfer filter is not local to group-by-filters [#5436](https://github.com/opentripplanner/OpenTripPlanner/pull/5436) @@ -49,6 +49,20 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Check transport mode when mapping GroupStops [#5518](https://github.com/opentripplanner/OpenTripPlanner/pull/5518) - Cleanup trip times - Part A [#5437](https://github.com/opentripplanner/OpenTripPlanner/pull/5437) - Transfer cost limit [#5516](https://github.com/opentripplanner/OpenTripPlanner/pull/5516) +- Fix missed trip when arrive-by search-window is off by one minute [#5520](https://github.com/opentripplanner/OpenTripPlanner/pull/5520) +- Transit group priority - Part 1 [#4999](https://github.com/opentripplanner/OpenTripPlanner/pull/4999) +- Remove `matchBusRoutesToStreets` [#5523](https://github.com/opentripplanner/OpenTripPlanner/pull/5523) +- Rename realtime to real-time in docs [#5535](https://github.com/opentripplanner/OpenTripPlanner/pull/5535) +- Add same submode in alternative legs filter [#5548](https://github.com/opentripplanner/OpenTripPlanner/pull/5548) +- Fix issue where stop points are sometimes added twice to index [#5552](https://github.com/opentripplanner/OpenTripPlanner/pull/5552) +- Improve shutdown logic [#5514](https://github.com/opentripplanner/OpenTripPlanner/pull/5514) +- Create TripOnServiceDate for new siri realtime servicejourneys [#5542](https://github.com/opentripplanner/OpenTripPlanner/pull/5542) +- New debug client [#5334](https://github.com/opentripplanner/OpenTripPlanner/pull/5334) +- Improve paging - avoid duplicates and missed itineraries when paging [#5551](https://github.com/opentripplanner/OpenTripPlanner/pull/5551) +- Create own parking preferences for bike and car in the internal model [#5521](https://github.com/opentripplanner/OpenTripPlanner/pull/5521) +- Make Transmodel GraphQl API an official OTP API [#5573](https://github.com/opentripplanner/OpenTripPlanner/pull/5573) +- Add option to include stations in `nearest` search [#5390](https://github.com/opentripplanner/OpenTripPlanner/pull/5390) +- GTFS Flex spec update: separate columns for `location_id`, `location_group_id` [#5564](https://github.com/opentripplanner/OpenTripPlanner/pull/5564) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) @@ -86,7 +100,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Graceful timeout error handling [#5130](https://github.com/opentripplanner/OpenTripPlanner/pull/5130) - Log http request headers - like correlationId [#5131](https://github.com/opentripplanner/OpenTripPlanner/pull/5131) - Fix vertex removal race condition [#5141](https://github.com/opentripplanner/OpenTripPlanner/pull/5141) -- Comment out replacing DSJ-ID from planned data with ID from realtime-data [#5140](https://github.com/opentripplanner/OpenTripPlanner/pull/5140) +- Comment out replacing DSJ-ID from planned data with ID from real-time-data [#5140](https://github.com/opentripplanner/OpenTripPlanner/pull/5140) - Remove San Francisco and vehicle rental fare calculators [#5145](https://github.com/opentripplanner/OpenTripPlanner/pull/5145) - Remove batch query from Transmodel API [#5147](https://github.com/opentripplanner/OpenTripPlanner/pull/5147) - Fix nullable absolute direction in GTFS GraphQL API [#5159](https://github.com/opentripplanner/OpenTripPlanner/pull/5159) @@ -184,7 +198,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Remove all edges from stop vertex in island pruning [#4846](https://github.com/opentripplanner/OpenTripPlanner/pull/4846) - Filter functionality for GroupOfLines/GroupOfRoutes in TransmodelAPI [#4812](https://github.com/opentripplanner/OpenTripPlanner/pull/4812) - Mapping for maxAccessEgressDurationPerMode in Transmodel API [#4829](https://github.com/opentripplanner/OpenTripPlanner/pull/4829) -- Use headsign from the original pattern in a realtime added pattern if the stop sequence is unchanged [#4845](https://github.com/opentripplanner/OpenTripPlanner/pull/4845) +- Use headsign from the original pattern in a real-time added pattern if the stop sequence is unchanged [#4845](https://github.com/opentripplanner/OpenTripPlanner/pull/4845) - Remove RouteMatcher [#4821](https://github.com/opentripplanner/OpenTripPlanner/pull/4821) - Improve boarding location linking on platforms [#4852](https://github.com/opentripplanner/OpenTripPlanner/pull/4852) - Always check allowed modes in VehicleRentalEdge [#4810](https://github.com/opentripplanner/OpenTripPlanner/pull/4810) @@ -232,7 +246,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Experimental support for GTFS Fares V2 [#4338](https://github.com/opentripplanner/OpenTripPlanner/pull/4338) - Document JVM configuration options [#4492](https://github.com/opentripplanner/OpenTripPlanner/pull/4492) - Support for HTTPS datasource for Graph Building [#4482](https://github.com/opentripplanner/OpenTripPlanner/pull/4482) -- Metrics for realtime trip updaters [#4471](https://github.com/opentripplanner/OpenTripPlanner/pull/4471) +- Metrics for real-time trip updaters [#4471](https://github.com/opentripplanner/OpenTripPlanner/pull/4471) - Configuration Documentation generated programmatically [#4478](https://github.com/opentripplanner/OpenTripPlanner/pull/4478) @@ -263,7 +277,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Enable overriding maxDirectStreetDuration per mode [#4104](https://github.com/opentripplanner/OpenTripPlanner/pull/4104) - Preserve language in SIRI/GTFS-RT alert messages [#4117](https://github.com/opentripplanner/OpenTripPlanner/pull/4117) - Use board/alight cost only for transits [#4079](https://github.com/opentripplanner/OpenTripPlanner/pull/4079) -- Improve SIRI realtime performance by reducing stopPattern duplicates [#4038](https://github.com/opentripplanner/OpenTripPlanner/pull/4038) +- Improve SIRI real-time performance by reducing stopPattern duplicates [#4038](https://github.com/opentripplanner/OpenTripPlanner/pull/4038) - Siri updaters for Azure ServiceBus [#4106](https://github.com/opentripplanner/OpenTripPlanner/pull/4106) - Fallback to recorded/expected arrival/departure time if other one is missing in SIRI-ET [#4055](https://github.com/opentripplanner/OpenTripPlanner/pull/4055) - Allow overriding GBFS system_id with configuration [#4147](https://github.com/opentripplanner/OpenTripPlanner/pull/4147) @@ -272,7 +286,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Don't indicate stop has been updated when NO_DATA is defined [#3962](https://github.com/opentripplanner/OpenTripPlanner/pull/3962) - Implement nearby searches for car and bicycle parking [#4165](https://github.com/opentripplanner/OpenTripPlanner/pull/4165) - Do not link cars to stop vertices in routing [#4166](https://github.com/opentripplanner/OpenTripPlanner/pull/4166) -- Add Siri realtime occupancy info [#4180](https://github.com/opentripplanner/OpenTripPlanner/pull/4180) +- Add Siri real-time occupancy info [#4180](https://github.com/opentripplanner/OpenTripPlanner/pull/4180) - Add gtfs stop description translations [#4158](https://github.com/opentripplanner/OpenTripPlanner/pull/4158) - Add option to discard min transfer times [#4195](https://github.com/opentripplanner/OpenTripPlanner/pull/4195) - Use negative delay from first stop in a GTFS RT update in previous stop times when required [#4035](https://github.com/opentripplanner/OpenTripPlanner/pull/4035) @@ -301,7 +315,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Account for boarding restrictions when calculating direct transfers [#4421](https://github.com/opentripplanner/OpenTripPlanner/pull/4421) - Configure the import of OSM extracts individually [#4419](https://github.com/opentripplanner/OpenTripPlanner/pull/4419) - Configure the import of elevation data individually [#4423](https://github.com/opentripplanner/OpenTripPlanner/pull/4423) -- Return typed errors from realtime updates, prepare for realtime statistics [#4424](https://github.com/opentripplanner/OpenTripPlanner/pull/4424) +- Return typed errors from real-time updates, prepare for real-time statistics [#4424](https://github.com/opentripplanner/OpenTripPlanner/pull/4424) - Add feature switch for matching ET messages on stops [#4347](https://github.com/opentripplanner/OpenTripPlanner/pull/4347) - Make safety defaults customizable for walking and cycling [#4438](https://github.com/opentripplanner/OpenTripPlanner/pull/4438) - Fix block-based interlining when importing several GTFS feeds [#4468](https://github.com/opentripplanner/OpenTripPlanner/pull/4468) @@ -339,7 +353,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle ### Detailed changes by Pull Request -- Fix NullPointerException when a RealTime update do not match an existing TripPattern [#3284](https://github.com/opentripplanner/OpenTripPlanner/issues/3284) +- Fix NullPointerException when a Real-Time update do not match an existing TripPattern [#3284](https://github.com/opentripplanner/OpenTripPlanner/issues/3284) - Support for versioning the configuration files [#3282](https://github.com/opentripplanner/OpenTripPlanner/issues/3282) - Prioritize "direct" routes over transfers in group-filters [#3309](https://github.com/opentripplanner/OpenTripPlanner/issues/3309) - Remove poor transit results for short trips, when walking is better [#3331](https://github.com/opentripplanner/OpenTripPlanner/issues/3331) @@ -426,7 +440,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Remove build parameter 'useTransfersTxt' [#3791](https://github.com/opentripplanner/OpenTripPlanner/pull/3791) - Add cursor-based paging [#3759](https://github.com/opentripplanner/OpenTripPlanner/pull/3759) - Data overlay sandbox feature [#3760](https://github.com/opentripplanner/OpenTripPlanner/pull/3760) -- Add support for sandboxed realtime vehicle parking updaters [#3796](https://github.com/opentripplanner/OpenTripPlanner/pull/3796) +- Add support for sandboxed real-time vehicle parking updaters [#3796](https://github.com/opentripplanner/OpenTripPlanner/pull/3796) - Add reading and exposing of Netex submodes [#3793](https://github.com/opentripplanner/OpenTripPlanner/pull/3793) - Fix: Account for wait-time in no-wait Raptor strategy [#3798](https://github.com/opentripplanner/OpenTripPlanner/pull/3798) - Read in flex window from Netex feeds [#3800](https://github.com/opentripplanner/OpenTripPlanner/pull/3800) @@ -436,7 +450,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Store stop indices in leg and use them to simplify logic in TripTimeShortHelper [#3820](https://github.com/opentripplanner/OpenTripPlanner/pull/3820) - Include all trips in `stopTimesForStop` [#3817](https://github.com/opentripplanner/OpenTripPlanner/pull/3817) - Store all alerts and add support for route_type and direction_id selectors [#3780](https://github.com/opentripplanner/OpenTripPlanner/pull/3780) -- Remove outdated realtime-update from TimetableSnapshot [#3770](https://github.com/opentripplanner/OpenTripPlanner/pull/3770) +- Remove outdated real-time-update from TimetableSnapshot [#3770](https://github.com/opentripplanner/OpenTripPlanner/pull/3770) - Contributing Guide [#3769](https://github.com/opentripplanner/OpenTripPlanner/pull/3769) - OTP support for NeTEx branding [#3829](https://github.com/opentripplanner/OpenTripPlanner/pull/3829) - Not allowed transfers and support for GTFS transfer points [#3792](https://github.com/opentripplanner/OpenTripPlanner/pull/3792) @@ -463,7 +477,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Optimize RAPTOR trip search by pre-calculating arrival/departure time arrays [#3919](https://github.com/opentripplanner/OpenTripPlanner/pull/3919) - Make turn restrictions faster and thread-safe by moving them into StreetEdge [#3899](https://github.com/opentripplanner/OpenTripPlanner/pull/3899) - Add routing using frequency trips [#3916](https://github.com/opentripplanner/OpenTripPlanner/pull/3916) -- Remove ET realtime override code [#3912](https://github.com/opentripplanner/OpenTripPlanner/pull/3912) +- Remove ET real-time override code [#3912](https://github.com/opentripplanner/OpenTripPlanner/pull/3912) - Allow traversal of pathways without traversal time, distance or steps [#3910](https://github.com/opentripplanner/OpenTripPlanner/pull/3910) @@ -493,8 +507,8 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Support for next/previous paging trip search results [#2941](https://github.com/opentripplanner/OpenTripPlanner/issues/2941) - Fix mismatch in duration for walk legs, resulting in negative wait times [#2955](https://github.com/opentripplanner/OpenTripPlanner/issues/2955) - NeTEx import now supports ServiceLinks [#2951](https://github.com/opentripplanner/OpenTripPlanner/issues/2951) -- Also check TripPatterns added by realtime when showing stoptimes for stop [#2954](https://github.com/opentripplanner/OpenTripPlanner/issues/2954) -- Copy geometries from previous TripPattern when realtime updates result in a TripPattern being replaced [#2987](https://github.com/opentripplanner/OpenTripPlanner/issues/2987) +- Also check TripPatterns added by real-time when showing stoptimes for stop [#2954](https://github.com/opentripplanner/OpenTripPlanner/issues/2954) +- Copy geometries from previous TripPattern when real-time updates result in a TripPattern being replaced [#2987](https://github.com/opentripplanner/OpenTripPlanner/issues/2987) - Support for the Norwegian language. - Update pathways support to official GTFS specification [#2923](https://github.com/opentripplanner/OpenTripPlanner/issues/2923) - Support for XML (de-)serialization is REMOVED from the REST API [#3031](https://github.com/opentripplanner/OpenTripPlanner/issues/3031) @@ -530,7 +544,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Remove the coupling to OneBusAway GTFS within OTP's internal model by creating new classes replacing the external classes [#2494](https://github.com/opentripplanner/OpenTripPlanner/issues/2494) - Allow itineraries in response to be sorted by duration [#2593](https://github.com/opentripplanner/OpenTripPlanner/issues/2593) - Fix reverse optimization bug #2653, #2411 -- increase GTFS-realtime feeds size limit from 64MB to 2G [#2738](https://github.com/opentripplanner/OpenTripPlanner/issues/2738) +- increase GTFS-real-time feeds size limit from 64MB to 2G [#2738](https://github.com/opentripplanner/OpenTripPlanner/issues/2738) - Fix XML response serialization [#2685](https://github.com/opentripplanner/OpenTripPlanner/issues/2685) - Refactor InterleavedBidirectionalHeuristic [#2671](https://github.com/opentripplanner/OpenTripPlanner/issues/2671) - Add "Accept" headers to GTFS-RT HTTP requests [#2796](https://github.com/opentripplanner/OpenTripPlanner/issues/2796) @@ -542,7 +556,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Support OSM highway=razed tag [#2660](https://github.com/opentripplanner/OpenTripPlanner/issues/2660) - Add bicimad bike rental updater [#2503](https://github.com/opentripplanner/OpenTripPlanner/issues/2503) - Add Smoove citybikes updater [#2515](https://github.com/opentripplanner/OpenTripPlanner/issues/2515) -- Allow big GTFS-realtime feeds by increasing protobuf size limit to 2G [#2739](https://github.com/opentripplanner/OpenTripPlanner/issues/2739) +- Allow big GTFS-real-time feeds by increasing protobuf size limit to 2G [#2739](https://github.com/opentripplanner/OpenTripPlanner/issues/2739) - Cannot transfer between stops at exactly the same location [#2371](https://github.com/opentripplanner/OpenTripPlanner/issues/2371) - Improve documentation for `mode` routing parameter [#2809](https://github.com/opentripplanner/OpenTripPlanner/issues/2809) - Switched to single license file, removing all OTP and OBA file license headers @@ -779,7 +793,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Disable some time consuming graph building steps by default - Finnish and Swedish translations - Subway-specific JSON configuration options (street to platform time) -- Realtime fetch / streaming configurable via JSON +- Real-time fetch / streaming configurable via JSON - Stairs reluctance is much higher when carrying a bike - Graph visualizer routing progress animates when a search is triggered via the web API - Assume WGS84 (spherical distance calculations) everywhere @@ -847,7 +861,7 @@ represents a much earlier stage in the development of OTP. ## 0.7.0 (2012-04-29) - Bike rental support (thanks Laurent Grégoire) -- Realtime bike rental availability feed support +- Real-time bike rental availability feed support - Updated to new version of One Bus Away GTFS/CSV, fixing timezone and string interning issues (thanks Brian Ferris) - Bugfixes in area routing, OSM loading, nonexistant NED tiles, route short names - Dutch and French language updates @@ -874,7 +888,7 @@ represents a much earlier stage in the development of OTP. - git commit IDs included in MavenVersion, allowing clearer OTP/Graph version mismatch warnings - fix problems with immediate reboarding and unexpected edges in itinerary builder - favicon (thanks Joel Haasnoot) -- Legs in API response have TripId (for realtime information) +- Legs in API response have TripId (for real-time information) - Polish locale (thanks Łukasz Witkowski) - transfers.txt can define station paths, entry costs for stations - allow loading a base graph into graphbuilder instead of starting from scratch diff --git a/docs/Configuration.md b/docs/Configuration.md index bfd168d5955..f1c0af287ab 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -65,29 +65,29 @@ documentation below we will refer to the following types: -| Type | Description | Examples | -|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| -| `boolean` | This is the Boolean JSON type | `true`, `false` | -| `string` | This is the String JSON type. | `"This is a string!"` | -| `double` | A decimal floating point _number_. 64 bit. | `3.15` | -| `integer` | A decimal integer _number_. 32 bit. | `1`, `-7`, `2100` | -| `long` | A decimal integer _number_. 64 bit. | `-1234567890` | -| `enum` | A fixed set of string literals. | `"RAIL"`, `"BUS"` | -| `enum-map` | List of key/value pairs, where the key is a enum and the value can be any given type. | `{ "RAIL: 1.2, "BUS": 2.3 }` | -| `enum-set` | List of enum string values | `[ "RAIL", "TRAM" ]` | -| `locale` | _`Language[\_country[\_variant]]`_. A Locale object represents a specific geographical, political, or cultural region. For more information see the [Java Locale](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Locale.html). | `"en_US"`, `"nn_NO"` | -| `date` | Local date. The format is _YYYY-MM-DD_ (ISO-8601). | `"2020-09-21"` | -| `date-or-period` | A _local date_, or a _period_ relative to today. The local date has the format `YYYY-MM-DD` and the period has the format `PnYnMnD` or `-PnYnMnD` where `n` is a integer number. | `"P1Y"`, `"-P3M2D"`, `"P1D"` | -| `duration` | A _duration_ is a amount of time. The format is `PnDTnHnMnS` or `nDnHnMnS` where `n` is a integer number. The `D`(days), `H`(hours), `M`(minutes) and `S`(seconds) are not case sensitive. | `"3h"`, `"2m"`, `"1d5h2m3s"`, `"-P2dT-1s"` | -| `regexp` | A regular expression pattern used to match a sting. | `"$^"`, `"gtfs"`, `"\w{3})-.*\.xml"` | -| `uri` | An URI path to a resource like a file or a URL. Relative URIs are resolved relative to the OTP base path. | `"http://foo.bar/"`, `"file:///Users/jon/local/file"`, `"graph.obj"` | -| `time-zone` | Time-Zone ID | `"UTC"`, `"Europe/Paris"`, `"-05:00"` | -| `feed-scoped-id` | FeedScopedId | `"NO:1001"`, `"1:101"` | -| `cost-linear-function` | A cost-linear-function used to calculate a cost from another cost or time/duration. Given a function of time: ``` f(t) = a + b * t ``` then `a` is the constant time part, `b` is the time-coefficient, and `t` is the variable. If `a=0s` and `b=0.0`, then the cost is always `0`(zero). Examples: `0s + 2.5t`, `10m + 0t` and `1h5m59s + 9.9t` The `constant` must be 0 or a positive number or duration. The unit is seconds unless specified using the duration format. A duration is automatically converted to a cost. The `coefficient` must be in range: [0.0, 100.0] | | -| `time-penalty` | A time-penalty is used to add a penalty to the duration/arrival-time/depature-time for a path. It will be invisible to the end user, but used during the routing when comparing stop-arrival/paths. Given a function of time: ``` f(t) = a + b * t ``` then `a` is the constant time part, `b` is the time-coefficient, and `t` is the variable. If `a=0s` and `b=0.0`, then the cost is always `0`(zero). Examples: `0s + 2.5t`, `10m + 0 x` and `1h5m59s + 9.9t` The `constant` must be 0 or a positive number(seconds) or a duration. The `coefficient` must be in range: [0.0, 100.0] | | -| `map` | List of key/value pairs, where the key is a string and the value can be any given type. | `{ "one": 1.2, "two": 2.3 }` | -| `object` | Config object containing nested elements | `"walk": { "speed": 1.3, "reluctance": 5 }` | -| `array` | Config object containing an array/list of elements | `"array": [ 1, 2, 3 ]` | +| Type | Description | Examples | +|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| +| `boolean` | This is the Boolean JSON type | `true`, `false` | +| `string` | This is the String JSON type. | `"This is a string!"` | +| `double` | A decimal floating point _number_. 64 bit. | `3.15` | +| `integer` | A decimal integer _number_. 32 bit. | `1`, `-7`, `2100` | +| `long` | A decimal integer _number_. 64 bit. | `-1234567890` | +| `enum` | A fixed set of string literals. | `"RAIL"`, `"BUS"` | +| `enum-map` | List of key/value pairs, where the key is a enum and the value can be any given type. | `{ "RAIL: 1.2, "BUS": 2.3 }` | +| `enum-set` | List of enum string values | `[ "RAIL", "TRAM" ]` | +| `locale` | _`Language[\_country[\_variant]]`_. A Locale object represents a specific geographical, political, or cultural region. For more information see the [Java Locale](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Locale.html). | `"en_US"`, `"nn_NO"` | +| `date` | Local date. The format is _YYYY-MM-DD_ (ISO-8601). | `"2020-09-21"` | +| `date-or-period` | A _local date_, or a _period_ relative to today. The local date has the format `YYYY-MM-DD` and the period has the format `PnYnMnD` or `-PnYnMnD` where `n` is a integer number. | `"P1Y"`, `"-P3M2D"`, `"P1D"` | +| `duration` | A _duration_ is a amount of time. The format is `PnDTnHnMnS` or `nDnHnMnS` where `n` is a integer number. The `D`(days), `H`(hours), `M`(minutes) and `S`(seconds) are not case sensitive. | `"3h"`, `"2m"`, `"1d5h2m3s"`, `"-P2dT-1s"` | +| `regexp` | A regular expression pattern used to match a sting. | `"$^"`, `"gtfs"`, `"\w{3})-.*\.xml"` | +| `uri` | An URI path to a resource like a file or a URL. Relative URIs are resolved relative to the OTP base path. | `"http://foo.bar/"`, `"file:///Users/jon/local/file"`, `"graph.obj"` | +| `time-zone` | Time-Zone ID | `"UTC"`, `"Europe/Paris"`, `"-05:00"` | +| `feed-scoped-id` | FeedScopedId | `"NO:1001"`, `"1:101"` | +| `cost-linear-function` | A cost-linear-function used to calculate a cost from another cost or time/duration. Given a function of time: ``` f(t) = a + b * t ``` then `a` is the constant time part, `b` is the time-coefficient, and `t` is the variable. If `a=0s` and `b=0.0`, then the cost is always `0`(zero). Examples: `0s + 2.5t`, `10m + 0t` and `1h5m59s + 9.9t` The `constant` must be 0 or a positive number or duration. The unit is seconds unless specified using the duration format. A duration is automatically converted to a cost. The `coefficient` must be in range: [0.0, 100.0] | | +| `time-penalty` | A time-penalty is used to add a penalty to the duration/arrival-time/departure-time for a path. It will be invisible to the end user, but used during the routing when comparing stop-arrival/paths. Given a function of time: ``` f(t) = a + b * t ``` then `a` is the constant time part, `b` is the time-coefficient, and `t` is the variable. If `a=0s` and `b=0.0`, then the cost is always `0`(zero). Examples: `0s + 2.5t`, `10m + 0 x` and `1h5m59s + 9.9t` The `constant` must be 0 or a positive number(seconds) or a duration. The `coefficient` must be in range: [0.0, 100.0] | | +| `map` | List of key/value pairs, where the key is a string and the value can be any given type. | `{ "one": 1.2, "two": 2.3 }` | +| `object` | Config object containing nested elements | `"walk": { "speed": 1.3, "reluctance": 5 }` | +| `array` | Config object containing an array/list of elements | `"array": [ 1, 2, 3 ]` | @@ -233,7 +233,8 @@ Here is a list of all features which can be toggled on/off and their default val | `MinimumTransferTimeIsDefinitive` | If the minimum transfer time is a lower bound (default) or the definitive time for the transfer. Set this to `true` if you want to set a transfer time lower than what OTP derives from OSM data. | | | | `OptimizeTransfers` | OTP will inspect all itineraries found and optimize where (which stops) the transfer will happen. Waiting time, priority and guaranteed transfers are taken into account. | ✓️ | | | `ParallelRouting` | Enable performing parts of the trip planning in parallel. | | | -| `TransferConstraints` | Enforce transfers to happen according to the _transfers.txt_(GTFS) and Interchanges(NeTEx). Turing this _off_ will increase the routing performance a little. | ✓️ | | +| `TransferConstraints` | Enforce transfers to happen according to the _transfers.txt_ (GTFS) and Interchanges (NeTEx). Turning this _off_ will increase the routing performance a little. | ✓️ | | +| `TransmodelGraphQlApi` | Enable Transmodel (NeTEx) GraphQL API. | ✓️ | ✓️ | | `ActuatorAPI` | Endpoint for actuators (service health status). | | ✓️ | | `AsyncGraphQLFetchers` | Whether the @async annotation in the GraphQL schema should lead to the fetch being executed asynchronously. This allows batch or alias queries to run in parallel at the cost of consuming extra threads. | | | | `Co2Emissions` | Enable the emissions sandbox module. | | ✓️ | @@ -241,13 +242,12 @@ Here is a list of all features which can be toggled on/off and their default val | `FaresV2` | Enable import of GTFS-Fares v2 data. | | ✓️ | | `FlexRouting` | Enable FLEX routing. | | ✓️ | | `GoogleCloudStorage` | Enable Google Cloud Storage integration. | | ✓️ | -| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with realtime data | | ✓️ | +| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with real-time data | | ✓️ | | `ReportApi` | Enable the report API. | | ✓️ | | `RestAPIPassInDefaultConfigAsJson` | Enable a default RouteRequest to be passed in as JSON on the REST API - FOR DEBUGGING ONLY! | | | | `SandboxAPIGeocoder` | Enable the Geocoder API. | | ✓️ | | `SandboxAPIMapboxVectorTilesApi` | Enable Mapbox vector tiles API. | | ✓️ | | `SandboxAPIParkAndRideApi` | Enable park-and-ride endpoint. | | ✓️ | -| `SandboxAPITransmodelApi` | Enable Entur Transmodel(NeTEx) GraphQL API. | | ✓️ | | `SandboxAPITravelTime` | Enable the isochrone/travel time surface API. | | ✓️ | | `TransferAnalyzer` | Analyze transfers during graph build. | | ✓️ | | `VehicleToStopHeuristics` | Enable improved heuristic for park-and-ride queries. | | ✓️ | diff --git a/docs/Data-Sources.md b/docs/Data-Sources.md index 317e0a1f2f7..bf426186f57 100644 --- a/docs/Data-Sources.md +++ b/docs/Data-Sources.md @@ -5,7 +5,7 @@ At the core of OpenTripPlanner is a library of Java code that finds efficient paths through multi-modal transportation networks built from [OpenStreetMap](http://wiki.openstreetmap.org/wiki/Main_Page) -and [GTFS](https://developers.google.com/transit/gtfs/) data. It can also receive GTFS-RT (realtime) +and [GTFS](https://developers.google.com/transit/gtfs/) data. It can also receive GTFS-RT (real-time) data. In addition to GTFS, OTP can also load data in the Nordic Profile of Netex, the EU-standard transit diff --git a/docs/Deployments.md b/docs/Deployments.md index f2946a9f0ba..d1df3984b05 100644 --- a/docs/Deployments.md +++ b/docs/Deployments.md @@ -6,7 +6,7 @@ The following are known deployments of OTP in a government- or agency-sponsored * **Norway (nationwide)** Since November 2017, the national integrated ticketing agency Entur has prodvided a [national journey planner](https://en-tur.no/) which consumes schedule data in the EU - standard NeTEx format with SIRI realtime updates. Entur has contributed greatly to the OTP2 effort + standard NeTEx format with SIRI real-time updates. Entur has contributed greatly to the OTP2 effort and primarily uses OTP2 in production, handling peak loads in excess of 20 requests per second. Most regional agencies in Norway, like **Ruter, Oslo area** uses OTP as a service provided by Entur. * **Finland (nationwide)** The [Helsinki Regional Transport Authority](https://www.reittiopas.fi/), @@ -22,7 +22,7 @@ The following are known deployments of OTP in a government- or agency-sponsored service [Matkahuolto](https://en.wikipedia.org/wiki/Matkahuolto) has [developed a trip planner in partnership with Kyyti](https://www.kyyti.com/matkahuoltos-new-app-brings-real-travel-chains-within-the-reach-of-citizens-in-addition-to-coach-travel-hsl-tickets-are-also-available/). * **Skåne, Sweden**, the JourneyPlanner and mobile app for the regional transit agency [Skånetrafiken](https://www.skanetrafiken.se/) - uses OTP2 with the nordic profile of NeTEx and SIRI for realtime updates. + uses OTP2 with the nordic profile of NeTEx and SIRI for real-time updates. * [**Northern Colorado**](https://discover.rideno.co/) * [**Philadelphia and surrounding areas**](https://plan.septa.org) * **Portland, Oregon** TriMet is the agency that originally started the OpenTripPlanner project. diff --git a/docs/Netex-Norway.md b/docs/Netex-Norway.md index 6157849bedc..0a447237592 100644 --- a/docs/Netex-Norway.md +++ b/docs/Netex-Norway.md @@ -85,11 +85,11 @@ using OTP's built in testing web client. Try some long trips like Oslo to Bergen get long distance trains and flights as alternatives. You might need to increase the walking limit above its very low default value. -## Adding SIRI Realtime Data +## Adding SIRI Real-time Data Another important feature in OTP2 is the ability to -use [SIRI realtime data](https://en.wikipedia.org/wiki/Service_Interface_for_Real_Time_Information). -Within the EU data standards, SIRI is analogous to GTFS-RT: a way to apply realtime updates on top +use [SIRI real-time data](https://en.wikipedia.org/wiki/Service_Interface_for_Real_Time_Information). +Within the EU data standards, SIRI is analogous to GTFS-RT: a way to apply real-time updates on top of schedule data. While technically a distinct specification from Netex, both Netex and SIRI use the Transmodel vocabulary, allowing SIRI messages to reference entities in Netex schedule data. Like GTFS-RT, SIRI is consumed by OTP2 using "graph updaters" which are configured in @@ -143,6 +143,6 @@ Note that between these SIRI updaters and the GTFS-RT Websocket updater, we now and streaming examples of GTFS-RT "incrementality" semantics, so should be able to finalize that part of the specification. -The final updater regularly performs a copy of the realtime data into a format suitable for use by -OTP2's new Raptor router. Without this updater the realtime data will be received and cataloged, but +The final updater regularly performs a copy of the real-time data into a format suitable for use by +OTP2's new Raptor router. Without this updater the real-time data will be received and cataloged, but not visible to the router. diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index f814753df4a..9e891cf732d 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -19,7 +19,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe | arriveBy | `boolean` | Whether the trip should depart or arrive at the specified date and time. | *Optional* | `false` | 2.0 | | [bikeBoardCost](#rd_bikeBoardCost) | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. | *Optional* | `600` | 2.0 | | bikeParkCost | `integer` | Cost to park a bike. | *Optional* | `120` | 2.0 | -| bikeParkTime | `integer` | Time to park a bike. | *Optional* | `60` | 2.0 | +| 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 | @@ -35,7 +35,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe | carDecelerationSpeed | `double` | The deceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | | carDropoffTime | `integer` | Time to park a car in a park and ride, w/o taking into account driving and walking cost. | *Optional* | `120` | 2.0 | | carParkCost | `integer` | Cost of parking a car. | *Optional* | `120` | 2.1 | -| carParkTime | `integer` | Time to park a car | *Optional* | `60` | 2.1 | +| 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 | @@ -47,7 +47,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe | elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | | escalatorReluctance | `double` | A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time | *Optional* | `1.5` | 2.4 | | geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | -| ignoreRealtimeUpdates | `boolean` | When true, realtime updates are ignored during this search. | *Optional* | `false` | 2.0 | +| ignoreRealtimeUpdates | `boolean` | When true, real-time updates are ignored during this search. | *Optional* | `false` | 2.0 | | [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | | locale | `locale` | TODO | *Optional* | `"en_US"` | 2.0 | | [maxDirectStreetDuration](#rd_maxDirectStreetDuration) | `duration` | This is the maximum duration for a direct street search for each mode. | *Optional* | `"PT4H"` | 2.1 | @@ -57,6 +57,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe | 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 | | [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 | @@ -107,6 +108,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe |    [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 | | [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 | @@ -245,6 +247,18 @@ 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

+ +**Since version:** `2.5` ∙ **Type:** `string` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"0s + 1.00 t"` +**Path:** /routingDefaults + +The relax function for 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-priority-groups. + +

relaxTransitSearchGeneralizedCostAtDestination

**Since version:** `2.3` ∙ **Type:** `double` ∙ **Cardinality:** `Optional` @@ -466,6 +480,9 @@ Sometimes there is a need to configure a longer alighting times for specific mod 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). + +

boardSlackForMode

**Since version:** `2.0` ∙ **Type:** `enum map of duration` ∙ **Cardinality:** `Optional` @@ -700,6 +717,9 @@ done because some street modes searches are much more resource intensive than ot 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` @@ -707,6 +727,9 @@ Vehicle parking facilities that don't have one of these tags will receive an ext 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` @@ -789,6 +812,26 @@ This enables the transfer wait time optimization. If not enabled generalizedCost function is used to pick the optimal transfer point. +

transitPriorityGroups

+ +**Since version:** `2.5` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` +**Path:** /routingDefaults + +Transit priority groups configuration + +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 +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. + + +**THIS IS STILL AN EXPERIMENTAL FEATURE - IT MAY CHANGE WITHOUT ANY NOTICE!** +

transitReluctanceForMode

**Since version:** `2.1` ∙ **Type:** `enum map of double` ∙ **Cardinality:** `Optional` @@ -912,7 +955,7 @@ include stairs as a last result. "dropOffTime" : 30, "dropOffCost" : 30 }, - "bikeParkTime" : 60, + "bikeParkTime" : "1m", "bikeParkCost" : 120, "carDropoffTime" : 120, "waitReluctance" : 1.0, diff --git a/docs/RouterConfiguration.md b/docs/RouterConfiguration.md index d0a58384819..65f50260ee5 100644 --- a/docs/RouterConfiguration.md +++ b/docs/RouterConfiguration.md @@ -46,7 +46,7 @@ A full list of them can be found in the [RouteRequest](RouteRequest.md). |          [logKey](#server_traceParameters_0_logKey) | `string` | The log event key used. | *Optional* | | 2.4 | | timetableUpdates | `object` | Global configuration for timetable updaters. | *Optional* | | 2.2 | |    [maxSnapshotFrequency](#timetableUpdates_maxSnapshotFrequency) | `duration` | How long a snapshot should be cached. | *Optional* | `"PT1S"` | 2.2 | -|    purgeExpiredData | `boolean` | Should expired realtime data be purged from the graph. Apply to GTFS-RT and Siri updates. | *Optional* | `true` | 2.2 | +|    purgeExpiredData | `boolean` | Should expired real-time data be purged from the graph. Apply to GTFS-RT and Siri updates. | *Optional* | `true` | 2.2 | | [transit](#transit) | `object` | Configuration for transit searches with RAPTOR. | *Optional* | | na | |    [iterationDepartureStepInSeconds](#transit_iterationDepartureStepInSeconds) | `integer` | Step for departure times between each RangeRaptor iterations. | *Optional* | `60` | na | |    [maxNumberOfTransfers](#transit_maxNumberOfTransfers) | `integer` | This parameter is used to allocate enough memory space for Raptor. | *Optional* | `12` | na | @@ -473,7 +473,7 @@ Used to group requests when monitoring OTP. "dropOffTime" : 30, "dropOffCost" : 30 }, - "bikeParkTime" : 60, + "bikeParkTime" : "1m", "bikeParkCost" : 120, "carDropoffTime" : 120, "waitReluctance" : 1.0, diff --git a/docs/SandboxExtension.md b/docs/SandboxExtension.md index 55ee214979a..38988f8245f 100644 --- a/docs/SandboxExtension.md +++ b/docs/SandboxExtension.md @@ -14,9 +14,8 @@ provided "as is". - [Geocoder API](sandbox/GeocoderAPI.md) - Adds an API to search for corners, stops and stations. - [Transfer analyser](sandbox/transferanalyzer.md) - Module used for analyzing the transfers between nearby stops generated by routing via OSM data. -- [Transmodel API](sandbox/TransmodelApi.md) - Enturs GraphQL Transmodel API. -- [SIRI Updater](sandbox/SiriUpdater.md) - Update OTP with realtime information from a Transmodel SIRI data source. -- [SIRI Azure Updater](sandbox/SiriAzureUpdater.md) - fetch SIRI realtime data through *Azure Service Bus* +- [SIRI Updater](sandbox/SiriUpdater.md) - Update OTP with real-time information from a Transmodel SIRI data source. +- [SIRI Azure Updater](sandbox/SiriAzureUpdater.md) - fetch SIRI real-time data through *Azure Service Bus* - [VehicleRentalServiceDirectory](sandbox/VehicleRentalServiceDirectory.md) - GBFS service directory endpoint. - [Smoove Bike Rental Updator Support](sandbox/SmooveBikeRental.md) - Smoove Bike Rental Updator(HSL) - [Mapbox Vector Tiles API](sandbox/MapboxVectorTilesApi.md) - Mapbox Vector Tiles API diff --git a/docs/System-Requirements.md b/docs/System-Requirements.md index 133370b8ede..387bba0cd03 100644 --- a/docs/System-Requirements.md +++ b/docs/System-Requirements.md @@ -10,7 +10,7 @@ OTP is relatively memory-hungry as it includes all the required data in memory. Single thread performance is an important factor for OTP's performance. Additionally, OTP benefits from larger CPU cache as reading from memory can be a bottleneck. -OTP's performance scales with the number of available CPU cores. OTP processes each request in a separate thread and usually one request doesn't utilize more than one thread, but with some requests and configurations, it's possible that multiple threads are used in parallel for a small part of a request or to process multiple queries within one request. How much parallel processing we utilize in requests might change in the future. Realtime updates also run in a separate thread. Therefore, to have a good performance, it makes sense to have multiple cores available. How OTP uses parallel processing also depends on the available cores (<= 2 cores vs >2 cores) in some cases. Therefore, load testing should be done against a machine that doesn't differ too much from production machines. +OTP's performance scales with the number of available CPU cores. OTP processes each request in a separate thread and usually one request doesn't utilize more than one thread, but with some requests and configurations, it's possible that multiple threads are used in parallel for a small part of a request or to process multiple queries within one request. How much parallel processing we utilize in requests might change in the future. Real-time updates also run in a separate thread. Therefore, to have a good performance, it makes sense to have multiple cores available. How OTP uses parallel processing also depends on the available cores (<= 2 cores vs >2 cores) in some cases. Therefore, load testing should be done against a machine that doesn't differ too much from production machines. Entur and the Digitransit project have found that the 3rd generation AMD processors have a slightly better performance for OTP2 than the Intel 3rd generation CPUs (and especially better than the 2nd generation CPUs). diff --git a/docs/Version-Comparison.md b/docs/Version-Comparison.md index 8700b23e657..454188d022e 100644 --- a/docs/Version-Comparison.md +++ b/docs/Version-Comparison.md @@ -139,10 +139,10 @@ Details of those two APIs are available at the following pages: - [GTFS GraphQL API](apis/GTFS-GraphQL-API.md) - HSL's GraphQL API used by the Digitransit project. -- [Transmodel API](sandbox/TransmodelApi.md) - Entur´s Transmodel API +- [Transmodel API](apis/TransmodelApi.md) - Entur's Transmodel API The plan is to merge the two APIs above, clean it up and make it the new official API. The HSL API -uses GTFS terminology, while the Entur API is Transmodel(NeTEx) based. Both APIs are similar in +uses GTFS terminology, while the Entur API is Transmodel (NeTEx) based. Both APIs are similar in semantics and structure, and provide the same functionality. ## Additional characteristics added in OTP2 diff --git a/docs/apis/Apis.md b/docs/apis/Apis.md index 4d6c7f46694..48b530a57e1 100644 --- a/docs/apis/Apis.md +++ b/docs/apis/Apis.md @@ -6,7 +6,7 @@ The [GTFS GraphQL API](GTFS-GraphQL-API.md) has been used by the Digitransit and projects as a general purpose routing and transit data API in production for many years. If your input data is mostly GTFS then this is probably the best choice as it uses the same vocabulary. -The [Transmodel GraphQL API](../sandbox/TransmodelApi.md) is used at +The [Transmodel GraphQL API](TransmodelApi.md) is used at Entur in production since 2020. Like the GTFS GraphQL API it is also a general purpose API. If your input data is mostly NeTeX then you might want to investigate this API as it uses the [Transmodel vocabulary](https://en.wikipedia.org/wiki/Transmodel) to describe diff --git a/docs/sandbox/TransmodelApi.md b/docs/apis/TransmodelApi.md similarity index 77% rename from docs/sandbox/TransmodelApi.md rename to docs/apis/TransmodelApi.md index 5069966bdbd..ce772734276 100644 --- a/docs/sandbox/TransmodelApi.md +++ b/docs/apis/TransmodelApi.md @@ -4,7 +4,26 @@ - Entur, Norway -## Changelog +## Documentation + +This is the official OTP2 API for Transmodel (NeTEx). The terminology is based on the +Transmodel (NeTEx) with some limitations/simplification. It provides both a routing API +(trip query) and index API for transit data. + +Entur provides a [GraphQL explorer](https://api.entur.io/graphql-explorer) where you may browse the GraphQL schema and try your own +queries. + +When running OTP locally the endpoint is available at: `http://localhost:8080/otp/routers/default/transmodel/index/graphql` + +### Configuration + +To turn this API off, add the feature `TransmodelGraphQlApi : false` in _otp-config.json_. + + +## Changelog - old + +The Transmodel API is now part of the main OTP supported APIs. New changes in the changelog +will be added to the main change log, and NOT here (2023-12-13). - Initial version of Transmodel Graph QL API (September 2019) - Added support for multimodal StopPlaces (November 2019) @@ -43,7 +62,7 @@ [#4074](https://github.com/opentripplanner/OpenTripPlanner/pull/4074) - Transmodel API transport mode not present or null is all transport modes [#4123](https://github.com/opentripplanner/OpenTripPlanner/pull/4123) -- Expose datedServiceJourney from EstimatedCall +- Expose datedServiceJourney from EstimatedCall [#4128](https://github.com/opentripplanner/OpenTripPlanner/pull/4128) - Expose stop-to-stop journey pattern geometries [#4161](https://github.com/opentripplanner/OpenTripPlanner/pull/4161) @@ -58,26 +77,5 @@ - Add flexible stops [#4485](https://github.com/opentripplanner/OpenTripPlanner/pull/4485) -## Documentation - -This is the official Entur OTP2 API. The terminology is based on the Transmodel(NeTEx) with some -limitations/simplification. It provides both a routing API (trip query) and index API for transit -data. - -Entur provide a [GraphQL explorer](https://api.entur.io/graphql-explorer) where you may browse the -GraphQL schema and try your own queries. - -After enabling this feature (see below), the endpoint is available -at: `http://localhost:8080/otp/routers/default/transmodel/index/graphql` - -### OTP2 Official GraphQL API (Not available) - -We **plan** to make a new offical OTP2 API, replacing the REST API. The plan is to base the new API -on this API and the [GTFS GraphQL API](../apis/GTFS-GraphQL-API.md). The new API will most likely have two -"translations": A GTFS version and a Transmodel version, we will try to keep the semantics the same. - -### Configuration - -To enable this you need to add the feature `SandboxAPITransmodelApi`. diff --git a/docs/examples/entur/build-config.json b/docs/examples/entur/build-config.json index 3829e975f34..e9351882774 100644 --- a/docs/examples/entur/build-config.json +++ b/docs/examples/entur/build-config.json @@ -5,7 +5,6 @@ "embedRouterConfig": true, "areaVisibility": true, "platformEntriesLinking": true, - "matchBusRoutesToStreets": false, "staticParkAndRide": true, "staticBikeParkAndRide": true, "maxDataImportIssuesPerFile": 1000, diff --git a/docs/examples/entur/otp-config.json b/docs/examples/entur/otp-config.json index fb1520688a7..1746cd186d4 100644 --- a/docs/examples/entur/otp-config.json +++ b/docs/examples/entur/otp-config.json @@ -1,7 +1,6 @@ { "otpFeatures": { "ActuatorAPI": true, - "SandboxAPITransmodelApi": true, "FlexRouting": true, "FloatingBike": true, "OptimizeTransfers": true, diff --git a/docs/examples/entur/router-config.json b/docs/examples/entur/router-config.json index bc662453c91..0023dab130a 100644 --- a/docs/examples/entur/router-config.json +++ b/docs/examples/entur/router-config.json @@ -21,7 +21,7 @@ "dropOffTime": 30, "dropOffCost": 30 }, - "bikeParkTime": 60, + "bikeParkTime": "1m", "bikeParkCost": 120, "carDropoffTime": 120, "waitReluctance": 1.0, diff --git a/docs/examples/skanetrafiken/Readme.md b/docs/examples/skanetrafiken/Readme.md index ab7ef5f33a6..fc342f4192b 100644 --- a/docs/examples/skanetrafiken/Readme.md +++ b/docs/examples/skanetrafiken/Readme.md @@ -35,13 +35,13 @@ To reduced graph size, only data for southern part of Sweden is used. OSM data is downloaded from `http://download.geofabrik.de/europe/denmark-latest.osm.pbf`. To reduce graph size, only data for northern part of Denmark is used. -## Realtime +## Real-time -The **Azure Service Bus** is used to propagate SIRI SX and ET realtime messages to OTP. +The **Azure Service Bus** is used to propagate SIRI SX and ET real-time messages to OTP. This is solved through Siri Azure updaters that Skånetrafiken had implemented in OTP. There are separate updaters for SIRI SX and ET. Those updaters are used to provide data for Swedish traffic (NeTEx). Right now, there is no -connection to any realtime source for danish traffic (GTFS data). +connection to any real-time source for danish traffic (GTFS data). Except for receiving messages from **Service Bus** there are two endpoints through which historical ET and SX messages can be downloaded at OTP startup. @@ -54,8 +54,8 @@ the subscription. Once the updaters are done with processing of history messages they will change their status to primed, and the system will start channeling request to this OTP instance. -This ensures that no realtime message is omitted and all OTP instance that ran in the -cluster does have exact same realtime data. +This ensures that no real-time message is omitted and all OTP instance that ran in the +cluster does have exact same real-time data. Thi means that no matter which instance the client is hitting it will always get the same search results. diff --git a/docs/examples/skanetrafiken/router-config.json b/docs/examples/skanetrafiken/router-config.json index 9cc8b659734..ddec543e516 100644 --- a/docs/examples/skanetrafiken/router-config.json +++ b/docs/examples/skanetrafiken/router-config.json @@ -33,7 +33,7 @@ "fuzzyTripMatching": false, "history": { "url": "", - // Get all realtime history for current operating day date + // Get all real-time history for current operating day date "fromDateTime": "-P0D", "timeout": 300000 } diff --git a/docs/sandbox/MapboxVectorTilesApi.md b/docs/sandbox/MapboxVectorTilesApi.md index a503e6a839d..8ef8ee179e7 100644 --- a/docs/sandbox/MapboxVectorTilesApi.md +++ b/docs/sandbox/MapboxVectorTilesApi.md @@ -80,7 +80,7 @@ The feature must be configured in `router-config.json` as follows "minZoom": 14, "cacheMaxSeconds": 600 }, - // Contains just stations and realtime information for them + // Contains just stations and real-time information for them { "name": "realtimeRentalStations", "type": "VehicleRentalStation", @@ -90,7 +90,7 @@ The feature must be configured in `router-config.json` as follows "cacheMaxSeconds": 60 }, // This exists for backwards compatibility. At some point, we might want - // to add a new realtime parking mapper with better translation support + // to add a new real-time parking mapper with better translation support // and less unnecessary fields. { "name": "stadtnaviVehicleParking", @@ -101,7 +101,7 @@ The feature must be configured in `router-config.json` as follows "cacheMaxSeconds": 60, "expansionFactor": 0.25 }, - // no realtime, translatable fields are translated based on accept-language header + // no real-time, translatable fields are translated based on accept-language header // and contains less fields than the Stadtnavi mapper { "name": "vehicleParking", @@ -190,4 +190,4 @@ key, and a function to create the mapper, with a `Graph` object as a parameter, * Translatable fields are now translated based on accept-language header * Added DigitransitRealtime for vehicle rental stations * Changed old vehicle parking mapper to be Stadtnavi - * Added a new Digitransit vehicle parking mapper with no realtime information and less fields + * Added a new Digitransit vehicle parking mapper with no real-time information and less fields diff --git a/docs/sandbox/StopConsolidation.md b/docs/sandbox/StopConsolidation.md index fa3ff822e88..750a873b547 100644 --- a/docs/sandbox/StopConsolidation.md +++ b/docs/sandbox/StopConsolidation.md @@ -30,7 +30,7 @@ This has the following consequences However, this feature has also severe downsides: - - It makes realtime trip updates referencing a stop id much more complicated and in many cases + - It makes real-time trip updates referencing a stop id much more complicated and in many cases impossible to resolve. You can only reference a stop by its sequence, which only works in GTFS-RT, not Siri. - Fare calculation and transfers are unlikely to work as expected. diff --git a/mkdocs.yml b/mkdocs.yml index 81d3e1c2052..e4341b296fc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -68,6 +68,7 @@ nav: - Introduction: 'apis/Apis.md' - GraphQL Tutorial: 'apis/GraphQL-Tutorial.md' - GTFS GraphQL API: 'apis/GTFS-GraphQL-API.md' + - Transmodel (NeTEx) GraphQL API: 'apis/TransmodelApi.md' - Configuration: - Introduction: 'Configuration.md' - Build: 'BuildConfiguration.md' @@ -94,7 +95,6 @@ nav: - Actuator API: 'sandbox/ActuatorAPI.md' - Direct Transfer Analyzer: 'sandbox/transferanalyzer.md' - Google Cloud Storage: 'sandbox/GoogleCloudStorage.md' - - Transmodel(NeTEx) GraphQL API: 'sandbox/TransmodelApi.md' - SIRI Updaters: 'sandbox/SiriUpdater.md' - SIRI Updater (Azure): 'sandbox/SiriAzureUpdater.md' - Vehicle Rental Service Directory API support: 'sandbox/VehicleRentalServiceDirectory.md' diff --git a/pom.xml b/pom.xml index 39fd75b0f39..e740c782b4e 100644 --- a/pom.xml +++ b/pom.xml @@ -56,20 +56,20 @@ - 130 + 134 - 30.0 - 2.48.1 + 30.1 + 2.49 2.16.0 - 3.1.3 + 3.1.5 5.10.1 1.11.5 5.5.3 - 1.4.11 + 1.4.14 9.8.0 2.0.9 2.0.15 - 1.25 + 1.26 4.0.4 UTF-8 @@ -144,7 +144,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.11.0 + 3.12.0 21 @@ -214,7 +214,7 @@ org.codehaus.mojo build-helper-maven-plugin - 3.4.0 + 3.5.0 build-helper-generate-sources @@ -247,7 +247,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.2.2 + 3.2.3 me.fabriciorby @@ -710,7 +710,7 @@ org.entur.gbfs gbfs-java-model - 3.0.13 + 3.0.16 @@ -729,13 +729,13 @@ com.tngtech.archunit archunit - 1.2.0 + 1.2.1 test org.mockito mockito-core - 5.7.0 + 5.8.0 test @@ -867,7 +867,7 @@ org.onebusaway onebusaway-gtfs - 1.4.5 + 1.4.10 org.slf4j @@ -912,7 +912,7 @@ org.apache.httpcomponents.client5 httpclient5 - 5.2.1 + 5.3 commons-cli @@ -986,6 +986,17 @@ prettierSkip + + + + ps + + + diff --git a/renovate.json5 b/renovate.json5 index b3511faba9b..5e12724db1b 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -29,6 +29,10 @@ ], "enabled": false }, + { + "matchFiles": ["client-next/package.json"], + "enabled": false + }, { // https://github.com/graphql-java-kickstart/renovate-config/blob/main/default.json "description": "GraphQL Java (ignoring snapshot builds)", @@ -93,7 +97,8 @@ "io.github.git-commit-id:git-commit-id-maven-plugin", "com.hubspot.maven.plugins:prettier-maven-plugin", "com.google.cloud.tools:jib-maven-plugin", - "org.apache.maven.plugins:maven-shade-plugin" + "org.apache.maven.plugins:maven-shade-plugin", + "org.apache.maven.plugins:maven-compiler-plugin" ], "matchPackagePrefixes": [ "org.junit.jupiter:", diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsModuleTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsModuleTest.java index 0594bb2d5af..e45e83a020d 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsModuleTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsModuleTest.java @@ -14,6 +14,7 @@ import org.opentripplanner.datastore.file.FileDataSource; import org.opentripplanner.framework.application.OtpFileNames; import org.opentripplanner.graph_builder.ConfiguredDataSource; +import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.gtfs.graphbuilder.GtfsFeedParameters; import org.opentripplanner.standalone.config.BuildConfig; import org.opentripplanner.test.support.ResourceLoader; @@ -42,7 +43,7 @@ void testMultipleGtfsDataReading() { configuredDataSource, buildConfig, emissionsDataModel, - null + DataImportIssueStore.NOOP ); emissionsModule.buildGraph(); assertEquals( diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java index 624330ba443..e30fb2d8b18 100644 --- a/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java @@ -125,7 +125,7 @@ private static void assertLegFareEquals( void calculateFareForSingleAgency() { List rides = List.of(getLeg(COMM_TRANS_AGENCY_ID, "400", 0)); calculateFare(rides, regular, DEFAULT_TEST_RIDE_PRICE); - calculateFare(rides, FareType.senior, TWO_DOLLARS); + calculateFare(rides, FareType.senior, DEFAULT_TEST_RIDE_PRICE); calculateFare(rides, FareType.youth, ZERO_USD); calculateFare(rides, FareType.electronicSpecial, TWO_DOLLARS); calculateFare(rides, FareType.electronicRegular, DEFAULT_TEST_RIDE_PRICE); @@ -145,11 +145,7 @@ void calculateFareWithNoFreeTransfer() { getLeg(COMM_TRANS_AGENCY_ID, 2) ); calculateFare(rides, regular, DEFAULT_TEST_RIDE_PRICE.times(3)); - calculateFare( - rides, - FareType.senior, - DEFAULT_TEST_RIDE_PRICE.plus(DEFAULT_TEST_RIDE_PRICE).plus(usDollars(1.25f)) - ); + calculateFare(rides, FareType.senior, DEFAULT_TEST_RIDE_PRICE.times(3)); calculateFare(rides, FareType.youth, Money.ZERO_USD); calculateFare( rides, @@ -429,7 +425,7 @@ void calculateCashFreeTransferKCMetro() { getLeg(KC_METRO_AGENCY_ID, 130) ); calculateFare(rides, regular, DEFAULT_TEST_RIDE_PRICE.times(3)); - calculateFare(rides, FareType.senior, DEFAULT_TEST_RIDE_PRICE.times(2).plus(usDollars(1.25f))); + calculateFare(rides, FareType.senior, DEFAULT_TEST_RIDE_PRICE.times(3)); calculateFare(rides, FareType.youth, Money.ZERO_USD); calculateFare(rides, FareType.electronicSpecial, usDollars(1.25f)); calculateFare(rides, FareType.electronicRegular, DEFAULT_TEST_RIDE_PRICE.times(2)); diff --git a/src/ext-test/java/org/opentripplanner/ext/mapping/TransmodelMappingUtilTest.java b/src/ext-test/java/org/opentripplanner/ext/mapping/TransmodelMappingUtilTest.java index 0978bd0309a..ce841be54a7 100644 --- a/src/ext-test/java/org/opentripplanner/ext/mapping/TransmodelMappingUtilTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/mapping/TransmodelMappingUtilTest.java @@ -5,7 +5,7 @@ import java.util.List; import org.junit.jupiter.api.Test; -import org.opentripplanner.ext.transmodelapi.mapping.TransitIdMapper; +import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.organization.Agency; diff --git a/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java b/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java index 43be9c6d735..af415b5e883 100644 --- a/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java @@ -137,7 +137,7 @@ private static TripPattern delay(TripPattern pattern1, int seconds) { } private static TripTimes delay(TripTimes tt, int seconds) { - var delayed = tt.copyOfScheduledTimes(); + var delayed = tt.copyScheduledTimes(); IntStream .range(0, delayed.getNumStops()) .forEach(i -> { diff --git a/src/ext-test/java/org/opentripplanner/ext/siri/AddedTripBuilderTest.java b/src/ext-test/java/org/opentripplanner/ext/siri/AddedTripBuilderTest.java index 8da3e118d12..b7431c16091 100644 --- a/src/ext-test/java/org/opentripplanner/ext/siri/AddedTripBuilderTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/siri/AddedTripBuilderTest.java @@ -32,6 +32,7 @@ import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.timetable.RealTimeState; import org.opentripplanner.transit.model.timetable.Trip; +import org.opentripplanner.transit.model.timetable.TripIdAndServiceDate; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.StopModel; import org.opentripplanner.transit.service.TransitModel; @@ -124,7 +125,8 @@ void testAddedTrip() { null, false, SHORT_NAME, - HEADSIGN + HEADSIGN, + List.of() ) .build(); @@ -172,6 +174,16 @@ void testAddedTrip() { .contains(TRANSIT_MODEL.getServiceCodes().get(trip.getServiceId())), "serviceId should be running on service date" ); + assertNotNull( + transitModelIndex.getTripOnServiceDateById().get(TRIP_ID), + "TripOnServiceDate should be added to transit index by id" + ); + assertNotNull( + transitModelIndex + .getTripOnServiceDateForTripAndDay() + .get(new TripIdAndServiceDate(TRIP_ID, SERVICE_DATE)), + "TripOnServiceDate should be added to transit index for trip and day" + ); // Assert stop pattern var stopPattern = addedTrip.successValue().stopPattern(); @@ -242,7 +254,8 @@ void testAddedTripOnAddedRoute() { null, false, SHORT_NAME, - HEADSIGN + HEADSIGN, + List.of() ) .build(); @@ -267,7 +280,8 @@ void testAddedTripOnAddedRoute() { null, false, SHORT_NAME, - HEADSIGN + HEADSIGN, + List.of() ) .build(); @@ -317,7 +331,8 @@ void testAddedTripOnExistingRoute() { null, false, SHORT_NAME, - HEADSIGN + HEADSIGN, + List.of() ) .build(); @@ -347,7 +362,8 @@ void testAddedTripWithoutReplacedRoute() { null, false, SHORT_NAME, - HEADSIGN + HEADSIGN, + List.of() ) .build(); @@ -389,7 +405,8 @@ void testAddedTripFailOnMissingServiceId() { null, false, SHORT_NAME, - HEADSIGN + HEADSIGN, + List.of() ) .build(); @@ -443,7 +460,8 @@ void testAddedTripFailOnNonIncreasingDwellTime() { null, false, SHORT_NAME, - HEADSIGN + HEADSIGN, + List.of() ) .build(); @@ -481,7 +499,8 @@ void testAddedTripFailOnTooFewCalls() { null, false, SHORT_NAME, - HEADSIGN + HEADSIGN, + List.of() ) .build(); @@ -527,7 +546,8 @@ void testAddedTripFailOnUnknownStop() { null, false, SHORT_NAME, - HEADSIGN + HEADSIGN, + List.of() ) .build(); diff --git a/src/ext-test/java/org/opentripplanner/ext/siri/TimetableHelperTest.java b/src/ext-test/java/org/opentripplanner/ext/siri/TimetableHelperTest.java index 97bd3be2398..dde2677a952 100644 --- a/src/ext-test/java/org/opentripplanner/ext/siri/TimetableHelperTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/siri/TimetableHelperTest.java @@ -19,8 +19,8 @@ import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.Station; import org.opentripplanner.transit.model.timetable.OccupancyStatus; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.Trip; -import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.opentripplanner.transit.service.StopModel; import uk.org.siri.siri20.OccupancyEnumeration; @@ -46,7 +46,7 @@ public class TimetableHelperTest { ZoneIds.CET ); - private TripTimes tripTimes; + private RealTimeTripTimes tripTimes; @BeforeEach public void setUp() { diff --git a/src/ext-test/resources/org/opentripplanner/ext/emissions/emissions-test-gtfs.zip b/src/ext-test/resources/org/opentripplanner/ext/emissions/emissions-test-gtfs.zip index 146ecfd2320..68c5a57484b 100644 Binary files a/src/ext-test/resources/org/opentripplanner/ext/emissions/emissions-test-gtfs.zip and b/src/ext-test/resources/org/opentripplanner/ext/emissions/emissions-test-gtfs.zip differ diff --git a/src/ext-test/resources/org/opentripplanner/ext/emissions/emissions-test-gtfs/emissions.txt b/src/ext-test/resources/org/opentripplanner/ext/emissions/emissions-test-gtfs/emissions.txt index 18fcb3e64de..b89e0c67fa0 100644 --- a/src/ext-test/resources/org/opentripplanner/ext/emissions/emissions-test-gtfs/emissions.txt +++ b/src/ext-test/resources/org/opentripplanner/ext/emissions/emissions-test-gtfs/emissions.txt @@ -5,3 +5,7 @@ route_id,avg_co2_per_vehicle_per_km,avg_passenger_count 1004,0.0,0.0 1005,0,-1 1006,0,1 +1007,, +,1, +,,2 +,, \ No newline at end of file diff --git a/src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java b/src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java index 54abf58e3a7..597a7d89380 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/Co2EmissionsDataReader.java @@ -12,6 +12,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.opentripplanner.framework.lang.Sandbox; +import org.opentripplanner.framework.lang.StringUtils; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.slf4j.Logger; @@ -92,30 +93,44 @@ private Map readEmissions(InputStream stream, String feedI String avgCo2PerVehiclePerKmString = reader.get("avg_co2_per_vehicle_per_km"); String avgPassengerCountString = reader.get("avg_passenger_count"); - if (avgCo2PerVehiclePerKmString.isEmpty()) { + if (!StringUtils.hasValue(routeId)) { issueStore.add( "InvalidEmissionData", - "Value for avg_co2_per_vehicle_per_km is missing in the Emissions.txt for route %s", + "Value for routeId is missing in the emissions.txt for line: %s.", + reader.getRawRecord() + ); + } + if (!StringUtils.hasValue(avgCo2PerVehiclePerKmString)) {} + { + issueStore.add( + "InvalidEmissionData", + "Value for avg_co2_per_vehicle_per_km is missing in the emissions.txt for route %s", routeId ); } - if (avgPassengerCountString.isEmpty()) { + if (!StringUtils.hasValue(avgPassengerCountString)) { issueStore.add( "InvalidEmissionData", - "Value for avg_passenger_count is missing in the Emissions.txt for route %s", + "Value for avg_passenger_count is missing in the emissions.txt for route %s", routeId ); } - - Double avgCo2PerVehiclePerMeter = Double.parseDouble(avgCo2PerVehiclePerKmString) / 1000; - Double avgPassengerCount = Double.parseDouble(reader.get("avg_passenger_count")); - Optional emissions = calculateEmissionsPerPassengerPerMeter( - routeId, - avgCo2PerVehiclePerMeter, - avgPassengerCount - ); - if (emissions.isPresent()) { - emissionsData.put(new FeedScopedId(feedId, routeId), emissions.get()); + if ( + StringUtils.hasValue(feedId) && + StringUtils.hasValue(routeId) && + StringUtils.hasValue(avgCo2PerVehiclePerKmString) && + StringUtils.hasValue(avgPassengerCountString) + ) { + Double avgCo2PerVehiclePerMeter = Double.parseDouble(avgCo2PerVehiclePerKmString) / 1000; + Double avgPassengerCount = Double.parseDouble(reader.get("avg_passenger_count")); + Optional emissions = calculateEmissionsPerPassengerPerMeter( + routeId, + avgCo2PerVehiclePerMeter, + avgPassengerCount + ); + if (emissions.isPresent()) { + emissionsData.put(new FeedScopedId(feedId, routeId), emissions.get()); + } } } return emissionsData; diff --git a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsModule.java b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsModule.java index 2da4b091138..a0eb21da779 100644 --- a/src/ext/java/org/opentripplanner/ext/emissions/EmissionsModule.java +++ b/src/ext/java/org/opentripplanner/ext/emissions/EmissionsModule.java @@ -58,6 +58,9 @@ public void buildGraph() { } this.emissionsDataModel.setCo2Emissions(emissionsData); this.emissionsDataModel.setCarAvgCo2PerMeter(carAvgEmissionsPerMeter); + LOG.info( + "Emissions building finished. Number of CO2 emission records saved: " + emissionsData.size() + ); } } } diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java b/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java index 486362697b3..42736472767 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java @@ -299,18 +299,15 @@ private Optional getSeniorFare( Leg leg ) { var route = leg.getRoute(); + var regularFare = getRegularFare(fareType, rideType, defaultFare, leg); + // Many agencies only provide senior discount if using ORCA return switch (rideType) { - case COMM_TRANS_LOCAL_SWIFT -> optionalUSD(1.25f); - case COMM_TRANS_COMMUTER_EXPRESS -> optionalUSD(2f); - case EVERETT_TRANSIT, SKAGIT_TRANSIT, WHATCOM_LOCAL, SKAGIT_LOCAL -> optionalUSD(0.5f); - case KITSAP_TRANSIT_FAST_FERRY_EASTBOUND -> fareType.equals(FareType.electronicSenior) // Kitsap only provide discounted senior fare for orca. - ? optionalUSD(1f) - : optionalUSD(2f); - case KC_WATER_TAXI_VASHON_ISLAND -> usesOrca(fareType) ? optionalUSD(3f) : optionalUSD(6.75f); - case KC_WATER_TAXI_WEST_SEATTLE -> usesOrca(fareType) - ? optionalUSD(2.5f) - : optionalUSD(5.75f); - case SOUND_TRANSIT, + case COMM_TRANS_LOCAL_SWIFT -> usesOrca(fareType) ? optionalUSD(1.25f) : regularFare; + case COMM_TRANS_COMMUTER_EXPRESS -> usesOrca(fareType) ? optionalUSD(2f) : regularFare; + case SKAGIT_TRANSIT, WHATCOM_LOCAL, SKAGIT_LOCAL -> optionalUSD(0.5f); + case EVERETT_TRANSIT -> usesOrca(fareType) ? optionalUSD(0.5f) : regularFare; + case KITSAP_TRANSIT_FAST_FERRY_EASTBOUND, + SOUND_TRANSIT, SOUND_TRANSIT_BUS, SOUND_TRANSIT_LINK, SOUND_TRANSIT_SOUNDER, @@ -320,10 +317,12 @@ private Optional getSeniorFare( SEATTLE_STREET_CAR, KITSAP_TRANSIT -> fareType.equals(FareType.electronicSenior) ? optionalUSD(1f) - : getRegularFare(fareType, rideType, defaultFare, leg); + : regularFare; + case KC_WATER_TAXI_VASHON_ISLAND -> usesOrca(fareType) ? optionalUSD(3f) : regularFare; + case KC_WATER_TAXI_WEST_SEATTLE -> usesOrca(fareType) ? optionalUSD(2.5f) : regularFare; case KITSAP_TRANSIT_FAST_FERRY_WESTBOUND -> fareType.equals(FareType.electronicSenior) ? optionalUSD(5f) - : optionalUSD(10f); + : regularFare; // Discount specific to Skagit transit and not Orca. case WASHINGTON_STATE_FERRIES -> Optional.of( getWashingtonStateFerriesFare(route.getLongName(), fareType, defaultFare) @@ -395,7 +394,7 @@ protected Optional getRidePrice( * If free transfers are applicable, the most expensive discount fare across all legs is added to * the final cumulative price. *

- * The computed fare for Orca card users takes into account realtime trip updates where available, + * The computed fare for Orca card users takes into account real-time trip updates where available, * so that, for instance, when a leg on a long itinerary is delayed to begin after the initial two * hour window has expired, the calculated fare for that trip will be two one-way fares instead of * one. diff --git a/src/ext/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolver.java b/src/ext/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolver.java index a59d9fc589a..d03ba1a2fc5 100644 --- a/src/ext/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolver.java +++ b/src/ext/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolver.java @@ -11,7 +11,7 @@ public class RealtimeResolver { /** - * Loop through all itineraries and populate legs with realtime data using legReference from the original leg + * Loop through all itineraries and populate legs with real-time data using legReference from the original leg */ public static void populateLegsWithRealtime( List itineraries, @@ -35,10 +35,10 @@ public static void populateLegsWithRealtime( return leg; } - var realtimeLeg = ref.getLeg(transitService); - if (realtimeLeg != null) { + var realTimeLeg = ref.getLeg(transitService); + if (realTimeLeg != null) { return combineReferenceWithOriginal( - realtimeLeg.asScheduledTransitLeg(), + realTimeLeg.asScheduledTransitLeg(), leg.asScheduledTransitLeg() ); } diff --git a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java index 2d7789422ea..7f0e51fab18 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java @@ -18,6 +18,7 @@ import org.opentripplanner.framework.time.ServiceDateUtils; import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.network.Route; @@ -27,11 +28,13 @@ import org.opentripplanner.transit.model.organization.Operator; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.timetable.RealTimeState; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.Trip; -import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripIdAndServiceDate; +import org.opentripplanner.transit.model.timetable.TripOnServiceDate; import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.opentripplanner.transit.service.TransitModel; -import org.opentripplanner.updater.spi.TripTimesValidationMapper; +import org.opentripplanner.updater.spi.DataValidationExceptionMapper; import org.opentripplanner.updater.spi.UpdateError; import org.rutebanken.netex.model.BusSubmodeEnumeration; import org.rutebanken.netex.model.RailSubmodeEnumeration; @@ -40,6 +43,7 @@ import uk.org.siri.siri20.EstimatedVehicleJourney; import uk.org.siri.siri20.NaturalLanguageStringStructure; import uk.org.siri.siri20.OccupancyEnumeration; +import uk.org.siri.siri20.VehicleJourneyRef; import uk.org.siri.siri20.VehicleModesEnumeration; class AddedTripBuilder { @@ -62,6 +66,7 @@ class AddedTripBuilder { private final boolean cancellation; private final String shortName; private final String headsign; + private final List replacedTrips; AddedTripBuilder( EstimatedVehicleJourney estimatedVehicleJourney, @@ -108,6 +113,8 @@ class AddedTripBuilder { this.entityResolver = entityResolver; this.getTripPatternId = getTripPatternId; timeZone = transitModel.getTimeZone(); + + replacedTrips = getReplacedVehicleJourneys(estimatedVehicleJourney); } AddedTripBuilder( @@ -126,7 +133,8 @@ class AddedTripBuilder { OccupancyEnumeration occupancy, boolean cancellation, String shortName, - String headsign + String headsign, + List replacedTrips ) { this.transitModel = transitModel; this.entityResolver = entityResolver; @@ -145,6 +153,7 @@ class AddedTripBuilder { this.cancellation = cancellation; this.shortName = shortName; this.headsign = headsign; + this.replacedTrips = replacedTrips; } Result build() { @@ -202,14 +211,14 @@ Result build() { .withStopPattern(stopPattern) .build(); - TripTimes tripTimes = TripTimesFactory.tripTimes( + RealTimeTripTimes tripTimes = TripTimesFactory.tripTimes( trip, aimedStopTimes, transitModel.getDeduplicator() ); tripTimes.setServiceCode(transitModel.getServiceCodes().get(trip.getServiceId())); pattern.add(tripTimes); - TripTimes updatedTripTimes = tripTimes.copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimes = tripTimes.copyScheduledTimes(); // Loop through calls again and apply updates for (int stopSequence = 0; stopSequence < calls.size(); stopSequence++) { @@ -231,22 +240,38 @@ Result build() { } /* Validate */ - var validityResult = updatedTripTimes.validateNonIncreasingTimes(); - if (validityResult.isPresent()) { - return TripTimesValidationMapper.toResult(tripId, validityResult.get()); + try { + updatedTripTimes.validateNonIncreasingTimes(); + } catch (DataValidationException e) { + return DataValidationExceptionMapper.toResult(e); } + var tripOnServiceDate = TripOnServiceDate + .of(tripId) + .withTrip(trip) + .withServiceDate(serviceDate) + .withReplacementFor(replacedTrips) + .build(); + // Adding trip to index necessary to include values in graphql-queries // TODO - SIRI: should more data be added to index? transitModel.getTransitModelIndex().getTripForId().put(tripId, trip); transitModel.getTransitModelIndex().getPatternForTrip().put(trip, pattern); transitModel.getTransitModelIndex().getPatternsForRoute().put(route, pattern); + transitModel + .getTransitModelIndex() + .getTripOnServiceDateById() + .put(tripOnServiceDate.getId(), tripOnServiceDate); + transitModel + .getTransitModelIndex() + .getTripOnServiceDateForTripAndDay() + .put(new TripIdAndServiceDate(tripId, serviceDate), tripOnServiceDate); return Result.success(new TripUpdate(stopPattern, updatedTripTimes, serviceDate)); } /** - * Method to create a Route. Commonly used to create a route if a realtime message + * Method to create a Route. Commonly used to create a route if a real-time message * refers to a route that is not in the transit model. * * We will find the first Route with same Operator, and use the same Authority @@ -394,4 +419,31 @@ static String resolveTransitSubMode(TransitMode transitMode, Route replacedRoute default -> null; }; } + + private List getReplacedVehicleJourneys( + EstimatedVehicleJourney estimatedVehicleJourney + ) { + List listOfReplacedVehicleJourneys = new ArrayList<>(); + + // VehicleJourneyRef is the reference to the serviceJourney being replaced. + VehicleJourneyRef vehicleJourneyRef = estimatedVehicleJourney.getVehicleJourneyRef(); // getVehicleJourneyRef + if (vehicleJourneyRef != null) { + var replacedDatedServiceJourney = entityResolver.resolveTripOnServiceDate( + vehicleJourneyRef.getValue() + ); + if (replacedDatedServiceJourney != null) { + listOfReplacedVehicleJourneys.add(replacedDatedServiceJourney); + } + } + + // Add additional replaced service journeys if present. + estimatedVehicleJourney + .getAdditionalVehicleJourneyReves() + .stream() + .map(entityResolver::resolveTripOnServiceDate) + .filter(Objects::nonNull) + .forEach(listOfReplacedVehicleJourneys::add); + + return listOfReplacedVehicleJourneys; + } } diff --git a/src/ext/java/org/opentripplanner/ext/siri/EntityResolver.java b/src/ext/java/org/opentripplanner/ext/siri/EntityResolver.java index a711d395f40..5ca467ad1d5 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/EntityResolver.java +++ b/src/ext/java/org/opentripplanner/ext/siri/EntityResolver.java @@ -238,51 +238,6 @@ public LocalDate resolveServiceDate(EstimatedVehicleJourney vehicleJourney) { return date.toLocalDate().minusDays(daysOffset); } - TripOnServiceDateBuilder createTripOnServiceDateBuilder( - EstimatedVehicleJourney estimatedVehicleJourney - ) { - var datedServiceJourneyId = resolveDatedServiceJourneyId(estimatedVehicleJourney); - - if (datedServiceJourneyId == null) { - if (estimatedVehicleJourney.getFramedVehicleJourneyRef() != null) { - var tripOnDate = resolveTripOnServiceDate( - estimatedVehicleJourney.getFramedVehicleJourneyRef() - ); - if (tripOnDate == null) { - return null; - } - datedServiceJourneyId = tripOnDate.getId(); - } - } - - if (datedServiceJourneyId == null) { - return null; - } - - List listOfReplacedVehicleJourneys = new ArrayList<>(); - - // VehicleJourneyRef is the reference to the serviceJourney being replaced. - VehicleJourneyRef vehicleJourneyRef = estimatedVehicleJourney.getVehicleJourneyRef(); - if (vehicleJourneyRef != null) { - var replacedDatedServiceJourney = resolveTripOnServiceDate(vehicleJourneyRef.getValue()); - if (replacedDatedServiceJourney != null) { - listOfReplacedVehicleJourneys.add(replacedDatedServiceJourney); - } - } - - // Add additional replaced service journeys if present. - estimatedVehicleJourney - .getAdditionalVehicleJourneyReves() - .stream() - .map(this::resolveTripOnServiceDate) - .filter(Objects::nonNull) - .forEach(listOfReplacedVehicleJourneys::add); - - return TripOnServiceDate - .of(datedServiceJourneyId) - .withReplacementFor(listOfReplacedVehicleJourneys); - } - /** * Calculate the difference in days between the service date and the departure at the first stop. */ diff --git a/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java b/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java index f56d3c6a75f..df4509eb2d6 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java @@ -12,15 +12,16 @@ import java.util.Set; import org.opentripplanner.ext.siri.mapper.PickDropMapper; import org.opentripplanner.framework.time.ServiceDateUtils; -import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.network.StopPattern; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.RealTimeState; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.TripTimes; -import org.opentripplanner.updater.spi.TripTimesValidationMapper; +import org.opentripplanner.updater.spi.DataValidationExceptionMapper; import org.opentripplanner.updater.spi.UpdateError; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +34,8 @@ */ public class ModifiedTripBuilder { - private static final Logger LOG = LoggerFactory.getLogger(TimetableHelper.class); + private static final Logger LOG = LoggerFactory.getLogger(ModifiedTripBuilder.class); + private final TripTimes existingTripTimes; private final TripPattern pattern; private final LocalDate serviceDate; @@ -94,7 +96,7 @@ public ModifiedTripBuilder( * in form the SIRI-ET update. */ public Result build() { - TripTimes newTimes = existingTripTimes.copyOfScheduledTimes(); + RealTimeTripTimes newTimes = existingTripTimes.copyScheduledTimes(); StopPattern stopPattern = createStopPattern(pattern, calls, entityResolver); @@ -114,16 +116,16 @@ public Result build() { newTimes.setRealTimeState(RealTimeState.MODIFIED); } - var error = newTimes.validateNonIncreasingTimes(); - final FeedScopedId id = newTimes.getTrip().getId(); - if (error.isPresent()) { - var updateError = error.get(); + // TODO - Handle DataValidationException at the outemost level(pr trip) + try { + newTimes.validateNonIncreasingTimes(); + } catch (DataValidationException e) { LOG.info( - "Invalid SIRI-ET data for trip {} - TripTimes are non-increasing after applying SIRI delay propagation at stop index {}", - id, - updateError.stopIndex() + "Invalid SIRI-ET data for trip {} - TripTimes failed to validate after applying SIRI delay propagation. {}", + newTimes.getTrip().getId(), + e.getMessage() ); - return TripTimesValidationMapper.toResult(id, updateError); + return DataValidationExceptionMapper.toResult(e); } int numStopsInUpdate = newTimes.getNumStops(); @@ -131,7 +133,7 @@ public Result build() { if (numStopsInUpdate != numStopsInPattern) { LOG.info( "Invalid SIRI-ET data for trip {} - Inconsistent number of updated stops ({}) and stops in pattern ({})", - id, + newTimes.getTrip().getId(), numStopsInUpdate, numStopsInPattern ); @@ -145,7 +147,7 @@ public Result build() { /** * Applies real-time updates from the calls into newTimes. */ - private void applyUpdates(TripTimes newTimes) { + private void applyUpdates(RealTimeTripTimes newTimes) { ZonedDateTime startOfService = ServiceDateUtils.asStartOfService(serviceDate, zoneId); Set alreadyVisited = new HashSet<>(); diff --git a/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java b/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java index 006329a404b..2f2f113fc79 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java +++ b/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java @@ -297,9 +297,9 @@ private TripAndPattern getTripAndPatternForJourney( continue; } - var realtimeAddedTripPattern = getRealtimeAddedTripPattern.apply(trip.getId(), serviceDate); - TripPattern tripPattern = realtimeAddedTripPattern != null - ? realtimeAddedTripPattern + var realTimeAddedTripPattern = getRealtimeAddedTripPattern.apply(trip.getId(), serviceDate); + TripPattern tripPattern = realTimeAddedTripPattern != null + ? realTimeAddedTripPattern : transitService.getPatternForTrip(trip); var firstStop = tripPattern.firstStop(); diff --git a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java index 0cedc491f79..345f8deba20 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java +++ b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java @@ -4,7 +4,7 @@ import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.NOT_MONITORED; import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.NO_FUZZY_TRIP_MATCH; import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.NO_START_DATE; -import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.NO_TRIP_ID; +import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.TRIP_NOT_FOUND; import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.TRIP_NOT_FOUND_IN_PATTERN; import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.UNKNOWN; @@ -18,14 +18,17 @@ import org.opentripplanner.model.TimetableSnapshot; import org.opentripplanner.model.TimetableSnapshotProvider; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.TransitLayerUpdater; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.TimetableSnapshotSourceParameters; +import org.opentripplanner.updater.spi.DataValidationExceptionMapper; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.spi.UpdateResult; import org.opentripplanner.updater.spi.UpdateSuccess; @@ -35,8 +38,8 @@ import uk.org.siri.siri20.EstimatedVehicleJourney; /** - * This class should be used to create snapshots of lookup tables of realtime data. This is - * necessary to provide planning threads a consistent constant view of a graph with realtime data at + * This class should be used to create snapshots of lookup tables of real-time data. This is + * necessary to provide planning threads a consistent constant view of a graph with real-time data at * a specific point in time. */ public class SiriTimetableSnapshotSource implements TimetableSnapshotProvider { @@ -59,7 +62,7 @@ public class SiriTimetableSnapshotSource implements TimetableSnapshotProvider { */ private final SiriTripPatternIdGenerator tripPatternIdGenerator = new SiriTripPatternIdGenerator(); /** - * A synchronized cache of trip patterns that are added to the graph due to GTFS-realtime + * A synchronized cache of trip patterns that are added to the graph due to GTFS-real-time * messages. */ private final SiriTripPatternCache tripPatternCache; @@ -81,7 +84,7 @@ public class SiriTimetableSnapshotSource implements TimetableSnapshotProvider { */ private volatile TimetableSnapshot snapshot = null; - /** Should expired realtime data be purged from the graph. */ + /** Should expired real-time data be purged from the graph. */ private final boolean purgeExpiredData; protected LocalDate lastPurgeDate = null; @@ -215,6 +218,8 @@ private Result apply( /* commit */ return addTripToGraphAndBuffer(result.successValue()); + } catch (DataValidationException e) { + return DataValidationExceptionMapper.toResult(e); } catch (Exception e) { LOG.warn( "{} EstimatedJourney {} failed.", @@ -320,7 +325,7 @@ private Result handleModifiedTrip( trip = tripAndPattern.trip(); pattern = tripAndPattern.tripPattern(); } else { - return UpdateError.result(null, NO_TRIP_ID); + return UpdateError.result(null, TRIP_NOT_FOUND); } Timetable currentTimetable = getCurrentTimetable(pattern, serviceDate); @@ -371,7 +376,7 @@ private Result addTripToGraphAndBuffer(TripUpdate tr // Add new trip times to the buffer and return success var result = buffer.update(pattern, tripUpdate.tripTimes(), serviceDate); - LOG.debug("Applied realtime data for trip {} on {}", trip, serviceDate); + LOG.debug("Applied real-time data for trip {} on {}", trip, serviceDate); return result; } @@ -395,7 +400,7 @@ private boolean markScheduledTripAsDeleted(Trip trip, final LocalDate serviceDat if (tripTimes == null) { LOG.warn("Could not mark scheduled trip as deleted {}", trip.getId()); } else { - final TripTimes newTripTimes = tripTimes.copyOfScheduledTimes(); + final RealTimeTripTimes newTripTimes = tripTimes.copyScheduledTimes(); newTripTimes.deleteTrip(); buffer.update(pattern, newTripTimes, serviceDate); success = true; @@ -416,10 +421,8 @@ private boolean removePreviousRealtimeUpdate(final Trip trip, final LocalDate se final TripPattern pattern = buffer.getRealtimeAddedTripPattern(trip.getId(), serviceDate); if (pattern != null) { - /* - Remove the previous realtime-added TripPattern from buffer. - Only one version of the realtime-update should exist - */ + // Remove the previous real-time-added TripPattern from buffer. + // Only one version of the real-time-update should exist buffer.removeLastAddedTripPattern(trip.getId(), serviceDate); buffer.removeRealtimeUpdatedTripTimes(pattern, trip.getId(), serviceDate); success = true; @@ -436,7 +439,7 @@ private boolean purgeExpiredData() { return false; } - LOG.debug("purging expired realtime data"); + LOG.debug("purging expired real-time data"); lastPurgeDate = previously; diff --git a/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java b/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java index ade320db298..fea8bda686f 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java +++ b/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java @@ -7,7 +7,7 @@ import org.opentripplanner.ext.siri.mapper.OccupancyMapper; import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.framework.time.ServiceDateUtils; -import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import uk.org.siri.siri20.NaturalLanguageStringStructure; import uk.org.siri.siri20.OccupancyEnumeration; @@ -52,7 +52,7 @@ private static int handleMissingRealtime(int... times) { public static void applyUpdates( ZonedDateTime departureDate, - TripTimes tripTimes, + RealTimeTripTimes tripTimes, int index, boolean isLastStop, boolean isJourneyPredictionInaccurate, @@ -75,29 +75,29 @@ public static void applyUpdates( } int scheduledArrivalTime = tripTimes.getArrivalTime(index); - int realtimeArrivalTime = getAvailableTime( + int realTimeArrivalTime = getAvailableTime( departureDate, call::getActualArrivalTime, call::getExpectedArrivalTime ); int scheduledDepartureTime = tripTimes.getDepartureTime(index); - int realtimeDepartureTime = getAvailableTime( + int realTimeDepartureTime = getAvailableTime( departureDate, call::getActualDepartureTime, call::getExpectedDepartureTime ); int[] possibleArrivalTimes = index == 0 - ? new int[] { realtimeArrivalTime, realtimeDepartureTime, scheduledArrivalTime } - : new int[] { realtimeArrivalTime, scheduledArrivalTime }; + ? new int[] { realTimeArrivalTime, realTimeDepartureTime, scheduledArrivalTime } + : new int[] { realTimeArrivalTime, scheduledArrivalTime }; var arrivalTime = handleMissingRealtime(possibleArrivalTimes); int arrivalDelay = arrivalTime - scheduledArrivalTime; tripTimes.updateArrivalDelay(index, arrivalDelay); int[] possibleDepartureTimes = isLastStop - ? new int[] { realtimeDepartureTime, realtimeArrivalTime, scheduledDepartureTime } - : new int[] { realtimeDepartureTime, scheduledDepartureTime }; + ? new int[] { realTimeDepartureTime, realTimeArrivalTime, scheduledDepartureTime } + : new int[] { realTimeDepartureTime, scheduledDepartureTime }; var departureTime = handleMissingRealtime(possibleDepartureTimes); int departureDelay = departureTime - scheduledDepartureTime; tripTimes.updateDepartureDelay(index, departureDelay); diff --git a/src/ext/java/org/opentripplanner/ext/siri/TripTimesAndStopPattern.java b/src/ext/java/org/opentripplanner/ext/siri/TripTimesAndStopPattern.java deleted file mode 100644 index 70d9e034188..00000000000 --- a/src/ext/java/org/opentripplanner/ext/siri/TripTimesAndStopPattern.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.opentripplanner.ext.siri; - -import org.opentripplanner.transit.model.network.StopPattern; -import org.opentripplanner.transit.model.timetable.TripTimes; - -public record TripTimesAndStopPattern(TripTimes times, StopPattern pattern) {} diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETGooglePubsubUpdater.java b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETGooglePubsubUpdater.java index cb664632443..4b5797f03de 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETGooglePubsubUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETGooglePubsubUpdater.java @@ -126,10 +126,10 @@ public SiriETGooglePubsubUpdater( SiriTimetableSnapshotSource timetableSnapshot ) { this.configRef = config.configRef(); - /* - URL that responds to HTTP GET which returns all initial data in protobuf-format. - Will be called once to initialize realtime-data. All updates will be received from Google Cloud Pubsub - */ + + // URL that responds to HTTP GET which returns all initial data in protobuf-format. Will be + // called once to initialize real-time-data. All updates will be received from Google Cloud + // Pubsub this.dataInitializationUrl = URI.create(config.dataInitializationUrl()); this.feedId = config.feedId(); this.reconnectPeriod = config.reconnectPeriod(); @@ -195,15 +195,6 @@ public void run() { } } - @Override - public void teardown() { - if (subscriber != null) { - LOG.info("Stopping SIRI-ET PubSub subscriber {}", subscriptionName); - subscriber.stopAsync(); - } - deleteSubscription(); - } - @Override public boolean isPrimed() { return this.primed; @@ -215,17 +206,16 @@ public String getConfigRef() { } private void addShutdownHook() { - // TODO: This should probably be on a higher level? - Thread shutdownHook = new Thread(this::teardown, "siri-et-google-pubsub-shutdown"); - boolean added = ApplicationShutdownSupport.addShutdownHook( - shutdownHook, - shutdownHook.getName() + ApplicationShutdownSupport.addShutdownHook( + "siri-et-google-pubsub-shutdown", + () -> { + if (subscriber != null) { + LOG.info("Stopping SIRI-ET PubSub subscriber '{}'.", subscriptionName); + subscriber.stopAsync(); + } + deleteSubscription(); + } ); - if (!added) { - // Handling corner case when instance is being shut down before it has been initialized - LOG.info("Instance is already shutting down - cleaning up immediately."); - teardown(); - } } /** diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETHttpTripUpdateSource.java b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETHttpTripUpdateSource.java index 3f1de760701..b3abe95dfc2 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETHttpTripUpdateSource.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETHttpTripUpdateSource.java @@ -49,7 +49,7 @@ public Optional getUpdates() { long t1 = System.currentTimeMillis(); try { var siri = siriLoader.fetchETFeed(requestorRef); - if (siri.isEmpty()) { + if (siri.map(Siri::getServiceDelivery).isEmpty()) { return Optional.empty(); } @@ -88,7 +88,7 @@ public String toString() { } private static SiriLoader createLoader(String url, Parameters parameters) { - // Load realtime updates from a file. + // Load real-time updates from a file. if (SiriFileLoader.matchesUrl(url)) { return new SiriFileLoader(url); } diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETUpdater.java b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETUpdater.java index b4cf5f143e3..c8ccd2c533b 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETUpdater.java @@ -39,7 +39,7 @@ public class SiriETUpdater extends PollingGraphUpdater { protected WriteToGraphCallback saveResultOnGraph; /** - * The place where we'll record the incoming realtime timetables to make them available to the + * The place where we'll record the incoming real-time timetables to make them available to the * router in a thread safe way. */ private final SiriTimetableSnapshotSource snapshotSource; diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriFileLoader.java b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriFileLoader.java index 306deeca484..84da542ef47 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriFileLoader.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriFileLoader.java @@ -72,7 +72,7 @@ private Optional fetchFeed() { if (!matchFilename(file)) { continue; } - LOG.info("Process realtime input file: " + file.getAbsolutePath()); + LOG.info("Process real-time input file: " + file.getAbsolutePath()); var inProgressFile = newFile(file, SUFFIX_IN_PROGRESS); try { file.renameTo(inProgressFile); diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java index 6f69ca99216..0d36233f22b 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java @@ -149,23 +149,15 @@ public void run() { subscriptionName ); - Thread shutdownHook = new Thread(this::teardown, "azur-siri-updater-shutdown"); - boolean added = ApplicationShutdownSupport.addShutdownHook( - shutdownHook, - shutdownHook.getName() + ApplicationShutdownSupport.addShutdownHook( + "azure-siri-updater-shutdown", + () -> { + LOG.info("Calling shutdownHook on AbstractAzureSiriUpdater"); + eventProcessor.close(); + serviceBusAdmin.deleteSubscription(topicName, subscriptionName).block(); + LOG.info("Subscription '{}' deleted on topic '{}'.", subscriptionName, topicName); + } ); - if (!added) { - // Handling corner case when instance is being shut down before it has been initialized - LOG.info("Instance is already shutting down - cleaning up immediately."); - teardown(); - } - } - - @Override - public void teardown() { - eventProcessor.stop(); - serviceBusAdmin.deleteSubscription(topicName, subscriptionName).block(); - LOG.info("Subscription {} deleted on topic {}", subscriptionName, topicName); } @Override diff --git a/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationModule.java b/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationModule.java index c70e91fca52..91cbe5e1856 100644 --- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationModule.java +++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationModule.java @@ -20,7 +20,7 @@ * that represent the same stop place) and swaps the "secondary" stops in patterns with their * "primary" equivalent. *

- * NOTE: This will make realtime trip updates for a modified pattern a lot harder. For Arcadis' + * NOTE: This will make real-time trip updates for a modified pattern a lot harder. For Arcadis' * initial implementation this is acceptable and will serve as encouragement for the data producers to * produce a consolidated transit feed rather than relying on this feature. */ diff --git a/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationParser.java b/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationParser.java index b55a84db040..0c01a280968 100644 --- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationParser.java +++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationParser.java @@ -2,8 +2,6 @@ import com.csvreader.CsvReader; import com.google.common.collect.ImmutableListMultimap; -import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -14,8 +12,6 @@ import org.apache.commons.lang3.BooleanUtils; import org.opentripplanner.ext.stopconsolidation.model.ConsolidatedStopGroup; import org.opentripplanner.transit.model.framework.FeedScopedId; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class StopConsolidationParser { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/PreferencesMapper.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/PreferencesMapper.java deleted file mode 100644 index 35883c8e72b..00000000000 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/PreferencesMapper.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.opentripplanner.ext.transmodelapi.mapping; - -import static org.opentripplanner.ext.transmodelapi.mapping.preferences.BikePreferencesMapper.mapBikePreferences; -import static org.opentripplanner.ext.transmodelapi.mapping.preferences.CarPreferencesMapper.mapCarPreferences; -import static org.opentripplanner.ext.transmodelapi.mapping.preferences.ItineraryFilterPreferencesMapper.mapItineraryFilterPreferences; -import static org.opentripplanner.ext.transmodelapi.mapping.preferences.ItineraryFilterPreferencesMapper.mapRentalPreferences; -import static org.opentripplanner.ext.transmodelapi.mapping.preferences.StreetPreferencesMapper.mapStreetPreferences; -import static org.opentripplanner.ext.transmodelapi.mapping.preferences.TransferPreferencesMapper.mapTransferPreferences; -import static org.opentripplanner.ext.transmodelapi.mapping.preferences.TransitPreferencesMapper.mapTransitPreferences; -import static org.opentripplanner.ext.transmodelapi.mapping.preferences.WalkPreferencesMapper.mapWalkPreferences; - -import graphql.schema.DataFetchingEnvironment; -import org.opentripplanner.ext.transmodelapi.support.DataFetcherDecorator; -import org.opentripplanner.routing.api.request.preference.RoutingPreferences; - -class PreferencesMapper { - - static void mapPreferences( - DataFetchingEnvironment environment, - DataFetcherDecorator callWith, - RoutingPreferences.Builder preferences - ) { - preferences.withStreet(street -> mapStreetPreferences(street, environment, preferences.street()) - ); - preferences.withWalk(walk -> mapWalkPreferences(walk, callWith)); - preferences.withBike(bike -> mapBikePreferences(bike, callWith)); - preferences.withCar(car -> mapCarPreferences(car, callWith)); - preferences.withTransfer(transfer -> mapTransferPreferences(transfer, environment, callWith)); - preferences.withTransit(transit -> mapTransitPreferences(transit, environment, callWith)); - preferences.withItineraryFilter(itineraryFilter -> - mapItineraryFilterPreferences(itineraryFilter, environment, callWith) - ); - preferences.withRental(rental -> mapRentalPreferences(rental, environment, callWith)); - } -} diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdater.java index a921836d907..e5086630941 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdater.java @@ -67,7 +67,7 @@ public HslParkUpdater( /** * Update the data from the sources. It first fetches parks from the facilities URL and park - * groups from hubs URL and then realtime updates from utilizations URL. If facilitiesFrequencySec + * groups from hubs URL and then real-time updates from utilizations URL. If facilitiesFrequencySec * is configured to be over 0, it also occasionally retches the parks as new parks might have been * added or the state of the old parks might have changed. * diff --git a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java index 20ca8054308..aa827ae5b4c 100644 --- a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java @@ -6,7 +6,9 @@ import org.opentripplanner.routing.algorithm.filterchain.api.TransitGeneralizedCostFilterParams; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.routing.api.request.preference.ItineraryFilterPreferences; +import org.opentripplanner.routing.api.request.preference.Relax; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; +import org.opentripplanner.routing.api.request.preference.VehicleParkingPreferences; import org.opentripplanner.routing.core.BicycleOptimizeType; class RequestToPreferencesMapper { @@ -42,8 +44,11 @@ void map() { private void mapCar() { preferences.withCar(car -> { setIfNotNull(req.carReluctance, car::withReluctance); - setIfNotNull(req.carParkCost, car::withParkCost); - setIfNotNull(req.carParkTime, car::withParkTime); + car.withParking(parking -> { + mapParking(parking); + setIfNotNull(req.carParkCost, parking::withParkCost); + setIfNotNull(req.carParkTime, parking::withParkTime); + }); }); } @@ -63,8 +68,6 @@ private void mapBike() { setIfNotNull(req.bikeBoardCost, bike::withBoardCost); setIfNotNull(req.bikeWalkingSpeed, bike::withWalkingSpeed); setIfNotNull(req.bikeWalkingReluctance, bike::withWalkingReluctance); - setIfNotNull(req.bikeParkCost, bike::withParkCost); - setIfNotNull(req.bikeParkTime, bike::withParkTime); setIfNotNull(req.bikeSwitchTime, bike::withSwitchTime); setIfNotNull(req.bikeSwitchCost, bike::withSwitchCost); setIfNotNull(req.bikeOptimizeType, bike::withOptimizeType); @@ -76,6 +79,12 @@ private void mapBike() { setIfNotNull(req.triangleSafetyFactor, triangle::withSafety); }); } + + bike.withParking(parking -> { + mapParking(parking); + setIfNotNull(req.bikeParkCost, parking::withParkCost); + setIfNotNull(req.bikeParkTime, parking::withParkTime); + }); }); } @@ -85,10 +94,17 @@ private BoardAndAlightSlack mapTransit() { setIfNotNull(req.alightSlack, tr::withDefaultAlightSlackSec); setIfNotNull(req.otherThanPreferredRoutesPenalty, tr::setOtherThanPreferredRoutesPenalty); setIfNotNull(req.ignoreRealtimeUpdates, tr::setIgnoreRealtimeUpdates); - setIfNotNull( - req.relaxTransitSearchGeneralizedCostAtDestination, - value -> tr.withRaptor(it -> it.withRelaxGeneralizedCostAtDestination(value)) - ); + + if (req.relaxTransitPriorityGroup != null) { + tr.withTransitGroupPriorityGeneralizedCostSlack( + CostLinearFunction.of(req.relaxTransitPriorityGroup) + ); + } else { + setIfNotNull( + req.relaxTransitSearchGeneralizedCostAtDestination, + v -> tr.withRaptor(r -> r.withRelaxGeneralizedCostAtDestination(v)) + ); + } }); return new BoardAndAlightSlack( @@ -159,6 +175,11 @@ private TransitGeneralizedCostFilterParams mapTransitGeneralizedCostFilterParams return new TransitGeneralizedCostFilterParams(costLimitFunction, intervalRelaxFactor); } + private void mapParking(VehicleParkingPreferences.Builder builder) { + builder.withRequiredVehicleParkingTags(req.requiredVehicleParkingTags); + builder.withBannedVehicleParkingTags(req.bannedVehicleParkingTags); + } + private void mapSystem() { preferences.withSystem(system -> { setIfNotNull(req.geoidElevation, system::withGeoidElevation); @@ -171,6 +192,17 @@ static void setIfNotNull(T value, @NotNull Consumer body) { } } + static void mapRelaxIfNotNull(String fx, @NotNull Consumer body) { + if (fx == null) { + return; + } + var a = fx.split("[\\sxXuUvVtT*+]+"); + if (a.length != 2) { + return; + } + body.accept(new Relax(Double.parseDouble(a[0]), Integer.parseInt(a[1]))); + } + /** * The combined value of board and alight slack is used in the initialization of transfer * preferences. diff --git a/src/main/java/org/opentripplanner/api/common/RoutingResource.java b/src/main/java/org/opentripplanner/api/common/RoutingResource.java index e60bfe41b82..9cb4f139bc4 100644 --- a/src/main/java/org/opentripplanner/api/common/RoutingResource.java +++ b/src/main/java/org/opentripplanner/api/common/RoutingResource.java @@ -27,8 +27,6 @@ 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.api.request.request.filter.VehicleParkingFilter.TagsFilter; -import org.opentripplanner.routing.api.request.request.filter.VehicleParkingFilterRequest; import org.opentripplanner.routing.core.BicycleOptimizeType; import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.standalone.config.framework.file.ConfigFileLoader; @@ -653,9 +651,16 @@ public abstract class RoutingResource { @QueryParam("geoidElevation") protected Boolean geoidElevation; + /** + * @deprecated Support has been removed. + */ + @Deprecated @QueryParam("useVehicleParkingAvailabilityInformation") protected Boolean useVehicleParkingAvailabilityInformation; + @QueryParam("relaxTransitPriorityGroup") + protected String relaxTransitPriorityGroup; + /** * Whether non-optimal transit paths at the destination should be returned. * This is optional. Use values between 1.0 and 2.0. For example to relax 10% use 1.1. @@ -749,20 +754,6 @@ protected RouteRequest buildRequest(MultivaluedMap queryParamete setIfNotNull(allowedVehicleRentalNetworks, rental::setAllowedNetworks); setIfNotNull(bannedVehicleRentalNetworks, rental::setBannedNetworks); } - { - var parking = journey.parking(); - setIfNotNull( - useVehicleParkingAvailabilityInformation, - parking::setUseAvailabilityInformation - ); - - parking.setFilter( - new VehicleParkingFilterRequest( - new TagsFilter(bannedVehicleParkingTags), - new TagsFilter(requiredVehicleParkingTags) - ) - ); - } setIfNotNull(arriveBy, request::setArriveBy); diff --git a/src/main/java/org/opentripplanner/api/configuration/APIEndpoints.java b/src/main/java/org/opentripplanner/api/configuration/APIEndpoints.java index 7873724dcf4..b0d38aa00cd 100644 --- a/src/main/java/org/opentripplanner/api/configuration/APIEndpoints.java +++ b/src/main/java/org/opentripplanner/api/configuration/APIEndpoints.java @@ -10,8 +10,8 @@ import static org.opentripplanner.framework.application.OTPFeature.SandboxAPIGeocoder; import static org.opentripplanner.framework.application.OTPFeature.SandboxAPIMapboxVectorTilesApi; import static org.opentripplanner.framework.application.OTPFeature.SandboxAPIParkAndRideApi; -import static org.opentripplanner.framework.application.OTPFeature.SandboxAPITransmodelApi; import static org.opentripplanner.framework.application.OTPFeature.SandboxAPITravelTime; +import static org.opentripplanner.framework.application.OTPFeature.TransmodelGraphQlApi; import java.util.ArrayList; import java.util.Collection; @@ -25,11 +25,11 @@ import org.opentripplanner.api.resource.ServerInfo; import org.opentripplanner.api.resource.UpdaterStatusResource; import org.opentripplanner.apis.gtfs.GtfsGraphQLAPI; +import org.opentripplanner.apis.transmodel.TransmodelAPI; import org.opentripplanner.ext.actuator.ActuatorAPI; import org.opentripplanner.ext.geocoder.GeocoderResource; import org.opentripplanner.ext.parkAndRideApi.ParkAndRideResource; import org.opentripplanner.ext.reportapi.resource.ReportResource; -import org.opentripplanner.ext.transmodelapi.TransmodelAPI; import org.opentripplanner.ext.traveltime.TravelTimeResource; import org.opentripplanner.ext.vectortiles.VectorTilesResource; import org.opentripplanner.framework.application.OTPFeature; @@ -51,16 +51,16 @@ private APIEndpoints() { // Add feature enabled APIs, these can be enabled by default, some is not. // See the OTPFeature enum for details. addIfEnabled(APIBikeRental, BikeRental.class); - addIfEnabled(APIServerInfo, ServerInfo.class); addIfEnabled(APIGraphInspectorTile, GraphInspectorTileResource.class); addIfEnabled(APIGraphInspectorTile, GraphInspectorVectorTileResource.class); + addIfEnabled(APIServerInfo, ServerInfo.class); addIfEnabled(APIUpdaterStatus, UpdaterStatusResource.class); + addIfEnabled(GtfsGraphQlApi, GtfsGraphQLAPI.class); + addIfEnabled(TransmodelGraphQlApi, TransmodelAPI.class); // Sandbox extension APIs addIfEnabled(ActuatorAPI, ActuatorAPI.class); addIfEnabled(ReportApi, ReportResource.class); - addIfEnabled(SandboxAPITransmodelApi, TransmodelAPI.class); - addIfEnabled(GtfsGraphQlApi, GtfsGraphQLAPI.class); addIfEnabled(SandboxAPIMapboxVectorTilesApi, VectorTilesResource.class); addIfEnabled(SandboxAPIParkAndRideApi, ParkAndRideResource.class); addIfEnabled(SandboxAPIGeocoder, GeocoderResource.class); diff --git a/src/main/java/org/opentripplanner/api/mapping/PlaceMapper.java b/src/main/java/org/opentripplanner/api/mapping/PlaceMapper.java index f6b0defd709..e806c5615d2 100644 --- a/src/main/java/org/opentripplanner/api/mapping/PlaceMapper.java +++ b/src/main/java/org/opentripplanner/api/mapping/PlaceMapper.java @@ -130,7 +130,7 @@ private ApiVehicleParkingWithEntrance mapVehicleParking( .hasWheelchairAccessibleCarPlaces(vp.hasWheelchairAccessibleCarPlaces()) .availability(mapVehicleParkingSpaces(vp.getAvailability())) .capacity(mapVehicleParkingSpaces(vp.getCapacity())) - .realtime(vehicleParkingWithEntrance.isRealtime()) + .realTime(vehicleParkingWithEntrance.isRealtime()) .build(); } } diff --git a/src/main/java/org/opentripplanner/api/mapping/TripPlanMapper.java b/src/main/java/org/opentripplanner/api/mapping/TripPlanMapper.java index 3247b675b65..8c5459af8ed 100644 --- a/src/main/java/org/opentripplanner/api/mapping/TripPlanMapper.java +++ b/src/main/java/org/opentripplanner/api/mapping/TripPlanMapper.java @@ -21,7 +21,7 @@ public ApiTripPlan mapTripPlan(TripPlan domain) { } ApiTripPlan api = new ApiTripPlan(); api.date = Date.from(domain.date); - // The origin/destination do not have arrival/depature times; Hence {@code null} is used. + // The origin/destination do not have arrival/departure times; Hence {@code null} is used. api.from = placeMapper.mapPlace(domain.from, null, null, null, null); api.to = placeMapper.mapPlace(domain.to, null, null, null, null); api.itineraries = itineraryMapper.mapItineraries(domain.itineraries); diff --git a/src/main/java/org/opentripplanner/api/mapping/TripTimeMapper.java b/src/main/java/org/opentripplanner/api/mapping/TripTimeMapper.java index c5303c744cf..2f5bfe91026 100644 --- a/src/main/java/org/opentripplanner/api/mapping/TripTimeMapper.java +++ b/src/main/java/org/opentripplanner/api/mapping/TripTimeMapper.java @@ -28,13 +28,13 @@ public static ApiTripTimeShort mapToApi(TripTimeOnDate domain) { api.stopCount = domain.getStopCount(); api.scheduledArrival = domain.getScheduledArrival(); api.scheduledDeparture = domain.getScheduledDeparture(); - api.realtimeArrival = domain.getRealtimeArrival(); - api.realtimeDeparture = domain.getRealtimeDeparture(); + api.realTimeArrival = domain.getRealtimeArrival(); + api.realTimeDeparture = domain.getRealtimeDeparture(); api.arrivalDelay = domain.getArrivalDelay(); api.departureDelay = domain.getDepartureDelay(); api.timepoint = domain.isTimepoint(); - api.realtime = domain.isRealtime(); - api.realtimeState = ApiRealTimeState.RealTimeState(domain.getRealtimeState()); + api.realTime = domain.isRealtime(); + api.realTimeState = ApiRealTimeState.RealTimeState(domain.getRealTimeState()); api.blockId = domain.getBlockId(); api.headsign = I18NStringMapper.mapToApi(domain.getHeadsign(), null); api.tripId = FeedScopedIdMapper.mapToApi(domain.getTrip().getId()); diff --git a/src/main/java/org/opentripplanner/api/model/ApiTripSearchMetadata.java b/src/main/java/org/opentripplanner/api/model/ApiTripSearchMetadata.java index 49c98d6e5b6..486118883f9 100644 --- a/src/main/java/org/opentripplanner/api/model/ApiTripSearchMetadata.java +++ b/src/main/java/org/opentripplanner/api/model/ApiTripSearchMetadata.java @@ -22,7 +22,7 @@ public class ApiTripSearchMetadata { * This is the suggested search time for the "next page" or time-window. Insert it together with * the {@link #searchWindowUsed} in the request to get a new set of trips following in the * time-window AFTER the current search. No duplicate trips should be returned, unless a trip is - * delayed and new realtime-data is available. + * delayed and new real-time-data is available. *

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

* Be careful to use paging/scrolling with the {@code numOfItineraries} parameter set. It is safe * to scroll backward when the {@code arriveBy=true}, but not if {@code arriveBy=false}. If you diff --git a/src/main/java/org/opentripplanner/api/model/ApiTripTimeShort.java b/src/main/java/org/opentripplanner/api/model/ApiTripTimeShort.java index 92a78f4aade..0a340d4a812 100644 --- a/src/main/java/org/opentripplanner/api/model/ApiTripTimeShort.java +++ b/src/main/java/org/opentripplanner/api/model/ApiTripTimeShort.java @@ -11,13 +11,13 @@ public class ApiTripTimeShort implements Serializable { public int stopCount; public int scheduledArrival = UNDEFINED; public int scheduledDeparture = UNDEFINED; - public int realtimeArrival = UNDEFINED; - public int realtimeDeparture = UNDEFINED; + public int realTimeArrival = UNDEFINED; + public int realTimeDeparture = UNDEFINED; public int arrivalDelay = UNDEFINED; public int departureDelay = UNDEFINED; public boolean timepoint = false; - public boolean realtime = false; - public ApiRealTimeState realtimeState = ApiRealTimeState.SCHEDULED; + public boolean realTime = false; + public ApiRealTimeState realTimeState = ApiRealTimeState.SCHEDULED; public long serviceDay; public String tripId; public String blockId; diff --git a/src/main/java/org/opentripplanner/api/model/ApiVehicleParkingWithEntrance.java b/src/main/java/org/opentripplanner/api/model/ApiVehicleParkingWithEntrance.java index 56e3e0c3710..8cac78f6c9e 100644 --- a/src/main/java/org/opentripplanner/api/model/ApiVehicleParkingWithEntrance.java +++ b/src/main/java/org/opentripplanner/api/model/ApiVehicleParkingWithEntrance.java @@ -73,15 +73,15 @@ public class ApiVehicleParkingWithEntrance { public final ApiVehicleParkingSpaces capacity; /** - * The number of available spaces. Only present if there is a realtime updater present. Maybe + * The number of available spaces. Only present if there is a real-time updater present. Maybe * {@code null} if unknown. */ public final ApiVehicleParkingSpaces availability; /** - * True if realtime information is used for checking availability. + * True if real-time information is used for checking availability. */ - public final boolean realtime; + public final boolean realTime; ApiVehicleParkingWithEntrance( String id, @@ -98,7 +98,7 @@ public class ApiVehicleParkingWithEntrance { boolean hasWheelchairAccessibleCarPlaces, ApiVehicleParkingSpaces capacity, ApiVehicleParkingSpaces availability, - boolean realtime + boolean realTime ) { this.id = id; this.name = name; @@ -114,7 +114,7 @@ public class ApiVehicleParkingWithEntrance { this.hasWheelchairAccessibleCarPlaces = hasWheelchairAccessibleCarPlaces; this.capacity = capacity; this.availability = availability; - this.realtime = realtime; + this.realTime = realTime; } public static ApiVehicleParkingWithEntranceBuilder builder() { @@ -137,7 +137,7 @@ public static class ApiVehicleParkingWithEntranceBuilder { private boolean hasWheelchairAccessibleCarPlaces; private ApiVehicleParkingSpaces capacity; private ApiVehicleParkingSpaces availability; - private boolean realtime; + private boolean realTime; ApiVehicleParkingWithEntranceBuilder() {} @@ -213,8 +213,8 @@ public ApiVehicleParkingWithEntranceBuilder availability(ApiVehicleParkingSpaces return this; } - public ApiVehicleParkingWithEntranceBuilder realtime(boolean realtime) { - this.realtime = realtime; + public ApiVehicleParkingWithEntranceBuilder realTime(boolean realTime) { + this.realTime = realTime; return this; } @@ -234,7 +234,7 @@ public ApiVehicleParkingWithEntrance build() { hasWheelchairAccessibleCarPlaces, capacity, availability, - realtime + realTime ); } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java index d7229b84b1f..dfc3e2d75f8 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java @@ -17,7 +17,7 @@ public record GraphQLRequestContext( FareService fareService, VehicleParkingService vehicleParkingService, VehicleRentalService vehicleRentalService, - RealtimeVehicleService realtimeVehicleService, + RealtimeVehicleService realTimeVehicleService, GraphFinder graphFinder, RouteRequest defaultRouteRequest ) { diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java index 18f5a7730d5..3fb339daa32 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java @@ -91,6 +91,7 @@ public static PlaceType toModel(GraphQLFilterPlaceType type) { case CAR_PARK -> PlaceType.CAR_PARK; case DEPARTURE_ROW -> PlaceType.PATTERN_AT_STOP; case STOP -> PlaceType.STOP; + case STATION -> PlaceType.STATION; }; } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PatternImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PatternImpl.java index 18ab5c5d5e9..241434c664f 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PatternImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PatternImpl.java @@ -249,7 +249,7 @@ private List getTrips(DataFetchingEnvironment environment) { } private RealtimeVehicleService getRealtimeVehiclesService(DataFetchingEnvironment environment) { - return environment.getContext().realtimeVehicleService(); + return environment.getContext().realTimeVehicleService(); } private TransitService getTransitService(DataFetchingEnvironment environment) { diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlaceInterfaceTypeResolver.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlaceInterfaceTypeResolver.java index 67ec004759d..cb2bfc77c32 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlaceInterfaceTypeResolver.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlaceInterfaceTypeResolver.java @@ -9,6 +9,7 @@ import org.opentripplanner.service.vehiclerental.model.VehicleRentalStation; import org.opentripplanner.service.vehiclerental.model.VehicleRentalVehicle; import org.opentripplanner.transit.model.site.RegularStop; +import org.opentripplanner.transit.model.site.Station; public class PlaceInterfaceTypeResolver implements TypeResolver { @@ -45,7 +46,7 @@ public GraphQLObjectType getType(TypeResolutionEnvironment environment) { if (o instanceof PatternAtStop) { return schema.getObjectType("DepartureRow"); } - if (o instanceof RegularStop) { + if (o instanceof RegularStop || o instanceof Station) { return schema.getObjectType("Stop"); } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlanImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlanImpl.java index 4e5c4584495..9ae58be1334 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlanImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlanImpl.java @@ -8,7 +8,7 @@ import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.StopArrival; -import org.opentripplanner.model.plan.pagecursor.PageCursor; +import org.opentripplanner.model.plan.paging.cursor.PageCursor; import org.opentripplanner.routing.api.response.RoutingError; import org.opentripplanner.routing.api.response.RoutingResponse; import org.opentripplanner.routing.api.response.TripSearchMetadata; diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index 1c2a7b7c72a..d749987384c 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -17,9 +17,11 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.locationtech.jts.geom.Coordinate; @@ -75,6 +77,10 @@ public class QueryTypeImpl implements GraphQLDataFetchers.GraphQLQueryType { DataImportIssueStore.NOOP ); + private final List DEFAULT_PLACE_TYPES = List.copyOf( + EnumSet.complementOf(EnumSet.of(PlaceType.STATION)) + ); + @Override public DataFetcher> agencies() { return environment -> getTransitService(environment).getAgencies(); @@ -276,6 +282,7 @@ public DataFetcher fuzzyTrip() { public DataFetcher> nearest() { return environment -> { List filterByStops = null; + List filterByStations = null; List filterByRoutes = null; List filterByBikeRentalStations = null; // TODO implement @@ -293,6 +300,10 @@ public DataFetcher> nearest() { filterByIds.getGraphQLStops() != null ? filterByIds.getGraphQLStops().stream().map(FeedScopedId::parse).toList() : null; + filterByStations = + filterByIds.getGraphQLStations() != null + ? filterByIds.getGraphQLStations().stream().map(FeedScopedId::parse).toList() + : null; filterByRoutes = filterByIds.getGraphQLRoutes() != null ? filterByIds.getGraphQLRoutes().stream().map(FeedScopedId::parse).toList() @@ -318,7 +329,7 @@ public DataFetcher> nearest() { : null; List filterByPlaceTypes = args.getGraphQLFilterByPlaceTypes() != null ? args.getGraphQLFilterByPlaceTypes().stream().map(GraphQLUtils::toModel).toList() - : null; + : DEFAULT_PLACE_TYPES; List places; try { @@ -333,6 +344,7 @@ public DataFetcher> nearest() { filterByModes, filterByPlaceTypes, filterByStops, + filterByStations, filterByRoutes, filterByBikeRentalStations, getTransitService(environment) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java index 9353027439b..0730d2fbc91 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java @@ -538,7 +538,7 @@ private List getTripTimeOnDatesForPatternAtStopIncludingTripsWit } /** - * Get a stream of {@link TripPattern} that were created realtime based of the provided pattern. + * Get a stream of {@link TripPattern} that were created real-time based of the provided pattern. * Only patterns that don't have removed (stops can still be skipped) or added stops are included. */ private Stream getRealtimeAddedPatternsAsStream( diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StoptimeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StoptimeImpl.java index a74d4f9461d..faf59ef9d6e 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StoptimeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StoptimeImpl.java @@ -71,7 +71,7 @@ public DataFetcher realtimeState() { return environment -> getSource(environment).isCanceledEffectively() ? RealTimeState.CANCELED.name() - : getSource(environment).getRealtimeState().name(); + : getSource(environment).getRealTimeState().name(); } @Override diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java index 78255ce7787..2502d2b9539 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java @@ -405,7 +405,7 @@ private TransitService getTransitService(DataFetchingEnvironment environment) { } private RealtimeVehicleService getRealtimeVehiclesService(DataFetchingEnvironment environment) { - return environment.getContext().realtimeVehicleService(); + return environment.getContext().realTimeVehicleService(); } private Trip getSource(DataFetchingEnvironment environment) { diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index cba63739dd5..ecc038fec18 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -237,6 +237,7 @@ public enum GraphQLFilterPlaceType { BIKE_PARK, CAR_PARK, DEPARTURE_ROW, + STATION, STOP, VEHICLE_RENT, } @@ -372,6 +373,7 @@ public static class GraphQLInputFiltersInput { private List bikeRentalStations; private List carParks; private List routes; + private List stations; private List stops; public GraphQLInputFiltersInput(Map args) { @@ -380,6 +382,7 @@ public GraphQLInputFiltersInput(Map args) { this.bikeRentalStations = (List) args.get("bikeRentalStations"); this.carParks = (List) args.get("carParks"); this.routes = (List) args.get("routes"); + this.stations = (List) args.get("stations"); this.stops = (List) args.get("stops"); } } @@ -400,6 +403,10 @@ public List getGraphQLRoutes() { return this.routes; } + public List getGraphQLStations() { + return this.stations; + } + public List getGraphQLStops() { return this.stops; } @@ -420,6 +427,10 @@ public void setGraphQLRoutes(List routes) { this.routes = routes; } + public void setGraphQLStations(List stations) { + this.stations = stations; + } + public void setGraphQLStops(List stops) { this.stops = stops; } 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 9b8d8e02dec..ab9e8bce823 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java @@ -21,11 +21,9 @@ 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.request.filter.SelectRequest; import org.opentripplanner.routing.api.request.request.filter.TransitFilterRequest; -import org.opentripplanner.routing.api.request.request.filter.VehicleParkingFilter; -import org.opentripplanner.routing.api.request.request.filter.VehicleParkingFilter.TagsFilter; -import org.opentripplanner.routing.api.request.request.filter.VehicleParkingFilterRequest; import org.opentripplanner.routing.core.BicycleOptimizeType; import org.opentripplanner.transit.model.basic.MainAndSubMode; import org.opentripplanner.transit.model.basic.TransitMode; @@ -84,9 +82,14 @@ public static RouteRequest toRouteRequest( callWith.argument("triangle.safetyFactor", triangle::withSafety); }); } + + bike.withParking(parking -> setParkingPreferences(callWith, parking)); }); - preferences.withCar(car -> callWith.argument("carReluctance", car::withReluctance)); + preferences.withCar(car -> { + callWith.argument("carReluctance", car::withReluctance); + car.withParking(parking -> setParkingPreferences(callWith, parking)); + }); preferences.withWalk(b -> { callWith.argument("walkReluctance", b::withReluctance); @@ -244,19 +247,6 @@ public static RouteRequest toRouteRequest( (Collection v) -> vehicleRental.setBannedNetworks(new HashSet<>(v)) ); - var parking = request.journey().parking(); - callWith.argument("parking.unpreferredCost", parking::setUnpreferredCost); - - callWith.argument( - "parking.filters", - (Collection> filters) -> parking.setFilter(parseFilters(filters)) - ); - - callWith.argument( - "parking.preferred", - (Collection> filters) -> parking.setPreferred(parseFilters(filters)) - ); - callWith.argument( "locale", (String v) -> request.setLocale(GraphQLUtils.getLocale(environment, v)) @@ -264,17 +254,16 @@ public static RouteRequest toRouteRequest( return request; } - private static VehicleParkingFilterRequest parseFilters(Collection> filters) { - var not = parseFilters(filters, "not"); - var select = parseFilters(filters, "select"); - return new VehicleParkingFilterRequest(not, select); + private static Set parseNotFilters(Collection> filters) { + return parseFilters(filters, "not"); + } + + private static Set parseSelectFilters(Collection> filters) { + return parseFilters(filters, "select"); } @Nonnull - private static Set parseFilters( - Collection> filters, - String key - ) { + private static Set parseFilters(Collection> filters, String key) { return filters .stream() .flatMap(f -> @@ -283,14 +272,12 @@ private static Set parseFilters( .collect(Collectors.toSet()); } - private static Stream parseOperation( - Collection>> map - ) { + private static Stream parseOperation(Collection>> map) { return map .stream() - .map(f -> { + .flatMap(f -> { var tags = f.getOrDefault("tags", List.of()); - return new TagsFilter(Set.copyOf(tags)); + return tags.stream(); }); } @@ -314,6 +301,29 @@ private static GenericLocation toGenericLocation(Map m) { return new GenericLocation(lat, lng); } + private static void setParkingPreferences( + CallerWithEnvironment callWith, + VehicleParkingPreferences.Builder parking + ) { + callWith.argument("parking.unpreferredCost", parking::withUnpreferredVehicleParkingTagCost); + + callWith.argument( + "parking.filters", + (Collection> filters) -> { + parking.withRequiredVehicleParkingTags(parseSelectFilters(filters)); + parking.withBannedVehicleParkingTags(parseNotFilters(filters)); + } + ); + + callWith.argument( + "parking.preferred", + (Collection> preferred) -> { + parking.withPreferredVehicleParkingTags(parseSelectFilters(preferred)); + parking.withNotPreferredVehicleParkingTags(parseNotFilters(preferred)); + } + ); + } + private static class CallerWithEnvironment { private final DataFetchingEnvironment environment; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/TransmodelAPI.java b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelAPI.java similarity index 96% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/TransmodelAPI.java rename to src/main/java/org/opentripplanner/apis/transmodel/TransmodelAPI.java index a1623e6eedf..6af4ab1953c 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/TransmodelAPI.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelAPI.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi; +package org.opentripplanner.apis.transmodel; import com.fasterxml.jackson.databind.ObjectMapper; import graphql.schema.GraphQLSchema; @@ -22,8 +22,8 @@ import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; -import org.opentripplanner.ext.transmodelapi.mapping.TransitIdMapper; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.transit.service.TransitModel; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/TransmodelAPIParameters.java b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelAPIParameters.java similarity index 92% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/TransmodelAPIParameters.java rename to src/main/java/org/opentripplanner/apis/transmodel/TransmodelAPIParameters.java index 498c8539b4d..5ab64dcd2e6 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/TransmodelAPIParameters.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelAPIParameters.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi; +package org.opentripplanner.apis.transmodel; import java.util.Collection; import org.opentripplanner.ext.actuator.MicrometerGraphQLInstrumentation; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/TransmodelGraph.java b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraph.java similarity index 95% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/TransmodelGraph.java rename to src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraph.java index 66547da6653..051e369dde0 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/TransmodelGraph.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraph.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi; +package org.opentripplanner.apis.transmodel; import graphql.ExecutionInput; import graphql.ExecutionResult; @@ -17,9 +17,9 @@ import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import org.opentripplanner.apis.transmodel.support.AbortOnTimeoutExecutionStrategy; +import org.opentripplanner.apis.transmodel.support.ExecutionResultMapper; import org.opentripplanner.ext.actuator.MicrometerGraphQLInstrumentation; -import org.opentripplanner.ext.transmodelapi.support.AbortOnTimeoutExecutionStrategy; -import org.opentripplanner.ext.transmodelapi.support.ExecutionResultMapper; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.framework.concurrent.OtpRequestThreadFactory; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/TransmodelGraphQLPlanner.java b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLPlanner.java similarity index 91% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/TransmodelGraphQLPlanner.java rename to src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLPlanner.java index a9d369a3982..39c7aa9c851 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/TransmodelGraphQLPlanner.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLPlanner.java @@ -1,13 +1,13 @@ -package org.opentripplanner.ext.transmodelapi; +package org.opentripplanner.apis.transmodel; import graphql.execution.DataFetcherResult; import graphql.schema.DataFetchingEnvironment; import java.util.List; import java.util.Locale; import java.util.Map; -import org.opentripplanner.ext.transmodelapi.mapping.TripRequestMapper; -import org.opentripplanner.ext.transmodelapi.mapping.ViaRequestMapper; -import org.opentripplanner.ext.transmodelapi.model.PlanResponse; +import org.opentripplanner.apis.transmodel.mapping.TripRequestMapper; +import org.opentripplanner.apis.transmodel.mapping.ViaRequestMapper; +import org.opentripplanner.apis.transmodel.model.PlanResponse; import org.opentripplanner.routing.algorithm.mapping.TripPlanMapper; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.RouteViaRequest; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/TransmodelGraphQLSchema.java b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java similarity index 92% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/TransmodelGraphQLSchema.java rename to src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java index 40a5d936885..00bf98c703f 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/TransmodelGraphQLSchema.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java @@ -1,12 +1,12 @@ -package org.opentripplanner.ext.transmodelapi; +package org.opentripplanner.apis.transmodel; import static java.lang.Boolean.TRUE; import static java.util.Collections.emptyList; -import static org.opentripplanner.ext.transmodelapi.mapping.SeverityMapper.getTransmodelSeverity; -import static org.opentripplanner.ext.transmodelapi.mapping.TransitIdMapper.mapIDsToDomainNullSafe; -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.FILTER_PLACE_TYPE_ENUM; -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.MULTI_MODAL_MODE; -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.TRANSPORT_MODE; +import static org.opentripplanner.apis.transmodel.mapping.SeverityMapper.getTransmodelSeverity; +import static org.opentripplanner.apis.transmodel.mapping.TransitIdMapper.mapIDsToDomainNullSafe; +import static org.opentripplanner.apis.transmodel.model.EnumTypes.FILTER_PLACE_TYPE_ENUM; +import static org.opentripplanner.apis.transmodel.model.EnumTypes.MULTI_MODAL_MODE; +import static org.opentripplanner.apis.transmodel.model.EnumTypes.TRANSPORT_MODE; import static org.opentripplanner.model.projectinfo.OtpProjectInfo.projectInfo; import graphql.Scalars; @@ -41,63 +41,63 @@ import java.util.stream.Stream; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Envelope; -import org.opentripplanner.ext.transmodelapi.mapping.PlaceMapper; -import org.opentripplanner.ext.transmodelapi.mapping.TransitIdMapper; -import org.opentripplanner.ext.transmodelapi.model.DefaultRouteRequestType; -import org.opentripplanner.ext.transmodelapi.model.EnumTypes; -import org.opentripplanner.ext.transmodelapi.model.TransmodelPlaceType; -import org.opentripplanner.ext.transmodelapi.model.framework.AuthorityType; -import org.opentripplanner.ext.transmodelapi.model.framework.BrandingType; -import org.opentripplanner.ext.transmodelapi.model.framework.InfoLinkType; -import org.opentripplanner.ext.transmodelapi.model.framework.MultilingualStringType; -import org.opentripplanner.ext.transmodelapi.model.framework.NoticeType; -import org.opentripplanner.ext.transmodelapi.model.framework.OperatorType; -import org.opentripplanner.ext.transmodelapi.model.framework.PenaltyForStreetModeType; -import org.opentripplanner.ext.transmodelapi.model.framework.PointsOnLinkType; -import org.opentripplanner.ext.transmodelapi.model.framework.RentalVehicleTypeType; -import org.opentripplanner.ext.transmodelapi.model.framework.ServerInfoType; -import org.opentripplanner.ext.transmodelapi.model.framework.StreetModeDurationInputType; -import org.opentripplanner.ext.transmodelapi.model.framework.SystemNoticeType; -import org.opentripplanner.ext.transmodelapi.model.framework.ValidityPeriodType; -import org.opentripplanner.ext.transmodelapi.model.network.DestinationDisplayType; -import org.opentripplanner.ext.transmodelapi.model.network.GroupOfLinesType; -import org.opentripplanner.ext.transmodelapi.model.network.JourneyPatternType; -import org.opentripplanner.ext.transmodelapi.model.network.LineType; -import org.opentripplanner.ext.transmodelapi.model.network.PresentationType; -import org.opentripplanner.ext.transmodelapi.model.network.StopToStopGeometryType; -import org.opentripplanner.ext.transmodelapi.model.plan.ElevationProfileStepType; -import org.opentripplanner.ext.transmodelapi.model.plan.LegType; -import org.opentripplanner.ext.transmodelapi.model.plan.PathGuidanceType; -import org.opentripplanner.ext.transmodelapi.model.plan.PlanPlaceType; -import org.opentripplanner.ext.transmodelapi.model.plan.RoutingErrorType; -import org.opentripplanner.ext.transmodelapi.model.plan.TripPatternType; -import org.opentripplanner.ext.transmodelapi.model.plan.TripQuery; -import org.opentripplanner.ext.transmodelapi.model.plan.TripType; -import org.opentripplanner.ext.transmodelapi.model.plan.ViaLocationInputType; -import org.opentripplanner.ext.transmodelapi.model.plan.ViaSegmentInputType; -import org.opentripplanner.ext.transmodelapi.model.plan.ViaTripQuery; -import org.opentripplanner.ext.transmodelapi.model.plan.ViaTripType; -import org.opentripplanner.ext.transmodelapi.model.siri.et.EstimatedCallType; -import org.opentripplanner.ext.transmodelapi.model.siri.sx.AffectsType; -import org.opentripplanner.ext.transmodelapi.model.siri.sx.PtSituationElementType; -import org.opentripplanner.ext.transmodelapi.model.stop.BikeParkType; -import org.opentripplanner.ext.transmodelapi.model.stop.BikeRentalStationType; -import org.opentripplanner.ext.transmodelapi.model.stop.MonoOrMultiModalStation; -import org.opentripplanner.ext.transmodelapi.model.stop.PlaceAtDistanceType; -import org.opentripplanner.ext.transmodelapi.model.stop.PlaceInterfaceType; -import org.opentripplanner.ext.transmodelapi.model.stop.QuayAtDistanceType; -import org.opentripplanner.ext.transmodelapi.model.stop.QuayType; -import org.opentripplanner.ext.transmodelapi.model.stop.RentalVehicleType; -import org.opentripplanner.ext.transmodelapi.model.stop.StopPlaceType; -import org.opentripplanner.ext.transmodelapi.model.stop.TariffZoneType; -import org.opentripplanner.ext.transmodelapi.model.timetable.BookingArrangementType; -import org.opentripplanner.ext.transmodelapi.model.timetable.DatedServiceJourneyQuery; -import org.opentripplanner.ext.transmodelapi.model.timetable.DatedServiceJourneyType; -import org.opentripplanner.ext.transmodelapi.model.timetable.InterchangeType; -import org.opentripplanner.ext.transmodelapi.model.timetable.ServiceJourneyType; -import org.opentripplanner.ext.transmodelapi.model.timetable.TimetabledPassingTimeType; -import org.opentripplanner.ext.transmodelapi.model.timetable.TripMetadataType; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.mapping.PlaceMapper; +import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; +import org.opentripplanner.apis.transmodel.model.DefaultRouteRequestType; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.TransmodelPlaceType; +import org.opentripplanner.apis.transmodel.model.framework.AuthorityType; +import org.opentripplanner.apis.transmodel.model.framework.BrandingType; +import org.opentripplanner.apis.transmodel.model.framework.InfoLinkType; +import org.opentripplanner.apis.transmodel.model.framework.MultilingualStringType; +import org.opentripplanner.apis.transmodel.model.framework.NoticeType; +import org.opentripplanner.apis.transmodel.model.framework.OperatorType; +import org.opentripplanner.apis.transmodel.model.framework.PenaltyForStreetModeType; +import org.opentripplanner.apis.transmodel.model.framework.PointsOnLinkType; +import org.opentripplanner.apis.transmodel.model.framework.RentalVehicleTypeType; +import org.opentripplanner.apis.transmodel.model.framework.ServerInfoType; +import org.opentripplanner.apis.transmodel.model.framework.StreetModeDurationInputType; +import org.opentripplanner.apis.transmodel.model.framework.SystemNoticeType; +import org.opentripplanner.apis.transmodel.model.framework.ValidityPeriodType; +import org.opentripplanner.apis.transmodel.model.network.DestinationDisplayType; +import org.opentripplanner.apis.transmodel.model.network.GroupOfLinesType; +import org.opentripplanner.apis.transmodel.model.network.JourneyPatternType; +import org.opentripplanner.apis.transmodel.model.network.LineType; +import org.opentripplanner.apis.transmodel.model.network.PresentationType; +import org.opentripplanner.apis.transmodel.model.network.StopToStopGeometryType; +import org.opentripplanner.apis.transmodel.model.plan.ElevationProfileStepType; +import org.opentripplanner.apis.transmodel.model.plan.LegType; +import org.opentripplanner.apis.transmodel.model.plan.PathGuidanceType; +import org.opentripplanner.apis.transmodel.model.plan.PlanPlaceType; +import org.opentripplanner.apis.transmodel.model.plan.RoutingErrorType; +import org.opentripplanner.apis.transmodel.model.plan.TripPatternType; +import org.opentripplanner.apis.transmodel.model.plan.TripQuery; +import org.opentripplanner.apis.transmodel.model.plan.TripType; +import org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType; +import org.opentripplanner.apis.transmodel.model.plan.ViaSegmentInputType; +import org.opentripplanner.apis.transmodel.model.plan.ViaTripQuery; +import org.opentripplanner.apis.transmodel.model.plan.ViaTripType; +import org.opentripplanner.apis.transmodel.model.siri.et.EstimatedCallType; +import org.opentripplanner.apis.transmodel.model.siri.sx.AffectsType; +import org.opentripplanner.apis.transmodel.model.siri.sx.PtSituationElementType; +import org.opentripplanner.apis.transmodel.model.stop.BikeParkType; +import org.opentripplanner.apis.transmodel.model.stop.BikeRentalStationType; +import org.opentripplanner.apis.transmodel.model.stop.MonoOrMultiModalStation; +import org.opentripplanner.apis.transmodel.model.stop.PlaceAtDistanceType; +import org.opentripplanner.apis.transmodel.model.stop.PlaceInterfaceType; +import org.opentripplanner.apis.transmodel.model.stop.QuayAtDistanceType; +import org.opentripplanner.apis.transmodel.model.stop.QuayType; +import org.opentripplanner.apis.transmodel.model.stop.RentalVehicleType; +import org.opentripplanner.apis.transmodel.model.stop.StopPlaceType; +import org.opentripplanner.apis.transmodel.model.stop.TariffZoneType; +import org.opentripplanner.apis.transmodel.model.timetable.BookingArrangementType; +import org.opentripplanner.apis.transmodel.model.timetable.DatedServiceJourneyQuery; +import org.opentripplanner.apis.transmodel.model.timetable.DatedServiceJourneyType; +import org.opentripplanner.apis.transmodel.model.timetable.InterchangeType; +import org.opentripplanner.apis.transmodel.model.timetable.ServiceJourneyType; +import org.opentripplanner.apis.transmodel.model.timetable.TimetabledPassingTimeType; +import org.opentripplanner.apis.transmodel.model.timetable.TripMetadataType; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.model.plan.legreference.LegReference; import org.opentripplanner.model.plan.legreference.LegReferenceSerializer; import org.opentripplanner.routing.alertpatch.TransitAlert; @@ -900,6 +900,7 @@ private GraphQLSchema create() { .argument(relay.getConnectionFieldArguments()) .dataFetcher(environment -> { List filterByStops = null; + List filterByStations = null; List filterByRoutes = null; List filterByBikeRentalStations = null; List filterByBikeParks = null; @@ -949,6 +950,7 @@ private GraphQLSchema create() { filterByTransportModes, filterByPlaceTypes, filterByStops, + filterByStations, filterByRoutes, filterByBikeRentalStations, GqlUtil.getTransitService(environment) diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/TransmodelRequestContext.java b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelRequestContext.java similarity index 95% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/TransmodelRequestContext.java rename to src/main/java/org/opentripplanner/apis/transmodel/TransmodelRequestContext.java index 66fae073975..ca4cdd62ee9 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/TransmodelRequestContext.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelRequestContext.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi; +package org.opentripplanner.apis.transmodel; import org.opentripplanner.routing.api.RoutingService; import org.opentripplanner.standalone.api.OtpServerRequestContext; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/FilterMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/FilterMapper.java similarity index 93% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/FilterMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/FilterMapper.java index c2282c46e4b..9141ce4c104 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/FilterMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/FilterMapper.java @@ -1,15 +1,15 @@ -package org.opentripplanner.ext.transmodelapi.mapping; +package org.opentripplanner.apis.transmodel.mapping; -import static org.opentripplanner.ext.transmodelapi.mapping.TransitIdMapper.mapIDsToDomainNullSafe; +import static org.opentripplanner.apis.transmodel.mapping.TransitIdMapper.mapIDsToDomainNullSafe; import graphql.schema.DataFetchingEnvironment; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; -import org.opentripplanner.ext.transmodelapi.model.TransmodelTransportSubmode; -import org.opentripplanner.ext.transmodelapi.support.DataFetcherDecorator; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.model.TransmodelTransportSubmode; +import org.opentripplanner.apis.transmodel.support.DataFetcherDecorator; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.request.filter.SelectRequest; import org.opentripplanner.routing.api.request.request.filter.TransitFilter; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/GenericLocationMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/GenericLocationMapper.java similarity index 93% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/GenericLocationMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/GenericLocationMapper.java index 860e014f5b4..2fb0ca876e3 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/GenericLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/GenericLocationMapper.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.mapping; +package org.opentripplanner.apis.transmodel.mapping; import java.util.Map; import org.opentripplanner.model.GenericLocation; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/GeometryMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/GeometryMapper.java similarity index 87% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/GeometryMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/GeometryMapper.java index e69fd164c76..e2bf687e3b8 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/GeometryMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/GeometryMapper.java @@ -1,8 +1,8 @@ -package org.opentripplanner.ext.transmodelapi.mapping; +package org.opentripplanner.apis.transmodel.mapping; import java.util.ArrayList; import java.util.List; -import org.opentripplanner.ext.transmodelapi.model.util.EncodedPolylineBeanWithStops; +import org.opentripplanner.apis.transmodel.model.util.EncodedPolylineBeanWithStops; import org.opentripplanner.framework.geometry.EncodedPolyline; import org.opentripplanner.transit.model.network.TripPattern; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/OccupancyStatusMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/OccupancyStatusMapper.java similarity index 95% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/OccupancyStatusMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/OccupancyStatusMapper.java index 978f8d46c2c..f764ca903bc 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/OccupancyStatusMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/OccupancyStatusMapper.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.mapping; +package org.opentripplanner.apis.transmodel.mapping; import org.opentripplanner.transit.model.timetable.OccupancyStatus; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/PassThroughLocationMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/PassThroughLocationMapper.java similarity index 96% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/PassThroughLocationMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/PassThroughLocationMapper.java index 76bd1464e0f..951467e8727 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/PassThroughLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/PassThroughLocationMapper.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.mapping; +package org.opentripplanner.apis.transmodel.mapping; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toList; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/PlaceMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/PlaceMapper.java similarity index 85% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/PlaceMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/PlaceMapper.java index 93ca73cebfa..68f2ffe5199 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/PlaceMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/PlaceMapper.java @@ -1,8 +1,8 @@ -package org.opentripplanner.ext.transmodelapi.mapping; +package org.opentripplanner.apis.transmodel.mapping; import java.util.List; import java.util.stream.Collectors; -import org.opentripplanner.ext.transmodelapi.model.TransmodelPlaceType; +import org.opentripplanner.apis.transmodel.model.TransmodelPlaceType; import org.opentripplanner.routing.graphfinder.PlaceType; /** diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/PreferencesMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/PreferencesMapper.java new file mode 100644 index 00000000000..2f8ea15515e --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/PreferencesMapper.java @@ -0,0 +1,35 @@ +package org.opentripplanner.apis.transmodel.mapping; + +import static org.opentripplanner.apis.transmodel.mapping.preferences.BikePreferencesMapper.mapBikePreferences; +import static org.opentripplanner.apis.transmodel.mapping.preferences.CarPreferencesMapper.mapCarPreferences; +import static org.opentripplanner.apis.transmodel.mapping.preferences.ItineraryFilterPreferencesMapper.mapItineraryFilterPreferences; +import static org.opentripplanner.apis.transmodel.mapping.preferences.ItineraryFilterPreferencesMapper.mapRentalPreferences; +import static org.opentripplanner.apis.transmodel.mapping.preferences.StreetPreferencesMapper.mapStreetPreferences; +import static org.opentripplanner.apis.transmodel.mapping.preferences.TransferPreferencesMapper.mapTransferPreferences; +import static org.opentripplanner.apis.transmodel.mapping.preferences.TransitPreferencesMapper.mapTransitPreferences; +import static org.opentripplanner.apis.transmodel.mapping.preferences.WalkPreferencesMapper.mapWalkPreferences; + +import graphql.schema.DataFetchingEnvironment; +import org.opentripplanner.apis.transmodel.support.DataFetcherDecorator; +import org.opentripplanner.routing.api.request.preference.RoutingPreferences; + +class PreferencesMapper { + + static void mapPreferences( + DataFetchingEnvironment environment, + DataFetcherDecorator callWith, + RoutingPreferences.Builder preferences + ) { + preferences.withStreet(street -> mapStreetPreferences(street, environment, preferences.street()) + ); + preferences.withWalk(walk -> mapWalkPreferences(walk, callWith)); + preferences.withBike(bike -> mapBikePreferences(bike, callWith)); + preferences.withCar(car -> mapCarPreferences(car, callWith)); + preferences.withTransfer(transfer -> mapTransferPreferences(transfer, environment, callWith)); + preferences.withTransit(transit -> mapTransitPreferences(transit, environment, callWith)); + preferences.withItineraryFilter(itineraryFilter -> + mapItineraryFilterPreferences(itineraryFilter, environment, callWith) + ); + preferences.withRental(rental -> mapRentalPreferences(rental, environment, callWith)); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/RequestModesMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/RequestModesMapper.java similarity index 94% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/RequestModesMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/RequestModesMapper.java index 78bbebe34fb..d02e251f4e2 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/RequestModesMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/RequestModesMapper.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.mapping; +package org.opentripplanner.apis.transmodel.mapping; import java.util.Map; import org.opentripplanner.routing.api.request.RequestModes; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/SelectRequestMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/SelectRequestMapper.java similarity index 89% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/SelectRequestMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/SelectRequestMapper.java index f4699ed7394..3d5de9bf9d0 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/SelectRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/SelectRequestMapper.java @@ -1,11 +1,11 @@ -package org.opentripplanner.ext.transmodelapi.mapping; +package org.opentripplanner.apis.transmodel.mapping; -import static org.opentripplanner.ext.transmodelapi.mapping.TransitIdMapper.mapIDsToDomainNullSafe; +import static org.opentripplanner.apis.transmodel.mapping.TransitIdMapper.mapIDsToDomainNullSafe; import java.util.ArrayList; import java.util.List; import java.util.Map; -import org.opentripplanner.ext.transmodelapi.model.TransmodelTransportSubmode; +import org.opentripplanner.apis.transmodel.model.TransmodelTransportSubmode; import org.opentripplanner.routing.api.request.request.filter.SelectRequest; import org.opentripplanner.transit.model.basic.MainAndSubMode; import org.opentripplanner.transit.model.basic.SubMode; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/SeverityMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/SeverityMapper.java similarity index 94% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/SeverityMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/SeverityMapper.java index 2bb1698c091..782bd4b1ae3 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/SeverityMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/SeverityMapper.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.mapping; +package org.opentripplanner.apis.transmodel.mapping; import org.opentripplanner.routing.alertpatch.AlertSeverity; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/TransitIdMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/TransitIdMapper.java similarity index 98% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/TransitIdMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/TransitIdMapper.java index 52d7e0b0aba..1554a8c9d7c 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/TransitIdMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/TransitIdMapper.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.mapping; +package org.opentripplanner.apis.transmodel.mapping; import java.util.Collection; import java.util.List; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java similarity index 92% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java index d3dd06bb945..f27798c295a 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java @@ -1,6 +1,6 @@ -package org.opentripplanner.ext.transmodelapi.mapping; +package org.opentripplanner.apis.transmodel.mapping; -import static org.opentripplanner.ext.transmodelapi.mapping.TransitIdMapper.mapIDsToDomainNullSafe; +import static org.opentripplanner.apis.transmodel.mapping.TransitIdMapper.mapIDsToDomainNullSafe; import graphql.schema.DataFetchingEnvironment; import java.time.Duration; @@ -11,9 +11,9 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import org.opentripplanner.ext.transmodelapi.TransmodelRequestContext; -import org.opentripplanner.ext.transmodelapi.support.DataFetcherDecorator; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.TransmodelRequestContext; +import org.opentripplanner.apis.transmodel.support.DataFetcherDecorator; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.transit.model.framework.FeedScopedId; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/ViaLocationMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java similarity index 94% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/ViaLocationMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java index fffeae5795c..af39338fb98 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/ViaLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.mapping; +package org.opentripplanner.apis.transmodel.mapping; import static org.opentripplanner.routing.api.response.InputField.INTERMEDIATE_PLACE; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/ViaRequestMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java similarity index 95% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/ViaRequestMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java index e2b52655360..0781fbe34a6 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/ViaRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java @@ -1,11 +1,11 @@ -package org.opentripplanner.ext.transmodelapi.mapping; +package org.opentripplanner.apis.transmodel.mapping; import graphql.schema.DataFetchingEnvironment; import java.time.Instant; import java.util.Collections; import java.util.List; import java.util.Map; -import org.opentripplanner.ext.transmodelapi.TransmodelRequestContext; +import org.opentripplanner.apis.transmodel.TransmodelRequestContext; import org.opentripplanner.framework.graphql.GraphQLUtils; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.RouteViaRequest; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/ViaSegmentMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaSegmentMapper.java similarity index 93% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/ViaSegmentMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaSegmentMapper.java index c33045c3548..c86967667d1 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/ViaSegmentMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaSegmentMapper.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.mapping; +package org.opentripplanner.apis.transmodel.mapping; import java.util.List; import java.util.Map; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/BikePreferencesMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapper.java similarity index 91% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/BikePreferencesMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapper.java index a726309dc24..d56dc09c251 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/BikePreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapper.java @@ -1,6 +1,6 @@ -package org.opentripplanner.ext.transmodelapi.mapping.preferences; +package org.opentripplanner.apis.transmodel.mapping.preferences; -import org.opentripplanner.ext.transmodelapi.support.DataFetcherDecorator; +import org.opentripplanner.apis.transmodel.support.DataFetcherDecorator; import org.opentripplanner.routing.api.request.preference.BikePreferences; import org.opentripplanner.routing.core.BicycleOptimizeType; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/CarPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/CarPreferencesMapper.java similarity index 84% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/CarPreferencesMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/CarPreferencesMapper.java index f962016ed5f..d6ba995f53c 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/CarPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/CarPreferencesMapper.java @@ -1,6 +1,6 @@ -package org.opentripplanner.ext.transmodelapi.mapping.preferences; +package org.opentripplanner.apis.transmodel.mapping.preferences; -import org.opentripplanner.ext.transmodelapi.support.DataFetcherDecorator; +import org.opentripplanner.apis.transmodel.support.DataFetcherDecorator; import org.opentripplanner.routing.api.request.preference.CarPreferences; public class CarPreferencesMapper { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/ItineraryFilterPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/ItineraryFilterPreferencesMapper.java similarity index 82% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/ItineraryFilterPreferencesMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/ItineraryFilterPreferencesMapper.java index 25335b231e9..1f0b7ac3925 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/ItineraryFilterPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/ItineraryFilterPreferencesMapper.java @@ -1,8 +1,8 @@ -package org.opentripplanner.ext.transmodelapi.mapping.preferences; +package org.opentripplanner.apis.transmodel.mapping.preferences; import graphql.schema.DataFetchingEnvironment; -import org.opentripplanner.ext.transmodelapi.model.plan.ItineraryFiltersInputType; -import org.opentripplanner.ext.transmodelapi.support.DataFetcherDecorator; +import org.opentripplanner.apis.transmodel.model.plan.ItineraryFiltersInputType; +import org.opentripplanner.apis.transmodel.support.DataFetcherDecorator; import org.opentripplanner.routing.api.request.preference.ItineraryFilterDebugProfile; import org.opentripplanner.routing.api.request.preference.ItineraryFilterPreferences; import org.opentripplanner.routing.api.request.preference.VehicleRentalPreferences; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/StreetPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/StreetPreferencesMapper.java similarity index 78% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/StreetPreferencesMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/StreetPreferencesMapper.java index 99c6857bd61..78aaff13846 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/StreetPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/StreetPreferencesMapper.java @@ -1,9 +1,9 @@ -package org.opentripplanner.ext.transmodelapi.mapping.preferences; +package org.opentripplanner.apis.transmodel.mapping.preferences; import graphql.schema.DataFetchingEnvironment; -import org.opentripplanner.ext.transmodelapi.model.framework.PenaltyForStreetModeType; -import org.opentripplanner.ext.transmodelapi.model.framework.StreetModeDurationInputType; -import org.opentripplanner.ext.transmodelapi.model.plan.TripQuery; +import org.opentripplanner.apis.transmodel.model.framework.PenaltyForStreetModeType; +import org.opentripplanner.apis.transmodel.model.framework.StreetModeDurationInputType; +import org.opentripplanner.apis.transmodel.model.plan.TripQuery; import org.opentripplanner.routing.api.request.preference.StreetPreferences; public class StreetPreferencesMapper { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/TransferPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/TransferPreferencesMapper.java similarity index 85% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/TransferPreferencesMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/TransferPreferencesMapper.java index 896edc8eb47..4bb8bbbcbec 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/TransferPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/TransferPreferencesMapper.java @@ -1,7 +1,7 @@ -package org.opentripplanner.ext.transmodelapi.mapping.preferences; +package org.opentripplanner.apis.transmodel.mapping.preferences; import graphql.schema.DataFetchingEnvironment; -import org.opentripplanner.ext.transmodelapi.support.DataFetcherDecorator; +import org.opentripplanner.apis.transmodel.support.DataFetcherDecorator; import org.opentripplanner.routing.api.request.preference.TransferPreferences; public class TransferPreferencesMapper { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/TransitPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/TransitPreferencesMapper.java similarity index 81% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/TransitPreferencesMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/TransitPreferencesMapper.java index d7fe4e254bc..caa8ebf7715 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/TransitPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/TransitPreferencesMapper.java @@ -1,8 +1,8 @@ -package org.opentripplanner.ext.transmodelapi.mapping.preferences; +package org.opentripplanner.apis.transmodel.mapping.preferences; import graphql.schema.DataFetchingEnvironment; -import org.opentripplanner.ext.transmodelapi.model.TransportModeSlack; -import org.opentripplanner.ext.transmodelapi.support.DataFetcherDecorator; +import org.opentripplanner.apis.transmodel.model.TransportModeSlack; +import org.opentripplanner.apis.transmodel.support.DataFetcherDecorator; import org.opentripplanner.routing.api.request.preference.TransitPreferences; public class TransitPreferencesMapper { @@ -33,6 +33,10 @@ public static void mapTransitPreferences( callWith.argument("ignoreRealtimeUpdates", transit::setIgnoreRealtimeUpdates); callWith.argument("includePlannedCancellations", transit::setIncludePlannedCancellations); callWith.argument("includeRealtimeCancellations", transit::setIncludeRealtimeCancellations); + callWith.argument( + "relaxTransitPriorityGroup", + transit::withTransitGroupPriorityGeneralizedCostSlack + ); callWith.argument( "relaxTransitSearchGeneralizedCostAtDestination", (Double value) -> transit.withRaptor(it -> it.withRelaxGeneralizedCostAtDestination(value)) diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/WalkPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/WalkPreferencesMapper.java similarity index 75% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/WalkPreferencesMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/WalkPreferencesMapper.java index 6319eb7ded4..bc8f6350ac9 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/WalkPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/WalkPreferencesMapper.java @@ -1,6 +1,6 @@ -package org.opentripplanner.ext.transmodelapi.mapping.preferences; +package org.opentripplanner.apis.transmodel.mapping.preferences; -import org.opentripplanner.ext.transmodelapi.support.DataFetcherDecorator; +import org.opentripplanner.apis.transmodel.support.DataFetcherDecorator; import org.opentripplanner.routing.api.request.preference.WalkPreferences; public class WalkPreferencesMapper { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/DefaultRouteRequestType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/DefaultRouteRequestType.java similarity index 98% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/DefaultRouteRequestType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/DefaultRouteRequestType.java index 0d52fb280d9..41f77669859 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/DefaultRouteRequestType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/DefaultRouteRequestType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model; +package org.opentripplanner.apis.transmodel.model; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; @@ -220,7 +220,7 @@ private GraphQLObjectType createGraphQLType() { .name("bikeParkTime") .description("Time to park a bike.") .type(Scalars.GraphQLInt) - .dataFetcher(env -> preferences.bike().parkTime()) + .dataFetcher(env -> (int) preferences.bike().parking().parkTime().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().parkCost()) + .dataFetcher(env -> preferences.bike().parking().parkCost().toSeconds()) .build() ) .field( @@ -394,7 +394,7 @@ private GraphQLObjectType createGraphQLType() { GraphQLFieldDefinition .newFieldDefinition() .name("ignoreRealTimeUpdates") - .description("When true, realtime updates are ignored during this search.") + .description("When true, real-time updates are ignored during this search.") .type(Scalars.GraphQLBoolean) .dataFetcher(env -> preferences.transit().ignoreRealtimeUpdates()) .build() diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java b/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java similarity index 99% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java index 2b6942d7b8a..70173062f8f 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model; +package org.opentripplanner.apis.transmodel.model; import graphql.schema.GraphQLEnumType; import java.util.Arrays; @@ -49,6 +49,7 @@ public class EnumTypes { .value("noFilter", AlternativeLegsFilter.NO_FILTER) .value("sameAuthority", AlternativeLegsFilter.SAME_AGENCY) .value("sameMode", AlternativeLegsFilter.SAME_MODE) + .value("sameSubmode", AlternativeLegsFilter.SAME_SUBMODE, "Must match both subMode and mode") .value("sameLine", AlternativeLegsFilter.SAME_ROUTE) .build(); diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/PlanResponse.java b/src/main/java/org/opentripplanner/apis/transmodel/model/PlanResponse.java similarity index 88% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/PlanResponse.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/PlanResponse.java index 89456fd6f6a..127952be88c 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/PlanResponse.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/PlanResponse.java @@ -1,10 +1,10 @@ -package org.opentripplanner.ext.transmodelapi.model; +package org.opentripplanner.apis.transmodel.model; import java.util.ArrayList; import java.util.List; import org.opentripplanner.api.resource.DebugOutput; import org.opentripplanner.model.plan.TripPlan; -import org.opentripplanner.model.plan.pagecursor.PageCursor; +import org.opentripplanner.model.plan.paging.cursor.PageCursor; import org.opentripplanner.routing.api.response.RoutingError; import org.opentripplanner.routing.api.response.TripSearchMetadata; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/TransmodelPlaceType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/TransmodelPlaceType.java similarity index 65% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/TransmodelPlaceType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/TransmodelPlaceType.java index fcd7a8d27e8..3abb54eccd8 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/TransmodelPlaceType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/TransmodelPlaceType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model; +package org.opentripplanner.apis.transmodel.model; public enum TransmodelPlaceType { QUAY, diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/TransmodelStopPlaceType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/TransmodelStopPlaceType.java similarity index 92% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/TransmodelStopPlaceType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/TransmodelStopPlaceType.java index 09b123076b6..7c2be97a2fa 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/TransmodelStopPlaceType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/TransmodelStopPlaceType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model; +package org.opentripplanner.apis.transmodel.model; public enum TransmodelStopPlaceType { ONSTREET_BUS("onstreetBus"), diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/TransmodelTransportSubmode.java b/src/main/java/org/opentripplanner/apis/transmodel/model/TransmodelTransportSubmode.java similarity index 98% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/TransmodelTransportSubmode.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/TransmodelTransportSubmode.java index 7a41f1717fe..e7b544b4e0f 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/TransmodelTransportSubmode.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/TransmodelTransportSubmode.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model; +package org.opentripplanner.apis.transmodel.model; import java.util.Arrays; import org.opentripplanner.transit.model.basic.SubMode; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/TransportModeSlack.java b/src/main/java/org/opentripplanner/apis/transmodel/model/TransportModeSlack.java similarity index 97% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/TransportModeSlack.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/TransportModeSlack.java index c4872d59195..ed26711de88 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/TransportModeSlack.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/TransportModeSlack.java @@ -1,8 +1,8 @@ -package org.opentripplanner.ext.transmodelapi.model; +package org.opentripplanner.apis.transmodel.model; import static graphql.schema.GraphQLInputObjectField.newInputObjectField; import static graphql.schema.GraphQLNonNull.nonNull; -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.TRANSPORT_MODE; +import static org.opentripplanner.apis.transmodel.model.EnumTypes.TRANSPORT_MODE; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/TripTimeShortHelper.java b/src/main/java/org/opentripplanner/apis/transmodel/model/TripTimeShortHelper.java similarity index 98% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/TripTimeShortHelper.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/TripTimeShortHelper.java index e3289bb4d72..2f2b38785bf 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/TripTimeShortHelper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/TripTimeShortHelper.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model; +package org.opentripplanner.apis.transmodel.model; import java.time.Instant; import java.time.LocalDate; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/AuthorityType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/AuthorityType.java similarity index 93% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/AuthorityType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/framework/AuthorityType.java index c92606a5dbe..27d20c8e423 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/AuthorityType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/AuthorityType.java @@ -1,6 +1,6 @@ -package org.opentripplanner.ext.transmodelapi.model.framework; +package org.opentripplanner.apis.transmodel.model.framework; -import static org.opentripplanner.ext.transmodelapi.support.GqlUtil.getTransitService; +import static org.opentripplanner.apis.transmodel.support.GqlUtil.getTransitService; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; @@ -10,7 +10,7 @@ import graphql.schema.GraphQLOutputType; import java.util.Objects; import java.util.stream.Collectors; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.transit.model.organization.Agency; public class AuthorityType { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/BrandingType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/BrandingType.java similarity index 94% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/BrandingType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/framework/BrandingType.java index 34d1e11b9af..59406f0a06f 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/BrandingType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/BrandingType.java @@ -1,9 +1,9 @@ -package org.opentripplanner.ext.transmodelapi.model.framework; +package org.opentripplanner.apis.transmodel.model.framework; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLObjectType; -import org.opentripplanner.ext.transmodelapi.mapping.TransitIdMapper; +import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; import org.opentripplanner.transit.model.organization.Branding; public class BrandingType { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/CoordinateInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/CoordinateInputType.java similarity index 93% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/CoordinateInputType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/framework/CoordinateInputType.java index 20d244b6851..32ed3b7927e 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/CoordinateInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/CoordinateInputType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.framework; +package org.opentripplanner.apis.transmodel.model.framework; import graphql.Scalars; import graphql.schema.GraphQLInputObjectField; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/InfoLinkType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/InfoLinkType.java similarity index 94% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/InfoLinkType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/framework/InfoLinkType.java index 4d0a133bc27..6541b48a44d 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/InfoLinkType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/InfoLinkType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.framework; +package org.opentripplanner.apis.transmodel.model.framework; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/LocationInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/LocationInputType.java similarity index 96% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/LocationInputType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/framework/LocationInputType.java index 9ab992c416b..e64c67d9910 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/LocationInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/LocationInputType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.framework; +package org.opentripplanner.apis.transmodel.model.framework; import graphql.Scalars; import graphql.schema.GraphQLInputObjectField; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/MultilingualStringType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/MultilingualStringType.java similarity index 94% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/MultilingualStringType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/framework/MultilingualStringType.java index 33f48669d80..9bcaab1b4e5 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/MultilingualStringType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/MultilingualStringType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.framework; +package org.opentripplanner.apis.transmodel.model.framework; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/NoticeType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/NoticeType.java similarity index 87% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/NoticeType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/framework/NoticeType.java index 8291a08e1da..15ac3fd2356 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/NoticeType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/NoticeType.java @@ -1,9 +1,9 @@ -package org.opentripplanner.ext.transmodelapi.model.framework; +package org.opentripplanner.apis.transmodel.model.framework; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLObjectType; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.transit.model.basic.Notice; public class NoticeType { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/OperatorType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/OperatorType.java similarity index 95% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/OperatorType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/framework/OperatorType.java index 1c1ac9a83c9..d45319b9d9e 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/OperatorType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/OperatorType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.framework; +package org.opentripplanner.apis.transmodel.model.framework; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; @@ -8,7 +8,7 @@ import graphql.schema.GraphQLOutputType; import java.util.Objects; import java.util.stream.Collectors; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.support.GqlUtil; public class OperatorType { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/PassThroughPointInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/PassThroughPointInputType.java similarity index 95% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/PassThroughPointInputType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/framework/PassThroughPointInputType.java index 55873844a5f..3bc6d9d2f4c 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/PassThroughPointInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/PassThroughPointInputType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.framework; +package org.opentripplanner.apis.transmodel.model.framework; import graphql.Scalars; import graphql.schema.GraphQLInputObjectField; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/PenaltyForStreetModeType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/PenaltyForStreetModeType.java similarity index 93% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/PenaltyForStreetModeType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/framework/PenaltyForStreetModeType.java index 855dd5e98df..9fd91832117 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/PenaltyForStreetModeType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/PenaltyForStreetModeType.java @@ -1,6 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.framework; - -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.STREET_MODE; +package org.opentripplanner.apis.transmodel.model.framework; import graphql.Scalars; import graphql.language.ArrayValue; @@ -17,8 +15,9 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import org.opentripplanner.ext.transmodelapi.model.scalars.DoubleFunction; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.scalars.DoubleFunction; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.framework.lang.ObjectUtils; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.api.request.framework.TimeAndCostPenalty; @@ -56,7 +55,7 @@ public static GraphQLInputObjectType create(GqlUtil gqlUtil) { GraphQLInputObjectField .newInputObjectField() .name(FIELD_STREET_MODE) - .type(new GraphQLNonNull(STREET_MODE)) + .type(new GraphQLNonNull(EnumTypes.STREET_MODE)) .description( """ List of modes with the given penalty is applied to. A street-mode should not be listed @@ -98,7 +97,7 @@ public static GraphQLInputObjectType create(GqlUtil gqlUtil) { /** Return a list of access-egress penalties */ public static Value mapToGraphQLValue(TimeAndCostPenaltyForEnum accessEgressPenalty) { - List values = STREET_MODE + List values = EnumTypes.STREET_MODE .getValues() .stream() .map(gqlModeType -> { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/PointsOnLinkType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/PointsOnLinkType.java similarity index 95% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/PointsOnLinkType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/framework/PointsOnLinkType.java index 0311c677b17..9807d041613 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/PointsOnLinkType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/PointsOnLinkType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.framework; +package org.opentripplanner.apis.transmodel.model.framework; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/RentalVehicleTypeType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/RentalVehicleTypeType.java similarity index 96% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/RentalVehicleTypeType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/framework/RentalVehicleTypeType.java index d128116fe42..f0f4ab03419 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/RentalVehicleTypeType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/RentalVehicleTypeType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.framework; +package org.opentripplanner.apis.transmodel.model.framework; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/ServerInfoType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/ServerInfoType.java similarity index 98% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/ServerInfoType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/framework/ServerInfoType.java index 9997e86791e..ae6889ab033 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/ServerInfoType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/ServerInfoType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.framework; +package org.opentripplanner.apis.transmodel.model.framework; import static org.opentripplanner.framework.application.OtpFileNames.BUILD_CONFIG_FILENAME; import static org.opentripplanner.framework.application.OtpFileNames.OTP_CONFIG_FILENAME; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/StreetModeDurationInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/StreetModeDurationInputType.java similarity index 92% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/StreetModeDurationInputType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/framework/StreetModeDurationInputType.java index 80d168207a6..bf274688617 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/StreetModeDurationInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/StreetModeDurationInputType.java @@ -1,6 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.framework; - -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.STREET_MODE; +package org.opentripplanner.apis.transmodel.model.framework; import graphql.language.ArrayValue; import graphql.language.EnumValue; @@ -17,7 +15,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.api.request.framework.DurationForEnum; @@ -36,7 +35,7 @@ public static GraphQLInputObjectType create(GqlUtil gqlUtil) { GraphQLInputObjectField .newInputObjectField() .name(FIELD_STREET_MODE) - .type(new GraphQLNonNull(STREET_MODE)) + .type(new GraphQLNonNull(EnumTypes.STREET_MODE)) .build() ) .field( @@ -51,7 +50,7 @@ public static GraphQLInputObjectType create(GqlUtil gqlUtil) { public static Value mapDurationForStreetModeGraphQLValue( DurationForEnum durationForStreetMode ) { - List list = STREET_MODE + List list = EnumTypes.STREET_MODE .getValues() .stream() .map(gqlModeType -> { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/SystemNoticeType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/SystemNoticeType.java similarity index 95% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/SystemNoticeType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/framework/SystemNoticeType.java index 8b0952ba379..1cb237a1114 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/SystemNoticeType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/SystemNoticeType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.framework; +package org.opentripplanner.apis.transmodel.model.framework; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/ValidityPeriodType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/ValidityPeriodType.java similarity index 84% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/ValidityPeriodType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/framework/ValidityPeriodType.java index d353431b791..35ea9258322 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/ValidityPeriodType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/ValidityPeriodType.java @@ -1,9 +1,9 @@ -package org.opentripplanner.ext.transmodelapi.model.framework; +package org.opentripplanner.apis.transmodel.model.framework; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLObjectType; -import org.opentripplanner.ext.transmodelapi.model.siri.sx.ValidityPeriod; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.model.siri.sx.ValidityPeriod; +import org.opentripplanner.apis.transmodel.support.GqlUtil; public class ValidityPeriodType { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/network/DestinationDisplayType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/network/DestinationDisplayType.java similarity index 95% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/network/DestinationDisplayType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/network/DestinationDisplayType.java index d797ed7b80c..ed5f82e58b0 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/network/DestinationDisplayType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/network/DestinationDisplayType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.network; +package org.opentripplanner.apis.transmodel.model.network; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/network/GroupOfLinesType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/network/GroupOfLinesType.java similarity index 92% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/network/GroupOfLinesType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/network/GroupOfLinesType.java index c316168b2e9..0339269dbd8 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/network/GroupOfLinesType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/network/GroupOfLinesType.java @@ -1,12 +1,12 @@ -package org.opentripplanner.ext.transmodelapi.model.network; +package org.opentripplanner.apis.transmodel.model.network; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLList; import graphql.schema.GraphQLNonNull; import graphql.schema.GraphQLObjectType; -import org.opentripplanner.ext.transmodelapi.mapping.TransitIdMapper; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.transit.model.network.GroupOfRoutes; public class GroupOfLinesType { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/network/JourneyPatternType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/network/JourneyPatternType.java similarity index 96% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/network/JourneyPatternType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/network/JourneyPatternType.java index eaae58009bb..0743b02a3c4 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/network/JourneyPatternType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/network/JourneyPatternType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.network; +package org.opentripplanner.apis.transmodel.model.network; import gnu.trove.set.TIntSet; import graphql.Scalars; @@ -14,9 +14,9 @@ import java.util.Optional; import java.util.stream.Collectors; import org.locationtech.jts.geom.LineString; -import org.opentripplanner.ext.transmodelapi.mapping.GeometryMapper; -import org.opentripplanner.ext.transmodelapi.model.EnumTypes; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.mapping.GeometryMapper; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.framework.geometry.EncodedPolyline; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.TripTimes; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/network/LineType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/network/LineType.java similarity index 94% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/network/LineType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/network/LineType.java index 3b8ecf93525..32ea357a0aa 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/network/LineType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/network/LineType.java @@ -1,6 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.network; - -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.TRANSPORT_MODE; +package org.opentripplanner.apis.transmodel.model.network; import graphql.Scalars; import graphql.schema.DataFetchingEnvironment; @@ -12,10 +10,10 @@ import graphql.schema.GraphQLTypeReference; import java.util.Collection; import java.util.stream.Collectors; -import org.opentripplanner.ext.transmodelapi.mapping.TransitIdMapper; -import org.opentripplanner.ext.transmodelapi.model.EnumTypes; -import org.opentripplanner.ext.transmodelapi.model.TransmodelTransportSubmode; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.TransmodelTransportSubmode; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.network.TripPattern; @@ -97,7 +95,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("transportMode") - .type(TRANSPORT_MODE) + .type(EnumTypes.TRANSPORT_MODE) .dataFetcher(environment -> ((Route) environment.getSource()).getMode()) .build() ) diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/network/PresentationType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/network/PresentationType.java similarity index 93% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/network/PresentationType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/network/PresentationType.java index 4b2d45d81dd..0632ba10c1d 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/network/PresentationType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/network/PresentationType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.network; +package org.opentripplanner.apis.transmodel.model.network; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/network/StopToStopGeometryType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/network/StopToStopGeometryType.java similarity index 91% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/network/StopToStopGeometryType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/network/StopToStopGeometryType.java index 00edaf2d9f8..800eda04cb2 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/network/StopToStopGeometryType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/network/StopToStopGeometryType.java @@ -1,9 +1,9 @@ -package org.opentripplanner.ext.transmodelapi.model.network; +package org.opentripplanner.apis.transmodel.model.network; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; -import org.opentripplanner.ext.transmodelapi.model.util.EncodedPolylineBeanWithStops; +import org.opentripplanner.apis.transmodel.model.util.EncodedPolylineBeanWithStops; public class StopToStopGeometryType { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/BannedInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/BannedInputType.java similarity index 92% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/BannedInputType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/BannedInputType.java index 585638f8c31..ab78395a160 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/BannedInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/BannedInputType.java @@ -1,7 +1,7 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; +package org.opentripplanner.apis.transmodel.model.plan; import graphql.schema.GraphQLInputObjectType; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.support.GqlUtil; public class BannedInputType { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ElevationProfileStepType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ElevationProfileStepType.java similarity index 97% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ElevationProfileStepType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/ElevationProfileStepType.java index 828a806885c..bc1f1185463 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ElevationProfileStepType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ElevationProfileStepType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; +package org.opentripplanner.apis.transmodel.model.plan; import graphql.Scalars; import graphql.schema.DataFetchingEnvironment; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/FilterInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/FilterInputType.java similarity index 95% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/FilterInputType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/FilterInputType.java index d37a8949212..ce792ca3790 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/FilterInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/FilterInputType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; +package org.opentripplanner.apis.transmodel.model.plan; import graphql.schema.GraphQLInputObjectField; import graphql.schema.GraphQLInputObjectType; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ItineraryFiltersInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ItineraryFiltersInputType.java similarity index 96% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ItineraryFiltersInputType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/ItineraryFiltersInputType.java index 4f384eee4ff..b5d82af5138 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ItineraryFiltersInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ItineraryFiltersInputType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; +package org.opentripplanner.apis.transmodel.model.plan; import graphql.Scalars; import graphql.schema.DataFetchingEnvironment; @@ -7,10 +7,10 @@ import graphql.schema.GraphQLNonNull; import java.util.Map; import java.util.function.Consumer; -import org.opentripplanner.ext.transmodelapi.model.EnumTypes; -import org.opentripplanner.ext.transmodelapi.model.scalars.DoubleFunction; -import org.opentripplanner.ext.transmodelapi.support.DataFetcherDecorator; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.scalars.DoubleFunction; +import org.opentripplanner.apis.transmodel.support.DataFetcherDecorator; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.routing.algorithm.filterchain.api.TransitGeneralizedCostFilterParams; import org.opentripplanner.routing.api.request.preference.ItineraryFilterPreferences; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/JourneyWhiteListed.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/JourneyWhiteListed.java similarity index 90% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/JourneyWhiteListed.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/JourneyWhiteListed.java index f7068060900..6d747ccb164 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/JourneyWhiteListed.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/JourneyWhiteListed.java @@ -1,6 +1,6 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; +package org.opentripplanner.apis.transmodel.model.plan; -import static org.opentripplanner.ext.transmodelapi.support.GqlUtil.newIdListInputField; +import static org.opentripplanner.apis.transmodel.support.GqlUtil.newIdListInputField; import graphql.schema.DataFetchingEnvironment; import graphql.schema.GraphQLInputObjectType; @@ -9,8 +9,8 @@ import java.util.Map; import java.util.Set; import java.util.stream.Stream; -import org.opentripplanner.ext.transmodelapi.mapping.TransitIdMapper; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.model.TripTimeOnDate; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.Route; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/LegType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/LegType.java similarity index 93% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/LegType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/LegType.java index d233aaec3c2..be05d00e16d 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/LegType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/LegType.java @@ -1,7 +1,7 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; +package org.opentripplanner.apis.transmodel.model.plan; -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.ALTERNATIVE_LEGS_FILTER; -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.LEG_MODE; +import static org.opentripplanner.apis.transmodel.model.EnumTypes.ALTERNATIVE_LEGS_FILTER; +import static org.opentripplanner.apis.transmodel.model.EnumTypes.LEG_MODE; import graphql.Scalars; import graphql.scalars.ExtendedScalars; @@ -19,10 +19,10 @@ import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; -import org.opentripplanner.ext.transmodelapi.model.EnumTypes; -import org.opentripplanner.ext.transmodelapi.model.TransmodelTransportSubmode; -import org.opentripplanner.ext.transmodelapi.model.TripTimeShortHelper; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.TransmodelTransportSubmode; +import org.opentripplanner.apis.transmodel.model.TripTimeShortHelper; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.framework.geometry.EncodedPolyline; import org.opentripplanner.model.plan.Leg; import org.opentripplanner.model.plan.StopArrival; @@ -30,7 +30,6 @@ import org.opentripplanner.model.plan.TransitLeg; import org.opentripplanner.model.plan.legreference.LegReferenceSerializer; import org.opentripplanner.routing.alternativelegs.AlternativeLegs; -import org.opentripplanner.transit.model.timetable.TripIdAndServiceDate; public class LegType { @@ -75,7 +74,8 @@ public static GraphQLObjectType create( .name("aimedStartTime") .description("The aimed date and time this leg starts.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) - .dataFetcher(env -> // startTime is already adjusted for realtime - need to subtract delay to get aimed time + .dataFetcher(env -> + // startTime is already adjusted for real-time - need to subtract delay to get aimed time leg(env) .getStartTime() .minusSeconds(leg(env).getDepartureDelay()) @@ -88,7 +88,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("expectedStartTime") - .description("The expected, realtime adjusted date and time this leg starts.") + .description("The expected, real-time adjusted date and time this leg starts.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) .dataFetcher(env -> leg(env).getStartTime().toInstant().toEpochMilli()) .build() @@ -99,7 +99,7 @@ public static GraphQLObjectType create( .name("aimedEndTime") .description("The aimed date and time this leg ends.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) - .dataFetcher(env -> // endTime is already adjusted for realtime - need to subtract delay to get aimed time + .dataFetcher(env -> // endTime is already adjusted for real-time - need to subtract delay to get aimed time leg(env) .getEndTime() .minusSeconds(leg(env).getArrivalDelay()) @@ -112,7 +112,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("expectedEndTime") - .description("The expected, realtime adjusted date and time this leg ends.") + .description("The expected, real-time adjusted date and time this leg ends.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) .dataFetcher(env -> leg(env).getEndTime().toInstant().toEpochMilli()) .build() @@ -308,17 +308,7 @@ public static GraphQLObjectType create( .name("datedServiceJourney") .description("The dated service journey used for this leg.") .type(datedServiceJourneyType) - .dataFetcher(env -> { - var trip = leg(env).getTrip(); - if (trip == null) { - return null; - } - return GqlUtil - .getTransitService(env) - .getTripOnServiceDateForTripAndDay( - new TripIdAndServiceDate(leg(env).getTrip().getId(), leg(env).getServiceDate()) - ); - }) + .dataFetcher(env -> leg(env).getTripOnServiceDate()) .build() ) .field( diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ModeAndSubModeInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ModeAndSubModeInputType.java similarity index 85% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ModeAndSubModeInputType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/ModeAndSubModeInputType.java index 8d4a4520da3..d98108e6c7d 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ModeAndSubModeInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ModeAndSubModeInputType.java @@ -1,11 +1,11 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; +package org.opentripplanner.apis.transmodel.model.plan; -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.TRANSPORT_MODE; +import static org.opentripplanner.apis.transmodel.model.EnumTypes.TRANSPORT_MODE; import graphql.schema.GraphQLInputObjectField; import graphql.schema.GraphQLInputObjectType; import graphql.schema.GraphQLList; -import org.opentripplanner.ext.transmodelapi.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.EnumTypes; public class ModeAndSubModeInputType { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ModeInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ModeInputType.java similarity index 90% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ModeInputType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/ModeInputType.java index a5fcb51fb88..a5b7b533e94 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ModeInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ModeInputType.java @@ -1,10 +1,9 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; - -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.STREET_MODE; +package org.opentripplanner.apis.transmodel.model.plan; import graphql.schema.GraphQLInputObjectField; import graphql.schema.GraphQLInputObjectType; import graphql.schema.GraphQLList; +import org.opentripplanner.apis.transmodel.model.EnumTypes; class ModeInputType { @@ -25,7 +24,7 @@ class ModeInputType { "network the transit network (first-mile). If the element is not present or null," + "only transit that can be immediately boarded from the origin will be used." ) - .type(STREET_MODE) + .type(EnumTypes.STREET_MODE) .build() ) .field( @@ -37,7 +36,7 @@ class ModeInputType { "the destination (last-mile). If the element is not present or null," + "only transit that can immediately arrive at the origin will be used." ) - .type(STREET_MODE) + .type(EnumTypes.STREET_MODE) .build() ) .field( @@ -49,7 +48,7 @@ class ModeInputType { "without using the transit network. If the element is not present or null," + "direct travel without using transit will be disallowed." ) - .type(STREET_MODE) + .type(EnumTypes.STREET_MODE) .build() ) .field( diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/PathGuidanceType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PathGuidanceType.java similarity index 97% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/PathGuidanceType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/PathGuidanceType.java index ce1f8909b79..c52baa0be4c 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/PathGuidanceType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PathGuidanceType.java @@ -1,11 +1,11 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; +package org.opentripplanner.apis.transmodel.model.plan; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLList; import graphql.schema.GraphQLNonNull; import graphql.schema.GraphQLObjectType; -import org.opentripplanner.ext.transmodelapi.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.EnumTypes; import org.opentripplanner.framework.graphql.GraphQLUtils; import org.opentripplanner.model.plan.WalkStep; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/PlanPlaceType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PlanPlaceType.java similarity index 96% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/PlanPlaceType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/PlanPlaceType.java index 195046a06fd..a39a25ae93d 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/PlanPlaceType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PlanPlaceType.java @@ -1,13 +1,12 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; +package org.opentripplanner.apis.transmodel.model.plan; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLNonNull; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; -import java.util.Locale; -import org.opentripplanner.ext.transmodelapi.model.EnumTypes; -import org.opentripplanner.ext.transmodelapi.model.scalars.GeoJSONCoordinatesScalar; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.scalars.GeoJSONCoordinatesScalar; import org.opentripplanner.framework.graphql.GraphQLUtils; import org.opentripplanner.model.plan.Place; import org.opentripplanner.model.plan.VertexType; 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 new file mode 100644 index 00000000000..3bd3ed129ef --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/RelaxCostType.java @@ -0,0 +1,71 @@ +package org.opentripplanner.apis.transmodel.model.plan; + +import graphql.Scalars; +import graphql.language.FloatValue; +import graphql.language.IntValue; +import graphql.language.ObjectField; +import graphql.language.ObjectValue; +import graphql.schema.GraphQLInputObjectField; +import graphql.schema.GraphQLInputObjectType; +import graphql.schema.GraphQLList; +import graphql.schema.GraphQLNonNull; +import org.opentripplanner.routing.api.request.framework.CostLinearFunction; + +public class RelaxCostType { + + public static final String RATIO = "ratio"; + public static final String CONSTANT = "constant"; + + static final GraphQLInputObjectType INPUT_TYPE = GraphQLInputObjectType + .newInputObject() + .name("RelaxCostInput") + .description( + """ + A relax-cost is used to increase the limit when comparing one cost to another cost. + This is used to include more results into the result. A `ratio=2.0` means a path(itinerary) + 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. + """ + ) + .field( + GraphQLInputObjectField + .newInputObjectField() + .name(RATIO) + .description("The factor to multiply with the 'other cost'. Minimum value is 1.0.") + .defaultValueLiteral(FloatValue.of(1.0)) + .type(Scalars.GraphQLFloat) + .build() + ) + .field( + GraphQLInputObjectField + .newInputObjectField() + .name(CONSTANT) + .description( + "The constant value to add to the limit. Must be a positive number. The unit" + + " is cost-seconds." + ) + .defaultValueLiteral(IntValue.of(0)) + .type(new GraphQLList(new GraphQLNonNull(Scalars.GraphQLID))) + .build() + ) + .build(); + + public static ObjectValue valueOf(CostLinearFunction value) { + return ObjectValue + .newObjectValue() + .objectField( + ObjectField.newObjectField().name(RATIO).value(FloatValue.of(value.coefficient())).build() + ) + .objectField( + ObjectField + .newObjectField() + .name(CONSTANT) + .value(IntValue.of(value.constant().toSeconds())) + .build() + ) + .build(); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/RoutingErrorType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/RoutingErrorType.java similarity index 86% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/RoutingErrorType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/RoutingErrorType.java index 52079d15167..79052f17b69 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/RoutingErrorType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/RoutingErrorType.java @@ -1,7 +1,7 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; +package org.opentripplanner.apis.transmodel.model.plan; -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.INPUT_FIELD; -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.ROUTING_ERROR_CODE; +import static org.opentripplanner.apis.transmodel.model.EnumTypes.INPUT_FIELD; +import static org.opentripplanner.apis.transmodel.model.EnumTypes.ROUTING_ERROR_CODE; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/SelectInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/SelectInputType.java similarity index 93% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/SelectInputType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/SelectInputType.java index 4935d1a23ef..900e8c927fb 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/SelectInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/SelectInputType.java @@ -1,13 +1,10 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; - -import static org.opentripplanner.ext.transmodelapi.support.GqlUtil.newIdListInputField; +package org.opentripplanner.apis.transmodel.model.plan; import graphql.Scalars; import graphql.schema.GraphQLInputObjectField; import graphql.schema.GraphQLInputObjectType; import graphql.schema.GraphQLList; import graphql.schema.GraphQLNonNull; -import java.util.List; public class SelectInputType { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TriangleFactorsInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TriangleFactorsInputType.java similarity index 96% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TriangleFactorsInputType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/TriangleFactorsInputType.java index 4263ca37cc9..73c96f7f726 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TriangleFactorsInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TriangleFactorsInputType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; +package org.opentripplanner.apis.transmodel.model.plan; import graphql.Scalars; import graphql.schema.GraphQLInputObjectField; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripPatternType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternType.java similarity index 93% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripPatternType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternType.java index 4381557d472..31e33ae6e4d 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripPatternType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; +package org.opentripplanner.apis.transmodel.model.plan; import graphql.Scalars; import graphql.scalars.ExtendedScalars; @@ -8,7 +8,7 @@ import graphql.schema.GraphQLNonNull; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.model.plan.Itinerary; public class TripPatternType { @@ -50,7 +50,8 @@ public static GraphQLObjectType create( .name("aimedStartTime") .description("The aimed date and time the trip starts.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) - .dataFetcher(env -> // startTime is already adjusted for realtime - need to subtract delay to get aimed time + .dataFetcher(env -> + // startTime is already adjusted for real-time - need to subtract delay to get aimed time itinerary(env) .startTime() .minusSeconds(itinerary(env).departureDelay()) @@ -63,7 +64,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("expectedStartTime") - .description("The expected, realtime adjusted date and time the trip starts.") + .description("The expected, real-time adjusted date and time the trip starts.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) .dataFetcher(env -> itinerary(env).startTime().toInstant().toEpochMilli()) .build() @@ -74,7 +75,8 @@ public static GraphQLObjectType create( .name("aimedEndTime") .description("The aimed date and time the trip ends.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) - .dataFetcher(env -> // endTime is already adjusted for realtime - need to subtract delay to get aimed time + .dataFetcher(env -> + // endTime is already adjusted for real-time - need to subtract delay to get aimed time itinerary(env) .endTime() .minusSeconds(itinerary(env).arrivalDelay()) @@ -87,7 +89,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("expectedEndTime") - .description("The expected, realtime adjusted date and time the trip ends.") + .description("The expected, real-time adjusted date and time the trip ends.") .type(new GraphQLNonNull(gqlUtil.dateTimeScalar)) .dataFetcher(env -> itinerary(env).endTime().toInstant().toEpochMilli()) .build() diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java similarity index 90% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java index 829015c37d7..d06c5954444 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java @@ -1,8 +1,9 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; +package org.opentripplanner.apis.transmodel.model.plan; -import static org.opentripplanner.ext.transmodelapi.model.framework.StreetModeDurationInputType.mapDurationForStreetModeGraphQLValue; +import static org.opentripplanner.apis.transmodel.model.framework.StreetModeDurationInputType.mapDurationForStreetModeGraphQLValue; import graphql.Scalars; +import graphql.language.NullValue; import graphql.schema.GraphQLArgument; import graphql.schema.GraphQLEnumType; import graphql.schema.GraphQLFieldDefinition; @@ -10,14 +11,14 @@ import graphql.schema.GraphQLList; import graphql.schema.GraphQLNonNull; import graphql.schema.GraphQLOutputType; -import org.opentripplanner.ext.transmodelapi.TransmodelGraphQLPlanner; -import org.opentripplanner.ext.transmodelapi.model.DefaultRouteRequestType; -import org.opentripplanner.ext.transmodelapi.model.EnumTypes; -import org.opentripplanner.ext.transmodelapi.model.TransportModeSlack; -import org.opentripplanner.ext.transmodelapi.model.framework.LocationInputType; -import org.opentripplanner.ext.transmodelapi.model.framework.PassThroughPointInputType; -import org.opentripplanner.ext.transmodelapi.model.framework.PenaltyForStreetModeType; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.TransmodelGraphQLPlanner; +import org.opentripplanner.apis.transmodel.model.DefaultRouteRequestType; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.TransportModeSlack; +import org.opentripplanner.apis.transmodel.model.framework.LocationInputType; +import org.opentripplanner.apis.transmodel.model.framework.PassThroughPointInputType; +import org.opentripplanner.apis.transmodel.model.framework.PenaltyForStreetModeType; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; import org.opentripplanner.routing.core.BicycleOptimizeType; @@ -187,7 +188,7 @@ public static GraphQLFieldDefinition create( GraphQLArgument .newArgument() .name("ignoreRealtimeUpdates") - .description("When true, realtime updates are ignored during this search.") + .description("When true, real-time updates are ignored during this search.") .type(Scalars.GraphQLBoolean) .defaultValue(preferences.transit().ignoreRealtimeUpdates()) .build() @@ -273,6 +274,36 @@ public static GraphQLFieldDefinition create( .type(new GraphQLList(new GraphQLNonNull(FilterInputType.INPUT_TYPE))) .build() ) + .argument( + GraphQLArgument + .newArgument() + .name("relaxTransitPriorityGroup") + .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 + 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. + + 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 + 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. + + THIS IS STILL AN EXPERIMENTAL FEATURE - IT MAY CHANGE WITHOUT ANY NOTICE! + """.stripIndent() + ) + .type(RelaxCostType.INPUT_TYPE) + .defaultValueLiteral( + preferences.transit().relaxTransitPriorityGroup().isNormal() + ? NullValue.of() + : RelaxCostType.valueOf(preferences.transit().relaxTransitPriorityGroup()) + ) + .build() + ) .argument( GraphQLArgument .newArgument() @@ -492,6 +523,7 @@ public static GraphQLFieldDefinition create( GraphQLArgument .newArgument() .name("relaxTransitSearchGeneralizedCostAtDestination") + .deprecate("This is replaced by 'relaxTransitPriorityGroup'.") .description( """ Whether non-optimal transit paths at the destination should be returned. Let c be the diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripType.java similarity index 96% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripType.java index 85fc140e2cb..d9c3c0caa02 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; +package org.opentripplanner.apis.transmodel.model.plan; import graphql.Scalars; import graphql.scalars.ExtendedScalars; @@ -9,10 +9,10 @@ import graphql.schema.GraphQLObjectType; import java.util.stream.Collectors; import org.opentripplanner.api.mapping.PlannerErrorMapper; -import org.opentripplanner.ext.transmodelapi.model.PlanResponse; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.model.PlanResponse; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.framework.graphql.GraphQLUtils; -import org.opentripplanner.model.plan.pagecursor.PageCursor; +import org.opentripplanner.model.plan.paging.cursor.PageCursor; public class TripType { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ViaLocationInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java similarity index 92% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ViaLocationInputType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java index fe5a3cf19e4..6ce02240817 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ViaLocationInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java @@ -1,10 +1,10 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; +package org.opentripplanner.apis.transmodel.model.plan; import graphql.Scalars; import graphql.schema.GraphQLInputObjectField; import graphql.schema.GraphQLInputObjectType; -import org.opentripplanner.ext.transmodelapi.model.framework.CoordinateInputType; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.model.framework.CoordinateInputType; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.routing.api.request.ViaLocation; public class ViaLocationInputType { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ViaSegmentInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaSegmentInputType.java similarity index 92% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ViaSegmentInputType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaSegmentInputType.java index fd5337d750f..0d591a1357a 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ViaSegmentInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaSegmentInputType.java @@ -1,11 +1,10 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; - -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.STREET_MODE; +package org.opentripplanner.apis.transmodel.model.plan; import graphql.schema.GraphQLInputObjectField; import graphql.schema.GraphQLInputObjectType; import graphql.schema.GraphQLList; import graphql.schema.GraphQLNonNull; +import org.opentripplanner.apis.transmodel.model.EnumTypes; public class ViaSegmentInputType { @@ -26,7 +25,7 @@ public static GraphQLInputObjectType create() { "network the transit network (first-mile). If the element is not present or null," + "only transit that can be immediately boarded from the origin will be used." ) - .type(STREET_MODE) + .type(EnumTypes.STREET_MODE) .build() ) .field( @@ -38,7 +37,7 @@ public static GraphQLInputObjectType create() { "the destination (last-mile). If the element is not present or null," + "only transit that can immediately arrive at the origin will be used." ) - .type(STREET_MODE) + .type(EnumTypes.STREET_MODE) .build() ) .field( @@ -50,7 +49,7 @@ public static GraphQLInputObjectType create() { "without using the transit network. If the element is not present or null," + "direct travel without using transit will be disallowed." ) - .type(STREET_MODE) + .type(EnumTypes.STREET_MODE) .build() ) .build(); diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ViaTripQuery.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaTripQuery.java similarity index 94% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ViaTripQuery.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaTripQuery.java index da2b015ca97..8641dbc60c2 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ViaTripQuery.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaTripQuery.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; +package org.opentripplanner.apis.transmodel.model.plan; import graphql.Scalars; import graphql.schema.GraphQLArgument; @@ -7,11 +7,11 @@ import graphql.schema.GraphQLList; import graphql.schema.GraphQLNonNull; import graphql.schema.GraphQLOutputType; -import org.opentripplanner.ext.transmodelapi.TransmodelGraphQLPlanner; -import org.opentripplanner.ext.transmodelapi.model.DefaultRouteRequestType; -import org.opentripplanner.ext.transmodelapi.model.EnumTypes; -import org.opentripplanner.ext.transmodelapi.model.framework.LocationInputType; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.TransmodelGraphQLPlanner; +import org.opentripplanner.apis.transmodel.model.DefaultRouteRequestType; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.framework.LocationInputType; +import org.opentripplanner.apis.transmodel.support.GqlUtil; public class ViaTripQuery { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ViaTripType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaTripType.java similarity index 98% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ViaTripType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaTripType.java index 62e3447ce43..8b55222f3b8 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ViaTripType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaTripType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.plan; +package org.opentripplanner.apis.transmodel.model.plan; import graphql.Scalars; import graphql.schema.DataFetchingEnvironment; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/DateScalarFactory.java b/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/DateScalarFactory.java similarity index 97% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/DateScalarFactory.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/scalars/DateScalarFactory.java index de453447a0f..6d45018ed2a 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/DateScalarFactory.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/DateScalarFactory.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.scalars; +package org.opentripplanner.apis.transmodel.model.scalars; import graphql.language.StringValue; import graphql.schema.Coercing; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/DateTimeScalarFactory.java b/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/DateTimeScalarFactory.java similarity index 98% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/DateTimeScalarFactory.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/scalars/DateTimeScalarFactory.java index cd49e46e74a..706adc81704 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/DateTimeScalarFactory.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/DateTimeScalarFactory.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.scalars; +package org.opentripplanner.apis.transmodel.model.scalars; import graphql.language.StringValue; import graphql.schema.Coercing; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/DoubleFunction.java b/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/DoubleFunction.java similarity index 92% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/DoubleFunction.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/scalars/DoubleFunction.java index 3bf3980a146..c1f8bb0f3f8 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/DoubleFunction.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/DoubleFunction.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.scalars; +package org.opentripplanner.apis.transmodel.model.scalars; import java.time.Duration; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/DoubleFunctionFactory.java b/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/DoubleFunctionFactory.java similarity index 97% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/DoubleFunctionFactory.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/scalars/DoubleFunctionFactory.java index e6bc10fdc0d..fd45b001642 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/DoubleFunctionFactory.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/DoubleFunctionFactory.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.scalars; +package org.opentripplanner.apis.transmodel.model.scalars; import graphql.GraphQLContext; import graphql.execution.CoercedVariables; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/GeoJSONCoordinatesScalar.java b/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/GeoJSONCoordinatesScalar.java similarity index 98% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/GeoJSONCoordinatesScalar.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/scalars/GeoJSONCoordinatesScalar.java index 2d373a01d45..2cb44433d50 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/GeoJSONCoordinatesScalar.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/GeoJSONCoordinatesScalar.java @@ -13,7 +13,7 @@ * limitations under the Licence. */ -package org.opentripplanner.ext.transmodelapi.model.scalars; +package org.opentripplanner.apis.transmodel.model.scalars; import graphql.language.ArrayValue; import graphql.language.FloatValue; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/LocalTimeScalarFactory.java b/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/LocalTimeScalarFactory.java similarity index 96% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/LocalTimeScalarFactory.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/scalars/LocalTimeScalarFactory.java index 32877e129f1..1dbf2dce8e8 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/LocalTimeScalarFactory.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/LocalTimeScalarFactory.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.scalars; +package org.opentripplanner.apis.transmodel.model.scalars; import graphql.language.StringValue; import graphql.schema.Coercing; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/TimeScalarFactory.java b/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/TimeScalarFactory.java similarity index 97% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/TimeScalarFactory.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/scalars/TimeScalarFactory.java index 30f5c8236a1..318ceea034f 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/scalars/TimeScalarFactory.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/TimeScalarFactory.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.scalars; +package org.opentripplanner.apis.transmodel.model.scalars; import graphql.Scalars; import graphql.language.StringValue; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/et/EstimatedCallType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/siri/et/EstimatedCallType.java similarity index 97% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/et/EstimatedCallType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/siri/et/EstimatedCallType.java index 7a39aee6cca..0c47fe36ba8 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/et/EstimatedCallType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/siri/et/EstimatedCallType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.siri.et; +package org.opentripplanner.apis.transmodel.model.siri.et; import static org.opentripplanner.model.PickDrop.COORDINATE_WITH_DRIVER; @@ -15,9 +15,9 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; -import org.opentripplanner.ext.transmodelapi.mapping.OccupancyStatusMapper; -import org.opentripplanner.ext.transmodelapi.model.EnumTypes; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.mapping.OccupancyStatusMapper; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.model.TripTimeOnDate; import org.opentripplanner.routing.alertpatch.StopCondition; import org.opentripplanner.routing.alertpatch.TransitAlert; @@ -194,7 +194,7 @@ public static GraphQLObjectType create( .newFieldDefinition() .name("realtimeState") .type(new GraphQLNonNull(EnumTypes.REALTIME_STATE)) - .dataFetcher(environment -> ((TripTimeOnDate) environment.getSource()).getRealtimeState()) + .dataFetcher(environment -> ((TripTimeOnDate) environment.getSource()).getRealTimeState()) .build() ) .field( @@ -258,7 +258,7 @@ public static GraphQLObjectType create( .description( "Whether stop is cancelled. This means that either the " + "ServiceJourney has a planned cancellation, the ServiceJourney has been " + - "cancelled by realtime data, or this particular StopPoint has been " + + "cancelled by real-time data, or this particular StopPoint has been " + "cancelled. This also means that both boarding and alighting has been " + "cancelled." ) diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/AffectsType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/AffectsType.java similarity index 93% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/AffectsType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/AffectsType.java index 529e939bdac..4aded08e3e8 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/AffectsType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/AffectsType.java @@ -1,6 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.siri.sx; - -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.STOP_CONDITION_ENUM; +package org.opentripplanner.apis.transmodel.model.siri.sx; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; @@ -9,8 +7,9 @@ import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; import graphql.schema.GraphQLUnionType; -import org.opentripplanner.ext.transmodelapi.model.stop.StopPlaceType; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.stop.StopPlaceType; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.routing.alertpatch.EntitySelector; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.timetable.TripIdAndServiceDate; @@ -56,7 +55,9 @@ public static GraphQLOutputType create( GraphQLFieldDefinition .newFieldDefinition() .name("stopConditions") - .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(STOP_CONDITION_ENUM)))) + .type( + new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(EnumTypes.STOP_CONDITION_ENUM))) + ) .build() ) .build(); @@ -156,7 +157,9 @@ public static GraphQLOutputType create( GraphQLFieldDefinition .newFieldDefinition() .name("stopConditions") - .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(STOP_CONDITION_ENUM)))) + .type( + new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(EnumTypes.STOP_CONDITION_ENUM))) + ) .build() ) .build(); @@ -226,7 +229,9 @@ public static GraphQLOutputType create( GraphQLFieldDefinition .newFieldDefinition() .name("stopConditions") - .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(STOP_CONDITION_ENUM)))) + .type( + new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(EnumTypes.STOP_CONDITION_ENUM))) + ) .build() ) .build(); diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/PtSituationElementType.java similarity index 97% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/PtSituationElementType.java index 6cc72568aab..b5616c2d3fc 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/PtSituationElementType.java @@ -1,7 +1,7 @@ -package org.opentripplanner.ext.transmodelapi.model.siri.sx; +package org.opentripplanner.apis.transmodel.model.siri.sx; import static java.util.Collections.emptyList; -import static org.opentripplanner.ext.transmodelapi.mapping.SeverityMapper.getTransmodelSeverity; +import static org.opentripplanner.apis.transmodel.mapping.SeverityMapper.getTransmodelSeverity; import graphql.Scalars; import graphql.relay.Relay; @@ -16,9 +16,9 @@ import java.util.List; import java.util.Objects; import java.util.stream.Collectors; -import org.opentripplanner.ext.transmodelapi.model.EnumTypes; -import org.opentripplanner.ext.transmodelapi.model.stop.MonoOrMultiModalStation; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.stop.MonoOrMultiModalStation; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.i18n.TranslatedString; import org.opentripplanner.routing.alertpatch.AlertUrl; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/ValidityPeriod.java b/src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/ValidityPeriod.java similarity index 50% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/ValidityPeriod.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/ValidityPeriod.java index a154a22da4c..216be5a3867 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/ValidityPeriod.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/ValidityPeriod.java @@ -1,3 +1,3 @@ -package org.opentripplanner.ext.transmodelapi.model.siri.sx; +package org.opentripplanner.apis.transmodel.model.siri.sx; public record ValidityPeriod(Long startTime, Long endTime) {} diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/BikeParkType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/BikeParkType.java similarity index 95% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/BikeParkType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/stop/BikeParkType.java index 6805253dd3d..470111513bf 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/BikeParkType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/BikeParkType.java @@ -1,11 +1,11 @@ -package org.opentripplanner.ext.transmodelapi.model.stop; +package org.opentripplanner.apis.transmodel.model.stop; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLInterfaceType; import graphql.schema.GraphQLNonNull; import graphql.schema.GraphQLObjectType; -import org.opentripplanner.ext.transmodelapi.mapping.TransitIdMapper; +import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; import org.opentripplanner.routing.vehicle_parking.VehicleParking; public class BikeParkType { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/BikeRentalStationType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/BikeRentalStationType.java similarity index 98% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/BikeRentalStationType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/stop/BikeRentalStationType.java index 2732f68a1af..3f2988a95d7 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/BikeRentalStationType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/BikeRentalStationType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.stop; +package org.opentripplanner.apis.transmodel.model.stop; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/MonoOrMultiModalStation.java b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/MonoOrMultiModalStation.java similarity index 98% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/MonoOrMultiModalStation.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/stop/MonoOrMultiModalStation.java index 25fa78e4061..3055c1a0115 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/MonoOrMultiModalStation.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/MonoOrMultiModalStation.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.stop; +package org.opentripplanner.apis.transmodel.model.stop; import java.time.ZoneId; import java.util.Collection; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/PlaceAtDistanceType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/PlaceAtDistanceType.java similarity index 97% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/PlaceAtDistanceType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/stop/PlaceAtDistanceType.java index 0848f17202e..809164e9cd2 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/PlaceAtDistanceType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/PlaceAtDistanceType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.stop; +package org.opentripplanner.apis.transmodel.model.stop; import graphql.Scalars; import graphql.relay.Relay; @@ -14,7 +14,7 @@ import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.opentripplanner.ext.transmodelapi.model.TransmodelPlaceType; +import org.opentripplanner.apis.transmodel.model.TransmodelPlaceType; import org.opentripplanner.routing.graphfinder.PlaceAtDistance; import org.opentripplanner.transit.model.site.MultiModalStation; import org.opentripplanner.transit.model.site.RegularStop; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/PlaceInterfaceType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/PlaceInterfaceType.java similarity index 97% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/PlaceInterfaceType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/stop/PlaceInterfaceType.java index 9d757696bac..0b6238c119d 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/PlaceInterfaceType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/PlaceInterfaceType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.stop; +package org.opentripplanner.apis.transmodel.model.stop; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/PlaceType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/PlaceType.java similarity index 61% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/PlaceType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/stop/PlaceType.java index 8f264b0ae40..a00bb0f9e16 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/PlaceType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/PlaceType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.stop; +package org.opentripplanner.apis.transmodel.model.stop; public enum PlaceType { STOP, diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayAtDistanceType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayAtDistanceType.java similarity index 92% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayAtDistanceType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayAtDistanceType.java index 3a11c6ae5ba..51f62141e33 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayAtDistanceType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayAtDistanceType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.stop; +package org.opentripplanner.apis.transmodel.model.stop; import graphql.Scalars; import graphql.relay.Relay; @@ -6,7 +6,7 @@ import graphql.schema.GraphQLNonNull; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; -import org.opentripplanner.ext.transmodelapi.mapping.TransitIdMapper; +import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; import org.opentripplanner.routing.graphfinder.NearbyStop; import org.opentripplanner.transit.model.framework.AbstractTransitEntity; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayType.java similarity index 96% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayType.java index 203efb10452..b80efab77b5 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayType.java @@ -1,6 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.stop; - -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.TRANSPORT_MODE; +package org.opentripplanner.apis.transmodel.model.stop; import graphql.Scalars; import graphql.schema.GraphQLArgument; @@ -17,10 +15,10 @@ import java.util.Collection; import java.util.Objects; import java.util.Optional; -import org.opentripplanner.ext.transmodelapi.model.EnumTypes; -import org.opentripplanner.ext.transmodelapi.model.plan.JourneyWhiteListed; -import org.opentripplanner.ext.transmodelapi.model.scalars.GeoJSONCoordinatesScalar; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.plan.JourneyWhiteListed; +import org.opentripplanner.apis.transmodel.model.scalars.GeoJSONCoordinatesScalar; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.framework.graphql.GraphQLUtils; import org.opentripplanner.model.TripTimeOnDate; import org.opentripplanner.routing.stoptimes.ArrivalDeparture; @@ -292,14 +290,14 @@ public static GraphQLObjectType create( .newArgument() .name("whiteListedModes") .description("Only show estimated calls for selected modes.") - .type(GraphQLList.list(TRANSPORT_MODE)) + .type(GraphQLList.list(EnumTypes.TRANSPORT_MODE)) .build() ) .argument( GraphQLArgument .newArgument() .name("includeCancelledTrips") - .description("Indicates that realtime-cancelled trips should also be included.") + .description("Indicates that real-time-cancelled trips should also be included.") .type(Scalars.GraphQLBoolean) .defaultValue(false) .build() diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/RentalVehicleType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/RentalVehicleType.java similarity index 97% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/RentalVehicleType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/stop/RentalVehicleType.java index 7042099da32..e44639e09e7 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/RentalVehicleType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/RentalVehicleType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.stop; +package org.opentripplanner.apis.transmodel.model.stop; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/StopPlaceType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java similarity index 95% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/StopPlaceType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java index 5a803138eb1..accaf1e35fb 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/StopPlaceType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java @@ -1,8 +1,6 @@ -package org.opentripplanner.ext.transmodelapi.model.stop; +package org.opentripplanner.apis.transmodel.model.stop; import static java.lang.Boolean.TRUE; -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.TRANSPORT_MODE; -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.TRANSPORT_SUBMODE; import graphql.Scalars; import graphql.schema.DataFetchingEnvironment; @@ -20,7 +18,6 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -28,13 +25,12 @@ import java.util.stream.Stream; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Envelope; -import org.opentripplanner.ext.transmodelapi.mapping.TransitIdMapper; -import org.opentripplanner.ext.transmodelapi.model.EnumTypes; -import org.opentripplanner.ext.transmodelapi.model.TransmodelTransportSubmode; -import org.opentripplanner.ext.transmodelapi.model.plan.JourneyWhiteListed; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.TransmodelTransportSubmode; +import org.opentripplanner.apis.transmodel.model.plan.JourneyWhiteListed; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.framework.graphql.GraphQLUtils; -import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.model.StopTimesInPattern; import org.opentripplanner.model.TripTimeOnDate; import org.opentripplanner.routing.stoptimes.ArrivalDeparture; @@ -168,7 +164,7 @@ public static GraphQLObjectType create( .newFieldDefinition() .name("transportMode") .description("The transport modes of quays under this stop place.") - .type(new GraphQLList(TRANSPORT_MODE)) + .type(new GraphQLList(EnumTypes.TRANSPORT_MODE)) .dataFetcher(environment -> ((MonoOrMultiModalStation) environment.getSource()).getChildStops() .stream() @@ -183,7 +179,7 @@ public static GraphQLObjectType create( .newFieldDefinition() .name("transportSubmode") .description("The transport submode serviced by this stop place.") - .type(new GraphQLList(TRANSPORT_SUBMODE)) + .type(new GraphQLList(EnumTypes.TRANSPORT_SUBMODE)) .dataFetcher(environment -> ((MonoOrMultiModalStation) environment.getSource()).getChildStops() .stream() @@ -341,14 +337,14 @@ public static GraphQLObjectType create( .newArgument() .name("whiteListedModes") .description("Only show estimated calls for selected modes.") - .type(GraphQLList.list(TRANSPORT_MODE)) + .type(GraphQLList.list(EnumTypes.TRANSPORT_MODE)) .build() ) .argument( GraphQLArgument .newArgument() .name("includeCancelledTrips") - .description("Indicates that realtime-cancelled trips should also be included.") + .description("Indicates that real-time-cancelled trips should also be included.") .type(Scalars.GraphQLBoolean) .defaultValue(false) .build() diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/TariffZoneType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/TariffZoneType.java similarity index 85% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/TariffZoneType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/stop/TariffZoneType.java index 6baed23aa65..c3953c7b88d 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/TariffZoneType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/TariffZoneType.java @@ -1,9 +1,9 @@ -package org.opentripplanner.ext.transmodelapi.model.stop; +package org.opentripplanner.apis.transmodel.model.stop; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLObjectType; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.transit.model.site.FareZone; public class TariffZoneType { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/BookingArrangementType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/BookingArrangementType.java similarity index 97% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/BookingArrangementType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/timetable/BookingArrangementType.java index 5cb3b0139af..c2a11441695 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/BookingArrangementType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/BookingArrangementType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.timetable; +package org.opentripplanner.apis.transmodel.model.timetable; import graphql.Scalars; import graphql.schema.DataFetchingEnvironment; @@ -6,8 +6,8 @@ import graphql.schema.GraphQLList; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; -import org.opentripplanner.ext.transmodelapi.model.EnumTypes; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.model.BookingInfo; import org.opentripplanner.model.BookingTime; import org.opentripplanner.transit.model.organization.ContactInfo; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/DatedServiceJourneyQuery.java b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyQuery.java similarity index 92% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/DatedServiceJourneyQuery.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyQuery.java index 1cc8aa51c98..c3c8ba420e4 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/DatedServiceJourneyQuery.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyQuery.java @@ -1,7 +1,6 @@ -package org.opentripplanner.ext.transmodelapi.model.timetable; +package org.opentripplanner.apis.transmodel.model.timetable; -import static org.opentripplanner.ext.transmodelapi.mapping.TransitIdMapper.mapIDsToDomainNullSafe; -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.SERVICE_ALTERATION; +import static org.opentripplanner.apis.transmodel.mapping.TransitIdMapper.mapIDsToDomainNullSafe; import graphql.Scalars; import graphql.schema.GraphQLArgument; @@ -12,8 +11,9 @@ import java.time.LocalDate; import java.util.List; import java.util.stream.Stream; -import org.opentripplanner.ext.transmodelapi.mapping.TransitIdMapper; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.timetable.TripAlteration; import org.opentripplanner.transit.model.timetable.TripOnServiceDate; @@ -75,7 +75,7 @@ public static GraphQLFieldDefinition createQuery( GraphQLArgument .newArgument() .name("alterations") - .type(new GraphQLList(new GraphQLNonNull(SERVICE_ALTERATION))) + .type(new GraphQLList(new GraphQLNonNull(EnumTypes.SERVICE_ALTERATION))) ) .argument( GraphQLArgument diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/DatedServiceJourneyType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyType.java similarity index 95% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/DatedServiceJourneyType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyType.java index 6634ee4e4d3..ed06996301e 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/DatedServiceJourneyType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyType.java @@ -1,6 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.timetable; - -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.SERVICE_ALTERATION; +package org.opentripplanner.apis.transmodel.model.timetable; import graphql.AssertException; import graphql.Scalars; @@ -15,7 +13,8 @@ import graphql.schema.GraphQLTypeReference; import java.util.List; import java.util.Optional; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.routing.TripTimesShortHelper; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.site.StopLocation; @@ -70,7 +69,7 @@ public static GraphQLObjectType create( .newFieldDefinition() .name("tripAlteration") .description("Alterations specified on the Trip in the planned data") - .type(SERVICE_ALTERATION) + .type(EnumTypes.SERVICE_ALTERATION) .dataFetcher(environment -> tripOnServiceDate(environment).getTripAlteration()) ) .field( @@ -146,7 +145,7 @@ public static GraphQLObjectType create( .withDirective(gqlUtil.timingData) .description( "Returns scheduled passingTimes for this dated service journey, " + - "updated with realtime-updates (if available). " + "updated with real-time-updates (if available). " ) .dataFetcher(environment -> { TripOnServiceDate tripOnServiceDate = tripOnServiceDate(environment); diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/InterchangeType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/InterchangeType.java similarity index 97% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/InterchangeType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/timetable/InterchangeType.java index 368784fa88a..35d32c51522 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/InterchangeType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/InterchangeType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.timetable; +package org.opentripplanner.apis.transmodel.model.timetable; import graphql.Scalars; import graphql.schema.DataFetchingEnvironment; @@ -6,7 +6,7 @@ import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; import java.util.function.Function; -import org.opentripplanner.ext.transmodelapi.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.EnumTypes; import org.opentripplanner.model.transfer.ConstrainedTransfer; import org.opentripplanner.model.transfer.TransferConstraint; import org.opentripplanner.model.transfer.TransferPoint; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/ServiceJourneyType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/ServiceJourneyType.java similarity index 95% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/ServiceJourneyType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/timetable/ServiceJourneyType.java index 3603db05120..0122cdbca5d 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/ServiceJourneyType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/ServiceJourneyType.java @@ -1,7 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.timetable; - -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.TRANSPORT_MODE; -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.TRANSPORT_SUBMODE; +package org.opentripplanner.apis.transmodel.model.timetable; import graphql.AssertException; import graphql.Scalars; @@ -18,9 +15,9 @@ import java.util.Optional; import java.util.stream.Collectors; import org.locationtech.jts.geom.LineString; -import org.opentripplanner.ext.transmodelapi.model.EnumTypes; -import org.opentripplanner.ext.transmodelapi.model.TransmodelTransportSubmode; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.TransmodelTransportSubmode; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.framework.geometry.EncodedPolyline; import org.opentripplanner.model.TripTimeOnDate; import org.opentripplanner.routing.TripTimesShortHelper; @@ -88,7 +85,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("transportMode") - .type(TRANSPORT_MODE) + .type(EnumTypes.TRANSPORT_MODE) .dataFetcher(environment -> ((trip(environment)).getMode())) .build() ) @@ -96,7 +93,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("transportSubmode") - .type(TRANSPORT_SUBMODE) + .type(EnumTypes.TRANSPORT_SUBMODE) .dataFetcher(environment -> TransmodelTransportSubmode.fromValue(((trip(environment))).getNetexSubMode()) ) @@ -240,7 +237,7 @@ public static GraphQLObjectType create( .type(new GraphQLNonNull(new GraphQLList(timetabledPassingTimeType))) .withDirective(gqlUtil.timingData) .description( - "Returns scheduled passing times only - without realtime-updates, for realtime-data use 'estimatedCalls'" + "Returns scheduled passing times only - without real-time-updates, for realtime-data use 'estimatedCalls'" ) .dataFetcher(env -> { Trip trip = trip(env); @@ -259,7 +256,7 @@ public static GraphQLObjectType create( .type(new GraphQLList(estimatedCallType)) .withDirective(gqlUtil.timingData) .description( - "Returns scheduled passingTimes for this ServiceJourney for a given date, updated with realtime-updates (if available). " + + "Returns scheduled passingTimes for this ServiceJourney for a given date, updated with real-time-updates (if available). " + "NB! This takes a date as argument (default=today) and returns estimatedCalls for that date and should only be used if the date is " + "known when creating the request. For fetching estimatedCalls for a given trip.leg, use leg.serviceJourneyEstimatedCalls instead." ) diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/TimetabledPassingTimeType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/TimetabledPassingTimeType.java similarity index 98% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/TimetabledPassingTimeType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/timetable/TimetabledPassingTimeType.java index 596580ccb75..9370ed74d93 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/TimetabledPassingTimeType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/TimetabledPassingTimeType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.timetable; +package org.opentripplanner.apis.transmodel.model.timetable; import graphql.Scalars; import graphql.schema.DataFetchingEnvironment; @@ -8,8 +8,8 @@ import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; import graphql.schema.GraphQLTypeReference; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.ext.flex.trip.FlexTrip; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopTime; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/TripMetadataType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/TripMetadataType.java similarity index 95% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/TripMetadataType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/timetable/TripMetadataType.java index 7e956f35bda..ccdb3a9b39d 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/TripMetadataType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/TripMetadataType.java @@ -1,10 +1,10 @@ -package org.opentripplanner.ext.transmodelapi.model.timetable; +package org.opentripplanner.apis.transmodel.model.timetable; import graphql.Scalars; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLNonNull; import graphql.schema.GraphQLObjectType; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.routing.api.response.TripSearchMetadata; public class TripMetadataType { diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/util/EncodedPolylineBeanWithStops.java b/src/main/java/org/opentripplanner/apis/transmodel/model/util/EncodedPolylineBeanWithStops.java similarity index 81% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/model/util/EncodedPolylineBeanWithStops.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/util/EncodedPolylineBeanWithStops.java index 98f68d24a01..92262a17381 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/util/EncodedPolylineBeanWithStops.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/util/EncodedPolylineBeanWithStops.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.util; +package org.opentripplanner.apis.transmodel.model.util; import org.opentripplanner.framework.geometry.EncodedPolyline; import org.opentripplanner.transit.model.site.StopLocation; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/support/AbortOnTimeoutExecutionStrategy.java b/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnTimeoutExecutionStrategy.java similarity index 96% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/support/AbortOnTimeoutExecutionStrategy.java rename to src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnTimeoutExecutionStrategy.java index f7085ae8a74..d143d65421c 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/support/AbortOnTimeoutExecutionStrategy.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnTimeoutExecutionStrategy.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.support; +package org.opentripplanner.apis.transmodel.support; import graphql.execution.AsyncExecutionStrategy; import graphql.schema.DataFetchingEnvironment; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/support/DataFetcherDecorator.java b/src/main/java/org/opentripplanner/apis/transmodel/support/DataFetcherDecorator.java similarity index 96% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/support/DataFetcherDecorator.java rename to src/main/java/org/opentripplanner/apis/transmodel/support/DataFetcherDecorator.java index 614b1cd8dbe..7c21d7e62ae 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/support/DataFetcherDecorator.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/support/DataFetcherDecorator.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.support; +package org.opentripplanner.apis.transmodel.support; import graphql.schema.DataFetchingEnvironment; import java.util.Arrays; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/support/ExecutionResultMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapper.java similarity index 97% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/support/ExecutionResultMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapper.java index 53b44944595..ecb5a38ef01 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/support/ExecutionResultMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapper.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.support; +package org.opentripplanner.apis.transmodel.support; import graphql.ErrorClassification; import graphql.ExecutionResult; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/support/GqlUtil.java b/src/main/java/org/opentripplanner/apis/transmodel/support/GqlUtil.java similarity index 87% rename from src/ext/java/org/opentripplanner/ext/transmodelapi/support/GqlUtil.java rename to src/main/java/org/opentripplanner/apis/transmodel/support/GqlUtil.java index e4f5ae892fe..3700ea60332 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/support/GqlUtil.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/support/GqlUtil.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.support; +package org.opentripplanner.apis.transmodel.support; import graphql.Scalars; import graphql.introspection.Introspection.DirectiveLocation; @@ -13,13 +13,13 @@ import java.time.ZoneId; import java.util.List; import java.util.Locale; -import org.opentripplanner.ext.transmodelapi.TransmodelRequestContext; -import org.opentripplanner.ext.transmodelapi.mapping.TransitIdMapper; -import org.opentripplanner.ext.transmodelapi.model.scalars.DateScalarFactory; -import org.opentripplanner.ext.transmodelapi.model.scalars.DateTimeScalarFactory; -import org.opentripplanner.ext.transmodelapi.model.scalars.DoubleFunctionFactory; -import org.opentripplanner.ext.transmodelapi.model.scalars.LocalTimeScalarFactory; -import org.opentripplanner.ext.transmodelapi.model.scalars.TimeScalarFactory; +import org.opentripplanner.apis.transmodel.TransmodelRequestContext; +import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; +import org.opentripplanner.apis.transmodel.model.scalars.DateScalarFactory; +import org.opentripplanner.apis.transmodel.model.scalars.DateTimeScalarFactory; +import org.opentripplanner.apis.transmodel.model.scalars.DoubleFunctionFactory; +import org.opentripplanner.apis.transmodel.model.scalars.LocalTimeScalarFactory; +import org.opentripplanner.apis.transmodel.model.scalars.TimeScalarFactory; import org.opentripplanner.framework.graphql.GraphQLUtils; import org.opentripplanner.framework.graphql.scalar.DurationScalarFactory; import org.opentripplanner.routing.graphfinder.GraphFinder; diff --git a/src/main/java/org/opentripplanner/framework/application/ApplicationShutdownSupport.java b/src/main/java/org/opentripplanner/framework/application/ApplicationShutdownSupport.java index 7f349fc0146..cd962a79d66 100644 --- a/src/main/java/org/opentripplanner/framework/application/ApplicationShutdownSupport.java +++ b/src/main/java/org/opentripplanner/framework/application/ApplicationShutdownSupport.java @@ -1,5 +1,6 @@ package org.opentripplanner.framework.application; +import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,21 +15,32 @@ private ApplicationShutdownSupport() {} /** * Attempt to add a shutdown hook. If the application is already shutting down, the shutdown hook - * will not be added. + * will be executed immediately. * - * @return true if the shutdown hook is successfully added, false otherwise. + * @param hookName the name of the thread + * @param shutdownHook the payload to be executed in the thread + * @return an Optional possibly containing the created thread, needed to un-schedule the shutdown hook */ - public static boolean addShutdownHook(Thread shutdownHook, String shutdownHookName) { + public static Optional addShutdownHook(String hookName, Runnable shutdownHook) { + final Thread shutdownThread = new Thread(shutdownHook, hookName); try { - LOG.info("Adding shutdown hook {}", shutdownHookName); - Runtime.getRuntime().addShutdownHook(shutdownHook); - return true; + LOG.info("Adding shutdown hook '{}'.", hookName); + Runtime.getRuntime().addShutdownHook(shutdownThread); + return Optional.of(shutdownThread); } catch (IllegalStateException ignore) { - LOG.info( - "OTP is already shutting down, the shutdown hook {} will not be added", - shutdownHookName - ); - return false; + LOG.info("OTP is already shutting down, running shutdown hook '{}' immediately.", hookName); + shutdownThread.start(); } + return Optional.empty(); + } + + /** + * Remove a previously scheduled shutdown hook. + * + * @param shutdownThread an Optional possibly containing a thread + */ + public static void removeShutdownHook(Thread shutdownThread) { + LOG.info("Removing shutdown hook '{}'.", shutdownThread.getName()); + Runtime.getRuntime().removeShutdownHook(shutdownThread); } } diff --git a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 9c730b94216..05d1284a883 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -61,8 +61,9 @@ public enum OTPFeature { TransferConstraints( true, false, - "Enforce transfers to happen according to the _transfers.txt_(GTFS) and Interchanges(NeTEx). Turing this _off_ will increase the routing performance a little." + "Enforce transfers to happen according to the _transfers.txt_ (GTFS) and Interchanges (NeTEx). Turning this _off_ will increase the routing performance a little." ), + TransmodelGraphQlApi(true, true, "Enable Transmodel (NeTEx) GraphQL API."), /* Sandbox extension features - Must be turned OFF by default */ @@ -84,7 +85,7 @@ public enum OTPFeature { RealtimeResolver( false, true, - "When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with realtime data" + "When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with real-time data" ), ReportApi(false, true, "Enable the report API."), RestAPIPassInDefaultConfigAsJson( @@ -95,7 +96,6 @@ public enum OTPFeature { SandboxAPIGeocoder(false, true, "Enable the Geocoder API."), SandboxAPIMapboxVectorTilesApi(false, true, "Enable Mapbox vector tiles API."), SandboxAPIParkAndRideApi(false, true, "Enable park-and-ride endpoint."), - SandboxAPITransmodelApi(false, true, "Enable Entur Transmodel(NeTEx) GraphQL API."), SandboxAPITravelTime(false, true, "Enable the isochrone/travel time surface API."), TransferAnalyzer(false, true, "Analyze transfers during graph build."), VehicleToStopHeuristics(false, true, "Enable improved heuristic for park-and-ride queries."); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ListSection.java b/src/main/java/org/opentripplanner/framework/collection/ListSection.java similarity index 54% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/ListSection.java rename to src/main/java/org/opentripplanner/framework/collection/ListSection.java index a46756775df..d9878aa34b1 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ListSection.java +++ b/src/main/java/org/opentripplanner/framework/collection/ListSection.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.filterchain; +package org.opentripplanner.framework.collection; /** * This enum is used to signal which part of a list an operation apply to. You may remove elements @@ -9,5 +9,16 @@ public enum ListSection { HEAD, /** The end of the list */ - TAIL, + TAIL; + + public boolean isHead() { + return this == HEAD; + } + + public ListSection invert() { + return switch (this) { + case HEAD -> TAIL; + case TAIL -> HEAD; + }; + } } diff --git a/src/main/java/org/opentripplanner/framework/collection/ListUtils.java b/src/main/java/org/opentripplanner/framework/collection/ListUtils.java index 481674bb7de..513c0bcc0d3 100644 --- a/src/main/java/org/opentripplanner/framework/collection/ListUtils.java +++ b/src/main/java/org/opentripplanner/framework/collection/ListUtils.java @@ -10,6 +10,22 @@ public class ListUtils { + /** + * Return the first element in the list. {@code null} is returned if the list is + * null or empty. + */ + public static T first(List list) { + return list == null || list.isEmpty() ? null : list.getFirst(); + } + + /** + * Return the last element in the list. {@code null} is returned if the list is + * null or empty. + */ + public static T last(List list) { + return list == null || list.isEmpty() ? null : list.getLast(); + } + /** * Combine a number of collections into a single list. */ diff --git a/src/main/java/org/opentripplanner/framework/concurrent/OtpRequestThreadFactory.java b/src/main/java/org/opentripplanner/framework/concurrent/OtpRequestThreadFactory.java index 1ad44728c80..e88bbb420cd 100644 --- a/src/main/java/org/opentripplanner/framework/concurrent/OtpRequestThreadFactory.java +++ b/src/main/java/org/opentripplanner/framework/concurrent/OtpRequestThreadFactory.java @@ -7,7 +7,7 @@ /** * This thread pool factory should be used to create all threads handling "user" requests in OTP. - * It is used to instrument new threads witch enable log information propagation and error handling, + * It is used to instrument new threads which enable log information propagation and error handling, * like thread interruption. By "user" we mean users of the query APIs like GTFS GraphQL API, * Transmodel GraphQL API and other http endpoints. *

diff --git a/src/main/java/org/opentripplanner/framework/error/OtpError.java b/src/main/java/org/opentripplanner/framework/error/OtpError.java new file mode 100644 index 00000000000..6b2ff37945e --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/error/OtpError.java @@ -0,0 +1,37 @@ +package org.opentripplanner.framework.error; + +import java.util.Locale; + +/** + * A generic representation of an error. The error should have a code used to group the same + * type of errors together. To avoid filling up memory with error strings during graph build + * we store errors in memory "decomposed". The {@link #messageTemplate()} and + * {@link #messageArguments()} is used to construct the message. Use the {@link Locale#ROOT} + * when constructing the message - we only support english with SI formatting. + */ +public interface OtpError { + /** + * An error code used to identify the error type. This is NOT an enum, but feel free + * to use an inum in the implementation, then use the enum NAME as the code or + * enum TYPE:NAME. Then name should be unique within OTP. + */ + String errorCode(); + + /** + * The string template used to create a human-readable error message. Use the + * {@link String#format(Locale, String, Object...)} format. + */ + String messageTemplate(); + + /** + * The arguments to inject into the message. + */ + Object[] messageArguments(); + + /** + * Construct a message. + */ + default String message() { + return String.format(Locale.ROOT, messageTemplate(), messageArguments()); + } +} diff --git a/src/main/java/org/opentripplanner/framework/lang/ArrayUtils.java b/src/main/java/org/opentripplanner/framework/lang/ArrayUtils.java new file mode 100644 index 00000000000..a3a67cb67cd --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/lang/ArrayUtils.java @@ -0,0 +1,14 @@ +package org.opentripplanner.framework.lang; + +import javax.annotation.Nullable; + +public class ArrayUtils { + + /** + * Return {@code true} if array has at least one element. Return {@code false} is array is + * {@code null} or has zero length. + */ + public static boolean hasContent(@Nullable T[] array) { + return array != null && array.length > 0; + } +} diff --git a/src/main/java/org/opentripplanner/framework/lang/Box.java b/src/main/java/org/opentripplanner/framework/lang/Box.java new file mode 100644 index 00000000000..ae54a3c5b7f --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/lang/Box.java @@ -0,0 +1,61 @@ +package org.opentripplanner.framework.lang; + +import java.util.Objects; +import javax.annotation.Nullable; + +/** + * A box around a mutable value reference. This can be used inside a lambda or passed into + * a function. + * @param the type of the wrapped value. + */ +public class Box { + + private T value; + + private Box(T value) { + this.value = value; + } + + public Box() { + this(null); + } + + public static Box empty() { + return new Box<>(); + } + + public static Box of(T value) { + return new Box<>(value); + } + + @Nullable + public T get() { + return value; + } + + public void set(@Nullable T value) { + this.value = value; + } + + public boolean isEmpty() { + return value == null; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Box box = (Box) o; + return Objects.equals(value, box.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return "[" + value + ']'; + } +} diff --git a/src/main/java/org/opentripplanner/framework/lang/IntUtils.java b/src/main/java/org/opentripplanner/framework/lang/IntUtils.java index 04eeff4695e..7be155308f9 100644 --- a/src/main/java/org/opentripplanner/framework/lang/IntUtils.java +++ b/src/main/java/org/opentripplanner/framework/lang/IntUtils.java @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import javax.annotation.Nullable; /** * A utility class for integer functions. @@ -86,23 +87,37 @@ public static double standardDeviation(List v) { return Math.sqrt(sum / v.size()); } - public static int requireNotNegative(int value) { - if (value < 0) { - throw new IllegalArgumentException("Negative value not expected: " + value); + /** + * Check is given {@code value} is in range {@code [min .. max]}. Both {@code min} and + * {@code max} is inclusive. Throws a {@link IllegalArgumentException} if not in range. + */ + public static int requireInRange(int value, int min, int max, String field) { + if (value < min || value > max) { + throw new IllegalArgumentException( + "The %s is not in range[%d, %d]: %d".formatted(field(field), min, max, value) + ); } return value; } public static int requireInRange(int value, int minInclusive, int maxInclusive) { - return requireInRange(value, minInclusive, maxInclusive, "value"); + return requireInRange(value, minInclusive, maxInclusive, null); } - public static int requireInRange(int value, int minInclusive, int maxInclusive, String field) { - if (value < minInclusive || value > maxInclusive) { + public static int requireNotNegative(int value, String field) { + if (value < 0) { throw new IllegalArgumentException( - "The %s is not in range[%d, %d]: %d".formatted(field, minInclusive, maxInclusive, value) + "Negative value not expected for %s: %d".formatted(field(field), value) ); } return value; } + + public static int requireNotNegative(int value) { + return requireNotNegative(value, null); + } + + private static String field(@Nullable String field) { + return field == null ? "value" : '\'' + field + '\''; + } } diff --git a/src/main/java/org/opentripplanner/framework/lang/ObjectUtils.java b/src/main/java/org/opentripplanner/framework/lang/ObjectUtils.java index ed1ed992cd2..1e67b8f253e 100644 --- a/src/main/java/org/opentripplanner/framework/lang/ObjectUtils.java +++ b/src/main/java/org/opentripplanner/framework/lang/ObjectUtils.java @@ -34,9 +34,17 @@ public static T ifNotNull( } public static T requireNotInitialized(T oldValue, T newValue) { + return requireNotInitialized(null, oldValue, newValue); + } + + public static T requireNotInitialized(@Nullable String name, T oldValue, T newValue) { if (oldValue != null) { throw new IllegalStateException( - "Field is already set! Old value: " + oldValue + ", new value: " + newValue + "Field%s is already set! Old value: %s, new value: %s.".formatted( + (name == null ? "" : " " + name), + oldValue, + newValue + ) ); } return newValue; diff --git a/src/main/java/org/opentripplanner/framework/text/CharacterEscapeFormatter.java b/src/main/java/org/opentripplanner/framework/text/CharacterEscapeFormatter.java new file mode 100644 index 00000000000..2bd5bf14b71 --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/text/CharacterEscapeFormatter.java @@ -0,0 +1,107 @@ +package org.opentripplanner.framework.text; + +/** + * This class is used to escape characters in a string, removing a special character from + * the string. For example, if you want to make sure a string does not contain {@code ';'}, + * the {@code ';'} can be replaced with {@code '\+'}. The slash({@code '\'}) is used as an + * escape character, so we need to escape all {@code '\'} as well. Now, the escaped string + * does not contain the special character anymore. The original string can be computed by + * reversing the process. + *

+ * A "special-character" is removed from a text using an escape character and + * a substitution character. For example, if: + *

    + *
  • the escape char is '\'
  • + *
  • the special char is ';'
  • + *
  • and the substitution char is '+'
  • + *
+ * + * then replace: + *
    + *
  • '\' with '\\' and
  • + *
  • ';' with '\;'
  • + *
+ * To get back the original text, the reverse process using {@link #decode(String)}. + *
+ *
+ * Original: "\tThis;is;an;example\+"
+ * Encoded:  "\\tThis\+is\+an\+example\\+"
+ * Decoded:  "\tThis;is;an;example\+"
+ * 
+ */ +public class CharacterEscapeFormatter { + + private final char escapeChar; + private final char specialChar; + private final char substitutionChar; + + /** + * @param escapeChar the character used as an escape character. + * @param specialChar the character to be removed/replaced in the encoded text. + * @param substitutionChar the character used together with the escape character to put in the + * encoded text as a placeholder for the special character. + */ + public CharacterEscapeFormatter(char escapeChar, char specialChar, char substitutionChar) { + this.escapeChar = escapeChar; + this.specialChar = specialChar; + this.substitutionChar = substitutionChar; + } + + /** + * Encode the given text and replace the {@code specialChar} with a placeholder. The original + * text can be retrieved by using {@link #decode(String)}. + * @param text the text to encode. + * @return the encoded text without the {@code specialChar}. + */ + public String encode(String text) { + final var buf = new StringBuilder(); + for (int i = 0; i < text.length(); ++i) { + char ch = text.charAt(i); + if (ch == escapeChar) { + buf.append(escapeChar).append(escapeChar); + } else if (ch == specialChar) { + buf.append(escapeChar).append(substitutionChar); + } else { + buf.append(ch); + } + } + return buf.toString(); + } + + /** + * Return the original text by decoding the encoded text. + * @see #encode(String) + */ + public String decode(String encodedText) { + if (encodedText.length() < 2) { + return encodedText; + } + final var buf = new StringBuilder(); + boolean prevEsc = false; + for (int i = 0; i < encodedText.length(); ++i) { + char ch = encodedText.charAt(i); + if (prevEsc) { + if (ch == escapeChar) { + buf.append(escapeChar); + } else if (ch == substitutionChar) { + buf.append(specialChar); + } else { + throw new IllegalStateException( + "Unexpected combination of escape-char '%c' and '%c' character at position %d. Text: '%s'.".formatted( + escapeChar, + ch, + i, + encodedText + ) + ); + } + prevEsc = false; + } else if (ch == escapeChar) { + prevEsc = true; + } else { + buf.append(ch); + } + } + return buf.toString(); + } +} diff --git a/src/main/java/org/opentripplanner/framework/text/MarkdownFormatter.java b/src/main/java/org/opentripplanner/framework/text/MarkdownFormatter.java index d74a14a3315..6fdb495a0d5 100644 --- a/src/main/java/org/opentripplanner/framework/text/MarkdownFormatter.java +++ b/src/main/java/org/opentripplanner/framework/text/MarkdownFormatter.java @@ -73,7 +73,7 @@ public static String escapeInTable(String text) { return text.replace("|", "¦"); } - /** Return whitespace witch can be used to indent inside a table cell. */ + /** Return whitespace which can be used to indent inside a table cell. */ public static String indentInTable(int level) { return level <= 0 ? "" : NBSP.repeat(3 * level); } diff --git a/src/main/java/org/opentripplanner/framework/text/Table.java b/src/main/java/org/opentripplanner/framework/text/Table.java index 9db832c9410..940868b82e2 100644 --- a/src/main/java/org/opentripplanner/framework/text/Table.java +++ b/src/main/java/org/opentripplanner/framework/text/Table.java @@ -48,7 +48,7 @@ public static TableBuilder of() { } /** - * Static method witch format a given table as valid Markdown table like: + * Static method which format a given table as valid Markdown table like: *
    * | A | B |
    * |---|---|
diff --git a/src/main/java/org/opentripplanner/framework/text/TableBuilder.java b/src/main/java/org/opentripplanner/framework/text/TableBuilder.java
index cf6623c2d99..12e80fd62a8 100644
--- a/src/main/java/org/opentripplanner/framework/text/TableBuilder.java
+++ b/src/main/java/org/opentripplanner/framework/text/TableBuilder.java
@@ -50,7 +50,7 @@ public TableBuilder withAlights(Collection aligns) {
   }
 
   /**
-   * Return the width needed for each column. The witch is calculated by taking
+   * Return the width needed for each column. The which is calculated by taking
    * the maximum of the {@code minWidth}, header width and the maximum width for all
    * cells in the column.
    */
diff --git a/src/main/java/org/opentripplanner/framework/time/DurationUtils.java b/src/main/java/org/opentripplanner/framework/time/DurationUtils.java
index fb30392cf9b..bf59964fbb2 100644
--- a/src/main/java/org/opentripplanner/framework/time/DurationUtils.java
+++ b/src/main/java/org/opentripplanner/framework/time/DurationUtils.java
@@ -124,7 +124,7 @@ public static Duration duration(String duration) {
   }
 
   /**
-   * This is used to parse a string witch may be a number {@code NNNN}(number of seconds) or a
+   * This is used to parse a string which may be a number {@code NNNN}(number of seconds) or a
    * duration with format {@code NhNmNs}, where {@code N} is a decimal number and
    * {@code h} is hours, {@code m} minutes and {@code s} seconds.
    * 

diff --git a/src/main/java/org/opentripplanner/framework/time/TimeUtils.java b/src/main/java/org/opentripplanner/framework/time/TimeUtils.java index afeeb77ff5d..61549eeced3 100644 --- a/src/main/java/org/opentripplanner/framework/time/TimeUtils.java +++ b/src/main/java/org/opentripplanner/framework/time/TimeUtils.java @@ -205,7 +205,7 @@ public static String msToString(long milliseconds) { * busy-wait again. *

* This method does a "busy" wait - it is not affected by a thread interrupt like - * {@link Thread#sleep(long)}; Hence do not interfere with timeout logic witch uses the interrupt + * {@link Thread#sleep(long)}; Hence do not interfere with timeout logic which uses the interrupt * flag. *

* THIS CODE IS NOT MEANT FOR PRODUCTION! @@ -226,7 +226,7 @@ public static long busyWaitOnce(int waitMs) { * number. *

* This method does a "busy" wait - it is not affected by a thread interrupt like - * {@link Thread#sleep(long)}; Hence do not interfere with timeout logic witch uses the interrupt + * {@link Thread#sleep(long)}; Hence do not interfere with timeout logic which uses the interrupt * flag. *

* THIS CODE IS NOT MEANT FOR PRODUCTION! diff --git a/src/main/java/org/opentripplanner/framework/token/Deserializer.java b/src/main/java/org/opentripplanner/framework/token/Deserializer.java new file mode 100644 index 00000000000..c48723d2e69 --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/Deserializer.java @@ -0,0 +1,74 @@ +package org.opentripplanner.framework.token; + +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +class Deserializer { + + private static final Pattern SPLIT_PATTERN = Pattern.compile( + "[" + TokenFormatterConfiguration.fieldSeparator() + "]" + ); + + private final List values; + + Deserializer(String token) { + byte[] bytes = Base64.getUrlDecoder().decode(token); + var tokenFormatter = TokenFormatterConfiguration.tokenFormatter(); + this.values = + Stream.of(SPLIT_PATTERN.split(new String(bytes), -1)).map(tokenFormatter::decode).toList(); + } + + List deserialize(TokenDefinition definition) { + try { + // Assume deprecated fields are included in the token + return readFields(definition, false); + } catch (Exception ignore) { + // If the token is the next version, then deprecated field are removed. Try + // skipping the deprecated tokens + return readFields(definition, true); + } + } + + private List readFields(TokenDefinition definition, boolean matchNewVersionPlusOne) { + List result = new ArrayList<>(); + matchVersion(definition, matchNewVersionPlusOne); + int index = 1; + + for (FieldDefinition field : definition.listFields()) { + if (matchNewVersionPlusOne && field.deprecated()) { + continue; + } + var v = read(field, index); + if (!field.deprecated()) { + result.add(v); + } + ++index; + } + return result; + } + + private void matchVersion(TokenDefinition definition, boolean matchVersionPlusOne) { + int matchVersion = (matchVersionPlusOne ? 1 : 0) + definition.version(); + + int version = readVersion(); + if (version != matchVersion) { + throw new IllegalStateException( + "Version does not match. Token version: " + + version + + ", schema version: " + + definition.version() + ); + } + } + + private Object read(FieldDefinition field, int index) { + return field.type().stringToValue(values.get(index)); + } + + private int readVersion() { + return Integer.parseInt(values.get(0)); + } +} diff --git a/src/main/java/org/opentripplanner/framework/token/FieldDefinition.java b/src/main/java/org/opentripplanner/framework/token/FieldDefinition.java new file mode 100644 index 00000000000..12f529c1b2f --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/FieldDefinition.java @@ -0,0 +1,54 @@ +package org.opentripplanner.framework.token; + +import java.util.Objects; + +class FieldDefinition { + + private final String name; + private final TokenType type; + private final boolean deprecated; + + private FieldDefinition(String name, TokenType type, boolean deprecated) { + this.name = Objects.requireNonNull(name); + this.type = Objects.requireNonNull(type); + this.deprecated = deprecated; + } + + public FieldDefinition(String name, TokenType type) { + this(name, type, false); + } + + public String name() { + return name; + } + + public TokenType type() { + return type; + } + + public boolean deprecated() { + return deprecated; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FieldDefinition that = (FieldDefinition) o; + return deprecated == that.deprecated && Objects.equals(name, that.name) && type == that.type; + } + + @Override + public int hashCode() { + return Objects.hash(name, type, deprecated); + } + + @Override + public String toString() { + return (deprecated ? "@deprecated " : "") + name + ":" + type; + } + + public FieldDefinition deprecate() { + return new FieldDefinition(name, type, true); + } +} diff --git a/src/main/java/org/opentripplanner/framework/token/Serializer.java b/src/main/java/org/opentripplanner/framework/token/Serializer.java new file mode 100644 index 00000000000..2d01a0ebf5e --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/Serializer.java @@ -0,0 +1,50 @@ +package org.opentripplanner.framework.token; + +import java.util.Base64; +import org.opentripplanner.framework.text.CharacterEscapeFormatter; + +class Serializer { + + private final TokenDefinition definition; + private final Object[] values; + private final StringBuilder buf = new StringBuilder(); + private final CharacterEscapeFormatter tokenFormatter = TokenFormatterConfiguration.tokenFormatter(); + + private Serializer(TokenDefinition definition, Object[] values) { + this.definition = definition; + this.values = values; + } + + static String serialize(TokenDefinition definition, Object[] values) { + var s = new Serializer(definition, values); + s.writeVersion(definition.version()); + for (var fieldName : definition.fieldNames()) { + s.write(fieldName); + } + return s.serialize(); + } + + private String serialize() { + return Base64.getUrlEncoder().encodeToString(buf.toString().getBytes()); + } + + private void write(String fieldName) { + write(fieldName, values[definition.index(fieldName)]); + } + + private void write(String fieldName, Object value) { + var type = definition.type(fieldName); + writeString(type.valueToString(value)); + } + + private void writeVersion(int value) { + writeString(TokenType.INT.valueToString(value)); + } + + private void writeString(String value) { + if (value != null) { + buf.append(tokenFormatter.encode(value)); + } + buf.append(TokenFormatterConfiguration.fieldSeparator()); + } +} diff --git a/src/main/java/org/opentripplanner/framework/token/Token.java b/src/main/java/org/opentripplanner/framework/token/Token.java new file mode 100644 index 00000000000..2dc02169271 --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/Token.java @@ -0,0 +1,86 @@ +package org.opentripplanner.framework.token; + +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Given a schema definition and a token version this class holds the values for + * all fields in a token. + */ +public class Token { + + private final TokenDefinition definition; + private final List fieldValues; + + Token(TokenDefinition definition, List fieldValues) { + this.definition = Objects.requireNonNull(definition); + this.fieldValues = Objects.requireNonNull(fieldValues); + } + + public int version() { + return definition.version(); + } + + public boolean getBoolean(String fieldName) { + return (boolean) get(fieldName, TokenType.BOOLEAN); + } + + public byte getByte(String fieldName) { + return (byte) get(fieldName, TokenType.BYTE); + } + + public Duration getDuration(String fieldName) { + return (Duration) get(fieldName, TokenType.DURATION); + } + + public int getInt(String fieldName) { + return (int) get(fieldName, TokenType.INT); + } + + public String getString(String fieldName) { + return (String) get(fieldName, TokenType.STRING); + } + + /** + * Be careful with enums. If values are added or deleted the backward/forward compatibility + * is compromised. This method return an empty value if the enum does not exist. + *

+ * To ensure that enum values are forward compatible the value must first be added, and then it + * can not be used in a token before OTP is released and deployed. Then when the enum value + * exist in the deployed server, then a new version of OTP can be rolled out which now can use + * the new value. + *

+ * To ensure backwards compatibility, enum values should be **deprecated**, not removed. The enum + * value can only be deleted, when all tokens with the value has expired (depends on use-case). + */ + public > Optional getEnum(String fieldName, Class enumClass) { + try { + return Optional.of(Enum.valueOf(enumClass, (String) get(fieldName, TokenType.ENUM))); + } catch (IllegalArgumentException ignore) { + return Optional.empty(); + } + } + + public Instant getTimeInstant(String fieldName) { + return (Instant) get(fieldName, TokenType.TIME_INSTANT); + } + + private Object get(String fieldName, TokenType type) { + return fieldValues.get(definition.getIndex(fieldName, type)); + } + + @Override + public String toString() { + return ( + "(v" + + version() + + ", " + + fieldValues.stream().map(Objects::toString).collect(Collectors.joining(", ")) + + ')' + ); + } +} diff --git a/src/main/java/org/opentripplanner/framework/token/TokenBuilder.java b/src/main/java/org/opentripplanner/framework/token/TokenBuilder.java new file mode 100644 index 00000000000..4061d073a2d --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/TokenBuilder.java @@ -0,0 +1,58 @@ +package org.opentripplanner.framework.token; + +import java.time.Duration; +import java.time.Instant; +import org.opentripplanner.framework.lang.ObjectUtils; + +/** + * This class is used to create a {@link Token} before encoding it. + */ +public class TokenBuilder { + + private final TokenDefinition definition; + private final Object[] values; + + public TokenBuilder(TokenDefinition definition) { + this.definition = definition; + this.values = new Object[definition.size()]; + } + + public TokenBuilder withBoolean(String fieldName, boolean v) { + return with(fieldName, TokenType.BOOLEAN, v); + } + + public TokenBuilder withByte(String fieldName, byte v) { + return with(fieldName, TokenType.BYTE, v); + } + + public TokenBuilder withEnum(String fieldName, Enum v) { + return with(fieldName, TokenType.ENUM, v); + } + + public TokenBuilder withDuration(String fieldName, Duration v) { + return with(fieldName, TokenType.DURATION, v); + } + + public TokenBuilder withInt(String fieldName, int v) { + return with(fieldName, TokenType.INT, v); + } + + public TokenBuilder withString(String fieldName, String v) { + return with(fieldName, TokenType.STRING, v); + } + + public TokenBuilder withTimeInstant(String fieldName, Instant v) { + return with(fieldName, TokenType.TIME_INSTANT, v); + } + + public String build() { + return Serializer.serialize(definition, values); + } + + private TokenBuilder with(String fieldName, TokenType type, Object value) { + int index = definition.getIndex(fieldName, type); + ObjectUtils.requireNotInitialized(fieldName, values[index], value); + values[index] = value; + return this; + } +} diff --git a/src/main/java/org/opentripplanner/framework/token/TokenDefinition.java b/src/main/java/org/opentripplanner/framework/token/TokenDefinition.java new file mode 100644 index 00000000000..d4ac7a61aec --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/TokenDefinition.java @@ -0,0 +1,107 @@ +package org.opentripplanner.framework.token; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import org.opentripplanner.framework.tostring.ToStringBuilder; + +/** + * A token definition is an ordered list of fields. A field has a name and a type. The + * definition is used to encode/decode a token. + */ +public class TokenDefinition { + + private final int version; + private final List fieldNames; + private final Map fields; + + TokenDefinition(int version, List fields) { + this.version = version; + this.fieldNames = fields.stream().map(FieldDefinition::name).toList(); + this.fields = immutableMapOf(fields); + } + + public int version() { + return version; + } + + public List fieldNames() { + return fieldNames; + } + + public TokenType type(String fieldName) { + return fields.get(fieldName).type(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TokenDefinition that = (TokenDefinition) o; + return version == that.version && listFields().equals(that.listFields()); + } + + @Override + public int hashCode() { + return Objects.hash(version, listFields()); + } + + @Override + public String toString() { + return ToStringBuilder + .of(TokenDefinition.class) + .addNum("version", version, 0) + .addCol("fields", listFields()) + .toString(); + } + + int size() { + return fieldNames.size(); + } + + int getIndex(String name, TokenType assertType) { + assertType(name, assertType); + return index(name); + } + + int index(String name) { + for (int i = 0; i < fieldNames.size(); ++i) { + if (fieldNames.get(i).equals(name)) { + return i; + } + } + throw unknownFieldNameException(name); + } + + List listNonDeprecatedFields() { + return listFields().stream().filter(it -> !it.deprecated()).toList(); + } + + List listFields() { + return fieldNames.stream().map(fields::get).toList(); + } + + private void assertType(String name, TokenType assertType) { + Objects.requireNonNull(name); + var field = fields.get(name); + + if (field == null) { + throw unknownFieldNameException(name); + } + + if (field.type().isNot(assertType)) { + throw new IllegalArgumentException( + "The defined type for '" + name + "' is " + field.type() + " not " + assertType + "." + ); + } + } + + private IllegalArgumentException unknownFieldNameException(String name) { + return new IllegalArgumentException("Unknown field: '" + name + "'"); + } + + private static Map immutableMapOf(List fields) { + return Map.copyOf(fields.stream().collect(Collectors.toMap(FieldDefinition::name, it -> it))); + } +} diff --git a/src/main/java/org/opentripplanner/framework/token/TokenDefinitionBuilder.java b/src/main/java/org/opentripplanner/framework/token/TokenDefinitionBuilder.java new file mode 100644 index 00000000000..06a935c7b47 --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/TokenDefinitionBuilder.java @@ -0,0 +1,96 @@ +package org.opentripplanner.framework.token; + +import java.util.ArrayList; +import java.util.List; +import org.opentripplanner.framework.collection.ListUtils; +import org.opentripplanner.framework.lang.IntUtils; + +public class TokenDefinitionBuilder { + + private final int version; + private final List tokensHead = new ArrayList<>(); + private final List fields = new ArrayList<>(); + + TokenDefinitionBuilder(int version) { + this.version = IntUtils.requireInRange(version, 1, 1_000_000); + } + + TokenDefinitionBuilder(List head, TokenDefinition last) { + this(last.version() + 1); + this.fields.addAll(last.listNonDeprecatedFields()); + this.tokensHead.addAll(head); + this.tokensHead.add(last); + } + + public TokenDefinitionBuilder addByte(String fieldName) { + return add(fieldName, TokenType.BYTE); + } + + public TokenDefinitionBuilder addBoolean(String fieldName) { + return add(fieldName, TokenType.BOOLEAN); + } + + public TokenDefinitionBuilder addDuration(String fieldName) { + return add(fieldName, TokenType.DURATION); + } + + public TokenDefinitionBuilder addEnum(String fieldName) { + return add(fieldName, TokenType.ENUM); + } + + public TokenDefinitionBuilder addInt(String fieldName) { + return add(fieldName, TokenType.INT); + } + + public TokenDefinitionBuilder addString(String fieldName) { + return add(fieldName, TokenType.STRING); + } + + public TokenDefinitionBuilder addTimeInstant(String fieldName) { + return add(fieldName, TokenType.TIME_INSTANT); + } + + /** + * A deprecated field will be removed from the *next* token. A value must be provided for the + * deprecated field when encoding the current version. But, you can not read it! This make sure + * that the previous version sees the deprecated value, while this version will still work with + * a token provided with the next version. The deprecated field is automatically removed from + * the next version. + */ + public TokenDefinitionBuilder deprecate(String fieldName) { + int index = indexOfField(fieldName); + if (index < 0) { + throw new IllegalArgumentException( + "The field '" + fieldName + "' does not exist! Deprecation failed." + ); + } + fields.set(index, fields.get(index).deprecate()); + return this; + } + + private int indexOfField(String fieldName) { + for (int i = 0; i < fields.size(); i++) { + if (fields.get(i).name().equals(fieldName)) { + return i; + } + } + return -1; + } + + public TokenDefinitionBuilder newVersion() { + return new TokenDefinitionBuilder(tokensHead, buildIt()); + } + + public TokenSchema build() { + return new TokenSchema(ListUtils.combine(tokensHead, List.of(buildIt()))); + } + + private TokenDefinition buildIt() { + return new TokenDefinition(version, fields); + } + + private TokenDefinitionBuilder add(String fieldName, TokenType type) { + fields.add(new FieldDefinition(fieldName, type)); + return this; + } +} diff --git a/src/main/java/org/opentripplanner/framework/token/TokenFormatterConfiguration.java b/src/main/java/org/opentripplanner/framework/token/TokenFormatterConfiguration.java new file mode 100644 index 00000000000..3945014dad5 --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/TokenFormatterConfiguration.java @@ -0,0 +1,25 @@ +package org.opentripplanner.framework.token; + +import org.opentripplanner.framework.text.CharacterEscapeFormatter; + +class TokenFormatterConfiguration { + + private static final char TOKEN_ESCAPE = '\\'; + private static final char TOKEN_SUBSTITUTION = '+'; + private static final char FIELD_SEPARATOR = '|'; + + /** Prevent instantiation - this is a utility class. */ + private TokenFormatterConfiguration() {} + + /** + * We use the pipe '|' for field separations. The IDs included in the token frequently use + * ':' so the visual difference is better than the alternatives like ',' ';' and TAB. + */ + static char fieldSeparator() { + return FIELD_SEPARATOR; + } + + static CharacterEscapeFormatter tokenFormatter() { + return new CharacterEscapeFormatter(TOKEN_ESCAPE, FIELD_SEPARATOR, TOKEN_SUBSTITUTION); + } +} diff --git a/src/main/java/org/opentripplanner/framework/token/TokenSchema.java b/src/main/java/org/opentripplanner/framework/token/TokenSchema.java new file mode 100644 index 00000000000..7ac3aa63c9f --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/TokenSchema.java @@ -0,0 +1,81 @@ +package org.opentripplanner.framework.token; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.opentripplanner.framework.tostring.ToStringBuilder; + +/** + * A token schema contains a set of token definitions, one for each version. This is + * used to decode a token - the same version used to encode a token is used to + * decode it. When encoding a token the latest version is always used. + *

+ * OTP only need to be backward compatible with the last version of otp. So, for each release of + * OTP the schema that is older than the previous version can be merged. By doing so, you do not + * need to keep code around to support very old versions. + */ +public class TokenSchema { + + private final List definitions; + + TokenSchema(List definitions) { + // Reverse the list so the newest version is first and the oldest version is last + var list = new ArrayList<>(definitions); + Collections.reverse(list); + this.definitions = List.copyOf(list); + } + + /** + * Define a set of versioned tokens. The version number for each will be auto-incremented. + * The provided {@code baseVersion} specify the version for the first token defined. + *

+ * Old unused tokens definitions can merged into the first used definition. When this is done + * the "new" base should be given the exact same version number as it had before. The best way to + * ensure this is to add unit tests for all active version. If done right, the tests should not + * change when merging the versions. + *

+ * Take a look at the unit tests to see an example on merging a schema. + *

+ * @param baseVersion The initial version for the first definition. The version number is + * automatically incremented when new definitions are added. If there is many + * definitions, the oldest definitions can be merged into one. The new + * definition base version should match the highest version number of the + * definitions merged into one. For example, if version 3, 4 and 5 is merged, + * the new merged base version is 5. Legal range is [1, 1_000_000] + */ + public static TokenDefinitionBuilder ofVersion(int baseVersion) { + return new TokenDefinitionBuilder(baseVersion); + } + + public Token decode(String token) { + var deserializer = new Deserializer(token); + for (TokenDefinition definition : definitions) { + try { + return new Token(definition, deserializer.deserialize(definition)); + } catch (Exception ignore) {} + } + throw new IllegalArgumentException( + "Token is not valid. Unable to parse token: '" + token + "'." + ); + } + + public TokenBuilder encode() { + return new TokenBuilder(currentDefinition()); + } + + /** + * We iterate over definitions in REVERSE order, because we want to use the + * latest version. + */ + public TokenDefinition currentDefinition() { + return definitions.get(0); + } + + @Override + public String toString() { + return ToStringBuilder + .of(TokenSchema.class) + .addObj("definition", currentDefinition()) + .toString(); + } +} diff --git a/src/main/java/org/opentripplanner/framework/token/TokenType.java b/src/main/java/org/opentripplanner/framework/token/TokenType.java new file mode 100644 index 00000000000..aea39949b0e --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/TokenType.java @@ -0,0 +1,59 @@ +package org.opentripplanner.framework.token; + +import java.time.Duration; +import java.time.Instant; +import javax.annotation.Nullable; +import org.opentripplanner.framework.time.DurationUtils; + +/** + * List of types we can store in a token. + *

+ * Enums are not safe, so do not add support for them. The reason is that new values can be added + * to the enum and the previous version will fail to read the new version - it is no longer forward + * compatible with the new value of the enum. + */ +public enum TokenType { + BOOLEAN, + BYTE, + DURATION, + ENUM, + INT, + STRING, + TIME_INSTANT; + + private static final String EMPTY = ""; + + boolean isNot(TokenType other) { + return this != other; + } + + public String valueToString(@Nullable Object value) { + if (value == null) { + return EMPTY; + } + return switch (this) { + case BOOLEAN -> Boolean.toString((boolean) value); + case BYTE -> Byte.toString((byte) value); + case DURATION -> DurationUtils.durationToStr((Duration) value); + case ENUM -> ((Enum) value).name(); + case INT -> Integer.toString((int) value); + case STRING -> (String) value; + case TIME_INSTANT -> value.toString(); + }; + } + + public Object stringToValue(String value) { + if (EMPTY.equals(value)) { + return null; + } + return switch (this) { + case BOOLEAN -> Boolean.valueOf(value); + case BYTE -> Byte.valueOf(value); + case DURATION -> DurationUtils.duration(value); + case ENUM -> value; + case INT -> Integer.valueOf(value); + case STRING -> value; + case TIME_INSTANT -> Instant.parse(value); + }; + } +} diff --git a/src/main/java/org/opentripplanner/framework/token/package.md b/src/main/java/org/opentripplanner/framework/token/package.md new file mode 100644 index 00000000000..fb68e4919d1 --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/token/package.md @@ -0,0 +1,71 @@ +# Summary + +This adds a component to specify a very simple schema for a token. The schema is a list of +versioned token definitions and the encode/decode methods enforce versioning, and forward and +backward compatibility. It also allows definitions to be merged. We only need to support one OTP +version. So after every release of OTP we can merge the definitions older than the last release. + +# Issue +[Issue Create a reusable token generator #5451](https://github.com/opentripplanner/OpenTripPlanner/issues/5451) + +# Example + +## Define Schema +```Java +// v1: (mode: byte) +var builder = TokenSchema.ofVersion(1).addByte("mode"); + +// v2: (mode: byte, searchWindow : Duration, numOfItineraries : int) +builder.newVersion().addDuration("searchWindow").addInt("numOfItineraries"); + +// v3: (mode: byte, @deprecated searchWindow : Duration, numOfItineraries : int, name : String) +builder = builder.newVersion().deprecate("searchWindow").addString("name"); + +// v4: (@deprecated mode: byte, numOfItineraries : int, name : String, dateTime : Instant) +builder = builder.newVersion().deprecate("mode").addTimeInstant("dateTime"); + +var schema = builder.build(); +``` + +## Merge Schema + +The merging is provided to simplify the code when old versions of the schema is no longer needed. +For example after releasing OTP `v2.5`, everything older than `v2.4` can be merged - we only +support backwards compatibility with the previous version. + +```Java +// v3 - v1, v2 and v3 merged +var builder = TokenSchema + .ofVersion(3) + .addByte("mode") + .addInt("numOfItineraries") + .addString("name"); +``` + +## Encode token + +Create a new token with latest version/definition(v4). Deprecated fields need to be inserted to +be forward compatible. + +```Java +var token = schema.encode() + .withInt("numOfItineraries", 4) + .withByte("mode", BUS_CODE) + .withTimeInstant("dateTime", Instant.now()) + .withString("name", "Oslo - Bergen") + .build(); +``` + +## Decode token + +The token returned is parsed using the schema version that was used to generate the token. When +acting on this, a mapping into the existing code must be provided for each supported version. If an +old version can not be supported anymore, then merging the Schema is an option. + +```Java +var token = schema.decode("rO0ABXcaAAIxMwAUMjAyMy0xMC0yM1QxMDowMDo1OVo="); + +if(token.version() == 1) { token.getByte("mode") ... } +if(token.version() == 2) { ... } +if(token.version() == 3) { ... } +``` diff --git a/src/main/java/org/opentripplanner/framework/tostring/ToStringBuilder.java b/src/main/java/org/opentripplanner/framework/tostring/ToStringBuilder.java index d2cec512420..cb5d26294db 100644 --- a/src/main/java/org/opentripplanner/framework/tostring/ToStringBuilder.java +++ b/src/main/java/org/opentripplanner/framework/tostring/ToStringBuilder.java @@ -60,6 +60,14 @@ public static ToStringBuilder of(Class clazz) { return new ToStringBuilder(clazz.getSimpleName()); } + /** + * Create a ToStringBuilder for a "named" type. The preferred method is {@link #of(Class)}, + * but this can be used if the type is unknown or irrelevant. + */ + public static ToStringBuilder of(String name) { + return new ToStringBuilder(name); + } + /** * Create a ToStringBuilder for a regular POJO type without including the type in the name. Some * classes are always embedded in other classes and the type is given, for these cases this diff --git a/src/main/java/org/opentripplanner/framework/tostring/ValueObjectToStringBuilder.java b/src/main/java/org/opentripplanner/framework/tostring/ValueObjectToStringBuilder.java index 7882bd347da..14584a8dd4a 100644 --- a/src/main/java/org/opentripplanner/framework/tostring/ValueObjectToStringBuilder.java +++ b/src/main/java/org/opentripplanner/framework/tostring/ValueObjectToStringBuilder.java @@ -1,6 +1,7 @@ package org.opentripplanner.framework.tostring; import java.time.Duration; +import java.time.Instant; import java.util.function.Function; import org.opentripplanner.framework.lang.OtpNumberFormat; import org.opentripplanner.framework.time.DurationUtils; @@ -153,6 +154,10 @@ public ValueObjectToStringBuilder addDurationSec(Integer durationSeconds) { return addIt(durationSeconds, DurationUtils::durationToStr); } + public ValueObjectToStringBuilder addTime(Instant time) { + return addIt(time, Object::toString); + } + /** * Add a cost in the format $N, as in "transit seconds", not centi-seconds as used by Raptor. */ diff --git a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java index e6aba29278b..8b2e55ffc2f 100644 --- a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java @@ -121,9 +121,6 @@ public static GraphBuilder create( } if (hasTransitData && (hasOsm || graphBuilder.graph.hasStreets)) { - if (config.matchBusRoutesToStreets) { - graphBuilder.addModule(factory.busRouteStreetMatcher()); - } graphBuilder.addModule(factory.osmBoardingLocationsModule()); } @@ -163,6 +160,10 @@ public static GraphBuilder create( graphBuilder.addModule(factory.graphCoherencyCheckerModule()); } + if (OTPFeature.Co2Emissions.isOn()) { + graphBuilder.addModule(factory.emissionsModule()); + } + if (config.dataImportReport) { graphBuilder.addModule(factory.dataImportIssueReporter()); } @@ -171,10 +172,6 @@ public static GraphBuilder create( graphBuilder.addModuleOptional(factory.dataOverlayFactory()); } - if (OTPFeature.Co2Emissions.isOn()) { - graphBuilder.addModule(factory.emissionsModule()); - } - graphBuilder.addModule(factory.calculateWorldEnvelopeModule()); return graphBuilder; diff --git a/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssueStore.java b/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssueStore.java index 1e28cec72f4..bd158436747 100644 --- a/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssueStore.java +++ b/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssueStore.java @@ -1,6 +1,7 @@ package org.opentripplanner.graph_builder.issue.api; import java.util.List; +import org.opentripplanner.framework.error.OtpError; /** * This service is used to store issued during data import. When the import is complete @@ -19,6 +20,9 @@ public interface DataImportIssueStore { /** Add an issue to the issue report. */ void add(DataImportIssue issue); + /** Add an issue to the issue report. */ + void add(OtpError issue); + /** Add an issue to the issue report without the need of creating an issue class. */ void add(String type, String message); diff --git a/src/main/java/org/opentripplanner/graph_builder/issue/api/NoopDataImportIssueStore.java b/src/main/java/org/opentripplanner/graph_builder/issue/api/NoopDataImportIssueStore.java index 504980d74a5..a3adfe7127c 100644 --- a/src/main/java/org/opentripplanner/graph_builder/issue/api/NoopDataImportIssueStore.java +++ b/src/main/java/org/opentripplanner/graph_builder/issue/api/NoopDataImportIssueStore.java @@ -1,6 +1,7 @@ package org.opentripplanner.graph_builder.issue.api; import java.util.List; +import org.opentripplanner.framework.error.OtpError; /** * A no-op implementation of the issue store, convenient for unit testing. No issues are @@ -11,6 +12,9 @@ class NoopDataImportIssueStore implements DataImportIssueStore { @Override public void add(DataImportIssue issue) {} + @Override + public void add(OtpError issue) {} + @Override public void add(String type, String message) {} diff --git a/src/main/java/org/opentripplanner/graph_builder/issue/service/DefaultDataImportIssueStore.java b/src/main/java/org/opentripplanner/graph_builder/issue/service/DefaultDataImportIssueStore.java index 53f8c65d10c..3cdcf9d0db6 100644 --- a/src/main/java/org/opentripplanner/graph_builder/issue/service/DefaultDataImportIssueStore.java +++ b/src/main/java/org/opentripplanner/graph_builder/issue/service/DefaultDataImportIssueStore.java @@ -3,6 +3,7 @@ import jakarta.inject.Singleton; import java.util.ArrayList; import java.util.List; +import org.opentripplanner.framework.error.OtpError; import org.opentripplanner.graph_builder.issue.api.DataImportIssue; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.issue.api.Issue; @@ -30,6 +31,11 @@ public void add(DataImportIssue issue) { } } + @Override + public void add(OtpError issue) { + add(issue.errorCode(), issue.messageTemplate(), issue.messageArguments()); + } + @Override public void add(String type, String message) { add(Issue.issue(type, message)); diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java index 1f50c7a6327..9fd99d35ea0 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java @@ -24,7 +24,6 @@ import org.opentripplanner.graph_builder.module.TripPatternNamer; import org.opentripplanner.graph_builder.module.geometry.CalculateWorldEnvelopeModule; import org.opentripplanner.graph_builder.module.islandpruning.PruneIslands; -import org.opentripplanner.graph_builder.module.map.BusRouteStreetMatcher; import org.opentripplanner.graph_builder.module.ned.ElevationModule; import org.opentripplanner.graph_builder.module.osm.OsmModule; import org.opentripplanner.gtfs.graphbuilder.GtfsModule; @@ -45,7 +44,6 @@ public interface GraphBuilderFactory { NetexModule netexModule(); TimeZoneAdjusterModule timeZoneAdjusterModule(); TripPatternNamer tripPatternNamer(); - BusRouteStreetMatcher busRouteStreetMatcher(); OsmBoardingLocationsModule osmBoardingLocationsModule(); StreetLinkerModule streetLinkerModule(); PruneIslands pruneIslands(); diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java index 82f183a0bc4..444adb5b727 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java @@ -46,7 +46,7 @@ import org.opentripplanner.transit.service.TransitModel; /** - * Configure all modules witch is not simple enough to be injected. + * Configure all modules which is not simple enough to be injected. */ @Module public class GraphBuilderModules { diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/BusRouteStreetMatcher.java b/src/main/java/org/opentripplanner/graph_builder/module/map/BusRouteStreetMatcher.java deleted file mode 100644 index 8fc77bb7662..00000000000 --- a/src/main/java/org/opentripplanner/graph_builder/module/map/BusRouteStreetMatcher.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.opentripplanner.graph_builder.module.map; - -import jakarta.inject.Inject; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.LineString; -import org.opentripplanner.framework.geometry.GeometryUtils; -import org.opentripplanner.framework.logging.ProgressTracker; -import org.opentripplanner.graph_builder.model.GraphBuilderModule; -import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.transit.model.network.Route; -import org.opentripplanner.transit.model.network.TripPattern; -import org.opentripplanner.transit.service.TransitModel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Uses the shapes from GTFS to determine which streets buses drive on. This is used to improve the - * quality of the shapes shown for the user. - *

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

- * The JTS version of this class has several design decisions that don't work for me. In particular, - * hasNext() in the original should be "isValid", and if we start mid-segment, we should continue at - * the end of this segment rather than the end of the next segment. - */ -public class LinearIterator implements Iterable { - - private final Geometry linearGeom; - - private final int numLines; - - /** - * Invariant: currentLine <> null if the iterator is pointing at a valid coordinate - * - * @throws IllegalArgumentException if linearGeom is not lineal - */ - private LineString currentLine; - - private int componentIndex = 0; - - private int vertexIndex = 0; - - private double segmentFraction; - - /** - * Creates an iterator initialized to the start of a linear {@link Geometry} - * - * @param linear the linear geometry to iterate over - * @throws IllegalArgumentException if linearGeom is not lineal - */ - public LinearIterator(Geometry linear) { - this(linear, 0, 0); - } - - /** - * Creates an iterator starting at a {@link LinearLocation} on a linear {@link Geometry} - * - * @param linear the linear geometry to iterate over - * @param start the location to start at - * @throws IllegalArgumentException if linearGeom is not lineal - */ - public LinearIterator(Geometry linear, LinearLocation start) { - this(linear, start.getComponentIndex(), start.getSegmentIndex()); - this.segmentFraction = start.getSegmentFraction(); - } - - /** - * Creates an iterator starting at a specified component and vertex in a linear {@link Geometry} - * - * @param linearGeom the linear geometry to iterate over - * @param componentIndex the component to start at - * @param vertexIndex the vertex to start at - * @throws IllegalArgumentException if linearGeom is not lineal - */ - public LinearIterator(Geometry linearGeom, int componentIndex, int vertexIndex) { - if (!(linearGeom instanceof Lineal)) throw new IllegalArgumentException( - "Lineal geometry is required" - ); - this.linearGeom = linearGeom; - numLines = linearGeom.getNumGeometries(); - this.componentIndex = componentIndex; - this.vertexIndex = vertexIndex; - loadCurrentLine(); - } - - public static LinearLocation getEndLocation(Geometry linear) { - //the version in LinearLocation is broken - - int lastComponentIndex = linear.getNumGeometries() - 1; - LineString lastLine = (LineString) linear.getGeometryN(lastComponentIndex); - int lastSegmentIndex = lastLine.getNumPoints() - 1; - return new LinearLocation(lastComponentIndex, lastSegmentIndex, 0.0); - } - - /** - * Tests whether there are any vertices left to iterator over. - * - * @return true if there are more vertices to scan - */ - public boolean hasNext() { - if (componentIndex >= numLines) { - return false; - } - if (componentIndex == numLines - 1 && vertexIndex >= currentLine.getNumPoints() - 1) { - return false; - } - return true; - } - - public boolean isValidIndex() { - if (componentIndex >= numLines) { - return false; - } - if (componentIndex == numLines - 1 && vertexIndex >= currentLine.getNumPoints()) { - return false; - } - return true; - } - - /** - * Moves the iterator ahead to the next vertex and (possibly) linear component. - */ - public void next() { - if (!hasNext()) return; - segmentFraction = 0.0; - vertexIndex++; - if (vertexIndex >= currentLine.getNumPoints()) { - componentIndex++; - if (componentIndex < linearGeom.getNumGeometries() - 1) { - loadCurrentLine(); - vertexIndex = 0; - } - } - } - - /** - * Checks whether the iterator cursor is pointing to the endpoint of a linestring. - * - * @return true if the iterator is at an endpoint - */ - public boolean isEndOfLine() { - if (componentIndex >= numLines) { - return false; - } - // LineString currentLine = (LineString) linear.getGeometryN(componentIndex); - if (vertexIndex < currentLine.getNumPoints() - 1) { - return false; - } - return true; - } - - /** - * The component index of the vertex the iterator is currently at. - * - * @return the current component index - */ - public int getComponentIndex() { - return componentIndex; - } - - /** - * The vertex index of the vertex the iterator is currently at. - * - * @return the current vertex index - */ - public int getVertexIndex() { - return vertexIndex; - } - - /** - * Gets the {@link LineString} component the iterator is current at. - * - * @return a linestring - */ - public LineString getLine() { - return currentLine; - } - - /** - * Gets the first {@link Coordinate} of the current segment. (the coordinate of the current - * vertex). - * - * @return a {@link Coordinate} - */ - public Coordinate getSegmentStart() { - return currentLine.getCoordinateN(vertexIndex); - } - - /** - * Gets the second {@link Coordinate} of the current segment. (the coordinate of the next vertex). - * If the iterator is at the end of a line, null is returned. - * - * @return a {@link Coordinate} or null - */ - public Coordinate getSegmentEnd() { - if (vertexIndex < getLine().getNumPoints() - 1) { - return currentLine.getCoordinateN(vertexIndex + 1); - } - return null; - } - - public LinearLocation getLocation() { - return new LinearLocation(componentIndex, vertexIndex, segmentFraction); - } - - @Override - public Iterator iterator() { - return new LinearIteratorIterator(); - } - - private void loadCurrentLine() { - if (componentIndex >= numLines) { - currentLine = null; - return; - } - currentLine = (LineString) linearGeom.getGeometryN(componentIndex); - } - - class LinearIteratorIterator implements Iterator { - - @Override - public boolean hasNext() { - return LinearIterator.this.hasNext(); - } - - @Override - public LinearLocation next() { - LinearLocation result = getLocation(); - LinearIterator.this.next(); - return result; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } -} diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/MatchState.java b/src/main/java/org/opentripplanner/graph_builder/module/map/MatchState.java deleted file mode 100644 index 424655eb377..00000000000 --- a/src/main/java/org/opentripplanner/graph_builder/module/map/MatchState.java +++ /dev/null @@ -1,119 +0,0 @@ -package org.opentripplanner.graph_builder.module.map; - -import java.util.ArrayList; -import java.util.List; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.linearref.LinearLocation; -import org.opentripplanner.framework.geometry.SphericalDistanceLibrary; -import org.opentripplanner.routing.api.request.StreetMode; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.edge.StreetEdge; -import org.opentripplanner.street.model.vertex.Vertex; -import org.opentripplanner.street.search.request.StreetSearchRequest; -import org.opentripplanner.street.search.state.State; - -public abstract class MatchState { - - private static final StreetSearchRequest REQUEST = StreetSearchRequest - .of() - .withMode(StreetMode.CAR) - .build(); - - protected static final double NEW_SEGMENT_PENALTY = 0.1; - - protected static final double NO_TRAVERSE_PENALTY = 20; - - public double currentError; - - public double accumulatedError; - - public MatchState parent; - - protected Edge edge; - - private double distanceAlongRoute = 0; - - public MatchState(MatchState parent, Edge edge, double distanceAlongRoute) { - this.distanceAlongRoute = distanceAlongRoute; - this.parent = parent; - this.edge = edge; - if (parent != null) { - this.accumulatedError = parent.accumulatedError + parent.currentError; - this.distanceAlongRoute += parent.distanceAlongRoute; - } - } - - public abstract List getNextStates(); - - public Edge getEdge() { - return edge; - } - - public double getTotalError() { - return accumulatedError + currentError; - } - - public double getDistanceAlongRoute() { - return distanceAlongRoute; - } - - /* computes the distance, in meters, along a geometry */ - protected static double distanceAlongGeometry( - Geometry geometry, - LinearLocation startIndex, - LinearLocation endIndex - ) { - if (endIndex == null) { - endIndex = LinearLocation.getEndLocation(geometry); - } - double total = 0; - LinearIterator it = new LinearIterator(geometry, startIndex); - LinearLocation index = startIndex; - Coordinate previousCoordinate = startIndex.getCoordinate(geometry); - - it.next(); - index = it.getLocation(); - while (index.compareTo(endIndex) < 0) { - Coordinate thisCoordinate = index.getCoordinate(geometry); - double distance = SphericalDistanceLibrary.fastDistance(previousCoordinate, thisCoordinate); - total += distance; - previousCoordinate = thisCoordinate; - if (!it.hasNext()) { - break; - } - it.next(); - index = it.getLocation(); - } - //now, last bit of last segment - Coordinate finalCoordinate = endIndex.getCoordinate(geometry); - total += SphericalDistanceLibrary.distance(previousCoordinate, finalCoordinate); - - return total; - } - - protected static double distance(Coordinate from, Coordinate to) { - return SphericalDistanceLibrary.fastDistance(from, to); - } - - protected boolean carsCanTraverse(Edge edge) { - // should be done with a method on edge (canTraverse already exists on turnEdge) - State s0 = new State(edge.getFromVertex(), REQUEST); - var states = edge.traverse(s0); - return !State.isEmpty(states); - } - - protected List getOutgoingMatchableEdges(Vertex vertex) { - List edges = new ArrayList<>(); - for (Edge e : vertex.getOutgoing()) { - if (!(e instanceof StreetEdge)) { - continue; - } - if (e.getGeometry() == null) { - continue; - } - edges.add(e); - } - return edges; - } -} diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/MidblockMatchState.java b/src/main/java/org/opentripplanner/graph_builder/module/map/MidblockMatchState.java deleted file mode 100644 index 111817e5ed8..00000000000 --- a/src/main/java/org/opentripplanner/graph_builder/module/map/MidblockMatchState.java +++ /dev/null @@ -1,265 +0,0 @@ -package org.opentripplanner.graph_builder.module.map; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.linearref.LinearLocation; -import org.locationtech.jts.linearref.LocationIndexedLine; -import org.locationtech.jts.util.AssertionFailedException; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.vertex.Vertex; - -public class MidblockMatchState extends MatchState { - - private static final double MAX_ERROR = 1000; - - private final LinearLocation edgeIndex; - private final Geometry edgeGeometry; - private final LocationIndexedLine indexedEdge; - public LinearLocation routeIndex; - Geometry routeGeometry; - - public MidblockMatchState( - MatchState parent, - Geometry routeGeometry, - Edge edge, - LinearLocation routeIndex, - LinearLocation edgeIndex, - double error, - double distanceAlongRoute - ) { - super(parent, edge, distanceAlongRoute); - this.routeGeometry = routeGeometry; - this.routeIndex = routeIndex; - this.edgeIndex = edgeIndex; - - edgeGeometry = edge.getGeometry(); - indexedEdge = new LocationIndexedLine(edgeGeometry); - currentError = error; - } - - @Override - public List getNextStates() { - ArrayList nextStates = new ArrayList<>(); - if (routeIndex.getSegmentIndex() == routeGeometry.getNumPoints() - 1) { - // this has either hit the end, or gone off the end. It's not real clear which. - // for now, let's assume it means that the ending is somewhere along this edge, - // so we return an end state - Coordinate pt = routeIndex.getCoordinate(routeGeometry); - double error = distance(pt, edgeIndex.getCoordinate(edgeGeometry)); - nextStates.add(new EndMatchState(this, error, 0)); - return nextStates; - } - - LinearIterator it = new LinearIterator(routeGeometry, routeIndex); - if (it.hasNext()) { - it.next(); - LinearLocation routeSuccessor = it.getLocation(); - - // now we want to see where this new point is in terms of the edge's geometry - Coordinate newRouteCoord = routeSuccessor.getCoordinate(routeGeometry); - LinearLocation newEdgeIndex = indexedEdge.project(newRouteCoord); - - Coordinate edgeCoord = newEdgeIndex.getCoordinate(edgeGeometry); - if (newEdgeIndex.compareTo(edgeIndex) <= 0) { - // we must make forward progress along the edge... or go to the next edge - /* this should not require the try/catch, but there is a bug in JTS */ - try { - LinearLocation projected2 = indexedEdge.indexOfAfter(edgeCoord, edgeIndex); - //another bug in JTS - if (Double.isNaN(projected2.getSegmentFraction())) { - // we are probably moving backwards - return Collections.emptyList(); - } else { - newEdgeIndex = projected2; - if (newEdgeIndex.equals(edgeIndex)) { - return Collections.emptyList(); - } - } - edgeCoord = newEdgeIndex.getCoordinate(edgeGeometry); - } catch (AssertionFailedException e) { - // we are not making progress, so just return an empty list - return Collections.emptyList(); - } - } - - if (newEdgeIndex.getSegmentIndex() == edgeGeometry.getNumPoints() - 1) { - // we might choose to continue from the end of the edge and a point mid-way - // along this route segment - - // find nearest point that makes progress along the route - Vertex toVertex = edge.getToVertex(); - Coordinate endCoord = toVertex.getCoordinate(); - LocationIndexedLine indexedRoute = new LocationIndexedLine(routeGeometry); - - // FIXME: it would be better to do this project/indexOfAfter in one step - // as the two-step version could snap to a bad place and be unable to escape. - - LinearLocation routeProjectedEndIndex = indexedRoute.project(endCoord); - Coordinate routeProjectedEndCoord = routeProjectedEndIndex.getCoordinate(routeGeometry); - - if (routeProjectedEndIndex.compareTo(routeIndex) <= 0) { - try { - routeProjectedEndIndex = indexedRoute.indexOfAfter(routeProjectedEndCoord, routeIndex); - if (Double.isNaN(routeProjectedEndIndex.getSegmentFraction())) { - // can't go forward - routeProjectedEndIndex = routeIndex; // this is bad, but not terrible - // since we are advancing along the edge - } - } catch (AssertionFailedException e) { - routeProjectedEndIndex = routeIndex; - } - routeProjectedEndCoord = routeProjectedEndIndex.getCoordinate(routeGeometry); - } - - double positionError = distance(routeProjectedEndCoord, endCoord); - double travelAlongRoute = distanceAlongGeometry( - routeGeometry, - routeIndex, - routeProjectedEndIndex - ); - double travelAlongEdge = distanceAlongGeometry(edgeGeometry, edgeIndex, newEdgeIndex); - double travelError = Math.abs(travelAlongEdge - travelAlongRoute); - - double error = positionError + travelError; - - if (error > MAX_ERROR) { - // we're not going to bother with states which are - // totally wrong - return nextStates; - } - - for (Edge e : getOutgoingMatchableEdges(toVertex)) { - double cost = error + NEW_SEGMENT_PENALTY; - if (!carsCanTraverse(e)) { - cost += NO_TRAVERSE_PENALTY; - } - MatchState nextState = new MidblockMatchState( - this, - routeGeometry, - e, - routeProjectedEndIndex, - new LinearLocation(), - cost, - travelAlongRoute - ); - nextStates.add(nextState); - } - } else { - double travelAlongEdge = distanceAlongGeometry(edgeGeometry, edgeIndex, newEdgeIndex); - double travelAlongRoute = distanceAlongGeometry(routeGeometry, routeIndex, routeSuccessor); - double travelError = Math.abs(travelAlongRoute - travelAlongEdge); - - double positionError = distance(edgeCoord, newRouteCoord); - - double error = travelError + positionError; - - MatchState nextState = new MidblockMatchState( - this, - routeGeometry, - edge, - routeSuccessor, - newEdgeIndex, - error, - travelAlongRoute - ); - nextStates.add(nextState); - - // it's also possible that, although we have not yet reached the end of this edge, - // we are going to turn, because the route turns earlier than the edge. In that - // case, we jump to the corner, and our error is the distance from the route point - // and the corner - - Vertex toVertex = edge.getToVertex(); - double travelAlongOldEdge = distanceAlongGeometry(edgeGeometry, edgeIndex, null); - - for (Edge e : getOutgoingMatchableEdges(toVertex)) { - Geometry newEdgeGeometry = e.getGeometry(); - LocationIndexedLine newIndexedEdge = new LocationIndexedLine(newEdgeGeometry); - newEdgeIndex = newIndexedEdge.project(newRouteCoord); - Coordinate newEdgeCoord = newEdgeIndex.getCoordinate(newEdgeGeometry); - positionError = distance(newEdgeCoord, newRouteCoord); - travelAlongEdge = - travelAlongOldEdge + - distanceAlongGeometry(newEdgeGeometry, new LinearLocation(), newEdgeIndex); - travelError = Math.abs(travelAlongRoute - travelAlongEdge); - - error = travelError + positionError; - - if (error > MAX_ERROR) { - // we're not going to bother with states which are - // totally wrong - return nextStates; - } - - double cost = error + NEW_SEGMENT_PENALTY; - if (!carsCanTraverse(e)) { - cost += NO_TRAVERSE_PENALTY; - } - - nextState = - new MidblockMatchState( - this, - routeGeometry, - e, - routeSuccessor, - new LinearLocation(), - cost, - travelAlongRoute - ); - nextStates.add(nextState); - } - } - return nextStates; - } else { - Coordinate routeCoord = routeIndex.getCoordinate(routeGeometry); - LinearLocation projected = indexedEdge.project(routeCoord); - double locationError = distance(projected.getCoordinate(edgeGeometry), routeCoord); - - MatchState end = new EndMatchState(this, locationError, 0); - return Arrays.asList(end); - } - } - - public int hashCode() { - return (edge.hashCode() * 1337 + hashCode(edgeIndex)) * 1337 + hashCode(routeIndex); - } - - public boolean equals(Object o) { - if (!(o instanceof MidblockMatchState)) { - return false; - } - MidblockMatchState other = (MidblockMatchState) o; - return ( - other.edge == edge && - other.edgeIndex.compareTo(edgeIndex) == 0 && - other.routeIndex.compareTo(routeIndex) == 0 - ); - } - - public String toString() { - return ( - "MidblockMatchState(" + - edge + - ", " + - edgeIndex.getSegmentIndex() + - ", " + - edgeIndex.getSegmentFraction() + - ") - " + - currentError - ); - } - - private int hashCode(LinearLocation location) { - return ( - location.getComponentIndex() * - 1000000 + - location.getSegmentIndex() * - 37 + - Double.valueOf(location.getSegmentFraction()).hashCode() - ); - } -} diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/StreetMatcher.java b/src/main/java/org/opentripplanner/graph_builder/module/map/StreetMatcher.java deleted file mode 100644 index e8bc92cc46f..00000000000 --- a/src/main/java/org/opentripplanner/graph_builder/module/map/StreetMatcher.java +++ /dev/null @@ -1,168 +0,0 @@ -package org.opentripplanner.graph_builder.module.map; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.Envelope; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.index.strtree.STRtree; -import org.locationtech.jts.linearref.LinearLocation; -import org.locationtech.jts.linearref.LocationIndexedLine; -import org.locationtech.jts.simplify.DouglasPeuckerSimplifier; -import org.opentripplanner.astar.model.BinHeap; -import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.edge.StreetEdge; -import org.opentripplanner.street.model.vertex.Vertex; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * This Performs most of the work for the MapBuilder graph builder module. It determines which - * sequence of graph edges a GTFS shape probably corresponds to. Note that GTFS shapes are not in - * any way constrained to OSM edges or even roads. - */ -public class StreetMatcher { - - private static final Logger log = LoggerFactory.getLogger(StreetMatcher.class); - private static final double DISTANCE_THRESHOLD = 0.0002; - private final STRtree index; - Graph graph; - - public StreetMatcher(Graph graph) { - this.graph = graph; - index = createIndex(); - index.build(); - } - - @SuppressWarnings("unchecked") - public List match(Geometry routeGeometry) { - routeGeometry = removeDuplicatePoints(routeGeometry); - - if (routeGeometry == null) { - return null; - } - - routeGeometry = DouglasPeuckerSimplifier.simplify(routeGeometry, 0.00001); - - // initial state: start midway along a block. - LocationIndexedLine indexedLine = new LocationIndexedLine(routeGeometry); - - LinearLocation startIndex = indexedLine.getStartIndex(); - - Coordinate routeStartCoordinate = startIndex.getCoordinate(routeGeometry); - Envelope envelope = new Envelope(routeStartCoordinate); - double distanceThreshold = DISTANCE_THRESHOLD; - envelope.expandBy(distanceThreshold); - - BinHeap states = new BinHeap<>(); - List nearbyEdges = index.query(envelope); - while (nearbyEdges.isEmpty()) { - envelope.expandBy(distanceThreshold); - distanceThreshold *= 2; - nearbyEdges = index.query(envelope); - } - - // compute initial states - for (Edge initialEdge : nearbyEdges) { - Geometry edgeGeometry = initialEdge.getGeometry(); - - LocationIndexedLine indexedEdge = new LocationIndexedLine(edgeGeometry); - LinearLocation initialLocation = indexedEdge.project(routeStartCoordinate); - - double error = MatchState.distance( - initialLocation.getCoordinate(edgeGeometry), - routeStartCoordinate - ); - MidblockMatchState state = new MidblockMatchState( - null, - routeGeometry, - initialEdge, - startIndex, - initialLocation, - error, - 0.01 - ); - states.insert(state, 0); //make sure all initial states are visited by inserting them at 0 - } - - // search for best-matching path - int seen_count = 0, total = 0; - HashSet seen = new HashSet<>(); - while (!states.empty()) { - double k = states.peek_min_key(); - MatchState state = states.extract_min(); - if (++total % 50000 == 0) { - log.debug("seen / total: {} / {}", seen_count, total); - } - if (seen.contains(state)) { - ++seen_count; - continue; - } else { - if (k != 0) { - //but do not mark states as closed if we start at them - seen.add(state); - } - } - if (state instanceof EndMatchState) { - return toEdgeList(state); - } - for (MatchState next : state.getNextStates()) { - if (seen.contains(next)) { - continue; - } - states.insert(next, next.getTotalError() - next.getDistanceAlongRoute()); - } - } - return null; - } - - STRtree createIndex() { - STRtree edgeIndex = new STRtree(); - for (Vertex v : graph.getVertices()) { - for (Edge e : v.getOutgoing()) { - if (e instanceof StreetEdge) { - Envelope envelope; - Geometry geometry = e.getGeometry(); - envelope = geometry.getEnvelopeInternal(); - edgeIndex.insert(envelope, e); - } - } - } - log.debug("Created index"); - return edgeIndex; - } - - private Geometry removeDuplicatePoints(Geometry routeGeometry) { - List coords = new ArrayList<>(); - Coordinate last = null; - for (Coordinate c : routeGeometry.getCoordinates()) { - if (!c.equals(last)) { - last = c; - coords.add(c); - } - } - if (coords.size() < 2) { - return null; - } - Coordinate[] coordArray = new Coordinate[coords.size()]; - return routeGeometry.getFactory().createLineString(coords.toArray(coordArray)); - } - - private List toEdgeList(MatchState next) { - ArrayList edges = new ArrayList<>(); - Edge lastEdge = null; - while (next != null) { - Edge edge = next.getEdge(); - if (edge != lastEdge) { - edges.add(edge); - lastEdge = edge; - } - next = next.parent; - } - Collections.reverse(edges); - return edges; - } -} diff --git a/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java b/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java index 1cb3c4c0186..bf26f9c6d7b 100644 --- a/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java +++ b/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java @@ -9,6 +9,7 @@ import java.util.Map; import java.util.Set; import org.opentripplanner.ext.flex.trip.FlexTrip; +import org.opentripplanner.framework.logging.ProgressTracker; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.issues.TripDegenerate; import org.opentripplanner.graph_builder.issues.TripUndefinedService; @@ -16,6 +17,7 @@ import org.opentripplanner.model.Frequency; import org.opentripplanner.model.StopTime; import org.opentripplanner.model.impl.OtpTransitServiceBuilder; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.Route; @@ -47,7 +49,6 @@ public class GenerateTripPatternsOperation { private final Multimap tripPatterns; private final ListMultimap frequenciesForTrip = ArrayListMultimap.create(); - private int tripCount = 0; private int freqCount = 0; private int scheduledCount = 0; @@ -70,17 +71,21 @@ public void run() { collectFrequencyByTrip(); final Collection trips = transitDaoBuilder.getTripsById().values(); - final int tripsSize = trips.size(); + var progressLogger = ProgressTracker.track("build trip patterns", 50_000, trips.size()); + LOG.info(progressLogger.startMessage()); /* Loop over all trips, handling each one as a frequency-based or scheduled trip. */ for (Trip trip : trips) { - if (++tripCount % 100000 == 0) { - LOG.debug("build trip patterns {}/{}", tripCount, tripsSize); + try { + buildTripPatternForTrip(trip); + //noinspection Convert2MethodRef + progressLogger.step(m -> LOG.info(m)); + } catch (DataValidationException e) { + issueStore.add(e.error()); } - - buildTripPatternForTrip(trip); } + LOG.info(progressLogger.completeMessage()); LOG.info( "Added {} frequency-based and {} single-trip timetable entries.", freqCount, diff --git a/src/main/java/org/opentripplanner/gtfs/mapping/LocationGroupMapper.java b/src/main/java/org/opentripplanner/gtfs/mapping/LocationGroupMapper.java index 4dec2b779ca..591a59c492e 100644 --- a/src/main/java/org/opentripplanner/gtfs/mapping/LocationGroupMapper.java +++ b/src/main/java/org/opentripplanner/gtfs/mapping/LocationGroupMapper.java @@ -5,7 +5,9 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import org.onebusaway.gtfs.model.Location; +import org.onebusaway.gtfs.model.LocationGroup; import org.onebusaway.gtfs.model.Stop; import org.opentripplanner.framework.collection.MapUtils; import org.opentripplanner.framework.i18n.NonLocalizedString; @@ -19,7 +21,7 @@ public class LocationGroupMapper { private final LocationMapper locationMapper; private final StopModelBuilder stopModelBuilder; - private final Map mappedLocationGroups = new HashMap<>(); + private final Map mappedLocationGroups = new HashMap<>(); public LocationGroupMapper( StopMapper stopMapper, @@ -31,29 +33,34 @@ public LocationGroupMapper( this.stopModelBuilder = stopModelBuilder; } - Collection map(Collection allLocationGroups) { + Collection map(Collection allLocationGroups) { return MapUtils.mapToList(allLocationGroups, this::map); } /** Map from GTFS to OTP model, {@code null} safe. */ - GroupStop map(org.onebusaway.gtfs.model.LocationGroup original) { + GroupStop map(LocationGroup original) { return original == null ? null : mappedLocationGroups.computeIfAbsent(original, this::doMap); } - private GroupStop doMap(org.onebusaway.gtfs.model.LocationGroup element) { + private GroupStop doMap(LocationGroup element) { GroupStopBuilder groupStopBuilder = stopModelBuilder .groupStop(mapAgencyAndId(element.getId())) .withName(new NonLocalizedString(element.getName())); - for (org.onebusaway.gtfs.model.StopLocation location : element.getLocations()) { - if (location instanceof Stop) { - groupStopBuilder.addLocation(stopMapper.map((Stop) location)); - } else if (location instanceof Location) { - groupStopBuilder.addLocation(locationMapper.map((Location) location)); - } else if (location instanceof org.onebusaway.gtfs.model.LocationGroup) { - throw new RuntimeException("Nested GroupStops are not allowed"); - } else { - throw new RuntimeException("Unknown location type: " + location.getClass().getSimpleName()); + for (var location : element.getLocations()) { + Objects.requireNonNull( + location, + "Location group '%s' contains a null element.".formatted(element.getId()) + ); + switch (location) { + case Stop stop -> groupStopBuilder.addLocation(stopMapper.map(stop)); + case Location loc -> groupStopBuilder.addLocation(locationMapper.map(loc)); + case LocationGroup ignored -> throw new RuntimeException( + "Nested GroupStops are not allowed" + ); + default -> throw new RuntimeException( + "Unknown location type: " + location.getClass().getSimpleName() + ); } } diff --git a/src/main/java/org/opentripplanner/gtfs/mapping/StopTimeMapper.java b/src/main/java/org/opentripplanner/gtfs/mapping/StopTimeMapper.java index 148a5160d24..47f49a58fc1 100644 --- a/src/main/java/org/opentripplanner/gtfs/mapping/StopTimeMapper.java +++ b/src/main/java/org/opentripplanner/gtfs/mapping/StopTimeMapper.java @@ -3,6 +3,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import org.onebusaway.gtfs.model.Location; import org.onebusaway.gtfs.model.LocationGroup; import org.onebusaway.gtfs.model.Stop; @@ -57,12 +58,18 @@ private StopTime doMap(org.onebusaway.gtfs.model.StopTime rhs) { StopTime lhs = new StopTime(); lhs.setTrip(tripMapper.map(rhs.getTrip())); - if (rhs.getStop() instanceof Stop) { - lhs.setStop(stopMapper.map((Stop) rhs.getStop())); - } else if (rhs.getStop() instanceof Location) { - lhs.setStop(locationMapper.map((Location) rhs.getStop())); - } else if (rhs.getStop() instanceof LocationGroup) { - lhs.setStop(locationGroupMapper.map((LocationGroup) rhs.getStop())); + var stopLocation = rhs.getStopLocation(); + Objects.requireNonNull( + stopLocation, + "Trip %s contains stop_time with no stop, location or group.".formatted(rhs.getTrip()) + ); + switch (stopLocation) { + case Stop stop -> lhs.setStop(stopMapper.map(stop)); + case Location location -> lhs.setStop(locationMapper.map(location)); + case LocationGroup locGroup -> lhs.setStop(locationGroupMapper.map(locGroup)); + default -> throw new IllegalArgumentException( + "Unknown location type: %s".formatted(stopLocation) + ); } I18NString stopHeadsign = null; diff --git a/src/main/java/org/opentripplanner/model/Timetable.java b/src/main/java/org/opentripplanner/model/Timetable.java index 170ed49092d..a8f0f1bbf44 100644 --- a/src/main/java/org/opentripplanner/model/Timetable.java +++ b/src/main/java/org/opentripplanner/model/Timetable.java @@ -25,15 +25,17 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.opentripplanner.framework.time.ServiceDateUtils; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.Direction; import org.opentripplanner.transit.model.timetable.FrequencyEntry; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.updater.GtfsRealtimeMapper; -import org.opentripplanner.updater.spi.TripTimesValidationMapper; +import org.opentripplanner.updater.spi.DataValidationExceptionMapper; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.trip.BackwardsDelayPropagationType; import org.slf4j.Logger; @@ -203,7 +205,7 @@ public Result createUpdatedTripTimesFromGTFSRT( LOG.trace("tripId {} found at index {} in timetable.", tripId, tripIndex); } - TripTimes newTimes = getTripTimes(tripIndex).copyOfScheduledTimes(); + RealTimeTripTimes newTimes = getTripTimes(tripIndex).copyScheduledTimes(); List skippedStopIndices = new ArrayList<>(); // The GTFS-RT reference specifies that StopTimeUpdates are sorted by stop_sequence. @@ -360,14 +362,10 @@ public Result createUpdatedTripTimesFromGTFSRT( } // Validate for non-increasing times. Log error if present. - var error = newTimes.validateNonIncreasingTimes(); - if (error.isPresent()) { - LOG.debug( - "TripTimes are non-increasing after applying GTFS-RT delay propagation to trip {} after stop index {}.", - tripId, - error.get().stopIndex() - ); - return TripTimesValidationMapper.toResult(newTimes.getTrip().getId(), error.get()); + try { + newTimes.validateNonIncreasingTimes(); + } catch (DataValidationException e) { + return DataValidationExceptionMapper.toResult(e); } if (tripUpdate.hasVehicle()) { @@ -432,12 +430,12 @@ public boolean isValidFor(LocalDate serviceDate) { // TODO maybe put this is a more appropriate place public void setServiceCodes(Map serviceCodes) { for (TripTimes tt : this.tripTimes) { - tt.setServiceCode(serviceCodes.get(tt.getTrip().getServiceId())); + ((RealTimeTripTimes) tt).setServiceCode(serviceCodes.get(tt.getTrip().getServiceId())); } // Repeated code... bad sign... for (FrequencyEntry freq : this.frequencyEntries) { TripTimes tt = freq.tripTimes; - tt.setServiceCode(serviceCodes.get(tt.getTrip().getServiceId())); + ((RealTimeTripTimes) tt).setServiceCode(serviceCodes.get(tt.getTrip().getServiceId())); } } diff --git a/src/main/java/org/opentripplanner/model/TimetableSnapshot.java b/src/main/java/org/opentripplanner/model/TimetableSnapshot.java index e9d61d9021b..066a27ba15a 100644 --- a/src/main/java/org/opentripplanner/model/TimetableSnapshot.java +++ b/src/main/java/org/opentripplanner/model/TimetableSnapshot.java @@ -70,9 +70,6 @@ public class TimetableSnapshot { */ private HashMap realtimeAddedTripPattern = new HashMap<>(); - private HashMap realtimeAddedTripOnServiceDate = new HashMap<>(); - private HashMap realtimeAddedTripOnServiceDateByTripIdAndServiceDate = new HashMap<>(); - /** * This maps contains all of the new or updated TripPatterns added by realtime data indexed on * stop. This has to be kept in order for them to be included in the stop times api call on a @@ -269,10 +266,6 @@ public TimetableSnapshot commit(TransitLayerUpdater transitLayerUpdater, boolean transitLayerUpdater.update(dirtyTimetables, timetables); } - ret.realtimeAddedTripOnServiceDate = - (HashMap) this.realtimeAddedTripOnServiceDate.clone(); - ret.realtimeAddedTripOnServiceDateByTripIdAndServiceDate = - (HashMap) this.realtimeAddedTripOnServiceDateByTripIdAndServiceDate.clone(); this.dirtyTimetables.clear(); this.dirty = false; @@ -374,22 +367,6 @@ public void setPatternsForStop(SetMultimap patternsFo this.patternsForStop = patternsForStop; } - public void addLastAddedTripOnServiceDate(TripOnServiceDate tripOnServiceDate) { - realtimeAddedTripOnServiceDate.put(tripOnServiceDate.getId(), tripOnServiceDate); - realtimeAddedTripOnServiceDateByTripIdAndServiceDate.put( - tripOnServiceDate.getTripIdAndServiceDate(), - tripOnServiceDate - ); - } - - public HashMap getRealtimeAddedTripOnServiceDate() { - return realtimeAddedTripOnServiceDate; - } - - public HashMap getRealtimeAddedTripOnServiceDateByTripIdAndServiceDate() { - return realtimeAddedTripOnServiceDateByTripIdAndServiceDate; - } - /** * Clear timetable for all patterns matching the provided feed id. * diff --git a/src/main/java/org/opentripplanner/model/TripTimeOnDate.java b/src/main/java/org/opentripplanner/model/TripTimeOnDate.java index 5cbdc682a1e..9ba85d6b532 100644 --- a/src/main/java/org/opentripplanner/model/TripTimeOnDate.java +++ b/src/main/java/org/opentripplanner/model/TripTimeOnDate.java @@ -189,7 +189,7 @@ public boolean isNoDataStop() { return tripTimes.isNoDataStop(stopIndex); } - public RealTimeState getRealtimeState() { + public RealTimeState getRealTimeState() { return tripTimes.isNoDataStop(stopIndex) ? RealTimeState.SCHEDULED : tripTimes.getRealTimeState(); diff --git a/src/main/java/org/opentripplanner/model/TripTimesPatch.java b/src/main/java/org/opentripplanner/model/TripTimesPatch.java index f804a502d51..25afaf81eae 100644 --- a/src/main/java/org/opentripplanner/model/TripTimesPatch.java +++ b/src/main/java/org/opentripplanner/model/TripTimesPatch.java @@ -1,6 +1,7 @@ package org.opentripplanner.model; import java.util.List; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.TripTimes; /** @@ -9,15 +10,15 @@ */ public class TripTimesPatch { - private final TripTimes tripTimes; + private final RealTimeTripTimes tripTimes; private final List skippedStopIndices; - public TripTimesPatch(TripTimes tripTimes, List skippedStopIndices) { + public TripTimesPatch(RealTimeTripTimes tripTimes, List skippedStopIndices) { this.tripTimes = tripTimes; this.skippedStopIndices = skippedStopIndices; } - public TripTimes getTripTimes() { + public RealTimeTripTimes getTripTimes() { return this.tripTimes; } diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index 0c111999912..58320bf1652 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -428,7 +428,7 @@ public void setLegs(List legs) { * accessible the itinerary is as a whole. This is not a very scientific method but just a rough * guidance that expresses certainty or uncertainty about the accessibility. *

- * An alternative to this is to use the `generalized-cost` and use that to indicate witch itineraries is the + * An alternative to this is to use the `generalized-cost` and use that to indicate which itineraries is the * best/most friendly with respect to making the journey in a wheelchair. The `generalized-cost` include, not * only a penalty for unknown and inaccessible boardings, but also a penalty for undesired uphill and downhill * street traversal. diff --git a/src/main/java/org/opentripplanner/model/plan/ItinerarySortKey.java b/src/main/java/org/opentripplanner/model/plan/ItinerarySortKey.java index 0918f398325..b9165270444 100644 --- a/src/main/java/org/opentripplanner/model/plan/ItinerarySortKey.java +++ b/src/main/java/org/opentripplanner/model/plan/ItinerarySortKey.java @@ -1,6 +1,7 @@ package org.opentripplanner.model.plan; 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; @@ -18,4 +19,21 @@ public interface ItinerarySortKey { int getGeneralizedCost(); int getNumberOfTransfers(); boolean isOnStreetAllTheWay(); + + default String keyAsString() { + return ValueObjectToStringBuilder + .of() + .addText("[") + .addTime(startTimeAsInstant()) + .addText(", ") + .addTime(endTimeAsInstant()) + .addText(", ") + .addCost(getGeneralizedCost()) + .addText(", Tx") + .addNum(getNumberOfTransfers()) + .addText(", ") + .addBool(isOnStreetAllTheWay(), "onStreet", "transit") + .addText("]") + .toString(); + } } diff --git a/src/main/java/org/opentripplanner/model/plan/Place.java b/src/main/java/org/opentripplanner/model/plan/Place.java index 6f39dcad3d1..71c6d9bc188 100644 --- a/src/main/java/org/opentripplanner/model/plan/Place.java +++ b/src/main/java/org/opentripplanner/model/plan/Place.java @@ -139,9 +139,9 @@ public static Place forVehicleParkingEntrance(VehicleParkingEntranceVertex verte traverseMode = TraverseMode.BICYCLE; } - boolean realTime = - request.parking().useAvailabilityInformation() && - vertex.getVehicleParking().hasRealTimeDataForMode(traverseMode, request.wheelchair()); + boolean realTime = vertex + .getVehicleParking() + .hasRealTimeDataForMode(traverseMode, request.wheelchair()); return new Place( vertex.getName(), WgsCoordinate.creatOptionalCoordinate(vertex.getLat(), vertex.getLon()), diff --git a/src/main/java/org/opentripplanner/model/plan/SortOrder.java b/src/main/java/org/opentripplanner/model/plan/SortOrder.java index 812973ebb4d..342f963ba00 100644 --- a/src/main/java/org/opentripplanner/model/plan/SortOrder.java +++ b/src/main/java/org/opentripplanner/model/plan/SortOrder.java @@ -39,7 +39,7 @@ public enum SortOrder { * This returns {@code true} for the default depart-after search, and {@code false} for an * arrive-by search. */ - public boolean isSortedByArrivalTimeAscending() { + public boolean isSortedByAscendingArrivalTime() { return this == STREET_AND_ARRIVAL_TIME; } } diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/ItineraryPageCut.java b/src/main/java/org/opentripplanner/model/plan/pagecursor/ItineraryPageCut.java deleted file mode 100644 index 53958837dd8..00000000000 --- a/src/main/java/org/opentripplanner/model/plan/pagecursor/ItineraryPageCut.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.opentripplanner.model.plan.pagecursor; - -import java.time.Instant; -import org.opentripplanner.framework.tostring.ToStringBuilder; -import org.opentripplanner.model.plan.ItinerarySortKey; -import org.opentripplanner.model.plan.SortOrder; -import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.DefaultCostCalculator; - -/** - * This class contains all the information needed to dedupe itineraries when - * paging. - *

- * It implements the ItinerarySortKey interface so that it can be sorted with itineraries which - * potentially contain duplicates. - */ -public record ItineraryPageCut( - Instant windowStart, - Instant windowEnd, - SortOrder sortOrder, - PagingDeduplicationSection deduplicationSection, - Instant arrivalTimeThreshold, - Instant departureTimeThreshold, - int generalizedCostThreshold, - int numOfTransfersThreshold, - boolean onStreetAllTheWayThreshold -) - implements ItinerarySortKey { - @Override - public String toString() { - return ToStringBuilder - .of(ItineraryPageCut.class) - .addDateTime("windowStart", windowStart) - .addDateTime("windowEnd", windowEnd) - .addEnum("sortOrder", sortOrder) - .addEnum("deduplicationSection", deduplicationSection) - .addBool("isOnStreetAllTheWayThreshold", onStreetAllTheWayThreshold) - .addDateTime("arrivalTimeThreshold", arrivalTimeThreshold) - .addCost( - "generalizedCostThreshold", - generalizedCostThreshold, - DefaultCostCalculator.ZERO_COST - ) - .addNum("numOfTransfersThreshold", numOfTransfersThreshold) - .addDateTime("departureTimeThreshold", departureTimeThreshold) - .toString(); - } - - @Override - public Instant startTimeAsInstant() { - return departureTimeThreshold(); - } - - @Override - public Instant endTimeAsInstant() { - return arrivalTimeThreshold(); - } - - @Override - public int getGeneralizedCost() { - return generalizedCostThreshold(); - } - - @Override - public int getNumberOfTransfers() { - return numOfTransfersThreshold(); - } - - @Override - public boolean isOnStreetAllTheWay() { - return isOnStreetAllTheWayThreshold(); - } - - public Instant windowStart() { - return windowStart; - } - - public Instant windowEnd() { - return windowEnd; - } - - public PagingDeduplicationSection deduplicationSection() { - return deduplicationSection; - } - - public SortOrder sortOrder() { - return sortOrder; - } - - public boolean isOnStreetAllTheWayThreshold() { - return onStreetAllTheWayThreshold; - } - - public Instant arrivalTimeThreshold() { - return arrivalTimeThreshold; - } - - public int generalizedCostThreshold() { - return generalizedCostThreshold; - } - - public int numOfTransfersThreshold() { - return numOfTransfersThreshold; - } - - public Instant departureTimeThreshold() { - return departureTimeThreshold; - } -} diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorInput.java b/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorInput.java deleted file mode 100644 index 58cd6c7e24b..00000000000 --- a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorInput.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.opentripplanner.model.plan.pagecursor; - -import java.time.Instant; - -/** - * This class holds information needed to create the next/previous page cursors when there were - * itineraries removed due to cropping the list of itineraries using the numItineraries parameter. - *

- * The Instant fields come from the sets of itineraries that were removed and the ones that were - * kept as a result of using the numItineraries parameter. - */ -public interface PageCursorInput { - Instant earliestRemovedDeparture(); - Instant earliestKeptArrival(); - Instant latestRemovedDeparture(); - Instant latestRemovedArrival(); - Instant firstRemovedArrivalTime(); - boolean firstRemovedIsOnStreetAllTheWay(); - int firstRemovedGeneralizedCost(); - int firstRemovedNumOfTransfers(); - Instant firstRemovedDepartureTime(); - PagingDeduplicationSection deduplicationSection(); -} diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorSerializer.java b/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorSerializer.java deleted file mode 100644 index b01f07892ac..00000000000 --- a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorSerializer.java +++ /dev/null @@ -1,174 +0,0 @@ -package org.opentripplanner.model.plan.pagecursor; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.time.Duration; -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.Base64; -import javax.annotation.Nullable; -import org.opentripplanner.framework.lang.StringUtils; -import org.opentripplanner.model.plan.SortOrder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -final class PageCursorSerializer { - - private static final int NOT_SET = Integer.MIN_VALUE; - private static final byte VERSION = 1; - private static final long TIME_ZERO = ZonedDateTime - .of(2020, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")) - .toEpochSecond(); - private static final Logger LOG = LoggerFactory.getLogger(PageCursor.class); - - /** private constructor to prevent instantiating this utility class */ - private PageCursorSerializer() { - /* empty */ - } - - @Nullable - public static String encode(PageCursor cursor) { - var buf = new ByteArrayOutputStream(); - try (var out = new ObjectOutputStream(buf)) { - // The order must be the same in the encode and decode function - writeByte(VERSION, out); - writeEnum(cursor.type, out); - writeTime(cursor.earliestDepartureTime, out); - writeTime(cursor.latestArrivalTime, out); - writeDuration(cursor.searchWindow, out); - writeEnum(cursor.originalSortOrder, out); - - if (cursor.containsItineraryPageCut()) { - writeTime(cursor.itineraryPageCut.windowStart(), out); - writeTime(cursor.itineraryPageCut.windowEnd(), out); - writeEnum(cursor.itineraryPageCut.deduplicationSection(), out); - writeBoolean(cursor.itineraryPageCut.isOnStreetAllTheWayThreshold(), out); - writeTime(cursor.itineraryPageCut.arrivalTimeThreshold(), out); - writeInt(cursor.itineraryPageCut.generalizedCostThreshold(), out); - writeInt(cursor.itineraryPageCut.numOfTransfersThreshold(), out); - writeTime(cursor.itineraryPageCut.departureTimeThreshold(), out); - } - out.flush(); - return Base64.getUrlEncoder().encodeToString(buf.toByteArray()); - } catch (IOException e) { - LOG.error("Failed to encode page cursor", e); - return null; - } - } - - @Nullable - public static PageCursor decode(String cursor) { - if (StringUtils.hasNoValueOrNullAsString(cursor)) { - return null; - } - try { - var buf = Base64.getUrlDecoder().decode(cursor); - var input = new ByteArrayInputStream(buf); - - var in = new ObjectInputStream(input); - // The order must be the same in the encode and decode function - - // The version should be used to make serialization read/write forward and backward - // compatible in the future. - var version = readByte(in); - var type = readEnum(in, PageType.class); - var edt = readTime(in); - var lat = readTime(in); - var searchWindow = readDuration(in); - var originalSortOrder = readEnum(in, SortOrder.class); - - if (in.available() > 0) { - var dedupeWindowStart = readTime(in); - var dedupeWindowEnd = readTime(in); - var cropSection = readEnum(in, PagingDeduplicationSection.class); - var isOnStreetAllTheWayThreshold = readBoolean(in); - var arrivalTimeDeletionThreshold = readTime(in); - var generalizedCostDeletionThreshold = readInt(in); - var numOfTransfersDeletionThreshold = readInt(in); - var departureTimeDeletionThreshold = readTime(in); - - ItineraryPageCut itineraryPageCut = new ItineraryPageCut( - dedupeWindowStart, - dedupeWindowEnd, - originalSortOrder, - cropSection, - arrivalTimeDeletionThreshold, - departureTimeDeletionThreshold, - generalizedCostDeletionThreshold, - numOfTransfersDeletionThreshold, - isOnStreetAllTheWayThreshold - ); - return new PageCursor(type, originalSortOrder, edt, lat, searchWindow) - .withItineraryPageCut(itineraryPageCut); - } - - return new PageCursor(type, originalSortOrder, edt, lat, searchWindow); - } catch (Exception e) { - String details = e.getMessage(); - if (details != null && !details.isBlank()) { - LOG.warn("Unable to decode page cursor: '{}'. Details: {}", cursor, details); - } else { - LOG.warn("Unable to decode page cursor: '{}'.", cursor); - } - return null; - } - } - - private static void writeByte(byte value, ObjectOutputStream out) throws IOException { - out.writeByte(value); - } - - private static byte readByte(ObjectInputStream in) throws IOException { - return in.readByte(); - } - - private static void writeInt(int value, ObjectOutputStream out) throws IOException { - out.writeInt(value); - } - - private static int readInt(ObjectInputStream in) throws IOException { - return in.readInt(); - } - - private static void writeBoolean(boolean value, ObjectOutputStream out) throws IOException { - out.writeBoolean(value); - } - - private static boolean readBoolean(ObjectInputStream in) throws IOException { - return in.readBoolean(); - } - - private static void writeTime(Instant time, ObjectOutputStream out) throws IOException { - out.writeInt(time == null ? NOT_SET : (int) (time.getEpochSecond() - TIME_ZERO)); - } - - @Nullable - private static Instant readTime(ObjectInputStream in) throws IOException { - var value = in.readInt(); - return value == NOT_SET ? null : Instant.ofEpochSecond(TIME_ZERO + value); - } - - private static void writeDuration(Duration duration, ObjectOutputStream out) throws IOException { - out.writeInt((int) duration.toSeconds()); - } - - private static Duration readDuration(ObjectInputStream in) throws IOException { - return Duration.ofSeconds(in.readInt()); - } - - private static > void writeEnum(T value, ObjectOutputStream out) - throws IOException { - out.writeUTF(value.name()); - } - - @SuppressWarnings("SameParameterValue") - private static > T readEnum(ObjectInputStream in, Class enumType) - throws IOException { - String value = in.readUTF(); - return Enum.valueOf(enumType, value); - } -} diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/PagingDeduplicationSection.java b/src/main/java/org/opentripplanner/model/plan/pagecursor/PagingDeduplicationSection.java deleted file mode 100644 index 0a469fdc6be..00000000000 --- a/src/main/java/org/opentripplanner/model/plan/pagecursor/PagingDeduplicationSection.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.opentripplanner.model.plan.pagecursor; - -/** - * The PagingDeduplicationSection enum is used to signal which part of an itinerary list may contain - * duplicates. When paging it is the opposite of the CropSection defined in ListSection. That is, if - * the list of itineraries was cropped at the bottom, then any duplicates will appear at the top of - * the list and vice versa. - */ -public enum PagingDeduplicationSection { - HEAD, - TAIL, -} diff --git a/src/main/java/org/opentripplanner/model/plan/PagingSearchWindowAdjuster.java b/src/main/java/org/opentripplanner/model/plan/paging/PagingSearchWindowAdjuster.java similarity index 99% rename from src/main/java/org/opentripplanner/model/plan/PagingSearchWindowAdjuster.java rename to src/main/java/org/opentripplanner/model/plan/paging/PagingSearchWindowAdjuster.java index 703499a5d26..f9e939532bb 100644 --- a/src/main/java/org/opentripplanner/model/plan/PagingSearchWindowAdjuster.java +++ b/src/main/java/org/opentripplanner/model/plan/paging/PagingSearchWindowAdjuster.java @@ -1,4 +1,4 @@ -package org.opentripplanner.model.plan; +package org.opentripplanner.model.plan.paging; import java.time.Duration; import java.time.Instant; diff --git a/src/main/java/org/opentripplanner/model/plan/paging/cursor/DeduplicationPageCut.java b/src/main/java/org/opentripplanner/model/plan/paging/cursor/DeduplicationPageCut.java new file mode 100644 index 00000000000..611b8116b8b --- /dev/null +++ b/src/main/java/org/opentripplanner/model/plan/paging/cursor/DeduplicationPageCut.java @@ -0,0 +1,50 @@ +package org.opentripplanner.model.plan.paging.cursor; + +import java.time.Instant; +import org.opentripplanner.model.plan.ItinerarySortKey; + +/** + * This class contains all the information needed to dedupe itineraries when + * paging - the exact same information as the {@link ItinerarySortKey}. + *

+ * It implements the ItinerarySortKey interface so that it can be sorted with itineraries which + * potentially contain duplicates. + */ +record DeduplicationPageCut( + Instant departureTime, + Instant arrivalTime, + int generalizedCost, + int numOfTransfers, + boolean onStreet +) + implements ItinerarySortKey { + @Override + public Instant startTimeAsInstant() { + return departureTime; + } + + @Override + public Instant endTimeAsInstant() { + return arrivalTime; + } + + @Override + public int getGeneralizedCost() { + return generalizedCost; + } + + @Override + public int getNumberOfTransfers() { + return numOfTransfers; + } + + @Override + public boolean isOnStreetAllTheWay() { + return onStreet; + } + + @Override + public String toString() { + return keyAsString(); + } +} diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursor.java b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursor.java similarity index 51% rename from src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursor.java rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursor.java index e0c1e30c64d..a52dc0429c1 100644 --- a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursor.java +++ b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursor.java @@ -1,9 +1,11 @@ -package org.opentripplanner.model.plan.pagecursor; +package org.opentripplanner.model.plan.paging.cursor; import java.time.Duration; import java.time.Instant; import javax.annotation.Nullable; +import org.opentripplanner.framework.collection.ListSection; import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.model.plan.ItinerarySortKey; import org.opentripplanner.model.plan.SortOrder; /** @@ -16,39 +18,17 @@ *

* THIS CLASS IS IMMUTABLE AND THREAD-SAFE */ -public class PageCursor { - - public final PageType type; - public final SortOrder originalSortOrder; - public final Instant earliestDepartureTime; - public final Instant latestArrivalTime; - public final Duration searchWindow; - - public ItineraryPageCut itineraryPageCut; - - PageCursor( - PageType type, - SortOrder originalSortOrder, - Instant earliestDepartureTime, - Instant latestArrivalTime, - Duration searchWindow - ) { - this.type = type; - this.searchWindow = searchWindow; - this.earliestDepartureTime = earliestDepartureTime; - this.latestArrivalTime = latestArrivalTime; - this.originalSortOrder = originalSortOrder; - } - - public PageCursor withItineraryPageCut(ItineraryPageCut itineraryPageCut) { - this.itineraryPageCut = itineraryPageCut; - return this; - } - +public record PageCursor( + PageType type, + SortOrder originalSortOrder, + Instant earliestDepartureTime, + Instant latestArrivalTime, + Duration searchWindow, + @Nullable ItinerarySortKey itineraryPageCut +) { public boolean containsItineraryPageCut() { return itineraryPageCut != null; } - @Nullable public String encode() { return PageCursorSerializer.encode(this); @@ -59,6 +39,27 @@ public static PageCursor decode(String cursor) { return PageCursorSerializer.decode(cursor); } + /** + * When paging we must crop the list of itineraries in the right end according to the sorting of + * the original search and according to the paging direction(next or previous). + */ + public ListSection cropItinerariesAt() { + // Depart after search + if (originalSortOrder().isSortedByAscendingArrivalTime()) { + return switch (type) { + case NEXT_PAGE -> ListSection.TAIL; + case PREVIOUS_PAGE -> ListSection.HEAD; + }; + } + // Arrive by search + else { + return switch (type) { + case NEXT_PAGE -> ListSection.HEAD; + case PREVIOUS_PAGE -> ListSection.TAIL; + }; + } + } + @Override public String toString() { return ToStringBuilder @@ -68,7 +69,8 @@ public String toString() { .addDateTime("edt", earliestDepartureTime) .addDateTime("lat", latestArrivalTime) .addDuration("searchWindow", searchWindow) - .addObj("itineraryPageCut", itineraryPageCut) + // This will only include the sort vector, not everything else in the itinerary + .addObjOp("itineraryPageCut", itineraryPageCut, ItinerarySortKey::keyAsString) .toString(); } } diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorFactory.java b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursorFactory.java similarity index 55% rename from src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorFactory.java rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursorFactory.java index a6073a540b5..92f59319cc1 100644 --- a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorFactory.java +++ b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursorFactory.java @@ -1,13 +1,13 @@ -package org.opentripplanner.model.plan.pagecursor; +package org.opentripplanner.model.plan.paging.cursor; -import static org.opentripplanner.model.plan.pagecursor.PageType.NEXT_PAGE; -import static org.opentripplanner.model.plan.pagecursor.PageType.PREVIOUS_PAGE; +import static org.opentripplanner.model.plan.paging.cursor.PageType.NEXT_PAGE; +import static org.opentripplanner.model.plan.paging.cursor.PageType.PREVIOUS_PAGE; import java.time.Duration; import java.time.Instant; -import java.time.temporal.ChronoUnit; import javax.annotation.Nullable; import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.model.plan.ItinerarySortKey; import org.opentripplanner.model.plan.SortOrder; public class PageCursorFactory { @@ -15,10 +15,11 @@ public class PageCursorFactory { private final SortOrder sortOrder; private final Duration newSearchWindow; private PageType currentPageType; - private SearchTime current = null; + private Instant currentEdt = null; + private Instant currentLat = null; private Duration currentSearchWindow = null; private boolean wholeSwUsed = true; - private ItineraryPageCut itineraryPageCut = null; + private ItinerarySortKey itineraryPageCut = null; private PageCursorInput pageCursorInput = null; private PageCursor nextCursor = null; @@ -42,7 +43,8 @@ public PageCursorFactory withOriginalSearch( this.currentPageType = pageType == null ? resolvePageTypeForTheFirstSearch(sortOrder) : pageType; - this.current = new SearchTime(edt, lat); + this.currentEdt = edt; + this.currentLat = lat; this.currentSearchWindow = searchWindow; return this; } @@ -59,18 +61,7 @@ public PageCursorFactory withOriginalSearch( public PageCursorFactory withRemovedItineraries(PageCursorInput pageCursorFactoryParams) { this.wholeSwUsed = false; this.pageCursorInput = pageCursorFactoryParams; - this.itineraryPageCut = - new ItineraryPageCut( - pageCursorFactoryParams.earliestRemovedDeparture().truncatedTo(ChronoUnit.SECONDS), - current.edt.plus(currentSearchWindow), - sortOrder, - pageCursorFactoryParams.deduplicationSection(), - pageCursorFactoryParams.firstRemovedArrivalTime(), - pageCursorFactoryParams.firstRemovedDepartureTime(), - pageCursorFactoryParams.firstRemovedGeneralizedCost(), - pageCursorFactoryParams.firstRemovedNumOfTransfers(), - pageCursorFactoryParams.firstRemovedIsOnStreetAllTheWay() - ); + this.itineraryPageCut = pageCursorFactoryParams.pageCut(); return this; } @@ -92,7 +83,8 @@ public String toString() { .of(PageCursorFactory.class) .addEnum("sortOrder", sortOrder) .addEnum("currentPageType", currentPageType) - .addObj("current", current) + .addDateTime("currentEdt", currentEdt) + .addDateTime("currentLat", currentLat) .addDuration("currentSearchWindow", currentSearchWindow) .addDuration("newSearchWindow", newSearchWindow) .addBoolIfTrue("searchWindowCropped", !wholeSwUsed) @@ -108,77 +100,53 @@ public String toString() { * equivalent when creating new cursors. */ private static PageType resolvePageTypeForTheFirstSearch(SortOrder sortOrder) { - return sortOrder.isSortedByArrivalTimeAscending() ? NEXT_PAGE : PREVIOUS_PAGE; + return sortOrder.isSortedByAscendingArrivalTime() ? NEXT_PAGE : PREVIOUS_PAGE; } /** Create page cursor pair (next and previous) */ private void createPageCursors() { - if (current == null || nextCursor != null || prevCursor != null) { + if (currentEdt == null || nextCursor != null || prevCursor != null) { return; } - SearchTime prev = new SearchTime(null, null); - SearchTime next = new SearchTime(null, null); + Instant prevEdt; + Instant nextEdt; if (wholeSwUsed) { - prev.edt = edtBeforeNewSw(); - next.edt = edtAfterUsedSw(); - if (!sortOrder.isSortedByArrivalTimeAscending()) { - prev.lat = current.lat; - } - } else { // If the whole search window was not used (i.e. if there were removed itineraries) + prevEdt = edtBeforeNewSw(); + nextEdt = edtAfterUsedSw(); + } + // If the whole search window was not used (i.e. if there were removed itineraries) + else { if (currentPageType == NEXT_PAGE) { - prev.edt = edtBeforeNewSw(); - next.edt = pageCursorInput.earliestRemovedDeparture(); - if (sortOrder.isSortedByArrivalTimeAscending()) { - prev.lat = pageCursorInput.earliestKeptArrival().truncatedTo(ChronoUnit.MINUTES); - } else { - prev.lat = current.lat; - } + prevEdt = edtBeforeNewSw(); + nextEdt = pageCursorInput.earliestRemovedDeparture(); } else { // The search-window start and end is [inclusive, exclusive], so to calculate the start of the // search-window from the last time included in the search window we need to include one extra // minute at the end. - prev.edt = pageCursorInput.latestRemovedDeparture().minus(newSearchWindow).plusSeconds(60); - next.edt = edtAfterUsedSw(); - prev.lat = pageCursorInput.latestRemovedArrival(); + prevEdt = pageCursorInput.latestRemovedDeparture().minus(newSearchWindow).plusSeconds(60); + nextEdt = edtAfterUsedSw(); } } - prevCursor = new PageCursor(PREVIOUS_PAGE, sortOrder, prev.edt, prev.lat, newSearchWindow); - nextCursor = new PageCursor(NEXT_PAGE, sortOrder, next.edt, next.lat, newSearchWindow); - - if (itineraryPageCut != null) { - nextCursor = nextCursor.withItineraryPageCut(itineraryPageCut); - prevCursor = prevCursor.withItineraryPageCut(itineraryPageCut); - } + prevCursor = + new PageCursor( + PREVIOUS_PAGE, + sortOrder, + prevEdt, + currentLat, + newSearchWindow, + itineraryPageCut + ); + nextCursor = + new PageCursor(NEXT_PAGE, sortOrder, nextEdt, null, newSearchWindow, itineraryPageCut); } private Instant edtBeforeNewSw() { - return current.edt.minus(newSearchWindow); + return currentEdt.minus(newSearchWindow); } private Instant edtAfterUsedSw() { - return current.edt.plus(currentSearchWindow); - } - - /** Temporary data class used to hold a pair of edt and lat */ - private static class SearchTime { - - Instant edt; - Instant lat; - - private SearchTime(Instant edt, Instant lat) { - this.edt = edt; - this.lat = lat; - } - - @Override - public String toString() { - return ToStringBuilder - .of(SearchTime.class) - .addDateTime("edt", edt) - .addDateTime("lat", lat) - .toString(); - } + return currentEdt.plus(currentSearchWindow); } } diff --git a/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursorInput.java b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursorInput.java new file mode 100644 index 00000000000..a9bef266739 --- /dev/null +++ b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursorInput.java @@ -0,0 +1,29 @@ +package org.opentripplanner.model.plan.paging.cursor; + +import java.time.Instant; +import org.opentripplanner.model.plan.ItinerarySortKey; + +/** + * This class holds information needed to create the next/previous page cursors when there were + * itineraries removed due to cropping the list of itineraries using the numItineraries parameter. + *

+ * The Instant fields come from the sets of itineraries that were removed and the ones that were + * kept as a result of using the numItineraries parameter. + */ +public interface PageCursorInput { + /** + * The earliest-removed-departure defines the start of the search-window following the + * current window. To include this removed itinerary (and all other removed itineraries) + * in the next-page search the search windows must overlap. + */ + Instant earliestRemovedDeparture(); + Instant latestRemovedDeparture(); + + /** + * In case the result has too many results: The {@code numberOfItineraries} request parameter + * is less than the number of itineraries found, then we keep the last itinerary kept and + * returned as part of the result. The sort vector will be included in the page-cursor and + * used in the next/previous page to filter away duplicates. + */ + ItinerarySortKey pageCut(); +} diff --git a/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursorSerializer.java b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursorSerializer.java new file mode 100644 index 00000000000..98f64c68062 --- /dev/null +++ b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursorSerializer.java @@ -0,0 +1,113 @@ +package org.opentripplanner.model.plan.paging.cursor; + +import javax.annotation.Nullable; +import org.opentripplanner.framework.lang.StringUtils; +import org.opentripplanner.framework.token.TokenSchema; +import org.opentripplanner.model.plan.ItinerarySortKey; +import org.opentripplanner.model.plan.SortOrder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class PageCursorSerializer { + + private static final byte VERSION = 1; + private static final Logger LOG = LoggerFactory.getLogger(PageCursor.class); + + private static final String TYPE_FIELD = "Type"; + private static final String EDT_FIELD = "EDT"; + private static final String LAT_FIELD = "LAT"; + private static final String SEARCH_WINDOW_FIELD = "SW"; + private static final String SORT_ORDER_FIELD = "SortOrder"; + private static final String CUT_ON_STREET_FIELD = "cutOnStreet"; + private static final String CUT_DEPARTURE_TIME_FIELD = "cutDepartureTime"; + private static final String CUT_ARRIVAL_TIME_FIELD = "cutArrivalTime"; + private static final String CUT_N_TRANSFERS_FIELD = "cutTx"; + private static final String CUT_COST_FIELD = "cutCost"; + + private static final TokenSchema SCHEMA_TOKEN = TokenSchema + .ofVersion(VERSION) + .addEnum(TYPE_FIELD) + .addTimeInstant(EDT_FIELD) + .addTimeInstant(LAT_FIELD) + .addDuration(SEARCH_WINDOW_FIELD) + .addEnum(SORT_ORDER_FIELD) + .addBoolean(CUT_ON_STREET_FIELD) + .addTimeInstant(CUT_DEPARTURE_TIME_FIELD) + .addTimeInstant(CUT_ARRIVAL_TIME_FIELD) + .addInt(CUT_N_TRANSFERS_FIELD) + .addInt(CUT_COST_FIELD) + .build(); + + /** private constructor to prevent instantiating this utility class */ + private PageCursorSerializer() {} + + @Nullable + public static String encode(PageCursor cursor) { + var tokenBuilder = SCHEMA_TOKEN + .encode() + .withEnum(TYPE_FIELD, cursor.type()) + .withTimeInstant(EDT_FIELD, cursor.earliestDepartureTime()) + .withTimeInstant(LAT_FIELD, cursor.latestArrivalTime()) + .withDuration(SEARCH_WINDOW_FIELD, cursor.searchWindow()) + .withEnum(SORT_ORDER_FIELD, cursor.originalSortOrder()); + + var cut = cursor.itineraryPageCut(); + if (cut != null) { + tokenBuilder + .withBoolean(CUT_ON_STREET_FIELD, cut.isOnStreetAllTheWay()) + .withTimeInstant(CUT_DEPARTURE_TIME_FIELD, cut.startTimeAsInstant()) + .withTimeInstant(CUT_ARRIVAL_TIME_FIELD, cut.endTimeAsInstant()) + .withInt(CUT_N_TRANSFERS_FIELD, cut.getNumberOfTransfers()) + .withInt(CUT_COST_FIELD, cut.getGeneralizedCost()); + } + + return tokenBuilder.build(); + } + + @Nullable + public static PageCursor decode(String cursor) { + if (StringUtils.hasNoValueOrNullAsString(cursor)) { + return null; + } + try { + ItinerarySortKey itineraryPageCut = null; + var token = SCHEMA_TOKEN.decode(cursor); + + // This throws an exception if an enum is serialized which is not in the code. + // This is a forward compatibility issue. To avoid this, add the value enum, role out. + // Start using the enum, roll out again. + PageType type = token.getEnum(TYPE_FIELD, PageType.class).orElseThrow(); + var edt = token.getTimeInstant(EDT_FIELD); + var lat = token.getTimeInstant(LAT_FIELD); + var searchWindow = token.getDuration(SEARCH_WINDOW_FIELD); + var originalSortOrder = token.getEnum(SORT_ORDER_FIELD, SortOrder.class).orElseThrow(); + + // We use the departure time to determine if the cut is present or not + var cutDepartureTime = token.getTimeInstant(CUT_DEPARTURE_TIME_FIELD); + + if (cutDepartureTime != null) { + itineraryPageCut = + new DeduplicationPageCut( + cutDepartureTime, + token.getTimeInstant(CUT_ARRIVAL_TIME_FIELD), + token.getInt(CUT_COST_FIELD), + token.getInt(CUT_N_TRANSFERS_FIELD), + token.getBoolean(CUT_ON_STREET_FIELD) + ); + } + + // Add logic to read in data from next version here. + // if(token.version() > 1) { /* get v2 here */} + + return new PageCursor(type, originalSortOrder, edt, lat, searchWindow, itineraryPageCut); + } catch (Exception e) { + String details = e.getMessage(); + if (StringUtils.hasValue(details)) { + LOG.warn("Unable to decode page cursor: '{}'. Details: {}", cursor, details); + } else { + LOG.warn("Unable to decode page cursor: '{}'.", cursor); + } + return null; + } + } +} diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageType.java b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageType.java similarity index 82% rename from src/main/java/org/opentripplanner/model/plan/pagecursor/PageType.java rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/PageType.java index 94b2ce47c93..ad85e169448 100644 --- a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageType.java +++ b/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.model.plan.pagecursor; +package org.opentripplanner.model.plan.paging.cursor; /** * Used to tell which way the paging is going, to the {@link #NEXT_PAGE} or to the {@link @@ -17,5 +17,9 @@ public enum PageType { * the sort order, the next page may hold itineraries which depart/arrive after or before the * current result. */ - NEXT_PAGE, + NEXT_PAGE; + + public boolean isNext() { + return this == NEXT_PAGE; + } } diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/images/pagecursor.excalidraw b/src/main/java/org/opentripplanner/model/plan/paging/cursor/images/pagecursor.excalidraw similarity index 100% rename from src/main/java/org/opentripplanner/model/plan/pagecursor/images/pagecursor.excalidraw rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/images/pagecursor.excalidraw diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-arrival-crop-sw-prev-page.svg b/src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-arrival-crop-sw-prev-page.svg similarity index 100% rename from src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-arrival-crop-sw-prev-page.svg rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-arrival-crop-sw-prev-page.svg diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-arrival-crop-sw.svg b/src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-arrival-crop-sw.svg similarity index 100% rename from src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-arrival-crop-sw.svg rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-arrival-crop-sw.svg diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-arrival.svg b/src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-arrival.svg similarity index 100% rename from src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-arrival.svg rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-arrival.svg diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-departure-crop-sw-next-page.svg b/src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-departure-crop-sw-next-page.svg similarity index 100% rename from src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-departure-crop-sw-next-page.svg rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-departure-crop-sw-next-page.svg diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-departure-crop-sw.svg b/src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-departure-crop-sw.svg similarity index 100% rename from src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-departure-crop-sw.svg rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-departure-crop-sw.svg diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-departure.svg b/src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-departure.svg similarity index 100% rename from src/main/java/org/opentripplanner/model/plan/pagecursor/images/sort-by-departure.svg rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/images/sort-by-departure.svg diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/readme.md b/src/main/java/org/opentripplanner/model/plan/paging/cursor/readme.md similarity index 99% rename from src/main/java/org/opentripplanner/model/plan/pagecursor/readme.md rename to src/main/java/org/opentripplanner/model/plan/paging/cursor/readme.md index d0707a93a0b..e98071cfa62 100644 --- a/src/main/java/org/opentripplanner/model/plan/pagecursor/readme.md +++ b/src/main/java/org/opentripplanner/model/plan/paging/cursor/readme.md @@ -18,7 +18,7 @@ moving on to the next. request to the next page. **sw'** is the search window for the new next/previous page. The search window may change between requests, so we need to account for it when computing the next/previous page cursors. -- **earliest-departure-time (edt)** The search-window start with the earliest-depature-time, which +- **earliest-departure-time (edt)** The search-window start with the earliest-departure-time, which is the first possible time any itinerary may start. **edt'** is the calculated value for the new cursor. - **latest-arrival-time (lat)** The latest time an itinerary can arrive to get accepted. The diff --git a/src/main/java/org/opentripplanner/model/transfer/TransferPoint.java b/src/main/java/org/opentripplanner/model/transfer/TransferPoint.java index b10e96e6f1c..dcfc7d381ad 100644 --- a/src/main/java/org/opentripplanner/model/transfer/TransferPoint.java +++ b/src/main/java/org/opentripplanner/model/transfer/TransferPoint.java @@ -52,7 +52,7 @@ */ public interface TransferPoint { /** - * Utility method witch can be used in APIs to get the trip, if it exists, from a transfer point. + * Utility method which can be used in APIs to get the trip, if it exists, from a transfer point. */ @Nullable static Trip getTrip(TransferPoint point) { @@ -60,7 +60,7 @@ static Trip getTrip(TransferPoint point) { } /** - * Utility method witch can be used in APIs to get the route, if it exists, from a transfer + * Utility method which can be used in APIs to get the route, if it exists, from a transfer * point. */ @Nullable diff --git a/src/main/java/org/opentripplanner/model/transfer/TransferPointMap.java b/src/main/java/org/opentripplanner/model/transfer/TransferPointMap.java index 3592153f080..9f231d06aab 100644 --- a/src/main/java/org/opentripplanner/model/transfer/TransferPointMap.java +++ b/src/main/java/org/opentripplanner/model/transfer/TransferPointMap.java @@ -73,7 +73,7 @@ E computeIfAbsent(TransferPoint point, Supplier creator) { } /** - * List all elements witch matches any of the transfer points added to the map. + * List all elements which matches any of the transfer points added to the map. */ List get(Trip trip, StopLocation stop, int stopPointInPattern) { return Stream diff --git a/src/main/java/org/opentripplanner/netex/mapping/GroupNetexMapper.java b/src/main/java/org/opentripplanner/netex/mapping/GroupNetexMapper.java index 3428f0f8abd..7abb05854f8 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/GroupNetexMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/GroupNetexMapper.java @@ -3,7 +3,9 @@ import com.google.common.collect.ArrayListMultimap; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.model.impl.OtpTransitServiceBuilder; import org.opentripplanner.model.transfer.ConstrainedTransfer; @@ -25,7 +27,7 @@ class GroupNetexMapper { /** * A map from trip/serviceJourney id to an ordered list of scheduled stop point ids. */ - final ArrayListMultimap scheduledStopPointsIndex = ArrayListMultimap.create(); + final Map> scheduledStopPointsIndex = new HashMap<>(); GroupNetexMapper( FeedScopedIdFactory idFactory, diff --git a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java index 4c20eee2ce6..26016947c09 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java @@ -1,6 +1,7 @@ package org.opentripplanner.netex.mapping; import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; import jakarta.xml.bind.JAXBElement; import java.time.LocalDateTime; import java.time.ZoneId; @@ -469,7 +470,7 @@ private void mapTripPatterns(Map serviceIds) { transitBuilder.getTripPatterns().put(it.getKey(), it.getValue()); } currentMapperIndexes.addStopTimesByNetexId(result.stopTimeByNetexId); - groupMapper.scheduledStopPointsIndex.putAll(result.scheduledStopPointsIndex); + groupMapper.scheduledStopPointsIndex.putAll(Multimaps.asMap(result.scheduledStopPointsIndex)); transitBuilder.getTripOnServiceDates().addAll(result.tripOnServiceDates); } } diff --git a/src/main/java/org/opentripplanner/netex/mapping/TransferMapper.java b/src/main/java/org/opentripplanner/netex/mapping/TransferMapper.java index b5b8f42ba36..cb908922160 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/TransferMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/TransferMapper.java @@ -1,6 +1,7 @@ package org.opentripplanner.netex.mapping; -import com.google.common.collect.ArrayListMultimap; +import java.util.List; +import java.util.Map; import javax.annotation.Nullable; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.model.transfer.ConstrainedTransfer; @@ -23,13 +24,13 @@ public class TransferMapper { private final FeedScopedIdFactory idFactory; private final DataImportIssueStore issueStore; - private final ArrayListMultimap scheduledStopPointsIndex; + private final Map> scheduledStopPointsIndex; private final EntityById trips; public TransferMapper( FeedScopedIdFactory idFactory, DataImportIssueStore issueStore, - ArrayListMultimap scheduledStopPointsIndex, + Map> scheduledStopPointsIndex, EntityById trips ) { this.idFactory = idFactory; @@ -139,32 +140,34 @@ private int findStopPosition( ScheduledStopPointRefStructure scheduledStopPointRef ) { String sspId = scheduledStopPointRef.getRef(); + var scheduledStopPoints = scheduledStopPointsIndex.get(sjId); + String errorMessage; + + if (scheduledStopPoints != null) { + var index = + switch (label) { + case Label.TO -> scheduledStopPoints.indexOf(sspId); + case Label.FROM -> scheduledStopPoints.lastIndexOf(sspId); + }; + if (index >= 0) { + return index; + } - int index = -1; - if (label == Label.TO) { - index = scheduledStopPointsIndex.get(sjId).indexOf(sspId); - } else if (label == Label.FROM) { - index = scheduledStopPointsIndex.get(sjId).lastIndexOf(sspId); - } - - if (index >= 0) { - return index; + errorMessage = "Scheduled-stop-point-ref not found"; + } else { + errorMessage = "Service-journey not found"; } - String detailedMsg = scheduledStopPointsIndex.containsKey(sjId) - ? "Scheduled-stop-point-ref not found" - : "Service-journey not found"; - issueStore.add( new InterchangePointMappingFailed( - detailedMsg, + errorMessage, interchangeId, label.label(fieldName), sjId, sspId ) ); - return index; + return -1; } private FeedScopedId createId(String id) { diff --git a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java index 675b9eab5c9..6fa000c1049 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java @@ -11,11 +11,13 @@ import java.util.Objects; import java.util.stream.Collectors; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; +import org.opentripplanner.model.StopTime; import org.opentripplanner.netex.index.api.ReadOnlyHierarchicalMap; import org.opentripplanner.netex.index.api.ReadOnlyHierarchicalMapById; import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory; import org.opentripplanner.transit.model.basic.SubMode; import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.EntityById; import org.opentripplanner.transit.model.framework.FeedScopedId; @@ -360,19 +362,20 @@ private org.opentripplanner.transit.model.network.Route lookupRoute( private void createTripTimes(List trips, TripPattern tripPattern) { for (Trip trip : trips) { - if (result.tripStopTimes.get(trip).size() == 0) { + List stopTimes = result.tripStopTimes.get(trip); + if (stopTimes.isEmpty()) { issueStore.add( "TripWithoutTripTimes", "Trip %s does not contain any trip times.", trip.getId() ); } else { - TripTimes tripTimes = TripTimesFactory.tripTimes( - trip, - result.tripStopTimes.get(trip), - deduplicator - ); - tripPattern.add(tripTimes); + try { + TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, deduplicator); + tripPattern.add(tripTimes); + } catch (DataValidationException e) { + issueStore.add(e.error()); + } } } } diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index 6e596e52076..37757823362 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -201,7 +201,7 @@ public Optional getTagOpt(String network) { /** * Get tag and convert it to an integer. If the tag exist, but can not be parsed into a number, - * then the error handler is called with the value witch failed to parse. + * then the error handler is called with the value which failed to parse. */ public OptionalInt getTagAsInt(String tag, Consumer errorHandler) { String value = getTag(tag); diff --git a/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java b/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java index 1953c60d5d6..16dff3b2e99 100644 --- a/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java +++ b/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java @@ -4,7 +4,6 @@ import static org.opentripplanner.raptor.api.model.RaptorConstants.TIME_NOT_SET; import javax.annotation.Nullable; -import org.opentripplanner.framework.lang.OtpNumberFormat; import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.framework.time.TimeUtils; @@ -34,7 +33,7 @@ public interface RaptorAccessEgress { *

* If this is {@link #isFree()}, then this method must return 0(zero). */ - int generalizedCost(); + int c1(); /** * The time duration to walk or travel the path in seconds. This is not the entire duration from @@ -203,8 +202,8 @@ default String asString(boolean includeStop, boolean includeCost, @Nullable Stri buf.append("Walk"); } buf.append(' ').append(DurationUtils.durationToStr(durationInSeconds())); - if (includeCost && generalizedCost() > 0) { - buf.append(' ').append(OtpNumberFormat.formatCostCenti(generalizedCost())); + if (includeCost && c1() > 0) { + buf.append(' ').append(RaptorValueFormatter.formatC1(c1())); } if (hasRides()) { buf.append(' ').append(numberOfRides()).append('x'); diff --git a/src/main/java/org/opentripplanner/raptor/api/model/RaptorTransfer.java b/src/main/java/org/opentripplanner/raptor/api/model/RaptorTransfer.java index 5245c410266..bab4b9ab166 100644 --- a/src/main/java/org/opentripplanner/raptor/api/model/RaptorTransfer.java +++ b/src/main/java/org/opentripplanner/raptor/api/model/RaptorTransfer.java @@ -20,7 +20,7 @@ public interface RaptorTransfer { * This method is called many times, so care needs to be taken that the value is stored, not * calculated for each invocation. */ - int generalizedCost(); + int c1(); /** * The time duration to walk or travel the path in seconds. This is not the entire duration from diff --git a/src/main/java/org/opentripplanner/raptor/api/model/RaptorValueFormatter.java b/src/main/java/org/opentripplanner/raptor/api/model/RaptorValueFormatter.java new file mode 100644 index 00000000000..dd40c1175ee --- /dev/null +++ b/src/main/java/org/opentripplanner/raptor/api/model/RaptorValueFormatter.java @@ -0,0 +1,58 @@ +package org.opentripplanner.raptor.api.model; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; + +public class RaptorValueFormatter { + + private static final String UNIT_C1 = "C₁"; + private static final String UNIT_C2 = "C₂"; + private static final String UNIT_WAIT_TIME_COST = "wtC₁"; + private static final String UNIT_TRANSFER_PRIORITY = "Tₚ"; + private static final String UNIT_TRANSFERS = "Tₓ"; + + private static final DecimalFormatSymbols DECIMAL_SYMBOLS = new DecimalFormatSymbols(); + + static { + DECIMAL_SYMBOLS.setDecimalSeparator('.'); + DECIMAL_SYMBOLS.setGroupingSeparator('_'); + DECIMAL_SYMBOLS.setMinusSign('-'); + } + + // In general DecimalFormat is not thread-safe, but we are not changing the state here, + // so this is ok. The 'format' is not changing the state. + private static final DecimalFormat FORMAT_CENTI = new DecimalFormat("#,##0.0#", DECIMAL_SYMBOLS); + private static final DecimalFormat FORMAT_INT = new DecimalFormat("#,##0", DECIMAL_SYMBOLS); + + public static String formatC1(int c1) { + return UNIT_C1 + formatCenti(c1); + } + + public static String formatC2(int c2) { + return UNIT_C2 + c2; + } + + public static String formatWaitTimeCost(int value) { + return UNIT_WAIT_TIME_COST + formatCenti(value); + } + + public static String formatNumOfTransfers(int value) { + return UNIT_TRANSFERS + FORMAT_INT.format(value); + } + + public static String formatTransferPriority(int value) { + return UNIT_TRANSFER_PRIORITY + FORMAT_INT.format(value); + } + + /** Format integers in centi units like 1234 => 12.34. */ + private static String formatCenti(int value) { + if (value % 100 == 0) { + value /= 100; + return FORMAT_INT.format(value); + } + if (Math.abs(value) >= 1_000_000) { + return FORMAT_INT.format(value / 100); + } + return FORMAT_CENTI.format(value / 100.0); + } +} diff --git a/src/main/java/org/opentripplanner/raptor/api/path/AccessPathLeg.java b/src/main/java/org/opentripplanner/raptor/api/path/AccessPathLeg.java index 1b9673bd66e..b76e0a1df4d 100644 --- a/src/main/java/org/opentripplanner/raptor/api/path/AccessPathLeg.java +++ b/src/main/java/org/opentripplanner/raptor/api/path/AccessPathLeg.java @@ -16,20 +16,20 @@ public final class AccessPathLeg implements PathLe private final RaptorAccessEgress access; private final int fromTime; private final int toTime; - private final int generalizedCost; + private final int c1; private final PathLeg next; public AccessPathLeg( @Nonnull RaptorAccessEgress access, int fromTime, int toTime, - int generalizedCost, + int c1, @Nonnull PathLeg next ) { this.access = access; this.fromTime = fromTime; this.toTime = toTime; - this.generalizedCost = generalizedCost; + this.c1 = c1; this.next = next; } @@ -52,8 +52,8 @@ public int toStop() { } @Override - public int generalizedCost() { - return generalizedCost; + public int c1() { + return c1; } @Override diff --git a/src/main/java/org/opentripplanner/raptor/api/path/EgressPathLeg.java b/src/main/java/org/opentripplanner/raptor/api/path/EgressPathLeg.java index fd137516cf4..2bbf06f1cda 100644 --- a/src/main/java/org/opentripplanner/raptor/api/path/EgressPathLeg.java +++ b/src/main/java/org/opentripplanner/raptor/api/path/EgressPathLeg.java @@ -15,13 +15,13 @@ public final class EgressPathLeg implements PathLe private final RaptorAccessEgress egress; private final int fromTime; private final int toTime; - private final int generalizedCost; + private final int c1; - public EgressPathLeg(RaptorAccessEgress egress, int fromTime, int toTime, int generalizedCost) { + public EgressPathLeg(RaptorAccessEgress egress, int fromTime, int toTime, int c1) { this.egress = egress; this.fromTime = fromTime; this.toTime = toTime; - this.generalizedCost = generalizedCost; + this.c1 = c1; } @Override @@ -43,8 +43,8 @@ public int toTime() { } @Override - public int generalizedCost() { - return generalizedCost; + public int c1() { + return c1; } @Override diff --git a/src/main/java/org/opentripplanner/raptor/api/path/PathLeg.java b/src/main/java/org/opentripplanner/raptor/api/path/PathLeg.java index a0cabc7e1e9..35a351a4d77 100644 --- a/src/main/java/org/opentripplanner/raptor/api/path/PathLeg.java +++ b/src/main/java/org/opentripplanner/raptor/api/path/PathLeg.java @@ -69,20 +69,20 @@ default int duration() { *

* The unit is centi-seconds (Raptor cost unit) */ - int generalizedCost(); + int c1(); /** - * The computed generalized-cost for this leg plus all legs following it. + * The c1 for this leg plus all legs following it. *

* {@code -1} is returned if no cost is computed by raptor. *

* The unit is centi-seconds (Raptor cost unit) */ - default int generalizedCostTotal() { - if (generalizedCost() < 0) { - return generalizedCost(); + default int c1Total() { + if (c1() < 0) { + return c1(); } - return stream().mapToInt(PathLeg::generalizedCost).sum(); + return stream().mapToInt(PathLeg::c1).sum(); } /** diff --git a/src/main/java/org/opentripplanner/raptor/api/path/PathStringBuilder.java b/src/main/java/org/opentripplanner/raptor/api/path/PathStringBuilder.java index 34a9be91889..4cf13e362fd 100644 --- a/src/main/java/org/opentripplanner/raptor/api/path/PathStringBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/api/path/PathStringBuilder.java @@ -3,11 +3,11 @@ import java.time.ZonedDateTime; import java.util.function.Consumer; import javax.annotation.Nullable; -import org.opentripplanner.framework.lang.OtpNumberFormat; import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; +import org.opentripplanner.raptor.api.model.RaptorValueFormatter; import org.opentripplanner.raptor.spi.RaptorCostCalculator; /** @@ -73,30 +73,32 @@ public PathStringBuilder street(String modeName, ZonedDateTime fromTime, ZonedDa return legSep().text(modeName).time(fromTime, toTime); } - public PathStringBuilder timeAndCostCentiSec(int fromTime, int toTime, int generalizedCost) { - return time(fromTime, toTime).generalizedCostSentiSec(generalizedCost); + public PathStringBuilder c1(int c1) { + if (c1 == RaptorCostCalculator.ZERO_COST) { + return this; + } + return text(RaptorValueFormatter.formatC1(c1)); } - /** Add generalizedCostCentiSec {@link #costCentiSec(int, int, String)} */ - public PathStringBuilder generalizedCostSentiSec(int cost) { - return costCentiSec(cost, RaptorCostCalculator.ZERO_COST, null); + public PathStringBuilder c2(int c2) { + if (c2 == RaptorConstants.NOT_SET) { + return this; + } + return text(RaptorValueFormatter.formatC2(c2)); } - /** - * Add a cost to the string with an optional unit. Try to be consistent with unit naming, use - * lower-case: - *

    - *
  • {@code null} - Generalized-cost (no unit used)
  • - *
  • {@code "wtc"} - Wait-time cost
  • - *
  • {@code "pri"} - Transfer priority cost
  • - *
- */ - public PathStringBuilder costCentiSec(int generalizedCostCents, int defaultValue, String unit) { - if (generalizedCostCents == defaultValue) { + public PathStringBuilder waitTimeCost(int wtc, int defaultValue) { + if (wtc == defaultValue) { + return this; + } + return text(RaptorValueFormatter.formatWaitTimeCost(wtc)); + } + + public PathStringBuilder transferPriority(int transferPriorityCost, int defaultValue) { + if (transferPriorityCost == defaultValue) { return this; } - var costText = OtpNumberFormat.formatCostCenti(generalizedCostCents); - return (unit != null) ? text(costText + unit) : text(costText); + return text(RaptorValueFormatter.formatTransferPriority(transferPriorityCost)); } public PathStringBuilder duration(int duration) { @@ -112,34 +114,34 @@ public PathStringBuilder time(int time) { } public PathStringBuilder numberOfTransfers(int nTransfers) { - return nTransfers != RaptorConstants.NOT_SET ? text(nTransfers + "tx") : this; + return nTransfers != RaptorConstants.NOT_SET + ? text(RaptorValueFormatter.formatNumOfTransfers(nTransfers)) + : this; } - public PathStringBuilder summary(int generalizedCostCents) { - return summaryStart().generalizedCostSentiSec(generalizedCostCents).summaryEnd(); + public PathStringBuilder summary(int c1) { + return summaryStart().c1(c1).summaryEnd(); } - public PathStringBuilder summary( - int startTime, - int endTime, - int nTransfers, - int generalizedCostCents - ) { - return summary(startTime, endTime, nTransfers, generalizedCostCents, null); + public PathStringBuilder summary(int startTime, int endTime, int nTransfers, int c1, int c2) { + return summary(startTime, endTime, nTransfers, c1, c2, null); } public PathStringBuilder summary( int startTime, int endTime, int nTransfers, - int generalizedCostCents, + int c1, + int c2, @Nullable Consumer appendToSummary ) { summaryStart() .time(startTime, endTime) .duration(Math.abs(endTime - startTime)) .numberOfTransfers(nTransfers) - .generalizedCostSentiSec(generalizedCostCents); + .c1(c1) + .c2(c2); + if (appendToSummary != null) { appendToSummary.accept(this); } diff --git a/src/main/java/org/opentripplanner/raptor/api/path/TransferPathLeg.java b/src/main/java/org/opentripplanner/raptor/api/path/TransferPathLeg.java index 4aba197f1ce..8e098add45b 100644 --- a/src/main/java/org/opentripplanner/raptor/api/path/TransferPathLeg.java +++ b/src/main/java/org/opentripplanner/raptor/api/path/TransferPathLeg.java @@ -15,7 +15,7 @@ public final class TransferPathLeg implements Path private final int fromTime; private final int toStop; private final int toTime; - private final int generalizedCost; + private final int c1; private final RaptorTransfer transfer; private final PathLeg next; @@ -23,7 +23,7 @@ public TransferPathLeg( int fromStop, int fromTime, int toTime, - int generalizedCost, + int c1, RaptorTransfer transfer, PathLeg next ) { @@ -31,7 +31,7 @@ public TransferPathLeg( this.fromTime = fromTime; this.toStop = transfer.stop(); this.toTime = toTime; - this.generalizedCost = generalizedCost; + this.c1 = c1; this.transfer = transfer; this.next = next; } @@ -61,8 +61,8 @@ public int toStop() { } @Override - public int generalizedCost() { - return generalizedCost; + public int c1() { + return c1; } @Override diff --git a/src/main/java/org/opentripplanner/raptor/api/path/TransitPathLeg.java b/src/main/java/org/opentripplanner/raptor/api/path/TransitPathLeg.java index e752920b6da..a01fb32a28d 100644 --- a/src/main/java/org/opentripplanner/raptor/api/path/TransitPathLeg.java +++ b/src/main/java/org/opentripplanner/raptor/api/path/TransitPathLeg.java @@ -17,7 +17,7 @@ public final class TransitPathLeg implements PathL private final int boardStopPos; private final int alightStopPos; private final RaptorConstrainedTransfer constrainedTransferAfterLeg; - private final int generalizedCost; + private final int c1; private final PathLeg next; private final int boardStop; private final int alightStop; @@ -29,7 +29,7 @@ public TransitPathLeg( int boardStopPos, int alightStopPos, RaptorConstrainedTransfer constrainedTransferAfterLeg, - int generalizedCost, + int c1, PathLeg next ) { this.trip = trip; @@ -38,7 +38,7 @@ public TransitPathLeg( this.boardStopPos = boardStopPos; this.alightStopPos = alightStopPos; this.constrainedTransferAfterLeg = constrainedTransferAfterLeg; - this.generalizedCost = generalizedCost; + this.c1 = c1; this.next = next; this.boardStop = trip.pattern().stopIndex(boardStopPos); this.alightStop = trip.pattern().stopIndex(alightStopPos); @@ -90,8 +90,8 @@ public int toStop() { } @Override - public int generalizedCost() { - return generalizedCost; + public int c1() { + return c1; } @Override diff --git a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java index 1f0621b8d36..010bff4bc08 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java @@ -183,10 +183,10 @@ public String toString() { .of(RaptorRequest.class) .addEnum("profile", profile) .addBoolIfTrue("reverse", searchDirection.isInReverse()) - .addCol("optimizations", optimizations) + .addCol("optimizations", optimizations, defaults.optimizations()) + .addObj("searchParams", searchParams) .addObj("multiCriteria", multiCriteria, defaults.multiCriteria()) .addObj("debug", debug, defaults.debug()) - .addObj("searchParams", searchParams) .addBoolIfTrue("withPerformanceTimers", performanceTimers != RaptorTimers.NOOP) .toString(); } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java b/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java index 28651104f9d..03911e3eb4c 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java @@ -281,13 +281,18 @@ public boolean equals(Object o) { @Override public String toString() { + var dft = defaults(); return ToStringBuilder .of(SearchParams.class) - .addServiceTime("earliestDepartureTime", earliestDepartureTime, RaptorConstants.TIME_NOT_SET) - .addServiceTime("latestArrivalTime", latestArrivalTime, RaptorConstants.TIME_NOT_SET) - .addDurationSec("searchWindow", searchWindowInSeconds, RaptorConstants.NOT_SET) + .addServiceTime("earliestDepartureTime", earliestDepartureTime, dft.earliestDepartureTime) + .addServiceTime("latestArrivalTime", latestArrivalTime, dft.latestArrivalTime) + .addDurationSec("searchWindow", searchWindowInSeconds, dft.searchWindowInSeconds) .addBoolIfTrue("departAsLateAsPossible", preferLateArrival) - .addNum("numberOfAdditionalTransfers", numberOfAdditionalTransfers, RaptorConstants.NOT_SET) + .addNum( + "numberOfAdditionalTransfers", + numberOfAdditionalTransfers, + dft.numberOfAdditionalTransfers + ) .addCollection("accessPaths", accessPaths, 5, RaptorAccessEgress::defaultToString) .addCollection("egressPaths", egressPaths, 5, RaptorAccessEgress::defaultToString) .toString(); diff --git a/src/main/java/org/opentripplanner/raptor/api/view/ArrivalView.java b/src/main/java/org/opentripplanner/raptor/api/view/ArrivalView.java index cfd47614bfc..862f7f17fb6 100644 --- a/src/main/java/org/opentripplanner/raptor/api/view/ArrivalView.java +++ b/src/main/java/org/opentripplanner/raptor/api/view/ArrivalView.java @@ -1,13 +1,16 @@ package org.opentripplanner.raptor.api.view; +import java.util.function.IntFunction; import javax.annotation.Nullable; -import org.opentripplanner.framework.lang.OtpNumberFormat; import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.raptor.api.model.PathLegType; +import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTransfer; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.model.RaptorValueFormatter; import org.opentripplanner.raptor.api.model.TransitArrival; import org.opentripplanner.raptor.spi.RaptorCostCalculator; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.DefaultCostCalculator; /** * The purpose of the stop-arrival-view is to provide a common interface for stop-arrivals for @@ -80,20 +83,12 @@ default boolean isFirstRound() { */ int c1(); - /** - * Whether the model supports accumulated criteria TWO. If C2 is not supported then calling - * {@link #c2()} will result in an exception. - */ - default boolean supportsC2() { - return false; - } - /** * The accumulated criteria TWO. Can be used for any int criteria used during routing. A * state with c1 and c2 is created dynamically if c2 is in use, if not this method will * throw an exception. *

- * {@link RaptorCostCalculator#ZERO_COST} is returned if no criteria exist, but the model + * {@link RaptorConstants#NOT_SET} is returned if no criteria exist, but the model * support it. */ int c2(); @@ -157,13 +152,13 @@ default EgressPathView egressPath() { boolean arrivedOnBoard(); - /** Use this to easy create a to String implementation. */ + /** Use this to create a {@code toString()} implementation. */ default String asString() { String arrival = "[" + TimeUtils.timeToStrCompact(arrivalTime()) + - " " + - OtpNumberFormat.formatCostCenti(c1()) + + cost(c1(), DefaultCostCalculator.ZERO_COST, RaptorValueFormatter::formatC1) + + cost(c2(), RaptorConstants.NOT_SET, RaptorValueFormatter::formatC2) + "]"; return switch (arrivedBy()) { case ACCESS -> String.format( @@ -195,4 +190,8 @@ default String asString() { ); }; } + + private static String cost(int cost, int defaultValue, IntFunction toString) { + return cost == defaultValue ? "" : " " + toString.apply(cost); + } } diff --git a/src/main/java/org/opentripplanner/raptor/path/Path.java b/src/main/java/org/opentripplanner/raptor/path/Path.java index eb123131089..a4d7770dd64 100644 --- a/src/main/java/org/opentripplanner/raptor/path/Path.java +++ b/src/main/java/org/opentripplanner/raptor/path/Path.java @@ -7,6 +7,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nullable; +import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTransferConstraint; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.path.AccessPathLeg; @@ -28,7 +29,7 @@ public class Path implements RaptorPath { private final int startTime; private final int endTime; private final int numberOfTransfers; - private final int generalizedCost; + private final int c1; private final int c2; private final AccessPathLeg accessLeg; private final EgressPathLeg egressLeg; @@ -39,22 +40,22 @@ private Path( int startTime, int endTime, int numberOfTransfers, - int generalizedCost + int c1 ) { this.iterationDepartureTime = iterationDepartureTime; this.startTime = startTime; this.endTime = endTime; this.numberOfTransfers = numberOfTransfers; - this.generalizedCost = generalizedCost; + this.c1 = c1; this.accessLeg = null; this.egressLeg = null; - this.c2 = 0; + this.c2 = RaptorConstants.NOT_SET; } - public Path(int iterationDepartureTime, AccessPathLeg accessLeg, int generalizedCost, int c2) { + public Path(int iterationDepartureTime, AccessPathLeg accessLeg, int c1, int c2) { this.iterationDepartureTime = iterationDepartureTime; this.startTime = accessLeg.fromTime(); - this.generalizedCost = generalizedCost; + this.c1 = c1; this.accessLeg = accessLeg; this.egressLeg = findEgressLeg(accessLeg); this.numberOfTransfers = countNumberOfTransfers(accessLeg, egressLeg); @@ -62,8 +63,8 @@ public Path(int iterationDepartureTime, AccessPathLeg accessLeg, int generali this.c2 = c2; } - public Path(int iterationDepartureTime, AccessPathLeg accessLeg, int generalizedCost) { - this(iterationDepartureTime, accessLeg, generalizedCost, 0); + public Path(int iterationDepartureTime, AccessPathLeg accessLeg, int c1) { + this(iterationDepartureTime, accessLeg, c1, RaptorConstants.NOT_SET); } /** Copy constructor */ @@ -122,7 +123,7 @@ public final int numberOfTransfersExAccessEgress() { @Override public final int c1() { - return generalizedCost; + return c1; } @Override @@ -240,7 +241,7 @@ protected String buildString( ); if (detailed) { buf.duration(leg.duration()); - buf.generalizedCostSentiSec(leg.generalizedCost()); + buf.c1(leg.c1()); } if (transitLeg.getConstrainedTransferAfterLeg() != null) { constraintPrevLeg = @@ -263,7 +264,7 @@ else if (leg.isEgressLeg()) { } } // Add summary info - buf.summary(startTime, endTime, numberOfTransfers, generalizedCost, appendToSummary); + buf.summary(startTime, endTime, numberOfTransfers, c1, c2, appendToSummary); return buf.toString(); } @@ -293,7 +294,10 @@ private static int countNumberOfTransfers( private void addWalkDetails(boolean detailed, PathStringBuilder buf, PathLeg leg) { if (detailed) { - buf.timeAndCostCentiSec(leg.fromTime(), leg.toTime(), leg.generalizedCost()); + int fromTime = leg.fromTime(); + int toTime = leg.toTime(); + int cost = leg.c1(); + buf.time(fromTime, toTime).c1(cost); } } } diff --git a/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java b/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java index 6dae7ee2c5a..c046a8f240d 100644 --- a/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java @@ -185,7 +185,7 @@ public int c2() { public RaptorPath build() { updateAggregatedFields(); var pathLegs = createPathLegs(costCalculator, slackProvider); - return new Path<>(iterationDepartureTime, pathLegs, pathLegs.generalizedCostTotal(), c2()); + return new Path<>(iterationDepartureTime, pathLegs, pathLegs.c1Total(), c2()); } @Override diff --git a/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java b/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java index eebdf69bfd9..76d49ef4a0a 100644 --- a/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java +++ b/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java @@ -214,18 +214,15 @@ public void timeShiftThisAndNextLeg( *

* This method is safe to use event as long as the next leg is set. */ - public int generalizedCost( - RaptorCostCalculator costCalculator, - RaptorSlackProvider slackProvider - ) { + public int c1(RaptorCostCalculator costCalculator, RaptorSlackProvider slackProvider) { if (costCalculator == null) { return RaptorCostCalculator.ZERO_COST; } if (isAccess()) { - return asAccessLeg().streetPath.generalizedCost(); + return asAccessLeg().streetPath.c1(); } if (isTransfer()) { - return asTransferLeg().transfer.generalizedCost(); + return asTransferLeg().transfer.c1(); } if (isTransit()) { return transitCost(costCalculator, slackProvider); @@ -360,11 +357,11 @@ AccessPathLeg createAccessPathLeg( /* Build helper methods, package local */ private static int cost(RaptorCostCalculator costCalculator, RaptorAccessEgress streetPath) { - return costCalculator != null ? streetPath.generalizedCost() : RaptorCostCalculator.ZERO_COST; + return costCalculator != null ? streetPath.c1() : RaptorCostCalculator.ZERO_COST; } private static int cost(RaptorCostCalculator costCalculator, RaptorTransfer transfer) { - return costCalculator != null ? transfer.generalizedCost() : RaptorCostCalculator.ZERO_COST; + return costCalculator != null ? transfer.c1() : RaptorCostCalculator.ZERO_COST; } private void setTime(int fromTime, int toTime) { diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java index b10783c830f..8c35f103106 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/SystemErrDebugLogger.java @@ -28,7 +28,7 @@ import org.opentripplanner.raptor.rangeraptor.transit.TripTimesSearch; /** - * A debug logger witch can be plugged into Raptor to do debug logging to standard error. This is + * A debug logger which can be plugged into Raptor to do debug logging to standard error. This is * used by the REST API, SpeedTest and in module tests. *

* See the Raptor design doc for a general description of the logging functionality. diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java index 446bca40fc1..b17b3078fc9 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java @@ -32,7 +32,7 @@ public class MultiCriteriaRoutingStrategy patternRideFactory; private final ParetoSet patternRides; private final PassThroughPointsService passThroughPointsService; - private final RaptorCostCalculator generalizedCostCalculator; + private final RaptorCostCalculator c1Calculator; private final SlackProvider slackProvider; public MultiCriteriaRoutingStrategy( @@ -40,7 +40,7 @@ public MultiCriteriaRoutingStrategy( TimeBasedBoardingSupport boardingSupport, PatternRideFactory patternRideFactory, PassThroughPointsService passThroughPointsService, - RaptorCostCalculator generalizedCostCalculator, + RaptorCostCalculator c1Calculator, SlackProvider slackProvider, ParetoSet patternRides ) { @@ -48,7 +48,7 @@ public MultiCriteriaRoutingStrategy( this.boardingSupport = Objects.requireNonNull(boardingSupport); this.patternRideFactory = Objects.requireNonNull(patternRideFactory); this.passThroughPointsService = Objects.requireNonNull(passThroughPointsService); - this.generalizedCostCalculator = Objects.requireNonNull(generalizedCostCalculator); + this.c1Calculator = Objects.requireNonNull(c1Calculator); this.slackProvider = Objects.requireNonNull(slackProvider); this.patternRides = Objects.requireNonNull(patternRides); } @@ -195,7 +195,7 @@ private int calculateCostAtBoardTime( ) { return ( prevArrival.c1() + - generalizedCostCalculator.boardingCost( + c1Calculator.boardingCost( prevArrival.isFirstRound(), prevArrival.arrivalTime(), boardEvent.boardStopIndex(), @@ -214,6 +214,6 @@ private int calculateCostAtBoardTime( * origin in the same iteration, having used the same number-of-rounds to board the same trip. */ private int calculateOnTripRelativeCost(int boardTime, T tripSchedule) { - return generalizedCostCalculator.onTripRelativeRidingCost(boardTime, tripSchedule); + return c1Calculator.onTripRelativeRidingCost(boardTime, tripSchedule); } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/ArrivalParetoSetComparatorFactory.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/ArrivalParetoSetComparatorFactory.java index f7907fa932a..68e103b18db 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/ArrivalParetoSetComparatorFactory.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/ArrivalParetoSetComparatorFactory.java @@ -8,7 +8,7 @@ public interface ArrivalParetoSetComparatorFactory> { /** * This comparator is used to compare regular stop arrivals. It uses {@code arrivalTime}, - * {@code paretoRound} and {@code generalizedCost} to compare arrivals. It does NOT include + * {@code paretoRound} and {@code c1} to compare arrivals. It does NOT include * {@code arrivedOnBoard}. Normally arriving on-board should give the arrival an advantage * - you can continue on foot, walking to the next stop. But, we only do this if it happens * in the same Raptor iteration and round - if it does it is taken care of by the order diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/AccessStopArrival.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/AccessStopArrival.java index be318be0645..e3787d41f78 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/AccessStopArrival.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/AccessStopArrival.java @@ -23,7 +23,7 @@ final class AccessStopArrival extends McStopArriva access.stop(), departureTime, access.durationInSeconds(), - access.generalizedCost(), + access.c1(), access.numberOfRides() ); this.access = access; @@ -31,7 +31,7 @@ final class AccessStopArrival extends McStopArriva @Override public int c2() { - throw new UnsupportedOperationException("C2 is not available for the C1 implementation"); + return RaptorConstants.NOT_SET; } @Override diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransferStopArrival.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransferStopArrival.java index cbe553f8f8f..05c165a158b 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransferStopArrival.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransferStopArrival.java @@ -3,6 +3,7 @@ import static org.opentripplanner.raptor.api.model.PathLegType.TRANSFER; import org.opentripplanner.raptor.api.model.PathLegType; +import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTransfer; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.model.TransitArrival; @@ -25,14 +26,14 @@ final class TransferStopArrival extends McStopArri 1, transferPath.stop(), arrivalTime, - previousState.c1() + transferPath.generalizedCost() + previousState.c1() + transferPath.c1() ); this.transfer = transferPath; } @Override public int c2() { - throw new UnsupportedOperationException("C2 is not available for the C1 implementation"); + return RaptorConstants.NOT_SET; } @Override diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransitStopArrival.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransitStopArrival.java index 60252a679b1..a30581512a7 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransitStopArrival.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransitStopArrival.java @@ -3,6 +3,7 @@ import static org.opentripplanner.raptor.api.model.PathLegType.TRANSIT; import org.opentripplanner.raptor.api.model.PathLegType; +import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.model.TransitArrival; import org.opentripplanner.raptor.api.view.TransitPathView; @@ -36,7 +37,7 @@ final class TransitStopArrival @Override public int c2() { - throw new UnsupportedOperationException("C2 is not available for the C1 implementation"); + return RaptorConstants.NOT_SET; } @Override diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AbstractStopArrivalC2.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AbstractStopArrivalC2.java index 33942592466..067772cb014 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AbstractStopArrivalC2.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AbstractStopArrivalC2.java @@ -49,11 +49,6 @@ abstract class AbstractStopArrivalC2 extends McSto this.c2 = c2; } - @Override - public boolean supportsC2() { - return true; - } - public final int c2() { return c2; } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AccessStopArrivalC2.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AccessStopArrivalC2.java index 674cb7c6522..ed4d9df1415 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AccessStopArrivalC2.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AccessStopArrivalC2.java @@ -24,7 +24,7 @@ final class AccessStopArrivalC2 extends AbstractSt departureTime, access.durationInSeconds(), access.numberOfRides(), - access.generalizedCost(), + access.c1(), RaptorCostCalculator.ZERO_COST ); this.access = access; diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransferStopArrivalC2.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransferStopArrivalC2.java index eb245d1fee9..42ee55ff784 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransferStopArrivalC2.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransferStopArrivalC2.java @@ -21,7 +21,7 @@ final class TransferStopArrivalC2 extends Abstract 1, transferPath.stop(), arrivalTime, - previous.c1() + transferPath.generalizedCost(), + previous.c1() + transferPath.c1(), previous.c2() ); this.transfer = transferPath; diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java index b05b0aeb5b0..649737bc42f 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java @@ -57,7 +57,7 @@ public McRangeRaptorConfig( /** * The PassThroughPointsService is injected into the transit-calculator, so it needs to be - * created before the context(witch create the calculator).So, to be able to do this, this + * created before the context(which create the calculator).So, to be able to do this, this * factory is static, and the service is passed back in when this config is instantiated. */ public static PassThroughPointsService passThroughPointsService( diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrival.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrival.java index 6133bd03952..351a432cc20 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrival.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrival.java @@ -35,18 +35,21 @@ public class DestinationArrival implements Arrival private final int arrivalTime; private final int numberOfTransfers; private final int c1; + private final int c2; public DestinationArrival( RaptorAccessEgress egress, ArrivalView previous, int arrivalTime, - int additionalCost + int additionalC1, + int c2 ) { this.previous = previous; this.egress = egress; this.arrivalTime = arrivalTime; this.numberOfTransfers = previous.round() - 1; - this.c1 = previous.c1() + additionalCost; + this.c1 = previous.c1() + additionalC1; + this.c2 = c2; } @Override @@ -69,14 +72,9 @@ public int c1() { return c1; } - @Override - public boolean supportsC2() { - return false; - } - @Override public int c2() { - return previous.c2(); + return c2; } @Override diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java index 5f0fa376f74..6ed88da4c89 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java @@ -188,7 +188,13 @@ private DestinationArrival createDestinationArrivalView( additionalCost += costCalculator.costEgress(egressPath); } - return new DestinationArrival<>(egressPath, stopArrival, arrivalTime, additionalCost); + return new DestinationArrival<>( + egressPath, + stopArrival, + arrivalTime, + additionalCost, + stopArrival.c2() + ); } /** diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ForwardPathMapper.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ForwardPathMapper.java index 9f7d392c2f0..8e9b77f9cb8 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ForwardPathMapper.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ForwardPathMapper.java @@ -4,7 +4,6 @@ import org.opentripplanner.raptor.api.path.RaptorPath; import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.api.view.ArrivalView; -import org.opentripplanner.raptor.path.Path; import org.opentripplanner.raptor.path.PathBuilder; import org.opentripplanner.raptor.rangeraptor.internalapi.WorkerLifeCycle; import org.opentripplanner.raptor.rangeraptor.transit.TripTimesSearch; @@ -70,9 +69,7 @@ public RaptorPath mapToPath(final DestinationArrival destinationArrival) { arrival = arrival.previous(); } - if (destinationArrival.supportsC2()) { - pathBuilder.c2(destinationArrival.c2()); - } + pathBuilder.c2(destinationArrival.c2()); return pathBuilder.build(); } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ReversePathMapper.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ReversePathMapper.java index 7dd09c2ac92..fde483b4cd8 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ReversePathMapper.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ReversePathMapper.java @@ -65,6 +65,9 @@ public RaptorPath mapToPath(final DestinationArrival destinationArrival) { switch (arrival.arrivedBy()) { case ACCESS: pathBuilder.egress(arrival.accessPath().access()); + + pathBuilder.c2(arrival.c2()); + return pathBuilder.build(); case TRANSIT: var times = tripSearch.find(arrival); diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/heuristics/HeuristicsAdapter.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/heuristics/HeuristicsAdapter.java index 78aab07f21a..ff38fff6d40 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/heuristics/HeuristicsAdapter.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/heuristics/HeuristicsAdapter.java @@ -185,7 +185,7 @@ private static AggregatedResults create( ); for (RaptorAccessEgress it : list) { - // Prevent transfer(walking) and the egress witch start with walking + // Prevent transfer(walking) and the egress which start with walking if (!(it.stopReachedOnBoard() || stopReachedByTransit)) { continue; } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Access.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Access.java index 6344d6d5c8e..e78b9d0a381 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Access.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Access.java @@ -4,6 +4,7 @@ import org.opentripplanner.raptor.api.model.PathLegType; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; +import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.view.AccessPathView; import org.opentripplanner.raptor.api.view.ArrivalView; @@ -29,7 +30,7 @@ public int c1() { @Override public int c2() { - throw new UnsupportedOperationException("C2 is not available for the C1 implementation"); + return RaptorConstants.NOT_SET; } @Override diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/StopsCursor.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/StopsCursor.java index d20f4ba5db8..f72047597a7 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/StopsCursor.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/StopsCursor.java @@ -59,7 +59,7 @@ public Access fictiveAccess(int round, RaptorAccessEgress accessPath, int arr /** * Return a fictive Transfer stop arrival view. The arrival does not exist in the state, but is - * linked with the previous arrival witch is a "real" arrival present in the state. This enables + * linked with the previous arrival which is a "real" arrival present in the state. This enables * path generation. */ public Transfer fictiveTransfer( @@ -76,7 +76,7 @@ public Transfer fictiveTransfer( /** * Return a fictive Transit stop arrival view. The arrival does not exist in the state, but is - * linked with the previous arrival witch is a "real" arrival present in the state. This enables + * linked with the previous arrival which is a "real" arrival present in the state. This enables * path generation. */ public Transit fictiveTransit( diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Transfer.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Transfer.java index a6c1fc7c003..a19a9f1c7e6 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Transfer.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Transfer.java @@ -3,6 +3,7 @@ import static org.opentripplanner.raptor.api.model.PathLegType.TRANSFER; import org.opentripplanner.raptor.api.model.PathLegType; +import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTransfer; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.view.ArrivalView; @@ -27,7 +28,7 @@ public int c1() { @Override public int c2() { - throw new UnsupportedOperationException("C2 is not available for the C1 implementation"); + return RaptorConstants.NOT_SET; } @Override diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Transit.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Transit.java index 4a967642bf4..e38849f6da7 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Transit.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/view/Transit.java @@ -3,6 +3,7 @@ import static org.opentripplanner.raptor.api.model.PathLegType.TRANSIT; import org.opentripplanner.raptor.api.model.PathLegType; +import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.view.ArrivalView; import org.opentripplanner.raptor.api.view.TransitPathView; @@ -29,7 +30,7 @@ public int c1() { @Override public int c2() { - throw new UnsupportedOperationException("C2 is not available for the C1 implementation"); + return RaptorConstants.NOT_SET; } @Override 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 fa4fc57dc84..2038ab543df 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java @@ -54,7 +54,7 @@ public Collection listAll() { } /** - * List all stops with an egress path witch start by walking. These egress paths can only be used + * List all stops with an egress path which start by walking. These egress paths can only be used * if arriving at the stop by transit. */ public int[] egressesWitchStartByWalking() { @@ -62,7 +62,7 @@ public int[] egressesWitchStartByWalking() { } /** - * List all stops with an egress path witch start on-board a "transit" ride. These + * List all stops with an egress path which start on-board a "transit" ride. These * egress paths can be used when arriving at the stop with both transfer or transit. */ public int[] egressesWitchStartByARide() { diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorSearchWindowCalculator.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorSearchWindowCalculator.java index 31608622bc5..bbb5a25c11e 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorSearchWindowCalculator.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorSearchWindowCalculator.java @@ -82,7 +82,7 @@ public RaptorSearchWindowCalculator calculate() { } // TravelWindow is the time from the earliest-departure-time to the latest-arrival-time - int travelWindow = searchWindowSeconds + roundUpToNearestMinute(heuristicMinTransitTime); + int travelWindow = searchWindowSeconds + roundDownToNearestMinute(heuristicMinTransitTime); if (!params.isEarliestDepartureTimeSet()) { earliestDepartureTime = latestArrivalTime - travelWindow; @@ -90,15 +90,14 @@ public RaptorSearchWindowCalculator calculate() { return this; } - int roundUpToNearestMinute(int minTravelTimeInSeconds) { + int roundDownToNearestMinute(int minTravelTimeInSeconds) { if (minTravelTimeInSeconds < 0) { throw new IllegalArgumentException( "This operation is not defined for negative numbers: " + minTravelTimeInSeconds ); } - // See the UnitTest for verification of this: - // We want: 0 -> 0, 1 -> 60, 59 -> 60 ... - return ((minTravelTimeInSeconds + 59) / 60) * 60; + // We want: 0 -> 0, 59 -> 0, 60 -> 60 ... + return (minTravelTimeInSeconds / 60) * 60; } /** diff --git a/src/main/java/org/opentripplanner/raptor/spi/Flyweight.java b/src/main/java/org/opentripplanner/raptor/spi/Flyweight.java index c2b0536c8ab..a3d9ee0db1c 100644 --- a/src/main/java/org/opentripplanner/raptor/spi/Flyweight.java +++ b/src/main/java/org/opentripplanner/raptor/spi/Flyweight.java @@ -6,7 +6,7 @@ import java.lang.annotation.Target; /** - * This interface is used to tag methods witch return flyweight objects. The implementation may + * This interface is used to tag methods which return flyweight objects. The implementation may * choose not to implement the return type as a flyweight object, but the Raptor implementation * is guaranteed to treat them as such - enabling the optimization. *

diff --git a/src/main/java/org/opentripplanner/raptor/spi/RaptorBoardOrAlightEvent.java b/src/main/java/org/opentripplanner/raptor/spi/RaptorBoardOrAlightEvent.java index da01e397c06..5913f950969 100644 --- a/src/main/java/org/opentripplanner/raptor/spi/RaptorBoardOrAlightEvent.java +++ b/src/main/java/org/opentripplanner/raptor/spi/RaptorBoardOrAlightEvent.java @@ -77,7 +77,7 @@ default int boardStopIndex() { /** * This is a helper method for the Raptor implementation to be able to board or execute * a alternativeBoardingFallback method depending on the event. This logic should ideally - * be put inside raptor, but due to performance(creating lambda instances, witch for some + * be put inside raptor, but due to performance(creating lambda instances, which for some * reason is not inlined) this need to be here. *

* @param boardCallback perform boarding if the event in none empty (or some other special diff --git a/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java b/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java index bd1b91d46d7..d575a0e4d8c 100644 --- a/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java +++ b/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java @@ -12,7 +12,7 @@ */ public interface RaptorCostCalculator { /** - * The cost is zero(0) it it is not calculated or if the cost "element" have no cost associated + * The cost is zero (0) if it's not calculated or if the cost "element" have no cost associated. * with it. */ int ZERO_COST = 0; @@ -63,7 +63,7 @@ int boardingCost( * This method allows the cost calculator to add cost in addition to the generalized-cost of the * given egress itself. For example you might want to add a transfer cost to FLEX egress. * - * @return the {@link RaptorTransfer#generalizedCost()} plus any additional board or transfer + * @return the {@link RaptorTransfer#c1()} plus any additional board or transfer * cost. */ int costEgress(RaptorAccessEgress egress); diff --git a/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java b/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java index f1b0a5e0c25..b95417e86b6 100644 --- a/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java +++ b/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.stream.Stream; +import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.path.AccessPathLeg; import org.opentripplanner.raptor.api.path.EgressPathLeg; @@ -72,7 +73,7 @@ public int c1() { @Override public int c2() { - return RaptorCostCalculator.ZERO_COST; + return RaptorConstants.NOT_SET; } @Override @@ -126,7 +127,7 @@ public String toString() { if (departureTime == 0 && arrivalTime == 0) { pathBuilder.summary(c1()); } else { - pathBuilder.summary(startTime(), endTime(), numberOfTransfers, c1()); + pathBuilder.summary(startTime(), endTime(), numberOfTransfers, c1(), c2()); } return pathBuilder.toString(); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java b/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java index 24442311617..ad2c4c92639 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java @@ -16,11 +16,11 @@ import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.framework.time.ServiceDateUtils; import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.model.plan.PagingSearchWindowAdjuster; 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; import org.opentripplanner.routing.algorithm.raptoradapter.router.AdditionalSearchDays; @@ -28,13 +28,13 @@ import org.opentripplanner.routing.algorithm.raptoradapter.router.TransitRouter; import org.opentripplanner.routing.algorithm.raptoradapter.router.street.DirectFlexRouter; import org.opentripplanner.routing.algorithm.raptoradapter.router.street.DirectStreetRouter; -import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitTuningParameters; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.api.response.RoutingError; import org.opentripplanner.routing.api.response.RoutingResponse; import org.opentripplanner.routing.error.RoutingValidationException; import org.opentripplanner.routing.framework.DebugTimingAggregator; +import org.opentripplanner.service.paging.PagingService; import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,8 +49,7 @@ public class RoutingWorker { private static final Logger LOG = LoggerFactory.getLogger(RoutingWorker.class); /** An object that accumulates profiling and debugging info for inclusion in the response. */ - public final DebugTimingAggregator debugTimingAggregator; - public final PagingSearchWindowAdjuster pagingSearchWindowAdjuster; + private final DebugTimingAggregator debugTimingAggregator; private final RouteRequest request; private final OtpServerRequestContext serverContext; @@ -78,11 +77,6 @@ public RoutingWorker(OtpServerRequestContext serverContext, RouteRequest request request.preferences().system().tags() ); this.transitSearchTimeZero = ServiceDateUtils.asStartOfService(request.dateTime(), zoneId); - this.pagingSearchWindowAdjuster = - createPagingSearchWindowAdjuster( - serverContext.transitTuningParameters(), - serverContext.raptorTuningParameters() - ); this.additionalSearchDays = createAdditionalSearchDays(serverContext.raptorTuningParameters(), zoneId, request); } @@ -165,18 +159,17 @@ public RoutingResponse route() { // Adjust the search-window for the next search if the current search-window // is off (too few or too many results found). - var searchWindowNextSearch = calculateSearchWindowNextSearch(filteredItineraries); + + var pagingService = createPagingService(itineraries); return RoutingResponseMapper.map( request, - transitSearchTimeZero, raptorSearchParamsUsed, - searchWindowNextSearch, - numItinerariesFilterResults, filteredItineraries, routingErrors, debugTimingAggregator, - serverContext.transitService() + serverContext.transitService(), + pagingService ); } @@ -197,27 +190,6 @@ private static AdditionalSearchDays createAdditionalSearchDays( ); } - /** - * Filter itineraries away that depart after the latest-departure-time for depart after search. - * These itineraries are a result of time-shifting the access leg and is needed for the raptor to - * prune the results. These itineraries are often not ideal, but if they pareto optimal for the - * "next" window, they will appear when a "next" search is performed. - */ - private Instant filterOnLatestDepartureTime() { - if ( - !request.arriveBy() && - raptorSearchParamsUsed != null && - raptorSearchParamsUsed.isSearchWindowSet() && - raptorSearchParamsUsed.isEarliestDepartureTimeSet() - ) { - int ldt = - raptorSearchParamsUsed.earliestDepartureTime() + - raptorSearchParamsUsed.searchWindowInSeconds(); - return transitSearchTimeZero.plusSeconds(ldt).toInstant(); - } - return null; - } - /** * Calculate the earliest-departure-time used in the transit search. * This method returns {@code null} if no transit search is performed. @@ -300,53 +272,19 @@ private Void routeTransit(List itineraries, Collection return null; } - private Duration calculateSearchWindowNextSearch(List itineraries) { - // No transit search performed - if (raptorSearchParamsUsed == null) { - return null; - } - - var sw = Duration.ofSeconds(raptorSearchParamsUsed.searchWindowInSeconds()); - - // SearchWindow cropped -> decrease search-window - if (numItinerariesFilterResults != null) { - Instant swStartTime = searchStartTime() - .plusSeconds(raptorSearchParamsUsed.earliestDepartureTime()); - boolean cropSWHead = request.doCropSearchWindowAtTail(); - Instant rmItineraryStartTime = numItinerariesFilterResults.firstRemovedDepartureTime; - - return pagingSearchWindowAdjuster.decreaseSearchWindow( - sw, - swStartTime, - rmItineraryStartTime, - cropSWHead - ); - } - // (num-of-itineraries found <= numItineraries) -> increase or keep search-window - else { - int nRequested = request.numItineraries(); - int nFound = (int) itineraries - .stream() - .filter(it -> !it.isFlaggedForDeletion() && it.hasTransit()) - .count(); - - return pagingSearchWindowAdjuster.increaseOrKeepSearchWindow(sw, nRequested, nFound); - } - } - private Instant searchStartTime() { return transitSearchTimeZero.toInstant(); } - private PagingSearchWindowAdjuster createPagingSearchWindowAdjuster( - TransitTuningParameters transitTuningParameters, - RaptorTuningParameters raptorTuningParameters - ) { - var c = raptorTuningParameters.dynamicSearchWindowCoefficients(); - return new PagingSearchWindowAdjuster( - c.minWindow(), - c.maxWindow(), - transitTuningParameters.pagingSearchWindowAdjustments() + private PagingService createPagingService(List itineraries) { + return PagingServiceFactory.createPagingService( + searchStartTime(), + serverContext.transitTuningParameters(), + serverContext.raptorTuningParameters(), + request, + raptorSearchParamsUsed, + numItinerariesFilterResults, + itineraries ); } } 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 1c5583ce0bd..872bad6b4ae 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java @@ -12,12 +12,14 @@ import java.util.function.Function; import javax.annotation.Nullable; import org.opentripplanner.ext.accessibilityscore.AccessibilityScoreFilter; +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.pagecursor.ItineraryPageCut; 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; @@ -59,7 +61,7 @@ public class ItineraryListFilterChainBuilder { private ItineraryFilterDebugProfile debug = ItineraryFilterDebugProfile.OFF; private int maxNumberOfItineraries = NOT_SET; - private ListSection maxNumberOfItinerariesCrop = ListSection.TAIL; + private ListSection maxNumberOfItinerariesCropSection = ListSection.TAIL; private CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly; private boolean removeWalkAllTheWayResults; private boolean sameFirstOrLastTripFilter; @@ -77,7 +79,7 @@ public class ItineraryListFilterChainBuilder { private boolean removeItinerariesWithSameRoutesAndStops; private double minBikeParkingDistance; private boolean removeTransitIfWalkingIsBetter = true; - private ItineraryPageCut itineraryPageCut; + private ItinerarySortKey itineraryPageCut; /** * Sandbox filters which decorate the itineraries with extra information. @@ -103,7 +105,7 @@ public ItineraryListFilterChainBuilder(SortOrder sortOrder) { * The maximum number of itineraries returned. This will remove all itineraries at the end of the * list AFTER the final sort of the itineraries. *

- * Se also the {@link #withMaxNumberOfItinerariesCrop(ListSection)} to change which end of the + * Se also the {@link #withMaxNumberOfItinerariesCropSection(ListSection)} to change which end of the * list is cropped. *

* Use {@code -1} to disable. @@ -121,8 +123,10 @@ public ItineraryListFilterChainBuilder withMaxNumberOfItineraries(int value) { * The default is to crop the tail. But, we need to crop the head to be able to paginate in the * opposite direction of the main sort-order of the original search. */ - public ItineraryListFilterChainBuilder withMaxNumberOfItinerariesCrop(ListSection section) { - this.maxNumberOfItinerariesCrop = section; + public ItineraryListFilterChainBuilder withMaxNumberOfItinerariesCropSection( + ListSection section + ) { + this.maxNumberOfItinerariesCropSection = section; return this; } @@ -279,7 +283,7 @@ public ItineraryListFilterChainBuilder withNumItinerariesFilterResultsConsumer( * @param itineraryPageCut contains the parameters to use for deduplication. */ public ItineraryListFilterChainBuilder withPagingDeduplicationFilter( - ItineraryPageCut itineraryPageCut + ItinerarySortKey itineraryPageCut ) { this.itineraryPageCut = itineraryPageCut; return this; @@ -350,10 +354,6 @@ public ItineraryListFilterChainBuilder withStopConsolidationFilter( public ItineraryListFilterChain build() { List filters = new ArrayList<>(); - if (itineraryPageCut != null) { - filters.add(new DeletionFlaggingFilter(new PagingFilter(itineraryPageCut))); - } - filters.addAll(buildGroupByTripIdAndDistanceFilters()); if (removeItinerariesWithSameRoutesAndStops) { @@ -362,7 +362,7 @@ public ItineraryListFilterChain build() { if (sameFirstOrLastTripFilter) { filters.add(new SortingFilter(generalizedCostComparator())); - filters.add(new DeletionFlaggingFilter(new SameFirstOrLastTripFilter())); + addRmFilter(filters, new SameFirstOrLastTripFilter()); } if (minBikeParkingDistance > 0) { @@ -389,23 +389,18 @@ public ItineraryListFilterChain build() { // Filter transit itineraries on generalized-cost if (transitGeneralizedCostFilterParams != null) { - filters.add( - new DeletionFlaggingFilter( - new TransitGeneralizedCostFilter( - transitGeneralizedCostFilterParams.costLimitFunction(), - transitGeneralizedCostFilterParams.intervalRelaxFactor() - ) + addRmFilter( + filters, + new TransitGeneralizedCostFilter( + transitGeneralizedCostFilterParams.costLimitFunction(), + transitGeneralizedCostFilterParams.intervalRelaxFactor() ) ); } // Filter non-transit itineraries on generalized-cost if (nonTransitGeneralizedCostLimit != null) { - filters.add( - new DeletionFlaggingFilter( - new NonTransitGeneralizedCostFilter(nonTransitGeneralizedCostLimit) - ) - ); + addRmFilter(filters, new NonTransitGeneralizedCostFilter(nonTransitGeneralizedCostLimit)); } // Apply all absolute filters AFTER the groupBy filters. Absolute filters are filters that @@ -420,60 +415,61 @@ public ItineraryListFilterChain build() { { // Filter transit itineraries by comparing against non-transit using generalized-cost if (removeTransitWithHigherCostThanBestOnStreetOnly != null) { - filters.add( - new DeletionFlaggingFilter( - new RemoveTransitIfStreetOnlyIsBetterFilter( - removeTransitWithHigherCostThanBestOnStreetOnly - ) + addRmFilter( + filters, + new RemoveTransitIfStreetOnlyIsBetterFilter( + removeTransitWithHigherCostThanBestOnStreetOnly ) ); } if (removeTransitIfWalkingIsBetter) { - filters.add(new DeletionFlaggingFilter(new RemoveTransitIfWalkingIsBetterFilter())); + addRmFilter(filters, new RemoveTransitIfWalkingIsBetterFilter()); } if (removeWalkAllTheWayResults) { - filters.add(new DeletionFlaggingFilter(new RemoveWalkOnlyFilter())); - } - - if (earliestDepartureTime != null) { - filters.add( - new DeletionFlaggingFilter( - new OutsideSearchWindowFilter(earliestDepartureTime, searchWindow) - ) - ); + addRmFilter(filters, new RemoveWalkOnlyFilter()); } if (bikeRentalDistanceRatio > 0) { - filters.add( - new DeletionFlaggingFilter( - new RemoveBikerentalWithMostlyWalkingFilter(bikeRentalDistanceRatio) - ) - ); + addRmFilter(filters, new RemoveBikerentalWithMostlyWalkingFilter(bikeRentalDistanceRatio)); } if (parkAndRideDurationRatio > 0) { - filters.add( - new DeletionFlaggingFilter( - new RemoveParkAndRideWithMostlyWalkingFilter(parkAndRideDurationRatio) - ) + addRmFilter( + filters, + new RemoveParkAndRideWithMostlyWalkingFilter(parkAndRideDurationRatio) ); } } - // Remove itineraries if max limit is set - if (maxNumberOfItineraries > 0) { - filters.add(new SortingFilter(SortOrderComparator.comparator(sortOrder))); - filters.add( - new DeletionFlaggingFilter( + // 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. + { + // Limit to search-window + if (earliestDepartureTime != null) { + addRmFilter(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)); + } + + // Remove itineraries if max limit is set + if (maxNumberOfItineraries > 0) { + filters.add(new SortingFilter(SortOrderComparator.comparator(sortOrder))); + addRmFilter( + filters, new NumItinerariesFilter( maxNumberOfItineraries, - maxNumberOfItinerariesCrop, + maxNumberOfItinerariesCropSection, numItinerariesFilterResultsConsumer ) - ) - ); + ); + } } // Do the final itineraries sort @@ -572,13 +568,12 @@ private List buildGroupByTripIdAndDistanceFilters() { if (group.maxCostOtherLegsFactor > 1.0) { var flagger = new OtherThanSameLegsMaxGeneralizedCostFilter(group.maxCostOtherLegsFactor); sysTags.add(flagger.name()); - nested.add(new DeletionFlaggingFilter(flagger)); + addRmFilter(nested, flagger); } nested.add(new SortingFilter(generalizedCostComparator())); - nested.add( - new DeletionFlaggingFilter(new MaxLimitFilter(tag, group.maxNumOfItinerariesPerGroup)) - ); + + addRmFilter(nested, new MaxLimitFilter(tag, group.maxNumOfItinerariesPerGroup)); nested.add(new RemoveDeletionFlagForLeastTransfersItinerary(sysTags)); @@ -589,4 +584,15 @@ private List buildGroupByTripIdAndDistanceFilters() { return groupByFilters; } + + private ListSection deduplicateSection() { + return maxNumberOfItinerariesCropSection.invert(); + } + + private static void addRmFilter( + List filters, + ItineraryDeletionFlagger removeFilter + ) { + filters.add(new DeletionFlaggingFilter(removeFilter)); + } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparator.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparator.java index c48b6f0bd5d..e52e53ab1c7 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparator.java @@ -101,12 +101,9 @@ public static SortOrderComparator numberOfTransfersComparator() { } public static SortOrderComparator comparator(SortOrder sortOrder) { - switch (sortOrder) { - case STREET_AND_ARRIVAL_TIME: - return STREET_AND_ARRIVAL_TIME; - case STREET_AND_DEPARTURE_TIME: - return STREET_AND_DEPARTURE_TIME; - } - throw new IllegalArgumentException(); + return switch (sortOrder) { + case STREET_AND_ARRIVAL_TIME -> STREET_AND_ARRIVAL_TIME; + case STREET_AND_DEPARTURE_TIME -> STREET_AND_DEPARTURE_TIME; + }; } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/ItineraryDeletionFlagger.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/ItineraryDeletionFlagger.java index 3f88179d65c..c6db9c14cf6 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/ItineraryDeletionFlagger.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/ItineraryDeletionFlagger.java @@ -1,5 +1,6 @@ package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; +import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -53,4 +54,16 @@ default List flagForRemoval(List itineraries) { default boolean skipAlreadyFlaggedItineraries() { return true; } + + /** + * Filter given {@code input} and remove itineraries which should be flagged for removal. This + * can be used in unit-tests - either testing the filter or using the filter in a test. + *

+ * This method should be used in unit-tests only. + */ + default List removeMatchesForTest(List input) { + var res = new ArrayList<>(input); + res.removeAll(flagForRemoval(input)); + return res; + } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilter.java index eb9874ad8ce..b939dd8ca68 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilter.java @@ -2,8 +2,8 @@ import java.util.List; import java.util.function.Consumer; +import org.opentripplanner.framework.collection.ListSection; import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.routing.algorithm.filterchain.ListSection; /** * Flag all itineraries after the provided limit. This flags the itineraries at the end of the list diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilterResults.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilterResults.java index e41ca954fe3..7226c19535b 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilterResults.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilterResults.java @@ -2,25 +2,19 @@ import java.time.Instant; import java.util.List; +import org.opentripplanner.framework.collection.ListSection; +import org.opentripplanner.framework.collection.ListUtils; import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.model.plan.pagecursor.PageCursorInput; -import org.opentripplanner.model.plan.pagecursor.PagingDeduplicationSection; -import org.opentripplanner.routing.algorithm.filterchain.ListSection; +import org.opentripplanner.model.plan.ItinerarySortKey; +import org.opentripplanner.model.plan.paging.cursor.PageCursorInput; public class NumItinerariesFilterResults implements PageCursorInput { - public final Instant earliestRemovedDeparture; - public final Instant latestRemovedDeparture; - public final Instant earliestRemovedArrival; - public final Instant latestRemovedArrival; - public final Instant earliestKeptArrival; - public final Instant firstRemovedArrivalTime; - public final boolean firstRemovedIsOnStreetAllTheWay; - public final int firstRemovedGeneralizedCost; - public final int firstRemovedNumOfTransfers; - public final Instant firstRemovedDepartureTime; - public final ListSection cropSection; + 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 @@ -38,105 +32,40 @@ public NumItinerariesFilterResults( .stream() .map(it -> it.startTime().toInstant()) .toList(); - List removedArrivals = removedItineraries - .stream() - .map(it -> it.endTime().toInstant()) - .toList(); this.earliestRemovedDeparture = removedDepartures.stream().min(Instant::compareTo).orElse(null); this.latestRemovedDeparture = removedDepartures.stream().max(Instant::compareTo).orElse(null); - this.earliestRemovedArrival = removedArrivals.stream().min(Instant::compareTo).orElse(null); - this.latestRemovedArrival = removedArrivals.stream().max(Instant::compareTo).orElse(null); - - this.earliestKeptArrival = - keptItineraries - .stream() - .map(it -> it.endTime().toInstant()) - .min(Instant::compareTo) - .orElseThrow(); - Itinerary firstRemovedItinerary; if (cropSection == ListSection.HEAD) { - firstRemovedItinerary = removedItineraries.get(removedItineraries.size() - 1); + pageCut = ListUtils.first(keptItineraries); } else { - firstRemovedItinerary = removedItineraries.get(0); + pageCut = ListUtils.last(keptItineraries); } - - this.firstRemovedIsOnStreetAllTheWay = firstRemovedItinerary.isOnStreetAllTheWay(); - this.firstRemovedArrivalTime = firstRemovedItinerary.endTime().toInstant(); - this.firstRemovedGeneralizedCost = firstRemovedItinerary.getGeneralizedCost(); - this.firstRemovedNumOfTransfers = firstRemovedItinerary.getNumberOfTransfers(); - this.firstRemovedDepartureTime = firstRemovedItinerary.startTime().toInstant(); - this.cropSection = cropSection; } - @Override - public String toString() { - return ToStringBuilder - .of(NumItinerariesFilterResults.class) - .addDateTime("earliestRemovedDeparture", earliestRemovedDeparture) - .addDateTime("latestRemovedDeparture", latestRemovedDeparture) - .addDateTime("earliestRemovedArrival", earliestRemovedArrival) - .addDateTime("latestRemovedArrival", latestRemovedArrival) - .addDateTime("earliestKeptArrival", earliestKeptArrival) - .addDateTime("firstRemovedArrivalTime", firstRemovedArrivalTime) - .addNum("firstRemovedGeneralizedCost", firstRemovedGeneralizedCost) - .addNum("firstRemovedNumOfTransfers", firstRemovedNumOfTransfers) - .addDateTime("firstRemovedDepartureTime", firstRemovedDepartureTime) - .addEnum("cropSection", cropSection) - .toString(); - } - @Override public Instant earliestRemovedDeparture() { return earliestRemovedDeparture; } - @Override - public Instant earliestKeptArrival() { - return earliestKeptArrival; - } - @Override public Instant latestRemovedDeparture() { return latestRemovedDeparture; } @Override - public Instant latestRemovedArrival() { - return latestRemovedArrival; - } - - @Override - public Instant firstRemovedArrivalTime() { - return firstRemovedArrivalTime; - } - - @Override - public boolean firstRemovedIsOnStreetAllTheWay() { - return firstRemovedIsOnStreetAllTheWay; - } - - @Override - public int firstRemovedGeneralizedCost() { - return firstRemovedGeneralizedCost; - } - - @Override - public int firstRemovedNumOfTransfers() { - return firstRemovedNumOfTransfers; + public ItinerarySortKey pageCut() { + return pageCut; } @Override - public Instant firstRemovedDepartureTime() { - return firstRemovedDepartureTime; - } - - @Override - public PagingDeduplicationSection deduplicationSection() { - return switch (cropSection) { - case HEAD -> PagingDeduplicationSection.TAIL; - case TAIL -> PagingDeduplicationSection.HEAD; - }; + public String toString() { + return ToStringBuilder + .of(NumItinerariesFilterResults.class) + .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/deletionflagger/OutsideSearchWindowFilter.java index abbcb7a28ee..dc189efbc33 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OutsideSearchWindowFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OutsideSearchWindowFilter.java @@ -11,8 +11,8 @@ * should appear again when paging to the next page. Hence, this filter will remove * such itineraries. The same is true for when paging to the previous page for arriveBy=true. *

- * Itineraries matching the start(earliest-departure-time) and end(latest-departure-time) - * of the search-window are included [inclusive, inclusive]. + * 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 { @@ -35,7 +35,7 @@ public String name() { public Predicate shouldBeFlaggedForRemoval() { return it -> { var time = it.startTime().toInstant(); - return time.isBefore(earliestDepartureTime) || time.isAfter(latestDepartureTime); + return time.isBefore(earliestDepartureTime) || !time.isBefore(latestDepartureTime); }; } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/PagingFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/PagingFilter.java index f15e683d4f7..893a239b46b 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/PagingFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/PagingFilter.java @@ -3,9 +3,10 @@ import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; +import org.opentripplanner.framework.collection.ListSection; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.ItinerarySortKey; -import org.opentripplanner.model.plan.pagecursor.ItineraryPageCut; +import org.opentripplanner.model.plan.SortOrder; import org.opentripplanner.routing.algorithm.filterchain.comparator.SortOrderComparator; /** @@ -22,12 +23,18 @@ public class PagingFilter implements ItineraryDeletionFlagger { public static final String TAG = "paging-filter"; - private final ItineraryPageCut itineraryPageCut; + private final ListSection deduplicateSection; + private final ItinerarySortKey itineraryPageCut; private final Comparator sortOrderComparator; - public PagingFilter(ItineraryPageCut itineraryPageCut) { + public PagingFilter( + SortOrder sortOrder, + ListSection deduplicateSection, + ItinerarySortKey itineraryPageCut + ) { + this.deduplicateSection = deduplicateSection; this.itineraryPageCut = itineraryPageCut; - this.sortOrderComparator = SortOrderComparator.comparator(itineraryPageCut.sortOrder()); + this.sortOrderComparator = SortOrderComparator.comparator(sortOrder); } @Override @@ -36,9 +43,9 @@ public String name() { } private boolean sortsIntoDeduplicationAreaRelativeToRemovedItinerary(Itinerary itinerary) { - return switch (itineraryPageCut.deduplicationSection()) { - case HEAD -> sortOrderComparator.compare(itinerary, itineraryPageCut) < 0; - case TAIL -> sortOrderComparator.compare(itinerary, itineraryPageCut) > 0; + return switch (deduplicateSection) { + case HEAD -> sortOrderComparator.compare(itinerary, itineraryPageCut) <= 0; + case TAIL -> sortOrderComparator.compare(itinerary, itineraryPageCut) >= 0; }; } @@ -46,13 +53,7 @@ private boolean sortsIntoDeduplicationAreaRelativeToRemovedItinerary(Itinerary i public List flagForRemoval(List itineraries) { return itineraries .stream() - .filter(it -> - ( - it.startTime().toInstant().isAfter(itineraryPageCut.windowStart()) && - it.startTime().toInstant().isBefore(itineraryPageCut.windowEnd()) && - sortsIntoDeduplicationAreaRelativeToRemovedItinerary(it) - ) - ) + .filter(this::sortsIntoDeduplicationAreaRelativeToRemovedItinerary) .collect(Collectors.toList()); } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/PagingServiceFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/PagingServiceFactory.java new file mode 100644 index 00000000000..8b122e6cf24 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/PagingServiceFactory.java @@ -0,0 +1,61 @@ +package org.opentripplanner.routing.algorithm.mapping; + +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import org.opentripplanner.model.plan.Itinerary; +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; + +public class PagingServiceFactory { + + public static PagingService createPagingService( + Instant searchStartTime, + TransitTuningParameters transitTuningParameters, + RaptorTuningParameters raptorTuningParameters, + RouteRequest request, + SearchParams raptorSearchParamsUsed, + NumItinerariesFilterResults numItinerariesFilterResults, + List itineraries + ) { + return new PagingService( + transitTuningParameters.pagingSearchWindowAdjustments(), + raptorTuningParameters.dynamicSearchWindowCoefficients().minWindow(), + raptorTuningParameters.dynamicSearchWindowCoefficients().maxWindow(), + searchWindowOf(raptorSearchParamsUsed), + edt(searchStartTime, raptorSearchParamsUsed), + lat(searchStartTime, raptorSearchParamsUsed), + request.itinerariesSortOrder(), + request.arriveBy(), + request.numItineraries(), + request.pageCursor(), + numItinerariesFilterResults, + itineraries + ); + } + + static Duration searchWindowOf(SearchParams searchParamsUsed) { + if (searchParamsUsed == null || !searchParamsUsed.isSearchWindowSet()) { + return null; + } + return Duration.ofSeconds(searchParamsUsed.searchWindowInSeconds()); + } + + static Instant edt(Instant transitSearchStartTime, SearchParams searchParamsUsed) { + if (searchParamsUsed == null || !searchParamsUsed.isEarliestDepartureTimeSet()) { + return null; + } + return transitSearchStartTime.plusSeconds(searchParamsUsed.earliestDepartureTime()); + } + + static Instant lat(Instant transitSearchStartTime, SearchParams searchParamsUsed) { + if (searchParamsUsed == null || !searchParamsUsed.isLatestArrivalTimeSet()) { + return null; + } + return transitSearchStartTime.plusSeconds(searchParamsUsed.latestArrivalTime()); + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java index 61ac1dd5cc1..a9b042083bf 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java @@ -191,7 +191,7 @@ private Leg mapTransitLeg(Leg prevTransitLeg, TransitPathLeg pathLeg) { int lastLegCost = 0; PathLeg nextLeg = pathLeg.nextLeg(); if (nextLeg.isEgressLeg() && isFree(nextLeg.asEgressLeg())) { - lastLegCost = pathLeg.nextLeg().generalizedCost(); + lastLegCost = pathLeg.nextLeg().c1(); } // Find stop positions in pattern where this leg boards and alights. @@ -221,7 +221,7 @@ private Leg mapTransitLeg(Leg prevTransitLeg, TransitPathLeg pathLeg) { (prevTransitLeg == null ? null : prevTransitLeg.getTransferToNextLeg()) ) .withTransferToNextLeg((ConstrainedTransfer) pathLeg.getConstrainedTransferAfterLeg()) - .withGeneralizedCost(toOtpDomainCost(pathLeg.generalizedCost() + lastLegCost)) + .withGeneralizedCost(toOtpDomainCost(pathLeg.c1() + lastLegCost)) .withFrequencyHeadwayInSeconds(frequencyHeadwayInSeconds) .build(); } @@ -242,7 +242,7 @@ private Leg mapTransitLeg(Leg prevTransitLeg, TransitPathLeg pathLeg) { (prevTransitLeg == null ? null : prevTransitLeg.getTransferToNextLeg()) ) .withTransferToNextLeg((ConstrainedTransfer) pathLeg.getConstrainedTransferAfterLeg()) - .withGeneralizedCost(toOtpDomainCost(pathLeg.generalizedCost() + lastLegCost)) + .withGeneralizedCost(toOtpDomainCost(pathLeg.c1() + lastLegCost)) .build(); } @@ -308,7 +308,7 @@ private List mapNonTransitLeg( .withFrom(from) .withTo(to) .withDistanceMeters(transfer.getDistanceMeters()) - .withGeneralizedCost(toOtpDomainCost(pathLeg.generalizedCost())) + .withGeneralizedCost(toOtpDomainCost(pathLeg.c1())) .withGeometry(GeometryUtils.makeLineString(transfer.getCoordinates())) .withWalkSteps(List.of()) .build() diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java index 5c105f4804a..b379480bc33 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java @@ -4,13 +4,14 @@ import java.time.Instant; import java.util.List; import java.util.function.Consumer; +import org.opentripplanner.ext.emissions.EmissionsFilter; import org.opentripplanner.ext.fares.FaresFilter; import org.opentripplanner.ext.ridehailing.RideHailingFilter; import org.opentripplanner.ext.stopconsolidation.ConsolidatedStopNameFilter; +import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.routing.algorithm.filterchain.GroupBySimilarity; import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChain; import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChainBuilder; -import org.opentripplanner.routing.algorithm.filterchain.ListSection; import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NumItinerariesFilterResults; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; @@ -42,7 +43,7 @@ public static ItineraryListFilterChain createFilterChain( // The page cursor has deduplication information only in certain cases. if (request.pageCursor() != null && request.pageCursor().containsItineraryPageCut()) { - builder = builder.withPagingDeduplicationFilter(request.pageCursor().itineraryPageCut); + builder = builder.withPagingDeduplicationFilter(request.pageCursor().itineraryPageCut()); } ItineraryFilterPreferences params = request.preferences().itineraryFilter(); @@ -64,12 +65,9 @@ public static ItineraryListFilterChain createFilterChain( ); } - if (request.maxNumberOfItinerariesCropHead()) { - builder.withMaxNumberOfItinerariesCrop(ListSection.HEAD); - } - builder .withMaxNumberOfItineraries(Math.min(request.numItineraries(), MAX_NUMBER_OF_ITINERARIES)) + .withMaxNumberOfItinerariesCropSection(request.cropItinerariesAt()) .withTransitGeneralizedCostLimit(params.transitGeneralizedCostLimit()) .withBikeRentalDistanceRatio(params.bikeRentalDistanceRatio()) .withParkAndRideDurationRatio(params.parkAndRideDurationRatio()) @@ -107,6 +105,10 @@ public static ItineraryListFilterChain createFilterChain( ); } + if (OTPFeature.Co2Emissions.isOn() && context.emissionsService() != null) { + builder.withEmissions(new EmissionsFilter(context.emissionsService())); + } + if (context.stopConsolidationService() != null) { builder.withStopConsolidationFilter( new ConsolidatedStopNameFilter(context.stopConsolidationService()) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RoutingResponseMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RoutingResponseMapper.java index 89263b1b7a4..94b8c792d95 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RoutingResponseMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RoutingResponseMapper.java @@ -2,26 +2,17 @@ import static org.opentripplanner.ext.realtimeresolver.RealtimeResolver.populateLegsWithRealtime; -import java.time.Duration; -import java.time.Instant; -import java.time.ZonedDateTime; import java.util.List; -import java.util.Objects; import java.util.Set; -import javax.annotation.Nullable; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.model.plan.SortOrder; -import org.opentripplanner.model.plan.pagecursor.PageCursor; -import org.opentripplanner.model.plan.pagecursor.PageCursorFactory; -import org.opentripplanner.model.plan.pagecursor.PageType; +import org.opentripplanner.model.plan.paging.cursor.PageCursor; import org.opentripplanner.raptor.api.request.SearchParams; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NumItinerariesFilterResults; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.response.RoutingError; import org.opentripplanner.routing.api.response.RoutingResponse; -import org.opentripplanner.routing.api.response.TripSearchMetadata; import org.opentripplanner.routing.framework.DebugTimingAggregator; +import org.opentripplanner.service.paging.PagingService; import org.opentripplanner.transit.service.TransitService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,14 +23,12 @@ public class RoutingResponseMapper { public static RoutingResponse map( RouteRequest request, - ZonedDateTime transitSearchTimeZero, SearchParams raptorSearchParamsUsed, - Duration searchWindowForNextSearch, - NumItinerariesFilterResults numItinerariesFilterResults, List itineraries, Set routingErrors, DebugTimingAggregator debugTimingAggregator, - TransitService transitService + TransitService transitService, + PagingService pagingService ) { // Search is performed without realtime, but we still want to // include realtime information in the result @@ -52,32 +41,15 @@ public static RoutingResponse map( // Create response var tripPlan = TripPlanMapper.mapTripPlan(request, itineraries); - var factory = mapIntoPageCursorFactory( - request.itinerariesSortOrder(), - transitSearchTimeZero, - raptorSearchParamsUsed, - searchWindowForNextSearch, - numItinerariesFilterResults, - request.pageCursor() == null ? null : request.pageCursor().type - ); - - PageCursor nextPageCursor = factory.nextPageCursor(); - PageCursor prevPageCursor = factory.previousPageCursor(); + // Paging + PageCursor nextPageCursor = pagingService.nextPageCursor(); + PageCursor prevPageCursor = pagingService.previousPageCursor(); if (LOG.isDebugEnabled()) { logPagingInformation(request.pageCursor(), prevPageCursor, nextPageCursor, routingErrors); } - var metadata = createTripSearchMetadata( - request, - raptorSearchParamsUsed, - numItinerariesFilterResults == null - ? null - : numItinerariesFilterResults.firstRemovedDepartureTime, - numItinerariesFilterResults == null - ? null - : numItinerariesFilterResults.firstRemovedArrivalTime - ); + var metadata = pagingService.createTripSearchMetadata(); return new RoutingResponse( tripPlan, @@ -89,96 +61,6 @@ public static RoutingResponse map( ); } - public static PageCursorFactory mapIntoPageCursorFactory( - SortOrder sortOrder, - ZonedDateTime transitSearchTimeZero, - SearchParams raptorSearchParamsUsed, - Duration searchWindowNextSearch, - NumItinerariesFilterResults numItinerariesFilterResults, - @Nullable PageType currentPageType - ) { - Objects.requireNonNull(sortOrder); - Objects.requireNonNull(transitSearchTimeZero); - - var factory = new PageCursorFactory(sortOrder, searchWindowNextSearch); - - // No transit search performed - if (raptorSearchParamsUsed == null) { - return factory; - } - - assertRequestPrerequisites(raptorSearchParamsUsed); - - factory = - mapSearchParametersIntoFactory( - factory, - transitSearchTimeZero, - raptorSearchParamsUsed, - currentPageType - ); - - if (numItinerariesFilterResults != null) { - factory = factory.withRemovedItineraries(numItinerariesFilterResults); - } - return factory; - } - - private static PageCursorFactory mapSearchParametersIntoFactory( - PageCursorFactory factory, - ZonedDateTime transitSearchTimeZero, - SearchParams raptorSearchParamsUsed, - PageType currentPageType - ) { - Instant edt = transitSearchTimeZero - .plusSeconds(raptorSearchParamsUsed.earliestDepartureTime()) - .toInstant(); - - Instant lat = raptorSearchParamsUsed.isLatestArrivalTimeSet() - ? transitSearchTimeZero.plusSeconds(raptorSearchParamsUsed.latestArrivalTime()).toInstant() - : null; - - var searchWindowUsed = Duration.ofSeconds(raptorSearchParamsUsed.routerSearchWindowInSeconds()); - - return factory.withOriginalSearch(currentPageType, edt, lat, searchWindowUsed); - } - - @Nullable - private static TripSearchMetadata createTripSearchMetadata( - RouteRequest request, - SearchParams searchParams, - Instant firstRemovedDepartureTime, - Instant firstRemovedArrivalTime - ) { - if (searchParams == null) { - return null; - } - - Instant reqTime = request.dateTime(); - - if (request.arriveBy()) { - return TripSearchMetadata.createForArriveBy( - reqTime, - searchParams.searchWindowInSeconds(), - firstRemovedArrivalTime - ); - } else { - return TripSearchMetadata.createForDepartAfter( - reqTime, - searchParams.searchWindowInSeconds(), - firstRemovedDepartureTime - ); - } - } - - private static void assertRequestPrerequisites(SearchParams raptorSearchParamsUsed) { - if (!raptorSearchParamsUsed.isSearchWindowSet()) { - throw new IllegalStateException("SearchWindow not set"); - } - if (!raptorSearchParamsUsed.isEarliestDepartureTimeSet()) { - throw new IllegalStateException("Earliest departure time not set"); - } - } - private static void logPagingInformation( PageCursor currentPageCursor, PageCursor prevPageCursor, diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index b9f9685341c..f18aa056504 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -177,7 +177,7 @@ private AccessEgresses fetchAccessEgresses() { if (OTPFeature.ParallelRouting.isOn()) { try { - // TODO: This is not using {@link OtpRequestThreadFactory} witch mean we do not get + // TODO: This is not using {@link OtpRequestThreadFactory} which mean we do not get // log-trace-parameters-propagation and graceful timeout handling here. CompletableFuture .allOf( diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java index 491f68800b8..e0ca070d76f 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java @@ -37,7 +37,7 @@ protected DefaultAccessEgress(DefaultAccessEgress other, TimeAndCost penalty) { } this.stop = other.stop(); this.durationInSeconds = other.durationInSeconds() + (int) penalty.time().toSeconds(); - this.generalizedCost = other.generalizedCost() + penalty.cost().toCentiSeconds(); + this.generalizedCost = other.c1() + penalty.cost().toCentiSeconds(); this.penalty = penalty; this.lastState = other.getLastState(); } @@ -62,7 +62,7 @@ public int stop() { } @Override - public int generalizedCost() { + public int c1() { return generalizedCost; } @@ -119,7 +119,7 @@ public final boolean equals(Object o) { return ( stop() == that.stop() && durationInSeconds() == that.durationInSeconds() && - generalizedCost() == that.generalizedCost() && + c1() == that.c1() && penalty().equals(that.penalty()) ); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultRaptorTransfer.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultRaptorTransfer.java index 4914970811a..3547a7ba05b 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultRaptorTransfer.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultRaptorTransfer.java @@ -2,18 +2,13 @@ import org.opentripplanner.raptor.api.model.RaptorTransfer; -public record DefaultRaptorTransfer( - int stop, - int durationInSeconds, - int generalizedCost, - Transfer transfer -) +public record DefaultRaptorTransfer(int stop, int durationInSeconds, int c1, Transfer transfer) implements RaptorTransfer { public static DefaultRaptorTransfer reverseOf(int fromStopIndex, RaptorTransfer transfer) { return new DefaultRaptorTransfer( fromStopIndex, transfer.durationInSeconds(), - transfer.generalizedCost(), + transfer.c1(), transfer instanceof DefaultRaptorTransfer drt ? drt.transfer : null ); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransferIndex.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransferIndex.java index 975526b946e..1d9b804067c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransferIndex.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransferIndex.java @@ -43,11 +43,7 @@ public static RaptorTransferIndex create( .stream() .flatMap(s -> s.asRaptorTransfer(request).stream()) .collect( - toMap( - RaptorTransfer::stop, - Function.identity(), - (a, b) -> a.generalizedCost() < b.generalizedCost() ? a : b - ) + toMap(RaptorTransfer::stop, Function.identity(), (a, b) -> a.c1() < b.c1() ? a : b) ) .values(); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/TransferIndexGenerator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/TransferIndexGenerator.java index 9fc2dc69194..7acddd9cea5 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/TransferIndexGenerator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/TransferIndexGenerator.java @@ -57,7 +57,7 @@ public ConstrainedTransfersForPatterns generateTransfers() { for (ConstrainedTransfer tx : constrainedTransfers) { var c = tx.getTransferConstraint(); - // Only add transfers witch have an effect on the Raptor routing here. + // Only add transfers which have an effect on the Raptor routing here. // Some transfers only have the priority set, and that is used in optimized- // transfers, but not in Raptor. if (!c.includeInRaptorRouting()) { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java index b738d63054e..26a1286d10d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java @@ -134,15 +134,15 @@ public int calculateMinCost(int minTravelTime, int minNumTransfers) { @Override public int costEgress(RaptorAccessEgress egress) { if (egress.hasRides()) { - return egress.generalizedCost() + transferCostOnly; + return egress.c1() + transferCostOnly; } else if (stopTransferCost != null) { // Remove cost that was added during alighting. // We do not want to add this cost on last alighting since it should only be applied on transfers // It has to be done here because during alighting we do not know yet if it will be // a transfer or not. - return egress.generalizedCost() - stopTransferCost[egress.stop()]; + return egress.c1() - stopTransferCost[egress.stop()]; } else { - return egress.generalizedCost(); + return egress.c1(); } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/RaptorCostConverter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/RaptorCostConverter.java index 0ee465c2e53..210197f107b 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/RaptorCostConverter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/RaptorCostConverter.java @@ -1,7 +1,7 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit.cost; import java.time.Duration; -import org.opentripplanner.framework.lang.OtpNumberFormat; +import org.opentripplanner.raptor.api.model.RaptorValueFormatter; /** * Convert Raptor internal cost to OTP domain model cost, and back. @@ -47,7 +47,7 @@ public static double toOtpDomainFactor(int raptorFactor) { * Convert Raptor internal cost to a string with format $###.## (in seconds) */ public static String toString(int raptorCost) { - return OtpNumberFormat.formatCostCenti(raptorCost); + return RaptorValueFormatter.formatC1(raptorCost); } /** 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/TransitPriorityGroup32n.java new file mode 100644 index 00000000000..9b744932b8b --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32n.java @@ -0,0 +1,80 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority; + +import org.opentripplanner.raptor.api.model.DominanceFunction; +import org.opentripplanner.raptor.api.request.RaptorTransitPriorityGroupCalculator; + +/** + * 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 { + + 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() { + @Override + public int mergeTransitPriorityGroupIds(int currentGroupIds, int boardingGroupId) { + return mergeInGroupId(currentGroupIds, boardingGroupId); + } + + @Override + public DominanceFunction dominanceFunction() { + return TransitPriorityGroup32n::dominate; + } + + @Override + public String toString() { + return "TransitPriorityGroup32nCalculator{}"; + } + }; + } + + /** + * Left dominate right, if right contains a group which does not exist in left. Left + * do NOT dominate right if they are equals or left is a super set of right. + */ + public static boolean dominate(int left, int right) { + return ((left ^ right) & right) != 0; + } + + @Override + public String toString() { + return "TransitPriorityGroup32n{}"; + } + + /** + * Use this method to map from a continuous group index [0..32) to the groupId used + * during routing. The ID is implementation specific and optimized for performance. + */ + public static int groupId(final int priorityGroupIndex) { + assertValidGroupSeqNo(priorityGroupIndex); + return priorityGroupIndex == MIN_SEQ_NO ? GROUP_ZERO : 0x01 << (priorityGroupIndex - 1); + } + + /** + * Merge a groupId into a set of groupIds. + */ + public static int mergeInGroupId(final int currentSetOfGroupIds, final int newGroupId) { + return currentSetOfGroupIds | newGroupId; + } + + private static void assertValidGroupSeqNo(int priorityGroupIndex) { + if (priorityGroupIndex < MIN_SEQ_NO) { + throw new IllegalArgumentException( + "Transit priority group can not be a negative number: " + priorityGroupIndex + ); + } + if (priorityGroupIndex > MAX_SEQ_NO) { + throw new IllegalArgumentException( + "Transit priority group 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 8e6121edffe..75c1bcf7214 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 @@ -9,8 +9,10 @@ import java.util.Collection; import java.util.List; import org.opentripplanner.framework.application.OTPFeature; +import org.opentripplanner.raptor.api.model.GeneralizedCostRelaxFunction; 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.Optimization; import org.opentripplanner.raptor.api.request.PassThroughPoint; import org.opentripplanner.raptor.api.request.RaptorRequest; @@ -18,7 +20,10 @@ import org.opentripplanner.raptor.rangeraptor.SystemErrDebugLogger; 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.api.request.RouteRequest; +import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.transit.model.site.StopLocation; public class RaptorRequestMapper { @@ -92,13 +97,13 @@ private RaptorRequest doMap() { } else { var c = request.pageCursor(); - if (c.earliestDepartureTime != null) { - searchParams.earliestDepartureTime(relativeTime(c.earliestDepartureTime)); + if (c.earliestDepartureTime() != null) { + searchParams.earliestDepartureTime(relativeTime(c.earliestDepartureTime())); } - if (c.latestArrivalTime != null) { - searchParams.latestArrivalTime(relativeTime(c.latestArrivalTime)); + if (c.latestArrivalTime() != null) { + searchParams.latestArrivalTime(relativeTime(c.latestArrivalTime())); } - searchParams.searchWindow(c.searchWindow); + searchParams.searchWindow(c.searchWindow()); } if (preferences.transfer().maxTransfers() != null) { @@ -110,12 +115,15 @@ private RaptorRequest doMap() { } builder.withMultiCriteria(mcBuilder -> { - preferences - .transit() - .raptor() - .relaxGeneralizedCostAtDestination() - .ifPresent(mcBuilder::withRelaxCostAtDestination); - mcBuilder.withPassThroughPoints(mapPassThroughPoints()); + var pt = preferences.transit(); + var r = pt.raptor(); + if (!pt.relaxTransitPriorityGroup().isNormal()) { + mcBuilder.withTransitPriorityCalculator(TransitPriorityGroup32n.priorityCalculator()); + mcBuilder.withRelaxC1(mapRelaxCost(pt.relaxTransitPriorityGroup())); + } else { + mcBuilder.withPassThroughPoints(mapPassThroughPoints()); + r.relaxGeneralizedCostAtDestination().ifPresent(mcBuilder::withRelaxCostAtDestination); + } }); for (Optimization optimization : preferences.transit().raptor().optimizations()) { @@ -185,6 +193,16 @@ private List mapPassThroughPoints() { .toList(); } + static RelaxFunction mapRelaxCost(CostLinearFunction relax) { + if (relax == null) { + return null; + } + return GeneralizedCostRelaxFunction.of( + relax.coefficient(), + RaptorCostConverter.toRaptorCost(relax.constant().toSeconds()) + ); + } + private int relativeTime(Instant time) { if (time == null) { return RaptorConstants.TIME_NOT_SET; 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 new file mode 100644 index 00000000000..ba9af45adba --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java @@ -0,0 +1,121 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.transit.request; + +import gnu.trove.impl.Constants; +import gnu.trove.map.TObjectIntMap; +import gnu.trove.map.hash.TObjectIntHashMap; +import java.util.Arrays; +import java.util.Collection; +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.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 + * group-ids for each requested pattern. It is made for optimal + * performance, since it is used in request scope. + *

+ * THIS CLASS IS NOT THREAD-SAFE. + */ +public class PriorityGroupConfigurator { + + private static final int BASE_GROUP_ID = TransitPriorityGroup32n.groupId(0); + private int groupIndexCounter = 0; + private final boolean enabled; + private final PriorityGroupMatcher[] agencyMatchers; + private final PriorityGroupMatcher[] globalMatchers; + + // Index matchers and ids + private final List agencyMatchersIds; + private final List globalMatchersIds; + + private PriorityGroupConfigurator() { + this.enabled = false; + this.agencyMatchers = null; + this.globalMatchers = null; + this.agencyMatchersIds = List.of(); + this.globalMatchersIds = List.of(); + } + + private PriorityGroupConfigurator( + Collection byAgency, + Collection global + ) { + this.agencyMatchers = PriorityGroupMatcher.of(byAgency); + this.globalMatchers = PriorityGroupMatcher.of(global); + this.enabled = Stream.of(agencyMatchers, globalMatchers).anyMatch(ArrayUtils::hasContent); + this.globalMatchersIds = + Arrays.stream(globalMatchers).map(m -> new MatcherAndId(m, nextGroupId())).toList(); + // We need to populate this dynamically + this.agencyMatchersIds = Arrays.stream(agencyMatchers).map(MatcherAgencyAndIds::new).toList(); + } + + public static PriorityGroupConfigurator empty() { + return new PriorityGroupConfigurator(); + } + + public static PriorityGroupConfigurator of( + Collection byAgency, + Collection global + ) { + if (Stream.of(byAgency, global).allMatch(Collection::isEmpty)) { + return empty(); + } + return new PriorityGroupConfigurator(byAgency, global); + } + + /** + * Fetch/lookup the transit-group-id for the given pattern. + *

+ * @throws IllegalArgumentException if more than 32 group-ids are requested. + */ + public int lookupTransitPriorityGroupId(RoutingTripPattern tripPattern) { + if (!enabled || tripPattern == null) { + return BASE_GROUP_ID; + } + + var p = tripPattern.getPattern(); + + for (var it : agencyMatchersIds) { + if (it.matcher().match(p)) { + var agencyId = p.getRoute().getAgency().getId(); + int groupId = it.ids().get(agencyId); + + if (groupId < 0) { + groupId = nextGroupId(); + it.ids.put(agencyId, groupId); + } + return groupId; + } + } + + for (var it : globalMatchersIds) { + if (it.matcher.match(p)) { + return it.groupId(); + } + } + // Fallback to base-group-id + return BASE_GROUP_ID; + } + + private int nextGroupId() { + return TransitPriorityGroup32n.groupId(++groupIndexCounter); + } + + /** Pair of matcher and groupId. Used only inside this class. */ + record MatcherAndId(PriorityGroupMatcher matcher, int groupId) {} + + /** Matcher with map of ids by agency. */ + record MatcherAgencyAndIds(PriorityGroupMatcher matcher, TObjectIntMap ids) { + MatcherAgencyAndIds(PriorityGroupMatcher matcher) { + this( + matcher, + new TObjectIntHashMap<>(Constants.DEFAULT_CAPACITY, Constants.DEFAULT_LOAD_FACTOR, -1) + ); + } + } +} 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 new file mode 100644 index 00000000000..dd7c1b46636 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java @@ -0,0 +1,206 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.transit.request; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +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.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. + *

+ * 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 + * a `CompositeMatcher`. So, a new matcher is only created if the field in the + * select is present. + */ +public abstract class PriorityGroupMatcher { + + private static final PriorityGroupMatcher NOOP = new PriorityGroupMatcher() { + @Override + boolean match(TripPattern pattern) { + return false; + } + + @Override + boolean isEmpty() { + return true; + } + }; + + public static PriorityGroupMatcher of(TransitPriorityGroupSelect select) { + if (select.isEmpty()) { + return NOOP; + } + List list = new ArrayList<>(); + + if (!select.modes().isEmpty()) { + list.add(new ModeMatcher(select.modes())); + } + if (!select.subModeRegexp().isEmpty()) { + list.add( + new RegExpMatcher("SubMode", select.subModeRegexp(), p -> p.getNetexSubmode().name()) + ); + } + if (!select.agencyIds().isEmpty()) { + list.add(new IdMatcher("Agency", select.agencyIds(), p -> p.getRoute().getAgency().getId())); + } + if (!select.routeIds().isEmpty()) { + list.add(new IdMatcher("Route", select.routeIds(), p -> p.getRoute().getId())); + } + return compositeOf(list); + } + + static PriorityGroupMatcher[] of(Collection selectors) { + return selectors + .stream() + .map(PriorityGroupMatcher::of) + .filter(Predicate.not(PriorityGroupMatcher::isEmpty)) + .toArray(PriorityGroupMatcher[]::new); + } + + private static String arrayToString(T[] values) { + return colToString(Arrays.asList(values)); + } + + private static String colToString(Collection values) { + return values.stream().map(Objects::toString).collect(Collectors.joining(" | ")); + } + + private static PriorityGroupMatcher compositeOf(List list) { + // Remove empty/noop matchers + list = list.stream().filter(Predicate.not(PriorityGroupMatcher::isEmpty)).toList(); + + if (list.isEmpty()) { + return NOOP; + } + if (list.size() == 1) { + return list.get(0); + } + return new CompositeMatcher(list); + } + + abstract boolean match(TripPattern pattern); + + boolean isEmpty() { + return false; + } + + private static final class ModeMatcher extends PriorityGroupMatcher { + + private final Set modes; + + public ModeMatcher(List modes) { + this.modes = EnumSet.copyOf(modes); + } + + @Override + boolean match(TripPattern pattern) { + return modes.contains(pattern.getMode()); + } + + @Override + public String toString() { + return "Mode(" + colToString(modes) + ')'; + } + } + + private static final class RegExpMatcher extends PriorityGroupMatcher { + + private final String typeName; + private final Pattern[] subModeRegexp; + private final Function toValue; + + public RegExpMatcher( + String typeName, + List subModeRegexp, + Function toValue + ) { + this.typeName = typeName; + this.subModeRegexp = subModeRegexp.stream().map(Pattern::compile).toArray(Pattern[]::new); + this.toValue = toValue; + } + + @Override + boolean match(TripPattern pattern) { + var value = toValue.apply(pattern); + for (Pattern p : subModeRegexp) { + if (p.matcher(value).matches()) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return typeName + "Regexp(" + arrayToString(subModeRegexp) + ')'; + } + } + + private static final class IdMatcher extends PriorityGroupMatcher { + + private final String typeName; + private final Set ids; + private final Function idProvider; + + public IdMatcher( + String typeName, + List ids, + Function idProvider + ) { + this.typeName = typeName; + this.ids = new HashSet<>(ids); + this.idProvider = idProvider; + } + + @Override + boolean match(TripPattern pattern) { + return ids.contains(idProvider.apply(pattern)); + } + + @Override + public String toString() { + return typeName + "Id(" + colToString(ids) + ')'; + } + } + + /** + * Take a list of matchers and provide a single interface. At least one matcher in the + * list must match for the composite matcher to return a match. + */ + private static final class CompositeMatcher extends PriorityGroupMatcher { + + private final PriorityGroupMatcher[] matchers; + + public CompositeMatcher(List matchers) { + this.matchers = matchers.toArray(PriorityGroupMatcher[]::new); + } + + @Override + boolean match(TripPattern pattern) { + for (var m : matchers) { + if (m.match(pattern)) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return "(" + arrayToString(matchers) + ')'; + } + } +} 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 a0b62414181..f1212b2f545 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 @@ -91,7 +91,8 @@ public RaptorRoutingRequestTransitData( List tripPatterns = transitDataCreator.createTripPatterns( additionalPastSearchDays, additionalFutureSearchDays, - filter + filter, + createTransitPriorityGroupConfigurator(request) ); this.patternIndex = transitDataCreator.createPatternIndex(tripPatterns); this.activeTripPatternsPerStop = transitDataCreator.createTripPatternsPerStop(tripPatterns); @@ -241,4 +242,15 @@ public RaptorConstrainedBoardingSearch transferConstraintsReverseS } return new ConstrainedBoardingSearch(false, toStopTransfers, fromStopTransfers); } + + private PriorityGroupConfigurator createTransitPriorityGroupConfigurator(RouteRequest request) { + if (request.preferences().transit().relaxTransitPriorityGroup().isNormal()) { + return PriorityGroupConfigurator.empty(); + } + var transitRequest = request.journey().transit(); + return PriorityGroupConfigurator.of( + transitRequest.priorityGroupsByAgency(), + transitRequest.priorityGroupsGlobal() + ); + } } 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 3aa7a6cf5ad..0cb155facd5 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 @@ -92,7 +92,8 @@ public List createPatternIndex(List tr static List merge( ZonedDateTime transitSearchTimeZero, List patternForDateList, - TransitDataProviderFilter filter + TransitDataProviderFilter filter, + PriorityGroupConfigurator priorityGroupConfigurator ) { // Group TripPatternForDate objects by TripPattern. // This is done in a loop to increase performance. @@ -145,7 +146,8 @@ static List merge( tripPattern, tripPattern.getAlightingPossible(), BoardAlight.ALIGHT - ) + ), + priorityGroupConfigurator.lookupTransitPriorityGroupId(tripPattern) ) ); } @@ -156,7 +158,8 @@ static List merge( List createTripPatterns( int additionalPastSearchDays, int additionalFutureSearchDays, - TransitDataProviderFilter filter + TransitDataProviderFilter filter, + PriorityGroupConfigurator priorityGroupConfigurator ) { List tripPatternForDates = getTripPatternsForDateRange( additionalPastSearchDays, @@ -164,7 +167,7 @@ List createTripPatterns( filter ); - return merge(transitSearchTimeZero, tripPatternForDates, filter); + return merge(transitSearchTimeZero, tripPatternForDates, filter, priorityGroupConfigurator); } private static List filterActiveTripPatterns( diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDates.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDates.java index 215796aa2ef..0fb39b227ea 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDates.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDates.java @@ -60,18 +60,22 @@ public class TripPatternForDates private final BitSet boardingPossible; private final BitSet alightingPossible; + private final int priorityGroupId; + TripPatternForDates( RoutingTripPattern tripPattern, TripPatternForDate[] tripPatternForDates, int[] offsets, BitSet boardingPossible, - BitSet alightningPossible + BitSet alightningPossible, + int priorityGroupId ) { this.tripPattern = tripPattern; this.tripPatternForDates = tripPatternForDates; this.offsets = offsets; this.boardingPossible = boardingPossible; this.alightingPossible = alightningPossible; + this.priorityGroupId = priorityGroupId; int numberOfTripSchedules = 0; boolean hasFrequencies = false; @@ -173,8 +177,7 @@ public int slackIndex() { @Override public int priorityGroupId() { - // TODO C2 - Implement this. - throw new UnsupportedOperationException(); + return priorityGroupId; } public int transitReluctanceFactorIndex() { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripSearchTimetable.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripSearchTimetable.java index 88e9d52e878..147eddfc605 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripSearchTimetable.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripSearchTimetable.java @@ -9,7 +9,7 @@ * This interface add two methods the the {@link RaptorTimeTable} to optimize the terip search * inside the transit model. They were previously in Raptor, but the trip Search is moded outside * of Raptor; We have keep these methods in an interface to be able to reuse the complex TripSearch - * in tests, witch do not use the transit model {@link TripSchedule}; Hence also the generic type + * in tests, which do not use the transit model {@link TripSchedule}; Hence also the generic type * on this interface. */ public interface TripSearchTimetable extends RaptorTimeTable { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/api/OptimizedPath.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/api/OptimizedPath.java index d18da1f0e08..ad4df23a42c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/api/OptimizedPath.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/api/OptimizedPath.java @@ -30,6 +30,7 @@ public OptimizedPath(RaptorPath originalPath) { originalPath.accessLeg(), originalPath.rangeRaptorIterationDepartureTime(), originalPath.c1(), + originalPath.c2(), priorityCost(originalPath), NEUTRAL_COST, NEUTRAL_COST @@ -40,11 +41,12 @@ public OptimizedPath( AccessPathLeg accessPathLeg, int iterationStartTime, int generalizedCost, + int c2, int transferPriorityCost, int waitTimeOptimizedCost, int breakTieCost ) { - super(iterationStartTime, accessPathLeg, generalizedCost); + super(iterationStartTime, accessPathLeg, generalizedCost, c2); this.transferPriorityCost = transferPriorityCost; this.waitTimeOptimizedCost = waitTimeOptimizedCost; this.breakTieCost = breakTieCost; @@ -61,7 +63,7 @@ public static int priorityCost(boolean transferExist, Supplier leg) { } private void appendSummary(PathStringBuilder buf) { - buf.costCentiSec(transferPriorityCost, TransferConstraint.ZERO_COST, "pri"); - buf.costCentiSec(generalizedCostWaitTimeOptimized(), c1(), "wtc"); + buf.transferPriority(transferPriorityCost, TransferConstraint.ZERO_COST); + buf.waitTimeCost(generalizedCostWaitTimeOptimized(), c1()); } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTail.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTail.java index 365ff1ef860..fa74648e8eb 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTail.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTail.java @@ -3,8 +3,10 @@ import javax.annotation.Nullable; import org.opentripplanner.framework.tostring.ValueObjectToStringBuilder; import org.opentripplanner.model.transfer.TransferConstraint; +import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTransfer; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.model.RaptorValueFormatter; import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.api.path.TransitPathLeg; import org.opentripplanner.raptor.path.PathBuilder; @@ -130,6 +132,7 @@ public OptimizedPath build() { createPathLegs(costCalculator(), slackProvider()), iterationDepartureTime, generalizedCost, + c2(), transferPriorityCost, waitTimeOptimizedCost, breakTieCost() @@ -138,15 +141,21 @@ public OptimizedPath build() { @Override public String toString() { - return ValueObjectToStringBuilder - .of() - .addObj(super.toString()) - .addText(" [") - .addCostCenti(generalizedCost()) - .addCostCenti(transferPriorityCost, "pri") - .addCostCenti(generalizedCostWaitTimeOptimized(), "wtc") - .addText("]") - .toString(); + var builder = ValueObjectToStringBuilder.of().addObj(super.toString()).addText(" ["); + + if (generalizedCost != RaptorCostCalculator.ZERO_COST) { + builder.addObj(RaptorValueFormatter.formatC1(generalizedCost())); + } + if (c2() != RaptorConstants.NOT_SET) { + builder.addObj(RaptorValueFormatter.formatC2(c2())); + } + if (transferPriorityCost != TransferConstraint.ZERO_COST) { + builder.addObj(RaptorValueFormatter.formatTransferPriority(transferPriorityCost)); + } + if (waitTimeOptimizedCost != TransferWaitTimeCostCalculator.ZERO_COST) { + builder.addObj(RaptorValueFormatter.formatWaitTimeCost(generalizedCostWaitTimeOptimized())); + } + return builder.addText("]").toString(); } @Override @@ -203,7 +212,7 @@ private void updateGeneralizedCost() { return; } this.generalizedCost = - legsAsStream().mapToInt(it -> it.generalizedCost(costCalculator(), slackProvider())).sum(); + legsAsStream().mapToInt(it -> it.c1(costCalculator(), slackProvider())).sum(); } /*private methods */ diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/TripToTripTransfer.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/TripToTripTransfer.java index 5014ae95bcd..0dce8933e3f 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/TripToTripTransfer.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/TripToTripTransfer.java @@ -45,7 +45,7 @@ public int transferDuration() { } public int generalizedCost() { - return sameStop() ? 0 : pathTransfer.generalizedCost(); + return sameStop() ? 0 : pathTransfer.c1(); } public boolean sameStop() { diff --git a/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegsFilter.java b/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegsFilter.java index 5154f8cd40a..9f883603e77 100644 --- a/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegsFilter.java +++ b/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegsFilter.java @@ -14,6 +14,10 @@ public enum AlternativeLegsFilter { ), SAME_MODE((Leg leg) -> (TripPattern tripPattern) -> leg.getTrip().getMode().equals(tripPattern.getMode()) + ), + SAME_SUBMODE((Leg leg) -> + (TripPattern tripPattern) -> + leg.getTrip().getNetexSubMode().equals(tripPattern.getNetexSubmode()) ); public final Function> predicateGenerator; diff --git a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index 586b106bc66..ce2fdb31c44 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -13,12 +13,12 @@ import java.util.Locale; import java.util.function.Consumer; import javax.annotation.Nullable; +import org.opentripplanner.framework.collection.ListSection; import org.opentripplanner.framework.time.DateUtils; import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.model.GenericLocation; import org.opentripplanner.model.plan.SortOrder; -import org.opentripplanner.model.plan.pagecursor.PageCursor; -import org.opentripplanner.model.plan.pagecursor.PageType; +import org.opentripplanner.model.plan.paging.cursor.PageCursor; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; import org.opentripplanner.routing.api.request.request.JourneyRequest; import org.opentripplanner.routing.api.response.InputField; @@ -157,7 +157,7 @@ public boolean isTripPlannedForNow() { public SortOrder itinerariesSortOrder() { if (pageCursor != null) { - return pageCursor.originalSortOrder; + return pageCursor.originalSortOrder(); } return arriveBy ? SortOrder.STREET_AND_DEPARTURE_TIME : SortOrder.STREET_AND_ARRIVAL_TIME; } @@ -171,10 +171,11 @@ public void applyPageCursor() { if (pageCursor != null) { // We switch to "depart-after" search when paging next(lat==null). It does not make // sense anymore to keep the latest-arrival-time when going to the "next page". - if (pageCursor.latestArrivalTime == null) { + if (pageCursor.latestArrivalTime() == null) { arriveBy = false; } - this.dateTime = arriveBy ? pageCursor.latestArrivalTime : pageCursor.earliestDepartureTime; + this.dateTime = + arriveBy ? pageCursor.latestArrivalTime() : pageCursor.earliestDepartureTime(); journey.setModes(journey.modes().copyOf().withDirectMode(StreetMode.NOT_SET).build()); LOG.debug("Request dateTime={} set from pageCursor.", dateTime); } @@ -182,35 +183,12 @@ public void applyPageCursor() { /** * When paging we must crop the list of itineraries in the right end according to the sorting of - * the original search and according to the page cursor type (next or previous). - *

- * We need to flip the cropping and crop the head/start of the itineraries when: - *

    - *
  • Paging to the previous page for a {@code depart-after/sort-on-arrival-time} search. - *
  • Paging to the next page for a {@code arrive-by/sort-on-departure-time} search. - *
+ * the original search and according to the paging direction (next or previous). We always + * crop at the end of the initial search. This is a utility function delegating to the + * pageCursor, if available. */ - public boolean maxNumberOfItinerariesCropHead() { - if (pageCursor == null) { - return false; - } - - var previousPage = pageCursor.type == PageType.PREVIOUS_PAGE; - return pageCursor.originalSortOrder.isSortedByArrivalTimeAscending() == previousPage; - } - - /** - * Related to {@link #maxNumberOfItinerariesCropHead()}, but is {@code true} if we should crop the - * search-window head(in the beginning) or tail(in the end). - *

- * For the first search we look if the sort is ascending(crop tail) or descending(crop head), and - * for paged results we look at the paging type: next(tail) and previous(head). - */ - public boolean doCropSearchWindowAtTail() { - if (pageCursor == null) { - return itinerariesSortOrder().isSortedByArrivalTimeAscending(); - } - return pageCursor.type == PageType.NEXT_PAGE; + public ListSection cropItinerariesAt() { + return pageCursor == null ? ListSection.TAIL : pageCursor.cropItinerariesAt(); } /** diff --git a/src/main/java/org/opentripplanner/routing/api/request/framework/AbstractLinearFunction.java b/src/main/java/org/opentripplanner/routing/api/request/framework/AbstractLinearFunction.java index b1fe459a7b7..622c6f9086a 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/framework/AbstractLinearFunction.java +++ b/src/main/java/org/opentripplanner/routing/api/request/framework/AbstractLinearFunction.java @@ -46,6 +46,13 @@ public final boolean isZero() { return isZero(constant) && coefficient == 0.0; } + /** + * Return true if the {@code f(x) = x} for all times (constant is zero and the coefficient is one). + */ + public final boolean isNormal() { + return isZero(constant) && coefficient == 1.0; + } + public final String serialize() { return LinearFunctionSerialization.serialize(this); } diff --git a/src/main/java/org/opentripplanner/routing/api/request/framework/CostLinearFunction.java b/src/main/java/org/opentripplanner/routing/api/request/framework/CostLinearFunction.java index 019bab48f7a..8ff119b91ad 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/framework/CostLinearFunction.java +++ b/src/main/java/org/opentripplanner/routing/api/request/framework/CostLinearFunction.java @@ -8,8 +8,16 @@ */ public final class CostLinearFunction extends AbstractLinearFunction { + /** + * Constant is zero and coefficient is zero. The result of {@code f(x) = 0}. + */ public static CostLinearFunction ZERO = new CostLinearFunction(Cost.ZERO, 0.0); + /** + * Constant is zero and coefficient is one. The result of {@code f(x) = x}. + */ + public static CostLinearFunction NORMAL = new CostLinearFunction(Cost.ZERO, 1.0); + private CostLinearFunction(Cost a, double b) { super(a, b); } 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 0985a132dd0..115e1264036 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 @@ -1,6 +1,7 @@ package org.opentripplanner.routing.api.request.preference; import static org.opentripplanner.framework.lang.DoubleUtils.doubleEquals; +import static org.opentripplanner.framework.lang.ObjectUtils.ifNotNull; import java.io.Serializable; import java.util.Objects; @@ -28,8 +29,7 @@ public final class BikePreferences implements Serializable { private final double walkingReluctance; private final int switchTime; private final Cost switchCost; - private final int parkTime; - private final Cost parkCost; + private final VehicleParkingPreferences parking; private final double stairsReluctance; private final BicycleOptimizeType optimizeType; private final TimeSlopeSafetyTriangle optimizeTriangle; @@ -42,9 +42,7 @@ private BikePreferences() { this.walkingReluctance = 5.0; this.switchTime = 0; this.switchCost = Cost.ZERO; - this.parkTime = 60; - /** Cost of parking a bike. */ - this.parkCost = Cost.costOfSeconds(120); + this.parking = VehicleParkingPreferences.DEFAULT; this.optimizeType = BicycleOptimizeType.SAFE; this.optimizeTriangle = TimeSlopeSafetyTriangle.DEFAULT; // very high reluctance to carry the bike up/down a flight of stairs @@ -59,8 +57,7 @@ private BikePreferences(Builder builder) { this.walkingReluctance = Units.reluctance(builder.walkingReluctance); this.switchTime = Units.duration(builder.switchTime); this.switchCost = builder.switchCost; - this.parkTime = Units.duration(builder.parkTime); - this.parkCost = builder.parkCost; + this.parking = builder.parking; this.optimizeType = Objects.requireNonNull(builder.optimizeType); this.optimizeTriangle = Objects.requireNonNull(builder.optimizeTriangle); this.stairsReluctance = Units.reluctance(builder.stairsReluctance); @@ -124,14 +121,9 @@ public int switchCost() { return switchCost.toSeconds(); } - /** Time to park a bike */ - public int parkTime() { - return parkTime; - } - - /** Cost of parking a bike. */ - public int parkCost() { - return parkCost.toSeconds(); + /** Parking preferences that can be different per request */ + public VehicleParkingPreferences parking() { + return parking; } /** @@ -162,8 +154,7 @@ public boolean equals(Object o) { doubleEquals(that.walkingReluctance, walkingReluctance) && switchTime == that.switchTime && switchCost.equals(that.switchCost) && - parkTime == that.parkTime && - parkCost.equals(that.parkCost) && + parking.equals(that.parking) && optimizeType == that.optimizeType && optimizeTriangle.equals(that.optimizeTriangle) && doubleEquals(stairsReluctance, that.stairsReluctance) @@ -180,8 +171,7 @@ public int hashCode() { walkingReluctance, switchTime, switchCost, - parkTime, - parkCost, + parking, optimizeType, optimizeTriangle, stairsReluctance @@ -199,8 +189,7 @@ public String toString() { .addNum("walkingReluctance", walkingReluctance, DEFAULT.walkingReluctance) .addDurationSec("switchTime", switchTime, DEFAULT.switchTime) .addObj("switchCost", switchCost, DEFAULT.switchCost) - .addDurationSec("parkTime", parkTime, DEFAULT.parkTime) - .addObj("parkCost", parkCost, DEFAULT.parkCost) + .addObj("parking", parking, DEFAULT.parking) .addEnum("optimizeType", optimizeType, DEFAULT.optimizeType) .addObj("optimizeTriangle", optimizeTriangle, DEFAULT.optimizeTriangle) .toString(); @@ -217,8 +206,7 @@ public static class Builder { private double walkingReluctance; private int switchTime; private Cost switchCost; - private int parkTime; - private Cost parkCost; + private VehicleParkingPreferences parking; private BicycleOptimizeType optimizeType; private TimeSlopeSafetyTriangle optimizeTriangle; @@ -233,8 +221,7 @@ public Builder(BikePreferences original) { this.walkingReluctance = original.walkingReluctance; this.switchTime = original.switchTime; this.switchCost = original.switchCost; - this.parkTime = original.parkTime; - this.parkCost = original.parkCost; + this.parking = original.parking; this.optimizeType = original.optimizeType; this.optimizeTriangle = original.optimizeTriangle; this.stairsReluctance = original.stairsReluctance; @@ -307,21 +294,8 @@ public Builder withSwitchCost(int switchCost) { return this; } - public int parkTime() { - return parkTime; - } - - public Builder withParkTime(int parkTime) { - this.parkTime = parkTime; - return this; - } - - public Cost parkCost() { - return parkCost; - } - - public Builder withParkCost(int parkCost) { - this.parkCost = Cost.costOfSeconds(parkCost); + public Builder withParking(Consumer body) { + this.parking = ifNotNull(this.parking, original.parking).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 014b2b0cdec..523e19afb70 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 @@ -1,5 +1,7 @@ package org.opentripplanner.routing.api.request.preference; +import static org.opentripplanner.framework.lang.ObjectUtils.ifNotNull; + import java.io.Serializable; import java.util.Objects; import java.util.function.Consumer; @@ -21,8 +23,7 @@ public final class CarPreferences implements Serializable { private final double speed; private final double reluctance; - private final int parkTime; - private final Cost parkCost; + private final VehicleParkingPreferences parking; private final int pickupTime; private final Cost pickupCost; private final int dropoffTime; @@ -33,8 +34,7 @@ public final class CarPreferences implements Serializable { private CarPreferences() { this.speed = 40.0; this.reluctance = 2.0; - this.parkTime = 60; - this.parkCost = Cost.costOfMinutes(2); + this.parking = VehicleParkingPreferences.DEFAULT; this.pickupTime = 60; this.pickupCost = Cost.costOfMinutes(2); this.dropoffTime = 120; @@ -45,8 +45,7 @@ private CarPreferences() { private CarPreferences(Builder builder) { this.speed = Units.speed(builder.speed); this.reluctance = Units.reluctance(builder.reluctance); - this.parkTime = Units.duration(builder.parkTime); - this.parkCost = builder.parkCost; + this.parking = builder.parking; this.pickupTime = Units.duration(builder.pickupTime); this.pickupCost = builder.pickupCost; this.dropoffTime = Units.duration(builder.dropoffTime); @@ -75,14 +74,9 @@ public double reluctance() { return reluctance; } - /** Time to park a car. */ - public int parkTime() { - return parkTime; - } - - /** Cost of parking a car. */ - public int parkCost() { - return parkCost.toSeconds(); + /** Parking preferences that can be different per request */ + public VehicleParkingPreferences parking() { + return parking; } /** Time of getting in/out of a carPickup (taxi) */ @@ -127,8 +121,7 @@ public boolean equals(Object o) { return ( DoubleUtils.doubleEquals(that.speed, speed) && DoubleUtils.doubleEquals(that.reluctance, reluctance) && - parkTime == that.parkTime && - parkCost.equals(that.parkCost) && + parking.equals(that.parking) && pickupTime == that.pickupTime && pickupCost.equals(that.pickupCost) && dropoffTime == that.dropoffTime && @@ -142,8 +135,7 @@ public int hashCode() { return Objects.hash( speed, reluctance, - parkTime, - parkCost, + parking, pickupTime, pickupCost, dropoffTime, @@ -158,8 +150,7 @@ public String toString() { .of(CarPreferences.class) .addNum("speed", speed, DEFAULT.speed) .addNum("reluctance", reluctance, DEFAULT.reluctance) - .addNum("parkTime", parkTime, DEFAULT.parkTime) - .addObj("parkCost", parkCost, DEFAULT.parkCost) + .addObj("parking", parking, DEFAULT.parking) .addNum("pickupTime", pickupTime, DEFAULT.pickupTime) .addObj("pickupCost", pickupCost, DEFAULT.pickupCost) .addNum("dropoffTime", dropoffTime, DEFAULT.dropoffTime) @@ -174,8 +165,7 @@ public static class Builder { private final CarPreferences original; private double speed; private double reluctance; - private int parkTime; - private Cost parkCost; + private VehicleParkingPreferences parking; private int pickupTime; private Cost pickupCost; private int dropoffTime; @@ -186,8 +176,7 @@ public Builder(CarPreferences original) { this.original = original; this.speed = original.speed; this.reluctance = original.reluctance; - this.parkTime = original.parkTime; - this.parkCost = original.parkCost; + this.parking = original.parking; this.pickupTime = original.pickupTime; this.pickupCost = original.pickupCost; this.dropoffTime = original.dropoffTime; @@ -209,13 +198,8 @@ public Builder withReluctance(double reluctance) { return this; } - public Builder withParkTime(int parkTime) { - this.parkTime = parkTime; - return this; - } - - public Builder withParkCost(int parkCost) { - this.parkCost = Cost.costOfSeconds(parkCost); + public Builder withParking(Consumer body) { + this.parking = ifNotNull(this.parking, original.parking).copyOf().apply(body).build(); return this; } diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/RaptorPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/RaptorPreferences.java index 905aa648945..03ed9482ff8 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/RaptorPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/RaptorPreferences.java @@ -34,7 +34,6 @@ public final class RaptorPreferences implements Serializable { private final SearchDirection searchDirection; private final Instant timeLimit; - private final Double relaxGeneralizedCostAtDestination; private RaptorPreferences() { @@ -50,7 +49,6 @@ private RaptorPreferences(RaptorPreferences.Builder builder) { this.profile = Objects.requireNonNull(builder.profile); this.searchDirection = Objects.requireNonNull(builder.searchDirection); this.timeLimit = builder.timeLimit; - this.relaxGeneralizedCostAtDestination = Units.normalizedOptionalFactor( builder.relaxGeneralizedCostAtDestination, @@ -91,6 +89,7 @@ public Instant timeLimit() { /** * See {@link SearchParams#relaxCostAtDestination()} for documentation. */ + @Deprecated public Optional relaxGeneralizedCostAtDestination() { return Optional.ofNullable(relaxGeneralizedCostAtDestination); } diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/Relax.java b/src/main/java/org/opentripplanner/routing/api/request/preference/Relax.java new file mode 100644 index 00000000000..8f10ffd1525 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/Relax.java @@ -0,0 +1,50 @@ +package org.opentripplanner.routing.api.request.preference; + +import java.util.Locale; +import org.opentripplanner.framework.lang.DoubleUtils; +import org.opentripplanner.framework.lang.IntUtils; + +/** + * Relax a value by the given ratio and slack. The relaxed value + * can be calculated using this function: + *

+ *   f(x) = ratio * x + slack
+ * 
+ * + * @param ratio the factor to multiply into the value, must be minimum 1.0 and max 4.0 + * @param slack the amount of slack to add to the value. + */ +public record Relax(double ratio, int slack) { + /** + * The "normal" will produce the same result: {@code f(x) == x } + */ + public static final Relax NORMAL = new Relax(1.0, 0); + + public Relax { + ratio = DoubleUtils.roundTo2Decimals(ratio); + DoubleUtils.requireInRange(ratio, 1.0, 4.0, "ratio"); + IntUtils.requireNotNegative(slack, "slack"); + } + + /** + * {@code true} if {@link #NORMAL}, this is the same as not applying the function. + *

+ * The relax operation should be skipped to save resources in this case, but it is + * safe to do it. + */ + public boolean hasNoEffect() { + return NORMAL.equals(this); + } + + /** + * Opposite of {@link #hasNoEffect()} + */ + public boolean hasEffect() { + return !hasNoEffect(); + } + + @Override + public String toString() { + return String.format(Locale.ROOT, "%d + %.2f * x", slack, ratio); + } +} 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 1077ce49334..b728b99f8e3 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 @@ -4,6 +4,7 @@ import static org.opentripplanner.framework.lang.ObjectUtils.ifNotNull; import java.io.Serializable; +import java.util.Objects; import java.util.function.Consumer; import javax.annotation.Nonnull; import org.opentripplanner.street.search.TraverseMode; @@ -95,6 +96,13 @@ public VehicleRentalPreferences rental() { return rental; } + /** + * Get parking preferences for the traverse mode. Note, only car and bike are supported. + */ + public VehicleParkingPreferences parking(TraverseMode mode) { + return mode == TraverseMode.CAR ? car.parking() : bike.parking(); + } + @Nonnull public ItineraryFilterPreferences itineraryFilter() { return itineraryFilter; @@ -116,6 +124,41 @@ public double getSpeed(TraverseMode mode, boolean walkingBike) { }; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RoutingPreferences that = (RoutingPreferences) o; + return ( + Objects.equals(transit, that.transit) && + Objects.equals(transfer, that.transfer) && + Objects.equals(walk, that.walk) && + Objects.equals(street, that.street) && + Objects.equals(wheelchair, that.wheelchair) && + Objects.equals(bike, that.bike) && + Objects.equals(car, that.car) && + Objects.equals(rental, that.rental) && + Objects.equals(system, that.system) && + Objects.equals(itineraryFilter, that.itineraryFilter) + ); + } + + @Override + public int hashCode() { + return Objects.hash( + transit, + transfer, + walk, + street, + wheelchair, + bike, + car, + rental, + system, + itineraryFilter + ); + } + public static class Builder { private final RoutingPreferences original; 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 e93dd5f5a6c..b901d738213 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 @@ -52,7 +52,7 @@ public TimeSlopeSafetyTriangle(double time, double slope, double safety) { } /** - * Creates a special builder, witch used together with the + * Creates a special builder, which used together with the * {@link Builder#buildOrDefault(TimeSlopeSafetyTriangle)} can return a new instance or the * default value if no values are set. This is useful in the APIs where we want to fall back to * the default {@link TimeSlopeSafetyTriangle}, if no values are set. If only one or two values 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 7da4516d782..da5c4aad60d 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java @@ -3,7 +3,6 @@ import static java.util.Objects.requireNonNull; import java.io.Serializable; -import java.time.Duration; import java.util.Map; import java.util.Objects; import java.util.function.Consumer; @@ -27,6 +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 boolean ignoreRealtimeUpdates; private final boolean includePlannedCancellations; private final boolean includeRealtimeCancellations; @@ -36,7 +36,8 @@ private TransitPreferences() { this.boardSlack = this.alightSlack = DurationForEnum.of(TransitMode.class).build(); this.reluctanceForMode = Map.of(); this.otherThanPreferredRoutesPenalty = Cost.costOfMinutes(5); - this.unpreferredCost = CostLinearFunction.of(Duration.ZERO, 1.0); + this.unpreferredCost = CostLinearFunction.NORMAL; + this.relaxTransitPriorityGroup = CostLinearFunction.NORMAL; this.ignoreRealtimeUpdates = false; this.includePlannedCancellations = false; this.includeRealtimeCancellations = false; @@ -49,6 +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.ignoreRealtimeUpdates = builder.ignoreRealtimeUpdates; this.includePlannedCancellations = builder.includePlannedCancellations; this.includeRealtimeCancellations = builder.includeRealtimeCancellations; @@ -126,7 +128,17 @@ public CostLinearFunction unpreferredCost() { } /** - * When true, realtime updates are ignored during this search. + * 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 + * 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; + } + + /** + * When true, real-time updates are ignored during this search. */ public boolean ignoreRealtimeUpdates() { return ignoreRealtimeUpdates; @@ -159,14 +171,15 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; TransitPreferences that = (TransitPreferences) o; return ( - otherThanPreferredRoutesPenalty == that.otherThanPreferredRoutesPenalty && - ignoreRealtimeUpdates == that.ignoreRealtimeUpdates && - includePlannedCancellations == that.includePlannedCancellations && - includeRealtimeCancellations == that.includeRealtimeCancellations && boardSlack.equals(that.boardSlack) && alightSlack.equals(that.alightSlack) && reluctanceForMode.equals(that.reluctanceForMode) && + otherThanPreferredRoutesPenalty == that.otherThanPreferredRoutesPenalty && unpreferredCost.equals(that.unpreferredCost) && + Objects.equals(relaxTransitPriorityGroup, that.relaxTransitPriorityGroup) && + ignoreRealtimeUpdates == that.ignoreRealtimeUpdates && + includePlannedCancellations == that.includePlannedCancellations && + includeRealtimeCancellations == that.includeRealtimeCancellations && raptor.equals(that.raptor) ); } @@ -179,6 +192,7 @@ public int hashCode() { reluctanceForMode, otherThanPreferredRoutesPenalty, unpreferredCost, + relaxTransitPriorityGroup, ignoreRealtimeUpdates, includePlannedCancellations, includeRealtimeCancellations, @@ -199,6 +213,7 @@ public String toString() { DEFAULT.otherThanPreferredRoutesPenalty ) .addObj("unpreferredCost", unpreferredCost, DEFAULT.unpreferredCost) + .addObj("relaxTransitPriorityGroup", relaxTransitPriorityGroup, CostLinearFunction.NORMAL) .addBoolIfTrue( "ignoreRealtimeUpdates", ignoreRealtimeUpdates != DEFAULT.ignoreRealtimeUpdates @@ -225,6 +240,7 @@ public static class Builder { private Map reluctanceForMode; private Cost otherThanPreferredRoutesPenalty; private CostLinearFunction unpreferredCost; + private CostLinearFunction relaxTransitPriorityGroup; private boolean ignoreRealtimeUpdates; private boolean includePlannedCancellations; private boolean includeRealtimeCancellations; @@ -237,6 +253,7 @@ public Builder(TransitPreferences original) { this.reluctanceForMode = original.reluctanceForMode; this.otherThanPreferredRoutesPenalty = original.otherThanPreferredRoutesPenalty; this.unpreferredCost = original.unpreferredCost; + this.relaxTransitPriorityGroup = original.relaxTransitPriorityGroup; this.ignoreRealtimeUpdates = original.ignoreRealtimeUpdates; this.includePlannedCancellations = original.includePlannedCancellations; this.includeRealtimeCancellations = original.includeRealtimeCancellations; @@ -285,6 +302,11 @@ public Builder setUnpreferredCostString(String constFunction) { return setUnpreferredCost(CostLinearFunction.of(constFunction)); } + public Builder withTransitGroupPriorityGeneralizedCostSlack(CostLinearFunction value) { + this.relaxTransitPriorityGroup = value; + return this; + } + public Builder setIgnoreRealtimeUpdates(boolean ignoreRealtimeUpdates) { this.ignoreRealtimeUpdates = ignoreRealtimeUpdates; 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 new file mode 100644 index 00000000000..c02862c4d79 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleParkingPreferences.java @@ -0,0 +1,208 @@ +package org.opentripplanner.routing.api.request.preference; + +import java.io.Serializable; +import java.time.Duration; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; +import org.opentripplanner.framework.model.Cost; +import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.routing.api.request.preference.filter.VehicleParkingFilter; +import org.opentripplanner.routing.api.request.preference.filter.VehicleParkingSelect; + +/** + * The parking preferences contain preferences for car and bicycle parking. These preferences + * include filtering, preference and realtime usage. + *

+ * THIS CLASS IS IMMUTABLE AND THREAD-SAFE. + */ +public final class VehicleParkingPreferences implements Serializable { + + public static final VehicleParkingPreferences DEFAULT = new VehicleParkingPreferences(); + private final Cost unpreferredVehicleParkingTagCost; + private final VehicleParkingFilter filter; + private final VehicleParkingFilter preferred; + private final Duration parkTime; + private final Cost parkCost; + + /** 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); + } + + private VehicleParkingPreferences(Builder builder) { + this.unpreferredVehicleParkingTagCost = builder.unpreferredVehicleParkingTagCost; + this.filter = + new VehicleParkingFilter( + builder.bannedVehicleParkingTags, + builder.requiredVehicleParkingTags + ); + this.preferred = + new VehicleParkingFilter( + builder.notPreferredVehicleParkingTags, + builder.preferredVehicleParkingTags + ); + this.parkTime = builder.parkTime; + this.parkCost = builder.parkCost; + } + + public static VehicleParkingPreferences.Builder of() { + return new Builder(DEFAULT); + } + + public VehicleParkingPreferences.Builder copyOf() { + return new Builder(this); + } + + /** + * What cost is applied to using parking that is not preferred. + */ + public Cost unpreferredVehicleParkingTagCost() { + return unpreferredVehicleParkingTagCost; + } + + /** + * Parking containing select filters must only be usable and parking containing with not filters + * cannot be used. + */ + public VehicleParkingFilter filter() { + return filter; + } + + /** + * Which vehicle parking tags are preferred. Vehicle parking facilities that don't have one of these + * tags receive an extra cost. + *

+ * This is useful if you want to use certain kind of facilities, like lockers for expensive e-bikes. + */ + public VehicleParkingFilter preferred() { + return preferred; + } + + /** Time to park a vehicle */ + public Duration parkTime() { + return parkTime; + } + + /** Cost of parking a bike. */ + public Cost parkCost() { + return parkCost; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + VehicleParkingPreferences that = (VehicleParkingPreferences) o; + return ( + 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) + ); + } + + @Override + public int hashCode() { + return Objects.hash(unpreferredVehicleParkingTagCost, filter, preferred, parkCost, parkTime); + } + + @Override + public String toString() { + return ToStringBuilder + .of(VehicleParkingPreferences.class) + .addObj( + "unpreferredVehicleParkingTagCost", + unpreferredVehicleParkingTagCost, + DEFAULT.unpreferredVehicleParkingTagCost + ) + .addObj("filter", filter, DEFAULT.filter) + .addObj("preferred", preferred, DEFAULT.preferred) + .addObj("parkCost", parkCost, DEFAULT.parkCost) + .addObj("parkTime", parkTime, DEFAULT.parkTime) + .toString(); + } + + public static class Builder { + + private final VehicleParkingPreferences original; + private Cost unpreferredVehicleParkingTagCost; + private List bannedVehicleParkingTags; + private List requiredVehicleParkingTags; + private List preferredVehicleParkingTags; + private List notPreferredVehicleParkingTags; + private Cost parkCost; + private Duration parkTime; + + private Builder(VehicleParkingPreferences original) { + this.original = original; + this.unpreferredVehicleParkingTagCost = original.unpreferredVehicleParkingTagCost; + this.bannedVehicleParkingTags = original.filter.not(); + this.requiredVehicleParkingTags = original.filter.select(); + this.preferredVehicleParkingTags = original.preferred.select(); + this.notPreferredVehicleParkingTags = original.preferred.not(); + this.parkCost = original.parkCost; + this.parkTime = original.parkTime; + } + + public Builder withUnpreferredVehicleParkingTagCost(int cost) { + this.unpreferredVehicleParkingTagCost = Cost.costOfSeconds(cost); + return this; + } + + public Builder withBannedVehicleParkingTags(Set bannedVehicleParkingTags) { + this.bannedVehicleParkingTags = + List.of(new VehicleParkingSelect.TagsSelect(bannedVehicleParkingTags)); + return this; + } + + public Builder withRequiredVehicleParkingTags(Set requiredVehicleParkingTags) { + this.requiredVehicleParkingTags = + List.of(new VehicleParkingSelect.TagsSelect(requiredVehicleParkingTags)); + return this; + } + + public Builder withPreferredVehicleParkingTags(Set preferredVehicleParkingTags) { + this.preferredVehicleParkingTags = + List.of(new VehicleParkingSelect.TagsSelect(preferredVehicleParkingTags)); + return this; + } + + public Builder withNotPreferredVehicleParkingTags(Set notPreferredVehicleParkingTags) { + this.notPreferredVehicleParkingTags = + List.of(new VehicleParkingSelect.TagsSelect(notPreferredVehicleParkingTags)); + return this; + } + + public Builder withParkCost(int cost) { + this.parkCost = Cost.costOfSeconds(cost); + return this; + } + + public Builder withParkTime(int seconds) { + this.parkTime = Duration.ofSeconds(seconds); + return this; + } + + public Builder withParkTime(Duration duration) { + this.parkTime = duration; + return this; + } + + public Builder apply(Consumer body) { + body.accept(this); + return this; + } + + public VehicleParkingPreferences build() { + var newObj = new VehicleParkingPreferences(this); + return original.equals(newObj) ? original : newObj; + } + } +} diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/filter/VehicleParkingFilter.java b/src/main/java/org/opentripplanner/routing/api/request/preference/filter/VehicleParkingFilter.java new file mode 100644 index 00000000000..c4f5cee422f --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/filter/VehicleParkingFilter.java @@ -0,0 +1,94 @@ +package org.opentripplanner.routing.api.request.preference.filter; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import javax.annotation.Nonnull; +import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.routing.vehicle_parking.VehicleParking; + +/** + * A filter class that checks if parking faclities match certain conditions for + * inclusion/exclusion or preference/unpreference. + */ +public class VehicleParkingFilter implements Serializable { + + private final VehicleParkingSelect[] not; + private final VehicleParkingSelect[] select; + + public VehicleParkingFilter( + Collection not, + Collection select + ) { + this.not = makeFilter(not); + this.select = makeFilter(select); + } + + public VehicleParkingFilter(VehicleParkingSelect not, VehicleParkingSelect select) { + this(List.of(not), List.of(select)); + } + + public List not() { + return Arrays.asList(not); + } + + public List select() { + return Arrays.asList(select); + } + + /** + * Create a request with no conditions. + */ + public static VehicleParkingFilter empty() { + return new VehicleParkingFilter(List.of(), List.of()); + } + + /** + * Checks if a parking facility matches the conditions defined in this filter. + */ + public boolean matches(VehicleParking p) { + for (var n : not) { + if (n.matches(p)) { + return false; + } + } + // not doesn't match and no selects means it matches + if (select.length == 0) { + return true; + } + for (var s : select) { + if (s.matches(p)) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return ToStringBuilder + .of(this.getClass()) + .addCol("not", Arrays.asList(not)) + .addCol("select", Arrays.asList(select)) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + VehicleParkingFilter that = (VehicleParkingFilter) o; + return (Arrays.equals(not, that.not) && Arrays.equals(select, that.select)); + } + + @Override + public int hashCode() { + return Arrays.hashCode(not) + Arrays.hashCode(select); + } + + @Nonnull + private static VehicleParkingSelect[] makeFilter(Collection select) { + return select.stream().filter(f -> !f.isEmpty()).toArray(VehicleParkingSelect[]::new); + } +} diff --git a/src/main/java/org/opentripplanner/routing/api/request/request/filter/VehicleParkingFilter.java b/src/main/java/org/opentripplanner/routing/api/request/preference/filter/VehicleParkingSelect.java similarity index 74% rename from src/main/java/org/opentripplanner/routing/api/request/request/filter/VehicleParkingFilter.java rename to src/main/java/org/opentripplanner/routing/api/request/preference/filter/VehicleParkingSelect.java index a4c240e7160..2d3935461d4 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/request/filter/VehicleParkingFilter.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/filter/VehicleParkingSelect.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.api.request.request.filter; +package org.opentripplanner.routing.api.request.preference.filter; import java.util.Collections; import java.util.Set; @@ -8,18 +8,18 @@ * A set of conditions that can be used to check if a parking facility should be included/excluded * or preferred/unpreferred. */ -public sealed interface VehicleParkingFilter { +public sealed interface VehicleParkingSelect { /** - * Checks if the parking facilities matches the conditions of the filter. + * Checks if the parking facilities matches the conditions of the select. */ boolean matches(VehicleParking p); /** - * Whether this filter defines any condition. + * Whether this select defines any condition. */ boolean isEmpty(); - record TagsFilter(Set tags) implements VehicleParkingFilter { + record TagsSelect(Set tags) implements VehicleParkingSelect { @Override public boolean matches(VehicleParking p) { return !Collections.disjoint(tags, p.getTags()); diff --git a/src/main/java/org/opentripplanner/routing/api/request/request/JourneyRequest.java b/src/main/java/org/opentripplanner/routing/api/request/request/JourneyRequest.java index b500bdd2398..39a775bf7f5 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/request/JourneyRequest.java +++ b/src/main/java/org/opentripplanner/routing/api/request/request/JourneyRequest.java @@ -8,7 +8,6 @@ public class JourneyRequest implements Cloneable, Serializable { // TODO VIA (Hannes): Move the fields below into StreetRequest private VehicleRentalRequest rental = new VehicleRentalRequest(); - private VehicleParkingRequest parking = new VehicleParkingRequest(); private TransitRequest transit = new TransitRequest(); private StreetRequest access = new StreetRequest(); private StreetRequest egress = new StreetRequest(); @@ -19,10 +18,6 @@ public VehicleRentalRequest rental() { return rental; } - public VehicleParkingRequest parking() { - return parking; - } - public TransitRequest transit() { return transit; } @@ -64,7 +59,6 @@ public JourneyRequest clone() { try { var clone = (JourneyRequest) super.clone(); clone.rental = this.rental.clone(); - clone.parking = this.parking.clone(); clone.transit = this.transit.clone(); clone.access = this.access.clone(); clone.egress = this.egress.clone(); 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 628b5753aea..67a56249328 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 @@ -2,11 +2,13 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.opentripplanner.model.modes.ExcludeAllTransitFilter; 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.transit.model.framework.FeedScopedId; // TODO VIA: Javadoc @@ -28,6 +30,9 @@ public class TransitRequest implements Cloneable, Serializable { private List preferredRoutes = List.of(); private List unpreferredRoutes = List.of(); + + private List priorityGroupsByAgency = new ArrayList<>(); + private List priorityGroupsGlobal = new ArrayList<>(); private DebugRaptor raptorDebugging = new DebugRaptor(); public void setBannedTripsFromString(String ids) { @@ -52,6 +57,39 @@ public void setFilters(List filters) { this.filters = filters; } + /** + * A unique group-id is assigned all patterns grouped by matching select and agency. + * In other words, two patterns matching the same select and with the same agency-id + * will get the same group-id. + *

+ * Note! Entities that are not matched are put in the BASE-GROUP with id 0. + */ + public List priorityGroupsByAgency() { + return priorityGroupsByAgency; + } + + /** + * All patterns matching the same select will be assigned the same group-id. + */ + public void addPriorityGroupsByAgency( + Collection priorityGroupsByAgency + ) { + this.priorityGroupsByAgency.addAll(priorityGroupsByAgency); + } + + /** + * A unique group-id is assigned all patterns grouped by matching selects. + *

+ * Note! Entities that are not matched are put in the BASE-GROUP with id 0. + */ + public List priorityGroupsGlobal() { + return priorityGroupsGlobal; + } + + public void addPriorityGroupsGlobal(Collection priorityGroupsGlobal) { + this.priorityGroupsGlobal.addAll(priorityGroupsGlobal); + } + @Deprecated public void setPreferredAgenciesFromString(String s) { if (!s.isEmpty()) { @@ -147,6 +185,9 @@ public TransitRequest clone() { clone.preferredRoutes = List.copyOf(this.preferredRoutes); clone.unpreferredRoutes = List.copyOf(this.unpreferredRoutes); clone.raptorDebugging = new DebugRaptor(this.raptorDebugging); + clone.priorityGroupsByAgency = new ArrayList<>(this.priorityGroupsByAgency); + clone.priorityGroupsGlobal = new ArrayList<>(this.priorityGroupsGlobal); + // filters are immutable clone.setFilters(this.filters); diff --git a/src/main/java/org/opentripplanner/routing/api/request/request/VehicleParkingRequest.java b/src/main/java/org/opentripplanner/routing/api/request/request/VehicleParkingRequest.java deleted file mode 100644 index d63ba79b990..00000000000 --- a/src/main/java/org/opentripplanner/routing/api/request/request/VehicleParkingRequest.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.opentripplanner.routing.api.request.request; - -import java.io.Serializable; -import org.opentripplanner.routing.api.request.request.filter.VehicleParkingFilterRequest; - -/** - * Class that stores information about what kind of parking lots should be used for Park & Ride - * and Bike & Ride searches. - */ -public class VehicleParkingRequest implements Cloneable, Serializable { - - private VehicleParkingFilterRequest filter = VehicleParkingFilterRequest.empty(); - private VehicleParkingFilterRequest preferred = VehicleParkingFilterRequest.empty(); - private int unpreferredTagCost = 5 * 60; - - private boolean useAvailabilityInformation = false; - - public void setFilter(VehicleParkingFilterRequest filter) { - this.filter = filter; - } - - public void setPreferred(VehicleParkingFilterRequest filter) { - this.preferred = filter; - } - - /** - * Which vehicle parking tags are preferred. Vehicle parking facilities that don't have one of these - * tags receive an extra cost. - *

- * This is useful if you want to use certain kind of facilities, like lockers for expensive e-bikes. - */ - public VehicleParkingFilterRequest preferred() { - return this.preferred; - } - - public void setUnpreferredCost(int cost) { - unpreferredTagCost = cost; - } - - public int unpreferredCost() { - return unpreferredTagCost; - } - - /** - * If realtime availability data should be used when deciding af a parking facility should be - * used. - */ - public void setUseAvailabilityInformation(boolean b) { - useAvailabilityInformation = b; - } - - public boolean useAvailabilityInformation() { - return useAvailabilityInformation; - } - - public VehicleParkingRequest clone() { - try { - return (VehicleParkingRequest) super.clone(); - } catch (CloneNotSupportedException e) { - /* this will never happen since our super is the cloneable object */ - throw new RuntimeException(e); - } - } - - public VehicleParkingFilterRequest filter() { - return filter; - } -} 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/TransitPriorityGroupSelect.java new file mode 100644 index 00000000000..6d763e9c3bc --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/api/request/request/filter/TransitPriorityGroupSelect.java @@ -0,0 +1,148 @@ +package org.opentripplanner.routing.api.request.request.filter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +/** + * Select a given set of transit routes base on the list of + * modes, sub-modes, agencies and routes. A transit entity matches + * if mode, sub-mode, agencyId or routeId matches - only one + * "thing" needs to match. + *

+ * The {@code TransitGroupSelect(modes:[BUS, TRAM], agencyIds:[A1, A3])} matches both: + *

    + *
  • {@code Entity(mode:BUS, agency:ANY)} and
  • + *
  • {@code Entity(mode:SUBWAY, agency:A3)}
  • + *
+ */ +public class TransitPriorityGroupSelect { + + private static final TransitPriorityGroupSelect DEFAULT = new TransitPriorityGroupSelect(); + + private final List modes; + private final List subModeRegexp; + private final List agencyIds; + private final List routeIds; + + public TransitPriorityGroupSelect() { + this.modes = List.of(); + this.subModeRegexp = List.of(); + this.agencyIds = List.of(); + this.routeIds = List.of(); + } + + private TransitPriorityGroupSelect(Builder builder) { + // Sort and keep only unique entries, this make this + // implementation consistent for eq/hc/toString. + this.modes = + List.copyOf( + builder.modes.stream().sorted(Comparator.comparingInt(Enum::ordinal)).distinct().toList() + ); + this.subModeRegexp = List.copyOf(builder.subModeRegexp.stream().sorted().distinct().toList()); + this.agencyIds = List.copyOf(builder.agencyIds.stream().sorted().distinct().toList()); + this.routeIds = List.copyOf(builder.routeIds.stream().sorted().distinct().toList()); + } + + public static Builder of() { + return new Builder(DEFAULT); + } + + public List modes() { + return modes; + } + + public List subModeRegexp() { + return subModeRegexp; + } + + public List agencyIds() { + return agencyIds; + } + + public List routeIds() { + return routeIds; + } + + public boolean isEmpty() { + return modes.isEmpty() && subModeRegexp.isEmpty() && agencyIds.isEmpty() && routeIds.isEmpty(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TransitPriorityGroupSelect that = (TransitPriorityGroupSelect) o; + return ( + Objects.equals(modes, that.modes) && + Objects.equals(subModeRegexp, that.subModeRegexp) && + Objects.equals(agencyIds, that.agencyIds) && + Objects.equals(routeIds, that.routeIds) + ); + } + + @Override + public int hashCode() { + return Objects.hash(modes, subModeRegexp, agencyIds, routeIds); + } + + @Override + public String toString() { + return isEmpty() + ? "TransitGroupSelect{ EMPTY }" + : ToStringBuilder + .of(TransitPriorityGroupSelect.class) + .addCol("modes", modes) + .addCol("subModeRegexp", subModeRegexp) + .addCol("agencyIds", agencyIds) + .addCol("routeIds", routeIds) + .toString(); + } + + public static class Builder { + + private final TransitPriorityGroupSelect original; + private final List modes; + private final List subModeRegexp; + private final List agencyIds; + private final List routeIds; + + public Builder(TransitPriorityGroupSelect original) { + this.original = original; + this.modes = new ArrayList<>(original.modes); + this.subModeRegexp = new ArrayList<>(original.subModeRegexp); + this.agencyIds = new ArrayList<>(original.agencyIds); + this.routeIds = new ArrayList<>(original.routeIds); + } + + public Builder addModes(Collection modes) { + this.modes.addAll(modes); + return this; + } + + public Builder addSubModeRegexp(Collection subModeRegexp) { + this.subModeRegexp.addAll(subModeRegexp); + return this; + } + + public Builder addAgencyIds(Collection agencyIds) { + this.agencyIds.addAll(agencyIds); + return this; + } + + public Builder addRouteIds(Collection routeIds) { + this.routeIds.addAll(routeIds); + return this; + } + + public TransitPriorityGroupSelect build() { + var obj = new TransitPriorityGroupSelect(this); + return original.equals(obj) ? original : obj; + } + } +} diff --git a/src/main/java/org/opentripplanner/routing/api/request/request/filter/VehicleParkingFilterRequest.java b/src/main/java/org/opentripplanner/routing/api/request/request/filter/VehicleParkingFilterRequest.java deleted file mode 100644 index abe07010bcd..00000000000 --- a/src/main/java/org/opentripplanner/routing/api/request/request/filter/VehicleParkingFilterRequest.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.opentripplanner.routing.api.request.request.filter; - -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import javax.annotation.Nonnull; -import org.opentripplanner.framework.tostring.ToStringBuilder; -import org.opentripplanner.routing.vehicle_parking.VehicleParking; - -/** - * A request object that checks if parking faclities match certain conditions for - * inclusion/exclusion or preference/unpreference. - */ -public class VehicleParkingFilterRequest { - - private final VehicleParkingFilter[] not; - private final VehicleParkingFilter[] select; - - public VehicleParkingFilterRequest( - Collection not, - Collection select - ) { - this.not = makeFilter(not); - this.select = makeFilter(select); - } - - public VehicleParkingFilterRequest(VehicleParkingFilter not, VehicleParkingFilter select) { - this(List.of(not), List.of(select)); - } - - /** - * Create a request with no conditions. - */ - public static VehicleParkingFilterRequest empty() { - return new VehicleParkingFilterRequest(List.of(), List.of()); - } - - /** - * Checks if a parking facility matches the conditions defined in this filter. - */ - public boolean matches(VehicleParking p) { - for (var n : not) { - if (n.matches(p)) { - return false; - } - } - // not doesn't match and no selects means it matches - if (select.length == 0) { - return true; - } - for (var s : select) { - if (s.matches(p)) { - return true; - } - } - return false; - } - - @Override - public String toString() { - return ToStringBuilder - .of(this.getClass()) - .addCol("not", Arrays.asList(not)) - .addCol("select", Arrays.asList(select)) - .toString(); - } - - @Nonnull - private static VehicleParkingFilter[] makeFilter(Collection select) { - return select.stream().filter(f -> !f.isEmpty()).toArray(VehicleParkingFilter[]::new); - } -} diff --git a/src/main/java/org/opentripplanner/routing/api/response/RoutingResponse.java b/src/main/java/org/opentripplanner/routing/api/response/RoutingResponse.java index 85b195943d6..f7bcaca72e2 100644 --- a/src/main/java/org/opentripplanner/routing/api/response/RoutingResponse.java +++ b/src/main/java/org/opentripplanner/routing/api/response/RoutingResponse.java @@ -3,7 +3,7 @@ import java.util.List; import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.model.plan.TripPlan; -import org.opentripplanner.model.plan.pagecursor.PageCursor; +import org.opentripplanner.model.plan.paging.cursor.PageCursor; import org.opentripplanner.routing.framework.DebugTimingAggregator; public class RoutingResponse { diff --git a/src/main/java/org/opentripplanner/routing/api/response/TripSearchMetadata.java b/src/main/java/org/opentripplanner/routing/api/response/TripSearchMetadata.java index a89b1413189..a5eb5842f23 100644 --- a/src/main/java/org/opentripplanner/routing/api/response/TripSearchMetadata.java +++ b/src/main/java/org/opentripplanner/routing/api/response/TripSearchMetadata.java @@ -17,15 +17,6 @@ public class TripSearchMetadata { */ public Duration searchWindowUsed; - /** - * This is the suggested search time for the "next page" or time window. Insert it together with - * the {@link #searchWindowUsed} in the request to get a new set of trips following in the - * time-window AFTER the current search. No duplicate trips should be returned, unless a trip is - * delayed and new realtime-data is available. - */ - @Deprecated - public Instant nextDateTime; - /** * This is the suggested search time for the "previous page" or time window. Insert it together * with the {@link #searchWindowUsed} in the request to get a new set of trips preceding in the @@ -37,45 +28,56 @@ public class TripSearchMetadata { @Deprecated public Instant prevDateTime; + /** + * This is the suggested search time for the "next page" or time window. Insert it together with + * the {@link #searchWindowUsed} in the request to get a new set of trips following in the + * time-window AFTER the current search. No duplicate trips should be returned, unless a trip is + * delayed and new realtime-data is available. + */ + @Deprecated + public Instant nextDateTime; + private TripSearchMetadata( Duration searchWindowUsed, Instant prevDateTime, Instant nextDateTime ) { this.searchWindowUsed = searchWindowUsed; - this.nextDateTime = nextDateTime; this.prevDateTime = prevDateTime; + this.nextDateTime = nextDateTime; } public static TripSearchMetadata createForArriveBy( - Instant reqTime, - int searchWindowUsed, - @Nullable Instant previousTimeInclusive + Instant earliestDepartureTimeUsed, + Duration searchWindowUsed, + @Nullable Instant firstDepartureTime ) { - Instant prevDateTime = previousTimeInclusive == null - ? reqTime.minusSeconds(searchWindowUsed) - // Round up to closest minute, to meet the _inclusive_ requirement - : previousTimeInclusive.minusSeconds(1).truncatedTo(ChronoUnit.MINUTES).plusSeconds(60); + Instant actualEdt = firstDepartureTime == null + ? earliestDepartureTimeUsed + // Round down to the minute before to avoid duplicates. This may cause missed itineraries. + : firstDepartureTime.minusSeconds(60).truncatedTo(ChronoUnit.MINUTES); return new TripSearchMetadata( - Duration.ofSeconds(searchWindowUsed), - prevDateTime, - reqTime.plusSeconds(searchWindowUsed) + searchWindowUsed, + actualEdt.minus(searchWindowUsed), + earliestDepartureTimeUsed.plus(searchWindowUsed) ); } public static TripSearchMetadata createForDepartAfter( - Instant reqTime, - int searchWindowUsed, - Instant nextDateTimeExcusive + Instant requestDepartureTime, + Duration searchWindowUsed, + Instant lastDepartureTime ) { - Instant nextDateTime = nextDateTimeExcusive == null - ? reqTime.plusSeconds(searchWindowUsed) - : nextDateTimeExcusive.truncatedTo(ChronoUnit.MINUTES); + Instant nextDateTime = lastDepartureTime == null + ? requestDepartureTime.plus(searchWindowUsed) + // There is no way to make this work properly. If we round down we get duplicates, if we + // round up we might skip itineraries. + : lastDepartureTime.plusSeconds(60).truncatedTo(ChronoUnit.MINUTES); return new TripSearchMetadata( - Duration.ofSeconds(searchWindowUsed), - reqTime.minusSeconds(searchWindowUsed), + searchWindowUsed, + requestDepartureTime.minus(searchWindowUsed), nextDateTime ); } diff --git a/src/main/java/org/opentripplanner/routing/graphfinder/DirectGraphFinder.java b/src/main/java/org/opentripplanner/routing/graphfinder/DirectGraphFinder.java index 660d225adbe..5ffa7cd2301 100644 --- a/src/main/java/org/opentripplanner/routing/graphfinder/DirectGraphFinder.java +++ b/src/main/java/org/opentripplanner/routing/graphfinder/DirectGraphFinder.java @@ -60,6 +60,7 @@ public List findClosestPlaces( List filterByModes, List filterByPlaceTypes, List filterByStops, + List filterByStations, List filterByRoutes, List filterByBikeRentalStations, TransitService transitService diff --git a/src/main/java/org/opentripplanner/routing/graphfinder/GraphFinder.java b/src/main/java/org/opentripplanner/routing/graphfinder/GraphFinder.java index 6138138ed45..4c0b0c81144 100644 --- a/src/main/java/org/opentripplanner/routing/graphfinder/GraphFinder.java +++ b/src/main/java/org/opentripplanner/routing/graphfinder/GraphFinder.java @@ -66,6 +66,7 @@ List findClosestPlaces( List filterByModes, List filterByPlaceTypes, List filterByStops, + List filterByStations, List filterByRoutes, List filterByBikeRentalStations, TransitService transitService diff --git a/src/main/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitor.java b/src/main/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitor.java index 49ef39e95ac..e71504d58f3 100644 --- a/src/main/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitor.java +++ b/src/main/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitor.java @@ -29,6 +29,7 @@ public class PlaceFinderTraverseVisitor implements TraverseVisitor private final TransitService transitService; private final Set filterByModes; private final Set filterByStops; + private final Set filterByStations; private final Set filterByRoutes; private final Set filterByVehicleRental; private final Set seenPatternAtStops = new HashSet<>(); @@ -40,6 +41,7 @@ public class PlaceFinderTraverseVisitor implements TraverseVisitor private final boolean includeVehicleRentals; private final boolean includeCarParking; private final boolean includeBikeParking; + private final boolean includeStations; private final int maxResults; private final double radiusMeters; @@ -64,23 +66,31 @@ public PlaceFinderTraverseVisitor( List filterByModes, List filterByPlaceTypes, List filterByStops, + List filterByStations, List filterByRoutes, List filterByBikeRentalStations, int maxResults, double radiusMeters ) { + if (filterByPlaceTypes == null || filterByPlaceTypes.isEmpty()) { + throw new IllegalArgumentException("No place type filter was included in request"); + } this.transitService = transitService; + this.filterByModes = toSet(filterByModes); this.filterByStops = toSet(filterByStops); + this.filterByStations = toSet(filterByStations); this.filterByRoutes = toSet(filterByRoutes); this.filterByVehicleRental = toSet(filterByBikeRentalStations); - includeStops = shouldInclude(filterByPlaceTypes, PlaceType.STOP); + includePatternAtStops = shouldInclude(filterByPlaceTypes, PlaceType.PATTERN_AT_STOP); includeVehicleRentals = shouldInclude(filterByPlaceTypes, PlaceType.VEHICLE_RENT); includeCarParking = shouldInclude(filterByPlaceTypes, PlaceType.CAR_PARK); includeBikeParking = shouldInclude(filterByPlaceTypes, PlaceType.BIKE_PARK); + includeStations = shouldInclude(filterByPlaceTypes, PlaceType.STATION); this.maxResults = maxResults; + this.radiusMeters = radiusMeters; } @@ -134,7 +144,7 @@ public SkipEdgeStrategy getSkipEdgeStrategy() { private static Set toSet(List list) { if (list == null) { - return null; + return Set.of(); } return Set.copyOf(list); } @@ -156,7 +166,7 @@ private void handleParking(VehicleParking parking, double distance) { } private boolean shouldInclude(List filterByPlaceTypes, PlaceType type) { - return filterByPlaceTypes == null || filterByPlaceTypes.contains(type); + return filterByPlaceTypes.contains(type); } private boolean stopHasPatternsWithMode(RegularStop stop, Set modes) { @@ -167,17 +177,54 @@ private boolean stopHasPatternsWithMode(RegularStop stop, Set modes .anyMatch(modes::contains); } + private boolean stopIsIncludedByStopFilter(RegularStop stop) { + return filterByStops.isEmpty() || filterByStops.contains(stop.getId()); + } + + private boolean stopIsIncludedByStationFilter(RegularStop stop) { + return ( + ((filterByStations.isEmpty() || filterByStations.contains(stop.getParentStation().getId()))) + ); + } + + private boolean stopIsIncludedByModeFilter(RegularStop stop) { + return filterByModes.isEmpty() || stopHasPatternsWithMode(stop, filterByModes); + } + + /* Checks whether the stop is included in the stop filter and whether the stop should be considered + * a stop or a station in the search.*/ + private boolean stopShouldNotBeIncludedAsStop(RegularStop stop) { + return ( + (includeStations && !stop.isPartOfStation() && !stopIsIncludedByStopFilter(stop)) || + (!includeStations && !stopIsIncludedByStopFilter(stop)) + ); + } + + /* Checks if the stop is a part of a station and whether that station is + * included in the station filter */ + private boolean stopShouldNotBeIncludedAsStation(RegularStop stop) { + return stop.isPartOfStation() && !stopIsIncludedByStationFilter(stop); + } + private void handleStop(RegularStop stop, double distance) { - if (filterByStops != null && !filterByStops.contains(stop.getId())) { - return; - } + // Do not consider stop if it is not included in the stop or mode filter + // or if it or its parent station has already been seen. if ( - includeStops && - !seenStops.contains(stop.getId()) && - (filterByModes == null || stopHasPatternsWithMode(stop, filterByModes)) + stopShouldNotBeIncludedAsStop(stop) || + stopShouldNotBeIncludedAsStation(stop) || + seenStops.contains(stop.getId()) || + seenStops.contains(stop.getStationOrStopId()) || + !stopIsIncludedByModeFilter(stop) ) { - placesFound.add(new PlaceAtDistance(stop, distance)); + return; + } + + if (includeStations && stop.getParentStation() != null) { + seenStops.add(stop.getParentStation().getId()); + placesFound.add(new PlaceAtDistance(stop.getParentStation(), distance)); + } else if (includeStops) { seenStops.add(stop.getId()); + placesFound.add(new PlaceAtDistance(stop, distance)); } } @@ -186,9 +233,9 @@ private void handlePatternsAtStop(RegularStop stop, double distance) { List patterns = transitService .getPatternsForStop(stop) .stream() - .filter(pattern -> filterByModes == null || filterByModes.contains(pattern.getMode())) + .filter(pattern -> filterByModes.isEmpty() || filterByModes.contains(pattern.getMode())) .filter(pattern -> - filterByRoutes == null || filterByRoutes.contains(pattern.getRoute().getId()) + filterByRoutes.isEmpty() || filterByRoutes.contains(pattern.getRoute().getId()) ) .filter(pattern -> pattern.canBoard(stop)) .toList(); @@ -209,7 +256,9 @@ private void handleVehicleRental(VehicleRentalPlace station, double distance) { if (!includeVehicleRentals) { return; } - if (filterByVehicleRental != null && !filterByVehicleRental.contains(station.getStationId())) { + if ( + !filterByVehicleRental.isEmpty() && !filterByVehicleRental.contains(station.getStationId()) + ) { return; } if (seenVehicleRentalPlaces.contains(station.getId())) { diff --git a/src/main/java/org/opentripplanner/routing/graphfinder/PlaceType.java b/src/main/java/org/opentripplanner/routing/graphfinder/PlaceType.java index 8f9eaaaed6b..6d4edbb00d9 100644 --- a/src/main/java/org/opentripplanner/routing/graphfinder/PlaceType.java +++ b/src/main/java/org/opentripplanner/routing/graphfinder/PlaceType.java @@ -9,4 +9,5 @@ public enum PlaceType { VEHICLE_RENT, BIKE_PARK, CAR_PARK, + STATION, } diff --git a/src/main/java/org/opentripplanner/routing/graphfinder/StreetGraphFinder.java b/src/main/java/org/opentripplanner/routing/graphfinder/StreetGraphFinder.java index 06472c9ad97..1b2b1d8f522 100644 --- a/src/main/java/org/opentripplanner/routing/graphfinder/StreetGraphFinder.java +++ b/src/main/java/org/opentripplanner/routing/graphfinder/StreetGraphFinder.java @@ -53,6 +53,7 @@ public List findClosestPlaces( List filterByModes, List filterByPlaceTypes, List filterByStops, + List filterByStations, List filterByRoutes, List filterByBikeRentalStations, TransitService transitService @@ -62,6 +63,7 @@ public List findClosestPlaces( filterByModes, filterByPlaceTypes, filterByStops, + filterByStations, filterByRoutes, filterByBikeRentalStations, maxResults, diff --git a/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java b/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java index 91398e13c73..320ffb2117d 100644 --- a/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java +++ b/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java @@ -59,9 +59,10 @@ public ViaRoutingResponse route(RouteViaRequest request) { private void logResponse(RoutingResponse response) { if (LOG.isDebugEnabled()) { + var m = response.getMetadata(); var text = MultiLineToStringBuilder .of("Response") - .addDuration("SearchWindowUsed", response.getMetadata().searchWindowUsed) + .addDuration("SearchWindowUsed", m == null ? null : m.searchWindowUsed) .add("NextPage", response.getNextPageCursor()) .add("PreviousPage", response.getPreviousPageCursor()) .addColNl( diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java index 3aadbdcd37c..e5b0fd083dc 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java @@ -215,31 +215,15 @@ public boolean hasRealTimeData() { return availability != null; } - public boolean hasSpacesAvailable( - TraverseMode traverseMode, - boolean wheelchairAccessible, - boolean useAvailability - ) { + public boolean hasSpacesAvailable(TraverseMode traverseMode, boolean wheelchairAccessible) { switch (traverseMode) { case BICYCLE: - if (useAvailability && hasRealTimeDataForMode(TraverseMode.BICYCLE, false)) { - return availability.getBicycleSpaces() > 0; - } else { - return bicyclePlaces; - } + return bicyclePlaces; case CAR: if (wheelchairAccessible) { - if (useAvailability && hasRealTimeDataForMode(TraverseMode.CAR, true)) { - return availability.getWheelchairAccessibleCarSpaces() > 0; - } else { - return wheelchairAccessibleCarPlaces; - } + return wheelchairAccessibleCarPlaces; } else { - if (useAvailability && hasRealTimeDataForMode(TraverseMode.CAR, false)) { - return availability.getCarSpaces() > 0; - } else { - return carPlaces; - } + return carPlaces; } default: return false; diff --git a/src/main/java/org/opentripplanner/service/paging/PagingService.java b/src/main/java/org/opentripplanner/service/paging/PagingService.java new file mode 100644 index 00000000000..e8f09acf4d4 --- /dev/null +++ b/src/main/java/org/opentripplanner/service/paging/PagingService.java @@ -0,0 +1,227 @@ +package org.opentripplanner.service.paging; + +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nullable; +import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.SortOrder; +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.PageType; +import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NumItinerariesFilterResults; +import org.opentripplanner.routing.api.response.TripSearchMetadata; + +public class PagingService { + + private final Duration searchWindowUsed; + private final Instant earliestDepartureTime; + private final Instant latestArrivalTime; + private final SortOrder itinerariesSortOrder; + private final boolean arriveBy; + private final int numberOfItineraries; + private final PageCursor pageCursor; + private final NumItinerariesFilterResults numItinerariesFilterResults; + private final PagingSearchWindowAdjuster searchWindowAdjuster; + private final List itineraries; + + // Lazy init + private PageCursorFactory pageCursorFactory = null; + + public PagingService( + List pagingSearchWindowAdjustments, + Duration minSearchWindowSize, + Duration maxSearchWindowSize, + @Nullable Duration searchWindowUsed, + @Nullable Instant earliestDepartureTime, + @Nullable Instant latestArrivalTime, + SortOrder itinerariesSortOrder, + boolean arriveBy, + int numberOfItineraries, + @Nullable PageCursor pageCursor, + NumItinerariesFilterResults numItinerariesFilterResults, + List itineraries + ) { + this.searchWindowUsed = searchWindowUsed; + this.earliestDepartureTime = earliestDepartureTime; + this.latestArrivalTime = latestArrivalTime; + this.itinerariesSortOrder = Objects.requireNonNull(itinerariesSortOrder); + this.arriveBy = arriveBy; + this.numberOfItineraries = numberOfItineraries; + this.pageCursor = pageCursor; + + this.numItinerariesFilterResults = numItinerariesFilterResults; + this.itineraries = Objects.requireNonNull(itineraries); + this.searchWindowAdjuster = + createSearchWindowAdjuster( + pagingSearchWindowAdjustments, + minSearchWindowSize, + maxSearchWindowSize + ); + } + + public PageCursor nextPageCursor() { + return pageCursorFactory().nextPageCursor(); + } + + public PageCursor previousPageCursor() { + return pageCursorFactory().previousPageCursor(); + } + + @Nullable + public TripSearchMetadata createTripSearchMetadata() { + if (noSuccessfulTransitSearchPerformed()) { + return null; + } + + if (arriveBy) { + return TripSearchMetadata.createForArriveBy( + earliestDepartureTime, + searchWindowUsed, + firstKeptDepartureTime() + ); + } else { + return TripSearchMetadata.createForDepartAfter( + earliestDepartureTime, + searchWindowUsed, + lastKeptDepartureTime() + ); + } + } + + private Duration calculateSearchWindowNextSearch() { + if (noSuccessfulTransitSearchPerformed()) { + return null; + } + + // SearchWindow cropped -> decrease search-window + if (numItinerariesFilterResults != null) { + boolean cropSWHead = doCropSearchWindowAtTail(); + Instant rmItineraryStartTime = numItinerariesFilterResults.pageCut().startTimeAsInstant(); + + return searchWindowAdjuster.decreaseSearchWindow( + searchWindowUsed, + earliestDepartureTime, + rmItineraryStartTime, + cropSWHead + ); + } + // (num-of-itineraries found <= numItineraries) -> increase or keep search-window + else { + int nFound = (int) itineraries + .stream() + .filter(it -> !it.isFlaggedForDeletion() && it.hasTransit()) + .count(); + + return searchWindowAdjuster.increaseOrKeepSearchWindow( + searchWindowUsed, + numberOfItineraries, + nFound + ); + } + } + + private Instant lastKeptDepartureTime() { + return numItinerariesFilterResults == null + ? null + : numItinerariesFilterResults.pageCut().startTimeAsInstant(); + } + + private Instant firstKeptDepartureTime() { + return numItinerariesFilterResults == null + ? null + : numItinerariesFilterResults.pageCut().startTimeAsInstant(); + } + + private PagingSearchWindowAdjuster createSearchWindowAdjuster( + List pagingSearchWindowAdjustments, + Duration minSearchWindowSize, + Duration maxSearchWindowSize + ) { + return new PagingSearchWindowAdjuster( + minSearchWindowSize, + maxSearchWindowSize, + pagingSearchWindowAdjustments + ); + } + + /** + * Related to {@link org.opentripplanner.routing.api.request.RouteRequest#cropItinerariesAt()}, + * but is {@code true} if we should crop the search-window head(in the beginning) or tail(in the + * end). + *

+ * For the first search we look if the sort is ascending(crop tail) or descending(crop head), and + * for paged results we look at the paging type: next(tail) and previous(head). + */ + private boolean doCropSearchWindowAtTail() { + if (pageCursor == null) { + return itinerariesSortOrder.isSortedByAscendingArrivalTime(); + } + return pageCursor.type().isNext(); + } + + private PageCursorFactory pageCursorFactory() { + if (pageCursorFactory == null) { + this.pageCursorFactory = + mapIntoPageCursorFactory(pageCursor == null ? null : pageCursor.type()); + } + return pageCursorFactory; + } + + private PageCursorFactory mapIntoPageCursorFactory(@Nullable PageType currentPageType) { + var searchWindowNextSearch = calculateSearchWindowNextSearch(); + var factory = new PageCursorFactory(itinerariesSortOrder, searchWindowNextSearch); + + if (noSuccessfulTransitSearchPerformed()) { + return factory; + } + + assertRequestPrerequisites(); + + factory = + factory.withOriginalSearch( + currentPageType, + earliestDepartureTime, + latestArrivalTime, + searchWindowUsed + ); + + if (numItinerariesFilterResults != null) { + factory = factory.withRemovedItineraries(numItinerariesFilterResults); + } + return factory; + } + + private void assertRequestPrerequisites() { + if (noSuccessfulTransitSearchPerformed()) { + throw new IllegalStateException("SearchWindow not set"); + } + if (earliestDepartureTime == null) { + throw new IllegalStateException("Earliest departure time not set"); + } + } + + /** + * Both SW and EDT must be available to compute paging tokens. + */ + private boolean noSuccessfulTransitSearchPerformed() { + return searchWindowUsed == null || earliestDepartureTime == null; + } + + @Override + public String toString() { + return ToStringBuilder + .of(PagingService.class) + .addDuration("searchWindowUsed", searchWindowUsed) + .addDateTime("earliestDepartureTime", earliestDepartureTime) + .addDateTime("latestArrivalTime", latestArrivalTime) + .addEnum("itinerariesSortOrder", itinerariesSortOrder) + .addBoolIfTrue("arriveBy", arriveBy) + .addNum("numberOfItineraries", numberOfItineraries) + .addObj("pageCursor", pageCursor) + .toString(); + } +} diff --git a/src/main/java/org/opentripplanner/standalone/OTPMain.java b/src/main/java/org/opentripplanner/standalone/OTPMain.java index f5871dde995..365e1fb49c8 100644 --- a/src/main/java/org/opentripplanner/standalone/OTPMain.java +++ b/src/main/java/org/opentripplanner/standalone/OTPMain.java @@ -222,7 +222,8 @@ private static void registerShutdownHookToGracefullyShutDownServer( TransitModel transitModel, RaptorConfig raptorConfig ) { - var hook = new Thread( + ApplicationShutdownSupport.addShutdownHook( + "server-shutdown", () -> { LOG.info("OTP shutdown started..."); UpdaterConfigurator.shutdownGraph(transitModel); @@ -230,10 +231,8 @@ private static void registerShutdownHookToGracefullyShutDownServer( WeakCollectionCleaner.DEFAULT.exit(); DeferredAuthorityFactory.exit(); LOG.info("OTP shutdown: resources released..."); - }, - "server-shutdown" + } ); - ApplicationShutdownSupport.addShutdownHook(hook, hook.getName()); } private static void setOtpConfigVersionsOnServerInfo(ConstructApplication app) { diff --git a/src/main/java/org/opentripplanner/standalone/OtpStartupInfo.java b/src/main/java/org/opentripplanner/standalone/OtpStartupInfo.java index 0b84c361fb4..c601846d889 100644 --- a/src/main/java/org/opentripplanner/standalone/OtpStartupInfo.java +++ b/src/main/java/org/opentripplanner/standalone/OtpStartupInfo.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.stream.Collectors; +import org.opentripplanner.framework.application.ApplicationShutdownSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,14 +38,10 @@ public static void logInfo() { // This is good when aggregating logs across multiple load balanced instances of OTP // Hint: a regexp filter like "^OTP (START|SHUTTING)" will list nodes going up/down LOG.info("OTP STARTING UP ({}) using Java {}", projectInfo().getVersionString(), javaVersion()); - Runtime - .getRuntime() - .addShutdownHook( - new Thread( - () -> LOG.info("OTP SHUTTING DOWN ({})", projectInfo().getVersionString()), - "server-shutdown-info" - ) - ); + ApplicationShutdownSupport.addShutdownHook( + "server-shutdown-info", + () -> LOG.info("OTP SHUTTING DOWN ({})", projectInfo().getVersionString()) + ); LOG.info(NEW_LINE + "{}", info()); } diff --git a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java index 8fa8069ba70..fa6ead99c5e 100644 --- a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java @@ -47,7 +47,7 @@ * *

* This class is not THREAD-SAFE, each HTTP request gets its own copy, but if there are multiple - * threads witch accesses this context within the HTTP Request, then the caller is responsible + * threads which accesses this context within the HTTP Request, then the caller is responsible * for the synchronization. Only request scoped components need to be synchronized - they are * potentially lazy initialized. */ @@ -107,7 +107,7 @@ public interface OtpServerRequestContext { TileRendererManager tileRendererManager(); /** - * Callback witch is injected into the {@code DirectStreetRouter}, used to visualize the + * Callback which is injected into the {@code DirectStreetRouter}, used to visualize the * search. */ @HttpRequestScoped diff --git a/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java b/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java index 4a56a0722e4..d62aa1bf41f 100644 --- a/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java @@ -114,8 +114,6 @@ public class BuildConfig implements OtpDataStoreConfig { public final boolean platformEntriesLinking; - public final boolean matchBusRoutesToStreets; - /** See {@link S3BucketConfig}. */ public final S3BucketConfig elevationBucket; @@ -271,14 +269,6 @@ When set to true (it is false by default), the elevation module will include the islandPruning = IslandPruningConfig.fromConfig(root); - matchBusRoutesToStreets = - root - .of("matchBusRoutesToStreets") - .since(V1_5) - .summary( - "Based on GTFS shape data, guess which OSM streets each bus runs on to improve stop linking." - ) - .asBoolean(false); maxDataImportIssuesPerFile = root .of("maxDataImportIssuesPerFile") diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/ConfigType.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/ConfigType.java index 18de8ab6e6c..f62ec56dbfc 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/ConfigType.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/ConfigType.java @@ -94,7 +94,7 @@ public enum ConfigType { TIME_PENALTY( JsonType.string, """ - A time-penalty is used to add a penalty to the duration/arrival-time/depature-time for + A time-penalty is used to add a penalty to the duration/arrival-time/departure-time for a path. It will be invisible to the end user, but used during the routing when comparing stop-arrival/paths. diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java index 1bad7033104..ce880058005 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java @@ -31,7 +31,7 @@ public static String kebabCase(String input) { } /** - * Used to create a list of all values with description of each value witch can be included + * Used to create a list of all values with description of each value which can be included * in documentation. The list will look like this: * ``` * - `on` Turn on. diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java index 18b4c0280a6..296c07805f9 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java @@ -41,6 +41,9 @@ public record NodeInfo( boolean skipChild ) implements Comparable { + static final String EXPERIMENTAL_FEATURE = + "**THIS IS STILL AN EXPERIMENTAL FEATURE - IT MAY CHANGE WITHOUT ANY NOTICE!**"; + static final String TYPE_QUALIFIER = "type"; static final String SOURCETYPE_QUALIFIER = "sourceType"; diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfoBuilder.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfoBuilder.java index f836e174e81..9c9303e790e 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfoBuilder.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfoBuilder.java @@ -5,6 +5,7 @@ import static org.opentripplanner.standalone.config.framework.json.ConfigType.ENUM_MAP; import static org.opentripplanner.standalone.config.framework.json.ConfigType.ENUM_SET; import static org.opentripplanner.standalone.config.framework.json.ConfigType.MAP; +import static org.opentripplanner.standalone.config.framework.json.NodeInfo.EXPERIMENTAL_FEATURE; import java.util.EnumSet; @@ -18,6 +19,7 @@ class NodeInfoBuilder { private OtpVersion since = OtpVersion.NA; private String summary = "TODO: Add short summary."; private String description = null; + private boolean experimentalFeature = false; private String defaultValue = null; private boolean required = true; private boolean skipChildren = false; @@ -63,6 +65,21 @@ NodeInfoBuilder withDescription(String description) { return this; } + String description() { + if (!experimentalFeature) { + return description; + } + if (description == null) { + return EXPERIMENTAL_FEATURE; + } + return description + "\n\n" + EXPERIMENTAL_FEATURE; + } + + NodeInfoBuilder withExperimentalFeature() { + this.experimentalFeature = true; + return this; + } + NodeInfoBuilder withOptional(String defaultValue) { this.defaultValue = defaultValue; return withOptional(); @@ -119,7 +136,7 @@ NodeInfo build() { return new NodeInfo( name, summary, - description, + description(), type, enumType, elementType, diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java index 0302fce4d3a..f1d90d0ec40 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java @@ -4,7 +4,6 @@ import static org.opentripplanner.standalone.config.framework.json.ConfigType.COST_LINEAR_FUNCTION; import static org.opentripplanner.standalone.config.framework.json.ConfigType.DOUBLE; import static org.opentripplanner.standalone.config.framework.json.ConfigType.DURATION; -import static org.opentripplanner.standalone.config.framework.json.ConfigType.ENUM; import static org.opentripplanner.standalone.config.framework.json.ConfigType.FEED_SCOPED_ID; import static org.opentripplanner.standalone.config.framework.json.ConfigType.INTEGER; import static org.opentripplanner.standalone.config.framework.json.ConfigType.LOCALE; @@ -23,7 +22,6 @@ import java.time.LocalDate; import java.time.ZoneId; import java.time.format.DateTimeParseException; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collection; import java.util.EnumMap; @@ -92,6 +90,11 @@ public ParameterBuilder description(String description) { return this; } + public ParameterBuilder experimentalFeature() { + this.info.withExperimentalFeature(); + return this; + } + /** * Add documentation for optional field with default value to a parameter. *

diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/UpdatersConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/UpdatersConfig.java index 7d8b0f7b33f..0f61fa4f7a2 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/UpdatersConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/UpdatersConfig.java @@ -125,7 +125,7 @@ private TimetableSnapshotSourceParameters timetableUpdates(NodeAdapter c) { .of("purgeExpiredData") .since(V2_2) .summary( - "Should expired realtime data be purged from the graph. Apply to GTFS-RT and Siri updates." + "Should expired real-time data be purged from the graph. Apply to GTFS-RT and Siri updates." ) .asBoolean(dflt.purgeExpiredData()) ); diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index c89670b7e0a..f6a79ea9192 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -5,6 +5,7 @@ import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_3; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_4; +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; @@ -17,16 +18,15 @@ import org.opentripplanner.routing.api.request.RequestModes; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; +import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.routing.api.request.preference.BikePreferences; import org.opentripplanner.routing.api.request.preference.CarPreferences; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; 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.routing.api.request.request.VehicleParkingRequest; -import org.opentripplanner.routing.api.request.request.filter.VehicleParkingFilter.TagsFilter; -import org.opentripplanner.routing.api.request.request.filter.VehicleParkingFilterRequest; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; import org.opentripplanner.standalone.config.sandbox.DataOverlayParametersMapper; import org.opentripplanner.transit.model.basic.TransitMode; @@ -55,7 +55,6 @@ public static RouteRequest mapRouteRequest(NodeAdapter c, RouteRequest dft) { } RouteRequest request = dft.clone(); - VehicleParkingRequest vehicleParking = request.journey().parking(); // Keep this alphabetically sorted so it is easy to check if a parameter is missing from the // mapping or duplicate exist. @@ -121,44 +120,6 @@ latest arrival time (LAT - EAT). .asDuration(dft.searchWindow()) ); - vehicleParking.setUnpreferredCost( - 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(vehicleParking.unpreferredCost()) - ); - - var bannedTags = c - .of("bannedVehicleParkingTags") - .since(V2_1) - .summary("Tags with which a vehicle parking will not be used. If empty, no tags are banned.") - .asStringSet(List.of()); - - var requiredTags = c - .of("requiredVehicleParkingTags") - .since(V2_1) - .summary( - "Tags without which a vehicle parking will not be used. If empty, no tags are required." - ) - .asStringSet(List.of()); - vehicleParking.setFilter( - new VehicleParkingFilterRequest(new TagsFilter(bannedTags), new TagsFilter(requiredTags)) - ); - - var preferredTags = 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." - ) - .asStringSet(List.of()); - - vehicleParking.setPreferred( - new VehicleParkingFilterRequest(List.of(), List.of(new TagsFilter(preferredTags))) - ); - request.setWheelchair(WheelchairConfig.wheelchairEnabled(c, WHEELCHAIR_ACCESSIBILITY)); NodeAdapter unpreferred = c @@ -206,6 +167,8 @@ cost function. The cost function (`unpreferredCost`) is defined as a linear func .asFeedScopedIds(request.journey().transit().unpreferredAgencies()) ); + TransitPriorityGroupConfig.mapTransitRequest(c, request.journey().transit()); + // Map preferences request.withPreferences(preferences -> mapPreferences(c, request, preferences)); @@ -298,7 +261,7 @@ The board time is added to the time when going from the stop (offboard) to onboa c .of("ignoreRealtimeUpdates") .since(V2_0) - .summary("When true, realtime updates are ignored during this search.") + .summary("When true, real-time updates are ignored during this search.") .asBoolean(dft.ignoreRealtimeUpdates()) ) .setOtherThanPreferredRoutesPenalty( @@ -332,14 +295,34 @@ The board time is added to the time when going from the stop (offboard) to onboa """ ) .asCostLinearFunction(dft.unpreferredCost()) + ); + + String relaxTransitPriorityGroupValue = c + .of("relaxTransitPriorityGroup") + .since(V2_5) + .summary("The relax function for transit-priority-groups") + .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. + """ ) - .withRaptor(it -> - c - .of("relaxTransitSearchGeneralizedCostAtDestination") - .since(V2_3) - .summary("Whether non-optimal transit paths at the destination should be returned") - .description( - """ + .asString(dft.relaxTransitPriorityGroup().toString()); + + if (relaxTransitPriorityGroupValue != null) { + builder.withTransitGroupPriorityGeneralizedCostSlack( + CostLinearFunction.of(relaxTransitPriorityGroupValue) + ); + } + + builder.withRaptor(it -> + c + .of("relaxTransitSearchGeneralizedCostAtDestination") + .since(V2_3) + .summary("Whether non-optimal transit paths at the destination should be returned") + .description( + """ Let c be the existing minimum pareto optimal generalized cost to beat. Then a trip with cost c' is accepted if the following is true: `c' < Math.round(c * relaxRaptorCostCriteria)`. @@ -349,10 +332,10 @@ The board time is added to the time when going from the stop (offboard) to onboa Values equals or less than zero is not allowed. Values greater than 2.0 are not supported, due to performance reasons. """ - ) - .asDoubleOptional() - .ifPresent(it::withRelaxGeneralizedCostAtDestination) - ); + ) + .asDoubleOptional() + .ifPresent(it::withRelaxGeneralizedCostAtDestination) + ); } private static void mapBikePreferences(NodeAdapter c, BikePreferences.Builder builder) { @@ -385,12 +368,6 @@ private static void mapBikePreferences(NodeAdapter c, BikePreferences.Builder bu ) .asInt(dft.boardCost()) ) - .withParkTime( - c.of("bikeParkTime").since(V2_0).summary("Time to park a bike.").asInt(dft.parkTime()) - ) - .withParkCost( - c.of("bikeParkCost").since(V2_0).summary("Cost to park a bike.").asInt(dft.parkCost()) - ) .withWalkingSpeed( c .of("bikeWalkingSpeed") @@ -462,6 +439,75 @@ private static void mapBikePreferences(NodeAdapter c, BikePreferences.Builder bu "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()) + ) ); } @@ -679,12 +725,6 @@ private static void mapCarPreferences(NodeAdapter c, CarPreferences.Builder buil ) .asInt(dft.dropoffTime()) ) - .withParkCost( - c.of("carParkCost").since(V2_1).summary("Cost of parking a car.").asInt(dft.parkCost()) - ) - .withParkTime( - c.of("carParkTime").since(V2_1).summary("Time to park a car").asInt(dft.parkTime()) - ) .withPickupCost( c .of("carPickupCost") @@ -712,6 +752,75 @@ private static void mapCarPreferences(NodeAdapter c, CarPreferences.Builder buil .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()) + ) ); } diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java new file mode 100644 index 00000000000..51faafc7cbf --- /dev/null +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/TransitPriorityGroupConfig.java @@ -0,0 +1,94 @@ +package org.opentripplanner.standalone.config.routerequest; + +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.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.standalone.config.framework.json.NodeAdapter; +import org.opentripplanner.standalone.config.framework.json.OtpVersion; +import org.opentripplanner.transit.model.basic.TransitMode; + +public class TransitPriorityGroupConfig { + + public static void mapTransitRequest(NodeAdapter root, TransitRequest transit) { + var c = root + .of("transitPriorityGroups") + .since(OtpVersion.V2_5) + .summary("Transit priority groups configuration") + .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 + 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. + """ + ) + .experimentalFeature() + .asObject(); + + transit.addPriorityGroupsByAgency( + TransitPriorityGroupConfig.mapList( + c, + "byAgency", + "All groups here are split by agency. For example if you list mode " + + "[RAIL, COACH] then all rail and coach services run by an agency get the same " + + "group-id." + ) + ); + transit.addPriorityGroupsGlobal( + TransitPriorityGroupConfig.mapList( + c, + "global", + "All services matching a 'global' group will get the same group-id. Use this " + + "to assign the same id to a specific mode/sub-mode/route." + ) + ); + } + + private static Collection mapList( + NodeAdapter root, + String parameterName, + String description + ) { + return root + .of(parameterName) + .since(V2_5) + .summary("Configuration for transit priority groups.") + .description(description + " The max total number of group-ids are 32, so be careful.") + .asObjects(TransitPriorityGroupConfig::mapTransitGroupSelect); + } + + private static TransitPriorityGroupSelect mapTransitGroupSelect(NodeAdapter c) { + return TransitPriorityGroupSelect + .of() + .addModes( + c + .of("modes") + .since(V2_5) + .summary("List all modes to select for this group.") + .asEnumSet(TransitMode.class) + ) + .addSubModeRegexp( + c + .of("subModes") + .since(V2_5) + .summary("List a set of regular expressions for matching sub-modes.") + .asStringList(List.of()) + ) + .addAgencyIds( + c.of("agencies").since(V2_3).summary("List agency ids to match.").asFeedScopedIds(List.of()) + ) + .addRouteIds( + c.of("routes").since(V2_3).summary("List route ids to match.").asFeedScopedIds(List.of()) + ) + .build(); + } +} diff --git a/src/main/java/org/opentripplanner/standalone/config/sandbox/TransmodelAPIConfig.java b/src/main/java/org/opentripplanner/standalone/config/sandbox/TransmodelAPIConfig.java index c4261f385c2..c6728f90d63 100644 --- a/src/main/java/org/opentripplanner/standalone/config/sandbox/TransmodelAPIConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/sandbox/TransmodelAPIConfig.java @@ -5,7 +5,7 @@ import java.util.Collection; import java.util.Set; -import org.opentripplanner.ext.transmodelapi.TransmodelAPIParameters; +import org.opentripplanner.apis.transmodel.TransmodelAPIParameters; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; /** diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java index 3191a67aacd..487e8489128 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -2,11 +2,11 @@ import jakarta.ws.rs.core.Application; import javax.annotation.Nullable; +import org.opentripplanner.apis.transmodel.TransmodelAPI; import org.opentripplanner.datastore.api.DataSource; import org.opentripplanner.ext.emissions.EmissionsDataModel; import org.opentripplanner.ext.geocoder.LuceneIndex; import org.opentripplanner.ext.stopconsolidation.StopConsolidationRepository; -import org.opentripplanner.ext.transmodelapi.TransmodelAPI; import org.opentripplanner.framework.application.LogMDCSupport; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.logging.ProgressTracker; @@ -167,7 +167,7 @@ private void setupTransitRoutingServer() { initializeTransferCache(routerConfig().transitTuningConfig(), transitModel()); - if (OTPFeature.SandboxAPITransmodelApi.isOn()) { + if (OTPFeature.TransmodelGraphQlApi.isOn()) { TransmodelAPI.setUp( routerConfig().transmodelApi(), transitModel(), diff --git a/src/main/java/org/opentripplanner/standalone/server/GrizzlyServer.java b/src/main/java/org/opentripplanner/standalone/server/GrizzlyServer.java index a0a2228529e..c422e9c24f3 100644 --- a/src/main/java/org/opentripplanner/standalone/server/GrizzlyServer.java +++ b/src/main/java/org/opentripplanner/standalone/server/GrizzlyServer.java @@ -1,10 +1,14 @@ package org.opentripplanner.standalone.server; +import static org.opentripplanner.framework.application.ApplicationShutdownSupport.addShutdownHook; +import static org.opentripplanner.framework.application.ApplicationShutdownSupport.removeShutdownHook; + import com.google.common.util.concurrent.ThreadFactoryBuilder; import jakarta.ws.rs.core.Application; import java.io.IOException; import java.net.BindException; import java.time.Duration; +import java.util.Optional; import org.glassfish.grizzly.http.CompressionConfig; import org.glassfish.grizzly.http.server.CLStaticHttpHandler; import org.glassfish.grizzly.http.server.HttpHandler; @@ -13,7 +17,6 @@ import org.glassfish.grizzly.http.server.StaticHttpHandler; import org.glassfish.grizzly.threadpool.ThreadPoolConfig; import org.glassfish.jersey.server.ContainerFactory; -import org.opentripplanner.framework.application.ApplicationShutdownSupport; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.standalone.config.CommandLineParameters; import org.slf4j.Logger; @@ -126,10 +129,11 @@ public void run() { // Graph graph = gs.getGraph(); // httpServer.getServerConfiguration().addHttpHandler(new OTPHttpHandler(graph), "/test/*"); - // Add shutdown hook to gracefully shut down Grizzly. - // Signal handling (sun.misc.Signal) is potentially not available on all JVMs. - Thread shutdownThread = new Thread(httpServer::shutdown, "grizzly-shutdown"); - ApplicationShutdownSupport.addShutdownHook(shutdownThread, shutdownThread.getName()); + // Add shutdown hook to gracefully shut down Grizzly. If no thread is returned then shutdown is already in progress. + Optional shutdownThread = addShutdownHook("grizzly-shutdown", httpServer::shutdown); + if (!shutdownThread.isPresent()) { + return; + } /* RELINQUISH CONTROL TO THE SERVER THREAD */ try { @@ -144,8 +148,7 @@ public void run() { LOG.info("Interrupted, shutting down."); } - // Clean up graceful shutdown hook before shutting down Grizzly. - Runtime.getRuntime().removeShutdownHook(shutdownThread); + shutdownThread.ifPresent(thread -> removeShutdownHook(thread)); httpServer.shutdown(); } diff --git a/src/main/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLink.java b/src/main/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLink.java index 460a5e2416a..e682a7bfac1 100644 --- a/src/main/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLink.java +++ b/src/main/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLink.java @@ -4,7 +4,7 @@ import org.locationtech.jts.geom.LineString; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.tostring.ToStringBuilder; -import org.opentripplanner.routing.api.request.request.VehicleParkingRequest; +import org.opentripplanner.routing.api.request.preference.VehicleParkingPreferences; import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.street.model.vertex.StreetVertex; import org.opentripplanner.street.model.vertex.VehicleParkingEntranceVertex; @@ -68,8 +68,11 @@ public State[] traverse(State s0) { } var vehicleParking = vehicleParkingEntranceVertex.getVehicleParking(); - final VehicleParkingRequest parkingRequest = s0.getRequest().parking(); - if (traversalBanned(parkingRequest, vehicleParking)) { + final VehicleParkingPreferences parkingPreferences = s0 + .getRequest() + .preferences() + .parking(s0.currentMode()); + if (traversalBanned(parkingPreferences, vehicleParking)) { return State.empty(); } @@ -81,10 +84,10 @@ public State[] traverse(State s0) { } private boolean traversalBanned( - VehicleParkingRequest parkingRequest, + VehicleParkingPreferences parkingPreferences, VehicleParking vehicleParking ) { - return !parkingRequest.filter().matches(vehicleParking); + return !parkingPreferences.filter().matches(vehicleParking); } @Override 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 93a2b9bed0c..be909c1dc4c 100644 --- a/src/main/java/org/opentripplanner/street/model/edge/VehicleParkingEdge.java +++ b/src/main/java/org/opentripplanner/street/model/edge/VehicleParkingEdge.java @@ -1,12 +1,14 @@ package org.opentripplanner.street.model.edge; +import java.time.Duration; import javax.annotation.Nonnull; import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.framework.model.Cost; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.api.request.preference.BikePreferences; import org.opentripplanner.routing.api.request.preference.CarPreferences; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; -import org.opentripplanner.routing.api.request.request.VehicleParkingRequest; +import org.opentripplanner.routing.api.request.preference.VehicleParkingPreferences; import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.street.model.vertex.VehicleParkingEntranceVertex; import org.opentripplanner.street.search.TraverseMode; @@ -92,33 +94,43 @@ protected State[] traverseUnPark(State s0) { if (streetMode.includesBiking()) { final BikePreferences bike = s0.getPreferences().bike(); - return traverseUnPark(s0, bike.parkCost(), bike.parkTime(), TraverseMode.BICYCLE); + return traverseUnPark( + s0, + bike.parking().parkCost(), + bike.parking().parkTime(), + TraverseMode.BICYCLE + ); } else if (streetMode.includesDriving()) { final CarPreferences car = s0.getPreferences().car(); - return traverseUnPark(s0, car.parkCost(), car.parkTime(), TraverseMode.CAR); + return traverseUnPark( + s0, + car.parking().parkCost(), + car.parking().parkTime(), + TraverseMode.CAR + ); } else { return State.empty(); } } - private State[] traverseUnPark(State s0, int parkingCost, int parkingTime, TraverseMode mode) { + private State[] traverseUnPark( + State s0, + Cost parkingCost, + Duration parkingTime, + TraverseMode mode + ) { final StreetSearchRequest request = s0.getRequest(); - if ( - !vehicleParking.hasSpacesAvailable( - mode, - request.wheelchair(), - request.parking().useAvailabilityInformation() - ) - ) { + if (!vehicleParking.hasSpacesAvailable(mode, request.wheelchair())) { return State.empty(); } StateEditor s0e = s0.edit(this); - s0e.incrementWeight(parkingCost); - s0e.incrementTimeInSeconds(parkingTime); + s0e.incrementWeight(parkingCost.toSeconds()); + s0e.incrementTimeInSeconds((int) parkingTime.toSeconds()); s0e.setVehicleParked(false, mode); - addUnpreferredTagCost(request.parking(), s0e); + var parkingPreferences = s0.getRequest().preferences().parking(s0.currentMode()); + addUnpreferredTagCost(parkingPreferences, s0e); return s0e.makeStateArray(); } @@ -137,38 +149,41 @@ private State[] traversePark(State s0) { return State.empty(); } - return traversePark(s0, preferences.bike().parkCost(), preferences.bike().parkTime()); + return traversePark( + s0, + preferences.bike().parking().parkCost(), + preferences.bike().parking().parkTime() + ); } else if (streetMode.includesDriving()) { - return traversePark(s0, preferences.car().parkCost(), preferences.car().parkTime()); + return traversePark( + s0, + preferences.car().parking().parkCost(), + preferences.car().parking().parkTime() + ); } else { return State.empty(); } } - private State[] traversePark(State s0, int parkingCost, int parkingTime) { - if ( - !vehicleParking.hasSpacesAvailable( - s0.currentMode(), - s0.getRequest().wheelchair(), - s0.getRequest().parking().useAvailabilityInformation() - ) - ) { + private State[] traversePark(State s0, Cost parkingCost, Duration parkingTime) { + if (!vehicleParking.hasSpacesAvailable(s0.currentMode(), s0.getRequest().wheelchair())) { return State.empty(); } StateEditor s0e = s0.edit(this); - s0e.incrementWeight(parkingCost); - s0e.incrementTimeInSeconds(parkingTime); + s0e.incrementWeight(parkingCost.toSeconds()); + s0e.incrementTimeInSeconds((int) parkingTime.toSeconds()); s0e.setVehicleParked(true, TraverseMode.WALK); - addUnpreferredTagCost(s0.getRequest().parking(), s0e); + var parkingPreferences = s0.getRequest().preferences().parking(s0.currentMode()); + addUnpreferredTagCost(parkingPreferences, s0e); return s0e.makeStateArray(); } - private void addUnpreferredTagCost(VehicleParkingRequest req, StateEditor s0e) { - if (!req.preferred().matches(vehicleParking)) { - s0e.incrementWeight(req.unpreferredCost()); + private void addUnpreferredTagCost(VehicleParkingPreferences preferences, StateEditor s0e) { + if (!preferences.preferred().matches(vehicleParking)) { + s0e.incrementWeight(preferences.unpreferredVehicleParkingTagCost().toSeconds()); } } } diff --git a/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequest.java b/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequest.java index 6f44351d27c..6d8bd5783f3 100644 --- a/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequest.java +++ b/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequest.java @@ -12,7 +12,6 @@ import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; -import org.opentripplanner.routing.api.request.request.VehicleParkingRequest; import org.opentripplanner.routing.api.request.request.VehicleRentalRequest; import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.intersection_model.IntersectionTraversalCalculator; @@ -40,7 +39,6 @@ public class StreetSearchRequest implements AStarRequest { private final StreetMode mode; private final boolean arriveBy; private final boolean wheelchair; - private final VehicleParkingRequest parking; private final VehicleRentalRequest rental; private final GenericLocation from; @@ -62,7 +60,6 @@ private StreetSearchRequest() { this.mode = StreetMode.WALK; this.arriveBy = false; this.wheelchair = false; - this.parking = new VehicleParkingRequest(); this.rental = new VehicleRentalRequest(); this.from = null; this.fromEnvelope = null; @@ -76,7 +73,6 @@ private StreetSearchRequest() { this.mode = builder.mode; this.arriveBy = builder.arriveBy; this.wheelchair = builder.wheelchair; - this.parking = builder.parking; this.rental = builder.rental; this.from = builder.from; this.fromEnvelope = createEnvelope(from); @@ -119,10 +115,6 @@ public boolean wheelchair() { return wheelchair; } - public VehicleParkingRequest parking() { - return parking; - } - public VehicleRentalRequest rental() { return rental; } diff --git a/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequestBuilder.java b/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequestBuilder.java index 1f102c6b45f..439e65a3289 100644 --- a/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequestBuilder.java +++ b/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequestBuilder.java @@ -5,7 +5,6 @@ import org.opentripplanner.model.GenericLocation; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; -import org.opentripplanner.routing.api.request.request.VehicleParkingRequest; import org.opentripplanner.routing.api.request.request.VehicleRentalRequest; public class StreetSearchRequestBuilder { @@ -15,7 +14,6 @@ public class StreetSearchRequestBuilder { RoutingPreferences preferences; boolean arriveBy; boolean wheelchair; - VehicleParkingRequest parking; VehicleRentalRequest rental; GenericLocation from; GenericLocation to; @@ -26,7 +24,6 @@ public class StreetSearchRequestBuilder { this.preferences = original.preferences(); this.arriveBy = original.arriveBy(); this.wheelchair = original.wheelchair(); - this.parking = original.parking().clone(); this.rental = original.rental(); this.from = original.from(); this.to = original.to(); @@ -61,11 +58,6 @@ public StreetSearchRequestBuilder withWheelchair(boolean wheelchair) { return this; } - public StreetSearchRequestBuilder withParking(VehicleParkingRequest parking) { - this.parking = parking; - return this; - } - public StreetSearchRequestBuilder withRental(VehicleRentalRequest rental) { this.rental = rental; return this; diff --git a/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequestMapper.java b/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequestMapper.java index 902b6d6d92a..9f1f3c567f8 100644 --- a/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequestMapper.java +++ b/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequestMapper.java @@ -11,7 +11,6 @@ public static StreetSearchRequestBuilder map(RouteRequest opt) { .withStartTime(opt.dateTime()) .withPreferences(opt.preferences()) .withWheelchair(opt.wheelchair()) - .withParking(opt.journey().parking()) .withRental(opt.journey().rental()) .withFrom(opt.from()) .withTo(opt.to()); @@ -23,7 +22,6 @@ public static StreetSearchRequestBuilder mapToTransferRequest(RouteRequest opt) .withStartTime(Instant.ofEpochSecond(0)) .withPreferences(opt.preferences()) .withWheelchair(opt.wheelchair()) - .withParking(opt.journey().parking()) .withRental(opt.journey().rental()) .withMode(opt.journey().transfer().mode()); } diff --git a/src/main/java/org/opentripplanner/transit/model/framework/DataValidationException.java b/src/main/java/org/opentripplanner/transit/model/framework/DataValidationException.java new file mode 100644 index 00000000000..20848ebecc1 --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/framework/DataValidationException.java @@ -0,0 +1,35 @@ +package org.opentripplanner.transit.model.framework; + +import org.opentripplanner.framework.error.OtpError; + +/** + * This class is used to throw a data validation exception. It holds an error which can be + * inserted into build issue store, into the updater logs or returned to the APIs. The framework + * to catch and handle this is NOT IN PLACE, see + * Error code design #5070. + *

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

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

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

+ * See https://github.com/opentripplanner/OpenTripPlanner/issues/5070 */ +@Deprecated public abstract sealed class Result { private Result() {} diff --git a/src/main/java/org/opentripplanner/transit/model/framework/TransitBuilder.java b/src/main/java/org/opentripplanner/transit/model/framework/TransitBuilder.java index bd18690d9a3..52fd68165bc 100644 --- a/src/main/java/org/opentripplanner/transit/model/framework/TransitBuilder.java +++ b/src/main/java/org/opentripplanner/transit/model/framework/TransitBuilder.java @@ -5,7 +5,7 @@ public interface TransitBuilder, B extends Transit * Build a new object based on the values set in the builder. This method is NOT context aware - * any context is not updated. Use the {@link TransitEntityBuilder#save()} method instead to * build an object and store it in the context. This method is useful if you need to build an - * object witch should be request scoped or used in a test. + * object which should be request scoped or used in a test. *

* For value objects are stored as "part of" an entity, but OTP tries to reuse objects using the * {@code Deduplicator}. This method may or may not be context aware, using a deduplicator to diff --git a/src/main/java/org/opentripplanner/transit/model/network/RoutingTripPattern.java b/src/main/java/org/opentripplanner/transit/model/network/RoutingTripPattern.java index 0d319af0529..00033b5f798 100644 --- a/src/main/java/org/opentripplanner/transit/model/network/RoutingTripPattern.java +++ b/src/main/java/org/opentripplanner/transit/model/network/RoutingTripPattern.java @@ -12,7 +12,7 @@ * - The RTP is accessed frequently during the Raptor search, and we want it to be as small as * possible to load/access it in the cache and CPU for performance reasons. * - Also, we deduplicate these so a RTP can be reused by more than one TP. - * - This also provide explicit documentation on witch fields are used during a search and which + * - This also provide explicit documentation on which fields are used during a search and which * are not. */ public class RoutingTripPattern implements DefaultTripPattern, Serializable { diff --git a/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java b/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java index e89ea1940f6..7057d9fd56e 100644 --- a/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java +++ b/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java @@ -159,11 +159,6 @@ public StopPattern getStopPattern() { return stopPattern; } - // TODO OTP2 this method modifies the state, it will be refactored in a subsequent step - public void setHopGeometry(int i, LineString hopGeometry) { - this.hopGeometries[i] = CompactLineStringUtils.compactLineString(hopGeometry, false); - } - public LineString getGeometry() { if (hopGeometries == null || hopGeometries.length == 0) { return null; @@ -176,10 +171,6 @@ public LineString getGeometry() { return GeometryUtils.concatenateLineStrings(lineStrings); } - public int numHopGeometries() { - return hopGeometries.length; - } - public int numberOfStops() { return stopPattern.getSize(); } diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/FrequencyEntry.java b/src/main/java/org/opentripplanner/transit/model/timetable/FrequencyEntry.java index fc34b08e0b2..61658f6b3e0 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/FrequencyEntry.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/FrequencyEntry.java @@ -6,6 +6,8 @@ /** * Uses a TripTimes to represent multiple trips following the same template at regular intervals. * (see GTFS frequencies.txt) + *

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

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

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

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

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

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

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

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

- * Also sets the realtime state to UPDATED. + * Time-shift all times on this trip. This is used when updating the time zone for the trip. */ - private void prepareForRealTimeUpdates() { - if (arrivalTimes == null) { - this.arrivalTimes = scheduledTripTimes.copyArrivalTimes(); - this.departureTimes = scheduledTripTimes.copyDepartureTimes(); - // Update the real-time state - this.realTimeState = RealTimeState.UPDATED; - this.stopRealTimeStates = new StopRealTimeState[arrivalTimes.length]; - Arrays.fill(stopRealTimeStates, StopRealTimeState.DEFAULT); - this.headsigns = scheduledTripTimes.copyHeadsigns(() -> null); - this.occupancyStatus = new OccupancyStatus[arrivalTimes.length]; - Arrays.fill(occupancyStatus, OccupancyStatus.NO_DATA_AVAILABLE); - // skip immutable types: scheduledTripTimes & wheelchairAccessibility - } - } + TripTimes adjustTimesToGraphTimeZone(Duration shiftDelta); } diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java index 1bb953b81c5..611974904f0 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java @@ -1,6 +1,6 @@ package org.opentripplanner.transit.model.timetable; -import java.util.Collection; +import java.util.List; import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model.framework.Deduplicator; @@ -18,11 +18,13 @@ public class TripTimesFactory { * non-interpolated stoptimes should already be marked at timepoints by a previous filtering * step. */ - public static TripTimes tripTimes( + public static RealTimeTripTimes tripTimes( Trip trip, - Collection stopTimes, + List stopTimes, Deduplicator deduplicator ) { - return new TripTimes(StopTimeToScheduledTripTimesMapper.map(trip, stopTimes, deduplicator)); + return new RealTimeTripTimes( + StopTimeToScheduledTripTimesMapper.map(trip, stopTimes, deduplicator) + ); } } diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ValidationError.java b/src/main/java/org/opentripplanner/transit/model/timetable/ValidationError.java deleted file mode 100644 index 1e150218dc5..00000000000 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ValidationError.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.opentripplanner.transit.model.timetable; - -/** - * Details about why a {@link TripTimes} instance is invalid. - */ -public record ValidationError(ErrorCode code, int stopIndex) { - public enum ErrorCode { - NEGATIVE_DWELL_TIME, - NEGATIVE_HOP_TIME, - } -} diff --git a/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java b/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java index 41e28e616d8..0d08bcf34b2 100644 --- a/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java +++ b/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java @@ -465,15 +465,6 @@ private TimetableSnapshot lazyGetTimeTableSnapShot() { @Override public TripOnServiceDate getTripOnServiceDateById(FeedScopedId datedServiceJourneyId) { - TimetableSnapshot timetableSnapshot = lazyGetTimeTableSnapShot(); - if (timetableSnapshot != null) { - TripOnServiceDate tripOnServiceDate = timetableSnapshot - .getRealtimeAddedTripOnServiceDate() - .get(datedServiceJourneyId); - if (tripOnServiceDate != null) { - return tripOnServiceDate; - } - } return transitModelIndex.getTripOnServiceDateById().get(datedServiceJourneyId); } @@ -486,15 +477,6 @@ public Collection getAllTripOnServiceDates() { public TripOnServiceDate getTripOnServiceDateForTripAndDay( TripIdAndServiceDate tripIdAndServiceDate ) { - TimetableSnapshot timetableSnapshot = lazyGetTimeTableSnapShot(); - if (timetableSnapshot != null) { - TripOnServiceDate tripOnServiceDate = timetableSnapshot - .getRealtimeAddedTripOnServiceDateByTripIdAndServiceDate() - .get(tripIdAndServiceDate); - if (tripOnServiceDate != null) { - return tripOnServiceDate; - } - } return transitModelIndex.getTripOnServiceDateForTripAndDay().get(tripIdAndServiceDate); } diff --git a/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java b/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java new file mode 100644 index 00000000000..dee103bc385 --- /dev/null +++ b/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java @@ -0,0 +1,36 @@ +package org.opentripplanner.updater.spi; + +import org.opentripplanner.transit.model.framework.DataValidationException; +import org.opentripplanner.transit.model.framework.Result; +import org.opentripplanner.transit.model.timetable.TimetableValidationError; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Converts a {@link TimetableValidationError} to the model of the updater ready to be consumed + * by the metrics APIS and logs. + */ +public class DataValidationExceptionMapper { + + private static final Logger LOG = LoggerFactory.getLogger(DataValidationExceptionMapper.class); + + public static Result toResult(DataValidationException error) { + if (error.error() instanceof TimetableValidationError tt) { + return Result.failure( + new UpdateError(tt.trip().getId(), mapTimeTableError(tt.code()), tt.stopIndex()) + ); + } + // The mapper should handle all possible errors + LOG.error("Unhandled error: " + error.getMessage(), error); + return Result.failure(UpdateError.noTripId(UpdateError.UpdateErrorType.UNKNOWN)); + } + + private static UpdateError.UpdateErrorType mapTimeTableError( + TimetableValidationError.ErrorCode code + ) { + return switch (code) { + case NEGATIVE_DWELL_TIME -> UpdateError.UpdateErrorType.NEGATIVE_DWELL_TIME; + case NEGATIVE_HOP_TIME -> UpdateError.UpdateErrorType.NEGATIVE_HOP_TIME; + }; + } +} diff --git a/src/main/java/org/opentripplanner/updater/spi/TripTimesValidationMapper.java b/src/main/java/org/opentripplanner/updater/spi/TripTimesValidationMapper.java deleted file mode 100644 index 127eddeabeb..00000000000 --- a/src/main/java/org/opentripplanner/updater/spi/TripTimesValidationMapper.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.opentripplanner.updater.spi; - -import org.opentripplanner.transit.model.framework.FeedScopedId; -import org.opentripplanner.transit.model.framework.Result; -import org.opentripplanner.transit.model.timetable.ValidationError; - -/** - * Converts a {@link ValidationError} to the model of the updater ready to be consumed - * by the metrics APIS and logs. - */ -public class TripTimesValidationMapper { - - public static Result toResult( - FeedScopedId tripId, - ValidationError validationError - ) { - var type = - switch (validationError.code()) { - case NEGATIVE_DWELL_TIME -> UpdateError.UpdateErrorType.NEGATIVE_DWELL_TIME; - case NEGATIVE_HOP_TIME -> UpdateError.UpdateErrorType.NEGATIVE_HOP_TIME; - }; - - return Result.failure(new UpdateError(tripId, type, validationError.stopIndex())); - } -} diff --git a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index 6cda95eb983..19db2c7e309 100644 --- a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -44,6 +44,7 @@ import org.opentripplanner.model.TimetableSnapshotProvider; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.TransitLayerUpdater; import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.framework.Result; @@ -53,8 +54,8 @@ import org.opentripplanner.transit.model.organization.Agency; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.RealTimeState; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.Trip; -import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitEditorService; @@ -62,6 +63,7 @@ import org.opentripplanner.updater.GtfsRealtimeFuzzyTripMatcher; import org.opentripplanner.updater.GtfsRealtimeMapper; import org.opentripplanner.updater.TimetableSnapshotSourceParameters; +import org.opentripplanner.updater.spi.DataValidationExceptionMapper; import org.opentripplanner.updater.spi.ResultLogger; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.spi.UpdateResult; @@ -119,7 +121,7 @@ public class TimetableSnapshotSource implements TimetableSnapshotProvider { */ private volatile TimetableSnapshot snapshot = null; - /** Should expired realtime data be purged from the graph. */ + /** Should expired real-time data be purged from the graph. */ private final boolean purgeExpiredData; protected LocalDate lastPurgeDate = null; @@ -279,31 +281,36 @@ public UpdateResult applyTripUpdates( tripDescriptor ); - Result result = - switch (tripScheduleRelationship) { - case SCHEDULED -> handleScheduledTrip( - tripUpdate, - tripId, - serviceDate, - backwardsDelayPropagationType - ); - case ADDED -> validateAndHandleAddedTrip( - tripUpdate, - tripDescriptor, - tripId, - serviceDate - ); - case CANCELED -> handleCanceledTrip(tripId, serviceDate, CancelationType.CANCEL); - case DELETED -> handleCanceledTrip(tripId, serviceDate, CancelationType.DELETE); - case REPLACEMENT -> validateAndHandleModifiedTrip( - tripUpdate, - tripDescriptor, - tripId, - serviceDate - ); - case UNSCHEDULED -> UpdateError.result(tripId, NOT_IMPLEMENTED_UNSCHEDULED); - case DUPLICATED -> UpdateError.result(tripId, NOT_IMPLEMENTED_DUPLICATED); - }; + Result result; + try { + result = + switch (tripScheduleRelationship) { + case SCHEDULED -> handleScheduledTrip( + tripUpdate, + tripId, + serviceDate, + backwardsDelayPropagationType + ); + case ADDED -> validateAndHandleAddedTrip( + tripUpdate, + tripDescriptor, + tripId, + serviceDate + ); + case CANCELED -> handleCanceledTrip(tripId, serviceDate, CancelationType.CANCEL); + case DELETED -> handleCanceledTrip(tripId, serviceDate, CancelationType.DELETE); + case REPLACEMENT -> validateAndHandleModifiedTrip( + tripUpdate, + tripDescriptor, + tripId, + serviceDate + ); + case UNSCHEDULED -> UpdateError.result(tripId, NOT_IMPLEMENTED_UNSCHEDULED); + case DUPLICATED -> UpdateError.result(tripId, NOT_IMPLEMENTED_DUPLICATED); + }; + } catch (DataValidationException e) { + result = DataValidationExceptionMapper.toResult(e); + } results.add(result); if (result.isFailure()) { @@ -708,7 +715,6 @@ private Result handleAddedTrip( // Just use first service id of set tripBuilder.withServiceId(serviceIds.iterator().next()); } - return addTripToGraphAndBuffer( tripBuilder.build(), tripUpdate.getVehicle(), @@ -886,7 +892,11 @@ private Result addTripToGraphAndBuffer( ); // Create new trip times - final TripTimes newTripTimes = TripTimesFactory.tripTimes(trip, stopTimes, deduplicator); + final RealTimeTripTimes newTripTimes = TripTimesFactory.tripTimes( + trip, + stopTimes, + deduplicator + ); // Update all times to mark trip times as realtime // TODO: should we incorporate the delay field if present? @@ -946,7 +956,9 @@ private boolean cancelScheduledTrip( if (tripIndex == -1) { debug(tripId, "Could not cancel scheduled trip because it's not in the timetable"); } else { - final TripTimes newTripTimes = timetable.getTripTimes(tripIndex).copyOfScheduledTimes(); + final RealTimeTripTimes newTripTimes = timetable + .getTripTimes(tripIndex) + .copyScheduledTimes(); switch (cancelationType) { case CANCEL -> newTripTimes.cancelTrip(); case DELETE -> newTripTimes.deleteTrip(); @@ -986,7 +998,9 @@ private boolean cancelPreviouslyAddedTrip( if (tripIndex == -1) { debug(tripId, "Could not cancel previously added trip on {}", serviceDate); } else { - final TripTimes newTripTimes = timetable.getTripTimes(tripIndex).copyOfScheduledTimes(); + final RealTimeTripTimes newTripTimes = timetable + .getTripTimes(tripIndex) + .copyScheduledTimes(); switch (cancelationType) { case CANCEL -> newTripTimes.cancelTrip(); case DELETE -> newTripTimes.deleteTrip(); diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 9dba2c90a5e..ac2413caeae 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1002,7 +1002,10 @@ enum FeedAlertType { } enum FilterPlaceType { - """Stops""" + """ + Stops. + NOTE: if this is selected at the same time as `STATION`, stops that have a parent station will not be returned but their parent stations will be returned instead. + """ STOP """Departure rows""" @@ -1019,6 +1022,13 @@ enum FilterPlaceType { """Parking lots that contain spaces for cars""" CAR_PARK + + + """ + Stations. + NOTE: if this is selected at the same time as `STOP`, stops that have a parent station will not be returned but their parent stations will be returned instead. + """ + STATION } type Geometry { @@ -1098,6 +1108,9 @@ input InputFilters { """Stops to include by GTFS id.""" stops: [String] + """Stations to include by GTFS id.""" + stations: [String] + """Routes to include by GTFS id.""" routes: [String] @@ -2560,7 +2573,7 @@ type QueryType { filterByModes: [Mode] """Only include places that match one of the given GTFS ids.""" - filterByIds: InputFilters + filterByIds: InputFilters @deprecated(reason: "Not actively maintained") before: String after: String first: Int @@ -3098,7 +3111,7 @@ type QueryType { omitCanceled: Boolean = true """ - When true, realtime updates are ignored during this search. Default value: false + When true, real-time updates are ignored during this search. Default value: false """ ignoreRealtimeUpdates: Boolean diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql similarity index 96% rename from src/ext/graphql/transmodelapi/schema.graphql rename to src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index a207d8d0d6f..2eff7d94020 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -158,7 +158,7 @@ type Contact { "A planned journey on a specific day" type DatedServiceJourney { - "Returns scheduled passingTimes for this dated service journey, updated with realtime-updates (if available). " + "Returns scheduled passingTimes for this dated service journey, updated with real-time-updates (if available). " estimatedCalls: [EstimatedCall] @timingData id: ID! "JourneyPattern for the dated service journey." @@ -211,7 +211,7 @@ type EstimatedCall { aimedDepartureTime: DateTime! "Booking arrangements for this EstimatedCall." bookingArrangements: BookingArrangement - "Whether stop is cancelled. This means that either the ServiceJourney has a planned cancellation, the ServiceJourney has been cancelled by realtime data, or this particular StopPoint has been cancelled. This also means that both boarding and alighting has been cancelled." + "Whether stop is cancelled. This means that either the ServiceJourney has a planned cancellation, the ServiceJourney has been cancelled by real-time data, or this particular StopPoint has been cancelled. This also means that both boarding and alighting has been cancelled." cancellation: Boolean! "The date the estimated call is valid for." date: Date! @@ -315,9 +315,9 @@ type Leg { includes both the start and end coordinate. """ elevationProfile: [ElevationProfileStep]! - "The expected, realtime adjusted date and time this leg ends." + "The expected, real-time adjusted date and time this leg ends." expectedEndTime: DateTime! - "The expected, realtime adjusted date and time this leg starts." + "The expected, real-time adjusted date and time this leg starts." expectedStartTime: DateTime! "EstimatedCall for the quay where the leg originates." fromEstimatedCall: EstimatedCall @timingData @@ -560,7 +560,7 @@ type Quay implements PlaceInterface { estimatedCalls( "Filters results by either departures, arrivals or both. For departures forBoarding has to be true and the departure time has to be within the specified time range. For arrivals, forAlight has to be true and the arrival time has to be within the specified time range. If both are asked for, either the conditions for arrivals or the conditions for departures will have to be true for an EstimatedCall to show." arrivalDeparture: ArrivalDeparture = departures, - "Indicates that realtime-cancelled trips should also be included." + "Indicates that real-time-cancelled trips should also be included." includeCancelledTrips: Boolean = false, "Limit the total number of departures returned." numberOfDepartures: Int = 5, @@ -797,7 +797,7 @@ type QueryType { filters: [TripFilterInput!], "The start location" from: Location!, - "When true, realtime updates are ignored during this search." + "When true, real-time updates are ignored during this search." ignoreRealtimeUpdates: Boolean = false, "When true, service journeys cancelled in scheduled route data will be included during this search." includePlannedCancellations: Boolean = false, @@ -824,6 +824,23 @@ type QueryType { "The list of points the journey is required to pass through." 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 + 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. + + 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 + 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. + + THIS IS STILL AN EXPERIMENTAL FEATURE - IT MAY CHANGE WITHOUT ANY NOTICE! + """ + relaxTransitPriorityGroup: 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 accepted if the following is true: @@ -835,7 +852,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, + relaxTransitSearchGeneralizedCostAtDestination: Float = null @deprecated(reason : "This is replaced by 'relaxTransitPriorityGroup'."), """ The length of the search-window in minutes. This parameter is optional. @@ -995,7 +1012,7 @@ type RoutingParameters { elevatorHopTime: Int "Whether to apply the ellipsoid->geoid offset to all elevations in the response." geoIdElevation: Boolean - "When true, realtime updates are ignored during this search." + "When true, real-time updates are ignored during this search." ignoreRealTimeUpdates: Boolean "When true, service journeys cancelled in scheduled route data will be included during this search." includedPlannedCancellations: Boolean @@ -1064,7 +1081,7 @@ type ServiceJourney { "Booking arrangements for flexible services." bookingArrangements: BookingArrangement @deprecated(reason : "BookingArrangements are defined per stop, and can be found under `passingTimes` or `estimatedCalls`") directionType: DirectionType - "Returns scheduled passingTimes for this ServiceJourney for a given date, updated with realtime-updates (if available). NB! This takes a date as argument (default=today) and returns estimatedCalls for that date and should only be used if the date is known when creating the request. For fetching estimatedCalls for a given trip.leg, use leg.serviceJourneyEstimatedCalls instead." + "Returns scheduled passingTimes for this ServiceJourney for a given date, updated with real-time-updates (if available). NB! This takes a date as argument (default=today) and returns estimatedCalls for that date and should only be used if the date is known when creating the request. For fetching estimatedCalls for a given trip.leg, use leg.serviceJourneyEstimatedCalls instead." estimatedCalls( "Date to get estimated calls for. Defaults to today." date: Date @@ -1075,7 +1092,7 @@ type ServiceJourney { line: Line! notices: [Notice!]! operator: Operator - "Returns scheduled passing times only - without realtime-updates, for realtime-data use 'estimatedCalls'" + "Returns scheduled passing times only - without real-time-updates, for realtime-data use 'estimatedCalls'" passingTimes: [TimetabledPassingTime]! @timingData "Detailed path travelled by service journey. Not available for flexible trips." pointsOnLink: PointsOnLink @@ -1105,7 +1122,7 @@ type StopPlace implements PlaceInterface { "List of visits to this stop place as part of vehicle journeys." estimatedCalls( arrivalDeparture: ArrivalDeparture = departures, - "Indicates that realtime-cancelled trips should also be included." + "Indicates that real-time-cancelled trips should also be included." includeCancelledTrips: Boolean = false, "Limit the total number of departures returned." numberOfDepartures: Int = 5, @@ -1263,9 +1280,9 @@ type TripPattern { duration: Long "Time that the trip arrives." endTime: DateTime @deprecated(reason : "Replaced with expectedEndTime") - "The expected, realtime adjusted date and time the trip ends." + "The expected, real-time adjusted date and time the trip ends." expectedEndTime: DateTime! - "The expected, realtime adjusted date and time the trip starts." + "The expected, real-time adjusted date and time the trip starts." expectedStartTime: DateTime! "Generalized cost or weight of the itinerary. Used for debugging." generalizedCost: Int @@ -1388,6 +1405,8 @@ enum AlternativeLegsFilter { sameAuthority sameLine sameMode + "Must match both subMode and mode" + sameSubmode } enum ArrivalDeparture { @@ -2001,6 +2020,22 @@ input PenaltyForStreetMode { timePenalty: DoubleFunction! } +""" +A relax-cost is used to increase the limit when comparing one cost to another cost. +This is used to include more results into the result. A `ratio=2.0` means a path(itinerary) +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. +""" +input RelaxCostInput { + "The constant value to add to the limit. Must be a positive number. The unit is cost-seconds." + constant: [ID!] = 0 + "The factor to multiply with the 'other cost'. Minimum value is 1.0." + ratio: Float = 1.0 +} + "A combination of street mode and corresponding duration" input StreetModeDurationInput { duration: Duration! diff --git a/src/test/java/org/opentripplanner/OtpArchitectureModules.java b/src/test/java/org/opentripplanner/OtpArchitectureModules.java index e4d08427c71..72520acaa1c 100644 --- a/src/test/java/org/opentripplanner/OtpArchitectureModules.java +++ b/src/test/java/org/opentripplanner/OtpArchitectureModules.java @@ -39,6 +39,7 @@ public interface OtpArchitectureModules { */ Module FRAMEWORK_UTILS = Module.of( FRAMEWORK.subPackage("application"), + FRAMEWORK.subPackage("error"), FRAMEWORK.subPackage("i18n"), FRAMEWORK.subPackage("lang"), FRAMEWORK.subPackage("logging"), diff --git a/src/test/java/org/opentripplanner/_support/arch/ArchComponent.java b/src/test/java/org/opentripplanner/_support/arch/ArchComponent.java index a1261d09648..7273b3123e3 100644 --- a/src/test/java/org/opentripplanner/_support/arch/ArchComponent.java +++ b/src/test/java/org/opentripplanner/_support/arch/ArchComponent.java @@ -9,7 +9,7 @@ public interface ArchComponent { /** * ArchUnit cached set of classes in OTP. It takes a bit of time to build the set of * classes, so it is nice to avoid this for every test. ArchUnit also support JUnit5 - * with a @ArchTest annotation witch cache and inject the classes, but the test become + * with a @ArchTest annotation which cache and inject the classes, but the test become * slightly more complex by using it and chasing it here works fine. */ JavaClasses OTP_CLASSES = new ClassFileImporter() diff --git a/src/test/java/org/opentripplanner/_support/debug/TestDebug.java b/src/test/java/org/opentripplanner/_support/debug/TestDebug.java new file mode 100644 index 00000000000..df08e373ffa --- /dev/null +++ b/src/test/java/org/opentripplanner/_support/debug/TestDebug.java @@ -0,0 +1,57 @@ +package org.opentripplanner._support.debug; + +/** + * Sometimes it is convenient to include debug logging in a unit test. This class uses the + * {@code System.err} for logging - to make sure the output is flushed. Using the standard logging + * framework for this is a bit unnecessary. Use this class instead of System err/out. + *

+ * To turn on debugging set {@code testDebug} as an environment variable or system property - + * if set this class will print debug info to the console. Use: + *

+ * $ export testDebug=true | java ...
+ * or
+ * $ java -DtestDebug ..
+ * 
+ * In IntelliJ it's recommended to add the system property in the JUnit template. In the test + * drop down, choose Edit Configuration... then Edit Configuration Templates.. and + * choose JUnit. + */ +public class TestDebug { + + private static final Boolean ENABLED = enabled(); + + /** This is a utility class - only static methods */ + private TestDebug() {} + + public static boolean on() { + return ENABLED; + } + + public static boolean off() { + return !ENABLED; + } + + public static void print(Object value) { + if (ENABLED) { + System.err.print(value); + } + } + + public static void println() { + if (ENABLED) { + System.err.println(); + } + } + + public static void println(Object value) { + if (ENABLED) { + System.err.println(value); + } + } + + private static boolean enabled() { + boolean sysDebug = System.getProperties().containsKey("testDebug"); + boolean envDebug = System.getenv().containsKey("testDebug"); + return sysDebug || envDebug; + } +} diff --git a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java index a293c940713..a65c46b12fe 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -33,6 +33,7 @@ import org.glassfish.jersey.message.internal.OutboundJaxrsResponse; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; +import org.locationtech.jts.geom.Coordinate; import org.opentripplanner._support.text.I18NStrings; import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.ext.fares.FaresToItineraryMapper; @@ -64,6 +65,9 @@ import org.opentripplanner.routing.core.FareType; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graphfinder.GraphFinder; +import org.opentripplanner.routing.graphfinder.NearbyStop; +import org.opentripplanner.routing.graphfinder.PlaceAtDistance; +import org.opentripplanner.routing.graphfinder.PlaceType; import org.opentripplanner.routing.impl.TransitAlertServiceImpl; import org.opentripplanner.routing.services.TransitAlertService; import org.opentripplanner.routing.vehicle_parking.VehicleParking; @@ -79,12 +83,14 @@ import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.AbstractBuilder; import org.opentripplanner.transit.model.framework.Deduplicator; +import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.transit.service.TransitService; class GraphQLIntegrationTest { @@ -275,7 +281,7 @@ public TransitAlertService getTransitAlertService() { GRAPH.getVehicleParkingService(), defaultVehicleRentalService, realtimeVehicleService, - GraphFinder.getInstance(GRAPH, transitService::findRegularStop), + finder, new RouteRequest() ); } @@ -375,4 +381,32 @@ private static String responseBody(Response response) { fail("expected an outbound response but got %s".formatted(response.getClass().getSimpleName())); return null; } + + private static final GraphFinder finder = new GraphFinder() { + @Override + public List findClosestStops(Coordinate coordinate, double radiusMeters) { + return null; + } + + @Override + public List findClosestPlaces( + double lat, + double lon, + double radiusMeters, + int maxResults, + List filterByModes, + List filterByPlaceTypes, + List filterByStops, + List filterByStations, + List filterByRoutes, + List filterByBikeRentalStations, + TransitService transitService + ) { + return List + .of(TransitModelForTest.of().stop("A").build()) + .stream() + .map(stop -> new PlaceAtDistance(stop, 0)) + .toList(); + } + }; } 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 af6352839fb..fba953cd93a 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java @@ -25,12 +25,13 @@ 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.request.VehicleParkingRequest; +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; import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService; +import org.opentripplanner.street.search.TraverseMode; import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; @@ -84,16 +85,8 @@ void parkingFilters() { assertNotNull(routeRequest); - final VehicleParkingRequest parking = routeRequest.journey().parking(); - assertEquals( - "VehicleParkingFilterRequest{not: [tags=[wheelbender]], select: [tags=[locker, roof]]}", - parking.filter().toString() - ); - assertEquals( - "VehicleParkingFilterRequest{select: [tags=[a, b]]}", - parking.preferred().toString() - ); - assertEquals(555, parking.unpreferredCost()); + testParkingFilters(routeRequest.preferences().parking(TraverseMode.CAR)); + testParkingFilters(routeRequest.preferences().parking(TraverseMode.BICYCLE)); } static Stream banningCases = Stream.of( @@ -219,4 +212,16 @@ private DataFetchingEnvironment executionContext(Map arguments) .arguments(arguments) .build(); } + + private void testParkingFilters(VehicleParkingPreferences parkingPreferences) { + assertEquals( + "VehicleParkingFilter{not: [tags=[wheelbender]], select: [tags=[locker, roof]]}", + parkingPreferences.filter().toString() + ); + assertEquals( + "VehicleParkingFilter{select: [tags=[a, b]]}", + parkingPreferences.preferred().toString() + ); + assertEquals(555, parkingPreferences.unpreferredVehicleParkingTagCost().toSeconds()); + } } diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/TransmodelGraphQLSchemaTest.java b/src/test/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaTest.java similarity index 79% rename from src/ext-test/java/org/opentripplanner/ext/transmodelapi/TransmodelGraphQLSchemaTest.java rename to src/test/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaTest.java index a5f2d4a50b4..d762d784d3f 100644 --- a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/TransmodelGraphQLSchemaTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi; +package org.opentripplanner.apis.transmodel; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.opentripplanner.framework.io.FileUtils.assertFileEquals; @@ -9,12 +9,14 @@ import java.io.File; import org.junit.jupiter.api.Test; import org.opentripplanner._support.time.ZoneIds; -import org.opentripplanner.ext.transmodelapi.support.GqlUtil; +import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.routing.api.request.RouteRequest; class TransmodelGraphQLSchemaTest { - public static final File SCHEMA_FILE = new File("src/ext/graphql/transmodelapi/schema.graphql"); + public static final File SCHEMA_FILE = new File( + "src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql" + ); @Test void testSchemaBuild() { diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TransitIdMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TransitIdMapperTest.java similarity index 82% rename from src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TransitIdMapperTest.java rename to src/test/java/org/opentripplanner/apis/transmodel/mapping/TransitIdMapperTest.java index df1a00230cb..f0dc8504e93 100644 --- a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TransitIdMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TransitIdMapperTest.java @@ -1,11 +1,14 @@ -package org.opentripplanner.ext.transmodelapi.mapping; +package org.opentripplanner.apis.transmodel.mapping; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; import java.util.List; import java.util.Set; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.opentripplanner.transit.model.framework.FeedScopedId; diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java similarity index 99% rename from src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java rename to src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java index e429d59c92c..3058f622281 100644 --- a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.mapping; +package org.opentripplanner.apis.transmodel.mapping; import static graphql.execution.ExecutionContextBuilder.newExecutionContextBuilder; import static java.util.stream.Collectors.toList; @@ -24,9 +24,9 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.opentripplanner._support.time.ZoneIds; +import org.opentripplanner.apis.transmodel.TransmodelRequestContext; import org.opentripplanner.ext.emissions.DefaultEmissionsService; import org.opentripplanner.ext.emissions.EmissionsDataModel; -import org.opentripplanner.ext.transmodelapi.TransmodelRequestContext; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.model.calendar.CalendarServiceData; import org.opentripplanner.model.plan.Itinerary; diff --git a/src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/BikePreferencesMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapperTest.java similarity index 90% rename from src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/BikePreferencesMapperTest.java rename to src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapperTest.java index d68c0652862..378571eeea9 100644 --- a/src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/BikePreferencesMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapperTest.java @@ -1,13 +1,13 @@ -package org.opentripplanner.ext.transmodelapi.mapping.preferences; +package org.opentripplanner.apis.transmodel.mapping.preferences; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.ext.transmodelapi.mapping.preferences.BikePreferencesMapper.mapBikePreferences; +import static org.opentripplanner.apis.transmodel.mapping.preferences.BikePreferencesMapper.mapBikePreferences; 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.ext.transmodelapi._support.TestDataFetcherDecorator; +import org.opentripplanner.apis.transmodel._support.TestDataFetcherDecorator; import org.opentripplanner.routing.api.request.preference.BikePreferences; import org.opentripplanner.routing.core.BicycleOptimizeType; diff --git a/src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/CarPreferencesMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/CarPreferencesMapperTest.java similarity index 85% rename from src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/CarPreferencesMapperTest.java rename to src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/CarPreferencesMapperTest.java index 96f99220469..08743e32e26 100644 --- a/src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/CarPreferencesMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/CarPreferencesMapperTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.mapping.preferences; +package org.opentripplanner.apis.transmodel.mapping.preferences; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -6,7 +6,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.opentripplanner.ext.transmodelapi._support.TestDataFetcherDecorator; +import org.opentripplanner.apis.transmodel._support.TestDataFetcherDecorator; import org.opentripplanner.routing.api.request.preference.CarPreferences; class CarPreferencesMapperTest { diff --git a/src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/WalkPreferencesMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/WalkPreferencesMapperTest.java similarity index 86% rename from src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/WalkPreferencesMapperTest.java rename to src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/WalkPreferencesMapperTest.java index d1e9c3fe81d..811c91d0d50 100644 --- a/src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/WalkPreferencesMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/WalkPreferencesMapperTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.mapping.preferences; +package org.opentripplanner.apis.transmodel.mapping.preferences; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -6,7 +6,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.opentripplanner.ext.transmodelapi._support.TestDataFetcherDecorator; +import org.opentripplanner.apis.transmodel._support.TestDataFetcherDecorator; import org.opentripplanner.routing.api.request.preference.WalkPreferences; class WalkPreferencesMapperTest { diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/model/EnumTypesTest.java b/src/test/java/org/opentripplanner/apis/transmodel/model/EnumTypesTest.java similarity index 93% rename from src/ext-test/java/org/opentripplanner/ext/transmodelapi/model/EnumTypesTest.java rename to src/test/java/org/opentripplanner/apis/transmodel/model/EnumTypesTest.java index 912a727060e..3d834fced58 100644 --- a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/model/EnumTypesTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/model/EnumTypesTest.java @@ -1,9 +1,9 @@ -package org.opentripplanner.ext.transmodelapi.model; +package org.opentripplanner.apis.transmodel.model; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.ROUTING_ERROR_CODE; -import static org.opentripplanner.ext.transmodelapi.model.EnumTypes.map; +import static org.opentripplanner.apis.transmodel.model.EnumTypes.ROUTING_ERROR_CODE; +import static org.opentripplanner.apis.transmodel.model.EnumTypes.map; import java.util.EnumSet; import java.util.List; diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/model/TransportModeSlackTest.java b/src/test/java/org/opentripplanner/apis/transmodel/model/TransportModeSlackTest.java similarity index 97% rename from src/ext-test/java/org/opentripplanner/ext/transmodelapi/model/TransportModeSlackTest.java rename to src/test/java/org/opentripplanner/apis/transmodel/model/TransportModeSlackTest.java index 52f4f2f2187..22d998c189b 100644 --- a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/model/TransportModeSlackTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/model/TransportModeSlackTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model; +package org.opentripplanner.apis.transmodel.model; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/model/scalars/DateTimeScalarFactoryTest.java b/src/test/java/org/opentripplanner/apis/transmodel/model/scalars/DateTimeScalarFactoryTest.java similarity index 96% rename from src/ext-test/java/org/opentripplanner/ext/transmodelapi/model/scalars/DateTimeScalarFactoryTest.java rename to src/test/java/org/opentripplanner/apis/transmodel/model/scalars/DateTimeScalarFactoryTest.java index 0ab88fb23b4..43b44cbd5a6 100644 --- a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/model/scalars/DateTimeScalarFactoryTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/model/scalars/DateTimeScalarFactoryTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.model.scalars; +package org.opentripplanner.apis.transmodel.model.scalars; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/support/ExecutionResultMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapperTest.java similarity index 97% rename from src/ext-test/java/org/opentripplanner/ext/transmodelapi/support/ExecutionResultMapperTest.java rename to src/test/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapperTest.java index bf0a6757a72..bce971e9f1e 100644 --- a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/support/ExecutionResultMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapperTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.support; +package org.opentripplanner.apis.transmodel.support; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.framework.lang.StringUtils.quoteReplace; diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/support/GqlUtilTest.java b/src/test/java/org/opentripplanner/apis/transmodel/support/GqlUtilTest.java similarity index 97% rename from src/ext-test/java/org/opentripplanner/ext/transmodelapi/support/GqlUtilTest.java rename to src/test/java/org/opentripplanner/apis/transmodel/support/GqlUtilTest.java index fe61394de1b..d2d15168baf 100644 --- a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/support/GqlUtilTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/support/GqlUtilTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.transmodelapi.support; +package org.opentripplanner.apis.transmodel.support; import static graphql.execution.ExecutionContextBuilder.newExecutionContextBuilder; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/org/opentripplanner/ext/transmodelapi/_support/TestDataFetcherDecorator.java b/src/test/java/org/opentripplanner/ext/transmodelapi/_support/TestDataFetcherDecorator.java index b4b2bd9de47..e62b5ceaa1b 100644 --- a/src/test/java/org/opentripplanner/ext/transmodelapi/_support/TestDataFetcherDecorator.java +++ b/src/test/java/org/opentripplanner/ext/transmodelapi/_support/TestDataFetcherDecorator.java @@ -1,8 +1,8 @@ -package org.opentripplanner.ext.transmodelapi._support; +package org.opentripplanner.apis.transmodel._support; import java.util.Map; import java.util.function.Consumer; -import org.opentripplanner.ext.transmodelapi.support.DataFetcherDecorator; +import org.opentripplanner.apis.transmodel.support.DataFetcherDecorator; public class TestDataFetcherDecorator extends DataFetcherDecorator { diff --git a/src/test/java/org/opentripplanner/framework/collection/ListSectionTest.java b/src/test/java/org/opentripplanner/framework/collection/ListSectionTest.java new file mode 100644 index 00000000000..199804e4881 --- /dev/null +++ b/src/test/java/org/opentripplanner/framework/collection/ListSectionTest.java @@ -0,0 +1,22 @@ +package org.opentripplanner.framework.collection; + +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 org.junit.jupiter.api.Test; + +class ListSectionTest { + + @Test + void isHead() { + assertTrue(ListSection.HEAD.isHead()); + assertFalse(ListSection.TAIL.isHead()); + } + + @Test + void oppositeEnd() { + assertEquals(ListSection.HEAD, ListSection.TAIL.invert()); + assertEquals(ListSection.TAIL, ListSection.HEAD.invert()); + } +} diff --git a/src/test/java/org/opentripplanner/framework/collection/ListUtilsTest.java b/src/test/java/org/opentripplanner/framework/collection/ListUtilsTest.java index 03d5dcbf8fe..6e72d5626c5 100644 --- a/src/test/java/org/opentripplanner/framework/collection/ListUtilsTest.java +++ b/src/test/java/org/opentripplanner/framework/collection/ListUtilsTest.java @@ -1,12 +1,31 @@ package org.opentripplanner.framework.collection; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.opentripplanner.framework.collection.ListUtils.first; +import static org.opentripplanner.framework.collection.ListUtils.last; import java.util.List; import org.junit.jupiter.api.Test; class ListUtilsTest { + @Test + void testFirst() { + assertNull(first(null)); + assertNull(first(List.of())); + assertEquals("A", first(List.of("A"))); + assertEquals("B", first(List.of("B", "C"))); + } + + @Test + void testLast() { + assertNull(last(null)); + assertNull(last(List.of())); + assertEquals("A", last(List.of("A"))); + assertEquals("C", last(List.of("B", "C"))); + } + @Test void combine() { var combined = ListUtils.combine(List.of(1, 2, 3), List.of(5, 6, 7)); diff --git a/src/test/java/org/opentripplanner/framework/lang/ArrayUtilsTest.java b/src/test/java/org/opentripplanner/framework/lang/ArrayUtilsTest.java new file mode 100644 index 00000000000..8933555e75c --- /dev/null +++ b/src/test/java/org/opentripplanner/framework/lang/ArrayUtilsTest.java @@ -0,0 +1,17 @@ +package org.opentripplanner.framework.lang; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.framework.lang.ArrayUtils.hasContent; + +import org.junit.jupiter.api.Test; + +class ArrayUtilsTest { + + @Test + void testHasContent() { + assertFalse(hasContent(null)); + assertFalse(hasContent(new String[] {})); + assertTrue(hasContent(new Double[] { 0.0 })); + } +} diff --git a/src/test/java/org/opentripplanner/framework/lang/BoxTest.java b/src/test/java/org/opentripplanner/framework/lang/BoxTest.java new file mode 100644 index 00000000000..7e072fd2f5a --- /dev/null +++ b/src/test/java/org/opentripplanner/framework/lang/BoxTest.java @@ -0,0 +1,47 @@ +package org.opentripplanner.framework.lang; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class BoxTest { + + public static final String VALUE = "Test"; + + @Test + void getValue() { + assertEquals(VALUE, Box.of(VALUE).get()); + assertNull(Box.empty().get()); + } + + @Test + void setValue() { + Box box = Box.empty(); + box.set(VALUE); + assertEquals(VALUE, box.get()); + } + + @Test + void testIsEmpty() { + assertTrue(Box.empty().isEmpty()); + assertFalse(Box.of("A").isEmpty()); + } + + @Test + void testEquals() { + assertEquals(Box.of(1), Box.of(1)); + assertNotEquals(Box.of(1), Box.of(2)); + + assertEquals(Box.of(1).hashCode(), Box.of(1).hashCode()); + assertNotEquals(Box.of(1).hashCode(), Box.of(2).hashCode()); + } + + @Test + void testToString() { + assertEquals("[Test]", Box.of(VALUE).toString()); + } +} diff --git a/src/test/java/org/opentripplanner/framework/lang/IntUtilsTest.java b/src/test/java/org/opentripplanner/framework/lang/IntUtilsTest.java index 09de5ab62ad..aa15e806fe6 100644 --- a/src/test/java/org/opentripplanner/framework/lang/IntUtilsTest.java +++ b/src/test/java/org/opentripplanner/framework/lang/IntUtilsTest.java @@ -35,6 +35,28 @@ void testIntArray() { assertArrayEquals(new int[] { 5, 5, 5 }, intArray(3, 5)); } + @Test + void testAssertInRange() { + IntUtils.requireInRange(1, 1, 1, "single-element-range"); + IntUtils.requireInRange(-2, -2, 1, "negative-start"); + IntUtils.requireInRange(-1, -2, -1, "negative-end"); + assertThrows( + IllegalArgumentException.class, + () -> IntUtils.requireInRange(1, 2, 1, "invalid-range") + ); + var ex = assertThrows( + IllegalArgumentException.class, + () -> IntUtils.requireInRange(1, 2, 3, "value-too-small") + ); + assertEquals("The 'value-too-small' is not in range[2, 3]: 1", ex.getMessage()); + ex = + assertThrows( + IllegalArgumentException.class, + () -> IntUtils.requireInRange(4, 0, 3, "value-too-big") + ); + assertEquals("The 'value-too-big' is not in range[0, 3]: 4", ex.getMessage()); + } + @Test public void testRound() { assertEquals(0, IntUtils.round(0.499)); @@ -67,11 +89,17 @@ void sestStandardDeviation() { @Test void testRequireNotNegative() { // OK - assertEquals(7, requireNotNegative(7)); - assertEquals(0, requireNotNegative(0)); + assertEquals(7, requireNotNegative(7, "ok")); + assertEquals(0, requireNotNegative(0, "ok")); - var ex = assertThrows(IllegalArgumentException.class, () -> requireNotNegative(-1)); - assertEquals("Negative value not expected: -1", ex.getMessage()); + var ex = assertThrows( + IllegalArgumentException.class, + () -> requireNotNegative(-1, "too-small") + ); + assertEquals("Negative value not expected for 'too-small': -1", ex.getMessage()); + + ex = assertThrows(IllegalArgumentException.class, () -> requireNotNegative(-1)); + assertEquals("Negative value not expected for value: -1", ex.getMessage()); } @Test @@ -80,9 +108,11 @@ void testRequireInRange() { assertEquals(5, requireInRange(5, 5, 5)); // Too small - assertThrows(IllegalArgumentException.class, () -> requireInRange(5, 6, 10)); + var ex = assertThrows(IllegalArgumentException.class, () -> requireInRange(5, 6, 10)); + assertEquals("The value is not in range[6, 10]: 5", ex.getMessage()); + // Too big - var ex = assertThrows(IllegalArgumentException.class, () -> requireInRange(5, 1, 4, "cost")); - assertEquals("The cost is not in range[1, 4]: 5", ex.getMessage()); + ex = assertThrows(IllegalArgumentException.class, () -> requireInRange(5, 1, 4, "cost")); + assertEquals("The 'cost' is not in range[1, 4]: 5", ex.getMessage()); } } diff --git a/src/test/java/org/opentripplanner/framework/lang/ObjectUtilsTest.java b/src/test/java/org/opentripplanner/framework/lang/ObjectUtilsTest.java index 47389348c76..9237b221fe5 100644 --- a/src/test/java/org/opentripplanner/framework/lang/ObjectUtilsTest.java +++ b/src/test/java/org/opentripplanner/framework/lang/ObjectUtilsTest.java @@ -34,10 +34,18 @@ void ifNotNullFunctional() { @Test void requireNotInitialized() { assertEquals("new", ObjectUtils.requireNotInitialized(null, "new")); - assertThrows( + var ex = assertThrows( IllegalStateException.class, - () -> ObjectUtils.requireNotInitialized("old", "new") + () -> ObjectUtils.requireNotInitialized("foo", "old", "new") ); + assertEquals("Field foo is already set! Old value: old, new value: new.", ex.getMessage()); + + ex = + assertThrows( + IllegalStateException.class, + () -> ObjectUtils.requireNotInitialized("old", "new") + ); + assertEquals("Field is already set! Old value: old, new value: new.", ex.getMessage()); } @Test diff --git a/src/test/java/org/opentripplanner/framework/model/UnitsTest.java b/src/test/java/org/opentripplanner/framework/model/UnitsTest.java index c1aaecfb1a0..818b9b7df16 100644 --- a/src/test/java/org/opentripplanner/framework/model/UnitsTest.java +++ b/src/test/java/org/opentripplanner/framework/model/UnitsTest.java @@ -39,7 +39,7 @@ void duration() { assertEquals(0, Units.duration(0)); assertEquals(10_000, Units.duration(10_000)); var ex = assertThrows(IllegalArgumentException.class, () -> Units.duration(-1)); - assertEquals("Negative value not expected: -1", ex.getMessage()); + assertEquals("Negative value not expected for value: -1", ex.getMessage()); } @Test diff --git a/src/test/java/org/opentripplanner/framework/text/CharacterEscapeFormatterTest.java b/src/test/java/org/opentripplanner/framework/text/CharacterEscapeFormatterTest.java new file mode 100644 index 00000000000..b33b06babb6 --- /dev/null +++ b/src/test/java/org/opentripplanner/framework/text/CharacterEscapeFormatterTest.java @@ -0,0 +1,46 @@ +package org.opentripplanner.framework.text; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class CharacterEscapeFormatterTest { + + @ParameterizedTest + @ValueSource( + strings = { + "This;is;a:text", + " ; ", + " ;", + "; ", + ";;;", + "^", + " ^ ", + " ^", + "^ ", + ";^;", + "^;", + ";^", + "%^%", + "^%", + "%^", + ";^;", + "^;", + ";^", + } + ) + public void encodeDecode(String original) { + var subject = new CharacterEscapeFormatter('^', ';', '%'); + + var escapedText = subject.encode(original); + var result = subject.decode(escapedText); + + assertNotEquals(escapedText, original); + + assertEquals(original, result); + assertFalse(escapedText.contains(";"), escapedText); + } +} diff --git a/src/test/java/org/opentripplanner/framework/token/AdvancedTokenSchemaTest.java b/src/test/java/org/opentripplanner/framework/token/AdvancedTokenSchemaTest.java new file mode 100644 index 00000000000..92fe6ad937e --- /dev/null +++ b/src/test/java/org/opentripplanner/framework/token/AdvancedTokenSchemaTest.java @@ -0,0 +1,182 @@ +package org.opentripplanner.framework.token; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.framework.token.AdvancedTokenSchemaTest.TestCase.testCase; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class AdvancedTokenSchemaTest implements TestTokenSchemaConstants { + + private static final List TEST_CASES = new ArrayList<>(); + + static { + // Version 1: [BYTE] + var builder = TokenSchema.ofVersion(1).addByte(BYTE_FIELD); + TEST_CASES.add( + TestCase.testCase(builder, "(v1, 17)", it -> it.encode().withByte(BYTE_FIELD, BYTE_VALUE)) + ); + + // Version 2: [BYTE, DURATION, INT] + builder = builder.newVersion().addDuration(DURATION_FIELD).addInt(INT_FIELD); + TEST_CASES.add( + testCase( + builder, + "(v2, 17, PT2M13S, 31)", + // We can add named fields in any order(token order: byte,Duration,Int) + it -> + it + .encode() + .withInt(INT_FIELD, INT_VALUE) + .withByte(BYTE_FIELD, BYTE_VALUE) + .withDuration(DURATION_FIELD, DURATION_VALUE) + ) + ); + + // Version 3 - BYTE, @deprecated DURATION, INT + builder = builder.newVersion().deprecate(DURATION_FIELD); + TEST_CASES.add( + testCase( + builder, + "(v3, 17, 31)", + it -> + it + .encode() + .withInt(INT_FIELD, INT_VALUE) + .withByte(BYTE_FIELD, BYTE_VALUE) + .withDuration(DURATION_FIELD, DURATION_VALUE) + ) + ); + + // Version 4 - BYTE, INT, STRING + builder = builder.newVersion().addString(STRING_FIELD); + TEST_CASES.add( + testCase( + builder, + "(v4, 17, 31, text)", + it -> + it + .encode() + .withInt(INT_FIELD, INT_VALUE) + .withByte(BYTE_FIELD, BYTE_VALUE) + .withString(STRING_FIELD, STRING_VALUE) + ) + ); + + // Version 5 - @deprecated BYTE, INT, STRING, TIME_INSTANT + builder = builder.newVersion().deprecate(BYTE_FIELD).addTimeInstant(TIME_INSTANT_FIELD); + TEST_CASES.add( + testCase( + builder, + "(v5, 31, text, 2023-10-23T10:00:59Z)", + it -> + it + .encode() + .withInt(INT_FIELD, INT_VALUE) + .withByte(BYTE_FIELD, BYTE_VALUE) + .withTimeInstant(TIME_INSTANT_FIELD, TIME_INSTANT_VALUE) + .withString(STRING_FIELD, STRING_VALUE) + ) + ); + // Version 6 - INT, STRING, TIME_INSTANT + builder = builder.newVersion(); + TEST_CASES.add( + testCase( + builder, + "(v6, 31, text, 2023-10-23T10:00:59Z)", + it -> + it + .encode() + .withInt(INT_FIELD, INT_VALUE) + .withTimeInstant(TIME_INSTANT_FIELD, TIME_INSTANT_VALUE) + .withString(STRING_FIELD, STRING_VALUE) + ) + ); + } + + private static List testCases() { + return TEST_CASES; + } + + @ParameterizedTest + @MethodSource(value = "testCases") + void testDecodeBackwardsCompatibility(TestCase testCase) { + allTestCasesFrom(testCase) + .forEach(s -> assertEquals(testCase.expected(), s.decode(testCase.token()).toString())); + } + + @ParameterizedTest + @MethodSource(value = "testCases") + void testDecodeForwardCompatibility(TestCase testCase) { + nextTestCase(testCase) + .map(TestCase::token) + .ifPresent(nextVersionToken -> + assertEquals(testCase.expected(), testCase.subject().decode(nextVersionToken).toString()) + ); + } + + @Test + void testMerge() { + var merged = TokenSchema + .ofVersion(6) + .addInt(INT_FIELD) + .addString(STRING_FIELD) + .addTimeInstant(TIME_INSTANT_FIELD) + .build(); + + var subjectV6 = TEST_CASES.get(5).subject(); + assertEquals(merged.currentDefinition(), subjectV6.currentDefinition()); + } + + @Test + void testDefinitionToString() { + var expected = List.of( + "TokenDefinition{version: 1, fields: [AByte:BYTE]}", + "TokenDefinition{version: 2, fields: [AByte:BYTE, ADur:DURATION, ANum:INT]}", + "TokenDefinition{version: 3, fields: [AByte:BYTE, @deprecated ADur:DURATION, ANum:INT]}", + "TokenDefinition{version: 4, fields: [AByte:BYTE, ANum:INT, AStr:STRING]}", + "TokenDefinition{version: 5, fields: [@deprecated AByte:BYTE, ANum:INT, AStr:STRING, ATime:TIME_INSTANT]}", + "TokenDefinition{version: 6, fields: [ANum:INT, AStr:STRING, ATime:TIME_INSTANT]}" + ); + for (int i = 0; i < TEST_CASES.size(); i++) { + assertEquals(expected.get(i), TEST_CASES.get(i).subject().currentDefinition().toString()); + } + } + + /** + * List of all test-cases including the given test-case until the end of all test-cases + */ + private static Stream allTestCasesFrom(TestCase testCase) { + return TEST_CASES + .subList(TEST_CASES.indexOf(testCase), TEST_CASES.size() - 1) + .stream() + .map(TestCase::subject); + } + + private static Optional nextTestCase(TestCase testCase) { + int index = TEST_CASES.indexOf(testCase) + 1; + return index < TEST_CASES.size() ? Optional.of(TEST_CASES.get(index)) : Optional.empty(); + } + + record TestCase(TokenSchema subject, String expected, String token) { + static TestCase testCase( + TokenDefinitionBuilder definitionBuilder, + String expected, + Function builder + ) { + var schema = definitionBuilder.build(); + return new TestCase(schema, expected, builder.apply(schema).build()); + } + + @Override + public String toString() { + return subject.currentDefinition().toString(); + } + } +} diff --git a/src/test/java/org/opentripplanner/framework/token/FieldDefinitionTest.java b/src/test/java/org/opentripplanner/framework/token/FieldDefinitionTest.java new file mode 100644 index 00000000000..3778dbef4f1 --- /dev/null +++ b/src/test/java/org/opentripplanner/framework/token/FieldDefinitionTest.java @@ -0,0 +1,55 @@ +package org.opentripplanner.framework.token; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class FieldDefinitionTest { + + private static final String NAME = "foo"; + public static final TokenType TOKEN_TYPE = TokenType.STRING; + private final FieldDefinition subject = new FieldDefinition(NAME, TOKEN_TYPE); + + @Test + void name() { + assertEquals(NAME, subject.name()); + } + + @Test + void type() { + assertEquals(TOKEN_TYPE, subject.type()); + } + + @Test + void testDeprecate() { + assertFalse(subject.deprecated()); + assertTrue(subject.deprecate().deprecated()); + } + + @Test + void testEqualsAndHashCode() { + var same = new FieldDefinition(NAME, TOKEN_TYPE); + var other1 = new FieldDefinition(NAME, TokenType.INT); + var other2 = new FieldDefinition("Bar", TOKEN_TYPE); + var other3 = subject.deprecate(); + + assertEquals(subject, subject); + assertEquals(same, subject); + assertNotEquals(other1, subject); + assertNotEquals(other2, subject); + assertNotEquals(other3, subject); + + assertEquals(same.hashCode(), subject.hashCode()); + assertNotEquals(other1.hashCode(), subject.hashCode()); + assertNotEquals(other2.hashCode(), subject.hashCode()); + assertNotEquals(other3.hashCode(), subject.hashCode()); + } + + @Test + void testToString() { + assertEquals("foo:STRING", subject.toString()); + } +} diff --git a/src/test/java/org/opentripplanner/framework/token/TestTokenSchemaConstants.java b/src/test/java/org/opentripplanner/framework/token/TestTokenSchemaConstants.java new file mode 100644 index 00000000000..09ac58a0d2d --- /dev/null +++ b/src/test/java/org/opentripplanner/framework/token/TestTokenSchemaConstants.java @@ -0,0 +1,35 @@ +package org.opentripplanner.framework.token; + +import java.time.Duration; +import java.time.Instant; +import java.time.Month; +import org.opentripplanner.framework.time.DurationUtils; + +public interface TestTokenSchemaConstants { + // Token field names. These are used to reference a specific field value in theString BYTE_FIELD = "AByte"; + // token to avoid index errors. They are not part of the serialized token.String DURATION_FIELD = "ADur"; + String BOOLEAN_TRUE_FIELD = "ABoolTrue"; + String BOOLEAN_FALSE_FIELD = "ABoolFalse"; + String BYTE_FIELD = "AByte"; + String DURATION_FIELD = "ADur"; + String ENUM_FIELD = "EnField"; + String INT_FIELD = "ANum"; + String STRING_FIELD = "AStr"; + String TIME_INSTANT_FIELD = "ATime"; + + byte BYTE_VALUE = 17; + Duration DURATION_VALUE = DurationUtils.duration("2m13s"); + Month ENUM_VALUE = Month.MAY; + Class ENUM_CLASS = Month.class; + int INT_VALUE = 31; + String STRING_VALUE = "text"; + Instant TIME_INSTANT_VALUE = Instant.parse("2023-10-23T10:00:59Z"); + + String BOOLEAN_ENCODED = "MXx0cnVlfGZhbHNlfA=="; + String BYTE_ENCODED = "MXwxN3w="; + String DURATION_ENCODED = "MnwybTEzc3w="; + String ENUM_ENCODED = "M3xNQVl8"; + String INT_ENCODED = "M3wzMXw="; + String STRING_ENCODED = "N3x0ZXh0fA=="; + String TIME_INSTANT_ENCODED = "MTN8MjAyMy0xMC0yM1QxMDowMDo1OVp8"; +} diff --git a/src/test/java/org/opentripplanner/framework/token/TokenSchemaTest.java b/src/test/java/org/opentripplanner/framework/token/TokenSchemaTest.java new file mode 100644 index 00000000000..089aac372cc --- /dev/null +++ b/src/test/java/org/opentripplanner/framework/token/TokenSchemaTest.java @@ -0,0 +1,177 @@ +package org.opentripplanner.framework.token; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.DayOfWeek; +import java.util.Optional; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class TokenSchemaTest implements TestTokenSchemaConstants { + + // Token field names. These are used to reference a specific field value in the + // token to avoid index errors. They are not part of the serialized token. + + private static final TokenSchema BOOLEAN_SCHEMA = TokenSchema + .ofVersion(1) + .addBoolean(BOOLEAN_TRUE_FIELD) + .addBoolean(BOOLEAN_FALSE_FIELD) + .build(); + + private static final TokenSchema BYTE_SCHEMA = TokenSchema + .ofVersion(1) + .addByte(BYTE_FIELD) + .build(); + private static final TokenSchema DURATION_SCHEMA = TokenSchema + .ofVersion(2) + .addDuration(DURATION_FIELD) + .build(); + private static final TokenSchema ENUM_SCHEMA = TokenSchema + .ofVersion(3) + .addEnum(ENUM_FIELD) + .build(); + private static final TokenSchema INT_SCHEMA = TokenSchema.ofVersion(3).addInt(INT_FIELD).build(); + private static final TokenSchema STRING_SCHEMA = TokenSchema + .ofVersion(7) + .addString(STRING_FIELD) + .build(); + private static final TokenSchema TIME_INSTANT_SCHEMA = TokenSchema + .ofVersion(13) + .addTimeInstant(TIME_INSTANT_FIELD) + .build(); + + @Test + public void encodeBoolean() { + // Add in opposite order of Schema, test naming fields work + var token = BOOLEAN_SCHEMA + .encode() + .withBoolean(BOOLEAN_FALSE_FIELD, false) + .withBoolean(BOOLEAN_TRUE_FIELD, true) + .build(); + assertEquals(BOOLEAN_ENCODED, token); + assertTrue(BOOLEAN_SCHEMA.decode(token).getBoolean(BOOLEAN_TRUE_FIELD)); + + var tokenResult = BOOLEAN_SCHEMA.decode(token); + assertFalse(tokenResult.getBoolean(BOOLEAN_FALSE_FIELD)); + } + + @Test + public void encodeByte() { + var token = BYTE_SCHEMA.encode().withByte(BYTE_FIELD, (byte) 17).build(); + assertEquals(BYTE_ENCODED, token); + assertEquals(BYTE_VALUE, BYTE_SCHEMA.decode(token).getByte(BYTE_FIELD)); + } + + @Test + public void testDuration() { + var token = DURATION_SCHEMA.encode().withDuration(DURATION_FIELD, DURATION_VALUE).build(); + assertEquals(DURATION_ENCODED, token); + assertEquals(DURATION_VALUE, DURATION_SCHEMA.decode(token).getDuration(DURATION_FIELD)); + } + + @Test + public void testEnum() { + var token = ENUM_SCHEMA.encode().withEnum(ENUM_FIELD, ENUM_VALUE).build(); + assertEquals(ENUM_ENCODED, token); + assertEquals(ENUM_VALUE, ENUM_SCHEMA.decode(token).getEnum(ENUM_FIELD, ENUM_CLASS).get()); + assertEquals(Optional.empty(), ENUM_SCHEMA.decode(token).getEnum(ENUM_FIELD, DayOfWeek.class)); + } + + @Test + public void testInt() { + var token = INT_SCHEMA.encode().withInt(INT_FIELD, INT_VALUE).build(); + assertEquals(INT_ENCODED, token); + assertEquals(INT_VALUE, INT_SCHEMA.decode(token).getInt(INT_FIELD)); + } + + @Test + public void testString() { + var token = STRING_SCHEMA.encode().withString(STRING_FIELD, STRING_VALUE).build(); + assertEquals(STRING_ENCODED, token); + assertEquals(STRING_VALUE, STRING_SCHEMA.decode(token).getString(STRING_FIELD)); + } + + @Test + public void encodeTimeInstant() { + var token = TIME_INSTANT_SCHEMA + .encode() + .withTimeInstant(TIME_INSTANT_FIELD, TIME_INSTANT_VALUE) + .build(); + assertEquals(TIME_INSTANT_ENCODED, token); + assertEquals( + TIME_INSTANT_VALUE, + TIME_INSTANT_SCHEMA.decode(token).getTimeInstant(TIME_INSTANT_FIELD) + ); + } + + @Test + public void encodeUndefinedFields() { + var ex = Assertions.assertThrows( + IllegalArgumentException.class, + () -> INT_SCHEMA.encode().withString("foo", "A") + ); + assertEquals("Unknown field: 'foo'", ex.getMessage()); + + Assertions.assertThrows( + NullPointerException.class, + () -> BYTE_SCHEMA.encode().withString(null, "A") + ); + } + + @Test + public void encodeFieldValueWithTypeMismatch() { + var ex = Assertions.assertThrows( + IllegalArgumentException.class, + () -> STRING_SCHEMA.encode().withByte(STRING_FIELD, (byte) 12) + ); + assertEquals("The defined type for 'AStr' is STRING not BYTE.", ex.getMessage()); + } + + @Test + public void decodeUndefinedToken() { + var ex = Assertions.assertThrows( + IllegalArgumentException.class, + () -> INT_SCHEMA.decode("foo") + ); + assertEquals("Token is not valid. Unable to parse token: 'foo'.", ex.getMessage()); + } + + @Test + public void testToString() { + assertEquals( + "TokenSchema{definition: TokenDefinition{version: 1, fields: [AByte:BYTE]}}", + BYTE_SCHEMA.toString() + ); + assertEquals( + "TokenSchema{definition: TokenDefinition{version: 3, fields: [ANum:INT]}}", + INT_SCHEMA.toString() + ); + assertEquals( + "TokenSchema{definition: TokenDefinition{version: 7, fields: [AStr:STRING]}}", + STRING_SCHEMA.toString() + ); + assertEquals( + "TokenSchema{definition: TokenDefinition{version: 2, fields: [ADur:DURATION]}}", + DURATION_SCHEMA.toString() + ); + assertEquals( + "TokenSchema{definition: TokenDefinition{version: 13, fields: [ATime:TIME_INSTANT]}}", + TIME_INSTANT_SCHEMA.toString() + ); + } + + @Test + void testDefinitionEqualsAndHashCode() { + var subject = BYTE_SCHEMA.currentDefinition(); + var same = TokenSchema.ofVersion(1).addByte(BYTE_FIELD).build().currentDefinition(); + + assertEquals(subject, same); + assertEquals(subject.hashCode(), same.hashCode()); + + assertNotEquals(subject, INT_SCHEMA.currentDefinition()); + assertNotEquals(subject.hashCode(), INT_SCHEMA.currentDefinition().hashCode()); + } +} diff --git a/src/test/java/org/opentripplanner/framework/token/TokenTypeTest.java b/src/test/java/org/opentripplanner/framework/token/TokenTypeTest.java new file mode 100644 index 00000000000..e4ba897928b --- /dev/null +++ b/src/test/java/org/opentripplanner/framework/token/TokenTypeTest.java @@ -0,0 +1,55 @@ +package org.opentripplanner.framework.token; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.Duration; +import java.time.Instant; +import java.time.Month; +import org.junit.jupiter.api.Test; + +class TokenTypeTest { + + @Test + void isNot() { + assertTrue(TokenType.INT.isNot(TokenType.STRING)); + assertFalse(TokenType.INT.isNot(TokenType.INT)); + } + + @Test + void valueToString() { + assertEquals("", TokenType.BYTE.valueToString(null)); + assertEquals("34", TokenType.BYTE.valueToString((byte) 34)); + assertEquals("true", TokenType.BOOLEAN.valueToString(true)); + assertEquals("2m30s", TokenType.DURATION.valueToString(Duration.ofSeconds(150))); + assertEquals("APRIL", TokenType.ENUM.valueToString(Month.APRIL)); + assertEquals("17", TokenType.INT.valueToString(17)); + assertEquals("FG", TokenType.STRING.valueToString("FG")); + assertEquals( + "2023-12-31T17:01:00Z", + TokenType.TIME_INSTANT.valueToString(Instant.parse("2023-12-31T17:01:00Z")) + ); + } + + @Test + void stringToValue() { + assertEquals((byte) 34, TokenType.BYTE.stringToValue("34")); + assertEquals(true, TokenType.BOOLEAN.stringToValue("true")); + assertEquals(Duration.ofSeconds(150), TokenType.DURATION.stringToValue("2m30s")); + assertEquals("APRIL", TokenType.ENUM.stringToValue("APRIL")); + assertEquals(17, TokenType.INT.stringToValue("17")); + assertEquals("FG", TokenType.STRING.stringToValue("FG")); + assertEquals( + Instant.parse("2023-12-31T17:01:00Z"), + TokenType.TIME_INSTANT.stringToValue("2023-12-31T17:01:00Z") + ); + + // Test nullable types with empty string + assertNull(TokenType.DURATION.stringToValue("")); + assertNull(TokenType.ENUM.stringToValue("")); + assertNull(TokenType.STRING.stringToValue("")); + assertNull(TokenType.TIME_INSTANT.stringToValue("")); + } +} diff --git a/src/test/java/org/opentripplanner/framework/tostring/ToStringBuilderTest.java b/src/test/java/org/opentripplanner/framework/tostring/ToStringBuilderTest.java index ca35c8272db..3e87006e953 100644 --- a/src/test/java/org/opentripplanner/framework/tostring/ToStringBuilderTest.java +++ b/src/test/java/org/opentripplanner/framework/tostring/ToStringBuilderTest.java @@ -20,6 +20,11 @@ public class ToStringBuilderTest { private static final ZoneId TIME_ZONE_ID_PARIS = ZoneIds.PARIS; + @Test + public void ofName() { + assertEquals("Name{}", ToStringBuilder.of("Name").toString()); + } + @Test public void addFieldIfTrue() { assertEquals("ToStringBuilderTest{x}", subject().addBoolIfTrue("x", true).toString()); diff --git a/src/test/java/org/opentripplanner/framework/tostring/ValueObjectToStringBuilderTest.java b/src/test/java/org/opentripplanner/framework/tostring/ValueObjectToStringBuilderTest.java index fad55e395bc..a59a0bf9b90 100644 --- a/src/test/java/org/opentripplanner/framework/tostring/ValueObjectToStringBuilderTest.java +++ b/src/test/java/org/opentripplanner/framework/tostring/ValueObjectToStringBuilderTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.time.Duration; +import java.time.Instant; import org.junit.jupiter.api.Test; public class ValueObjectToStringBuilderTest { @@ -107,6 +108,11 @@ public void addDuration() { assertEquals("", subject().skipNull().addDuration(null).toString()); } + @Test + public void addTime() { + assertEquals("1970-01-01T01:01:01Z", subject().addTime(Instant.ofEpochSecond(3661)).toString()); + } + @Test public void addCost() { assertEquals("null", subject().addCostCenti(null).toString()); diff --git a/src/test/java/org/opentripplanner/graph_builder/module/map/StreetMatcherTest.java b/src/test/java/org/opentripplanner/graph_builder/module/map/StreetMatcherTest.java deleted file mode 100644 index 92001fbf8a7..00000000000 --- a/src/test/java/org/opentripplanner/graph_builder/module/map/StreetMatcherTest.java +++ /dev/null @@ -1,157 +0,0 @@ -package org.opentripplanner.graph_builder.module.map; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.GeometryFactory; -import org.locationtech.jts.geom.LineString; -import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.street.model._data.StreetModelForTest; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.vertex.SimpleVertex; -import org.opentripplanner.street.model.vertex.StreetVertex; -import org.opentripplanner.street.model.vertex.VertexLabel; - -public class StreetMatcherTest { - - static GeometryFactory gf = new GeometryFactory(); - - private Graph graph; - - @BeforeEach - public void before() { - graph = new Graph(); - - vertex("56th_24th", 47.669457, -122.387577); - vertex("56th_22nd", 47.669462, -122.384739); - vertex("56th_20th", 47.669457, -122.382106); - - vertex("market_24th", 47.668690, -122.387577); - vertex("market_ballard", 47.668683, -122.386096); - vertex("market_22nd", 47.668686, -122.384749); - vertex("market_leary", 47.668669, -122.384392); - vertex("market_russell", 47.668655, -122.382997); - vertex("market_20th", 47.668684, -122.382117); - - vertex("shilshole_24th", 47.668419, -122.387534); - vertex("shilshole_22nd", 47.666519, -122.384744); - vertex("shilshole_vernon", 47.665938, -122.384048); - vertex("shilshole_20th", 47.664356, -122.382192); - - vertex("ballard_turn", 47.668509, -122.386069); - vertex("ballard_22nd", 47.667624, -122.384744); - vertex("ballard_vernon", 47.666422, -122.383158); - vertex("ballard_20th", 47.665476, -122.382128); - - vertex("leary_vernon", 47.666863, -122.382353); - vertex("leary_20th", 47.666682, -122.382160); - - vertex("russell_20th", 47.667846, -122.382128); - - edges("56th_24th", "56th_22nd", "56th_20th"); - - edges("56th_24th", "market_24th"); - edges("56th_22nd", "market_22nd"); - edges("56th_20th", "market_20th"); - - edges( - "market_24th", - "market_ballard", - "market_22nd", - "market_leary", - "market_russell", - "market_20th" - ); - edges("market_24th", "shilshole_24th", "shilshole_22nd", "shilshole_vernon", "shilshole_20th"); - edges("market_ballard", "ballard_turn", "ballard_22nd", "ballard_vernon", "ballard_20th"); - edges("market_leary", "leary_vernon", "leary_20th"); - edges("market_russell", "russell_20th"); - - edges("market_22nd", "ballard_22nd", "shilshole_22nd"); - edges("leary_vernon", "ballard_vernon", "shilshole_vernon"); - edges("market_20th", "russell_20th", "leary_20th", "ballard_20th", "shilshole_20th"); - } - - @Test - public void testStreetMatcher() { - LineString geometry = geometry(-122.385689, 47.669484, -122.387384, 47.669470); - - StreetMatcher matcher = new StreetMatcher(graph); - - List match = matcher.match(geometry); - assertNotNull(match); - assertEquals(1, match.size()); - assertEquals("56th_24th", match.get(0).getToVertex().getLabelString()); - - geometry = geometry(-122.385689, 47.669484, -122.387384, 47.669470, -122.387588, 47.669325); - - match = matcher.match(geometry); - assertNotNull(match); - assertEquals(2, match.size()); - - geometry = - geometry( - -122.385689, - 47.669484, - -122.387384, - 47.669470, - -122.387588, - 47.669325, - -122.387255, - 47.668675 - ); - - match = matcher.match(geometry); - assertNotNull(match); - assertEquals(3, match.size()); - - geometry = - geometry( - -122.384756, - 47.669260, - -122.384777, - 47.667454, - -122.383554, - 47.666789, - -122.3825, - 47.666 - ); - match = matcher.match(geometry); - assertNotNull(match); - assertEquals(4, match.size()); - assertEquals("ballard_20th", match.get(3).getToVertex().getLabelString()); - } - - private LineString geometry(double... ordinates) { - Coordinate[] coords = new Coordinate[ordinates.length / 2]; - - for (int i = 0; i < ordinates.length; i += 2) { - coords[i / 2] = new Coordinate(ordinates[i], ordinates[i + 1]); - } - return gf.createLineString(coords); - } - - /**** - * Private Methods - ****/ - - private SimpleVertex vertex(String label, double lat, double lon) { - var v = new SimpleVertex(label, lat, lon); - graph.addVertex(v); - return v; - } - - private void edges(String... vLabels) { - for (int i = 0; i < vLabels.length - 1; i++) { - StreetVertex vA = (StreetVertex) graph.getVertex(VertexLabel.string(vLabels[i])); - StreetVertex vB = (StreetVertex) graph.getVertex(VertexLabel.string(vLabels[i + 1])); - - StreetModelForTest.streetEdge(vA, vB); - StreetModelForTest.streetEdge(vB, vA); - } - } -} diff --git a/src/test/java/org/opentripplanner/gtfs/mapping/StopTimesMapperTest.java b/src/test/java/org/opentripplanner/gtfs/mapping/StopTimeMapperTest.java similarity index 55% rename from src/test/java/org/opentripplanner/gtfs/mapping/StopTimesMapperTest.java rename to src/test/java/org/opentripplanner/gtfs/mapping/StopTimeMapperTest.java index d0833474adf..6e082d9e98d 100644 --- a/src/test/java/org/opentripplanner/gtfs/mapping/StopTimesMapperTest.java +++ b/src/test/java/org/opentripplanner/gtfs/mapping/StopTimeMapperTest.java @@ -2,24 +2,35 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.List; +import org.geojson.LngLatAlt; +import org.geojson.Polygon; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.AgencyAndId; +import org.onebusaway.gtfs.model.Location; +import org.onebusaway.gtfs.model.LocationGroup; import org.onebusaway.gtfs.model.Stop; import org.onebusaway.gtfs.model.StopTime; import org.onebusaway.gtfs.model.Trip; +import org.opentripplanner._support.geometry.Polygons; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.model.PickDrop; +import org.opentripplanner.transit.model.site.AreaStop; +import org.opentripplanner.transit.model.site.GroupStop; import org.opentripplanner.transit.service.StopModel; import org.opentripplanner.transit.service.StopModelBuilder; -public class StopTimesMapperTest { +public class StopTimeMapperTest { private static final String FEED_ID = "FEED"; @@ -41,8 +52,6 @@ public class StopTimesMapperTest { private static final double SHAPE_DIST_TRAVELED = 2.5d; - private static final Stop STOP = new Stop(); - private static final String STOP_NAME = "Stop"; private static final String HEAD_SIGN = "Head Sign"; @@ -53,9 +62,11 @@ public class StopTimesMapperTest { private static final Trip TRIP = new GtfsTestData().trip; - private static final StopTime STOP_TIME = new StopTime(); - public static final DataImportIssueStore ISSUE_STORE = DataImportIssueStore.NOOP; + private static final List ZONE_COORDINATES = Arrays + .stream(Polygons.BERLIN.getCoordinates()) + .map(c -> new LngLatAlt(c.x, c.y)) + .toList(); private final StopModelBuilder stopModelBuilder = StopModel.of(); @@ -85,36 +96,60 @@ public class StopTimesMapperTest { new TranslationHelper() ); - static { + /** + * Build a static ("regular") stop. + */ + private static Stop buildStop() { + var stop = new Stop(); + stop.setId(AGENCY_AND_ID); + stop.setName(STOP_NAME); + stop.setLat(53.12); + stop.setLon(12.34); + return stop; + } + + /** + * Builds a stop time with a fixed stop. + */ + static StopTime buildDefaultStopTime() { + var stopTime = buildStopTime(); + var stop = buildStop(); + stopTime.setStop(stop); + return stopTime; + } + + /** + * Builds a stop time without a stop. Useful for testing flex fields. + */ + private static StopTime buildStopTime() { TRIP.setId(AGENCY_AND_ID); - STOP.setId(AGENCY_AND_ID); - STOP.setName(STOP_NAME); - - STOP_TIME.setId(ID); - STOP_TIME.setArrivalTime(ARRIVAL_TIME); - STOP_TIME.setDepartureTime(DEPARTURE_TIME); - STOP_TIME.setDropOffType(DROP_OFF_TYPE); - STOP_TIME.setFarePeriodId(FARE_PERIOD_ID); - STOP_TIME.setPickupType(PICKUP_TYPE); - STOP_TIME.setRouteShortName(ROUTE_SHORT_NAME); - STOP_TIME.setShapeDistTraveled(SHAPE_DIST_TRAVELED); - STOP_TIME.setStop(STOP); - STOP_TIME.setStopHeadsign(HEAD_SIGN); - STOP_TIME.setStopSequence(STOP_SEQUENCE); - STOP_TIME.setTimepoint(TIMEPOINT); - STOP_TIME.setTrip(TRIP); + + var stopTime = new StopTime(); + stopTime.setId(ID); + stopTime.setArrivalTime(ARRIVAL_TIME); + stopTime.setDepartureTime(DEPARTURE_TIME); + stopTime.setDropOffType(DROP_OFF_TYPE); + stopTime.setFarePeriodId(FARE_PERIOD_ID); + stopTime.setPickupType(PICKUP_TYPE); + stopTime.setRouteShortName(ROUTE_SHORT_NAME); + stopTime.setShapeDistTraveled(SHAPE_DIST_TRAVELED); + stopTime.setStopHeadsign(HEAD_SIGN); + stopTime.setStopSequence(STOP_SEQUENCE); + stopTime.setTimepoint(TIMEPOINT); + stopTime.setTrip(TRIP); + return stopTime; } @Test public void testMapCollection() { assertNull(subject.map((Collection) null)); assertTrue(subject.map(Collections.emptyList()).isEmpty()); - assertEquals(1, subject.map(Collections.singleton(STOP_TIME)).size()); + assertEquals(1, subject.map(Collections.singleton(buildDefaultStopTime())).size()); } @Test public void testMap() { - org.opentripplanner.model.StopTime result = subject.map(STOP_TIME); + var result = subject.map(buildDefaultStopTime()); assertEquals(ARRIVAL_TIME, result.getArrivalTime()); assertEquals(DEPARTURE_TIME, result.getDepartureTime()); @@ -132,7 +167,9 @@ public void testMap() { @Test public void testMapWithNulls() { - org.opentripplanner.model.StopTime result = subject.map(new StopTime()); + var st = new StopTime(); + st.setStop(buildStop()); + var result = subject.map(st); assertFalse(result.isArrivalTimeSet()); assertFalse(result.isDepartureTimeSet()); @@ -141,18 +178,56 @@ public void testMapWithNulls() { assertEquals(PickDrop.SCHEDULED, result.getPickupType()); assertNull(result.getRouteShortName()); assertFalse(result.isShapeDistTraveledSet()); - assertNull(result.getStop()); + assertNotNull(result.getStop()); assertNull(result.getStopHeadsign()); assertEquals(0, result.getStopSequence()); assertFalse(result.isTimepointSet()); } - /** Mapping the same object twice, should return the the same instance. */ + /** Mapping the same object twice, should return the same instance. */ @Test public void testMapCache() { - org.opentripplanner.model.StopTime result1 = subject.map(STOP_TIME); - org.opentripplanner.model.StopTime result2 = subject.map(STOP_TIME); - + var st = buildDefaultStopTime(); + var result1 = subject.map(st); + var result2 = subject.map(st); assertSame(result1, result2); } + + @Test + public void testNull() { + var st = buildStopTime(); + Assertions.assertThrows(NullPointerException.class, () -> subject.map(st)); + } + + @Test + public void testFlexLocation() { + var st = buildStopTime(); + var flexLocation = new Location(); + flexLocation.setId(AGENCY_AND_ID); + var polygon = new Polygon(); + polygon.setExteriorRing(ZONE_COORDINATES); + flexLocation.setGeometry(polygon); + st.setStop(flexLocation); + var mapped = subject.map(st); + + assertInstanceOf(AreaStop.class, mapped.getStop()); + var areaStop = (AreaStop) mapped.getStop(); + assertEquals(Polygons.BERLIN, areaStop.getGeometry()); + assertEquals("A:1", areaStop.getId().toString()); + } + + @Test + public void testFlexLocationGroup() { + var st = buildStopTime(); + var locGroup = new LocationGroup(); + locGroup.setName("A location group"); + locGroup.setId(AGENCY_AND_ID); + locGroup.addLocation(buildStop()); + st.setStop(locGroup); + var mapped = subject.map(st); + assertInstanceOf(GroupStop.class, mapped.getStop()); + + var groupStop = (GroupStop) mapped.getStop(); + assertEquals("[RegularStop{A:1 Stop}]", groupStop.getLocations().toString()); + } } diff --git a/src/test/java/org/opentripplanner/mmri/BeneficialChangesTest.java b/src/test/java/org/opentripplanner/mmri/BeneficialChangesTest.java index 5d2bcc7309f..fbef33e50e8 100644 --- a/src/test/java/org/opentripplanner/mmri/BeneficialChangesTest.java +++ b/src/test/java/org/opentripplanner/mmri/BeneficialChangesTest.java @@ -22,6 +22,6 @@ public void test3c1() { validateLeg(leg, 1388531040000L, 1388531100000L, "3c3", "3c2", null); - assertEquals("Stop 3c2 ~ BUS bus 0:04 0:05 ~ Stop 3c3 [$90]", itinerary.toStr()); + assertEquals("Stop 3c2 ~ BUS bus 0:04 0:05 ~ Stop 3c3 [C₁90]", itinerary.toStr()); } } diff --git a/src/test/java/org/opentripplanner/mmri/ExcludedRoutesTest.java b/src/test/java/org/opentripplanner/mmri/ExcludedRoutesTest.java index 7a47734d294..407c1848529 100644 --- a/src/test/java/org/opentripplanner/mmri/ExcludedRoutesTest.java +++ b/src/test/java/org/opentripplanner/mmri/ExcludedRoutesTest.java @@ -22,6 +22,6 @@ public void test3d1() { validateLeg(leg, 1388530860000L, 1388530980000L, "3d2", "3d1", null); - assertEquals("Stop 3d1 ~ BUS bus 2 0:01 0:03 ~ Stop 3d2 [$150]", itinerary.toStr()); + assertEquals("Stop 3d1 ~ BUS bus 2 0:01 0:03 ~ Stop 3d2 [C₁150]", itinerary.toStr()); } } diff --git a/src/test/java/org/opentripplanner/mmri/ExcludedTripsTest.java b/src/test/java/org/opentripplanner/mmri/ExcludedTripsTest.java index c0fc04ca62b..e8cf6191c84 100644 --- a/src/test/java/org/opentripplanner/mmri/ExcludedTripsTest.java +++ b/src/test/java/org/opentripplanner/mmri/ExcludedTripsTest.java @@ -22,6 +22,6 @@ public void test3e1() { validateLeg(leg, 1388530980000L, 1388531040000L, "3e2", "3e1", null); - assertEquals("Stop 3e1 ~ BUS bus 0:03 0:04 ~ Stop 3e2 [$90]", itinerary.toStr()); + assertEquals("Stop 3e1 ~ BUS bus 0:03 0:04 ~ Stop 3e2 [C₁90]", itinerary.toStr()); } } diff --git a/src/test/java/org/opentripplanner/mmri/FirstForbiddenTripToTripTransferTest.java b/src/test/java/org/opentripplanner/mmri/FirstForbiddenTripToTripTransferTest.java index c3ffc9d1956..56c00a76d25 100644 --- a/src/test/java/org/opentripplanner/mmri/FirstForbiddenTripToTripTransferTest.java +++ b/src/test/java/org/opentripplanner/mmri/FirstForbiddenTripToTripTransferTest.java @@ -24,7 +24,7 @@ public void test2e3() { validateLeg(legs[1], 1388531040000L, 1388531100000L, "2e36", "2e34", null); assertEquals( - "Stop 2e31 ~ RAIL train 1 0:01 0:03 ~ Stop 2e34 ~ RAIL train 2 0:04 0:05 ~ Stop 2e36 [$300]", + "Stop 2e31 ~ RAIL train 1 0:01 0:03 ~ Stop 2e34 ~ RAIL train 2 0:04 0:05 ~ Stop 2e36 [C₁300]", itinerary.toStr() ); } diff --git a/src/test/java/org/opentripplanner/mmri/FirstPreferredTripToTripTransferTest.java b/src/test/java/org/opentripplanner/mmri/FirstPreferredTripToTripTransferTest.java index d08fbdddf9c..0f7705e760b 100644 --- a/src/test/java/org/opentripplanner/mmri/FirstPreferredTripToTripTransferTest.java +++ b/src/test/java/org/opentripplanner/mmri/FirstPreferredTripToTripTransferTest.java @@ -24,7 +24,7 @@ public void test2e1() { validateLeg(legs[1], 1388530980000L, 1388531100000L, "2e16", "2e13", null); assertEquals( - "Stop 2e11 ~ RAIL train 1 0:01 0:02 ~ Stop 2e13 ~ RAIL train 2 0:03 0:05 ~ Stop 2e16 [$270]", + "Stop 2e11 ~ RAIL train 1 0:01 0:02 ~ Stop 2e13 ~ RAIL train 2 0:03 0:05 ~ Stop 2e16 [C₁270]", itinerary.toStr() ); } diff --git a/src/test/java/org/opentripplanner/mmri/FirstUnpreferredTransferTest.java b/src/test/java/org/opentripplanner/mmri/FirstUnpreferredTransferTest.java index 039ec7450ed..db938dcaca8 100644 --- a/src/test/java/org/opentripplanner/mmri/FirstUnpreferredTransferTest.java +++ b/src/test/java/org/opentripplanner/mmri/FirstUnpreferredTransferTest.java @@ -24,7 +24,7 @@ public void test3g1() { validateLeg(legs[1], 1388531040000L, 1388531100000L, "3g16", "3g14", null); assertEquals( - "Stop 3g11 ~ RAIL train 1 0:01 0:03 ~ Stop 3g14 ~ RAIL train 2 0:04 0:05 ~ Stop 3g16 [$300]", + "Stop 3g11 ~ RAIL train 1 0:01 0:03 ~ Stop 3g14 ~ RAIL train 2 0:04 0:05 ~ Stop 3g16 [C₁300]", itinerary.toStr() ); } diff --git a/src/test/java/org/opentripplanner/mmri/OptimizationTest.java b/src/test/java/org/opentripplanner/mmri/OptimizationTest.java index 2113a88f06d..1d546663191 100644 --- a/src/test/java/org/opentripplanner/mmri/OptimizationTest.java +++ b/src/test/java/org/opentripplanner/mmri/OptimizationTest.java @@ -22,7 +22,7 @@ public void test2a1() { validateLeg(leg, 1388530860000L, 1388530920000L, "2a2", "2a1", null); - assertEquals("Stop 2a1 ~ BUS short 0:01 0:02 ~ Stop 2a2 [$90]", itinerary.toStr()); + assertEquals("Stop 2a1 ~ BUS short 0:01 0:02 ~ Stop 2a2 [C₁90]", itinerary.toStr()); } @Test @@ -33,6 +33,6 @@ public void test2a2() { validateLeg(leg, 1388531100000L, 1388531160000L, "2a2", "2a1", null); - assertEquals("Stop 2a1 ~ BUS long 0:05 0:06 ~ Stop 2a2 [$90]", itinerary.toStr()); + assertEquals("Stop 2a1 ~ BUS long 0:05 0:06 ~ Stop 2a2 [C₁90]", itinerary.toStr()); } } diff --git a/src/test/java/org/opentripplanner/mmri/PreferencesTest.java b/src/test/java/org/opentripplanner/mmri/PreferencesTest.java index 0ab71b884af..d3c55097511 100644 --- a/src/test/java/org/opentripplanner/mmri/PreferencesTest.java +++ b/src/test/java/org/opentripplanner/mmri/PreferencesTest.java @@ -26,7 +26,7 @@ public void test2c1() { validateLeg(legs[1], 1388530980000L, 1388531040000L, "2c3", "2c2", null); assertEquals( - "Stop 2c1 ~ RAIL train 1 0:01 0:02 ~ Stop 2c2 ~ RAIL train 2 0:03 0:04 ~ Stop 2c3 [$240]", + "Stop 2c1 ~ RAIL train 1 0:01 0:02 ~ Stop 2c2 ~ RAIL train 2 0:03 0:04 ~ Stop 2c3 [C₁240]", itinerary.toStr() ); } diff --git a/src/test/java/org/opentripplanner/mmri/SecondForbiddenTripToTripTransferTest.java b/src/test/java/org/opentripplanner/mmri/SecondForbiddenTripToTripTransferTest.java index 5bb0d189e4d..335fe4d43d1 100644 --- a/src/test/java/org/opentripplanner/mmri/SecondForbiddenTripToTripTransferTest.java +++ b/src/test/java/org/opentripplanner/mmri/SecondForbiddenTripToTripTransferTest.java @@ -24,7 +24,7 @@ public void test2e4() { validateLeg(legs[1], 1388530980000L, 1388531100000L, "2e46", "2e43", null); assertEquals( - "Stop 2e41 ~ RAIL train 1 0:01 0:02 ~ Stop 2e43 ~ RAIL train 2 0:03 0:05 ~ Stop 2e46 [$300]", + "Stop 2e41 ~ RAIL train 1 0:01 0:02 ~ Stop 2e43 ~ RAIL train 2 0:03 0:05 ~ Stop 2e46 [C₁300]", itinerary.toStr() ); } diff --git a/src/test/java/org/opentripplanner/mmri/SecondPreferredTripToTripTransferTest.java b/src/test/java/org/opentripplanner/mmri/SecondPreferredTripToTripTransferTest.java index c09aeb50612..118dd64472a 100644 --- a/src/test/java/org/opentripplanner/mmri/SecondPreferredTripToTripTransferTest.java +++ b/src/test/java/org/opentripplanner/mmri/SecondPreferredTripToTripTransferTest.java @@ -24,7 +24,7 @@ public void test2e2() { validateLeg(legs[1], 1388531040000L, 1388531100000L, "2e26", "2e24", null); assertEquals( - "Stop 2e21 ~ RAIL train 1 0:01 0:03 ~ Stop 2e24 ~ RAIL train 2 0:04 0:05 ~ Stop 2e26 [$270]", + "Stop 2e21 ~ RAIL train 1 0:01 0:03 ~ Stop 2e24 ~ RAIL train 2 0:04 0:05 ~ Stop 2e26 [C₁270]", itinerary.toStr() ); } diff --git a/src/test/java/org/opentripplanner/mmri/SecondUnpreferredTransferTest.java b/src/test/java/org/opentripplanner/mmri/SecondUnpreferredTransferTest.java index bdd30a65fc0..847bf75e70d 100644 --- a/src/test/java/org/opentripplanner/mmri/SecondUnpreferredTransferTest.java +++ b/src/test/java/org/opentripplanner/mmri/SecondUnpreferredTransferTest.java @@ -24,7 +24,7 @@ public void test3g2() { validateLeg(legs[1], 1388530980000L, 1388531100000L, "3g26", "3g23", null); assertEquals( - "Stop 3g21 ~ RAIL train 1 0:01 0:02 ~ Stop 3g23 ~ RAIL train 2 0:03 0:05 ~ Stop 3g26 [$300]", + "Stop 3g21 ~ RAIL train 1 0:01 0:02 ~ Stop 3g23 ~ RAIL train 2 0:03 0:05 ~ Stop 3g26 [C₁300]", itinerary.toStr() ); } diff --git a/src/test/java/org/opentripplanner/mmri/StopToStopTransfersTest.java b/src/test/java/org/opentripplanner/mmri/StopToStopTransfersTest.java index 7b1a51864d7..6ef244b9d99 100644 --- a/src/test/java/org/opentripplanner/mmri/StopToStopTransfersTest.java +++ b/src/test/java/org/opentripplanner/mmri/StopToStopTransfersTest.java @@ -24,7 +24,7 @@ public void test2d1() { validateLeg(legs[1], 1388530980000L, 1388531040000L, "2d4", "2d3", null); assertEquals( - "Stop 2d1 ~ RAIL train 1 0:01 0:03 ~ Stop 2d3 ~ RAIL train 2 0:03 0:04 ~ Stop 2d4 [$210]", + "Stop 2d1 ~ RAIL train 1 0:01 0:03 ~ Stop 2d3 ~ RAIL train 2 0:03 0:04 ~ Stop 2d4 [C₁210]", itinerary.toStr() ); } diff --git a/src/test/java/org/opentripplanner/mmri/TimeTest.java b/src/test/java/org/opentripplanner/mmri/TimeTest.java index 08c9462ce18..77123d3e656 100644 --- a/src/test/java/org/opentripplanner/mmri/TimeTest.java +++ b/src/test/java/org/opentripplanner/mmri/TimeTest.java @@ -23,7 +23,7 @@ public void test1g1() { validateLeg(leg, 1388530980000L, 1388531040000L, "1g2", "1g1", null); - assertEquals("Stop 1g1 ~ BUS bus 0:03 0:04 ~ Stop 1g2 [$90]", itinerary.toStr()); + assertEquals("Stop 1g1 ~ BUS bus 0:03 0:04 ~ Stop 1g2 [C₁90]", itinerary.toStr()); } @Test @@ -34,7 +34,7 @@ public void test1g2() { validateLeg(leg, 1388530860000L, 1388530920000L, "1g2", "1g1", null); - assertEquals("Stop 1g1 ~ BUS bus 0:01 0:02 ~ Stop 1g2 [$90]", itinerary.toStr()); + assertEquals("Stop 1g1 ~ BUS bus 0:01 0:02 ~ Stop 1g2 [C₁90]", itinerary.toStr()); } @Test @@ -69,7 +69,7 @@ public void test1g5() { validateLeg(leg, 1388703780000L, 1388703840000L, "1g2", "1g1", null); - assertEquals("Stop 1g1 ~ BUS bus 0:03 0:04 ~ Stop 1g2 [$90]", itinerary.toStr()); + assertEquals("Stop 1g1 ~ BUS bus 0:03 0:04 ~ Stop 1g2 [C₁90]", itinerary.toStr()); } @Test @@ -78,8 +78,8 @@ public void test1g6() { Leg leg = itinerary.getLegs().toArray(new Leg[1])[0]; - validateLeg(leg, 1388703780000L, 1388703840000L, "1g2", "1g1", null); + validateLeg(leg, 1388703660000L, 1388703720000L, "1g2", "1g1", null); - assertEquals("Stop 1g1 ~ BUS bus 0:03 0:04 ~ Stop 1g2 [$90]", itinerary.toStr()); + assertEquals("Stop 1g1 ~ BUS bus 0:01 0:02 ~ Stop 1g2 [C₁90]", itinerary.toStr()); } } diff --git a/src/test/java/org/opentripplanner/mmri/UnplannedChangesTest.java b/src/test/java/org/opentripplanner/mmri/UnplannedChangesTest.java index 78de0af7313..753edd58fa6 100644 --- a/src/test/java/org/opentripplanner/mmri/UnplannedChangesTest.java +++ b/src/test/java/org/opentripplanner/mmri/UnplannedChangesTest.java @@ -22,7 +22,7 @@ public void test3b1() { validateLeg(leg, 1388531460000L, 1388531520000L, "3b2", "3b1", null); - assertEquals("Stop 3b1 ~ BUS bus 0:11 0:12 ~ Stop 3b2 [$90]", itinerary.toStr()); + assertEquals("Stop 3b1 ~ BUS bus 0:11 0:12 ~ Stop 3b2 [C₁90]", itinerary.toStr()); } @Test @@ -33,6 +33,6 @@ public void test3b2() { validateLeg(leg, 1388531460000L, 1388531520000L, "3b2", "3b1", null); - assertEquals("Stop 3b1 ~ BUS bus 0:11 0:12 ~ Stop 3b2 [$90]", itinerary.toStr()); + assertEquals("Stop 3b1 ~ BUS bus 0:11 0:12 ~ Stop 3b2 [C₁90]", itinerary.toStr()); } } diff --git a/src/test/java/org/opentripplanner/mmri/WheelchairTest.java b/src/test/java/org/opentripplanner/mmri/WheelchairTest.java index 5a3754e491b..c63235b067e 100644 --- a/src/test/java/org/opentripplanner/mmri/WheelchairTest.java +++ b/src/test/java/org/opentripplanner/mmri/WheelchairTest.java @@ -19,6 +19,6 @@ public void test2b1() { validateLeg(itinerary.firstLeg(), 1388530980000L, 1388531040000L, "2b2", "2b1", null); - assertEquals("Stop 2b1 ~ BUS attr 0:03 0:04 ~ Stop 2b2 [$90]", itinerary.toStr()); + assertEquals("Stop 2b1 ~ BUS attr 0:03 0:04 ~ Stop 2b2 [C₁90]", itinerary.toStr()); } } diff --git a/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java b/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java index d65f2057a2e..df67e3c2f44 100644 --- a/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java +++ b/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java @@ -39,7 +39,7 @@ public void testDerivedFieldsWithWalkingOnly() { assertEquals(420.0d, result.firstLeg().getDistanceMeters(), 1E-3); assertSameLocation(B, result.lastLeg().getTo()); - assertEquals("A ~ Walk 5m ~ B [$600]", result.toStr()); + assertEquals("A ~ Walk 5m ~ B [C₁600]", result.toStr()); } @Test @@ -63,7 +63,7 @@ public void testDerivedFieldsWithBusAllTheWay() { assertEquals(TransitModelForTest.id("55"), result.firstLeg().getTrip().getId()); assertEquals(7500, result.firstLeg().getDistanceMeters(), 1E-3); - assertEquals("A ~ BUS 55 11:00 11:10 ~ B [$720]", result.toStr()); + assertEquals("A ~ BUS 55 11:00 11:10 ~ B [C₁720]", result.toStr()); } @Test @@ -87,7 +87,7 @@ public void testDerivedFieldsWithTrainAllTheWay() { assertEquals(TransitModelForTest.id("20"), result.firstLeg().getTrip().getId()); assertEquals(15_000, result.firstLeg().getDistanceMeters(), 1E-3); - assertEquals("A ~ RAIL R2 11:05 11:15 ~ B [$720]", result.toStr()); + assertEquals("A ~ RAIL R2 11:05 11:15 ~ B [C₁720]", result.toStr()); } @Test @@ -129,7 +129,7 @@ public void testDerivedFieldsWithBusAndWalkingAccessAndEgress() { assertEquals(1464, result.getGeneralizedCost()); assertFalse(result.isWalkOnly()); - assertEquals("A ~ Walk 2m ~ B ~ BUS 1 11:10 11:20 ~ C ~ Walk 3m ~ D [$1464]", result.toStr()); + assertEquals("A ~ Walk 2m ~ B ~ BUS 1 11:10 11:20 ~ C ~ Walk 3m ~ D [C₁1_464]", result.toStr()); } @Test @@ -155,7 +155,7 @@ public void walkBusBusWalkTrainWalk() { assertEquals( "A ~ Walk 2m ~ B ~ BUS 55 11:04 11:14 ~ C ~ BUS 21 11:16 11:20 ~ D " + - "~ Walk 3m ~ E ~ RAIL R2 11:30 11:50 ~ F ~ Walk 1m ~ G [$3648]", + "~ Walk 3m ~ E ~ RAIL R2 11:30 11:50 ~ F ~ Walk 1m ~ G [C₁3_648]", result.toStr() ); } diff --git a/src/test/java/org/opentripplanner/model/plan/PlanTestConstants.java b/src/test/java/org/opentripplanner/model/plan/PlanTestConstants.java index 7be75ed1760..edb967b5b16 100644 --- a/src/test/java/org/opentripplanner/model/plan/PlanTestConstants.java +++ b/src/test/java/org/opentripplanner/model/plan/PlanTestConstants.java @@ -22,6 +22,7 @@ public interface PlanTestConstants { int D1m = DurationUtils.durationInSeconds("1m"); int D2m = DurationUtils.durationInSeconds("2m"); int D3m = DurationUtils.durationInSeconds("3m"); + int D4m = DurationUtils.durationInSeconds("4m"); int D5m = DurationUtils.durationInSeconds("5m"); int D10m = DurationUtils.durationInSeconds("10m"); int D12m = DurationUtils.durationInSeconds("12m"); diff --git a/src/test/java/org/opentripplanner/model/plan/PagingSearchWindowAdjusterTest.java b/src/test/java/org/opentripplanner/model/plan/paging/PagingSearchWindowAdjusterTest.java similarity index 98% rename from src/test/java/org/opentripplanner/model/plan/PagingSearchWindowAdjusterTest.java rename to src/test/java/org/opentripplanner/model/plan/paging/PagingSearchWindowAdjusterTest.java index bf4e311f83c..22dcd49e63f 100644 --- a/src/test/java/org/opentripplanner/model/plan/PagingSearchWindowAdjusterTest.java +++ b/src/test/java/org/opentripplanner/model/plan/paging/PagingSearchWindowAdjusterTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.model.plan; +package org.opentripplanner.model.plan.paging; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.framework.time.DurationUtils.duration; diff --git a/src/test/java/org/opentripplanner/model/plan/paging/cursor/DeduplicationPageCutTest.java b/src/test/java/org/opentripplanner/model/plan/paging/cursor/DeduplicationPageCutTest.java new file mode 100644 index 00000000000..f2b58d6e9c5 --- /dev/null +++ b/src/test/java/org/opentripplanner/model/plan/paging/cursor/DeduplicationPageCutTest.java @@ -0,0 +1,30 @@ +package org.opentripplanner.model.plan.paging.cursor; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.Instant; +import org.junit.jupiter.api.Test; + +class DeduplicationPageCutTest { + + public static final Instant DEPARTURE_TIME = Instant.ofEpochSecond(1_000_000); + public static final Instant ARRIVAL_TIME = Instant.ofEpochSecond(2_000_000); + public static final int GENERALIZED_COST = 1700; + public static final int NUM_OF_TRANSFERS = 2; + public static final boolean ON_STREET = false; + + @Test + void testToString() { + assertEquals( + "[1970-01-12T13:46:40Z, 1970-01-24T03:33:20Z, $1700, Tx2, transit]", + new DeduplicationPageCut( + DEPARTURE_TIME, + ARRIVAL_TIME, + GENERALIZED_COST, + NUM_OF_TRANSFERS, + ON_STREET + ) + .toString() + ); + } +} diff --git a/src/test/java/org/opentripplanner/model/plan/pagecursor/PageCursorFactoryTest.java b/src/test/java/org/opentripplanner/model/plan/paging/cursor/PageCursorFactoryTest.java similarity index 79% rename from src/test/java/org/opentripplanner/model/plan/pagecursor/PageCursorFactoryTest.java rename to src/test/java/org/opentripplanner/model/plan/paging/cursor/PageCursorFactoryTest.java index 5e6951ea7cf..7bf54d05bc2 100644 --- a/src/test/java/org/opentripplanner/model/plan/pagecursor/PageCursorFactoryTest.java +++ b/src/test/java/org/opentripplanner/model/plan/paging/cursor/PageCursorFactoryTest.java @@ -1,17 +1,18 @@ -package org.opentripplanner.model.plan.pagecursor; +package org.opentripplanner.model.plan.paging.cursor; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.model.plan.SortOrder.STREET_AND_ARRIVAL_TIME; import static org.opentripplanner.model.plan.SortOrder.STREET_AND_DEPARTURE_TIME; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.model.plan.pagecursor.PageType.NEXT_PAGE; -import static org.opentripplanner.model.plan.pagecursor.PageType.PREVIOUS_PAGE; +import static org.opentripplanner.model.plan.paging.cursor.PageType.NEXT_PAGE; +import static org.opentripplanner.model.plan.paging.cursor.PageType.PREVIOUS_PAGE; import java.time.Duration; import java.time.Instant; import org.junit.jupiter.api.Test; import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.ItinerarySortKey; import org.opentripplanner.model.plan.PlanTestConstants; @SuppressWarnings("ConstantConditions") @@ -49,8 +50,7 @@ public void sortArrivalAscendingCropSearchWindow() { .withRemovedItineraries( new TestPageCursorInput( newItinerary(A).bus(55, timeAsSeconds(T12_00), timeAsSeconds(T12_10), B).build(), - newItinerary(A).bus(65, timeAsSeconds(T12_30), timeAsSeconds(T13_30), B).build(), - PagingDeduplicationSection.HEAD + newItinerary(A).bus(65, timeAsSeconds(T12_30), timeAsSeconds(T13_30), B).build() ) ); @@ -58,7 +58,7 @@ public void sortArrivalAscendingCropSearchWindow() { assertPageCursor(nextPage, T12_30, null, D90M, NEXT_PAGE, true); var prevPage = factory.previousPageCursor(); - assertPageCursor(prevPage, T10_30, T12_10, D90M, PREVIOUS_PAGE, true); + assertPageCursor(prevPage, T10_30, null, D90M, PREVIOUS_PAGE, true); } @Test @@ -80,8 +80,7 @@ public void sortArrivalAscendingCropSearchWindowPreviousPage() { .withRemovedItineraries( new TestPageCursorInput( newItinerary(A).bus(55, timeAsSeconds(T12_00), timeAsSeconds(T12_10), B).build(), - newItinerary(A).bus(65, timeAsSeconds(T12_30), timeAsSeconds(T13_30), B).build(), - PagingDeduplicationSection.TAIL + newItinerary(A).bus(65, timeAsSeconds(T12_30), timeAsSeconds(T13_30), B).build() ) ); @@ -89,7 +88,7 @@ public void sortArrivalAscendingCropSearchWindowPreviousPage() { assertPageCursor(nextPage, T13_00, null, D90M, NEXT_PAGE, true); var prevPage = factory.previousPageCursor(); - assertPageCursor(prevPage, T11_01, T13_30, D90M, PREVIOUS_PAGE, true); + assertPageCursor(prevPage, T11_01, null, D90M, PREVIOUS_PAGE, true); } @Test @@ -111,8 +110,7 @@ public void sortDepartureDescendingCropSearchWindow() { .withRemovedItineraries( new TestPageCursorInput( newItinerary(A).bus(55, timeAsSeconds(T12_00), timeAsSeconds(T12_30), B).build(), - newItinerary(A).bus(65, timeAsSeconds(T12_30), timeAsSeconds(T13_00), B).build(), - PagingDeduplicationSection.HEAD + newItinerary(A).bus(65, timeAsSeconds(T12_30), timeAsSeconds(T13_00), B).build() ) ); @@ -120,7 +118,7 @@ public void sortDepartureDescendingCropSearchWindow() { assertPageCursor(nextPage, T13_00, null, D90M, NEXT_PAGE, true); var prevPage = factory.previousPageCursor(); - assertPageCursor(prevPage, T11_01, T13_00, D90M, PREVIOUS_PAGE, true); + assertPageCursor(prevPage, T11_01, T13_30, D90M, PREVIOUS_PAGE, true); } @Test @@ -142,8 +140,7 @@ public void sortDepartureDescendingCropSearchWindowNextPage() { .withRemovedItineraries( new TestPageCursorInput( newItinerary(A).bus(55, timeAsSeconds(T12_00), timeAsSeconds(T12_30), B).build(), - newItinerary(A).bus(65, timeAsSeconds(T12_30), timeAsSeconds(T13_00), B).build(), - PagingDeduplicationSection.TAIL + newItinerary(A).bus(65, timeAsSeconds(T12_30), timeAsSeconds(T13_00), B).build() ) ); @@ -170,11 +167,11 @@ private void assertPageCursor( PageType expPageType, Boolean hasDedupeParams ) { - assertEquals(expEdt, pageCursor.earliestDepartureTime); - assertEquals(expLat, pageCursor.latestArrivalTime); - assertEquals(expSearchWindow, pageCursor.searchWindow); - assertEquals(expPageType, pageCursor.type); - assertEquals(hasDedupeParams, pageCursor.itineraryPageCut != null); + assertEquals(expEdt, pageCursor.earliestDepartureTime()); + assertEquals(expLat, pageCursor.latestArrivalTime()); + assertEquals(expSearchWindow, pageCursor.searchWindow()); + assertEquals(expPageType, pageCursor.type()); + assertEquals(hasDedupeParams, pageCursor.itineraryPageCut() != null); } private record TestPageCursorInput( @@ -182,30 +179,16 @@ private record TestPageCursorInput( Instant earliestRemovedDeparture, Instant latestRemovedDeparture, Instant latestRemovedArrival, - Instant firstRemovedArrivalTime, - boolean firstRemovedIsOnStreetAllTheWay, - int firstRemovedGeneralizedCost, - int firstRemovedNumOfTransfers, - Instant firstRemovedDepartureTime, - PagingDeduplicationSection deduplicationSection + ItinerarySortKey pageCut ) implements PageCursorInput { - public TestPageCursorInput( - Itinerary keptItinerary, - Itinerary removedItinerary, - PagingDeduplicationSection deduplicationSection - ) { + public TestPageCursorInput(Itinerary keptItinerary, Itinerary removedItinerary) { this( keptItinerary.endTimeAsInstant(), removedItinerary.startTimeAsInstant(), removedItinerary.startTimeAsInstant(), removedItinerary.endTimeAsInstant(), - removedItinerary.endTimeAsInstant(), - removedItinerary.isOnStreetAllTheWay(), - removedItinerary.getGeneralizedCost(), - removedItinerary.getNumberOfTransfers(), - removedItinerary.startTimeAsInstant(), - deduplicationSection + removedItinerary ); } } diff --git a/src/test/java/org/opentripplanner/model/plan/paging/cursor/PageCursorSerializerTest.java b/src/test/java/org/opentripplanner/model/plan/paging/cursor/PageCursorSerializerTest.java new file mode 100644 index 00000000000..20c0230f309 --- /dev/null +++ b/src/test/java/org/opentripplanner/model/plan/paging/cursor/PageCursorSerializerTest.java @@ -0,0 +1,77 @@ +package org.opentripplanner.model.plan.paging.cursor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.opentripplanner.model.plan.SortOrder.STREET_AND_DEPARTURE_TIME; +import static org.opentripplanner.model.plan.paging.cursor.PageType.PREVIOUS_PAGE; + +import java.time.Duration; +import java.time.Instant; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.time.DurationUtils; +import org.opentripplanner.model.plan.ItinerarySortKey; + +class PageCursorSerializerTest { + + private static final Instant EDT = Instant.parse("2023-12-31T23:59:59Z"); + private static final Instant LAT = Instant.parse("2024-01-15T00:00:01Z"); + private static final Duration SW = DurationUtils.duration("5h"); + private static final Instant DT = Instant.parse("2024-01-10T10:00:00Z"); + private static final Instant AT = Instant.parse("2024-01-10T12:00:00Z"); + + public static final String TOKEN_V1 = + "MXxQUkVWSU9VU19QQUdFfDIwMjMtMTItMzFUMjM6NTk6NTlafDIwMjQtMDEtMTVUMDA6MDA6MDFafDVofFNUUkVFVF" + + "9BTkRfREVQQVJUVVJFX1RJTUV8dHJ1ZXwyMDI0LTAxLTEwVDEwOjAwOjAwWnwyMDI0LTAxLTEwVDEyOjAwOjAwWnwz" + + "fDEyMDB8"; + public static final String TOKEN_V1_W_NULLS = + "MXxQUkVWSU9VU19QQUdFfDIwMjMtMTItMzFUMjM6NTk6NTlafHw1aHxTVFJFRVRfQU5EX0RFUEFSVFVSRV9USU1FfH" + + "x8fHx8"; + + private static final ItinerarySortKey CUT = new DeduplicationPageCut(DT, AT, 1200, 3, true); + + private static final PageCursor PAGE_CURSOR_V1 = new PageCursor( + PREVIOUS_PAGE, + STREET_AND_DEPARTURE_TIME, + EDT, + LAT, + SW, + CUT + ); + + private final PageCursor pageCursorV1withNulls = new PageCursor( + PREVIOUS_PAGE, + STREET_AND_DEPARTURE_TIME, + EDT, + null, + SW, + null + ); + + @Test + void encode() { + assertEquals(TOKEN_V1, PageCursorSerializer.encode(PAGE_CURSOR_V1)); + assertEquals(TOKEN_V1_W_NULLS, PageCursorSerializer.encode(pageCursorV1withNulls)); + } + + @Test + void decodeTokenV1() { + PageCursor tokenV1 = PageCursorSerializer.decode(TOKEN_V1); + assertEquals(PREVIOUS_PAGE, tokenV1.type()); + assertEquals(STREET_AND_DEPARTURE_TIME, tokenV1.originalSortOrder()); + assertEquals(EDT, tokenV1.earliestDepartureTime()); + assertEquals(LAT, tokenV1.latestArrivalTime()); + assertEquals(SW, tokenV1.searchWindow()); + assertEquals(CUT, tokenV1.itineraryPageCut()); + } + + @Test + void decodeTokenV1_W_NULLS() { + PageCursor tokenV1 = PageCursorSerializer.decode(TOKEN_V1_W_NULLS); + assertEquals(PREVIOUS_PAGE, tokenV1.type()); + assertEquals(STREET_AND_DEPARTURE_TIME, tokenV1.originalSortOrder()); + assertEquals(EDT, tokenV1.earliestDepartureTime()); + assertNull(tokenV1.latestArrivalTime()); + assertEquals(SW, tokenV1.searchWindow()); + assertNull(tokenV1.itineraryPageCut()); + } +} diff --git a/src/test/java/org/opentripplanner/model/plan/pagecursor/PageCursorTest.java b/src/test/java/org/opentripplanner/model/plan/paging/cursor/PageCursorTest.java similarity index 52% rename from src/test/java/org/opentripplanner/model/plan/pagecursor/PageCursorTest.java rename to src/test/java/org/opentripplanner/model/plan/paging/cursor/PageCursorTest.java index 1b6b452f2a9..10b59007c17 100644 --- a/src/test/java/org/opentripplanner/model/plan/pagecursor/PageCursorTest.java +++ b/src/test/java/org/opentripplanner/model/plan/paging/cursor/PageCursorTest.java @@ -1,22 +1,43 @@ -package org.opentripplanner.model.plan.pagecursor; +package org.opentripplanner.model.plan.paging.cursor; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.opentripplanner.framework.collection.ListSection.HEAD; +import static org.opentripplanner.framework.collection.ListSection.TAIL; import static org.opentripplanner.model.plan.SortOrder.STREET_AND_ARRIVAL_TIME; import static org.opentripplanner.model.plan.SortOrder.STREET_AND_DEPARTURE_TIME; -import static org.opentripplanner.model.plan.pagecursor.PageType.NEXT_PAGE; -import static org.opentripplanner.model.plan.pagecursor.PageType.PREVIOUS_PAGE; +import static org.opentripplanner.model.plan.paging.cursor.PageType.NEXT_PAGE; +import static org.opentripplanner.model.plan.paging.cursor.PageType.PREVIOUS_PAGE; import java.time.Duration; import java.time.Instant; import java.time.ZoneId; +import java.util.List; import java.util.TimeZone; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; 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.opentripplanner._support.time.ZoneIds; +import org.opentripplanner.framework.collection.ListSection; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.Place; +import org.opentripplanner.model.plan.PlanTestConstants; +import org.opentripplanner.model.plan.SortOrder; +import org.opentripplanner.model.plan.TestItineraryBuilder; +import org.opentripplanner.transit.model._data.TransitModelForTest; -public class PageCursorTest { +class PageCursorTest implements PlanTestConstants { + + public static final TransitModelForTest TEST_MODEL = TransitModelForTest.of(); + public static final Place A = Place.forStop( + TEST_MODEL.stop("A").withCoordinate(5.0, 8.0).build() + ); + public static final Place B = Place.forStop( + TEST_MODEL.stop("B").withCoordinate(6.0, 8.5).build() + ); private static final ZoneId ZONE_ID = ZoneIds.GMT; private static final String EDT_STR = "2021-01-31T12:20:00Z"; @@ -24,6 +45,11 @@ public class PageCursorTest { private static final Instant EDT = Instant.parse(EDT_STR); private static final Instant LAT = Instant.parse(LAT_STR); private static final Duration SEARCH_WINDOW = Duration.parse("PT2h"); + private static final Itinerary PAGE_CUT = TestItineraryBuilder + .newItinerary(A, 0) + .walk(20, Place.forStop(TEST_MODEL.stop("1:stop", 1d, 1d).build())) + .bus(23, 0, 50, B) + .build(); private TimeZone originalTimeZone; private PageCursor subjectDepartAfter; @@ -35,9 +61,9 @@ public void setup() { TimeZone.setDefault(TimeZone.getTimeZone(ZONE_ID)); subjectDepartAfter = - new PageCursor(NEXT_PAGE, STREET_AND_ARRIVAL_TIME, EDT, null, SEARCH_WINDOW); + new PageCursor(NEXT_PAGE, STREET_AND_ARRIVAL_TIME, EDT, null, SEARCH_WINDOW, null); subjectArriveBy = - new PageCursor(PREVIOUS_PAGE, STREET_AND_DEPARTURE_TIME, EDT, LAT, SEARCH_WINDOW); + new PageCursor(PREVIOUS_PAGE, STREET_AND_DEPARTURE_TIME, EDT, LAT, SEARCH_WINDOW, PAGE_CUT); } @AfterEach @@ -60,11 +86,30 @@ public void testToString() { EDT_STR + ", lat: " + LAT_STR + - ", searchWindow: 2h}", + ", searchWindow: 2h, " + + "itineraryPageCut: [2020-02-02T00:00:00Z, 2020-02-02T00:00:50Z, $194, Tx0, transit]}", subjectArriveBy.toString() ); } + static List cropItinerariesAtTestCase() { + return List.of( + Arguments.of(NEXT_PAGE, STREET_AND_ARRIVAL_TIME, TAIL), + Arguments.of(NEXT_PAGE, STREET_AND_DEPARTURE_TIME, HEAD), + Arguments.of(PREVIOUS_PAGE, STREET_AND_ARRIVAL_TIME, HEAD), + Arguments.of(PREVIOUS_PAGE, STREET_AND_DEPARTURE_TIME, TAIL) + ); + } + + @ParameterizedTest + @MethodSource("cropItinerariesAtTestCase") + public void cropItinerariesAt(PageType page, SortOrder order, ListSection expSection) { + assertEquals( + expSection, + new PageCursor(page, order, EDT, null, SEARCH_WINDOW, null).cropItinerariesAt() + ); + } + @Test @SuppressWarnings("ConstantConditions") public void encodeAndDecode() { diff --git a/src/test/java/org/opentripplanner/model/plan/paging/cursor/PageTypeTest.java b/src/test/java/org/opentripplanner/model/plan/paging/cursor/PageTypeTest.java new file mode 100644 index 00000000000..0e84d0b2e0a --- /dev/null +++ b/src/test/java/org/opentripplanner/model/plan/paging/cursor/PageTypeTest.java @@ -0,0 +1,15 @@ +package org.opentripplanner.model.plan.paging.cursor; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class PageTypeTest { + + @Test + void isNext() { + assertTrue(PageType.NEXT_PAGE.isNext()); + assertFalse(PageType.PREVIOUS_PAGE.isNext()); + } +} diff --git a/src/test/java/org/opentripplanner/model/routing/TripSearchMetadataTest.java b/src/test/java/org/opentripplanner/model/routing/TripSearchMetadataTest.java index 4cfd21ad1e6..fce79cd2d4c 100644 --- a/src/test/java/org/opentripplanner/model/routing/TripSearchMetadataTest.java +++ b/src/test/java/org/opentripplanner/model/routing/TripSearchMetadataTest.java @@ -9,82 +9,81 @@ public class TripSearchMetadataTest { - public static final Duration THIRTY_MINUTES = Duration.ofMinutes(30); - public static final int SEARCH_WINDOW_USED = (int) THIRTY_MINUTES.toSeconds(); + private static final Duration SEARCH_WINDOW_USED = Duration.ofMinutes(30); @Test - public void createMetadataForArriveWithSearchWindowOnly() { + void createMetadataForArriveWithSearchWindowOnly() { TripSearchMetadata subject = TripSearchMetadata.createForArriveBy( Instant.parse("2020-05-17T10:20:00Z"), SEARCH_WINDOW_USED, null ); - assertEquals(THIRTY_MINUTES, subject.searchWindowUsed); + assertEquals(SEARCH_WINDOW_USED, subject.searchWindowUsed); assertEquals("2020-05-17T09:50:00Z", subject.prevDateTime.toString()); assertEquals("2020-05-17T10:50:00Z", subject.nextDateTime.toString()); } @Test - public void createMetadataForArriveByWithTimeGiven() { + void createMetadataForArriveByWithTimeGiven() { TripSearchMetadata subject; - // New arrival-time with seconds, 10:05:15, should be rounded up to 10:06:00 + // New arrival-time with seconds, 10:35:01, should be rounded up to 10:36:00 subject = TripSearchMetadata.createForArriveBy( Instant.parse("2020-05-17T10:20:00Z"), SEARCH_WINDOW_USED, - Instant.parse("2020-05-17T10:05:15Z") + Instant.parse("2020-05-17T10:35:00Z") ); - assertEquals(THIRTY_MINUTES, subject.searchWindowUsed); - assertEquals("2020-05-17T10:06:00Z", subject.prevDateTime.toString()); + assertEquals(SEARCH_WINDOW_USED, subject.searchWindowUsed); + assertEquals("2020-05-17T10:04:00Z", subject.prevDateTime.toString()); assertEquals("2020-05-17T10:50:00Z", subject.nextDateTime.toString()); - // New arrival-time without seconds, 10:05:00, should stay the same: 10:05:00 + // New arrival-time without seconds, 10:36:00, should stay the same: 10:36:00 subject = TripSearchMetadata.createForArriveBy( Instant.parse("2020-05-17T11:20:00Z"), SEARCH_WINDOW_USED, - Instant.parse("2020-05-17T11:05:00Z") + Instant.parse("2020-05-17T11:35:59Z") ); - assertEquals("2020-05-17T11:05:00Z", subject.prevDateTime.toString()); + assertEquals("2020-05-17T11:04:00Z", subject.prevDateTime.toString()); assertEquals("2020-05-17T11:50:00Z", subject.nextDateTime.toString()); } @Test - public void createMetadataForDepartAfterWithSearchWindowOnly() { + void createMetadataForDepartAfterWithSearchWindowOnly() { TripSearchMetadata subject = TripSearchMetadata.createForDepartAfter( Instant.parse("2020-05-17T10:20:00Z"), SEARCH_WINDOW_USED, null ); - assertEquals(THIRTY_MINUTES, subject.searchWindowUsed); + assertEquals(SEARCH_WINDOW_USED, subject.searchWindowUsed); assertEquals("2020-05-17T09:50:00Z", subject.prevDateTime.toString()); assertEquals("2020-05-17T10:50:00Z", subject.nextDateTime.toString()); } @Test - public void createMetadataForDepartAfterWithTimeGiven() { + void createMetadataForDepartAfterWithTimeGiven() { TripSearchMetadata subject; - // New departure-time with seconds, 10:35:15, should be rounded down to 10:35:00 + // New departure-time, 10:35:00, should be rounded up to 10:36:00 subject = TripSearchMetadata.createForDepartAfter( Instant.parse("2020-05-17T10:20:00Z"), SEARCH_WINDOW_USED, - Instant.parse("2020-05-17T10:35:15Z") + Instant.parse("2020-05-17T10:35:00Z") ); - assertEquals(THIRTY_MINUTES, subject.searchWindowUsed); + assertEquals(SEARCH_WINDOW_USED, subject.searchWindowUsed); assertEquals("2020-05-17T09:50:00Z", subject.prevDateTime.toString()); - assertEquals("2020-05-17T10:35:00Z", subject.nextDateTime.toString()); + assertEquals("2020-05-17T10:36:00Z", subject.nextDateTime.toString()); - // New departure-time without seconds, 11:35:00, should stay the same: 11:35:00 + // New departure-time, 11:35:59, should be rounded up to 11:36:00 subject = TripSearchMetadata.createForDepartAfter( Instant.parse("2020-05-17T11:20:00Z"), SEARCH_WINDOW_USED, - Instant.parse("2020-05-17T11:35:00Z") + Instant.parse("2020-05-17T11:35:59Z") ); assertEquals("2020-05-17T10:50:00Z", subject.prevDateTime.toString()); - assertEquals("2020-05-17T11:35:00Z", subject.nextDateTime.toString()); + assertEquals("2020-05-17T11:36:00Z", subject.nextDateTime.toString()); } } diff --git a/src/test/java/org/opentripplanner/netex/mapping/StationMapperTest.java b/src/test/java/org/opentripplanner/netex/mapping/StationMapperTest.java index 99b097dc684..b100a38782a 100644 --- a/src/test/java/org/opentripplanner/netex/mapping/StationMapperTest.java +++ b/src/test/java/org/opentripplanner/netex/mapping/StationMapperTest.java @@ -10,7 +10,6 @@ import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.netex.NetexTestDataSupport; import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory; -import org.opentripplanner.transit.model.framework.EntityById; import org.opentripplanner.transit.model.site.Station; import org.opentripplanner.transit.service.StopModel; import org.rutebanken.netex.model.Quay; diff --git a/src/test/java/org/opentripplanner/raptor/_data/api/PathUtils.java b/src/test/java/org/opentripplanner/raptor/_data/api/PathUtils.java index 1489589561b..9da872adbae 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/api/PathUtils.java +++ b/src/test/java/org/opentripplanner/raptor/_data/api/PathUtils.java @@ -29,7 +29,11 @@ public static String pathsToString(Collection> paths) { } public static String pathsToStringDetailed(RaptorResponse response) { - return pathsToString(response.paths(), p -> p.toStringDetailed(TRANSLATOR::stopIndexToName)); + return pathsToStringDetailed(response.paths()); + } + + public static String pathsToStringDetailed(Collection> paths) { + return pathsToString(paths, p -> p.toStringDetailed(TRANSLATOR::stopIndexToName)); } public static String join(String... paths) { @@ -37,16 +41,14 @@ public static String join(String... paths) { } public static String withoutCost(String path) { - return path.replaceAll(" \\$\\d+", ""); + return path.replaceAll(" C₁[\\d_]+", ""); } public static String[] withoutCost(String... paths) { return Stream.of(paths).map(path -> withoutCost(path)).toList().toArray(new String[0]); } - /* private methods */ - - private static String pathsToString( + public static String pathsToString( Collection> paths, Function, String> mapToStr ) { diff --git a/src/test/java/org/opentripplanner/raptor/_data/api/TestPathBuilder.java b/src/test/java/org/opentripplanner/raptor/_data/api/TestPathBuilder.java index da24a375714..19d9904cd5b 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/api/TestPathBuilder.java +++ b/src/test/java/org/opentripplanner/raptor/_data/api/TestPathBuilder.java @@ -8,6 +8,7 @@ import org.opentripplanner.raptor._data.transit.TestTransfer; import org.opentripplanner.raptor._data.transit.TestTripPattern; import org.opentripplanner.raptor._data.transit.TestTripSchedule; +import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.path.RaptorPath; import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.path.PathBuilder; @@ -31,6 +32,7 @@ public class TestPathBuilder implements RaptorTestConstants { private final RaptorSlackProvider slackProvider; private PathBuilder builder; private int startTime; + private int c2 = RaptorConstants.NOT_SET; public TestPathBuilder( RaptorSlackProvider slackProvider, @@ -47,6 +49,12 @@ public TestPathBuilder(@Nullable RaptorCostCalculator costCalc this(new DefaultSlackProvider(TRANSFER_SLACK, BOARD_SLACK, ALIGHT_SLACK), costCalculator); } + /** Assign c2 value for path. TODO: Add c2 value for each leg. */ + public TestPathBuilder c2(int c2) { + this.c2 = c2; + return this; + } + /** * Create access starting at the fixed given {@code starting}. Opening hours is used to enforce * the access start time and prevent time-shifting it. @@ -116,6 +124,7 @@ public RaptorPath egress(int duration) { public RaptorPath egress(TestAccessEgress transfer) { builder.egress(transfer); + builder.c2(c2); return builder.build(); } diff --git a/src/test/java/org/opentripplanner/raptor/_data/api/TestPathBuilderTestRaptor.java b/src/test/java/org/opentripplanner/raptor/_data/api/TestPathBuilderTestRaptor.java index 3ce1e85cb41..bc11f32e4cc 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/api/TestPathBuilderTestRaptor.java +++ b/src/test/java/org/opentripplanner/raptor/_data/api/TestPathBuilderTestRaptor.java @@ -4,7 +4,7 @@ import static org.opentripplanner.framework.time.DurationUtils.durationInSeconds; import static org.opentripplanner.framework.time.TimeUtils.time; import static org.opentripplanner.model.transfer.TransferConstraint.REGULAR_TRANSFER; -import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.COST_CALCULATOR; +import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.C1_CALCULATOR; import static org.opentripplanner.raptor._data.transit.TestAccessEgress.walk; import org.junit.jupiter.api.Assertions; @@ -17,7 +17,7 @@ */ public class TestPathBuilderTestRaptor implements RaptorTestConstants { - private final TestPathBuilder subject = new TestPathBuilder(COST_CALCULATOR); + private final TestPathBuilder subject = new TestPathBuilder(C1_CALCULATOR); @Test public void testSimplePathWithOneTransit() { @@ -29,7 +29,7 @@ public void testSimplePathWithOneTransit() { .egress(D2m); var transitLeg = path.accessLeg().nextLeg().asTransitLeg(); - int boardCost = COST_CALCULATOR.boardingCost( + int boardCost = C1_CALCULATOR.boardingCost( true, path.accessLeg().toTime(), STOP_A, @@ -38,7 +38,7 @@ public void testSimplePathWithOneTransit() { REGULAR_TRANSFER ); - int transitCost = COST_CALCULATOR.transitArrivalCost( + int transitCost = C1_CALCULATOR.transitArrivalCost( boardCost, ALIGHT_SLACK, transitDuration, @@ -46,14 +46,14 @@ public void testSimplePathWithOneTransit() { STOP_B ); - int accessEgressCost = COST_CALCULATOR.costEgress(walk(STOP_B, D2m + D1m)); + int accessEgressCost = C1_CALCULATOR.costEgress(walk(STOP_B, D2m + D1m)); assertEquals(accessEgressCost + transitCost, path.c1()); assertEquals( - "Walk 1m 10:00:15 10:01:15 $120 ~ A 45s " + - "~ BUS L1 10:02 10:07 5m $438 ~ B 15s " + - "~ Walk 2m 10:07:15 10:09:15 $210 " + - "[10:00:15 10:09:15 9m 0tx $768]", + "Walk 1m 10:00:15 10:01:15 C₁120 ~ A 45s " + + "~ BUS L1 10:02 10:07 5m C₁438 ~ B 15s " + + "~ Walk 2m 10:07:15 10:09:15 C₁210 " + + "[10:00:15 10:09:15 9m Tₓ0 C₁768]", path.toStringDetailed(this::stopIndexToName) ); } @@ -61,6 +61,7 @@ public void testSimplePathWithOneTransit() { @Test public void testBasicPath() { var path = subject + .c2(7) .access(BasicPathTestCase.ACCESS_START, STOP_A, BasicPathTestCase.ACCESS_DURATION) .bus( BasicPathTestCase.LINE_11, @@ -68,7 +69,7 @@ public void testBasicPath() { BasicPathTestCase.L11_DURATION, STOP_B ) - .walk(BasicPathTestCase.TX_DURATION, STOP_C, BasicPathTestCase.TX_COST) + .walk(BasicPathTestCase.TX_DURATION, STOP_C, BasicPathTestCase.TX_C1) .bus( BasicPathTestCase.LINE_21, BasicPathTestCase.L21_START, @@ -82,6 +83,7 @@ public void testBasicPath() { STOP_E ) .egress(BasicPathTestCase.EGRESS_DURATION); + Assertions.assertEquals( BasicPathTestCase.BASIC_PATH_AS_STRING, path.toString(this::stopIndexToName) @@ -90,6 +92,6 @@ public void testBasicPath() { BasicPathTestCase.BASIC_PATH_AS_DETAILED_STRING, path.toStringDetailed(this::stopIndexToName) ); - Assertions.assertEquals(BasicPathTestCase.TOTAL_COST, path.c1()); + Assertions.assertEquals(BasicPathTestCase.TOTAL_C1, path.c1()); } } diff --git a/src/test/java/org/opentripplanner/raptor/_data/stoparrival/AbstractStopArrival.java b/src/test/java/org/opentripplanner/raptor/_data/stoparrival/AbstractStopArrival.java index c7edf20b8b9..fbedae88e45 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/stoparrival/AbstractStopArrival.java +++ b/src/test/java/org/opentripplanner/raptor/_data/stoparrival/AbstractStopArrival.java @@ -3,12 +3,13 @@ import org.opentripplanner.raptor._data.transit.TestTripSchedule; import org.opentripplanner.raptor.api.view.ArrivalView; -public abstract class AbstractStopArrival implements ArrivalView { +abstract class AbstractStopArrival implements ArrivalView { private final int round; private final int stop; private final int arrivalTime; - private final int cost; + private final int c1; + private final int c2; private final ArrivalView previous; AbstractStopArrival( @@ -16,13 +17,30 @@ public abstract class AbstractStopArrival implements ArrivalView previous ) { this.round = round; this.stop = stop; this.arrivalTime = arrivalTime; - this.cost = (previous == null ? 0 : previous.c1()) + extraCost; this.previous = previous; + this.c2 = c2; + + if (previous == null) { + this.c1 = extraCost; + } else { + this.c1 = previous.c1() + extraCost; + } + } + + AbstractStopArrival( + int round, + int stop, + int arrivalTime, + int extraCost, + ArrivalView previous + ) { + this(round, stop, arrivalTime, extraCost, previous.c2(), previous); } @Override @@ -42,12 +60,12 @@ public int arrivalTime() { @Override public int c1() { - return cost; + return c1; } @Override public int c2() { - throw new UnsupportedOperationException("C2 is not available for the C1 implementation"); + return c2; } @Override diff --git a/src/test/java/org/opentripplanner/raptor/_data/stoparrival/Access.java b/src/test/java/org/opentripplanner/raptor/_data/stoparrival/Access.java index 408e0a4e966..a4ad6d18f38 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/stoparrival/Access.java +++ b/src/test/java/org/opentripplanner/raptor/_data/stoparrival/Access.java @@ -2,25 +2,16 @@ import static org.opentripplanner.raptor.api.model.PathLegType.ACCESS; -import org.opentripplanner.raptor._data.transit.TestAccessEgress; import org.opentripplanner.raptor.api.model.PathLegType; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.view.AccessPathView; -public class Access extends AbstractStopArrival { +class Access extends AbstractStopArrival { private final RaptorAccessEgress access; - public Access(int stop, int departureTime, int arrivalTime, int cost) { - this( - stop, - arrivalTime, - TestAccessEgress.walk(stop, Math.abs(arrivalTime - departureTime), cost) - ); - } - - public Access(int stop, int arrivalTime, RaptorAccessEgress path) { - super(0, stop, arrivalTime, path.generalizedCost(), null); + Access(int stop, int arrivalTime, RaptorAccessEgress path, int c2) { + super(0, stop, arrivalTime, path.c1(), c2, null); this.access = path; } diff --git a/src/test/java/org/opentripplanner/raptor/_data/stoparrival/BasicPathTestCase.java b/src/test/java/org/opentripplanner/raptor/_data/stoparrival/BasicPathTestCase.java index 030ae71b4ad..4a92264e87c 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/stoparrival/BasicPathTestCase.java +++ b/src/test/java/org/opentripplanner/raptor/_data/stoparrival/BasicPathTestCase.java @@ -4,6 +4,10 @@ import static org.opentripplanner.framework.time.DurationUtils.durationToStr; import static org.opentripplanner.framework.time.TimeUtils.time; import static org.opentripplanner.model.transfer.TransferConstraint.REGULAR_TRANSFER; +import static org.opentripplanner.raptor._data.stoparrival.TestArrivals.access; +import static org.opentripplanner.raptor._data.stoparrival.TestArrivals.bus; +import static org.opentripplanner.raptor._data.stoparrival.TestArrivals.egress; +import static org.opentripplanner.raptor._data.stoparrival.TestArrivals.transfer; import static org.opentripplanner.raptor._data.transit.TestAccessEgress.flexWithOnBoard; import static org.opentripplanner.raptor._data.transit.TestTripPattern.pattern; import static org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter.toRaptorCost; @@ -24,6 +28,7 @@ import org.opentripplanner.raptor.api.path.RaptorPath; import org.opentripplanner.raptor.api.path.TransferPathLeg; import org.opentripplanner.raptor.api.path.TransitPathLeg; +import org.opentripplanner.raptor.api.view.ArrivalView; import org.opentripplanner.raptor.path.Path; import org.opentripplanner.raptor.rangeraptor.internalapi.WorkerLifeCycle; import org.opentripplanner.raptor.rangeraptor.lifecycle.LifeCycleSubscriptions; @@ -59,18 +64,18 @@ public class BasicPathTestCase implements RaptorTestConstants { private static final RaptorConstrainedTransfer EMPTY_CONSTRAINTS = null; public static final String BASIC_PATH_AS_DETAILED_STRING = - "Walk 3m 10:00:15 10:03:15 $360 " + + "Walk 3m 10:00:15 10:03:15 C₁360 " + "~ A 45s ~ " + - "BUS L11 10:04 10:35 31m $1998 " + + "BUS L11 10:04 10:35 31m C₁1_998 " + "~ B 15s ~ " + - "Walk 3m45s 10:35:15 10:39 $450 " + + "Walk 3m45s 10:35:15 10:39 C₁450 " + "~ C 21m ~ " + - "BUS L21 11:00 11:23 23m $2640 " + + "BUS L21 11:00 11:23 23m C₁2_640 " + "~ D 17m ~ " + - "BUS L31 11:40 11:52 12m $1776 " + + "BUS L31 11:40 11:52 12m C₁1_776 " + "~ E 15s ~ " + - "Walk 7m45s 11:52:15 12:00 $930 " + - "[10:00:15 12:00 1h59m45s 2tx $8154]"; + "Walk 7m45s 11:52:15 12:00 C₁930 " + + "[10:00:15 12:00 1h59m45s Tₓ2 C₁8_154 C₂7]"; public static final String BASIC_PATH_AS_STRING = "Walk 3m ~ A" + @@ -79,16 +84,17 @@ public class BasicPathTestCase implements RaptorTestConstants { " ~ BUS L21 11:00 11:23 ~ D" + " ~ BUS L31 11:40 11:52 ~ E" + " ~ Walk 7m45s " + - "[10:00:15 12:00 1h59m45s 2tx $8154]"; + "[10:00:15 12:00 1h59m45s Tₓ2 C₁8_154 C₂7]"; - private static final int BOARD_COST_SEC = 60; - private static final int TRANSFER_COST_SEC = 120; + private static final int BOARD_C1_SEC = 60; + private static final int TRANSFER_C1_SEC = 120; private static final double[] TRANSIT_RELUCTANCE = new double[] { 1.0 }; public static final int TRANSIT_RELUCTANCE_INDEX = 0; public static final double WAIT_RELUCTANCE = 0.8; + private static final int C2 = 7; /** Stop cost for stop NA, A, C, E .. H is zero(0), B: 30s, and D: 60s. ?=0, A=1 .. H=8 */ - private static final int[] STOP_COSTS = { 0, 0, 3_000, 0, 6_000, 0, 0, 0, 0, 0 }; + private static final int[] STOP_C1S = { 0, 0, 3_000, 0, 6_000, 0, 0, 0, 0, 0 }; // Some times which should not have eny effect on tests private static final int VERY_EARLY = time("00:00"); @@ -104,48 +110,53 @@ public class BasicPathTestCase implements RaptorTestConstants { STOP_A, ACCESS_DURATION ); - public static final int ACCESS_COST = ACCESS_TRANSFER.generalizedCost(); + public static final int ACCESS_C1 = ACCESS_TRANSFER.c1(); + public static final int ACCESS_C2 = 0; // Trip 1 (A ~ BUS L11 10:04 10:35 ~ B) public static final int L11_START = time("10:04"); private static final int L11_END = time("10:35"); public static final int L11_DURATION = L11_END - L11_START; private static final int L11_WAIT_DURATION = L11_START - ACCESS_END + ALIGHT_SLACK; - public static final int LINE_11_COST = - STOP_COSTS[STOP_A] + - STOP_COSTS[STOP_B] + - toRaptorCost(BOARD_COST_SEC + WAIT_RELUCTANCE * L11_WAIT_DURATION + L11_DURATION); + public static final int LINE_11_C1 = + STOP_C1S[STOP_A] + + STOP_C1S[STOP_B] + + toRaptorCost(BOARD_C1_SEC + WAIT_RELUCTANCE * L11_WAIT_DURATION + L11_DURATION); + public static final int LINE_11_C2 = 2; // Transfers (B ~ Walk 3m45s ~ C) private static final int TX_START = time("10:35:15"); private static final int TX_END = time("10:39:00"); public static final int TX_DURATION = TX_END - TX_START; public static final RaptorTransfer TX_TRANSFER = TestTransfer.transfer(STOP_C, TX_DURATION); - public static final int TX_COST = TX_TRANSFER.generalizedCost(); + public static final int TX_C1 = TX_TRANSFER.c1(); + public static final int TX_C3 = 3; // Trip 2 (C ~ BUS L21 11:00 11:23 ~ D) public static final int L21_START = time("11:00"); private static final int L21_END = time("11:23"); public static final int L21_DURATION = L21_END - L21_START; private static final int L21_WAIT_DURATION = L21_START - TX_END + ALIGHT_SLACK; - public static final int LINE_21_COST = - STOP_COSTS[STOP_C] + - STOP_COSTS[STOP_D] + + public static final int LINE_21_C1 = + STOP_C1S[STOP_C] + + STOP_C1S[STOP_D] + toRaptorCost( - BOARD_COST_SEC + TRANSFER_COST_SEC + WAIT_RELUCTANCE * L21_WAIT_DURATION + L21_DURATION + BOARD_C1_SEC + TRANSFER_C1_SEC + WAIT_RELUCTANCE * L21_WAIT_DURATION + L21_DURATION ); + public static final int LINE_21_C2 = 5; // Trip 3 (D ~ BUS L31 11:40 11:52 ~ E) public static final int L31_START = time("11:40"); private static final int L31_END = time("11:52"); public static final int L31_DURATION = L31_END - L31_START; private static final int L31_WAIT_DURATION = L31_START - (L21_END + ALIGHT_SLACK) + ALIGHT_SLACK; - public static final int LINE_31_COST = - STOP_COSTS[STOP_D] + - STOP_COSTS[STOP_E] + + public static final int LINE_31_C1 = + STOP_C1S[STOP_D] + + STOP_C1S[STOP_E] + toRaptorCost( - BOARD_COST_SEC + TRANSFER_COST_SEC + WAIT_RELUCTANCE * L31_WAIT_DURATION + L31_DURATION + BOARD_C1_SEC + TRANSFER_C1_SEC + WAIT_RELUCTANCE * L31_WAIT_DURATION + L31_DURATION ); + public static final int LINE_31_C2 = 6; // Egress (E ~ Walk 7m45s ~ ) public static final int EGRESS_START = time("11:52:15"); @@ -155,25 +166,26 @@ public class BasicPathTestCase implements RaptorTestConstants { STOP_E, EGRESS_DURATION ); - public static final int EGRESS_COST = EGRESS_TRANSFER.generalizedCost(); + public static final int EGRESS_C1 = EGRESS_TRANSFER.c1(); + public static final int EGRESS_C2 = 7; public static final int TRIP_DURATION = EGRESS_END - ACCESS_START; private static final RaptorAccessEgress ACCESS = TestAccessEgress.walk( STOP_A, ACCESS_DURATION, - ACCESS_COST + ACCESS_C1 ); private static final RaptorAccessEgress EGRESS = TestAccessEgress.walk( STOP_E, EGRESS_DURATION, - EGRESS_COST + EGRESS_C1 ); // this is of course not a real flex egress private static final RaptorAccessEgress FLEX = flexWithOnBoard( STOP_E, EGRESS_DURATION, - EGRESS_COST + EGRESS_C1 ); public static final String LINE_11 = "L11"; @@ -200,16 +212,16 @@ public class BasicPathTestCase implements RaptorTestConstants { .transitReluctanceIndex(TRANSIT_RELUCTANCE_INDEX) .build(); - public static final RaptorCostCalculator COST_CALCULATOR = new DefaultCostCalculator<>( - BOARD_COST_SEC, - TRANSFER_COST_SEC, + public static final RaptorCostCalculator C1_CALCULATOR = new DefaultCostCalculator<>( + BOARD_C1_SEC, + TRANSFER_C1_SEC, WAIT_RELUCTANCE, TRANSIT_RELUCTANCE, - STOP_COSTS + STOP_C1S ); - public static final int TOTAL_COST = - ACCESS_COST + LINE_11_COST + TX_COST + LINE_21_COST + LINE_31_COST + EGRESS_COST; + public static final int TOTAL_C1 = + ACCESS_C1 + LINE_11_C1 + TX_C1 + LINE_21_C1 + LINE_31_C1 + EGRESS_C1; /** Wait time between trip L11 and L21 including slack */ public static final int WAIT_TIME_L11_L21 = L21_START - L11_END - TX_DURATION; @@ -222,18 +234,19 @@ public static WorkerLifeCycle lifeCycle() { } public static DestinationArrival basicTripByForwardSearch() { - AbstractStopArrival prevArrival; - prevArrival = new Access(STOP_A, ACCESS_START, ACCESS_END, ACCESS_COST); - prevArrival = new Bus(1, STOP_B, L11_END, LINE_11_COST, TRIP_1, prevArrival); - prevArrival = new Transfer(1, STOP_C, TX_START, TX_END, TX_COST, prevArrival); - prevArrival = new Bus(2, STOP_D, L21_END, LINE_21_COST, TRIP_2, prevArrival); - prevArrival = new Bus(3, STOP_E, L31_END, LINE_31_COST, TRIP_3, prevArrival); - Egress egress = new Egress(EGRESS_START, EGRESS_END, EGRESS_COST, prevArrival); + ArrivalView prevArrival, egress; + prevArrival = access(STOP_A, ACCESS_START, ACCESS_END, ACCESS_C1, ACCESS_C2); + prevArrival = bus(1, STOP_B, L11_END, LINE_11_C1, LINE_11_C2, TRIP_1, prevArrival); + prevArrival = transfer(1, STOP_C, TX_START, TX_END, TX_C1, prevArrival); + prevArrival = bus(2, STOP_D, L21_END, LINE_21_C1, LINE_21_C2, TRIP_2, prevArrival); + prevArrival = bus(3, STOP_E, L31_END, LINE_31_C1, LINE_31_C2, TRIP_3, prevArrival); + egress = egress(EGRESS_START, EGRESS_END, EGRESS_C1, EGRESS_C2, prevArrival); return new DestinationArrival<>( - TestAccessEgress.walk(egress.previous().stop(), egress.durationInSeconds()), + egress.egressPath().egress(), egress.previous(), egress.arrivalTime(), - egress.additionalCost() + egress.egressPath().egress().c1(), + egress.c2() ); } @@ -242,19 +255,20 @@ public static DestinationArrival basicTripByForwardSearch() { * search: */ public static DestinationArrival basicTripByReverseSearch() { - AbstractStopArrival nextArrival; - nextArrival = new Access(STOP_E, EGRESS_END, EGRESS_START, EGRESS_COST); + ArrivalView nextArrival, egress; + nextArrival = access(STOP_E, EGRESS_END, EGRESS_START, EGRESS_C1, EGRESS_C2); // Board slack is subtracted from the arrival time to get the latest possible - nextArrival = new Bus(1, STOP_D, L31_START, LINE_31_COST, TRIP_3, nextArrival); - nextArrival = new Bus(2, STOP_C, L21_START, LINE_21_COST, TRIP_2, nextArrival); - nextArrival = new Transfer(2, STOP_B, TX_END, TX_START, TX_COST, nextArrival); - nextArrival = new Bus(3, STOP_A, L11_START, LINE_11_COST, TRIP_1, nextArrival); - Egress egress = new Egress(ACCESS_END, ACCESS_START, ACCESS_COST, nextArrival); + nextArrival = bus(1, STOP_D, L31_START, LINE_31_C1, LINE_31_C2, TRIP_3, nextArrival); + nextArrival = bus(2, STOP_C, L21_START, LINE_21_C1, LINE_21_C2, TRIP_2, nextArrival); + nextArrival = transfer(2, STOP_B, TX_END, TX_START, TX_C1, nextArrival); + nextArrival = bus(3, STOP_A, L11_START, LINE_11_C1, LINE_11_C2, TRIP_1, nextArrival); + egress = egress(ACCESS_END, ACCESS_START, ACCESS_C1, ACCESS_C2, nextArrival); return new DestinationArrival<>( - TestAccessEgress.walk(egress.previous().stop(), egress.durationInSeconds()), + egress.egressPath().egress(), egress.previous(), egress.arrivalTime(), - egress.additionalCost() + egress.egressPath().egress().c1(), + egress.c2() ); } @@ -267,7 +281,7 @@ public static RaptorPath basicTripAsPath() { EGRESS, EGRESS_START, EGRESS_END, - EGRESS_COST + EGRESS_C1 ); TransitPathLeg leg5 = new TransitPathLeg<>( TRIP_3, @@ -276,7 +290,7 @@ public static RaptorPath basicTripAsPath() { TRIP_3.findDepartureStopPosition(L31_START, STOP_D), TRIP_3.findArrivalStopPosition(L31_END, STOP_E), EMPTY_CONSTRAINTS, - LINE_31_COST, + LINE_31_C1, leg6 ); TransitPathLeg leg4 = new TransitPathLeg<>( @@ -286,7 +300,7 @@ public static RaptorPath basicTripAsPath() { TRIP_2.findDepartureStopPosition(L21_START, STOP_C), TRIP_2.findArrivalStopPosition(L21_END, STOP_D), EMPTY_CONSTRAINTS, - LINE_21_COST, + LINE_21_C1, leg5 ); var transfer = TestTransfer.transfer(STOP_C, TX_END - TX_START); @@ -294,7 +308,7 @@ public static RaptorPath basicTripAsPath() { STOP_B, TX_START, TX_END, - transfer.generalizedCost(), + transfer.c1(), transfer, leg4.asTransitLeg() ); @@ -305,32 +319,27 @@ public static RaptorPath basicTripAsPath() { TRIP_1.findDepartureStopPosition(L11_START, STOP_A), TRIP_1.findArrivalStopPosition(L11_END, STOP_B), EMPTY_CONSTRAINTS, - LINE_11_COST, + LINE_11_C1, leg3 ); AccessPathLeg leg1 = new AccessPathLeg<>( ACCESS, ACCESS_START, ACCESS_END, - ACCESS_COST, + ACCESS_C1, leg2.asTransitLeg() ); - return new Path<>(RAPTOR_ITERATION_START_TIME, leg1, TOTAL_COST, 0); + return new Path<>(RAPTOR_ITERATION_START_TIME, leg1, TOTAL_C1, 7); } public static RaptorPath flexTripAsPath() { - PathLeg leg6 = new EgressPathLeg<>( - FLEX, - EGRESS_START, - EGRESS_END, - EGRESS_COST - ); + PathLeg leg6 = new EgressPathLeg<>(FLEX, EGRESS_START, EGRESS_END, EGRESS_C1); var transfer = TestTransfer.transfer(STOP_E, TX_END - TX_START); PathLeg leg3 = new TransferPathLeg<>( STOP_B, TX_START, TX_END, - transfer.generalizedCost(), + transfer.c1(), transfer, leg6 ); @@ -341,17 +350,17 @@ public static RaptorPath flexTripAsPath() { TRIP_1.findDepartureStopPosition(L11_START, STOP_A), TRIP_1.findArrivalStopPosition(L11_END, STOP_B), EMPTY_CONSTRAINTS, - LINE_11_COST, + LINE_11_C1, leg3 ); AccessPathLeg leg1 = new AccessPathLeg<>( ACCESS, ACCESS_START, ACCESS_END, - ACCESS_COST, + ACCESS_C1, leg2.asTransitLeg() ); - return new Path<>(RAPTOR_ITERATION_START_TIME, leg1, TOTAL_COST, 0); + return new Path<>(RAPTOR_ITERATION_START_TIME, leg1, TOTAL_C1, C2); } public static List basicTripStops() { @@ -402,15 +411,15 @@ public void testSetup() { // The calculator is not under test here, so we assert everything is as expected assertEquals( - LINE_11_COST, + LINE_11_C1, transitArrivalCost(ACCESS_END, TRIP_1, STOP_A, L11_START, STOP_B, L11_END) ); assertEquals( - LINE_21_COST, + LINE_21_C1, transitArrivalCost(TX_END, TRIP_2, STOP_C, L21_START, STOP_D, L21_END) ); assertEquals( - LINE_31_COST, + LINE_31_C1, transitArrivalCost(L21_END + ALIGHT_SLACK, TRIP_3, STOP_D, L31_START, STOP_E, L31_END) ); @@ -431,7 +440,7 @@ private static int transitArrivalCost( int alightTime ) { boolean firstTransit = TRIP_1 == trip; - int boardCost = COST_CALCULATOR.boardingCost( + int boardCost = C1_CALCULATOR.boardingCost( firstTransit, prevArrivalTime, boardStop, @@ -440,7 +449,7 @@ private static int transitArrivalCost( REGULAR_TRANSFER ); - return COST_CALCULATOR.transitArrivalCost( + return C1_CALCULATOR.transitArrivalCost( boardCost, ALIGHT_SLACK, alightTime - boardTime, diff --git a/src/test/java/org/opentripplanner/raptor/_data/stoparrival/Egress.java b/src/test/java/org/opentripplanner/raptor/_data/stoparrival/Egress.java index 13ce8398e77..a244acdf720 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/stoparrival/Egress.java +++ b/src/test/java/org/opentripplanner/raptor/_data/stoparrival/Egress.java @@ -2,51 +2,48 @@ import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.raptor._data.transit.TestTripSchedule; +import org.opentripplanner.raptor.api.model.PathLegType; +import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.view.ArrivalView; +import org.opentripplanner.raptor.api.view.EgressPathView; -public class Egress { +public class Egress extends AbstractStopArrival { - private final int arrivalTime; - private final int durationInSeconds; - private final int cost; - private final ArrivalView previous; + private final RaptorAccessEgress egressPath; public Egress( - int departureTime, int arrivalTime, - int cost, + RaptorAccessEgress egressPath, + int c2, ArrivalView previous ) { - this.arrivalTime = arrivalTime; - this.durationInSeconds = Math.abs(arrivalTime - departureTime); - this.cost = cost; - this.previous = previous; + super(previous.round(), previous.stop(), arrivalTime, egressPath.c1(), c2, previous); + this.egressPath = egressPath; } - public int additionalCost() { - return cost; - } - - public int durationInSeconds() { - return durationInSeconds; - } - - public int arrivalTime() { - return arrivalTime; - } - - public ArrivalView previous() { - return previous; + @Override + public EgressPathView egressPath() { + return () -> egressPath; } @Override public String toString() { return String.format( "Egress { round: %d, stop: %d, arrival-time: %s $%d }", - previous.round(), - previous.stop(), - TimeUtils.timeToStrCompact(arrivalTime), - previous.c1() + additionalCost() + round(), + stop(), + TimeUtils.timeToStrCompact(arrivalTime()), + c1() ); } + + @Override + public PathLegType arrivedBy() { + return PathLegType.EGRESS; + } + + @Override + public boolean arrivedOnBoard() { + return egressPath.stopReachedOnBoard(); + } } diff --git a/src/test/java/org/opentripplanner/raptor/_data/stoparrival/FlexAccessAndEgressPathTestCase.java b/src/test/java/org/opentripplanner/raptor/_data/stoparrival/FlexAccessAndEgressPathTestCase.java index 9291871a555..1e694e3d771 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/stoparrival/FlexAccessAndEgressPathTestCase.java +++ b/src/test/java/org/opentripplanner/raptor/_data/stoparrival/FlexAccessAndEgressPathTestCase.java @@ -3,6 +3,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.framework.time.DurationUtils.durationInSeconds; import static org.opentripplanner.framework.time.TimeUtils.time; +import static org.opentripplanner.raptor._data.stoparrival.TestArrivals.access; +import static org.opentripplanner.raptor._data.stoparrival.TestArrivals.bus; +import static org.opentripplanner.raptor.api.model.RaptorValueFormatter.formatC1; import static org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter.toRaptorCost; import org.junit.jupiter.api.Test; @@ -13,7 +16,9 @@ import org.opentripplanner.raptor._data.transit.TestTripPattern; import org.opentripplanner.raptor._data.transit.TestTripSchedule; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; +import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTransfer; +import org.opentripplanner.raptor.api.view.ArrivalView; import org.opentripplanner.raptor.rangeraptor.path.DestinationArrival; import org.opentripplanner.raptor.spi.DefaultSlackProvider; import org.opentripplanner.raptor.spi.RaptorSlackProvider; @@ -46,12 +51,12 @@ public class FlexAccessAndEgressPathTestCase implements RaptorTestConstants { // The transit reluctance is ignored, any value should work private static final int TRANSIT_RELUCTANCE_INDEX = -1; public static final double WAIT_RELUCTANCE = 0.8; - public static final int BOARD_COST_SEC = 60; - public static final int TRANSFER_COST_SEC = 120; - // The COST_CALCULATOR is not under test, so we use it to calculate correct cost values. - public static final DefaultCostCalculator COST_CALCULATOR = new DefaultCostCalculator<>( - BOARD_COST_SEC, - TRANSFER_COST_SEC, + public static final int BOARD_C1_SEC = 60; + public static final int TRANSFER_C1_SEC = 120; + // The C1_CALCULATOR is not under test, so we use it to calculate correct cost values. + public static final DefaultCostCalculator C1_CALCULATOR = new DefaultCostCalculator<>( + BOARD_C1_SEC, + TRANSFER_C1_SEC, WAIT_RELUCTANCE, null, null @@ -65,13 +70,13 @@ public class FlexAccessAndEgressPathTestCase implements RaptorTestConstants { // FLEX Access 5m tx 1 ~ A. Note! The actual times might get time-shifted. public static final int ACCESS_DURATION = durationInSeconds("5m15s"); - public static final int ACCESS_COST = toRaptorCost(600); + public static final int ACCESS_C1 = toRaptorCost(600); // Using transfer reluctance is incorrect, we should use the cost from the access path public static final TestAccessEgress ACCESS = TestAccessEgress.flex( STOP_A, ACCESS_DURATION, ONE_RIDE, - ACCESS_COST + ACCESS_C1 ); // Alternative Flex access with restricted opening hours: 09:00 - 09:50 public static final int ACCESS_OPEN = time("09:00"); @@ -87,7 +92,7 @@ public class FlexAccessAndEgressPathTestCase implements RaptorTestConstants { public static final int TX1_DURATION = TX1_END - TX1_START; public static final RaptorTransfer TX1_TRANSFER = TestTransfer.transfer(STOP_B, TX1_DURATION); public static final RaptorTransfer TX1_TRANSFER_REV = TestTransfer.transfer(STOP_A, TX1_DURATION); - public static final int TX1_COST = TX1_TRANSFER.generalizedCost(); + public static final int TX1_C1 = TX1_TRANSFER.c1(); // Trip A (B ~ BUS L11 10:08 10:20 ~ C) public static final int L1_START = time("10:08"); @@ -105,18 +110,18 @@ public class FlexAccessAndEgressPathTestCase implements RaptorTestConstants { public static final int TX2_DURATION = TX2_END - TX2_START; public static final RaptorTransfer TX2_TRANSFER = TestTransfer.transfer(STOP_D, TX2_DURATION); public static final RaptorTransfer TX2_TRANSFER_REV = TestTransfer.transfer(STOP_C, TX2_DURATION); - public static final int TX2_COST = TX2_TRANSFER.generalizedCost(); + public static final int TX2_C1 = TX2_TRANSFER.c1(); // Wait 15s (ALIGHT_SLACK) // D ~ FLEX Egress 6m tx 1 . Note! The actual times might get time-shifted. public static final int EGRESS_DURATION = durationInSeconds("6m"); - public static final int EGRESS_COST = toRaptorCost(800); + public static final int EGRESS_C1 = toRaptorCost(800); // Using transfer reluctance is incorrect, we should use the cost from the egress path public static final TestAccessEgress EGRESS = TestAccessEgress.flex( STOP_D, EGRESS_DURATION, ONE_RIDE, - EGRESS_COST + EGRESS_C1 ); public static final int EGRESS_OPENING = TimeUtils.time("10:30"); public static final int EGRESS_CLOSING = TimeUtils.time("11:00"); @@ -125,12 +130,12 @@ public class FlexAccessAndEgressPathTestCase implements RaptorTestConstants { EGRESS_CLOSING ); - public static final int EGRESS_COST_W_1M_SLACK = - EGRESS_COST + toRaptorCost(TRANSFER_COST_SEC) + COST_CALCULATOR.waitCost(TRANSFER_SLACK); - public static final int EGRESS_COST_W_7M45S_SLACK = - EGRESS_COST_W_1M_SLACK + COST_CALCULATOR.waitCost(durationInSeconds("6m45s")); - public static final int EGRESS_COST_W_9M45S_SLACK = - EGRESS_COST_W_1M_SLACK + COST_CALCULATOR.waitCost(durationInSeconds("8m45s")); + public static final int EGRESS_C1_W_1M_SLACK = + EGRESS_C1 + toRaptorCost(TRANSFER_C1_SEC) + C1_CALCULATOR.waitCost(TRANSFER_SLACK); + public static final int EGRESS_C1_W_7M45S_SLACK = + EGRESS_C1_W_1M_SLACK + C1_CALCULATOR.waitCost(durationInSeconds("6m45s")); + public static final int EGRESS_C1_W_9M45S_SLACK = + EGRESS_C1_W_1M_SLACK + C1_CALCULATOR.waitCost(durationInSeconds("8m45s")); public static final String LINE_A = "A"; public static final String LINE_B = "B"; @@ -147,23 +152,23 @@ public class FlexAccessAndEgressPathTestCase implements RaptorTestConstants { .transitReluctanceIndex(TRANSIT_RELUCTANCE_INDEX) .build(); - public static final int L1_COST_EX_WAIT = COST_CALCULATOR.transitArrivalCost( - COST_CALCULATOR.boardingCostRegularTransfer(false, L1_START, STOP_B, L1_START), + public static final int L1_C1_EX_WAIT = C1_CALCULATOR.transitArrivalCost( + C1_CALCULATOR.boardingCostRegularTransfer(false, L1_START, STOP_B, L1_START), ZERO, L1_TRANSIT_DURATION, TRIP_A, STOP_C ); - private static final int TOT_COST_A = toRaptorCost(2564); - private static final int TOT_COST_W_OPENING_HOURS_A = toRaptorCost(3512); - private static final int TOT_COST_B = toRaptorCost(2924); - private static final int TOT_COST_W_OPENING_HOURS_B = toRaptorCost(3728); + private static final int TOT_C1_A = toRaptorCost(2564); + private static final int TOT_C1_W_OPENING_HOURS_A = toRaptorCost(3512); + private static final int TOT_C1_B = toRaptorCost(2924); + private static final int TOT_C1_W_OPENING_HOURS_B = toRaptorCost(3728); // Wait before 12m45s + ALIGHT SLACK 15s - private static final int L1_COST_INC_WAIT_W_OPENING_HOURS_A = - L1_COST_EX_WAIT + COST_CALCULATOR.waitCost(durationInSeconds("13m")); - private static final int L1_COST_INC_WAIT_W_OPENING_HOURS_B = - L1_COST_EX_WAIT + COST_CALCULATOR.waitCost(durationInSeconds("12m")); + private static final int L1_C1_INC_WAIT_W_OPENING_HOURS_A = + L1_C1_EX_WAIT + C1_CALCULATOR.waitCost(durationInSeconds("13m")); + private static final int L1_C1_INC_WAIT_W_OPENING_HOURS_B = + L1_C1_EX_WAIT + C1_CALCULATOR.waitCost(durationInSeconds("12m")); /* TEST CASES WITH EXPECTED TO-STRING TEXTS */ @@ -174,12 +179,12 @@ public static DestinationArrival flexCaseAForwardSearch() { public static String flexCaseAText() { return String.format( "Flex 5m15s 1x 10:01 10:06:15 %s ~ A 1m45s ~ " + - "BUS A 10:08 10:20 12m $996 ~ D 1m15s ~ " + + "BUS A 10:08 10:20 12m C₁996 ~ D 1m15s ~ " + "Flex 6m 1x 10:21:15 10:27:15 %s " + - "[10:01 10:27:15 26m15s 2tx %s]", - RaptorCostConverter.toString(ACCESS_COST), - RaptorCostConverter.toString(EGRESS_COST_W_1M_SLACK), - RaptorCostConverter.toString(TOT_COST_A) + "[10:01 10:27:15 26m15s Tₓ2 %s]", + RaptorCostConverter.toString(ACCESS_C1), + RaptorCostConverter.toString(EGRESS_C1_W_1M_SLACK), + RaptorCostConverter.toString(TOT_C1_A) ); } @@ -190,14 +195,14 @@ public static DestinationArrival flexCaseBForwardSearch() { public static String flexCaseBText() { return String.format( "Flex 5m15s 1x 10:00 10:05:15 %s ~ A 0s ~ " + - "Walk 1m 10:05:15 10:06:15 $120 ~ B 1m45s ~ " + - "BUS B 10:08 10:20 12m $996 ~ C 15s ~ " + - "Walk 2m 10:20:15 10:22:15 $240 ~ D 1m ~ " + + "Walk 1m 10:05:15 10:06:15 C₁120 ~ B 1m45s ~ " + + "BUS B 10:08 10:20 12m C₁996 ~ C 15s ~ " + + "Walk 2m 10:20:15 10:22:15 C₁240 ~ D 1m ~ " + "Flex 6m 1x 10:23:15 10:29:15 %s" + - " [10:00 10:29:15 29m15s 2tx %s]", - RaptorCostConverter.toString(ACCESS_COST), - RaptorCostConverter.toString(EGRESS_COST_W_1M_SLACK), - RaptorCostConverter.toString(TOT_COST_B) + " [10:00 10:29:15 29m15s Tₓ2 %s]", + RaptorCostConverter.toString(ACCESS_C1), + RaptorCostConverter.toString(EGRESS_C1_W_1M_SLACK), + RaptorCostConverter.toString(TOT_C1_B) ); } @@ -210,11 +215,11 @@ public static String flexCaseAWithOpeningHoursText() { "Flex 5m15s 1x Open(9:00 9:50) 9:50 9:55:15 %s ~ A 12m45s ~ " + "BUS A 10:08 10:20 12m %s ~ D 10m ~ " + "Flex 6m 1x Open(10:30 11:00) 10:30 10:36 %s " + - "[9:50 10:36 46m 2tx %s]", - RaptorCostConverter.toString(ACCESS_COST), - RaptorCostConverter.toString(L1_COST_INC_WAIT_W_OPENING_HOURS_A), - RaptorCostConverter.toString(EGRESS_COST_W_9M45S_SLACK), - RaptorCostConverter.toString(TOT_COST_W_OPENING_HOURS_A) + "[9:50 10:36 46m Tₓ2 %s]", + formatC1(ACCESS_C1), + formatC1(L1_C1_INC_WAIT_W_OPENING_HOURS_A), + formatC1(EGRESS_C1_W_9M45S_SLACK), + formatC1(TOT_C1_W_OPENING_HOURS_A) ); } @@ -225,15 +230,15 @@ public static DestinationArrival flexCaseBWithOpeningHoursForw public static String flexCaseBWithOpeningHoursText() { return String.format( "Flex 5m15s 1x Open(9:00 9:50) 9:50 9:55:15 %s ~ A 0s ~ " + - "Walk 1m 9:55:15 9:56:15 $120 ~ B 11m45s ~ " + + "Walk 1m 9:55:15 9:56:15 C₁120 ~ B 11m45s ~ " + "BUS B 10:08 10:20 12m %s ~ C 15s ~ " + - "Walk 2m 10:20:15 10:22:15 $240 ~ D 7m45s ~ " + + "Walk 2m 10:20:15 10:22:15 C₁240 ~ D 7m45s ~ " + "Flex 6m 1x Open(10:30 11:00) 10:30 10:36 %s" + - " [9:50 10:36 46m 2tx %s]", - RaptorCostConverter.toString(ACCESS_COST), - RaptorCostConverter.toString(L1_COST_INC_WAIT_W_OPENING_HOURS_B), - RaptorCostConverter.toString(EGRESS_COST_W_7M45S_SLACK), - RaptorCostConverter.toString(TOT_COST_W_OPENING_HOURS_B) + " [9:50 10:36 46m Tₓ2 %s]", + formatC1(ACCESS_C1), + formatC1(L1_C1_INC_WAIT_W_OPENING_HOURS_B), + formatC1(EGRESS_C1_W_7M45S_SLACK), + formatC1(TOT_C1_W_OPENING_HOURS_B) ); } @@ -269,9 +274,9 @@ public void testSetup() { // Assert cost // The calculator is not under test here, so we assert everything is as expected - assertEquals(12000, TX1_COST); - assertEquals(90000, L1_COST_EX_WAIT); - assertEquals(24000, TX2_COST); + assertEquals(12000, TX1_C1); + assertEquals(90000, L1_C1_EX_WAIT); + assertEquals(24000, TX2_C1); } /* PRIVATE METHODS */ @@ -282,24 +287,24 @@ private static DestinationArrival flexForwardSearch( String line ) { int departureTime, arrivalTime, waitTime; - AbstractStopArrival prevArrival; + ArrivalView prevArrival; if (LINE_A.equals(line)) { // The latest time the access can arrive is the same as the TX1 arrival time in case B arrivalTime = accessPath.latestArrivalTime(TX1_END); - prevArrival = new Access(accessPath.stop(), arrivalTime, accessPath); + prevArrival = access(accessPath.stop(), arrivalTime, accessPath); int waitCost = costL1ForwardIncWait(prevArrival.arrivalTime()); - prevArrival = new Bus(2, STOP_D, L1_STOP_ARR_TIME, waitCost, TRIP_A, prevArrival); + prevArrival = bus(2, STOP_D, L1_STOP_ARR_TIME, waitCost, 0, TRIP_A, prevArrival); } else { arrivalTime = accessPath.latestArrivalTime(TX1_START); - prevArrival = new Access(accessPath.stop(), arrivalTime, accessPath); + prevArrival = access(accessPath.stop(), arrivalTime, accessPath); int timeShift = TX1_START - prevArrival.arrivalTime(); prevArrival = new Transfer(1, TX1_END - timeShift, TX1_TRANSFER, prevArrival); int waitCost = costL1ForwardIncWait(prevArrival.arrivalTime()); - prevArrival = new Bus(2, STOP_C, L1_STOP_ARR_TIME, waitCost, TRIP_B, prevArrival); + prevArrival = bus(2, STOP_C, L1_STOP_ARR_TIME, waitCost, 0, TRIP_B, prevArrival); prevArrival = new Transfer(2, TX2_END, TX2_TRANSFER, prevArrival); } @@ -311,9 +316,15 @@ private static DestinationArrival flexForwardSearch( arrivalTime = departureTime + egressPath.durationInSeconds(); waitTime = departureTime - prevArrival.arrivalTime(); int additionalCost = - egressPath.generalizedCost() + toRaptorCost(waitTime * WAIT_RELUCTANCE + TRANSFER_COST_SEC); - - return new DestinationArrival<>(egressPath, prevArrival, arrivalTime, additionalCost); + egressPath.c1() + toRaptorCost(waitTime * WAIT_RELUCTANCE + TRANSFER_C1_SEC); + + return new DestinationArrival<>( + egressPath, + prevArrival, + arrivalTime, + additionalCost, + RaptorConstants.NOT_SET + ); } private static DestinationArrival flexReverseSearch( @@ -322,23 +333,23 @@ private static DestinationArrival flexReverseSearch( String line ) { int departureTime, arrivalTime, cost; - AbstractStopArrival prevArrival; + ArrivalView prevArrival; if (LINE_A.equals(line)) { arrivalTime = L1_END + ALIGHT_SLACK + TRANSFER_SLACK; arrivalTime = egressPath.earliestDepartureTime(arrivalTime); - prevArrival = new Access(egressPath.stop(), arrivalTime, egressPath); + prevArrival = access(egressPath.stop(), arrivalTime, egressPath); cost = costL1ReverseIncWait(prevArrival.arrivalTime()); - prevArrival = new Bus(2, STOP_A, L1_STOP_ARR_TIME_REV, cost, TRIP_A, prevArrival); + prevArrival = bus(2, STOP_A, L1_STOP_ARR_TIME_REV, cost, 0, TRIP_A, prevArrival); } else { arrivalTime = L1_END + ALIGHT_SLACK + TX2_DURATION + TRANSFER_SLACK; arrivalTime = egressPath.earliestDepartureTime(arrivalTime); - prevArrival = new Access(egressPath.stop(), arrivalTime, egressPath); + prevArrival = access(egressPath.stop(), arrivalTime, egressPath); arrivalTime = prevArrival.arrivalTime() - TX2_DURATION; prevArrival = new Transfer(1, arrivalTime, TX2_TRANSFER_REV, prevArrival); cost = costL1ReverseIncWait(prevArrival.arrivalTime()); - prevArrival = new Bus(2, STOP_B, L1_STOP_ARR_TIME_REV, cost, TRIP_B, prevArrival); + prevArrival = bus(2, STOP_B, L1_STOP_ARR_TIME_REV, cost, 0, TRIP_B, prevArrival); arrivalTime = prevArrival.arrivalTime() - TX1_DURATION; prevArrival = new Transfer(2, arrivalTime, TX1_TRANSFER_REV, prevArrival); } @@ -350,18 +361,24 @@ private static DestinationArrival flexReverseSearch( arrivalTime = departureTime - accessPath.durationInSeconds(); int waitTime = prevArrival.arrivalTime() - departureTime; int additionalCost = - accessPath.generalizedCost() + toRaptorCost(waitTime * WAIT_RELUCTANCE + TRANSFER_COST_SEC); - - return new DestinationArrival<>(accessPath, prevArrival, arrivalTime, additionalCost); + accessPath.c1() + toRaptorCost(waitTime * WAIT_RELUCTANCE + TRANSFER_C1_SEC); + + return new DestinationArrival<>( + accessPath, + prevArrival, + arrivalTime, + additionalCost, + RaptorConstants.NOT_SET + ); } private static int costL1ForwardIncWait(int prevArrivalTime) { int waitTime = L1_START - prevArrivalTime + ALIGHT_SLACK; - return toRaptorCost(waitTime * WAIT_RELUCTANCE) + L1_COST_EX_WAIT; + return toRaptorCost(waitTime * WAIT_RELUCTANCE) + L1_C1_EX_WAIT; } private static int costL1ReverseIncWait(int prevArrivalTime) { int waitTime = (prevArrivalTime - L1_END) + BOARD_SLACK; - return toRaptorCost(waitTime * WAIT_RELUCTANCE) + L1_COST_EX_WAIT; + return toRaptorCost(waitTime * WAIT_RELUCTANCE) + L1_C1_EX_WAIT; } } diff --git a/src/test/java/org/opentripplanner/raptor/_data/stoparrival/TestArrivals.java b/src/test/java/org/opentripplanner/raptor/_data/stoparrival/TestArrivals.java new file mode 100644 index 00000000000..a4a042e89c2 --- /dev/null +++ b/src/test/java/org/opentripplanner/raptor/_data/stoparrival/TestArrivals.java @@ -0,0 +1,105 @@ +package org.opentripplanner.raptor._data.stoparrival; + +import org.opentripplanner.raptor._data.transit.TestAccessEgress; +import org.opentripplanner.raptor._data.transit.TestTransfer; +import org.opentripplanner.raptor._data.transit.TestTripSchedule; +import org.opentripplanner.raptor.api.model.RaptorAccessEgress; +import org.opentripplanner.raptor.api.model.RaptorConstants; +import org.opentripplanner.raptor.api.model.RaptorTransfer; +import org.opentripplanner.raptor.api.view.ArrivalView; + +public class TestArrivals { + + public static ArrivalView access( + int stop, + int arrivalTime, + RaptorAccessEgress path, + int c2 + ) { + return new Access(stop, arrivalTime, path, c2); + } + + public static ArrivalView access( + int stop, + int arrivalTime, + RaptorAccessEgress path + ) { + return access(stop, arrivalTime, path, RaptorConstants.NOT_SET); + } + + public static ArrivalView access( + int stop, + int departureTime, + int arrivalTime, + int c1, + int c2 + ) { + return access( + stop, + arrivalTime, + TestAccessEgress.walk(stop, Math.abs(arrivalTime - departureTime), c1), + c2 + ); + } + + public static ArrivalView access( + int stop, + int departureTime, + int arrivalTime, + int c1 + ) { + return access(stop, departureTime, arrivalTime, c1, RaptorConstants.NOT_SET); + } + + public static ArrivalView transfer( + int round, + int arrivalTime, + RaptorTransfer transfer, + ArrivalView previous + ) { + return new Transfer(round, arrivalTime, transfer, previous); + } + + public static ArrivalView transfer( + int round, + int stop, + int departureTime, + int arrivalTime, + int extraCost, + ArrivalView previous + ) { + return transfer( + round, + arrivalTime, + TestTransfer.transfer(stop, Math.abs(arrivalTime - departureTime), extraCost), + previous + ); + } + + public static ArrivalView bus( + int round, + int stop, + int arrivalTime, + int c1, + int c2, + TestTripSchedule trip, + ArrivalView previous + ) { + return new Transit(round, stop, arrivalTime, c1, c2, trip, previous); + } + + public static ArrivalView egress( + int departureTime, + int arrivalTime, + int c1, + int c2, + ArrivalView previous + ) { + return new Egress( + departureTime, + TestAccessEgress.walk(previous.stop(), Math.abs(arrivalTime - departureTime), c1), + c2, + previous + ); + } +} diff --git a/src/test/java/org/opentripplanner/raptor/_data/stoparrival/Transfer.java b/src/test/java/org/opentripplanner/raptor/_data/stoparrival/Transfer.java index 99921c6b3e6..e7df3b1f98c 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/stoparrival/Transfer.java +++ b/src/test/java/org/opentripplanner/raptor/_data/stoparrival/Transfer.java @@ -2,37 +2,22 @@ import static org.opentripplanner.raptor.api.model.PathLegType.TRANSFER; -import org.opentripplanner.raptor._data.transit.TestTransfer; import org.opentripplanner.raptor._data.transit.TestTripSchedule; import org.opentripplanner.raptor.api.model.PathLegType; import org.opentripplanner.raptor.api.model.RaptorTransfer; import org.opentripplanner.raptor.api.view.ArrivalView; -public class Transfer extends AbstractStopArrival { +class Transfer extends AbstractStopArrival { private final RaptorTransfer transfer; - public Transfer( - int round, - int stop, - int departureTime, - int arrivalTime, - int extraCost, - ArrivalView previous - ) { - super(round, stop, arrivalTime, extraCost, previous); - // In a reverse search we the arrival is before the departure - int durationInSeconds = Math.abs(arrivalTime - departureTime); - this.transfer = TestTransfer.transfer(stop, durationInSeconds, extraCost); - } - - public Transfer( + Transfer( int round, int arrivalTime, RaptorTransfer transfer, ArrivalView previous ) { - super(round, transfer.stop(), arrivalTime, transfer.generalizedCost(), previous); + super(round, transfer.stop(), arrivalTime, transfer.c1(), previous.c2(), previous); this.transfer = transfer; } diff --git a/src/test/java/org/opentripplanner/raptor/_data/stoparrival/Bus.java b/src/test/java/org/opentripplanner/raptor/_data/stoparrival/Transit.java similarity index 84% rename from src/test/java/org/opentripplanner/raptor/_data/stoparrival/Bus.java rename to src/test/java/org/opentripplanner/raptor/_data/stoparrival/Transit.java index 5362c8aaa35..e2f539bf455 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/stoparrival/Bus.java +++ b/src/test/java/org/opentripplanner/raptor/_data/stoparrival/Transit.java @@ -7,19 +7,20 @@ import org.opentripplanner.raptor.api.view.ArrivalView; import org.opentripplanner.raptor.api.view.TransitPathView; -public class Bus extends AbstractStopArrival implements TransitPathView { +class Transit extends AbstractStopArrival implements TransitPathView { private final TestTripSchedule trip; - public Bus( + Transit( int round, int stop, int arrivalTime, - int cost, + int c1, + int c2, TestTripSchedule trip, ArrivalView previous ) { - super(round, stop, arrivalTime, cost, previous); + super(round, stop, arrivalTime, c1, c2, previous); this.trip = trip; } diff --git a/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java b/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java index ecb077b486e..56957e2e33a 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java +++ b/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java @@ -176,7 +176,7 @@ public int stop() { } @Override - public int generalizedCost() { + public int c1() { return cost; } diff --git a/src/test/java/org/opentripplanner/raptor/_data/transit/TestTransfer.java b/src/test/java/org/opentripplanner/raptor/_data/transit/TestTransfer.java index faae8696fb4..5645f6342d6 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/transit/TestTransfer.java +++ b/src/test/java/org/opentripplanner/raptor/_data/transit/TestTransfer.java @@ -32,7 +32,7 @@ public int stop() { } @Override - public int generalizedCost() { + public int c1() { return cost; } diff --git a/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripPattern.java b/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripPattern.java index 1b68063259f..63dcbeb5863 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripPattern.java +++ b/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripPattern.java @@ -20,6 +20,8 @@ public class TestTripPattern implements DefaultTripPattern { private int patternIndex = 0; + private int priorityGroupId = 0; + /** *
    * 0 - 000 : No restriction
@@ -57,6 +59,11 @@ TestTripPattern withPatternIndex(int index) {
     return this;
   }
 
+  public TestTripPattern withPriorityGroup(int priorityGroupId) {
+    this.priorityGroupId = priorityGroupId;
+    return this;
+  }
+
   public TestTripPattern withRoute(Route route) {
     this.route = route;
     return this;
@@ -119,8 +126,7 @@ public int slackIndex() {
 
   @Override
   public int priorityGroupId() {
-    // TODO C2 - Add routing tests for priority groups later ...
-    throw new UnsupportedOperationException();
+    return priorityGroupId;
   }
 
   @Override
diff --git a/src/test/java/org/opentripplanner/raptor/api/path/PathTest.java b/src/test/java/org/opentripplanner/raptor/api/path/PathTest.java
index 3ad076cd04b..de71574a952 100644
--- a/src/test/java/org/opentripplanner/raptor/api/path/PathTest.java
+++ b/src/test/java/org/opentripplanner/raptor/api/path/PathTest.java
@@ -10,7 +10,7 @@
 import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.BASIC_PATH_AS_STRING;
 import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.EGRESS_END;
 import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.RAPTOR_ITERATION_START_TIME;
-import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.TOTAL_COST;
+import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.TOTAL_C1;
 import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.basicTripStops;
 import static org.opentripplanner.raptor._data.transit.TestTripPattern.pattern;
 
@@ -99,7 +99,7 @@ public void listStops() {
 
   @Test
   public void cost() {
-    assertEquals(TOTAL_COST, subject.c1());
+    assertEquals(TOTAL_C1, subject.c1());
   }
 
   @Test
@@ -153,7 +153,7 @@ public void testCountTransfersWithStaySeated() {
       egress,
       egressStart,
       egressEnd,
-      egress.generalizedCost()
+      egress.c1()
     );
 
     var trip3 = TestTripSchedule
@@ -195,10 +195,10 @@ public void testCountTransfersWithStaySeated() {
       access,
       accessStart,
       accessEnd,
-      access.generalizedCost(),
+      access.c1(),
       leg2.asTransitLeg()
     );
-    RaptorPath path = new Path<>(accessStart, leg1, TOTAL_COST, 0);
+    RaptorPath path = new Path<>(accessStart, leg1, TOTAL_C1, 0);
     assertEquals(0, path.numberOfTransfers());
   }
 
@@ -211,7 +211,7 @@ public void testCountTransfersWithTransfer() {
       egress,
       egressStart,
       egressEnd,
-      egress.generalizedCost()
+      egress.c1()
     );
 
     var trip3 = TestTripSchedule
@@ -252,10 +252,10 @@ public void testCountTransfersWithTransfer() {
       access,
       accessStart,
       accessEnd,
-      access.generalizedCost(),
+      access.c1(),
       leg2.asTransitLeg()
     );
-    RaptorPath path = new Path<>(accessStart, leg1, TOTAL_COST, 0);
+    RaptorPath path = new Path<>(accessStart, leg1, TOTAL_C1, 0);
     assertEquals(1, path.numberOfTransfers());
   }
 }
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/A01_SingleRouteTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/A01_SingleRouteTest.java
index dc6bdfb6616..a28687bdd2b 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/A01_SingleRouteTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/A01_SingleRouteTest.java
@@ -68,7 +68,7 @@ void setup() {
   }
 
   static List testCases() {
-    var path = "Walk 30s ~ B ~ BUS R1 0:01 0:05 ~ D ~ Walk 20s [0:00:30 0:05:20 4m50s 0tx $940]";
+    var path = "Walk 30s ~ B ~ BUS R1 0:01 0:05 ~ D ~ Walk 20s [0:00:30 0:05:20 4m50s Tₓ0 C₁940]";
     return RaptorModuleTestCase
       .of()
       .addMinDuration("4m50s", TX_0, T00_00, T00_10)
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/A04_BoardingTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/A04_BoardingTest.java
index fabe4db61ec..ff9b81030aa 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/A04_BoardingTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/A04_BoardingTest.java
@@ -94,7 +94,7 @@ static List testCases() {
         "~ BUS L1_1 0:10 0:18 ~ B " +
         "~ BUS L2 0:20 0:31 ~ F " +
         "~ BUS L3_2 0:35 0:40 ~ H " +
-        "~ Walk 1m [0:09 0:41 32m 2tx]"
+        "~ Walk 1m [0:09 0:41 32m Tₓ2]"
       )
       // A reverse test on the standard profile is included to demonstrate
       // that the min-travel-duration and the standard give different results
@@ -107,7 +107,7 @@ static List testCases() {
         "~ BUS L1_2 0:14 0:18 ~ C " +
         "~ BUS L2 0:21 0:32 ~ G " +
         "~ BUS L3_3 0:35 0:44 ~ H " +
-        "~ Walk 1m [0:13 0:45 32m 2tx]"
+        "~ Walk 1m [0:13 0:45 32m Tₓ2]"
       )
       .add(
         multiCriteria(),
@@ -116,7 +116,7 @@ static List testCases() {
         "~ BUS L1_2 0:14 0:18 ~ C " +
         "~ BUS L2 0:21 0:31 ~ F " +
         "~ BUS L3_2 0:35 0:40 ~ H " +
-        "~ Walk 1m [0:13 0:41 28m 2tx $3600]"
+        "~ Walk 1m [0:13 0:41 28m Tₓ2 C₁3_600]"
       )
       .build();
   }
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/B01_AccessTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/B01_AccessTest.java
index 026141e82dd..37814d6c6dc 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/B01_AccessTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/B01_AccessTest.java
@@ -64,9 +64,9 @@ void setup() {
 
   static List testCases() {
     var expStd =
-      "Walk 7m 0:11 0:18 ~ D 0s ~ BUS R1 0:18 0:25 7m ~ F 0s ~ Walk 1s 0:25 0:25:01 [0:11 0:25:01 14m1s 0tx]";
+      "Walk 7m 0:11 0:18 ~ D 0s ~ BUS R1 0:18 0:25 7m ~ F 0s ~ Walk 1s 0:25 0:25:01 [0:11 0:25:01 14m1s Tₓ0]";
     var expStdOne =
-      "Walk 1s 0:09:59 0:10 ~ B 0s ~ BUS R1 0:10 0:25 15m ~ F 0s ~ Walk 1s 0:25 0:25:01 [0:09:59 0:25:01 15m2s 0tx]";
+      "Walk 1s 0:09:59 0:10 ~ B 0s ~ BUS R1 0:10 0:25 15m ~ F 0s ~ Walk 1s 0:25 0:25:01 [0:09:59 0:25:01 15m2s Tₓ0]";
 
     return RaptorModuleTestCase
       .of()
@@ -77,9 +77,9 @@ static List testCases() {
       .add(TC_STANDARD_ONE, expStdOne)
       .add(
         multiCriteria(),
-        "Walk 7m 0:11 0:18 $840 ~ D 0s ~ BUS R1 0:18 0:25 7m $1020 ~ F 0s ~ Walk 1s 0:25 0:25:01 $2 [0:11 0:25:01 14m1s 0tx $1862]",
-        "Walk 4m 0:10 0:14 $480 ~ C 0s ~ BUS R1 0:14 0:25 11m $1260 ~ F 0s ~ Walk 1s 0:25 0:25:01 $2 [0:10 0:25:01 15m1s 0tx $1742]",
-        "Walk 1s 0:09:59 0:10 $2 ~ B 0s ~ BUS R1 0:10 0:25 15m $1500 ~ F 0s ~ Walk 1s 0:25 0:25:01 $2 [0:09:59 0:25:01 15m2s 0tx $1504]"
+        "Walk 7m 0:11 0:18 C₁840 ~ D 0s ~ BUS R1 0:18 0:25 7m C₁1_020 ~ F 0s ~ Walk 1s 0:25 0:25:01 C₁2 [0:11 0:25:01 14m1s Tₓ0 C₁1_862]",
+        "Walk 4m 0:10 0:14 C₁480 ~ C 0s ~ BUS R1 0:14 0:25 11m C₁1_260 ~ F 0s ~ Walk 1s 0:25 0:25:01 C₁2 [0:10 0:25:01 15m1s Tₓ0 C₁1_742]",
+        "Walk 1s 0:09:59 0:10 C₁2 ~ B 0s ~ BUS R1 0:10 0:25 15m C₁1_500 ~ F 0s ~ Walk 1s 0:25 0:25:01 C₁2 [0:09:59 0:25:01 15m2s Tₓ0 C₁1_504]"
       )
       .build();
   }
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/B02_EgressTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/B02_EgressTest.java
index d156c24c128..b32784c0c99 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/B02_EgressTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/B02_EgressTest.java
@@ -58,9 +58,9 @@ void setup() {
   }
 
   static List testCases() {
-    String expStd = "Walk 20s ~ B ~ BUS R1 0:10 0:20 ~ E ~ Walk 7m [0:09:40 0:27 17m20s 0tx]";
+    String expStd = "Walk 20s ~ B ~ BUS R1 0:10 0:20 ~ E ~ Walk 7m [0:09:40 0:27 17m20s Tₓ0]";
     String expStdRevOne =
-      "Walk 20s ~ B ~ BUS R1 0:10 0:28 ~ G ~ Walk 1s [0:09:40 0:28:01 18m21s 0tx]";
+      "Walk 20s ~ B ~ BUS R1 0:10 0:28 ~ G ~ Walk 1s [0:09:40 0:28:01 18m21s Tₓ0]";
     return RaptorModuleTestCase
       .of()
       .addMinDuration("17m20s", TX_0, T00_00, T00_30)
@@ -70,9 +70,9 @@ static List testCases() {
       .add(TC_STANDARD_REV_ONE, expStdRevOne)
       .add(
         multiCriteria(),
-        "Walk 20s ~ B ~ BUS R1 0:10 0:20 ~ E ~ Walk 7m [0:09:40 0:27 17m20s 0tx $2080]",
-        "Walk 20s ~ B ~ BUS R1 0:10 0:24 ~ F ~ Walk 4m [0:09:40 0:28 18m20s 0tx $1960]",
-        "Walk 20s ~ B ~ BUS R1 0:10 0:28 ~ G ~ Walk 1s [0:09:40 0:28:01 18m21s 0tx $1722]"
+        "Walk 20s ~ B ~ BUS R1 0:10 0:20 ~ E ~ Walk 7m [0:09:40 0:27 17m20s Tₓ0 C₁2_080]",
+        "Walk 20s ~ B ~ BUS R1 0:10 0:24 ~ F ~ Walk 4m [0:09:40 0:28 18m20s Tₓ0 C₁1_960]",
+        "Walk 20s ~ B ~ BUS R1 0:10 0:28 ~ G ~ Walk 1s [0:09:40 0:28:01 18m21s Tₓ0 C₁1_722]"
       )
       .build();
   }
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/B03_AccessEgressTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/B03_AccessEgressTest.java
index 16c3212c549..8cff8320f37 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/B03_AccessEgressTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/B03_AccessEgressTest.java
@@ -67,9 +67,9 @@ public void setup() {
 
   /** Only the multi-criteria test-cases differ for timetableView on/off */
   private static RaptorModuleTestCaseFactory standardTestCases() {
-    String expStd = "Walk 7m ~ C ~ BUS R1 0:18 0:32 ~ F ~ Walk 7m [0:11 0:39 28m 0tx]";
-    String expStdOne = "Walk 1s ~ A ~ BUS R1 0:10 0:32 ~ F ~ Walk 7m [0:09:59 0:39 29m1s 0tx]";
-    String expStdRevOne = "Walk 7m ~ C ~ BUS R1 0:18 0:40 ~ H ~ Walk 1s [0:11 0:40:01 29m1s 0tx]";
+    String expStd = "Walk 7m ~ C ~ BUS R1 0:18 0:32 ~ F ~ Walk 7m [0:11 0:39 28m Tₓ0]";
+    String expStdOne = "Walk 1s ~ A ~ BUS R1 0:10 0:32 ~ F ~ Walk 7m [0:09:59 0:39 29m1s Tₓ0]";
+    String expStdRevOne = "Walk 7m ~ C ~ BUS R1 0:18 0:40 ~ H ~ Walk 1s [0:11 0:40:01 29m1s Tₓ0]";
     return RaptorModuleTestCase
       .of()
       .withRequest(r -> r.searchParams().timetable(true))
@@ -83,15 +83,15 @@ static List testCases() {
     return standardTestCases()
       .add(
         multiCriteria(),
-        "Walk 7m ~ C ~ BUS R1 0:18 0:32 ~ F ~ Walk 7m [0:11 0:39 28m 0tx $3120]",
-        "Walk 4m ~ B ~ BUS R1 0:14 0:32 ~ F ~ Walk 7m [0:10 0:39 29m 0tx $3000]",
-        "Walk 1s ~ A ~ BUS R1 0:10 0:32 ~ F ~ Walk 7m [0:09:59 0:39 29m1s 0tx $2762]",
-        "Walk 7m ~ C ~ BUS R1 0:18 0:36 ~ G ~ Walk 4m [0:11 0:40 29m 0tx $3000]",
-        "Walk 4m ~ B ~ BUS R1 0:14 0:36 ~ G ~ Walk 4m [0:10 0:40 30m 0tx $2880]",
-        "Walk 1s ~ A ~ BUS R1 0:10 0:36 ~ G ~ Walk 4m [0:09:59 0:40 30m1s 0tx $2642]",
-        "Walk 7m ~ C ~ BUS R1 0:18 0:40 ~ H ~ Walk 1s [0:11 0:40:01 29m1s 0tx $2762]",
-        "Walk 4m ~ B ~ BUS R1 0:14 0:40 ~ H ~ Walk 1s [0:10 0:40:01 30m1s 0tx $2642]",
-        "Walk 1s ~ A ~ BUS R1 0:10 0:40 ~ H ~ Walk 1s [0:09:59 0:40:01 30m2s 0tx $2404]"
+        "Walk 7m ~ C ~ BUS R1 0:18 0:32 ~ F ~ Walk 7m [0:11 0:39 28m Tₓ0 C₁3_120]",
+        "Walk 4m ~ B ~ BUS R1 0:14 0:32 ~ F ~ Walk 7m [0:10 0:39 29m Tₓ0 C₁3_000]",
+        "Walk 1s ~ A ~ BUS R1 0:10 0:32 ~ F ~ Walk 7m [0:09:59 0:39 29m1s Tₓ0 C₁2_762]",
+        "Walk 7m ~ C ~ BUS R1 0:18 0:36 ~ G ~ Walk 4m [0:11 0:40 29m Tₓ0 C₁3_000]",
+        "Walk 4m ~ B ~ BUS R1 0:14 0:36 ~ G ~ Walk 4m [0:10 0:40 30m Tₓ0 C₁2_880]",
+        "Walk 1s ~ A ~ BUS R1 0:10 0:36 ~ G ~ Walk 4m [0:09:59 0:40 30m1s Tₓ0 C₁2_642]",
+        "Walk 7m ~ C ~ BUS R1 0:18 0:40 ~ H ~ Walk 1s [0:11 0:40:01 29m1s Tₓ0 C₁2_762]",
+        "Walk 4m ~ B ~ BUS R1 0:14 0:40 ~ H ~ Walk 1s [0:10 0:40:01 30m1s Tₓ0 C₁2_642]",
+        "Walk 1s ~ A ~ BUS R1 0:10 0:40 ~ H ~ Walk 1s [0:09:59 0:40:01 30m2s Tₓ0 C₁2_404]"
       )
       .build();
   }
@@ -107,11 +107,11 @@ static List testCasesWithoutTimetable() {
       .withRequest(r -> r.searchParams().timetable(false))
       .add(
         multiCriteria(),
-        "Walk 7m ~ C ~ BUS R1 0:18 0:32 ~ F ~ Walk 7m [0:11 0:39 28m 0tx $3120]",
-        "Walk 4m ~ B ~ BUS R1 0:14 0:32 ~ F ~ Walk 7m [0:10 0:39 29m 0tx $3000]",
-        "Walk 1s ~ A ~ BUS R1 0:10 0:32 ~ F ~ Walk 7m [0:09:59 0:39 29m1s 0tx $2762]",
-        "Walk 1s ~ A ~ BUS R1 0:10 0:36 ~ G ~ Walk 4m [0:09:59 0:40 30m1s 0tx $2642]",
-        "Walk 1s ~ A ~ BUS R1 0:10 0:40 ~ H ~ Walk 1s [0:09:59 0:40:01 30m2s 0tx $2404]"
+        "Walk 7m ~ C ~ BUS R1 0:18 0:32 ~ F ~ Walk 7m [0:11 0:39 28m Tₓ0 C₁3_120]",
+        "Walk 4m ~ B ~ BUS R1 0:14 0:32 ~ F ~ Walk 7m [0:10 0:39 29m Tₓ0 C₁3_000]",
+        "Walk 1s ~ A ~ BUS R1 0:10 0:32 ~ F ~ Walk 7m [0:09:59 0:39 29m1s Tₓ0 C₁2_762]",
+        "Walk 1s ~ A ~ BUS R1 0:10 0:36 ~ G ~ Walk 4m [0:09:59 0:40 30m1s Tₓ0 C₁2_642]",
+        "Walk 1s ~ A ~ BUS R1 0:10 0:40 ~ H ~ Walk 1s [0:09:59 0:40:01 30m2s Tₓ0 C₁2_404]"
       )
       .build();
   }
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/B04_AccessEgressBoardingTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/B04_AccessEgressBoardingTest.java
index acb3c647a60..6ac5ff4b028 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/B04_AccessEgressBoardingTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/B04_AccessEgressBoardingTest.java
@@ -38,14 +38,14 @@ public class B04_AccessEgressBoardingTest implements RaptorTestConstants {
 
   /** Board R1 at first possible stop A (not B) and arrive at stop E (the earliest arrival time) */
   private static final String EXP_PATH_BEST_ARRIVAL_TIME =
-    "Walk 1s ~ A ~ BUS R1 0:10 0:34 ~ E ~ Walk 10s [0:09:59 0:34:10 24m11s 0tx]";
+    "Walk 1s ~ A ~ BUS R1 0:10 0:34 ~ E ~ Walk 10s [0:09:59 0:34:10 24m11s Tₓ0]";
 
   /**
    * Searching in REVERSE we will "board" R1 at the first possible stop F and "alight" at the
    * optimal stop B (the best "arrival-time").
    */
   private static final String EXP_PATH_BEST_ARRIVAL_TIME_REVERSE =
-    "Walk 10s ~ B ~ BUS R1 0:14 0:38 ~ F ~ Walk 1s [0:13:50 0:38:01 24m11s 0tx]";
+    "Walk 10s ~ B ~ BUS R1 0:14 0:38 ~ F ~ Walk 1s [0:13:50 0:38:01 24m11s Tₓ0]";
 
   private final TestTransitData data = new TestTransitData();
   private final RaptorRequestBuilder requestBuilder = new RaptorRequestBuilder<>();
@@ -80,7 +80,7 @@ void setup() {
 
   static List testCases() {
     var expected =
-      "Walk 10s ~ B ~ BUS R1 0:14 0:34 ~ E ~ Walk 10s [0:13:50 0:34:10 20m20s 0tx $1840]";
+      "Walk 10s ~ B ~ BUS R1 0:14 0:34 ~ E ~ Walk 10s [0:13:50 0:34:10 20m20s Tₓ0 C₁1_840]";
 
     return RaptorModuleTestCase
       .of()
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/C01_TransferBoardAndAlightSlackTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/C01_TransferBoardAndAlightSlackTest.java
index 3fea0bcaee6..18d4d7d5024 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/C01_TransferBoardAndAlightSlackTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/C01_TransferBoardAndAlightSlackTest.java
@@ -78,7 +78,7 @@ static List testCases() {
       "~ BUS R1 0:02:11 0:03:01 ~ C " +
       "~ BUS R2 0:04:41 0:05:01 ~ D " +
       "~ Walk 20s " +
-      "[0:01:11 0:05:31 4m20s 1tx $1510]";
+      "[0:01:11 0:05:31 4m20s Tₓ1 C₁1_510]";
 
     return RaptorModuleTestCase
       .of()
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/C02_OnStreetTransfersTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/C02_OnStreetTransfersTest.java
index dbf580a4e67..a482a75c1e9 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/C02_OnStreetTransfersTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/C02_OnStreetTransfersTest.java
@@ -79,7 +79,7 @@ static List testCases() {
       "Walk 30s ~ D ~ " +
       "BUS R2 0:04 0:05 ~ E ~ " +
       "Walk 20s " +
-      "[0:01:30 0:05:20 3m50s 1tx $1510]";
+      "[0:01:30 0:05:20 3m50s Tₓ1 C₁1_510]";
     return RaptorModuleTestCase
       .of()
       .addMinDuration("3m50s", TX_1, T00_00, T00_30)
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/C03_OnBoardArrivalDominateTransfersTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/C03_OnBoardArrivalDominateTransfersTest.java
index 7459c094e28..2bfc2979f6e 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/C03_OnBoardArrivalDominateTransfersTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/C03_OnBoardArrivalDominateTransfersTest.java
@@ -68,7 +68,7 @@ static List testCases() {
       "BUS R1 0:05 0:08 ~ B ~ " +
       "BUS R2 0:12 0:15 ~ C ~ " +
       "Walk 1m " +
-      "[0:04 0:16 12m 1tx $2040]";
+      "[0:04 0:16 12m Tₓ1 C₁2_040]";
 
     return RaptorModuleTestCase
       .of()
@@ -81,8 +81,8 @@ static List testCases() {
       // stops (at least egress stops) or we can compute paths during the search. The last
       // solution is probably the one which give the best performance, but the first will make
       // mc-raptor perform better (tighter heuristics).
-      .add(TC_MIN_DURATION, "[0:00 0:09 9m 0tx]")
-      .add(TC_MIN_DURATION_REV, "[0:21 0:30 9m 1tx]")
+      .add(TC_MIN_DURATION, "[0:00 0:09 9m Tₓ0]")
+      .add(TC_MIN_DURATION_REV, "[0:21 0:30 9m Tₓ1]")
       .add(standard(), PathUtils.withoutCost(path))
       .add(multiCriteria(), path)
       .build();
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/D01_SingeRouteBoardAlightRestrictionsTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/D01_SingeRouteBoardAlightRestrictionsTest.java
index af84c28e2cf..a63c208cae3 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/D01_SingeRouteBoardAlightRestrictionsTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/D01_SingeRouteBoardAlightRestrictionsTest.java
@@ -72,7 +72,7 @@ void setup() {
   }
 
   static List testCases() {
-    var path = "Walk 30s ~ B ~ BUS R1 0:01 0:05 ~ D ~ Walk 20s [0:00:30 0:05:20 4m50s 0tx $940]";
+    var path = "Walk 30s ~ B ~ BUS R1 0:01 0:05 ~ D ~ Walk 20s [0:00:30 0:05:20 4m50s Tₓ0 C₁940]";
     return RaptorModuleTestCase
       .of()
       .addMinDuration("4m50s", TX_0, T00_00, T00_10)
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/D02_TransitModeReluctanceTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/D02_TransitModeReluctanceTest.java
index ffa1dd53bd2..970ae42a3ef 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/D02_TransitModeReluctanceTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/D02_TransitModeReluctanceTest.java
@@ -73,12 +73,12 @@ static Stream testCases() {
           Arguments.of(
             PREFER_R1,
             config,
-            "Walk 30s ~ A ~ BUS R1 0:01 0:02:40 ~ B ~ Walk 20s " + "[0:00:30 0:03 2m30s 0tx $799]"
+            "Walk 30s ~ A ~ BUS R1 0:01 0:02:40 ~ B ~ Walk 20s " + "[0:00:30 0:03 2m30s Tₓ0 C₁799]"
           ),
           Arguments.of(
             PREFER_R2,
             config,
-            "Walk 30s ~ A ~ BUS R2 0:01 0:02:40 ~ B ~ Walk 20s " + "[0:00:30 0:03 2m30s 0tx $789]"
+            "Walk 30s ~ A ~ BUS R2 0:01 0:02:40 ~ B ~ Walk 20s " + "[0:00:30 0:03 2m30s Tₓ0 C₁789]"
           )
         )
       );
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/D03_UnpreferredRouteTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/D03_UnpreferredRouteTest.java
index c8aa5eaf7d8..41f2fb2ca12 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/D03_UnpreferredRouteTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/D03_UnpreferredRouteTest.java
@@ -31,10 +31,10 @@
 public class D03_UnpreferredRouteTest implements RaptorTestConstants {
 
   private static final String EXPECTED =
-    "Walk 30s ~ A ~ BUS %s 0:01 0:02:40 ~ B ~ Walk 20s " + "[0:00:30 0:03 2m30s 0tx $%d]";
+    "Walk 30s ~ A ~ BUS %s 0:01 0:02:40 ~ B ~ Walk 20s " + "[0:00:30 0:03 2m30s Tₓ0 C₁%d]";
   private static final FeedScopedId ROUTE_ID_1 = TransitModelForTest.id("1");
   private static final FeedScopedId ROUTE_ID_2 = TransitModelForTest.id("2");
-  private static final CostLinearFunction UNPREFERRED_COST = CostLinearFunction.of("5m + 1t");
+  private static final CostLinearFunction UNPREFERRED_C1 = CostLinearFunction.of("5m + 1t");
   private final TestTransitData data = new TestTransitData();
   private final RaptorRequestBuilder requestBuilder = new RaptorRequestBuilder<>();
   private final RaptorService raptorService = new RaptorService<>(
@@ -95,7 +95,7 @@ private void unpreferRoute(FeedScopedId routeId) {
       }
     }
     data.mcCostParamsBuilder().unpreferredPatterns(patterns);
-    data.mcCostParamsBuilder().unpreferredCost(UNPREFERRED_COST);
+    data.mcCostParamsBuilder().unpreferredCost(UNPREFERRED_C1);
   }
 
   private static String expected(String route, int cost) {
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/E01_StaySeatedTransferTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/E01_StaySeatedTransferTest.java
index cb667ca211e..d1d1f2191f5 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/E01_StaySeatedTransferTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/E01_StaySeatedTransferTest.java
@@ -87,7 +87,7 @@ static List testCases() {
     // Note! The number of transfers is zero with stay-seated/interlining
     var path =
       "Walk 30s ~ A ~ BUS R1 0:02 0:05 ~ B ~ BUS R2 0:05 0:10 ~ C ~ Walk 30s " +
-      "[0:01:10 0:10:40 9m30s 0tx $1230]";
+      "[0:01:10 0:10:40 9m30s Tₓ0 C₁1_230]";
     return RaptorModuleTestCase
       .of()
       .addMinDuration("9m30s", TX_1, T00_00, T00_30)
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/E02_GuaranteedWalkTransferTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/E02_GuaranteedWalkTransferTest.java
index 128d70705bb..c448560c7fb 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/E02_GuaranteedWalkTransferTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/E02_GuaranteedWalkTransferTest.java
@@ -83,7 +83,7 @@ public void setup() {
   static List testCases() {
     var path =
       "Walk 30s ~ A ~ BUS R1 0:02 0:05 ~ B ~ Walk 30s ~ C ~ BUS R2 0:05 0:10 ~ D ~ Walk 30s " +
-      "[0:01:10 0:10:40 9m30s 1tx $1260]";
+      "[0:01:10 0:10:40 9m30s Tₓ1 C₁1_260]";
     return RaptorModuleTestCase
       .of()
       // BUG! 10 minutes is wrong, it should be 9m30s - Raptor may drop optimal paths,
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/E03_NotAllowedConstrainedTransferTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/E03_NotAllowedConstrainedTransferTest.java
index ea81abd0f8c..0866042548d 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/E03_NotAllowedConstrainedTransferTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/E03_NotAllowedConstrainedTransferTest.java
@@ -82,7 +82,7 @@ public void setup() {
   static List testCases() {
     var path =
       "Walk 30s ~ A ~ BUS R1 0:02 0:05 ~ B ~ BUS R3 0:15 0:20 ~ C ~ Walk 30s " +
-      "[0:01:30 0:20:30 19m 1tx $2500]";
+      "[0:01:30 0:20:30 19m Tₓ1 C₁2_500]";
     return RaptorModuleTestCase
       .of()
       .addMinDuration("9m", TX_1, T00_00, T00_30)
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/F01_AccessWithRidesTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/F01_AccessWithRidesTest.java
index c9b67eb1924..e93f464826c 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/F01_AccessWithRidesTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/F01_AccessWithRidesTest.java
@@ -37,9 +37,9 @@
 public class F01_AccessWithRidesTest implements RaptorTestConstants {
 
   private static final int TRANSFER_SLACK = 60;
-  private static final int COST_ONE_STOP = RaptorCostConverter.toRaptorCost(2 * 60);
-  private static final int COST_TRANSFER_SLACK = RaptorCostConverter.toRaptorCost(TRANSFER_SLACK);
-  private static final int COST_ONE_SEC = RaptorCostConverter.toRaptorCost(1);
+  private static final int C1_ONE_STOP = RaptorCostConverter.toRaptorCost(2 * 60);
+  private static final int C1_TRANSFER_SLACK = RaptorCostConverter.toRaptorCost(TRANSFER_SLACK);
+  private static final int C1_ONE_SEC = RaptorCostConverter.toRaptorCost(1);
 
   private final TestTransitData data = new TestTransitData();
   private final RaptorRequestBuilder requestBuilder = new RaptorRequestBuilder<>();
@@ -61,13 +61,13 @@ void setup() {
       // All access paths are all pareto-optimal (McRaptor).
       .addAccessPaths(
         // lowest num-of-transfers (0)
-        walk(STOP_B, D10m, COST_ONE_STOP + COST_TRANSFER_SLACK),
+        walk(STOP_B, D10m, C1_ONE_STOP + C1_TRANSFER_SLACK),
         // lowest cost
-        flexAndWalk(STOP_C, D2m, TWO_RIDES, 2 * COST_ONE_STOP - COST_ONE_SEC),
+        flexAndWalk(STOP_C, D2m, TWO_RIDES, 2 * C1_ONE_STOP - C1_ONE_SEC),
         // latest departure time
-        flex(STOP_D, D3m, TWO_RIDES, 3 * COST_ONE_STOP),
+        flex(STOP_D, D3m, TWO_RIDES, 3 * C1_ONE_STOP),
         // best on combination of transfers and time
-        flexAndWalk(STOP_E, D7m, ONE_RIDE, 4 * COST_ONE_STOP)
+        flexAndWalk(STOP_E, D7m, ONE_RIDE, 4 * C1_ONE_STOP)
       )
       .addEgressPaths(walk(STOP_F, D1m));
 
@@ -77,24 +77,24 @@ void setup() {
   }
 
   static List testCases() {
-    String expFlexAccess = "Flex 3m 2x ~ D ~ BUS R1 0:14 0:20 ~ F ~ Walk 1m [0:10 0:21 11m 2tx]";
-    String expWalkAccess = "Walk 10m ~ B ~ BUS R1 0:10 0:20 ~ F ~ Walk 1m [0:00 0:21 21m 0tx]";
+    String expFlexAccess = "Flex 3m 2x ~ D ~ BUS R1 0:14 0:20 ~ F ~ Walk 1m [0:10 0:21 11m Tₓ2]";
+    String expWalkAccess = "Walk 10m ~ B ~ BUS R1 0:10 0:20 ~ F ~ Walk 1m [0:00 0:21 21m Tₓ0]";
     return RaptorModuleTestCase
       .of()
       // TODO - Why do we get only one result here - when there is 3 different pareto-optimal
       //      - paths
-      .add(TC_MIN_DURATION, "[0:00 0:11 11m 0tx]")
+      .add(TC_MIN_DURATION, "[0:00 0:11 11m Tₓ0]")
       // Return pareto optimal paths with 0, 1 and 2 num-of-transfers
-      .add(TC_MIN_DURATION_REV, "[0:19 0:30 11m 2tx]", "[0:17 0:30 13m 1tx]", "[0:09 0:30 21m 0tx]")
+      .add(TC_MIN_DURATION_REV, "[0:19 0:30 11m Tₓ2]", "[0:17 0:30 13m Tₓ1]", "[0:09 0:30 21m Tₓ0]")
       .add(standard().not(TC_STANDARD_ONE), expFlexAccess)
       // First boarding wins with one-iteration (apply to min-duration and std-one)
       .add(TC_STANDARD_ONE, expWalkAccess)
       .add(
         multiCriteria(),
-        "Flex 3m 2x ~ D ~ BUS R1 0:14 0:20 ~ F ~ Walk 1m [0:10 0:21 11m 2tx $1500]", // ldt
-        "Flex+Walk 2m 2x ~ C ~ BUS R1 0:12 0:20 ~ F ~ Walk 1m [0:09 0:21 12m 2tx $1499]", // cost
-        "Flex+Walk 7m 1x ~ E ~ BUS R1 0:16 0:20 ~ F ~ Walk 1m [0:08 0:21 13m 1tx $1500]", // tx+time
-        "Walk 10m ~ B ~ BUS R1 0:10 0:20 ~ F ~ Walk 1m [0:00 0:21 21m 0tx $1500]" // tx
+        "Flex 3m 2x ~ D ~ BUS R1 0:14 0:20 ~ F ~ Walk 1m [0:10 0:21 11m Tₓ2 C₁1_500]", // ldt
+        "Flex+Walk 2m 2x ~ C ~ BUS R1 0:12 0:20 ~ F ~ Walk 1m [0:09 0:21 12m Tₓ2 C₁1_499]", // cost
+        "Flex+Walk 7m 1x ~ E ~ BUS R1 0:16 0:20 ~ F ~ Walk 1m [0:08 0:21 13m Tₓ1 C₁1_500]", // tx+time
+        "Walk 10m ~ B ~ BUS R1 0:10 0:20 ~ F ~ Walk 1m [0:00 0:21 21m Tₓ0 C₁1_500]" // tx
       )
       .build();
   }
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/F02_EgressWithRidesTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/F02_EgressWithRidesTest.java
index 5e858039897..87b2237fac8 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/F02_EgressWithRidesTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/F02_EgressWithRidesTest.java
@@ -71,15 +71,15 @@ void setup() {
 
   static List testCases() {
     var prefix = "Walk 1m ~ B ~ BUS R1 0:10 ";
-    var bestArrivalTime = prefix + "0:14 ~ D ~ Flex 3m 2x [0:09 0:18 9m 2tx $1380]";
-    var bestNTransfers = prefix + "0:18 ~ F ~ Walk 10m [0:09 0:28 19m 0tx $2400]";
-    var bestCost = prefix + "0:16 ~ E ~ Flex+Walk 1m59s 2x [0:09 0:18:59 9m59s 2tx $1378]";
-    var bestTxAndTime = prefix + "0:12 ~ C ~ Flex+Walk 7m 1x [0:09 0:20 11m 1tx $1740]";
+    var bestArrivalTime = prefix + "0:14 ~ D ~ Flex 3m 2x [0:09 0:18 9m Tₓ2 C₁1_380]";
+    var bestNTransfers = prefix + "0:18 ~ F ~ Walk 10m [0:09 0:28 19m Tₓ0 C₁2_400]";
+    var bestCost = prefix + "0:16 ~ E ~ Flex+Walk 1m59s 2x [0:09 0:18:59 9m59s Tₓ2 C₁1_378]";
+    var bestTxAndTime = prefix + "0:12 ~ C ~ Flex+Walk 7m 1x [0:09 0:20 11m Tₓ1 C₁1_740]";
 
     return RaptorModuleTestCase
       .of()
-      .add(TC_MIN_DURATION, "[0:00 0:09 9m 2tx]", "[0:00 0:11 11m 1tx]", "[0:00 0:19 19m 0tx]")
-      .add(TC_MIN_DURATION_REV, "[0:21 0:30 9m 0tx]")
+      .add(TC_MIN_DURATION, "[0:00 0:09 9m Tₓ2]", "[0:00 0:11 11m Tₓ1]", "[0:00 0:19 19m Tₓ0]")
+      .add(TC_MIN_DURATION_REV, "[0:21 0:30 9m Tₓ0]")
       .add(standard().not(TC_STANDARD_REV_ONE), withoutCost(bestArrivalTime))
       // "First" alighting wins for reverse
       .add(TC_STANDARD_REV_ONE, withoutCost(bestNTransfers))
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/F03_AccessEgressWithRidesBoardAndAlightSlackTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/F03_AccessEgressWithRidesBoardAndAlightSlackTest.java
index 26a769fc2af..0c6895e7e68 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/F03_AccessEgressWithRidesBoardAndAlightSlackTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/F03_AccessEgressWithRidesBoardAndAlightSlackTest.java
@@ -75,7 +75,7 @@ public void setup() {
   static List testCases() {
     var path =
       "Flex+Walk 2m 1x ~ B ~ BUS R1 0:04 0:06 ~ C ~ Flex 2m 1x " +
-      "[0:00:30 0:09:10 8m40s 2tx $1360]";
+      "[0:00:30 0:09:10 8m40s Tₓ2 C₁1_360]";
     return RaptorModuleTestCase
       .of()
       // TODO - Alight slack is missing
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/F04_AccessEgressWithRidesNoTransitTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/F04_AccessEgressWithRidesNoTransitTest.java
index e3bde4e53a7..0057b8a1d20 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/F04_AccessEgressWithRidesNoTransitTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/F04_AccessEgressWithRidesNoTransitTest.java
@@ -66,8 +66,8 @@ public void setup() {
   /* Flex ~ B ~ Walk ~ C ~ Flex */
 
   static List flexTransferFlexTestCases() {
-    var path = "Flex 2m 1x ~ B ~ Walk 5m ~ C ~ Flex 2m 1x [0:10 0:20 10m 1tx $1140]";
-    var stdPathRev = "Flex 2m 1x ~ B ~ Walk 5m ~ C ~ Flex 2m 1x [0:20 0:30 10m 1tx]";
+    var path = "Flex 2m 1x ~ B ~ Walk 5m ~ C ~ Flex 2m 1x [0:10 0:20 10m Tₓ1 C₁1_140]";
+    var stdPathRev = "Flex 2m 1x ~ B ~ Walk 5m ~ C ~ Flex 2m 1x [0:20 0:30 10m Tₓ1]";
     return RaptorModuleTestCase
       .of()
       .withRequest(requestBuilder ->
@@ -93,9 +93,9 @@ void flexTransferFlexTest(RaptorModuleTestCase testCase) {
 
   static List flexOpeningHoursTransferFlexTestCases() {
     var path =
-      "Flex 2m 1x Open(0:12 0:16) ~ B ~ Walk 5m ~ C ~ Flex 2m 1x [0:12 0:22 10m 1tx $1140]";
+      "Flex 2m 1x Open(0:12 0:16) ~ B ~ Walk 5m ~ C ~ Flex 2m 1x [0:12 0:22 10m Tₓ1 C₁1_140]";
     var stdPathRev =
-      "Flex 2m 1x Open(0:12 0:16) ~ B ~ Walk 5m ~ C ~ Flex 2m 1x [0:16 0:26 10m 1tx]";
+      "Flex 2m 1x Open(0:12 0:16) ~ B ~ Walk 5m ~ C ~ Flex 2m 1x [0:16 0:26 10m Tₓ1]";
     return RaptorModuleTestCase
       .of()
       .withRequest(requestBuilder ->
@@ -130,10 +130,10 @@ static List flexTransferFlexOpeningHoursTestCases() {
           .addEgressPaths(flex(STOP_C, D2m).openingHours("0:22", "0:26"))
       )
       .addMinDuration("10m", TX_1, T00_10, T00_30)
-      .add(TC_STANDARD, path + "[0:14 0:24 10m 1tx]")
-      .add(TC_STANDARD_ONE, path + "[0:10 0:24 14m 1tx]")
-      .add(standard().reverseOnly(), path + "[0:18 0:28 10m 1tx]")
-      .add(multiCriteria(), path + "[0:14 0:24 10m 1tx $1140]")
+      .add(TC_STANDARD, path + "[0:14 0:24 10m Tₓ1]")
+      .add(TC_STANDARD_ONE, path + "[0:10 0:24 14m Tₓ1]")
+      .add(standard().reverseOnly(), path + "[0:18 0:28 10m Tₓ1]")
+      .add(multiCriteria(), path + "[0:14 0:24 10m Tₓ1 C₁1_140]")
       .build();
   }
 
@@ -146,8 +146,8 @@ void flexTransferFlexOpeningHoursTest(RaptorModuleTestCase testCase) {
   /* Flex+Walk ~ C ~ Flex  (No transfer) */
 
   static List flexAndWalkToFlexTestCases() {
-    var path = "Flex+Walk 7m 1x ~ C ~ Flex 2m 1x [0:10 0:20 10m 1tx $1140]";
-    var stdPathRev = "Flex+Walk 7m 1x ~ C ~ Flex 2m 1x [0:20 0:30 10m 1tx]";
+    var path = "Flex+Walk 7m 1x ~ C ~ Flex 2m 1x [0:10 0:20 10m Tₓ1 C₁1_140]";
+    var stdPathRev = "Flex+Walk 7m 1x ~ C ~ Flex 2m 1x [0:20 0:30 10m Tₓ1]";
     return RaptorModuleTestCase
       .of()
       .withRequest(requestBuilder ->
@@ -172,8 +172,8 @@ void flexAndWalkToFlexTest(RaptorModuleTestCase testCase) {
   /* Flex ~ C ~ Walk+Flex  (No transfer) */
 
   static List flexToFlexAndWalkTestCases() {
-    var path = "Flex 2m 1x ~ C ~ Flex+Walk 7m 1x [0:10 0:20 10m 1tx $1140]";
-    var stdPathRev = "Flex 2m 1x ~ C ~ Flex+Walk 7m 1x [0:20 0:30 10m 1tx]";
+    var path = "Flex 2m 1x ~ C ~ Flex+Walk 7m 1x [0:10 0:20 10m Tₓ1 C₁1_140]";
+    var stdPathRev = "Flex 2m 1x ~ C ~ Flex+Walk 7m 1x [0:20 0:30 10m Tₓ1]";
     return RaptorModuleTestCase
       .of()
       .withRequest(requestBuilder ->
diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/F05_OnBoardAccessEgressAndTransfersTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/F05_OnBoardAccessEgressAndTransfersTest.java
index 59d7dcd7d4e..139185c33fc 100644
--- a/src/test/java/org/opentripplanner/raptor/moduletests/F05_OnBoardAccessEgressAndTransfersTest.java
+++ b/src/test/java/org/opentripplanner/raptor/moduletests/F05_OnBoardAccessEgressAndTransfersTest.java
@@ -25,7 +25,7 @@
  * FEATURE UNDER TEST
  * 

* Raptor should be able to route Access arriving on-board and egress departing on-board connecting - * to transit by transfers. Access and egress witch arrive/depart at/from the same stops by + * to transit by transfers. Access and egress which arrive/depart at/from the same stops by * walking should not be possible. */ public class F05_OnBoardAccessEgressAndTransfersTest implements RaptorTestConstants { @@ -57,7 +57,7 @@ public void setup() { static List testCases() { var path = - "Flex 5m 1x ~ A ~ Walk 10s ~ B ~ BUS R1 0:10 0:20 ~ C ~ Walk 10s ~ D ~ Flex 5m 1x [0:03:50 0:26:10 22m20s 2tx $2560]"; + "Flex 5m 1x ~ A ~ Walk 10s ~ B ~ BUS R1 0:10 0:20 ~ C ~ Walk 10s ~ D ~ Flex 5m 1x [0:03:50 0:26:10 22m20s Tₓ2 C₁2_560]"; return RaptorModuleTestCase .of() diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/F11_AccessWithRidesMultipleOptimalPathsTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/F11_AccessWithRidesMultipleOptimalPathsTest.java index 12c849b7349..fdc7c1dadd2 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/F11_AccessWithRidesMultipleOptimalPathsTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/F11_AccessWithRidesMultipleOptimalPathsTest.java @@ -92,11 +92,11 @@ static List testCase3mWalkAccess() { var endL2AndWalk = "~ BUS L2 0:15 0:20 ~ E ~ Walk 3m "; // Min-Duration 19m - this is - var flexTransferTransit = startFlexAccess + endWalkAndL3 + "[0:02 0:22 20m 1tx $2580]"; + var flexTransferTransit = startFlexAccess + endWalkAndL3 + "[0:02 0:22 20m Tₓ1 C₁2_580]"; // Best duration 18m, - var transitAndTransit = startWalkAndL1 + endL2AndWalk + "[0:02 0:23 21m 1tx]"; + var transitAndTransit = startWalkAndL1 + endL2AndWalk + "[0:02 0:23 21m Tₓ1]"; // Min-Duration 19m - var flexAndTransit = startFlexAccess + endL2AndWalk + "[0:03 0:23 20m 1tx $2640]"; + var flexAndTransit = startFlexAccess + endL2AndWalk + "[0:03 0:23 20m Tₓ1 C₁2_640]"; return RaptorModuleTestCase .of() @@ -125,11 +125,11 @@ static List testCase1mWalkAccess() { var endL2AndWalk = "~ BUS L2 0:15 0:20 ~ E ~ Walk 1m "; // Min-Duration 19m - this is - var flexTransferTransit = startFlexAccess + endWalkAndL3 + "[0:02 0:22 20m 1tx $2580]"; + var flexTransferTransit = startFlexAccess + endWalkAndL3 + "[0:02 0:22 20m Tₓ1 C₁2_580]"; // Best duration 16m, - var transitAndTransit = startWalkAndL1 + endL2AndWalk + "[0:02 0:21 19m 1tx]"; + var transitAndTransit = startWalkAndL1 + endL2AndWalk + "[0:02 0:21 19m Tₓ1]"; // Min-Duration 17m - var flexAndTransit = startFlexAccess + endL2AndWalk + "[0:03 0:21 18m 1tx $2400]"; + var flexAndTransit = startFlexAccess + endL2AndWalk + "[0:03 0:21 18m Tₓ1 C₁2_400]"; return RaptorModuleTestCase .of() diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/F12_EgressWithRidesMultipleOptimalPathsTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/F12_EgressWithRidesMultipleOptimalPathsTest.java index 12d20f51d01..1ec5399a668 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/F12_EgressWithRidesMultipleOptimalPathsTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/F12_EgressWithRidesMultipleOptimalPathsTest.java @@ -57,15 +57,15 @@ public class F12_EgressWithRidesMultipleOptimalPathsTest implements RaptorTestConstants { private static final String EXPECTED_PATH_FLEX_7M = - "A ~ BUS R2 0:05 0:16 ~ B ~ Walk 2m ~ C ~ Flex 7m 1x [0:05 0:26 21m 1tx $2160]"; + "A ~ BUS R2 0:05 0:16 ~ B ~ Walk 2m ~ C ~ Flex 7m 1x [0:05 0:26 21m Tₓ1 C₁2_160]"; private static final String EXPECTED_PATH_WALK_5M = - "A ~ BUS R1 0:04 0:20 ~ C ~ Walk 5m [0:04 0:25 21m 0tx $2160]"; + "A ~ BUS R1 0:04 0:20 ~ C ~ Walk 5m [0:04 0:25 21m Tₓ0 C₁2_160]"; private static final String EXPECTED_PATH_WALK_7M = - "A ~ BUS R1 0:04 0:20 ~ C ~ Walk 7m [0:04 0:27 23m 0tx $2400]"; + "A ~ BUS R1 0:04 0:20 ~ C ~ Walk 7m [0:04 0:27 23m Tₓ0 C₁2_400]"; - private static final int COST_10m = RaptorCostConverter.toRaptorCost(D10m); + private static final int C1_10m = RaptorCostConverter.toRaptorCost(D10m); private final RaptorService raptorService = new RaptorService<>( RaptorConfig.defaultConfigForTest() @@ -102,10 +102,10 @@ static List withFlexAsBestOptionTestCases() { .of() // with Flex egress as the best destination arrival-time .withRequest(r -> - r.searchParams().addEgressPaths(flex(STOP_C, D7m, 1, COST_10m), walk(STOP_C, D7m)) + r.searchParams().addEgressPaths(flex(STOP_C, D7m, 1, C1_10m), walk(STOP_C, D7m)) ) - .add(TC_MIN_DURATION, "[0:00 0:21 21m 1tx]", "[0:00 0:23 23m 0tx]") - .add(TC_MIN_DURATION_REV, "[0:09 0:30 21m 0tx]") + .add(TC_MIN_DURATION, "[0:00 0:21 21m Tₓ1]", "[0:00 0:23 23m Tₓ0]") + .add(TC_MIN_DURATION_REV, "[0:09 0:30 21m Tₓ0]") .add(TC_STANDARD, withoutCost(EXPECTED_PATH_FLEX_7M, EXPECTED_PATH_WALK_7M)) .add(TC_STANDARD_ONE, withoutCost(EXPECTED_PATH_FLEX_7M)) .add(TC_STANDARD_REV, withoutCost(EXPECTED_PATH_FLEX_7M)) @@ -125,7 +125,7 @@ static List withWalkingAsBestOptionTestCase() { .of() // with walk egress as the best destination arrival-time .withRequest(r -> - r.searchParams().addEgressPaths(flex(STOP_C, D7m, 1, COST_10m), walk(STOP_C, D5m)) + r.searchParams().addEgressPaths(flex(STOP_C, D7m, 1, C1_10m), walk(STOP_C, D5m)) ) .addMinDuration("21m", TX_0, T00_00, T00_30) .add(standard().forwardOnly(), withoutCost(EXPECTED_PATH_WALK_5M)) diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/G01_AccessWithOpeningHoursTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/G01_AccessWithOpeningHoursTest.java index a266552a3b1..7f5e5421e79 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/G01_AccessWithOpeningHoursTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/G01_AccessWithOpeningHoursTest.java @@ -91,12 +91,12 @@ public void setup() { private static List openInSearchIntervalCases(String access) { var expected = new ExpectedList( - access + EXP_00_15 + "[0:13 0:31 18m 0tx $1860]", - access + EXP_00_20 + "[0:18 0:36 18m 0tx $1860]", - access + EXP_00_25 + "[0:23 0:41 18m 0tx $1860]", - access + EXP_00_30 + "[0:28 0:46 18m 0tx $1860]", - access + EXP_24_15 + "[0:13+1d 0:31+1d 18m 0tx $1860]", - access + EXP_24_20 + "[0:18+1d 0:36+1d 18m 0tx $1860]" + access + EXP_00_15 + "[0:13 0:31 18m Tₓ0 C₁1_860]", + access + EXP_00_20 + "[0:18 0:36 18m Tₓ0 C₁1_860]", + access + EXP_00_25 + "[0:23 0:41 18m Tₓ0 C₁1_860]", + access + EXP_00_30 + "[0:28 0:46 18m Tₓ0 C₁1_860]", + access + EXP_24_15 + "[0:13+1d 0:31+1d 18m Tₓ0 C₁1_860]", + access + EXP_24_20 + "[0:18+1d 0:36+1d 18m Tₓ0 C₁1_860]" ); return tcBuilderWithMinDuration(T00_00, T24_40) @@ -138,8 +138,8 @@ public void openInSearchIntervalTest(RaptorModuleTestCase testCase) { private static List openInSearchIntervalStartSearchNextDayTestCase() { String access = "Walk 2m Open(0:00 1:00)"; var expected = new ExpectedList( - access + EXP_24_15 + "[0:13+1d 0:31+1d 18m 0tx $1860]", - access + EXP_24_20 + "[0:18+1d 0:36+1d 18m 0tx $1860]" + access + EXP_24_15 + "[0:13+1d 0:31+1d 18m Tₓ0 C₁1_860]", + access + EXP_24_20 + "[0:18+1d 0:36+1d 18m Tₓ0 C₁1_860]" ); return tcBuilderWithMinDuration(T24_10, T24_40) @@ -165,10 +165,10 @@ public void openInSearchIntervalStartSearchNextDayTest(RaptorModuleTestCase test private static List openInSecondHalfTodayTestCase() { String access = "Walk 2m Open(0:23 1:00)"; var expected = new ExpectedList( - access + EXP_00_25 + "[0:23 0:41 18m 0tx $1860]", - access + EXP_00_30 + "[0:28 0:46 18m 0tx $1860]", - access + EXP_24_15 + "[1:00 0:31+1d 23h31m 0tx $85440]", - access + EXP_24_20 + "[1:00 0:36+1d 23h36m 0tx $0000]" + access + EXP_00_25 + "[0:23 0:41 18m Tₓ0 C₁1_860]", + access + EXP_00_30 + "[0:28 0:46 18m Tₓ0 C₁1_860]", + access + EXP_24_15 + "[1:00 0:31+1d 23h31m Tₓ0 C₁85_440]", + access + EXP_24_20 + "[1:00 0:36+1d 23h36m Tₓ0 C₁0_000]" ); return tcBuilderWithMinDuration(T00_00, T24_40) @@ -195,12 +195,12 @@ public void openInSecondHalfTodayTest(RaptorModuleTestCase testCase) { private static List openInFirstHalfIntervalTestCase() { String access = "Walk 2m Open(0:00 0:20)"; var expected = new ExpectedList( - access + EXP_00_15 + "[0:13 0:31 18m 0tx $1860]", - access + EXP_00_20 + "[0:18 0:36 18m 0tx $1860]", - access + EXP_00_25 + "[0:20 0:41 21m 0tx $2040]", - access + EXP_00_30 + "[0:20 0:46 26m 0tx $0000]", - access + EXP_24_15 + "[0:13+1d 0:31+1d 18m 0tx $1860]", - access + EXP_24_20 + "[0:18+1d 0:36+1d 18m 0tx $1860]" + access + EXP_00_15 + "[0:13 0:31 18m Tₓ0 C₁1_860]", + access + EXP_00_20 + "[0:18 0:36 18m Tₓ0 C₁1_860]", + access + EXP_00_25 + "[0:20 0:41 21m Tₓ0 C₁2_040]", + access + EXP_00_30 + "[0:20 0:46 26m Tₓ0 C₁0_000]", + access + EXP_24_15 + "[0:13+1d 0:31+1d 18m Tₓ0 C₁1_860]", + access + EXP_24_20 + "[0:18+1d 0:36+1d 18m Tₓ0 C₁1_860]" ); return tcBuilderWithMinDuration(T00_00, T24_40) @@ -231,8 +231,8 @@ public void openInFirstHalfIntervalTest(RaptorModuleTestCase testCase) { private static List partiallyOpenIntervalTestNextDayTestCase() { String access = "Walk 2m Open(0:18 0:20)"; var expected = new ExpectedList( - access + EXP_24_20 + "[0:18+1d 0:36+1d 18m 0tx $1860]", - access + EXP_24_25 + "[0:20+1d 0:41+1d 21m 0tx $2040]" + access + EXP_24_20 + "[0:18+1d 0:36+1d 18m Tₓ0 C₁1_860]", + access + EXP_24_25 + "[0:20+1d 0:41+1d 21m Tₓ0 C₁2_040]" ); return tcBuilderWithMinDuration(T24_10, T25_00) diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/G02_EgressWithOpeningHoursTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/G02_EgressWithOpeningHoursTest.java index 6d0eaf4e162..9498f0b0080 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/G02_EgressWithOpeningHoursTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/G02_EgressWithOpeningHoursTest.java @@ -76,10 +76,10 @@ public void setup() { private static List openNoTimeRestrictionTestCase() { var expected = new ExpectedList( - "A ~ BUS R1 0:10 0:20 ~ B ~ Walk 2m [0:10 0:22 12m 0tx $1440]", - "A ~ BUS R1 0:20 0:30 ~ B ~ Walk 2m [0:20 0:32 12m 0tx $1440]", - "A ~ BUS R1 0:30 0:40 ~ B ~ Walk 2m [0:30 0:42 12m 0tx $1440]", - "A ~ BUS R1 0:20+1d 0:30+1d ~ B ~ Walk 2m [0:20+1d 0:32+1d 12m 0tx]" + "A ~ BUS R1 0:10 0:20 ~ B ~ Walk 2m [0:10 0:22 12m Tₓ0 C₁1_440]", + "A ~ BUS R1 0:20 0:30 ~ B ~ Walk 2m [0:20 0:32 12m Tₓ0 C₁1_440]", + "A ~ BUS R1 0:30 0:40 ~ B ~ Walk 2m [0:30 0:42 12m Tₓ0 C₁1_440]", + "A ~ BUS R1 0:20+1d 0:30+1d ~ B ~ Walk 2m [0:20+1d 0:32+1d 12m Tₓ0]" ); return RaptorModuleTestCase @@ -105,10 +105,10 @@ void openNoTimeRestrictionTest(RaptorModuleTestCase testCase) { private static List openOneHourTestCase() { var expected = new ExpectedList( - "A ~ BUS R1 0:10 0:20 ~ B ~ Walk 2m Open(0:00 1:00) [0:10 0:22 12m 0tx $1440]", - "A ~ BUS R1 0:20 0:30 ~ B ~ Walk 2m Open(0:00 1:00) [0:20 0:32 12m 0tx $1440]", - "A ~ BUS R1 0:30 0:40 ~ B ~ Walk 2m Open(0:00 1:00) [0:30 0:42 12m 0tx $1440]", - "A ~ BUS R1 0:20+1d 0:30+1d ~ B ~ Walk 2m Open(0:00 1:00) [0:20+1d 0:32+1d 12m 0tx $1440]" + "A ~ BUS R1 0:10 0:20 ~ B ~ Walk 2m Open(0:00 1:00) [0:10 0:22 12m Tₓ0 C₁1_440]", + "A ~ BUS R1 0:20 0:30 ~ B ~ Walk 2m Open(0:00 1:00) [0:20 0:32 12m Tₓ0 C₁1_440]", + "A ~ BUS R1 0:30 0:40 ~ B ~ Walk 2m Open(0:00 1:00) [0:30 0:42 12m Tₓ0 C₁1_440]", + "A ~ BUS R1 0:20+1d 0:30+1d ~ B ~ Walk 2m Open(0:00 1:00) [0:20+1d 0:32+1d 12m Tₓ0 C₁1_440]" ); return RaptorModuleTestCase @@ -133,7 +133,7 @@ void openOneHourTest(RaptorModuleTestCase testCase) { private static List openInWholeSearchIntervalTestNextDayTestCase() { var expected = - "A ~ BUS R1 0:20+1d 0:30+1d ~ B ~ Walk 2m Open(0:00 1:00) [0:20+1d 0:32+1d 12m 0tx $1440]"; + "A ~ BUS R1 0:20+1d 0:30+1d ~ B ~ Walk 2m Open(0:00 1:00) [0:20+1d 0:32+1d 12m Tₓ0 C₁1_440]"; return RaptorModuleTestCase .of() @@ -157,8 +157,8 @@ void openInWholeSearchIntervalTestNextDayTest(RaptorModuleTestCase testCase) { private static List openInFirstHalfIntervalTestCase() { var expected = new ExpectedList( - "A ~ BUS R1 0:10 0:20 ~ B ~ Walk 2m Open(0:00 0:25) [0:10 0:22 12m 0tx $1440]", - "A ~ BUS R1 0:30 0:40 ~ B ~ Walk 2m Open(0:00 0:25) [0:30 0:02+1d 23h32m 0tx $85440]" + "A ~ BUS R1 0:10 0:20 ~ B ~ Walk 2m Open(0:00 0:25) [0:10 0:22 12m Tₓ0 C₁1_440]", + "A ~ BUS R1 0:30 0:40 ~ B ~ Walk 2m Open(0:00 0:25) [0:30 0:02+1d 23h32m Tₓ0 C₁85_440]" ); return RaptorModuleTestCase @@ -183,7 +183,7 @@ void openInFirstHalfIntervalTest(RaptorModuleTestCase testCase) { private static List openInFirstHalfIntervalTestNextDayTestCase() { var expected = - "A ~ BUS R1 0:20+1d 0:30+1d ~ B ~ Walk 2m Open(0:25 0:40) [0:20+1d 0:32+1d 12m 0tx $1440]"; + "A ~ BUS R1 0:20+1d 0:30+1d ~ B ~ Walk 2m Open(0:25 0:40) [0:20+1d 0:32+1d 12m Tₓ0 C₁1_440]"; return RaptorModuleTestCase .of() @@ -209,10 +209,10 @@ void openInFirstHalfIntervalTestNextDayTest(RaptorModuleTestCase testCase) { private static List partiallyOpenIntervalTestCase() { var expected = new ExpectedList( - "A ~ BUS R1 0:10 0:20 ~ B ~ Walk 2m Open(0:25 0:35) [0:10 0:27 17m 0tx $1740]", - "A ~ BUS R1 0:20 0:30 ~ B ~ Walk 2m Open(0:25 0:35) [0:20 0:32 12m 0tx $1440]", - "A ~ BUS R1 0:30 0:40 ~ B ~ Walk 2m Open(0:25 0:35) [0:30 0:27+1d 23h57m 0tx $86940]", - "A ~ BUS R1 0:20+1d 0:30+1d ~ B ~ Walk 2m Open(0:25 0:35) [0:20+1d 0:32+1d 12m 0tx]" + "A ~ BUS R1 0:10 0:20 ~ B ~ Walk 2m Open(0:25 0:35) [0:10 0:27 17m Tₓ0 C₁1_740]", + "A ~ BUS R1 0:20 0:30 ~ B ~ Walk 2m Open(0:25 0:35) [0:20 0:32 12m Tₓ0 C₁1_440]", + "A ~ BUS R1 0:30 0:40 ~ B ~ Walk 2m Open(0:25 0:35) [0:30 0:27+1d 23h57m Tₓ0 C₁86_940]", + "A ~ BUS R1 0:20+1d 0:30+1d ~ B ~ Walk 2m Open(0:25 0:35) [0:20+1d 0:32+1d 12m Tₓ0]" ); return RaptorModuleTestCase diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/G03_AccessWithOpeningHoursMultipleOptionsTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/G03_AccessWithOpeningHoursMultipleOptionsTest.java index 32d561b3ab8..3d36b813764 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/G03_AccessWithOpeningHoursMultipleOptionsTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/G03_AccessWithOpeningHoursMultipleOptionsTest.java @@ -65,16 +65,16 @@ public void setup() { static List openInWholeSearchIntervalTestCases() { var expA = - "Walk 1m Open(0:05 0:08) 0:08 0:09 $120 ~ B 1m " + - "~ BUS R1 0:10 0:20 10m $1260 ~ C 0s " + - "~ Walk 2m 0:20 0:22 $240 " + - "[0:08 0:22 14m 0tx $1620]"; + "Walk 1m Open(0:05 0:08) 0:08 0:09 C₁120 ~ B 1m " + + "~ BUS R1 0:10 0:20 10m C₁1_260 ~ C 0s " + + "~ Walk 2m 0:20 0:22 C₁240 " + + "[0:08 0:22 14m Tₓ0 C₁1_620]"; var expB = - "Walk 1m Open(0:10 0:13) 0:13 0:14 $120 ~ B 1m " + - "~ BUS R1 0:15 0:25 10m $1260 ~ C 0s " + - "~ Walk 2m 0:25 0:27 $240 " + - "[0:13 0:27 14m 0tx $1620]"; + "Walk 1m Open(0:10 0:13) 0:13 0:14 C₁120 ~ B 1m " + + "~ BUS R1 0:15 0:25 10m C₁1_260 ~ C 0s " + + "~ Walk 2m 0:25 0:27 C₁240 " + + "[0:13 0:27 14m Tₓ0 C₁1_620]"; return RaptorModuleTestCase .of() diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/G04_EgressWithOpeningHoursMultipleOptionsTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/G04_EgressWithOpeningHoursMultipleOptionsTest.java index ac14b461cab..682fff0d16c 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/G04_EgressWithOpeningHoursMultipleOptionsTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/G04_EgressWithOpeningHoursMultipleOptionsTest.java @@ -61,16 +61,16 @@ public void setup() { static List openInWholeSearchIntervalTestCases() { var expA = - "Walk 2m 0:03 0:05 $240 ~ B 0s " + - "~ BUS R1 0:05 0:15 10m $1200 ~ C 2m " + - "~ Walk 1m Open(0:17 0:19) 0:17 0:18 $240 " + - "[0:03 0:18 15m 0tx $1680]"; + "Walk 2m 0:03 0:05 C₁240 ~ B 0s " + + "~ BUS R1 0:05 0:15 10m C₁1_200 ~ C 2m " + + "~ Walk 1m Open(0:17 0:19) 0:17 0:18 C₁240 " + + "[0:03 0:18 15m Tₓ0 C₁1_680]"; var expB = - "Walk 2m 0:08 0:10 $240 ~ B 0s " + - "~ BUS R1 0:10 0:20 10m $1200 ~ C 2m " + - "~ Walk 1m Open(0:22 0:24) 0:22 0:23 $240 " + - "[0:08 0:23 15m 0tx $1680]"; + "Walk 2m 0:08 0:10 C₁240 ~ B 0s " + + "~ BUS R1 0:10 0:20 10m C₁1_200 ~ C 2m " + + "~ Walk 1m Open(0:22 0:24) 0:22 0:23 C₁240 " + + "[0:08 0:23 15m Tₓ0 C₁1_680]"; return RaptorModuleTestCase .of() diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/G05_ClosedAccessOpeningHoursTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/G05_ClosedAccessOpeningHoursTest.java index 9a464b2d1b4..332f49c1f8f 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/G05_ClosedAccessOpeningHoursTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/G05_ClosedAccessOpeningHoursTest.java @@ -53,7 +53,7 @@ public void setup() { } static List testCases() { - var expected = "Walk 7m ~ A ~ BUS R1 0:10 0:20 ~ E [0:03 0:20 17m 0tx $2040]"; + var expected = "Walk 7m ~ A ~ BUS R1 0:10 0:20 ~ E [0:03 0:20 17m Tₓ0 C₁2_040]"; return RaptorModuleTestCase .of() .withRequest(r -> r.searchParams().addAccessPaths(walk(STOP_B, D2m))) diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/G06_ClosedEgressOpeningHoursTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/G06_ClosedEgressOpeningHoursTest.java index 15c1c6c9933..9b828d03de3 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/G06_ClosedEgressOpeningHoursTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/G06_ClosedEgressOpeningHoursTest.java @@ -53,7 +53,7 @@ public void setup() { } static List testCases() { - var expected = "A ~ BUS R1 0:05 0:10 ~ E ~ Walk 5m [0:05 0:15 10m 0tx $1500]"; + var expected = "A ~ BUS R1 0:05 0:10 ~ E ~ Walk 5m [0:05 0:15 10m Tₓ0 C₁1_500]"; return RaptorModuleTestCase .of() diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/H11_GuaranteedTransferWithFlexAccessTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/H11_GuaranteedTransferWithFlexAccessTest.java index 36281346e66..9cf2aaf7b82 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/H11_GuaranteedTransferWithFlexAccessTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/H11_GuaranteedTransferWithFlexAccessTest.java @@ -35,7 +35,7 @@ */ public class H11_GuaranteedTransferWithFlexAccessTest implements RaptorTestConstants { - private static final int COST_ONE_STOP = RaptorCostConverter.toRaptorCost(2 * 60); + private static final int C1_ONE_STOP = RaptorCostConverter.toRaptorCost(2 * 60); private final TestTransitData data = new TestTransitData(); private final RaptorRequestBuilder requestBuilder = new RaptorRequestBuilder<>(); @@ -58,7 +58,7 @@ public void setup() { requestBuilder .searchParams() - .addAccessPaths(flex(STOP_A, D3m, ONE_RIDE, 2 * COST_ONE_STOP)) + .addAccessPaths(flex(STOP_A, D3m, ONE_RIDE, 2 * C1_ONE_STOP)) .addEgressPaths(walk(STOP_D, D1m)); requestBuilder @@ -77,12 +77,12 @@ static List testCases() { "~ BUS R1 0:30 0:45 ~ C " + "~ BUS R2 0:45 0:55 ~ D " + "~ Walk 1m " + - "[0:16 0:56 40m 2tx $3820]"; + "[0:16 0:56 40m Tₓ2 C₁3_820]"; return RaptorModuleTestCase .of() - .add(TC_MIN_DURATION, "[0:00 0:40 40m 2tx]") - .add(TC_MIN_DURATION_REV, "[0:20 1:00 40m 2tx]") + .add(TC_MIN_DURATION, "[0:00 0:40 40m Tₓ2]") + .add(TC_MIN_DURATION_REV, "[0:20 1:00 40m Tₓ2]") .add(standard(), withoutCost(expected)) .add(multiCriteria(), expected) .build(); diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/J01_PassThroughTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/J01_PassThroughTest.java index 4cc776674da..d37a1adf61f 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/J01_PassThroughTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/J01_PassThroughTest.java @@ -28,35 +28,35 @@ import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; import org.opentripplanner.raptor.configure.RaptorConfig; -/* -FEATURE UNDER TEST - -Raptor should be able to handle route request with a specified pass-through point. -If a stop point is specified as pass-through point in the request then all the results returned -from raptor should include this stop point either as alight or board point for a trip or as an -intermediate point in the trip. - -It should be possible to specify more than one pass through point. The result should include stop -points in the order as they were specified in the request. Only alternatives that passes through -all the stop point should be included in the result. - -In order to support stop areas raptor should also support multiple stop points in the same pass-through -group. It should be possible to define both stop A and B as a pass-through. Then alternatives that -pass either stop A or B should not be dropped. +/** + * FEATURE UNDER TEST + * + * Raptor should be able to handle route request with a specified pass-through point. + * If a stop point is specified as pass-through point in the request then all the results returned + * from raptor should include this stop point either as alight or board point for a trip or as an + * intermediate point in the trip. + * + * It should be possible to specify more than one pass through point. The result should include + * stop points in the order as they were specified in the request. Only alternatives that pass + * through all the stop point should be included in the result. + * + * In order to support stop areas raptor should also support multiple stop points in the same + * pass-through group. It should be possible to define both stop A and B as a pass-through. Then + * alternatives that pass either stop A or B should not be dropped. */ -public class J01_PassThroughTest { +class J01_PassThroughTest { - public static final List PASS_THROUGH_STOP_A = List.of(point("A", STOP_A)); - public static final List PASS_THROUGH_STOP_C = List.of(point("C", STOP_C)); - public static final List PASS_THROUGH_STOP_D = List.of(point("D", STOP_D)); - public static final List PASS_THROUGH_STOP_B_OR_C = List.of( + static final List PASS_THROUGH_STOP_A = List.of(point("A", STOP_A)); + static final List PASS_THROUGH_STOP_C = List.of(point("C", STOP_C)); + static final List PASS_THROUGH_STOP_D = List.of(point("D", STOP_D)); + static final List PASS_THROUGH_STOP_B_OR_C = List.of( point("B&C", STOP_B, STOP_C) ); - public static final List PASS_THROUGH_STOP_B_THEN_C = List.of( + static final List PASS_THROUGH_STOP_B_THEN_C = List.of( point("B", STOP_B), point("C", STOP_C) ); - public static final List PASS_THROUGH_STOP_B_THEN_D = List.of( + static final List PASS_THROUGH_STOP_B_THEN_D = List.of( point("B", STOP_B), point("D", STOP_D) ); @@ -86,7 +86,7 @@ private RaptorRequestBuilder prepareRequest() { @Test @DisplayName("Pass-through stop point as a last point in the journey.") - public void passThroughPointOnEgress() { + void passThroughPointOnEgress() { var data = new TestTransitData(); // Create two routes. @@ -110,14 +110,14 @@ public void passThroughPointOnEgress() { // Verify that only the journey with pass-through stop point is included in response assertEquals( - "Walk 30s ~ A ~ BUS R2 0:02 0:50 ~ D ~ Walk 30s [0:01:30 0:50:30 49m 0tx $3600]", + "Walk 30s ~ A ~ BUS R2 0:02 0:50 ~ D ~ Walk 30s [0:01:30 0:50:30 49m Tₓ0 C₁3_600 C₂1]", pathsToString(raptorService.route(requestBuilder.build(), data)) ); } @Test @DisplayName("Pass-through stop point as a first point in the journey.") - public void passThroughPointOnAccess() { + void passThroughPointOnAccess() { var data = new TestTransitData(); // Create two routes. @@ -144,14 +144,14 @@ public void passThroughPointOnAccess() { // Verify that only the journey with pass-through stop point is included in response assertEquals( - "Walk 30s ~ A ~ BUS R2 0:02 0:50 ~ D ~ Walk 30s [0:01:30 0:50:30 49m 0tx $3600]", + "Walk 30s ~ A ~ BUS R2 0:02 0:50 ~ D ~ Walk 30s [0:01:30 0:50:30 49m Tₓ0 C₁3_600 C₂1]", pathsToString(raptorService.route(requestBuilder.build(), data)) ); } @Test @DisplayName("Pass-through stop point as an intermediate point in the journey.") - public void passThroughPointInTheMiddle() { + void passThroughPointInTheMiddle() { var data = new TestTransitData(); // Create two routes. @@ -175,14 +175,14 @@ public void passThroughPointInTheMiddle() { // Verify that only the journey with pass-through stop point is included in response assertEquals( - "Walk 30s ~ A ~ BUS R2 0:02 0:50 ~ D ~ Walk 30s [0:01:30 0:50:30 49m 0tx $3600]", + "Walk 30s ~ A ~ BUS R2 0:02 0:50 ~ D ~ Walk 30s [0:01:30 0:50:30 49m Tₓ0 C₁3_600 C₂1]", pathsToString(raptorService.route(requestBuilder.build(), data)) ); } @Test @DisplayName("Multiple pass-through stop points") - public void multiplePassThroughPoints() { + void multiplePassThroughPoints() { var data = new TestTransitData(); // Create two routes. @@ -208,14 +208,14 @@ public void multiplePassThroughPoints() { // Verify that Raptor generated journey with a transfer to r2 so that both pass-through points // are included assertEquals( - "Walk 30s ~ A ~ BUS R1 0:02 0:10 ~ C ~ BUS R2 0:15 0:50 ~ F ~ Walk 30s [0:01:30 0:50:30 49m 1tx $4300]", + "Walk 30s ~ A ~ BUS R1 0:02 0:10 ~ C ~ BUS R2 0:15 0:50 ~ F ~ Walk 30s [0:01:30 0:50:30 49m Tₓ1 C₁4_300 C₂2]", pathsToString(raptorService.route(requestBuilder.build(), data)) ); } @Test @DisplayName("Pass-through order") - public void passThroughOrder() { + void passThroughOrder() { var data = new TestTransitData(); // Create two routes. @@ -237,14 +237,14 @@ public void passThroughOrder() { // Verify that only route with correct pass-through order is returned assertEquals( - "Walk 30s ~ A ~ BUS R1 0:05 0:20 ~ D ~ Walk 30s [0:04:30 0:20:30 16m 0tx $1620]", + "Walk 30s ~ A ~ BUS R1 0:05 0:20 ~ D ~ Walk 30s [0:04:30 0:20:30 16m Tₓ0 C₁1_620 C₂2]", pathsToString(raptorService.route(requestBuilder.build(), data)) ); } @Test @DisplayName("Multiple stops in same pass-through group") - public void passThroughGroup() { + void passThroughGroup() { var data = new TestTransitData(); // Create two routes. @@ -270,8 +270,9 @@ public void passThroughGroup() { // Verify that both routes are included as a valid result assertEquals( """ - Walk 2m ~ B ~ BUS R2 0:05 0:14 ~ E ~ Walk 30s [0:03 0:14:30 11m30s 0tx $1440] - Walk 30s ~ A ~ BUS R1 0:04 0:15 ~ E ~ Walk 30s [0:03:30 0:15:30 12m 0tx $1380]""", + Walk 2m ~ B ~ BUS R2 0:05 0:14 ~ E ~ Walk 30s [0:03 0:14:30 11m30s Tₓ0 C₁1_440 C₂1] + Walk 30s ~ A ~ BUS R1 0:04 0:15 ~ E ~ Walk 30s [0:03:30 0:15:30 12m Tₓ0 C₁1_380 C₂1] + """.trim(), pathsToString(raptorService.route(requestBuilder.build(), data)) ); } diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/J01_SearchWindowAccessSlack.java b/src/test/java/org/opentripplanner/raptor/moduletests/J01_SearchWindowAccessSlack.java index 145119b9cc9..8caa3a862ca 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/J01_SearchWindowAccessSlack.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/J01_SearchWindowAccessSlack.java @@ -65,11 +65,11 @@ void setup() { } static List testCases() { - var path = "Walk 1m ~ A ~ BUS R1 0:10 0:20 ~ B ~ Walk 30s [0:09 0:20:30 11m30s 0tx $1380]"; + var path = "Walk 1m ~ A ~ BUS R1 0:10 0:20 ~ B ~ Walk 30s [0:09 0:20:30 11m30s Tₓ0 C₁1_380]"; return RaptorModuleTestCase .of() - .add(TC_MIN_DURATION, "[0:09 0:20:30 11m30s 0tx]") - .add(TC_MIN_DURATION_REV, "[0:18:30 0:30 11m30s 0tx]") + .add(TC_MIN_DURATION, "[0:09 0:20:30 11m30s Tₓ0]") + .add(TC_MIN_DURATION_REV, "[0:18:30 0:30 11m30s Tₓ0]") .add(standard(), PathUtils.withoutCost(path)) .add(multiCriteria(), path) .build(); diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/K01_TransitPriorityTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/K01_TransitPriorityTest.java new file mode 100644 index 00000000000..be544f26a11 --- /dev/null +++ b/src/test/java/org/opentripplanner/raptor/moduletests/K01_TransitPriorityTest.java @@ -0,0 +1,135 @@ +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; +import static org.opentripplanner.raptor._data.RaptorTestConstants.T01_00; +import static org.opentripplanner.raptor._data.api.PathUtils.pathsToString; +import static org.opentripplanner.raptor._data.transit.TestAccessEgress.walk; +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 java.time.Duration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +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.configure.RaptorConfig; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter; + +/** + * FEATURE UNDER TEST + * + * Raptor should be able to handle route request with transit-priority. + */ +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 int C1_SLACK_90s = RaptorCostConverter.toRaptorCost(90); + + private final TestTransitData data = new TestTransitData(); + private final RaptorRequestBuilder requestBuilder = new RaptorRequestBuilder<>(); + private final RaptorService raptorService = new RaptorService<>( + RaptorConfig.defaultConfigForTest() + ); + + @BeforeEach + private void prepareRequest() { + // Each pattern depart at the same time, but arrive with 60s between them. + // Given a slack on the cost equals to ~90s make both L1 and L2 optimal, but no L3 + data.withRoutes( + route(pattern("L1", STOP_B, STOP_C).withPriorityGroup(GROUP_A)) + .withTimetable(schedule("00:02 00:12")), + route(pattern("L2", STOP_B, STOP_C).withPriorityGroup(GROUP_B)) + .withTimetable(schedule("00:02 00:13")), + route(pattern("L3", STOP_B, STOP_C).withPriorityGroup(GROUP_C)) + .withTimetable(schedule("00:02 00:14")) + ); + + requestBuilder + .profile(RaptorProfile.MULTI_CRITERIA) + // TODO: 2023-07-24 Currently heuristics does not work with pass-through so we + // have to turn them off. Make sure to re-enable optimization later when it's fixed + .clearOptimizations(); + + requestBuilder + .searchParams() + .earliestDepartureTime(T00_00) + .latestArrivalTime(T01_00) + .searchWindow(Duration.ofMinutes(2)) + .timetable(true); + + requestBuilder.withMultiCriteria(mc -> + // Raptor cost 9000 ~= 90 seconds slack + mc + .withRelaxC1(value -> value + C1_SLACK_90s) + .withTransitPriorityCalculator(PRIORITY_GROUP_CALCULATOR) + ); + // Add 1 second access/egress paths + requestBuilder.searchParams().addAccessPaths(walk(STOP_B, 1)).addEgressPaths(walk(STOP_C, 1)); + assetGroupCalculatorIsSetupCorrect(); + } + + @Test + public void transitPriority() { + // We expect L1 & L2 but not L3, since the cost of L3 is > $90.00. + assertEquals( + """ + Walk 1s ~ B ~ BUS L1 0:02 0:12 ~ C ~ Walk 1s [0:01:59 0:12:01 10m2s Tₓ0 C₁1_204 C₂1] + Walk 1s ~ B ~ BUS L2 0:02 0:13 ~ C ~ Walk 1s [0:01:59 0:13:01 11m2s Tₓ0 C₁1_264 C₂2] + """.trim(), + 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/ModuleTestDebugLogging.java b/src/test/java/org/opentripplanner/raptor/moduletests/support/ModuleTestDebugLogging.java index 6dac5975d6f..921568e9b0b 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/support/ModuleTestDebugLogging.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/support/ModuleTestDebugLogging.java @@ -14,12 +14,12 @@ *

  * **  RUN RAPTOR FOR MINUTE: 0:09  **
  *
- * ARRIVAL  |   LEG   | RND |  STOP |  ARRIVE  |    COST   | TRIP | DETAILS
+ * ARRIVAL  |   LEG   | RND |  STOP |  ARRIVE  |    C1     | TRIP | DETAILS
  *  Accept  |  Access |   0 |     2 | 00:09:30 |    12 000 |      | Accepted element: Walk 30s ~ 2 (cost: 12000)
  *
  * **  RUN RAPTOR FOR MINUTE: 0:08  **
  *
- * ARRIVAL  |   LEG   | RND |  STOP |  ARRIVE  |    COST   | TRIP | DETAILS
+ * ARRIVAL  |   LEG   | RND |  STOP |  ARRIVE  |    C1     | TRIP | DETAILS
  *   Drop   |  Access |   0 |     2 | 00:09:30 |    12 000 |      | Droped element: Walk 30s ~ 2 (cost: 12000)
  *   ->by   |  Access |   0 |     2 | 00:08:30 |    12 000 |      | ->by element: Walk 30s ~ 2 (cost: 12000)
  *  Accept  |  Access |   0 |     2 | 00:08:30 |    12 000 |      | Accepted element: Walk 30s ~ 2 (cost: 12000)
diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/StopArrivalStateParetoSetTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/StopArrivalStateParetoSetTest.java
index 7a07a08c445..e9c39ba6bbf 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/StopArrivalStateParetoSetTest.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/StopArrivalStateParetoSetTest.java
@@ -44,27 +44,27 @@ public class StopArrivalStateParetoSetTest {
   private static final int STOP_6 = 6;
 
   // Make sure all "base" arrivals have the same cost
-  private static final int BASE_COST = 1;
+  private static final int BASE_C1 = 1;
 
   private static final StopArrivalFactoryC1 STOP_ARRIVAL_FACTORY = new StopArrivalFactoryC1<>();
   private static final McStopArrival ACCESS_ARRIVAL = newAccessStopState(
     999,
     5,
-    BASE_COST
+    BASE_C1
   );
 
   private static final McStopArrival TRANSIT_L1 = newTransitStopState(
     ROUND_1,
     998,
     10,
-    BASE_COST
+    BASE_C1
   );
 
   private static final McStopArrival TRANSIT_L2 = newTransitStopState(
     ROUND_2,
     997,
     20,
-    BASE_COST
+    BASE_C1
   );
   public static final ArrivalParetoSetComparatorFactory> COMPARATOR_FACTORY = ArrivalParetoSetComparatorFactory.factory(
     RelaxFunction.NORMAL,
diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/ArrivalParetoSetComparatorFactoryTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/ArrivalParetoSetComparatorFactoryTest.java
index 55b96421316..9b6311b58bc 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/ArrivalParetoSetComparatorFactoryTest.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/ArrivalParetoSetComparatorFactoryTest.java
@@ -5,7 +5,6 @@
 
 import org.junit.jupiter.api.Test;
 import org.opentripplanner.raptor._data.transit.TestTripSchedule;
-import org.opentripplanner.raptor.api.model.DominanceFunction;
 import org.opentripplanner.raptor.api.model.GeneralizedCostRelaxFunction;
 import org.opentripplanner.raptor.api.model.PathLegType;
 import org.opentripplanner.raptor.api.model.RelaxFunction;
@@ -15,8 +14,8 @@ class ArrivalParetoSetComparatorFactoryTest {
   private static final int STOP = 9;
   private static final boolean ARRIVED_ON_BOARD = true;
   private static final boolean ARRIVED_ON_FOOT = false;
-  private static final int COST_100 = 100;
-  private static final int COST_777 = 777;
+  private static final int C1_100 = 100;
+  private static final int C1_777 = 777;
   private static final int PARETO_ROUND_ONE = 1;
   private static final int PARETO_ROUND_TWO = 2;
   private static final int ARRIVAL_TIME_EARLY = 12;
@@ -39,8 +38,8 @@ void compareArrivalTimeRoundAndCost() {
       comparatorC1
         .compareArrivalTimeRoundAndCost()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_100, ARRIVED_ON_BOARD),
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_777, ARRIVED_ON_FOOT)
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_100, ARRIVED_ON_BOARD),
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_777, ARRIVED_ON_FOOT)
         )
     );
     // Arrival-time is better
@@ -48,8 +47,8 @@ void compareArrivalTimeRoundAndCost() {
       comparatorC1
         .compareArrivalTimeRoundAndCost()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, COST_777, COST_777, ARRIVED_ON_FOOT),
-          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_ONE, COST_100, COST_100, ARRIVED_ON_BOARD)
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, C1_777, C1_777, ARRIVED_ON_FOOT),
+          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_ONE, C1_100, C1_100, ARRIVED_ON_BOARD)
         )
     );
     // Pareto-round is better
@@ -57,8 +56,8 @@ void compareArrivalTimeRoundAndCost() {
       comparatorC1
         .compareArrivalTimeRoundAndCost()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_777, COST_777, ARRIVED_ON_FOOT),
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, COST_100, COST_100, ARRIVED_ON_BOARD)
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_777, C1_777, ARRIVED_ON_FOOT),
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, C1_100, C1_100, ARRIVED_ON_BOARD)
         )
     );
     // C1 is better
@@ -66,8 +65,8 @@ void compareArrivalTimeRoundAndCost() {
       comparatorC1
         .compareArrivalTimeRoundAndCost()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_777, ARRIVED_ON_FOOT),
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_777, COST_100, ARRIVED_ON_BOARD)
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_777, ARRIVED_ON_FOOT),
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_777, C1_100, ARRIVED_ON_BOARD)
         )
     );
   }
@@ -79,8 +78,8 @@ void compareArrivalTimeRoundAndCostWithC2() {
       comparatorC1AndC2
         .compareArrivalTimeRoundAndCost()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_100, ARRIVED_ON_BOARD),
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_100, ARRIVED_ON_FOOT)
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_100, ARRIVED_ON_BOARD),
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_100, ARRIVED_ON_FOOT)
         )
     );
     // Arrival-time is better
@@ -88,8 +87,8 @@ void compareArrivalTimeRoundAndCostWithC2() {
       comparatorC1AndC2
         .compareArrivalTimeRoundAndCost()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, COST_777, COST_100, ARRIVED_ON_FOOT),
-          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_ONE, COST_100, COST_100, ARRIVED_ON_BOARD)
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, C1_777, C1_100, ARRIVED_ON_FOOT),
+          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_ONE, C1_100, C1_100, ARRIVED_ON_BOARD)
         )
     );
     // Pareto-round is better
@@ -97,8 +96,8 @@ void compareArrivalTimeRoundAndCostWithC2() {
       comparatorC1AndC2
         .compareArrivalTimeRoundAndCost()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_777, COST_100, ARRIVED_ON_FOOT),
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, COST_100, COST_100, ARRIVED_ON_BOARD)
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_777, C1_100, ARRIVED_ON_FOOT),
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, C1_100, C1_100, ARRIVED_ON_BOARD)
         )
     );
     // C1 is better
@@ -106,8 +105,8 @@ void compareArrivalTimeRoundAndCostWithC2() {
       comparatorC1AndC2
         .compareArrivalTimeRoundAndCost()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_100, ARRIVED_ON_FOOT),
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_777, COST_100, ARRIVED_ON_BOARD)
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_100, ARRIVED_ON_FOOT),
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_777, C1_100, ARRIVED_ON_BOARD)
         )
     );
 
@@ -116,8 +115,8 @@ void compareArrivalTimeRoundAndCostWithC2() {
       comparatorC1AndC2
         .compareArrivalTimeRoundAndCost()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_777, ARRIVED_ON_FOOT),
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_100, ARRIVED_ON_BOARD)
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_777, ARRIVED_ON_FOOT),
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_100, ARRIVED_ON_BOARD)
         )
     );
   }
@@ -129,8 +128,8 @@ void compareArrivalTimeRoundCostAndOnBoardArrivalWithC2() {
       comparatorC1AndC2
         .compareArrivalTimeRoundCostAndOnBoardArrival()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_100, ARRIVED_ON_BOARD),
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_100, ARRIVED_ON_BOARD)
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_100, ARRIVED_ON_BOARD),
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_100, ARRIVED_ON_BOARD)
         )
     );
     // Arrival-time is better
@@ -138,8 +137,8 @@ void compareArrivalTimeRoundCostAndOnBoardArrivalWithC2() {
       comparatorC1AndC2
         .compareArrivalTimeRoundCostAndOnBoardArrival()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, COST_777, COST_100, ARRIVED_ON_FOOT),
-          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_ONE, COST_100, COST_100, ARRIVED_ON_BOARD)
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, C1_777, C1_100, ARRIVED_ON_FOOT),
+          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_ONE, C1_100, C1_100, ARRIVED_ON_BOARD)
         )
     );
     // Pareto-round is better
@@ -147,8 +146,8 @@ void compareArrivalTimeRoundCostAndOnBoardArrivalWithC2() {
       comparatorC1AndC2
         .compareArrivalTimeRoundCostAndOnBoardArrival()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_ONE, COST_777, COST_100, ARRIVED_ON_FOOT),
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, COST_100, COST_100, ARRIVED_ON_BOARD)
+          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_ONE, C1_777, C1_100, ARRIVED_ON_FOOT),
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, C1_100, C1_100, ARRIVED_ON_BOARD)
         )
     );
     // C1 is better
@@ -156,8 +155,8 @@ void compareArrivalTimeRoundCostAndOnBoardArrivalWithC2() {
       comparatorC1AndC2
         .compareArrivalTimeRoundCostAndOnBoardArrival()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, COST_100, COST_100, ARRIVED_ON_FOOT),
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_777, COST_100, ARRIVED_ON_BOARD)
+          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, C1_100, C1_100, ARRIVED_ON_FOOT),
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_777, C1_100, ARRIVED_ON_BOARD)
         )
     );
     // Arrived on-board is better
@@ -165,8 +164,8 @@ void compareArrivalTimeRoundCostAndOnBoardArrivalWithC2() {
       comparatorC1AndC2
         .compareArrivalTimeRoundCostAndOnBoardArrival()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, COST_777, COST_100, ARRIVED_ON_BOARD),
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_100, ARRIVED_ON_FOOT)
+          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, C1_777, C1_100, ARRIVED_ON_BOARD),
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_100, ARRIVED_ON_FOOT)
         )
     );
     // C2 is better
@@ -174,8 +173,8 @@ void compareArrivalTimeRoundCostAndOnBoardArrivalWithC2() {
       comparatorC1AndC2
         .compareArrivalTimeRoundCostAndOnBoardArrival()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, COST_100, COST_777, ARRIVED_ON_FOOT),
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_100, ARRIVED_ON_BOARD)
+          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, C1_100, C1_777, ARRIVED_ON_FOOT),
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_100, ARRIVED_ON_BOARD)
         )
     );
   }
@@ -187,8 +186,8 @@ void compareArrivalTimeRoundCostAndOnBoardArrival() {
       comparatorC1
         .compareArrivalTimeRoundCostAndOnBoardArrival()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_100, ARRIVED_ON_BOARD),
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_777, ARRIVED_ON_BOARD)
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_100, ARRIVED_ON_BOARD),
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_777, ARRIVED_ON_BOARD)
         )
     );
     // Arrival-time is better
@@ -196,8 +195,8 @@ void compareArrivalTimeRoundCostAndOnBoardArrival() {
       comparatorC1
         .compareArrivalTimeRoundCostAndOnBoardArrival()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, COST_777, COST_777, ARRIVED_ON_FOOT),
-          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_ONE, COST_100, COST_100, ARRIVED_ON_BOARD)
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, C1_777, C1_777, ARRIVED_ON_FOOT),
+          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_ONE, C1_100, C1_100, ARRIVED_ON_BOARD)
         )
     );
     // Pareto-round is better
@@ -205,8 +204,8 @@ void compareArrivalTimeRoundCostAndOnBoardArrival() {
       comparatorC1
         .compareArrivalTimeRoundCostAndOnBoardArrival()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_ONE, COST_777, COST_777, ARRIVED_ON_FOOT),
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, COST_100, COST_100, ARRIVED_ON_BOARD)
+          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_ONE, C1_777, C1_777, ARRIVED_ON_FOOT),
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, C1_100, C1_100, ARRIVED_ON_BOARD)
         )
     );
     // C1 is better
@@ -214,8 +213,8 @@ void compareArrivalTimeRoundCostAndOnBoardArrival() {
       comparatorC1
         .compareArrivalTimeRoundCostAndOnBoardArrival()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, COST_100, COST_777, ARRIVED_ON_FOOT),
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_777, COST_100, ARRIVED_ON_BOARD)
+          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, C1_100, C1_777, ARRIVED_ON_FOOT),
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_777, C1_100, ARRIVED_ON_BOARD)
         )
     );
     // Arrived on-board is better
@@ -223,8 +222,8 @@ void compareArrivalTimeRoundCostAndOnBoardArrival() {
       comparatorC1
         .compareArrivalTimeRoundCostAndOnBoardArrival()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, COST_777, COST_777, ARRIVED_ON_BOARD),
-          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_100, ARRIVED_ON_FOOT)
+          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, C1_777, C1_777, ARRIVED_ON_BOARD),
+          new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_100, ARRIVED_ON_FOOT)
         )
     );
   }
@@ -239,7 +238,7 @@ void compareRelaxedC1Test() {
       ARRIVAL_TIME_EARLY,
       PARETO_ROUND_ONE,
       bestC1,
-      COST_100,
+      C1_100,
       ARRIVED_ON_BOARD
     );
 
@@ -249,7 +248,7 @@ void compareRelaxedC1Test() {
       subject
         .compareArrivalTimeRoundAndCost()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, rejectC1, COST_777, ARRIVED_ON_FOOT),
+          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, rejectC1, C1_777, ARRIVED_ON_FOOT),
           referenceArrival
         )
     );
@@ -257,7 +256,7 @@ void compareRelaxedC1Test() {
       subject
         .compareArrivalTimeRoundAndCost()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, okC1, COST_777, ARRIVED_ON_FOOT),
+          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, okC1, C1_777, ARRIVED_ON_FOOT),
           referenceArrival
         )
     );
@@ -267,7 +266,7 @@ void compareRelaxedC1Test() {
       subject
         .compareArrivalTimeRoundCostAndOnBoardArrival()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, rejectC1, COST_777, ARRIVED_ON_FOOT),
+          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, rejectC1, C1_777, ARRIVED_ON_FOOT),
           referenceArrival
         )
     );
@@ -275,7 +274,7 @@ void compareRelaxedC1Test() {
       subject
         .compareArrivalTimeRoundCostAndOnBoardArrival()
         .leftDominanceExist(
-          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, okC1, COST_777, ARRIVED_ON_FOOT),
+          new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, okC1, C1_777, ARRIVED_ON_FOOT),
           referenceArrival
         )
     );
diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrivalTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrivalTest.java
index ee74e11ff0f..64593a91b2a 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrivalTest.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrivalTest.java
@@ -14,8 +14,8 @@ class McStopArrivalTest {
   private static final int STOP = 9;
   private static final boolean ARRIVED_ON_BOARD = true;
   private static final boolean ARRIVED_ON_FOOT = false;
-  private static final int COST_100 = 100;
-  private static final int COST_777 = 777;
+  private static final int C1_100 = 100;
+  private static final int C1_777 = 777;
   private static final int PARETO_ROUND_ONE = 1;
   private static final int PARETO_ROUND_TWO = 2;
   private static final int ARRIVAL_TIME_EARLY = 12;
@@ -26,29 +26,29 @@ void testCompareBase() {
     // Same values for arrival-time, pareto-round and c1. Ignore c2 and arrivedOnBoard
     assertFalse(
       McStopArrival.compareBase(
-        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_100, ARRIVED_ON_BOARD),
-        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_777, ARRIVED_ON_FOOT)
+        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_100, ARRIVED_ON_BOARD),
+        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_777, ARRIVED_ON_FOOT)
       )
     );
     // Arrival-time is better
     assertTrue(
       McStopArrival.compareBase(
-        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, COST_777, COST_777, ARRIVED_ON_FOOT),
-        new A(ARRIVAL_TIME_LATE, PARETO_ROUND_ONE, COST_100, COST_100, ARRIVED_ON_BOARD)
+        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, C1_777, C1_777, ARRIVED_ON_FOOT),
+        new A(ARRIVAL_TIME_LATE, PARETO_ROUND_ONE, C1_100, C1_100, ARRIVED_ON_BOARD)
       )
     );
     // Pareto-round is better
     assertTrue(
       McStopArrival.compareBase(
-        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_777, COST_777, ARRIVED_ON_FOOT),
-        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, COST_100, COST_100, ARRIVED_ON_BOARD)
+        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_777, C1_777, ARRIVED_ON_FOOT),
+        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, C1_100, C1_100, ARRIVED_ON_BOARD)
       )
     );
     // C1 is better
     assertTrue(
       McStopArrival.compareBase(
-        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_777, ARRIVED_ON_FOOT),
-        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_777, COST_100, ARRIVED_ON_BOARD)
+        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_777, ARRIVED_ON_FOOT),
+        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_777, C1_100, ARRIVED_ON_BOARD)
       )
     );
   }
@@ -58,15 +58,15 @@ void testCompareArrivedOnBoard() {
     // Same values for arrivedOnBoard. Ignore arrival-time, pareto-round, c1 and c2
     assertFalse(
       McStopArrival.compareArrivedOnBoard(
-        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_100, ARRIVED_ON_BOARD),
-        new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, COST_777, COST_777, ARRIVED_ON_BOARD)
+        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_100, ARRIVED_ON_BOARD),
+        new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, C1_777, C1_777, ARRIVED_ON_BOARD)
       )
     );
     // Arrived on-board is better
     assertTrue(
       McStopArrival.compareArrivedOnBoard(
-        new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, COST_777, COST_777, ARRIVED_ON_BOARD),
-        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, COST_100, COST_100, ARRIVED_ON_FOOT)
+        new A(ARRIVAL_TIME_LATE, PARETO_ROUND_TWO, C1_777, C1_777, ARRIVED_ON_BOARD),
+        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, C1_100, C1_100, ARRIVED_ON_FOOT)
       )
     );
   }
@@ -83,7 +83,7 @@ void testRelaxedCompareBase() {
       ARRIVAL_TIME_EARLY,
       PARETO_ROUND_ONE,
       bestC1,
-      COST_100,
+      C1_100,
       ARRIVED_ON_BOARD
     );
 
@@ -92,8 +92,8 @@ void testRelaxedCompareBase() {
     assertFalse(
       McStopArrival.relaxedCompareBase(
         relaxC1,
-        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, rejectC1, COST_100, ARRIVED_ON_BOARD),
-        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, bestC1, COST_777, ARRIVED_ON_FOOT)
+        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, rejectC1, C1_100, ARRIVED_ON_BOARD),
+        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, bestC1, C1_777, ARRIVED_ON_FOOT)
       )
     );
 
@@ -101,8 +101,8 @@ void testRelaxedCompareBase() {
     assertTrue(
       McStopArrival.relaxedCompareBase(
         relaxC1,
-        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, rejectC1, COST_100, ARRIVED_ON_BOARD),
-        new A(ARRIVAL_TIME_LATE, PARETO_ROUND_ONE, bestC1, COST_100, ARRIVED_ON_BOARD)
+        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, rejectC1, C1_100, ARRIVED_ON_BOARD),
+        new A(ARRIVAL_TIME_LATE, PARETO_ROUND_ONE, bestC1, C1_100, ARRIVED_ON_BOARD)
       )
     );
 
@@ -110,16 +110,16 @@ void testRelaxedCompareBase() {
     assertTrue(
       McStopArrival.relaxedCompareBase(
         relaxC1,
-        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, rejectC1, COST_100, ARRIVED_ON_BOARD),
-        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, bestC1, COST_100, ARRIVED_ON_BOARD)
+        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, rejectC1, C1_100, ARRIVED_ON_BOARD),
+        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_TWO, bestC1, C1_100, ARRIVED_ON_BOARD)
       )
     );
     // C1 is better, other the same
     assertTrue(
       McStopArrival.relaxedCompareBase(
         relaxC1,
-        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, okC1, COST_100, ARRIVED_ON_BOARD),
-        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, bestC1, COST_100, ARRIVED_ON_BOARD)
+        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, okC1, C1_100, ARRIVED_ON_BOARD),
+        new A(ARRIVAL_TIME_EARLY, PARETO_ROUND_ONE, bestC1, C1_100, ARRIVED_ON_BOARD)
       )
     );
   }
diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/AccessStopArrivalTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/AccessStopArrivalTest.java
index 59a9278d638..7abb1910ed9 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/AccessStopArrivalTest.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/AccessStopArrivalTest.java
@@ -11,6 +11,7 @@
 import org.junit.jupiter.api.Test;
 import org.opentripplanner.raptor._data.transit.TestAccessEgress;
 import org.opentripplanner.raptor.api.model.RaptorAccessEgress;
+import org.opentripplanner.raptor.api.model.RaptorConstants;
 import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
 import org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.McStopArrival;
 
@@ -21,7 +22,7 @@ class AccessStopArrivalTest {
   private static final int ACCESS_DURATION = 10 * 60;
   private static final int ALIGHT_TIME = DEPARTURE_TIME + ACCESS_DURATION;
   private static final TestAccessEgress WALK = TestAccessEgress.walk(ALIGHT_STOP, ACCESS_DURATION);
-  private static final int COST = WALK.generalizedCost();
+  private static final int C1 = WALK.c1();
 
   private final AccessStopArrival subject = new AccessStopArrival<>(
     DEPARTURE_TIME,
@@ -46,12 +47,12 @@ public void arrivalTime() {
 
   @Test
   public void c1() {
-    assertEquals(COST, subject.c1());
+    assertEquals(C1, subject.c1());
   }
 
   @Test
   public void c2() {
-    assertThrows(UnsupportedOperationException.class, subject::c2);
+    assertEquals(RaptorConstants.NOT_SET, subject.c2());
   }
 
   @Test
@@ -79,7 +80,7 @@ public void hashCodeThrowsExceptionByDesign() {
   @Test
   public void testToString() {
     assertEquals(
-      "Access { stop: 100, arrival: [8:10 $1200], path: Walk 10m $1200 ~ 100 }",
+      "Access { stop: 100, arrival: [8:10 C₁1_200], path: Walk 10m C₁1_200 ~ 100 }",
       subject.toString()
     );
   }
diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/StopArrivalFactoryC1Test.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/StopArrivalFactoryC1Test.java
index 00d0ce5b523..d47edf1c58f 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/StopArrivalFactoryC1Test.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/StopArrivalFactoryC1Test.java
@@ -1,8 +1,6 @@
 package org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.c1;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import org.junit.jupiter.api.Test;
 import org.opentripplanner.framework.time.DurationUtils;
@@ -11,6 +9,7 @@
 import org.opentripplanner.raptor._data.transit.TestTransfer;
 import org.opentripplanner.raptor._data.transit.TestTripPattern;
 import org.opentripplanner.raptor._data.transit.TestTripSchedule;
+import org.opentripplanner.raptor.api.model.RaptorConstants;
 import org.opentripplanner.raptor.api.view.PatternRideView;
 import org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.McStopArrival;
 import org.opentripplanner.raptor.rangeraptor.multicriteria.ride.c1.PatternRideC1;
@@ -78,13 +77,12 @@ public void testCreateAccessStopArrival() {
     var stopArrival = accessArrival();
 
     assertEquals(STOP_A, stopArrival.stop());
-    assertEquals(ACCESS.generalizedCost(), stopArrival.c1());
+    assertEquals(ACCESS.c1(), stopArrival.c1());
     assertEquals(ACCESS_DURATION, stopArrival.travelDuration());
     assertEquals(ORIGIN_DEPARTURE_TIME + ACCESS_DURATION, stopArrival.arrivalTime());
 
     // c2 not supported
-    assertFalse(stopArrival.supportsC2());
-    assertThrows(UnsupportedOperationException.class, stopArrival::c2);
+    assertEquals(RaptorConstants.NOT_SET, stopArrival.c2());
   }
 
   @Test
@@ -97,8 +95,7 @@ public void testCreateTransitStopArrival() {
     assertEquals(STOP_ARRIVAL_TRANSIT_TIME - ORIGIN_DEPARTURE_TIME, stopArrival.travelDuration());
 
     // c2 not supported
-    assertFalse(stopArrival.supportsC2());
-    assertThrows(UnsupportedOperationException.class, stopArrival::c2);
+    assertEquals(RaptorConstants.NOT_SET, stopArrival.c2());
   }
 
   @Test
@@ -107,7 +104,7 @@ public void testCreateTransferStopArrival() {
     var stopArrival = transferArrival(prevArrival);
 
     assertEquals(STOP_C, stopArrival.stop());
-    assertEquals(prevArrival.c1() + TRANSFER.generalizedCost(), stopArrival.c1());
+    assertEquals(prevArrival.c1() + TRANSFER.c1(), stopArrival.c1());
     assertEquals(
       prevArrival.arrivalTime() + TRANSFER.durationInSeconds(),
       stopArrival.arrivalTime()
@@ -115,7 +112,6 @@ public void testCreateTransferStopArrival() {
     assertEquals(DurationUtils.durationInSeconds("11m30s"), stopArrival.travelDuration());
 
     // c2 not supported
-    assertFalse(stopArrival.supportsC2());
-    assertThrows(UnsupportedOperationException.class, stopArrival::c2);
+    assertEquals(RaptorConstants.NOT_SET, stopArrival.c2());
   }
 }
diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransferStopArrivalTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransferStopArrivalTest.java
index 2e5ee35d846..e3baf5e797b 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransferStopArrivalTest.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransferStopArrivalTest.java
@@ -3,7 +3,6 @@
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.opentripplanner.raptor.api.model.PathLegType.TRANSFER;
 import static org.opentripplanner.raptor.api.model.PathLegType.TRANSIT;
@@ -11,6 +10,7 @@
 import org.junit.jupiter.api.Test;
 import org.opentripplanner.raptor._data.transit.TestAccessEgress;
 import org.opentripplanner.raptor._data.transit.TestTransfer;
+import org.opentripplanner.raptor.api.model.RaptorConstants;
 import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
 
 class TransferStopArrivalTest {
@@ -24,13 +24,13 @@ class TransferStopArrivalTest {
     ACCESS_TO_STOP,
     ACCESS_DURATION
   );
-  private static final int ACCESS_COST = ACCESS_WALK.generalizedCost();
+  private static final int ACCESS_C1 = ACCESS_WALK.c1();
 
   private static final int TRANSIT_TO_STOP = 101;
   private static final int TRANSIT_BOARD_TIME = 9 * 60 * 60;
   private static final int TRANSIT_LEG_DURATION = 1200;
   private static final int TRANSIT_ALIGHT_TIME = TRANSIT_BOARD_TIME + TRANSIT_LEG_DURATION;
-  private static final int TRANSIT_COST = 128000;
+  private static final int TRANSIT_C1 = 128000;
   private static final RaptorTripSchedule TRANSIT_TRIP = null;
   private static final int ROUND = 1;
 
@@ -41,9 +41,9 @@ class TransferStopArrivalTest {
     TRANSFER_TO_STOP,
     TRANSFER_LEG_DURATION
   );
-  private static final int TRANSFER_COST = TRANSFER_WALK.generalizedCost();
+  private static final int TRANSFER_C1 = TRANSFER_WALK.c1();
 
-  private static final int EXPECTED_COST = ACCESS_COST + TRANSIT_COST + TRANSFER_COST;
+  private static final int EXPECTED_C1 = ACCESS_C1 + TRANSIT_C1 + TRANSFER_C1;
 
   private static final AccessStopArrival ACCESS_ARRIVAL = new AccessStopArrival<>(
     ACCESS_DEPARTURE_TIME,
@@ -54,7 +54,7 @@ class TransferStopArrivalTest {
     ACCESS_ARRIVAL.timeShiftNewArrivalTime(TRANSIT_BOARD_TIME - BOARD_SLACK),
     TRANSIT_TO_STOP,
     TRANSIT_ALIGHT_TIME,
-    ACCESS_ARRIVAL.c1() + TRANSIT_COST,
+    ACCESS_ARRIVAL.c1() + TRANSIT_C1,
     TRANSIT_TRIP
   );
 
@@ -82,12 +82,12 @@ public void arrivalTime() {
 
   @Test
   public void c1() {
-    assertEquals(EXPECTED_COST, subject.c1());
+    assertEquals(EXPECTED_C1, subject.c1());
   }
 
   @Test
   public void c2() {
-    assertThrows(UnsupportedOperationException.class, subject::c2);
+    assertEquals(RaptorConstants.NOT_SET, subject.c2());
   }
 
   @Test
@@ -111,7 +111,7 @@ public void previous() {
   @Test
   public void testToString() {
     assertEquals(
-      "Walk { round: 1, stop: 102, arrival: [9:26 $2600], path: On-Street 6m ~ 102 }",
+      "Walk { round: 1, stop: 102, arrival: [9:26 C₁2_600], path: On-Street 6m ~ 102 }",
       subject.toString()
     );
   }
diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransitStopArrivalTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransitStopArrivalTest.java
index b74a78e5659..1bebfc1e798 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransitStopArrivalTest.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransitStopArrivalTest.java
@@ -2,7 +2,6 @@
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.opentripplanner.raptor._data.transit.TestTripPattern.pattern;
 import static org.opentripplanner.raptor.api.model.PathLegType.TRANSIT;
@@ -10,6 +9,7 @@
 import org.junit.jupiter.api.Test;
 import org.opentripplanner.raptor._data.transit.TestAccessEgress;
 import org.opentripplanner.raptor._data.transit.TestTripSchedule;
+import org.opentripplanner.raptor.api.model.RaptorConstants;
 import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
 
 class TransitStopArrivalTest {
@@ -23,7 +23,7 @@ class TransitStopArrivalTest {
     ACCESS_TO_STOP,
     ACCESS_DURATION
   );
-  private static final int ACCESS_COST = ACCESS_WALK.generalizedCost();
+  private static final int ACCESS_C1 = ACCESS_WALK.c1();
   private static final AccessStopArrival ACCESS_ARRIVAL = new AccessStopArrival<>(
     ACCESS_DEPARTURE_TIME,
     ACCESS_WALK
@@ -38,13 +38,13 @@ class TransitStopArrivalTest {
     .build();
   private static final int TRANSIT_TRAVEL_DURATION =
     ACCESS_DURATION + BOARD_SLACK + TRANSIT_LEG_DURATION;
-  private static final int TRANSIT_COST = 128000;
+  private static final int TRANSIT_C1 = 128000;
   private static final int ROUND = 1;
   private final TransitStopArrival subject = new TransitStopArrival<>(
     ACCESS_ARRIVAL.timeShiftNewArrivalTime(TRANSIT_BOARD_TIME - BOARD_SLACK),
     TRANSIT_TO_STOP,
     TRANSIT_ALIGHT_TIME,
-    ACCESS_ARRIVAL.c1() + TRANSIT_COST,
+    ACCESS_ARRIVAL.c1() + TRANSIT_C1,
     TRANSIT_TRIP
   );
 
@@ -76,12 +76,12 @@ public void arrivalTime() {
 
   @Test
   public void c1() {
-    assertEquals(ACCESS_COST + TRANSIT_COST, subject.c1());
+    assertEquals(ACCESS_C1 + TRANSIT_C1, subject.c1());
   }
 
   @Test
   public void c2() {
-    assertThrows(UnsupportedOperationException.class, subject::c2);
+    assertEquals(RaptorConstants.NOT_SET, subject.c2());
   }
 
   @Test
@@ -102,7 +102,7 @@ public void access() {
   @Test
   public void testToString() {
     assertEquals(
-      "Transit { round: 1, stop: 101, arrival: [9:20 $1880], pattern: BUS T1 }",
+      "Transit { round: 1, stop: 101, arrival: [9:20 C₁1_880], pattern: BUS T1 }",
       subject.toString()
     );
   }
diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AccessStopArrivalC2Test.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AccessStopArrivalC2Test.java
index 08c6b134dac..371eb2921bc 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AccessStopArrivalC2Test.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AccessStopArrivalC2Test.java
@@ -20,7 +20,7 @@ class AccessStopArrivalC2Test {
   private static final int ACCESS_DURATION = 10 * 60;
   private static final int ALIGHT_TIME = DEPARTURE_TIME + ACCESS_DURATION;
   private static final TestAccessEgress WALK = TestAccessEgress.walk(ALIGHT_STOP, ACCESS_DURATION);
-  private static final int COST = WALK.generalizedCost();
+  private static final int C1 = WALK.c1();
 
   private final AccessStopArrivalC2 subject = new AccessStopArrivalC2<>(
     DEPARTURE_TIME,
@@ -45,7 +45,7 @@ public void arrivalTime() {
 
   @Test
   public void c1() {
-    assertEquals(COST, subject.c1());
+    assertEquals(C1, subject.c1());
   }
 
   @Test
@@ -79,7 +79,7 @@ public void hashCodeThrowsExceptionByDesign() {
   @Test
   public void testToString() {
     assertEquals(
-      "Access { stop: 100, arrival: [8:10 $1200], path: Walk 10m $1200 ~ 100 }",
+      "Access { stop: 100, arrival: [8:10 C₁1_200 C₂0], path: Walk 10m C₁1_200 ~ 100 }",
       subject.toString()
     );
   }
diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/StopArrivalFactoryC2Test.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/StopArrivalFactoryC2Test.java
index 3578deb5b2e..4c78461b016 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/StopArrivalFactoryC2Test.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/StopArrivalFactoryC2Test.java
@@ -1,7 +1,6 @@
 package org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.c2;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import org.junit.jupiter.api.Test;
 import org.opentripplanner.framework.time.DurationUtils;
@@ -80,10 +79,9 @@ public void testCreateAccessStopArrival() {
     var stopArrival = accessArrival();
 
     assertEquals(STOP_A, stopArrival.stop());
-    assertEquals(ACCESS.generalizedCost(), stopArrival.c1());
+    assertEquals(ACCESS.c1(), stopArrival.c1());
     assertEquals(ACCESS_DURATION, stopArrival.travelDuration());
     assertEquals(ORIGIN_DEPARTURE_TIME + ACCESS_DURATION, stopArrival.arrivalTime());
-    assertTrue(stopArrival.supportsC2());
     assertEquals(RaptorCostCalculator.ZERO_COST, stopArrival.c2());
   }
 
@@ -95,7 +93,6 @@ public void testCreateTransitStopArrival() {
     assertEquals(STOP_ARRIVAL_TRANSIT_C1, stopArrival.c1());
     assertEquals(STOP_ARRIVAL_TRANSIT_TIME, stopArrival.arrivalTime());
     assertEquals(STOP_ARRIVAL_TRANSIT_TIME - ORIGIN_DEPARTURE_TIME, stopArrival.travelDuration());
-    assertTrue(stopArrival.supportsC2());
     assertEquals(RIDE_C2, stopArrival.c2());
   }
 
@@ -105,13 +102,12 @@ public void testCreateTransferStopArrival() {
     var stopArrival = transferArrival(prevArrival);
 
     assertEquals(STOP_C, stopArrival.stop());
-    assertEquals(prevArrival.c1() + TRANSFER.generalizedCost(), stopArrival.c1());
+    assertEquals(prevArrival.c1() + TRANSFER.c1(), stopArrival.c1());
     assertEquals(
       prevArrival.arrivalTime() + TRANSFER.durationInSeconds(),
       stopArrival.arrivalTime()
     );
     assertEquals(DurationUtils.durationInSeconds("11m30s"), stopArrival.travelDuration());
-    assertTrue(stopArrival.supportsC2());
     assertEquals(RIDE_C2, stopArrival.c2());
   }
 }
diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransferStopArrivalC2Test.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransferStopArrivalC2Test.java
index 55e1d6b6930..a8abe5bdddb 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransferStopArrivalC2Test.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransferStopArrivalC2Test.java
@@ -1,11 +1,9 @@
 package org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.c2;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.opentripplanner.raptor.api.model.PathLegType.TRANSFER;
-import static org.opentripplanner.raptor.api.model.PathLegType.TRANSIT;
 
 import org.junit.jupiter.api.Test;
 import org.opentripplanner.raptor._data.transit.TestAccessEgress;
@@ -23,13 +21,13 @@ class TransferStopArrivalC2Test {
     ACCESS_TO_STOP,
     ACCESS_DURATION
   );
-  private static final int ACCESS_COST = ACCESS_WALK.generalizedCost();
+  private static final int ACCESS_C1 = ACCESS_WALK.c1();
 
   private static final int TRANSIT_TO_STOP = 101;
   private static final int TRANSIT_BOARD_TIME = 9 * 60 * 60;
   private static final int TRANSIT_LEG_DURATION = 1200;
   private static final int TRANSIT_ALIGHT_TIME = TRANSIT_BOARD_TIME + TRANSIT_LEG_DURATION;
-  private static final int TRANSIT_COST = 128000;
+  private static final int TRANSIT_C1 = 128000;
   private static final RaptorTripSchedule TRANSIT_TRIP = null;
   private static final int ROUND = 1;
 
@@ -40,11 +38,11 @@ class TransferStopArrivalC2Test {
     TRANSFER_TO_STOP,
     TRANSFER_LEG_DURATION
   );
-  private static final int TRANSFER_COST = TRANSFER_WALK.generalizedCost();
+  private static final int TRANSFER_C1 = TRANSFER_WALK.c1();
 
-  private static final int EXPECTED_COST = ACCESS_COST + TRANSIT_COST + TRANSFER_COST;
+  private static final int EXPECTED_C1 = ACCESS_C1 + TRANSIT_C1 + TRANSFER_C1;
 
-  private static final int TRANSIT_C2 = 600;
+  private static final int TRANSIT_C2 = 6;
 
   private static final AccessStopArrivalC2 ACCESS_ARRIVAL = new AccessStopArrivalC2<>(
     ACCESS_DEPARTURE_TIME,
@@ -55,7 +53,7 @@ class TransferStopArrivalC2Test {
     ACCESS_ARRIVAL.timeShiftNewArrivalTime(TRANSIT_BOARD_TIME - BOARD_SLACK),
     TRANSIT_TO_STOP,
     TRANSIT_ALIGHT_TIME,
-    ACCESS_ARRIVAL.c1() + TRANSIT_COST,
+    ACCESS_ARRIVAL.c1() + TRANSIT_C1,
     TRANSIT_C2,
     TRANSIT_TRIP
   );
@@ -84,7 +82,7 @@ public void arrivalTime() {
 
   @Test
   public void c1() {
-    assertEquals(EXPECTED_COST, subject.c1());
+    assertEquals(EXPECTED_C1, subject.c1());
   }
 
   @Test
@@ -114,7 +112,7 @@ public void previous() {
   @Test
   public void testToString() {
     assertEquals(
-      "Walk { round: 1, stop: 102, arrival: [9:26 $2600], path: On-Street 6m ~ 102 }",
+      "Walk { round: 1, stop: 102, arrival: [9:26 C₁2_600 C₂6], path: On-Street 6m ~ 102 }",
       subject.toString()
     );
   }
diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransitStopArrivalC2Test.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransitStopArrivalC2Test.java
index f25034959a6..8ddf4530c56 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransitStopArrivalC2Test.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransitStopArrivalC2Test.java
@@ -1,7 +1,6 @@
 package org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.c2;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.opentripplanner.raptor._data.transit.TestTripPattern.pattern;
@@ -23,7 +22,7 @@ class TransitStopArrivalC2Test {
     ACCESS_TO_STOP,
     ACCESS_DURATION
   );
-  private static final int ACCESS_COST = ACCESS_WALK.generalizedCost();
+  private static final int ACCESS_C1 = ACCESS_WALK.c1();
   private static final AccessStopArrivalC2 ACCESS_ARRIVAL = new AccessStopArrivalC2<>(
     ACCESS_DEPARTURE_TIME,
     ACCESS_WALK
@@ -78,7 +77,7 @@ public void arrivalTime() {
 
   @Test
   public void c1() {
-    assertEquals(ACCESS_COST + TRANSIT_C1, subject.c1());
+    assertEquals(ACCESS_C1 + TRANSIT_C1, subject.c1());
   }
 
   @Test
@@ -104,7 +103,7 @@ public void access() {
   @Test
   public void testToString() {
     assertEquals(
-      "Transit { round: 1, stop: 101, arrival: [9:20 $1880], pattern: BUS T1 }",
+      "Transit { round: 1, stop: 101, arrival: [9:20 C₁1_880 C₂8000], pattern: BUS T1 }",
       subject.toString()
     );
   }
diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c1/PatternRideC1Test.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c1/PatternRideC1Test.java
index 24bdba53cd2..02d2a0dd701 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c1/PatternRideC1Test.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c1/PatternRideC1Test.java
@@ -1,6 +1,5 @@
 package org.opentripplanner.raptor.rangeraptor.multicriteria.ride.c1;
 
-import static org.junit.jupiter.api.Assertions.assertAll;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertSame;
@@ -14,8 +13,8 @@ public class PatternRideC1Test {
 
   @Test
   public void testParetoComparatorRelativeCost() {
-    final var LOW_COST = 100;
-    final var HIGH_COST = 500;
+    final var C1_LOW = 100;
+    final var C1_HIGH = 500;
     final var TRIP_SORT_INDEX_1 = 1;
     final var TRIP_SORT_INDEX_2 = 2;
 
@@ -23,29 +22,29 @@ public void testParetoComparatorRelativeCost() {
 
     assertTrue(
       comparator.leftDominanceExist(
-        new PatternRideC1<>(null, 0, 0, 0, LOW_COST, LOW_COST, TRIP_SORT_INDEX_1, null),
-        new PatternRideC1<>(null, 0, 0, 0, LOW_COST, LOW_COST, TRIP_SORT_INDEX_2, null)
+        new PatternRideC1<>(null, 0, 0, 0, C1_LOW, C1_LOW, TRIP_SORT_INDEX_1, null),
+        new PatternRideC1<>(null, 0, 0, 0, C1_LOW, C1_LOW, TRIP_SORT_INDEX_2, null)
       )
     );
 
     assertFalse(
       comparator.leftDominanceExist(
-        new PatternRideC1<>(null, 0, 0, 0, LOW_COST, LOW_COST, TRIP_SORT_INDEX_1, null),
-        new PatternRideC1<>(null, 0, 0, 0, LOW_COST, LOW_COST, TRIP_SORT_INDEX_1, null)
+        new PatternRideC1<>(null, 0, 0, 0, C1_LOW, C1_LOW, TRIP_SORT_INDEX_1, null),
+        new PatternRideC1<>(null, 0, 0, 0, C1_LOW, C1_LOW, TRIP_SORT_INDEX_1, null)
       )
     );
 
     assertTrue(
       comparator.leftDominanceExist(
-        new PatternRideC1<>(null, 0, 0, 0, LOW_COST, LOW_COST, TRIP_SORT_INDEX_1, null),
-        new PatternRideC1<>(null, 0, 0, 0, HIGH_COST, HIGH_COST, TRIP_SORT_INDEX_1, null)
+        new PatternRideC1<>(null, 0, 0, 0, C1_LOW, C1_LOW, TRIP_SORT_INDEX_1, null),
+        new PatternRideC1<>(null, 0, 0, 0, C1_HIGH, C1_HIGH, TRIP_SORT_INDEX_1, null)
       )
     );
 
     assertFalse(
       comparator.leftDominanceExist(
-        new PatternRideC1<>(null, 0, 0, 0, HIGH_COST, HIGH_COST, TRIP_SORT_INDEX_1, null),
-        new PatternRideC1<>(null, 0, 0, 0, LOW_COST, LOW_COST, TRIP_SORT_INDEX_1, null)
+        new PatternRideC1<>(null, 0, 0, 0, C1_HIGH, C1_HIGH, TRIP_SORT_INDEX_1, null),
+        new PatternRideC1<>(null, 0, 0, 0, C1_LOW, C1_LOW, TRIP_SORT_INDEX_1, null)
       )
     );
   }
diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c2/PatternRideC2Test.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c2/PatternRideC2Test.java
index 731d6843de2..899c4c5ba67 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c2/PatternRideC2Test.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c2/PatternRideC2Test.java
@@ -1,6 +1,5 @@
 package org.opentripplanner.raptor.rangeraptor.multicriteria.ride.c2;
 
-import static org.junit.jupiter.api.Assertions.assertAll;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -11,50 +10,50 @@ public class PatternRideC2Test {
 
   @Test
   public void testParetoComparatorRelativeCost() {
-    final var LOW_COST = 100;
-    final var HIGH_COST = 500;
+    final var C1_LOW = 100;
+    final var C1_HIGH = 500;
     final var TRIP_SORT_INDEX_1 = 1;
     final var TRIP_SORT_INDEX_2 = 2;
     var comparator = PatternRideC2.paretoComparatorRelativeCost((l1, l2) -> l1 > l2);
 
     assertTrue(
       comparator.leftDominanceExist(
-        new PatternRideC2<>(null, 0, 0, 0, LOW_COST, LOW_COST, LOW_COST, TRIP_SORT_INDEX_1, null),
-        new PatternRideC2<>(null, 0, 0, 0, LOW_COST, LOW_COST, LOW_COST, TRIP_SORT_INDEX_2, null)
+        new PatternRideC2<>(null, 0, 0, 0, C1_LOW, C1_LOW, C1_LOW, TRIP_SORT_INDEX_1, null),
+        new PatternRideC2<>(null, 0, 0, 0, C1_LOW, C1_LOW, C1_LOW, TRIP_SORT_INDEX_2, null)
       )
     );
     assertFalse(
       comparator.leftDominanceExist(
-        new PatternRideC2<>(null, 0, 0, 0, LOW_COST, LOW_COST, LOW_COST, TRIP_SORT_INDEX_1, null),
-        new PatternRideC2<>(null, 0, 0, 0, LOW_COST, LOW_COST, LOW_COST, TRIP_SORT_INDEX_1, null)
+        new PatternRideC2<>(null, 0, 0, 0, C1_LOW, C1_LOW, C1_LOW, TRIP_SORT_INDEX_1, null),
+        new PatternRideC2<>(null, 0, 0, 0, C1_LOW, C1_LOW, C1_LOW, TRIP_SORT_INDEX_1, null)
       )
     );
 
     assertTrue(
       comparator.leftDominanceExist(
-        new PatternRideC2<>(null, 0, 0, 0, LOW_COST, LOW_COST, LOW_COST, TRIP_SORT_INDEX_1, null),
-        new PatternRideC2<>(null, 0, 0, 0, HIGH_COST, HIGH_COST, LOW_COST, TRIP_SORT_INDEX_1, null)
+        new PatternRideC2<>(null, 0, 0, 0, C1_LOW, C1_LOW, C1_LOW, TRIP_SORT_INDEX_1, null),
+        new PatternRideC2<>(null, 0, 0, 0, C1_HIGH, C1_HIGH, C1_LOW, TRIP_SORT_INDEX_1, null)
       )
     );
 
     assertFalse(
       comparator.leftDominanceExist(
-        new PatternRideC2<>(null, 0, 0, 0, HIGH_COST, HIGH_COST, LOW_COST, TRIP_SORT_INDEX_1, null),
-        new PatternRideC2<>(null, 0, 0, 0, LOW_COST, LOW_COST, LOW_COST, TRIP_SORT_INDEX_1, null)
+        new PatternRideC2<>(null, 0, 0, 0, C1_HIGH, C1_HIGH, C1_LOW, TRIP_SORT_INDEX_1, null),
+        new PatternRideC2<>(null, 0, 0, 0, C1_LOW, C1_LOW, C1_LOW, TRIP_SORT_INDEX_1, null)
       )
     );
 
     assertTrue(
       comparator.leftDominanceExist(
-        new PatternRideC2<>(null, 0, 0, 0, LOW_COST, LOW_COST, HIGH_COST, TRIP_SORT_INDEX_1, null),
-        new PatternRideC2<>(null, 0, 0, 0, LOW_COST, LOW_COST, LOW_COST, TRIP_SORT_INDEX_1, null)
+        new PatternRideC2<>(null, 0, 0, 0, C1_LOW, C1_LOW, C1_HIGH, TRIP_SORT_INDEX_1, null),
+        new PatternRideC2<>(null, 0, 0, 0, C1_LOW, C1_LOW, C1_LOW, TRIP_SORT_INDEX_1, null)
       )
     );
 
     assertFalse(
       comparator.leftDominanceExist(
-        new PatternRideC2<>(null, 0, 0, 0, LOW_COST, LOW_COST, LOW_COST, TRIP_SORT_INDEX_1, null),
-        new PatternRideC2<>(null, 0, 0, 0, LOW_COST, LOW_COST, HIGH_COST, TRIP_SORT_INDEX_1, null)
+        new PatternRideC2<>(null, 0, 0, 0, C1_LOW, C1_LOW, C1_LOW, TRIP_SORT_INDEX_1, null),
+        new PatternRideC2<>(null, 0, 0, 0, C1_LOW, C1_LOW, C1_HIGH, TRIP_SORT_INDEX_1, null)
       )
     );
   }
diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalTest.java
index 458e88510d3..af2aa88fb5c 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalTest.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalTest.java
@@ -14,7 +14,6 @@
 public class DestinationArrivalTest {
 
   private static final int ANY = 9_999;
-  private static final int BOARD_SLACK = 60;
   private static final int ACCESS_STOP = 100;
   private static final int ACCESS_DEPARTURE_TIME = 8 * 60 * 60;
   private static final int ACCESS_DURATION = 72;
@@ -22,7 +21,7 @@ public class DestinationArrivalTest {
     ACCESS_STOP,
     ACCESS_DURATION
   );
-  private static final int ACCESS_COST = ACCESS_WALK.generalizedCost();
+  private static final int ACCESS_COST = ACCESS_WALK.c1();
 
   private static final int TRANSIT_STOP = 101;
   private static final int TRANSIT_BOARD_TIME = ACCESS_DEPARTURE_TIME + 10 * 60;
@@ -31,10 +30,11 @@ public class DestinationArrivalTest {
   private static final int TRANSIT_COST = 84000;
 
   private static final int DESTINATION_DURATION_TIME = 50;
-  private static final int DESTINATION_COST = 50000;
+  private static final int DESTINATION_C1 = 50000;
+  private static final int DESTINATION_C2 = 5;
 
   private static final int EXPECTED_ARRIVAL_TIME = TRANSIT_ALIGHT_TIME + DESTINATION_DURATION_TIME;
-  private static final int EXPECTED_TOTAL_COST = ACCESS_COST + TRANSIT_COST + DESTINATION_COST;
+  private static final int EXPECTED_TOTAL_COST = ACCESS_COST + TRANSIT_COST + DESTINATION_C1;
 
   private static final StopArrivalFactoryC1 STOP_ARRIVAL_FACTORY = new StopArrivalFactoryC1();
 
@@ -57,7 +57,8 @@ public class DestinationArrivalTest {
     TestAccessEgress.walk(TRANSIT_STOP, DESTINATION_DURATION_TIME),
     TRANSIT_ARRIVAL,
     TRANSIT_ALIGHT_TIME + DESTINATION_DURATION_TIME,
-    DESTINATION_COST
+    DESTINATION_C1,
+    DESTINATION_C2
   );
 
   @Test
@@ -83,7 +84,7 @@ public void previous() {
   @Test
   public void testToString() {
     assertEquals(
-      "Egress { round: 1, from-stop: 101, arrival: [8:14:50 $1484], path: Walk 50s $100 ~ 101 }",
+      "Egress { round: 1, from-stop: 101, arrival: [8:14:50 C₁1_484 C₂5], path: Walk 50s C₁100 ~ 101 }",
       subject.toString()
     );
   }
diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/path/PathMapperTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/path/PathMapperTest.java
index cbd2186a2c1..bac2c1b0431 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/path/PathMapperTest.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/path/PathMapperTest.java
@@ -2,7 +2,7 @@
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.BASIC_PATH_AS_DETAILED_STRING;
-import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.COST_CALCULATOR;
+import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.C1_CALCULATOR;
 import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.basicTripByForwardSearch;
 import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.basicTripByReverseSearch;
 import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.lifeCycle;
@@ -32,7 +32,7 @@ public class PathMapperTest implements RaptorTestConstants {
   private static final RaptorSlackProvider FLEX_SLACK_PROVIDER =
     FlexAccessAndEgressPathTestCase.SLACK_PROVIDER;
   private static final DefaultCostCalculator FLEX_COST_CALCULATOR =
-    FlexAccessAndEgressPathTestCase.COST_CALCULATOR;
+    FlexAccessAndEgressPathTestCase.C1_CALCULATOR;
 
   /* BASIC CASES */
 
@@ -42,7 +42,7 @@ public void mapToPathBasicForwardSearch() {
     var destArrival = basicTripByForwardSearch();
     var mapper = new ForwardPathMapper<>(
       SLACK_PROVIDER,
-      COST_CALCULATOR,
+      C1_CALCULATOR,
       this::stopIndexToName,
       null,
       lifeCycle(),
@@ -62,7 +62,7 @@ public void mapToPathBasicReverseSearch() {
     var destArrival = basicTripByReverseSearch();
     var mapper = new ReversePathMapper<>(
       SLACK_PROVIDER,
-      COST_CALCULATOR,
+      C1_CALCULATOR,
       this::stopIndexToName,
       null,
       lifeCycle(),
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 02ba0b22c53..6d3b25c1d31 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPathsTest.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPathsTest.java
@@ -51,12 +51,12 @@ class EgressPathsTest {
   @Test
   void byStopStandard() {
     var byStop = subjectStd.byStop();
-    assertEquals("[Walk 1m $120 ~ 2]", byStop.get(STOP_A).toString());
-    assertEquals("[Flex 1m $120 1x ~ 3]", byStop.get(STOP_B).toString());
-    assertEquals("[Flex 1m $120 1x ~ 4]", byStop.get(STOP_C).toString());
-    assertEquals("[Flex 1m $120 2x ~ 5]", byStop.get(STOP_D).toString());
+    assertEquals("[Walk 1m C₁120 ~ 2]", byStop.get(STOP_A).toString());
+    assertEquals("[Flex 1m C₁120 1x ~ 3]", byStop.get(STOP_B).toString());
+    assertEquals("[Flex 1m C₁120 1x ~ 4]", byStop.get(STOP_C).toString());
+    assertEquals("[Flex 1m C₁120 2x ~ 5]", byStop.get(STOP_D).toString());
     assertEquals(
-      "[Walk 2m $240 ~ 6, Walk 1m $120 Open(10:00 11:45) ~ 6, Walk 1m $120 Open(11:30 12:30) ~ 6]",
+      "[Walk 2m C₁240 ~ 6, Walk 1m C₁120 Open(10:00 11:45) ~ 6, Walk 1m C₁120 Open(11:30 12:30) ~ 6]",
       byStop.get(STOP_E).toString()
     );
     // Verify no more stops (A, B, C, D, E)
@@ -73,29 +73,29 @@ void stops() {
   void listAll() {
     assertEquals(
       """
-      Flex 1m $120 1x ~ 3
-      Flex 1m $120 1x ~ 4
-      Flex 1m $120 2x ~ 5
-      Walk 1m $120 Open(10:00 11:45) ~ 6
-      Walk 1m $120 Open(11:30 12:30) ~ 6
-      Walk 1m $120 ~ 2
-      Walk 2m $240 ~ 6
+      Flex 1m C₁120 1x ~ 3
+      Flex 1m C₁120 1x ~ 4
+      Flex 1m C₁120 2x ~ 5
+      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 ~ 6
       """.strip(),
       subjectStd.listAll().stream().map(Object::toString).sorted().collect(Collectors.joining("\n"))
     );
     assertEquals(
       """
-      Flex 1m $120 1x ~ 3
-      Flex 1m $120 1x ~ 4
-      Flex 1m $120 2x ~ 5
-      Flex 1m $120 3x ~ 5
-      Flex 2m $240 1x ~ 3
-      Flex+Walk 1m $120 1x ~ 4
-      Walk 1m $120 Open(10:00 11:45) ~ 6
-      Walk 1m $120 Open(11:30 12:30) ~ 6
-      Walk 1m $120 ~ 2
-      Walk 2m $240 Open(14:00 14:00) ~ 6
-      Walk 2m $240 ~ 6
+      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"))
     );
diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorSearchWindowCalculatorTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorSearchWindowCalculatorTest.java
index 4605d4bfc28..4898c5e2ae9 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorSearchWindowCalculatorTest.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorSearchWindowCalculatorTest.java
@@ -2,9 +2,13 @@
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.time.Duration;
+import java.util.List;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
 import org.opentripplanner.raptor._data.transit.TestTripSchedule;
 import org.opentripplanner.raptor.api.model.RaptorConstants;
 import org.opentripplanner.raptor.api.request.DynamicSearchWindowCoefficients;
@@ -58,19 +62,48 @@ public void calcEarliestDeparture() {
     subject.withHeuristics(minTransitTime, minWaitTime).withSearchParams(searchParams).calculate();
 
     /*
-           search-window: round_N(C + T * minTransitTime + W * minWaitTime)
+           search-window: round_60(C + T * minTransitTime + W * minWaitTime)
                = round_60(600 + 0.6 * 500 + 0.4 * 200)
                = round_60(980) = 960
 
            EDT = LAT - (search-window + minTripTime)
-           EDT = 3000 - (960s + roundUp_60(500))
-           EDT = 3000 - (960s + 540s)
-           EDT = 1500
+           EDT = 3000 - (960s + round_60(500))
+           EDT = 3000 - (960s + 480s)
+           EDT = 1560
          */
     assertEquals(960, subject.getSearchWindowSeconds());
-    assertEquals(1_500, subject.getEarliestDepartureTime());
+    assertEquals(1_560, subject.getEarliestDepartureTime());
     // Given - verify not changed
     assertEquals(3_000, subject.getLatestArrivalTime());
+    assertTrue(
+      minTransitTime >
+      subject.getLatestArrivalTime() -
+      (subject.getSearchWindowSeconds() + subject.getEarliestDepartureTime())
+    );
+  }
+
+  @Test
+  public void calcEarliestDepartureExact() {
+    SearchParams searchParams = new RaptorRequestBuilder()
+      .searchParams()
+      .latestArrivalTime(3_000)
+      .buildSearchParam();
+
+    int minTransitTime = 600;
+    int minWaitTime = 0;
+
+    subject.withHeuristics(minTransitTime, minWaitTime).withSearchParams(searchParams).calculate();
+
+    assertEquals(960, subject.getSearchWindowSeconds());
+    assertEquals(1_440, subject.getEarliestDepartureTime());
+    // Given - verify not changed
+    assertEquals(3_000, subject.getLatestArrivalTime());
+
+    assertEquals(
+      minTransitTime,
+      subject.getLatestArrivalTime() -
+      (subject.getSearchWindowSeconds() + subject.getEarliestDepartureTime())
+    );
   }
 
   @Test
@@ -166,17 +199,23 @@ public void calculateNotDefinedIfMinTravelTimeNotSet() {
     assertThrows(IllegalArgumentException.class, subject::calculate);
   }
 
-  @Test
-  public void roundUpToNearestMinute() {
-    assertEquals(0, subject.roundUpToNearestMinute(0));
-    assertEquals(60, subject.roundUpToNearestMinute(1));
-    assertEquals(60, subject.roundUpToNearestMinute(60));
-    assertEquals(120, subject.roundUpToNearestMinute(61));
+  static List roundUpToNearestMinuteTestCase() {
+    return List.of(V2.of(0, 0), V2.of(0, 59), V2.of(60, 60), V2.of(60, 119), V2.of(120, 120));
+  }
+
+  @ParameterizedTest
+  @MethodSource("roundUpToNearestMinuteTestCase")
+  void roundUpToNearestMinute(V2 v2) {
+    assertEquals(v2.expected(), subject.roundDownToNearestMinute(v2.value()));
   }
 
   @Test
-  public void roundUpToNearestMinuteNotDefinedForNegativeNumbers() {
-    assertThrows(IllegalArgumentException.class, () -> subject.roundUpToNearestMinute(-1));
+  void roundUpToNearestMinuteNotDefinedForNegativeNumbers() {
+    var ex = assertThrows(
+      IllegalArgumentException.class,
+      () -> subject.roundDownToNearestMinute(-1)
+    );
+    assertEquals("This operation is not defined for negative numbers: -1", ex.getMessage());
   }
 
   @Test
@@ -188,4 +227,10 @@ public void roundStep() {
     assertEquals(60, subject.roundStep(30f));
     assertEquals(480, subject.roundStep(450f));
   }
+
+  record V2(int expected, int value) {
+    static V2 of(int expected, int value) {
+      return new V2(expected, value);
+    }
+  }
 }
diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/TripTimesSearchTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/TripTimesSearchTest.java
index 24d93a74146..50295b0341e 100644
--- a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/TripTimesSearchTest.java
+++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/TripTimesSearchTest.java
@@ -3,6 +3,8 @@
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.opentripplanner.framework.time.TimeUtils.timeToStrLong;
+import static org.opentripplanner.raptor._data.stoparrival.TestArrivals.access;
+import static org.opentripplanner.raptor._data.stoparrival.TestArrivals.bus;
 import static org.opentripplanner.raptor._data.transit.TestAccessEgress.free;
 import static org.opentripplanner.raptor._data.transit.TestTripPattern.pattern;
 import static org.opentripplanner.raptor.rangeraptor.transit.TripTimesSearch.findTripForwardSearch;
@@ -13,9 +15,8 @@
 import org.junit.jupiter.api.Test;
 import org.opentripplanner.framework.time.TimeUtils;
 import org.opentripplanner.raptor._data.RaptorTestConstants;
-import org.opentripplanner.raptor._data.stoparrival.Access;
-import org.opentripplanner.raptor._data.stoparrival.Bus;
 import org.opentripplanner.raptor._data.transit.TestTripSchedule;
+import org.opentripplanner.raptor.api.view.ArrivalView;
 import org.opentripplanner.raptor.spi.BoardAndAlightTime;
 
 public class TripTimesSearchTest implements RaptorTestConstants {
@@ -37,12 +38,12 @@ public void findTripWithPlentySlack() {
     BoardAndAlightTime r;
 
     // Search AFTER EDT
-    r = findTripForwardSearch(busFwd(STOP_A, STOP_C, C_ALIGHT_LATE));
+    r = findTripForwardSearch(busFwd(STOP_A, STOP_C, C_ALIGHT_LATE, schedule));
 
     assertTimes(r, A_BOARD_TIME, C_ALIGHT_TIME);
 
     // Search BEFORE LAT
-    r = findTripReverseSearch(busRev(STOP_C, STOP_A, A_BOARD_EARLY));
+    r = findTripReverseSearch(busRev(STOP_C, STOP_A, A_BOARD_EARLY, schedule));
 
     assertTimes(r, A_BOARD_TIME, C_ALIGHT_TIME);
   }
@@ -52,12 +53,12 @@ public void findTripWithApproximateTimes() {
     BoardAndAlightTime r;
 
     // Search AFTER EDT
-    r = findTripForwardSearchApproximateTime(busFwd(STOP_A, STOP_C, C_ALIGHT_LATE));
+    r = findTripForwardSearchApproximateTime(busFwd(STOP_A, STOP_C, C_ALIGHT_LATE, schedule));
 
     assertTimes(r, A_BOARD_TIME, C_ALIGHT_TIME);
 
     // Search BEFORE LAT
-    r = findTripReverseSearchApproximateTime(busRev(STOP_C, STOP_A, A_BOARD_EARLY));
+    r = findTripReverseSearchApproximateTime(busRev(STOP_C, STOP_A, A_BOARD_EARLY, schedule));
 
     assertTimes(r, A_BOARD_TIME, C_ALIGHT_TIME);
   }
@@ -67,12 +68,12 @@ public void findTripWithoutSlack() {
     BoardAndAlightTime r;
 
     // Search AFTER EDT
-    r = findTripForwardSearch(busFwd(STOP_A, STOP_C, C_ALIGHT_TIME));
+    r = findTripForwardSearch(busFwd(STOP_A, STOP_C, C_ALIGHT_TIME, schedule));
 
     assertTimes(r, A_BOARD_TIME, C_ALIGHT_TIME);
 
     // Search BEFORE LAT
-    r = findTripReverseSearch(busRev(STOP_C, STOP_A, A_BOARD_TIME));
+    r = findTripReverseSearch(busRev(STOP_C, STOP_A, A_BOARD_TIME, schedule));
 
     assertTimes(r, A_BOARD_TIME, C_ALIGHT_TIME);
   }
@@ -117,7 +118,7 @@ public void findInLoop() {
   public void noTripFoundWhenArrivalIsToEarly() {
     assertThrows(
       IllegalStateException.class,
-      () -> findTripForwardSearch(busFwd(STOP_A, STOP_C, C_ALIGHT_TIME - 1)),
+      () -> findTripForwardSearch(busFwd(STOP_A, STOP_C, C_ALIGHT_TIME - 1, schedule)),
       "No stops matching 'toStop'."
     );
   }
@@ -126,7 +127,7 @@ public void noTripFoundWhenArrivalIsToEarly() {
   public void noTripFoundWhenReverseArrivalIsToLate() {
     assertThrows(
       IllegalStateException.class,
-      () -> findTripReverseSearch(busRev(STOP_C, STOP_A, A_BOARD_TIME + 1)),
+      () -> findTripReverseSearch(busRev(STOP_C, STOP_A, A_BOARD_TIME + 1, schedule)),
       "No stops matching 'fromStop'."
     );
   }
@@ -135,7 +136,7 @@ public void noTripFoundWhenReverseArrivalIsToLate() {
   public void noTripFoundWhenArrivalIsWayTooEarly() {
     assertThrows(
       IllegalStateException.class,
-      () -> findTripForwardSearch(busFwd(STOP_A, STOP_C, 0)),
+      () -> findTripForwardSearch(busFwd(STOP_A, STOP_C, 0, schedule)),
       "No stops matching 'toStop'."
     );
   }
@@ -144,7 +145,7 @@ public void noTripFoundWhenArrivalIsWayTooEarly() {
   public void noTripFoundWhenReverseArrivalIsWayTooEarly() {
     assertThrows(
       IllegalStateException.class,
-      () -> findTripReverseSearch(busRev(STOP_C, STOP_A, 10_000)),
+      () -> findTripReverseSearch(busRev(STOP_C, STOP_A, 10_000, schedule)),
       "No stops matching 'fromStop'."
     );
   }
@@ -153,7 +154,7 @@ public void noTripFoundWhenReverseArrivalIsWayTooEarly() {
   public void noTripFoundWhenFromStopIsMissing() {
     assertThrows(
       IllegalStateException.class,
-      () -> findTripForwardSearch(busFwd(STOP_A, STOP_A, C_ALIGHT_LATE)),
+      () -> findTripForwardSearch(busFwd(STOP_A, STOP_A, C_ALIGHT_LATE, schedule)),
       "No stops matching 'fromStop'."
     );
   }
@@ -162,7 +163,7 @@ public void noTripFoundWhenFromStopIsMissing() {
   public void noTripFoundWhenToStopIsMissingInReverseSearch() {
     assertThrows(
       IllegalStateException.class,
-      () -> findTripReverseSearch(busRev(STOP_C, STOP_C, A_BOARD_EARLY)),
+      () -> findTripReverseSearch(busRev(STOP_C, STOP_C, A_BOARD_EARLY, schedule)),
       "No stops matching 'toStop'"
     );
   }
@@ -189,17 +190,17 @@ public void findTripWhenScheduleLoops() {
     // TEST FORWARD SEARCH
     {
       // Board in the 2nd loop at stop 2 and get off at stop 3
-      r = findTripForwardSearch(busFwd(122, 133, 800));
+      r = findTripForwardSearch(busFwd(122, 133, 800, schedule));
       assertEquals(710, r.boardTime());
       assertEquals(800, r.alightTime());
 
       // Board in the 1st loop at stop 4 and get off at stop 3
-      r = findTripForwardSearch(busFwd(144, 133, 800));
+      r = findTripForwardSearch(busFwd(144, 133, 800, schedule));
       assertEquals(410, r.boardTime());
       assertEquals(800, r.alightTime());
 
       // Board in the 1st stop, ride the loop twice, alight at the last stop
-      r = findTripForwardSearch(busFwd(1, 1155, 1100));
+      r = findTripForwardSearch(busFwd(1, 1155, 1100, schedule));
       assertEquals(10, r.boardTime());
       assertEquals(1100, r.alightTime());
     }
@@ -207,40 +208,40 @@ public void findTripWhenScheduleLoops() {
     // TEST REVERSE SEARCH
     {
       // Board in the 2nd loop at stop 2 and get off at stop 3
-      r = findTripReverseSearch(busRev(133, 122, 710));
+      r = findTripReverseSearch(busRev(133, 122, 710, schedule));
       assertEquals(710, r.boardTime());
       assertEquals(800, r.alightTime());
 
       // Board in the 1st loop at stop 4 and get off at stop 3
-      r = findTripReverseSearch(busRev(133, 144, 410));
+      r = findTripReverseSearch(busRev(133, 144, 410, schedule));
       assertEquals(410, r.boardTime());
       assertEquals(800, r.alightTime());
 
       // Board in the 1st stop, ride the loop twice, alight at the last stop
-      r = findTripReverseSearch(busRev(1155, 1, 10));
+      r = findTripReverseSearch(busRev(1155, 1, 10, schedule));
       assertEquals(10, r.boardTime());
       assertEquals(1100, r.alightTime());
     }
   }
 
-  private static Bus busFwd(
+  private static ArrivalView busFwd(
     int accessStop,
     int transitToStop,
     int arrivalTime,
     TestTripSchedule trip
   ) {
-    Access access = new Access(accessStop, -9999, free(accessStop));
-    return new Bus(1, transitToStop, arrivalTime, -9999, trip, access);
+    var access = access(accessStop, -9999, free(accessStop));
+    return bus(1, transitToStop, arrivalTime, -9999, -9999, trip, access);
   }
 
-  private static Bus busRev(
+  private static ArrivalView busRev(
     int accessStop,
     int transitToStop,
     int arrivalTime,
     TestTripSchedule trip
   ) {
-    Access access = new Access(accessStop, -9999, free(accessStop));
-    return new Bus(1, transitToStop, arrivalTime, -9999, trip, access);
+    var access = access(accessStop, -9999, free(accessStop));
+    return bus(1, transitToStop, arrivalTime, -9999, -9999, trip, access);
   }
 
   private void assertForwardAppxTime(
@@ -279,12 +280,4 @@ private void assertTimes(BoardAndAlightTime r, int expBoardTime, int expAlightTi
     assertEquals(timeToStrLong(expBoardTime), timeToStrLong(r.boardTime()));
     assertEquals(timeToStrLong(expAlightTime), timeToStrLong(r.alightTime()));
   }
-
-  private Bus busFwd(int accessToStop, int transitToStop, int arrivalTime) {
-    return busFwd(accessToStop, transitToStop, arrivalTime, schedule);
-  }
-
-  private Bus busRev(int accessToStop, int transitToStop, int arrivalTime) {
-    return busRev(accessToStop, transitToStop, arrivalTime, schedule);
-  }
 }
diff --git a/src/test/java/org/opentripplanner/raptor/spi/UnknownPathTest.java b/src/test/java/org/opentripplanner/raptor/spi/UnknownPathTest.java
index ecb5724b9cd..c1eec607877 100644
--- a/src/test/java/org/opentripplanner/raptor/spi/UnknownPathTest.java
+++ b/src/test/java/org/opentripplanner/raptor/spi/UnknownPathTest.java
@@ -14,7 +14,7 @@ void forwardDirectPath() {
     assertEquals(1200, subject.endTime());
     assertEquals(3, subject.numberOfTransfers());
     assertEquals(900, subject.durationInSeconds());
-    assertEquals("[0:05 0:20 15m 3tx]", subject.toString());
+    assertEquals("[0:05 0:20 15m Tₓ3]", subject.toString());
   }
 
   @Test
@@ -25,6 +25,6 @@ void reversDirectPath() {
     assertEquals(1200, subject.endTime());
     assertEquals(2, subject.numberOfTransfers());
     assertEquals(900, subject.durationInSeconds());
-    assertEquals("[0:05 0:20 15m 2tx]", subject.toString());
+    assertEquals("[0:05 0:20 15m Tₓ2]", subject.toString());
   }
 }
diff --git a/src/test/java/org/opentripplanner/raptor/util/PathStringBuilderTest.java b/src/test/java/org/opentripplanner/raptor/util/PathStringBuilderTest.java
index 7e918a71191..d347f3848e4 100644
--- a/src/test/java/org/opentripplanner/raptor/util/PathStringBuilderTest.java
+++ b/src/test/java/org/opentripplanner/raptor/util/PathStringBuilderTest.java
@@ -64,28 +64,28 @@ public void summary() {
     int START_TIME = time(12, 35, 0);
     int END_TIME = time(13, 45, 0);
     assertEquals(
-      "[12:35 13:45 1h10m 1tx $1.23]",
-      subject.summary(START_TIME, END_TIME, 1, 123).toString()
+      "[12:35 13:45 1h10m Tₓ1 C₁1.23 C₂5]",
+      subject.summary(START_TIME, END_TIME, 1, 123, 5).toString()
     );
   }
 
   @Test
   public void summaryGeneralizedCostOnly() {
-    assertEquals("[$0.01]", subject.summary(1).toString());
+    assertEquals("[C₁0.01]", subject.summary(1).toString());
   }
 
   @Test
   public void path() {
     int egressDuration = 3600 + 37 * 60 + 7;
     assertEquals(
-      "Walk 37s ~ 227 ~ BUS 10:46:05 10:55 ~ 112 ~ Walk 1h37m7s [10:44 12:33 1h49m 0tx $567]",
+      "Walk 37s ~ 227 ~ BUS 10:46:05 10:55 ~ 112 ~ Walk 1h37m7s [10:44 12:33 1h49m Tₓ0 C₁567 C₂7]",
       subject
         .walk(37)
         .stop(227)
         .transit(MODE, T_10_46_05, T_10_55)
         .stop(112)
         .walk(egressDuration)
-        .summary(time(10, 44, 0), time(12, 33, 0), 0, 56700)
+        .summary(time(10, 44, 0), time(12, 33, 0), 0, 56700, 7)
         .toString()
     );
   }
@@ -93,14 +93,14 @@ public void path() {
   @Test
   public void pathWithoutAccessAndEgress() {
     assertEquals(
-      "227 ~ BUS 10:46:05 10:55 ~ 112 [10:46:05 10:55 8m55s 0tx $60 3pz]",
+      "227 ~ BUS 10:46:05 10:55 ~ 112 [10:46:05 10:55 8m55s Tₓ0 C₁60 C₂9 3pz]",
       subject
         .accessEgress(free(227))
         .stop(227)
         .transit(MODE, T_10_46_05, T_10_55)
         .stop(112)
         .accessEgress(free(112))
-        .summary(T_10_46_05, T_10_55, 0, 6000, b -> b.text("3pz"))
+        .summary(T_10_46_05, T_10_55, 0, 6000, 9, b -> b.text("3pz"))
         .toString()
     );
   }
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 3d709a54191..1ce1aec36bd 100644
--- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java
+++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java
@@ -26,6 +26,7 @@
 import org.opentripplanner.ext.emissions.EmissionsService;
 import org.opentripplanner.model.SystemNotice;
 import org.opentripplanner.model.plan.Itinerary;
+import org.opentripplanner.model.plan.Place;
 import org.opentripplanner.model.plan.PlanTestConstants;
 import org.opentripplanner.model.plan.TestItineraryBuilder;
 import org.opentripplanner.routing.alertpatch.StopCondition;
@@ -33,6 +34,7 @@
 import org.opentripplanner.routing.api.response.RoutingError;
 import org.opentripplanner.routing.api.response.RoutingErrorCode;
 import org.opentripplanner.routing.services.TransitAlertService;
+import org.opentripplanner.transit.model._data.TransitModelForTest;
 import org.opentripplanner.transit.model.framework.FeedScopedId;
 
 /**
@@ -41,12 +43,19 @@
  */
 public class ItineraryListFilterChainTest implements PlanTestConstants {
 
+  private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of();
+  private static final Place A = Place.forStop(TEST_MODEL.stop("A").build());
+  private static final Place B = Place.forStop(TEST_MODEL.stop("B").build());
+  private static final Place C = Place.forStop(TEST_MODEL.stop("C").build());
+  private static final Place D = Place.forStop(TEST_MODEL.stop("D").build());
+  private static final Place E = Place.forStop(TEST_MODEL.stop("E").build());
+
   private static final int I3_LATE_START_TIME = T11_33;
+  private static final Duration SW_D10m = Duration.ofSeconds(D10m);
 
   private Itinerary i1;
   private Itinerary i2;
   private Itinerary i3;
-  private Itinerary i4;
 
   @BeforeEach
   public void setUpItineraries() {
@@ -59,9 +68,6 @@ public void setUpItineraries() {
 
     // Not optimal, departure is very late
     i3 = newItinerary(A).bus(20, I3_LATE_START_TIME, I3_LATE_START_TIME + D1m, E).build();
-
-    // car itinerary for emissions test
-    i4 = newItinerary(A).drive(T11_30, PlanTestConstants.T11_50, B).build();
   }
 
   @Test
@@ -75,7 +81,7 @@ public void testDefaultFilterChain() {
   @Test
   public void testFilterChainWithSearchWindowFilterSet() {
     ItineraryListFilterChain chain = createBuilder(false, false, 10)
-      .withSearchWindow(TestItineraryBuilder.newTime(T11_00).toInstant(), Duration.ofMinutes(10))
+      .withSearchWindow(TestItineraryBuilder.newTime(T11_00).toInstant(), SW_D10m)
       .build();
     var result = chain.filter(List.of(i1, i2, i3));
     assertEquals(toStr(List.of(i1)), toStr(result));
@@ -101,7 +107,7 @@ public void withMinBikeParkingDistance() {
   public void testDebugFilterChain() {
     // Given a filter-chain with debugging enabled
     ItineraryListFilterChain chain = createBuilder(false, true, 3)
-      .withSearchWindow(newTime(T11_00).toInstant(), Duration.ofMinutes(6))
+      .withSearchWindow(newTime(T11_00).toInstant(), SW_D10m)
       .build();
 
     // Walk first, then transit sorted on arrival-time
@@ -160,7 +166,6 @@ public void testSameFirstOrLastTripFilter() {
     int ID_3 = 3;
 
     Itinerary i1 = newItinerary(A).bus(ID_1, 0, 50, B).bus(ID_2, 52, 100, C).build();
-
     Itinerary i2 = newItinerary(A).bus(ID_1, 0, 50, B).bus(ID_3, 52, 150, C).build();
 
     List input = List.of(i1, i2);
diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/DeletionFlaggerTestHelper.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/DeletionFlaggerTestHelper.java
deleted file mode 100644
index 2762f54f9de..00000000000
--- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/DeletionFlaggerTestHelper.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.opentripplanner.routing.algorithm.filterchain.deletionflagger;
-
-import java.util.List;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-import org.opentripplanner.model.plan.Itinerary;
-
-public class DeletionFlaggerTestHelper {
-
-  protected static List process(
-    List itineraries,
-    ItineraryDeletionFlagger flagger
-  ) {
-    List filtered = flagger.flagForRemoval(itineraries);
-    return itineraries
-      .stream()
-      .filter(Predicate.not(filtered::contains))
-      .collect(Collectors.toList());
-  }
-}
diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/MaxLimitFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/MaxLimitFilterTest.java
index 78c85b6bdb0..b4418de6c68 100644
--- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/MaxLimitFilterTest.java
+++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/MaxLimitFilterTest.java
@@ -25,27 +25,21 @@ public void name() {
   public void testNormalFilterMaxLimit3() {
     MaxLimitFilter subject = new MaxLimitFilter("Test", 3);
     List itineraries = List.of(i1, i2, i3);
-    assertEquals(
-      toStr(itineraries),
-      toStr(DeletionFlaggerTestHelper.process(itineraries, subject))
-    );
+    assertEquals(toStr(itineraries), toStr(subject.removeMatchesForTest(itineraries)));
   }
 
   @Test
   public void testNormalFilterMaxLimit1() {
     MaxLimitFilter subject = new MaxLimitFilter("Test", 1);
     List itineraries = List.of(i1, i2, i3);
-    assertEquals(
-      toStr(List.of(i1)),
-      toStr(DeletionFlaggerTestHelper.process(itineraries, subject))
-    );
+    assertEquals(toStr(List.of(i1)), toStr(subject.removeMatchesForTest(itineraries)));
   }
 
   @Test
   public void testNormalFilterMaxLimit0() {
     MaxLimitFilter subject = new MaxLimitFilter("Test", 0);
     List itineraries = List.of(i1, i2, i3);
-    var result = DeletionFlaggerTestHelper.process(itineraries, subject);
+    var result = subject.removeMatchesForTest(itineraries);
     assertEquals(toStr(List.of()), toStr(result));
   }
 }
diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilterTest.java
index ead30cfe38c..879edd784b0 100644
--- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilterTest.java
+++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/NumItinerariesFilterTest.java
@@ -8,8 +8,8 @@
 
 import java.util.List;
 import org.junit.jupiter.api.Test;
+import org.opentripplanner.framework.collection.ListSection;
 import org.opentripplanner.model.plan.Itinerary;
-import org.opentripplanner.routing.algorithm.filterchain.ListSection;
 
 public class NumItinerariesFilterTest {
 
@@ -29,7 +29,7 @@ public void name() {
   public void testCropHead() {
     NumItinerariesFilter subject = new NumItinerariesFilter(1, ListSection.HEAD, null);
     List itineraries = List.of(i1, i2, i3);
-    var result = DeletionFlaggerTestHelper.process(itineraries, subject);
+    var result = subject.removeMatchesForTest(itineraries);
     assertEquals(toStr(List.of(i3)), toStr(result));
   }
 
@@ -38,35 +38,19 @@ public void testCropTailAndSubscribe() {
     var subject = new NumItinerariesFilter(2, ListSection.TAIL, it -> subscribeResult = it);
     var itineraries = List.of(i1, i2, i3);
 
-    var processedList = DeletionFlaggerTestHelper.process(itineraries, subject);
+    var processedList = subject.removeMatchesForTest(itineraries);
 
     assertEquals(
       i3.startTime().toInstant().toString(),
-      subscribeResult.earliestRemovedDeparture.toString()
-    );
-    assertEquals(
-      i3.endTime().toInstant().toString(),
-      subscribeResult.earliestRemovedArrival.toString()
-    );
-
-    assertEquals(
-      i3.startTime().toInstant().toString(),
-      subscribeResult.latestRemovedDeparture.toString()
-    );
-    assertEquals(
-      i3.endTime().toInstant().toString(),
-      subscribeResult.latestRemovedArrival.toString()
+      subscribeResult.earliestRemovedDeparture().toString()
     );
 
     assertEquals(
       i3.startTime().toInstant().toString(),
-      subscribeResult.firstRemovedDepartureTime.toString()
+      subscribeResult.latestRemovedDeparture().toString()
     );
 
-    assertEquals(
-      i1.endTime().toInstant().toString(),
-      subscribeResult.earliestKeptArrival.toString()
-    );
+    assertEquals(i2.keyAsString(), subscribeResult.pageCut().keyAsString());
 
     assertEquals(toStr(List.of(i1, i2)), toStr(processedList));
   }
@@ -76,35 +60,19 @@ public void testCropHeadAndSubscribe() {
     var subject = new NumItinerariesFilter(1, ListSection.HEAD, it -> subscribeResult = it);
     var itineraries = List.of(i1, i2, i3);
 
-    var processedList = DeletionFlaggerTestHelper.process(itineraries, subject);
+    var processedList = subject.removeMatchesForTest(itineraries);
 
     assertEquals(
       i2.startTime().toInstant().toString(),
-      subscribeResult.earliestRemovedDeparture.toString()
-    );
-    assertEquals(
-      i1.endTime().toInstant().toString(),
-      subscribeResult.earliestRemovedArrival.toString()
-    );
-
-    assertEquals(
-      i2.startTime().toInstant().toString(),
-      subscribeResult.latestRemovedDeparture.toString()
-    );
-    assertEquals(
-      i2.endTime().toInstant().toString(),
-      subscribeResult.latestRemovedArrival.toString()
+      subscribeResult.earliestRemovedDeparture().toString()
     );
 
     assertEquals(
       i2.startTime().toInstant().toString(),
-      subscribeResult.firstRemovedDepartureTime.toString()
+      subscribeResult.latestRemovedDeparture().toString()
     );
 
-    assertEquals(
-      i3.endTime().toInstant().toString(),
-      subscribeResult.earliestKeptArrival.toString()
-    );
+    assertEquals(i3.keyAsString(), subscribeResult.pageCut().keyAsString());
 
     assertEquals(toStr(List.of(i3)), toStr(processedList));
   }
diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OutsideSearchWindowFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OutsideSearchWindowFilterTest.java
index f4983e1ddbc..207210092b7 100644
--- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OutsideSearchWindowFilterTest.java
+++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/OutsideSearchWindowFilterTest.java
@@ -29,9 +29,9 @@ public class OutsideSearchWindowFilterTest implements PlanTestConstants {
   static List filterOnSearchWindowTestCases() {
     return List.of(
       Arguments.of("Departure time(09:30) matches earliest-departure-time", "09:30", false),
-      Arguments.of("Departure time(09:30) matches latest-departure-time", "09:20", false),
+      Arguments.of("Departure time(09:30) matches latest-departure-time", "09:20:01", false),
       Arguments.of("Departure time(09:30) is before earliest-departure-time", "09:30:01", true),
-      Arguments.of("Departure time(09:30) is after latest-departure-time", "09:19:59", true)
+      Arguments.of("Departure time(09:30) is after latest-departure-time", "09:20", true)
     );
   }
 
diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/PagingFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/PagingFilterTest.java
index 3766f7a6766..965889bdefc 100644
--- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/PagingFilterTest.java
+++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/PagingFilterTest.java
@@ -1,49 +1,70 @@
 package org.opentripplanner.routing.algorithm.filterchain.deletionflagger;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.opentripplanner.framework.collection.ListUtils.first;
+import static org.opentripplanner.framework.collection.ListUtils.last;
 import static org.opentripplanner.model.plan.Itinerary.toStr;
 import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary;
-import static org.opentripplanner.model.plan.TestItineraryBuilder.newTime;
 
-import java.time.Instant;
+import java.util.ArrayList;
 import java.util.List;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.opentripplanner._support.debug.TestDebug;
+import org.opentripplanner.framework.collection.ListSection;
+import org.opentripplanner.framework.time.TimeUtils;
 import org.opentripplanner.model.plan.Itinerary;
+import org.opentripplanner.model.plan.Place;
 import org.opentripplanner.model.plan.PlanTestConstants;
 import org.opentripplanner.model.plan.SortOrder;
-import org.opentripplanner.model.plan.pagecursor.ItineraryPageCut;
-import org.opentripplanner.model.plan.pagecursor.PagingDeduplicationSection;
+import org.opentripplanner.routing.algorithm.filterchain.comparator.SortOrderComparator;
+import org.opentripplanner.transit.model._data.TransitModelForTest;
 
 public class PagingFilterTest implements PlanTestConstants {
 
-  private static final Itinerary early = newItinerary(A).bus(1, T11_04, T11_07, B).build();
+  private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of();
 
-  private static final Itinerary middle = newItinerary(A)
-    .bus(2, T11_03, T11_05, B)
-    .bus(21, T11_07, T11_10, C)
-    .build();
-  private static final Itinerary late = newItinerary(A).bus(3, T11_00, T11_12, B).build();
-  private static final Instant oldSearchWindowEndTime = newTime(T11_05).toInstant();
+  private static final Place A = TEST_MODEL.place("A", 10, 11);
+  private static final Place B = TEST_MODEL.place("B", 10, 13);
+  private static final Place C = TEST_MODEL.place("C", 10, 14);
+  private static final Place D = TEST_MODEL.place("D", 10, 15);
+
+  private static final int EARLY_START = T11_04;
+  private static final int EARLY_END = T11_07;
+  private static final int MIDDLE_START = T11_03;
+  private static final int MIDDLE_END = T11_10;
+  private static final int LATE_START = T11_00;
+  private static final int LATE_END = T11_12;
+
+  /** [11:04, 11:07, $300, Tx0, transit] */
+  private static final Itinerary early = newItinerary(A).bus(1, EARLY_START, EARLY_END, D).build();
+
+  /**  [11:03, 11:10, $636, Tx1, transit] */
+  private static final Itinerary middle = createMiddle();
+
+  /** [11:00, 11:12, $840, Tx0, transit] */
+  private static final Itinerary late = newItinerary(A).bus(3, LATE_START, LATE_END, D).build();
 
   private static PagingFilter pagingFilter;
 
+  /**
+   * This set of itineraries contains all combinations of itineraries with the following values:
+   * 
    + *
  1. departure-time: 10:00 and 10:01
  2. + *
  3. arrival-time: 11:00 and 11:01
  4. + *
  5. number of transfers: zero or one
  6. + *
  7. cost: 5 or 7
  8. + *
  9. mode: car or transit
  10. + *
+ * There are 8 car itineraries and 16 transit (car do not have transfers) = 24 itineraries + */ + private final List allItineraries = allPossibleSortingCombinationsOfItineraries(); + @BeforeEach public void setup() { - pagingFilter = - new PagingFilter( - new ItineraryPageCut( - late.startTime().toInstant(), - oldSearchWindowEndTime, - SortOrder.STREET_AND_ARRIVAL_TIME, - PagingDeduplicationSection.HEAD, - middle.endTime().toInstant(), - middle.startTime().toInstant(), - middle.getGeneralizedCost(), - middle.getNumberOfTransfers(), - false - ) - ); + pagingFilter = new PagingFilter(SortOrder.STREET_AND_ARRIVAL_TIME, ListSection.HEAD, middle); } @Test @@ -55,57 +76,203 @@ public void testName() { public void testPotentialDuplicateMarkedForDeletionWithEarlierArrival() { List itineraries = List.of(early, middle, late); - assertEquals( - toStr(List.of(middle, late)), - toStr(DeletionFlaggerTestHelper.process(itineraries, pagingFilter)) - ); + itineraries.forEach(it -> TestDebug.println(it.keyAsString())); + assertEquals(toStr(List.of(late)), toStr(pagingFilter.removeMatchesForTest(itineraries))); } @Test public void testPotentialDuplicateMarkedForDeletionWithLowerGeneralizedCost() { - Itinerary middleLowCost = newItinerary(A) - .bus(2, T11_03, T11_05, B) - .bus(21, T11_07, T11_10, C) - .build(); - - middleLowCost.setGeneralizedCost(1); + Itinerary middleHighCost = createMiddle(); + middleHighCost.setGeneralizedCost(middle.getGeneralizedCost() + 1); - List itineraries = List.of(middleLowCost, middle, late); + List itineraries = List.of(middleHighCost, middle, late); assertEquals( - toStr(List.of(middle, late)), - toStr(DeletionFlaggerTestHelper.process(itineraries, pagingFilter)) + toStr(List.of(middleHighCost, late)), + toStr(pagingFilter.removeMatchesForTest(itineraries)) ); } @Test public void testPotentialDuplicateMarkedForDeletionWithFewerNumberOfTransfers() { - Itinerary middleNumberOfTransfers = newItinerary(A).bus(21, T11_03, T11_10, C).build(); + int t0 = MIDDLE_START; - middleNumberOfTransfers.setGeneralizedCost(middle.getGeneralizedCost()); + Itinerary middleHighNumberOfTransfers = newItinerary(A) + .bus(21, t0, t0 + D1m, B) + .bus(22, t0 + D2m, t0 + D3m, C) + .bus(23, t0 + D4m, MIDDLE_END, D) + .build(); + + middleHighNumberOfTransfers.setGeneralizedCost(middle.getGeneralizedCost()); - List itineraries = List.of(middleNumberOfTransfers, middle, late); + List itineraries = List.of(middleHighNumberOfTransfers, middle, late); assertEquals( - toStr(List.of(middle, late)), - toStr(DeletionFlaggerTestHelper.process(itineraries, pagingFilter)) + toStr(List.of(middleHighNumberOfTransfers, late)), + toStr(pagingFilter.removeMatchesForTest(itineraries)) ); } @Test public void testPotentialDuplicateMarkedForDeletionWithLaterDepartureTime() { - Itinerary middleLaterDepartureTime = newItinerary(A) - .bus(2, T11_04, T11_05, B) - .bus(21, T11_07, T11_10, C) + int t0 = MIDDLE_START; + Itinerary middleEarlierDepartureTime = newItinerary(A) + .bus(2, t0 - D1m, t0 + D3m, B) + .bus(21, t0 + D4m, MIDDLE_END, C) .build(); - middleLaterDepartureTime.setGeneralizedCost(middle.getGeneralizedCost()); + middleEarlierDepartureTime.setGeneralizedCost(middle.getGeneralizedCost()); - List itineraries = List.of(middleLaterDepartureTime, middle, late); + List itineraries = List.of(middleEarlierDepartureTime, middle, late); assertEquals( - toStr(List.of(middle, late)), - toStr(DeletionFlaggerTestHelper.process(itineraries, pagingFilter)) + toStr(List.of(middleEarlierDepartureTime, late)), + toStr(pagingFilter.removeMatchesForTest(itineraries)) ); } + + @ParameterizedTest + @ValueSource( + ints = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 } + ) + public void testDepartAfterSearchHeadFilter(int index) { + allItineraries.sort(SortOrderComparator.defaultComparatorDepartAfter()); + + // Crop at top of list + var itinerary = allItineraries.get(index); + var f = new PagingFilter(SortOrder.STREET_AND_ARRIVAL_TIME, ListSection.HEAD, itinerary); + + var result = f.removeMatchesForTest(allItineraries); + + result.forEach(it -> TestDebug.println(it.toStr())); + + if (index == 23) { + assertEquals("", toStr(result)); + } else { + assertItineraryEq(allItineraries.get(index + 1), first(result)); + } + } + + @ParameterizedTest + @ValueSource( + ints = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 } + ) + public void testDepartAfterSearchTailFilter(int index) { + allItineraries.sort(SortOrderComparator.defaultComparatorDepartAfter()); + + // Crop at top of list + var itinerary = allItineraries.get(index); + var f = new PagingFilter(SortOrder.STREET_AND_ARRIVAL_TIME, ListSection.TAIL, itinerary); + + var result = f.removeMatchesForTest(allItineraries); + + result.forEach(it -> TestDebug.println(it.toStr())); + + if (index == 0) { + assertEquals("", toStr(result)); + } else { + assertItineraryEq(allItineraries.get(index - 1), last(result)); + } + } + + @ParameterizedTest + @ValueSource( + ints = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 } + ) + public void testArriveBySearchHeadFilter(int index) { + allItineraries.sort(SortOrderComparator.defaultComparatorArriveBy()); + + // Crop at top of list + var itinerary = allItineraries.get(index); + var f = new PagingFilter(SortOrder.STREET_AND_DEPARTURE_TIME, ListSection.HEAD, itinerary); + + var result = f.removeMatchesForTest(allItineraries); + + result.forEach(it -> TestDebug.println(it.toStr())); + + if (index == 23) { + assertEquals("", toStr(result)); + } else { + assertItineraryEq(allItineraries.get(index + 1), first(result)); + } + } + + @ParameterizedTest + @ValueSource( + ints = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 } + ) + public void testArriveBySearchTailFilter(int index) { + allItineraries.sort(SortOrderComparator.defaultComparatorArriveBy()); + + // Crop at top of list + var pageCut = allItineraries.get(index); + var f = new PagingFilter(SortOrder.STREET_AND_DEPARTURE_TIME, ListSection.TAIL, pageCut); + + var result = f.removeMatchesForTest(allItineraries); + + result.forEach(it -> TestDebug.println(it.toStr())); + + if (index == 0) { + assertEquals("", toStr(result)); + } else { + assertItineraryEq(allItineraries.get(index - 1), last(result)); + } + } + + private static List allPossibleSortingCombinationsOfItineraries() { + int tx_0 = 0; + int tx_1 = 1; + boolean car = false; + boolean transit = true; + List itineraries = new ArrayList<>(); + + for (int start : List.of(TimeUtils.time("10:00"), TimeUtils.time("10:01"))) { + for (int end : List.of(TimeUtils.time("11:00"), TimeUtils.time("11:01"))) { + for (int cost : List.of(5, 7)) { + itineraries.add(itinerary(start, end, cost, tx_0, car)); + itineraries.add(itinerary(start, end, cost, tx_0, transit)); + itineraries.add(itinerary(start, end, cost, tx_1, transit)); + } + } + } + return itineraries; + } + + private static Itinerary itinerary( + int departureTime, + int arrivalTime, + int cost, + int nTransfers, + boolean transit + ) { + var builder = newItinerary(A); + + if (transit) { + if (nTransfers == 0) { + builder.bus(10, departureTime, arrivalTime, B); + } else if (nTransfers == 1) { + builder + .bus(20, departureTime, departureTime + 120, B) + .bus(21, departureTime + 240, arrivalTime, B); + } else { + throw new IllegalArgumentException("nTransfers not supported: " + nTransfers); + } + } else { + builder.drive(departureTime, arrivalTime, B); + } + var it = builder.build(); + it.setGeneralizedCost(cost); + return it; + } + + private static Itinerary createMiddle() { + return newItinerary(A) + .bus(2, MIDDLE_START, MIDDLE_START + D2m, B) + .bus(21, MIDDLE_END - D3m, MIDDLE_END, D) + .build(); + } + + private static void assertItineraryEq(Itinerary expected, Itinerary actual) { + assertEquals(expected.keyAsString(), actual.keyAsString()); + } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveBikeRentalWithMostlyWalkingTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveBikeRentalWithMostlyWalkingTest.java index 91e4c1b1454..5fe2bce098b 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveBikeRentalWithMostlyWalkingTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveBikeRentalWithMostlyWalkingTest.java @@ -41,7 +41,7 @@ public void filter() { Assertions.assertEquals( Itinerary.toStr(expected), - Itinerary.toStr(DeletionFlaggerTestHelper.process(input, subject)) + Itinerary.toStr(subject.removeMatchesForTest(input)) ); } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveParkAndRideWithMostlyWalkingTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveParkAndRideWithMostlyWalkingTest.java index 1855f8ddb82..2c65f882473 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveParkAndRideWithMostlyWalkingTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveParkAndRideWithMostlyWalkingTest.java @@ -41,7 +41,7 @@ public void filter() { Assertions.assertEquals( Itinerary.toStr(expected), - Itinerary.toStr(DeletionFlaggerTestHelper.process(input, subject)) + Itinerary.toStr(subject.removeMatchesForTest(input)) ); } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilterTest.java index 931953e013f..9cbba961e71 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilterTest.java @@ -20,12 +20,10 @@ public void filterAwayNothingIfNoWalking() { Itinerary i2 = newItinerary(A).rail(110, 6, 9, E).build(); // When: - List result = DeletionFlaggerTestHelper.process( - List.of(i1, i2), - new RemoveTransitIfStreetOnlyIsBetterFilter( - CostLinearFunction.of(Duration.ofSeconds(200), 1.2) - ) + ItineraryDeletionFlagger flagger = new RemoveTransitIfStreetOnlyIsBetterFilter( + CostLinearFunction.of(Duration.ofSeconds(200), 1.2) ); + List result = flagger.removeMatchesForTest(List.of(i1, i2)); // Then: assertEquals(toStr(List.of(i1, i2)), toStr(result)); @@ -50,12 +48,10 @@ public void filterAwayLongTravelTimeWithoutWaitTime() { i2.setGeneralizedCost(360); // When: - List result = DeletionFlaggerTestHelper.process( - List.of(i2, bicycle, walk, i1), - new RemoveTransitIfStreetOnlyIsBetterFilter( - CostLinearFunction.of(Duration.ofSeconds(60), 1.2) - ) + ItineraryDeletionFlagger flagger = new RemoveTransitIfStreetOnlyIsBetterFilter( + CostLinearFunction.of(Duration.ofSeconds(60), 1.2) ); + List result = flagger.removeMatchesForTest(List.of(i2, bicycle, walk, i1)); // Then: assertEquals(toStr(List.of(bicycle, walk, i1)), toStr(result)); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterTest.java index b68ac9c46a2..9b008993ba3 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterTest.java @@ -4,7 +4,6 @@ import static org.opentripplanner.model.plan.Itinerary.toStr; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import java.time.Duration; import java.util.List; import org.junit.jupiter.api.Test; import org.opentripplanner.model.plan.Itinerary; @@ -19,10 +18,8 @@ public void filterAwayNothingIfNoWalking() { Itinerary i2 = newItinerary(A).rail(110, 6, 9, E).build(); // When: - List result = DeletionFlaggerTestHelper.process( - List.of(i1, i2), - new RemoveTransitIfWalkingIsBetterFilter() - ); + List result = new RemoveTransitIfWalkingIsBetterFilter() + .removeMatchesForTest(List.of(i1, i2)); // Then: assertEquals(toStr(List.of(i1, i2)), toStr(result)); @@ -42,10 +39,8 @@ 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 = DeletionFlaggerTestHelper.process( - List.of(i1, i2, bicycle, walk), - new RemoveTransitIfWalkingIsBetterFilter() - ); + List result = new RemoveTransitIfWalkingIsBetterFilter() + .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/deletionflagger/RemoveWalkOnlyFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveWalkOnlyFilterTest.java index 806859b9081..62029ca389f 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveWalkOnlyFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveWalkOnlyFilterTest.java @@ -38,9 +38,6 @@ public void filter() { var input = List.of(t1, w1, t2, w2, t3, t4); var expected = List.of(t1, t2, t3, t4); - assertEquals( - Itinerary.toStr(expected), - Itinerary.toStr(DeletionFlaggerTestHelper.process(input, subject)) - ); + assertEquals(Itinerary.toStr(expected), Itinerary.toStr(subject.removeMatchesForTest(input))); } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/TransitGeneralizedCostFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/TransitGeneralizedCostFilterTest.java index 0f93f44cdc3..37faa5f2feb 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/TransitGeneralizedCostFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/TransitGeneralizedCostFilterTest.java @@ -38,10 +38,7 @@ public void filterWithoutWaitCost() { var all = List.of(i1, i2, i3, i4); // Expect - i4 to be dropped - assertEquals( - toStr(List.of(i1, i2, i3)), - toStr(DeletionFlaggerTestHelper.process(all, subject)) - ); + assertEquals(toStr(List.of(i1, i2, i3)), toStr(subject.removeMatchesForTest(all))); } @Test @@ -69,10 +66,7 @@ public void filterWithWaitCostSameDepartureTime() { var all = List.of(i1, i2, i3, i4); // Expect - i4 to be dropped - assertEquals( - toStr(List.of(i1, i2, i3)), - toStr(DeletionFlaggerTestHelper.process(all, subject)) - ); + assertEquals(toStr(List.of(i1, i2, i3)), toStr(subject.removeMatchesForTest(all))); } @Test @@ -100,9 +94,6 @@ public void filterWithWaitCostDifferentDepartureTime() { var all = List.of(i1, i2, i3, i4); // Expect - i3 to be dropped - assertEquals( - toStr(List.of(i1, i2, i4)), - toStr(DeletionFlaggerTestHelper.process(all, subject)) - ); + assertEquals(toStr(List.of(i1, i2, i4)), toStr(subject.removeMatchesForTest(all))); } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/mapping/PagingServiceFactoryTest.java b/src/test/java/org/opentripplanner/routing/algorithm/mapping/PagingServiceFactoryTest.java new file mode 100644 index 00000000000..369f58d6ef1 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/PagingServiceFactoryTest.java @@ -0,0 +1,99 @@ +package org.opentripplanner.routing.algorithm.mapping; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.time.DurationUtils; +import org.opentripplanner.framework.time.TimeUtils; +import org.opentripplanner.raptor._data.transit.TestAccessEgress; +import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; +import org.opentripplanner.raptor.api.request.RaptorTuningParameters; +import org.opentripplanner.raptor.api.request.SearchParams; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitTuningParameters; +import org.opentripplanner.routing.api.request.RouteRequest; + +class PagingServiceFactoryTest { + + private static final Instant TRANSIT_START_TIME = Instant.parse("2023-11-11T00:00:00Z"); + private static final int EDT = TimeUtils.time("10:00"); + private static final int LAT = TimeUtils.time("11:30"); + + private static final Instant EXP_EDT = Instant.parse("2023-11-11T10:00:00Z"); + private static final Instant EXP_LAT = Instant.parse("2023-11-11T11:30:00Z"); + + private static final Duration SEARCH_WINDOW = DurationUtils.duration("2h30m"); + private static final SearchParams SEARCH_PARAMS_DEPART_AFTER = new RaptorRequestBuilder<>() + .searchParams() + .earliestDepartureTime(EDT) + .addAccessPaths(TestAccessEgress.walk(1, 1)) + .addEgressPaths(TestAccessEgress.walk(1, 1)) + .build() + .searchParams(); + private static final SearchParams SEARCH_PARAMS_ARRIVE_BY = new RaptorRequestBuilder<>() + .searchParams() + .latestArrivalTime(LAT) + .addAccessPaths(TestAccessEgress.walk(1, 1)) + .addEgressPaths(TestAccessEgress.walk(1, 1)) + .build() + .searchParams(); + private static final SearchParams SEARCH_PARAMS_ALL = new RaptorRequestBuilder<>() + .searchParams() + .earliestDepartureTime(EDT) + .latestArrivalTime(LAT) + .searchWindow(SEARCH_WINDOW) + .addAccessPaths(TestAccessEgress.walk(1, 1)) + .addEgressPaths(TestAccessEgress.walk(1, 1)) + .build() + .searchParams(); + + @Test + void createPagingService() { + var subject = PagingServiceFactory.createPagingService( + TRANSIT_START_TIME, + TransitTuningParameters.FOR_TEST, + new RaptorTuningParameters() {}, + new RouteRequest(), + SEARCH_PARAMS_ALL, + null, + List.of() + ); + assertEquals( + "PagingService{" + + "searchWindowUsed: 2h30m, " + + "earliestDepartureTime: 2023-11-11T10:00:00Z, " + + "latestArrivalTime: 2023-11-11T11:30:00Z, " + + "itinerariesSortOrder: STREET_AND_ARRIVAL_TIME, " + + "numberOfItineraries: 50" + + "}", + subject.toString() + ); + } + + @Test + void searchWindowOf() { + assertNull(PagingServiceFactory.searchWindowOf(null)); + assertNull(PagingServiceFactory.searchWindowOf(SEARCH_PARAMS_DEPART_AFTER)); + assertNull(PagingServiceFactory.searchWindowOf(SEARCH_PARAMS_ARRIVE_BY)); + assertEquals(SEARCH_WINDOW, PagingServiceFactory.searchWindowOf(SEARCH_PARAMS_ALL)); + } + + @Test + void testEarliestDepartureTime() { + assertNull(PagingServiceFactory.edt(TRANSIT_START_TIME, null)); + assertNull(PagingServiceFactory.edt(TRANSIT_START_TIME, SEARCH_PARAMS_ARRIVE_BY)); + assertEquals(EXP_EDT, PagingServiceFactory.edt(TRANSIT_START_TIME, SEARCH_PARAMS_DEPART_AFTER)); + assertEquals(EXP_EDT, PagingServiceFactory.edt(TRANSIT_START_TIME, SEARCH_PARAMS_ALL)); + } + + @Test + void testLatestDepartureTime() { + assertNull(PagingServiceFactory.lat(TRANSIT_START_TIME, null)); + assertNull(PagingServiceFactory.lat(TRANSIT_START_TIME, SEARCH_PARAMS_DEPART_AFTER)); + assertEquals(EXP_LAT, PagingServiceFactory.lat(TRANSIT_START_TIME, SEARCH_PARAMS_ARRIVE_BY)); + assertEquals(EXP_LAT, PagingServiceFactory.lat(TRANSIT_START_TIME, SEARCH_PARAMS_ALL)); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java index 06220c22858..ffd2a5f8591 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java @@ -90,8 +90,8 @@ public void createItineraryTestZeroDurationEgress(int LAST_LEG_COST) { RaptorPath path = createTestTripSchedulePath(getTestTripSchedule()) .egress(TestAccessEgress.free(2, RaptorCostConverter.toRaptorCost(LAST_LEG_COST))); - int transitLegCost = path.accessLeg().nextLeg().generalizedCost(); - int egressLegCost = path.accessLeg().nextLeg().nextLeg().generalizedCost(); + int transitLegCost = path.accessLeg().nextLeg().c1(); + int egressLegCost = path.accessLeg().nextLeg().nextLeg().c1(); // Act var itinerary = mapper.createItinerary(path); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/mapping/RoutingResponseMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/mapping/RoutingResponseMapperTest.java deleted file mode 100644 index 646eedd628a..00000000000 --- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/RoutingResponseMapperTest.java +++ /dev/null @@ -1,126 +0,0 @@ -package org.opentripplanner.routing.algorithm.mapping; - -import static java.time.ZoneOffset.UTC; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.opentripplanner.model.plan.PlanTestConstants.A; -import static org.opentripplanner.model.plan.PlanTestConstants.B; - -import java.time.Duration; -import java.time.ZonedDateTime; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.opentripplanner.framework.time.TimeUtils; -import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.model.plan.SortOrder; -import org.opentripplanner.model.plan.TestItineraryBuilder; -import org.opentripplanner.model.plan.pagecursor.PageType; -import org.opentripplanner.raptor._data.transit.TestTripSchedule; -import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; -import org.opentripplanner.raptor.api.request.SearchParams; -import org.opentripplanner.routing.algorithm.filterchain.ListSection; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.NumItinerariesFilterResults; - -public class RoutingResponseMapperTest { - - static final ZonedDateTime TRANSIT_TIME_ZERO = TestItineraryBuilder.SERVICE_DAY.atStartOfDay(UTC); - - static final int T12_00 = TimeUtils.hm2time(12, 0); - static final int T12_30 = TimeUtils.hm2time(12, 30); - static final int T13_00 = TimeUtils.hm2time(13, 0); - static final int T13_30 = TimeUtils.hm2time(13, 30); - - static final Duration D1H = Duration.ofHours(1); - static final Duration D90M = Duration.ofMinutes(90); - - private static final SearchParams SEARCH_PARAMS = new RaptorRequestBuilder() - .searchParams() - .earliestDepartureTime(T12_00) - .latestArrivalTime(T13_30) - .searchWindow(D1H) - .buildSearchParam(); - - private static final Itinerary REMOVED_ITINERARY = TestItineraryBuilder - .newItinerary(A, T12_30) - .bus(1, T12_30, T13_00, B) - .build(); - - private static final Itinerary KEPT_ITINERARY = TestItineraryBuilder - .newItinerary(A, T12_00) - .bus(1, T12_00, T12_30, B) - .build(); - - @Test - public void mapIntoPageCursorFactoryNoTransitSearchParams() { - var factory = RoutingResponseMapper.mapIntoPageCursorFactory( - SortOrder.STREET_AND_ARRIVAL_TIME, - TRANSIT_TIME_ZERO, - null, - null, - null, - PageType.NEXT_PAGE - ); - - assertNull(factory.nextPageCursor()); - assertNull(factory.previousPageCursor()); - } - - @Test - void mapIntoPageCursorFactoryWithSearchParamsNoItineraryRemoved() { - var factory = RoutingResponseMapper.mapIntoPageCursorFactory( - SortOrder.STREET_AND_ARRIVAL_TIME, - TRANSIT_TIME_ZERO, - SEARCH_PARAMS, - D90M, - null, - PageType.NEXT_PAGE - ); - - // There is no way to access the internals of the factory, so we use the toString() - assertEquals( - "PageCursorFactory{" + - "sortOrder: STREET_AND_ARRIVAL_TIME, " + - "currentPageType: NEXT_PAGE, " + - "current: SearchTime{edt: 2020-02-02T12:00:00Z, lat: 2020-02-02T13:30:00Z}, " + - "currentSearchWindow: 1h, " + - "newSearchWindow: 1h30m" + - "}", - factory.toString() - ); - } - - @Test - public void testArriveByReversedRemovedInsidePreviousPage() { - var factory = RoutingResponseMapper.mapIntoPageCursorFactory( - SortOrder.STREET_AND_DEPARTURE_TIME, - TRANSIT_TIME_ZERO, - SEARCH_PARAMS, - D90M, - new NumItinerariesFilterResults( - List.of(KEPT_ITINERARY), - List.of(REMOVED_ITINERARY), - ListSection.TAIL - ), - PageType.NEXT_PAGE - ); - - // There is no way to access the internals of the factory, so we use the toString() - assertEquals( - "PageCursorFactory{" + - "sortOrder: STREET_AND_DEPARTURE_TIME, " + - "currentPageType: NEXT_PAGE, " + - "current: SearchTime{edt: 2020-02-02T12:00:00Z, lat: 2020-02-02T13:30:00Z}, " + - "currentSearchWindow: 1h, " + - "newSearchWindow: 1h30m, " + - "searchWindowCropped, " + - "pageCursorFactoryParams: " + - new NumItinerariesFilterResults( - List.of(KEPT_ITINERARY), - List.of(REMOVED_ITINERARY), - ListSection.TAIL - ) + - "}", - factory.toString() - ); - } -} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap b/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap index 8f527b15bf7..31d59483807 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap +++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap @@ -106,7 +106,7 @@ org.opentripplanner.routing.algorithm.mapping.CarSnapshotTest.directCarPark=[ "hasWheelchairAccessibleCarPlaces" : false, "id" : "OSM:OSMWay/-102488", "name" : "P+R", - "realtime" : false, + "realTime" : false, "tags" : [ "osm:amenity=parking" ] @@ -137,7 +137,7 @@ org.opentripplanner.routing.algorithm.mapping.CarSnapshotTest.directCarPark=[ "hasWheelchairAccessibleCarPlaces" : false, "id" : "OSM:OSMWay/-102488", "name" : "P+R", - "realtime" : false, + "realTime" : false, "tags" : [ "osm:amenity=parking" ] diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressPenaltyDecoratorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressPenaltyDecoratorTest.java index 23dfc03b252..e063ddfbb14 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressPenaltyDecoratorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressPenaltyDecoratorTest.java @@ -42,9 +42,12 @@ class AccessEgressPenaltyDecoratorTest { @BeforeAll static void verifyTestSetup() { - assertEquals("Walk 15m38s $238035 w/penalty(13m23s $1606) ~ 1", EXP_WALK_W_PENALTY.toString()); assertEquals( - "Walk 11m53s $237887 w/penalty(11m8s $1336) ~ 1", + "Walk 15m38s C₁238_035 w/penalty(13m23s $1606) ~ 1", + EXP_WALK_W_PENALTY.toString() + ); + assertEquals( + "Walk 11m53s C₁237_887 w/penalty(11m8s $1336) ~ 1", EXP_CAR_RENTAL_W_PENALTY.toString() ); } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java index f19a19d5060..a9e088a2ad3 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java @@ -46,8 +46,8 @@ void stop() { void generalizedCost() { // TODO - The value is ? int expected = 23642959; - assertEquals(expected, subject.generalizedCost()); - assertEquals(expected + COST_PENALTY.toCentiSeconds(), subjectWithPenalty.generalizedCost()); + assertEquals(expected, subject.c1()); + assertEquals(expected + COST_PENALTY.toCentiSeconds(), subjectWithPenalty.c1()); } @Test @@ -129,8 +129,8 @@ void timeShiftDepartureTimeToActualTime() { @Test void testToString() { - assertEquals("Walk 1d8h50m15s $236429 ~ 5", subject.toString()); - assertEquals("Walk 1d8h50m16s $236440 w/penalty(1s $11) ~ 5", subjectWithPenalty.toString()); + assertEquals("Walk 1d8h50m15s C₁236_429 ~ 5", subject.toString()); + assertEquals("Walk 1d8h50m16s C₁236_440 w/penalty(1s $11) ~ 5", subjectWithPenalty.toString()); } @Test diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransferTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransferTest.java index b1e2b881b1a..dacd0dcac9c 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransferTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransferTest.java @@ -22,7 +22,7 @@ class TransferTest { Coordinates.BERLIN_BRANDENBURG_GATE ); private static final IntersectionVertex BOSTON_V = intersectionVertex(Coordinates.BOSTON); - private static final int MAX_RAPTOR_TRANSFER_COST = RaptorCostConverter.toRaptorCost( + private static final int MAX_RAPTOR_TRANSFER_C1 = RaptorCostConverter.toRaptorCost( Transfer.MAX_TRANSFER_COST ); @@ -87,10 +87,10 @@ void allowLowCost() { } private static void assertMaxCost(RaptorTransfer transfer) { - assertEquals(MAX_RAPTOR_TRANSFER_COST, transfer.generalizedCost()); + assertEquals(MAX_RAPTOR_TRANSFER_C1, transfer.c1()); } private static void assertBelowMaxCost(RaptorTransfer transfer) { - assertTrue(MAX_RAPTOR_TRANSFER_COST > transfer.generalizedCost()); + assertTrue(MAX_RAPTOR_TRANSFER_C1 > transfer.c1()); } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/ConstrainedBoardingSearchTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/ConstrainedBoardingSearchTest.java index ec7edf3bda6..214ea55eb3c 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/ConstrainedBoardingSearchTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/constrainedtransfer/ConstrainedBoardingSearchTest.java @@ -110,7 +110,7 @@ public class ConstrainedBoardingSearchTest { @BeforeEach void setup() { route1 = - new TestRouteData( + TestRouteData.of( "R1", TransitMode.RAIL, List.of(STOP_A, STOP_B, STOP_C), @@ -119,7 +119,7 @@ void setup() { ); route2 = - new TestRouteData( + TestRouteData.of( "R2", TransitMode.BUS, List.of(STOP_B, STOP_C, STOP_D), diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/RaptorCostConverterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/RaptorCostConverterTest.java index cf57617cc6c..3827bdd31b6 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/RaptorCostConverterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/RaptorCostConverterTest.java @@ -40,6 +40,6 @@ public void toRaptorCosts() { @Test public void testToString() { - assertEquals("$120", RaptorCostConverter.toString(12_000)); + assertEquals("C₁120", RaptorCostConverter.toString(12_000)); } } 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/TransitPriorityGroup32nTest.java new file mode 100644 index 00000000000..85083a3ee6a --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitPriorityGroup32nTest.java @@ -0,0 +1,67 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.raptor.api.request.RaptorTransitPriorityGroupCalculator; + +class TransitPriorityGroup32nTest { + + private static final int GROUP_INDEX_0 = 0; + private static final int GROUP_INDEX_1 = 1; + private static final int GROUP_INDEX_2 = 2; + 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(); + + @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)); + + assertThrows(IllegalArgumentException.class, () -> TransitPriorityGroup32n.groupId(-1)); + assertThrows(IllegalArgumentException.class, () -> TransitPriorityGroup32n.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)); + assertEqualsHex( + GROUP_0 | GROUP_1 | GROUP_2 | GROUP_30 | GROUP_31, + subjct.mergeTransitPriorityGroupIds(GROUP_0 | GROUP_1 | GROUP_2 | GROUP_30, GROUP_31) + ); + } + + @Test + void dominanceFunction() { + assertFalse(subjct.dominanceFunction().leftDominateRight(GROUP_0, GROUP_0)); + assertFalse(subjct.dominanceFunction().leftDominateRight(GROUP_31, GROUP_31)); + assertFalse(subjct.dominanceFunction().leftDominateRight(GROUP_1 | GROUP_2, GROUP_1 | GROUP_2)); + + assertTrue(subjct.dominanceFunction().leftDominateRight(GROUP_0, GROUP_1)); + assertFalse(subjct.dominanceFunction().leftDominateRight(GROUP_1, GROUP_0)); + + assertTrue(subjct.dominanceFunction().leftDominateRight(GROUP_1, GROUP_1 | GROUP_2)); + assertFalse(subjct.dominanceFunction().leftDominateRight(GROUP_1 | GROUP_2, GROUP_1)); + } + + static void assertEqualsHex(int expected, int actual) { + assertEquals(expected, actual, "%08x == %08x".formatted(expected, actual)); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java new file mode 100644 index 00000000000..11c59030180 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java @@ -0,0 +1,36 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +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.routing.api.request.framework.CostLinearFunction; + +class RaptorRequestMapperTest { + + private static final CostLinearFunction R1 = CostLinearFunction.of("50 + 1.0x"); + private static final CostLinearFunction R2 = CostLinearFunction.of("0 + 1.5x"); + private static final CostLinearFunction R3 = CostLinearFunction.of("30 + 2.0x"); + + static List testCasesRelaxedCost() { + return List.of( + Arguments.of(CostLinearFunction.NORMAL, 0, 0), + Arguments.of(CostLinearFunction.NORMAL, 10, 10), + Arguments.of(R1, 0, 5000), + Arguments.of(R1, 7, 5007), + Arguments.of(R2, 0, 0), + Arguments.of(R2, 100, 150), + Arguments.of(R3, 0, 3000), + Arguments.of(R3, 100, 3200) + ); + } + + @ParameterizedTest + @MethodSource("testCasesRelaxedCost") + void mapRelaxCost(CostLinearFunction input, int cost, int expected) { + var calcCost = RaptorRequestMapper.mapRelaxCost(input); + assertEquals(expected, calcCost.relax(cost)); + } +} 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 new file mode 100644 index 00000000000..4cd2b65e8c3 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfiguratorTest.java @@ -0,0 +1,87 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.transit.request; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TestTransitCaseData.STOP_A; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TestTransitCaseData.STOP_B; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TestTransitCaseData.STOP_D; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.routing.api.request.request.filter.TransitPriorityGroupSelect; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.network.RoutingTripPattern; + +class PriorityGroupConfiguratorTest { + + private final TestRouteData routeA = TestRouteData.of( + "R1", + TransitMode.RAIL, + List.of(STOP_A, STOP_B), + "10:00 10:10" + ); + private final TestRouteData routeB = TestRouteData.of( + "B2", + TransitMode.BUS, + List.of(STOP_B, STOP_D), + "10:15 10:40" + ); + private final TestRouteData routeC = TestRouteData.of( + "R3", + TransitMode.RAIL, + List.of(STOP_A, STOP_B), + "10:00 10:10" + ); + private final TestRouteData routeD = TestRouteData.of( + "R3", + TransitMode.FERRY, + List.of(STOP_A, STOP_B), + "10:00 10:10" + ); + + private final RoutingTripPattern railA = routeA.getTripPattern().getRoutingTripPattern(); + private final RoutingTripPattern busB = routeB.getTripPattern().getRoutingTripPattern(); + private final RoutingTripPattern railC = routeC.getTripPattern().getRoutingTripPattern(); + private final RoutingTripPattern ferryC = routeD.getTripPattern().getRoutingTripPattern(); + + @Test + void emptyConfigurationShouldReturnGroupZero() { + var subject = PriorityGroupConfigurator.of(List.of(), List.of()); + assertEquals(0, subject.lookupTransitPriorityGroupId(railA)); + assertEquals(0, subject.lookupTransitPriorityGroupId(busB)); + assertEquals(0, subject.lookupTransitPriorityGroupId(null)); + } + + @Test + void lookupTransitPriorityGroupIdBySameAgency() { + var subject = PriorityGroupConfigurator.of( + List.of( + TransitPriorityGroupSelect.of().addModes(List.of(TransitMode.BUS)).build(), + TransitPriorityGroupSelect.of().addModes(List.of(TransitMode.RAIL)).build() + ), + List.of() + ); + + assertEquals(0, subject.lookupTransitPriorityGroupId(null)); + assertEquals(0, subject.lookupTransitPriorityGroupId(ferryC)); + assertEquals(1, subject.lookupTransitPriorityGroupId(railA)); + assertEquals(2, subject.lookupTransitPriorityGroupId(busB)); + assertEquals(1, subject.lookupTransitPriorityGroupId(railC)); + } + + @Test + 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() + ) + ); + + assertEquals(0, subject.lookupTransitPriorityGroupId(null)); + assertEquals(0, subject.lookupTransitPriorityGroupId(ferryC)); + assertEquals(2, subject.lookupTransitPriorityGroupId(railA)); + assertEquals(1, subject.lookupTransitPriorityGroupId(busB)); + assertEquals(2, subject.lookupTransitPriorityGroupId(railC)); + } +} 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 new file mode 100644 index 00000000000..1205b6b2205 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcherTest.java @@ -0,0 +1,118 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.transit.request; + +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 java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.routing.api.request.request.filter.TransitPriorityGroupSelect; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.network.TripPattern; + +class PriorityGroupMatcherTest { + + private final TestRouteData r1 = TestRouteData.rail("R1").withAgency("A1").build(); + private final TestRouteData b1 = TestRouteData.bus("B2").withAgency("A2").build(); + private final TestRouteData f1 = TestRouteData + .ferry("F1") + .withAgency("A1") + .withSubmode("localFerry") + .build(); + + private final TripPattern rail1 = r1.getTripPattern(); + private final TripPattern bus = b1.getTripPattern(); + private final TripPattern ferry = f1.getTripPattern(); + private final FeedScopedId r1agencyId = rail1.getRoute().getAgency().getId(); + private final FeedScopedId r1routeId = rail1.getRoute().getId(); + private final FeedScopedId anyId = new FeedScopedId("F", "ANY"); + + @Test + void testMode() { + var m = PriorityGroupMatcher.of( + TransitPriorityGroupSelect.of().addModes(List.of(TransitMode.BUS, TransitMode.TRAM)).build() + ); + assertEquals("Mode(BUS | TRAM)", m.toString()); + assertFalse(m.isEmpty()); + assertTrue(m.match(bus)); + assertFalse(m.match(rail1)); + assertFalse(m.match(ferry)); + } + + @Test + void testAgencyIds() { + var matchers = List.of( + PriorityGroupMatcher.of( + TransitPriorityGroupSelect.of().addAgencyIds(List.of(r1agencyId)).build() + ), + PriorityGroupMatcher.of( + TransitPriorityGroupSelect.of().addAgencyIds(List.of(r1agencyId, anyId)).build() + ) + ); + + assertEquals("AgencyId(F:A1)", matchers.get(0).toString()); + assertEquals("AgencyId(F:A1 | F:ANY)", matchers.get(1).toString()); + + for (PriorityGroupMatcher m : matchers) { + assertFalse(m.isEmpty()); + assertTrue(m.match(rail1)); + assertTrue(m.match(ferry)); + assertFalse(m.match(bus)); + } + } + + @Test + void routeIds() { + var matchers = List.of( + PriorityGroupMatcher.of( + TransitPriorityGroupSelect.of().addRouteIds(List.of(r1routeId)).build() + ), + PriorityGroupMatcher.of( + TransitPriorityGroupSelect.of().addRouteIds(List.of(r1routeId, anyId)).build() + ) + ); + + assertEquals("RouteId(F:R1)", matchers.get(0).toString()); + assertEquals("RouteId(F:R1 | F:ANY)", matchers.get(1).toString()); + + for (PriorityGroupMatcher m : matchers) { + assertFalse(m.isEmpty()); + assertTrue(m.match(rail1)); + assertFalse(m.match(ferry)); + assertFalse(m.match(bus)); + } + } + + @Test + void testSubMode() { + var subject = PriorityGroupMatcher.of( + TransitPriorityGroupSelect.of().addSubModeRegexp(List.of(".*local.*")).build() + ); + + assertEquals("SubModeRegexp(.*local.*)", subject.toString()); + + assertFalse(subject.isEmpty()); + assertFalse(subject.match(rail1)); + assertTrue(subject.match(ferry)); + assertFalse(subject.match(bus)); + } + + @Test + void testToString() { + var m = PriorityGroupMatcher.of( + TransitPriorityGroupSelect + .of() + .addModes(List.of(TransitMode.BUS, TransitMode.TRAM)) + .addAgencyIds(List.of(anyId, r1agencyId)) + .addRouteIds(List.of(r1routeId)) + .addSubModeRegexp(List.of(".*local.*")) + .build() + ); + + assertEquals( + "(Mode(BUS | TRAM) | SubModeRegexp(.*local.*) | AgencyId(F:A1 | F:ANY) | RouteId(F:R1))", + m.toString() + ); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java index 20ca21a50a6..ed71b3de400 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java @@ -6,7 +6,6 @@ import java.time.LocalDate; import java.time.ZonedDateTime; import java.util.ArrayList; -import java.util.Arrays; import java.util.BitSet; import java.util.List; import org.junit.jupiter.api.Test; @@ -16,13 +15,12 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripPatternForDate; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.basic.TransitMode; -import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.RoutingTripPattern; import org.opentripplanner.transit.model.network.StopPattern; import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.timetable.ScheduledTripTimes; import org.opentripplanner.transit.model.timetable.TripTimes; -import org.opentripplanner.transit.model.timetable.TripTimesFactory; public class RaptorRoutingRequestTransitDataCreatorTest { @@ -66,7 +64,8 @@ public void testMergeTripPatterns() { List combinedTripPatterns = RaptorRoutingRequestTransitDataCreator.merge( startOfTime, tripPatternsForDates, - new TestTransitDataProviderFilter() + new TestTransitDataProviderFilter(), + PriorityGroupConfigurator.empty() ); // Get the results @@ -98,21 +97,15 @@ private static TripPatternForDates findTripPatternForDate( } private TripTimes createTripTimesForTest() { - StopTime stopTime1 = new StopTime(); - StopTime stopTime2 = new StopTime(); - - stopTime1.setDepartureTime(0); - stopTime2.setArrivalTime(7200); - - return TripTimesFactory.tripTimes( - TransitModelForTest.trip("Test").build(), - Arrays.asList(stopTime1, stopTime2), - new Deduplicator() - ); + return ScheduledTripTimes + .of() + .withTrip(TransitModelForTest.trip("Test").build()) + .withDepartureTimes("00:00 02:00") + .build(); } /** - * Utility function to create bare minimum of valid StopTime with no interesting attributes + * Utility function to create bare minimum of valid StopTime * * @return StopTime instance */ diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java index 86d5f9404ed..4823ea84300 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java @@ -14,7 +14,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ValueSource; -import org.opentripplanner.ext.transmodelapi.model.TransmodelTransportSubmode; +import org.opentripplanner.apis.transmodel.model.TransmodelTransportSubmode; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopTime; @@ -40,6 +40,7 @@ import org.opentripplanner.transit.model.network.StopPattern; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.site.RegularStop; +import org.opentripplanner.transit.model.timetable.RealTimeTripTimes; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripAlteration; import org.opentripplanner.transit.model.timetable.TripBuilder; @@ -513,7 +514,7 @@ void keepAccessibleTrip() { @Test void keepRealTimeAccessibleTrip() { - TripTimes realTimeWheelchairAccessibleTrip = createTestTripTimes( + RealTimeTripTimes realTimeWheelchairAccessibleTrip = createTestTripTimes( TRIP_ID, ROUTE, BikeAccess.NOT_ALLOWED, @@ -616,7 +617,7 @@ void includeRealtimeCancellationsTest() { TripAlteration.PLANNED ); - TripTimes tripTimesWithCancellation = createTestTripTimes( + RealTimeTripTimes tripTimesWithCancellation = createTestTripTimes( TRIP_ID, ROUTE, BikeAccess.NOT_ALLOWED, @@ -860,7 +861,7 @@ private List combinedFilterForModesAndBannedAgencies( ); } - private TripTimes createTestTripTimes( + private RealTimeTripTimes createTestTripTimes( FeedScopedId tripId, Route route, BikeAccess bikeAccess, diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java index a7a0c2fd160..f57f03705fd 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java @@ -2,6 +2,8 @@ import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TestTransitCaseData.DATE; import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TestTransitCaseData.OFFSET; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TestTransitCaseData.STOP_A; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TestTransitCaseData.STOP_B; import java.util.ArrayList; import java.util.Arrays; @@ -39,13 +41,13 @@ public class TestRouteData { private final TripPattern tripPattern; private Trip currentTrip; - public TestRouteData(String route, TransitMode mode, List stops, String... times) { + public TestRouteData(Route route, List stops, List times) { final Deduplicator deduplicator = new Deduplicator(); - this.route = TransitModelForTest.route(route).withMode(mode).withShortName(route).build(); + this.route = route; this.trips = - Arrays - .stream(times) - .map(it -> parseTripInfo(route, it, stops, deduplicator)) + times + .stream() + .map(it -> parseTripInfo(route.getName(), it, stops, deduplicator)) .collect(Collectors.toList()); List stopTimesFistTrip = firstTrip().getStopTimes(); @@ -72,7 +74,8 @@ public TestRouteData(String route, TransitMode mode, List stops, St }, new int[] { OFFSET }, null, - null + null, + 0 ); int id = 0; for (Trip trip : trips) { @@ -84,6 +87,35 @@ public TestRouteData(String route, TransitMode mode, List stops, St this.timetable = patternForDates; } + public static TestRouteData of( + String route, + TransitMode mode, + List stops, + String... times + ) { + return new TestRouteData.Builder(route) + .withMode(mode) + .withStops(stops) + .withTimes(Arrays.asList(times)) + .build(); + } + + public static TestRouteData.Builder of(String route, TransitMode mode) { + return new TestRouteData.Builder(route).withMode(mode); + } + + public static TestRouteData.Builder bus(String route) { + return of(route, TransitMode.BUS); + } + + public static TestRouteData.Builder rail(String route) { + return of(route, TransitMode.RAIL); + } + + public static TestRouteData.Builder ferry(String route) { + return of(route, TransitMode.FERRY); + } + public Route getRoute() { return route; } @@ -168,4 +200,74 @@ private StopTime stopTime(Trip trip, RegularStop stop, int time, int seq) { s.setRouteShortName("NA"); return s; } + + public static class Builder { + + private final String route; + private String agency; + private TransitMode mode = TransitMode.BUS; + private String submode; + private List stops; + private List times; + + public Builder(String route) { + this.route = route; + } + + public Builder withAgency(String agency) { + this.agency = agency; + return this; + } + + public Builder withMode(TransitMode mode) { + this.mode = mode; + return this; + } + + public Builder withStops(List stops) { + this.stops = stops; + return this; + } + + public List stops() { + if (stops == null) { + withStops(List.of(STOP_A, STOP_B)); + } + return stops; + } + + public Builder withTimes(List times) { + this.times = times; + return this; + } + + public List times() { + if (times == null) { + var buf = new StringBuilder(); + int t = TimeUtils.time("10:00"); + for (var ignore : stops()) { + t += 600; + buf.append(" ").append(TimeUtils.timeToStrLong(t)); + } + this.times = List.of(buf.substring(1)); + } + return times; + } + + public Builder withSubmode(String submode) { + this.submode = submode; + return this; + } + + public TestRouteData build() { + var routeBuilder = TransitModelForTest.route(route).withMode(mode).withShortName(route); + if (agency != null) { + routeBuilder.withAgency(TransitModelForTest.agency(agency)); + } + if (submode != null) { + routeBuilder.withNetexSubmode(submode); + } + return new TestRouteData(routeBuilder.build(), stops(), times()); + } + } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/api/OptimizedPathTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/api/OptimizedPathTest.java index eb9880996e4..788192c45b5 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/api/OptimizedPathTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/api/OptimizedPathTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.raptor._data.RaptorTestConstants; import org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase; +import org.opentripplanner.raptor.api.model.RaptorValueFormatter; class OptimizedPathTest implements RaptorTestConstants { @@ -15,20 +16,20 @@ void copyBasicPath() { var path = new OptimizedPath<>(BasicPathTestCase.basicTripAsPath()); // Verify all costs - assertEquals(BasicPathTestCase.TOTAL_COST, path.c1()); + assertEquals(BasicPathTestCase.TOTAL_C1, path.c1()); assertEquals(0, path.breakTieCost()); - assertEquals(BasicPathTestCase.TOTAL_COST, path.generalizedCostWaitTimeOptimized()); + assertEquals(BasicPathTestCase.TOTAL_C1, path.generalizedCostWaitTimeOptimized()); assertEquals(66_00, path.transferPriorityCost()); // And toString is the same (transfer priority cost added) assertEquals( - BasicPathTestCase.BASIC_PATH_AS_STRING.replace("]", " $66pri]"), + BasicPathTestCase.BASIC_PATH_AS_STRING.replace("]", " Tₚ6_600]"), path.toString(this::stopIndexToName) ); // Verify details assertEquals( - BasicPathTestCase.BASIC_PATH_AS_DETAILED_STRING.replace("]", " $66pri]"), + BasicPathTestCase.BASIC_PATH_AS_DETAILED_STRING.replace("]", " Tₚ6_600]"), path.toStringDetailed(this::stopIndexToName) ); @@ -43,6 +44,7 @@ void copyBasicPathWithCostsAndVerifyCosts() { // Define some constants final int generalizedCost = 881100; + final int c2 = 7; final int transferPriorityCost = 120100; final int waitTimeOptimizedCost = 130200; final int generalizedCostWaitTimeOptimized = generalizedCost + waitTimeOptimizedCost; @@ -52,6 +54,7 @@ void copyBasicPathWithCostsAndVerifyCosts() { accessLeg, orgPath.rangeRaptorIterationDepartureTime(), generalizedCost, + c2, transferPriorityCost, waitTimeOptimizedCost, breakTieCost @@ -63,8 +66,15 @@ void copyBasicPathWithCostsAndVerifyCosts() { assertEquals(transferPriorityCost, path.transferPriorityCost()); var exp = BasicPathTestCase.BASIC_PATH_AS_STRING.replace( - "$8154]", - "$8811 $1201pri " + "$" + (generalizedCostWaitTimeOptimized / 100) + "wtc]" + "C₁8_154 C₂7]", + RaptorValueFormatter.formatC1(generalizedCost) + + " " + + RaptorValueFormatter.formatC2(c2) + + " " + + RaptorValueFormatter.formatTransferPriority(transferPriorityCost) + + " " + + RaptorValueFormatter.formatWaitTimeCost(generalizedCostWaitTimeOptimized) + + "]" ); assertEquals(exp, path.toString(this::stopIndexToName)); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinSafeTransferTimeCalculatorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinSafeTransferTimeCalculatorTest.java index a1bfba17afb..ae10dd0d03e 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinSafeTransferTimeCalculatorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinSafeTransferTimeCalculatorTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.framework.time.TimeUtils.time; -import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.COST_CALCULATOR; +import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.C1_CALCULATOR; import static org.opentripplanner.routing.algorithm.transferoptimization.model.MinSafeTransferTimeCalculator.bound; import java.util.List; @@ -18,7 +18,7 @@ public class MinSafeTransferTimeCalculatorTest implements RaptorTestConstants { private static final int D2m = DurationUtils.durationInSeconds("2m"); private static final int TRANSIT_TIME = 2000 - (BOARD_SLACK + ALIGHT_SLACK); - private static final TestPathBuilder PATH_BUILDER = new TestPathBuilder(COST_CALCULATOR); + private static final TestPathBuilder PATH_BUILDER = new TestPathBuilder(C1_CALCULATOR); private final MinSafeTransferTimeCalculator subject = new MinSafeTransferTimeCalculator<>( SLACK_PROVIDER diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTailTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTailTest.java index bd4e5e12c38..f9bdf0df273 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTailTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTailTest.java @@ -57,7 +57,7 @@ class OptimizedPathTailTest implements RaptorTestConstants { private final OptimizedPathTail subject = new OptimizedPathTail<>( SLACK_PROVIDER, - BasicPathTestCase.COST_CALCULATOR, + BasicPathTestCase.C1_CALCULATOR, 0, waitTimeCalc, stopBoardAlightCost, @@ -84,7 +84,7 @@ void testToString() { "~ BUS L21 11:00 11:23 ~ D " + "~ BUS L31 11:40 11:52 ~ E " + "~ Walk 7m45s " + - "[$7989 $46pri $-93137wtc]"; + "[C₁7_989 Tₚ4_600 wtC₁-93_137]"; assertEquals(exp, subject.toString()); } @@ -99,7 +99,7 @@ void shouldHandleATransferAfterLastTransit() { "~ BUS L11 10:04 10:35 ~ B " + "~ Walk 3m45s ~ E " + "~ Flex 7m45s 1x " + - "[$3906 $0pri $3966wtc]"; + "[C₁3_906 wtC₁3_966]"; assertEquals(exp, subject.toString()); } @@ -140,13 +140,13 @@ void testBuildingPath() { // We have replaced the first transfer with a 2 minute walk var expPath = - "Walk 3m 10:00:15 10:03:15 $360 ~ A 45s " + - "~ BUS L11 10:04 10:35 31m $1998 ~ B 15s " + - "~ Walk 2m 10:35:15 10:37:15 $240 ~ C 22m45s " + - "~ BUS L21 11:00 11:23 23m $2724 ~ D 17m {staySeated} " + - "~ BUS L31 11:40 11:52 12m $1737 ~ E 15s " + - "~ Walk 7m45s 11:52:15 12:00 $930 " + - "[10:00:15 12:00 1h59m45s 1tx $7989 $46pri $-93137wtc]"; + "Walk 3m 10:00:15 10:03:15 C₁360 ~ A 45s " + + "~ BUS L11 10:04 10:35 31m C₁1_998 ~ B 15s " + + "~ Walk 2m 10:35:15 10:37:15 C₁240 ~ C 22m45s " + + "~ BUS L21 11:00 11:23 23m C₁2_724 ~ D 17m {staySeated} " + + "~ BUS L31 11:40 11:52 12m C₁1_737 ~ E 15s " + + "~ Walk 7m45s 11:52:15 12:00 C₁930 " + + "[10:00:15 12:00 1h59m45s Tₓ1 C₁7_989 Tₚ4_600 wtC₁-93_137]"; assertEquals(expPath, path.toStringDetailed(this::stopIndexToName)); } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterTest.java index 910789edbd5..8ecf0006e48 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.COST_CALCULATOR; +import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.C1_CALCULATOR; import java.util.List; import java.util.Objects; @@ -80,7 +80,7 @@ static class A extends OptimizedPathTail { public final int y; private A(String name, int x, int y) { - super(SLACK_PROVIDER, COST_CALCULATOR, T00_00, WAIT_TIME_CALC, null, 0.0, null); + super(SLACK_PROVIDER, C1_CALCULATOR, T00_00, WAIT_TIME_CALC, null, 0.0, null); this.name = name; this.x = x; this.y = y; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java index cc071d91799..926e6ebe15c 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java @@ -3,9 +3,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.framework.time.TimeUtils.time; import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestCase.testCase; +import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.domainService; import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.first; import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.pathBuilder; -import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.subject; import java.util.List; import org.junit.jupiter.params.ParameterizedTest; @@ -76,11 +76,12 @@ static List tripWithoutTransfersTestCases() { @MethodSource("tripWithoutTransfersTestCases") public void tripWithoutTransfers(TestCase tc) { var originalPath = pathBuilder() + .c2(tc.points().size()) .access(ITERATION_START_TIME, STOP_B, D1s) .bus(trip1, STOP_D) .egress(D1s); - var subject = subject(tc.points()); + var subject = domainService(tc.points()); // When var result = subject.findBestTransitPath(originalPath); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughOneTransferTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughOneTransferTest.java index 054915e84f4..ad41f43a586 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughOneTransferTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughOneTransferTest.java @@ -3,9 +3,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.framework.time.TimeUtils.time; import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestCase.testCase; +import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.domainService; import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.pathBuilder; import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.pathFocus; -import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.subject; import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.tx; import java.util.List; @@ -141,6 +141,7 @@ public void tripWithOneTransfer(TestCase tc) { // We need *a* path - the transfer here can be any. var originalPath = pathBuilder() + .c2(tc.points().size()) .access(ITERATION_START_TIME, STOP_B, D1s) .bus(trip1, STOP_D) .walk(txCost.walkDuration(STOP_D, STOP_F), STOP_F) @@ -148,6 +149,7 @@ public void tripWithOneTransfer(TestCase tc) { .egress(D1s); var expectedPath = pathBuilder() + .c2(tc.points().size()) .access(ITERATION_START_TIME, STOP_B, D1s) .bus(trip1, tc.stopIndexA()) .walk(txCost.walkDuration(tc.stopIndexA(), tc.stopIndexB()), tc.stopIndexB()) @@ -157,7 +159,7 @@ public void tripWithOneTransfer(TestCase tc) { // These are illegal transfers for the given path, we add them here to make sure they // do not interfere with the result. For simpler debugging problems try commenting out these // lines, just do not forget to comment them back in when the problem is fixed. - var subject = subject( + var subject = domainService( tc.points(), List.of( tx(trip1, STOP_C, trip2, STOP_G, txCost), diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java index fd2fd827d7c..e92e0ba0b3b 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java @@ -3,16 +3,15 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.framework.time.TimeUtils.time; import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestCase.testCase; +import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.domainService; import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.pathBuilder; -import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.pathFocus; -import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.subject; import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.tx; import java.util.List; -import java.util.stream.Collectors; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.raptor._data.RaptorTestConstants; +import org.opentripplanner.raptor._data.api.PathUtils; import org.opentripplanner.raptor._data.transit.TestTripSchedule; import org.opentripplanner.raptor.api.path.RaptorPath; @@ -195,7 +194,7 @@ public void tripWithTwoTransfer(TestCase tc) { RaptorPath expectedPath; { - var b = pathBuilder().access(ITERATION_START_TIME, STOP_B, D1s); + var b = pathBuilder().c2(tc.points().size()).access(ITERATION_START_TIME, STOP_B, D1s); if (tc.stopIndexA() == STOP_C) { b.bus(trip1, STOP_C).walk(costCG, STOP_G); @@ -219,21 +218,17 @@ public void tripWithTwoTransfer(TestCase tc) { tx(trip2, STOP_J, trip3, STOP_L, costJL) ); - var subject = subject(tc.points(), firstTransfers, secondTransfers); + var subject = domainService(tc.points(), firstTransfers, secondTransfers); // When var result = subject.findBestTransitPath(originalPath); // Then expect a set containing the expected path only - var resultAsString = result - .stream() - .map(it -> it.toString(this::stopIndexToName)) - .collect(Collectors.joining(", ")); assertEquals( - pathFocus(expectedPath.toString(this::stopIndexToName)), - pathFocus(resultAsString), - resultAsString + expectedPath.toString(this::stopIndexToName), + // Remove transferPriority cost + PathUtils.pathsToString(result).replace(" Tₚ6_600", "") ); } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestUtils.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestUtils.java index 57695d2ac5c..2faf0ee95c1 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestUtils.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestUtils.java @@ -54,7 +54,7 @@ static TripToTripTransfer tx( return TestTransferBuilder.tx(fromTrip, fromStop, toTrip, toStop).walk(txCost).build(); } - static OptimizePathDomainService subject( + static OptimizePathDomainService domainService( List passThroughPoints, final List>... transfers ) { @@ -82,6 +82,6 @@ static T first(Collection c) { * Remove stuff we do not care about, like the priority cost and times. */ static String pathFocus(String resultString) { - return resultString.replaceAll(" \\$\\d+pri]", "]").replaceAll(" \\d{2}:\\d{2}", ""); + return resultString.replaceAll(" Tₚ[\\d_]+]", "]").replaceAll(" \\d{2}:\\d{2}", ""); } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceConstrainedTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceConstrainedTest.java index d6725f4a425..a040da1d23f 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceConstrainedTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceConstrainedTest.java @@ -80,7 +80,7 @@ public void testTransferPriorityAllowed() { testPriority( STOP_D, ALLOWED, - "A ~ BUS T1 10:02 10:10 ~ B ~ Walk 1m ~ C ~ BUS T2 10:13 10:18 ~ D [10:01:20 10:18:20 17m 1tx $1120 $33pri]" + "A ~ BUS T1 10:02 10:10 ~ B ~ Walk 1m ~ C ~ BUS T2 10:13 10:18 ~ D [10:01:20 10:18:20 17m Tₓ1 C₁1_120 Tₚ3_300]" ); } @@ -89,7 +89,7 @@ public void testTransferPriorityRecommended() { testPriority( STOP_E, RECOMMENDED, - "A ~ BUS T1 10:02 10:15 ~ C ~ Walk 2m ~ D ~ BUS T2 10:18 10:24 ~ E [10:01:20 10:24:20 23m 1tx $1540 $32pri]" + "A ~ BUS T1 10:02 10:15 ~ C ~ Walk 2m ~ D ~ BUS T2 10:18 10:24 ~ E [10:01:20 10:24:20 23m Tₓ1 C₁1_540 Tₚ3_200]" ); } @@ -98,7 +98,7 @@ public void testTransferPriorityPreferred() { testPriority( STOP_F, PREFERRED, - "A ~ BUS T1 10:02 10:20 ~ D ~ Walk 3m ~ E ~ BUS T2 10:24 10:30 ~ F [10:01:20 10:30:20 29m 1tx $1960 $31pri]" + "A ~ BUS T1 10:02 10:20 ~ D ~ Walk 3m ~ E ~ BUS T2 10:24 10:30 ~ F [10:01:20 10:30:20 29m Tₓ1 C₁1_960 Tₚ3_100]" ); } @@ -106,7 +106,7 @@ public void testTransferPriorityPreferred() { public void testTransferGuaranteed() { testGuaranteed( STOP_G, - "A ~ BUS T1 10:02 10:25 ~ E ~ Walk 4m ~ F ~ BUS T2 10:30 10:36 ~ G [10:01:20 10:36:20 35m 1tx $2350 $23pri]" + "A ~ BUS T1 10:02 10:25 ~ E ~ Walk 4m ~ F ~ BUS T2 10:30 10:36 ~ G [10:01:20 10:36:20 35m Tₓ1 C₁2_350 Tₚ2_300]" ); } @@ -114,7 +114,7 @@ public void testTransferGuaranteed() { public void testTransferStaySeated() { testStaySeated( STOP_H, - "A ~ BUS T1 10:02 10:30 ~ F ~ Walk 5m ~ G ~ BUS T2 10:36 10:40 ~ H [10:01:20 10:40:20 39m 0tx $2650 $13pri]" + "A ~ BUS T1 10:02 10:30 ~ F ~ Walk 5m ~ G ~ BUS T2 10:36 10:40 ~ H [10:01:20 10:40:20 39m Tₓ0 C₁2_650 Tₚ1_300]" ); } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java index c15a7b8247a..d05a4090b39 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java @@ -5,9 +5,7 @@ import static org.opentripplanner.routing.algorithm.transferoptimization.services.TestTransferBuilder.tx; import static org.opentripplanner.routing.algorithm.transferoptimization.services.TransferGeneratorDummy.dummyTransferGenerator; -import java.util.Collection; import java.util.List; -import java.util.stream.Collectors; import javax.annotation.Nullable; import org.junit.jupiter.api.Test; import org.opentripplanner.raptor._data.RaptorTestConstants; @@ -86,9 +84,8 @@ public void testTripWithoutTransfers() { // Then expect a set containing the original path assertEquals( original.toStringDetailed(this::stopIndexToName), - first(result).toStringDetailed(this::stopIndexToName) + PathUtils.pathsToStringDetailed(result) ); - assertEquals(1, result.size()); } /** @@ -133,10 +130,9 @@ public void testTripWithOneTransfer() { // Insert wait-time cost summary info var expected = original .toStringDetailed(this::stopIndexToName) - .replace("$2770]", "$2770 $33pri $3103.81wtc]"); + .replace("C₁2_770]", "C₁2_770 Tₚ3_300 wtC₁3_103.81]"); - assertEquals(expected, first(result).toStringDetailed(this::stopIndexToName)); - assertEquals(1, result.size()); + assertEquals(expected, PathUtils.pathsToStringDetailed(result)); } /** @@ -197,7 +193,7 @@ public void testPathWithThreeTripsAndMultiplePlacesToTransfer() { assertEquals( "A ~ BUS T1 10:02 10:10 ~ B ~ BUS T2 10:12 10:35 ~ F ~ BUS T3 10:37 10:49 ~ G " + - "[10:01:20 10:49:20 48m 2tx $2950 $66pri]", + "[10:01:20 10:49:20 48m Tₓ2 C₁2_950 Tₚ6_600]", PathUtils.pathsToString(result) ); @@ -214,7 +210,7 @@ public void testPathWithThreeTripsAndMultiplePlacesToTransfer() { "A ~ BUS T1 10:02 10:10 ~ B ~ Walk 30s ~ C " + "~ BUS T2 10:15 10:35 ~ F " + "~ BUS T3 10:37 10:49 ~ G " + - "[10:01:20 10:49:20 48m 2tx $2980 $66pri $3294.05wtc]", + "[10:01:20 10:49:20 48m Tₓ2 C₁2_980 Tₚ6_600 wtC₁3_294.05]", PathUtils.pathsToString(result) ); } @@ -267,7 +263,7 @@ public void testConstrainedTransferIsPreferred() { var it = result.iterator().next(); assertEquals( - "A ~ BUS T1 10:02 10:15 ~ C ~ BUS T2 10:17 10:30 ~ D [10:01:20 10:30:20 29m 1tx $1750 $23pri]", + "A ~ BUS T1 10:02 10:15 ~ C ~ BUS T2 10:17 10:30 ~ D [10:01:20 10:30:20 29m Tₓ1 C₁1_750 Tₚ2_300]", it.toString(this::stopIndexToName) ); // Verify the attached Transfer is exist and is valid @@ -327,8 +323,8 @@ public void testSameStopTimesInPattern() { var result = subject.findBestTransitPath(original); assertEquals( - "A ~ BUS T1 10:10 10:10 ~ B ~ BUS T2 10:13 10:30 ~ D [10:09:20 10:30:20 21m 1tx $1300 $33pri]", - result.stream().map(it -> it.toString(this::stopIndexToName)).collect(Collectors.joining()) + "A ~ BUS T1 10:10 10:10 ~ B ~ BUS T2 10:13 10:30 ~ D [10:09:20 10:30:20 21m Tₓ1 C₁1_300 Tₚ3_300]", + PathUtils.pathsToString(result) ); } @@ -361,8 +357,4 @@ static OptimizePathDomainService subject( (new RaptorTestConstants() {})::stopIndexToName ); } - - static T first(Collection c) { - return c.stream().findFirst().orElseThrow(); - } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorTest.java index 8a5e15b3ac3..230097ebfc3 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorTest.java @@ -2,7 +2,7 @@ import static java.time.Duration.ofMinutes; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.COST_CALCULATOR; +import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.C1_CALCULATOR; import static org.opentripplanner.raptor._data.transit.TestRoute.route; import static org.opentripplanner.raptor._data.transit.TestTripSchedule.schedule; @@ -46,7 +46,7 @@ public class TransferGeneratorTest implements RaptorTestConstants { ALIGHT_SLACK ); - private final TestPathBuilder pathBuilder = new TestPathBuilder(SLACK_PROVIDER, COST_CALCULATOR); + private final TestPathBuilder pathBuilder = new TestPathBuilder(SLACK_PROVIDER, C1_CALCULATOR); private final TestTransitData data = new TestTransitData().withSlackProvider(SLACK_PROVIDER); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java index 5e92adc4d9e..d62f123e7d6 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java @@ -117,12 +117,7 @@ private static String firstRide(Collection c) { private TransitPathLeg transitLeg(int egressStop) { TestAccessEgress walk = TestAccessEgress.walk(egressStop, EGRESS_END - EGRESS_START); - var egress = new EgressPathLeg( - walk, - EGRESS_START, - EGRESS_END, - walk.generalizedCost() - ); + var egress = new EgressPathLeg(walk, EGRESS_START, EGRESS_END, walk.c1()); int toTime = TRIP.arrival(TRIP.findArrivalStopPosition(Integer.MAX_VALUE, egressStop)); int cost = 100 * (STOP_TIME_THREE - STOP_TIME_ONE); return new TransitPathLeg<>( 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 c15512c82de..e8e3ff576a9 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 @@ -16,8 +16,6 @@ class BikePreferencesTest { 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 int PARK_TIME = 330; - public static final int PARK_COST = 950; public static final TimeSlopeSafetyTriangle TRIANGLE = TimeSlopeSafetyTriangle .of() .withSlope(1) @@ -33,8 +31,6 @@ class BikePreferencesTest { .withWalkingReluctance(WALKING_RELUCTANCE) .withSwitchTime(SWITCH_TIME) .withSwitchCost(SWITCH_COST) - .withParkTime(PARK_TIME) - .withParkCost(PARK_COST) .withOptimizeType(OPTIMIZE_TYPE) .withOptimizeTriangle(it -> it.withSlope(1).build()) .build(); @@ -74,16 +70,6 @@ void switchCost() { assertEquals(SWITCH_COST, subject.switchCost()); } - @Test - void parkTime() { - assertEquals(PARK_TIME, subject.parkTime()); - } - - @Test - void parkCost() { - assertEquals(PARK_COST, subject.parkCost()); - } - @Test void optimizeType() { assertEquals(OPTIMIZE_TYPE, subject.optimizeType()); @@ -121,8 +107,6 @@ void testToString() { "walkingReluctance: 1.45, " + "switchTime: 3m20s, " + "switchCost: $450, " + - "parkTime: 5m30s, " + - "parkCost: $950, " + "optimizeType: TRIANGLE, " + "optimizeTriangle: TimeSlopeSafetyTriangle[time=0.0, slope=1.0, safety=0.0]" + "}", 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 d94f21d567b..7bc0a1620ab 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 @@ -12,8 +12,6 @@ class CarPreferencesTest { private static final double EXPECTED_SPEED = 20.0; private static final double RELUCTANCE = 5.111; private static final double EXPECTED_RELUCTANCE = 5.1; - private static final int PARK_TIME = 300; - private static final int PARK_COST = 250; private static final int PICKUP_TIME = 600; private static final int PICKUP_COST = 500; private static final double ACCELERATION_SPEED = 3.1; @@ -24,8 +22,6 @@ class CarPreferencesTest { .of() .withSpeed(SPEED) .withReluctance(RELUCTANCE) - .withParkTime(PARK_TIME) - .withParkCost(PARK_COST) .withPickupTime(PICKUP_TIME) .withPickupCost(PICKUP_COST) .withDropoffTime(DROPOFF_TIME) @@ -43,16 +39,6 @@ void reluctance() { assertEquals(EXPECTED_RELUCTANCE, subject.reluctance()); } - @Test - void parkTime() { - assertEquals(PARK_TIME, subject.parkTime()); - } - - @Test - void parkCost() { - assertEquals(PARK_COST, subject.parkCost()); - } - @Test void pickupTime() { assertEquals(PICKUP_TIME, subject.pickupTime()); @@ -97,8 +83,6 @@ void testToString() { "CarPreferences{" + "speed: 20.0, " + "reluctance: 5.1, " + - "parkTime: 300, " + - "parkCost: $250, " + "pickupTime: 600, " + "pickupCost: $500, " + "dropoffTime: 450, " + diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/RelaxTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/RelaxTest.java new file mode 100644 index 00000000000..d3c1a2702c2 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/RelaxTest.java @@ -0,0 +1,48 @@ +package org.opentripplanner.routing.api.request.preference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class RelaxTest { + + @Test + void testConstructor() { + new Relax(0.9951, 0); + new Relax(4.0, 2_000_000_000); + assertThrows(IllegalArgumentException.class, () -> new Relax(0.9949, 0)); + assertThrows(IllegalArgumentException.class, () -> new Relax(4.01, 300)); + assertThrows(IllegalArgumentException.class, () -> new Relax(2.1, -1)); + } + + @Test + void hasNoEffect() { + assertTrue(new Relax(1.0049, 0).hasNoEffect()); + assertFalse(new Relax(1.01, 0).hasNoEffect()); + } + + @Test + void hasEffect() { + assertTrue(new Relax(1.01, 0).hasEffect()); + assertFalse(new Relax(1.0049, 0).hasEffect()); + } + + @Test + void testToString() { + assertEquals("0 + 1.00 * x", Relax.NORMAL.toString()); + assertEquals("300 + 2.10 * x", new Relax(2.1, 300).toString()); + } + + @Test + void ratio() { + assertEquals(2.0, new Relax(2.0, 333).ratio()); + } + + @Test + void slack() { + assertEquals(333, new Relax(2.0, 333).slack()); + } +} 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 0b4e383dfde..811c4a70b29 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 @@ -9,6 +9,7 @@ import java.time.Duration; import java.util.Map; import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.model.Cost; import org.opentripplanner.raptor.api.model.SearchDirection; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.transit.model.basic.TransitMode; @@ -26,6 +27,10 @@ class TransitPreferencesTest { private static final Duration D25m = Duration.ofMinutes(25); private static final Duration D35m = Duration.ofMinutes(35); private static final SearchDirection RAPTOR_SEARCH_DIRECTION = SearchDirection.REVERSE; + private static final CostLinearFunction TRANSIT_GROUP_PRIORITY_RELAX = CostLinearFunction.of( + Cost.costOfSeconds(300), + 1.5 + ); private static final boolean IGNORE_REALTIME_UPDATES = true; private static final boolean INCLUDE_PLANNED_CANCELLATIONS = true; private static final boolean INCLUDE_REALTIME_CANCELLATIONS = true; @@ -37,6 +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) .setIgnoreRealtimeUpdates(IGNORE_REALTIME_UPDATES) .setIncludePlannedCancellations(INCLUDE_PLANNED_CANCELLATIONS) .setIncludeRealtimeCancellations(INCLUDE_REALTIME_CANCELLATIONS) @@ -70,6 +76,11 @@ void unpreferredCost() { assertEquals(UNPREFERRED_COST, subject.unpreferredCost()); } + @Test + void relaxTransitPriorityGroup() { + assertEquals(TRANSIT_GROUP_PRIORITY_RELAX, subject.relaxTransitPriorityGroup()); + } + @Test void ignoreRealtimeUpdates() { assertFalse(TransitPreferences.DEFAULT.ignoreRealtimeUpdates()); @@ -114,6 +125,7 @@ void testToString() { "reluctanceForMode: {AIRPLANE=2.1}, " + "otherThanPreferredRoutesPenalty: $350, " + "unpreferredCost: 5m + 1.15 t, " + + "relaxTransitPriorityGroup: 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 new file mode 100644 index 00000000000..35666b53f9f --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleParkingPreferencesTest.java @@ -0,0 +1,88 @@ +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 java.util.Set; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.model.Cost; + +class VehicleParkingPreferencesTest { + + private static final Set PREFERRED_TAGS = Set.of("foo"); + private static final Set NOT_PREFERRED_TAGS = Set.of("bar"); + private static final int UNPREFERRED_COST = 360; + private static final Set REQUIRED_TAGS = Set.of("bar"); + private static final Set BANNED_TAGS = Set.of("not"); + private static final Cost PARKING_COST = Cost.costOfMinutes(4); + private static final Duration PARKING_TIME = Duration.ofMinutes(2); + + private final VehicleParkingPreferences subject = createPreferences(); + + @Test + void preferred() { + assertEquals(tagsToString(PREFERRED_TAGS), subject.preferred().select().toString()); + assertEquals(tagsToString(NOT_PREFERRED_TAGS), subject.preferred().not().toString()); + } + + @Test + void filter() { + assertEquals(tagsToString(REQUIRED_TAGS), subject.filter().select().toString()); + assertEquals(tagsToString(BANNED_TAGS), subject.filter().not().toString()); + } + + @Test + void unpreferredCost() { + assertEquals(UNPREFERRED_COST, subject.unpreferredVehicleParkingTagCost().toSeconds()); + } + + @Test + void parkCost() { + assertEquals(PARKING_COST, subject.parkCost()); + } + + @Test + void parkTime() { + assertEquals(PARKING_TIME, subject.parkTime()); + } + + @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(); + assertEqualsAndHashCode(subject, other, same); + } + + @Test + void testToString() { + assertEquals("VehicleParkingPreferences{}", VehicleParkingPreferences.DEFAULT.toString()); + assertEquals( + "VehicleParkingPreferences{" + + "unpreferredVehicleParkingTagCost: $360, " + + "filter: VehicleParkingFilter{not: [tags=[not]], select: [tags=[bar]]}, " + + "preferred: VehicleParkingFilter{not: [tags=[bar]], select: [tags=[foo]]}, " + + "parkCost: $240, " + + "parkTime: PT2M}", + subject.toString() + ); + } + + private static String tagsToString(Set tags) { + return "[tags=" + tags + "]"; + } + + private VehicleParkingPreferences createPreferences() { + return VehicleParkingPreferences + .of() + .withPreferredVehicleParkingTags(PREFERRED_TAGS) + .withNotPreferredVehicleParkingTags(NOT_PREFERRED_TAGS) + .withUnpreferredVehicleParkingTagCost(UNPREFERRED_COST) + .withRequiredVehicleParkingTags(REQUIRED_TAGS) + .withBannedVehicleParkingTags(BANNED_TAGS) + .withParkCost(PARKING_COST.toSeconds()) + .withParkTime(PARKING_TIME) + .build(); + } +} diff --git a/src/test/java/org/opentripplanner/routing/core/RoutingPreferencesTest.java b/src/test/java/org/opentripplanner/routing/core/RoutingPreferencesTest.java index 1ca95b1abdb..8131fdd1299 100644 --- a/src/test/java/org/opentripplanner/routing/core/RoutingPreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/core/RoutingPreferencesTest.java @@ -11,11 +11,13 @@ public class RoutingPreferencesTest { @Test public void copyOfShouldReturnTheSameInstanceWhenBuild() { var pref = new RoutingPreferences(); - var copy = pref.copyOf().build(); - assertNotSame(pref, copy); + var same = pref.copyOf().build(); + assertSame(pref, same); + // Change one thing to force making a copy + var copy = pref.copyOf().withCar(c -> c.withReluctance(3.5)).build(); + assertNotSame(pref.car(), copy.car()); // Immutable classes should not change - assertSame(pref.car(), copy.car()); assertSame(pref.bike(), copy.bike()); assertSame(pref.walk(), copy.walk()); assertSame(pref.transfer(), copy.transfer()); diff --git a/src/test/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitorTest.java b/src/test/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitorTest.java new file mode 100644 index 00000000000..1ef675c8101 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitorTest.java @@ -0,0 +1,277 @@ +package org.opentripplanner.routing.graphfinder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.model.plan.PlanTestConstants.T11_00; +import static org.opentripplanner.model.plan.PlanTestConstants.T11_05; +import static org.opentripplanner.model.plan.PlanTestConstants.T11_10; +import static org.opentripplanner.transit.model._data.TransitModelForTest.id; +import static org.opentripplanner.transit.model._data.TransitModelForTest.route; +import static org.opentripplanner.transit.model._data.TransitModelForTest.tripPattern; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.framework.i18n.NonLocalizedString; +import org.opentripplanner.model.StopTime; +import org.opentripplanner.street.search.state.TestStateBuilder; +import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.network.Route; +import org.opentripplanner.transit.model.network.StopPattern; +import org.opentripplanner.transit.model.network.TripPatternBuilder; +import org.opentripplanner.transit.model.site.RegularStop; +import org.opentripplanner.transit.model.site.Station; +import org.opentripplanner.transit.service.DefaultTransitService; +import org.opentripplanner.transit.service.TransitModel; + +public class PlaceFinderTraverseVisitorTest { + + static TransitModelForTest model = TransitModelForTest.of(); + static final Station STATION1 = Station + .of(id("S1")) + .withName(new NonLocalizedString("Station 1")) + .withCoordinate(1.1, 1.1) + .build(); + + static final Station STATION2 = Station + .of(id("S2")) + .withName(new NonLocalizedString("Station 2")) + .withCoordinate(1.1, 1.1) + .build(); + static final RegularStop STOP1 = model + .stop("stop-1") + .withCoordinate(new WgsCoordinate(1, 1)) + .withParentStation(STATION1) + .build(); + static final RegularStop STOP2 = model + .stop("stop-2") + .withCoordinate(1.001, 1.001) + .withParentStation(STATION2) + .build(); + + static final RegularStop STOP3 = model.stop("stop-3").withCoordinate(1.002, 1.002).build(); + static final RegularStop STOP4 = model.stop("stop-4").withCoordinate(1.003, 1.003).build(); + + static final Route r = route("r").build(); + + static TransitModel a = new TransitModel(); + + static { + a.addTransitMode(TransitMode.BUS); + TripPatternBuilder t = tripPattern("trip", r); + var st1 = new StopTime(); + st1.setStop(STOP1); + st1.setArrivalTime(T11_00); + + var st2 = new StopTime(); + st2.setStop(STOP2); + st2.setArrivalTime(T11_05); + t.withStopPattern(new StopPattern(List.of(st1, st2))); + a.addTripPattern(id("tp1"), t.build()); + + var st3 = new StopTime(); + st3.setStop(STOP3); + st3.setArrivalTime(T11_10); + t.withStopPattern(new StopPattern(List.of(st3))); + a.addTripPattern(id("tp2"), t.build()); + + var st4 = new StopTime(); + st4.setStop(STOP4); + st4.setArrivalTime(T11_10); + t.withStopPattern(new StopPattern(List.of(st4))); + a.addTripPattern(id("tp3"), t.build()); + + a.index(); + } + + static DefaultTransitService transitService = new DefaultTransitService(a); + + @Test + void stopsOnly() { + var visitor = new PlaceFinderTraverseVisitor( + transitService, + List.of(TransitMode.BUS), + List.of(PlaceType.STOP), + null, + null, + null, + null, + 1, + 500 + ); + + assertEquals(List.of(), visitor.placesFound); + var state1 = TestStateBuilder.ofWalking().streetEdge().stop(STOP1).build(); + + visitor.visitVertex(state1); + + var state2 = TestStateBuilder.ofWalking().streetEdge().streetEdge().stop(STOP2).build(); + visitor.visitVertex(state2); + var res = visitor.placesFound.stream().map(PlaceAtDistance::place).toList(); + + assertEquals(List.of(STOP1, STOP2), res); + + visitor.visitVertex(state1); + } + + @Test + void stationsOnly() { + var visitor = new PlaceFinderTraverseVisitor( + transitService, + List.of(TransitMode.BUS), + List.of(PlaceType.STATION), + null, + null, + null, + null, + 1, + 500 + ); + + assertEquals(List.of(), visitor.placesFound); + var state1 = TestStateBuilder.ofWalking().streetEdge().stop(STOP1).build(); + + visitor.visitVertex(state1); + + var state2 = TestStateBuilder.ofWalking().streetEdge().streetEdge().stop(STOP2).build(); + visitor.visitVertex(state2); + var res = visitor.placesFound.stream().map(PlaceAtDistance::place).toList(); + + assertEquals(List.of(STATION1, STATION2), res); + + visitor.visitVertex(state1); + } + + @Test + void stopsAndStations() { + var visitor = new PlaceFinderTraverseVisitor( + transitService, + List.of(TransitMode.BUS), + List.of(PlaceType.STOP, PlaceType.STATION), + null, + null, + null, + null, + 1, + 500 + ); + + assertEquals(List.of(), visitor.placesFound); + var state1 = TestStateBuilder.ofWalking().streetEdge().stop(STOP1).build(); + + visitor.visitVertex(state1); + + var state2 = TestStateBuilder.ofWalking().streetEdge().streetEdge().stop(STOP3).build(); + visitor.visitVertex(state2); + + // Revisited stop should not be added to found places + visitor.visitVertex(state1); + var res = visitor.placesFound.stream().map(PlaceAtDistance::place).toList(); + + assertEquals(List.of(STATION1, STOP3), res); + + visitor.visitVertex(state1); + } + + @Test + void stopsAndStationsWithStationFilter() { + var visitor = new PlaceFinderTraverseVisitor( + transitService, + List.of(TransitMode.BUS), + List.of(PlaceType.STOP, PlaceType.STATION), + List.of(STOP2.getId(), STOP3.getId()), + List.of(STATION1.getId()), + null, + null, + 1, + 500 + ); + + assertEquals(List.of(), visitor.placesFound); + var state1 = TestStateBuilder.ofWalking().streetEdge().stop(STOP1).build(); + + visitor.visitVertex(state1); + + var state2 = TestStateBuilder.ofWalking().streetEdge().streetEdge().stop(STOP2).build(); + visitor.visitVertex(state2); + + var state3 = TestStateBuilder.ofWalking().streetEdge().streetEdge().stop(STOP3).build(); + visitor.visitVertex(state3); + + var res = visitor.placesFound.stream().map(PlaceAtDistance::place).toList(); + + // Stop 3 should be included as it is not part of a station. + // Stop 2 should not be included as its parent station is not included in the station filter. + assertEquals(List.of(STATION1, STOP3), res); + + visitor.visitVertex(state1); + } + + @Test + void stopsAndStationsWithStopFilter() { + var visitor = new PlaceFinderTraverseVisitor( + transitService, + List.of(TransitMode.BUS), + List.of(PlaceType.STOP, PlaceType.STATION), + List.of(STOP2.getId()), + null, + null, + null, + 1, + 500 + ); + + assertEquals(List.of(), visitor.placesFound); + var state1 = TestStateBuilder.ofWalking().streetEdge().stop(STOP1).build(); + + visitor.visitVertex(state1); + + var state2 = TestStateBuilder.ofWalking().streetEdge().streetEdge().stop(STOP2).build(); + visitor.visitVertex(state2); + + var state3 = TestStateBuilder.ofWalking().streetEdge().streetEdge().stop(STOP3).build(); + visitor.visitVertex(state3); + + var res = visitor.placesFound.stream().map(PlaceAtDistance::place).toList(); + + // Stop 3 should not be included as it is included in the stop filter + assertEquals(List.of(STATION1, STATION2), res); + + visitor.visitVertex(state1); + } + + @Test + void stopsAndStationsWithStopAndStationFilter() { + var visitor = new PlaceFinderTraverseVisitor( + transitService, + List.of(TransitMode.BUS), + List.of(PlaceType.STOP, PlaceType.STATION), + List.of(STOP4.getId()), + List.of(STATION1.getId()), + null, + null, + 1, + 500 + ); + + assertEquals(List.of(), visitor.placesFound); + var state1 = TestStateBuilder.ofWalking().streetEdge().stop(STOP1).build(); + + visitor.visitVertex(state1); + + var state2 = TestStateBuilder.ofWalking().streetEdge().streetEdge().stop(STOP2).build(); + visitor.visitVertex(state2); + + var state3 = TestStateBuilder.ofWalking().streetEdge().streetEdge().stop(STOP3).build(); + visitor.visitVertex(state3); + + var state4 = TestStateBuilder.ofWalking().streetEdge().streetEdge().stop(STOP4).build(); + visitor.visitVertex(state4); + + var res = visitor.placesFound.stream().map(PlaceAtDistance::place).toList(); + + assertEquals(List.of(STATION1, STOP4), res); + + visitor.visitVertex(state1); + } +} diff --git a/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java b/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java index 7901c06f9f4..3285e27594c 100644 --- a/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java +++ b/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java @@ -151,6 +151,7 @@ void findClosestPlacesLimiting() { 10.0, 10, null, + List.of(PlaceType.STOP, PlaceType.PATTERN_AT_STOP, PlaceType.VEHICLE_RENT), null, null, null, @@ -167,6 +168,13 @@ void findClosestPlacesLimiting() { 200.0, 100, null, + List.of( + PlaceType.STOP, + PlaceType.PATTERN_AT_STOP, + PlaceType.VEHICLE_RENT, + PlaceType.CAR_PARK, + PlaceType.BIKE_PARK + ), null, null, null, @@ -183,6 +191,7 @@ void findClosestPlacesLimiting() { 200.0, 3, null, + List.of(PlaceType.STOP, PlaceType.PATTERN_AT_STOP, PlaceType.VEHICLE_RENT), null, null, null, @@ -210,6 +219,7 @@ void findClosestPlacesWithAModeFilter() { null, null, null, + null, transitService ) ); @@ -226,6 +236,7 @@ void findClosestPlacesWithAModeFilter() { null, null, null, + null, transitService ) ); @@ -250,6 +261,7 @@ void findClosestPlacesWithAStopFilter() { null, null, null, + null, transitService ) ); @@ -266,6 +278,7 @@ void findClosestPlacesWithAStopFilter() { List.of(S2.getStop().getId()), null, null, + null, transitService ) ); @@ -290,6 +303,7 @@ void findClosestPlacesWithAStopAndRouteFilter() { null, null, null, + null, transitService ) ); @@ -304,6 +318,7 @@ void findClosestPlacesWithAStopAndRouteFilter() { null, List.of(PlaceType.STOP, PlaceType.PATTERN_AT_STOP), List.of(S2.getStop().getId()), + null, List.of(R1.getId()), null, transitService @@ -331,6 +346,7 @@ void findClosestPlacesWithARouteFilter() { null, null, null, + null, transitService ) ); @@ -345,6 +361,7 @@ void findClosestPlacesWithARouteFilter() { null, List.of(PlaceType.STOP, PlaceType.PATTERN_AT_STOP), null, + null, List.of(R2.getId()), null, transitService @@ -369,6 +386,7 @@ void findClosestPlacesWithAVehicleRentalFilter() { null, null, null, + null, transitService ) ); @@ -384,6 +402,7 @@ void findClosestPlacesWithAVehicleRentalFilter() { List.of(PlaceType.VEHICLE_RENT), null, null, + null, List.of("BR2"), transitService ) @@ -406,6 +425,7 @@ void findClosestPlacesWithABikeParkFilter() { null, null, null, + null, transitService ) ); @@ -427,6 +447,7 @@ void findClosestPlacesWithACarParkFilter() { null, null, null, + null, transitService ) ); diff --git a/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java b/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java index 96f49bb9c78..fde87776909 100644 --- a/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java +++ b/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java @@ -59,16 +59,13 @@ void testPreviousLegs() { alternativeLegs.stream().map(Leg.class::cast).map(List::of).map(Itinerary::new).toList() ); - var expectd = String.join( - ", ", - List.of( - "B ~ BUS 2 0:20 0:30 ~ C [$-1]", - "B ~ BUS 1 0:10 0:20 ~ C [$-1]", - "B ~ BUS 1 8:20 8:30 ~ C [$-1]" // Previous day - ) - ); + var expected = + "B ~ BUS 2 0:20 0:30 ~ C [C₁-1], " + + "B ~ BUS 1 0:10 0:20 ~ C [C₁-1], " + + // Previous day + "B ~ BUS 1 8:20 8:30 ~ C [C₁-1]"; - assertEquals(expectd, legs); + assertEquals(expected, legs); } @Test @@ -98,16 +95,13 @@ void testNextLegs() { alternativeLegs.stream().map(Leg.class::cast).map(List::of).map(Itinerary::new).toList() ); - var expectd = String.join( - ", ", - List.of( - "B ~ BUS 3 1:00 1:10 ~ C [$-1]", - "B ~ BUS 1 8:20 8:30 ~ C [$-1]", - "B ~ BUS 1 0:10 0:20 ~ C [$-1]" // Next day - ) - ); + var expected = + "B ~ BUS 3 1:00 1:10 ~ C [C₁-1], " + + "B ~ BUS 1 8:20 8:30 ~ C [C₁-1], " + + // Next day + "B ~ BUS 1 0:10 0:20 ~ C [C₁-1]"; - assertEquals(expectd, legs); + assertEquals(expected, legs); } @Test @@ -136,12 +130,7 @@ void testCircularRoutes() { alternativeLegs.stream().map(Leg.class::cast).map(List::of).map(Itinerary::new).toList() ); - var expected = String.join( - ", ", - List.of("X ~ BUS 19 10:30 10:40 ~ Y [$-1]", "X ~ BUS 19 10:00 10:10 ~ Y [$-1]") - ); - - assertEquals(expected, legs); + assertEquals("X ~ BUS 19 10:30 10:40 ~ Y [C₁-1], X ~ BUS 19 10:00 10:10 ~ Y [C₁-1]", legs); } @Test @@ -170,7 +159,7 @@ void testComplexCircularRoutes() { alternativeLegs.stream().map(Leg.class::cast).map(List::of).map(Itinerary::new).toList() ); - var expected = String.join(", ", List.of("X ~ BUS 19 10:30 11:00 ~ B [$-1]")); + var expected = String.join(", ", List.of("X ~ BUS 19 10:30 11:00 ~ B [C₁-1]")); assertEquals(expected, legs); } } diff --git a/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java b/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java index 54194696e71..1432c68fd49 100644 --- a/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java +++ b/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java @@ -38,7 +38,9 @@ public static void setUp() throws Exception { transitService.getTripForId(new FeedScopedId(feedId, "5.1")) ); var tt = transitService.getTimetableForTripPattern(pattern, LocalDate.now()); - tt.getTripTimes(0).cancelTrip(); + var newTripTimes = tt.getTripTimes(0).copyScheduledTimes(); + newTripTimes.cancelTrip(); + tt.setTripTimes(0, newTripTimes); } /** diff --git a/src/test/java/org/opentripplanner/service/paging/PS1_LegacyMetaDataTest.java b/src/test/java/org/opentripplanner/service/paging/PS1_LegacyMetaDataTest.java new file mode 100644 index 00000000000..609607d84ff --- /dev/null +++ b/src/test/java/org/opentripplanner/service/paging/PS1_LegacyMetaDataTest.java @@ -0,0 +1,101 @@ +package org.opentripplanner.service.paging; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.service.paging.TestPagingModel.D30m; + +import java.time.Duration; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.time.TimeUtils; + +/** + * This tests the entire paging service module. + *

+ * To debug this test, set either the system property 'testDebug' or the environment variable 'testDebug' to 'true'. + */ +@SuppressWarnings("DataFlowIssue") +class PS1_LegacyMetaDataTest { + + private static final int T08_45 = TimeUtils.time("08:45"); + private static final int T09_15 = TimeUtils.time("09:15"); + + private static final int T12_00 = TimeUtils.time("12:00"); + private static final int T13_00 = TimeUtils.time("13:00"); + + private static final Duration SEARCH_WINDOW_USED = D30m; + + @Test + @SuppressWarnings("deprecation") + void testCreateTripSearchMetadataDepartAfterWithPageCut() { + var model = TestPagingModel.testDataWithManyItinerariesCaseA(); + var testDriver = model.departAfterDriver(T12_00, SEARCH_WINDOW_USED, 3); + var subject = testDriver.pagingService(); + + assertEquals(D30m, subject.createTripSearchMetadata().searchWindowUsed); + assertEquals( + "11:30", + TestPagingUtils.cleanStr(subject.createTripSearchMetadata().prevDateTime) + ); + // 12:11 will drop results, the solution is to use the complete sort-vector. + // The cursor implementation does that + assertEquals( + "12:11", + TestPagingUtils.cleanStr(subject.createTripSearchMetadata().nextDateTime) + ); + } + + @Test + @SuppressWarnings("deprecation") + void testCreateTripSearchMetadataDepartAfterNormalSearchWindow() { + var model = TestPagingModel.testDataWithFewItinerariesCaseB(); + var testDriver = model.departAfterDriver(T08_45, SEARCH_WINDOW_USED, 3); + var subject = testDriver.pagingService(); + + assertEquals(D30m, subject.createTripSearchMetadata().searchWindowUsed); + assertEquals( + "08:15", + TestPagingUtils.cleanStr(subject.createTripSearchMetadata().prevDateTime) + ); + // 12:11 will drop results, the solution is to use the complete sort-vector. + // The cursor implementation does that + assertEquals( + "09:15", + TestPagingUtils.cleanStr(subject.createTripSearchMetadata().nextDateTime) + ); + } + + @Test + @SuppressWarnings("deprecation") + void testCreateTripSearchMetadataArriveByWithPageCut() { + var model = TestPagingModel.testDataWithManyItinerariesCaseA(); + var testDriver = model.arriveByDriver(T12_00, T13_00, SEARCH_WINDOW_USED, 3); + var subject = testDriver.pagingService(); + + assertEquals(D30m, subject.createTripSearchMetadata().searchWindowUsed); + assertEquals( + "11:39", + TestPagingUtils.cleanStr(subject.createTripSearchMetadata().prevDateTime) + ); + assertEquals( + "12:30", + TestPagingUtils.cleanStr(subject.createTripSearchMetadata().nextDateTime) + ); + } + + @Test + @SuppressWarnings("deprecation") + void testCreateTripSearchMetadataArriveByWithNormalSearchWindow() { + var model = TestPagingModel.testDataWithFewItinerariesCaseB(); + var testDriver = model.arriveByDriver(T09_15, T12_00, SEARCH_WINDOW_USED, 3); + var subject = testDriver.pagingService(); + + assertEquals(D30m, subject.createTripSearchMetadata().searchWindowUsed); + assertEquals( + "08:45", + TestPagingUtils.cleanStr(subject.createTripSearchMetadata().prevDateTime) + ); + assertEquals( + "09:45", + TestPagingUtils.cleanStr(subject.createTripSearchMetadata().nextDateTime) + ); + } +} diff --git a/src/test/java/org/opentripplanner/service/paging/PS2_ManyParetoOptimalItinerariesTest.java b/src/test/java/org/opentripplanner/service/paging/PS2_ManyParetoOptimalItinerariesTest.java new file mode 100644 index 00000000000..76a1ae0de1b --- /dev/null +++ b/src/test/java/org/opentripplanner/service/paging/PS2_ManyParetoOptimalItinerariesTest.java @@ -0,0 +1,224 @@ +package org.opentripplanner.service.paging; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.service.paging.TestPagingModel.D30m; +import static org.opentripplanner.service.paging.TestPagingModel.T12_00; +import static org.opentripplanner.service.paging.TestPagingModel.T12_30; +import static org.opentripplanner.service.paging.TestPagingModel.T13_00; +import static org.opentripplanner.service.paging.TestPagingModel.T13_30; + +import java.time.Duration; +import org.junit.jupiter.api.Test; +import org.opentripplanner.model.plan.SortOrder; +import org.opentripplanner.model.plan.paging.cursor.PageCursor; +import org.opentripplanner.model.plan.paging.cursor.PageType; + +/** + * This test focus on testing the paging with many itineraries which all is pareto-optimal. + *

+ * Each test do a next/prev search followed by another search (both prev/next) and verify + * all properties in the page-token. There are 4 test: + *

+ * - DEPART AFTER search -> verify NEXT -> search next -> verify NEXT & PREVIOUS
+ * - DEPART AFTER search -> verify PREVIOUS -> search next -> verify NEXT & PREVIOUS
+ * - ARRIVE BY search -> verify NEXT -> search previous -> verify NEXT & PREVIOUS
+ * - ARRIVE BY search -> verify PREVIOUS -> search previous -> verify NEXT & PREVIOUS
+ * 
+ * Note! We are not doing the actual search, just emulating the search using the + * {@link TestDriver} mock. + *

+ * All components required to test paging is used including the {@link PagingService} and the + * 3 filters: + *

    + *
  1. PagingFilter
  2. + *
  3. OutsideSearchWindowFilter
  4. + *
  5. NumItinerariesFilter
  6. + *
+ *

+ */ +@SuppressWarnings("DataFlowIssue") +class PS2_ManyParetoOptimalItinerariesTest { + + private static final Duration SEARCH_WINDOW_USED = D30m; + private final TestPagingModel model = TestPagingModel.testDataWithManyItinerariesCaseA(); + + /** + * Test paging DEPART AFTER search with NEXT -> NEXT/PREVIOUS + */ + @Test + void testPagingDepartAfterAndNext() { + var testDriver = model.departAfterDriver(T12_00, SEARCH_WINDOW_USED, 3); + var subject = testDriver.pagingService(); + var nextCursor = subject.nextPageCursor(); + + assertPageCursor( + nextCursor, + PageType.NEXT_PAGE, + SortOrder.STREET_AND_ARRIVAL_TIME, + "12:09", + "" + ); + + // TEST PAGING AFTER NEXT -> NEXT & PREVIOUS + { + var nextDriver = testDriver.newPage(nextCursor); + var nextSubject = nextDriver.pagingService(nextCursor); + + // PREV + assertPageCursor( + nextSubject.previousPageCursor(), + PageType.PREVIOUS_PAGE, + SortOrder.STREET_AND_ARRIVAL_TIME, + "11:39", + "" + ); + // NEXT + assertPageCursor( + nextSubject.nextPageCursor(), + PageType.NEXT_PAGE, + SortOrder.STREET_AND_ARRIVAL_TIME, + "12:29", + "" + ); + } + } + + /** + * Test paging DEPART AFTER search with PREVIOUS -> NEXT/PREVIOUS + */ + @Test + void testPagingDepartAfterAndPrevious() { + var testDriver = model.departAfterDriver(T12_30, SEARCH_WINDOW_USED, 3); + var subject = testDriver.pagingService(); + var prevCursor = subject.previousPageCursor(); + + assertPageCursor( + prevCursor, + PageType.PREVIOUS_PAGE, + SortOrder.STREET_AND_ARRIVAL_TIME, + "12:00", + "" + ); + + // TEST PAGING AFTER PREVIOUS + { + var prevDriver = testDriver.newPage(prevCursor); + var prevSubject = prevDriver.pagingService(prevCursor); + + // PREV + assertPageCursor( + prevSubject.previousPageCursor(), + PageType.PREVIOUS_PAGE, + SortOrder.STREET_AND_ARRIVAL_TIME, + "11:41", + "" + ); + // NEXT + assertPageCursor( + prevSubject.nextPageCursor(), + PageType.NEXT_PAGE, + SortOrder.STREET_AND_ARRIVAL_TIME, + "12:30", + "" + ); + } + } + + /** + * Test paging ARRIVE BY search with NEXT -> NEXT/PREVIOUS + */ + @Test + void testPagingArriveByAndNext() { + var testDriver = model.arriveByDriver(T12_00, T13_00, D30m, 3); + var subject = testDriver.pagingService(); + + var nextCursor = subject.nextPageCursor(); + + assertPageCursor( + nextCursor, + PageType.NEXT_PAGE, + SortOrder.STREET_AND_DEPARTURE_TIME, + "12:30", + "" + ); + + // TEST PAGING AFTER NEXT -> NEXT & PREVIOUS + { + var nextDriver = testDriver.newPage(nextCursor); + var nextSubject = nextDriver.pagingService(nextCursor); + + // PREV + assertPageCursor( + nextSubject.previousPageCursor(), + PageType.PREVIOUS_PAGE, + SortOrder.STREET_AND_DEPARTURE_TIME, + "12:00", + "" + ); + // NEXT + assertPageCursor( + nextSubject.nextPageCursor(), + PageType.NEXT_PAGE, + SortOrder.STREET_AND_DEPARTURE_TIME, + "12:40", + "" + ); + } + } + + /** + * Test paging ARRIVE BY search with PREVIOUS -> NEXT/PREVIOUS + */ + @Test + void testPagingArriveByAndPrevious() { + var testDriver = model.arriveByDriver(T12_30, T13_30, SEARCH_WINDOW_USED, 3); + var subject = testDriver.pagingService(); + + var cursor = subject.previousPageCursor(); + + assertPageCursor( + cursor, + PageType.PREVIOUS_PAGE, + SortOrder.STREET_AND_DEPARTURE_TIME, + "12:11", + "13:30" + ); + + // TEST PAGING AFTER PREVIOUS + { + var prevDriver = testDriver.newPage(cursor); + var prevSubject = prevDriver.pagingService(cursor); + + // PREV + assertPageCursor( + prevSubject.previousPageCursor(), + PageType.PREVIOUS_PAGE, + SortOrder.STREET_AND_DEPARTURE_TIME, + "12:00", + "13:30" + ); + // NEXT + assertPageCursor( + prevSubject.nextPageCursor(), + PageType.NEXT_PAGE, + SortOrder.STREET_AND_DEPARTURE_TIME, + "12:41", + "" + ); + } + } + + private static void assertPageCursor( + PageCursor cursor, + PageType pageType, + SortOrder sortOrder, + String edt, + String lat + ) { + assertEquals(pageType, cursor.type()); + assertEquals(sortOrder, cursor.originalSortOrder()); + assertEquals(D30m, cursor.searchWindow()); + assertEquals(edt, TestPagingUtils.cleanStr(cursor.earliestDepartureTime())); + assertEquals(lat, TestPagingUtils.cleanStr(cursor.latestArrivalTime())); + } +} diff --git a/src/test/java/org/opentripplanner/service/paging/PS3_FewItinerariesOnSearchWindowLimitTest.java b/src/test/java/org/opentripplanner/service/paging/PS3_FewItinerariesOnSearchWindowLimitTest.java new file mode 100644 index 00000000000..4db61579a47 --- /dev/null +++ b/src/test/java/org/opentripplanner/service/paging/PS3_FewItinerariesOnSearchWindowLimitTest.java @@ -0,0 +1,188 @@ +package org.opentripplanner.service.paging; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.model.plan.paging.cursor.PageType.NEXT_PAGE; +import static org.opentripplanner.model.plan.paging.cursor.PageType.PREVIOUS_PAGE; +import static org.opentripplanner.service.paging.TestPagingUtils.cleanStr; + +import java.time.Duration; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.framework.time.TimeUtils; +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.PageCursor; +import org.opentripplanner.model.plan.paging.cursor.PageType; + +/** + * This test focus on testing the paging with few itineraries. There should be no page-cuts. The + * test focus on paging back and forth, matching itineraries with departure time at + * the exact same time as the search-window earliest-departure-time. + *

+ * Note! We are not doing the actual search, just emulating the search using the + * {@link TestDriver} mock. + *

+ * All components required to test paging is used including the {@link PagingService} and the + * 3 filters: + *

    + *
  1. PagingFilter
  2. + *
  3. OutsideSearchWindowFilter
  4. + *
  5. NumItinerariesFilter
  6. + *
+ *

+ */ +@SuppressWarnings("DataFlowIssue") +class PS3_FewItinerariesOnSearchWindowLimitTest { + + private static final Duration SEARCH_WINDOW = Duration.ofHours(6); + private static final int SEARCH_WINDOW_SEC = (int) SEARCH_WINDOW.toSeconds(); + /** We are avoiding the pageCut in this test - hence setting the value high > 2*/ + private static final int NUM_OF_ITINERARIES = 10; + private static final String LATEST_ARRIVAL_TIME_TEXT = "12:00+1d"; + private static final int LATEST_ARRIVAL_TIME = TimeUtils.time(LATEST_ARRIVAL_TIME_TEXT); + private static final boolean DEPART_AFTER = false; + private static final boolean ARRIVE_BY = true; + public static final String EMPTY = ""; + + private final TestPagingModel model = TestPagingModel.testDataWithFewItinerariesCaseB(); + private TestDriver driver; + + /** + * List of test-cases - for example: + *

+   *   Given: "08:00", DEPART_AFTER, "1 N 2 N - P 2 -"
+   *     - "08:00"        : The first search departure time
+   *     - DEPART_AFTER   : If first search is arriveBy or depart after search
+   *     - "1 N 2 N - .." : Paging sequence - N:NEXT or P:PREVIOUS, 0-4 is the expected itinerary
+   *                        index found in the search between paging events, and '-' means no
+   *                        itinerary found.
+   * 
+ */ + static List testCases() { + return List.of( + // Itineraries depart inside search-window, step NEXT 6 times + //Arguments.of("08:00", DEPART_AFTER, "1 N 2 N - N - N 3 N - N"), + // Itineraries depart inside search-window, step PREV 6 times + //Arguments.of("08:00+1d", DEPART_AFTER, "3 P - P - P 2 P 1 P - P"), + // Itineraries depart inside search-window, step BACK and FORTH + //Arguments.of("08:00", DEPART_AFTER, "1 N 2 N - P 2 N - P 2 P 1 N 2 N"), + // Itineraries depart exactly at the start of search-window [inc, exc>, same sequence as + // test above + Arguments.of("09:00", DEPART_AFTER, "1 N 2 N - P 2 N - P 2 P 1 N 2 N"), + // Itineraries depart inside search-window, step NEXT 6 times + Arguments.of("10:00-1d", ARRIVE_BY, "3 N - N - N 2 N 1 N - N"), + // Itineraries depart inside search-window, step PREV 6 times + Arguments.of("08:00+1d", ARRIVE_BY, "0 P - P - P 1 P 2 P - P"), + // Itineraries depart inside search-window, step BACK and FORTH + Arguments.of("08:00", ARRIVE_BY, "2 P - P - P 3 N - N - P - N - N 2 N 1 P 2"), + // Itineraries depart exactly at the start of search-window [inc, exc>, same sequence as + // test above + Arguments.of("09:00", ARRIVE_BY, "2 P - P - P 3 N - N - P - N - N 2 N 1 P 2") + ); + } + + /** + * Test paging DEPART AFTER search with NEXT -> NEXT/PREVIOUS + */ + @ParameterizedTest + @MethodSource("testCases") + void test(String edt, boolean arriveBy, String testCaseTokens) { + PageCursor cursor; + SortOrder expectedSortOrder; + + int currTime = TimeUtils.time(edt); + + if (arriveBy) { + expectedSortOrder = SortOrder.STREET_AND_DEPARTURE_TIME; + driver = + model.arriveByDriver(currTime, LATEST_ARRIVAL_TIME, SEARCH_WINDOW, NUM_OF_ITINERARIES); + } else { + expectedSortOrder = SortOrder.STREET_AND_ARRIVAL_TIME; + driver = model.departAfterDriver(currTime, SEARCH_WINDOW, NUM_OF_ITINERARIES); + } + + var subject = driver.pagingService(); + var testCases = parseTestCases(testCaseTokens); + + for (var tc : testCases) { + if (tc.gotoNewPage()) { + if (tc.gotoPage() == NEXT_PAGE) { + cursor = subject.nextPageCursor(); + currTime += SEARCH_WINDOW_SEC; + } else { + cursor = subject.previousPageCursor(); + currTime -= SEARCH_WINDOW_SEC; + } + String description = tc.testDescription(); + assertEquals(tc.gotoPage(), cursor.type(), description); + assertEquals(expectedSortOrder, cursor.originalSortOrder(), description); + assertEquals(SEARCH_WINDOW, cursor.searchWindow(), description); + assertEquals( + TimeUtils.timeToStrCompact(currTime), + cleanStr(cursor.earliestDepartureTime()), + description + ); + + // Expect initial LAT - could not change to another time - TODO: FIX THIS AND ENABLE TEST + // assertEquals(expLat, cleanStr(cursor.earliestDepartureTime()), description); + driver = driver.newPage(cursor); + subject = driver.pagingService(cursor); + } else { + assertEquals(tc.expItinerary, getResultAsString(driver.kept()), tc.testDescription()); + } + } + } + + private List parseTestCases(String tokens) { + tokens = tokens.replaceAll("\\s+", ""); + var sequence = new StringBuilder(); + return tokens.chars().mapToObj(ch -> parseToken(Character.toString(ch), sequence)).toList(); + } + + private TestCase parseToken(String token, StringBuilder sequence) { + switch (token) { + case "-": + sequence.append(" > -"); + return new TestCase(sequence.substring(3), null, -1, EMPTY); + case "0", "1", "2", "3": + sequence.append(" > ").append(token); + int i = Integer.parseInt(token); + return new TestCase( + sequence.substring(3), + null, + i, + cleanStr(driver.all().get(i).keyAsString()) + ); + case "N": + sequence.append(" > NEXT"); + return new TestCase(sequence.substring(3), NEXT_PAGE, -1, EMPTY); + case "P": + sequence.append(" > PREV"); + return new TestCase(sequence.substring(3), PREVIOUS_PAGE, -1, EMPTY); + default: + throw new IllegalArgumentException(token); + } + } + + private static String getResultAsString(List kept) { + return kept + .stream() + .map(ItinerarySortKey::keyAsString) + .map(TestPagingUtils::cleanStr) + .collect(Collectors.joining()); + } + + record TestCase(String sequence, PageType gotoPage, int expIndex, String expItinerary) { + boolean gotoNewPage() { + return gotoPage != null; + } + + String testDescription() { + return "Failed after paging sequence: ( " + sequence + " )"; + } + } +} diff --git a/src/test/java/org/opentripplanner/service/paging/TestDriver.java b/src/test/java/org/opentripplanner/service/paging/TestDriver.java new file mode 100644 index 00000000000..0b40b140139 --- /dev/null +++ b/src/test/java/org/opentripplanner/service/paging/TestDriver.java @@ -0,0 +1,197 @@ +package org.opentripplanner.service.paging; + +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import javax.annotation.Nullable; +import org.opentripplanner._support.debug.TestDebug; +import org.opentripplanner.framework.collection.ListSection; +import org.opentripplanner.framework.lang.Box; +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.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; + +/** + * This class simulate/mock the context the paging is operating in. + */ +final class TestDriver { + + private final int nResults; + private final Duration searchWindow; + private final List all; + private final List kept; + private final Instant edt; + private final Instant lat; + private final SortOrder sortOrder; + private final ListSection cropSection; + private final NumItinerariesFilterResults results; + + public TestDriver( + int nResults, + Duration searchWindow, + List all, + List kept, + Instant edt, + Instant lat, + SortOrder sortOrder, + ListSection cropSection, + NumItinerariesFilterResults results + ) { + this.nResults = nResults; + this.searchWindow = searchWindow; + this.all = all; + this.kept = kept; + this.edt = edt; + this.lat = lat; + this.sortOrder = sortOrder; + this.cropSection = cropSection; + this.results = results; + debug(); + } + + static TestDriver driver( + int edt, + int lat, + Duration searchWindow, + int nResults, + SortOrder sortOrder, + List all + ) { + return createNewDriver( + TestPagingModel.time(edt), + TestPagingModel.time(lat), + searchWindow, + nResults, + sortOrder, + all, + ListSection.TAIL, + null + ); + } + + int nResults() { + return nResults; + } + + public Duration searchWindow() { + return searchWindow; + } + + List kept() { + return kept; + } + + List all() { + return all; + } + + Instant earliestDepartureTime() { + return edt; + } + + Instant latestArrivalTime() { + return lat; + } + + SortOrder sortOrder() { + return sortOrder; + } + + boolean arrivedBy() { + return !sortOrder.isSortedByAscendingArrivalTime(); + } + + NumItinerariesFilterResults filterResults() { + return results; + } + + ItinerarySortKey expectedCut() { + return results == null ? null : results.pageCut(); + } + + TestDriver newPage(PageCursor cursor) { + return createNewDriver( + cursor.earliestDepartureTime(), + cursor.latestArrivalTime(), + searchWindow, + nResults, + sortOrder, + all, + cursor.cropItinerariesAt(), + cursor.itineraryPageCut() + ); + } + + PagingService pagingService() { + return TestPagingModel.pagingService(this); + } + + PagingService pagingService(PageCursor cursor) { + return TestPagingModel.pagingService(this, cursor); + } + + private static TestDriver createNewDriver( + Instant edt, + Instant lat, + Duration searchWindow, + int nResults, + SortOrder sortOrder, + List all, + ListSection cropItineraries, + @Nullable ItinerarySortKey pageCut + ) { + List kept = all; + + // Filter search-window + var swFilter = new OutsideSearchWindowFilter(edt, searchWindow); + kept = swFilter.removeMatchesForTest(kept); + + // Simulate Raptor - apply LAT filtering done by raptor + if (lat != null) { + kept = kept.stream().filter(it -> !lat.isBefore(it.endTime().toInstant())).toList(); + } + + //Page filter + if (pageCut != null) { + var pageFilter = new PagingFilter(sortOrder, cropItineraries.invert(), pageCut); + kept = pageFilter.removeMatchesForTest(kept); + } + + // Filter nResults + var filterResultBox = new Box(); + var maxNumFilter = new NumItinerariesFilter(nResults, cropItineraries, filterResultBox::set); + kept = maxNumFilter.removeMatchesForTest(kept); + + return new TestDriver( + nResults, + searchWindow, + all, + kept, + edt, + lat, + sortOrder, + ListSection.TAIL, + filterResultBox.get() + ); + } + + private void debug() { + if (TestDebug.off()) { + return; + } + TestDebug.println(); + TestDebug.println("ITINERARIES:"); + all.forEach(it -> { + var value = TestPagingUtils.toString(it); + if (kept.contains(it)) { + value += " *"; + } + TestDebug.println(value); + }); + } +} diff --git a/src/test/java/org/opentripplanner/service/paging/TestPagingModel.java b/src/test/java/org/opentripplanner/service/paging/TestPagingModel.java new file mode 100644 index 00000000000..f47e3444264 --- /dev/null +++ b/src/test/java/org/opentripplanner/service/paging/TestPagingModel.java @@ -0,0 +1,233 @@ +package org.opentripplanner.service.paging; + +import static org.opentripplanner.framework.time.TimeUtils.hm2time; +import static org.opentripplanner.model.plan.SortOrder.STREET_AND_ARRIVAL_TIME; +import static org.opentripplanner.model.plan.SortOrder.STREET_AND_DEPARTURE_TIME; + +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import org.opentripplanner.framework.time.TimeUtils; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.Place; +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.transit.model._data.TransitModelForTest; + +class TestPagingModel { + + // Times CASE - A + static final int T12_00 = hm2time(12, 0); + static final int T12_09 = hm2time(12, 9); + static final int T12_10 = hm2time(12, 10); + static final int T12_25 = hm2time(12, 25); + static final int T12_29 = hm2time(12, 29); + static final int T12_30 = hm2time(12, 30); + static final int T12_39 = hm2time(12, 39); + static final int T12_40 = hm2time(12, 40); + static final int T12_41 = hm2time(12, 41); + static final int T12_55 = hm2time(12, 55); + static final int T12_59 = hm2time(12, 59); + static final int T13_00 = hm2time(13, 0); + static final int T13_10 = hm2time(13, 10); + static final int T13_11 = hm2time(13, 11); + static final int T13_25 = hm2time(13, 25); + static final int T13_30 = hm2time(13, 30); + static final int TIME_NOT_SET = -9_999_999; + + // Times CASE - B + static final int T15_00_MINUS_1d = TimeUtils.time("15:00:00-1d"); + static final int T15_30_MINUS_1d = TimeUtils.time("15:30:00-1d"); + static final int T09_00 = hm2time(9, 0); + static final int T09_30 = hm2time(9, 30); + static final int T15_00 = hm2time(15, 0); + static final int T15_30 = hm2time(15, 30); + static final int T09_00_PLUS_1d = TimeUtils.time("09:00:00+1d"); + static final int T09_30_PLUS_1d = TimeUtils.time("09:30:00+1d"); + + static final Duration D30m = Duration.ofMinutes(30); + + // The SEARCH-WINDOW is set to "fixed" 30m in this test for simplicity + private static final List SEARCH_WINDOW_ADJUSTMENTS = List.of(); + + private static final Instant TRANSIT_START_TIME = TestItineraryBuilder.newTime(0).toInstant(); + + private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of(); + private static final Place A = Place.forStop(TEST_MODEL.stop("A").build()); + private static final Place B = Place.forStop(TEST_MODEL.stop("B").build()); + + static final boolean ON_STREET = false; + static final boolean TRANSIT = true; + static final int COST_HIGH = 10; + static final int COST_LOW = 9; + static final int TX_1 = 1; + static final int TX_0 = 0; + + private static final List ITINERARIES_CASE_A = List.of( + // EDT time-shifted onStreet result (apply to first depart-after search) + itinerary(T12_00, T12_30, COST_HIGH, TX_0, ON_STREET), + // Next itineraries are almost the same - a criterion is better for each + itinerary(T12_10, T12_41, COST_HIGH, TX_1, TRANSIT), + itinerary(T12_10, T12_40, COST_LOW, TX_1, TRANSIT), + itinerary(T12_10, T12_40, COST_HIGH, TX_0, TRANSIT), + itinerary(T12_09, T12_40, COST_HIGH, TX_1, TRANSIT), + itinerary(T12_25, T12_55, COST_HIGH, TX_1, TRANSIT), + // An itinerary with a very long duration + itinerary(T12_29, T13_11, COST_LOW, TX_0, TRANSIT), + // Next itineraries are almost the same - a criterion is better for each + itinerary(T12_40, T13_11, COST_HIGH, TX_1, TRANSIT), + itinerary(T12_40, T13_10, COST_LOW, TX_1, TRANSIT), + itinerary(T12_40, T13_10, COST_HIGH, TX_0, TRANSIT), + itinerary(T12_39, T13_10, COST_HIGH, TX_1, TRANSIT), + itinerary(T12_55, T13_25, COST_HIGH, TX_1, TRANSIT), + // LAT time-shifted onStreet result (apply to first arrive-by search) + itinerary(T12_59, T13_30, COST_HIGH, TX_0, ON_STREET) + ); + static final List ITINERARIES_CASE_A_DEPART_AFTER = ITINERARIES_CASE_A + // Skip last itinerary (onStreet arriveBy) + .subList(0, ITINERARIES_CASE_A.size() - 1) + .stream() + .sorted(SortOrderComparator.comparator(STREET_AND_ARRIVAL_TIME)) + .toList(); + + static final List ITINERARIES_CASE_A_ARRIVE_BY = ITINERARIES_CASE_A + // Skip first itinerary (onStreet departAfter) + .subList(1, ITINERARIES_CASE_A.size()) + .stream() + .sorted(SortOrderComparator.comparator(SortOrder.STREET_AND_DEPARTURE_TIME)) + .toList(); + + /** + * Case B only have 4 itineraries over 3 days: + *
+   *  - 15:00-1d
+   *  - 12:00
+   *  - 15:00
+   *  - 12:00+1d
+   *  
+ */ + private static final List ITINERARIES_CASE_B = List.of( + itinerary(T15_00_MINUS_1d, T15_30_MINUS_1d, COST_HIGH, TX_1, TRANSIT), + itinerary(T09_00, T09_30, COST_HIGH, TX_1, TRANSIT), + itinerary(T15_00, T15_30, COST_HIGH, TX_1, TRANSIT), + itinerary(T09_00_PLUS_1d, T09_30_PLUS_1d, COST_HIGH, TX_1, TRANSIT) + ); + + static final List ITINERARIES_CASE_B_DEPART_AFTER = ITINERARIES_CASE_B + .stream() + .sorted(SortOrderComparator.comparator(STREET_AND_ARRIVAL_TIME)) + .toList(); + + static final List ITINERARIES_CASE_B_ARRIVE_BY = ITINERARIES_CASE_B + .stream() + .sorted(SortOrderComparator.comparator(SortOrder.STREET_AND_DEPARTURE_TIME)) + .toList(); + + private final List itinerariesDepartAfter; + private final List itinerariesArriveBy; + + private TestPagingModel( + List itinerariesDepartAfter, + List itinerariesArriveBy + ) { + this.itinerariesDepartAfter = itinerariesDepartAfter; + this.itinerariesArriveBy = itinerariesArriveBy; + } + + static TestPagingModel testDataWithManyItinerariesCaseA() { + return new TestPagingModel(ITINERARIES_CASE_A_DEPART_AFTER, ITINERARIES_CASE_A_ARRIVE_BY); + } + + static TestPagingModel testDataWithFewItinerariesCaseB() { + return new TestPagingModel(ITINERARIES_CASE_B_DEPART_AFTER, ITINERARIES_CASE_B_ARRIVE_BY); + } + + static PagingService pagingService(TestDriver testDriver) { + return new PagingService( + SEARCH_WINDOW_ADJUSTMENTS, + testDriver.searchWindow(), + testDriver.searchWindow(), + testDriver.searchWindow(), + testDriver.earliestDepartureTime(), + testDriver.latestArrivalTime(), + testDriver.sortOrder(), + testDriver.arrivedBy(), + testDriver.nResults(), + null, + testDriver.filterResults(), + testDriver.kept() + ); + } + + static PagingService pagingService(TestDriver testDriver, PageCursor pageCursor) { + return new PagingService( + SEARCH_WINDOW_ADJUSTMENTS, + testDriver.searchWindow(), + testDriver.searchWindow(), + testDriver.searchWindow(), + pageCursor.earliestDepartureTime(), + pageCursor.latestArrivalTime(), + pageCursor.originalSortOrder(), + testDriver.arrivedBy(), + testDriver.nResults(), + pageCursor, + testDriver.filterResults(), + testDriver.kept() + ); + } + + TestDriver arriveByDriver(int edt, int lat, Duration searchWindow, int nResults) { + return TestDriver.driver( + edt, + lat, + searchWindow, + nResults, + STREET_AND_DEPARTURE_TIME, + itinerariesArriveBy + ); + } + + TestDriver departAfterDriver(int edt, Duration searchWindow, int nResults) { + return TestDriver.driver( + edt, + TIME_NOT_SET, + searchWindow, + nResults, + STREET_AND_ARRIVAL_TIME, + itinerariesDepartAfter + ); + } + + static Instant time(int time) { + return time == TIME_NOT_SET ? null : TRANSIT_START_TIME.plusSeconds(time); + } + + private static Itinerary itinerary( + int departureTime, + int arrivalTime, + int cost, + int nTransfers, + boolean transit + ) { + var builder = TestItineraryBuilder.newItinerary(A); + + if (transit) { + if (nTransfers == 0) { + builder.bus(10, departureTime, arrivalTime, B); + } else if (nTransfers == 1) { + builder + .bus(20, departureTime, departureTime + 120, B) + .bus(21, departureTime + 240, arrivalTime, B); + } else { + throw new IllegalArgumentException("nTransfers not supported: " + nTransfers); + } + } else { + builder.drive(departureTime, arrivalTime, B); + } + var it = builder.build(); + it.setGeneralizedCost(cost); + return it; + } +} diff --git a/src/test/java/org/opentripplanner/service/paging/TestPagingUtils.java b/src/test/java/org/opentripplanner/service/paging/TestPagingUtils.java new file mode 100644 index 00000000000..9533e22ced7 --- /dev/null +++ b/src/test/java/org/opentripplanner/service/paging/TestPagingUtils.java @@ -0,0 +1,28 @@ +package org.opentripplanner.service.paging; + +import org.opentripplanner.model.plan.ItinerarySortKey; + +class TestPagingUtils { + + static String cleanStr(Object value) { + if (value == null) { + return ""; + } + return value + .toString() + .replaceAll("2020-02-01T(\\d\\d:\\d\\d):00Z", "$1-1d") + .replaceAll("2020-02-02T(\\d\\d:\\d\\d):00Z", "$1") + .replaceAll("2020-02-03T(\\d\\d:\\d\\d):00Z", "$1+1d") + .replaceAll("0(\\d:00)", "$1"); + } + + static String toString(ItinerarySortKey it) { + if (it == null) { + return ""; + } + var value = it.keyAsString(); + value = cleanStr(value); + // indent cost with one digit + return value.replaceAll("(\\$\\d,|transit])", " $1"); + } +} diff --git a/src/test/java/org/opentripplanner/smoketest/SmokeTest.java b/src/test/java/org/opentripplanner/smoketest/SmokeTest.java index 1a1085b7029..f9f52424b88 100644 --- a/src/test/java/org/opentripplanner/smoketest/SmokeTest.java +++ b/src/test/java/org/opentripplanner/smoketest/SmokeTest.java @@ -49,7 +49,7 @@ public class SmokeTest { */ public static LocalDateTime weekdayAtNoon() { var today = LocalDate.now(); - return today.with(TemporalAdjusters.next(DayOfWeek.TUESDAY)).atTime(LocalTime.of(12, 0)); + return today.with(TemporalAdjusters.next(DayOfWeek.THURSDAY)).atTime(LocalTime.of(12, 0)); } public static void assertThatThereAreVehicleRentalStations() { diff --git a/src/test/java/org/opentripplanner/standalone/config/framework/json/NodeInfoTest.java b/src/test/java/org/opentripplanner/standalone/config/framework/json/NodeInfoTest.java index 38d7be14f1e..3227e43b23d 100644 --- a/src/test/java/org/opentripplanner/standalone/config/framework/json/NodeInfoTest.java +++ b/src/test/java/org/opentripplanner/standalone/config/framework/json/NodeInfoTest.java @@ -78,6 +78,15 @@ void typeDescription() { assertEquals("enum set", createBuilder().withEnumSet(AnEnum.class).build().typeDescription()); } + @Test + void experimentalFeature() { + var subject = createBuilder().withExperimentalFeature().build(); + assertEquals(NodeInfo.EXPERIMENTAL_FEATURE, subject.description()); + + subject = createBuilder().withDescription("Description").withExperimentalFeature().build(); + assertEquals("Description\n\n" + NodeInfo.EXPERIMENTAL_FEATURE, subject.description()); + } + private NodeInfoBuilder createBuilder() { return NodeInfo .of() diff --git a/src/test/java/org/opentripplanner/street/integration/ParkAndRideTest.java b/src/test/java/org/opentripplanner/street/integration/ParkAndRideTest.java index 10ab2e27232..6dd53ae0c6e 100644 --- a/src/test/java/org/opentripplanner/street/integration/ParkAndRideTest.java +++ b/src/test/java/org/opentripplanner/street/integration/ParkAndRideTest.java @@ -11,8 +11,6 @@ import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.api.request.request.StreetRequest; -import org.opentripplanner.routing.api.request.request.filter.VehicleParkingFilter.TagsFilter; -import org.opentripplanner.routing.api.request.request.filter.VehicleParkingFilterRequest; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.street.model.vertex.StreetVertex; import org.opentripplanner.street.model.vertex.Vertex; @@ -140,19 +138,24 @@ protected List runStreetSearchAndCreateDescriptor( var request = new RouteRequest(); request.withPreferences(preferences -> preferences - .withBike(b -> b.withParkCost(120).withParkTime(60)) - .withCar(c -> c.withParkCost(240).withParkTime(180)) + .withBike(b -> + b.withParking(parking -> { + parking.withRequiredVehicleParkingTags(requiredTags); + parking.withBannedVehicleParkingTags(bannedTags); + parking.withParkCost(120); + parking.withParkTime(60); + }) + ) + .withCar(c -> + c.withParking(parking -> { + parking.withRequiredVehicleParkingTags(requiredTags); + parking.withBannedVehicleParkingTags(bannedTags); + parking.withParkCost(240); + parking.withParkTime(180); + }) + ) ); request.setWheelchair(requireWheelChairAccessible); - request - .journey() - .parking() - .setFilter( - new VehicleParkingFilterRequest( - List.of(new TagsFilter(bannedTags)), - List.of(new TagsFilter(requiredTags)) - ) - ); request.setArriveBy(arriveBy); var tree = StreetSearchBuilder 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 ce94cc92a4c..a9932381b40 100644 --- a/src/test/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLinkTest.java +++ b/src/test/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLinkTest.java @@ -14,9 +14,6 @@ import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.routing.api.request.StreetMode; -import org.opentripplanner.routing.api.request.request.VehicleParkingRequest; -import org.opentripplanner.routing.api.request.request.filter.VehicleParkingFilter; -import org.opentripplanner.routing.api.request.request.filter.VehicleParkingFilterRequest; import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingEntrance; import org.opentripplanner.street.model.vertex.VehicleParkingEntranceVertex; @@ -59,17 +56,18 @@ void foo(Set parkingTags, Set not, Set select, boolean s .build(); var entranceVertex = new VehicleParkingEntranceVertex(entrance); - var parkingReq = new VehicleParkingRequest(); - - Set notFilter = Set.of(new VehicleParkingFilter.TagsFilter(not)); - Set selectFilter = Set.of(new VehicleParkingFilter.TagsFilter(select)); - - parkingReq.setFilter(new VehicleParkingFilterRequest(notFilter, selectFilter)); var req = StreetSearchRequest.of(); req.withMode(StreetMode.BIKE_TO_PARK); - req.withParking(parkingReq); - req.withPreferences(p -> p.withBike(bike -> bike.withParkCost(0))); + req.withPreferences(p -> + p.withBike(bike -> { + bike.withParking(parkingPreferences -> { + parkingPreferences.withRequiredVehicleParkingTags(select); + parkingPreferences.withBannedVehicleParkingTags(not); + parkingPreferences.withParkCost(0); + }); + }) + ); var edge = StreetVehicleParkingLink.createStreetVehicleParkingLink( streetVertex, diff --git a/src/test/java/org/opentripplanner/street/model/edge/VehicleParkingEdgeTest.java b/src/test/java/org/opentripplanner/street/model/edge/VehicleParkingEdgeTest.java index a39fed89d6f..7dad61fbd6b 100644 --- a/src/test/java/org/opentripplanner/street/model/edge/VehicleParkingEdgeTest.java +++ b/src/test/java/org/opentripplanner/street/model/edge/VehicleParkingEdgeTest.java @@ -8,7 +8,6 @@ import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.routing.api.request.StreetMode; -import org.opentripplanner.routing.api.request.request.VehicleParkingRequest; import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; import org.opentripplanner.street.model.vertex.VehicleParkingEntranceVertex; @@ -46,8 +45,7 @@ public void realtimeAvailableCarPlacesTest() { StreetMode.CAR_TO_PARK, false, true, - VehicleParkingSpaces.builder().carSpaces(1).build(), - true + VehicleParkingSpaces.builder().carSpaces(1).build() ); var s1 = traverse(); @@ -57,28 +55,13 @@ public void realtimeAvailableCarPlacesTest() { @Test public void realtimeAvailableCarPlacesFallbackTest() { - initEdgeAndRequest(StreetMode.CAR_TO_PARK, false, true, null, true); + initEdgeAndRequest(StreetMode.CAR_TO_PARK, false, true, null); var s1 = traverse(); assertFalse(State.isEmpty(s1)); } - @Test - public void realtimeNotAvailableCarPlacesTest() { - initEdgeAndRequest( - StreetMode.CAR_TO_PARK, - false, - true, - VehicleParkingSpaces.builder().carSpaces(0).build(), - true - ); - - var s1 = traverse(); - - assertTrue(State.isEmpty(s1)); - } - @Test public void availableBicyclePlacesTest() { initEdgeAndRequest(StreetMode.BIKE_TO_PARK, true, false); @@ -103,8 +86,7 @@ public void realtimeAvailableBicyclePlacesTest() { StreetMode.BIKE_TO_PARK, true, false, - VehicleParkingSpaces.builder().bicycleSpaces(1).build(), - true + VehicleParkingSpaces.builder().bicycleSpaces(1).build() ); var s1 = traverse(); @@ -114,51 +96,33 @@ public void realtimeAvailableBicyclePlacesTest() { @Test public void realtimeAvailableBicyclePlacesFallbackTest() { - initEdgeAndRequest(StreetMode.BIKE_TO_PARK, true, false, null, true); + initEdgeAndRequest(StreetMode.BIKE_TO_PARK, true, false, null); var s1 = traverse(); assertFalse(State.isEmpty(s1)); } - @Test - public void realtimeNotAvailableBicyclePlacesTest() { - initEdgeAndRequest( - StreetMode.BIKE_TO_PARK, - true, - false, - VehicleParkingSpaces.builder().bicycleSpaces(0).build(), - true - ); - - var s1 = traverse(); - - assertTrue(State.isEmpty(s1)); - } - private void initEdgeAndRequest( StreetMode parkingMode, boolean bicyclePlaces, boolean carPlaces ) { - initEdgeAndRequest(parkingMode, bicyclePlaces, carPlaces, null, false); + initEdgeAndRequest(parkingMode, bicyclePlaces, carPlaces, null); } private void initEdgeAndRequest( StreetMode parkingMode, boolean bicyclePlaces, boolean carPlaces, - VehicleParkingSpaces availability, - boolean realtime + VehicleParkingSpaces availability ) { var vehicleParking = createVehicleParking(bicyclePlaces, carPlaces, availability); this.vertex = new VehicleParkingEntranceVertex(vehicleParking.getEntrances().get(0)); vehicleParkingEdge = VehicleParkingEdge.createVehicleParkingEdge(vertex); - var parking = new VehicleParkingRequest(); - parking.setUseAvailabilityInformation(realtime); - this.request = StreetSearchRequest.of().withMode(parkingMode).withParking(parking).build(); + this.request = StreetSearchRequest.of().withMode(parkingMode).build(); } private VehicleParking createVehicleParking( 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 0417aaf717c..41ecbe162bb 100644 --- a/src/test/java/org/opentripplanner/street/model/edge/VehicleParkingPreferredTagsTest.java +++ b/src/test/java/org/opentripplanner/street/model/edge/VehicleParkingPreferredTagsTest.java @@ -3,8 +3,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.params.provider.Arguments.of; -import java.util.Collection; -import java.util.List; import java.util.Set; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; @@ -13,9 +11,6 @@ import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.routing.api.request.StreetMode; -import org.opentripplanner.routing.api.request.request.VehicleParkingRequest; -import org.opentripplanner.routing.api.request.request.filter.VehicleParkingFilter; -import org.opentripplanner.routing.api.request.request.filter.VehicleParkingFilterRequest; import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingEntrance; import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; @@ -85,18 +80,18 @@ private void runTest( var fromV = new VehicleParkingEntranceVertex(entrance); var edge = VehicleParkingEdge.createVehicleParkingEdge(fromV); - var parkingReq = new VehicleParkingRequest(); - Collection select = List.of( - new VehicleParkingFilter.TagsFilter(preferredTags) - ); - parkingReq.setPreferred(new VehicleParkingFilterRequest(List.of(), select)); - parkingReq.setUnpreferredCost(EXTRA_COST); - var req = StreetSearchRequest.of(); req.withMode(StreetMode.BIKE_TO_PARK); - req.withParking(parkingReq); req.withArriveBy(arriveBy); - req.withPreferences(p -> p.withBike(bike -> bike.withParkCost(0))); + req.withPreferences(p -> + p.withBike(bike -> { + bike.withParking(parkingPreferences -> { + parkingPreferences.withUnpreferredVehicleParkingTagCost(EXTRA_COST); + parkingPreferences.withPreferredVehicleParkingTags(preferredTags); + parkingPreferences.withParkCost(0); + }); + }) + ); var result = traverse(fromV, edge, req.build()); diff --git a/src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java b/src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java similarity index 65% rename from src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java rename to src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java index 4c2cc41cef2..5b3a8f76052 100644 --- a/src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java +++ b/src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java @@ -3,12 +3,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; -import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_DWELL_TIME; -import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_HOP_TIME; +import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_DWELL_TIME; +import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_HOP_TIME; -import java.util.Collection; import java.util.LinkedList; import java.util.List; import org.junit.jupiter.api.Nested; @@ -17,17 +17,18 @@ import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.RegularStop; -class TripTimesTest { +class RealTimeTripTimesTest { private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of(); private static final String TRIP_ID = "testTripId"; - private static final List stops = List.of( + private static final List stopIds = List.of( id("A"), id("B"), id("C"), @@ -43,10 +44,10 @@ static TripTimes createInitialTripTimes() { List stopTimes = new LinkedList<>(); - for (int i = 0; i < stops.size(); ++i) { + for (int i = 0; i < stopIds.size(); ++i) { StopTime stopTime = new StopTime(); - RegularStop stop = TEST_MODEL.stop(stops.get(i).getId(), 0.0, 0.0).build(); + RegularStop stop = TEST_MODEL.stop(stopIds.get(i).getId(), 0.0, 0.0).build(); stopTime.setStop(stop); stopTime.setArrivalTime(i * 60); stopTime.setDepartureTime(i * 60); @@ -69,7 +70,7 @@ class Headsign { @Test void shouldHandleBothNullScenario() { Trip trip = TransitModelForTest.trip("TRIP").build(); - Collection stopTimes = List.of(EMPTY_STOPPOINT, EMPTY_STOPPOINT, EMPTY_STOPPOINT); + List stopTimes = List.of(EMPTY_STOPPOINT, EMPTY_STOPPOINT, EMPTY_STOPPOINT); TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); @@ -80,7 +81,7 @@ void shouldHandleBothNullScenario() { @Test void shouldHandleTripOnlyHeadSignScenario() { Trip trip = TransitModelForTest.trip("TRIP").withHeadsign(DIRECTION).build(); - Collection stopTimes = List.of(EMPTY_STOPPOINT, EMPTY_STOPPOINT, EMPTY_STOPPOINT); + List stopTimes = List.of(EMPTY_STOPPOINT, EMPTY_STOPPOINT, EMPTY_STOPPOINT); TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); @@ -93,11 +94,7 @@ void shouldHandleStopsOnlyHeadSignScenario() { Trip trip = TransitModelForTest.trip("TRIP").build(); StopTime stopWithHeadsign = new StopTime(); stopWithHeadsign.setStopHeadsign(STOP_TEST_DIRECTION); - Collection stopTimes = List.of( - stopWithHeadsign, - stopWithHeadsign, - stopWithHeadsign - ); + List stopTimes = List.of(stopWithHeadsign, stopWithHeadsign, stopWithHeadsign); TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); @@ -110,11 +107,7 @@ void shouldHandleStopsEqualToTripHeadSignScenario() { Trip trip = TransitModelForTest.trip("TRIP").withHeadsign(DIRECTION).build(); StopTime stopWithHeadsign = new StopTime(); stopWithHeadsign.setStopHeadsign(DIRECTION); - Collection stopTimes = List.of( - stopWithHeadsign, - stopWithHeadsign, - stopWithHeadsign - ); + List stopTimes = List.of(stopWithHeadsign, stopWithHeadsign, stopWithHeadsign); TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); @@ -127,7 +120,7 @@ void shouldHandleDifferingTripAndStopHeadSignScenario() { Trip trip = TransitModelForTest.trip("TRIP").withHeadsign(DIRECTION).build(); StopTime stopWithHeadsign = new StopTime(); stopWithHeadsign.setStopHeadsign(STOP_TEST_DIRECTION); - Collection stopTimes = List.of(stopWithHeadsign, EMPTY_STOPPOINT, EMPTY_STOPPOINT); + List stopTimes = List.of(stopWithHeadsign, EMPTY_STOPPOINT, EMPTY_STOPPOINT); TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); @@ -141,7 +134,7 @@ void shouldHandleDifferingTripAndStopHeadSignScenario() { @Test public void testStopUpdate() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimesA = createInitialTripTimes().copyScheduledTimes(); updatedTripTimesA.updateArrivalTime(3, 190); updatedTripTimesA.updateDepartureTime(3, 190); @@ -156,7 +149,7 @@ public void testStopUpdate() { @Test public void testPassedUpdate() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimesA = createInitialTripTimes().copyScheduledTimes(); updatedTripTimesA.updateDepartureTime(0, 30); @@ -164,32 +157,6 @@ public void testPassedUpdate() { assertEquals(60, updatedTripTimesA.getArrivalTime(1)); } - @Test - public void testNegativeDwellTime() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); - - updatedTripTimesA.updateArrivalTime(1, 60); - updatedTripTimesA.updateDepartureTime(1, 59); - - var error = updatedTripTimesA.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(1, error.get().stopIndex()); - assertEquals(NEGATIVE_DWELL_TIME, error.get().code()); - } - - @Test - public void testNegativeHopTime() { - TripTimes updatedTripTimesB = createInitialTripTimes().copyOfScheduledTimes(); - - updatedTripTimesB.updateDepartureTime(6, 421); - updatedTripTimesB.updateArrivalTime(7, 420); - - var error = updatedTripTimesB.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(7, error.get().stopIndex()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); - } - /** * Test negative hop time with stop cancellations. * Scheduled: 5 at 300, 6 at 360, 7 at 420 @@ -200,7 +167,7 @@ public void testNegativeHopTime() { */ @Test public void testNegativeHopTimeWithStopCancellations() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.updateDepartureTime(5, 421); updatedTripTimes.updateArrivalTime(6, 481); @@ -208,14 +175,14 @@ public void testNegativeHopTimeWithStopCancellations() { updatedTripTimes.setCancelled(6); updatedTripTimes.updateArrivalTime(7, 420); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); - - assertTrue(updatedTripTimes.interpolateMissingTimes()); - error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + var error = assertThrows( + DataValidationException.class, + updatedTripTimes::validateNonIncreasingTimes + ); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 7 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); } /** @@ -227,7 +194,7 @@ public void testNegativeHopTimeWithStopCancellations() { */ @Test public void testPositiveHopTimeWithStopCancellationsLate() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.updateDepartureTime(5, 400); updatedTripTimes.updateArrivalTime(6, 460); @@ -235,13 +202,18 @@ public void testPositiveHopTimeWithStopCancellationsLate() { updatedTripTimes.setCancelled(6); updatedTripTimes.updateArrivalTime(7, 420); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + var error = assertThrows( + DataValidationException.class, + updatedTripTimes::validateNonIncreasingTimes + ); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 7 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); assertTrue(updatedTripTimes.interpolateMissingTimes()); - error = updatedTripTimes.validateNonIncreasingTimes(); - assertFalse(error.isPresent()); + + updatedTripTimes.validateNonIncreasingTimes(); } /** @@ -253,19 +225,23 @@ public void testPositiveHopTimeWithStopCancellationsLate() { */ @Test public void testPositiveHopTimeWithStopCancellationsEarly() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.updateDepartureTime(5, 300); updatedTripTimes.setCancelled(6); updatedTripTimes.updateArrivalTime(7, 320); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + var error = assertThrows( + DataValidationException.class, + updatedTripTimes::validateNonIncreasingTimes + ); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 7 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); assertTrue(updatedTripTimes.interpolateMissingTimes()); - error = updatedTripTimes.validateNonIncreasingTimes(); - assertFalse(error.isPresent()); + updatedTripTimes.validateNonIncreasingTimes(); } /** @@ -278,21 +254,29 @@ public void testPositiveHopTimeWithStopCancellationsEarly() { */ @Test public void testPositiveHopTimeWithTerminalCancellation() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.setCancelled(0); updatedTripTimes.setCancelled(1); updatedTripTimes.updateArrivalTime(2, 0); updatedTripTimes.updateDepartureTime(2, 10); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + var error = assertThrows( + DataValidationException.class, + updatedTripTimes::validateNonIncreasingTimes + ); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 2 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); assertFalse(updatedTripTimes.interpolateMissingTimes()); - error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + error = + assertThrows(DataValidationException.class, updatedTripTimes::validateNonIncreasingTimes); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 2 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); } /** @@ -304,14 +288,14 @@ public void testPositiveHopTimeWithTerminalCancellation() { */ @Test public void testInterpolationWithTerminalCancellation() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.setCancelled(6); updatedTripTimes.setCancelled(7); assertFalse(updatedTripTimes.interpolateMissingTimes()); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertFalse(error.isPresent()); + + updatedTripTimes.validateNonIncreasingTimes(); } /** @@ -323,7 +307,7 @@ public void testInterpolationWithTerminalCancellation() { */ @Test public void testInterpolationWithMultipleStopCancellations() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.setCancelled(1); updatedTripTimes.setCancelled(2); @@ -334,13 +318,18 @@ public void testInterpolationWithMultipleStopCancellations() { updatedTripTimes.updateArrivalTime(7, 350); updatedTripTimes.updateDepartureTime(7, 350); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + var error = assertThrows( + DataValidationException.class, + updatedTripTimes::validateNonIncreasingTimes + ); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 7 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); assertTrue(updatedTripTimes.interpolateMissingTimes()); - error = updatedTripTimes.validateNonIncreasingTimes(); - assertFalse(error.isPresent()); + + updatedTripTimes.validateNonIncreasingTimes(); } /** @@ -352,7 +341,7 @@ public void testInterpolationWithMultipleStopCancellations() { */ @Test public void testInterpolationWithMultipleStopCancellations2() { - TripTimes updatedTripTimes = createInitialTripTimes(); + var updatedTripTimes = createInitialTripTimes().copyScheduledTimes(); updatedTripTimes.setCancelled(1); updatedTripTimes.setCancelled(2); @@ -364,28 +353,32 @@ public void testInterpolationWithMultipleStopCancellations2() { updatedTripTimes.updateArrivalTime(7, 240); updatedTripTimes.updateDepartureTime(7, 240); - var error = updatedTripTimes.validateNonIncreasingTimes(); - assertTrue(error.isPresent()); - assertEquals(NEGATIVE_HOP_TIME, error.get().code()); + var error = assertThrows( + DataValidationException.class, + updatedTripTimes::validateNonIncreasingTimes + ); + assertEquals( + "NEGATIVE_HOP_TIME for stop position 3 in trip Trip{F:testTripId RRtestTripId}.", + error.error().message() + ); assertTrue(updatedTripTimes.interpolateMissingTimes()); - error = updatedTripTimes.validateNonIncreasingTimes(); - assertFalse(error.isPresent()); + updatedTripTimes.validateNonIncreasingTimes(); } @Test public void testNonIncreasingUpdateCrossingMidnight() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimesA = createInitialTripTimes().copyScheduledTimes(); updatedTripTimesA.updateArrivalTime(0, -300); //"Yesterday" updatedTripTimesA.updateDepartureTime(0, 50); - assertTrue(updatedTripTimesA.validateNonIncreasingTimes().isEmpty()); + updatedTripTimesA.validateNonIncreasingTimes(); } @Test public void testDelay() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimesA = createInitialTripTimes().copyScheduledTimes(); updatedTripTimesA.updateDepartureDelay(0, 10); updatedTripTimesA.updateArrivalDelay(6, 13); @@ -395,14 +388,14 @@ public void testDelay() { @Test public void testCancel() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimesA = createInitialTripTimes().copyScheduledTimes(); updatedTripTimesA.cancelTrip(); assertEquals(RealTimeState.CANCELED, updatedTripTimesA.getRealTimeState()); } @Test public void testNoData() { - TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); + RealTimeTripTimes updatedTripTimesA = createInitialTripTimes().copyScheduledTimes(); updatedTripTimesA.setNoData(1); assertFalse(updatedTripTimesA.isNoDataStop(0)); assertTrue(updatedTripTimesA.isNoDataStop(1)); @@ -433,46 +426,38 @@ void unknownGtfsSequence() { } @Test - public void testApply() { - Trip trip = TransitModelForTest.trip(TRIP_ID).build(); - - List stopTimes = new LinkedList<>(); - - StopTime stopTime0 = new StopTime(); - StopTime stopTime1 = new StopTime(); - StopTime stopTime2 = new StopTime(); + public void validateNegativeDwellTime() { + var expMsg = "NEGATIVE_DWELL_TIME for stop position 3 in trip Trip{F:testTripId RRtestTripId}."; + var tt = createInitialTripTimes(); + var updatedTt = tt.copyScheduledTimes(); - RegularStop stop0 = TEST_MODEL.stop(stops.get(0).getId(), 0.0, 0.0).build(); - RegularStop stop1 = TEST_MODEL.stop(stops.get(1).getId(), 0.0, 0.0).build(); - RegularStop stop2 = TEST_MODEL.stop(stops.get(2).getId(), 0.0, 0.0).build(); + updatedTt.updateArrivalTime(3, 69); + updatedTt.updateDepartureTime(3, 68); - stopTime0.setStop(stop0); - stopTime0.setDepartureTime(0); - stopTime0.setStopSequence(0); + var ex = assertThrows(DataValidationException.class, updatedTt::validateNonIncreasingTimes); + var error = (TimetableValidationError) ex.error(); - stopTime1.setStop(stop1); - stopTime1.setArrivalTime(30); - stopTime1.setDepartureTime(60); - stopTime1.setStopSequence(1); - - stopTime2.setStop(stop2); - stopTime2.setArrivalTime(90); - stopTime2.setStopSequence(2); - - stopTimes.add(stopTime0); - stopTimes.add(stopTime1); - stopTimes.add(stopTime2); + assertEquals(3, error.stopIndex()); + assertEquals(NEGATIVE_DWELL_TIME, error.code()); + assertEquals(expMsg, error.message()); + assertEquals(expMsg, ex.getMessage()); + } - TripTimes differingTripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); + @Test + public void validateNegativeHopTime() { + var expMsg = "NEGATIVE_HOP_TIME for stop position 2 in trip Trip{F:testTripId RRtestTripId}."; + var tt = createInitialTripTimes(); + var updatedTt = tt.copyScheduledTimes(); - TripTimes updatedTripTimesA = differingTripTimes.copyOfScheduledTimes(); + updatedTt.updateDepartureTime(1, 100); + updatedTt.updateArrivalTime(2, 99); - updatedTripTimesA.updateArrivalTime(1, 89); - updatedTripTimesA.updateDepartureTime(1, 98); + var ex = assertThrows(DataValidationException.class, updatedTt::validateNonIncreasingTimes); + var error = (TimetableValidationError) ex.error(); - var validationResult = updatedTripTimesA.validateNonIncreasingTimes(); - assertTrue(validationResult.isPresent()); - assertEquals(2, validationResult.get().stopIndex()); - assertEquals(NEGATIVE_DWELL_TIME, validationResult.get().code()); + assertEquals(2, error.stopIndex()); + assertEquals(NEGATIVE_HOP_TIME, error.code()); + assertEquals(expMsg, error.message()); + assertEquals(expMsg, ex.getMessage()); } } diff --git a/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java b/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java new file mode 100644 index 00000000000..ed6c5c70f92 --- /dev/null +++ b/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java @@ -0,0 +1,176 @@ +package org.opentripplanner.transit.model.timetable; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.transit.model._data.TransitModelForTest.id; + +import java.util.BitSet; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.time.TimeUtils; +import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.basic.Accessibility; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +class ScheduledTripTimesTest { + + private static final Trip TRIP = TransitModelForTest.trip("Trip-1").build(); + + private static final List STOP_IDS = List.of(id("A"), id("B"), id("C")); + private static final int SERVICE_CODE = 5; + private static final BitSet TIMEPOINTS = new BitSet(3); + private static final int T10_00 = TimeUtils.time("10:00"); + private static final int T10_01 = T10_00 + 60; + private static final int T11_00 = TimeUtils.time("11:00"); + private static final int T11_02 = T11_00 + 120; + private static final int T12_00 = TimeUtils.time("12:00"); + private static final int T12_03 = T12_00 + 180; + public static final int STOP_POS_0 = 0; + public static final int STOP_POS_1 = 1; + public static final int STOP_POS_2 = 2; + + static { + TIMEPOINTS.set(1); + } + + private final ScheduledTripTimes subject = ScheduledTripTimes + .of() + .withArrivalTimes("10:00 11:00 12:00") + .withDepartureTimes("10:01 11:02 12:03") + .withServiceCode(SERVICE_CODE) + .withTrip(TRIP) + .withTimepoints(TIMEPOINTS) + .build(); + + @Test + void getServiceCode() { + assertEquals(SERVICE_CODE, subject.getServiceCode()); + } + + @Test + void getScheduledArrivalTime() { + assertEquals(T10_00, subject.getScheduledArrivalTime(STOP_POS_0)); + assertEquals(T11_00, subject.getScheduledArrivalTime(STOP_POS_1)); + assertEquals(T12_00, subject.getScheduledArrivalTime(STOP_POS_2)); + } + + @Test + void getArrivalTime() { + assertEquals(T10_00, subject.getArrivalTime(STOP_POS_0)); + assertEquals(T11_00, subject.getArrivalTime(STOP_POS_1)); + assertEquals(T12_00, subject.getArrivalTime(STOP_POS_2)); + } + + @Test + void getArrivalDelay() { + assertEquals(0, subject.getArrivalDelay(STOP_POS_0)); + assertEquals(0, subject.getArrivalDelay(STOP_POS_1)); + assertEquals(0, subject.getArrivalDelay(STOP_POS_2)); + } + + @Test + void getScheduledDepartureTime() { + assertEquals(T10_01, subject.getScheduledDepartureTime(STOP_POS_0)); + assertEquals(T11_02, subject.getScheduledDepartureTime(STOP_POS_1)); + assertEquals(T12_03, subject.getScheduledDepartureTime(STOP_POS_2)); + } + + @Test + void getDepartureTime() { + assertEquals(T10_01, subject.getDepartureTime(STOP_POS_0)); + assertEquals(T11_02, subject.getDepartureTime(STOP_POS_1)); + assertEquals(T12_03, subject.getDepartureTime(STOP_POS_2)); + } + + @Test + void getDepartureDelay() { + assertEquals(0, subject.getDepartureDelay(STOP_POS_0)); + assertEquals(0, subject.getDepartureDelay(STOP_POS_1)); + assertEquals(0, subject.getDepartureDelay(STOP_POS_2)); + } + + @Test + void isTimepoint() { + assertFalse(subject.isTimepoint(STOP_POS_0)); + assertTrue(subject.isTimepoint(STOP_POS_1)); + assertFalse(subject.isTimepoint(STOP_POS_2)); + } + + @Test + void validateLastArrivalTimeIsNotMoreThan20DaysAfterFirstDepartureTime() { + var ex = assertThrows( + IllegalArgumentException.class, + () -> + ScheduledTripTimes + .of() + .withDepartureTimes("10:00 12:00 10:00:01+20d") + .withServiceCode(SERVICE_CODE) + .withTrip(TRIP) + .build() + ); + assertEquals("The value is not in range[-43200, 1728000]: 1728001", ex.getMessage()); + } + + @Test + void getTrip() { + assertEquals(TRIP, subject.getTrip()); + } + + @Test + void sortIndex() { + assertEquals(T10_01, subject.sortIndex()); + } + + @Test + void isScheduled() { + assertTrue(subject.isScheduled()); + } + + @Test + void isCanceledOrDeleted() { + assertFalse(subject.isCanceledOrDeleted()); + } + + @Test + void isCanceled() { + assertFalse(subject.isCanceled()); + } + + @Test + void isDeleted() { + assertFalse(subject.isDeleted()); + } + + @Test + void getRealTimeState() { + assertEquals(RealTimeState.SCHEDULED, subject.getRealTimeState()); + } + + @Test + void getNumStops() { + assertEquals(3, subject.getNumStops()); + } + + @Test + void getWheelchairAccessibility() { + assertEquals(Accessibility.NO_INFORMATION, subject.getWheelchairAccessibility()); + } + + @Test + void getOccupancyStatus() { + assertEquals(OccupancyStatus.NO_DATA_AVAILABLE, subject.getOccupancyStatus(0)); + } + + @Test + void copyArrivalTimes() { + assertArrayEquals(new int[] { T10_00, T11_00, T12_00 }, subject.copyArrivalTimes()); + } + + @Test + void copyDepartureTimes() { + assertArrayEquals(new int[] { T10_01, T11_02, T12_03 }, subject.copyDepartureTimes()); + } +} diff --git a/src/test/java/org/opentripplanner/transit/model/timetable/TimetableValidationErrorTest.java b/src/test/java/org/opentripplanner/transit/model/timetable/TimetableValidationErrorTest.java new file mode 100644 index 00000000000..7dd67665e92 --- /dev/null +++ b/src/test/java/org/opentripplanner/transit/model/timetable/TimetableValidationErrorTest.java @@ -0,0 +1,24 @@ +package org.opentripplanner.transit.model.timetable; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.basic.TransitMode; + +class TimetableValidationErrorTest { + + private TimetableValidationError subject = new TimetableValidationError( + TimetableValidationError.ErrorCode.NEGATIVE_HOP_TIME, + 3, + TransitModelForTest.trip("A").withMode(TransitMode.BUS).withShortName("Line A").build() + ); + + @Test + void message() { + assertEquals( + "NEGATIVE_HOP_TIME for stop position 3 in trip Trip{F:A Line A}.", + subject.message() + ); + } +} diff --git a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java index 463e9342e6d..642e192539c 100644 --- a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java +++ b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java @@ -15,8 +15,6 @@ import java.util.function.Predicate; import org.opentripplanner.TestServerContext; import org.opentripplanner.datastore.OtpDataStore; -import org.opentripplanner.ext.stopconsolidation.internal.DefaultStopConsolidationRepository; -import org.opentripplanner.ext.stopconsolidation.internal.DefaultStopConsolidationService; import org.opentripplanner.framework.application.OtpAppException; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.raptor.configure.RaptorConfig; diff --git a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTestRequest.java b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTestRequest.java index 1bfe202f92f..2dfaad077c6 100644 --- a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTestRequest.java +++ b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTestRequest.java @@ -63,7 +63,7 @@ RouteRequest toRouteRequest() { request.setTo(input.toPlace()); // Filter the results inside the SpeedTest, not in the itineraries filter, - // when ignoring street results. This will use the default witch is 50. + // when ignoring street results. This will use the default which is 50. if (!config.ignoreStreetResults) { request.setNumItineraries(opts.numOfItineraries()); } diff --git a/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/ExpectedResults.java b/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/ExpectedResults.java index 1b14bc8fc12..eb66b3671e4 100644 --- a/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/ExpectedResults.java +++ b/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/ExpectedResults.java @@ -10,7 +10,7 @@ /** * This class contains the results for a given test case. A set of results * for each {@link SpeedTestProfile} is kept. A default set is also available, - * witch can be used if there is not set for a given profile. + * which can be used if there is not set for a given profile. */ public class ExpectedResults { diff --git a/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java b/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java index 285cfbe4ac1..d25de6ff021 100644 --- a/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java +++ b/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java @@ -2,7 +2,6 @@ import static com.google.transit.realtime.GtfsRealtime.TripDescriptor.ScheduleRelationship.ADDED; import static com.google.transit.realtime.GtfsRealtime.TripDescriptor.ScheduleRelationship.SCHEDULED; -import static com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship.NO_DATA; import static com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship.SKIPPED; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/nearest.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/nearest.json new file mode 100644 index 00000000000..82d929adeb1 --- /dev/null +++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/nearest.json @@ -0,0 +1,17 @@ +{ + "data" : { + "nearest" : { + "edges" : [ + { + "node" : { + "place" : { + "id" : "U3RvcDpGOkE", + "gtfsId" : "F:A", + "parentStation" : null + } + } + } + ] + } + } +} \ No newline at end of file diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/nearest.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/nearest.graphql new file mode 100644 index 00000000000..3616ec03ff8 --- /dev/null +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/nearest.graphql @@ -0,0 +1,17 @@ +{ + nearest(lat: 60.19915, lon: 24.94089, maxDistance: 500) { + edges { + node { + place { + ...on Stop { + id + gtfsId + parentStation { + id + } + } + } + } + } + } + } \ No newline at end of file diff --git a/src/test/resources/standalone/config/router-config.json b/src/test/resources/standalone/config/router-config.json index ebc2e4f7c02..69f859d784d 100644 --- a/src/test/resources/standalone/config/router-config.json +++ b/src/test/resources/standalone/config/router-config.json @@ -34,7 +34,7 @@ "dropOffTime": 30, "dropOffCost": 30 }, - "bikeParkTime": 60, + "bikeParkTime": "1m", "bikeParkCost": 120, "carDropoffTime": 120, "waitReluctance": 1.0, diff --git a/test/performance/norway/build-config.json b/test/performance/norway/build-config.json index 5fa7f03ed58..83de34a027b 100644 --- a/test/performance/norway/build-config.json +++ b/test/performance/norway/build-config.json @@ -6,7 +6,6 @@ "embedRouterConfig": true, "areaVisibility": true, "platformEntriesLinking": true, - "matchBusRoutesToStreets": false, "staticParkAndRide": true, "staticBikeParkAndRide": true, "maxDataImportIssuesPerFile": 1000,