diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index da79a466e0b..a67cae22195 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -47,7 +47,7 @@ Write a few words on how the new code is tested. - Was the code designed so it is unit testable? - Were any tests applied to the smallest appropriate unit? - Do all tests - pass [the continuous integration service](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/docs/Developers-Guide.md#continuous-integration) + pass [the continuous integration service](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/doc/user/Developers-Guide.md#continuous-integration) ? ### Documentation @@ -59,7 +59,7 @@ Write a few words on how the new code is tested. ### Changelog -The [changelog file](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/docs/Changelog.md) +The [changelog file](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/doc/user/Changelog.md) is generated from the pull-request title, make sure the title describe the feature or issue fixed. To exclude the PR from the changelog add the label `skip changelog` to the PR. diff --git a/.github/workflows/cibuild.yml b/.github/workflows/cibuild.yml index c9dc2fa4c35..8c2caf88f5c 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -151,7 +151,7 @@ jobs: if: github.event_name == 'pull_request' - name: Install Python dependencies - run: pip install -r docs/requirements.txt + run: pip install -r doc/user/requirements.txt - name: Build main documentation diff --git a/.github/workflows/post-merge.yml b/.github/workflows/post-merge.yml index 86f3741aada..b26198e99a6 100644 --- a/.github/workflows/post-merge.yml +++ b/.github/workflows/post-merge.yml @@ -29,8 +29,8 @@ jobs: run: | # add a line above the one which contains AUTOMATIC_CHANGELOG_PLACEHOLDER ITEM="${TITLE} [#${NUMBER}](${URL})" - TEMP_FILE=docs/Changelog.generated.md - FILE=docs/Changelog.md + TEMP_FILE=doc/user/Changelog.generated.md + FILE=doc/user/Changelog.md awk "/CHANGELOG_PLACEHOLDER/{print \"- $ITEM\"}1" $FILE > $TEMP_FILE mv $TEMP_FILE $FILE git add $FILE diff --git a/.gitignore b/.gitignore index b98d0263a70..296ef151ef9 100644 --- a/.gitignore +++ b/.gitignore @@ -30,7 +30,7 @@ o_o_standalone_config_IncludeFileDirectiveTest_part.json _site/ build/ dist/ -docs/_build/ +doc/user/_build/ gen-java/ gen-javabean/ gen-py/ diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index acd90b787e6..4bd313c7a06 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,24 +1,28 @@ # OTP Architecture -OTP is developed over more than 10 years, and most of the design documentation is in the code as +OTP has been developed for more than 15 years, and most of the design documentation is in the code as comments and JavaDoc. Over the years the complexity have increased, and the natural developer turnover creates a demand for more architecture and design documentation. The new OTP2 documentation is put together with the source; hopefully making it easier to maintain. Instead of documenting modules in old style _package-info.java_ files we use _package.md_ files. This document should serve as an index to all existing top-level documented components. -This document is far from complete - hopefully it can evolve over time and become a good +This document is far from complete. Hopefully it can evolve over time and become a good introduction to OTP architecture. The OTP project GitHub issues are a good place to look for detailed discussions on many design decisions. -Be sure to also read the [developer documentation](docs/Developers-Guide.md). +We document [Decision Records](DEVELOPMENT_DECISION_RECORDS.md) in a log. Make sure you as a developer are familiar +with the decisions and follow them. Reviewers should use them actively when reviewing code and may +use them to ask for changes. + +Be sure to also read the [developer documentation](doc/user/Developers-Guide.md). ## Modules/Components The diagram shows a simplified/generic version on how we want to model the OTP components with 2 examples. The Transit model is more complex than the VehiclePosition model. -![MainModelOverview](docs/images/ServiceModelOverview.png) +![MainModelOverview](doc/dev/images/ServiceModelOverview.png) - `Use Case Service` A service which combine the functionality in many `Domain Services` to fulfill a use-case or set of features. It may have an api with request/response classes. These are @@ -26,7 +30,7 @@ examples. The Transit model is more complex than the VehiclePosition model. class has the same name as the interface with prefix `Default`. - `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 , + implementation has 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 is used to maintain the model by the updaters. @@ -41,10 +45,6 @@ but this is a start and we would like to expand this list in the future. The Configuration module is responsible for loading and parsing OTP configuration files and map them into Plan Old Java Objects (POJOs). These POJOs are injected into the other components. -### [REST API](src/main/java/org/opentripplanner/api/package.md) - -Short introduction to the REST API. - ### [GTFS import module](src/main/java/org/opentripplanner/gtfs/package.md) Used to import GTFS transit data files. diff --git a/DEVELOPMENT_DECISION_RECORDS.md b/DEVELOPMENT_DECISION_RECORDS.md new file mode 100644 index 00000000000..10b9e005809 --- /dev/null +++ b/DEVELOPMENT_DECISION_RECORDS.md @@ -0,0 +1,106 @@ +# Development Decision Records + + +## Use-Decision-Records + +We will [use decision-records](doc/dev/decisionrecords/UseDecisionRecords.md) to document OTP +development relevant decision. Use the [template](doc/dev/decisionrecords/_TEMPLATE.md) to describe +new decision records. + + +## Scout-Rule + +Leave things BETTER than you found them, clean up code you visit or/and add unit +tests. Expect to include some code cleanup as part of all PRs. + +## Follow-Naming-Conventions + +Use established terminology from GTFS, NeTEx or the existing OTP code. Make sure the code is easy +to read and understand. [Follow naming conventions](CODE_CONVENTIONS.md#naming-conventions) . + + +## Write-Code-Documentation - Use JavaDoc + +Document the business intention and decisions in the relevant code. Do not repeat the logic +expressed in the code. Use JavaDoc, you may have to refactor part of your code to encapsulate the +business logic into a method or class to do this. + +Document all `public` types, methods and fields with JavaDoc. It is ok to document implementation +notes on `private` members and as inline comments. + +> If you decide to NOT follow these decision records, then you must document why. + +**See also** + - [Developers-Guide > Code comments](doc/user/Developers-Guide.md#code-comments). + - [Codestyle > Javadoc Guidlines](doc/dev/decisionrecords/Codestyle.md#javadoc-guidlines) - JavaDoc checklist + + +## Document-Config-and-APIs + +Document API and configuration parameters. + + +## Respect-Codestyle + +OTP uses prettier to format code. For more information on code style see the +[Codestyle](doc/dev/decisionrecords/Codestyle.md) document. + + +## Use-Object-Oriented-Principals + +Respect Object-Oriented principals + - Honor encapsulation & Single-responsibility principle + - Abstraction - Use interfaces when a module needs "someone" to play a role + - Inheritance - Inheritances expresses “is-a” and/or “has-a” relationship, do not use it "just" + to share data/functionality. + - Use polymorphism and not `instanceof`. + + +## Use-Dependency-Injection + +Use dependency injection to wire components. You can use manual DI or Dagger. Put the +wiring code in `/configure/Module.java`. + +OTP will use a dependency injection library or framework to handle object lifecycles (particularly +request-scoped vs. singleton scoped) and ensure selective availability of components, services, +context, and configuration at their site of use. Systems that operate via imperative Java code +(whether hand-written or generated) will be preferred over those operating through reflection or +external markup files. See [additional background](https://github.com/opentripplanner/OpenTripPlanner/pull/5360#issuecomment-1910134299). + +## Use-Module-Encapsulation + +Keep modules clean. Consider adding an `api`, `spi` and mapping code to +isolate the module from the rest of the code. Avoid circular dependencies between modules. + + +## DRY - Do not repeat yourself + +Keep the code [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) - Do not +repeat yourself. Avoid implementing the same business rule in two places -> refactor. + + +## Avoid-Feature-Envy + +[Feature envy](https://refactoring.guru/smells/feature-envy) + + +## Test-Coverage + +All _business_ logic should have unit tests. Keep integration/system tests to a minimum. Add test at +the lowest level practical to test the business feature. Prefer unit tests on a method over a class, +over a module, over the system. On all non-trivial code, full _branch_ test coverage is preferable. +Tests should be designed to genuinely demonstrate correctness or adherence to specifications, not +simply inflate line coverage numbers. + + +## Use-Immutable-Types + +Prefer immutable types over mutable. Use builders where appropriate. See +[Records, POJOs and Builders](doc/dev/decisionrecords/RecordsPOJOsBuilders.md#records-pojos-and-builders) + + +## Be-Careful-With-Records + +[Avoid using records if you cannot encapsulate it properly](doc/dev/decisionrecords/RecordsPOJOsBuilders.md#records) + + diff --git a/client/package-lock.json b/client/package-lock.json index 9e2798660ec..4632899b38f 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -12,7 +12,7 @@ "bootstrap": "5.3.3", "graphql": "16.9.0", "graphql-request": "7.1.0", - "maplibre-gl": "4.5.0", + "maplibre-gl": "4.6.0", "react": "18.3.1", "react-bootstrap": "2.10.4", "react-dom": "18.3.1", @@ -23,8 +23,8 @@ "@graphql-codegen/client-preset": "4.3.3", "@graphql-codegen/introspection": "4.0.3", "@parcel/watcher": "2.4.1", - "@testing-library/react": "16.0.0", - "@types/react": "18.3.3", + "@testing-library/react": "16.0.1", + "@types/react": "18.3.5", "@types/react-dom": "18.3.0", "@typescript-eslint/eslint-plugin": "7.18.0", "@typescript-eslint/parser": "7.18.0", @@ -32,15 +32,15 @@ "@vitest/coverage-v8": "2.0.5", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", - "eslint-plugin-import": "2.29.1", + "eslint-plugin-import": "2.30.0", "eslint-plugin-jsx-a11y": "6.9.0", - "eslint-plugin-react": "7.35.0", + "eslint-plugin-react": "7.35.1", "eslint-plugin-react-hooks": "4.6.2", - "eslint-plugin-react-refresh": "0.4.9", - "jsdom": "24.1.1", + "eslint-plugin-react-refresh": "0.4.11", + "jsdom": "25.0.0", "prettier": "3.3.3", "typescript": "5.5.4", - "vite": "5.3.5", + "vite": "5.4.2", "vitest": "2.0.5" } }, @@ -3010,9 +3010,9 @@ } }, "node_modules/@maplibre/maplibre-gl-style-spec": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-20.3.0.tgz", - "integrity": "sha512-eSiQ3E5LUSxAOY9ABXGyfNhout2iEa6mUxKeaQ9nJ8NL1NuaQYU7zKqzx/LEYcXe1neT4uYAgM1wYZj3fTSXtA==", + "version": "20.3.1", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-20.3.1.tgz", + "integrity": "sha512-5ueL4UDitzVtceQ8J4kY+Px3WK+eZTsmGwha3MBKHKqiHvKrjWWwBCIl1K8BuJSc5OFh83uI8IFNoFvQxX2uUw==", "license": "ISC", "dependencies": { "@mapbox/jsonlint-lines-primitives": "~2.0.2", @@ -3022,7 +3022,7 @@ "quickselect": "^2.0.0", "rw": "^1.3.3", "sort-object": "^3.0.3", - "tinyqueue": "^2.0.3" + "tinyqueue": "^3.0.0" }, "bin": { "gl-style-format": "dist/gl-style-format.mjs", @@ -3537,174 +3537,236 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", - "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.2.tgz", + "integrity": "sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz", - "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.2.tgz", + "integrity": "sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", - "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.2.tgz", + "integrity": "sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz", - "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.2.tgz", + "integrity": "sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz", - "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.2.tgz", + "integrity": "sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.2.tgz", + "integrity": "sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", - "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.2.tgz", + "integrity": "sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz", - "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.2.tgz", + "integrity": "sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.2.tgz", + "integrity": "sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz", - "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.2.tgz", + "integrity": "sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.2.tgz", + "integrity": "sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", - "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.2.tgz", + "integrity": "sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz", - "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.2.tgz", + "integrity": "sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz", - "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.2.tgz", + "integrity": "sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz", - "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.2.tgz", + "integrity": "sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz", - "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.2.tgz", + "integrity": "sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.7.tgz", @@ -3734,9 +3796,9 @@ } }, "node_modules/@testing-library/react": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.0.0.tgz", - "integrity": "sha512-guuxUKRWQ+FgNX0h0NS0FIq3Q3uLtWVpBzcLOggmfMoUpgBnzBzvLLd4fbm6yS8ydJd94cIfY4yP9qUQjM2KwQ==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.0.1.tgz", + "integrity": "sha512-dSmwJVtJXmku+iocRhWOUFbrERC76TX2Mnf0ATODz8brzAZrMBbzLwQixlBSanZxR6LddK3eiwpSFZgDET1URg==", "dev": true, "license": "MIT", "dependencies": { @@ -3846,11 +3908,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "node_modules/@types/junit-report-builder": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/junit-report-builder/-/junit-report-builder-3.0.2.tgz", - "integrity": "sha512-R5M+SYhMbwBeQcNXYWNCZkl09vkVfAtcPIaCGdzIkkbeaTrVbGQ7HVgi4s+EmM/M1K4ZuWQH0jGcvMvNePfxYA==" - }, "node_modules/@types/mapbox__point-geometry": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz", @@ -3894,9 +3951,9 @@ "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" }, "node_modules/@types/react": { - "version": "18.3.3", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", - "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "version": "18.3.5", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.5.tgz", + "integrity": "sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==", "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -5701,9 +5758,10 @@ } }, "node_modules/earcut": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", - "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.0.tgz", + "integrity": "sha512-41Fs7Q/PLq1SDbqjsgcY7GA42T0jvaCNGXgGtsNdvg+Yv8eIu06bxv4/PoREkZ9nMDNwnUSG9OFB9+yv8eKhDg==", + "license": "ISC" }, "node_modules/eastasianwidth": { "version": "0.2.0", @@ -6074,10 +6132,11 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", - "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.9.0.tgz", + "integrity": "sha512-McVbYmwA3NEKwRQY5g4aWMdcZE5xZxV8i8l7CqJSrameuGSQJtSWaL/LxTEzSKKaCcOhlpDR8XEfYXWPrdo/ZQ==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^3.2.7" }, @@ -6100,26 +6159,28 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", - "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz", + "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", "dev": true, + "license": "MIT", "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", "array.prototype.flat": "^1.3.2", "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", + "eslint-module-utils": "^2.9.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", "semver": "^6.3.1", "tsconfig-paths": "^3.15.0" }, @@ -6135,6 +6196,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6145,6 +6207,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.1" } @@ -6154,6 +6217,7 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, @@ -6166,6 +6230,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -6249,9 +6314,9 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.35.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz", - "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==", + "version": "7.35.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.1.tgz", + "integrity": "sha512-B5ok2JgbaaWn/zXbKCGgKDNL2tsID3Pd/c/yvjcpsd9HQDwyYc/TQv3AZMmOvrJgCs3AnYNUHRCQEMMQAYJ7Yg==", "dev": true, "license": "MIT", "dependencies": { @@ -6294,9 +6359,9 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.9.tgz", - "integrity": "sha512-QK49YrBAo5CLNLseZ7sZgvgTy21E6NEw22eZqc4teZfH8pxV3yXc9XXOYfUI6JNpw7mfHNkAeWtBxrTyykB6HA==", + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.11.tgz", + "integrity": "sha512-wrAKxMbVr8qhXTtIKfXqAn5SAtRZt0aXxe5P23Fh4pUAdC6XEsybGLB8P0PI4j1yYqOgUEUlzKAGDfo7rJOjcw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -7066,27 +7131,41 @@ } }, "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==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-4.0.0.tgz", + "integrity": "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==", + "license": "MIT", "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" + "ini": "^4.1.3", + "kind-of": "^6.0.3", + "which": "^4.0.0" }, "engines": { - "node": ">=6" + "node": ">=16" + } + }, + "node_modules/global-prefix/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "license": "ISC", + "engines": { + "node": ">=16" } }, "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==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "license": "ISC", "dependencies": { - "isexe": "^2.0.0" + "isexe": "^3.1.1" }, "bin": { - "which": "bin/which" + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" } }, "node_modules/globals": { @@ -7537,9 +7616,13 @@ "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==" + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/inquirer": { "version": "8.2.6", @@ -7697,12 +7780,16 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dev": true, + "license": "MIT", "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8098,7 +8185,8 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "node_modules/isobject": { "version": "3.0.1", @@ -8233,9 +8321,9 @@ } }, "node_modules/jsdom": { - "version": "24.1.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.1.tgz", - "integrity": "sha512-5O1wWV99Jhq4DV7rCLIoZ/UIhyQeDR7wHVyZAHAshbrvZsLs+Xzz7gtwnlJTJDjleiTKh54F4dXrX70vJQTyJQ==", + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.0.tgz", + "integrity": "sha512-OhoFVT59T7aEq75TVw9xxEfkXgacpqAhQaYgP9y/fDqWQCMB/b1H66RfmPm/MaeaAIU9nDwMOVTlPN51+ao6CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8731,9 +8819,9 @@ } }, "node_modules/maplibre-gl": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.5.0.tgz", - "integrity": "sha512-qOS1hn4d/pn2i0uva4S5Oz+fACzTkgBKq+NpwT/Tqzi4MSyzcWNtDELzLUSgWqHfNIkGCl5CZ/w7dtis+t4RCw==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.6.0.tgz", + "integrity": "sha512-zobZK+fE+XM+7K81fk5pSBYWZlTGjGT0P96y2fR4DV2ry35ZBfAd0uWNatll69EgYeE+uOhN1MvEk+z1PCuyOQ==", "license": "BSD-3-Clause", "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", @@ -8743,25 +8831,24 @@ "@mapbox/unitbezier": "^0.0.1", "@mapbox/vector-tile": "^1.3.1", "@mapbox/whoots-js": "^3.1.0", - "@maplibre/maplibre-gl-style-spec": "^20.3.0", + "@maplibre/maplibre-gl-style-spec": "^20.3.1", "@types/geojson": "^7946.0.14", "@types/geojson-vt": "3.2.5", - "@types/junit-report-builder": "^3.0.2", "@types/mapbox__point-geometry": "^0.1.4", "@types/mapbox__vector-tile": "^1.3.4", "@types/pbf": "^3.0.5", "@types/supercluster": "^7.1.3", - "earcut": "^2.2.4", + "earcut": "^3.0.0", "geojson-vt": "^4.0.2", "gl-matrix": "^3.4.3", - "global-prefix": "^3.0.0", + "global-prefix": "^4.0.0", "kdbush": "^4.0.2", "murmurhash-js": "^1.0.0", - "pbf": "^3.2.1", + "pbf": "^3.3.0", "potpack": "^2.0.0", - "quickselect": "^2.0.0", + "quickselect": "^3.0.0", "supercluster": "^8.0.1", - "tinyqueue": "^2.0.3", + "tinyqueue": "^3.0.0", "vt-pbf": "^3.1.3" }, "engines": { @@ -8772,6 +8859,12 @@ "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" } }, + "node_modules/maplibre-gl/node_modules/quickselect": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", + "license": "ISC" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -9495,9 +9588,10 @@ } }, "node_modules/pbf": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz", - "integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.3.0.tgz", + "integrity": "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==", + "license": "BSD-3-Clause", "dependencies": { "ieee754": "^1.1.12", "resolve-protobuf-schema": "^2.1.0" @@ -9535,9 +9629,9 @@ } }, "node_modules/postcss": { - "version": "8.4.40", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", - "integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==", + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", "dev": true, "funding": [ { @@ -10069,10 +10163,11 @@ } }, "node_modules/rollup": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", - "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz", + "integrity": "sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "1.0.5" }, @@ -10084,19 +10179,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.13.0", - "@rollup/rollup-android-arm64": "4.13.0", - "@rollup/rollup-darwin-arm64": "4.13.0", - "@rollup/rollup-darwin-x64": "4.13.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.13.0", - "@rollup/rollup-linux-arm64-gnu": "4.13.0", - "@rollup/rollup-linux-arm64-musl": "4.13.0", - "@rollup/rollup-linux-riscv64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-musl": "4.13.0", - "@rollup/rollup-win32-arm64-msvc": "4.13.0", - "@rollup/rollup-win32-ia32-msvc": "4.13.0", - "@rollup/rollup-win32-x64-msvc": "4.13.0", + "@rollup/rollup-android-arm-eabi": "4.21.2", + "@rollup/rollup-android-arm64": "4.21.2", + "@rollup/rollup-darwin-arm64": "4.21.2", + "@rollup/rollup-darwin-x64": "4.21.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.21.2", + "@rollup/rollup-linux-arm-musleabihf": "4.21.2", + "@rollup/rollup-linux-arm64-gnu": "4.21.2", + "@rollup/rollup-linux-arm64-musl": "4.21.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.21.2", + "@rollup/rollup-linux-riscv64-gnu": "4.21.2", + "@rollup/rollup-linux-s390x-gnu": "4.21.2", + "@rollup/rollup-linux-x64-gnu": "4.21.2", + "@rollup/rollup-linux-x64-musl": "4.21.2", + "@rollup/rollup-win32-arm64-msvc": "4.21.2", + "@rollup/rollup-win32-ia32-msvc": "4.21.2", + "@rollup/rollup-win32-x64-msvc": "4.21.2", "fsevents": "~2.3.2" } }, @@ -10903,9 +11001,10 @@ } }, "node_modules/tinyqueue": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", - "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", + "license": "ISC" }, "node_modules/tinyrainbow": { "version": "1.2.0", @@ -11392,15 +11491,15 @@ } }, "node_modules/vite": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz", - "integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz", + "integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.39", - "rollup": "^4.13.0" + "postcss": "^8.4.41", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -11419,6 +11518,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -11436,6 +11536,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, diff --git a/client/package.json b/client/package.json index fe8836e8d36..5b26215fe5f 100644 --- a/client/package.json +++ b/client/package.json @@ -21,7 +21,7 @@ "bootstrap": "5.3.3", "graphql": "16.9.0", "graphql-request": "7.1.0", - "maplibre-gl": "4.5.0", + "maplibre-gl": "4.6.0", "react": "18.3.1", "react-bootstrap": "2.10.4", "react-dom": "18.3.1", @@ -32,8 +32,8 @@ "@graphql-codegen/client-preset": "4.3.3", "@graphql-codegen/introspection": "4.0.3", "@parcel/watcher": "2.4.1", - "@testing-library/react": "16.0.0", - "@types/react": "18.3.3", + "@testing-library/react": "16.0.1", + "@types/react": "18.3.5", "@types/react-dom": "18.3.0", "@typescript-eslint/eslint-plugin": "7.18.0", "@typescript-eslint/parser": "7.18.0", @@ -41,15 +41,15 @@ "@vitest/coverage-v8": "2.0.5", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", - "eslint-plugin-import": "2.29.1", + "eslint-plugin-import": "2.30.0", "eslint-plugin-jsx-a11y": "6.9.0", - "eslint-plugin-react": "7.35.0", + "eslint-plugin-react": "7.35.1", "eslint-plugin-react-hooks": "4.6.2", - "eslint-plugin-react-refresh": "0.4.9", - "jsdom": "24.1.1", + "eslint-plugin-react-refresh": "0.4.11", + "jsdom": "25.0.0", "prettier": "3.3.3", "typescript": "5.5.4", - "vite": "5.3.5", + "vite": "5.4.2", "vitest": "2.0.5" } } diff --git a/client/src/components/ItineraryList/ItineraryLegDetails.tsx b/client/src/components/ItineraryList/ItineraryLegDetails.tsx index 56fdf430388..e75813a4a45 100644 --- a/client/src/components/ItineraryList/ItineraryLegDetails.tsx +++ b/client/src/components/ItineraryList/ItineraryLegDetails.tsx @@ -10,14 +10,9 @@ export function ItineraryLegDetails({ leg, isLast }: { leg: Leg; isLast: boolean
{formatDistance(leg.distance)}, {formatDuration(leg.duration)}
-
- - - -
+ + -{' '} +
{leg.mode}{' '} {leg.line && ( @@ -28,9 +23,9 @@ export function ItineraryLegDetails({ leg, isLast }: { leg: Leg; isLast: boolean , {leg.authority?.name} )}{' '} -
{leg.mode !== Mode.Foot && ( <> +
{leg.fromPlace.name} →{' '} )}{' '} diff --git a/client/src/style.css b/client/src/style.css index 7dd2565c449..1a24ac2c072 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -86,7 +86,7 @@ } .itinerary-leg-details .mode { - margin-top: 10px; + margin-top: 2px; } .itinerary-header-itinerary-number { diff --git a/docs/Codestyle.md b/doc/dev/decisionrecords/Codestyle.md similarity index 99% rename from docs/Codestyle.md rename to doc/dev/decisionrecords/Codestyle.md index d468937adfa..f9ffc1a9056 100644 --- a/docs/Codestyle.md +++ b/doc/dev/decisionrecords/Codestyle.md @@ -58,7 +58,7 @@ Editor > Code Style_. Then select **Project** in the \_Scheme drop down. You can run the Prettier Maven plugin as an external tool in IntelliJ. Set it up as an `External tool` and assign a key-shortcut to the tool execution. -![External Tool Dialog](images/ExternalToolDialog.png) +![External Tool Dialog](../images/ExternalToolDialog.png) ``` Name: Prettier Format Current File diff --git a/CODE_CONVENTIONS.md b/doc/dev/decisionrecords/NamingConventions.md similarity index 56% rename from CODE_CONVENTIONS.md rename to doc/dev/decisionrecords/NamingConventions.md index 49b92fbe2f8..ed6175f4f4c 100644 --- a/CODE_CONVENTIONS.md +++ b/doc/dev/decisionrecords/NamingConventions.md @@ -1,34 +1,4 @@ -# Code Conventions - -We try to follow these conventions or best practices. The goal is to get cleaner code and make the -review process easier: - -- the developer knows what to expect -- the reviewer knows what to look for -- discussions and personal preferences can be avoided saving time -- new topics should be documented here - -These conventions are not "hard" rules, and often there might be other forces which pull a -decision in another direction, in that case documenting your choice is often enough to pass the -review. - -## Best practices - in focus - -- [ ] Document `public` interfaces, classes and methods - especially those part of a module api. -- [ ] Leave Things BETTER than you found them - clean up code you visit or/and add unit tests. -- [ ] [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) - Do not repeat yourself. Avoid implementing the same business rule in two places -> refactor. -- [ ] [Feature envy](https://refactoring.guru/smells/feature-envy) -- [ ] Make types immutable if possible. References to other Entities might need to be mutable, if - so try to init them once, and throw an exception if set again. - Example: - -```java -Builder initStop(Stop stop) { - this.stop = requireNotInitialized(this.stop, stop); -} -``` - -## Naming Conventions +# 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 @@ -36,7 +6,7 @@ should follow the Java standard naming conventions. For example a "real-time upd is named `RealTimeUpdater`. If in doubt check the Oxford Dictionary(American). -### Packages +## Packages Try to arrange code by domain functionality, not technology. The main structure of a package should be `org.opentripplanner...`. @@ -55,14 +25,14 @@ be `org.opentripplanner...`. | `util` | General "util" functionality, often characterized by `static` methods. Dependencies to other OTP packages is NOT allowed, only 3rd party utils libs. | | `o.o.apis` | OTP external endpoints. Note! Many apis are in the Sandbox where they are in the `o.o.ext` package. | -> **Note!** The above is the goal, the current package structure needs cleanup. +> **Note!** The above is the goal, the current package structure needs cleanup. > **Note!** Util methods depending on an OTP type/component should go into that type/component, not in the -utils class. E.g. static factory methods. Warning the "pure" utilities right now are placed into +utils class. E.g. static factory methods. Warning the "pure" utilities right now are placed into sub-packages of `o.o.util`, the root package needs cleanup. -### Methods +## Methods Here are a list of common prefixes used, and what to expect. @@ -88,15 +58,15 @@ These prefixes are also "allowed", but not preferred - they have some kind of ne | `setStop(Stop stop)` | Set a mutable stop reference. Avoid if not part of natural lifecycle. Use `initStop(...)` if possible | | `getStop() : Stop` | Old style accessor, use the shorter form `stop() : Stop` | -### Service, Model and Repository +## Service, Model and Repository -![MainModelOverview](docs/images/ServiceModelOverview.png) +![MainModelOverview](../images/ServiceModelOverview.png) Naming convention for builders with and without a context. -##### Graph Build and tests run without a context +#### Graph Build and tests run without a context ```Java // Create a new Stop @@ -105,112 +75,3 @@ trip = Trip.of(id).withName("The Express").build(); // Modify and existing stop stop = stop.copyOf().withPrivateCode("TEX").build(); ``` - - -## Records, POJOs and Builders - -We prefer immutable typesafe types over flexibility and "short" class definitions. This make -the code more robust and less error-prune. - -### Records - -You may use records, but avoid using records if you can not encapsulate it properly. Be especially -aware of arrays fields (can not be protected) and collections (remember to make a defensive copy). -If you need to override `equals` and `hashCode`, then it is probably not worth it. -Be aware that `equals` compare references, not the value of a field. Consider overriding `toString`. - -### Builders - -OTP used a simple builder pattern in many places, especially when creating immutable types. - -#### Builder conventions -- Use factory methods to create builder, either `of()` or `copyOf()`. The _copyOf_ uses an existing - instance as its base. The `of()` creates a builder with all default values set. All constructors - should be private (or package local) to enforce the use of the factory methods. -- If the class have more than 5 fields avoid using an inner class builder, instead create a builder - in the same package. -- Make all fields in the main class final to enforce immutability. -- Consider using utility methods for parameter checking, like `Objects#requireNonNull` and - `ObjectUtils.ifNotNull`. -- Validate all fields in the main type constructor(i.e. not in the builder), especially null checks. - Prefer default values over null-checks. All business logic using the type can rely on its validity. -- You may keep the original instance in the builder to avoid creating a new object if nothing - changed. This prevents polluting the heap for long-lived objects and make comparison very fast. -- There is no need to provide all get accessors in the Builder if not needed. -- Unit-test builders and verify all fields are copied over. -- For nested builders see the field `nested` in the example. - -
- Builder example - -```Java -/** - * THIS CLASS IS IMMUTABLE AND THREAD-SAFE - */ -public class A { - public static final A DEFAULT = new A(); - private final List names; - private final int age; - private final B nested; - - private A() { - this.names = List.of("default"); - this.age = 7; - this.nested = B.of(); - } - - private A(Builder builder) { - this.names = List.copyOf(builder.names); - this.age = builder.age; - this.nested = builder.nested(); - - if(age < 0 || age > 150) { - throw new IllegalArgumentException("Age is out of range[0..150]: " + age); - } - } - - public static A.Builder of() { return DEFAULT.copyOf(); } - public A.Builder copyOf() { return new Builder(this); } - - public List listNames() { return names; } - public int age() { return age; } - - public boolean equals(Object other) { ... } - public int hashCode() { ... } - public String toString() { return ToStringBuilder.of(A.class)...; } - - public static class Builder { - private final A original; - private final List names; - private int age; - private B.Builder nested = null; - - public Builder(A original) { - this.original = original; - this.names = new ArrayList<>(original.names); - this.age = original.age; - } - - public Builder withName(String name) { this.names.add(name); return this; } - - public int age() { return age; } - public Builder withAge(int age) { this.age = age; return this; } - - private B nested() { return nested==null ? original.nested() : nested.build(); } - public Builder withB(Consumer body) { - if(nested == null) { nested = original.nested.copyOf(); } - body.accept(nested); - return this; - } - public A build() { - A value = new A(this); - return original.equals(value) ? original : value; - } - } -} -``` - -
- - - diff --git a/doc/dev/decisionrecords/RecordsPOJOsBuilders.md b/doc/dev/decisionrecords/RecordsPOJOsBuilders.md new file mode 100644 index 00000000000..e0752fc70b0 --- /dev/null +++ b/doc/dev/decisionrecords/RecordsPOJOsBuilders.md @@ -0,0 +1,110 @@ +## Records, POJOs and Builders + +We prefer immutable typesafe types over flexibility and "short" class definitions. This makes +the code more robust and less error-prone. References to other entities might need to be mutable, +if so try to init them once, and throw an exception if set again. Example: + +```java +Builder initStop(Stop stop) { + this.stop = requireNotInitialized(this.stop, stop); +} +``` + + +### Records + +You may use records, but avoid using records if you cannot encapsulate them properly. Generally, records are considered appropriate and useful for throw-away compound types private to an implementation, such as hash table keys or compound values in a temporary list or set. On the other hand, records are generally not appropriate in the domain model where we insist on full encapsulation, which records cannot readily provide. Be especially +aware of array fields (which can not be protected) and collections (remember to make a defensive copy). Consider overriding `toString`. But if you need to override `equals` and `hashCode`, then it is probably not worth using a record. The implicit `equals()` and `hashCode()` implementations for records behave as if they call `equals` and `hashCode` on each field of the record, so their behavior will depend heavily on the types of these fields. + +### Builders + +OTP used a simple builder pattern in many places, especially when creating immutable types. + +#### Builder conventions +- Use factory methods to create builder, either `of()` or `copyOf()`. The _copyOf_ uses an existing + instance as its base. The `of()` creates a builder with all default values set. All constructors + should be private (or package local) to enforce the use of the factory methods. +- If the class has more than 5 fields, then avoid using an inner class builder, instead create a + builder in the same package. +- Make all fields in the main class final to enforce immutability. +- Consider using utility methods for parameter checking, like `Objects#requireNonNull` and + `ObjectUtils.ifNotNull`. +- Validate all fields in the main type constructor(i.e. not in the builder), especially null checks. + Prefer default values over null-checks. All business logic using the type can rely on its validity. +- You may keep the original instance in the builder to avoid creating a new object if nothing + changed. This prevents polluting the heap for long-lived objects and make comparison very fast. +- There is no need to provide all get accessors in the Builder if not needed. +- Unit-test builders and verify all fields are copied over. +- For nested builders see the field `nested` in the example. + +
+ Builder example + +```Java +/** + * THIS CLASS IS IMMUTABLE AND THREAD-SAFE + */ +public class A { + public static final A DEFAULT = new A(); + private final List names; + private final int age; + private final B nested; + + private A() { + this.names = List.of("default"); + this.age = 7; + this.nested = B.of(); + } + + private A(Builder builder) { + this.names = List.copyOf(builder.names); + this.age = builder.age; + this.nested = builder.nested(); + + if(age < 0 || age > 150) { + throw new IllegalArgumentException("Age is out of range[0..150]: " + age); + } + } + + public static A.Builder of() { return DEFAULT.copyOf(); } + public A.Builder copyOf() { return new Builder(this); } + + public List listNames() { return names; } + public int age() { return age; } + + public boolean equals(Object other) { ... } + public int hashCode() { ... } + public String toString() { return ToStringBuilder.of(A.class)...; } + + public static class Builder { + private final A original; + private final List names; + private int age; + private B.Builder nested = null; + + public Builder(A original) { + this.original = original; + this.names = new ArrayList<>(original.names); + this.age = original.age; + } + + public Builder withName(String name) { this.names.add(name); return this; } + + public int age() { return age; } + public Builder withAge(int age) { this.age = age; return this; } + + private B nested() { return nested==null ? original.nested() : nested.build(); } + public Builder withB(Consumer body) { + if(nested == null) { nested = original.nested.copyOf(); } + body.accept(nested); + return this; + } + public A build() { + A value = new A(this); + return original.equals(value) ? original : value; + } + } +} +``` + +
diff --git a/doc/dev/decisionrecords/UseDecisionRecords.md b/doc/dev/decisionrecords/UseDecisionRecords.md new file mode 100644 index 00000000000..a3520ea5133 --- /dev/null +++ b/doc/dev/decisionrecords/UseDecisionRecords.md @@ -0,0 +1,37 @@ +# Decision Records + +An OTP Decision Record is a justified software design choice that addresses a significant +functional or non-functional requirement. [Architectural Decision Records](https://adr.github.io/) is a similar +concept, but we have widened the scope to include any relevant decision about OTP development. + + +## Process + +Decisions we make in the developer meetings are recorded in the [Developer Decision Records](/DEVELOPMENT_DECISION_RECORDS.md) +list. If the decision is small and uncontroversial, but yet important and can be expressed in +maximum 2 sentences, we will list it here without any more documentation. If the decision requires +a bit more discussion and explanations, then we will create a PR with a document for it. + +Use the **[template](/doc/dev/decisionrecords/_TEMPLATE.md) as a starting point for documenting +the decision. + + +### How to discuss and document a Decision Record + +- Create a new pull-request and describe the decision record by adding a document to the + `/doc/dev/decisionrecords` folder. Use the [template](/doc/dev/decisionrecords/_TEMPLATE.md). + template. +- Present the decision record in a developer meeting. Make sure to update the main description + based on the feedback/discussion and decisions in the developer meeting. +- The final approval is done in the developer meeting, at least 3 developers representing 3 + different organisations should approve it. No vote against the proposal. If the developers + are not able to agree, the PLC can decide. +- References to Development Decision Records in reviews can be done by linking or just typing. + For example `Use-Dependency-Injection` or [Use-Dependency-Injection](../../../DEVELOPMENT_DECISION_RECORDS.md#use-dependency-injection) + +### Checklist +- [ ] Give it a meaningful title that quickly lets the reader understand what it is all about. +- [ ] Get it approved in a developer meeting with 3 votes in favor (3 organisations). +- [ ] Add the name and description to the list in the [Development Decision Records](../../../DEVELOPMENT_DECISION_RECORDS.md) list. + Maximum two sentences should be used. Try to keep it as short as possible. +- [ ] Remember to link to the PR. diff --git a/doc/dev/decisionrecords/_TEMPLATE.md b/doc/dev/decisionrecords/_TEMPLATE.md new file mode 100644 index 00000000000..45bb3b11d34 --- /dev/null +++ b/doc/dev/decisionrecords/_TEMPLATE.md @@ -0,0 +1,33 @@ +# {TITLE} + +{DESCRIPTION} + + +Original pull-request: {#NNNN} + + +### Context and Problem Statement + + + +### Other options + + - + +### Decision & Consequences + + + +#### Positive Consequences + + - + +#### Negative Consequences + + - diff --git a/docs/images/ExternalToolDialog.png b/doc/dev/images/ExternalToolDialog.png similarity index 100% rename from docs/images/ExternalToolDialog.png rename to doc/dev/images/ExternalToolDialog.png diff --git a/docs/images/ServiceModelOverview.png b/doc/dev/images/ServiceModelOverview.png similarity index 100% rename from docs/images/ServiceModelOverview.png rename to doc/dev/images/ServiceModelOverview.png diff --git a/doc-templates/BuildConfiguration.md b/doc/templates/BuildConfiguration.md similarity index 99% rename from doc-templates/BuildConfiguration.md rename to doc/templates/BuildConfiguration.md index 43a29e1fb79..7e768e30eb1 100644 --- a/doc-templates/BuildConfiguration.md +++ b/doc/templates/BuildConfiguration.md @@ -1,7 +1,7 @@ diff --git a/doc-templates/Configuration.md b/doc/templates/Configuration.md similarity index 99% rename from doc-templates/Configuration.md rename to doc/templates/Configuration.md index e06adc46dc5..0ef96b8d4fa 100644 --- a/doc-templates/Configuration.md +++ b/doc/templates/Configuration.md @@ -1,7 +1,7 @@ diff --git a/doc-templates/Emissions.md b/doc/templates/Emissions.md similarity index 100% rename from doc-templates/Emissions.md rename to doc/templates/Emissions.md diff --git a/doc-templates/Flex.md b/doc/templates/Flex.md similarity index 100% rename from doc-templates/Flex.md rename to doc/templates/Flex.md diff --git a/doc-templates/GraphQL-Tutorial.md b/doc/templates/GraphQL-Tutorial.md similarity index 97% rename from doc-templates/GraphQL-Tutorial.md rename to doc/templates/GraphQL-Tutorial.md index 51f0ef7880a..11a2e304119 100644 --- a/doc-templates/GraphQL-Tutorial.md +++ b/doc/templates/GraphQL-Tutorial.md @@ -1,7 +1,7 @@ diff --git a/doc-templates/OsmMapper.md b/doc/templates/OsmMapper.md similarity index 100% rename from doc-templates/OsmMapper.md rename to doc/templates/OsmMapper.md diff --git a/doc-templates/RideHailing.md b/doc/templates/RideHailing.md similarity index 100% rename from doc-templates/RideHailing.md rename to doc/templates/RideHailing.md diff --git a/doc-templates/RouteRequest.md b/doc/templates/RouteRequest.md similarity index 92% rename from doc-templates/RouteRequest.md rename to doc/templates/RouteRequest.md index 4abfdec71e2..a452e1d1480 100644 --- a/doc-templates/RouteRequest.md +++ b/doc/templates/RouteRequest.md @@ -1,7 +1,7 @@ diff --git a/doc-templates/RouterConfiguration.md b/doc/templates/RouterConfiguration.md similarity index 96% rename from doc-templates/RouterConfiguration.md rename to doc/templates/RouterConfiguration.md index f181bf5db93..b6c6ccf9c4b 100644 --- a/doc-templates/RouterConfiguration.md +++ b/doc/templates/RouterConfiguration.md @@ -1,7 +1,7 @@ diff --git a/doc-templates/RoutingModes.md b/doc/templates/RoutingModes.md similarity index 100% rename from doc-templates/RoutingModes.md rename to doc/templates/RoutingModes.md diff --git a/doc-templates/StopConsolidation.md b/doc/templates/StopConsolidation.md similarity index 97% rename from doc-templates/StopConsolidation.md rename to doc/templates/StopConsolidation.md index c92cab6afe1..6817ee47d4c 100644 --- a/doc-templates/StopConsolidation.md +++ b/doc/templates/StopConsolidation.md @@ -1,7 +1,7 @@ # Stop consolidation diff --git a/doc-templates/UpdaterConfig.md b/doc/templates/UpdaterConfig.md similarity index 98% rename from doc-templates/UpdaterConfig.md rename to doc/templates/UpdaterConfig.md index f16d28f570c..440cd96f733 100644 --- a/doc-templates/UpdaterConfig.md +++ b/doc/templates/UpdaterConfig.md @@ -1,7 +1,7 @@ diff --git a/doc-templates/VehicleParking.md b/doc/templates/VehicleParking.md similarity index 84% rename from doc-templates/VehicleParking.md rename to doc/templates/VehicleParking.md index 5d149e40f9a..721cbc2657a 100644 --- a/doc-templates/VehicleParking.md +++ b/doc/templates/VehicleParking.md @@ -48,6 +48,16 @@ All updaters have the following parameters in common: +## SIRI-FM + +The SIRI-FM updater works slightly differently from the others in that it only updates the availability +of parking but does not create new lots in realtime. + +The data source must conform to the [Italian SIRI-FM](https://github.com/5Tsrl/siri-italian-profile) profile +which requires SIRI 2.1. + + + ## Changelog - Create initial sandbox implementation (January 2022, [#3796](https://github.com/opentripplanner/OpenTripPlanner/pull/3796)) diff --git a/doc-templates/sandbox/MapboxVectorTilesApi.md b/doc/templates/sandbox/MapboxVectorTilesApi.md similarity index 100% rename from doc-templates/sandbox/MapboxVectorTilesApi.md rename to doc/templates/sandbox/MapboxVectorTilesApi.md diff --git a/doc-templates/sandbox/VehicleRentalServiceDirectory.md b/doc/templates/sandbox/VehicleRentalServiceDirectory.md similarity index 100% rename from doc-templates/sandbox/VehicleRentalServiceDirectory.md rename to doc/templates/sandbox/VehicleRentalServiceDirectory.md diff --git a/doc-templates/sandbox/siri/SiriAzureUpdater.md b/doc/templates/sandbox/siri/SiriAzureUpdater.md similarity index 100% rename from doc-templates/sandbox/siri/SiriAzureUpdater.md rename to doc/templates/sandbox/siri/SiriAzureUpdater.md diff --git a/doc-templates/sandbox/siri/SiriGooglePubSubUpdater.md b/doc/templates/sandbox/siri/SiriGooglePubSubUpdater.md similarity index 100% rename from doc-templates/sandbox/siri/SiriGooglePubSubUpdater.md rename to doc/templates/sandbox/siri/SiriGooglePubSubUpdater.md diff --git a/doc-templates/sandbox/siri/SiriUpdater.md b/doc/templates/sandbox/siri/SiriUpdater.md similarity index 100% rename from doc-templates/sandbox/siri/SiriUpdater.md rename to doc/templates/sandbox/siri/SiriUpdater.md diff --git a/docs/Accessibility.md b/doc/user/Accessibility.md similarity index 99% rename from docs/Accessibility.md rename to doc/user/Accessibility.md index 49373c522c2..27cb47ce92e 100644 --- a/docs/Accessibility.md +++ b/doc/user/Accessibility.md @@ -109,4 +109,4 @@ inaccessible to wheelchair users. ## Example A full configuration example is available -at [`/docs/examples`](https://github.com/opentripplanner/OpenTripPlanner/tree/dev-2.x/docs/examples/ibi) \ No newline at end of file +at [`/docs/examples`](https://github.com/opentripplanner/OpenTripPlanner/tree/dev-2.x/doc/user/examples/ibi) \ No newline at end of file diff --git a/docs/Analysis.md b/doc/user/Analysis.md similarity index 100% rename from docs/Analysis.md rename to doc/user/Analysis.md diff --git a/docs/Basic-Tutorial.md b/doc/user/Basic-Tutorial.md similarity index 98% rename from docs/Basic-Tutorial.md rename to doc/user/Basic-Tutorial.md index a7a69b8a3bb..d6e46f3f1f5 100644 --- a/docs/Basic-Tutorial.md +++ b/doc/user/Basic-Tutorial.md @@ -1,207 +1,207 @@ -# OpenTripPlanner Basic Tutorial - -This page should allow you to set up and test your own OTP2 server. If all goes well it should only -take a few minutes! - -## Get Java - -As a Java program, OTP must be run within a Java virtual machine (JVM), which is provided as part of -the Java runtime (JRE) or Java development kit (JDK). OTP2 is compatible with Java 21 or later. We -recommend running on Java 21 rather than a later version, as it is a long-term support release. -Run `java -version` to check that you have version 21 or newer of the JVM installed. If you do not, -you will need to install a recent OpenJDK or Oracle Java package for your operating system. - -## Get OTP - -OpenTripPlanner is written in Java and distributed as a single runnable JAR file. This is a "shaded" -JAR containing all other libraries needed for OTP to work, and is available from the Maven Central -repository. You will be able to go -to [the OTP directory at Maven Central](https://repo1.maven.org/maven2/org/opentripplanner/otp/), -navigate to -the [directory of releases](https://repo1.maven.org/maven2/org/opentripplanner/otp/2.5.0/), -and download -the [file with `shaded.jar` suffix](https://repo1.maven.org/maven2/org/opentripplanner/otp/2.5.0/otp-2.5.0-shaded.jar) -. - -You may also want to get your own copy of the OTP source code -and [build a bleeding edge development JAR from scratch](Getting-OTP.md), especially if you plan to -do some development yourself. In that case, check out the branch `dev-2.x`. - -## Get some data - -### GTFS for Transit Schedules and Stops - -First you'll need GTFS data to build a transit network. There's an excellent description of the GTFS -format [here](http://gtfs.org/). Transport agencies throughout the world provide GTFS schedules to -the public. Transitland has a -[registry of feeds](https://transit.land/feed-registry) and [TransitFeeds](http://transitfeeds.com/) -also provides an extensive catalog. The best option is often to simply fetch the data directly from -a transit operator or agency. If you know of a feed you want to work with, download it and put it in -an empty directory you have created for your OTP instance such as `/home/username/otp` on -Linux, `/Users/username/otp` on MacOS, or `C:\Users\username\otp` on Windows. For OTP2 to detect a -GTFS file, **its name must end in `.zip` and must contain the letters 'gtfs'**. We often use the -convention of saving GTFS files with names ending in `.gtfs.zip` which meets both these criteria, -reflecting the fact that a GTFS feed is just a ZIP file containing a specific set of files. If you -don't have a particular feed in mind, the one for Portland, Oregon's TriMet agency is a good option. -It is available at [this URL](http://developer.trimet.org/schedule/gtfs.zip). This is a -moderate-sized input of good quality (TriMet initiated OTP development and helped develop the GTFS -format). On Linux, this could be done on the command line as follows: - - $ cd /home/username - $ mkdir otp - $ cd otp - $ wget "http://developer.trimet.org/schedule/gtfs.zip" -O trimet.gtfs.zip - -### OSM for Streets - -You'll also need OpenStreetMap data to build a road network for walking, cycling, and -driving. [OpenStreetMap](https://www.openstreetmap.org/) is a global collaborative map database that -rivals or surpasses the quality of commercial maps in many locations. Several services extract -smaller geographic regions from this database. Interline Technologies maintains a collection -of [extracts updated daily for urban areas around the world](https://www.interline.io/osm/extracts/) -. [Geofabrik](http://download.geofabrik.de/) provides extracts for larger areas like countries or -states, from which you can prepare your own smaller bounding-box extracts -using [Osmosis](http://wiki.openstreetmap.org/wiki/Osmosis#Extracting_bounding_boxes) -, [osmconvert](http://wiki.openstreetmap.org/wiki/Osmconvert#Applying_Geographical_Borders), or (our -favorite) [Osmium-Tool](https://osmcode.org/osmium-tool/manual.html#creating-geographic-extracts). -There is also [Protomaps](https://app.protomaps.com/) which can create custom extracts -for any region of the world with an easy to use drag and drop interface. -OSM data can be delivered as XML or in the more compact binary PBF format. OpenTripPlanner consumes -only PBF because it's smaller and more efficient. - -Download OSM PBF data for the same geographic region as your GTFS feed, and place this PBF file in -the same directory you created for the OSM data. If you are using the TriMet GTFS feed, you could -download -the [Geofabrik extract for the US state of Oregon](http://download.geofabrik.de/north-america/us/oregon.html) -, then further trim that to just -the [TriMet service area](https://trimet.org/pdfs/taxinfo/trimetdistrictboundary.pdf) using the -bounding box switch of one of the above tools. On Linux or MacOS you could do that as follows: - - $ cd /home/username - $ wget http://download.geofabrik.de/north-america/us/oregon-latest.osm.pbf - $ osmconvert oregon-latest.osm.pbf -b=-123.043,45.246,-122.276,45.652 --complete-ways -o=portland.pbf - $ mv portland.pbf otp - -We find [this tool](https://boundingbox.klokantech.com/) useful for determining the geographic -coordinates of bounding boxes. The CSV option in that tool produces exactly the format expected by -the `osmconvert -b` switch. The `--complete-ways` switch is important to handle roads that cross -outside your bounding box. - -If you have extracted a smaller PBF file from a larger region, be sure to put only your extract (not -the original larger file) in the directory with your GTFS data. Otherwise OTP will try to load both -the original file and the extract in a later step. See -the [page on preparing OSM data](Preparing-OSM.md) for additional information and example commands -for cropping and filtering OSM data. - -## Starting OTP - -A typical command to start OTP looks like `java -Xmx2G -jar otp.shaded.jar `. The -`-Xmx` parameter sets the limit on how much memory OTP is allowed to consume. GTFS and OSM data sets -are often very large, and OTP is relatively memory-hungry. You will need at least 1GB of memory when -working with the Portland TriMet data set, and several gigabytes for larger inputs. -[Here is more information about the system requirements](System-Requirements.md). If you have -sufficient memory in your computer, set this to a couple of gigabytes (e.g. `-Xmx2G`). Java uses -a [garbage collection](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)) approach -to memory management, which requires some "breathing room" to efficiently operate. Without -sufficient free memory OTP can grind to a halt. [VisualVM](https://visualvm.github.io) is a good way -to inspect Java memory usage, especially with -the [VisualGC plugin](https://visualvm.github.io/plugins.html). - -## Building Graphs - -There are two main phases to preparing and deploying an OTP server. The first is to analyze the -GTFS, OSM and any other inputs (such as elevation data) and build a representation of the -transportation network. Following mathematical terminology we call this -a ['graph'](http://en.wikipedia.org/wiki/Graph_%28mathematics%29), and refer to this phase as "graph -building". The second phase is to start a server that provides trip planning and other API services -for this graph. - -It is possible to save the graph to a file on disk after the first phase, then load the graph from -the file in the second phase. This allows restarting the server or starting multiple instances of -the server without repeating the often time-consuming process of building the graph. It is also -possible to split the graph building process into separate OSM and GTFS stages for similar reasons: -to allow reusing results from slow processes, such as applying elevation data to streets. These -different options are controlled with command line switches, and will be described in more detail -below and in other tutorials. - -## Simple One-step Server - -The simplest way to use OTP is to build a graph in a single step and start a server immediately, -without saving it to disk. The command to do so is: - - $ java -Xmx2G -jar otp-2.5.0-shaded.jar --build --serve /home/username/otp - -where `/home/username/otp` should be the directory where you put your configuration and input files. - -If you're using the Portland input data, the graph build operation should take about one minute to -complete, and then you'll see a `Grizzly server running` message. At this point you have an -OpenTripPlanner server running locally and can open [http://localhost:8080/](http://localhost:8080/) -in a web browser. You should be presented with a Javascript client application that will interact -with your local OpenTripPlanner instance. - -This map-based user interface is in fact sending HTTP GET requests to the OTP server running on your -local machine. It can be informative to watch the HTTP requests and responses being generated using -the developer tools in your web browser. OTP's built-in web server will run by default on port 8080. -If by any chance some other software is already using that port number, you can specify a different -port number with a switch -`--port 8801`. - - -## Saving a Graph - -If you want speed up the process of repeatedly starting up a server with the same graph, you can -build a graph from street and transit data then save it to a file using the `--build` and `--save` -command line parameters together. If for example your current working directory (`.`) contains the -input files and the OTP JAR file, you can use this command: - - $ java -Xmx2G -jar otp-2.5.0-shaded.jar --build --save . - -This will produce a file called `graph.obj` in the same directory as the inputs. The server can then -be started later using the `--load` parameter, and will read this file instead of building the graph -from scratch: - - $ java -Xmx2G -jar otp-2.5.0-shaded.jar --load . - -Another reason to perform these two phases separately is that the building process loads the entire -GTFS and OSM data sets into memory, so can require significantly more memory than just running a -server. Accordingly, you may want to perform the build on one machine (e.g. a throw-away cloud -instance with more memory or compute capacity), then copy the resulting graph file to one or more -smaller machines to serve the API. - -## Layering GTFS onto OSM - -Building the street graph (especially with elevation data) can take a long time. It is common for -transit data to change more frequently than street data, so it can be convenient to build the street -graph once, and then layer transit data on top of the streets to make the final graph. - -Again assuming the input files and OTP JAR file are in the current working directory, you can build -a street graph with OSM and elevation data only (ignoring transit input files) with this command: - - $ java -Xmx2G -jar otp-2.5.0-shaded.jar --buildStreet . - -Then, to build a graph layering transit data on top of the saved street graph (built using the -previous command): - - $ java -Xmx2G -jar otp-2.5.0-shaded.jar --loadStreet --save . - -Finally, the server can be started using the `--load` parameter: - - $ java -Xmx2G -jar otp-2.5.0-shaded.jar --load . - -## Command Line Switches - -The flow diagram below summarizes all the command line switches used in the above examples, and how -they control which actions are taken when OTP starts up. - -![Command-Line-Parameter-Flow](images/cli-flow.svg) - -You must use at least one of the required parameters: `--load`, `--loadStreet`, `--build` -, `--buildStreet`. A _required_ parameter may imply other parameters when the flow allows for no -other choice. For example, `--load` implies `--serve`, so `--serve` is not necessary and has no -additional effect when used together with `--load`. - -You can run the OTP .jar file with the `--help` option for a full list of command line parameters. - -## Exploring the API - -If you want to learn how to use OTP's API's, check out the [GraphQL tutorial](apis/GraphQL-Tutorial.md). +# OpenTripPlanner Basic Tutorial + +This page should allow you to set up and test your own OTP2 server. If all goes well it should only +take a few minutes! + +## Get Java + +As a Java program, OTP must be run within a Java virtual machine (JVM), which is provided as part of +the Java runtime (JRE) or Java development kit (JDK). OTP2 is compatible with Java 21 or later. We +recommend running on Java 21 rather than a later version, as it is a long-term support release. +Run `java -version` to check that you have version 21 or newer of the JVM installed. If you do not, +you will need to install a recent OpenJDK or Oracle Java package for your operating system. + +## Get OTP + +OpenTripPlanner is written in Java and distributed as a single runnable JAR file. This is a "shaded" +JAR containing all other libraries needed for OTP to work, and is available from the Maven Central +repository. You will be able to go +to [the OTP directory at Maven Central](https://repo1.maven.org/maven2/org/opentripplanner/otp/), +navigate to +the [directory of releases](https://repo1.maven.org/maven2/org/opentripplanner/otp/2.5.0/), +and download +the [file with `shaded.jar` suffix](https://repo1.maven.org/maven2/org/opentripplanner/otp/2.5.0/otp-2.5.0-shaded.jar) +. + +You may also want to get your own copy of the OTP source code +and [build a bleeding edge development JAR from scratch](Getting-OTP.md), especially if you plan to +do some development yourself. In that case, check out the branch `dev-2.x`. + +## Get some data + +### GTFS for Transit Schedules and Stops + +First you'll need GTFS data to build a transit network. There's an excellent description of the GTFS +format [here](http://gtfs.org/). Transport agencies throughout the world provide GTFS schedules to +the public. Transitland has a +[registry of feeds](https://transit.land/feed-registry) and [TransitFeeds](http://transitfeeds.com/) +also provides an extensive catalog. The best option is often to simply fetch the data directly from +a transit operator or agency. If you know of a feed you want to work with, download it and put it in +an empty directory you have created for your OTP instance such as `/home/username/otp` on +Linux, `/Users/username/otp` on MacOS, or `C:\Users\username\otp` on Windows. For OTP2 to detect a +GTFS file, **its name must end in `.zip` and must contain the letters 'gtfs'**. We often use the +convention of saving GTFS files with names ending in `.gtfs.zip` which meets both these criteria, +reflecting the fact that a GTFS feed is just a ZIP file containing a specific set of files. If you +don't have a particular feed in mind, the one for Portland, Oregon's TriMet agency is a good option. +It is available at [this URL](http://developer.trimet.org/schedule/gtfs.zip). This is a +moderate-sized input of good quality (TriMet initiated OTP development and helped develop the GTFS +format). On Linux, this could be done on the command line as follows: + + $ cd /home/username + $ mkdir otp + $ cd otp + $ wget "http://developer.trimet.org/schedule/gtfs.zip" -O trimet.gtfs.zip + +### OSM for Streets + +You'll also need OpenStreetMap data to build a road network for walking, cycling, and +driving. [OpenStreetMap](https://www.openstreetmap.org/) is a global collaborative map database that +rivals or surpasses the quality of commercial maps in many locations. Several services extract +smaller geographic regions from this database. Interline Technologies maintains a collection +of [extracts updated daily for urban areas around the world](https://www.interline.io/osm/extracts/) +. [Geofabrik](http://download.geofabrik.de/) provides extracts for larger areas like countries or +states, from which you can prepare your own smaller bounding-box extracts +using [Osmosis](http://wiki.openstreetmap.org/wiki/Osmosis#Extracting_bounding_boxes) +, [osmconvert](http://wiki.openstreetmap.org/wiki/Osmconvert#Applying_Geographical_Borders), or (our +favorite) [Osmium-Tool](https://osmcode.org/osmium-tool/manual.html#creating-geographic-extracts). +There is also [Protomaps](https://app.protomaps.com/) which can create custom extracts +for any region of the world with an easy to use drag and drop interface. +OSM data can be delivered as XML or in the more compact binary PBF format. OpenTripPlanner consumes +only PBF because it's smaller and more efficient. + +Download OSM PBF data for the same geographic region as your GTFS feed, and place this PBF file in +the same directory you created for the OSM data. If you are using the TriMet GTFS feed, you could +download +the [Geofabrik extract for the US state of Oregon](http://download.geofabrik.de/north-america/us/oregon.html) +, then further trim that to just +the [TriMet service area](https://trimet.org/pdfs/taxinfo/trimetdistrictboundary.pdf) using the +bounding box switch of one of the above tools. On Linux or MacOS you could do that as follows: + + $ cd /home/username + $ wget http://download.geofabrik.de/north-america/us/oregon-latest.osm.pbf + $ osmconvert oregon-latest.osm.pbf -b=-123.043,45.246,-122.276,45.652 --complete-ways -o=portland.pbf + $ mv portland.pbf otp + +We find [this tool](https://boundingbox.klokantech.com/) useful for determining the geographic +coordinates of bounding boxes. The CSV option in that tool produces exactly the format expected by +the `osmconvert -b` switch. The `--complete-ways` switch is important to handle roads that cross +outside your bounding box. + +If you have extracted a smaller PBF file from a larger region, be sure to put only your extract (not +the original larger file) in the directory with your GTFS data. Otherwise OTP will try to load both +the original file and the extract in a later step. See +the [page on preparing OSM data](Preparing-OSM.md) for additional information and example commands +for cropping and filtering OSM data. + +## Starting OTP + +A typical command to start OTP looks like `java -Xmx2G -jar otp.shaded.jar `. The +`-Xmx` parameter sets the limit on how much memory OTP is allowed to consume. GTFS and OSM data sets +are often very large, and OTP is relatively memory-hungry. You will need at least 1GB of memory when +working with the Portland TriMet data set, and several gigabytes for larger inputs. +[Here is more information about the system requirements](System-Requirements.md). If you have +sufficient memory in your computer, set this to a couple of gigabytes (e.g. `-Xmx2G`). Java uses +a [garbage collection](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)) approach +to memory management, which requires some "breathing room" to efficiently operate. Without +sufficient free memory OTP can grind to a halt. [VisualVM](https://visualvm.github.io) is a good way +to inspect Java memory usage, especially with +the [VisualGC plugin](https://visualvm.github.io/plugins.html). + +## Building Graphs + +There are two main phases to preparing and deploying an OTP server. The first is to analyze the +GTFS, OSM and any other inputs (such as elevation data) and build a representation of the +transportation network. Following mathematical terminology we call this +a ['graph'](http://en.wikipedia.org/wiki/Graph_%28mathematics%29), and refer to this phase as "graph +building". The second phase is to start a server that provides trip planning and other API services +for this graph. + +It is possible to save the graph to a file on disk after the first phase, then load the graph from +the file in the second phase. This allows restarting the server or starting multiple instances of +the server without repeating the often time-consuming process of building the graph. It is also +possible to split the graph building process into separate OSM and GTFS stages for similar reasons: +to allow reusing results from slow processes, such as applying elevation data to streets. These +different options are controlled with command line switches, and will be described in more detail +below and in other tutorials. + +## Simple One-step Server + +The simplest way to use OTP is to build a graph in a single step and start a server immediately, +without saving it to disk. The command to do so is: + + $ java -Xmx2G -jar otp-2.5.0-shaded.jar --build --serve /home/username/otp + +where `/home/username/otp` should be the directory where you put your configuration and input files. + +If you're using the Portland input data, the graph build operation should take about one minute to +complete, and then you'll see a `Grizzly server running` message. At this point you have an +OpenTripPlanner server running locally and can open [http://localhost:8080/](http://localhost:8080/) +in a web browser. You should be presented with a Javascript client application that will interact +with your local OpenTripPlanner instance. + +This map-based user interface is in fact sending HTTP GET requests to the OTP server running on your +local machine. It can be informative to watch the HTTP requests and responses being generated using +the developer tools in your web browser. OTP's built-in web server will run by default on port 8080. +If by any chance some other software is already using that port number, you can specify a different +port number with a switch +`--port 8801`. + + +## Saving a Graph + +If you want speed up the process of repeatedly starting up a server with the same graph, you can +build a graph from street and transit data then save it to a file using the `--build` and `--save` +command line parameters together. If for example your current working directory (`.`) contains the +input files and the OTP JAR file, you can use this command: + + $ java -Xmx2G -jar otp-2.5.0-shaded.jar --build --save . + +This will produce a file called `graph.obj` in the same directory as the inputs. The server can then +be started later using the `--load` parameter, and will read this file instead of building the graph +from scratch: + + $ java -Xmx2G -jar otp-2.5.0-shaded.jar --load . + +Another reason to perform these two phases separately is that the building process loads the entire +GTFS and OSM data sets into memory, so can require significantly more memory than just running a +server. Accordingly, you may want to perform the build on one machine (e.g. a throw-away cloud +instance with more memory or compute capacity), then copy the resulting graph file to one or more +smaller machines to serve the API. + +## Layering GTFS onto OSM + +Building the street graph (especially with elevation data) can take a long time. It is common for +transit data to change more frequently than street data, so it can be convenient to build the street +graph once, and then layer transit data on top of the streets to make the final graph. + +Again assuming the input files and OTP JAR file are in the current working directory, you can build +a street graph with OSM and elevation data only (ignoring transit input files) with this command: + + $ java -Xmx2G -jar otp-2.5.0-shaded.jar --buildStreet . + +Then, to build a graph layering transit data on top of the saved street graph (built using the +previous command): + + $ java -Xmx2G -jar otp-2.5.0-shaded.jar --loadStreet --save . + +Finally, the server can be started using the `--load` parameter: + + $ java -Xmx2G -jar otp-2.5.0-shaded.jar --load . + +## Command Line Switches + +The flow diagram below summarizes all the command line switches used in the above examples, and how +they control which actions are taken when OTP starts up. + +![Command-Line-Parameter-Flow](images/cli-flow.svg) + +You must use at least one of the required parameters: `--load`, `--loadStreet`, `--build` +, `--buildStreet`. A _required_ parameter may imply other parameters when the flow allows for no +other choice. For example, `--load` implies `--serve`, so `--serve` is not necessary and has no +additional effect when used together with `--load`. + +You can run the OTP .jar file with the `--help` option for a full list of command line parameters. + +## Exploring the API + +If you want to learn how to use OTP's API's, check out the [GraphQL tutorial](apis/GraphQL-Tutorial.md). diff --git a/docs/Bibliography.md b/doc/user/Bibliography.md similarity index 100% rename from docs/Bibliography.md rename to doc/user/Bibliography.md diff --git a/docs/BoardingLocations.md b/doc/user/BoardingLocations.md similarity index 100% rename from docs/BoardingLocations.md rename to doc/user/BoardingLocations.md diff --git a/docs/BuildConfiguration.md b/doc/user/BuildConfiguration.md similarity index 99% rename from docs/BuildConfiguration.md rename to doc/user/BuildConfiguration.md index 6a6b42cd664..b311991120e 100644 --- a/docs/BuildConfiguration.md +++ b/doc/user/BuildConfiguration.md @@ -1,7 +1,7 @@ diff --git a/docs/Changelog.md b/doc/user/Changelog.md similarity index 99% rename from docs/Changelog.md rename to doc/user/Changelog.md index 64840e36790..5e93c90b66c 100644 --- a/docs/Changelog.md +++ b/doc/user/Changelog.md @@ -53,6 +53,10 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Keep at least one result for min-transfers and each transit-group in itinerary-group-filter [#5919](https://github.com/opentripplanner/OpenTripPlanner/pull/5919) - Extract parking lots from NeTEx feeds [#5946](https://github.com/opentripplanner/OpenTripPlanner/pull/5946) - Filter routes and patterns by service date in GTFS GraphQL API [#5869](https://github.com/opentripplanner/OpenTripPlanner/pull/5869) +- SIRI-FM vehicle parking updates [#5979](https://github.com/opentripplanner/OpenTripPlanner/pull/5979) +- Take realtime patterns into account when storing realtime vehicles [#5994](https://github.com/opentripplanner/OpenTripPlanner/pull/5994) +- Debug client itinerary list style improvements [#6012](https://github.com/opentripplanner/OpenTripPlanner/pull/6012) +- Developer Decision Records [#5932](https://github.com/opentripplanner/OpenTripPlanner/pull/5932) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.5.0 (2024-03-13) diff --git a/docs/Configuration.md b/doc/user/Configuration.md similarity index 99% rename from docs/Configuration.md rename to doc/user/Configuration.md index 7e48dab5344..8e366e476a9 100644 --- a/docs/Configuration.md +++ b/doc/user/Configuration.md @@ -1,7 +1,7 @@ diff --git a/docs/Container-Image.md b/doc/user/Container-Image.md similarity index 100% rename from docs/Container-Image.md rename to doc/user/Container-Image.md diff --git a/docs/Data-Sources.md b/doc/user/Data-Sources.md similarity index 86% rename from docs/Data-Sources.md rename to doc/user/Data-Sources.md index bf426186f57..5ee5fea29d1 100644 --- a/docs/Data-Sources.md +++ b/doc/user/Data-Sources.md @@ -4,9 +4,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 (real-time) -data. +from [OpenStreetMap](http://wiki.openstreetmap.org/wiki/Main_Page), [GTFS/GTFS RT](https://gtfs.org/documentation/overview/) and [GBFS](https://gbfs.org/). In addition to GTFS, OTP can also load data in the Nordic Profile of Netex, the EU-standard transit data interchange format. The upcoming EU-wide profile was heavily influenced by the Nordic Profile diff --git a/docs/Deployments.md b/doc/user/Deployments.md similarity index 98% rename from docs/Deployments.md rename to doc/user/Deployments.md index d1df3984b05..56ff23c113c 100644 --- a/docs/Deployments.md +++ b/doc/user/Deployments.md @@ -1,96 +1,96 @@ -# OpenTripPlanner Deployments Worldwide - -## Official Production - -The following are known deployments of OTP in a government- or agency-sponsored production capacity: - -* **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 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/), - the [Finnish Transport Agency](https://opas.matka.fi/), and - other [Finnish cities](https://waltti.fi/?lang=en) have collaborated to - create [Digitransit](https://digitransit.fi/en/), providing OTP-based trip planners, APIs, open - data, Docker containers and open source code. Each member organisation runs its own instance of a - shared codebase and deployment environment. Their source code is - available [on Github](https://github.com/HSLdevcom/), including - a [new custom UI](https://github.com/HSLdevcom/digitransit-ui). This system also has a strong - real-time component. -* **Finland Intercity** The Finnish intercity coach - 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 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. - Their [Regional Trip Planner](http://ride.trimet.org) is based on OTP and provides about 40,000 - trip plans on a typical weekday. -* [**New York City**](https://new.mta.info/) -* **New York State** The State Department of - Transportation's [transit trip planner](https://511ny.org/#TransitRegion-1) provides itineraries - for public transit systems throughout the state in a single unified OTP instance. -* **Los Angeles, California** The new [metro.net trip planner](https://www.metro.net/). -* **Atlanta, Georgia** The Metropolitan Atlanta Rapid Transit Authority's ( - MARTA) [trip planner](http://itsmarta.com/planatrip.aspx) and the Atlanta region's transit - information hub [https://atlrides.com/](https://atlrides.com/) both use OTP to power their website trip - planners. -* **Boston, Massachusetts** - The [Massachusetts Bay Transportation Authority trip planner](https://www.mbta.com/trip-planner). -* **Seattle, Washington** The [Sound Transit Trip Planner](https://www.soundtransit.org/tripplanner) - is based on OTP. OTP also powers the trip planning feature of - the [OneBusAway native apps](http://onebusaway.org/) in the Puget Sound region. Technical details - are [here](https://github.com/OneBusAway/onebusaway-android/blob/master/SYSTEM_ARCHITECTURE.md#add-trip-planning-andor-bike-share-optional) - . -* [**Ride Metro Houston**](https://planyourtrip.ridemetro.org/) -* **Tampa, Florida** Hillsoborough Area Regional Transit uses an OpenTripPlanner server to power the - trip planning feature of the [OneBusAway native apps](http://onebusaway.org/) in their region. - Technical details - are [here](https://github.com/OneBusAway/onebusaway-android/blob/master/SYSTEM_ARCHITECTURE.md#add-trip-planning-andor-bike-share-optional) - . -* [**Piemonte Region, Italy**](https://map.muoversinpiemonte.it/#planner) and the [**City of - Torino**](https://www.muoversiatorino.it/) built on OpenTripPlanner - by [5T](http://www.5t.torino.it/). -* [**Valencia, Spain**](http://www.emtvalencia.es/geoportal/?lang=en_otp) from the Municipal - Transport Company of Valencia S.A.U. -* [**Grenoble, France**](http://www.metromobilite.fr/) from SMTC, Grenoble Alpes métropole, l'État - Français, the Rhône-alpes region, the Isère council and the City of Grenoble. -* **Rennes, France** where the STAR network provides an OTP client - for [iOS](https://itunes.apple.com/us/app/starbusmetro/id899970416?mt=8) - , [Android](https://play.google.com/store/apps/details?id=com.bookbeo.starbusmetro), Windows Phone - et Web. -* **Alençon, France** integrated urban and school bus - network [planner from Réunir Alençon](https://altobus.com/mon-itineraire/). -* [**Poznań, Poland**](http://ztm.poznan.pl/#planner) from Urban Transport Authority of Poznań (ZTM - Poznan). -* **Trento Province, Italy** - - [ViaggiaTrento](https://play.google.com/store/apps/details?id=eu.trentorise.smartcampus.viaggiatrento) - and [ViaggiaRovereto](https://play.google.com/store/apps/details?id=eu.trentorise.smartcampus.viaggiarovereto) - were implemented as part of the [SmartCampus Project](http://www.smartcampuslab.it), a research - project founded by [TrentoRise](http://trentorise.eu), [UNITN](http://www.unitn.it), - and [FBK](http://www.fbk.eu). -* **University of South Florida** (Tampa, Florida). The [USF Maps App](https://maps.usf.edu/) is a - responsive web application for that helps university students, staff, and visitors find their way - around the campus using multiple modes of transportation, including the USF Bull Runner campus - shuttle, Share-A-Bull bike share, and pedestrian pathways. - Open-sourced [on Github](https://github.com/CUTR-at-USF/usf-mobullity). -* **Lower Saxony, Germany** The [VBN](https://www.vbn.de/en/) transportation authority offers an [OTP instance](https://www.vbn.de/en/service/developer-information/opendata-and-openservice) as alternative to the [Hafas](https://www.hacon.de/en/portfolio/information-ticketing/#section_8294) passenger information system. -* **Leipzig, Germany** As of summer 2020 [Leipzig Move](https://leipzig-move.de/) has been using - OpenTripPlanner. - -## Independent Production - -The following OTP-based services are presented as production-quality deployments, but are not backed -by an official transportation authority or government. OTP is also known to be used on the back end -of several popular multi-city mobile trip planning applications. - -* **The Netherlands (nationwide)** [Plannerstack Foundation](http://www.plannerstack.org/) provides - national scale trip planning APIs using OTP and other open source trip planners, based - on [OpenOV's extremely detailed open data](http://gtfs.openov.nl/) including minutely real-time - updates for every vehicle in the country. -* [OTP Android](https://play.google.com/store/apps/details?id=edu.usf.cutr.opentripplanner.android) - by CUTR-USF and Vreixo González can find itineraries on many different OTP servers via a service - discovery mechanism. -* [**ViviBus Bologna**](http://www.vivibus.it/) Bologna, Italy. +# OpenTripPlanner Deployments Worldwide + +## Official Production + +The following are known deployments of OTP in a government- or agency-sponsored production capacity: + +* **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 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/), + the [Finnish Transport Agency](https://opas.matka.fi/), and + other [Finnish cities](https://waltti.fi/?lang=en) have collaborated to + create [Digitransit](https://digitransit.fi/en/), providing OTP-based trip planners, APIs, open + data, Docker containers and open source code. Each member organisation runs its own instance of a + shared codebase and deployment environment. Their source code is + available [on Github](https://github.com/HSLdevcom/), including + a [new custom UI](https://github.com/HSLdevcom/digitransit-ui). This system also has a strong + real-time component. +* **Finland Intercity** The Finnish intercity coach + 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 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. + Their [Regional Trip Planner](http://ride.trimet.org) is based on OTP and provides about 40,000 + trip plans on a typical weekday. +* [**New York City**](https://new.mta.info/) +* **New York State** The State Department of + Transportation's [transit trip planner](https://511ny.org/#TransitRegion-1) provides itineraries + for public transit systems throughout the state in a single unified OTP instance. +* **Los Angeles, California** The new [metro.net trip planner](https://www.metro.net/). +* **Atlanta, Georgia** The Metropolitan Atlanta Rapid Transit Authority's ( + MARTA) [trip planner](http://itsmarta.com/planatrip.aspx) and the Atlanta region's transit + information hub [https://atlrides.com/](https://atlrides.com/) both use OTP to power their website trip + planners. +* **Boston, Massachusetts** + The [Massachusetts Bay Transportation Authority trip planner](https://www.mbta.com/trip-planner). +* **Seattle, Washington** The [Sound Transit Trip Planner](https://www.soundtransit.org/tripplanner) + is based on OTP. OTP also powers the trip planning feature of + the [OneBusAway native apps](http://onebusaway.org/) in the Puget Sound region. Technical details + are [here](https://github.com/OneBusAway/onebusaway-android/blob/master/SYSTEM_ARCHITECTURE.md#add-trip-planning-andor-bike-share-optional) + . +* [**Ride Metro Houston**](https://planyourtrip.ridemetro.org/) +* **Tampa, Florida** Hillsoborough Area Regional Transit uses an OpenTripPlanner server to power the + trip planning feature of the [OneBusAway native apps](http://onebusaway.org/) in their region. + Technical details + are [here](https://github.com/OneBusAway/onebusaway-android/blob/master/SYSTEM_ARCHITECTURE.md#add-trip-planning-andor-bike-share-optional) + . +* [**Piemonte Region, Italy**](https://map.muoversinpiemonte.it/#planner) and the [**City of + Torino**](https://www.muoversiatorino.it/) built on OpenTripPlanner + by [5T](http://www.5t.torino.it/). +* [**Valencia, Spain**](http://www.emtvalencia.es/geoportal/?lang=en_otp) from the Municipal + Transport Company of Valencia S.A.U. +* [**Grenoble, France**](http://www.metromobilite.fr/) from SMTC, Grenoble Alpes métropole, l'État + Français, the Rhône-alpes region, the Isère council and the City of Grenoble. +* **Rennes, France** where the STAR network provides an OTP client + for [iOS](https://itunes.apple.com/us/app/starbusmetro/id899970416?mt=8) + , [Android](https://play.google.com/store/apps/details?id=com.bookbeo.starbusmetro), Windows Phone + et Web. +* **Alençon, France** integrated urban and school bus + network [planner from Réunir Alençon](https://altobus.com/mon-itineraire/). +* [**Poznań, Poland**](http://ztm.poznan.pl/#planner) from Urban Transport Authority of Poznań (ZTM + Poznan). +* **Trento Province, Italy** + - [ViaggiaTrento](https://play.google.com/store/apps/details?id=eu.trentorise.smartcampus.viaggiatrento) + and [ViaggiaRovereto](https://play.google.com/store/apps/details?id=eu.trentorise.smartcampus.viaggiarovereto) + were implemented as part of the [SmartCampus Project](http://www.smartcampuslab.it), a research + project founded by [TrentoRise](http://trentorise.eu), [UNITN](http://www.unitn.it), + and [FBK](http://www.fbk.eu). +* **University of South Florida** (Tampa, Florida). The [USF Maps App](https://maps.usf.edu/) is a + responsive web application for that helps university students, staff, and visitors find their way + around the campus using multiple modes of transportation, including the USF Bull Runner campus + shuttle, Share-A-Bull bike share, and pedestrian pathways. + Open-sourced [on Github](https://github.com/CUTR-at-USF/usf-mobullity). +* **Lower Saxony, Germany** The [VBN](https://www.vbn.de/en/) transportation authority offers an [OTP instance](https://www.vbn.de/en/service/developer-information/opendata-and-openservice) as alternative to the [Hafas](https://www.hacon.de/en/portfolio/information-ticketing/#section_8294) passenger information system. +* **Leipzig, Germany** As of summer 2020 [Leipzig Move](https://leipzig-move.de/) has been using + OpenTripPlanner. + +## Independent Production + +The following OTP-based services are presented as production-quality deployments, but are not backed +by an official transportation authority or government. OTP is also known to be used on the back end +of several popular multi-city mobile trip planning applications. + +* **The Netherlands (nationwide)** [Plannerstack Foundation](http://www.plannerstack.org/) provides + national scale trip planning APIs using OTP and other open source trip planners, based + on [OpenOV's extremely detailed open data](http://gtfs.openov.nl/) including minutely real-time + updates for every vehicle in the country. +* [OTP Android](https://play.google.com/store/apps/details?id=edu.usf.cutr.opentripplanner.android) + by CUTR-USF and Vreixo González can find itineraries on many different OTP servers via a service + discovery mechanism. +* [**ViviBus Bologna**](http://www.vivibus.it/) Bologna, Italy. diff --git a/docs/Developers-Guide.md b/doc/user/Developers-Guide.md similarity index 94% rename from docs/Developers-Guide.md rename to doc/user/Developers-Guide.md index 8a68f83f301..368a12edb62 100644 --- a/docs/Developers-Guide.md +++ b/doc/user/Developers-Guide.md @@ -197,17 +197,16 @@ Please use only ISO 8601 date format (YYYY-MM-DD) in documentation, comments, an project. This avoids the ambiguity that can result from differing local interpretations of date formats like 02/01/12. -## Code style - -The OTP code style is described on a separate [style guide page](Codestyle.md). - ## Code conventions and architecture -The [architecture](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/ARCHITECTURE.md) -and [code conventions](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/CODE_CONVENTIONS.md) -are only available on GitHub, not in the project documentation. These documents contain relative -links to code so, they are a bit easier to maintain that way. The target audience is also active -OTP developers that have the code checked out locally. +The development and architecture documentation are only available on GitHub, not in the user project +documentation (https://www.opentripplanner.org/). These documents contain relative links to code, +so they are a bit easier to maintain that way. The primary audience is also active OTP developers +that have the code checked out locally. + + - [Architecture](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/ARCHITECTURE.md) + - [Code Conventions](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/CODE_CONVENTIONS.md) + - [Development Decision Records](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/DEVELOPMENT_DECISION_RECORDS.md) ## Continuous Integration @@ -220,7 +219,7 @@ compile and test the new code, providing feedback on the stability of the build. ### Changelog workflow -The [changelog file](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/docs/Changelog.md) +The [changelog file](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/doc/user/Changelog.md) is generated from the pull-request(PR) _title_ using the [changelog workflow](https://github.com/opentripplanner/OpenTripPlanner/actions/workflows/automatic-changelog.yml) . The workflow runs after the PR is merged, and it changes, commits and pushes the _Changelog.md_. A diff --git a/docs/Frontends.md b/doc/user/Frontends.md similarity index 100% rename from docs/Frontends.md rename to doc/user/Frontends.md diff --git a/docs/Getting-OTP.md b/doc/user/Getting-OTP.md similarity index 100% rename from docs/Getting-OTP.md rename to doc/user/Getting-OTP.md diff --git a/docs/Governance.md b/doc/user/Governance.md similarity index 100% rename from docs/Governance.md rename to doc/user/Governance.md diff --git a/docs/History.md b/doc/user/History.md similarity index 100% rename from docs/History.md rename to doc/user/History.md diff --git a/docs/In-Station-Navigation.md b/doc/user/In-Station-Navigation.md similarity index 100% rename from docs/In-Station-Navigation.md rename to doc/user/In-Station-Navigation.md diff --git a/docs/IslandPruning.md b/doc/user/IslandPruning.md similarity index 100% rename from docs/IslandPruning.md rename to doc/user/IslandPruning.md diff --git a/docs/Localization.md b/doc/user/Localization.md similarity index 100% rename from docs/Localization.md rename to doc/user/Localization.md diff --git a/docs/Logging.md b/doc/user/Logging.md similarity index 100% rename from docs/Logging.md rename to doc/user/Logging.md diff --git a/docs/Migrating-Configuration.md b/doc/user/Migrating-Configuration.md similarity index 100% rename from docs/Migrating-Configuration.md rename to doc/user/Migrating-Configuration.md diff --git a/docs/Netex-Norway.md b/doc/user/Netex-Norway.md similarity index 100% rename from docs/Netex-Norway.md rename to doc/user/Netex-Norway.md diff --git a/docs/Preparing-OSM.md b/doc/user/Preparing-OSM.md similarity index 100% rename from docs/Preparing-OSM.md rename to doc/user/Preparing-OSM.md diff --git a/docs/Presentations.md b/doc/user/Presentations.md similarity index 100% rename from docs/Presentations.md rename to doc/user/Presentations.md diff --git a/docs/Product-Overview.md b/doc/user/Product-Overview.md similarity index 100% rename from docs/Product-Overview.md rename to doc/user/Product-Overview.md diff --git a/docs/README-DOCS.txt b/doc/user/README-DOCS.txt similarity index 100% rename from docs/README-DOCS.txt rename to doc/user/README-DOCS.txt diff --git a/docs/ReleaseChecklist.md b/doc/user/ReleaseChecklist.md similarity index 96% rename from docs/ReleaseChecklist.md rename to doc/user/ReleaseChecklist.md index 0f7c0667762..c67849bc538 100644 --- a/docs/ReleaseChecklist.md +++ b/doc/user/ReleaseChecklist.md @@ -14,8 +14,8 @@ manually is more tedious, but keeps eyes on each step and is less prone to failu * Check all links and references to the release and update to the target release version. Search all files for with a regular expression: `2\.[012]\.0` and replace if appropriate with the new version. - * In `docs/index.md` replace what is the latest version and add a new line for the previous one -* Update `docs/Changelog.md` + * In `doc/user/index.md` replace what is the latest version and add a new line for the previous one +* Update `doc/user/Changelog.md` * Lines should have been added or updated as each pull request was merged * If you suspect any changes are not reflected in the Changelog, review the commit log and add any missing items @@ -78,9 +78,9 @@ manually is more tedious, but keeps eyes on each step and is less prone to failu * `git merge master` * `git push` * Set up next development iteration - * Add a new section header to `docs/Changelog.md` like `x.y+1.0-SNAPSHOT (in progress)` + * Add a new section header to `doc/user/Changelog.md` like `x.y+1.0-SNAPSHOT (in progress)` * Edit minor version in `pom.xml` to `x.y+1.0-SNAPSHOT` - * `git add pom.xml docs/Changelog.md` + * `git add pom.xml doc/user/Changelog.md` * `git commit -m "Prepare next development iteration x.y+1.0-SNAPSHOT"` * `git push` * Send a message in Gitter and email the OTP users mailing lists diff --git a/docs/Roadmap.md b/doc/user/Roadmap.md similarity index 100% rename from docs/Roadmap.md rename to doc/user/Roadmap.md diff --git a/docs/RouteRequest.md b/doc/user/RouteRequest.md similarity index 99% rename from docs/RouteRequest.md rename to doc/user/RouteRequest.md index a8baaf1603b..674ab238888 100644 --- a/docs/RouteRequest.md +++ b/doc/user/RouteRequest.md @@ -1,7 +1,7 @@ diff --git a/docs/RouterConfiguration.md b/doc/user/RouterConfiguration.md similarity index 99% rename from docs/RouterConfiguration.md rename to doc/user/RouterConfiguration.md index a3042e7b91f..4e565cfe17d 100644 --- a/docs/RouterConfiguration.md +++ b/doc/user/RouterConfiguration.md @@ -1,7 +1,7 @@ @@ -875,6 +875,12 @@ Used to group requests when monitoring OTP. "feedId" : "bikeep", "sourceType" : "bikeep", "url" : "https://services.bikeep.com/location/v1/public-areas/no-baia-mobility/locations" + }, + { + "type" : "vehicle-parking", + "feedId" : "parking", + "sourceType" : "siri-fm", + "url" : "https://transmodel.api.opendatahub.com/siri-lite/fm/parking" } ], "rideHailingServices" : [ diff --git a/docs/RoutingModes.md b/doc/user/RoutingModes.md similarity index 100% rename from docs/RoutingModes.md rename to doc/user/RoutingModes.md diff --git a/docs/SandboxExtension.md b/doc/user/SandboxExtension.md similarity index 97% rename from docs/SandboxExtension.md rename to doc/user/SandboxExtension.md index 0d05518fd56..70dec6f678d 100644 --- a/docs/SandboxExtension.md +++ b/doc/user/SandboxExtension.md @@ -30,7 +30,7 @@ added in the test directory: `src/ext-test` - To integrate the new feature into OTP you may have to create new extension points in the main/core code. Changes to the core OTP are subject to normal a review process. -- Create a readme file (`docs/sandbox/.md` package including: +- Create a readme file (`doc/user/sandbox/.md` package including: - Extension Name - Contact info - Change log diff --git a/docs/StopAreas.md b/doc/user/StopAreas.md similarity index 100% rename from docs/StopAreas.md rename to doc/user/StopAreas.md diff --git a/docs/System-Requirements.md b/doc/user/System-Requirements.md similarity index 100% rename from docs/System-Requirements.md rename to doc/user/System-Requirements.md diff --git a/docs/Troubleshooting-Routing.md b/doc/user/Troubleshooting-Routing.md similarity index 100% rename from docs/Troubleshooting-Routing.md rename to doc/user/Troubleshooting-Routing.md diff --git a/docs/UpdaterConfig.md b/doc/user/UpdaterConfig.md similarity index 99% rename from docs/UpdaterConfig.md rename to doc/user/UpdaterConfig.md index ebe0acf94b4..f3a0d982e68 100644 --- a/docs/UpdaterConfig.md +++ b/doc/user/UpdaterConfig.md @@ -1,7 +1,7 @@ diff --git a/docs/Version-Comparison.md b/doc/user/Version-Comparison.md similarity index 100% rename from docs/Version-Comparison.md rename to doc/user/Version-Comparison.md diff --git a/docs/Visual-Identity.md b/doc/user/Visual-Identity.md similarity index 100% rename from docs/Visual-Identity.md rename to doc/user/Visual-Identity.md diff --git a/docs/apis/Apis.md b/doc/user/apis/Apis.md similarity index 100% rename from docs/apis/Apis.md rename to doc/user/apis/Apis.md diff --git a/docs/apis/GTFS-GraphQL-API.md b/doc/user/apis/GTFS-GraphQL-API.md similarity index 100% rename from docs/apis/GTFS-GraphQL-API.md rename to doc/user/apis/GTFS-GraphQL-API.md diff --git a/docs/apis/GraphQL-Tutorial.md b/doc/user/apis/GraphQL-Tutorial.md similarity index 98% rename from docs/apis/GraphQL-Tutorial.md rename to doc/user/apis/GraphQL-Tutorial.md index bfc87813f1d..d65fbc144ba 100644 --- a/docs/apis/GraphQL-Tutorial.md +++ b/doc/user/apis/GraphQL-Tutorial.md @@ -1,7 +1,7 @@ diff --git a/docs/apis/TransmodelApi.md b/doc/user/apis/TransmodelApi.md similarity index 100% rename from docs/apis/TransmodelApi.md rename to doc/user/apis/TransmodelApi.md diff --git a/docs/examples/Readme.md b/doc/user/examples/Readme.md similarity index 100% rename from docs/examples/Readme.md rename to doc/user/examples/Readme.md diff --git a/docs/examples/entur/Readme.md b/doc/user/examples/entur/Readme.md similarity index 100% rename from docs/examples/entur/Readme.md rename to doc/user/examples/entur/Readme.md diff --git a/docs/examples/entur/build-config.json b/doc/user/examples/entur/build-config.json similarity index 100% rename from docs/examples/entur/build-config.json rename to doc/user/examples/entur/build-config.json diff --git a/docs/examples/entur/otp-config.json b/doc/user/examples/entur/otp-config.json similarity index 100% rename from docs/examples/entur/otp-config.json rename to doc/user/examples/entur/otp-config.json diff --git a/docs/examples/entur/router-config.json b/doc/user/examples/entur/router-config.json similarity index 100% rename from docs/examples/entur/router-config.json rename to doc/user/examples/entur/router-config.json diff --git a/docs/examples/ibi/atlanta/build-config.json b/doc/user/examples/ibi/atlanta/build-config.json similarity index 100% rename from docs/examples/ibi/atlanta/build-config.json rename to doc/user/examples/ibi/atlanta/build-config.json diff --git a/docs/examples/ibi/atlanta/otp-config.json b/doc/user/examples/ibi/atlanta/otp-config.json similarity index 100% rename from docs/examples/ibi/atlanta/otp-config.json rename to doc/user/examples/ibi/atlanta/otp-config.json diff --git a/docs/examples/ibi/atlanta/router-config.json b/doc/user/examples/ibi/atlanta/router-config.json similarity index 100% rename from docs/examples/ibi/atlanta/router-config.json rename to doc/user/examples/ibi/atlanta/router-config.json diff --git a/docs/examples/ibi/houston/build-config.json b/doc/user/examples/ibi/houston/build-config.json similarity index 100% rename from docs/examples/ibi/houston/build-config.json rename to doc/user/examples/ibi/houston/build-config.json diff --git a/docs/examples/ibi/portland/build-config.json b/doc/user/examples/ibi/portland/build-config.json similarity index 100% rename from docs/examples/ibi/portland/build-config.json rename to doc/user/examples/ibi/portland/build-config.json diff --git a/docs/examples/ibi/portland/otp-config.json b/doc/user/examples/ibi/portland/otp-config.json similarity index 100% rename from docs/examples/ibi/portland/otp-config.json rename to doc/user/examples/ibi/portland/otp-config.json diff --git a/docs/examples/ibi/portland/router-config.json b/doc/user/examples/ibi/portland/router-config.json similarity index 100% rename from docs/examples/ibi/portland/router-config.json rename to doc/user/examples/ibi/portland/router-config.json diff --git a/docs/examples/ibi/seattle/build-config.json b/doc/user/examples/ibi/seattle/build-config.json similarity index 100% rename from docs/examples/ibi/seattle/build-config.json rename to doc/user/examples/ibi/seattle/build-config.json diff --git a/docs/examples/ibi/seattle/router-config.json b/doc/user/examples/ibi/seattle/router-config.json similarity index 100% rename from docs/examples/ibi/seattle/router-config.json rename to doc/user/examples/ibi/seattle/router-config.json diff --git a/docs/examples/ibi/septa/router-config.json b/doc/user/examples/ibi/septa/router-config.json similarity index 100% rename from docs/examples/ibi/septa/router-config.json rename to doc/user/examples/ibi/septa/router-config.json diff --git a/docs/examples/skanetrafiken/Readme.md b/doc/user/examples/skanetrafiken/Readme.md similarity index 100% rename from docs/examples/skanetrafiken/Readme.md rename to doc/user/examples/skanetrafiken/Readme.md diff --git a/docs/examples/skanetrafiken/build-config.json b/doc/user/examples/skanetrafiken/build-config.json similarity index 100% rename from docs/examples/skanetrafiken/build-config.json rename to doc/user/examples/skanetrafiken/build-config.json diff --git a/docs/examples/skanetrafiken/oresund.json b/doc/user/examples/skanetrafiken/oresund.json similarity index 100% rename from docs/examples/skanetrafiken/oresund.json rename to doc/user/examples/skanetrafiken/oresund.json diff --git a/docs/examples/skanetrafiken/router-config.json b/doc/user/examples/skanetrafiken/router-config.json similarity index 100% rename from docs/examples/skanetrafiken/router-config.json rename to doc/user/examples/skanetrafiken/router-config.json diff --git a/docs/github_issue_linker.py b/doc/user/github_issue_linker.py similarity index 100% rename from docs/github_issue_linker.py rename to doc/user/github_issue_linker.py diff --git a/docs/images/badprojection.png b/doc/user/images/badprojection.png similarity index 100% rename from docs/images/badprojection.png rename to doc/user/images/badprojection.png diff --git a/docs/images/bicycle-safety-report.png b/doc/user/images/bicycle-safety-report.png similarity index 100% rename from docs/images/bicycle-safety-report.png rename to doc/user/images/bicycle-safety-report.png diff --git a/docs/images/boarding-schwabstrasse.png b/doc/user/images/boarding-schwabstrasse.png similarity index 100% rename from docs/images/boarding-schwabstrasse.png rename to doc/user/images/boarding-schwabstrasse.png diff --git a/docs/images/buckhead-station.png b/doc/user/images/buckhead-station.png similarity index 100% rename from docs/images/buckhead-station.png rename to doc/user/images/buckhead-station.png diff --git a/docs/images/cli-flow.svg b/doc/user/images/cli-flow.svg similarity index 100% rename from docs/images/cli-flow.svg rename to doc/user/images/cli-flow.svg diff --git a/docs/images/example-isochrone.png b/doc/user/images/example-isochrone.png similarity index 100% rename from docs/images/example-isochrone.png rename to doc/user/images/example-isochrone.png diff --git a/docs/images/exiting-oesterfeld.png b/doc/user/images/exiting-oesterfeld.png similarity index 100% rename from docs/images/exiting-oesterfeld.png rename to doc/user/images/exiting-oesterfeld.png diff --git a/docs/images/graphiql-autocomplete.png b/doc/user/images/graphiql-autocomplete.png similarity index 100% rename from docs/images/graphiql-autocomplete.png rename to doc/user/images/graphiql-autocomplete.png diff --git a/docs/images/graphiql-documentation.png b/doc/user/images/graphiql-documentation.png similarity index 100% rename from docs/images/graphiql-documentation.png rename to doc/user/images/graphiql-documentation.png diff --git a/docs/images/graphiql.png b/doc/user/images/graphiql.png similarity index 100% rename from docs/images/graphiql.png rename to doc/user/images/graphiql.png diff --git a/docs/images/nothruisland.png b/doc/user/images/nothruisland.png similarity index 100% rename from docs/images/nothruisland.png rename to doc/user/images/nothruisland.png diff --git a/docs/images/osmislands.png b/doc/user/images/osmislands.png similarity index 100% rename from docs/images/osmislands.png rename to doc/user/images/osmislands.png diff --git a/docs/images/otp-logo.svg b/doc/user/images/otp-logo.svg similarity index 87% rename from docs/images/otp-logo.svg rename to doc/user/images/otp-logo.svg index 1ed23d0be8e..6e32af842a6 100644 --- a/docs/images/otp-logo.svg +++ b/doc/user/images/otp-logo.svg @@ -1,7 +1,8 @@ - + sourceType **Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required` **Path:** /updaters/[2] -**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` +**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` | `siri-fm` The source of the vehicle updates. @@ -125,13 +125,13 @@ Used for converting abstract opening hours into concrete points in time. The id of the data source, which will be the prefix of the parking lot's id. -This will end up in the API responses as the feed id of of the parking lot. +This will end up in the API responses as the feed id of the parking lot.

sourceType

**Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required` **Path:** /updaters/[3] -**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` +**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` | `siri-fm` The source of the vehicle updates. @@ -210,13 +210,13 @@ Tags to add to the parking lots. The id of the data source, which will be the prefix of the parking lot's id. -This will end up in the API responses as the feed id of of the parking lot. +This will end up in the API responses as the feed id of the parking lot.

sourceType

**Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required` **Path:** /updaters/[4] -**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` +**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` | `siri-fm` The source of the vehicle updates. @@ -275,13 +275,13 @@ HTTP headers to add to the request. Any header key, value can be inserted. The id of the data source, which will be the prefix of the parking lot's id. -This will end up in the API responses as the feed id of of the parking lot. +This will end up in the API responses as the feed id of the parking lot.

sourceType

**Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required` **Path:** /updaters/[5] -**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` +**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` | `siri-fm` The source of the vehicle updates. @@ -336,13 +336,13 @@ HTTP headers to add to the request. Any header key, value can be inserted. The id of the data source, which will be the prefix of the parking lot's id. -This will end up in the API responses as the feed id of of the parking lot. +This will end up in the API responses as the feed id of the parking lot.

sourceType

**Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required` **Path:** /updaters/[14] -**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` +**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` | `siri-fm` The source of the vehicle updates. @@ -373,6 +373,87 @@ HTTP headers to add to the request. Any header key, value can be inserted. +## SIRI-FM + +The SIRI-FM updater works slightly differently from the others in that it only updates the availability +of parking but does not create new lots in realtime. + +The data source must conform to the [Italian SIRI-FM](https://github.com/5Tsrl/siri-italian-profile) profile +which requires SIRI 2.1. + + + + +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|----------------------------------|:---------------:|------------------------------------------------------------------------------|:----------:|---------------|:-----:| +| type = "vehicle-parking" | `enum` | The type of the updater. | *Required* | | 1.5 | +| [feedId](#u__15__feedId) | `string` | The id of the data source, which will be the prefix of the parking lot's id. | *Required* | | 2.2 | +| frequency | `duration` | How often to update the source. | *Optional* | `"PT1M"` | 2.6 | +| [sourceType](#u__15__sourceType) | `enum` | The source of the vehicle updates. | *Required* | | 2.2 | +| [url](#u__15__url) | `uri` | URL of the SIRI-FM Light endpoint. | *Required* | | 2.6 | +| [headers](#u__15__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.6 | + + +#### Details + +

feedId

+ +**Since version:** `2.2` ∙ **Type:** `string` ∙ **Cardinality:** `Required` +**Path:** /updaters/[15] + +The id of the data source, which will be the prefix of the parking lot's id. + +This will end up in the API responses as the feed id of the parking lot. + +

sourceType

+ +**Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required` +**Path:** /updaters/[15] +**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` | `siri-fm` + +The source of the vehicle updates. + +

url

+ +**Since version:** `2.6` ∙ **Type:** `uri` ∙ **Cardinality:** `Required` +**Path:** /updaters/[15] + +URL of the SIRI-FM Light endpoint. + +SIRI Light means that it must be available as a HTTP GET request rather than the usual +SIRI request mechanism of HTTP POST. + +The contents must also conform to the [Italian SIRI profile](https://github.com/5Tsrl/siri-italian-profile) +which requires SIRI 2.1. + + +

headers

+ +**Since version:** `2.6` ∙ **Type:** `map of string` ∙ **Cardinality:** `Optional` +**Path:** /updaters/[15] + +HTTP headers to add to the request. Any header key, value can be inserted. + + + +##### Example configuration + +```JSON +// router-config.json +{ + "updaters" : [ + { + "type" : "vehicle-parking", + "feedId" : "parking", + "sourceType" : "siri-fm", + "url" : "https://transmodel.api.opendatahub.com/siri-lite/fm/parking" + } + ] +} +``` + + + ## Changelog - Create initial sandbox implementation (January 2022, [#3796](https://github.com/opentripplanner/OpenTripPlanner/pull/3796)) diff --git a/docs/sandbox/VehicleRentalServiceDirectory.md b/doc/user/sandbox/VehicleRentalServiceDirectory.md similarity index 100% rename from docs/sandbox/VehicleRentalServiceDirectory.md rename to doc/user/sandbox/VehicleRentalServiceDirectory.md diff --git a/docs/sandbox/siri/SiriAzureUpdater.md b/doc/user/sandbox/siri/SiriAzureUpdater.md similarity index 100% rename from docs/sandbox/siri/SiriAzureUpdater.md rename to doc/user/sandbox/siri/SiriAzureUpdater.md diff --git a/docs/sandbox/siri/SiriGooglePubSubUpdater.md b/doc/user/sandbox/siri/SiriGooglePubSubUpdater.md similarity index 100% rename from docs/sandbox/siri/SiriGooglePubSubUpdater.md rename to doc/user/sandbox/siri/SiriGooglePubSubUpdater.md diff --git a/docs/sandbox/siri/SiriUpdater.md b/doc/user/sandbox/siri/SiriUpdater.md similarity index 100% rename from docs/sandbox/siri/SiriUpdater.md rename to doc/user/sandbox/siri/SiriUpdater.md diff --git a/docs/sandbox/transferanalyzer.md b/doc/user/sandbox/transferanalyzer.md similarity index 100% rename from docs/sandbox/transferanalyzer.md rename to doc/user/sandbox/transferanalyzer.md diff --git a/docs/images/TransitTimeLine.svg b/docs/images/TransitTimeLine.svg deleted file mode 100644 index 4924b2b3113..00000000000 --- a/docs/images/TransitTimeLine.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - Produced by OmniGraffle 6.6.2 2021-02-08 15:47:55 +0000Canvas 1Layer 1Bord-TimeLine 11Line 31Line 21Stop ABoard SlackBoard SlackAlight SlackAlight SlackAlight SlackTransit SlackTransit Slack«Extra Slack»«Extra Slack»Board SlackEarliest-Board-Time(Earliest-)Board-TimeStop BStop CStop DStop EStop-ArrivalStop-ArrivalStop-ArrivalStop-ArrivalStop-ArrivalOriginDestinationWait Time (Duration)Transit DurationWait Time (Duration)Transit DurationAlight-TimeAlight-Time diff --git a/mkdocs.yml b/mkdocs.yml index dbb9f86c877..8b3748be2b0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -6,7 +6,7 @@ site_name: OpenTripPlanner 2 site_url: https://docs.opentripplanner.org repo_url: https://github.com/opentripplanner/OpenTripPlanner -docs_dir: docs +docs_dir: doc/user site_dir: target/mkdocs strict: true @@ -96,7 +96,6 @@ nav: - "Developers' Guide": 'Developers-Guide.md' - Localization: 'Localization.md' - Bibliography: 'Bibliography.md' - - Codestyle: 'Codestyle.md' - Sandbox Development: 'SandboxExtension.md' - Release Checklist: 'ReleaseChecklist.md' - Sandbox: diff --git a/pom.xml b/pom.xml index db6167578cd..398432f7303 100644 --- a/pom.xml +++ b/pom.xml @@ -59,15 +59,15 @@ 156 31.3 - 2.51.1 + 2.52 2.17.2 - 3.1.7 - 5.10.3 + 3.1.8 + 5.11.0 1.13.2 5.6.0 - 1.5.6 + 1.5.7 9.11.1 - 2.0.13 + 2.0.16 2.0.15 1.27 4.0.5 @@ -176,12 +176,12 @@ - + package-javadoc package jar - ${basedir}/docs/javadoc + ${basedir}/doc/javadoc javadoc @@ -553,7 +553,7 @@ com.google.cloud libraries-bom - 26.40.0 + 26.44.0 pom import @@ -584,7 +584,7 @@ net.logstash.logback logstash-logback-encoder - 7.4 + 8.0 @@ -701,7 +701,7 @@ com.google.truth truth - 1.4.2 + 1.4.4 test @@ -873,7 +873,7 @@ com.graphql-java graphql-java - 22.1 + 22.2 com.graphql-java @@ -937,7 +937,7 @@ org.apache.commons commons-compress - 1.26.2 + 1.27.0 test @@ -1020,7 +1020,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.2.4 + 3.2.5 sign-artifacts diff --git a/renovate.json5 b/renovate.json5 index 98ec1f24fbe..b672797d48f 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -34,7 +34,7 @@ "matchFiles": ["client/package.json"], "matchUpdateTypes": ["patch", "minor"], "groupName": "Debug UI dependencies (non-major)", - "schedule": ["on the first day of the week"], + "schedule": ["after 6pm on the 3rd and 17th day of the month"], "reviewers": ["testower"] }, { @@ -45,6 +45,8 @@ // some dependencies that we auto-merge release very often and even the auto-merges create a lot of // noise, so we slow it down a bit { + "description": "Automerge test dependencies in a single PR", + "groupName": "Test dependencies", "matchPackageNames": [ "org.mockito:mockito-core", "com.tngtech.archunit:archunit", @@ -57,7 +59,6 @@ "matchPackagePrefixes": [ "org.junit.jupiter:", ], - "groupName": "Test dependencies", "automerge": true, "schedule": "on the 17th day of the month" }, @@ -70,12 +71,21 @@ "automerge": true }, { + "description": "Automerge Maven plugins in a single PR", + "groupName": "Maven plugins", "matchPackageNames": [ - "ch.qos.logback:logback-classic", "io.github.git-commit-id:git-commit-id-maven-plugin", - "org.apache.maven.plugins:maven-gpg-plugin" + "org.apache.maven.plugins:maven-gpg-plugin", + "org.codehaus.mojo:build-helper-maven-plugin", + "org.apache.maven.plugins:maven-source-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-compiler-plugin", + "org.apache.maven.plugins:maven-jar-plugin", + "org.sonatype.plugins:nexus-staging-maven-plugin" ], - "schedule": "on the 19th day of the month", + "schedule": "on the 23rd day of the month", "automerge": true }, { @@ -92,18 +102,17 @@ "org.onebusaway:onebusaway-gtfs", "com.google.cloud:libraries-bom", "com.google.guava:guava", - "@graphql-codegen/add", - "@graphql-codegen/cli", - "@graphql-codegen/java", - "@graphql-codegen/java-resolvers", - "graphql", "io.micrometer:micrometer-registry-prometheus", "io.micrometer:micrometer-registry-influx" ], - // we don't use the 'monthly' preset because that only fires on the first day of the month - // when there might already other PRs open "schedule": "on the 7th through 8th day of the month" }, + { + "groupName": "Update GTFS API code generation in a single PR", + "matchFiles": ["src/main/java/org/opentripplanner/apis/gtfs/generated/package.json"], + "reviewers": ["optionsome", "leonardehrenfried"], + "schedule": "on the 11th through 12th day of the month" + }, { "description": "in order to keep review burden low, don't update these quite so frequently", "matchPackagePrefixes": [ @@ -114,6 +123,7 @@ ] }, { + "groupName": "mkdocs", "description": "automerge mkdocs-material every quarter", "matchPackageNames": [ "mkdocs", @@ -125,23 +135,14 @@ "automerge": true }, { - "description": "automatically merge test, logging and build dependencies", - "matchPackageNames": [ - // maven plugins - "org.codehaus.mojo:build-helper-maven-plugin", - "org.apache.maven.plugins:maven-source-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-compiler-plugin", - "org.apache.maven.plugins:maven-jar-plugin", - "org.sonatype.plugins:nexus-staging-maven-plugin" - ], + "description": "Automerge logging dependencies in a single PR", + "groupName": "logging dependencies", "matchPackagePrefixes": [ - "org.slf4j:" + "org.slf4j:", + "ch.qos.logback:" ], "automerge": true, - "schedule": "after 11pm and before 5am every weekday" + "schedule": "on the 4th day of the month" }, { "description": "give some projects time to publish a changelog before opening the PR", diff --git a/src/client/index.html b/src/client/index.html index d1734898ca0..aa609bb0696 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -5,8 +5,8 @@ OTP Debug Client - - + +
diff --git a/src/ext-test/java/org/opentripplanner/ext/accessibilityscore/DecorateWithAccessibilityScoreTest.java b/src/ext-test/java/org/opentripplanner/ext/accessibilityscore/DecorateWithAccessibilityScoreTest.java index d70b9f32f62..27c10c08129 100644 --- a/src/ext-test/java/org/opentripplanner/ext/accessibilityscore/DecorateWithAccessibilityScoreTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/accessibilityscore/DecorateWithAccessibilityScoreTest.java @@ -2,20 +2,26 @@ 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.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; import java.util.List; +import java.util.function.Function; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Place; import org.opentripplanner.model.plan.PlanTestConstants; +import org.opentripplanner.model.plan.TestItineraryBuilder; import org.opentripplanner.routing.api.request.preference.WheelchairPreferences; -public class DecorateWithAccessibilityScoreTest implements PlanTestConstants { +class DecorateWithAccessibilityScoreTest implements PlanTestConstants { private static final int ID = 1; + private static final DecorateWithAccessibilityScore DECORATOR = new DecorateWithAccessibilityScore( + WheelchairPreferences.DEFAULT.maxSlope() + ); static List accessibilityScoreTestCase() { return List.of( @@ -48,17 +54,28 @@ static List accessibilityScoreTestCase() { @ParameterizedTest @MethodSource("accessibilityScoreTestCase") - public void accessibilityScoreTest(Itinerary itinerary, float expectedAccessibilityScore) { - var filter = new DecorateWithAccessibilityScore(WheelchairPreferences.DEFAULT.maxSlope()); - - filter.decorate(itinerary); + void accessibilityScoreTest(Itinerary itinerary, float expectedAccessibilityScore) { + DECORATOR.decorate(itinerary); assertEquals(expectedAccessibilityScore, itinerary.getAccessibilityScore()); - itinerary - .getLegs() - .forEach(l -> { - assertNotNull(l.accessibilityScore()); - }); + itinerary.getLegs().forEach(l -> assertNotNull(l.accessibilityScore())); + } + + private static List> nonWalkingCases() { + return List.of( + b -> b.bicycle(10, 20, B), + b -> b.drive(10, 20, B), + b -> b.rentedBicycle(10, 20, B) + ); + } + + @MethodSource("nonWalkingCases") + @ParameterizedTest + void noScoreForNonWalking(Function modifier) { + var itinerary = modifier.apply(newItinerary(A, 0)).build(); + DECORATOR.decorate(itinerary); + assertNull(itinerary.getAccessibilityScore()); + itinerary.getLegs().forEach(l -> assertNull(l.accessibilityScore())); } } diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java index 615ef90cbbd..2c352e0f760 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java @@ -5,17 +5,17 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; class EnglishNgramAnalyzerTest { @Test - void ngram() throws IOException { - var analyzer = new EnglishNGramAnalyzer(); - List result = analyze("Alexanderplatz", analyzer); + void ngram() { + List result = tokenize("Alexanderplatz"); //System.out.println(result.stream().collect(Collectors.joining("\",\"", "\"", "\""))); assertEquals( @@ -82,14 +82,79 @@ void ngram() throws IOException { ); } - public List analyze(String text, Analyzer analyzer) throws IOException { - List result = new ArrayList<>(); - TokenStream tokenStream = analyzer.tokenStream("name", text); - CharTermAttribute attr = tokenStream.addAttribute(CharTermAttribute.class); - tokenStream.reset(); - while (tokenStream.incrementToken()) { - result.add(attr.toString()); + @Test + void ampersand() { + List result = tokenize("Meridian Ave N & N 148th St"); + + assertEquals( + List.of( + "Meri", + "Merid", + "Meridi", + "Meridia", + "Meridian", + "erid", + "eridi", + "eridia", + "eridian", + "ridi", + "ridia", + "ridian", + "idia", + "idian", + "dian", + "Av", + "N", + "N", + "148", + "St" + ), + result + ); + } + + @ParameterizedTest + @CsvSource( + value = { + "1st:1", + "2nd:2", + "3rd:3", + "4th:4", + "6th:6", + "148th:148", + "102nd:102", + "1003rd:1003", + "St:St", + "S3:S3", + "Aard:Aard", + }, + delimiter = ':' + ) + void numberSuffixes(String input, String expected) { + var result = tokenize(input); + assertEquals(List.of(expected), result); + } + + @Test + void wordBoundary() { + var result = tokenize("1stst"); + assertEquals(List.of("1sts", "1stst", "stst"), result); + } + + private List tokenize(String text) { + try (var analyzer = new EnglishNGramAnalyzer()) { + List result; + TokenStream tokenStream; + result = new ArrayList<>(); + tokenStream = analyzer.tokenStream("name", text); + CharTermAttribute attr = tokenStream.addAttribute(CharTermAttribute.class); + tokenStream.reset(); + while (tokenStream.incrementToken()) { + result.add(attr.toString()); + } + return result; + } catch (IOException e) { + throw new RuntimeException(e); } - return result; } } diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index 3e6c2b15195..de6e600037c 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -12,7 +12,7 @@ import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; -import javax.annotation.Nonnull; +import java.util.stream.Stream; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -98,6 +98,10 @@ class LuceneIndexTest { .withCoordinate(52.52277, 13.41046) .build(); + static final RegularStop MERIDIAN_AVE = TEST_MODEL.stop("Meridian Ave N & N 148th St").build(); + static final RegularStop MERIDIAN_N1 = TEST_MODEL.stop("Meridian N & Spencer").build(); + static final RegularStop MERIDIAN_N2 = TEST_MODEL.stop("N 205th St & Meridian Ave N").build(); + static LuceneIndex index; static StopClusterMapper mapper; @@ -113,7 +117,10 @@ static void setup() { LICHTERFELDE_OST_2, WESTHAFEN, ARTS_CENTER, - ARTHUR + ARTHUR, + MERIDIAN_N1, + MERIDIAN_N2, + MERIDIAN_AVE ) .forEach(stopModel::withRegularStop); List @@ -295,9 +302,32 @@ void agenciesAndFeedPublisher() { assertEquals(List.of(StopClusterMapper.toAgency(BVG)), cluster.primary().agencies()); assertEquals("A Publisher", cluster.primary().feedPublisher().name()); } + + @ParameterizedTest + @ValueSource( + strings = { + "Meridian Ave N & N 148th", + "Meridian Ave N & N 148", + "Meridian Ave N N 148", + "Meridian Ave N 148", + "Meridian & 148 N", + "148 N & Meridian", + "Meridian & N 148", + "Meridian Ave 148", + "Meridian Av 148", + "meridian av 148", + } + ) + void numericAdjectives(String query) { + var names = index.queryStopClusters(query).map(c -> c.primary().name()).toList(); + assertEquals( + Stream.of(MERIDIAN_AVE, MERIDIAN_N2, MERIDIAN_N1).map(s -> s.getName().toString()).toList(), + names + ); + } } - private static @Nonnull Function primaryId() { + private static Function primaryId() { return c -> c.primary().id(); } } diff --git a/src/ext-test/java/org/opentripplanner/ext/vectortiles/VectorTilesConfigDocTest.java b/src/ext-test/java/org/opentripplanner/ext/vectortiles/VectorTilesConfigDocTest.java index 233e3fa3737..51e1738ff6c 100644 --- a/src/ext-test/java/org/opentripplanner/ext/vectortiles/VectorTilesConfigDocTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/vectortiles/VectorTilesConfigDocTest.java @@ -4,8 +4,8 @@ import static org.opentripplanner.framework.io.FileUtils.readFile; import static org.opentripplanner.framework.io.FileUtils.writeFile; import static org.opentripplanner.framework.text.MarkdownFormatter.HEADER_4; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_PATH; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.USER_DOC_PATH; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceSection; import static org.opentripplanner.standalone.config.framework.json.JsonSupport.jsonNodeFromPath; @@ -24,8 +24,8 @@ public class VectorTilesConfigDocTest { private static final String DOCUMENT = "sandbox/MapboxVectorTilesApi.md"; - private static final File TEMPLATE = new File(TEMPLATE_ROOT, DOCUMENT); - private static final File OUT_FILE = new File(DOCS_ROOT, DOCUMENT); + private static final File TEMPLATE = new File(TEMPLATE_PATH, DOCUMENT); + private static final File OUT_FILE = new File(USER_DOC_PATH, DOCUMENT); private static final SkipNodes SKIP_NODES = SkipNodes.of().build(); @Test diff --git a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehiclerental/mapper/VehicleRentalLayerTest.java b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehiclerental/mapper/VehicleRentalLayerTest.java index 34a8f28f9a4..984d4927041 100644 --- a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehiclerental/mapper/VehicleRentalLayerTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehiclerental/mapper/VehicleRentalLayerTest.java @@ -38,6 +38,7 @@ public void floatingVehicle() { assertEquals("A:B", map.get("id")); assertEquals("BICYCLE", map.get("formFactor")); assertEquals("A", map.get("network")); + assertEquals(true, map.get("pickupAllowed")); assertNull(map.get("name")); } diff --git a/src/ext-test/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterTest.java b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterTest.java new file mode 100644 index 00000000000..07502b597d9 --- /dev/null +++ b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterTest.java @@ -0,0 +1,28 @@ +package org.opentripplanner.ext.vehicleparking.sirifm; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.Duration; +import org.junit.jupiter.api.Test; +import org.opentripplanner.test.support.ResourceLoader; +import org.opentripplanner.updater.spi.HttpHeaders; + +class SiriFmUpdaterTest { + + @Test + void parse() { + var uri = ResourceLoader.of(this).uri("siri-fm.xml"); + var parameters = new SiriFmUpdaterParameters( + "noi", + uri, + "noi", + Duration.ofSeconds(30), + HttpHeaders.empty() + ); + var updater = new SiriFmDatasource(parameters); + updater.update(); + var updates = updater.getUpdates(); + + assertEquals(4, updates.size()); + } +} diff --git a/src/ext-test/java/org/opentripplanner/ext/vehiclerentalservicedirectory/generatedoc/VehicleRentalServiceDirectoryConfigDocTest.java b/src/ext-test/java/org/opentripplanner/ext/vehiclerentalservicedirectory/generatedoc/VehicleRentalServiceDirectoryConfigDocTest.java index 4936bb4dd44..2afcd95949c 100644 --- a/src/ext-test/java/org/opentripplanner/ext/vehiclerentalservicedirectory/generatedoc/VehicleRentalServiceDirectoryConfigDocTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/vehiclerentalservicedirectory/generatedoc/VehicleRentalServiceDirectoryConfigDocTest.java @@ -5,8 +5,8 @@ import static org.opentripplanner.framework.io.FileUtils.readFile; import static org.opentripplanner.framework.io.FileUtils.writeFile; import static org.opentripplanner.framework.text.MarkdownFormatter.HEADER_4; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_PATH; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.USER_DOC_PATH; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceJsonExample; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceParametersDetails; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceParametersTable; @@ -26,8 +26,8 @@ public class VehicleRentalServiceDirectoryConfigDocTest { private static final String DOCUMENT = "sandbox/VehicleRentalServiceDirectory.md"; - private static final File TEMPLATE = new File(TEMPLATE_ROOT, DOCUMENT); - private static final File OUT_FILE = new File(DOCS_ROOT, DOCUMENT); + private static final File TEMPLATE = new File(TEMPLATE_PATH, DOCUMENT); + private static final File OUT_FILE = new File(USER_DOC_PATH, DOCUMENT); private static final String CONFIG_PATH = "org/opentripplanner/ext/vehiclerentalservicedirectory/generatedoc/" + ROUTER_CONFIG_FILENAME; private static final String CONFIG_TAG = "vehicleRentalServiceDirectory"; diff --git a/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/sirifm/siri-fm.xml b/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/sirifm/siri-fm.xml new file mode 100644 index 00000000000..f4595488284 --- /dev/null +++ b/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/sirifm/siri-fm.xml @@ -0,0 +1,56 @@ + + + 2024-07-17T11:07:40Z + RAP Alto Adige - Open Data Hub + + 2024-07-17T11:07:40Z + RAP Alto Adige - Open Data Hub + + IT:ITH10:Parking:105 + + available + + + presentCount + bays + 33 + + + + IT:ITH10:Parking:TRENTO_areaexsitviacanestrinip1 + + notAvailable + + + presentCount + bays + 300 + + + + IT:ITH10:Parking:TRENTO_autosilobuonconsigliop3 + + notAvailable + + + presentCount + bays + 633 + + + + IT:ITH10:Parking:TRENTO_cteviabomportop6 + + notAvailable + + + presentCount + bays + 250 + + + + + \ No newline at end of file diff --git a/src/ext/java/org/opentripplanner/ext/accessibilityscore/DecorateWithAccessibilityScore.java b/src/ext/java/org/opentripplanner/ext/accessibilityscore/DecorateWithAccessibilityScore.java index 51a138b9f9c..d95f2c4c0cd 100644 --- a/src/ext/java/org/opentripplanner/ext/accessibilityscore/DecorateWithAccessibilityScore.java +++ b/src/ext/java/org/opentripplanner/ext/accessibilityscore/DecorateWithAccessibilityScore.java @@ -116,7 +116,7 @@ private Itinerary addAccessibilityScore(Itinerary i) { .map(leg -> { if (leg instanceof ScheduledTransitLeg transitLeg) { return transitLeg.withAccessibilityScore(compute(transitLeg)); - } else if (leg instanceof StreetLeg streetLeg) { + } else if (leg instanceof StreetLeg streetLeg && leg.isWalkingLeg()) { return streetLeg.withAccessibilityScore(compute(streetLeg)); } else { return leg; diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java b/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java index ffe46604744..17bf529a559 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java @@ -1,14 +1,16 @@ package org.opentripplanner.ext.geocoder; +import java.util.regex.Pattern; import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.LowerCaseFilter; import org.apache.lucene.analysis.TokenStream; -import org.apache.lucene.analysis.core.LowerCaseFilter; import org.apache.lucene.analysis.core.StopFilter; import org.apache.lucene.analysis.en.EnglishAnalyzer; import org.apache.lucene.analysis.en.EnglishPossessiveFilter; import org.apache.lucene.analysis.en.PorterStemFilter; import org.apache.lucene.analysis.miscellaneous.CapitalizationFilter; import org.apache.lucene.analysis.ngram.NGramTokenFilter; +import org.apache.lucene.analysis.pattern.PatternReplaceFilter; import org.apache.lucene.analysis.standard.StandardTokenizer; /** @@ -17,14 +19,21 @@ * of a stop name can be matched efficiently. *

* For example the query of "exanderpl" will match the stop name "Alexanderplatz". + *

+ * It also removes number suffixes in the American street names, like "147th Street", which will + * be tokenized to "147 Street". */ class EnglishNGramAnalyzer extends Analyzer { + // Matches one or more numbers followed by the English suffixes "st", "nd", "rd", "th" + private static final Pattern NUMBER_SUFFIX_PATTERN = Pattern.compile("(\\d+)(st|nd|rd|th)\\b"); + @Override protected TokenStreamComponents createComponents(String fieldName) { StandardTokenizer src = new StandardTokenizer(); TokenStream result = new EnglishPossessiveFilter(src); result = new LowerCaseFilter(result); + result = new PatternReplaceFilter(result, NUMBER_SUFFIX_PATTERN, "$1", true); result = new StopFilter(result, EnglishAnalyzer.ENGLISH_STOP_WORDS_SET); result = new PorterStemFilter(result); result = new CapitalizationFilter(result); diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java index fe7bef8ad13..71b80ac58a6 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java @@ -288,7 +288,7 @@ private Stream matchingDocuments( } }); } else { - var nameParser = new QueryParser(NAME, analyzer); + var nameParser = new QueryParser(NAME_NGRAM, analyzer); var nameQuery = nameParser.parse(searchTerms); var ngramNameQuery = new TermQuery( diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/vehiclerental/mapper/DigitransitRentalVehiclePropertyMapper.java b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/vehiclerental/mapper/DigitransitRentalVehiclePropertyMapper.java index 33e661866bc..00ae4e5ed88 100644 --- a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/vehiclerental/mapper/DigitransitRentalVehiclePropertyMapper.java +++ b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/vehiclerental/mapper/DigitransitRentalVehiclePropertyMapper.java @@ -15,6 +15,7 @@ protected Collection map(VehicleRentalVehicle place) { var items = new ArrayList(); items.addAll(getFeedScopedIdAndNetwork(place)); items.add(new KeyValue("formFactor", place.vehicleType.formFactor.toString())); + items.add(new KeyValue("pickupAllowed", place.isAllowPickup())); return items; } } diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterParameters.java index be937ecdd5e..86e03731dd8 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterParameters.java @@ -1,5 +1,7 @@ package org.opentripplanner.ext.vehicleparking.bikeep; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.FULL; + import java.net.URI; import java.time.Duration; import org.opentripplanner.updater.spi.HttpHeaders; @@ -22,4 +24,9 @@ public record BikeepUpdaterParameters( public VehicleParkingSourceType sourceType() { return VehicleParkingSourceType.BIKEEP; } + + @Override + public UpdateType updateType() { + return FULL; + } } diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterParameters.java index 26e40f4ec4a..73f26a43aa4 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterParameters.java @@ -1,5 +1,7 @@ package org.opentripplanner.ext.vehicleparking.bikely; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.FULL; + import java.net.URI; import java.time.Duration; import org.opentripplanner.updater.spi.HttpHeaders; @@ -22,4 +24,9 @@ public record BikelyUpdaterParameters( public VehicleParkingSourceType sourceType() { return VehicleParkingSourceType.BIKELY; } + + @Override + public UpdateType updateType() { + return FULL; + } } diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdaterParameters.java index 4b75d38ea6f..b4ad24ae080 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdaterParameters.java @@ -1,5 +1,7 @@ package org.opentripplanner.ext.vehicleparking.hslpark; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.FULL; + import java.time.Duration; import java.time.ZoneId; import org.opentripplanner.updater.vehicle_parking.VehicleParkingSourceType; @@ -25,4 +27,9 @@ public record HslParkUpdaterParameters( public Duration frequency() { return Duration.ofSeconds(utilizationsFrequencySec); } + + @Override + public UpdateType updateType() { + return FULL; + } } diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/noi/NoiUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/noi/NoiUpdaterParameters.java index 371ffdc9f33..b34769c9dd2 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/noi/NoiUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/noi/NoiUpdaterParameters.java @@ -1,5 +1,7 @@ package org.opentripplanner.ext.vehicleparking.noi; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.FULL; + import java.net.URI; import java.time.Duration; import org.opentripplanner.updater.spi.HttpHeaders; @@ -22,4 +24,9 @@ public record NoiUpdaterParameters( public VehicleParkingSourceType sourceType() { return VehicleParkingSourceType.NOI_OPEN_DATA_HUB; } + + @Override + public UpdateType updateType() { + return FULL; + } } diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdaterParameters.java index 263987c60d0..3014d932e08 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdaterParameters.java @@ -1,5 +1,7 @@ package org.opentripplanner.ext.vehicleparking.parkapi; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.FULL; + import java.time.Duration; import java.time.ZoneId; import java.util.List; @@ -21,4 +23,9 @@ public record ParkAPIUpdaterParameters( VehicleParkingSourceType sourceType, ZoneId timeZone ) - implements VehicleParkingUpdaterParameters {} + implements VehicleParkingUpdaterParameters { + @Override + public UpdateType updateType() { + return FULL; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmDatasource.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmDatasource.java new file mode 100644 index 00000000000..abfdf4d29be --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmDatasource.java @@ -0,0 +1,90 @@ +package org.opentripplanner.ext.vehicleparking.sirifm; + +import static uk.org.siri.siri21.CountingTypeEnumeration.PRESENT_COUNT; + +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import org.entur.siri21.util.SiriXml; +import org.opentripplanner.framework.io.OtpHttpClient; +import org.opentripplanner.framework.io.OtpHttpClientFactory; +import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.updater.spi.DataSource; +import org.opentripplanner.updater.spi.HttpHeaders; +import org.opentripplanner.updater.vehicle_parking.AvailabiltyUpdate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import uk.org.siri.siri21.FacilityConditionStructure; + +/** + * Parses SIRI 2.1 XML data into parking availability updates. The data needs to conform to the + * Italian profile of SIRI-FM. + */ +public class SiriFmDatasource implements DataSource { + + private static final Logger LOG = LoggerFactory.getLogger(SiriFmDatasource.class); + private final SiriFmUpdaterParameters params; + private final OtpHttpClient httpClient; + private final Map headers; + private List updates = List.of(); + + public SiriFmDatasource(SiriFmUpdaterParameters parameters) { + params = parameters; + headers = HttpHeaders.of().acceptApplicationXML().add(parameters.httpHeaders()).build().asMap(); + httpClient = new OtpHttpClientFactory().create(LOG); + } + + @Override + public String toString() { + return ToStringBuilder + .of(this.getClass()) + .addStr("url", this.params.url().toString()) + .toString(); + } + + @Override + public boolean update() { + updates = + httpClient.getAndMap( + params.url(), + headers, + resp -> { + var siri = SiriXml.parseXml(resp); + + return Stream + .ofNullable(siri.getServiceDelivery()) + .flatMap(sd -> sd.getFacilityMonitoringDeliveries().stream()) + .flatMap(d -> d.getFacilityConditions().stream()) + .filter(this::conformsToItalianProfile) + .map(this::mapToUpdate) + .toList(); + } + ); + return true; + } + + private AvailabiltyUpdate mapToUpdate(FacilityConditionStructure c) { + var id = new FeedScopedId(params.feedId(), c.getFacilityRef().getValue()); + var available = c.getMonitoredCountings().getFirst().getCount().intValue(); + return new AvailabiltyUpdate(id, available); + } + + /** + * Checks if the {@link FacilityConditionStructure} contains all the necessary information that + * are required by the Italian Siri-FM profile. + */ + private boolean conformsToItalianProfile(FacilityConditionStructure c) { + return ( + c.getFacilityRef() != null && + c.getFacilityRef().getValue() != null && + c.getMonitoredCountings().size() == 1 && + c.getMonitoredCountings().getFirst().getCountingType() == PRESENT_COUNT + ); + } + + @Override + public List getUpdates() { + return updates; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterParameters.java new file mode 100644 index 00000000000..5afdffb7067 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterParameters.java @@ -0,0 +1,34 @@ +package org.opentripplanner.ext.vehicleparking.sirifm; + +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingSourceType.SIRI_FM; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.AVAILABILITY_ONLY; + +import java.net.URI; +import java.time.Duration; +import org.opentripplanner.ext.vehicleparking.noi.NoiUpdater; +import org.opentripplanner.updater.spi.HttpHeaders; +import org.opentripplanner.updater.vehicle_parking.VehicleParkingSourceType; +import org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters; + +/** + * Class that extends {@link VehicleParkingUpdaterParameters} with parameters required by {@link + * NoiUpdater}. + */ +public record SiriFmUpdaterParameters( + String configRef, + URI url, + String feedId, + Duration frequency, + HttpHeaders httpHeaders +) + implements VehicleParkingUpdaterParameters { + @Override + public VehicleParkingSourceType sourceType() { + return SIRI_FM; + } + + @Override + public UpdateType updateType() { + return AVAILABILITY_ONLY; + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json b/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json index 6a840640ca9..d6cbf02d5e7 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json @@ -10,10 +10,10 @@ }, "license": "LGPL-3.0", "dependencies": { - "@graphql-codegen/add": "5.0.2", + "@graphql-codegen/add": "5.0.3", "@graphql-codegen/cli": "5.0.2", "@graphql-codegen/java": "4.0.1", "@graphql-codegen/java-resolvers": "3.0.0", - "graphql": "16.8.1" + "graphql": "16.9.0" } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock b/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock index 77829ecc911..25686abd94a 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock @@ -699,7 +699,15 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" -"@graphql-codegen/add@5.0.2", "@graphql-codegen/add@^5.0.2": +"@graphql-codegen/add@5.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@graphql-codegen/add/-/add-5.0.3.tgz#1ede6bac9a93661ed7fa5808b203d079e1b1d215" + integrity sha512-SxXPmramkth8XtBlAHu4H4jYcYXM/o3p01+psU+0NADQowA8jtYkK6MW5rV6T+CxkEaNZItfSmZRPgIuypcqnA== + dependencies: + "@graphql-codegen/plugin-helpers" "^5.0.3" + tslib "~2.6.0" + +"@graphql-codegen/add@^5.0.2": version "5.0.2" resolved "https://registry.yarnpkg.com/@graphql-codegen/add/-/add-5.0.2.tgz#71b3ae0465a4537172dddb84531b6967ca5545f2" integrity sha512-ouBkSvMFUhda5VoKumo/ZvsZM9P5ZTyDsI8LW18VxSNWOjrTeLXBWHG8Gfaai0HwhflPtCYVABbriEcOmrRShQ== @@ -2173,10 +2181,10 @@ graphql-ws@^5.14.0: resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-5.14.0.tgz#766f249f3974fc2c48fae0d1fb20c2c4c79cd591" integrity sha512-itrUTQZP/TgswR4GSSYuwWUzrE/w5GhbwM2GX3ic2U7aw33jgEsayfIlvaj7/GcIvZgNMzsPTrE5hqPuFUiE5g== -graphql@16.8.1: - version "16.8.1" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" - integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== +graphql@16.9.0: + version "16.9.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.9.0.tgz#1c310e63f16a49ce1fbb230bd0a000e99f6f115f" + integrity sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw== has-flag@^3.0.0: version "3.0.0" diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransitLayer.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransitLayer.java index c92bbd750ad..e5f0544f584 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransitLayer.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransitLayer.java @@ -1,7 +1,6 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit; import java.time.LocalDate; -import java.time.ZoneId; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -51,8 +50,6 @@ public class TransitLayer { private final StopModel stopModel; - private final ZoneId transitDataZoneId; - private final RaptorRequestTransferCache transferCache; private ConstrainedTransfersForPatterns constrainedTransfers; @@ -73,7 +70,6 @@ public TransitLayer(TransitLayer transitLayer) { transitLayer.transfersByStopIndex, transitLayer.transferService, transitLayer.stopModel, - transitLayer.transitDataZoneId, transitLayer.transferCache, transitLayer.constrainedTransfers, transitLayer.transferIndexGenerator, @@ -86,7 +82,6 @@ public TransitLayer( List> transfersByStopIndex, TransferService transferService, StopModel stopModel, - ZoneId transitDataZoneId, RaptorRequestTransferCache transferCache, ConstrainedTransfersForPatterns constrainedTransfers, TransferIndexGenerator transferIndexGenerator, @@ -96,7 +91,6 @@ public TransitLayer( this.transfersByStopIndex = transfersByStopIndex; this.transferService = transferService; this.stopModel = stopModel; - this.transitDataZoneId = transitDataZoneId; this.transferCache = transferCache; this.constrainedTransfers = constrainedTransfers; this.transferIndexGenerator = transferIndexGenerator; @@ -117,16 +111,6 @@ public Collection getTripPatternsForRunningDate(LocalDate da return tripPatternsRunningOnDate.getOrDefault(date, List.of()); } - /** - * This is the time zone which is used for interpreting all local "service" times (in transfers, - * trip schedules and so on). This is the time zone of the internal OTP time - which is used in - * logging and debugging. This is independent of the time zone of imported data and of the time - * zone used on any API - it can be the same, but it does not need to. - */ - public ZoneId getTransitDataZoneId() { - return transitDataZoneId; - } - public int getStopCount() { return stopModel.stopIndexSize(); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java index 8ce328fe1b6..d375b4c546c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java @@ -103,7 +103,6 @@ private TransitLayer map(TransitTuningParameters tuningParameters) { transferByStopIndex, transitService.getTransferService(), stopModel, - transitService.getTimeZone(), transferCache, constrainedTransfers, transferIndexGenerator, 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 d5dfc4ce8c1..098af909296 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java @@ -96,8 +96,10 @@ public class VehicleParking implements Serializable { private final List entrances = new ArrayList<>(); /** * The currently available spaces at this vehicle parking. + *

+ * The volatile keyword is used to ensure safe publication by clearing CPU caches. */ - private VehicleParkingSpaces availability; + private volatile VehicleParkingSpaces availability; /** * The vehicle parking group this parking belongs to. */ @@ -239,19 +241,24 @@ public boolean hasRealTimeDataForMode( return false; } - switch (traverseMode) { - case BICYCLE: - return availability.getBicycleSpaces() != null; - case CAR: + return switch (traverseMode) { + case BICYCLE -> availability.getBicycleSpaces() != null; + case CAR -> { var places = wheelchairAccessibleCarPlaces ? availability.getWheelchairAccessibleCarSpaces() : availability.getCarSpaces(); - return places != null; - default: - return false; - } + yield places != null; + } + default -> false; + }; } + /** + * The only mutable method in this class: it allows to update the available parking spaces during + * real-time updates. + * Since the entity is used both by writer threads (real-time updates) and reader threads + * (A* routing), the variable holding the information is marked as volatile. + */ public void updateAvailability(VehicleParkingSpaces vehicleParkingSpaces) { this.availability = vehicleParkingSpaces; } diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingService.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingService.java index 639066871be..b0c08a2309b 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingService.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingService.java @@ -21,13 +21,17 @@ public class VehicleParkingService implements Serializable { /** * To ensure that his is thread-safe, the set stored here should always be immutable. + *

+ * The volatile keyword is used to ensure safe publication by clearing CPU caches. */ - private Set vehicleParkings = Set.of(); + private volatile Set vehicleParkings = Set.of(); /** * To ensure that his is thread-safe, {@link ImmutableListMultimap} is used. + *

+ * The volatile keyword is used to ensure safe publication by clearing CPU caches. */ - private ImmutableListMultimap vehicleParkingGroups = ImmutableListMultimap.of(); + private volatile ImmutableListMultimap vehicleParkingGroups = ImmutableListMultimap.of(); /** * Does atomic update of {@link VehicleParking} and index of {@link VehicleParkingGroup} in this diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java index 07fa48fa84f..0058cdd9e15 100644 --- a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java @@ -8,7 +8,6 @@ import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nonnull; import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; @@ -33,8 +32,16 @@ public DefaultRealtimeVehicleService(TransitService transitService) { this.transitService = transitService; } + /** + * Stores the relationship between a list of realtime vehicles with a pattern. If the pattern is + * a realtime-added one, then the original (scheduled) one is used as the key for the map storing + * the information. + */ @Override public void setRealtimeVehicles(TripPattern pattern, List updates) { + if (pattern.getOriginalTripPattern() != null) { + pattern = pattern.getOriginalTripPattern(); + } vehicles.put(pattern, List.copyOf(updates)); } @@ -43,8 +50,18 @@ public void clearRealtimeVehicles(TripPattern pattern) { vehicles.remove(pattern); } + /** + * Gets the realtime vehicles for a given pattern. If the pattern is a realtime-added one + * then the original (scheduled) one is used for the lookup instead, so you receive the correct + * result no matter if you use the realtime or static information. + * + * @see DefaultRealtimeVehicleService#setRealtimeVehicles(TripPattern, List) + */ @Override public List getRealtimeVehicles(@Nonnull TripPattern pattern) { + if (pattern.getOriginalTripPattern() != null) { + pattern = pattern.getOriginalTripPattern(); + } // the list is made immutable during insertion, so we can safely return them return vehicles.getOrDefault(pattern, List.of()); } diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java index c687899009f..88b47aac4eb 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java @@ -13,6 +13,7 @@ import org.opentripplanner.ext.vehicleparking.hslpark.HslParkUpdaterParameters; import org.opentripplanner.ext.vehicleparking.noi.NoiUpdaterParameters; import org.opentripplanner.ext.vehicleparking.parkapi.ParkAPIUpdaterParameters; +import org.opentripplanner.ext.vehicleparking.sirifm.SiriFmUpdaterParameters; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; import org.opentripplanner.updater.vehicle_parking.VehicleParkingSourceType; import org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters; @@ -29,7 +30,7 @@ public static VehicleParkingUpdaterParameters create(String updaterRef, NodeAdap .of("feedId") .since(V2_2) .summary("The id of the data source, which will be the prefix of the parking lot's id.") - .description("This will end up in the API responses as the feed id of of the parking lot.") + .description("This will end up in the API responses as the feed id of the parking lot.") .asString(); return switch (sourceType) { case HSL_PARK -> new HslParkUpdaterParameters( @@ -100,6 +101,30 @@ public static VehicleParkingUpdaterParameters create(String updaterRef, NodeAdap .asDuration(Duration.ofMinutes(1)), HttpHeadersConfig.headers(c, V2_6) ); + case SIRI_FM -> new SiriFmUpdaterParameters( + updaterRef, + c + .of("url") + .since(V2_6) + .summary("URL of the SIRI-FM Light endpoint.") + .description( + """ + SIRI Light means that it must be available as a HTTP GET request rather than the usual + SIRI request mechanism of HTTP POST. + + The contents must also conform to the [Italian SIRI profile](https://github.com/5Tsrl/siri-italian-profile) + which requires SIRI 2.1. + """ + ) + .asUri(), + feedId, + c + .of("frequency") + .since(V2_6) + .summary("How often to update the source.") + .asDuration(Duration.ofMinutes(1)), + HttpHeadersConfig.headers(c, V2_6) + ); }; } diff --git a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java index 103120b7ecb..83e0bd0fe85 100644 --- a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java +++ b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java @@ -25,13 +25,13 @@ import org.opentripplanner.updater.trip.MqttGtfsRealtimeUpdater; import org.opentripplanner.updater.trip.PollingTripUpdater; import org.opentripplanner.updater.trip.TimetableSnapshotSource; +import org.opentripplanner.updater.vehicle_parking.AvailabilityDatasourceFactory; +import org.opentripplanner.updater.vehicle_parking.VehicleParkingAvailabilityUpdater; import org.opentripplanner.updater.vehicle_parking.VehicleParkingDataSourceFactory; import org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdater; import org.opentripplanner.updater.vehicle_position.PollingVehiclePositionUpdater; import org.opentripplanner.updater.vehicle_rental.VehicleRentalUpdater; import org.opentripplanner.updater.vehicle_rental.datasources.VehicleRentalDataSourceFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Sets up and starts all the graph updaters. @@ -42,8 +42,6 @@ */ public class UpdaterConfigurator { - private static final Logger LOG = LoggerFactory.getLogger(UpdaterConfigurator.class); - private final Graph graph; private final TransitModel transitModel; private final UpdatersParameters updatersParameters; @@ -187,15 +185,32 @@ private List createUpdatersFromConfig() { ); } for (var configItem : updatersParameters.getVehicleParkingUpdaterParameters()) { - var source = VehicleParkingDataSourceFactory.create(configItem, openingHoursCalendarService); - updaters.add( - new VehicleParkingUpdater( - configItem, - source, - graph.getLinker(), - graph.getVehicleParkingService() - ) - ); + switch (configItem.updateType()) { + case FULL -> { + var source = VehicleParkingDataSourceFactory.create( + configItem, + openingHoursCalendarService + ); + updaters.add( + new VehicleParkingUpdater( + configItem, + source, + graph.getLinker(), + graph.getVehicleParkingService() + ) + ); + } + case AVAILABILITY_ONLY -> { + var source = AvailabilityDatasourceFactory.create(configItem); + updaters.add( + new VehicleParkingAvailabilityUpdater( + configItem, + source, + graph.getVehicleParkingService() + ) + ); + } + } } for (var configItem : updatersParameters.getSiriAzureETUpdaterParameters()) { updaters.add( diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabilityDatasourceFactory.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabilityDatasourceFactory.java new file mode 100644 index 00000000000..876cd303c78 --- /dev/null +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabilityDatasourceFactory.java @@ -0,0 +1,23 @@ +package org.opentripplanner.updater.vehicle_parking; + +import org.opentripplanner.ext.vehicleparking.sirifm.SiriFmDatasource; +import org.opentripplanner.ext.vehicleparking.sirifm.SiriFmUpdaterParameters; +import org.opentripplanner.updater.spi.DataSource; + +/** + * Class that can be used to return a custom vehicle parking {@link DataSource}. + */ +public class AvailabilityDatasourceFactory { + + public static DataSource create(VehicleParkingUpdaterParameters parameters) { + return switch (parameters.sourceType()) { + case SIRI_FM -> new SiriFmDatasource((SiriFmUpdaterParameters) parameters); + case PARK_API, + BICYCLE_PARK_API, + HSL_PARK, + BIKEEP, + NOI_OPEN_DATA_HUB, + BIKELY -> throw new IllegalArgumentException("Cannot instantiate SIRI-FM data source"); + }; + } +} diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java new file mode 100644 index 00000000000..a9d352cbaf2 --- /dev/null +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java @@ -0,0 +1,12 @@ +package org.opentripplanner.updater.vehicle_parking; + +import java.util.Objects; +import org.opentripplanner.framework.lang.IntUtils; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +public record AvailabiltyUpdate(FeedScopedId vehicleParkingId, int spacesAvailable) { + public AvailabiltyUpdate { + Objects.requireNonNull(vehicleParkingId); + IntUtils.requireNotNegative(spacesAvailable); + } +} diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java new file mode 100644 index 00000000000..31074aafe38 --- /dev/null +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java @@ -0,0 +1,104 @@ +package org.opentripplanner.updater.vehicle_parking; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.routing.graph.Graph; +import org.opentripplanner.routing.vehicle_parking.VehicleParking; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.updater.GraphWriterRunnable; +import org.opentripplanner.updater.spi.DataSource; +import org.opentripplanner.updater.spi.PollingGraphUpdater; +import org.opentripplanner.updater.spi.WriteToGraphCallback; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Graph updater that dynamically sets availability information on vehicle parking lots. This + * updater fetches data from a single {@link DataSource}. + */ +public class VehicleParkingAvailabilityUpdater extends PollingGraphUpdater { + + private static final Logger LOG = LoggerFactory.getLogger( + VehicleParkingAvailabilityUpdater.class + ); + private final DataSource source; + private WriteToGraphCallback saveResultOnGraph; + + private final VehicleParkingService vehicleParkingService; + + public VehicleParkingAvailabilityUpdater( + VehicleParkingUpdaterParameters parameters, + DataSource source, + VehicleParkingService vehicleParkingService + ) { + super(parameters); + this.source = source; + this.vehicleParkingService = vehicleParkingService; + + LOG.info("Creating vehicle-parking updater running every {}: {}", pollingPeriod(), source); + } + + @Override + public void setup(WriteToGraphCallback writeToGraphCallback) { + this.saveResultOnGraph = writeToGraphCallback; + } + + @Override + protected void runPolling() { + if (source.update()) { + var updates = source.getUpdates(); + + var graphWriterRunnable = new AvailabilityUpdater(updates); + saveResultOnGraph.execute(graphWriterRunnable); + } + } + + private class AvailabilityUpdater implements GraphWriterRunnable { + + private final List updates; + private final Map parkingById; + + private AvailabilityUpdater(List updates) { + this.updates = List.copyOf(updates); + this.parkingById = + vehicleParkingService + .getVehicleParkings() + .collect(Collectors.toUnmodifiableMap(VehicleParking::getId, Function.identity())); + } + + @Override + public void run(Graph graph, TransitModel ignored) { + updates.forEach(this::handleUpdate); + } + + private void handleUpdate(AvailabiltyUpdate update) { + if (!parkingById.containsKey(update.vehicleParkingId())) { + LOG.warn( + "Parking with id {} does not exist. Skipping availability update.", + update.vehicleParkingId() + ); + } else { + var parking = parkingById.get(update.vehicleParkingId()); + var builder = VehicleParkingSpaces.builder(); + if (parking.hasCarPlaces()) { + builder.carSpaces(update.spacesAvailable()); + } + if (parking.hasBicyclePlaces()) { + builder.bicycleSpaces(update.spacesAvailable()); + } + parking.updateAvailability(builder.build()); + } + } + } + + @Override + public String toString() { + return ToStringBuilder.of(this.getClass()).addObj("source", source).toString(); + } +} diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingDataSourceFactory.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingDataSourceFactory.java index a956fda0d87..09ecb67a54d 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingDataSourceFactory.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingDataSourceFactory.java @@ -42,6 +42,7 @@ public static DataSource create( case BIKELY -> new BikelyUpdater((BikelyUpdaterParameters) parameters); case NOI_OPEN_DATA_HUB -> new NoiUpdater((NoiUpdaterParameters) parameters); case BIKEEP -> new BikeepUpdater((BikeepUpdaterParameters) parameters); + case SIRI_FM -> throw new IllegalArgumentException("Cannot instantiate SIRI-FM data source"); }; } } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingSourceType.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingSourceType.java index f6a28177d8e..1601245b16c 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingSourceType.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingSourceType.java @@ -7,4 +7,5 @@ public enum VehicleParkingSourceType { BIKELY, NOI_OPEN_DATA_HUB, BIKEEP, + SIRI_FM, } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java index 3422ec3e300..bff0022383f 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java @@ -8,4 +8,10 @@ */ public interface VehicleParkingUpdaterParameters extends PollingGraphUpdaterParameters { VehicleParkingSourceType sourceType(); + UpdateType updateType(); + + enum UpdateType { + FULL, + AVAILABILITY_ONLY, + } } diff --git a/src/test/java/org/opentripplanner/generate/doc/BuildConfigurationDocTest.java b/src/test/java/org/opentripplanner/generate/doc/BuildConfigurationDocTest.java index 4009a455abe..8189f18f20e 100644 --- a/src/test/java/org/opentripplanner/generate/doc/BuildConfigurationDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/BuildConfigurationDocTest.java @@ -4,8 +4,8 @@ import static org.opentripplanner.framework.io.FileUtils.readFile; import static org.opentripplanner.framework.io.FileUtils.writeFile; import static org.opentripplanner.framework.text.MarkdownFormatter.HEADER_3; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_PATH; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.USER_DOC_PATH; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceJsonExample; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceParametersDetails; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceParametersTable; @@ -25,8 +25,8 @@ public class BuildConfigurationDocTest { private static final String CONFIG_JSON = OtpFileNames.BUILD_CONFIG_FILENAME; - private static final File TEMPLATE = new File(TEMPLATE_ROOT, "BuildConfiguration.md"); - private static final File OUT_FILE = new File(DOCS_ROOT, "BuildConfiguration.md"); + private static final File TEMPLATE = new File(TEMPLATE_PATH, "BuildConfiguration.md"); + private static final File OUT_FILE = new File(USER_DOC_PATH, "BuildConfiguration.md"); private static final String CONFIG_PATH = "standalone/config/" + CONFIG_JSON; private static final SkipNodes SKIP_NODES = SkipNodes @@ -38,7 +38,7 @@ public class BuildConfigurationDocTest { .build(); /** - * NOTE! This test updates the {@code docs/Configuration.md} document based on the latest + * NOTE! This test updates the {@code doc/user/Configuration.md} document based on the latest * version of the code. The following is auto generated: *

    *
  • The configuration type table
  • diff --git a/src/test/java/org/opentripplanner/generate/doc/ConfigurationDocTest.java b/src/test/java/org/opentripplanner/generate/doc/ConfigurationDocTest.java index 19a562d8b3f..fed5ddb325d 100644 --- a/src/test/java/org/opentripplanner/generate/doc/ConfigurationDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/ConfigurationDocTest.java @@ -3,8 +3,8 @@ import static org.opentripplanner.framework.io.FileUtils.assertFileEquals; import static org.opentripplanner.framework.io.FileUtils.readFile; import static org.opentripplanner.framework.io.FileUtils.writeFile; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_PATH; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.USER_DOC_PATH; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceSection; import static org.opentripplanner.generate.doc.support.ConfigTypeTable.configTypeTable; import static org.opentripplanner.generate.doc.support.OTPFeatureTable.otpFeaturesTable; @@ -16,22 +16,22 @@ @GeneratesDocumentation public class ConfigurationDocTest { - private static final File TEMPLATE = new File(TEMPLATE_ROOT, "Configuration.md"); + private static final File TEMPLATE = new File(TEMPLATE_PATH, "Configuration.md"); - private static final File OUT_FILE = new File(DOCS_ROOT, "Configuration.md"); + private static final File OUT_FILE = new File(USER_DOC_PATH, "Configuration.md"); private static final String CONFIG_TYPE_PLACEHOLDER = "CONFIGURATION-TYPES-TABLE"; private static final String OTP_FEATURE_PLACEHOLDER = "OTP-FEATURE-TABLE"; /** - * NOTE! This test updates the {@code docs/Configuration.md} document based on the latest + * NOTE! This test updates the {@code doc/user/Configuration.md} document based on the latest * version of the code. The following is auto generated: *
      *
    • The configuration type table
    • *
    • The list of OTP features
    • *
    - * This test fails if the document have changed. This make sure that this test fails in the - * CI pipeline if config file changes is not committed. Manually inspect the changes in the + * This test fails if the document has changed. This makes sure that this test fails in the + * CI pipeline if config file changes are not committed. Manually inspect the changes in the * configuration, commit the configuration document, and run test again to pass. */ @Test diff --git a/src/test/java/org/opentripplanner/generate/doc/EmissionsConfigurationDocTest.java b/src/test/java/org/opentripplanner/generate/doc/EmissionsConfigurationDocTest.java index 68caa043e26..5010c78d527 100644 --- a/src/test/java/org/opentripplanner/generate/doc/EmissionsConfigurationDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/EmissionsConfigurationDocTest.java @@ -4,8 +4,8 @@ import static org.opentripplanner.framework.io.FileUtils.readFile; import static org.opentripplanner.framework.io.FileUtils.writeFile; import static org.opentripplanner.framework.text.MarkdownFormatter.HEADER_4; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_PATH; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.USER_DOC_PATH; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceSection; import static org.opentripplanner.standalone.config.framework.json.JsonSupport.jsonNodeFromResource; @@ -24,8 +24,8 @@ @GeneratesDocumentation public class EmissionsConfigurationDocTest { - private static final File TEMPLATE = new File(TEMPLATE_ROOT, "Emissions.md"); - private static final File OUT_FILE = new File(DOCS_ROOT + "/sandbox", "Emissions.md"); + private static final File TEMPLATE = new File(TEMPLATE_PATH, "Emissions.md"); + private static final File OUT_FILE = new File(USER_DOC_PATH + "/sandbox", "Emissions.md"); private static final String CONFIG_JSON = OtpFileNames.BUILD_CONFIG_FILENAME; private static final String CONFIG_PATH = "standalone/config/" + CONFIG_JSON; private static final SkipNodes SKIP_NODES = SkipNodes.of().build(); diff --git a/src/test/java/org/opentripplanner/generate/doc/FlexConfigurationDocTest.java b/src/test/java/org/opentripplanner/generate/doc/FlexConfigurationDocTest.java index fd2d7092dc5..e18090466b9 100644 --- a/src/test/java/org/opentripplanner/generate/doc/FlexConfigurationDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/FlexConfigurationDocTest.java @@ -4,8 +4,8 @@ import static org.opentripplanner.framework.io.FileUtils.readFile; import static org.opentripplanner.framework.io.FileUtils.writeFile; import static org.opentripplanner.framework.text.MarkdownFormatter.HEADER_4; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_PATH; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.USER_DOC_PATH; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceSection; import static org.opentripplanner.standalone.config.framework.json.JsonSupport.jsonNodeFromResource; @@ -23,8 +23,8 @@ @GeneratesDocumentation public class FlexConfigurationDocTest { - private static final File TEMPLATE = new File(TEMPLATE_ROOT, "Flex.md"); - private static final File OUT_FILE = new File(DOCS_ROOT + "/sandbox", "Flex.md"); + private static final File TEMPLATE = new File(TEMPLATE_PATH, "Flex.md"); + private static final File OUT_FILE = new File(USER_DOC_PATH + "/sandbox", "Flex.md"); private static final String ROUTER_CONFIG_FILENAME = "standalone/config/router-config.json"; private static final SkipNodes SKIP_NODES = SkipNodes.of().build(); diff --git a/src/test/java/org/opentripplanner/generate/doc/GraphQLTutorialDocTest.java b/src/test/java/org/opentripplanner/generate/doc/GraphQLTutorialDocTest.java index 3597844a027..7eb6a685cca 100644 --- a/src/test/java/org/opentripplanner/generate/doc/GraphQLTutorialDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/GraphQLTutorialDocTest.java @@ -4,8 +4,8 @@ import static org.opentripplanner.framework.io.FileUtils.assertFileEquals; import static org.opentripplanner.framework.io.FileUtils.readFile; import static org.opentripplanner.framework.io.FileUtils.writeFile; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_PATH; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.USER_DOC_PATH; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceSection; import com.google.common.io.Resources; @@ -20,12 +20,12 @@ @GeneratesDocumentation public class GraphQLTutorialDocTest { - private static final File TEMPLATE = new File(TEMPLATE_ROOT, "GraphQL-Tutorial.md"); + private static final File TEMPLATE = new File(TEMPLATE_PATH, "GraphQL-Tutorial.md"); - private static final File OUT_FILE = new File(DOCS_ROOT + "/apis", "GraphQL-Tutorial.md"); + private static final File OUT_FILE = new File(USER_DOC_PATH + "/apis", "GraphQL-Tutorial.md"); /** - * NOTE! This test updates the {@code docs/GraphQlTutorial.md} document based on the latest + * NOTE! This test updates the {@code doc/user/GraphQlTutorial.md} document based on the latest * version of the code. * This test fails if the document have changed. This make sure that this test fails in the * CI pipeline if config file changes is not committed. Manually inspect the changes in the diff --git a/src/test/java/org/opentripplanner/generate/doc/OsmMapperDocTest.java b/src/test/java/org/opentripplanner/generate/doc/OsmMapperDocTest.java index 3090c696e37..d3eb0a44122 100644 --- a/src/test/java/org/opentripplanner/generate/doc/OsmMapperDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/OsmMapperDocTest.java @@ -3,8 +3,8 @@ import static org.opentripplanner.framework.io.FileUtils.assertFileEquals; import static org.opentripplanner.framework.io.FileUtils.readFile; import static org.opentripplanner.framework.io.FileUtils.writeFile; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_PATH; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.USER_DOC_PATH; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceSection; import static org.opentripplanner.openstreetmap.tagmapping.OsmTagMapperSource.ATLANTA; import static org.opentripplanner.openstreetmap.tagmapping.OsmTagMapperSource.CONSTANT_SPEED_FINLAND; @@ -20,6 +20,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.framework.text.Table; import org.opentripplanner.framework.text.TableBuilder; +import org.opentripplanner.generate.doc.framework.DocsTestConstants; import org.opentripplanner.generate.doc.framework.GeneratesDocumentation; import org.opentripplanner.openstreetmap.tagmapping.OsmTagMapper; import org.opentripplanner.openstreetmap.tagmapping.OsmTagMapperSource; @@ -30,7 +31,7 @@ public class OsmMapperDocTest { private static final String FILE_NAME = "OsmMapper.md"; - private static final File TEMPLATE = new File(TEMPLATE_ROOT, FILE_NAME); + private static final File TEMPLATE = new File(TEMPLATE_PATH, FILE_NAME); private static final Set SKIP_MAPPERS = Set.of( ATLANTA, HOUSTON, @@ -70,7 +71,7 @@ public void updateDocs(OsmTagMapperSource source) { private static File outputFile(OsmTagMapper mapper) { var name = mapper.getClass().getSimpleName().replaceAll("Mapper", ".md"); - return new File("%s/osm/".formatted(DOCS_ROOT), name); + return new File("%s/osm/".formatted(USER_DOC_PATH), name); } private static Table propTable(WayPropertySet wps) { diff --git a/src/test/java/org/opentripplanner/generate/doc/RideHailingDocTest.java b/src/test/java/org/opentripplanner/generate/doc/RideHailingDocTest.java index 7ebd90260f1..2d04e002413 100644 --- a/src/test/java/org/opentripplanner/generate/doc/RideHailingDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/RideHailingDocTest.java @@ -5,8 +5,8 @@ import static org.opentripplanner.framework.io.FileUtils.readFile; import static org.opentripplanner.framework.io.FileUtils.writeFile; import static org.opentripplanner.framework.text.MarkdownFormatter.HEADER_4; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_PATH; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.USER_DOC_PATH; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceSection; import static org.opentripplanner.standalone.config.framework.json.JsonSupport.jsonNodeFromResource; @@ -24,8 +24,8 @@ @GeneratesDocumentation public class RideHailingDocTest { - private static final File TEMPLATE = new File(TEMPLATE_ROOT, "RideHailing.md"); - private static final File OUT_FILE = new File(DOCS_ROOT + "/sandbox", "RideHailing.md"); + private static final File TEMPLATE = new File(TEMPLATE_PATH, "RideHailing.md"); + private static final File OUT_FILE = new File(USER_DOC_PATH + "/sandbox", "RideHailing.md"); private static final String ROUTER_CONFIG_PATH = "standalone/config/" + ROUTER_CONFIG_FILENAME; private static final SkipNodes SKIP_NODES = SkipNodes.of().build(); diff --git a/src/test/java/org/opentripplanner/generate/doc/RouteRequestDocTest.java b/src/test/java/org/opentripplanner/generate/doc/RouteRequestDocTest.java index 76642db3e5a..b4725c5e2a1 100644 --- a/src/test/java/org/opentripplanner/generate/doc/RouteRequestDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/RouteRequestDocTest.java @@ -5,8 +5,8 @@ import static org.opentripplanner.framework.io.FileUtils.readFile; import static org.opentripplanner.framework.io.FileUtils.writeFile; import static org.opentripplanner.framework.text.MarkdownFormatter.HEADER_3; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_PATH; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.USER_DOC_PATH; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceJsonExample; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceParametersDetails; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceParametersTable; @@ -25,8 +25,8 @@ @GeneratesDocumentation public class RouteRequestDocTest { - private static final File TEMPLATE = new File(TEMPLATE_ROOT, "RouteRequest.md"); - private static final File OUT_FILE = new File(DOCS_ROOT, "RouteRequest.md"); + private static final File TEMPLATE = new File(TEMPLATE_PATH, "RouteRequest.md"); + private static final File OUT_FILE = new File(USER_DOC_PATH, "RouteRequest.md"); private static final String ROUTER_CONFIG_PATH = "standalone/config/" + ROUTER_CONFIG_FILENAME; private static final SkipNodes SKIP_NODES = SkipNodes .of() @@ -34,7 +34,7 @@ public class RouteRequestDocTest { .build(); /** - * NOTE! This test updates the {@code docs/RouteRequest.md} document based on the latest + * NOTE! This test updates the {@code doc/user/RouteRequest.md} document based on the latest * version of the code. The following is auto generated: *
      *
    • The configuration type table
    • diff --git a/src/test/java/org/opentripplanner/generate/doc/RouterConfigurationDocTest.java b/src/test/java/org/opentripplanner/generate/doc/RouterConfigurationDocTest.java index 90cdd9de975..77490c506fd 100644 --- a/src/test/java/org/opentripplanner/generate/doc/RouterConfigurationDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/RouterConfigurationDocTest.java @@ -5,8 +5,8 @@ import static org.opentripplanner.framework.io.FileUtils.readFile; import static org.opentripplanner.framework.io.FileUtils.writeFile; import static org.opentripplanner.framework.text.MarkdownFormatter.HEADER_3; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_PATH; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.USER_DOC_PATH; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceJsonExample; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceParametersDetails; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceParametersTable; @@ -24,8 +24,8 @@ @GeneratesDocumentation public class RouterConfigurationDocTest { - private static final File TEMPLATE = new File(TEMPLATE_ROOT, "RouterConfiguration.md"); - private static final File OUT_FILE = new File(DOCS_ROOT, "RouterConfiguration.md"); + private static final File TEMPLATE = new File(TEMPLATE_PATH, "RouterConfiguration.md"); + private static final File OUT_FILE = new File(USER_DOC_PATH, "RouterConfiguration.md"); private static final String CONFIG_PATH = "standalone/config/" + ROUTER_CONFIG_FILENAME; private static final SkipNodes SKIP_NODES = SkipNodes @@ -40,7 +40,7 @@ public class RouterConfigurationDocTest { .build(); /** - * NOTE! This test updates the {@code docs/Configuration.md} document based on the latest + * NOTE! This test updates the {@code doc/user/Configuration.md} document based on the latest * version of the code. The following is auto generated: *
        *
      • The configuration type table
      • diff --git a/src/test/java/org/opentripplanner/generate/doc/RoutingModeDocTest.java b/src/test/java/org/opentripplanner/generate/doc/RoutingModeDocTest.java index 0c6edc7e16b..188101e3b5d 100644 --- a/src/test/java/org/opentripplanner/generate/doc/RoutingModeDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/RoutingModeDocTest.java @@ -3,8 +3,8 @@ import static org.opentripplanner.framework.io.FileUtils.assertFileEquals; import static org.opentripplanner.framework.io.FileUtils.readFile; import static org.opentripplanner.framework.io.FileUtils.writeFile; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_PATH; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.USER_DOC_PATH; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceSection; import java.io.File; @@ -19,8 +19,8 @@ @GeneratesDocumentation public class RoutingModeDocTest { - private static final File TEMPLATE = new File(TEMPLATE_ROOT, "RoutingModes.md"); - private static final File OUT_FILE = new File(DOCS_ROOT, "RoutingModes.md"); + private static final File TEMPLATE = new File(TEMPLATE_PATH, "RoutingModes.md"); + private static final File OUT_FILE = new File(USER_DOC_PATH, "RoutingModes.md"); @Test public void updateDocs() { diff --git a/src/test/java/org/opentripplanner/generate/doc/SiriAzureConfigDocTest.java b/src/test/java/org/opentripplanner/generate/doc/SiriAzureConfigDocTest.java index 374ac2b4bbb..fcf93770f38 100644 --- a/src/test/java/org/opentripplanner/generate/doc/SiriAzureConfigDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/SiriAzureConfigDocTest.java @@ -5,8 +5,8 @@ import static org.opentripplanner.framework.io.FileUtils.readFile; import static org.opentripplanner.framework.io.FileUtils.writeFile; import static org.opentripplanner.framework.text.MarkdownFormatter.HEADER_4; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_PATH; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.USER_DOC_PATH; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceSection; import static org.opentripplanner.standalone.config.framework.json.JsonSupport.jsonNodeFromResource; @@ -25,8 +25,8 @@ @GeneratesDocumentation public class SiriAzureConfigDocTest { - private static final File TEMPLATE = new File(TEMPLATE_ROOT, "sandbox/siri/SiriAzureUpdater.md"); - private static final File OUT_FILE = new File(DOCS_ROOT, "sandbox/siri/SiriAzureUpdater.md"); + private static final File TEMPLATE = new File(TEMPLATE_PATH, "sandbox/siri/SiriAzureUpdater.md"); + private static final File OUT_FILE = new File(USER_DOC_PATH, "sandbox/siri/SiriAzureUpdater.md"); private static final String ROUTER_CONFIG_PATH = "standalone/config/" + ROUTER_CONFIG_FILENAME; private static final Set INCLUDE_UPDATERS = Set.of( @@ -37,7 +37,7 @@ public class SiriAzureConfigDocTest { public static final ObjectMapper mapper = new ObjectMapper(); /** - * NOTE! This test updates the {@code docs/sandbox/SiriUpdater.md} document based on the latest + * NOTE! This test updates the {@code doc/user/sandbox/SiriUpdater.md} document based on the latest * version of the code. */ @Test diff --git a/src/test/java/org/opentripplanner/generate/doc/SiriConfigDocTest.java b/src/test/java/org/opentripplanner/generate/doc/SiriConfigDocTest.java index 2474f37c402..71df3027c7d 100644 --- a/src/test/java/org/opentripplanner/generate/doc/SiriConfigDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/SiriConfigDocTest.java @@ -5,8 +5,8 @@ import static org.opentripplanner.framework.io.FileUtils.readFile; import static org.opentripplanner.framework.io.FileUtils.writeFile; import static org.opentripplanner.framework.text.MarkdownFormatter.HEADER_4; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_PATH; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.USER_DOC_PATH; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceSection; import static org.opentripplanner.standalone.config.framework.json.JsonSupport.jsonNodeFromResource; @@ -25,8 +25,8 @@ @GeneratesDocumentation public class SiriConfigDocTest { - private static final File TEMPLATE = new File(TEMPLATE_ROOT, "sandbox/siri/SiriUpdater.md"); - private static final File OUT_FILE = new File(DOCS_ROOT, "sandbox/siri/SiriUpdater.md"); + private static final File TEMPLATE = new File(TEMPLATE_PATH, "sandbox/siri/SiriUpdater.md"); + private static final File OUT_FILE = new File(USER_DOC_PATH, "sandbox/siri/SiriUpdater.md"); private static final String ROUTER_CONFIG_PATH = "standalone/config/" + ROUTER_CONFIG_FILENAME; private static final Set INCLUDE_UPDATERS = Set.of("siri-et-updater", "siri-sx-updater"); @@ -34,7 +34,7 @@ public class SiriConfigDocTest { public static final ObjectMapper mapper = new ObjectMapper(); /** - * NOTE! This test updates the {@code docs/sandbox/SiriUpdater.md} document based on the latest + * NOTE! This test updates the {@code doc/user/sandbox/SiriUpdater.md} document based on the latest * version of the code. */ @Test diff --git a/src/test/java/org/opentripplanner/generate/doc/SiriGooglePubSubConfigDocTest.java b/src/test/java/org/opentripplanner/generate/doc/SiriGooglePubSubConfigDocTest.java index f5aa3e130ed..ce414b298d0 100644 --- a/src/test/java/org/opentripplanner/generate/doc/SiriGooglePubSubConfigDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/SiriGooglePubSubConfigDocTest.java @@ -5,8 +5,8 @@ import static org.opentripplanner.framework.io.FileUtils.readFile; import static org.opentripplanner.framework.io.FileUtils.writeFile; import static org.opentripplanner.framework.text.MarkdownFormatter.HEADER_4; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_PATH; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.USER_DOC_PATH; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceSection; import static org.opentripplanner.standalone.config.framework.json.JsonSupport.jsonNodeFromResource; @@ -26,11 +26,11 @@ public class SiriGooglePubSubConfigDocTest { private static final File TEMPLATE = new File( - TEMPLATE_ROOT, + TEMPLATE_PATH, "sandbox/siri/SiriGooglePubSubUpdater.md" ); private static final File OUT_FILE = new File( - DOCS_ROOT, + USER_DOC_PATH, "sandbox/siri/SiriGooglePubSubUpdater.md" ); @@ -40,7 +40,7 @@ public class SiriGooglePubSubConfigDocTest { public static final ObjectMapper mapper = new ObjectMapper(); /** - * NOTE! This test updates the {@code docs/sandbox/SiriGooglePubSubUpdater.md} document based on the latest + * NOTE! This test updates the {@code doc/user/sandbox/SiriGooglePubSubUpdater.md} document based on the latest * version of the code. */ @Test diff --git a/src/test/java/org/opentripplanner/generate/doc/StopConsolidationDocTest.java b/src/test/java/org/opentripplanner/generate/doc/StopConsolidationDocTest.java index 62f76032a09..2861253aac7 100644 --- a/src/test/java/org/opentripplanner/generate/doc/StopConsolidationDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/StopConsolidationDocTest.java @@ -3,8 +3,8 @@ import static org.opentripplanner.framework.io.FileUtils.assertFileEquals; import static org.opentripplanner.framework.io.FileUtils.readFile; import static org.opentripplanner.framework.io.FileUtils.writeFile; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_PATH; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.USER_DOC_PATH; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceSection; import static org.opentripplanner.standalone.config.framework.json.JsonSupport.jsonNodeFromResource; @@ -21,8 +21,8 @@ public class StopConsolidationDocTest { private static final String FILE_NAME = "StopConsolidation.md"; - private static final File TEMPLATE = new File(TEMPLATE_ROOT, FILE_NAME); - private static final File OUT_FILE = new File(DOCS_ROOT + "/sandbox", FILE_NAME); + private static final File TEMPLATE = new File(TEMPLATE_PATH, FILE_NAME); + private static final File OUT_FILE = new File(USER_DOC_PATH + "/sandbox", FILE_NAME); private static final String CONFIG_FILENAME = "standalone/config/build-config.json"; diff --git a/src/test/java/org/opentripplanner/generate/doc/UpdaterConfigDocTest.java b/src/test/java/org/opentripplanner/generate/doc/UpdaterConfigDocTest.java index 264d0d6850a..7a3f2969c4d 100644 --- a/src/test/java/org/opentripplanner/generate/doc/UpdaterConfigDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/UpdaterConfigDocTest.java @@ -5,8 +5,8 @@ import static org.opentripplanner.framework.io.FileUtils.readFile; import static org.opentripplanner.framework.io.FileUtils.writeFile; import static org.opentripplanner.framework.text.MarkdownFormatter.HEADER_4; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_PATH; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.USER_DOC_PATH; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceSection; import static org.opentripplanner.standalone.config.framework.json.JsonSupport.jsonNodeFromResource; @@ -25,8 +25,8 @@ @GeneratesDocumentation public class UpdaterConfigDocTest { - private static final File TEMPLATE = new File(TEMPLATE_ROOT, "UpdaterConfig.md"); - private static final File OUT_FILE = new File(DOCS_ROOT, "UpdaterConfig.md"); + private static final File TEMPLATE = new File(TEMPLATE_PATH, "UpdaterConfig.md"); + private static final File OUT_FILE = new File(USER_DOC_PATH, "UpdaterConfig.md"); private static final String ROUTER_CONFIG_PATH = "standalone/config/" + ROUTER_CONFIG_FILENAME; private static final Set SKIP_UPDATERS = Set.of( @@ -41,7 +41,7 @@ public class UpdaterConfigDocTest { public static final ObjectMapper mapper = new ObjectMapper(); /** - * NOTE! This test updates the {@code docs/Configuration.md} document based on the latest + * NOTE! This test updates the {@code doc/user/Configuration.md} document based on the latest * version of the code. The following is auto generated: *
          *
        • The configuration type table
        • diff --git a/src/test/java/org/opentripplanner/generate/doc/VehicleParkingDocTest.java b/src/test/java/org/opentripplanner/generate/doc/VehicleParkingDocTest.java index abc9ceee806..caffafdf72b 100644 --- a/src/test/java/org/opentripplanner/generate/doc/VehicleParkingDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/VehicleParkingDocTest.java @@ -5,8 +5,8 @@ import static org.opentripplanner.framework.io.FileUtils.readFile; import static org.opentripplanner.framework.io.FileUtils.writeFile; import static org.opentripplanner.framework.text.MarkdownFormatter.HEADER_4; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; -import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_PATH; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.USER_DOC_PATH; import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceSection; import static org.opentripplanner.standalone.config.framework.json.JsonSupport.jsonNodeFromResource; @@ -23,8 +23,8 @@ @GeneratesDocumentation public class VehicleParkingDocTest { - private static final File TEMPLATE = new File(TEMPLATE_ROOT, "VehicleParking.md"); - private static final File OUT_FILE = new File(DOCS_ROOT + "/sandbox", "VehicleParking.md"); + private static final File TEMPLATE = new File(TEMPLATE_PATH, "VehicleParking.md"); + private static final File OUT_FILE = new File(USER_DOC_PATH + "/sandbox", "VehicleParking.md"); private static final String ROUTER_CONFIG_PATH = "standalone/config/" + ROUTER_CONFIG_FILENAME; private static final SkipNodes SKIP_NODES = SkipNodes.of().build(); diff --git a/src/test/java/org/opentripplanner/generate/doc/framework/DocsTestConstants.java b/src/test/java/org/opentripplanner/generate/doc/framework/DocsTestConstants.java index d7a67a3590b..a7f7afa806a 100644 --- a/src/test/java/org/opentripplanner/generate/doc/framework/DocsTestConstants.java +++ b/src/test/java/org/opentripplanner/generate/doc/framework/DocsTestConstants.java @@ -9,8 +9,9 @@ */ public interface DocsTestConstants { Logger LOG = LoggerFactory.getLogger(DocsTestConstants.class); - File TEMPLATE_ROOT = new File("doc-templates"); - File DOCS_ROOT = new File("docs"); + File DOC_ROOT = new File("doc"); + File TEMPLATE_PATH = new File(DOC_ROOT, "templates"); + File USER_DOC_PATH = new File(DOC_ROOT, "user"); /** * This method return {@code true} if the /docs directory is available. If not, a warning is @@ -18,14 +19,14 @@ public interface DocsTestConstants { * annotation. */ static boolean docsExistOrWarn() { - if (DOCS_ROOT.exists()) { + if (USER_DOC_PATH.exists()) { return true; } LOG.warn( """ SKIP TEST - '/docs' NOT FOUND - The docs/doc-templates directory might not be available if you run the tests outside the + The doc/templates directory might not be available if you run the tests outside the root of the projects. This may happen if the project root is not the working directory, if you run tests using jar files or in a Maven multi-module project. 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 5da127de2ba..8f5d1a0208e 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java @@ -237,7 +237,6 @@ private static TransitLayer getTransitLayer() { null, null, null, - null, null ); } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransitLayerTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransitLayerTest.java index 7c674252e6a..58a56ccb96f 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransitLayerTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransitLayerTest.java @@ -65,7 +65,6 @@ void testGetTripPatternsRunningOnDateCopy() { null, null, null, - null, null ); var runningOnDate = transitLayer.getTripPatternsRunningOnDateCopy(date); @@ -95,7 +94,6 @@ void testGetTripPatternsForRunningDate() { null, null, null, - null, null ); var runningOnDate = transitLayer.getTripPatternsForRunningDate(date); @@ -124,7 +122,6 @@ void testGetTripPatternsOnServiceDateCopyWithSameRunningAndServiceDate() { null, null, null, - null, null ); var startingOnDate = transitLayer.getTripPatternsOnServiceDateCopy(date); @@ -153,7 +150,6 @@ void testGetTripPatternsOnServiceDateCopyWithServiceRunningAfterMidnight() { null, null, null, - null, null ); var startingOnDate = transitLayer.getTripPatternsOnServiceDateCopy(serviceDate); @@ -187,7 +183,6 @@ void testGetTripPatternsOnServiceDateCopyWithServiceRunningBeforeAndAfterMidnigh null, null, null, - null, null ); var startingOnDate = transitLayer.getTripPatternsOnServiceDateCopy(firstRunningDate); diff --git a/src/test/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleServiceTest.java b/src/test/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleServiceTest.java new file mode 100644 index 00000000000..a1c9dddaab0 --- /dev/null +++ b/src/test/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleServiceTest.java @@ -0,0 +1,55 @@ +package org.opentripplanner.service.realtimevehicles.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.framework.geometry.WgsCoordinate.GREENWICH; +import static org.opentripplanner.transit.model._data.TransitModelForTest.route; +import static org.opentripplanner.transit.model._data.TransitModelForTest.tripPattern; + +import java.time.Instant; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; +import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.network.Route; +import org.opentripplanner.transit.model.network.StopPattern; +import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.service.DefaultTransitService; +import org.opentripplanner.transit.service.TransitModel; + +class DefaultRealtimeVehicleServiceTest { + + private static final Route ROUTE = route("r1").build(); + private static final TransitModelForTest MODEL = TransitModelForTest.of(); + private static final StopPattern STOP_PATTERN = TransitModelForTest.stopPattern( + MODEL.stop("1").build(), + MODEL.stop("2").build() + ); + private static final TripPattern ORIGINAL = tripPattern("original", ROUTE) + .withStopPattern(STOP_PATTERN) + .build(); + private static final Instant TIME = Instant.ofEpochSecond(1000); + private static final List VEHICLES = List.of( + RealtimeVehicle.builder().withTime(TIME).withCoordinates(GREENWICH).build() + ); + + @Test + void originalPattern() { + var service = new DefaultRealtimeVehicleService(new DefaultTransitService(new TransitModel())); + service.setRealtimeVehicles(ORIGINAL, VEHICLES); + var updates = service.getRealtimeVehicles(ORIGINAL); + assertEquals(VEHICLES, updates); + } + + @Test + void realtimeAddedPattern() { + var service = new DefaultRealtimeVehicleService(new DefaultTransitService(new TransitModel())); + var realtimePattern = tripPattern("realtime-added", ROUTE) + .withStopPattern(STOP_PATTERN) + .withOriginalTripPattern(ORIGINAL) + .withCreatedByRealtimeUpdater(true) + .build(); + service.setRealtimeVehicles(realtimePattern, VEHICLES); + var updates = service.getRealtimeVehicles(ORIGINAL); + assertEquals(VEHICLES, updates); + } +} diff --git a/src/test/java/org/opentripplanner/standalone/config/ExampleConfigTest.java b/src/test/java/org/opentripplanner/standalone/config/ExampleConfigTest.java index b250126c019..12b8749c1fe 100644 --- a/src/test/java/org/opentripplanner/standalone/config/ExampleConfigTest.java +++ b/src/test/java/org/opentripplanner/standalone/config/ExampleConfigTest.java @@ -22,7 +22,7 @@ @GeneratesDocumentation public class ExampleConfigTest { - @FilePatternSource(pattern = "docs/examples/**/" + ROUTER_CONFIG_FILENAME) + @FilePatternSource(pattern = "doc/user/examples/**/" + ROUTER_CONFIG_FILENAME) @ParameterizedTest(name = "Check validity of {0}") void routerConfig(Path filename) { testConfig(filename, a -> new RouterConfig(a, true)); @@ -30,7 +30,8 @@ void routerConfig(Path filename) { @FilePatternSource( pattern = { - "docs/examples/**/" + BUILD_CONFIG_FILENAME, "test/performance/**/" + BUILD_CONFIG_FILENAME, + "doc/user/examples/**/" + BUILD_CONFIG_FILENAME, + "test/performance/**/" + BUILD_CONFIG_FILENAME, } ) @ParameterizedTest(name = "Check validity of {0}") @@ -45,7 +46,9 @@ void speedTestConfig(Path filename) { } @FilePatternSource( - pattern = { "test/performance/**/otp-config.json", "docs/examples/**/" + OTP_CONFIG_FILENAME } + pattern = { + "test/performance/**/otp-config.json", "doc/user/examples/**/" + OTP_CONFIG_FILENAME, + } ) @ParameterizedTest(name = "Check validity of {0}") void otpConfig(Path filename) { diff --git a/src/test/java/org/opentripplanner/test/support/FilePatternArgumentsProvider.java b/src/test/java/org/opentripplanner/test/support/FilePatternArgumentsProvider.java index 35869a9094e..c084a81a73f 100644 --- a/src/test/java/org/opentripplanner/test/support/FilePatternArgumentsProvider.java +++ b/src/test/java/org/opentripplanner/test/support/FilePatternArgumentsProvider.java @@ -19,7 +19,7 @@ /** * This annotation processor allows you to provide a file pattern like - * "docs/examples/**\/build-config.json" as the input for a JUnit + * "doc/user/examples/**\/build-config.json" as the input for a JUnit * {@link org.junit.jupiter.params.ParameterizedTest}. *

          * Check the usages of {@link FilePatternSource} to see examples for how to use. diff --git a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java new file mode 100644 index 00000000000..87222eeba8f --- /dev/null +++ b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java @@ -0,0 +1,156 @@ +package org.opentripplanner.updater.vehicle_parking; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.opentripplanner.standalone.config.framework.json.JsonSupport.newNodeAdapterForTest; +import static org.opentripplanner.transit.model._data.TransitModelForTest.id; + +import com.google.common.util.concurrent.Futures; +import java.util.List; +import java.util.concurrent.Future; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.routing.graph.Graph; +import org.opentripplanner.routing.vehicle_parking.VehicleParking; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; +import org.opentripplanner.standalone.config.routerconfig.updaters.VehicleParkingUpdaterConfig; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.updater.GraphUpdaterManager; +import org.opentripplanner.updater.GraphWriterRunnable; +import org.opentripplanner.updater.spi.DataSource; +import org.opentripplanner.updater.spi.GraphUpdater; + +class VehicleParkingAvailabilityUpdaterTest { + + private static final VehicleParkingUpdaterParameters PARAMETERS = VehicleParkingUpdaterConfig.create( + "ref", + newNodeAdapterForTest( + """ + { + "type" : "vehicle-parking", + "feedId" : "parking", + "sourceType" : "siri-fm", + "frequency": "0s", + "url" : "https://transmodel.api.opendatahub.com/siri-lite/fm/parking" + } + """ + ) + ); + + private static final FeedScopedId ID = id("parking1"); + private static final AvailabiltyUpdate DEFAULT_UPDATE = new AvailabiltyUpdate(ID, 8); + + @Test + void updateCarAvailability() { + var service = buildParkingService(VehicleParkingSpaces.builder().carSpaces(10).build()); + var updater = new VehicleParkingAvailabilityUpdater( + PARAMETERS, + new StubDatasource(DEFAULT_UPDATE), + service + ); + + runUpdaterOnce(updater); + + var updated = service.getVehicleParkings().toList().getFirst(); + assertEquals(ID, updated.getId()); + assertEquals(8, updated.getAvailability().getCarSpaces()); + assertNull(updated.getAvailability().getBicycleSpaces()); + } + + @Test + void updateBicycleAvailability() { + var service = buildParkingService(VehicleParkingSpaces.builder().bicycleSpaces(15).build()); + var updater = new VehicleParkingAvailabilityUpdater( + PARAMETERS, + new StubDatasource(DEFAULT_UPDATE), + service + ); + + runUpdaterOnce(updater); + + var updated = service.getVehicleParkings().toList().getFirst(); + assertEquals(ID, updated.getId()); + assertEquals(8, updated.getAvailability().getBicycleSpaces()); + assertNull(updated.getAvailability().getCarSpaces()); + } + + @Test + void notFound() { + var service = buildParkingService(VehicleParkingSpaces.builder().bicycleSpaces(15).build()); + var updater = new VehicleParkingAvailabilityUpdater( + PARAMETERS, + new StubDatasource(new AvailabiltyUpdate(id("not-found"), 100)), + service + ); + + runUpdaterOnce(updater); + + var updated = service.getVehicleParkings().toList().getFirst(); + assertEquals(ID, updated.getId()); + assertNull(updated.getAvailability()); + } + + private static VehicleParkingService buildParkingService(VehicleParkingSpaces capacity) { + var service = new VehicleParkingService(); + + var parking = parkingBuilder() + .carPlaces(capacity.getCarSpaces() != null) + .bicyclePlaces(capacity.getBicycleSpaces() != null) + .capacity(capacity) + .build(); + service.updateVehicleParking(List.of(parking), List.of()); + return service; + } + + private static VehicleParking.VehicleParkingBuilder parkingBuilder() { + return VehicleParking + .builder() + .id(ID) + .name(I18NString.of("parking")) + .coordinate(WgsCoordinate.GREENWICH); + } + + private void runUpdaterOnce(VehicleParkingAvailabilityUpdater updater) { + class GraphUpdaterMock extends GraphUpdaterManager { + + private static final Graph GRAPH = new Graph(); + private static final TransitModel TRANSIT_MODEL = new TransitModel(); + + public GraphUpdaterMock(List updaters) { + super(GRAPH, TRANSIT_MODEL, updaters); + } + + @Override + public Future execute(GraphWriterRunnable runnable) { + runnable.run(GRAPH, TRANSIT_MODEL); + return Futures.immediateVoidFuture(); + } + } + + var graphUpdaterManager = new GraphUpdaterMock(List.of(updater)); + graphUpdaterManager.startUpdaters(); + graphUpdaterManager.stop(false); + } + + private static class StubDatasource implements DataSource { + + private final AvailabiltyUpdate update; + + private StubDatasource(AvailabiltyUpdate update) { + this.update = update; + } + + @Override + public boolean update() { + return true; + } + + @Override + public List getUpdates() { + return List.of(update); + } + } +} diff --git a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterTest.java b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterTest.java index a31b5cfb387..f49299eb4ea 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterTest.java @@ -54,6 +54,11 @@ public VehicleParkingSourceType sourceType() { return null; } + @Override + public UpdateType updateType() { + return UpdateType.FULL; + } + @Override public Duration frequency() { return Duration.ZERO; diff --git a/src/test/resources/standalone/config/router-config.json b/src/test/resources/standalone/config/router-config.json index 3a3ef9b4cf0..c526de423c1 100644 --- a/src/test/resources/standalone/config/router-config.json +++ b/src/test/resources/standalone/config/router-config.json @@ -438,6 +438,13 @@ "feedId": "bikeep", "sourceType": "bikeep", "url": "https://services.bikeep.com/location/v1/public-areas/no-baia-mobility/locations" + }, + // SIRI-FM vehicle parking updater + { + "type": "vehicle-parking", + "feedId": "parking", + "sourceType": "siri-fm", + "url": "https://transmodel.api.opendatahub.com/siri-lite/fm/parking" } ], "rideHailingServices": [