diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 526d966f30b..00000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,9 +0,0 @@ -# See https://help.github.com/articles/about-codeowners/ - -# Members of the following team will be notified of each pull request, -# and merging PRs requires approval by a member of this team. -* @opentripplanner/otp-review-team - -# The default team above could be overridden for changes to documentation. -# TODO: reduce acceptance requirements or expand to a larger team -docs/* @opentripplanner/otp-review-team diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 15e9e772595..00000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: Bug report -about: Report a bug or issue -title: '' -labels: '' -assignees: '' - ---- - -**NOTE:** this issue system is intended for reporting bugs and tracking progress in software -development. For all other usage and software development questions or discussion, please post a -question in our chat room: https://gitter.im/opentripplanner/OpenTripPlanner. - - -## Expected behavior - -## Observed behavior - -## Version of OTP used (exact commit hash or JAR name) - -## Data sets in use (links to GTFS and OSM PBF files) - -## Command line used to start OTP - -## Router config and graph build config JSON - -## Steps to reproduce the problem diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 3d316646331..00000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: Feature request -about: Suggest a feature or improvement for OTP -title: '' -labels: new feature -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** - - -**Goal / high level use-case** - - - -**Describe the solution you'd like** - - -**Describe alternatives you've considered** - - -**Additional context** - diff --git a/.github/ISSUE_TEMPLATE/roadmap_epic.md b/.github/ISSUE_TEMPLATE/roadmap_epic.md deleted file mode 100644 index 2b38ba3191f..00000000000 --- a/.github/ISSUE_TEMPLATE/roadmap_epic.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: Roadmap Epic -about: Suggest an idea for the Roadmap -title: '' -labels: Roadmap -assignees: '' - ---- - -### Describe expected behavior -- What: -- Why: -- When: - -### Linked issue(s) - - -### OTP PO Discussion meeting details -- Date: -- Link(s): - -### Extra Comments (Optional) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index da79a466e0b..00000000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,70 +0,0 @@ -## PR Instructions - -When creating a pull request, please follow the format below. For each section, *replace* the -guidance text with your own text, keeping the section heading. If you have nothing to say in a -particular section, you can completely delete the section including its heading to indicate that you -have taken the requested steps. None of these instructions or the guidance text (non-heading text) -should be present in the submitted PR. These sections serve as a checklist: when you have replaced -or deleted all of them, the PR is considered complete. As of 2021, most regular OTP contributors -participate in our twice-weekly conference calls. For all but the simplest and smallest PRs, -participation in these discussions is necessary to facilitate the review and merge process. Other -developers can ask questions and provide immediate feedback on technical design and code style, and -resolve any concerns about long term maintenance and comprehension of new code. - -### Summary - -Explain in one or two sentences what this PR achieves. - -### Issue - -Link to or create an [issue](https://github.com/opentripplanner/OpenTripPlanner/issues) that -describes the relevant feature or bug. You need not create an issue for small bugfixes and code -cleanups, but in that case do describe the problem clearly and completely in the "summary" section -above. In the linked issue (or summary section for smaller PRs) please describe: - -- Motivation (problem or need encountered) -- How the code works -- Technical approach and any design considerations or decisions - -Remember that the PR will be reviewed by another developer who may not be familiar with your use -cases or the code you're modifying. It generally takes much less effort for the author of a PR to -explain the background and technical details than for a reviewer to infer or deduce them. PRs may be -closed if they or their linked issues do not contain sufficient information for a reviewer to -proceed. - -Add [GitHub keywords](https://help.github.com/articles/closing-issues-using-keywords/) to this PR's -description, for example: - -Closes #45 - -### Unit tests - -Write a few words on how the new code is tested. - -- Were unit tests added/updated? -- Was any manual verification done? -- Any observations on changes to performance? -- 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) - ? - -### Documentation - -- Have you added documentation in code covering design and rationale behind the code? -- Were all non-trivial public classes and methods documented with Javadoc? -- Were any new configuration options added? If so were the tables in - the [configuration documentation](Configuration.md) updated? - -### Changelog - -The [changelog file](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/docs/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. - -### Bumping the serialization version id - -If you have made changes to the way the routing graph is serialized, for example by renaming a field -in one of the edges, then you must add the label `bump serialization id` to the PR. With this label -Github Actions will increase the field `otp.serialization.version.id` in `pom.xml`. diff --git a/.github/workflows/cibuild.yml b/.github/workflows/cibuild.yml deleted file mode 100644 index 28a84953af2..00000000000 --- a/.github/workflows/cibuild.yml +++ /dev/null @@ -1,236 +0,0 @@ -name: OTP CI Build -# On [push, pull_request] causes double-builds when creating PRs. -# But triggering on push only will miss pull requests from outside authors. -# The push event's ref is the name of the pushed branch; -# The pull_request event's branch name is the merge target branch. -on: - push: - branches: - - master - - dev-1.x - - dev-2.x - pull_request: - branches: - - master - - dev-1.x - - dev-2.x -jobs: - build-linux: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - # Starting in v2.2 checkout action fetches all tags when fetch-depth=0, for auto-versioning. - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - # nodejs is needed because the dynamic download of it via the prettier maven plugin often - # times out - # Example: https://github.com/opentripplanner/OpenTripPlanner/actions/runs/4490450225/jobs/7897533439 - - uses: actions/setup-node@v4 - with: - node-version: 18 - - # Java setup step completes very fast, no need to run in a preconfigured docker container - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: 21 - distribution: temurin - cache: maven - - - name: Prepare coverage agent, build and test - # these are split into two steps because otherwise maven keeps long-running HTTP connections - # to Maven Central open which then hang during the package phase because the Azure (Github Actions) - # NAT drops them - # https://github.com/actions/runner-images/issues/1499 - # we set nodePath and npmPath to skip downloading the node binary, which frequently times out - run: | - mvn --batch-mode jacoco:prepare-agent test jacoco:report -P prettierCheck -Dprettier.nodePath=node -Dprettier.npmPath=npm - mvn --batch-mode package -Dmaven.test.skip -P prettierSkip - - - name: Send coverage data to codecov.io - if: github.repository_owner == 'opentripplanner' - uses: codecov/codecov-action@v3.1.1 - with: - files: target/site/jacoco/jacoco.xml - - - name: Deploy to Github Package Registry - if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev-1.x' || github.ref == 'refs/heads/dev-2.x') - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: mvn --batch-mode deploy --settings maven-settings.xml -DskipTests -DGITHUB_REPOSITORY=$GITHUB_REPOSITORY -P prettierSkip -P deployGitHub - - build-windows: - timeout-minutes: 20 - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: 21 - distribution: temurin - cache: maven - # on windows there are frequent failures caused by page files being too small - # https://github.com/actions/virtual-environments/issues/785 - - name: Configure Windows Pagefile - uses: al-cheb/configure-pagefile-action@v1.3 - - name: Run tests - run: mvn --batch-mode test -P prettierSkip - - docs: - if: github.repository_owner == 'opentripplanner' - runs-on: ubuntu-latest - env: - REMOTE: docs - LOCAL_BRANCH: local-pages - REMOTE_BRANCH: main - TOKEN: ${{ secrets.CHANGELOG_TOKEN }} - MASTER_BRANCH_VERSION: 2.4.0 - - steps: - - - uses: actions/checkout@v4 - # this is necessary so that the correct credentials are put into the git configuration - # when we push to dev-2.x and push the HTML to the git repo - if: github.event_name == 'push' && (github.ref == 'refs/heads/dev-2.x' || github.ref == 'refs/heads/master') - with: - token: ${{ secrets.CHANGELOG_TOKEN }} - # fetch a large-ish number of commits so that we can check when the GraphQL schema file - # was modified last - fetch-depth: 1000 - - - uses: actions/checkout@v4 - # for a simple PR where we don't push, we don't need any credentials - if: github.event_name == 'pull_request' - - - name: Install Python dependencies - run: pip install -r docs/requirements.txt - - - - name: Build main documentation - if: github.event_name == 'pull_request' - run: mkdocs build - - - uses: actions/setup-node@v4 - with: - node-version: 18 - - - name: Build GTFS GraphQL API documentation - run: | - npm install -g @magidoc/cli@4.0.0 - magidoc generate - - - name: Deploy compiled HTML to Github pages - if: github.event_name == 'push' && (github.ref == 'refs/heads/dev-2.x' || github.ref == 'refs/heads/master') - run: | - git config --global user.name 'OTP Bot' - git config --global user.email 'bot@opentripplanner.org' - - # mike, the versioning plugin for mkdocs, expects there to be a local branch to push to so - # we are cloning one here and commit to it - # mike has support for specifing the origin but then it tries to rebase the _local_ gh-pages - # branch onto the remote which fails. that's the reason for this git hackery. - - git remote add $REMOTE https://$TOKEN@github.com/opentripplanner/docs.git - git fetch $REMOTE $REMOTE_BRANCH:$LOCAL_BRANCH - - # prefix is the root folder where to deploy the HTML, we use 'en' to emulate the URL - # structure of readthedocs - - if [ ${{ github.ref }} = 'refs/heads/master' ]; - then - mike deploy --branch $LOCAL_BRANCH --deploy-prefix en --title=$MASTER_BRANCH_VERSION --update-aliases v$MASTER_BRANCH_VERSION latest - else - mike deploy --branch $LOCAL_BRANCH --deploy-prefix en dev-2.x - fi - - # commit and push the GraphQL documentation if the schema file is newer than the - # compiled output. it's necessary to have this check because the diffs of the magidoc tool - # this are quite large and unnecessarily increase the size of the docs repo even when the - # schema hasn't changed. - # example commit: https://github.com/opentripplanner/docs/commit/45e6ddf8e4a4 - - SCHEMA_FILE_MODIFIED=`git log -n 1 --pretty=format:%ct src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls` - echo "schema modified at ${SCHEMA_FILE_MODIFIED}" - git checkout $LOCAL_BRANCH - DOCS_MODIFIED=`git log -n 1 --pretty=format:%ct api/dev-2.x/graphql-gtfs/introduction.html` - echo "docs modified at ${DOCS_MODIFIED}" - if [ "${SCHEMA_FILE_MODIFIED}" -gt "${DOCS_MODIFIED}" ]; then - echo "schema.graphqls has been modified, committing updated documentation" - mkdir -p api - rsync -r --delete target/magidoc/api/ api/dev-2.x/ - git add api - git commit -am "Add Magidoc GraphQL documentation" - else - echo "schema.graphqls has not been modified, not committing documentation" - fi - - git push $REMOTE $LOCAL_BRANCH:$REMOTE_BRANCH - - - graphql-code-generation: - if: github.repository_owner == 'opentripplanner' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 16 - - name: Run code generator - working-directory: src/main/java/org/opentripplanner/apis/gtfs/generated - run: | - yarn install - yarn generate - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: 21 - distribution: temurin - cache: maven - - name: Compile Java code - run: mvn --batch-mode compile -DskipTests -P prettierSkip - - container-image: - if: github.repository_owner == 'opentripplanner' && github.event_name == 'push' && (github.ref == 'refs/heads/dev-2.x' || github.ref == 'refs/heads/master') - runs-on: ubuntu-latest - needs: - - build-windows - - build-linux - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: 21 - distribution: temurin - cache: maven - - uses: actions/setup-node@v4 - with: - node-version: 18 - - name: Build container image with Jib, push to Dockerhub - env: - CONTAINER_REPO: docker.io/opentripplanner/opentripplanner - CONTAINER_REGISTRY_USER: otpbot - CONTAINER_REGISTRY_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} - run: | - # we give the container two tags - # - "latest" - # - a string like "2.3_2022-12-12T21-38" - - version_with_snapshot=`mvn -q help:evaluate -Dexpression=project.version -q -DforceStdout` - version=${version_with_snapshot/-SNAPSHOT/} - - image_version=${version} - - ## if the Maven version contains SNAPSHOT, then add date to tag - if [[ $version_with_snapshot == *"SNAPSHOT"* ]]; then - image_date=`date +%Y-%m-%dT%H-%M` - image_version="${version}_${image_date}" - echo "Maven version ${version_with_snapshot} contains SNAPSHOT, adding date to container image tag" - fi - - mvn --batch-mode -P prettierSkip compile com.google.cloud.tools:jib-maven-plugin:build -Djib.to.tags=latest,$image_version diff --git a/.github/workflows/close_stale_pr_and_issues.yml b/.github/workflows/close_stale_pr_and_issues.yml index 98619f7e763..4b96429f108 100644 --- a/.github/workflows/close_stale_pr_and_issues.yml +++ b/.github/workflows/close_stale_pr_and_issues.yml @@ -13,12 +13,12 @@ jobs: if: github.repository_owner == 'opentripplanner' runs-on: ubuntu-latest steps: - - uses: actions/stale@v6.0.1 + - uses: actions/stale@v5.0.0 id: stale with: stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 30 days' days-before-stale: 90 days-before-close: 30 - operations-per-run: 260 + operations-per-run: 180 exempt-issue-labels: 'Roadmap' ascending: true diff --git a/.github/workflows/debug-client.yml b/.github/workflows/debug-client.yml deleted file mode 100644 index 56304db88f2..00000000000 --- a/.github/workflows/debug-client.yml +++ /dev/null @@ -1,88 +0,0 @@ -name: Debug client - -on: - push: - paths: - - 'client-next/**' - pull_request: - paths: - - 'client-next/**' - -# to avoid conflicts, make sure that only one workflow pushes to Github at the same time -concurrency: - group: github-push - -jobs: - debug-client: - if: github.repository_owner == 'opentripplanner' - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - # this is necessary so that the correct credentials are put into the git configuration - # when we push to dev-2.x and push the compiled output to the git repo - - uses: actions/checkout@v4 - if: github.event_name == 'push' - with: - token: ${{ secrets.CHANGELOG_TOKEN }} - fetch-depth: 0 - - # for a simple PR where we don't push, we don't need any credentials - - uses: actions/checkout@v4 - if: github.event_name == 'pull_request' - - - uses: actions/setup-node@v4 - with: - node-version: 18 - - - name: Set version - run: echo "VERSION=`date +%Y/%m/%Y-%m-%dT%H:%M`" >> $GITHUB_ENV - - - name: Build debug client - working-directory: client-next - run: | - npm install - npm run build -- --base https://cdn.jsdelivr.net/gh/opentripplanner/debug-client-assets@main/${VERSION}/ - npm run coverage - - - name: Deploy compiled assets to repo - if: github.event_name == 'push' && github.ref == 'refs/heads/dev-2.x' - env: - REMOTE: debug-client - LOCAL_BRANCH: local-assets - REMOTE_BRANCH: main - TOKEN: ${{ secrets.CHANGELOG_TOKEN }} - run: | - # Configure git user - git config --global user.name 'OTP Bot' - git config --global user.email 'bot@opentripplanner.org' - - # Fetch the assets repo - git remote add $REMOTE https://$TOKEN@github.com/opentripplanner/debug-client-assets.git - git fetch --depth=1 $REMOTE $REMOTE_BRANCH:$LOCAL_BRANCH - - git checkout $LOCAL_BRANCH - - - # Copy the compiled output to a versioned folder - mkdir -p $VERSION - rsync -r client-next/output/* ./$VERSION/ - git add $VERSION - git commit -am "Add version ${VERSION} of debug client" - - # Push to assets repo https://github.com/opentripplanner/debug-client-assets - git push $REMOTE $LOCAL_BRANCH:$REMOTE_BRANCH - - # Switch back to the OTP code - git checkout dev-2.x - git pull --rebase - - CLIENT_HTML_OUTPUT=src/client/debug-client-preview/index.html - mkdir -p src/client/debug-client-preview/ - cp client-next/output/index.html ${CLIENT_HTML_OUTPUT} - - # just to debug - cat ${CLIENT_HTML_OUTPUT} - - git add -f ${CLIENT_HTML_OUTPUT} - git commit -m "Upgrade debug client to version ${VERSION}" - git push ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git HEAD:dev-2.x diff --git a/.github/workflows/dev-pipeline.yml b/.github/workflows/dev-pipeline.yml new file mode 100644 index 00000000000..46ccb1c6ebc --- /dev/null +++ b/.github/workflows/dev-pipeline.yml @@ -0,0 +1,20 @@ +name: Process dev-2.x push or pr +on: + push: + branches: + - dev-2.x + pull_request: + branches: + - dev-2.x +jobs: + docker-push: + if: github.ref == 'refs/heads/dev-2.x' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Build docker image from dev-2.x and push it + run: ./.github/workflows/scripts/build_and_push_dev.sh + env: + DOCKER_USER: ${{ secrets.DOCKER_USER }} + DOCKER_AUTH: ${{ secrets.DOCKER_AUTH }} diff --git a/.github/workflows/performance-test.yml b/.github/workflows/performance-test.yml deleted file mode 100644 index 3c2ffdba465..00000000000 --- a/.github/workflows/performance-test.yml +++ /dev/null @@ -1,116 +0,0 @@ -name: Performance test - -on: - push: - branches: - - dev-2.x - -jobs: - perf-test: - if: github.repository_owner == 'opentripplanner' && !startsWith(github.event.head_commit.message ,'Bump serialization version id for') && !startsWith(github.event.head_commit.message ,'Upgrade debug client to version') - runs-on: performance-test - strategy: - fail-fast: false - matrix: - include: - - # Profiles - # - # The profile variable is used to filter out some locations when the speed test is run - # on a branch during development. - # - # - locations with the 'core' profile are always run (even when on a branch) - # - locations with the 'extended' profile are only run after merging to dev-2.x - - - location: germany # all of Germany (500k stops, 200k patterns) but no OSM - iterations: 1 - jfr-delay: "50s" - profile: core - - - location: norway - iterations: 4 - jfr-delay: "35s" - profile: core - - # disabled due to https://github.com/opentripplanner/OpenTripPlanner/issues/5702 - #- location: skanetrafiken - # iterations: 1 - # jfr-delay: "50s" - # profile: core - - # extended locations that are run only after merging to dev-2.x - - - location: hamburg # German city - iterations: 1 - jfr-delay: "50s" - profile: extended - - - location: baden-wuerttemberg # German state of Baden-Württemberg: https://en.wikipedia.org/wiki/Baden-W%C3%BCrttemberg - iterations: 1 - jfr-delay: "50s" - profile: extended - - - location: switzerland - iterations: 1 - jfr-delay: "50s" - profile: extended - - - location: washington-state - iterations: 1 - jfr-delay: "20s" - profile: extended - - steps: - - uses: actions/checkout@v4 - if: matrix.profile == 'core' || github.ref == 'refs/heads/dev-2.x' - with: - fetch-depth: 0 - - - name: Set up JDK - if: matrix.profile == 'core' || github.ref == 'refs/heads/dev-2.x' - uses: actions/setup-java@v4 - with: - java-version: 21 - distribution: temurin - timeout-minutes: 5 - - - name: Set up Maven - if: matrix.profile == 'core' || github.ref == 'refs/heads/dev-2.x' - uses: stCarolas/setup-maven@v.4.5 - with: - maven-version: 3.8.2 - - - name: Build jar - if: matrix.profile == 'core' || github.ref == 'refs/heads/dev-2.x' - env: - MAVEN_OPTS: "-Dmaven.repo.local=/home/lenni/.m2/repository/" - run: mvn -DskipTests --batch-mode package -P prettierSkip - - - name: Build graph - if: matrix.profile == 'core' || github.ref == 'refs/heads/dev-2.x' - run: | - cp target/otp-*-SNAPSHOT-shaded.jar otp.jar - java -Xmx32G -jar otp.jar --build --save test/performance/${{ matrix.location }}/ - - - name: Run speed test - if: matrix.profile == 'core' || github.ref == 'refs/heads/dev-2.x' - env: - PERFORMANCE_INFLUX_DB_PASSWORD: ${{ secrets.PERFORMANCE_INFLUX_DB_PASSWORD }} - SPEEDTEST_LOCATION: ${{ matrix.location }} - MAVEN_OPTS: "-Xmx50g -XX:StartFlightRecording=delay=${{ matrix.jfr-delay }},duration=30m,filename=${{ matrix.location}}-speed-test.jfr -Dmaven.repo.local=/home/lenni/.m2/repository/" - run: | - mvn exec:java -Dexec.mainClass="org.opentripplanner.transit.speed_test.SpeedTest" -Dexec.classpathScope=test -Dexec.args="--dir=test/performance/${{ matrix.location }} -p md -n ${{ matrix.iterations }} -i 3 -0" -P prettierSkip - - - name: Archive travel results file - if: matrix.profile == 'core' || github.ref == 'refs/heads/dev-2.x' - uses: actions/upload-artifact@v3 - with: - name: ${{ matrix.location }}-travelSearch-results.csv - path: test/performance/${{ matrix.location }}/travelSearch-results.csv - - - name: Archive Flight Recorder instrumentation file - if: matrix.profile == 'core' || github.ref == 'refs/heads/dev-2.x' - uses: actions/upload-artifact@v3 - with: - name: ${{ matrix.location }}-flight-recorder - path: ${{ matrix.location}}-speed-test.jfr diff --git a/.github/workflows/prod-pipeline.yml b/.github/workflows/prod-pipeline.yml new file mode 100644 index 00000000000..7ddc84ccf48 --- /dev/null +++ b/.github/workflows/prod-pipeline.yml @@ -0,0 +1,32 @@ +name: Build v2-prod from release +on: + release: + types: + - published +jobs: + v2-release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set time zone to Europe/Helsinki + uses: zcong1993/setup-timezone@master + with: + timezone: "Europe/Helsinki" + - name: Check Tag + id: check-tag + run: | + if [[ ${GITHUB_REF##*/} =~ ^202[0-9][0-1][0-9][0-3][0-9] ]]; then + echo "match=true" >> $GITHUB_OUTPUT + else + echo invalid release tag + exit 1 + fi + - name: Push latest image as v2-prod + if: steps.check-tag.outputs.match == 'true' + run: ./.github/workflows/scripts/push_prod.sh + env: + DOCKER_USER: ${{ secrets.DOCKER_USER }} + DOCKER_AUTH: ${{ secrets.DOCKER_AUTH }} + DOCKER_BASE_TAG: v2-prod + DOCKER_DEV_TAG: v2 diff --git a/.github/workflows/prune-container-images.yml b/.github/workflows/prune-container-images.yml deleted file mode 100644 index c1653701c3b..00000000000 --- a/.github/workflows/prune-container-images.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: 'Prune container images' - -on: - schedule: - - cron: '0 12 * * 1' - workflow_dispatch: - -jobs: - container-image: - if: github.repository_owner == 'opentripplanner' - runs-on: ubuntu-latest - steps: - - name: Delete unused container images - env: - CONTAINER_REPO: opentripplanner/opentripplanner - CONTAINER_REGISTRY_USER: otpbot - CONTAINER_REGISTRY_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} - run: | - # remove all snapshot container images that have not been pulled for over a year - # --keep-semver makes sure that any image with a x.y.z version scheme is unaffected by this - pip install prune-container-repo==0.0.4 - prune-container-repo -u ${CONTAINER_REGISTRY_USER} -r ${CONTAINER_REPO} --days=365 --keep-semver --activate diff --git a/.github/workflows/scripts/build_and_push_dev.sh b/.github/workflows/scripts/build_and_push_dev.sh new file mode 100755 index 00000000000..7e6fbd27023 --- /dev/null +++ b/.github/workflows/scripts/build_and_push_dev.sh @@ -0,0 +1,25 @@ +#!/bin/bash +set -e + +DOCKER_IMAGE="hsldevcom/opentripplanner" +DOCKER_TAG="v2" + +COMMIT_HASH=$(git rev-parse --short "$GITHUB_SHA") + +DOCKER_TAG_LONG=$DOCKER_TAG-$(date +"%Y-%m-%dT%H.%M.%S")-$COMMIT_HASH +DOCKER_IMAGE_TAG=$DOCKER_IMAGE:$DOCKER_TAG +DOCKER_IMAGE_TAG_LONG=$DOCKER_IMAGE:$DOCKER_TAG_LONG + +# Build image +echo Building OTP +docker build --tag="$DOCKER_IMAGE:builder" -f Dockerfile.builder . +docker run --rm --entrypoint tar "$DOCKER_IMAGE:builder" -c target|tar x -C ./ +docker build --tag="$DOCKER_IMAGE_TAG_LONG" -f Dockerfile . + +docker login -u $DOCKER_USER -p $DOCKER_AUTH +echo "Pushing $DOCKER_TAG image" +docker push $DOCKER_IMAGE_TAG_LONG +docker tag $DOCKER_IMAGE_TAG_LONG $DOCKER_IMAGE_TAG +docker push $DOCKER_IMAGE_TAG + +echo Build completed diff --git a/.github/workflows/scripts/push_prod.sh b/.github/workflows/scripts/push_prod.sh new file mode 100755 index 00000000000..72840a143cd --- /dev/null +++ b/.github/workflows/scripts/push_prod.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -e + +COMMIT_HASH=$(git rev-parse --short "$GITHUB_SHA") +ORG=hsldevcom + +function imagedeploy { + DOCKER_IMAGE=$ORG/$1 + DOCKER_TAG=${DOCKER_BASE_TAG:-prod} + DOCKER_DEV_TAG=${DOCKER_DEV_TAG:-latest} + + DOCKER_TAG_LONG=$DOCKER_TAG-$(date +"%Y-%m-%dT%H.%M.%S")-$COMMIT_HASH + DOCKER_IMAGE_TAG=$DOCKER_IMAGE:$DOCKER_TAG + DOCKER_IMAGE_TAG_LONG=$DOCKER_IMAGE:$DOCKER_TAG_LONG + DOCKER_IMAGE_DEV=$DOCKER_IMAGE:$DOCKER_DEV_TAG + + echo "processing prod release" + docker pull $DOCKER_IMAGE_DEV + docker tag $DOCKER_IMAGE_DEV $DOCKER_IMAGE_TAG + docker tag $DOCKER_IMAGE_DEV $DOCKER_IMAGE_TAG_LONG + docker push $DOCKER_IMAGE_TAG + docker push $DOCKER_IMAGE_TAG_LONG +} + +docker login -u $DOCKER_USER -p $DOCKER_AUTH + +imagedeploy "opentripplanner" + +echo Deploy complete diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000000..dac78dc4622 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM eclipse-temurin:21-jre +MAINTAINER Reittiopas version: 1.0 + +VOLUME /opt/opentripplanner/graphs + +RUN apt-get update \ + && apt-get install -y curl bash fonts-dejavu fontconfig unzip tzdata \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +ENV OTP_ROOT="/opt/opentripplanner" +ENV ROUTER_DATA_CONTAINER_URL="https://api.digitransit.fi/routing-data/v2/finland" + +WORKDIR ${OTP_ROOT} + +ADD run.sh ${OTP_ROOT}/run.sh +ADD target/*-shaded.jar ${OTP_ROOT}/otp-shaded.jar + +ENV PORT=8080 +EXPOSE ${PORT} +ENV ROUTER_NAME=finland +ENV JAVA_OPTS="-Xms8G -Xmx8G" + +ENTRYPOINT exec ./run.sh \ No newline at end of file diff --git a/Dockerfile.builder b/Dockerfile.builder new file mode 100644 index 00000000000..c109fbd6417 --- /dev/null +++ b/Dockerfile.builder @@ -0,0 +1,16 @@ +FROM maven:3-eclipse-temurin-21 +MAINTAINER Reittiopas version: 0.1 + +ENV OTP_ROOT="/opt/opentripplanner" + +WORKDIR ${OTP_ROOT} + +ADD pom.xml ${OTP_ROOT}/pom.xml +ADD src ${OTP_ROOT}/src +ADD doc-templates ${OTP_ROOT}/doc-templates +ADD docs ${OTP_ROOT}/docs +ADD test ${OTP_ROOT}/test +add .git ${OTP_ROOT}/.git + +# Build OTP +RUN mvn package \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100755 index 00000000000..1e577cc845b --- /dev/null +++ b/run.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +# Fail fast if graph building fails +set -e + +JAR=`ls *-shaded*` +echo JAR=$JAR +SLEEP_TIME=5 + +function url { + echo $ROUTER_DATA_CONTAINER_URL/$1 +} + +function build_graph { + GRAPHNAME=$1 + FILE=$2 + echo "building graph..." + DIR="graphs" + rm -rf $DIR || true + mkdir -p $DIR + unzip -o -d $DIR $FILE && rm $FILE + mv $DIR/router-$GRAPHNAME $DIR/$GRAPHNAME + java $JAVA_OPTS -jar $JAR --build --save ./$DIR/$GRAPHNAME +} + +function download_graph { + NAME=$1 + VERSION=$2 + GRAPH_FILE=graph-$NAME.zip + URL=$(url "graph-$NAME-$VERSION.zip") + echo "Downloading graph from $URL" + for i in {1..6}; do + HTTP_STATUS=$(curl --write-out %{http_code} --silent --output $GRAPH_FILE $URL) + + if [ "$HTTP_STATUS" -eq 404 ]; then + rm $GRAPH_FILE > /dev/null + echo "Graph not found" + return 1 + fi + + if [ "$HTTP_STATUS" -eq 200 ]; then break + fi + + rm $GRAPH_FILE > /dev/null + sleep $SLEEP_TIME + + done + + if [ -f graph-$NAME.zip ]; then + # if the graph already exists then overwrite it, otherwise we need to build a new one on every start + unzip -o $GRAPH_FILE -d ./graphs + SUCCESS=$? + rm $GRAPH_FILE + return $SUCCESS + else + return 1 + fi +} + +function version { + java -jar $JAR --version|grep -o -P '(?<=commit: ).*(?=,)' +} + +function process { + NAME=$1 + URL=$(url "router-$NAME.zip") + FILE="$NAME.zip" + + echo "Retrieving graph source bundle from $URL" + until curl -f -s $URL -o $FILE + do + echo "Error retrieving graph source bundle $URL from otp-data-server... retrying in $SLEEP_TIME s..." + sleep $SLEEP_TIME + done + + build_graph $NAME $FILE +} + +VERSION=$(version) + +echo VERSION $VERSION +echo ROUTER $ROUTER_NAME + +download_graph $ROUTER_NAME $VERSION || process $ROUTER_NAME + +echo "graphString is: $ROUTER_NAME" +java -Dsentry.release=$VERSION $JAVA_OPTS -Duser.timezone=Europe/Helsinki -Djava.awt.headless=true --add-exports java.desktop/sun.font=ALL-UNNAMED -jar $JAR --serve --port $PORT --load ./graphs/$ROUTER_NAME diff --git a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java index 91d9009e2f0..ac05842a70d 100644 --- a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java @@ -1,15 +1,28 @@ package org.opentripplanner.ext.vectortiles.layers.stops; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.framework.time.TimeUtils.time; +import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.opentripplanner.ext.realtimeresolver.RealtimeResolver; import org.opentripplanner.framework.i18n.TranslatedString; +import org.opentripplanner.model.plan.Place; +import org.opentripplanner.routing.alertpatch.AlertEffect; +import org.opentripplanner.routing.alertpatch.EntitySelector; +import org.opentripplanner.routing.alertpatch.TimePeriod; +import org.opentripplanner.routing.alertpatch.TransitAlert; +import org.opentripplanner.routing.impl.TransitAlertServiceImpl; +import org.opentripplanner.routing.services.TransitAlertService; +import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.StopModel; @@ -18,9 +31,10 @@ public class StopsLayerTest { private RegularStop stop; + private RegularStop stop2; @BeforeEach - public void setUp() { + public final void setUp() { var nameTranslations = TranslatedString.getI18NString( new HashMap<>() { { @@ -49,6 +63,14 @@ public void setUp() { .withDescription(descTranslations) .withCoordinate(50, 10) .build(); + stop2 = + StopModel + .of() + .regularStop(new FeedScopedId("F", "name")) + .withName(nameTranslations) + .withDescription(descTranslations) + .withCoordinate(51, 10) + .build(); } @Test @@ -89,4 +111,47 @@ public void digitransitStopPropertyMapperTranslationTest() { assertEquals("nameDE", map.get("name")); assertEquals("descDE", map.get("desc")); } + + @Test + public void digitransitRealtimeStopPropertyMapperTest() { + var deduplicator = new Deduplicator(); + var transitModel = new TransitModel(new StopModel(), deduplicator); + transitModel.index(); + var alertService = new TransitAlertServiceImpl(transitModel); + var transitService = new DefaultTransitService(transitModel) { + @Override + public TransitAlertService getTransitAlertService() { + return alertService; + } + }; + + Route route = TransitModelForTest.route("route").build(); + var itinerary = newItinerary(Place.forStop(stop), time("11:00")) + .bus(route, 1, time("11:05"), time("11:20"), Place.forStop(stop2)) + .build(); + + var alert = TransitAlert + .of(stop.getId()) + .addEntity(new EntitySelector.Stop(stop.getId())) + .addTimePeriod(new TimePeriod(0, 0)) + .withEffect(AlertEffect.NO_SERVICE) + .build(); + transitService.getTransitAlertService().setAlerts(List.of(alert)); + + var itineraries = List.of(itinerary); + RealtimeResolver.populateLegsWithRealtime(itineraries, transitService); + + DigitransitRealtimeStopPropertyMapper mapper = DigitransitRealtimeStopPropertyMapper.create( + transitService, + new Locale("en-US") + ); + + Map map = new HashMap<>(); + mapper.map(stop).forEach(o -> map.put(o.key(), o.value())); + + assertEquals("F:name", map.get("gtfsId")); + assertEquals("name", map.get("name")); + assertEquals("desc", map.get("desc")); + assertEquals("[{\"alertEffect\":\"NO_SERVICE\"}]", map.get("alerts")); + } } diff --git a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java index 345f8deba20..87f100464a5 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java +++ b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java @@ -354,7 +354,7 @@ private Result handleModifiedTrip( // Also check whether trip id has been used for previously ADDED/MODIFIED trip message and // remove the previously created trip - removePreviousRealtimeUpdate(trip, serviceDate); + this.buffer.removePreviousRealtimeUpdate(trip.getId(), serviceDate); return updateResult; } @@ -410,27 +410,6 @@ private boolean markScheduledTripAsDeleted(Trip trip, final LocalDate serviceDat return success; } - /** - * Removes previous trip-update from buffer if there is an update with given trip on service date - * - * @param serviceDate service date - * @return true if a previously added trip was removed - */ - private boolean removePreviousRealtimeUpdate(final Trip trip, final LocalDate serviceDate) { - boolean success = false; - - final TripPattern pattern = buffer.getRealtimeAddedTripPattern(trip.getId(), serviceDate); - if (pattern != null) { - // Remove the previous real-time-added TripPattern from buffer. - // Only one version of the real-time-update should exist - buffer.removeLastAddedTripPattern(trip.getId(), serviceDate); - buffer.removeRealtimeUpdatedTripTimes(pattern, trip.getId(), serviceDate); - success = true; - } - - return success; - } - private boolean purgeExpiredData() { final LocalDate today = LocalDate.now(transitModel.getTimeZone()); final LocalDate previously = today.minusDays(2); // Just to be safe... diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/VectorTilesResource.java b/src/ext/java/org/opentripplanner/ext/vectortiles/VectorTilesResource.java index 29701ee2307..8622e96e2ff 100644 --- a/src/ext/java/org/opentripplanner/ext/vectortiles/VectorTilesResource.java +++ b/src/ext/java/org/opentripplanner/ext/vectortiles/VectorTilesResource.java @@ -20,7 +20,7 @@ import org.opentripplanner.apis.support.TileJson; import org.opentripplanner.ext.vectortiles.layers.areastops.AreaStopsLayerBuilder; import org.opentripplanner.ext.vectortiles.layers.stations.StationsLayerBuilder; -import org.opentripplanner.ext.vectortiles.layers.stops.StopsLayerBuilder; +import org.opentripplanner.ext.vectortiles.layers.stops.StopsBaseLayerBuilder; import org.opentripplanner.ext.vectortiles.layers.vehicleparkings.VehicleParkingGroupsLayerBuilder; import org.opentripplanner.ext.vectortiles.layers.vehicleparkings.VehicleParkingsLayerBuilder; import org.opentripplanner.ext.vectortiles.layers.vehiclerental.VehicleRentalPlacesLayerBuilder; @@ -122,7 +122,7 @@ private static LayerBuilder createLayerBuilder( OtpServerRequestContext context ) { return switch (layerParameters.type()) { - case Stop -> new StopsLayerBuilder(context.transitService(), layerParameters, locale); + case Stop -> new StopsBaseLayerBuilder(context.transitService(), layerParameters, locale); case Station -> new StationsLayerBuilder(context.transitService(), layerParameters, locale); case AreaStop -> new AreaStopsLayerBuilder(context.transitService(), layerParameters, locale); case VehicleRental -> new VehicleRentalPlacesLayerBuilder( diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitRealtimeStopPropertyMapper.java b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitRealtimeStopPropertyMapper.java new file mode 100644 index 00000000000..08506f86975 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitRealtimeStopPropertyMapper.java @@ -0,0 +1,64 @@ +package org.opentripplanner.ext.vectortiles.layers.stops; + +import static org.opentripplanner.ext.vectortiles.layers.stops.DigitransitStopPropertyMapper.getRoutes; +import static org.opentripplanner.ext.vectortiles.layers.stops.DigitransitStopPropertyMapper.getType; + +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.opentripplanner.apis.support.mapping.PropertyMapper; +import org.opentripplanner.framework.i18n.I18NStringMapper; +import org.opentripplanner.inspector.vector.KeyValue; +import org.opentripplanner.transit.model.site.RegularStop; +import org.opentripplanner.transit.service.TransitService; + +public class DigitransitRealtimeStopPropertyMapper extends PropertyMapper { + + private final TransitService transitService; + private final I18NStringMapper i18NStringMapper; + + public DigitransitRealtimeStopPropertyMapper(TransitService transitService, Locale locale) { + this.transitService = transitService; + this.i18NStringMapper = new I18NStringMapper(locale); + } + + protected static DigitransitRealtimeStopPropertyMapper create( + TransitService transitService, + Locale locale + ) { + return new DigitransitRealtimeStopPropertyMapper(transitService, locale); + } + + @Override + protected Collection map(RegularStop stop) { + String alerts = JSONArray.toJSONString( + transitService + .getTransitAlertService() + .getStopAlerts(stop.getId()) + .stream() + .map(alert -> { + JSONObject alertObject = new JSONObject(); + alertObject.put("alertEffect", alert.effect().toString()); + return alertObject; + }) + .toList() + ); + + return List.of( + new KeyValue("gtfsId", stop.getId().toString()), + new KeyValue("name", i18NStringMapper.mapNonnullToApi(stop.getName())), + new KeyValue("code", stop.getCode()), + new KeyValue("platform", stop.getPlatformCode()), + new KeyValue("desc", i18NStringMapper.mapToApi(stop.getDescription())), + new KeyValue( + "parentStation", + stop.getParentStation() != null ? stop.getParentStation().getId() : "null" + ), + new KeyValue("type", getType(transitService, stop)), + new KeyValue("routes", getRoutes(transitService, stop)), + new KeyValue("alerts", alerts) + ); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java index 2c5a4519e96..2b6d7536f1c 100644 --- a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java +++ b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java @@ -20,7 +20,7 @@ public class DigitransitStopPropertyMapper extends PropertyMapper { private final TransitService transitService; private final I18NStringMapper i18NStringMapper; - private DigitransitStopPropertyMapper(TransitService transitService, Locale locale) { + DigitransitStopPropertyMapper(TransitService transitService, Locale locale) { this.transitService = transitService; this.i18NStringMapper = new I18NStringMapper(locale); } @@ -34,30 +34,6 @@ protected static DigitransitStopPropertyMapper create( @Override protected Collection map(RegularStop stop) { - Collection patternsForStop = transitService.getPatternsForStop(stop); - - String type = patternsForStop - .stream() - .map(TripPattern::getMode) - .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())) - .entrySet() - .stream() - .max(Map.Entry.comparingByValue()) - .map(Map.Entry::getKey) - .map(Enum::name) - .orElse(null); - - String routes = JSONArray.toJSONString( - transitService - .getRoutesForStop(stop) - .stream() - .map(route -> { - JSONObject routeObject = new JSONObject(); - routeObject.put("gtfsType", route.getGtfsType()); - return routeObject; - }) - .toList() - ); return List.of( new KeyValue("gtfsId", stop.getId().toString()), new KeyValue("name", i18NStringMapper.mapNonnullToApi(stop.getName())), @@ -68,8 +44,37 @@ protected Collection map(RegularStop stop) { "parentStation", stop.getParentStation() != null ? stop.getParentStation().getId() : "null" ), - new KeyValue("type", type), - new KeyValue("routes", routes) + new KeyValue("type", getType(transitService, stop)), + new KeyValue("routes", getRoutes(transitService, stop)) ); } + + protected static String getRoutes(TransitService transitService, RegularStop stop) { + return JSONArray.toJSONString( + transitService + .getRoutesForStop(stop) + .stream() + .map(route -> { + JSONObject routeObject = new JSONObject(); + routeObject.put("gtfsType", route.getGtfsType()); + return routeObject; + }) + .toList() + ); + } + + protected static String getType(TransitService transitService, RegularStop stop) { + Collection patternsForStop = transitService.getPatternsForStop(stop); + + return patternsForStop + .stream() + .map(TripPattern::getMode) + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())) + .entrySet() + .stream() + .max(Map.Entry.comparingByValue()) + .map(Map.Entry::getKey) + .map(Enum::name) + .orElse(null); + } } diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsBaseLayerBuilder.java b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsBaseLayerBuilder.java new file mode 100644 index 00000000000..43dd499c9c0 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsBaseLayerBuilder.java @@ -0,0 +1,30 @@ +package org.opentripplanner.ext.vectortiles.layers.stops; + +import static java.util.Map.entry; + +import java.util.Locale; +import java.util.Map; +import org.opentripplanner.ext.vectortiles.VectorTilesResource; +import org.opentripplanner.inspector.vector.LayerParameters; +import org.opentripplanner.transit.service.TransitService; + +public class StopsBaseLayerBuilder extends StopsLayerBuilder { + + public StopsBaseLayerBuilder( + TransitService service, + LayerParameters layerParameters, + Locale locale + ) { + super( + service, + Map.ofEntries( + entry(MapperType.Digitransit, new DigitransitStopPropertyMapper(service, locale)), + entry( + MapperType.DigitransitRealtime, + new DigitransitRealtimeStopPropertyMapper(service, locale) + ) + ), + layerParameters + ); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java index 2ad85587fe5..fa7036efd52 100644 --- a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java @@ -14,7 +14,7 @@ import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.service.TransitService; -public class StopsLayerBuilder extends LayerBuilder { +public class StopsLayerBuilder extends LayerBuilder { static Map>> mappers = Map.of( MapperType.Digitransit, @@ -24,11 +24,11 @@ public class StopsLayerBuilder extends LayerBuilder { public StopsLayerBuilder( TransitService transitService, - LayerParameters layerParameters, - Locale locale + Map> mappers, + LayerParameters layerParameters ) { super( - mappers.get(MapperType.valueOf(layerParameters.mapper())).apply(transitService, locale), + mappers.get(MapperType.valueOf(layerParameters.mapper())), layerParameters.name(), layerParameters.expansionFactor() ); @@ -51,5 +51,6 @@ protected List getGeometries(Envelope query) { enum MapperType { Digitransit, + DigitransitRealtime, } } diff --git a/src/main/java/org/opentripplanner/model/TimetableSnapshot.java b/src/main/java/org/opentripplanner/model/TimetableSnapshot.java index 066a27ba15a..937c3e13ffd 100644 --- a/src/main/java/org/opentripplanner/model/TimetableSnapshot.java +++ b/src/main/java/org/opentripplanner/model/TimetableSnapshot.java @@ -20,7 +20,6 @@ import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.TripIdAndServiceDate; -import org.opentripplanner.transit.model.timetable.TripOnServiceDate; import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.spi.UpdateSuccess; @@ -111,38 +110,6 @@ public Timetable resolve(TripPattern pattern, LocalDate serviceDate) { return pattern.getScheduledTimetable(); } - public void removeRealtimeUpdatedTripTimes( - TripPattern tripPattern, - FeedScopedId tripId, - LocalDate serviceDate - ) { - SortedSet sortedTimetables = this.timetables.get(tripPattern); - if (sortedTimetables != null) { - TripTimes tripTimesToRemove = null; - for (Timetable timetable : sortedTimetables) { - if (timetable.isValidFor(serviceDate)) { - final TripTimes tripTimes = timetable.getTripTimes(tripId); - if (tripTimes == null) { - LOG.debug("No triptimes to remove for trip {}", tripId); - } else if (tripTimesToRemove != null) { - LOG.debug("Found two triptimes to remove for trip {}", tripId); - } else { - tripTimesToRemove = tripTimes; - } - } - } - - if (tripTimesToRemove != null) { - for (Timetable sortedTimetable : sortedTimetables) { - boolean isDirty = sortedTimetable.getTripTimes().remove(tripTimesToRemove); - if (isDirty) { - dirtyTimetables.add(sortedTimetable); - } - } - } - } - } - /** * Get the current trip pattern given a trip id and a service date, if it has been changed from * the scheduled pattern with an update, for which the stopPattern is different. @@ -295,11 +262,24 @@ public void clear(String feedId) { } /** - * Removes the latest added trip pattern from the cache. This should be done when removing the - * trip times from the timetable the trip has been added to. + * Removes previous trip-update from buffer if there is an update with given trip on service date + * + * @param serviceDate service date + * @return true if a previously added trip was removed */ - public void removeLastAddedTripPattern(FeedScopedId feedScopedTripId, LocalDate serviceDate) { - realtimeAddedTripPattern.remove(new TripIdAndServiceDate(feedScopedTripId, serviceDate)); + public boolean removePreviousRealtimeUpdate(FeedScopedId tripId, LocalDate serviceDate) { + boolean success = false; + + final TripPattern pattern = getRealtimeAddedTripPattern(tripId, serviceDate); + if (pattern != null) { + // Remove the previous real-time-added TripPattern from buffer. + // Only one version of the real-time-update should exist + removeLastAddedTripPattern(tripId, serviceDate); + removeRealtimeUpdatedTripTimes(pattern, tripId, serviceDate); + success = true; + } + + return success; } /** @@ -391,6 +371,46 @@ protected boolean clearRealtimeAddedTripPattern(String feedId) { ); } + private void removeRealtimeUpdatedTripTimes( + TripPattern tripPattern, + FeedScopedId tripId, + LocalDate serviceDate + ) { + SortedSet sortedTimetables = this.timetables.get(tripPattern); + if (sortedTimetables != null) { + TripTimes tripTimesToRemove = null; + for (Timetable timetable : sortedTimetables) { + if (timetable.isValidFor(serviceDate)) { + final TripTimes tripTimes = timetable.getTripTimes(tripId); + if (tripTimes == null) { + LOG.debug("No triptimes to remove for trip {}", tripId); + } else if (tripTimesToRemove != null) { + LOG.debug("Found two triptimes to remove for trip {}", tripId); + } else { + tripTimesToRemove = tripTimes; + } + } + } + + if (tripTimesToRemove != null) { + for (Timetable sortedTimetable : sortedTimetables) { + boolean isDirty = sortedTimetable.getTripTimes().remove(tripTimesToRemove); + if (isDirty) { + dirtyTimetables.add(sortedTimetable); + } + } + } + } + } + + /** + * Removes the latest added trip pattern from the cache. This should be done when removing the + * trip times from the timetable the trip has been added to. + */ + private void removeLastAddedTripPattern(FeedScopedId feedScopedTripId, LocalDate serviceDate) { + realtimeAddedTripPattern.remove(new TripIdAndServiceDate(feedScopedTripId, serviceDate)); + } + /** * Add the patterns to the stop index, only if they come from a modified pattern */ 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 60cfb72ef7d..461c15e8f66 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 @@ -119,9 +119,11 @@ public List getTripPatternsRunningOnDateCopy(LocalDate runni public List getTripPatternsStartingOnDateCopy(LocalDate date) { List tripPatternsRunningOnDate = getTripPatternsRunningOnDateCopy(date); + tripPatternsRunningOnDate.addAll(getTripPatternsRunningOnDateCopy(date.plusDays(1))); return tripPatternsRunningOnDate .stream() .filter(t -> t.getLocalDate().equals(date)) + .distinct() .collect(Collectors.toList()); } diff --git a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index 049d4933c51..2c2497044fb 100644 --- a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -272,6 +272,17 @@ public UpdateResult applyTripUpdates( serviceDate = localDateNow.get(); } + // Check whether trip id has been used for previously ADDED trip message and mark previously + // created trip as DELETED + var canceledPreviouslyAddedTrip = cancelPreviouslyAddedTrip( + tripId, + serviceDate, + CancelationType.DELETE + ); + // Remove previous realtime updates for this trip. This is necessary to avoid previous + // stop pattern modifications from persisting + this.buffer.removePreviousRealtimeUpdate(tripId, serviceDate); + uIndex += 1; LOG.debug("trip update #{} ({} updates) :", uIndex, tripUpdate.getStopTimeUpdateCount()); LOG.trace("{}", tripUpdate); @@ -297,8 +308,18 @@ public UpdateResult applyTripUpdates( tripId, serviceDate ); - case CANCELED -> handleCanceledTrip(tripId, serviceDate, CancelationType.CANCEL); - case DELETED -> handleCanceledTrip(tripId, serviceDate, CancelationType.DELETE); + case CANCELED -> handleCanceledTrip( + tripId, + serviceDate, + CancelationType.CANCEL, + canceledPreviouslyAddedTrip + ); + case DELETED -> handleCanceledTrip( + tripId, + serviceDate, + CancelationType.DELETE, + canceledPreviouslyAddedTrip + ); case REPLACEMENT -> validateAndHandleModifiedTrip( tripUpdate, tripDescriptor, @@ -435,11 +456,6 @@ private Result handleScheduledTrip( return UpdateError.result(tripId, NO_SERVICE_ON_DATE); } - // If this trip_id has been used for previously ADDED/MODIFIED trip message (e.g. when the - // sequence of stops has changed, and is now changing back to the originally scheduled one), - // mark that previously created trip as DELETED. - cancelPreviouslyAddedTrip(tripId, serviceDate, CancelationType.DELETE); - // Get new TripTimes based on scheduled timetable var result = pattern .getScheduledTimetable() @@ -686,10 +702,6 @@ private Result handleAddedTrip( "number of stop should match the number of stop time updates" ); - // Check whether trip id has been used for previously ADDED trip message and mark previously - // created trip as DELETED - cancelPreviouslyAddedTrip(tripId, serviceDate, CancelationType.DELETE); - Route route = getOrCreateRoute(tripDescriptor, tripId); // Create new Trip @@ -1104,10 +1116,6 @@ private Result handleModifiedTrip( var tripId = trip.getId(); cancelScheduledTrip(tripId, serviceDate, CancelationType.DELETE); - // Check whether trip id has been used for previously ADDED/REPLACEMENT trip message and mark it - // as DELETED - cancelPreviouslyAddedTrip(tripId, serviceDate, CancelationType.DELETE); - // Add new trip return addTripToGraphAndBuffer( trip, @@ -1122,19 +1130,17 @@ private Result handleModifiedTrip( private Result handleCanceledTrip( FeedScopedId tripId, final LocalDate serviceDate, - CancelationType markAsDeleted + CancelationType markAsDeleted, + boolean canceledPreviouslyAddedTrip ) { + // if previously a added trip was removed, there can't be a scheduled trip to remove + if (canceledPreviouslyAddedTrip) { + return Result.success(UpdateSuccess.noWarnings()); + } // Try to cancel scheduled trip final boolean cancelScheduledSuccess = cancelScheduledTrip(tripId, serviceDate, markAsDeleted); - // Try to cancel previously added trip - final boolean cancelPreviouslyAddedSuccess = cancelPreviouslyAddedTrip( - tripId, - serviceDate, - markAsDeleted - ); - - if (!cancelScheduledSuccess && !cancelPreviouslyAddedSuccess) { + if (!cancelScheduledSuccess) { debug(tripId, "No pattern found for tripId. Skipping cancellation."); return UpdateError.result(tripId, NO_TRIP_FOR_CANCELLATION_FOUND); }