From cb687e6ab0f09539418f833c6c628a169549fa4d Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 19 May 2023 18:35:18 +0200 Subject: [PATCH 01/26] CircleCI config, release scripts, and pom.xml updates. --- .circleci/config.yml | 52 +++++ .circleci/prepare_release | 235 +++++++++++++++++++++++ .circleci/release | 84 ++++++++ .circleci/update_deployment_config | 46 +++++ .gitignore | 1 + pom.xml | 12 +- src/main/java/EnturUpdatePomVersion.java | 135 +++++++++++++ 7 files changed, 559 insertions(+), 6 deletions(-) create mode 100644 .circleci/config.yml create mode 100755 .circleci/prepare_release create mode 100755 .circleci/release create mode 100755 .circleci/update_deployment_config create mode 100644 src/main/java/EnturUpdatePomVersion.java diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000000..d01a0c323e9 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +aliases: + - &jfrog-login + name: Rename jfrog environment variable for maven setting.xml + command: | + echo "export JFROG_USER=$ARTIFACTORY_USER" >> $BASH_ENV + echo "export JFROG_PASS=$ARTIFACTORY_PASSWORD" >> $BASH_ENV + - &post_build + name: Update deployment with OTP version + command: | + sudo apt-get update + sudo apt-get install libxml2-utils + chmod u+x .circleci/update_deployment_config + .circleci/update_deployment_config + +jobs: + build: + docker: + - image: cimg/openjdk:17.0 + environment: + DEBIAN_FRONTEND: "noninteractive" + MAVEN_OPTS: -Xmx6G + resource_class: large + steps: + - checkout + - restore_cache: + keys: + - dep-cache-{{ checksum "pom.xml" }} + # fallback to the most recent cache if there is no exact match for this pom.xml + - dep-cache- + - run: wget https://raw.githubusercontent.com/entur/circleci-toolbox-image-java11/master/tools/m2/settings.xml -O .circleci/settings.xml + - run: *jfrog-login + - run: mvn org.apache.maven.plugins:maven-dependency-plugin:3.1.0:go-offline -s .circleci/settings.xml + - save_cache: + paths: + - ~/.m2 + key: dep-cache-{{ checksum "pom.xml" }} + - run: mvn install -PprettierSkip org.apache.maven.plugins:maven-deploy-plugin:2.8.2:deploy -s .circleci/settings.xml -DaltDeploymentRepository=snapshots::default::https://entur2.jfrog.io/entur2/libs-release-local + - run: *post_build + +workflows: + version: 2 + release: + jobs: + - build: + name: build-release + context: global + filters: + branches: + only: + - otp2_entur_develop \ No newline at end of file diff --git a/.circleci/prepare_release b/.circleci/prepare_release new file mode 100755 index 00000000000..5b2ad0e23c4 --- /dev/null +++ b/.circleci/prepare_release @@ -0,0 +1,235 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ENTUR_DEVELOP=otp2_entur_develop +REMOTE_REPO="`git remote -v | grep "entur/OpenTripPlanner" | grep "push" | awk '{print $1;}'`" +STATUS_FILE=".prepare_release.tmp" +STATUS="" +DRY_RUN="" +OTP_BASE="" + +function main() { + setup "$@" + resumePreviousExecution + resetEnturDevelop + rebaseAndMergeExtBranch otp2_ext_config + rebaseAndMergeExtBranch otp2_ext_hack_sorlandsbanen + logSuccess +} + +function setup() { + if [[ $# -eq 2 && "$1" == "--dryRun" ]] ; then + DRY_RUN="--dryRun" + OTP_BASE="$2" + elif [[ $# -eq 1 ]] ; then + OTP_BASE="$1" + else + printHelp + exit 1 + fi + + echo "" + echo "Options: ${DRY_RUN}" + echo "Git base branch/commit: ${OTP_BASE}" + echo "Entur develop branch: ${ENTUR_DEVELOP}" + echo "Entur remote repo(pull/push): ${REMOTE_REPO}" + echo "" + + if git diff-index --quiet HEAD --; then + echo "" + echo "OK - No local changes, prepare to checkout '${ENTUR_DEVELOP}'" + echo "" + else + echo "" + echo "You have local modification, the script will abort. Nothing done!" + exit 2 + fi + + git fetch ${REMOTE_REPO} +} + +# This script create a status file '.prepare_release.tmp'. This file is used to resume the +# script in the same spot as where is left when the error occurred. This allow us to fix the +# problem (merge conflict or compile error) and re-run the script to complete the proses. +function resumePreviousExecution() { + readStatus + + if [[ -n "${STATUS}" ]] ; then + echo "" + echo "Resume: ${STATUS}?" + echo "" + echo " If all problems are resolved you may continue." + echo " Exit to clear status and start over." + echo "" + + ANSWER="" + while [[ ! "$ANSWER" =~ [yx] ]]; do + echo "Do you want to resume: [y:Yes, x:Exit]" + read ANSWER + done + + if [[ "${ANSWER}" == "x" ]] ; then + exit 0 + fi + fi +} + +function resetEnturDevelop() { + echo "" + echo "## ------------------------------------------------------------------------------------- ##" + echo "## RESET '${ENTUR_DEVELOP}' TO '${OTP_BASE}'" + echo "## ------------------------------------------------------------------------------------- ##" + echo "" + echo "Would you like to reset the '${ENTUR_DEVELOP}' to '${OTP_BASE}'? " + echo "" + + whatDoYouWant + + if [[ "${ANSWER}" == "y" ]] ; then + echo "" + echo "Checkout '${ENTUR_DEVELOP}'" + git checkout ${ENTUR_DEVELOP} + + echo "" + echo "Reset '${ENTUR_DEVELOP}' branch to '${OTP_BASE}' (hard)" + git reset --hard "${OTP_BASE}" + echo "" + fi +} + +function rebaseAndMergeExtBranch() { + EXT_BRANCH="$1" + EXT_STATUS_REBASE="Rebase '${EXT_BRANCH}'" + EXT_STATUS_COMPILE="Compile '${EXT_BRANCH}'" + + echo "" + echo "## ------------------------------------------------------------------------------------- ##" + echo "## REBASE AND MERGE '${EXT_BRANCH}' INTO '${ENTUR_DEVELOP}'" + echo "## ------------------------------------------------------------------------------------- ##" + echo "" + echo "You are about to rebase and merge '${EXT_BRANCH}' into '${ENTUR_DEVELOP}'. Any local" + echo "modification in the '${EXT_BRANCH}' will be lost." + echo "" + + whatDoYouWant + + if [[ "${ANSWER}" == "y" ]] ; then + echo "" + echo "Checkout '${EXT_BRANCH}'" + git checkout "${EXT_BRANCH}" + + echo "" + echo "Reset to '${REMOTE_REPO}/${EXT_BRANCH}'" + git reset --hard "${REMOTE_REPO}/${EXT_BRANCH}" + + echo "" + echo "Top 2 commits in '${EXT_BRANCH}'" + echo "-------------------------------------------------------------------------------------------" + git --no-pager log -2 + echo "-------------------------------------------------------------------------------------------" + echo "" + echo "You are about to rebase the TOP COMMIT ONLY(see above). Check that the " + echo "'${EXT_BRANCH}' only have ONE commit that you want to keep." + echo "" + + whatDoYouWant + + if [[ "${ANSWER}" == "y" ]] ; then + echo "" + echo "Rebase '${EXT_BRANCH}' onto '${ENTUR_DEVELOP}'" + setStatus "${EXT_STATUS_REBASE}" + git rebase --onto ${ENTUR_DEVELOP} HEAD~1 + fi + fi + + if [[ "${STATUS}" == "${EXT_STATUS_REBASE}" || "${STATUS}" == "${EXT_STATUS_COMPILE}" ]] ; then + # Reset status in case the test-compile fails. We need to do this because the status file + # is deleted after reading the status in the setup() function. + setStatus "${EXT_STATUS_COMPILE}" + + mvn clean test-compile + clearStatus + + echo "" + echo "Push '${EXT_BRANCH}'" + if [[ -z "${DRY_RUN}" ]] ; then + git push -f + else + echo "Skip: git push -f (--dryRun)" + fi + + echo "" + echo "Checkout '${ENTUR_DEVELOP}' and merge in '${EXT_BRANCH}'" + git checkout "${ENTUR_DEVELOP}" + git merge "${EXT_BRANCH}" + fi +} + +function logSuccess() { + echo "" + echo "## ------------------------------------------------------------------------------------- ##" + echo "## PREPARE RELEASE DONE -- SUCCESS" + echo "## ------------------------------------------------------------------------------------- ##" + echo " - '${REMOTE_REPO}/${ENTUR_DEVELOP}' reset to '${OTP_BASE}'" + echo " - 'otp2_ext_config' merged" + echo " - 'otp2_ext_hack_sorlandsbanen' merged" + echo "" + echo "" +} + +function whatDoYouWant() { + echo "" + ANSWER="" + + if [[ -n "${STATUS}" ]] ; then + # Skip until process is resumed + ANSWER="s" + else + while [[ ! "$ANSWER" =~ [ysx] ]]; do + echo "Do you want to continue: [y:Yes, s:Skip, x:Exit]" + read ANSWER + done + + if [[ "${ANSWER}" == "x" ]] ; then + exit 0 + fi + fi +} + +function setStatus() { + STATUS="$1" + echo "$STATUS" > "${STATUS_FILE}" +} + +function readStatus() { + if [[ -f "${STATUS_FILE}" ]] ; then + STATUS=`cat $STATUS_FILE` + rm "$STATUS_FILE" + else + STATUS="" + fi +} + +function clearStatus() { + STATUS="" + rm "${STATUS_FILE}" +} + +function printHelp() { + echo "" + echo "This script take ONE argument , the base **branch** or **commit** to use for the" + echo "release. The '${ENTUR_DEVELOP}' branch is reset to this commit and then the extension" + echo "branches is rebased onto that. The 'release' script is used to complete the release." + echo "It tag and push all changes to remote git repo." + echo "" + echo "Options:" + echo " --dryRun : Run script locally, nothing is pushed to remote server." + echo "" + echo "Usage:" + echo " $ .circleci/prepare_release otp/dev-2.x" + echo " $ .circleci/prepare_release --dryRun otp/dev-2.x" + echo "" +} + +main "$@" diff --git a/.circleci/release b/.circleci/release new file mode 100755 index 00000000000..9cac0d97958 --- /dev/null +++ b/.circleci/release @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +set -euo pipefail + +GIT_REMOTE_REPO="`git remote -v | grep "entur/OpenTripPlanner" | grep "push" | awk '{print $1;}'`" +MASTER_BRANCH=otp2_entur_develop +TAGS_FILE=target/git-entur-tags.txt +DRY_RUN="" + +function main() { + setup "$@" + listAllTags + mergeInOldReleaseWithNoChanges + setPomVersion + tagRelease + pushToRemote +} + +function setup() { + echo "" + echo "git fetch ${GIT_REMOTE_REPO}" + git fetch ${GIT_REMOTE_REPO} + + echo "Verify current branch is ${MASTER_BRANCH} " + git status | grep -q "On branch ${MASTER_BRANCH}" + + if [[ "${1+x}" == "--dryRun" ]] ; then + DRY_RUN="--dryRun" + fi +} + +function listAllTags() { + ## List all Entur tags to allow the UpdatePomVersion java program find the next version number + echo "" + echo "Dump all entur tags to ${TAGS_FILE}" + git tag -l | grep entur > ${TAGS_FILE} +} + +function setPomVersion() { + echo "" + echo "Update pom.xml with new version" + javac -d target/classes src/main/java/EnturUpdatePomVersion.java + + VERSION="`java -cp target/classes EnturUpdatePomVersion ${TAGS_FILE}`" + echo "" + echo "New version set: ${VERSION}" + echo "" + + ## Verify everything builds and tests run + echo "" + mvn clean test + + ## Add [ci skip] here before moving this to the CI server + echo "" + echo "Add and commit pom.xml" + git commit -m "Version ${VERSION}" pom.xml +} + +function mergeInOldReleaseWithNoChanges() { + echo "" + echo "Merge the old version of '${GIT_REMOTE_REPO}' into the new version. This only keep " + echo "a reference to the old version, the resulting tree of the merge is that of the new" + echo "branch head, effectively ignoring all changes from the old release." + git merge -s ours "${GIT_REMOTE_REPO}/${MASTER_BRANCH}" -m "Merge old release into '${MASTER_BRANCH}' - NO CHANGES COPIED OVER" +} + + +function tagRelease() { + echo "" + echo "Tag version ${VERSION}" + git tag -a v${VERSION} -m "Version ${VERSION}" +} + +function pushToRemote() { + echo "" + echo "Push pom.xml and new tag" + if [[ -z "${DRY_RUN}" ]] ; then + git push -f ${GIT_REMOTE_REPO} "v${VERSION}" ${MASTER_BRANCH} + else + echo "Skip: push -f ${GIT_REMOTE_REPO} "v${VERSION}" ${MASTER_BRANCH} (--dryRun)" + fi +} + +main "$@" diff --git a/.circleci/update_deployment_config b/.circleci/update_deployment_config new file mode 100755 index 00000000000..64550f19797 --- /dev/null +++ b/.circleci/update_deployment_config @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## +## This script run AFTER OTP is build on the CI Server. It checkout the +## deployment config and update the version number, commit and push. This +## trigger the deployment config build witch create and deploy a new OTP2 +## docker image. +## +## Note! There is no need to run this script locally, unless you want to debug it. +## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## + + +set -euo pipefail + +OTP_DEPLOYMENT_CONFIG=target/otp-deployment-config +DOCKER_FILE=Dockerfile + +VERSION=$(xmllint --xpath "//*[local-name()='project']/*[local-name()='version']/text()" pom.xml) + +echo "Checkout otp-deplyment-config in ${OTP_DEPLOYMENT_CONFIG}" +git clone -n https://github.com/entur/otp-deployment-config.git --depth 1 ${OTP_DEPLOYMENT_CONFIG} + +pushd ${OTP_DEPLOYMENT_CONFIG} + +echo "Checkout latest version of master Dockerfile" +git checkout master ${DOCKER_FILE} + +echo "Update OTP version number in ${DOCKER_FILE}" +if [[ "$OSTYPE" == "darwin"* ]]; then + sed -E -i '' "s/OTP_VERSION=[\.0-9]+-entur-[0-9]+/OTP_VERSION=${VERSION}/" ${DOCKER_FILE} +else + sed -E -i "s/OTP_VERSION=[\.0-9]+-entur-[0-9]+/OTP_VERSION=${VERSION}/" ${DOCKER_FILE} +fi + +if [[ "$CIRCLE_USERNAME" != "" ]]; then + git config user.email "circleci@entur.no" + git config user.name "circleci ($CIRCLE_USERNAME)" +fi + +echo "Add and commit Dockerfile" +git commit -m "New OTP2 Version ${VERSION}" ${DOCKER_FILE} + +echo "Push otp-deployment-config to GitHub" +git push + +popd \ No newline at end of file diff --git a/.gitignore b/.gitignore index a90f06cdcb0..3bc96654225 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .project .pydevproject .settings +.prepare_release.tmp .sonar .nvmrc *~ diff --git a/pom.xml b/pom.xml index fadcf31e056..99551256865 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 120 + EN-0051 29.2 2.48 @@ -84,11 +84,11 @@ - - github - OpenTripPlanner Maven Repository on Github Packages - https://maven.pkg.github.com/${GITHUB_REPOSITORY}/ - + + snapshots + entur2-snapshots + https://entur2.jfrog.io/entur2/libs-snapshot-local + diff --git a/src/main/java/EnturUpdatePomVersion.java b/src/main/java/EnturUpdatePomVersion.java new file mode 100644 index 00000000000..97bd72b3c6e --- /dev/null +++ b/src/main/java/EnturUpdatePomVersion.java @@ -0,0 +1,135 @@ +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.regex.Pattern; + +public class EnturUpdatePomVersion { + + private static final String VERSION_SEP = "-"; + private static final String ENTUR_PREFIX = "entur" + VERSION_SEP; + private static final Path POM_FILE = Path.of("pom.xml"); + + private final List tags = new ArrayList<>(); + private final List pomFile = new ArrayList<>(); + private String mainVersion; + private int versionNumber = 0; + + public static void main(String[] args) { + try { + new EnturUpdatePomVersion().withArgs(args).run(); + } catch (Exception e) { + System.err.println(e.getMessage()); + e.printStackTrace(System.err); + System.exit(10); + } + } + + private EnturUpdatePomVersion withArgs(String[] args) throws IOException { + if (args.length != 1 || args[0].matches("(?i)-h|--help")) { + printHelp(); + System.exit(1); + } + String arg = args[0]; + + if (arg.matches("\\d+")) { + versionNumber = resolveVersionFromNumericString(arg); + } else { + tags.addAll(readTagsFromFile(arg)); + } + return this; + } + + private void run() throws IOException { + readAndReplaceVersion(); + replacePomFile(); + } + + public void readAndReplaceVersion() throws IOException { + var pattern = Pattern.compile( + "(\\s*)(\\d+.\\d+.\\d+)" + + VERSION_SEP + + "(" + + ENTUR_PREFIX + + "\\d+|SNAPSHOT)(\\s*)" + ); + boolean found = false; + int i = 0; + + for (String line : Files.readAllLines(POM_FILE, UTF_8)) { + // Look for the version in the 25 first lines + if (!found) { + var m = pattern.matcher(line); + if (m.matches()) { + mainVersion = m.group(2); + String newVersion = mainVersion + VERSION_SEP + ENTUR_PREFIX + resolveVersionNumber(); + line = m.group(1) + newVersion + m.group(4); + System.out.println(newVersion); + found = true; + } + if (++i == 25) { + throw new IllegalStateException( + "Version not found in first 25 lines of the pom.xml file." + ); + } + } + pomFile.add(line); + } + if (!found) { + throw new IllegalStateException( + "Version not found in 'pom.xml'. Nothing matching pattern: " + pattern + ); + } + } + + public void replacePomFile() throws IOException { + Files.delete(POM_FILE); + Files.write(POM_FILE, pomFile, UTF_8); + } + + private static void printHelp() { + System.err.println( + "Use this small program to replace the OTP version '2.1.0-SNAPSHOT' \n" + + "with a new version number with a Entur qualifier like '2.1.0-entur-1'.\n" + + "\n" + + "Usage:\n" + + " $ java -cp .circleci UpdatePomVersion \n" + ); + } + + private int resolveVersionNumber() { + var pattern = Pattern.compile("v" + mainVersion + VERSION_SEP + ENTUR_PREFIX + "(\\d+)"); + int maxTagVersion = tags + .stream() + .mapToInt(tag -> { + var m = pattern.matcher(tag); + return m.matches() ? Integer.parseInt(m.group(1)) : -999; + }) + .max() + .orElse(-999); + + return 1 + Math.max(maxTagVersion, versionNumber); + } + + public static int resolveVersionFromNumericString(String arg) { + try { + return Integer.parseInt(arg); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Unable to parse input, decimal number expected: '" + arg + "'" + ); + } + } + + private static Collection readTagsFromFile(String arg) throws IOException { + var tags = Files.readAllLines(Path.of(arg)); + if (tags.isEmpty()) { + throw new IllegalStateException("Unable to load git tags from file: " + arg); + } + return tags; + } +} From cbacd6be583e5535323b1e0f0678ed690f3fcce3 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 26 Sep 2023 15:13:38 +0200 Subject: [PATCH 02/26] Version 2.5.0-entur-1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 99551256865..95179dd49bc 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.5.0-SNAPSHOT + 2.5.0-entur-1 jar From 171867d68eb8344c1d0a1f526dedb38cf984c6dc Mon Sep 17 00:00:00 2001 From: Hannes Junnila Date: Fri, 9 Dec 2022 09:38:06 +0200 Subject: [PATCH 03/26] =?UTF-8?q?hack:=20Add=20train=20option=20for=20S?= =?UTF-8?q?=C3=B8rlandsbanen.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/Configuration.md | 1 + docs/RouteRequest.md | 1 + src/ext/graphql/transmodelapi/schema.graphql | 2 + .../ConcurrentCompositeWorker.java | 53 +++++++ .../sorlandsbanen/EnturHackSorlandsBanen.java | 141 ++++++++++++++++++ .../ext/sorlandsbanen/PathKey.java | 51 +++++++ .../RaptorWorkerResultComposite.java | 88 +++++++++++ .../transmodelapi/model/plan/TripQuery.java | 8 + .../common/RequestToPreferencesMapper.java | 1 + .../api/common/RoutingResource.java | 3 + .../framework/application/OTPFeature.java | 1 + .../raptor/api/request/RaptorRequest.java | 4 + .../api/request/RaptorRequestBuilder.java | 6 + .../service/RangeRaptorDynamicSearch.java | 10 +- .../raptoradapter/router/TransitRouter.java | 3 +- .../transit/cost/DefaultCostCalculator.java | 16 ++ .../transit/cost/FactorStrategy.java | 2 +- .../cost/IndexBasedFactorStrategy.java | 4 +- .../transit/mappers/RaptorRequestMapper.java | 16 +- .../RaptorRoutingRequestTransitData.java | 33 ++++ .../preference/TransitPreferences.java | 17 +++ .../routerequest/RouteRequestConfig.java | 7 + .../raptor/RaptorArchitectureTest.java | 6 +- 23 files changed, 464 insertions(+), 10 deletions(-) create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java diff --git a/docs/Configuration.md b/docs/Configuration.md index 5a33c70fd43..f91a7caf1b3 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -228,6 +228,7 @@ Here is a list of all features which can be toggled on/off and their default val | `ConsiderPatternsForDirectTransfers` | Enable limiting transfers so that there is only a single transfer to each pattern. | ✓️ | | | `DebugClient` | Enable the debug web client located at the root of the web server. | ✓️ | | | `FloatingBike` | Enable floating bike routing. | ✓️ | | +| `HackSorlandsbanen` | Includ Sørlandsbanen | | ✓️ | | `GtfsGraphQlApi` | Enable GTFS GraphQL API. | ✓️ | ✓️ | | `MinimumTransferTimeIsDefinitive` | If the minimum transfer time is a lower bound (default) or the definitive time for the transfer. Set this to `true` if you want to set a transfer time lower than what OTP derives from OSM data. | | | | `OptimizeTransfers` | OTP will inspect all itineraries found and optimize where (which stops) the transfer will happen. Waiting time, priority and guaranteed transfers are taken into account. | ✓️ | | diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index f814753df4a..b6284d22e4d 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -46,6 +46,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe | elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 | | elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | | escalatorReluctance | `double` | A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time | *Optional* | `1.5` | 2.4 | +| extraSearchCoachReluctance | `double` | TODO | *Optional* | `0.0` | 2.1 | | geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | | ignoreRealtimeUpdates | `boolean` | When true, realtime updates are ignored during this search. | *Optional* | `false` | 2.0 | | [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql index f568c46f7fc..da202d1d7f6 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/ext/graphql/transmodelapi/schema.graphql @@ -790,6 +790,8 @@ type QueryType { dateTime: DateTime, "Debug the itinerary-filter-chain. OTP will attach a system notice to itineraries instead of removing them. This is very convenient when tuning the filters." debugItineraryFilter: Boolean = false, + "FOR TESTING ONLY" + extraSearchCoachReluctance: Float, "A list of filters for which trips should be included. A trip will be included if it matches with at least one filter. An empty list of filters means that all trips should be included. If a search include this parameter, \"whiteListed\", \"banned\" & \"modes.transportModes\" filters will be ignored." filters: [TripFilterInput!], "The start location" diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java new file mode 100644 index 00000000000..e80e106ce1e --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java @@ -0,0 +1,53 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.opentripplanner.framework.application.OTPFeature; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; +import org.opentripplanner.raptor.api.response.StopArrivals; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.multicriteria.McRaptorWorkerResult; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TripScheduleWithOffset; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class ConcurrentCompositeWorker implements RaptorWorker { + + private static final Logger LOG = LoggerFactory.getLogger(ConcurrentCompositeWorker.class); + + private final RaptorWorker mainWorker; + private final RaptorWorker alternativeWorker; + + ConcurrentCompositeWorker(RaptorWorker mainWorker, RaptorWorker alternativeWorker) { + this.mainWorker = mainWorker; + this.alternativeWorker = alternativeWorker; + } + + @Override + public RaptorWorkerResult route() { + if (OTPFeature.ParallelRouting.isOn()) { + var mainResultFuture = CompletableFuture.supplyAsync(mainWorker::route); + var alternativeResultFuture = CompletableFuture.supplyAsync(alternativeWorker::route); + + try { + return new RaptorWorkerResultComposite<>( + mainResultFuture.get(), + alternativeResultFuture.get() + ); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } else { + var mainResult = mainWorker.route(); + var alternativeResult = alternativeWorker.route(); + return new RaptorWorkerResultComposite<>(mainResult, alternativeResult); + } + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java new file mode 100644 index 00000000000..2b52e850f42 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java @@ -0,0 +1,141 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.function.Function; +import org.opentripplanner.framework.geometry.SphericalDistanceLibrary; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.model.GenericLocation; +import org.opentripplanner.raptor.api.model.RaptorAccessEgress; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.request.RaptorRequest; +import org.opentripplanner.raptor.api.request.SearchParams; +import org.opentripplanner.raptor.configure.RaptorConfig; +import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; +import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.FactorStrategy; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.IndexBasedFactorStrategy; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.RaptorRoutingRequestTransitData; +import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.site.StopLocation; + +public class EnturHackSorlandsBanen { + + private static final double SOUTH_BOARDER_LIMIT = 59.1; + private static final int MIN_DISTANCE_LIMIT = 120_000; + + public static boolean match(RaptorRequest mcRequest) { + return mcRequest.extraSearchCoachReluctance > 0.1; + } + + public static RaptorWorker worker( + RaptorConfig config, + RaptorTransitDataProvider transitData, + RaptorRequest mcRequest, + Heuristics destinationHeuristics + ) { + //noinspection unchecked + RaptorTransitDataProvider altTransitData = (RaptorTransitDataProvider) ( + (RaptorRoutingRequestTransitData) transitData + ).enturHackSorlandsbanen(mapFactors(mcRequest.extraSearchCoachReluctance)); + + return new ConcurrentCompositeWorker<>( + config.createMcWorker(transitData, mcRequest, destinationHeuristics), + config.createMcWorker(altTransitData, mcRequest, destinationHeuristics) + ); + } + + public static RaptorRequest enableHack( + RaptorRequest raptorRequest, + RouteRequest request, + TransitLayer transitLayer + ) { + if (request.preferences().transit().extraSearchCoachReluctance() < 0.1) { + return raptorRequest; + } + + SearchParams params = raptorRequest.searchParams(); + + WgsCoordinate from = findStopCoordinate(request.from(), params.accessPaths(), transitLayer); + WgsCoordinate to = findStopCoordinate(request.to(), params.egressPaths(), transitLayer); + + if (from.latitude() > SOUTH_BOARDER_LIMIT && to.latitude() > SOUTH_BOARDER_LIMIT) { + return raptorRequest; + } + + double distanceMeters = SphericalDistanceLibrary.distance( + from.latitude(), + from.longitude(), + to.latitude(), + to.longitude() + ); + + if (distanceMeters < MIN_DISTANCE_LIMIT) { + return raptorRequest; + } + + raptorRequest.extraSearchCoachReluctance = + request.preferences().transit().extraSearchCoachReluctance(); + return raptorRequest; + } + + /* private methods */ + + private static Function mapFactors( + final double extraSearchCoachReluctance + ) { + return (FactorStrategy originalFactors) -> { + int[] modeReluctance = new int[TransitMode.values().length]; + for (TransitMode mode : TransitMode.values()) { + int index = mode.ordinal(); + int originalFactor = originalFactors.factor(index); + modeReluctance[index] = + mode == TransitMode.COACH + ? (int) (extraSearchCoachReluctance * originalFactor + 0.5) + : originalFactor; + } + return new IndexBasedFactorStrategy(modeReluctance); + }; + } + + /** + * Find a coordinate matching the given location, in order: + * - First return the coordinate of the location if it exists. + * - Then loop through the access/egress stops and try to find the + * stop or station given by the location id, return the stop/station coordinate. + * - Return the fist stop in the access/egress list coordinate. + */ + @SuppressWarnings("ConstantConditions") + private static WgsCoordinate findStopCoordinate( + GenericLocation location, + Collection accessEgress, + TransitLayer transitLayer + ) { + if (location.lat != null) { + return new WgsCoordinate(location.lat, location.lng); + } + + StopLocation firstStop = null; + for (RaptorAccessEgress it : accessEgress) { + StopLocation stop = transitLayer.getStopByIndex(it.stop()); + if (stop.getId().equals(location.stopId)) { + return stop.getCoordinate(); + } + if (idIsParentStation(stop, location.stopId)) { + return stop.getParentStation().getCoordinate(); + } + if (firstStop == null) { + firstStop = stop; + } + } + return firstStop.getCoordinate(); + } + + private static boolean idIsParentStation(StopLocation stop, FeedScopedId pId) { + return stop.getParentStation() != null && stop.getParentStation().getId().equals(pId); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java new file mode 100644 index 00000000000..94267b3e7c2 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java @@ -0,0 +1,51 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; + +final class PathKey { + + private final int hash; + + PathKey(RaptorPath path) { + this.hash = hash(path); + } + + private static int hash(RaptorPath path) { + if (path == null) { + return 0; + } + int result = 1; + + PathLeg leg = path.accessLeg(); + + while (!leg.isEgressLeg()) { + result = 31 * result + leg.toStop(); + result = 31 * result + leg.toTime(); + + if (leg.isTransitLeg()) { + result = 31 * result + leg.asTransitLeg().trip().pattern().debugInfo().hashCode(); + } + leg = leg.nextLeg(); + } + result = 31 * result + leg.toTime(); + + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o.getClass() != PathKey.class) { + return false; + } + return hash == ((PathKey) o).hash; + } + + @Override + public int hashCode() { + return hash; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java new file mode 100644 index 00000000000..094e652fc4c --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java @@ -0,0 +1,88 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.internalapi.SingleCriteriaStopArrivals; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TripScheduleWithOffset; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RaptorWorkerResultComposite + implements RaptorWorkerResult { + + private static final Logger LOG = LoggerFactory.getLogger(RaptorWorkerResultComposite.class); + + private RaptorWorkerResult mainResult; + private RaptorWorkerResult alternativeResult; + + public RaptorWorkerResultComposite( + RaptorWorkerResult mainResult, + RaptorWorkerResult alternativeResult + ) { + this.mainResult = mainResult; + this.alternativeResult = alternativeResult; + } + + @Override + public Collection> extractPaths() { + Map> paths = new HashMap<>(); + addAll(paths, mainResult.extractPaths()); + addExtraRail(paths, alternativeResult.extractPaths()); + return paths.values(); + } + + @Override + public SingleCriteriaStopArrivals extractBestOverallArrivals() { + return mainResult.extractBestOverallArrivals(); + } + + @Override + public SingleCriteriaStopArrivals extractBestTransitArrivals() { + return mainResult.extractBestTransitArrivals(); + } + + @Override + public SingleCriteriaStopArrivals extractBestNumberOfTransfers() { + return mainResult.extractBestNumberOfTransfers(); + } + + @Override + public boolean isDestinationReached() { + return mainResult.isDestinationReached(); + } + + private void addExtraRail(Map> map, Collection> paths) { + paths.forEach(p -> { + if (hasRail(p)) { + var v = map.put(new PathKey(p), p); + LOG.debug("Ex.Rail {} : {}", (v == null ? "ADD " : "SKIP"), p); + } else { + LOG.debug("Ex. NOT Rail : {}", p); + } + }); + } + + private void addAll(Map> map, Collection> paths) { + paths.forEach(p -> { + var v = map.put(new PathKey(p), p); + LOG.debug("Normal {} : {}", (v == null ? "ADD " : "SKIP"), p); + }); + } + + private static boolean hasRail(RaptorPath path) { + return path + .legStream() + .filter(PathLeg::isTransitLeg) + .anyMatch(leg -> { + var trip = (TripScheduleWithOffset) leg.asTransitLeg().trip(); + var mode = trip.getOriginalTripPattern().getMode(); + return mode == TransitMode.RAIL; + }); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java index d4c4e424c43..b9207e57ee1 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java @@ -554,6 +554,14 @@ public static GraphQLFieldDefinition create( ) .build() ) + .argument( + GraphQLArgument + .newArgument() + .name("extraSearchCoachReluctance") + .description("FOR TESTING ONLY") + .type(Scalars.GraphQLFloat) + .build() + ) .dataFetcher(environment -> new TransmodelGraphQLPlanner().plan(environment)) .build(); } diff --git a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java index 20ca8054308..ff61f201624 100644 --- a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java @@ -89,6 +89,7 @@ private BoardAndAlightSlack mapTransit() { req.relaxTransitSearchGeneralizedCostAtDestination, value -> tr.withRaptor(it -> it.withRelaxGeneralizedCostAtDestination(value)) ); + setIfNotNull(req.extraSearchCoachReluctance, tr::setExtraSearchCoachReluctance); }); return new BoardAndAlightSlack( diff --git a/src/main/java/org/opentripplanner/api/common/RoutingResource.java b/src/main/java/org/opentripplanner/api/common/RoutingResource.java index e60bfe41b82..f214f0126c6 100644 --- a/src/main/java/org/opentripplanner/api/common/RoutingResource.java +++ b/src/main/java/org/opentripplanner/api/common/RoutingResource.java @@ -200,6 +200,9 @@ public abstract class RoutingResource { @QueryParam("carReluctance") protected Double carReluctance; + @QueryParam("extraSearchCoachReluctance") + protected Double extraSearchCoachReluctance; + /** * How much worse is waiting for a transit vehicle than being on a transit vehicle, as a * multiplier. The default value treats wait and on-vehicle time as the same. diff --git a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 0854032a665..d08c4a731a4 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -29,6 +29,7 @@ public enum OTPFeature { ), DebugClient(true, false, "Enable the debug web client located at the root of the web server."), FloatingBike(true, false, "Enable floating bike routing."), + HackSorlandsbanen(false, true, "Includ Sørlandsbanen"), GtfsGraphQlApi(true, true, "Enable GTFS GraphQL API."), /** * If this feature flag is switched on, then the minimum transfer time is not the minimum transfer diff --git a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java index 1f0621b8d36..6b99e7ae238 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java @@ -29,6 +29,9 @@ public class RaptorRequest { private final DebugRequest debug; private final RaptorTimers performanceTimers; + // HACK SØRLANDSBANEN + public double extraSearchCoachReluctance = 0.0; + private RaptorRequest() { searchParams = SearchParams.defaults(); profile = RaptorProfile.MULTI_CRITERIA; @@ -49,6 +52,7 @@ private RaptorRequest() { this.multiCriteria = builder.multiCriteria(); this.performanceTimers = builder.performanceTimers(); this.debug = builder.debug().build(); + this.extraSearchCoachReluctance = builder.extraSearchCoachReluctance; verify(); } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java index ed2aab3de20..7bb8b538843 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java @@ -34,6 +34,9 @@ public class RaptorRequestBuilder { // Performance monitoring private RaptorTimers performanceTimers; + /** HACK SØRLANDSBANEN */ + public double extraSearchCoachReluctance; + // Algorithm private RaptorProfile profile; @@ -60,6 +63,9 @@ public RaptorRequestBuilder() { // Debug this.debug = new DebugRequestBuilder(defaults.debug()); + + // HACK SØRLANDSBANEN + this.extraSearchCoachReluctance = defaults.extraSearchCoachReluctance; } public SearchParamsBuilder searchParams() { diff --git a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java index fe8bb884912..86b10f7d96f 100644 --- a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java +++ b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java @@ -11,6 +11,8 @@ import java.util.concurrent.Future; import java.util.stream.Collectors; import javax.annotation.Nullable; +import org.opentripplanner.ext.sorlandsbanen.EnturHackSorlandsBanen; +import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.raptor.RaptorService; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; @@ -134,7 +136,13 @@ private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request) // Create worker if (request.profile().is(MULTI_CRITERIA)) { - raptorWorker = config.createMcWorker(transitData, request, getDestinationHeuristics()); + // HACK SØRLANDSBANEN + if (OTPFeature.HackSorlandsbanen.isOn() && EnturHackSorlandsBanen.match(request)) { + raptorWorker = + EnturHackSorlandsBanen.worker(config, transitData, request, getDestinationHeuristics()); + } else { + raptorWorker = config.createMcWorker(transitData, request, getDestinationHeuristics()); + } } else { raptorWorker = config.createStdWorker(transitData, request); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index e8a4c9e3311..1f1a2dcdd90 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -128,7 +128,8 @@ private TransitRouterResult route() { accessEgresses.getAccesses(), accessEgresses.getEgresses(), accessEgresses.calculateMaxAccessTimePenalty(), - serverContext.meterRegistry() + serverContext.meterRegistry(), + transitLayer ); // Route transit diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java index b738d63054e..4f39b2631fe 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java @@ -1,5 +1,6 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit.cost; +import java.util.function.Function; import javax.annotation.Nullable; import org.opentripplanner.model.transfer.TransferConstraint; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; @@ -211,4 +212,19 @@ private int boardingCostConstrainedTransfer( // fallback to regular transfer return boardingCostRegularTransfer(firstBoarding, prevArrivalTime, boardStop, boardTime); } + + /*-- HACK SØRLANDSBANEN :: BEGIN --*/ + + public DefaultCostCalculator( + DefaultCostCalculator original, + Function modeReluctanceMapper + ) { + this.boardCostOnly = original.boardCostOnly; + this.boardAndTransferCost = original.boardAndTransferCost; + this.waitFactor = original.waitFactor; + this.transferCostOnly = original.transferCostOnly; + this.transitFactors = modeReluctanceMapper.apply(original.transitFactors); + this.stopTransferCost = original.stopTransferCost; + } + /*-- HACK SØRLANDSBANEN :: END --*/ } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java index fba2a7cfe38..8a14c27566d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java @@ -6,7 +6,7 @@ *

* The class and methods are {@code final} to help the JIT compiler optimize the use of this class. */ -interface FactorStrategy { +public interface FactorStrategy { /** * Return the factor for the given index. */ diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java index 152f199248c..4c66b5b452e 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java @@ -6,12 +6,12 @@ * This class keep a facto for each index and the minimum factor for fast retrieval during Raptor * search. */ -final class IndexBasedFactorStrategy implements FactorStrategy { +public final class IndexBasedFactorStrategy implements FactorStrategy { private final int[] factors; private final int minFactor; - private IndexBasedFactorStrategy(int[] factors) { + public IndexBasedFactorStrategy(int[] factors) { this.factors = factors; this.minFactor = findMinimumFactor(factors); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index 729cf839a27..7e2c59444c1 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -7,6 +7,7 @@ import java.time.Instant; import java.time.ZonedDateTime; import java.util.Collection; +import org.opentripplanner.ext.sorlandsbanen.EnturHackSorlandsBanen; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; @@ -15,6 +16,7 @@ import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; import org.opentripplanner.raptor.rangeraptor.SystemErrDebugLogger; import org.opentripplanner.routing.algorithm.raptoradapter.router.performance.PerformanceTimersForRaptor; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; import org.opentripplanner.routing.api.request.RouteRequest; @@ -28,6 +30,8 @@ public class RaptorRequestMapper { private final boolean isMultiThreadedEnbled; private final MeterRegistry meterRegistry; + private final TransitLayer transitLayer; + private RaptorRequestMapper( RouteRequest request, boolean isMultiThreaded, @@ -35,13 +39,15 @@ private RaptorRequestMapper( Collection egressPaths, Duration searchWindowAccessSlack, long transitSearchTimeZeroEpocSecond, - MeterRegistry meterRegistry + MeterRegistry meterRegistry, + TransitLayer transitLayer ) { this.request = request; this.isMultiThreadedEnbled = isMultiThreaded; this.accessPaths = accessPaths; this.egressPaths = egressPaths; this.searchWindowAccessSlack = searchWindowAccessSlack; + this.transitLayer = transitLayer; this.transitSearchTimeZeroEpocSecond = transitSearchTimeZeroEpocSecond; this.meterRegistry = meterRegistry; } @@ -53,7 +59,8 @@ public static RaptorRequest mapRequest( Collection accessPaths, Collection egressPaths, Duration searchWindowAccessSlack, - MeterRegistry meterRegistry + MeterRegistry meterRegistry, + TransitLayer transitLayer ) { return new RaptorRequestMapper( request, @@ -62,7 +69,8 @@ public static RaptorRequest mapRequest( egressPaths, searchWindowAccessSlack, transitSearchTimeZero.toEpochSecond(), - meterRegistry + meterRegistry, + transitLayer ) .doMap(); } @@ -166,7 +174,7 @@ private RaptorRequest doMap() { ) ); - return builder.build(); + return EnturHackSorlandsBanen.enableHack(builder.build(), request, transitLayer); } private int relativeTime(Instant time) { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java index a0b62414181..df5996009ec 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java @@ -4,6 +4,7 @@ import java.util.BitSet; import java.util.Iterator; import java.util.List; +import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.opentripplanner.framework.application.OTPFeature; @@ -27,6 +28,8 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.constrainedtransfer.ConstrainedBoardingSearch; import org.opentripplanner.routing.algorithm.raptoradapter.transit.constrainedtransfer.ConstrainedTransfersForPatterns; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.CostCalculatorFactory; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.DefaultCostCalculator; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.FactorStrategy; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.GeneralizedCostParametersMapper; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.transit.model.network.RoutingTripPattern; @@ -241,4 +244,34 @@ public RaptorConstrainedBoardingSearch transferConstraintsReverseS } return new ConstrainedBoardingSearch(false, toStopTransfers, fromStopTransfers); } + + /*-- HACK SØRLANDSBANEN :: BEGIN --*/ + + private RaptorRoutingRequestTransitData( + RaptorRoutingRequestTransitData original, + Function mapFactors + ) { + this.transitLayer = original.transitLayer; + this.transitSearchTimeZero = original.transitSearchTimeZero; + this.activeTripPatternsPerStop = original.activeTripPatternsPerStop; + this.patternIndex = original.patternIndex; + this.transferIndex = original.transferIndex; + this.transferService = original.transferService; + this.constrainedTransfers = original.constrainedTransfers; + this.validTransitDataStartTime = original.validTransitDataStartTime; + this.validTransitDataEndTime = original.validTransitDataEndTime; + this.generalizedCostCalculator = + new DefaultCostCalculator<>( + (DefaultCostCalculator) original.generalizedCostCalculator, + mapFactors + ); + this.slackProvider = original.slackProvider(); + } + + public RaptorTransitDataProvider enturHackSorlandsbanen( + Function mapFactors + ) { + return new RaptorRoutingRequestTransitData(this, mapFactors); + } + /*-- HACK SØRLANDSBANEN :: END --*/ } diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java index 7da4516d782..96945693cdd 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java @@ -31,6 +31,7 @@ public final class TransitPreferences implements Serializable { private final boolean includePlannedCancellations; private final boolean includeRealtimeCancellations; private final RaptorPreferences raptor; + private final double extraSearchCoachReluctance; private TransitPreferences() { this.boardSlack = this.alightSlack = DurationForEnum.of(TransitMode.class).build(); @@ -41,6 +42,7 @@ private TransitPreferences() { this.includePlannedCancellations = false; this.includeRealtimeCancellations = false; this.raptor = RaptorPreferences.DEFAULT; + this.extraSearchCoachReluctance = 0.0; } private TransitPreferences(Builder builder) { @@ -53,6 +55,7 @@ private TransitPreferences(Builder builder) { this.includePlannedCancellations = builder.includePlannedCancellations; this.includeRealtimeCancellations = builder.includeRealtimeCancellations; this.raptor = requireNonNull(builder.raptor); + this.extraSearchCoachReluctance = builder.extraSearchCoachReluctance; } public static Builder of() { @@ -153,6 +156,11 @@ public RaptorPreferences raptor() { return raptor; } + /** Zero means turned off. HACK SØRLANDSBANEN */ + public double extraSearchCoachReluctance() { + return extraSearchCoachReluctance; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -167,6 +175,7 @@ public boolean equals(Object o) { alightSlack.equals(that.alightSlack) && reluctanceForMode.equals(that.reluctanceForMode) && unpreferredCost.equals(that.unpreferredCost) && + extraSearchCoachReluctance == that.extraSearchCoachReluctance && raptor.equals(that.raptor) ); } @@ -182,6 +191,7 @@ public int hashCode() { ignoreRealtimeUpdates, includePlannedCancellations, includeRealtimeCancellations, + extraSearchCoachReluctance, raptor ); } @@ -229,6 +239,7 @@ public static class Builder { private boolean includePlannedCancellations; private boolean includeRealtimeCancellations; private RaptorPreferences raptor; + private double extraSearchCoachReluctance; public Builder(TransitPreferences original) { this.original = original; @@ -241,6 +252,7 @@ public Builder(TransitPreferences original) { this.includePlannedCancellations = original.includePlannedCancellations; this.includeRealtimeCancellations = original.includeRealtimeCancellations; this.raptor = original.raptor; + this.extraSearchCoachReluctance = original.extraSearchCoachReluctance; } public TransitPreferences original() { @@ -305,6 +317,11 @@ public Builder withRaptor(Consumer body) { return this; } + public Builder setExtraSearchCoachReluctance(double extraSearchCoachReluctance) { + this.extraSearchCoachReluctance = extraSearchCoachReluctance; + return this; + } + public Builder apply(Consumer body) { body.accept(this); return this; diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index c89670b7e0a..db212bd31d3 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -352,6 +352,13 @@ The board time is added to the time when going from the stop (offboard) to onboa ) .asDoubleOptional() .ifPresent(it::withRelaxGeneralizedCostAtDestination) + ) + .setExtraSearchCoachReluctance( + c + .of("extraSearchCoachReluctance") + .since(V2_1) + .summary("TODO") + .asDouble(dft.extraSearchCoachReluctance()) ); } diff --git a/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java b/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java index 39022da42ca..aeca5a36ebf 100644 --- a/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java +++ b/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java @@ -39,6 +39,9 @@ public class RaptorArchitectureTest { private static final Package RR_STANDARD = RANGE_RAPTOR.subPackage("standard"); private static final Package RR_STD_CONFIGURE = RR_STANDARD.subPackage("configure"); private static final Package RR_CONTEXT = RANGE_RAPTOR.subPackage("context"); + private static final Package EXT_SORLANDSBANAN_HACK = Package.of( + "org.opentripplanner.ext.sorlandsbanen" + ); /** * Packages used by standard-range-raptor and multi-criteria-range-raptor. @@ -200,7 +203,8 @@ void enforcePackageDependenciesInRaptorService() { RAPTOR_UTIL, CONFIGURE, RR_INTERNAL_API, - RR_TRANSIT + RR_TRANSIT, + EXT_SORLANDSBANAN_HACK ) .verify(); } From bdcb062aa147b3ff5c03d975ecfe6c06b4c81fe8 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Wed, 27 Sep 2023 12:43:18 +0200 Subject: [PATCH 04/26] Version 2.5.0-entur-2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 95179dd49bc..02779a0b1e0 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.5.0-entur-1 + 2.5.0-entur-2 jar From 53fdcf60883322435cc89d89d5b98039a3831f1c Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 19 May 2023 18:35:18 +0200 Subject: [PATCH 05/26] CircleCI config, release scripts, and pom.xml updates. --- .circleci/config.yml | 52 +++++ .circleci/prepare_release | 235 +++++++++++++++++++++++ .circleci/release | 84 ++++++++ .circleci/update_deployment_config | 46 +++++ .gitignore | 1 + pom.xml | 12 +- src/main/java/EnturUpdatePomVersion.java | 135 +++++++++++++ 7 files changed, 559 insertions(+), 6 deletions(-) create mode 100644 .circleci/config.yml create mode 100755 .circleci/prepare_release create mode 100755 .circleci/release create mode 100755 .circleci/update_deployment_config create mode 100644 src/main/java/EnturUpdatePomVersion.java diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000000..d01a0c323e9 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +aliases: + - &jfrog-login + name: Rename jfrog environment variable for maven setting.xml + command: | + echo "export JFROG_USER=$ARTIFACTORY_USER" >> $BASH_ENV + echo "export JFROG_PASS=$ARTIFACTORY_PASSWORD" >> $BASH_ENV + - &post_build + name: Update deployment with OTP version + command: | + sudo apt-get update + sudo apt-get install libxml2-utils + chmod u+x .circleci/update_deployment_config + .circleci/update_deployment_config + +jobs: + build: + docker: + - image: cimg/openjdk:17.0 + environment: + DEBIAN_FRONTEND: "noninteractive" + MAVEN_OPTS: -Xmx6G + resource_class: large + steps: + - checkout + - restore_cache: + keys: + - dep-cache-{{ checksum "pom.xml" }} + # fallback to the most recent cache if there is no exact match for this pom.xml + - dep-cache- + - run: wget https://raw.githubusercontent.com/entur/circleci-toolbox-image-java11/master/tools/m2/settings.xml -O .circleci/settings.xml + - run: *jfrog-login + - run: mvn org.apache.maven.plugins:maven-dependency-plugin:3.1.0:go-offline -s .circleci/settings.xml + - save_cache: + paths: + - ~/.m2 + key: dep-cache-{{ checksum "pom.xml" }} + - run: mvn install -PprettierSkip org.apache.maven.plugins:maven-deploy-plugin:2.8.2:deploy -s .circleci/settings.xml -DaltDeploymentRepository=snapshots::default::https://entur2.jfrog.io/entur2/libs-release-local + - run: *post_build + +workflows: + version: 2 + release: + jobs: + - build: + name: build-release + context: global + filters: + branches: + only: + - otp2_entur_develop \ No newline at end of file diff --git a/.circleci/prepare_release b/.circleci/prepare_release new file mode 100755 index 00000000000..5b2ad0e23c4 --- /dev/null +++ b/.circleci/prepare_release @@ -0,0 +1,235 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ENTUR_DEVELOP=otp2_entur_develop +REMOTE_REPO="`git remote -v | grep "entur/OpenTripPlanner" | grep "push" | awk '{print $1;}'`" +STATUS_FILE=".prepare_release.tmp" +STATUS="" +DRY_RUN="" +OTP_BASE="" + +function main() { + setup "$@" + resumePreviousExecution + resetEnturDevelop + rebaseAndMergeExtBranch otp2_ext_config + rebaseAndMergeExtBranch otp2_ext_hack_sorlandsbanen + logSuccess +} + +function setup() { + if [[ $# -eq 2 && "$1" == "--dryRun" ]] ; then + DRY_RUN="--dryRun" + OTP_BASE="$2" + elif [[ $# -eq 1 ]] ; then + OTP_BASE="$1" + else + printHelp + exit 1 + fi + + echo "" + echo "Options: ${DRY_RUN}" + echo "Git base branch/commit: ${OTP_BASE}" + echo "Entur develop branch: ${ENTUR_DEVELOP}" + echo "Entur remote repo(pull/push): ${REMOTE_REPO}" + echo "" + + if git diff-index --quiet HEAD --; then + echo "" + echo "OK - No local changes, prepare to checkout '${ENTUR_DEVELOP}'" + echo "" + else + echo "" + echo "You have local modification, the script will abort. Nothing done!" + exit 2 + fi + + git fetch ${REMOTE_REPO} +} + +# This script create a status file '.prepare_release.tmp'. This file is used to resume the +# script in the same spot as where is left when the error occurred. This allow us to fix the +# problem (merge conflict or compile error) and re-run the script to complete the proses. +function resumePreviousExecution() { + readStatus + + if [[ -n "${STATUS}" ]] ; then + echo "" + echo "Resume: ${STATUS}?" + echo "" + echo " If all problems are resolved you may continue." + echo " Exit to clear status and start over." + echo "" + + ANSWER="" + while [[ ! "$ANSWER" =~ [yx] ]]; do + echo "Do you want to resume: [y:Yes, x:Exit]" + read ANSWER + done + + if [[ "${ANSWER}" == "x" ]] ; then + exit 0 + fi + fi +} + +function resetEnturDevelop() { + echo "" + echo "## ------------------------------------------------------------------------------------- ##" + echo "## RESET '${ENTUR_DEVELOP}' TO '${OTP_BASE}'" + echo "## ------------------------------------------------------------------------------------- ##" + echo "" + echo "Would you like to reset the '${ENTUR_DEVELOP}' to '${OTP_BASE}'? " + echo "" + + whatDoYouWant + + if [[ "${ANSWER}" == "y" ]] ; then + echo "" + echo "Checkout '${ENTUR_DEVELOP}'" + git checkout ${ENTUR_DEVELOP} + + echo "" + echo "Reset '${ENTUR_DEVELOP}' branch to '${OTP_BASE}' (hard)" + git reset --hard "${OTP_BASE}" + echo "" + fi +} + +function rebaseAndMergeExtBranch() { + EXT_BRANCH="$1" + EXT_STATUS_REBASE="Rebase '${EXT_BRANCH}'" + EXT_STATUS_COMPILE="Compile '${EXT_BRANCH}'" + + echo "" + echo "## ------------------------------------------------------------------------------------- ##" + echo "## REBASE AND MERGE '${EXT_BRANCH}' INTO '${ENTUR_DEVELOP}'" + echo "## ------------------------------------------------------------------------------------- ##" + echo "" + echo "You are about to rebase and merge '${EXT_BRANCH}' into '${ENTUR_DEVELOP}'. Any local" + echo "modification in the '${EXT_BRANCH}' will be lost." + echo "" + + whatDoYouWant + + if [[ "${ANSWER}" == "y" ]] ; then + echo "" + echo "Checkout '${EXT_BRANCH}'" + git checkout "${EXT_BRANCH}" + + echo "" + echo "Reset to '${REMOTE_REPO}/${EXT_BRANCH}'" + git reset --hard "${REMOTE_REPO}/${EXT_BRANCH}" + + echo "" + echo "Top 2 commits in '${EXT_BRANCH}'" + echo "-------------------------------------------------------------------------------------------" + git --no-pager log -2 + echo "-------------------------------------------------------------------------------------------" + echo "" + echo "You are about to rebase the TOP COMMIT ONLY(see above). Check that the " + echo "'${EXT_BRANCH}' only have ONE commit that you want to keep." + echo "" + + whatDoYouWant + + if [[ "${ANSWER}" == "y" ]] ; then + echo "" + echo "Rebase '${EXT_BRANCH}' onto '${ENTUR_DEVELOP}'" + setStatus "${EXT_STATUS_REBASE}" + git rebase --onto ${ENTUR_DEVELOP} HEAD~1 + fi + fi + + if [[ "${STATUS}" == "${EXT_STATUS_REBASE}" || "${STATUS}" == "${EXT_STATUS_COMPILE}" ]] ; then + # Reset status in case the test-compile fails. We need to do this because the status file + # is deleted after reading the status in the setup() function. + setStatus "${EXT_STATUS_COMPILE}" + + mvn clean test-compile + clearStatus + + echo "" + echo "Push '${EXT_BRANCH}'" + if [[ -z "${DRY_RUN}" ]] ; then + git push -f + else + echo "Skip: git push -f (--dryRun)" + fi + + echo "" + echo "Checkout '${ENTUR_DEVELOP}' and merge in '${EXT_BRANCH}'" + git checkout "${ENTUR_DEVELOP}" + git merge "${EXT_BRANCH}" + fi +} + +function logSuccess() { + echo "" + echo "## ------------------------------------------------------------------------------------- ##" + echo "## PREPARE RELEASE DONE -- SUCCESS" + echo "## ------------------------------------------------------------------------------------- ##" + echo " - '${REMOTE_REPO}/${ENTUR_DEVELOP}' reset to '${OTP_BASE}'" + echo " - 'otp2_ext_config' merged" + echo " - 'otp2_ext_hack_sorlandsbanen' merged" + echo "" + echo "" +} + +function whatDoYouWant() { + echo "" + ANSWER="" + + if [[ -n "${STATUS}" ]] ; then + # Skip until process is resumed + ANSWER="s" + else + while [[ ! "$ANSWER" =~ [ysx] ]]; do + echo "Do you want to continue: [y:Yes, s:Skip, x:Exit]" + read ANSWER + done + + if [[ "${ANSWER}" == "x" ]] ; then + exit 0 + fi + fi +} + +function setStatus() { + STATUS="$1" + echo "$STATUS" > "${STATUS_FILE}" +} + +function readStatus() { + if [[ -f "${STATUS_FILE}" ]] ; then + STATUS=`cat $STATUS_FILE` + rm "$STATUS_FILE" + else + STATUS="" + fi +} + +function clearStatus() { + STATUS="" + rm "${STATUS_FILE}" +} + +function printHelp() { + echo "" + echo "This script take ONE argument , the base **branch** or **commit** to use for the" + echo "release. The '${ENTUR_DEVELOP}' branch is reset to this commit and then the extension" + echo "branches is rebased onto that. The 'release' script is used to complete the release." + echo "It tag and push all changes to remote git repo." + echo "" + echo "Options:" + echo " --dryRun : Run script locally, nothing is pushed to remote server." + echo "" + echo "Usage:" + echo " $ .circleci/prepare_release otp/dev-2.x" + echo " $ .circleci/prepare_release --dryRun otp/dev-2.x" + echo "" +} + +main "$@" diff --git a/.circleci/release b/.circleci/release new file mode 100755 index 00000000000..9cac0d97958 --- /dev/null +++ b/.circleci/release @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +set -euo pipefail + +GIT_REMOTE_REPO="`git remote -v | grep "entur/OpenTripPlanner" | grep "push" | awk '{print $1;}'`" +MASTER_BRANCH=otp2_entur_develop +TAGS_FILE=target/git-entur-tags.txt +DRY_RUN="" + +function main() { + setup "$@" + listAllTags + mergeInOldReleaseWithNoChanges + setPomVersion + tagRelease + pushToRemote +} + +function setup() { + echo "" + echo "git fetch ${GIT_REMOTE_REPO}" + git fetch ${GIT_REMOTE_REPO} + + echo "Verify current branch is ${MASTER_BRANCH} " + git status | grep -q "On branch ${MASTER_BRANCH}" + + if [[ "${1+x}" == "--dryRun" ]] ; then + DRY_RUN="--dryRun" + fi +} + +function listAllTags() { + ## List all Entur tags to allow the UpdatePomVersion java program find the next version number + echo "" + echo "Dump all entur tags to ${TAGS_FILE}" + git tag -l | grep entur > ${TAGS_FILE} +} + +function setPomVersion() { + echo "" + echo "Update pom.xml with new version" + javac -d target/classes src/main/java/EnturUpdatePomVersion.java + + VERSION="`java -cp target/classes EnturUpdatePomVersion ${TAGS_FILE}`" + echo "" + echo "New version set: ${VERSION}" + echo "" + + ## Verify everything builds and tests run + echo "" + mvn clean test + + ## Add [ci skip] here before moving this to the CI server + echo "" + echo "Add and commit pom.xml" + git commit -m "Version ${VERSION}" pom.xml +} + +function mergeInOldReleaseWithNoChanges() { + echo "" + echo "Merge the old version of '${GIT_REMOTE_REPO}' into the new version. This only keep " + echo "a reference to the old version, the resulting tree of the merge is that of the new" + echo "branch head, effectively ignoring all changes from the old release." + git merge -s ours "${GIT_REMOTE_REPO}/${MASTER_BRANCH}" -m "Merge old release into '${MASTER_BRANCH}' - NO CHANGES COPIED OVER" +} + + +function tagRelease() { + echo "" + echo "Tag version ${VERSION}" + git tag -a v${VERSION} -m "Version ${VERSION}" +} + +function pushToRemote() { + echo "" + echo "Push pom.xml and new tag" + if [[ -z "${DRY_RUN}" ]] ; then + git push -f ${GIT_REMOTE_REPO} "v${VERSION}" ${MASTER_BRANCH} + else + echo "Skip: push -f ${GIT_REMOTE_REPO} "v${VERSION}" ${MASTER_BRANCH} (--dryRun)" + fi +} + +main "$@" diff --git a/.circleci/update_deployment_config b/.circleci/update_deployment_config new file mode 100755 index 00000000000..64550f19797 --- /dev/null +++ b/.circleci/update_deployment_config @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## +## This script run AFTER OTP is build on the CI Server. It checkout the +## deployment config and update the version number, commit and push. This +## trigger the deployment config build witch create and deploy a new OTP2 +## docker image. +## +## Note! There is no need to run this script locally, unless you want to debug it. +## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## + + +set -euo pipefail + +OTP_DEPLOYMENT_CONFIG=target/otp-deployment-config +DOCKER_FILE=Dockerfile + +VERSION=$(xmllint --xpath "//*[local-name()='project']/*[local-name()='version']/text()" pom.xml) + +echo "Checkout otp-deplyment-config in ${OTP_DEPLOYMENT_CONFIG}" +git clone -n https://github.com/entur/otp-deployment-config.git --depth 1 ${OTP_DEPLOYMENT_CONFIG} + +pushd ${OTP_DEPLOYMENT_CONFIG} + +echo "Checkout latest version of master Dockerfile" +git checkout master ${DOCKER_FILE} + +echo "Update OTP version number in ${DOCKER_FILE}" +if [[ "$OSTYPE" == "darwin"* ]]; then + sed -E -i '' "s/OTP_VERSION=[\.0-9]+-entur-[0-9]+/OTP_VERSION=${VERSION}/" ${DOCKER_FILE} +else + sed -E -i "s/OTP_VERSION=[\.0-9]+-entur-[0-9]+/OTP_VERSION=${VERSION}/" ${DOCKER_FILE} +fi + +if [[ "$CIRCLE_USERNAME" != "" ]]; then + git config user.email "circleci@entur.no" + git config user.name "circleci ($CIRCLE_USERNAME)" +fi + +echo "Add and commit Dockerfile" +git commit -m "New OTP2 Version ${VERSION}" ${DOCKER_FILE} + +echo "Push otp-deployment-config to GitHub" +git push + +popd \ No newline at end of file diff --git a/.gitignore b/.gitignore index a90f06cdcb0..3bc96654225 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .project .pydevproject .settings +.prepare_release.tmp .sonar .nvmrc *~ diff --git a/pom.xml b/pom.xml index c26e6980914..630d33acfd6 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 121 + EN-0052 29.2 2.48.1 @@ -84,11 +84,11 @@ - - github - OpenTripPlanner Maven Repository on Github Packages - https://maven.pkg.github.com/${GITHUB_REPOSITORY}/ - + + snapshots + entur2-snapshots + https://entur2.jfrog.io/entur2/libs-snapshot-local + diff --git a/src/main/java/EnturUpdatePomVersion.java b/src/main/java/EnturUpdatePomVersion.java new file mode 100644 index 00000000000..97bd72b3c6e --- /dev/null +++ b/src/main/java/EnturUpdatePomVersion.java @@ -0,0 +1,135 @@ +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.regex.Pattern; + +public class EnturUpdatePomVersion { + + private static final String VERSION_SEP = "-"; + private static final String ENTUR_PREFIX = "entur" + VERSION_SEP; + private static final Path POM_FILE = Path.of("pom.xml"); + + private final List tags = new ArrayList<>(); + private final List pomFile = new ArrayList<>(); + private String mainVersion; + private int versionNumber = 0; + + public static void main(String[] args) { + try { + new EnturUpdatePomVersion().withArgs(args).run(); + } catch (Exception e) { + System.err.println(e.getMessage()); + e.printStackTrace(System.err); + System.exit(10); + } + } + + private EnturUpdatePomVersion withArgs(String[] args) throws IOException { + if (args.length != 1 || args[0].matches("(?i)-h|--help")) { + printHelp(); + System.exit(1); + } + String arg = args[0]; + + if (arg.matches("\\d+")) { + versionNumber = resolveVersionFromNumericString(arg); + } else { + tags.addAll(readTagsFromFile(arg)); + } + return this; + } + + private void run() throws IOException { + readAndReplaceVersion(); + replacePomFile(); + } + + public void readAndReplaceVersion() throws IOException { + var pattern = Pattern.compile( + "(\\s*)(\\d+.\\d+.\\d+)" + + VERSION_SEP + + "(" + + ENTUR_PREFIX + + "\\d+|SNAPSHOT)(\\s*)" + ); + boolean found = false; + int i = 0; + + for (String line : Files.readAllLines(POM_FILE, UTF_8)) { + // Look for the version in the 25 first lines + if (!found) { + var m = pattern.matcher(line); + if (m.matches()) { + mainVersion = m.group(2); + String newVersion = mainVersion + VERSION_SEP + ENTUR_PREFIX + resolveVersionNumber(); + line = m.group(1) + newVersion + m.group(4); + System.out.println(newVersion); + found = true; + } + if (++i == 25) { + throw new IllegalStateException( + "Version not found in first 25 lines of the pom.xml file." + ); + } + } + pomFile.add(line); + } + if (!found) { + throw new IllegalStateException( + "Version not found in 'pom.xml'. Nothing matching pattern: " + pattern + ); + } + } + + public void replacePomFile() throws IOException { + Files.delete(POM_FILE); + Files.write(POM_FILE, pomFile, UTF_8); + } + + private static void printHelp() { + System.err.println( + "Use this small program to replace the OTP version '2.1.0-SNAPSHOT' \n" + + "with a new version number with a Entur qualifier like '2.1.0-entur-1'.\n" + + "\n" + + "Usage:\n" + + " $ java -cp .circleci UpdatePomVersion \n" + ); + } + + private int resolveVersionNumber() { + var pattern = Pattern.compile("v" + mainVersion + VERSION_SEP + ENTUR_PREFIX + "(\\d+)"); + int maxTagVersion = tags + .stream() + .mapToInt(tag -> { + var m = pattern.matcher(tag); + return m.matches() ? Integer.parseInt(m.group(1)) : -999; + }) + .max() + .orElse(-999); + + return 1 + Math.max(maxTagVersion, versionNumber); + } + + public static int resolveVersionFromNumericString(String arg) { + try { + return Integer.parseInt(arg); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Unable to parse input, decimal number expected: '" + arg + "'" + ); + } + } + + private static Collection readTagsFromFile(String arg) throws IOException { + var tags = Files.readAllLines(Path.of(arg)); + if (tags.isEmpty()) { + throw new IllegalStateException("Unable to load git tags from file: " + arg); + } + return tags; + } +} From 3c5742a03eaddb4b608355a08bd37f3f9ea6c042 Mon Sep 17 00:00:00 2001 From: Hannes Junnila Date: Fri, 9 Dec 2022 09:38:06 +0200 Subject: [PATCH 06/26] =?UTF-8?q?hack:=20Add=20train=20option=20for=20S?= =?UTF-8?q?=C3=B8rlandsbanen.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/Configuration.md | 3 +- docs/RouteRequest.md | 1 + src/ext/graphql/transmodelapi/schema.graphql | 2 + .../ConcurrentCompositeWorker.java | 53 +++++++ .../sorlandsbanen/EnturHackSorlandsBanen.java | 141 ++++++++++++++++++ .../ext/sorlandsbanen/PathKey.java | 51 +++++++ .../RaptorWorkerResultComposite.java | 88 +++++++++++ .../transmodelapi/model/plan/TripQuery.java | 8 + .../common/RequestToPreferencesMapper.java | 1 + .../api/common/RoutingResource.java | 3 + .../framework/application/OTPFeature.java | 3 +- .../raptor/api/request/RaptorRequest.java | 4 + .../api/request/RaptorRequestBuilder.java | 6 + .../service/RangeRaptorDynamicSearch.java | 10 +- .../raptoradapter/router/TransitRouter.java | 3 +- .../transit/cost/DefaultCostCalculator.java | 16 ++ .../transit/cost/FactorStrategy.java | 2 +- .../cost/IndexBasedFactorStrategy.java | 4 +- .../transit/mappers/RaptorRequestMapper.java | 16 +- .../RaptorRoutingRequestTransitData.java | 33 ++++ .../preference/TransitPreferences.java | 17 +++ .../routerequest/RouteRequestConfig.java | 7 + .../raptor/RaptorArchitectureTest.java | 6 +- 23 files changed, 466 insertions(+), 12 deletions(-) create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java diff --git a/docs/Configuration.md b/docs/Configuration.md index 5073dafed31..f91a7caf1b3 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -228,7 +228,8 @@ Here is a list of all features which can be toggled on/off and their default val | `ConsiderPatternsForDirectTransfers` | Enable limiting transfers so that there is only a single transfer to each pattern. | ✓️ | | | `DebugClient` | Enable the debug web client located at the root of the web server. | ✓️ | | | `FloatingBike` | Enable floating bike routing. | ✓️ | | -| `GtfsGraphQlApi` | Enable GTFS GraphQL API. | ✓️ | | +| `HackSorlandsbanen` | Includ Sørlandsbanen | | ✓️ | +| `GtfsGraphQlApi` | Enable GTFS GraphQL API. | ✓️ | ✓️ | | `MinimumTransferTimeIsDefinitive` | If the minimum transfer time is a lower bound (default) or the definitive time for the transfer. Set this to `true` if you want to set a transfer time lower than what OTP derives from OSM data. | | | | `OptimizeTransfers` | OTP will inspect all itineraries found and optimize where (which stops) the transfer will happen. Waiting time, priority and guaranteed transfers are taken into account. | ✓️ | | | `ParallelRouting` | Enable performing parts of the trip planning in parallel. | | | diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index f814753df4a..b6284d22e4d 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -46,6 +46,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe | elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 | | elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | | escalatorReluctance | `double` | A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time | *Optional* | `1.5` | 2.4 | +| extraSearchCoachReluctance | `double` | TODO | *Optional* | `0.0` | 2.1 | | geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | | ignoreRealtimeUpdates | `boolean` | When true, realtime updates are ignored during this search. | *Optional* | `false` | 2.0 | | [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql index 28ca9cd129b..4178413af3c 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/ext/graphql/transmodelapi/schema.graphql @@ -790,6 +790,8 @@ type QueryType { dateTime: DateTime, "Debug the itinerary-filter-chain. OTP will attach a system notice to itineraries instead of removing them. This is very convenient when tuning the filters." debugItineraryFilter: Boolean = false, + "FOR TESTING ONLY" + extraSearchCoachReluctance: Float, "A list of filters for which trips should be included. A trip will be included if it matches with at least one filter. An empty list of filters means that all trips should be included. If a search include this parameter, \"whiteListed\", \"banned\" & \"modes.transportModes\" filters will be ignored." filters: [TripFilterInput!], "The start location" diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java new file mode 100644 index 00000000000..e80e106ce1e --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java @@ -0,0 +1,53 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.opentripplanner.framework.application.OTPFeature; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; +import org.opentripplanner.raptor.api.response.StopArrivals; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.multicriteria.McRaptorWorkerResult; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TripScheduleWithOffset; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class ConcurrentCompositeWorker implements RaptorWorker { + + private static final Logger LOG = LoggerFactory.getLogger(ConcurrentCompositeWorker.class); + + private final RaptorWorker mainWorker; + private final RaptorWorker alternativeWorker; + + ConcurrentCompositeWorker(RaptorWorker mainWorker, RaptorWorker alternativeWorker) { + this.mainWorker = mainWorker; + this.alternativeWorker = alternativeWorker; + } + + @Override + public RaptorWorkerResult route() { + if (OTPFeature.ParallelRouting.isOn()) { + var mainResultFuture = CompletableFuture.supplyAsync(mainWorker::route); + var alternativeResultFuture = CompletableFuture.supplyAsync(alternativeWorker::route); + + try { + return new RaptorWorkerResultComposite<>( + mainResultFuture.get(), + alternativeResultFuture.get() + ); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } else { + var mainResult = mainWorker.route(); + var alternativeResult = alternativeWorker.route(); + return new RaptorWorkerResultComposite<>(mainResult, alternativeResult); + } + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java new file mode 100644 index 00000000000..2b52e850f42 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java @@ -0,0 +1,141 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.function.Function; +import org.opentripplanner.framework.geometry.SphericalDistanceLibrary; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.model.GenericLocation; +import org.opentripplanner.raptor.api.model.RaptorAccessEgress; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.request.RaptorRequest; +import org.opentripplanner.raptor.api.request.SearchParams; +import org.opentripplanner.raptor.configure.RaptorConfig; +import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; +import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.FactorStrategy; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.IndexBasedFactorStrategy; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.RaptorRoutingRequestTransitData; +import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.site.StopLocation; + +public class EnturHackSorlandsBanen { + + private static final double SOUTH_BOARDER_LIMIT = 59.1; + private static final int MIN_DISTANCE_LIMIT = 120_000; + + public static boolean match(RaptorRequest mcRequest) { + return mcRequest.extraSearchCoachReluctance > 0.1; + } + + public static RaptorWorker worker( + RaptorConfig config, + RaptorTransitDataProvider transitData, + RaptorRequest mcRequest, + Heuristics destinationHeuristics + ) { + //noinspection unchecked + RaptorTransitDataProvider altTransitData = (RaptorTransitDataProvider) ( + (RaptorRoutingRequestTransitData) transitData + ).enturHackSorlandsbanen(mapFactors(mcRequest.extraSearchCoachReluctance)); + + return new ConcurrentCompositeWorker<>( + config.createMcWorker(transitData, mcRequest, destinationHeuristics), + config.createMcWorker(altTransitData, mcRequest, destinationHeuristics) + ); + } + + public static RaptorRequest enableHack( + RaptorRequest raptorRequest, + RouteRequest request, + TransitLayer transitLayer + ) { + if (request.preferences().transit().extraSearchCoachReluctance() < 0.1) { + return raptorRequest; + } + + SearchParams params = raptorRequest.searchParams(); + + WgsCoordinate from = findStopCoordinate(request.from(), params.accessPaths(), transitLayer); + WgsCoordinate to = findStopCoordinate(request.to(), params.egressPaths(), transitLayer); + + if (from.latitude() > SOUTH_BOARDER_LIMIT && to.latitude() > SOUTH_BOARDER_LIMIT) { + return raptorRequest; + } + + double distanceMeters = SphericalDistanceLibrary.distance( + from.latitude(), + from.longitude(), + to.latitude(), + to.longitude() + ); + + if (distanceMeters < MIN_DISTANCE_LIMIT) { + return raptorRequest; + } + + raptorRequest.extraSearchCoachReluctance = + request.preferences().transit().extraSearchCoachReluctance(); + return raptorRequest; + } + + /* private methods */ + + private static Function mapFactors( + final double extraSearchCoachReluctance + ) { + return (FactorStrategy originalFactors) -> { + int[] modeReluctance = new int[TransitMode.values().length]; + for (TransitMode mode : TransitMode.values()) { + int index = mode.ordinal(); + int originalFactor = originalFactors.factor(index); + modeReluctance[index] = + mode == TransitMode.COACH + ? (int) (extraSearchCoachReluctance * originalFactor + 0.5) + : originalFactor; + } + return new IndexBasedFactorStrategy(modeReluctance); + }; + } + + /** + * Find a coordinate matching the given location, in order: + * - First return the coordinate of the location if it exists. + * - Then loop through the access/egress stops and try to find the + * stop or station given by the location id, return the stop/station coordinate. + * - Return the fist stop in the access/egress list coordinate. + */ + @SuppressWarnings("ConstantConditions") + private static WgsCoordinate findStopCoordinate( + GenericLocation location, + Collection accessEgress, + TransitLayer transitLayer + ) { + if (location.lat != null) { + return new WgsCoordinate(location.lat, location.lng); + } + + StopLocation firstStop = null; + for (RaptorAccessEgress it : accessEgress) { + StopLocation stop = transitLayer.getStopByIndex(it.stop()); + if (stop.getId().equals(location.stopId)) { + return stop.getCoordinate(); + } + if (idIsParentStation(stop, location.stopId)) { + return stop.getParentStation().getCoordinate(); + } + if (firstStop == null) { + firstStop = stop; + } + } + return firstStop.getCoordinate(); + } + + private static boolean idIsParentStation(StopLocation stop, FeedScopedId pId) { + return stop.getParentStation() != null && stop.getParentStation().getId().equals(pId); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java new file mode 100644 index 00000000000..94267b3e7c2 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java @@ -0,0 +1,51 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; + +final class PathKey { + + private final int hash; + + PathKey(RaptorPath path) { + this.hash = hash(path); + } + + private static int hash(RaptorPath path) { + if (path == null) { + return 0; + } + int result = 1; + + PathLeg leg = path.accessLeg(); + + while (!leg.isEgressLeg()) { + result = 31 * result + leg.toStop(); + result = 31 * result + leg.toTime(); + + if (leg.isTransitLeg()) { + result = 31 * result + leg.asTransitLeg().trip().pattern().debugInfo().hashCode(); + } + leg = leg.nextLeg(); + } + result = 31 * result + leg.toTime(); + + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o.getClass() != PathKey.class) { + return false; + } + return hash == ((PathKey) o).hash; + } + + @Override + public int hashCode() { + return hash; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java new file mode 100644 index 00000000000..094e652fc4c --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java @@ -0,0 +1,88 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.internalapi.SingleCriteriaStopArrivals; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TripScheduleWithOffset; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RaptorWorkerResultComposite + implements RaptorWorkerResult { + + private static final Logger LOG = LoggerFactory.getLogger(RaptorWorkerResultComposite.class); + + private RaptorWorkerResult mainResult; + private RaptorWorkerResult alternativeResult; + + public RaptorWorkerResultComposite( + RaptorWorkerResult mainResult, + RaptorWorkerResult alternativeResult + ) { + this.mainResult = mainResult; + this.alternativeResult = alternativeResult; + } + + @Override + public Collection> extractPaths() { + Map> paths = new HashMap<>(); + addAll(paths, mainResult.extractPaths()); + addExtraRail(paths, alternativeResult.extractPaths()); + return paths.values(); + } + + @Override + public SingleCriteriaStopArrivals extractBestOverallArrivals() { + return mainResult.extractBestOverallArrivals(); + } + + @Override + public SingleCriteriaStopArrivals extractBestTransitArrivals() { + return mainResult.extractBestTransitArrivals(); + } + + @Override + public SingleCriteriaStopArrivals extractBestNumberOfTransfers() { + return mainResult.extractBestNumberOfTransfers(); + } + + @Override + public boolean isDestinationReached() { + return mainResult.isDestinationReached(); + } + + private void addExtraRail(Map> map, Collection> paths) { + paths.forEach(p -> { + if (hasRail(p)) { + var v = map.put(new PathKey(p), p); + LOG.debug("Ex.Rail {} : {}", (v == null ? "ADD " : "SKIP"), p); + } else { + LOG.debug("Ex. NOT Rail : {}", p); + } + }); + } + + private void addAll(Map> map, Collection> paths) { + paths.forEach(p -> { + var v = map.put(new PathKey(p), p); + LOG.debug("Normal {} : {}", (v == null ? "ADD " : "SKIP"), p); + }); + } + + private static boolean hasRail(RaptorPath path) { + return path + .legStream() + .filter(PathLeg::isTransitLeg) + .anyMatch(leg -> { + var trip = (TripScheduleWithOffset) leg.asTransitLeg().trip(); + var mode = trip.getOriginalTripPattern().getMode(); + return mode == TransitMode.RAIL; + }); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java index 8fa4fa31512..e1b1ba3d59f 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java @@ -563,6 +563,14 @@ public static GraphQLFieldDefinition create( ) .build() ) + .argument( + GraphQLArgument + .newArgument() + .name("extraSearchCoachReluctance") + .description("FOR TESTING ONLY") + .type(Scalars.GraphQLFloat) + .build() + ) .dataFetcher(environment -> new TransmodelGraphQLPlanner().plan(environment)) .build(); } diff --git a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java index 20ca8054308..ff61f201624 100644 --- a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java @@ -89,6 +89,7 @@ private BoardAndAlightSlack mapTransit() { req.relaxTransitSearchGeneralizedCostAtDestination, value -> tr.withRaptor(it -> it.withRelaxGeneralizedCostAtDestination(value)) ); + setIfNotNull(req.extraSearchCoachReluctance, tr::setExtraSearchCoachReluctance); }); return new BoardAndAlightSlack( diff --git a/src/main/java/org/opentripplanner/api/common/RoutingResource.java b/src/main/java/org/opentripplanner/api/common/RoutingResource.java index e60bfe41b82..f214f0126c6 100644 --- a/src/main/java/org/opentripplanner/api/common/RoutingResource.java +++ b/src/main/java/org/opentripplanner/api/common/RoutingResource.java @@ -200,6 +200,9 @@ public abstract class RoutingResource { @QueryParam("carReluctance") protected Double carReluctance; + @QueryParam("extraSearchCoachReluctance") + protected Double extraSearchCoachReluctance; + /** * How much worse is waiting for a transit vehicle than being on a transit vehicle, as a * multiplier. The default value treats wait and on-vehicle time as the same. diff --git a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 0652fb667a8..d08c4a731a4 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -29,7 +29,8 @@ public enum OTPFeature { ), DebugClient(true, false, "Enable the debug web client located at the root of the web server."), FloatingBike(true, false, "Enable floating bike routing."), - GtfsGraphQlApi(true, false, "Enable GTFS GraphQL API."), + HackSorlandsbanen(false, true, "Includ Sørlandsbanen"), + GtfsGraphQlApi(true, true, "Enable GTFS GraphQL API."), /** * If this feature flag is switched on, then the minimum transfer time is not the minimum transfer * time, but the definitive transfer time. Use this to override what we think the transfer will diff --git a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java index 1f0621b8d36..6b99e7ae238 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java @@ -29,6 +29,9 @@ public class RaptorRequest { private final DebugRequest debug; private final RaptorTimers performanceTimers; + // HACK SØRLANDSBANEN + public double extraSearchCoachReluctance = 0.0; + private RaptorRequest() { searchParams = SearchParams.defaults(); profile = RaptorProfile.MULTI_CRITERIA; @@ -49,6 +52,7 @@ private RaptorRequest() { this.multiCriteria = builder.multiCriteria(); this.performanceTimers = builder.performanceTimers(); this.debug = builder.debug().build(); + this.extraSearchCoachReluctance = builder.extraSearchCoachReluctance; verify(); } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java index ed2aab3de20..7bb8b538843 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java @@ -34,6 +34,9 @@ public class RaptorRequestBuilder { // Performance monitoring private RaptorTimers performanceTimers; + /** HACK SØRLANDSBANEN */ + public double extraSearchCoachReluctance; + // Algorithm private RaptorProfile profile; @@ -60,6 +63,9 @@ public RaptorRequestBuilder() { // Debug this.debug = new DebugRequestBuilder(defaults.debug()); + + // HACK SØRLANDSBANEN + this.extraSearchCoachReluctance = defaults.extraSearchCoachReluctance; } public SearchParamsBuilder searchParams() { diff --git a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java index fe8bb884912..86b10f7d96f 100644 --- a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java +++ b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java @@ -11,6 +11,8 @@ import java.util.concurrent.Future; import java.util.stream.Collectors; import javax.annotation.Nullable; +import org.opentripplanner.ext.sorlandsbanen.EnturHackSorlandsBanen; +import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.raptor.RaptorService; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; @@ -134,7 +136,13 @@ private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request) // Create worker if (request.profile().is(MULTI_CRITERIA)) { - raptorWorker = config.createMcWorker(transitData, request, getDestinationHeuristics()); + // HACK SØRLANDSBANEN + if (OTPFeature.HackSorlandsbanen.isOn() && EnturHackSorlandsBanen.match(request)) { + raptorWorker = + EnturHackSorlandsBanen.worker(config, transitData, request, getDestinationHeuristics()); + } else { + raptorWorker = config.createMcWorker(transitData, request, getDestinationHeuristics()); + } } else { raptorWorker = config.createStdWorker(transitData, request); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index e8a4c9e3311..1f1a2dcdd90 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -128,7 +128,8 @@ private TransitRouterResult route() { accessEgresses.getAccesses(), accessEgresses.getEgresses(), accessEgresses.calculateMaxAccessTimePenalty(), - serverContext.meterRegistry() + serverContext.meterRegistry(), + transitLayer ); // Route transit diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java index b738d63054e..4f39b2631fe 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java @@ -1,5 +1,6 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit.cost; +import java.util.function.Function; import javax.annotation.Nullable; import org.opentripplanner.model.transfer.TransferConstraint; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; @@ -211,4 +212,19 @@ private int boardingCostConstrainedTransfer( // fallback to regular transfer return boardingCostRegularTransfer(firstBoarding, prevArrivalTime, boardStop, boardTime); } + + /*-- HACK SØRLANDSBANEN :: BEGIN --*/ + + public DefaultCostCalculator( + DefaultCostCalculator original, + Function modeReluctanceMapper + ) { + this.boardCostOnly = original.boardCostOnly; + this.boardAndTransferCost = original.boardAndTransferCost; + this.waitFactor = original.waitFactor; + this.transferCostOnly = original.transferCostOnly; + this.transitFactors = modeReluctanceMapper.apply(original.transitFactors); + this.stopTransferCost = original.stopTransferCost; + } + /*-- HACK SØRLANDSBANEN :: END --*/ } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java index fba2a7cfe38..8a14c27566d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java @@ -6,7 +6,7 @@ *

* The class and methods are {@code final} to help the JIT compiler optimize the use of this class. */ -interface FactorStrategy { +public interface FactorStrategy { /** * Return the factor for the given index. */ diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java index 152f199248c..4c66b5b452e 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java @@ -6,12 +6,12 @@ * This class keep a facto for each index and the minimum factor for fast retrieval during Raptor * search. */ -final class IndexBasedFactorStrategy implements FactorStrategy { +public final class IndexBasedFactorStrategy implements FactorStrategy { private final int[] factors; private final int minFactor; - private IndexBasedFactorStrategy(int[] factors) { + public IndexBasedFactorStrategy(int[] factors) { this.factors = factors; this.minFactor = findMinimumFactor(factors); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index 8e6121edffe..5b0de00bc6c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -8,6 +8,7 @@ import java.time.ZonedDateTime; import java.util.Collection; import java.util.List; +import org.opentripplanner.ext.sorlandsbanen.EnturHackSorlandsBanen; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; @@ -17,6 +18,7 @@ import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; import org.opentripplanner.raptor.rangeraptor.SystemErrDebugLogger; import org.opentripplanner.routing.algorithm.raptoradapter.router.performance.PerformanceTimersForRaptor; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.transit.model.site.StopLocation; @@ -31,6 +33,8 @@ public class RaptorRequestMapper { private final boolean isMultiThreadedEnbled; private final MeterRegistry meterRegistry; + private final TransitLayer transitLayer; + private RaptorRequestMapper( RouteRequest request, boolean isMultiThreaded, @@ -38,13 +42,15 @@ private RaptorRequestMapper( Collection egressPaths, Duration searchWindowAccessSlack, long transitSearchTimeZeroEpocSecond, - MeterRegistry meterRegistry + MeterRegistry meterRegistry, + TransitLayer transitLayer ) { this.request = request; this.isMultiThreadedEnbled = isMultiThreaded; this.accessPaths = accessPaths; this.egressPaths = egressPaths; this.searchWindowAccessSlack = searchWindowAccessSlack; + this.transitLayer = transitLayer; this.transitSearchTimeZeroEpocSecond = transitSearchTimeZeroEpocSecond; this.meterRegistry = meterRegistry; } @@ -56,7 +62,8 @@ public static RaptorRequest mapRequest( Collection accessPaths, Collection egressPaths, Duration searchWindowAccessSlack, - MeterRegistry meterRegistry + MeterRegistry meterRegistry, + TransitLayer transitLayer ) { return new RaptorRequestMapper( request, @@ -65,7 +72,8 @@ public static RaptorRequest mapRequest( egressPaths, searchWindowAccessSlack, transitSearchTimeZero.toEpochSecond(), - meterRegistry + meterRegistry, + transitLayer ) .doMap(); } @@ -171,7 +179,7 @@ private RaptorRequest doMap() { ) ); - return builder.build(); + return EnturHackSorlandsBanen.enableHack(builder.build(), request, transitLayer); } private List mapPassThroughPoints() { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java index a0b62414181..df5996009ec 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java @@ -4,6 +4,7 @@ import java.util.BitSet; import java.util.Iterator; import java.util.List; +import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.opentripplanner.framework.application.OTPFeature; @@ -27,6 +28,8 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.constrainedtransfer.ConstrainedBoardingSearch; import org.opentripplanner.routing.algorithm.raptoradapter.transit.constrainedtransfer.ConstrainedTransfersForPatterns; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.CostCalculatorFactory; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.DefaultCostCalculator; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.FactorStrategy; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.GeneralizedCostParametersMapper; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.transit.model.network.RoutingTripPattern; @@ -241,4 +244,34 @@ public RaptorConstrainedBoardingSearch transferConstraintsReverseS } return new ConstrainedBoardingSearch(false, toStopTransfers, fromStopTransfers); } + + /*-- HACK SØRLANDSBANEN :: BEGIN --*/ + + private RaptorRoutingRequestTransitData( + RaptorRoutingRequestTransitData original, + Function mapFactors + ) { + this.transitLayer = original.transitLayer; + this.transitSearchTimeZero = original.transitSearchTimeZero; + this.activeTripPatternsPerStop = original.activeTripPatternsPerStop; + this.patternIndex = original.patternIndex; + this.transferIndex = original.transferIndex; + this.transferService = original.transferService; + this.constrainedTransfers = original.constrainedTransfers; + this.validTransitDataStartTime = original.validTransitDataStartTime; + this.validTransitDataEndTime = original.validTransitDataEndTime; + this.generalizedCostCalculator = + new DefaultCostCalculator<>( + (DefaultCostCalculator) original.generalizedCostCalculator, + mapFactors + ); + this.slackProvider = original.slackProvider(); + } + + public RaptorTransitDataProvider enturHackSorlandsbanen( + Function mapFactors + ) { + return new RaptorRoutingRequestTransitData(this, mapFactors); + } + /*-- HACK SØRLANDSBANEN :: END --*/ } diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java index 7da4516d782..96945693cdd 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java @@ -31,6 +31,7 @@ public final class TransitPreferences implements Serializable { private final boolean includePlannedCancellations; private final boolean includeRealtimeCancellations; private final RaptorPreferences raptor; + private final double extraSearchCoachReluctance; private TransitPreferences() { this.boardSlack = this.alightSlack = DurationForEnum.of(TransitMode.class).build(); @@ -41,6 +42,7 @@ private TransitPreferences() { this.includePlannedCancellations = false; this.includeRealtimeCancellations = false; this.raptor = RaptorPreferences.DEFAULT; + this.extraSearchCoachReluctance = 0.0; } private TransitPreferences(Builder builder) { @@ -53,6 +55,7 @@ private TransitPreferences(Builder builder) { this.includePlannedCancellations = builder.includePlannedCancellations; this.includeRealtimeCancellations = builder.includeRealtimeCancellations; this.raptor = requireNonNull(builder.raptor); + this.extraSearchCoachReluctance = builder.extraSearchCoachReluctance; } public static Builder of() { @@ -153,6 +156,11 @@ public RaptorPreferences raptor() { return raptor; } + /** Zero means turned off. HACK SØRLANDSBANEN */ + public double extraSearchCoachReluctance() { + return extraSearchCoachReluctance; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -167,6 +175,7 @@ public boolean equals(Object o) { alightSlack.equals(that.alightSlack) && reluctanceForMode.equals(that.reluctanceForMode) && unpreferredCost.equals(that.unpreferredCost) && + extraSearchCoachReluctance == that.extraSearchCoachReluctance && raptor.equals(that.raptor) ); } @@ -182,6 +191,7 @@ public int hashCode() { ignoreRealtimeUpdates, includePlannedCancellations, includeRealtimeCancellations, + extraSearchCoachReluctance, raptor ); } @@ -229,6 +239,7 @@ public static class Builder { private boolean includePlannedCancellations; private boolean includeRealtimeCancellations; private RaptorPreferences raptor; + private double extraSearchCoachReluctance; public Builder(TransitPreferences original) { this.original = original; @@ -241,6 +252,7 @@ public Builder(TransitPreferences original) { this.includePlannedCancellations = original.includePlannedCancellations; this.includeRealtimeCancellations = original.includeRealtimeCancellations; this.raptor = original.raptor; + this.extraSearchCoachReluctance = original.extraSearchCoachReluctance; } public TransitPreferences original() { @@ -305,6 +317,11 @@ public Builder withRaptor(Consumer body) { return this; } + public Builder setExtraSearchCoachReluctance(double extraSearchCoachReluctance) { + this.extraSearchCoachReluctance = extraSearchCoachReluctance; + return this; + } + public Builder apply(Consumer body) { body.accept(this); return this; diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index c89670b7e0a..db212bd31d3 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -352,6 +352,13 @@ The board time is added to the time when going from the stop (offboard) to onboa ) .asDoubleOptional() .ifPresent(it::withRelaxGeneralizedCostAtDestination) + ) + .setExtraSearchCoachReluctance( + c + .of("extraSearchCoachReluctance") + .since(V2_1) + .summary("TODO") + .asDouble(dft.extraSearchCoachReluctance()) ); } diff --git a/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java b/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java index 39022da42ca..aeca5a36ebf 100644 --- a/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java +++ b/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java @@ -39,6 +39,9 @@ public class RaptorArchitectureTest { private static final Package RR_STANDARD = RANGE_RAPTOR.subPackage("standard"); private static final Package RR_STD_CONFIGURE = RR_STANDARD.subPackage("configure"); private static final Package RR_CONTEXT = RANGE_RAPTOR.subPackage("context"); + private static final Package EXT_SORLANDSBANAN_HACK = Package.of( + "org.opentripplanner.ext.sorlandsbanen" + ); /** * Packages used by standard-range-raptor and multi-criteria-range-raptor. @@ -200,7 +203,8 @@ void enforcePackageDependenciesInRaptorService() { RAPTOR_UTIL, CONFIGURE, RR_INTERNAL_API, - RR_TRANSIT + RR_TRANSIT, + EXT_SORLANDSBANAN_HACK ) .verify(); } From 0b852eb93005a776a04e72a958894350e0499c3d Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 16 Oct 2023 12:33:18 +0200 Subject: [PATCH 07/26] Version 2.5.0-entur-3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 630d33acfd6..dd1aedaf89a 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.5.0-SNAPSHOT + 2.5.0-entur-3 jar From 79db117f2bf4440f305613013d6b1b34d19d76b3 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 16 Oct 2023 15:53:12 +0200 Subject: [PATCH 08/26] Fix parameter type --- .../opentripplanner/ext/transmodelapi/model/stop/QuayType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java index 0010efd2f3f..203efb10452 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java @@ -318,7 +318,7 @@ public static GraphQLObjectType create( JourneyWhiteListed whiteListed = new JourneyWhiteListed(environment); Collection transitModes = environment.getArgument("whiteListedModes"); - Integer startTimeInput = environment.getArgument("startTime"); + Long startTimeInput = environment.getArgument("startTime"); Instant startTime = startTimeInput != null ? Instant.ofEpochMilli(startTimeInput) : Instant.now(); From 69c15bf60c60d66cd1779aa1b0607092281b8de4 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 16 Oct 2023 16:02:17 +0200 Subject: [PATCH 09/26] Version 2.5.0-entur-4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dd1aedaf89a..00b7b761d8c 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.5.0-entur-3 + 2.5.0-entur-4 jar From ec7c3f8236ca18bdbe4803bcf29b43db115b770e Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 19 May 2023 18:35:18 +0200 Subject: [PATCH 10/26] CircleCI config, release scripts, and pom.xml updates. --- .circleci/config.yml | 52 +++++ .circleci/prepare_release | 235 +++++++++++++++++++++++ .circleci/release | 84 ++++++++ .circleci/update_deployment_config | 46 +++++ .gitignore | 1 + pom.xml | 12 +- src/main/java/EnturUpdatePomVersion.java | 135 +++++++++++++ 7 files changed, 559 insertions(+), 6 deletions(-) create mode 100644 .circleci/config.yml create mode 100755 .circleci/prepare_release create mode 100755 .circleci/release create mode 100755 .circleci/update_deployment_config create mode 100644 src/main/java/EnturUpdatePomVersion.java diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000000..d01a0c323e9 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +aliases: + - &jfrog-login + name: Rename jfrog environment variable for maven setting.xml + command: | + echo "export JFROG_USER=$ARTIFACTORY_USER" >> $BASH_ENV + echo "export JFROG_PASS=$ARTIFACTORY_PASSWORD" >> $BASH_ENV + - &post_build + name: Update deployment with OTP version + command: | + sudo apt-get update + sudo apt-get install libxml2-utils + chmod u+x .circleci/update_deployment_config + .circleci/update_deployment_config + +jobs: + build: + docker: + - image: cimg/openjdk:17.0 + environment: + DEBIAN_FRONTEND: "noninteractive" + MAVEN_OPTS: -Xmx6G + resource_class: large + steps: + - checkout + - restore_cache: + keys: + - dep-cache-{{ checksum "pom.xml" }} + # fallback to the most recent cache if there is no exact match for this pom.xml + - dep-cache- + - run: wget https://raw.githubusercontent.com/entur/circleci-toolbox-image-java11/master/tools/m2/settings.xml -O .circleci/settings.xml + - run: *jfrog-login + - run: mvn org.apache.maven.plugins:maven-dependency-plugin:3.1.0:go-offline -s .circleci/settings.xml + - save_cache: + paths: + - ~/.m2 + key: dep-cache-{{ checksum "pom.xml" }} + - run: mvn install -PprettierSkip org.apache.maven.plugins:maven-deploy-plugin:2.8.2:deploy -s .circleci/settings.xml -DaltDeploymentRepository=snapshots::default::https://entur2.jfrog.io/entur2/libs-release-local + - run: *post_build + +workflows: + version: 2 + release: + jobs: + - build: + name: build-release + context: global + filters: + branches: + only: + - otp2_entur_develop \ No newline at end of file diff --git a/.circleci/prepare_release b/.circleci/prepare_release new file mode 100755 index 00000000000..5b2ad0e23c4 --- /dev/null +++ b/.circleci/prepare_release @@ -0,0 +1,235 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ENTUR_DEVELOP=otp2_entur_develop +REMOTE_REPO="`git remote -v | grep "entur/OpenTripPlanner" | grep "push" | awk '{print $1;}'`" +STATUS_FILE=".prepare_release.tmp" +STATUS="" +DRY_RUN="" +OTP_BASE="" + +function main() { + setup "$@" + resumePreviousExecution + resetEnturDevelop + rebaseAndMergeExtBranch otp2_ext_config + rebaseAndMergeExtBranch otp2_ext_hack_sorlandsbanen + logSuccess +} + +function setup() { + if [[ $# -eq 2 && "$1" == "--dryRun" ]] ; then + DRY_RUN="--dryRun" + OTP_BASE="$2" + elif [[ $# -eq 1 ]] ; then + OTP_BASE="$1" + else + printHelp + exit 1 + fi + + echo "" + echo "Options: ${DRY_RUN}" + echo "Git base branch/commit: ${OTP_BASE}" + echo "Entur develop branch: ${ENTUR_DEVELOP}" + echo "Entur remote repo(pull/push): ${REMOTE_REPO}" + echo "" + + if git diff-index --quiet HEAD --; then + echo "" + echo "OK - No local changes, prepare to checkout '${ENTUR_DEVELOP}'" + echo "" + else + echo "" + echo "You have local modification, the script will abort. Nothing done!" + exit 2 + fi + + git fetch ${REMOTE_REPO} +} + +# This script create a status file '.prepare_release.tmp'. This file is used to resume the +# script in the same spot as where is left when the error occurred. This allow us to fix the +# problem (merge conflict or compile error) and re-run the script to complete the proses. +function resumePreviousExecution() { + readStatus + + if [[ -n "${STATUS}" ]] ; then + echo "" + echo "Resume: ${STATUS}?" + echo "" + echo " If all problems are resolved you may continue." + echo " Exit to clear status and start over." + echo "" + + ANSWER="" + while [[ ! "$ANSWER" =~ [yx] ]]; do + echo "Do you want to resume: [y:Yes, x:Exit]" + read ANSWER + done + + if [[ "${ANSWER}" == "x" ]] ; then + exit 0 + fi + fi +} + +function resetEnturDevelop() { + echo "" + echo "## ------------------------------------------------------------------------------------- ##" + echo "## RESET '${ENTUR_DEVELOP}' TO '${OTP_BASE}'" + echo "## ------------------------------------------------------------------------------------- ##" + echo "" + echo "Would you like to reset the '${ENTUR_DEVELOP}' to '${OTP_BASE}'? " + echo "" + + whatDoYouWant + + if [[ "${ANSWER}" == "y" ]] ; then + echo "" + echo "Checkout '${ENTUR_DEVELOP}'" + git checkout ${ENTUR_DEVELOP} + + echo "" + echo "Reset '${ENTUR_DEVELOP}' branch to '${OTP_BASE}' (hard)" + git reset --hard "${OTP_BASE}" + echo "" + fi +} + +function rebaseAndMergeExtBranch() { + EXT_BRANCH="$1" + EXT_STATUS_REBASE="Rebase '${EXT_BRANCH}'" + EXT_STATUS_COMPILE="Compile '${EXT_BRANCH}'" + + echo "" + echo "## ------------------------------------------------------------------------------------- ##" + echo "## REBASE AND MERGE '${EXT_BRANCH}' INTO '${ENTUR_DEVELOP}'" + echo "## ------------------------------------------------------------------------------------- ##" + echo "" + echo "You are about to rebase and merge '${EXT_BRANCH}' into '${ENTUR_DEVELOP}'. Any local" + echo "modification in the '${EXT_BRANCH}' will be lost." + echo "" + + whatDoYouWant + + if [[ "${ANSWER}" == "y" ]] ; then + echo "" + echo "Checkout '${EXT_BRANCH}'" + git checkout "${EXT_BRANCH}" + + echo "" + echo "Reset to '${REMOTE_REPO}/${EXT_BRANCH}'" + git reset --hard "${REMOTE_REPO}/${EXT_BRANCH}" + + echo "" + echo "Top 2 commits in '${EXT_BRANCH}'" + echo "-------------------------------------------------------------------------------------------" + git --no-pager log -2 + echo "-------------------------------------------------------------------------------------------" + echo "" + echo "You are about to rebase the TOP COMMIT ONLY(see above). Check that the " + echo "'${EXT_BRANCH}' only have ONE commit that you want to keep." + echo "" + + whatDoYouWant + + if [[ "${ANSWER}" == "y" ]] ; then + echo "" + echo "Rebase '${EXT_BRANCH}' onto '${ENTUR_DEVELOP}'" + setStatus "${EXT_STATUS_REBASE}" + git rebase --onto ${ENTUR_DEVELOP} HEAD~1 + fi + fi + + if [[ "${STATUS}" == "${EXT_STATUS_REBASE}" || "${STATUS}" == "${EXT_STATUS_COMPILE}" ]] ; then + # Reset status in case the test-compile fails. We need to do this because the status file + # is deleted after reading the status in the setup() function. + setStatus "${EXT_STATUS_COMPILE}" + + mvn clean test-compile + clearStatus + + echo "" + echo "Push '${EXT_BRANCH}'" + if [[ -z "${DRY_RUN}" ]] ; then + git push -f + else + echo "Skip: git push -f (--dryRun)" + fi + + echo "" + echo "Checkout '${ENTUR_DEVELOP}' and merge in '${EXT_BRANCH}'" + git checkout "${ENTUR_DEVELOP}" + git merge "${EXT_BRANCH}" + fi +} + +function logSuccess() { + echo "" + echo "## ------------------------------------------------------------------------------------- ##" + echo "## PREPARE RELEASE DONE -- SUCCESS" + echo "## ------------------------------------------------------------------------------------- ##" + echo " - '${REMOTE_REPO}/${ENTUR_DEVELOP}' reset to '${OTP_BASE}'" + echo " - 'otp2_ext_config' merged" + echo " - 'otp2_ext_hack_sorlandsbanen' merged" + echo "" + echo "" +} + +function whatDoYouWant() { + echo "" + ANSWER="" + + if [[ -n "${STATUS}" ]] ; then + # Skip until process is resumed + ANSWER="s" + else + while [[ ! "$ANSWER" =~ [ysx] ]]; do + echo "Do you want to continue: [y:Yes, s:Skip, x:Exit]" + read ANSWER + done + + if [[ "${ANSWER}" == "x" ]] ; then + exit 0 + fi + fi +} + +function setStatus() { + STATUS="$1" + echo "$STATUS" > "${STATUS_FILE}" +} + +function readStatus() { + if [[ -f "${STATUS_FILE}" ]] ; then + STATUS=`cat $STATUS_FILE` + rm "$STATUS_FILE" + else + STATUS="" + fi +} + +function clearStatus() { + STATUS="" + rm "${STATUS_FILE}" +} + +function printHelp() { + echo "" + echo "This script take ONE argument , the base **branch** or **commit** to use for the" + echo "release. The '${ENTUR_DEVELOP}' branch is reset to this commit and then the extension" + echo "branches is rebased onto that. The 'release' script is used to complete the release." + echo "It tag and push all changes to remote git repo." + echo "" + echo "Options:" + echo " --dryRun : Run script locally, nothing is pushed to remote server." + echo "" + echo "Usage:" + echo " $ .circleci/prepare_release otp/dev-2.x" + echo " $ .circleci/prepare_release --dryRun otp/dev-2.x" + echo "" +} + +main "$@" diff --git a/.circleci/release b/.circleci/release new file mode 100755 index 00000000000..9cac0d97958 --- /dev/null +++ b/.circleci/release @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +set -euo pipefail + +GIT_REMOTE_REPO="`git remote -v | grep "entur/OpenTripPlanner" | grep "push" | awk '{print $1;}'`" +MASTER_BRANCH=otp2_entur_develop +TAGS_FILE=target/git-entur-tags.txt +DRY_RUN="" + +function main() { + setup "$@" + listAllTags + mergeInOldReleaseWithNoChanges + setPomVersion + tagRelease + pushToRemote +} + +function setup() { + echo "" + echo "git fetch ${GIT_REMOTE_REPO}" + git fetch ${GIT_REMOTE_REPO} + + echo "Verify current branch is ${MASTER_BRANCH} " + git status | grep -q "On branch ${MASTER_BRANCH}" + + if [[ "${1+x}" == "--dryRun" ]] ; then + DRY_RUN="--dryRun" + fi +} + +function listAllTags() { + ## List all Entur tags to allow the UpdatePomVersion java program find the next version number + echo "" + echo "Dump all entur tags to ${TAGS_FILE}" + git tag -l | grep entur > ${TAGS_FILE} +} + +function setPomVersion() { + echo "" + echo "Update pom.xml with new version" + javac -d target/classes src/main/java/EnturUpdatePomVersion.java + + VERSION="`java -cp target/classes EnturUpdatePomVersion ${TAGS_FILE}`" + echo "" + echo "New version set: ${VERSION}" + echo "" + + ## Verify everything builds and tests run + echo "" + mvn clean test + + ## Add [ci skip] here before moving this to the CI server + echo "" + echo "Add and commit pom.xml" + git commit -m "Version ${VERSION}" pom.xml +} + +function mergeInOldReleaseWithNoChanges() { + echo "" + echo "Merge the old version of '${GIT_REMOTE_REPO}' into the new version. This only keep " + echo "a reference to the old version, the resulting tree of the merge is that of the new" + echo "branch head, effectively ignoring all changes from the old release." + git merge -s ours "${GIT_REMOTE_REPO}/${MASTER_BRANCH}" -m "Merge old release into '${MASTER_BRANCH}' - NO CHANGES COPIED OVER" +} + + +function tagRelease() { + echo "" + echo "Tag version ${VERSION}" + git tag -a v${VERSION} -m "Version ${VERSION}" +} + +function pushToRemote() { + echo "" + echo "Push pom.xml and new tag" + if [[ -z "${DRY_RUN}" ]] ; then + git push -f ${GIT_REMOTE_REPO} "v${VERSION}" ${MASTER_BRANCH} + else + echo "Skip: push -f ${GIT_REMOTE_REPO} "v${VERSION}" ${MASTER_BRANCH} (--dryRun)" + fi +} + +main "$@" diff --git a/.circleci/update_deployment_config b/.circleci/update_deployment_config new file mode 100755 index 00000000000..64550f19797 --- /dev/null +++ b/.circleci/update_deployment_config @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## +## This script run AFTER OTP is build on the CI Server. It checkout the +## deployment config and update the version number, commit and push. This +## trigger the deployment config build witch create and deploy a new OTP2 +## docker image. +## +## Note! There is no need to run this script locally, unless you want to debug it. +## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## + + +set -euo pipefail + +OTP_DEPLOYMENT_CONFIG=target/otp-deployment-config +DOCKER_FILE=Dockerfile + +VERSION=$(xmllint --xpath "//*[local-name()='project']/*[local-name()='version']/text()" pom.xml) + +echo "Checkout otp-deplyment-config in ${OTP_DEPLOYMENT_CONFIG}" +git clone -n https://github.com/entur/otp-deployment-config.git --depth 1 ${OTP_DEPLOYMENT_CONFIG} + +pushd ${OTP_DEPLOYMENT_CONFIG} + +echo "Checkout latest version of master Dockerfile" +git checkout master ${DOCKER_FILE} + +echo "Update OTP version number in ${DOCKER_FILE}" +if [[ "$OSTYPE" == "darwin"* ]]; then + sed -E -i '' "s/OTP_VERSION=[\.0-9]+-entur-[0-9]+/OTP_VERSION=${VERSION}/" ${DOCKER_FILE} +else + sed -E -i "s/OTP_VERSION=[\.0-9]+-entur-[0-9]+/OTP_VERSION=${VERSION}/" ${DOCKER_FILE} +fi + +if [[ "$CIRCLE_USERNAME" != "" ]]; then + git config user.email "circleci@entur.no" + git config user.name "circleci ($CIRCLE_USERNAME)" +fi + +echo "Add and commit Dockerfile" +git commit -m "New OTP2 Version ${VERSION}" ${DOCKER_FILE} + +echo "Push otp-deployment-config to GitHub" +git push + +popd \ No newline at end of file diff --git a/.gitignore b/.gitignore index a90f06cdcb0..3bc96654225 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .project .pydevproject .settings +.prepare_release.tmp .sonar .nvmrc *~ diff --git a/pom.xml b/pom.xml index 0a41eb308d1..922ab5db5c4 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 123 + EN-0053 30.0 2.48.1 @@ -84,11 +84,11 @@ - - github - OpenTripPlanner Maven Repository on Github Packages - https://maven.pkg.github.com/${GITHUB_REPOSITORY}/ - + + snapshots + entur2-snapshots + https://entur2.jfrog.io/entur2/libs-snapshot-local + diff --git a/src/main/java/EnturUpdatePomVersion.java b/src/main/java/EnturUpdatePomVersion.java new file mode 100644 index 00000000000..97bd72b3c6e --- /dev/null +++ b/src/main/java/EnturUpdatePomVersion.java @@ -0,0 +1,135 @@ +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.regex.Pattern; + +public class EnturUpdatePomVersion { + + private static final String VERSION_SEP = "-"; + private static final String ENTUR_PREFIX = "entur" + VERSION_SEP; + private static final Path POM_FILE = Path.of("pom.xml"); + + private final List tags = new ArrayList<>(); + private final List pomFile = new ArrayList<>(); + private String mainVersion; + private int versionNumber = 0; + + public static void main(String[] args) { + try { + new EnturUpdatePomVersion().withArgs(args).run(); + } catch (Exception e) { + System.err.println(e.getMessage()); + e.printStackTrace(System.err); + System.exit(10); + } + } + + private EnturUpdatePomVersion withArgs(String[] args) throws IOException { + if (args.length != 1 || args[0].matches("(?i)-h|--help")) { + printHelp(); + System.exit(1); + } + String arg = args[0]; + + if (arg.matches("\\d+")) { + versionNumber = resolveVersionFromNumericString(arg); + } else { + tags.addAll(readTagsFromFile(arg)); + } + return this; + } + + private void run() throws IOException { + readAndReplaceVersion(); + replacePomFile(); + } + + public void readAndReplaceVersion() throws IOException { + var pattern = Pattern.compile( + "(\\s*)(\\d+.\\d+.\\d+)" + + VERSION_SEP + + "(" + + ENTUR_PREFIX + + "\\d+|SNAPSHOT)(\\s*)" + ); + boolean found = false; + int i = 0; + + for (String line : Files.readAllLines(POM_FILE, UTF_8)) { + // Look for the version in the 25 first lines + if (!found) { + var m = pattern.matcher(line); + if (m.matches()) { + mainVersion = m.group(2); + String newVersion = mainVersion + VERSION_SEP + ENTUR_PREFIX + resolveVersionNumber(); + line = m.group(1) + newVersion + m.group(4); + System.out.println(newVersion); + found = true; + } + if (++i == 25) { + throw new IllegalStateException( + "Version not found in first 25 lines of the pom.xml file." + ); + } + } + pomFile.add(line); + } + if (!found) { + throw new IllegalStateException( + "Version not found in 'pom.xml'. Nothing matching pattern: " + pattern + ); + } + } + + public void replacePomFile() throws IOException { + Files.delete(POM_FILE); + Files.write(POM_FILE, pomFile, UTF_8); + } + + private static void printHelp() { + System.err.println( + "Use this small program to replace the OTP version '2.1.0-SNAPSHOT' \n" + + "with a new version number with a Entur qualifier like '2.1.0-entur-1'.\n" + + "\n" + + "Usage:\n" + + " $ java -cp .circleci UpdatePomVersion \n" + ); + } + + private int resolveVersionNumber() { + var pattern = Pattern.compile("v" + mainVersion + VERSION_SEP + ENTUR_PREFIX + "(\\d+)"); + int maxTagVersion = tags + .stream() + .mapToInt(tag -> { + var m = pattern.matcher(tag); + return m.matches() ? Integer.parseInt(m.group(1)) : -999; + }) + .max() + .orElse(-999); + + return 1 + Math.max(maxTagVersion, versionNumber); + } + + public static int resolveVersionFromNumericString(String arg) { + try { + return Integer.parseInt(arg); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Unable to parse input, decimal number expected: '" + arg + "'" + ); + } + } + + private static Collection readTagsFromFile(String arg) throws IOException { + var tags = Files.readAllLines(Path.of(arg)); + if (tags.isEmpty()) { + throw new IllegalStateException("Unable to load git tags from file: " + arg); + } + return tags; + } +} From 0d03d6f26cc6b5cfd33415204fa4e186e13ad8e5 Mon Sep 17 00:00:00 2001 From: Hannes Junnila Date: Fri, 9 Dec 2022 09:38:06 +0200 Subject: [PATCH 11/26] =?UTF-8?q?hack:=20Add=20train=20option=20for=20S?= =?UTF-8?q?=C3=B8rlandsbanen.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/Configuration.md | 61 ++++---- docs/RouteRequest.md | 1 + src/ext/graphql/transmodelapi/schema.graphql | 2 + .../ConcurrentCompositeWorker.java | 53 +++++++ .../sorlandsbanen/EnturHackSorlandsBanen.java | 141 ++++++++++++++++++ .../ext/sorlandsbanen/PathKey.java | 51 +++++++ .../RaptorWorkerResultComposite.java | 88 +++++++++++ .../transmodelapi/model/plan/TripQuery.java | 8 + .../common/RequestToPreferencesMapper.java | 1 + .../api/common/RoutingResource.java | 3 + .../framework/application/OTPFeature.java | 1 + .../raptor/api/request/RaptorRequest.java | 4 + .../api/request/RaptorRequestBuilder.java | 6 + .../service/RangeRaptorDynamicSearch.java | 10 +- .../raptoradapter/router/TransitRouter.java | 3 +- .../transit/cost/DefaultCostCalculator.java | 16 ++ .../transit/cost/FactorStrategy.java | 2 +- .../cost/IndexBasedFactorStrategy.java | 4 +- .../transit/mappers/RaptorRequestMapper.java | 16 +- .../RaptorRoutingRequestTransitData.java | 33 ++++ .../preference/TransitPreferences.java | 17 +++ .../routerequest/RouteRequestConfig.java | 7 + .../raptor/RaptorArchitectureTest.java | 6 +- 23 files changed, 493 insertions(+), 41 deletions(-) create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java diff --git a/docs/Configuration.md b/docs/Configuration.md index 5692b5f87d0..5073dafed31 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -219,37 +219,36 @@ Here is a list of all features which can be toggled on/off and their default val -| Feature | Description | Enabled by default | Sandbox | -|--------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------:|:-------:| -| `APIBikeRental` | Enable the bike rental endpoint. | ✓️ | | -| `APIServerInfo` | Enable the server info endpoint. | ✓️ | | -| `APIGraphInspectorTile` | Enable the inspector endpoint for graph information for inspection/debugging purpose. | ✓️ | | -| `APIUpdaterStatus` | Enable endpoint for graph updaters status. | ✓️ | | -| `ConsiderPatternsForDirectTransfers` | Enable limiting transfers so that there is only a single transfer to each pattern. | ✓️ | | -| `DebugClient` | Enable the debug web client located at the root of the web server. | ✓️ | | -| `FloatingBike` | Enable floating bike routing. | ✓️ | | -| `GtfsGraphQlApi` | Enable GTFS GraphQL API. | ✓️ | | -| `GtfsGraphQlApiRentalStationFuzzyMatching` | Does vehicleRentalStation query also allow ids that are not feed scoped. | | | -| `MinimumTransferTimeIsDefinitive` | If the minimum transfer time is a lower bound (default) or the definitive time for the transfer. Set this to `true` if you want to set a transfer time lower than what OTP derives from OSM data. | | | -| `OptimizeTransfers` | OTP will inspect all itineraries found and optimize where (which stops) the transfer will happen. Waiting time, priority and guaranteed transfers are taken into account. | ✓️ | | -| `ParallelRouting` | Enable performing parts of the trip planning in parallel. | | | -| `TransferConstraints` | Enforce transfers to happen according to the _transfers.txt_(GTFS) and Interchanges(NeTEx). Turing this _off_ will increase the routing performance a little. | ✓️ | | -| `ActuatorAPI` | Endpoint for actuators (service health status). | | ✓️ | -| `AsyncGraphQLFetchers` | Whether the @async annotation in the GraphQL schema should lead to the fetch being executed asynchronously. This allows batch or alias queries to run in parallel at the cost of consuming extra threads. | | | -| `DataOverlay` | Enable usage of data overlay when calculating costs for the street network. | | ✓️ | -| `FaresV2` | Enable import of GTFS-Fares v2 data. | | ✓️ | -| `FlexRouting` | Enable FLEX routing. | | ✓️ | -| `GoogleCloudStorage` | Enable Google Cloud Storage integration. | | ✓️ | -| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with realtime data | | ✓️ | -| `ReportApi` | Enable the report API. | | ✓️ | -| `RestAPIPassInDefaultConfigAsJson` | Enable a default RouteRequest to be passed in as JSON on the REST API - FOR DEBUGGING ONLY! | | | -| `SandboxAPIGeocoder` | Enable the Geocoder API. | | ✓️ | -| `SandboxAPIMapboxVectorTilesApi` | Enable Mapbox vector tiles API. | | ✓️ | -| `SandboxAPIParkAndRideApi` | Enable park-and-ride endpoint. | | ✓️ | -| `SandboxAPITransmodelApi` | Enable Entur Transmodel(NeTEx) GraphQL API. | | ✓️ | -| `SandboxAPITravelTime` | Enable the isochrone/travel time surface API. | | ✓️ | -| `TransferAnalyzer` | Analyze transfers during graph build. | | ✓️ | -| `VehicleToStopHeuristics` | Enable improved heuristic for park-and-ride queries. | | ✓️ | +| Feature | Description | Enabled by default | Sandbox | +|--------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------:|:-------:| +| `APIBikeRental` | Enable the bike rental endpoint. | ✓️ | | +| `APIServerInfo` | Enable the server info endpoint. | ✓️ | | +| `APIGraphInspectorTile` | Enable the inspector endpoint for graph information for inspection/debugging purpose. | ✓️ | | +| `APIUpdaterStatus` | Enable endpoint for graph updaters status. | ✓️ | | +| `ConsiderPatternsForDirectTransfers` | Enable limiting transfers so that there is only a single transfer to each pattern. | ✓️ | | +| `DebugClient` | Enable the debug web client located at the root of the web server. | ✓️ | | +| `FloatingBike` | Enable floating bike routing. | ✓️ | | +| `GtfsGraphQlApi` | Enable GTFS GraphQL API. | ✓️ | | +| `MinimumTransferTimeIsDefinitive` | If the minimum transfer time is a lower bound (default) or the definitive time for the transfer. Set this to `true` if you want to set a transfer time lower than what OTP derives from OSM data. | | | +| `OptimizeTransfers` | OTP will inspect all itineraries found and optimize where (which stops) the transfer will happen. Waiting time, priority and guaranteed transfers are taken into account. | ✓️ | | +| `ParallelRouting` | Enable performing parts of the trip planning in parallel. | | | +| `TransferConstraints` | Enforce transfers to happen according to the _transfers.txt_(GTFS) and Interchanges(NeTEx). Turing this _off_ will increase the routing performance a little. | ✓️ | | +| `ActuatorAPI` | Endpoint for actuators (service health status). | | ✓️ | +| `AsyncGraphQLFetchers` | Whether the @async annotation in the GraphQL schema should lead to the fetch being executed asynchronously. This allows batch or alias queries to run in parallel at the cost of consuming extra threads. | | | +| `DataOverlay` | Enable usage of data overlay when calculating costs for the street network. | | ✓️ | +| `FaresV2` | Enable import of GTFS-Fares v2 data. | | ✓️ | +| `FlexRouting` | Enable FLEX routing. | | ✓️ | +| `GoogleCloudStorage` | Enable Google Cloud Storage integration. | | ✓️ | +| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with realtime data | | ✓️ | +| `ReportApi` | Enable the report API. | | ✓️ | +| `RestAPIPassInDefaultConfigAsJson` | Enable a default RouteRequest to be passed in as JSON on the REST API - FOR DEBUGGING ONLY! | | | +| `SandboxAPIGeocoder` | Enable the Geocoder API. | | ✓️ | +| `SandboxAPIMapboxVectorTilesApi` | Enable Mapbox vector tiles API. | | ✓️ | +| `SandboxAPIParkAndRideApi` | Enable park-and-ride endpoint. | | ✓️ | +| `SandboxAPITransmodelApi` | Enable Entur Transmodel(NeTEx) GraphQL API. | | ✓️ | +| `SandboxAPITravelTime` | Enable the isochrone/travel time surface API. | | ✓️ | +| `TransferAnalyzer` | Analyze transfers during graph build. | | ✓️ | +| `VehicleToStopHeuristics` | Enable improved heuristic for park-and-ride queries. | | ✓️ | diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index f814753df4a..b6284d22e4d 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -46,6 +46,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe | elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 | | elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | | escalatorReluctance | `double` | A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time | *Optional* | `1.5` | 2.4 | +| extraSearchCoachReluctance | `double` | TODO | *Optional* | `0.0` | 2.1 | | geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | | ignoreRealtimeUpdates | `boolean` | When true, realtime updates are ignored during this search. | *Optional* | `false` | 2.0 | | [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql index 474da1e34d7..eaeed9ecb2a 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/ext/graphql/transmodelapi/schema.graphql @@ -793,6 +793,8 @@ type QueryType { dateTime: DateTime, "Debug the itinerary-filter-chain. OTP will attach a system notice to itineraries instead of removing them. This is very convenient when tuning the filters." debugItineraryFilter: Boolean = false, + "FOR TESTING ONLY" + extraSearchCoachReluctance: Float, "A list of filters for which trips should be included. A trip will be included if it matches with at least one filter. An empty list of filters means that all trips should be included. If a search include this parameter, \"whiteListed\", \"banned\" & \"modes.transportModes\" filters will be ignored." filters: [TripFilterInput!], "The start location" diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java new file mode 100644 index 00000000000..e80e106ce1e --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java @@ -0,0 +1,53 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.opentripplanner.framework.application.OTPFeature; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; +import org.opentripplanner.raptor.api.response.StopArrivals; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.multicriteria.McRaptorWorkerResult; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TripScheduleWithOffset; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class ConcurrentCompositeWorker implements RaptorWorker { + + private static final Logger LOG = LoggerFactory.getLogger(ConcurrentCompositeWorker.class); + + private final RaptorWorker mainWorker; + private final RaptorWorker alternativeWorker; + + ConcurrentCompositeWorker(RaptorWorker mainWorker, RaptorWorker alternativeWorker) { + this.mainWorker = mainWorker; + this.alternativeWorker = alternativeWorker; + } + + @Override + public RaptorWorkerResult route() { + if (OTPFeature.ParallelRouting.isOn()) { + var mainResultFuture = CompletableFuture.supplyAsync(mainWorker::route); + var alternativeResultFuture = CompletableFuture.supplyAsync(alternativeWorker::route); + + try { + return new RaptorWorkerResultComposite<>( + mainResultFuture.get(), + alternativeResultFuture.get() + ); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } else { + var mainResult = mainWorker.route(); + var alternativeResult = alternativeWorker.route(); + return new RaptorWorkerResultComposite<>(mainResult, alternativeResult); + } + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java new file mode 100644 index 00000000000..2b52e850f42 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java @@ -0,0 +1,141 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.function.Function; +import org.opentripplanner.framework.geometry.SphericalDistanceLibrary; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.model.GenericLocation; +import org.opentripplanner.raptor.api.model.RaptorAccessEgress; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.request.RaptorRequest; +import org.opentripplanner.raptor.api.request.SearchParams; +import org.opentripplanner.raptor.configure.RaptorConfig; +import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; +import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.FactorStrategy; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.IndexBasedFactorStrategy; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.RaptorRoutingRequestTransitData; +import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.site.StopLocation; + +public class EnturHackSorlandsBanen { + + private static final double SOUTH_BOARDER_LIMIT = 59.1; + private static final int MIN_DISTANCE_LIMIT = 120_000; + + public static boolean match(RaptorRequest mcRequest) { + return mcRequest.extraSearchCoachReluctance > 0.1; + } + + public static RaptorWorker worker( + RaptorConfig config, + RaptorTransitDataProvider transitData, + RaptorRequest mcRequest, + Heuristics destinationHeuristics + ) { + //noinspection unchecked + RaptorTransitDataProvider altTransitData = (RaptorTransitDataProvider) ( + (RaptorRoutingRequestTransitData) transitData + ).enturHackSorlandsbanen(mapFactors(mcRequest.extraSearchCoachReluctance)); + + return new ConcurrentCompositeWorker<>( + config.createMcWorker(transitData, mcRequest, destinationHeuristics), + config.createMcWorker(altTransitData, mcRequest, destinationHeuristics) + ); + } + + public static RaptorRequest enableHack( + RaptorRequest raptorRequest, + RouteRequest request, + TransitLayer transitLayer + ) { + if (request.preferences().transit().extraSearchCoachReluctance() < 0.1) { + return raptorRequest; + } + + SearchParams params = raptorRequest.searchParams(); + + WgsCoordinate from = findStopCoordinate(request.from(), params.accessPaths(), transitLayer); + WgsCoordinate to = findStopCoordinate(request.to(), params.egressPaths(), transitLayer); + + if (from.latitude() > SOUTH_BOARDER_LIMIT && to.latitude() > SOUTH_BOARDER_LIMIT) { + return raptorRequest; + } + + double distanceMeters = SphericalDistanceLibrary.distance( + from.latitude(), + from.longitude(), + to.latitude(), + to.longitude() + ); + + if (distanceMeters < MIN_DISTANCE_LIMIT) { + return raptorRequest; + } + + raptorRequest.extraSearchCoachReluctance = + request.preferences().transit().extraSearchCoachReluctance(); + return raptorRequest; + } + + /* private methods */ + + private static Function mapFactors( + final double extraSearchCoachReluctance + ) { + return (FactorStrategy originalFactors) -> { + int[] modeReluctance = new int[TransitMode.values().length]; + for (TransitMode mode : TransitMode.values()) { + int index = mode.ordinal(); + int originalFactor = originalFactors.factor(index); + modeReluctance[index] = + mode == TransitMode.COACH + ? (int) (extraSearchCoachReluctance * originalFactor + 0.5) + : originalFactor; + } + return new IndexBasedFactorStrategy(modeReluctance); + }; + } + + /** + * Find a coordinate matching the given location, in order: + * - First return the coordinate of the location if it exists. + * - Then loop through the access/egress stops and try to find the + * stop or station given by the location id, return the stop/station coordinate. + * - Return the fist stop in the access/egress list coordinate. + */ + @SuppressWarnings("ConstantConditions") + private static WgsCoordinate findStopCoordinate( + GenericLocation location, + Collection accessEgress, + TransitLayer transitLayer + ) { + if (location.lat != null) { + return new WgsCoordinate(location.lat, location.lng); + } + + StopLocation firstStop = null; + for (RaptorAccessEgress it : accessEgress) { + StopLocation stop = transitLayer.getStopByIndex(it.stop()); + if (stop.getId().equals(location.stopId)) { + return stop.getCoordinate(); + } + if (idIsParentStation(stop, location.stopId)) { + return stop.getParentStation().getCoordinate(); + } + if (firstStop == null) { + firstStop = stop; + } + } + return firstStop.getCoordinate(); + } + + private static boolean idIsParentStation(StopLocation stop, FeedScopedId pId) { + return stop.getParentStation() != null && stop.getParentStation().getId().equals(pId); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java new file mode 100644 index 00000000000..94267b3e7c2 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java @@ -0,0 +1,51 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; + +final class PathKey { + + private final int hash; + + PathKey(RaptorPath path) { + this.hash = hash(path); + } + + private static int hash(RaptorPath path) { + if (path == null) { + return 0; + } + int result = 1; + + PathLeg leg = path.accessLeg(); + + while (!leg.isEgressLeg()) { + result = 31 * result + leg.toStop(); + result = 31 * result + leg.toTime(); + + if (leg.isTransitLeg()) { + result = 31 * result + leg.asTransitLeg().trip().pattern().debugInfo().hashCode(); + } + leg = leg.nextLeg(); + } + result = 31 * result + leg.toTime(); + + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o.getClass() != PathKey.class) { + return false; + } + return hash == ((PathKey) o).hash; + } + + @Override + public int hashCode() { + return hash; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java new file mode 100644 index 00000000000..094e652fc4c --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java @@ -0,0 +1,88 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.internalapi.SingleCriteriaStopArrivals; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TripScheduleWithOffset; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RaptorWorkerResultComposite + implements RaptorWorkerResult { + + private static final Logger LOG = LoggerFactory.getLogger(RaptorWorkerResultComposite.class); + + private RaptorWorkerResult mainResult; + private RaptorWorkerResult alternativeResult; + + public RaptorWorkerResultComposite( + RaptorWorkerResult mainResult, + RaptorWorkerResult alternativeResult + ) { + this.mainResult = mainResult; + this.alternativeResult = alternativeResult; + } + + @Override + public Collection> extractPaths() { + Map> paths = new HashMap<>(); + addAll(paths, mainResult.extractPaths()); + addExtraRail(paths, alternativeResult.extractPaths()); + return paths.values(); + } + + @Override + public SingleCriteriaStopArrivals extractBestOverallArrivals() { + return mainResult.extractBestOverallArrivals(); + } + + @Override + public SingleCriteriaStopArrivals extractBestTransitArrivals() { + return mainResult.extractBestTransitArrivals(); + } + + @Override + public SingleCriteriaStopArrivals extractBestNumberOfTransfers() { + return mainResult.extractBestNumberOfTransfers(); + } + + @Override + public boolean isDestinationReached() { + return mainResult.isDestinationReached(); + } + + private void addExtraRail(Map> map, Collection> paths) { + paths.forEach(p -> { + if (hasRail(p)) { + var v = map.put(new PathKey(p), p); + LOG.debug("Ex.Rail {} : {}", (v == null ? "ADD " : "SKIP"), p); + } else { + LOG.debug("Ex. NOT Rail : {}", p); + } + }); + } + + private void addAll(Map> map, Collection> paths) { + paths.forEach(p -> { + var v = map.put(new PathKey(p), p); + LOG.debug("Normal {} : {}", (v == null ? "ADD " : "SKIP"), p); + }); + } + + private static boolean hasRail(RaptorPath path) { + return path + .legStream() + .filter(PathLeg::isTransitLeg) + .anyMatch(leg -> { + var trip = (TripScheduleWithOffset) leg.asTransitLeg().trip(); + var mode = trip.getOriginalTripPattern().getMode(); + return mode == TransitMode.RAIL; + }); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java index 8fa4fa31512..e1b1ba3d59f 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java @@ -563,6 +563,14 @@ public static GraphQLFieldDefinition create( ) .build() ) + .argument( + GraphQLArgument + .newArgument() + .name("extraSearchCoachReluctance") + .description("FOR TESTING ONLY") + .type(Scalars.GraphQLFloat) + .build() + ) .dataFetcher(environment -> new TransmodelGraphQLPlanner().plan(environment)) .build(); } diff --git a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java index 20ca8054308..ff61f201624 100644 --- a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java @@ -89,6 +89,7 @@ private BoardAndAlightSlack mapTransit() { req.relaxTransitSearchGeneralizedCostAtDestination, value -> tr.withRaptor(it -> it.withRelaxGeneralizedCostAtDestination(value)) ); + setIfNotNull(req.extraSearchCoachReluctance, tr::setExtraSearchCoachReluctance); }); return new BoardAndAlightSlack( diff --git a/src/main/java/org/opentripplanner/api/common/RoutingResource.java b/src/main/java/org/opentripplanner/api/common/RoutingResource.java index e60bfe41b82..f214f0126c6 100644 --- a/src/main/java/org/opentripplanner/api/common/RoutingResource.java +++ b/src/main/java/org/opentripplanner/api/common/RoutingResource.java @@ -200,6 +200,9 @@ public abstract class RoutingResource { @QueryParam("carReluctance") protected Double carReluctance; + @QueryParam("extraSearchCoachReluctance") + protected Double extraSearchCoachReluctance; + /** * How much worse is waiting for a transit vehicle than being on a transit vehicle, as a * multiplier. The default value treats wait and on-vehicle time as the same. diff --git a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index a80bd26bad1..11e5cc98aac 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -29,6 +29,7 @@ public enum OTPFeature { ), DebugClient(true, false, "Enable the debug web client located at the root of the web server."), FloatingBike(true, false, "Enable floating bike routing."), + HackSorlandsbanen(false, true, "Includ Sørlandsbanen"), GtfsGraphQlApi(true, false, "Enable GTFS GraphQL API."), GtfsGraphQlApiRentalStationFuzzyMatching( false, diff --git a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java index 1f0621b8d36..6b99e7ae238 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java @@ -29,6 +29,9 @@ public class RaptorRequest { private final DebugRequest debug; private final RaptorTimers performanceTimers; + // HACK SØRLANDSBANEN + public double extraSearchCoachReluctance = 0.0; + private RaptorRequest() { searchParams = SearchParams.defaults(); profile = RaptorProfile.MULTI_CRITERIA; @@ -49,6 +52,7 @@ private RaptorRequest() { this.multiCriteria = builder.multiCriteria(); this.performanceTimers = builder.performanceTimers(); this.debug = builder.debug().build(); + this.extraSearchCoachReluctance = builder.extraSearchCoachReluctance; verify(); } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java index ed2aab3de20..7bb8b538843 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java @@ -34,6 +34,9 @@ public class RaptorRequestBuilder { // Performance monitoring private RaptorTimers performanceTimers; + /** HACK SØRLANDSBANEN */ + public double extraSearchCoachReluctance; + // Algorithm private RaptorProfile profile; @@ -60,6 +63,9 @@ public RaptorRequestBuilder() { // Debug this.debug = new DebugRequestBuilder(defaults.debug()); + + // HACK SØRLANDSBANEN + this.extraSearchCoachReluctance = defaults.extraSearchCoachReluctance; } public SearchParamsBuilder searchParams() { diff --git a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java index fe8bb884912..86b10f7d96f 100644 --- a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java +++ b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java @@ -11,6 +11,8 @@ import java.util.concurrent.Future; import java.util.stream.Collectors; import javax.annotation.Nullable; +import org.opentripplanner.ext.sorlandsbanen.EnturHackSorlandsBanen; +import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.raptor.RaptorService; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; @@ -134,7 +136,13 @@ private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request) // Create worker if (request.profile().is(MULTI_CRITERIA)) { - raptorWorker = config.createMcWorker(transitData, request, getDestinationHeuristics()); + // HACK SØRLANDSBANEN + if (OTPFeature.HackSorlandsbanen.isOn() && EnturHackSorlandsBanen.match(request)) { + raptorWorker = + EnturHackSorlandsBanen.worker(config, transitData, request, getDestinationHeuristics()); + } else { + raptorWorker = config.createMcWorker(transitData, request, getDestinationHeuristics()); + } } else { raptorWorker = config.createStdWorker(transitData, request); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index b9f9685341c..e59c4d1673c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -128,7 +128,8 @@ private TransitRouterResult route() { accessEgresses.getAccesses(), accessEgresses.getEgresses(), accessEgresses.calculateMaxAccessTimePenalty(), - serverContext.meterRegistry() + serverContext.meterRegistry(), + transitLayer ); // Route transit diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java index b738d63054e..4f39b2631fe 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java @@ -1,5 +1,6 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit.cost; +import java.util.function.Function; import javax.annotation.Nullable; import org.opentripplanner.model.transfer.TransferConstraint; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; @@ -211,4 +212,19 @@ private int boardingCostConstrainedTransfer( // fallback to regular transfer return boardingCostRegularTransfer(firstBoarding, prevArrivalTime, boardStop, boardTime); } + + /*-- HACK SØRLANDSBANEN :: BEGIN --*/ + + public DefaultCostCalculator( + DefaultCostCalculator original, + Function modeReluctanceMapper + ) { + this.boardCostOnly = original.boardCostOnly; + this.boardAndTransferCost = original.boardAndTransferCost; + this.waitFactor = original.waitFactor; + this.transferCostOnly = original.transferCostOnly; + this.transitFactors = modeReluctanceMapper.apply(original.transitFactors); + this.stopTransferCost = original.stopTransferCost; + } + /*-- HACK SØRLANDSBANEN :: END --*/ } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java index fba2a7cfe38..8a14c27566d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java @@ -6,7 +6,7 @@ *

* The class and methods are {@code final} to help the JIT compiler optimize the use of this class. */ -interface FactorStrategy { +public interface FactorStrategy { /** * Return the factor for the given index. */ diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java index 152f199248c..4c66b5b452e 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java @@ -6,12 +6,12 @@ * This class keep a facto for each index and the minimum factor for fast retrieval during Raptor * search. */ -final class IndexBasedFactorStrategy implements FactorStrategy { +public final class IndexBasedFactorStrategy implements FactorStrategy { private final int[] factors; private final int minFactor; - private IndexBasedFactorStrategy(int[] factors) { + public IndexBasedFactorStrategy(int[] factors) { this.factors = factors; this.minFactor = findMinimumFactor(factors); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index 8e6121edffe..5b0de00bc6c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -8,6 +8,7 @@ import java.time.ZonedDateTime; import java.util.Collection; import java.util.List; +import org.opentripplanner.ext.sorlandsbanen.EnturHackSorlandsBanen; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; @@ -17,6 +18,7 @@ import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; import org.opentripplanner.raptor.rangeraptor.SystemErrDebugLogger; import org.opentripplanner.routing.algorithm.raptoradapter.router.performance.PerformanceTimersForRaptor; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.transit.model.site.StopLocation; @@ -31,6 +33,8 @@ public class RaptorRequestMapper { private final boolean isMultiThreadedEnbled; private final MeterRegistry meterRegistry; + private final TransitLayer transitLayer; + private RaptorRequestMapper( RouteRequest request, boolean isMultiThreaded, @@ -38,13 +42,15 @@ private RaptorRequestMapper( Collection egressPaths, Duration searchWindowAccessSlack, long transitSearchTimeZeroEpocSecond, - MeterRegistry meterRegistry + MeterRegistry meterRegistry, + TransitLayer transitLayer ) { this.request = request; this.isMultiThreadedEnbled = isMultiThreaded; this.accessPaths = accessPaths; this.egressPaths = egressPaths; this.searchWindowAccessSlack = searchWindowAccessSlack; + this.transitLayer = transitLayer; this.transitSearchTimeZeroEpocSecond = transitSearchTimeZeroEpocSecond; this.meterRegistry = meterRegistry; } @@ -56,7 +62,8 @@ public static RaptorRequest mapRequest( Collection accessPaths, Collection egressPaths, Duration searchWindowAccessSlack, - MeterRegistry meterRegistry + MeterRegistry meterRegistry, + TransitLayer transitLayer ) { return new RaptorRequestMapper( request, @@ -65,7 +72,8 @@ public static RaptorRequest mapRequest( egressPaths, searchWindowAccessSlack, transitSearchTimeZero.toEpochSecond(), - meterRegistry + meterRegistry, + transitLayer ) .doMap(); } @@ -171,7 +179,7 @@ private RaptorRequest doMap() { ) ); - return builder.build(); + return EnturHackSorlandsBanen.enableHack(builder.build(), request, transitLayer); } private List mapPassThroughPoints() { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java index a0b62414181..df5996009ec 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java @@ -4,6 +4,7 @@ import java.util.BitSet; import java.util.Iterator; import java.util.List; +import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.opentripplanner.framework.application.OTPFeature; @@ -27,6 +28,8 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.constrainedtransfer.ConstrainedBoardingSearch; import org.opentripplanner.routing.algorithm.raptoradapter.transit.constrainedtransfer.ConstrainedTransfersForPatterns; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.CostCalculatorFactory; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.DefaultCostCalculator; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.FactorStrategy; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.GeneralizedCostParametersMapper; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.transit.model.network.RoutingTripPattern; @@ -241,4 +244,34 @@ public RaptorConstrainedBoardingSearch transferConstraintsReverseS } return new ConstrainedBoardingSearch(false, toStopTransfers, fromStopTransfers); } + + /*-- HACK SØRLANDSBANEN :: BEGIN --*/ + + private RaptorRoutingRequestTransitData( + RaptorRoutingRequestTransitData original, + Function mapFactors + ) { + this.transitLayer = original.transitLayer; + this.transitSearchTimeZero = original.transitSearchTimeZero; + this.activeTripPatternsPerStop = original.activeTripPatternsPerStop; + this.patternIndex = original.patternIndex; + this.transferIndex = original.transferIndex; + this.transferService = original.transferService; + this.constrainedTransfers = original.constrainedTransfers; + this.validTransitDataStartTime = original.validTransitDataStartTime; + this.validTransitDataEndTime = original.validTransitDataEndTime; + this.generalizedCostCalculator = + new DefaultCostCalculator<>( + (DefaultCostCalculator) original.generalizedCostCalculator, + mapFactors + ); + this.slackProvider = original.slackProvider(); + } + + public RaptorTransitDataProvider enturHackSorlandsbanen( + Function mapFactors + ) { + return new RaptorRoutingRequestTransitData(this, mapFactors); + } + /*-- HACK SØRLANDSBANEN :: END --*/ } diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java index 7da4516d782..96945693cdd 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java @@ -31,6 +31,7 @@ public final class TransitPreferences implements Serializable { private final boolean includePlannedCancellations; private final boolean includeRealtimeCancellations; private final RaptorPreferences raptor; + private final double extraSearchCoachReluctance; private TransitPreferences() { this.boardSlack = this.alightSlack = DurationForEnum.of(TransitMode.class).build(); @@ -41,6 +42,7 @@ private TransitPreferences() { this.includePlannedCancellations = false; this.includeRealtimeCancellations = false; this.raptor = RaptorPreferences.DEFAULT; + this.extraSearchCoachReluctance = 0.0; } private TransitPreferences(Builder builder) { @@ -53,6 +55,7 @@ private TransitPreferences(Builder builder) { this.includePlannedCancellations = builder.includePlannedCancellations; this.includeRealtimeCancellations = builder.includeRealtimeCancellations; this.raptor = requireNonNull(builder.raptor); + this.extraSearchCoachReluctance = builder.extraSearchCoachReluctance; } public static Builder of() { @@ -153,6 +156,11 @@ public RaptorPreferences raptor() { return raptor; } + /** Zero means turned off. HACK SØRLANDSBANEN */ + public double extraSearchCoachReluctance() { + return extraSearchCoachReluctance; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -167,6 +175,7 @@ public boolean equals(Object o) { alightSlack.equals(that.alightSlack) && reluctanceForMode.equals(that.reluctanceForMode) && unpreferredCost.equals(that.unpreferredCost) && + extraSearchCoachReluctance == that.extraSearchCoachReluctance && raptor.equals(that.raptor) ); } @@ -182,6 +191,7 @@ public int hashCode() { ignoreRealtimeUpdates, includePlannedCancellations, includeRealtimeCancellations, + extraSearchCoachReluctance, raptor ); } @@ -229,6 +239,7 @@ public static class Builder { private boolean includePlannedCancellations; private boolean includeRealtimeCancellations; private RaptorPreferences raptor; + private double extraSearchCoachReluctance; public Builder(TransitPreferences original) { this.original = original; @@ -241,6 +252,7 @@ public Builder(TransitPreferences original) { this.includePlannedCancellations = original.includePlannedCancellations; this.includeRealtimeCancellations = original.includeRealtimeCancellations; this.raptor = original.raptor; + this.extraSearchCoachReluctance = original.extraSearchCoachReluctance; } public TransitPreferences original() { @@ -305,6 +317,11 @@ public Builder withRaptor(Consumer body) { return this; } + public Builder setExtraSearchCoachReluctance(double extraSearchCoachReluctance) { + this.extraSearchCoachReluctance = extraSearchCoachReluctance; + return this; + } + public Builder apply(Consumer body) { body.accept(this); return this; diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index c89670b7e0a..db212bd31d3 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -352,6 +352,13 @@ The board time is added to the time when going from the stop (offboard) to onboa ) .asDoubleOptional() .ifPresent(it::withRelaxGeneralizedCostAtDestination) + ) + .setExtraSearchCoachReluctance( + c + .of("extraSearchCoachReluctance") + .since(V2_1) + .summary("TODO") + .asDouble(dft.extraSearchCoachReluctance()) ); } diff --git a/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java b/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java index 39022da42ca..aeca5a36ebf 100644 --- a/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java +++ b/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java @@ -39,6 +39,9 @@ public class RaptorArchitectureTest { private static final Package RR_STANDARD = RANGE_RAPTOR.subPackage("standard"); private static final Package RR_STD_CONFIGURE = RR_STANDARD.subPackage("configure"); private static final Package RR_CONTEXT = RANGE_RAPTOR.subPackage("context"); + private static final Package EXT_SORLANDSBANAN_HACK = Package.of( + "org.opentripplanner.ext.sorlandsbanen" + ); /** * Packages used by standard-range-raptor and multi-criteria-range-raptor. @@ -200,7 +203,8 @@ void enforcePackageDependenciesInRaptorService() { RAPTOR_UTIL, CONFIGURE, RR_INTERNAL_API, - RR_TRANSIT + RR_TRANSIT, + EXT_SORLANDSBANAN_HACK ) .verify(); } From a2a275611d2d2646cc8cf7afc5b231d7e94787f0 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 30 Oct 2023 09:10:45 +0100 Subject: [PATCH 12/26] Fix documentation --- docs/Configuration.md | 62 ++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index 5073dafed31..9fbf4858e8c 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -219,36 +219,38 @@ Here is a list of all features which can be toggled on/off and their default val -| Feature | Description | Enabled by default | Sandbox | -|--------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------:|:-------:| -| `APIBikeRental` | Enable the bike rental endpoint. | ✓️ | | -| `APIServerInfo` | Enable the server info endpoint. | ✓️ | | -| `APIGraphInspectorTile` | Enable the inspector endpoint for graph information for inspection/debugging purpose. | ✓️ | | -| `APIUpdaterStatus` | Enable endpoint for graph updaters status. | ✓️ | | -| `ConsiderPatternsForDirectTransfers` | Enable limiting transfers so that there is only a single transfer to each pattern. | ✓️ | | -| `DebugClient` | Enable the debug web client located at the root of the web server. | ✓️ | | -| `FloatingBike` | Enable floating bike routing. | ✓️ | | -| `GtfsGraphQlApi` | Enable GTFS GraphQL API. | ✓️ | | -| `MinimumTransferTimeIsDefinitive` | If the minimum transfer time is a lower bound (default) or the definitive time for the transfer. Set this to `true` if you want to set a transfer time lower than what OTP derives from OSM data. | | | -| `OptimizeTransfers` | OTP will inspect all itineraries found and optimize where (which stops) the transfer will happen. Waiting time, priority and guaranteed transfers are taken into account. | ✓️ | | -| `ParallelRouting` | Enable performing parts of the trip planning in parallel. | | | -| `TransferConstraints` | Enforce transfers to happen according to the _transfers.txt_(GTFS) and Interchanges(NeTEx). Turing this _off_ will increase the routing performance a little. | ✓️ | | -| `ActuatorAPI` | Endpoint for actuators (service health status). | | ✓️ | -| `AsyncGraphQLFetchers` | Whether the @async annotation in the GraphQL schema should lead to the fetch being executed asynchronously. This allows batch or alias queries to run in parallel at the cost of consuming extra threads. | | | -| `DataOverlay` | Enable usage of data overlay when calculating costs for the street network. | | ✓️ | -| `FaresV2` | Enable import of GTFS-Fares v2 data. | | ✓️ | -| `FlexRouting` | Enable FLEX routing. | | ✓️ | -| `GoogleCloudStorage` | Enable Google Cloud Storage integration. | | ✓️ | -| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with realtime data | | ✓️ | -| `ReportApi` | Enable the report API. | | ✓️ | -| `RestAPIPassInDefaultConfigAsJson` | Enable a default RouteRequest to be passed in as JSON on the REST API - FOR DEBUGGING ONLY! | | | -| `SandboxAPIGeocoder` | Enable the Geocoder API. | | ✓️ | -| `SandboxAPIMapboxVectorTilesApi` | Enable Mapbox vector tiles API. | | ✓️ | -| `SandboxAPIParkAndRideApi` | Enable park-and-ride endpoint. | | ✓️ | -| `SandboxAPITransmodelApi` | Enable Entur Transmodel(NeTEx) GraphQL API. | | ✓️ | -| `SandboxAPITravelTime` | Enable the isochrone/travel time surface API. | | ✓️ | -| `TransferAnalyzer` | Analyze transfers during graph build. | | ✓️ | -| `VehicleToStopHeuristics` | Enable improved heuristic for park-and-ride queries. | | ✓️ | +| Feature | Description | Enabled by default | Sandbox | +|--------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------:|:-------:| +| `APIBikeRental` | Enable the bike rental endpoint. | ✓️ | | +| `APIServerInfo` | Enable the server info endpoint. | ✓️ | | +| `APIGraphInspectorTile` | Enable the inspector endpoint for graph information for inspection/debugging purpose. | ✓️ | | +| `APIUpdaterStatus` | Enable endpoint for graph updaters status. | ✓️ | | +| `ConsiderPatternsForDirectTransfers` | Enable limiting transfers so that there is only a single transfer to each pattern. | ✓️ | | +| `DebugClient` | Enable the debug web client located at the root of the web server. | ✓️ | | +| `FloatingBike` | Enable floating bike routing. | ✓️ | | +| `HackSorlandsbanen` | Includ Sørlandsbanen | | ✓️ | +| `GtfsGraphQlApi` | Enable GTFS GraphQL API. | ✓️ | | +| `GtfsGraphQlApiRentalStationFuzzyMatching` | Does vehicleRentalStation query also allow ids that are not feed scoped. | | | +| `MinimumTransferTimeIsDefinitive` | If the minimum transfer time is a lower bound (default) or the definitive time for the transfer. Set this to `true` if you want to set a transfer time lower than what OTP derives from OSM data. | | | +| `OptimizeTransfers` | OTP will inspect all itineraries found and optimize where (which stops) the transfer will happen. Waiting time, priority and guaranteed transfers are taken into account. | ✓️ | | +| `ParallelRouting` | Enable performing parts of the trip planning in parallel. | | | +| `TransferConstraints` | Enforce transfers to happen according to the _transfers.txt_(GTFS) and Interchanges(NeTEx). Turing this _off_ will increase the routing performance a little. | ✓️ | | +| `ActuatorAPI` | Endpoint for actuators (service health status). | | ✓️ | +| `AsyncGraphQLFetchers` | Whether the @async annotation in the GraphQL schema should lead to the fetch being executed asynchronously. This allows batch or alias queries to run in parallel at the cost of consuming extra threads. | | | +| `DataOverlay` | Enable usage of data overlay when calculating costs for the street network. | | ✓️ | +| `FaresV2` | Enable import of GTFS-Fares v2 data. | | ✓️ | +| `FlexRouting` | Enable FLEX routing. | | ✓️ | +| `GoogleCloudStorage` | Enable Google Cloud Storage integration. | | ✓️ | +| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with realtime data | | ✓️ | +| `ReportApi` | Enable the report API. | | ✓️ | +| `RestAPIPassInDefaultConfigAsJson` | Enable a default RouteRequest to be passed in as JSON on the REST API - FOR DEBUGGING ONLY! | | | +| `SandboxAPIGeocoder` | Enable the Geocoder API. | | ✓️ | +| `SandboxAPIMapboxVectorTilesApi` | Enable Mapbox vector tiles API. | | ✓️ | +| `SandboxAPIParkAndRideApi` | Enable park-and-ride endpoint. | | ✓️ | +| `SandboxAPITransmodelApi` | Enable Entur Transmodel(NeTEx) GraphQL API. | | ✓️ | +| `SandboxAPITravelTime` | Enable the isochrone/travel time surface API. | | ✓️ | +| `TransferAnalyzer` | Analyze transfers during graph build. | | ✓️ | +| `VehicleToStopHeuristics` | Enable improved heuristic for park-and-ride queries. | | ✓️ | From b16abdc15882be45e87112b3afae434f4e5c4371 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 30 Oct 2023 09:13:52 +0100 Subject: [PATCH 13/26] Version 2.5.0-entur-5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 922ab5db5c4..e066ea2e3ec 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.5.0-SNAPSHOT + 2.5.0-entur-5 jar From b69065391b87c179587967b720264b6f453722fe Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 2 Nov 2023 13:40:10 +0100 Subject: [PATCH 14/26] Fix: Add back walk-reluctance in Transmodel API Chery picked: 5efd85df, b0042520, bb31141a --- src/ext/graphql/transmodelapi/schema.graphql | 2 +- .../mapping/PreferencesMapper.java | 4 +- .../preferences/BikePreferencesMapper.java | 24 +++++- .../preferences/CarPreferencesMapper.java | 21 ++++++ .../preferences/WalkPreferencesMapper.java | 6 +- .../transmodelapi/model/plan/TripQuery.java | 1 + .../_support/TestDataFetcherDecorator.java | 30 ++++++++ .../BikePreferencesMapperTest.java | 74 +++++++++++++++++++ .../preferences/CarPreferencesMapperTest.java | 25 +++++++ .../WalkPreferencesMapperTest.java | 31 ++++++++ 10 files changed, 211 insertions(+), 7 deletions(-) create mode 100644 src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/CarPreferencesMapper.java create mode 100644 src/test/java/org/opentripplanner/ext/transmodelapi/_support/TestDataFetcherDecorator.java create mode 100644 src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/BikePreferencesMapperTest.java create mode 100644 src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/CarPreferencesMapperTest.java create mode 100644 src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/WalkPreferencesMapperTest.java diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql index eaeed9ecb2a..e3d8f60cf7f 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/ext/graphql/transmodelapi/schema.graphql @@ -792,7 +792,7 @@ type QueryType { "Date and time for the earliest time the user is willing to start the journey (if arriveBy=false/not set) or the latest acceptable time of arriving (arriveBy=true). Defaults to now" dateTime: DateTime, "Debug the itinerary-filter-chain. OTP will attach a system notice to itineraries instead of removing them. This is very convenient when tuning the filters." - debugItineraryFilter: Boolean = false, + debugItineraryFilter: Boolean = false @deprecated(reason : "Use `itineraryFilter.debug` instead."), "FOR TESTING ONLY" extraSearchCoachReluctance: Float, "A list of filters for which trips should be included. A trip will be included if it matches with at least one filter. An empty list of filters means that all trips should be included. If a search include this parameter, \"whiteListed\", \"banned\" & \"modes.transportModes\" filters will be ignored." diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/PreferencesMapper.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/PreferencesMapper.java index c6fea176e5f..35883c8e72b 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/PreferencesMapper.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/PreferencesMapper.java @@ -1,6 +1,7 @@ package org.opentripplanner.ext.transmodelapi.mapping; import static org.opentripplanner.ext.transmodelapi.mapping.preferences.BikePreferencesMapper.mapBikePreferences; +import static org.opentripplanner.ext.transmodelapi.mapping.preferences.CarPreferencesMapper.mapCarPreferences; import static org.opentripplanner.ext.transmodelapi.mapping.preferences.ItineraryFilterPreferencesMapper.mapItineraryFilterPreferences; import static org.opentripplanner.ext.transmodelapi.mapping.preferences.ItineraryFilterPreferencesMapper.mapRentalPreferences; import static org.opentripplanner.ext.transmodelapi.mapping.preferences.StreetPreferencesMapper.mapStreetPreferences; @@ -19,10 +20,11 @@ static void mapPreferences( DataFetcherDecorator callWith, RoutingPreferences.Builder preferences ) { - preferences.withWalk(walk -> mapWalkPreferences(walk, environment, callWith)); preferences.withStreet(street -> mapStreetPreferences(street, environment, preferences.street()) ); + preferences.withWalk(walk -> mapWalkPreferences(walk, callWith)); preferences.withBike(bike -> mapBikePreferences(bike, callWith)); + preferences.withCar(car -> mapCarPreferences(car, callWith)); preferences.withTransfer(transfer -> mapTransferPreferences(transfer, environment, callWith)); preferences.withTransit(transit -> mapTransitPreferences(transit, environment, callWith)); preferences.withItineraryFilter(itineraryFilter -> diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/BikePreferencesMapper.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/BikePreferencesMapper.java index 7ee7a95aa4b..256268473ad 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/BikePreferencesMapper.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/BikePreferencesMapper.java @@ -6,15 +6,35 @@ public class BikePreferencesMapper { + public static final double WALK_BIKE_RELATIVE_RELUCTANCE = 2.7; + public static void mapBikePreferences( BikePreferences.Builder bike, DataFetcherDecorator callWith ) { callWith.argument("bikeSpeed", bike::withSpeed); - callWith.argument("bikeSwitchTime", bike::withSwitchTime); - callWith.argument("bikeSwitchCost", bike::withSwitchCost); + + // These are not wsposed on the Transmodel API + // callWith.argument("bikeSwitchTime", bike::withSwitchTime); + // callWith.argument("bikeSwitchCost", bike::withSwitchCost); + callWith.argument("bicycleOptimisationMethod", bike::withOptimizeType); + // WALK reluctance is used for backwards compatibility, then overridden + callWith.argument( + "walkReluctance", + r -> { + bike.withReluctance((double) r); + bike.withWalkingReluctance(WALK_BIKE_RELATIVE_RELUCTANCE * (double) r); + } + ); + + // TODO: Override WALK reluctance with BIKE reluctance + // callWith.argument("bike.reluctance", r -> { + // bike.withReluctance((double)r); + // bike.withWalkingReluctance(WALK_BIKE_RELATIVE_RELUCTANCE * (double)r ); + //}); + if (bike.optimizeType() == BicycleOptimizeType.TRIANGLE) { bike.withOptimizeTriangle(triangle -> { callWith.argument("triangleFactors.time", triangle::withTime); diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/CarPreferencesMapper.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/CarPreferencesMapper.java new file mode 100644 index 00000000000..f962016ed5f --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/CarPreferencesMapper.java @@ -0,0 +1,21 @@ +package org.opentripplanner.ext.transmodelapi.mapping.preferences; + +import org.opentripplanner.ext.transmodelapi.support.DataFetcherDecorator; +import org.opentripplanner.routing.api.request.preference.CarPreferences; + +public class CarPreferencesMapper { + + public static void mapCarPreferences(CarPreferences.Builder car, DataFetcherDecorator callWith) { + // Walk reluctance is used for backward compatibility + callWith.argument("walkReluctance", car::withReluctance); + // UNSUPPORTED PARAMETERS + + // Override WALK reluctance with CAR reluctance + // callWith.argument("car.reluctance", car::withReluctance); + // callWith.argument("car.speed", car::withSpeed); + // callWith.argument("car.park.cost", car::withParkCost); + // callWith.argument("car.park.time", car::withParkTime); + // callWith.argument("car.pickup.cost", car::withPickupCost); + // callWith.argument("car.pickup.time", car::withPickupTime); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/WalkPreferencesMapper.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/WalkPreferencesMapper.java index 21769e71d58..6319eb7ded4 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/WalkPreferencesMapper.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/WalkPreferencesMapper.java @@ -1,6 +1,5 @@ package org.opentripplanner.ext.transmodelapi.mapping.preferences; -import graphql.schema.DataFetchingEnvironment; import org.opentripplanner.ext.transmodelapi.support.DataFetcherDecorator; import org.opentripplanner.routing.api.request.preference.WalkPreferences; @@ -8,10 +7,11 @@ public class WalkPreferencesMapper { public static void mapWalkPreferences( WalkPreferences.Builder walk, - DataFetchingEnvironment environment, DataFetcherDecorator callWith ) { - callWith.argument("walkBoardCost", walk::withBoardCost); + // This is not part of API + // callWith.argument("walkBoardCost", walk::withBoardCost); callWith.argument("walkSpeed", walk::withSpeed); + callWith.argument("walkReluctance", walk::withReluctance); } } diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java index e1b1ba3d59f..b749d9e4491 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java @@ -485,6 +485,7 @@ public static GraphQLFieldDefinition create( ) .type(Scalars.GraphQLBoolean) .defaultValue(preferences.itineraryFilter().debug().debugEnabled()) + .deprecate("Use `itineraryFilter.debug` instead.") .build() ) .argument( diff --git a/src/test/java/org/opentripplanner/ext/transmodelapi/_support/TestDataFetcherDecorator.java b/src/test/java/org/opentripplanner/ext/transmodelapi/_support/TestDataFetcherDecorator.java new file mode 100644 index 00000000000..b4b2bd9de47 --- /dev/null +++ b/src/test/java/org/opentripplanner/ext/transmodelapi/_support/TestDataFetcherDecorator.java @@ -0,0 +1,30 @@ +package org.opentripplanner.ext.transmodelapi._support; + +import java.util.Map; +import java.util.function.Consumer; +import org.opentripplanner.ext.transmodelapi.support.DataFetcherDecorator; + +public class TestDataFetcherDecorator extends DataFetcherDecorator { + + private final Map data; + + private TestDataFetcherDecorator(Map data) { + super(null); + this.data = data; + } + + public static TestDataFetcherDecorator of(String fieldName, Object value) { + return new TestDataFetcherDecorator(Map.of(fieldName, value)); + } + + @SuppressWarnings("unchecked") + @Override + public void argument(String name, Consumer consumer) { + T value = (T) data.get(name); + if (value != null) { + consumer.accept(value); + } else { + System.out.println("No mapping for: " + name); + } + } +} diff --git a/src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/BikePreferencesMapperTest.java b/src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/BikePreferencesMapperTest.java new file mode 100644 index 00000000000..d68c0652862 --- /dev/null +++ b/src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/BikePreferencesMapperTest.java @@ -0,0 +1,74 @@ +package org.opentripplanner.ext.transmodelapi.mapping.preferences; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.ext.transmodelapi.mapping.preferences.BikePreferencesMapper.mapBikePreferences; + +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.ext.transmodelapi._support.TestDataFetcherDecorator; +import org.opentripplanner.routing.api.request.preference.BikePreferences; +import org.opentripplanner.routing.core.BicycleOptimizeType; + +class BikePreferencesMapperTest { + + static List mapBikePreferencesTestCases() { + return List.of( + Arguments.of( + "walkReluctance", + 10.0, + "BikePreferences{reluctance: 10.0, walkingReluctance: 27.0}" + ), + Arguments.of("bikeSpeed", 10.0, "BikePreferences{speed: 10.0}"), + Arguments.of( + "bicycleOptimisationMethod", + BicycleOptimizeType.TRIANGLE, + "BikePreferences{optimizeType: TRIANGLE}" + ), + // No effect unless BicycleOptimize is TRIANGLE + Arguments.of("triangleFactors.time", 0.17, "BikePreferences{}"), + Arguments.of("triangleFactors.slope", 0.12, "BikePreferences{}"), + Arguments.of("triangleFactors.safety", 0.13, "BikePreferences{}") + ); + } + + @ParameterizedTest + @MethodSource("mapBikePreferencesTestCases") + void testMapBikePreferences(String field, Object value, String expected) { + var preferences = BikePreferences.of(); + mapBikePreferences(preferences, TestDataFetcherDecorator.of(field, value)); + assertEquals(expected, preferences.build().toString()); + } + + static List mapBikePreferencesOptimizeTriangleTestCases() { + return List.of( + Arguments.of( + "triangleFactors.time", + 0.17, + "TimeSlopeSafetyTriangle[time=1.0, slope=0.0, safety=0.0]" + ), + Arguments.of( + "triangleFactors.slope", + 0.12, + "TimeSlopeSafetyTriangle[time=0.0, slope=1.0, safety=0.0]" + ), + Arguments.of( + "triangleFactors.safety", + 0.13, + "TimeSlopeSafetyTriangle[time=0.0, slope=0.0, safety=1.0]" + ) + ); + } + + @ParameterizedTest + @MethodSource("mapBikePreferencesOptimizeTriangleTestCases") + void testMapBikePreferencesOptimizeTriangle(String field, Object value, String expected) { + var preferences = BikePreferences.of().withOptimizeType(BicycleOptimizeType.TRIANGLE); + mapBikePreferences(preferences, TestDataFetcherDecorator.of(field, value)); + assertEquals( + "BikePreferences{optimizeType: TRIANGLE, optimizeTriangle: " + expected + "}", + preferences.build().toString() + ); + } +} diff --git a/src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/CarPreferencesMapperTest.java b/src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/CarPreferencesMapperTest.java new file mode 100644 index 00000000000..96f99220469 --- /dev/null +++ b/src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/CarPreferencesMapperTest.java @@ -0,0 +1,25 @@ +package org.opentripplanner.ext.transmodelapi.mapping.preferences; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.ext.transmodelapi._support.TestDataFetcherDecorator; +import org.opentripplanner.routing.api.request.preference.CarPreferences; + +class CarPreferencesMapperTest { + + static List mapCarPreferencesTestCases() { + return List.of(Arguments.of("walkReluctance", 7.5, "CarPreferences{reluctance: 7.5}")); + } + + @ParameterizedTest + @MethodSource("mapCarPreferencesTestCases") + void mapCarPreferences(String field, Object value, String expected) { + var preferences = CarPreferences.of(); + CarPreferencesMapper.mapCarPreferences(preferences, TestDataFetcherDecorator.of(field, value)); + assertEquals(expected, preferences.build().toString()); + } +} diff --git a/src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/WalkPreferencesMapperTest.java b/src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/WalkPreferencesMapperTest.java new file mode 100644 index 00000000000..d1e9c3fe81d --- /dev/null +++ b/src/test/java/org/opentripplanner/ext/transmodelapi/mapping/preferences/WalkPreferencesMapperTest.java @@ -0,0 +1,31 @@ +package org.opentripplanner.ext.transmodelapi.mapping.preferences; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.ext.transmodelapi._support.TestDataFetcherDecorator; +import org.opentripplanner.routing.api.request.preference.WalkPreferences; + +class WalkPreferencesMapperTest { + + static List mapWalkPreferencesTestCases() { + return List.of( + Arguments.of("walkReluctance", 7.5, "WalkPreferences{reluctance: 7.5}"), + Arguments.of("walkSpeed", 3.2, "WalkPreferences{speed: 3.2}") + ); + } + + @ParameterizedTest + @MethodSource("mapWalkPreferencesTestCases") + void mapWalkPreferences(String field, Object value, String expected) { + var preferences = WalkPreferences.of(); + WalkPreferencesMapper.mapWalkPreferences( + preferences, + TestDataFetcherDecorator.of(field, value) + ); + assertEquals(expected, preferences.build().toString()); + } +} From 280e23262bbc0e23c328128b81694821e424e680 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 2 Nov 2023 13:41:34 +0100 Subject: [PATCH 15/26] Version 2.5.0-entur-6 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e066ea2e3ec..a9bc75dd46f 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.5.0-entur-5 + 2.5.0-entur-6 jar From c664ad9a65dd36657a449c1ca995d4bb5e77b61a Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 19 May 2023 18:35:18 +0200 Subject: [PATCH 16/26] CircleCI config, release scripts, and pom.xml updates. --- .circleci/config.yml | 52 +++++ .circleci/prepare_release | 235 +++++++++++++++++++++++ .circleci/release | 84 ++++++++ .circleci/update_deployment_config | 46 +++++ .gitignore | 1 + pom.xml | 12 +- src/main/java/EnturUpdatePomVersion.java | 135 +++++++++++++ 7 files changed, 559 insertions(+), 6 deletions(-) create mode 100644 .circleci/config.yml create mode 100755 .circleci/prepare_release create mode 100755 .circleci/release create mode 100755 .circleci/update_deployment_config create mode 100644 src/main/java/EnturUpdatePomVersion.java diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000000..d01a0c323e9 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +aliases: + - &jfrog-login + name: Rename jfrog environment variable for maven setting.xml + command: | + echo "export JFROG_USER=$ARTIFACTORY_USER" >> $BASH_ENV + echo "export JFROG_PASS=$ARTIFACTORY_PASSWORD" >> $BASH_ENV + - &post_build + name: Update deployment with OTP version + command: | + sudo apt-get update + sudo apt-get install libxml2-utils + chmod u+x .circleci/update_deployment_config + .circleci/update_deployment_config + +jobs: + build: + docker: + - image: cimg/openjdk:17.0 + environment: + DEBIAN_FRONTEND: "noninteractive" + MAVEN_OPTS: -Xmx6G + resource_class: large + steps: + - checkout + - restore_cache: + keys: + - dep-cache-{{ checksum "pom.xml" }} + # fallback to the most recent cache if there is no exact match for this pom.xml + - dep-cache- + - run: wget https://raw.githubusercontent.com/entur/circleci-toolbox-image-java11/master/tools/m2/settings.xml -O .circleci/settings.xml + - run: *jfrog-login + - run: mvn org.apache.maven.plugins:maven-dependency-plugin:3.1.0:go-offline -s .circleci/settings.xml + - save_cache: + paths: + - ~/.m2 + key: dep-cache-{{ checksum "pom.xml" }} + - run: mvn install -PprettierSkip org.apache.maven.plugins:maven-deploy-plugin:2.8.2:deploy -s .circleci/settings.xml -DaltDeploymentRepository=snapshots::default::https://entur2.jfrog.io/entur2/libs-release-local + - run: *post_build + +workflows: + version: 2 + release: + jobs: + - build: + name: build-release + context: global + filters: + branches: + only: + - otp2_entur_develop \ No newline at end of file diff --git a/.circleci/prepare_release b/.circleci/prepare_release new file mode 100755 index 00000000000..5b2ad0e23c4 --- /dev/null +++ b/.circleci/prepare_release @@ -0,0 +1,235 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ENTUR_DEVELOP=otp2_entur_develop +REMOTE_REPO="`git remote -v | grep "entur/OpenTripPlanner" | grep "push" | awk '{print $1;}'`" +STATUS_FILE=".prepare_release.tmp" +STATUS="" +DRY_RUN="" +OTP_BASE="" + +function main() { + setup "$@" + resumePreviousExecution + resetEnturDevelop + rebaseAndMergeExtBranch otp2_ext_config + rebaseAndMergeExtBranch otp2_ext_hack_sorlandsbanen + logSuccess +} + +function setup() { + if [[ $# -eq 2 && "$1" == "--dryRun" ]] ; then + DRY_RUN="--dryRun" + OTP_BASE="$2" + elif [[ $# -eq 1 ]] ; then + OTP_BASE="$1" + else + printHelp + exit 1 + fi + + echo "" + echo "Options: ${DRY_RUN}" + echo "Git base branch/commit: ${OTP_BASE}" + echo "Entur develop branch: ${ENTUR_DEVELOP}" + echo "Entur remote repo(pull/push): ${REMOTE_REPO}" + echo "" + + if git diff-index --quiet HEAD --; then + echo "" + echo "OK - No local changes, prepare to checkout '${ENTUR_DEVELOP}'" + echo "" + else + echo "" + echo "You have local modification, the script will abort. Nothing done!" + exit 2 + fi + + git fetch ${REMOTE_REPO} +} + +# This script create a status file '.prepare_release.tmp'. This file is used to resume the +# script in the same spot as where is left when the error occurred. This allow us to fix the +# problem (merge conflict or compile error) and re-run the script to complete the proses. +function resumePreviousExecution() { + readStatus + + if [[ -n "${STATUS}" ]] ; then + echo "" + echo "Resume: ${STATUS}?" + echo "" + echo " If all problems are resolved you may continue." + echo " Exit to clear status and start over." + echo "" + + ANSWER="" + while [[ ! "$ANSWER" =~ [yx] ]]; do + echo "Do you want to resume: [y:Yes, x:Exit]" + read ANSWER + done + + if [[ "${ANSWER}" == "x" ]] ; then + exit 0 + fi + fi +} + +function resetEnturDevelop() { + echo "" + echo "## ------------------------------------------------------------------------------------- ##" + echo "## RESET '${ENTUR_DEVELOP}' TO '${OTP_BASE}'" + echo "## ------------------------------------------------------------------------------------- ##" + echo "" + echo "Would you like to reset the '${ENTUR_DEVELOP}' to '${OTP_BASE}'? " + echo "" + + whatDoYouWant + + if [[ "${ANSWER}" == "y" ]] ; then + echo "" + echo "Checkout '${ENTUR_DEVELOP}'" + git checkout ${ENTUR_DEVELOP} + + echo "" + echo "Reset '${ENTUR_DEVELOP}' branch to '${OTP_BASE}' (hard)" + git reset --hard "${OTP_BASE}" + echo "" + fi +} + +function rebaseAndMergeExtBranch() { + EXT_BRANCH="$1" + EXT_STATUS_REBASE="Rebase '${EXT_BRANCH}'" + EXT_STATUS_COMPILE="Compile '${EXT_BRANCH}'" + + echo "" + echo "## ------------------------------------------------------------------------------------- ##" + echo "## REBASE AND MERGE '${EXT_BRANCH}' INTO '${ENTUR_DEVELOP}'" + echo "## ------------------------------------------------------------------------------------- ##" + echo "" + echo "You are about to rebase and merge '${EXT_BRANCH}' into '${ENTUR_DEVELOP}'. Any local" + echo "modification in the '${EXT_BRANCH}' will be lost." + echo "" + + whatDoYouWant + + if [[ "${ANSWER}" == "y" ]] ; then + echo "" + echo "Checkout '${EXT_BRANCH}'" + git checkout "${EXT_BRANCH}" + + echo "" + echo "Reset to '${REMOTE_REPO}/${EXT_BRANCH}'" + git reset --hard "${REMOTE_REPO}/${EXT_BRANCH}" + + echo "" + echo "Top 2 commits in '${EXT_BRANCH}'" + echo "-------------------------------------------------------------------------------------------" + git --no-pager log -2 + echo "-------------------------------------------------------------------------------------------" + echo "" + echo "You are about to rebase the TOP COMMIT ONLY(see above). Check that the " + echo "'${EXT_BRANCH}' only have ONE commit that you want to keep." + echo "" + + whatDoYouWant + + if [[ "${ANSWER}" == "y" ]] ; then + echo "" + echo "Rebase '${EXT_BRANCH}' onto '${ENTUR_DEVELOP}'" + setStatus "${EXT_STATUS_REBASE}" + git rebase --onto ${ENTUR_DEVELOP} HEAD~1 + fi + fi + + if [[ "${STATUS}" == "${EXT_STATUS_REBASE}" || "${STATUS}" == "${EXT_STATUS_COMPILE}" ]] ; then + # Reset status in case the test-compile fails. We need to do this because the status file + # is deleted after reading the status in the setup() function. + setStatus "${EXT_STATUS_COMPILE}" + + mvn clean test-compile + clearStatus + + echo "" + echo "Push '${EXT_BRANCH}'" + if [[ -z "${DRY_RUN}" ]] ; then + git push -f + else + echo "Skip: git push -f (--dryRun)" + fi + + echo "" + echo "Checkout '${ENTUR_DEVELOP}' and merge in '${EXT_BRANCH}'" + git checkout "${ENTUR_DEVELOP}" + git merge "${EXT_BRANCH}" + fi +} + +function logSuccess() { + echo "" + echo "## ------------------------------------------------------------------------------------- ##" + echo "## PREPARE RELEASE DONE -- SUCCESS" + echo "## ------------------------------------------------------------------------------------- ##" + echo " - '${REMOTE_REPO}/${ENTUR_DEVELOP}' reset to '${OTP_BASE}'" + echo " - 'otp2_ext_config' merged" + echo " - 'otp2_ext_hack_sorlandsbanen' merged" + echo "" + echo "" +} + +function whatDoYouWant() { + echo "" + ANSWER="" + + if [[ -n "${STATUS}" ]] ; then + # Skip until process is resumed + ANSWER="s" + else + while [[ ! "$ANSWER" =~ [ysx] ]]; do + echo "Do you want to continue: [y:Yes, s:Skip, x:Exit]" + read ANSWER + done + + if [[ "${ANSWER}" == "x" ]] ; then + exit 0 + fi + fi +} + +function setStatus() { + STATUS="$1" + echo "$STATUS" > "${STATUS_FILE}" +} + +function readStatus() { + if [[ -f "${STATUS_FILE}" ]] ; then + STATUS=`cat $STATUS_FILE` + rm "$STATUS_FILE" + else + STATUS="" + fi +} + +function clearStatus() { + STATUS="" + rm "${STATUS_FILE}" +} + +function printHelp() { + echo "" + echo "This script take ONE argument , the base **branch** or **commit** to use for the" + echo "release. The '${ENTUR_DEVELOP}' branch is reset to this commit and then the extension" + echo "branches is rebased onto that. The 'release' script is used to complete the release." + echo "It tag and push all changes to remote git repo." + echo "" + echo "Options:" + echo " --dryRun : Run script locally, nothing is pushed to remote server." + echo "" + echo "Usage:" + echo " $ .circleci/prepare_release otp/dev-2.x" + echo " $ .circleci/prepare_release --dryRun otp/dev-2.x" + echo "" +} + +main "$@" diff --git a/.circleci/release b/.circleci/release new file mode 100755 index 00000000000..9cac0d97958 --- /dev/null +++ b/.circleci/release @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +set -euo pipefail + +GIT_REMOTE_REPO="`git remote -v | grep "entur/OpenTripPlanner" | grep "push" | awk '{print $1;}'`" +MASTER_BRANCH=otp2_entur_develop +TAGS_FILE=target/git-entur-tags.txt +DRY_RUN="" + +function main() { + setup "$@" + listAllTags + mergeInOldReleaseWithNoChanges + setPomVersion + tagRelease + pushToRemote +} + +function setup() { + echo "" + echo "git fetch ${GIT_REMOTE_REPO}" + git fetch ${GIT_REMOTE_REPO} + + echo "Verify current branch is ${MASTER_BRANCH} " + git status | grep -q "On branch ${MASTER_BRANCH}" + + if [[ "${1+x}" == "--dryRun" ]] ; then + DRY_RUN="--dryRun" + fi +} + +function listAllTags() { + ## List all Entur tags to allow the UpdatePomVersion java program find the next version number + echo "" + echo "Dump all entur tags to ${TAGS_FILE}" + git tag -l | grep entur > ${TAGS_FILE} +} + +function setPomVersion() { + echo "" + echo "Update pom.xml with new version" + javac -d target/classes src/main/java/EnturUpdatePomVersion.java + + VERSION="`java -cp target/classes EnturUpdatePomVersion ${TAGS_FILE}`" + echo "" + echo "New version set: ${VERSION}" + echo "" + + ## Verify everything builds and tests run + echo "" + mvn clean test + + ## Add [ci skip] here before moving this to the CI server + echo "" + echo "Add and commit pom.xml" + git commit -m "Version ${VERSION}" pom.xml +} + +function mergeInOldReleaseWithNoChanges() { + echo "" + echo "Merge the old version of '${GIT_REMOTE_REPO}' into the new version. This only keep " + echo "a reference to the old version, the resulting tree of the merge is that of the new" + echo "branch head, effectively ignoring all changes from the old release." + git merge -s ours "${GIT_REMOTE_REPO}/${MASTER_BRANCH}" -m "Merge old release into '${MASTER_BRANCH}' - NO CHANGES COPIED OVER" +} + + +function tagRelease() { + echo "" + echo "Tag version ${VERSION}" + git tag -a v${VERSION} -m "Version ${VERSION}" +} + +function pushToRemote() { + echo "" + echo "Push pom.xml and new tag" + if [[ -z "${DRY_RUN}" ]] ; then + git push -f ${GIT_REMOTE_REPO} "v${VERSION}" ${MASTER_BRANCH} + else + echo "Skip: push -f ${GIT_REMOTE_REPO} "v${VERSION}" ${MASTER_BRANCH} (--dryRun)" + fi +} + +main "$@" diff --git a/.circleci/update_deployment_config b/.circleci/update_deployment_config new file mode 100755 index 00000000000..64550f19797 --- /dev/null +++ b/.circleci/update_deployment_config @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## +## This script run AFTER OTP is build on the CI Server. It checkout the +## deployment config and update the version number, commit and push. This +## trigger the deployment config build witch create and deploy a new OTP2 +## docker image. +## +## Note! There is no need to run this script locally, unless you want to debug it. +## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## + + +set -euo pipefail + +OTP_DEPLOYMENT_CONFIG=target/otp-deployment-config +DOCKER_FILE=Dockerfile + +VERSION=$(xmllint --xpath "//*[local-name()='project']/*[local-name()='version']/text()" pom.xml) + +echo "Checkout otp-deplyment-config in ${OTP_DEPLOYMENT_CONFIG}" +git clone -n https://github.com/entur/otp-deployment-config.git --depth 1 ${OTP_DEPLOYMENT_CONFIG} + +pushd ${OTP_DEPLOYMENT_CONFIG} + +echo "Checkout latest version of master Dockerfile" +git checkout master ${DOCKER_FILE} + +echo "Update OTP version number in ${DOCKER_FILE}" +if [[ "$OSTYPE" == "darwin"* ]]; then + sed -E -i '' "s/OTP_VERSION=[\.0-9]+-entur-[0-9]+/OTP_VERSION=${VERSION}/" ${DOCKER_FILE} +else + sed -E -i "s/OTP_VERSION=[\.0-9]+-entur-[0-9]+/OTP_VERSION=${VERSION}/" ${DOCKER_FILE} +fi + +if [[ "$CIRCLE_USERNAME" != "" ]]; then + git config user.email "circleci@entur.no" + git config user.name "circleci ($CIRCLE_USERNAME)" +fi + +echo "Add and commit Dockerfile" +git commit -m "New OTP2 Version ${VERSION}" ${DOCKER_FILE} + +echo "Push otp-deployment-config to GitHub" +git push + +popd \ No newline at end of file diff --git a/.gitignore b/.gitignore index a90f06cdcb0..3bc96654225 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .project .pydevproject .settings +.prepare_release.tmp .sonar .nvmrc *~ diff --git a/pom.xml b/pom.xml index 16dac4d6cc8..e594639ae08 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 124 + EN-0054 30.0 2.48.1 @@ -84,11 +84,11 @@ - - github - OpenTripPlanner Maven Repository on Github Packages - https://maven.pkg.github.com/${GITHUB_REPOSITORY}/ - + + snapshots + entur2-snapshots + https://entur2.jfrog.io/entur2/libs-snapshot-local + diff --git a/src/main/java/EnturUpdatePomVersion.java b/src/main/java/EnturUpdatePomVersion.java new file mode 100644 index 00000000000..97bd72b3c6e --- /dev/null +++ b/src/main/java/EnturUpdatePomVersion.java @@ -0,0 +1,135 @@ +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.regex.Pattern; + +public class EnturUpdatePomVersion { + + private static final String VERSION_SEP = "-"; + private static final String ENTUR_PREFIX = "entur" + VERSION_SEP; + private static final Path POM_FILE = Path.of("pom.xml"); + + private final List tags = new ArrayList<>(); + private final List pomFile = new ArrayList<>(); + private String mainVersion; + private int versionNumber = 0; + + public static void main(String[] args) { + try { + new EnturUpdatePomVersion().withArgs(args).run(); + } catch (Exception e) { + System.err.println(e.getMessage()); + e.printStackTrace(System.err); + System.exit(10); + } + } + + private EnturUpdatePomVersion withArgs(String[] args) throws IOException { + if (args.length != 1 || args[0].matches("(?i)-h|--help")) { + printHelp(); + System.exit(1); + } + String arg = args[0]; + + if (arg.matches("\\d+")) { + versionNumber = resolveVersionFromNumericString(arg); + } else { + tags.addAll(readTagsFromFile(arg)); + } + return this; + } + + private void run() throws IOException { + readAndReplaceVersion(); + replacePomFile(); + } + + public void readAndReplaceVersion() throws IOException { + var pattern = Pattern.compile( + "(\\s*)(\\d+.\\d+.\\d+)" + + VERSION_SEP + + "(" + + ENTUR_PREFIX + + "\\d+|SNAPSHOT)(\\s*)" + ); + boolean found = false; + int i = 0; + + for (String line : Files.readAllLines(POM_FILE, UTF_8)) { + // Look for the version in the 25 first lines + if (!found) { + var m = pattern.matcher(line); + if (m.matches()) { + mainVersion = m.group(2); + String newVersion = mainVersion + VERSION_SEP + ENTUR_PREFIX + resolveVersionNumber(); + line = m.group(1) + newVersion + m.group(4); + System.out.println(newVersion); + found = true; + } + if (++i == 25) { + throw new IllegalStateException( + "Version not found in first 25 lines of the pom.xml file." + ); + } + } + pomFile.add(line); + } + if (!found) { + throw new IllegalStateException( + "Version not found in 'pom.xml'. Nothing matching pattern: " + pattern + ); + } + } + + public void replacePomFile() throws IOException { + Files.delete(POM_FILE); + Files.write(POM_FILE, pomFile, UTF_8); + } + + private static void printHelp() { + System.err.println( + "Use this small program to replace the OTP version '2.1.0-SNAPSHOT' \n" + + "with a new version number with a Entur qualifier like '2.1.0-entur-1'.\n" + + "\n" + + "Usage:\n" + + " $ java -cp .circleci UpdatePomVersion \n" + ); + } + + private int resolveVersionNumber() { + var pattern = Pattern.compile("v" + mainVersion + VERSION_SEP + ENTUR_PREFIX + "(\\d+)"); + int maxTagVersion = tags + .stream() + .mapToInt(tag -> { + var m = pattern.matcher(tag); + return m.matches() ? Integer.parseInt(m.group(1)) : -999; + }) + .max() + .orElse(-999); + + return 1 + Math.max(maxTagVersion, versionNumber); + } + + public static int resolveVersionFromNumericString(String arg) { + try { + return Integer.parseInt(arg); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Unable to parse input, decimal number expected: '" + arg + "'" + ); + } + } + + private static Collection readTagsFromFile(String arg) throws IOException { + var tags = Files.readAllLines(Path.of(arg)); + if (tags.isEmpty()) { + throw new IllegalStateException("Unable to load git tags from file: " + arg); + } + return tags; + } +} From 137511118d15a3700203a07ca8880a4a7e2a8f7f Mon Sep 17 00:00:00 2001 From: Hannes Junnila Date: Fri, 9 Dec 2022 09:38:06 +0200 Subject: [PATCH 17/26] =?UTF-8?q?hack:=20Add=20train=20option=20for=20S?= =?UTF-8?q?=C3=B8rlandsbanen.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/Configuration.md | 62 ++++---- docs/RouteRequest.md | 1 + src/ext/graphql/transmodelapi/schema.graphql | 2 + .../ConcurrentCompositeWorker.java | 53 +++++++ .../sorlandsbanen/EnturHackSorlandsBanen.java | 141 ++++++++++++++++++ .../ext/sorlandsbanen/PathKey.java | 51 +++++++ .../RaptorWorkerResultComposite.java | 88 +++++++++++ .../transmodelapi/model/plan/TripQuery.java | 8 + .../common/RequestToPreferencesMapper.java | 1 + .../api/common/RoutingResource.java | 3 + .../framework/application/OTPFeature.java | 1 + .../raptor/api/request/RaptorRequest.java | 4 + .../api/request/RaptorRequestBuilder.java | 6 + .../service/RangeRaptorDynamicSearch.java | 10 +- .../raptoradapter/router/TransitRouter.java | 3 +- .../transit/cost/DefaultCostCalculator.java | 16 ++ .../transit/cost/FactorStrategy.java | 2 +- .../cost/IndexBasedFactorStrategy.java | 4 +- .../transit/mappers/RaptorRequestMapper.java | 16 +- .../RaptorRoutingRequestTransitData.java | 33 ++++ .../preference/TransitPreferences.java | 17 +++ .../routerequest/RouteRequestConfig.java | 7 + .../raptor/RaptorArchitectureTest.java | 6 +- 23 files changed, 493 insertions(+), 42 deletions(-) create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java diff --git a/docs/Configuration.md b/docs/Configuration.md index bfd168d5955..5073dafed31 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -219,38 +219,36 @@ Here is a list of all features which can be toggled on/off and their default val -| Feature | Description | Enabled by default | Sandbox | -|--------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------:|:-------:| -| `APIBikeRental` | Enable the bike rental endpoint. | ✓️ | | -| `APIServerInfo` | Enable the server info endpoint. | ✓️ | | -| `APIGraphInspectorTile` | Enable the inspector endpoint for graph information for inspection/debugging purpose. | ✓️ | | -| `APIUpdaterStatus` | Enable endpoint for graph updaters status. | ✓️ | | -| `ConsiderPatternsForDirectTransfers` | Enable limiting transfers so that there is only a single transfer to each pattern. | ✓️ | | -| `DebugClient` | Enable the debug web client located at the root of the web server. | ✓️ | | -| `FloatingBike` | Enable floating bike routing. | ✓️ | | -| `GtfsGraphQlApi` | Enable GTFS GraphQL API. | ✓️ | | -| `GtfsGraphQlApiRentalStationFuzzyMatching` | Does vehicleRentalStation query also allow ids that are not feed scoped. | | | -| `MinimumTransferTimeIsDefinitive` | If the minimum transfer time is a lower bound (default) or the definitive time for the transfer. Set this to `true` if you want to set a transfer time lower than what OTP derives from OSM data. | | | -| `OptimizeTransfers` | OTP will inspect all itineraries found and optimize where (which stops) the transfer will happen. Waiting time, priority and guaranteed transfers are taken into account. | ✓️ | | -| `ParallelRouting` | Enable performing parts of the trip planning in parallel. | | | -| `TransferConstraints` | Enforce transfers to happen according to the _transfers.txt_(GTFS) and Interchanges(NeTEx). Turing this _off_ will increase the routing performance a little. | ✓️ | | -| `ActuatorAPI` | Endpoint for actuators (service health status). | | ✓️ | -| `AsyncGraphQLFetchers` | Whether the @async annotation in the GraphQL schema should lead to the fetch being executed asynchronously. This allows batch or alias queries to run in parallel at the cost of consuming extra threads. | | | -| `Co2Emissions` | Enable the emissions sandbox module. | | ✓️ | -| `DataOverlay` | Enable usage of data overlay when calculating costs for the street network. | | ✓️ | -| `FaresV2` | Enable import of GTFS-Fares v2 data. | | ✓️ | -| `FlexRouting` | Enable FLEX routing. | | ✓️ | -| `GoogleCloudStorage` | Enable Google Cloud Storage integration. | | ✓️ | -| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with realtime data | | ✓️ | -| `ReportApi` | Enable the report API. | | ✓️ | -| `RestAPIPassInDefaultConfigAsJson` | Enable a default RouteRequest to be passed in as JSON on the REST API - FOR DEBUGGING ONLY! | | | -| `SandboxAPIGeocoder` | Enable the Geocoder API. | | ✓️ | -| `SandboxAPIMapboxVectorTilesApi` | Enable Mapbox vector tiles API. | | ✓️ | -| `SandboxAPIParkAndRideApi` | Enable park-and-ride endpoint. | | ✓️ | -| `SandboxAPITransmodelApi` | Enable Entur Transmodel(NeTEx) GraphQL API. | | ✓️ | -| `SandboxAPITravelTime` | Enable the isochrone/travel time surface API. | | ✓️ | -| `TransferAnalyzer` | Analyze transfers during graph build. | | ✓️ | -| `VehicleToStopHeuristics` | Enable improved heuristic for park-and-ride queries. | | ✓️ | +| Feature | Description | Enabled by default | Sandbox | +|--------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------:|:-------:| +| `APIBikeRental` | Enable the bike rental endpoint. | ✓️ | | +| `APIServerInfo` | Enable the server info endpoint. | ✓️ | | +| `APIGraphInspectorTile` | Enable the inspector endpoint for graph information for inspection/debugging purpose. | ✓️ | | +| `APIUpdaterStatus` | Enable endpoint for graph updaters status. | ✓️ | | +| `ConsiderPatternsForDirectTransfers` | Enable limiting transfers so that there is only a single transfer to each pattern. | ✓️ | | +| `DebugClient` | Enable the debug web client located at the root of the web server. | ✓️ | | +| `FloatingBike` | Enable floating bike routing. | ✓️ | | +| `GtfsGraphQlApi` | Enable GTFS GraphQL API. | ✓️ | | +| `MinimumTransferTimeIsDefinitive` | If the minimum transfer time is a lower bound (default) or the definitive time for the transfer. Set this to `true` if you want to set a transfer time lower than what OTP derives from OSM data. | | | +| `OptimizeTransfers` | OTP will inspect all itineraries found and optimize where (which stops) the transfer will happen. Waiting time, priority and guaranteed transfers are taken into account. | ✓️ | | +| `ParallelRouting` | Enable performing parts of the trip planning in parallel. | | | +| `TransferConstraints` | Enforce transfers to happen according to the _transfers.txt_(GTFS) and Interchanges(NeTEx). Turing this _off_ will increase the routing performance a little. | ✓️ | | +| `ActuatorAPI` | Endpoint for actuators (service health status). | | ✓️ | +| `AsyncGraphQLFetchers` | Whether the @async annotation in the GraphQL schema should lead to the fetch being executed asynchronously. This allows batch or alias queries to run in parallel at the cost of consuming extra threads. | | | +| `DataOverlay` | Enable usage of data overlay when calculating costs for the street network. | | ✓️ | +| `FaresV2` | Enable import of GTFS-Fares v2 data. | | ✓️ | +| `FlexRouting` | Enable FLEX routing. | | ✓️ | +| `GoogleCloudStorage` | Enable Google Cloud Storage integration. | | ✓️ | +| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with realtime data | | ✓️ | +| `ReportApi` | Enable the report API. | | ✓️ | +| `RestAPIPassInDefaultConfigAsJson` | Enable a default RouteRequest to be passed in as JSON on the REST API - FOR DEBUGGING ONLY! | | | +| `SandboxAPIGeocoder` | Enable the Geocoder API. | | ✓️ | +| `SandboxAPIMapboxVectorTilesApi` | Enable Mapbox vector tiles API. | | ✓️ | +| `SandboxAPIParkAndRideApi` | Enable park-and-ride endpoint. | | ✓️ | +| `SandboxAPITransmodelApi` | Enable Entur Transmodel(NeTEx) GraphQL API. | | ✓️ | +| `SandboxAPITravelTime` | Enable the isochrone/travel time surface API. | | ✓️ | +| `TransferAnalyzer` | Analyze transfers during graph build. | | ✓️ | +| `VehicleToStopHeuristics` | Enable improved heuristic for park-and-ride queries. | | ✓️ | diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index f814753df4a..b6284d22e4d 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -46,6 +46,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe | elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 | | elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | | escalatorReluctance | `double` | A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time | *Optional* | `1.5` | 2.4 | +| extraSearchCoachReluctance | `double` | TODO | *Optional* | `0.0` | 2.1 | | geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | | ignoreRealtimeUpdates | `boolean` | When true, realtime updates are ignored during this search. | *Optional* | `false` | 2.0 | | [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql index 474da1e34d7..eaeed9ecb2a 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/ext/graphql/transmodelapi/schema.graphql @@ -793,6 +793,8 @@ type QueryType { dateTime: DateTime, "Debug the itinerary-filter-chain. OTP will attach a system notice to itineraries instead of removing them. This is very convenient when tuning the filters." debugItineraryFilter: Boolean = false, + "FOR TESTING ONLY" + extraSearchCoachReluctance: Float, "A list of filters for which trips should be included. A trip will be included if it matches with at least one filter. An empty list of filters means that all trips should be included. If a search include this parameter, \"whiteListed\", \"banned\" & \"modes.transportModes\" filters will be ignored." filters: [TripFilterInput!], "The start location" diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java new file mode 100644 index 00000000000..e80e106ce1e --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java @@ -0,0 +1,53 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.opentripplanner.framework.application.OTPFeature; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; +import org.opentripplanner.raptor.api.response.StopArrivals; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.multicriteria.McRaptorWorkerResult; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TripScheduleWithOffset; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class ConcurrentCompositeWorker implements RaptorWorker { + + private static final Logger LOG = LoggerFactory.getLogger(ConcurrentCompositeWorker.class); + + private final RaptorWorker mainWorker; + private final RaptorWorker alternativeWorker; + + ConcurrentCompositeWorker(RaptorWorker mainWorker, RaptorWorker alternativeWorker) { + this.mainWorker = mainWorker; + this.alternativeWorker = alternativeWorker; + } + + @Override + public RaptorWorkerResult route() { + if (OTPFeature.ParallelRouting.isOn()) { + var mainResultFuture = CompletableFuture.supplyAsync(mainWorker::route); + var alternativeResultFuture = CompletableFuture.supplyAsync(alternativeWorker::route); + + try { + return new RaptorWorkerResultComposite<>( + mainResultFuture.get(), + alternativeResultFuture.get() + ); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } else { + var mainResult = mainWorker.route(); + var alternativeResult = alternativeWorker.route(); + return new RaptorWorkerResultComposite<>(mainResult, alternativeResult); + } + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java new file mode 100644 index 00000000000..2b52e850f42 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java @@ -0,0 +1,141 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.function.Function; +import org.opentripplanner.framework.geometry.SphericalDistanceLibrary; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.model.GenericLocation; +import org.opentripplanner.raptor.api.model.RaptorAccessEgress; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.request.RaptorRequest; +import org.opentripplanner.raptor.api.request.SearchParams; +import org.opentripplanner.raptor.configure.RaptorConfig; +import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; +import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.FactorStrategy; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.IndexBasedFactorStrategy; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.RaptorRoutingRequestTransitData; +import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.site.StopLocation; + +public class EnturHackSorlandsBanen { + + private static final double SOUTH_BOARDER_LIMIT = 59.1; + private static final int MIN_DISTANCE_LIMIT = 120_000; + + public static boolean match(RaptorRequest mcRequest) { + return mcRequest.extraSearchCoachReluctance > 0.1; + } + + public static RaptorWorker worker( + RaptorConfig config, + RaptorTransitDataProvider transitData, + RaptorRequest mcRequest, + Heuristics destinationHeuristics + ) { + //noinspection unchecked + RaptorTransitDataProvider altTransitData = (RaptorTransitDataProvider) ( + (RaptorRoutingRequestTransitData) transitData + ).enturHackSorlandsbanen(mapFactors(mcRequest.extraSearchCoachReluctance)); + + return new ConcurrentCompositeWorker<>( + config.createMcWorker(transitData, mcRequest, destinationHeuristics), + config.createMcWorker(altTransitData, mcRequest, destinationHeuristics) + ); + } + + public static RaptorRequest enableHack( + RaptorRequest raptorRequest, + RouteRequest request, + TransitLayer transitLayer + ) { + if (request.preferences().transit().extraSearchCoachReluctance() < 0.1) { + return raptorRequest; + } + + SearchParams params = raptorRequest.searchParams(); + + WgsCoordinate from = findStopCoordinate(request.from(), params.accessPaths(), transitLayer); + WgsCoordinate to = findStopCoordinate(request.to(), params.egressPaths(), transitLayer); + + if (from.latitude() > SOUTH_BOARDER_LIMIT && to.latitude() > SOUTH_BOARDER_LIMIT) { + return raptorRequest; + } + + double distanceMeters = SphericalDistanceLibrary.distance( + from.latitude(), + from.longitude(), + to.latitude(), + to.longitude() + ); + + if (distanceMeters < MIN_DISTANCE_LIMIT) { + return raptorRequest; + } + + raptorRequest.extraSearchCoachReluctance = + request.preferences().transit().extraSearchCoachReluctance(); + return raptorRequest; + } + + /* private methods */ + + private static Function mapFactors( + final double extraSearchCoachReluctance + ) { + return (FactorStrategy originalFactors) -> { + int[] modeReluctance = new int[TransitMode.values().length]; + for (TransitMode mode : TransitMode.values()) { + int index = mode.ordinal(); + int originalFactor = originalFactors.factor(index); + modeReluctance[index] = + mode == TransitMode.COACH + ? (int) (extraSearchCoachReluctance * originalFactor + 0.5) + : originalFactor; + } + return new IndexBasedFactorStrategy(modeReluctance); + }; + } + + /** + * Find a coordinate matching the given location, in order: + * - First return the coordinate of the location if it exists. + * - Then loop through the access/egress stops and try to find the + * stop or station given by the location id, return the stop/station coordinate. + * - Return the fist stop in the access/egress list coordinate. + */ + @SuppressWarnings("ConstantConditions") + private static WgsCoordinate findStopCoordinate( + GenericLocation location, + Collection accessEgress, + TransitLayer transitLayer + ) { + if (location.lat != null) { + return new WgsCoordinate(location.lat, location.lng); + } + + StopLocation firstStop = null; + for (RaptorAccessEgress it : accessEgress) { + StopLocation stop = transitLayer.getStopByIndex(it.stop()); + if (stop.getId().equals(location.stopId)) { + return stop.getCoordinate(); + } + if (idIsParentStation(stop, location.stopId)) { + return stop.getParentStation().getCoordinate(); + } + if (firstStop == null) { + firstStop = stop; + } + } + return firstStop.getCoordinate(); + } + + private static boolean idIsParentStation(StopLocation stop, FeedScopedId pId) { + return stop.getParentStation() != null && stop.getParentStation().getId().equals(pId); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java new file mode 100644 index 00000000000..94267b3e7c2 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java @@ -0,0 +1,51 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; + +final class PathKey { + + private final int hash; + + PathKey(RaptorPath path) { + this.hash = hash(path); + } + + private static int hash(RaptorPath path) { + if (path == null) { + return 0; + } + int result = 1; + + PathLeg leg = path.accessLeg(); + + while (!leg.isEgressLeg()) { + result = 31 * result + leg.toStop(); + result = 31 * result + leg.toTime(); + + if (leg.isTransitLeg()) { + result = 31 * result + leg.asTransitLeg().trip().pattern().debugInfo().hashCode(); + } + leg = leg.nextLeg(); + } + result = 31 * result + leg.toTime(); + + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o.getClass() != PathKey.class) { + return false; + } + return hash == ((PathKey) o).hash; + } + + @Override + public int hashCode() { + return hash; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java new file mode 100644 index 00000000000..094e652fc4c --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java @@ -0,0 +1,88 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.internalapi.SingleCriteriaStopArrivals; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TripScheduleWithOffset; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RaptorWorkerResultComposite + implements RaptorWorkerResult { + + private static final Logger LOG = LoggerFactory.getLogger(RaptorWorkerResultComposite.class); + + private RaptorWorkerResult mainResult; + private RaptorWorkerResult alternativeResult; + + public RaptorWorkerResultComposite( + RaptorWorkerResult mainResult, + RaptorWorkerResult alternativeResult + ) { + this.mainResult = mainResult; + this.alternativeResult = alternativeResult; + } + + @Override + public Collection> extractPaths() { + Map> paths = new HashMap<>(); + addAll(paths, mainResult.extractPaths()); + addExtraRail(paths, alternativeResult.extractPaths()); + return paths.values(); + } + + @Override + public SingleCriteriaStopArrivals extractBestOverallArrivals() { + return mainResult.extractBestOverallArrivals(); + } + + @Override + public SingleCriteriaStopArrivals extractBestTransitArrivals() { + return mainResult.extractBestTransitArrivals(); + } + + @Override + public SingleCriteriaStopArrivals extractBestNumberOfTransfers() { + return mainResult.extractBestNumberOfTransfers(); + } + + @Override + public boolean isDestinationReached() { + return mainResult.isDestinationReached(); + } + + private void addExtraRail(Map> map, Collection> paths) { + paths.forEach(p -> { + if (hasRail(p)) { + var v = map.put(new PathKey(p), p); + LOG.debug("Ex.Rail {} : {}", (v == null ? "ADD " : "SKIP"), p); + } else { + LOG.debug("Ex. NOT Rail : {}", p); + } + }); + } + + private void addAll(Map> map, Collection> paths) { + paths.forEach(p -> { + var v = map.put(new PathKey(p), p); + LOG.debug("Normal {} : {}", (v == null ? "ADD " : "SKIP"), p); + }); + } + + private static boolean hasRail(RaptorPath path) { + return path + .legStream() + .filter(PathLeg::isTransitLeg) + .anyMatch(leg -> { + var trip = (TripScheduleWithOffset) leg.asTransitLeg().trip(); + var mode = trip.getOriginalTripPattern().getMode(); + return mode == TransitMode.RAIL; + }); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java index 8fa4fa31512..e1b1ba3d59f 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java @@ -563,6 +563,14 @@ public static GraphQLFieldDefinition create( ) .build() ) + .argument( + GraphQLArgument + .newArgument() + .name("extraSearchCoachReluctance") + .description("FOR TESTING ONLY") + .type(Scalars.GraphQLFloat) + .build() + ) .dataFetcher(environment -> new TransmodelGraphQLPlanner().plan(environment)) .build(); } diff --git a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java index 20ca8054308..ff61f201624 100644 --- a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java @@ -89,6 +89,7 @@ private BoardAndAlightSlack mapTransit() { req.relaxTransitSearchGeneralizedCostAtDestination, value -> tr.withRaptor(it -> it.withRelaxGeneralizedCostAtDestination(value)) ); + setIfNotNull(req.extraSearchCoachReluctance, tr::setExtraSearchCoachReluctance); }); return new BoardAndAlightSlack( diff --git a/src/main/java/org/opentripplanner/api/common/RoutingResource.java b/src/main/java/org/opentripplanner/api/common/RoutingResource.java index e60bfe41b82..f214f0126c6 100644 --- a/src/main/java/org/opentripplanner/api/common/RoutingResource.java +++ b/src/main/java/org/opentripplanner/api/common/RoutingResource.java @@ -200,6 +200,9 @@ public abstract class RoutingResource { @QueryParam("carReluctance") protected Double carReluctance; + @QueryParam("extraSearchCoachReluctance") + protected Double extraSearchCoachReluctance; + /** * How much worse is waiting for a transit vehicle than being on a transit vehicle, as a * multiplier. The default value treats wait and on-vehicle time as the same. diff --git a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 9c730b94216..61d41c296f2 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -29,6 +29,7 @@ public enum OTPFeature { ), DebugClient(true, false, "Enable the debug web client located at the root of the web server."), FloatingBike(true, false, "Enable floating bike routing."), + HackSorlandsbanen(false, true, "Includ Sørlandsbanen"), GtfsGraphQlApi(true, false, "Enable GTFS GraphQL API."), GtfsGraphQlApiRentalStationFuzzyMatching( false, diff --git a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java index 1f0621b8d36..6b99e7ae238 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java @@ -29,6 +29,9 @@ public class RaptorRequest { private final DebugRequest debug; private final RaptorTimers performanceTimers; + // HACK SØRLANDSBANEN + public double extraSearchCoachReluctance = 0.0; + private RaptorRequest() { searchParams = SearchParams.defaults(); profile = RaptorProfile.MULTI_CRITERIA; @@ -49,6 +52,7 @@ private RaptorRequest() { this.multiCriteria = builder.multiCriteria(); this.performanceTimers = builder.performanceTimers(); this.debug = builder.debug().build(); + this.extraSearchCoachReluctance = builder.extraSearchCoachReluctance; verify(); } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java index ed2aab3de20..7bb8b538843 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java @@ -34,6 +34,9 @@ public class RaptorRequestBuilder { // Performance monitoring private RaptorTimers performanceTimers; + /** HACK SØRLANDSBANEN */ + public double extraSearchCoachReluctance; + // Algorithm private RaptorProfile profile; @@ -60,6 +63,9 @@ public RaptorRequestBuilder() { // Debug this.debug = new DebugRequestBuilder(defaults.debug()); + + // HACK SØRLANDSBANEN + this.extraSearchCoachReluctance = defaults.extraSearchCoachReluctance; } public SearchParamsBuilder searchParams() { diff --git a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java index fe8bb884912..86b10f7d96f 100644 --- a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java +++ b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java @@ -11,6 +11,8 @@ import java.util.concurrent.Future; import java.util.stream.Collectors; import javax.annotation.Nullable; +import org.opentripplanner.ext.sorlandsbanen.EnturHackSorlandsBanen; +import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.raptor.RaptorService; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; @@ -134,7 +136,13 @@ private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request) // Create worker if (request.profile().is(MULTI_CRITERIA)) { - raptorWorker = config.createMcWorker(transitData, request, getDestinationHeuristics()); + // HACK SØRLANDSBANEN + if (OTPFeature.HackSorlandsbanen.isOn() && EnturHackSorlandsBanen.match(request)) { + raptorWorker = + EnturHackSorlandsBanen.worker(config, transitData, request, getDestinationHeuristics()); + } else { + raptorWorker = config.createMcWorker(transitData, request, getDestinationHeuristics()); + } } else { raptorWorker = config.createStdWorker(transitData, request); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index b9f9685341c..e59c4d1673c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -128,7 +128,8 @@ private TransitRouterResult route() { accessEgresses.getAccesses(), accessEgresses.getEgresses(), accessEgresses.calculateMaxAccessTimePenalty(), - serverContext.meterRegistry() + serverContext.meterRegistry(), + transitLayer ); // Route transit diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java index b738d63054e..4f39b2631fe 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java @@ -1,5 +1,6 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit.cost; +import java.util.function.Function; import javax.annotation.Nullable; import org.opentripplanner.model.transfer.TransferConstraint; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; @@ -211,4 +212,19 @@ private int boardingCostConstrainedTransfer( // fallback to regular transfer return boardingCostRegularTransfer(firstBoarding, prevArrivalTime, boardStop, boardTime); } + + /*-- HACK SØRLANDSBANEN :: BEGIN --*/ + + public DefaultCostCalculator( + DefaultCostCalculator original, + Function modeReluctanceMapper + ) { + this.boardCostOnly = original.boardCostOnly; + this.boardAndTransferCost = original.boardAndTransferCost; + this.waitFactor = original.waitFactor; + this.transferCostOnly = original.transferCostOnly; + this.transitFactors = modeReluctanceMapper.apply(original.transitFactors); + this.stopTransferCost = original.stopTransferCost; + } + /*-- HACK SØRLANDSBANEN :: END --*/ } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java index fba2a7cfe38..8a14c27566d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java @@ -6,7 +6,7 @@ *

* The class and methods are {@code final} to help the JIT compiler optimize the use of this class. */ -interface FactorStrategy { +public interface FactorStrategy { /** * Return the factor for the given index. */ diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java index 152f199248c..4c66b5b452e 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java @@ -6,12 +6,12 @@ * This class keep a facto for each index and the minimum factor for fast retrieval during Raptor * search. */ -final class IndexBasedFactorStrategy implements FactorStrategy { +public final class IndexBasedFactorStrategy implements FactorStrategy { private final int[] factors; private final int minFactor; - private IndexBasedFactorStrategy(int[] factors) { + public IndexBasedFactorStrategy(int[] factors) { this.factors = factors; this.minFactor = findMinimumFactor(factors); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index 8e6121edffe..5b0de00bc6c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -8,6 +8,7 @@ import java.time.ZonedDateTime; import java.util.Collection; import java.util.List; +import org.opentripplanner.ext.sorlandsbanen.EnturHackSorlandsBanen; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; @@ -17,6 +18,7 @@ import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; import org.opentripplanner.raptor.rangeraptor.SystemErrDebugLogger; import org.opentripplanner.routing.algorithm.raptoradapter.router.performance.PerformanceTimersForRaptor; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.transit.model.site.StopLocation; @@ -31,6 +33,8 @@ public class RaptorRequestMapper { private final boolean isMultiThreadedEnbled; private final MeterRegistry meterRegistry; + private final TransitLayer transitLayer; + private RaptorRequestMapper( RouteRequest request, boolean isMultiThreaded, @@ -38,13 +42,15 @@ private RaptorRequestMapper( Collection egressPaths, Duration searchWindowAccessSlack, long transitSearchTimeZeroEpocSecond, - MeterRegistry meterRegistry + MeterRegistry meterRegistry, + TransitLayer transitLayer ) { this.request = request; this.isMultiThreadedEnbled = isMultiThreaded; this.accessPaths = accessPaths; this.egressPaths = egressPaths; this.searchWindowAccessSlack = searchWindowAccessSlack; + this.transitLayer = transitLayer; this.transitSearchTimeZeroEpocSecond = transitSearchTimeZeroEpocSecond; this.meterRegistry = meterRegistry; } @@ -56,7 +62,8 @@ public static RaptorRequest mapRequest( Collection accessPaths, Collection egressPaths, Duration searchWindowAccessSlack, - MeterRegistry meterRegistry + MeterRegistry meterRegistry, + TransitLayer transitLayer ) { return new RaptorRequestMapper( request, @@ -65,7 +72,8 @@ public static RaptorRequest mapRequest( egressPaths, searchWindowAccessSlack, transitSearchTimeZero.toEpochSecond(), - meterRegistry + meterRegistry, + transitLayer ) .doMap(); } @@ -171,7 +179,7 @@ private RaptorRequest doMap() { ) ); - return builder.build(); + return EnturHackSorlandsBanen.enableHack(builder.build(), request, transitLayer); } private List mapPassThroughPoints() { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java index a0b62414181..df5996009ec 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java @@ -4,6 +4,7 @@ import java.util.BitSet; import java.util.Iterator; import java.util.List; +import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.opentripplanner.framework.application.OTPFeature; @@ -27,6 +28,8 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.constrainedtransfer.ConstrainedBoardingSearch; import org.opentripplanner.routing.algorithm.raptoradapter.transit.constrainedtransfer.ConstrainedTransfersForPatterns; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.CostCalculatorFactory; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.DefaultCostCalculator; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.FactorStrategy; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.GeneralizedCostParametersMapper; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.transit.model.network.RoutingTripPattern; @@ -241,4 +244,34 @@ public RaptorConstrainedBoardingSearch transferConstraintsReverseS } return new ConstrainedBoardingSearch(false, toStopTransfers, fromStopTransfers); } + + /*-- HACK SØRLANDSBANEN :: BEGIN --*/ + + private RaptorRoutingRequestTransitData( + RaptorRoutingRequestTransitData original, + Function mapFactors + ) { + this.transitLayer = original.transitLayer; + this.transitSearchTimeZero = original.transitSearchTimeZero; + this.activeTripPatternsPerStop = original.activeTripPatternsPerStop; + this.patternIndex = original.patternIndex; + this.transferIndex = original.transferIndex; + this.transferService = original.transferService; + this.constrainedTransfers = original.constrainedTransfers; + this.validTransitDataStartTime = original.validTransitDataStartTime; + this.validTransitDataEndTime = original.validTransitDataEndTime; + this.generalizedCostCalculator = + new DefaultCostCalculator<>( + (DefaultCostCalculator) original.generalizedCostCalculator, + mapFactors + ); + this.slackProvider = original.slackProvider(); + } + + public RaptorTransitDataProvider enturHackSorlandsbanen( + Function mapFactors + ) { + return new RaptorRoutingRequestTransitData(this, mapFactors); + } + /*-- HACK SØRLANDSBANEN :: END --*/ } diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java index 7da4516d782..96945693cdd 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java @@ -31,6 +31,7 @@ public final class TransitPreferences implements Serializable { private final boolean includePlannedCancellations; private final boolean includeRealtimeCancellations; private final RaptorPreferences raptor; + private final double extraSearchCoachReluctance; private TransitPreferences() { this.boardSlack = this.alightSlack = DurationForEnum.of(TransitMode.class).build(); @@ -41,6 +42,7 @@ private TransitPreferences() { this.includePlannedCancellations = false; this.includeRealtimeCancellations = false; this.raptor = RaptorPreferences.DEFAULT; + this.extraSearchCoachReluctance = 0.0; } private TransitPreferences(Builder builder) { @@ -53,6 +55,7 @@ private TransitPreferences(Builder builder) { this.includePlannedCancellations = builder.includePlannedCancellations; this.includeRealtimeCancellations = builder.includeRealtimeCancellations; this.raptor = requireNonNull(builder.raptor); + this.extraSearchCoachReluctance = builder.extraSearchCoachReluctance; } public static Builder of() { @@ -153,6 +156,11 @@ public RaptorPreferences raptor() { return raptor; } + /** Zero means turned off. HACK SØRLANDSBANEN */ + public double extraSearchCoachReluctance() { + return extraSearchCoachReluctance; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -167,6 +175,7 @@ public boolean equals(Object o) { alightSlack.equals(that.alightSlack) && reluctanceForMode.equals(that.reluctanceForMode) && unpreferredCost.equals(that.unpreferredCost) && + extraSearchCoachReluctance == that.extraSearchCoachReluctance && raptor.equals(that.raptor) ); } @@ -182,6 +191,7 @@ public int hashCode() { ignoreRealtimeUpdates, includePlannedCancellations, includeRealtimeCancellations, + extraSearchCoachReluctance, raptor ); } @@ -229,6 +239,7 @@ public static class Builder { private boolean includePlannedCancellations; private boolean includeRealtimeCancellations; private RaptorPreferences raptor; + private double extraSearchCoachReluctance; public Builder(TransitPreferences original) { this.original = original; @@ -241,6 +252,7 @@ public Builder(TransitPreferences original) { this.includePlannedCancellations = original.includePlannedCancellations; this.includeRealtimeCancellations = original.includeRealtimeCancellations; this.raptor = original.raptor; + this.extraSearchCoachReluctance = original.extraSearchCoachReluctance; } public TransitPreferences original() { @@ -305,6 +317,11 @@ public Builder withRaptor(Consumer body) { return this; } + public Builder setExtraSearchCoachReluctance(double extraSearchCoachReluctance) { + this.extraSearchCoachReluctance = extraSearchCoachReluctance; + return this; + } + public Builder apply(Consumer body) { body.accept(this); return this; diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index c89670b7e0a..db212bd31d3 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -352,6 +352,13 @@ The board time is added to the time when going from the stop (offboard) to onboa ) .asDoubleOptional() .ifPresent(it::withRelaxGeneralizedCostAtDestination) + ) + .setExtraSearchCoachReluctance( + c + .of("extraSearchCoachReluctance") + .since(V2_1) + .summary("TODO") + .asDouble(dft.extraSearchCoachReluctance()) ); } diff --git a/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java b/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java index 39022da42ca..aeca5a36ebf 100644 --- a/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java +++ b/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java @@ -39,6 +39,9 @@ public class RaptorArchitectureTest { private static final Package RR_STANDARD = RANGE_RAPTOR.subPackage("standard"); private static final Package RR_STD_CONFIGURE = RR_STANDARD.subPackage("configure"); private static final Package RR_CONTEXT = RANGE_RAPTOR.subPackage("context"); + private static final Package EXT_SORLANDSBANAN_HACK = Package.of( + "org.opentripplanner.ext.sorlandsbanen" + ); /** * Packages used by standard-range-raptor and multi-criteria-range-raptor. @@ -200,7 +203,8 @@ void enforcePackageDependenciesInRaptorService() { RAPTOR_UTIL, CONFIGURE, RR_INTERNAL_API, - RR_TRANSIT + RR_TRANSIT, + EXT_SORLANDSBANAN_HACK ) .verify(); } From 88f928d7e6d8cc1a46b307e91574b162d3d1a1f3 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 7 Nov 2023 13:27:12 +0100 Subject: [PATCH 18/26] Fix documentation --- docs/Configuration.md | 63 ++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index 5073dafed31..ebe2ec24327 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -219,36 +219,39 @@ Here is a list of all features which can be toggled on/off and their default val -| Feature | Description | Enabled by default | Sandbox | -|--------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------:|:-------:| -| `APIBikeRental` | Enable the bike rental endpoint. | ✓️ | | -| `APIServerInfo` | Enable the server info endpoint. | ✓️ | | -| `APIGraphInspectorTile` | Enable the inspector endpoint for graph information for inspection/debugging purpose. | ✓️ | | -| `APIUpdaterStatus` | Enable endpoint for graph updaters status. | ✓️ | | -| `ConsiderPatternsForDirectTransfers` | Enable limiting transfers so that there is only a single transfer to each pattern. | ✓️ | | -| `DebugClient` | Enable the debug web client located at the root of the web server. | ✓️ | | -| `FloatingBike` | Enable floating bike routing. | ✓️ | | -| `GtfsGraphQlApi` | Enable GTFS GraphQL API. | ✓️ | | -| `MinimumTransferTimeIsDefinitive` | If the minimum transfer time is a lower bound (default) or the definitive time for the transfer. Set this to `true` if you want to set a transfer time lower than what OTP derives from OSM data. | | | -| `OptimizeTransfers` | OTP will inspect all itineraries found and optimize where (which stops) the transfer will happen. Waiting time, priority and guaranteed transfers are taken into account. | ✓️ | | -| `ParallelRouting` | Enable performing parts of the trip planning in parallel. | | | -| `TransferConstraints` | Enforce transfers to happen according to the _transfers.txt_(GTFS) and Interchanges(NeTEx). Turing this _off_ will increase the routing performance a little. | ✓️ | | -| `ActuatorAPI` | Endpoint for actuators (service health status). | | ✓️ | -| `AsyncGraphQLFetchers` | Whether the @async annotation in the GraphQL schema should lead to the fetch being executed asynchronously. This allows batch or alias queries to run in parallel at the cost of consuming extra threads. | | | -| `DataOverlay` | Enable usage of data overlay when calculating costs for the street network. | | ✓️ | -| `FaresV2` | Enable import of GTFS-Fares v2 data. | | ✓️ | -| `FlexRouting` | Enable FLEX routing. | | ✓️ | -| `GoogleCloudStorage` | Enable Google Cloud Storage integration. | | ✓️ | -| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with realtime data | | ✓️ | -| `ReportApi` | Enable the report API. | | ✓️ | -| `RestAPIPassInDefaultConfigAsJson` | Enable a default RouteRequest to be passed in as JSON on the REST API - FOR DEBUGGING ONLY! | | | -| `SandboxAPIGeocoder` | Enable the Geocoder API. | | ✓️ | -| `SandboxAPIMapboxVectorTilesApi` | Enable Mapbox vector tiles API. | | ✓️ | -| `SandboxAPIParkAndRideApi` | Enable park-and-ride endpoint. | | ✓️ | -| `SandboxAPITransmodelApi` | Enable Entur Transmodel(NeTEx) GraphQL API. | | ✓️ | -| `SandboxAPITravelTime` | Enable the isochrone/travel time surface API. | | ✓️ | -| `TransferAnalyzer` | Analyze transfers during graph build. | | ✓️ | -| `VehicleToStopHeuristics` | Enable improved heuristic for park-and-ride queries. | | ✓️ | +| Feature | Description | Enabled by default | Sandbox | +|--------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------:|:-------:| +| `APIBikeRental` | Enable the bike rental endpoint. | ✓️ | | +| `APIServerInfo` | Enable the server info endpoint. | ✓️ | | +| `APIGraphInspectorTile` | Enable the inspector endpoint for graph information for inspection/debugging purpose. | ✓️ | | +| `APIUpdaterStatus` | Enable endpoint for graph updaters status. | ✓️ | | +| `ConsiderPatternsForDirectTransfers` | Enable limiting transfers so that there is only a single transfer to each pattern. | ✓️ | | +| `DebugClient` | Enable the debug web client located at the root of the web server. | ✓️ | | +| `FloatingBike` | Enable floating bike routing. | ✓️ | | +| `HackSorlandsbanen` | Includ Sørlandsbanen | | ✓️ | +| `GtfsGraphQlApi` | Enable GTFS GraphQL API. | ✓️ | | +| `GtfsGraphQlApiRentalStationFuzzyMatching` | Does vehicleRentalStation query also allow ids that are not feed scoped. | | | +| `MinimumTransferTimeIsDefinitive` | If the minimum transfer time is a lower bound (default) or the definitive time for the transfer. Set this to `true` if you want to set a transfer time lower than what OTP derives from OSM data. | | | +| `OptimizeTransfers` | OTP will inspect all itineraries found and optimize where (which stops) the transfer will happen. Waiting time, priority and guaranteed transfers are taken into account. | ✓️ | | +| `ParallelRouting` | Enable performing parts of the trip planning in parallel. | | | +| `TransferConstraints` | Enforce transfers to happen according to the _transfers.txt_(GTFS) and Interchanges(NeTEx). Turing this _off_ will increase the routing performance a little. | ✓️ | | +| `ActuatorAPI` | Endpoint for actuators (service health status). | | ✓️ | +| `AsyncGraphQLFetchers` | Whether the @async annotation in the GraphQL schema should lead to the fetch being executed asynchronously. This allows batch or alias queries to run in parallel at the cost of consuming extra threads. | | | +| `Co2Emissions` | Enable the emissions sandbox module. | | ✓️ | +| `DataOverlay` | Enable usage of data overlay when calculating costs for the street network. | | ✓️ | +| `FaresV2` | Enable import of GTFS-Fares v2 data. | | ✓️ | +| `FlexRouting` | Enable FLEX routing. | | ✓️ | +| `GoogleCloudStorage` | Enable Google Cloud Storage integration. | | ✓️ | +| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with realtime data | | ✓️ | +| `ReportApi` | Enable the report API. | | ✓️ | +| `RestAPIPassInDefaultConfigAsJson` | Enable a default RouteRequest to be passed in as JSON on the REST API - FOR DEBUGGING ONLY! | | | +| `SandboxAPIGeocoder` | Enable the Geocoder API. | | ✓️ | +| `SandboxAPIMapboxVectorTilesApi` | Enable Mapbox vector tiles API. | | ✓️ | +| `SandboxAPIParkAndRideApi` | Enable park-and-ride endpoint. | | ✓️ | +| `SandboxAPITransmodelApi` | Enable Entur Transmodel(NeTEx) GraphQL API. | | ✓️ | +| `SandboxAPITravelTime` | Enable the isochrone/travel time surface API. | | ✓️ | +| `TransferAnalyzer` | Analyze transfers during graph build. | | ✓️ | +| `VehicleToStopHeuristics` | Enable improved heuristic for park-and-ride queries. | | ✓️ | From c8febb7c14edcb8f4992efb77b1d4d4d63e670f0 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 7 Nov 2023 13:33:39 +0100 Subject: [PATCH 19/26] Version 2.5.0-entur-7 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ce397e317d0..7c7573d8b14 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.5.0-SNAPSHOT + 2.5.0-entur-7 jar From e9d4bb2152ea2245d6631210532efda57d67f03a Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 19 May 2023 18:35:18 +0200 Subject: [PATCH 20/26] CircleCI config, release scripts, and pom.xml updates. --- .circleci/config.yml | 52 +++++ .circleci/prepare_release | 235 +++++++++++++++++++++++ .circleci/release | 84 ++++++++ .circleci/update_deployment_config | 46 +++++ .gitignore | 1 + pom.xml | 12 +- src/main/java/EnturUpdatePomVersion.java | 135 +++++++++++++ 7 files changed, 559 insertions(+), 6 deletions(-) create mode 100644 .circleci/config.yml create mode 100755 .circleci/prepare_release create mode 100755 .circleci/release create mode 100755 .circleci/update_deployment_config create mode 100644 src/main/java/EnturUpdatePomVersion.java diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000000..d01a0c323e9 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +aliases: + - &jfrog-login + name: Rename jfrog environment variable for maven setting.xml + command: | + echo "export JFROG_USER=$ARTIFACTORY_USER" >> $BASH_ENV + echo "export JFROG_PASS=$ARTIFACTORY_PASSWORD" >> $BASH_ENV + - &post_build + name: Update deployment with OTP version + command: | + sudo apt-get update + sudo apt-get install libxml2-utils + chmod u+x .circleci/update_deployment_config + .circleci/update_deployment_config + +jobs: + build: + docker: + - image: cimg/openjdk:17.0 + environment: + DEBIAN_FRONTEND: "noninteractive" + MAVEN_OPTS: -Xmx6G + resource_class: large + steps: + - checkout + - restore_cache: + keys: + - dep-cache-{{ checksum "pom.xml" }} + # fallback to the most recent cache if there is no exact match for this pom.xml + - dep-cache- + - run: wget https://raw.githubusercontent.com/entur/circleci-toolbox-image-java11/master/tools/m2/settings.xml -O .circleci/settings.xml + - run: *jfrog-login + - run: mvn org.apache.maven.plugins:maven-dependency-plugin:3.1.0:go-offline -s .circleci/settings.xml + - save_cache: + paths: + - ~/.m2 + key: dep-cache-{{ checksum "pom.xml" }} + - run: mvn install -PprettierSkip org.apache.maven.plugins:maven-deploy-plugin:2.8.2:deploy -s .circleci/settings.xml -DaltDeploymentRepository=snapshots::default::https://entur2.jfrog.io/entur2/libs-release-local + - run: *post_build + +workflows: + version: 2 + release: + jobs: + - build: + name: build-release + context: global + filters: + branches: + only: + - otp2_entur_develop \ No newline at end of file diff --git a/.circleci/prepare_release b/.circleci/prepare_release new file mode 100755 index 00000000000..5b2ad0e23c4 --- /dev/null +++ b/.circleci/prepare_release @@ -0,0 +1,235 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ENTUR_DEVELOP=otp2_entur_develop +REMOTE_REPO="`git remote -v | grep "entur/OpenTripPlanner" | grep "push" | awk '{print $1;}'`" +STATUS_FILE=".prepare_release.tmp" +STATUS="" +DRY_RUN="" +OTP_BASE="" + +function main() { + setup "$@" + resumePreviousExecution + resetEnturDevelop + rebaseAndMergeExtBranch otp2_ext_config + rebaseAndMergeExtBranch otp2_ext_hack_sorlandsbanen + logSuccess +} + +function setup() { + if [[ $# -eq 2 && "$1" == "--dryRun" ]] ; then + DRY_RUN="--dryRun" + OTP_BASE="$2" + elif [[ $# -eq 1 ]] ; then + OTP_BASE="$1" + else + printHelp + exit 1 + fi + + echo "" + echo "Options: ${DRY_RUN}" + echo "Git base branch/commit: ${OTP_BASE}" + echo "Entur develop branch: ${ENTUR_DEVELOP}" + echo "Entur remote repo(pull/push): ${REMOTE_REPO}" + echo "" + + if git diff-index --quiet HEAD --; then + echo "" + echo "OK - No local changes, prepare to checkout '${ENTUR_DEVELOP}'" + echo "" + else + echo "" + echo "You have local modification, the script will abort. Nothing done!" + exit 2 + fi + + git fetch ${REMOTE_REPO} +} + +# This script create a status file '.prepare_release.tmp'. This file is used to resume the +# script in the same spot as where is left when the error occurred. This allow us to fix the +# problem (merge conflict or compile error) and re-run the script to complete the proses. +function resumePreviousExecution() { + readStatus + + if [[ -n "${STATUS}" ]] ; then + echo "" + echo "Resume: ${STATUS}?" + echo "" + echo " If all problems are resolved you may continue." + echo " Exit to clear status and start over." + echo "" + + ANSWER="" + while [[ ! "$ANSWER" =~ [yx] ]]; do + echo "Do you want to resume: [y:Yes, x:Exit]" + read ANSWER + done + + if [[ "${ANSWER}" == "x" ]] ; then + exit 0 + fi + fi +} + +function resetEnturDevelop() { + echo "" + echo "## ------------------------------------------------------------------------------------- ##" + echo "## RESET '${ENTUR_DEVELOP}' TO '${OTP_BASE}'" + echo "## ------------------------------------------------------------------------------------- ##" + echo "" + echo "Would you like to reset the '${ENTUR_DEVELOP}' to '${OTP_BASE}'? " + echo "" + + whatDoYouWant + + if [[ "${ANSWER}" == "y" ]] ; then + echo "" + echo "Checkout '${ENTUR_DEVELOP}'" + git checkout ${ENTUR_DEVELOP} + + echo "" + echo "Reset '${ENTUR_DEVELOP}' branch to '${OTP_BASE}' (hard)" + git reset --hard "${OTP_BASE}" + echo "" + fi +} + +function rebaseAndMergeExtBranch() { + EXT_BRANCH="$1" + EXT_STATUS_REBASE="Rebase '${EXT_BRANCH}'" + EXT_STATUS_COMPILE="Compile '${EXT_BRANCH}'" + + echo "" + echo "## ------------------------------------------------------------------------------------- ##" + echo "## REBASE AND MERGE '${EXT_BRANCH}' INTO '${ENTUR_DEVELOP}'" + echo "## ------------------------------------------------------------------------------------- ##" + echo "" + echo "You are about to rebase and merge '${EXT_BRANCH}' into '${ENTUR_DEVELOP}'. Any local" + echo "modification in the '${EXT_BRANCH}' will be lost." + echo "" + + whatDoYouWant + + if [[ "${ANSWER}" == "y" ]] ; then + echo "" + echo "Checkout '${EXT_BRANCH}'" + git checkout "${EXT_BRANCH}" + + echo "" + echo "Reset to '${REMOTE_REPO}/${EXT_BRANCH}'" + git reset --hard "${REMOTE_REPO}/${EXT_BRANCH}" + + echo "" + echo "Top 2 commits in '${EXT_BRANCH}'" + echo "-------------------------------------------------------------------------------------------" + git --no-pager log -2 + echo "-------------------------------------------------------------------------------------------" + echo "" + echo "You are about to rebase the TOP COMMIT ONLY(see above). Check that the " + echo "'${EXT_BRANCH}' only have ONE commit that you want to keep." + echo "" + + whatDoYouWant + + if [[ "${ANSWER}" == "y" ]] ; then + echo "" + echo "Rebase '${EXT_BRANCH}' onto '${ENTUR_DEVELOP}'" + setStatus "${EXT_STATUS_REBASE}" + git rebase --onto ${ENTUR_DEVELOP} HEAD~1 + fi + fi + + if [[ "${STATUS}" == "${EXT_STATUS_REBASE}" || "${STATUS}" == "${EXT_STATUS_COMPILE}" ]] ; then + # Reset status in case the test-compile fails. We need to do this because the status file + # is deleted after reading the status in the setup() function. + setStatus "${EXT_STATUS_COMPILE}" + + mvn clean test-compile + clearStatus + + echo "" + echo "Push '${EXT_BRANCH}'" + if [[ -z "${DRY_RUN}" ]] ; then + git push -f + else + echo "Skip: git push -f (--dryRun)" + fi + + echo "" + echo "Checkout '${ENTUR_DEVELOP}' and merge in '${EXT_BRANCH}'" + git checkout "${ENTUR_DEVELOP}" + git merge "${EXT_BRANCH}" + fi +} + +function logSuccess() { + echo "" + echo "## ------------------------------------------------------------------------------------- ##" + echo "## PREPARE RELEASE DONE -- SUCCESS" + echo "## ------------------------------------------------------------------------------------- ##" + echo " - '${REMOTE_REPO}/${ENTUR_DEVELOP}' reset to '${OTP_BASE}'" + echo " - 'otp2_ext_config' merged" + echo " - 'otp2_ext_hack_sorlandsbanen' merged" + echo "" + echo "" +} + +function whatDoYouWant() { + echo "" + ANSWER="" + + if [[ -n "${STATUS}" ]] ; then + # Skip until process is resumed + ANSWER="s" + else + while [[ ! "$ANSWER" =~ [ysx] ]]; do + echo "Do you want to continue: [y:Yes, s:Skip, x:Exit]" + read ANSWER + done + + if [[ "${ANSWER}" == "x" ]] ; then + exit 0 + fi + fi +} + +function setStatus() { + STATUS="$1" + echo "$STATUS" > "${STATUS_FILE}" +} + +function readStatus() { + if [[ -f "${STATUS_FILE}" ]] ; then + STATUS=`cat $STATUS_FILE` + rm "$STATUS_FILE" + else + STATUS="" + fi +} + +function clearStatus() { + STATUS="" + rm "${STATUS_FILE}" +} + +function printHelp() { + echo "" + echo "This script take ONE argument , the base **branch** or **commit** to use for the" + echo "release. The '${ENTUR_DEVELOP}' branch is reset to this commit and then the extension" + echo "branches is rebased onto that. The 'release' script is used to complete the release." + echo "It tag and push all changes to remote git repo." + echo "" + echo "Options:" + echo " --dryRun : Run script locally, nothing is pushed to remote server." + echo "" + echo "Usage:" + echo " $ .circleci/prepare_release otp/dev-2.x" + echo " $ .circleci/prepare_release --dryRun otp/dev-2.x" + echo "" +} + +main "$@" diff --git a/.circleci/release b/.circleci/release new file mode 100755 index 00000000000..9cac0d97958 --- /dev/null +++ b/.circleci/release @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +set -euo pipefail + +GIT_REMOTE_REPO="`git remote -v | grep "entur/OpenTripPlanner" | grep "push" | awk '{print $1;}'`" +MASTER_BRANCH=otp2_entur_develop +TAGS_FILE=target/git-entur-tags.txt +DRY_RUN="" + +function main() { + setup "$@" + listAllTags + mergeInOldReleaseWithNoChanges + setPomVersion + tagRelease + pushToRemote +} + +function setup() { + echo "" + echo "git fetch ${GIT_REMOTE_REPO}" + git fetch ${GIT_REMOTE_REPO} + + echo "Verify current branch is ${MASTER_BRANCH} " + git status | grep -q "On branch ${MASTER_BRANCH}" + + if [[ "${1+x}" == "--dryRun" ]] ; then + DRY_RUN="--dryRun" + fi +} + +function listAllTags() { + ## List all Entur tags to allow the UpdatePomVersion java program find the next version number + echo "" + echo "Dump all entur tags to ${TAGS_FILE}" + git tag -l | grep entur > ${TAGS_FILE} +} + +function setPomVersion() { + echo "" + echo "Update pom.xml with new version" + javac -d target/classes src/main/java/EnturUpdatePomVersion.java + + VERSION="`java -cp target/classes EnturUpdatePomVersion ${TAGS_FILE}`" + echo "" + echo "New version set: ${VERSION}" + echo "" + + ## Verify everything builds and tests run + echo "" + mvn clean test + + ## Add [ci skip] here before moving this to the CI server + echo "" + echo "Add and commit pom.xml" + git commit -m "Version ${VERSION}" pom.xml +} + +function mergeInOldReleaseWithNoChanges() { + echo "" + echo "Merge the old version of '${GIT_REMOTE_REPO}' into the new version. This only keep " + echo "a reference to the old version, the resulting tree of the merge is that of the new" + echo "branch head, effectively ignoring all changes from the old release." + git merge -s ours "${GIT_REMOTE_REPO}/${MASTER_BRANCH}" -m "Merge old release into '${MASTER_BRANCH}' - NO CHANGES COPIED OVER" +} + + +function tagRelease() { + echo "" + echo "Tag version ${VERSION}" + git tag -a v${VERSION} -m "Version ${VERSION}" +} + +function pushToRemote() { + echo "" + echo "Push pom.xml and new tag" + if [[ -z "${DRY_RUN}" ]] ; then + git push -f ${GIT_REMOTE_REPO} "v${VERSION}" ${MASTER_BRANCH} + else + echo "Skip: push -f ${GIT_REMOTE_REPO} "v${VERSION}" ${MASTER_BRANCH} (--dryRun)" + fi +} + +main "$@" diff --git a/.circleci/update_deployment_config b/.circleci/update_deployment_config new file mode 100755 index 00000000000..64550f19797 --- /dev/null +++ b/.circleci/update_deployment_config @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## +## This script run AFTER OTP is build on the CI Server. It checkout the +## deployment config and update the version number, commit and push. This +## trigger the deployment config build witch create and deploy a new OTP2 +## docker image. +## +## Note! There is no need to run this script locally, unless you want to debug it. +## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## + + +set -euo pipefail + +OTP_DEPLOYMENT_CONFIG=target/otp-deployment-config +DOCKER_FILE=Dockerfile + +VERSION=$(xmllint --xpath "//*[local-name()='project']/*[local-name()='version']/text()" pom.xml) + +echo "Checkout otp-deplyment-config in ${OTP_DEPLOYMENT_CONFIG}" +git clone -n https://github.com/entur/otp-deployment-config.git --depth 1 ${OTP_DEPLOYMENT_CONFIG} + +pushd ${OTP_DEPLOYMENT_CONFIG} + +echo "Checkout latest version of master Dockerfile" +git checkout master ${DOCKER_FILE} + +echo "Update OTP version number in ${DOCKER_FILE}" +if [[ "$OSTYPE" == "darwin"* ]]; then + sed -E -i '' "s/OTP_VERSION=[\.0-9]+-entur-[0-9]+/OTP_VERSION=${VERSION}/" ${DOCKER_FILE} +else + sed -E -i "s/OTP_VERSION=[\.0-9]+-entur-[0-9]+/OTP_VERSION=${VERSION}/" ${DOCKER_FILE} +fi + +if [[ "$CIRCLE_USERNAME" != "" ]]; then + git config user.email "circleci@entur.no" + git config user.name "circleci ($CIRCLE_USERNAME)" +fi + +echo "Add and commit Dockerfile" +git commit -m "New OTP2 Version ${VERSION}" ${DOCKER_FILE} + +echo "Push otp-deployment-config to GitHub" +git push + +popd \ No newline at end of file diff --git a/.gitignore b/.gitignore index a90f06cdcb0..3bc96654225 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .project .pydevproject .settings +.prepare_release.tmp .sonar .nvmrc *~ diff --git a/pom.xml b/pom.xml index c1e1f3b50d7..5b7108c8ec3 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 128 + EN-0056 30.0 2.48.1 @@ -84,11 +84,11 @@ - - github - OpenTripPlanner Maven Repository on Github Packages - https://maven.pkg.github.com/${GITHUB_REPOSITORY}/ - + + snapshots + entur2-snapshots + https://entur2.jfrog.io/entur2/libs-snapshot-local + diff --git a/src/main/java/EnturUpdatePomVersion.java b/src/main/java/EnturUpdatePomVersion.java new file mode 100644 index 00000000000..97bd72b3c6e --- /dev/null +++ b/src/main/java/EnturUpdatePomVersion.java @@ -0,0 +1,135 @@ +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.regex.Pattern; + +public class EnturUpdatePomVersion { + + private static final String VERSION_SEP = "-"; + private static final String ENTUR_PREFIX = "entur" + VERSION_SEP; + private static final Path POM_FILE = Path.of("pom.xml"); + + private final List tags = new ArrayList<>(); + private final List pomFile = new ArrayList<>(); + private String mainVersion; + private int versionNumber = 0; + + public static void main(String[] args) { + try { + new EnturUpdatePomVersion().withArgs(args).run(); + } catch (Exception e) { + System.err.println(e.getMessage()); + e.printStackTrace(System.err); + System.exit(10); + } + } + + private EnturUpdatePomVersion withArgs(String[] args) throws IOException { + if (args.length != 1 || args[0].matches("(?i)-h|--help")) { + printHelp(); + System.exit(1); + } + String arg = args[0]; + + if (arg.matches("\\d+")) { + versionNumber = resolveVersionFromNumericString(arg); + } else { + tags.addAll(readTagsFromFile(arg)); + } + return this; + } + + private void run() throws IOException { + readAndReplaceVersion(); + replacePomFile(); + } + + public void readAndReplaceVersion() throws IOException { + var pattern = Pattern.compile( + "(\\s*)(\\d+.\\d+.\\d+)" + + VERSION_SEP + + "(" + + ENTUR_PREFIX + + "\\d+|SNAPSHOT)(\\s*)" + ); + boolean found = false; + int i = 0; + + for (String line : Files.readAllLines(POM_FILE, UTF_8)) { + // Look for the version in the 25 first lines + if (!found) { + var m = pattern.matcher(line); + if (m.matches()) { + mainVersion = m.group(2); + String newVersion = mainVersion + VERSION_SEP + ENTUR_PREFIX + resolveVersionNumber(); + line = m.group(1) + newVersion + m.group(4); + System.out.println(newVersion); + found = true; + } + if (++i == 25) { + throw new IllegalStateException( + "Version not found in first 25 lines of the pom.xml file." + ); + } + } + pomFile.add(line); + } + if (!found) { + throw new IllegalStateException( + "Version not found in 'pom.xml'. Nothing matching pattern: " + pattern + ); + } + } + + public void replacePomFile() throws IOException { + Files.delete(POM_FILE); + Files.write(POM_FILE, pomFile, UTF_8); + } + + private static void printHelp() { + System.err.println( + "Use this small program to replace the OTP version '2.1.0-SNAPSHOT' \n" + + "with a new version number with a Entur qualifier like '2.1.0-entur-1'.\n" + + "\n" + + "Usage:\n" + + " $ java -cp .circleci UpdatePomVersion \n" + ); + } + + private int resolveVersionNumber() { + var pattern = Pattern.compile("v" + mainVersion + VERSION_SEP + ENTUR_PREFIX + "(\\d+)"); + int maxTagVersion = tags + .stream() + .mapToInt(tag -> { + var m = pattern.matcher(tag); + return m.matches() ? Integer.parseInt(m.group(1)) : -999; + }) + .max() + .orElse(-999); + + return 1 + Math.max(maxTagVersion, versionNumber); + } + + public static int resolveVersionFromNumericString(String arg) { + try { + return Integer.parseInt(arg); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Unable to parse input, decimal number expected: '" + arg + "'" + ); + } + } + + private static Collection readTagsFromFile(String arg) throws IOException { + var tags = Files.readAllLines(Path.of(arg)); + if (tags.isEmpty()) { + throw new IllegalStateException("Unable to load git tags from file: " + arg); + } + return tags; + } +} From 1cf343c75da266ef6c2bae5f98447d860a49e211 Mon Sep 17 00:00:00 2001 From: Hannes Junnila Date: Fri, 9 Dec 2022 09:38:06 +0200 Subject: [PATCH 21/26] =?UTF-8?q?hack:=20Add=20train=20option=20for=20S?= =?UTF-8?q?=C3=B8rlandsbanen.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/Configuration.md | 1 + docs/RouteRequest.md | 1 + src/ext/graphql/transmodelapi/schema.graphql | 2 + .../ConcurrentCompositeWorker.java | 53 +++++++ .../sorlandsbanen/EnturHackSorlandsBanen.java | 141 ++++++++++++++++++ .../ext/sorlandsbanen/PathKey.java | 51 +++++++ .../RaptorWorkerResultComposite.java | 88 +++++++++++ .../transmodelapi/model/plan/TripQuery.java | 8 + .../common/RequestToPreferencesMapper.java | 1 + .../api/common/RoutingResource.java | 3 + .../framework/application/OTPFeature.java | 1 + .../raptor/api/request/RaptorRequest.java | 4 + .../api/request/RaptorRequestBuilder.java | 6 + .../service/RangeRaptorDynamicSearch.java | 10 +- .../raptoradapter/router/TransitRouter.java | 3 +- .../transit/cost/DefaultCostCalculator.java | 16 ++ .../transit/cost/FactorStrategy.java | 2 +- .../cost/IndexBasedFactorStrategy.java | 4 +- .../transit/mappers/RaptorRequestMapper.java | 16 +- .../RaptorRoutingRequestTransitData.java | 33 ++++ .../preference/TransitPreferences.java | 17 +++ .../routerequest/RouteRequestConfig.java | 7 + .../raptor/RaptorArchitectureTest.java | 6 +- 23 files changed, 464 insertions(+), 10 deletions(-) create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java diff --git a/docs/Configuration.md b/docs/Configuration.md index bfd168d5955..ebe2ec24327 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -228,6 +228,7 @@ Here is a list of all features which can be toggled on/off and their default val | `ConsiderPatternsForDirectTransfers` | Enable limiting transfers so that there is only a single transfer to each pattern. | ✓️ | | | `DebugClient` | Enable the debug web client located at the root of the web server. | ✓️ | | | `FloatingBike` | Enable floating bike routing. | ✓️ | | +| `HackSorlandsbanen` | Includ Sørlandsbanen | | ✓️ | | `GtfsGraphQlApi` | Enable GTFS GraphQL API. | ✓️ | | | `GtfsGraphQlApiRentalStationFuzzyMatching` | Does vehicleRentalStation query also allow ids that are not feed scoped. | | | | `MinimumTransferTimeIsDefinitive` | If the minimum transfer time is a lower bound (default) or the definitive time for the transfer. Set this to `true` if you want to set a transfer time lower than what OTP derives from OSM data. | | | diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index f814753df4a..b6284d22e4d 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -46,6 +46,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe | elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 | | elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | | escalatorReluctance | `double` | A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time | *Optional* | `1.5` | 2.4 | +| extraSearchCoachReluctance | `double` | TODO | *Optional* | `0.0` | 2.1 | | geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | | ignoreRealtimeUpdates | `boolean` | When true, realtime updates are ignored during this search. | *Optional* | `false` | 2.0 | | [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql index a207d8d0d6f..e3d8f60cf7f 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/ext/graphql/transmodelapi/schema.graphql @@ -793,6 +793,8 @@ type QueryType { dateTime: DateTime, "Debug the itinerary-filter-chain. OTP will attach a system notice to itineraries instead of removing them. This is very convenient when tuning the filters." debugItineraryFilter: Boolean = false @deprecated(reason : "Use `itineraryFilter.debug` instead."), + "FOR TESTING ONLY" + extraSearchCoachReluctance: Float, "A list of filters for which trips should be included. A trip will be included if it matches with at least one filter. An empty list of filters means that all trips should be included. If a search include this parameter, \"whiteListed\", \"banned\" & \"modes.transportModes\" filters will be ignored." filters: [TripFilterInput!], "The start location" diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java new file mode 100644 index 00000000000..e80e106ce1e --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java @@ -0,0 +1,53 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.opentripplanner.framework.application.OTPFeature; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; +import org.opentripplanner.raptor.api.response.StopArrivals; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.multicriteria.McRaptorWorkerResult; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TripScheduleWithOffset; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class ConcurrentCompositeWorker implements RaptorWorker { + + private static final Logger LOG = LoggerFactory.getLogger(ConcurrentCompositeWorker.class); + + private final RaptorWorker mainWorker; + private final RaptorWorker alternativeWorker; + + ConcurrentCompositeWorker(RaptorWorker mainWorker, RaptorWorker alternativeWorker) { + this.mainWorker = mainWorker; + this.alternativeWorker = alternativeWorker; + } + + @Override + public RaptorWorkerResult route() { + if (OTPFeature.ParallelRouting.isOn()) { + var mainResultFuture = CompletableFuture.supplyAsync(mainWorker::route); + var alternativeResultFuture = CompletableFuture.supplyAsync(alternativeWorker::route); + + try { + return new RaptorWorkerResultComposite<>( + mainResultFuture.get(), + alternativeResultFuture.get() + ); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } else { + var mainResult = mainWorker.route(); + var alternativeResult = alternativeWorker.route(); + return new RaptorWorkerResultComposite<>(mainResult, alternativeResult); + } + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java new file mode 100644 index 00000000000..2b52e850f42 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java @@ -0,0 +1,141 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.function.Function; +import org.opentripplanner.framework.geometry.SphericalDistanceLibrary; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.model.GenericLocation; +import org.opentripplanner.raptor.api.model.RaptorAccessEgress; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.request.RaptorRequest; +import org.opentripplanner.raptor.api.request.SearchParams; +import org.opentripplanner.raptor.configure.RaptorConfig; +import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; +import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.FactorStrategy; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.IndexBasedFactorStrategy; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.RaptorRoutingRequestTransitData; +import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.site.StopLocation; + +public class EnturHackSorlandsBanen { + + private static final double SOUTH_BOARDER_LIMIT = 59.1; + private static final int MIN_DISTANCE_LIMIT = 120_000; + + public static boolean match(RaptorRequest mcRequest) { + return mcRequest.extraSearchCoachReluctance > 0.1; + } + + public static RaptorWorker worker( + RaptorConfig config, + RaptorTransitDataProvider transitData, + RaptorRequest mcRequest, + Heuristics destinationHeuristics + ) { + //noinspection unchecked + RaptorTransitDataProvider altTransitData = (RaptorTransitDataProvider) ( + (RaptorRoutingRequestTransitData) transitData + ).enturHackSorlandsbanen(mapFactors(mcRequest.extraSearchCoachReluctance)); + + return new ConcurrentCompositeWorker<>( + config.createMcWorker(transitData, mcRequest, destinationHeuristics), + config.createMcWorker(altTransitData, mcRequest, destinationHeuristics) + ); + } + + public static RaptorRequest enableHack( + RaptorRequest raptorRequest, + RouteRequest request, + TransitLayer transitLayer + ) { + if (request.preferences().transit().extraSearchCoachReluctance() < 0.1) { + return raptorRequest; + } + + SearchParams params = raptorRequest.searchParams(); + + WgsCoordinate from = findStopCoordinate(request.from(), params.accessPaths(), transitLayer); + WgsCoordinate to = findStopCoordinate(request.to(), params.egressPaths(), transitLayer); + + if (from.latitude() > SOUTH_BOARDER_LIMIT && to.latitude() > SOUTH_BOARDER_LIMIT) { + return raptorRequest; + } + + double distanceMeters = SphericalDistanceLibrary.distance( + from.latitude(), + from.longitude(), + to.latitude(), + to.longitude() + ); + + if (distanceMeters < MIN_DISTANCE_LIMIT) { + return raptorRequest; + } + + raptorRequest.extraSearchCoachReluctance = + request.preferences().transit().extraSearchCoachReluctance(); + return raptorRequest; + } + + /* private methods */ + + private static Function mapFactors( + final double extraSearchCoachReluctance + ) { + return (FactorStrategy originalFactors) -> { + int[] modeReluctance = new int[TransitMode.values().length]; + for (TransitMode mode : TransitMode.values()) { + int index = mode.ordinal(); + int originalFactor = originalFactors.factor(index); + modeReluctance[index] = + mode == TransitMode.COACH + ? (int) (extraSearchCoachReluctance * originalFactor + 0.5) + : originalFactor; + } + return new IndexBasedFactorStrategy(modeReluctance); + }; + } + + /** + * Find a coordinate matching the given location, in order: + * - First return the coordinate of the location if it exists. + * - Then loop through the access/egress stops and try to find the + * stop or station given by the location id, return the stop/station coordinate. + * - Return the fist stop in the access/egress list coordinate. + */ + @SuppressWarnings("ConstantConditions") + private static WgsCoordinate findStopCoordinate( + GenericLocation location, + Collection accessEgress, + TransitLayer transitLayer + ) { + if (location.lat != null) { + return new WgsCoordinate(location.lat, location.lng); + } + + StopLocation firstStop = null; + for (RaptorAccessEgress it : accessEgress) { + StopLocation stop = transitLayer.getStopByIndex(it.stop()); + if (stop.getId().equals(location.stopId)) { + return stop.getCoordinate(); + } + if (idIsParentStation(stop, location.stopId)) { + return stop.getParentStation().getCoordinate(); + } + if (firstStop == null) { + firstStop = stop; + } + } + return firstStop.getCoordinate(); + } + + private static boolean idIsParentStation(StopLocation stop, FeedScopedId pId) { + return stop.getParentStation() != null && stop.getParentStation().getId().equals(pId); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java new file mode 100644 index 00000000000..94267b3e7c2 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java @@ -0,0 +1,51 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; + +final class PathKey { + + private final int hash; + + PathKey(RaptorPath path) { + this.hash = hash(path); + } + + private static int hash(RaptorPath path) { + if (path == null) { + return 0; + } + int result = 1; + + PathLeg leg = path.accessLeg(); + + while (!leg.isEgressLeg()) { + result = 31 * result + leg.toStop(); + result = 31 * result + leg.toTime(); + + if (leg.isTransitLeg()) { + result = 31 * result + leg.asTransitLeg().trip().pattern().debugInfo().hashCode(); + } + leg = leg.nextLeg(); + } + result = 31 * result + leg.toTime(); + + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o.getClass() != PathKey.class) { + return false; + } + return hash == ((PathKey) o).hash; + } + + @Override + public int hashCode() { + return hash; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java new file mode 100644 index 00000000000..094e652fc4c --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java @@ -0,0 +1,88 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.internalapi.SingleCriteriaStopArrivals; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TripScheduleWithOffset; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RaptorWorkerResultComposite + implements RaptorWorkerResult { + + private static final Logger LOG = LoggerFactory.getLogger(RaptorWorkerResultComposite.class); + + private RaptorWorkerResult mainResult; + private RaptorWorkerResult alternativeResult; + + public RaptorWorkerResultComposite( + RaptorWorkerResult mainResult, + RaptorWorkerResult alternativeResult + ) { + this.mainResult = mainResult; + this.alternativeResult = alternativeResult; + } + + @Override + public Collection> extractPaths() { + Map> paths = new HashMap<>(); + addAll(paths, mainResult.extractPaths()); + addExtraRail(paths, alternativeResult.extractPaths()); + return paths.values(); + } + + @Override + public SingleCriteriaStopArrivals extractBestOverallArrivals() { + return mainResult.extractBestOverallArrivals(); + } + + @Override + public SingleCriteriaStopArrivals extractBestTransitArrivals() { + return mainResult.extractBestTransitArrivals(); + } + + @Override + public SingleCriteriaStopArrivals extractBestNumberOfTransfers() { + return mainResult.extractBestNumberOfTransfers(); + } + + @Override + public boolean isDestinationReached() { + return mainResult.isDestinationReached(); + } + + private void addExtraRail(Map> map, Collection> paths) { + paths.forEach(p -> { + if (hasRail(p)) { + var v = map.put(new PathKey(p), p); + LOG.debug("Ex.Rail {} : {}", (v == null ? "ADD " : "SKIP"), p); + } else { + LOG.debug("Ex. NOT Rail : {}", p); + } + }); + } + + private void addAll(Map> map, Collection> paths) { + paths.forEach(p -> { + var v = map.put(new PathKey(p), p); + LOG.debug("Normal {} : {}", (v == null ? "ADD " : "SKIP"), p); + }); + } + + private static boolean hasRail(RaptorPath path) { + return path + .legStream() + .filter(PathLeg::isTransitLeg) + .anyMatch(leg -> { + var trip = (TripScheduleWithOffset) leg.asTransitLeg().trip(); + var mode = trip.getOriginalTripPattern().getMode(); + return mode == TransitMode.RAIL; + }); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java index 829015c37d7..b749d9e4491 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java @@ -564,6 +564,14 @@ public static GraphQLFieldDefinition create( ) .build() ) + .argument( + GraphQLArgument + .newArgument() + .name("extraSearchCoachReluctance") + .description("FOR TESTING ONLY") + .type(Scalars.GraphQLFloat) + .build() + ) .dataFetcher(environment -> new TransmodelGraphQLPlanner().plan(environment)) .build(); } diff --git a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java index 20ca8054308..ff61f201624 100644 --- a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java @@ -89,6 +89,7 @@ private BoardAndAlightSlack mapTransit() { req.relaxTransitSearchGeneralizedCostAtDestination, value -> tr.withRaptor(it -> it.withRelaxGeneralizedCostAtDestination(value)) ); + setIfNotNull(req.extraSearchCoachReluctance, tr::setExtraSearchCoachReluctance); }); return new BoardAndAlightSlack( diff --git a/src/main/java/org/opentripplanner/api/common/RoutingResource.java b/src/main/java/org/opentripplanner/api/common/RoutingResource.java index e60bfe41b82..f214f0126c6 100644 --- a/src/main/java/org/opentripplanner/api/common/RoutingResource.java +++ b/src/main/java/org/opentripplanner/api/common/RoutingResource.java @@ -200,6 +200,9 @@ public abstract class RoutingResource { @QueryParam("carReluctance") protected Double carReluctance; + @QueryParam("extraSearchCoachReluctance") + protected Double extraSearchCoachReluctance; + /** * How much worse is waiting for a transit vehicle than being on a transit vehicle, as a * multiplier. The default value treats wait and on-vehicle time as the same. diff --git a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 9c730b94216..61d41c296f2 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -29,6 +29,7 @@ public enum OTPFeature { ), DebugClient(true, false, "Enable the debug web client located at the root of the web server."), FloatingBike(true, false, "Enable floating bike routing."), + HackSorlandsbanen(false, true, "Includ Sørlandsbanen"), GtfsGraphQlApi(true, false, "Enable GTFS GraphQL API."), GtfsGraphQlApiRentalStationFuzzyMatching( false, diff --git a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java index 1f0621b8d36..6b99e7ae238 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java @@ -29,6 +29,9 @@ public class RaptorRequest { private final DebugRequest debug; private final RaptorTimers performanceTimers; + // HACK SØRLANDSBANEN + public double extraSearchCoachReluctance = 0.0; + private RaptorRequest() { searchParams = SearchParams.defaults(); profile = RaptorProfile.MULTI_CRITERIA; @@ -49,6 +52,7 @@ private RaptorRequest() { this.multiCriteria = builder.multiCriteria(); this.performanceTimers = builder.performanceTimers(); this.debug = builder.debug().build(); + this.extraSearchCoachReluctance = builder.extraSearchCoachReluctance; verify(); } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java index ed2aab3de20..7bb8b538843 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java @@ -34,6 +34,9 @@ public class RaptorRequestBuilder { // Performance monitoring private RaptorTimers performanceTimers; + /** HACK SØRLANDSBANEN */ + public double extraSearchCoachReluctance; + // Algorithm private RaptorProfile profile; @@ -60,6 +63,9 @@ public RaptorRequestBuilder() { // Debug this.debug = new DebugRequestBuilder(defaults.debug()); + + // HACK SØRLANDSBANEN + this.extraSearchCoachReluctance = defaults.extraSearchCoachReluctance; } public SearchParamsBuilder searchParams() { diff --git a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java index fe8bb884912..86b10f7d96f 100644 --- a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java +++ b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java @@ -11,6 +11,8 @@ import java.util.concurrent.Future; import java.util.stream.Collectors; import javax.annotation.Nullable; +import org.opentripplanner.ext.sorlandsbanen.EnturHackSorlandsBanen; +import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.raptor.RaptorService; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; @@ -134,7 +136,13 @@ private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request) // Create worker if (request.profile().is(MULTI_CRITERIA)) { - raptorWorker = config.createMcWorker(transitData, request, getDestinationHeuristics()); + // HACK SØRLANDSBANEN + if (OTPFeature.HackSorlandsbanen.isOn() && EnturHackSorlandsBanen.match(request)) { + raptorWorker = + EnturHackSorlandsBanen.worker(config, transitData, request, getDestinationHeuristics()); + } else { + raptorWorker = config.createMcWorker(transitData, request, getDestinationHeuristics()); + } } else { raptorWorker = config.createStdWorker(transitData, request); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index b9f9685341c..e59c4d1673c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -128,7 +128,8 @@ private TransitRouterResult route() { accessEgresses.getAccesses(), accessEgresses.getEgresses(), accessEgresses.calculateMaxAccessTimePenalty(), - serverContext.meterRegistry() + serverContext.meterRegistry(), + transitLayer ); // Route transit diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java index b738d63054e..4f39b2631fe 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java @@ -1,5 +1,6 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit.cost; +import java.util.function.Function; import javax.annotation.Nullable; import org.opentripplanner.model.transfer.TransferConstraint; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; @@ -211,4 +212,19 @@ private int boardingCostConstrainedTransfer( // fallback to regular transfer return boardingCostRegularTransfer(firstBoarding, prevArrivalTime, boardStop, boardTime); } + + /*-- HACK SØRLANDSBANEN :: BEGIN --*/ + + public DefaultCostCalculator( + DefaultCostCalculator original, + Function modeReluctanceMapper + ) { + this.boardCostOnly = original.boardCostOnly; + this.boardAndTransferCost = original.boardAndTransferCost; + this.waitFactor = original.waitFactor; + this.transferCostOnly = original.transferCostOnly; + this.transitFactors = modeReluctanceMapper.apply(original.transitFactors); + this.stopTransferCost = original.stopTransferCost; + } + /*-- HACK SØRLANDSBANEN :: END --*/ } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java index fba2a7cfe38..8a14c27566d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java @@ -6,7 +6,7 @@ *

* The class and methods are {@code final} to help the JIT compiler optimize the use of this class. */ -interface FactorStrategy { +public interface FactorStrategy { /** * Return the factor for the given index. */ diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java index 152f199248c..4c66b5b452e 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java @@ -6,12 +6,12 @@ * This class keep a facto for each index and the minimum factor for fast retrieval during Raptor * search. */ -final class IndexBasedFactorStrategy implements FactorStrategy { +public final class IndexBasedFactorStrategy implements FactorStrategy { private final int[] factors; private final int minFactor; - private IndexBasedFactorStrategy(int[] factors) { + public IndexBasedFactorStrategy(int[] factors) { this.factors = factors; this.minFactor = findMinimumFactor(factors); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index 8e6121edffe..5b0de00bc6c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -8,6 +8,7 @@ import java.time.ZonedDateTime; import java.util.Collection; import java.util.List; +import org.opentripplanner.ext.sorlandsbanen.EnturHackSorlandsBanen; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; @@ -17,6 +18,7 @@ import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; import org.opentripplanner.raptor.rangeraptor.SystemErrDebugLogger; import org.opentripplanner.routing.algorithm.raptoradapter.router.performance.PerformanceTimersForRaptor; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.transit.model.site.StopLocation; @@ -31,6 +33,8 @@ public class RaptorRequestMapper { private final boolean isMultiThreadedEnbled; private final MeterRegistry meterRegistry; + private final TransitLayer transitLayer; + private RaptorRequestMapper( RouteRequest request, boolean isMultiThreaded, @@ -38,13 +42,15 @@ private RaptorRequestMapper( Collection egressPaths, Duration searchWindowAccessSlack, long transitSearchTimeZeroEpocSecond, - MeterRegistry meterRegistry + MeterRegistry meterRegistry, + TransitLayer transitLayer ) { this.request = request; this.isMultiThreadedEnbled = isMultiThreaded; this.accessPaths = accessPaths; this.egressPaths = egressPaths; this.searchWindowAccessSlack = searchWindowAccessSlack; + this.transitLayer = transitLayer; this.transitSearchTimeZeroEpocSecond = transitSearchTimeZeroEpocSecond; this.meterRegistry = meterRegistry; } @@ -56,7 +62,8 @@ public static RaptorRequest mapRequest( Collection accessPaths, Collection egressPaths, Duration searchWindowAccessSlack, - MeterRegistry meterRegistry + MeterRegistry meterRegistry, + TransitLayer transitLayer ) { return new RaptorRequestMapper( request, @@ -65,7 +72,8 @@ public static RaptorRequest mapRequest( egressPaths, searchWindowAccessSlack, transitSearchTimeZero.toEpochSecond(), - meterRegistry + meterRegistry, + transitLayer ) .doMap(); } @@ -171,7 +179,7 @@ private RaptorRequest doMap() { ) ); - return builder.build(); + return EnturHackSorlandsBanen.enableHack(builder.build(), request, transitLayer); } private List mapPassThroughPoints() { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java index a0b62414181..df5996009ec 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java @@ -4,6 +4,7 @@ import java.util.BitSet; import java.util.Iterator; import java.util.List; +import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.opentripplanner.framework.application.OTPFeature; @@ -27,6 +28,8 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.constrainedtransfer.ConstrainedBoardingSearch; import org.opentripplanner.routing.algorithm.raptoradapter.transit.constrainedtransfer.ConstrainedTransfersForPatterns; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.CostCalculatorFactory; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.DefaultCostCalculator; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.FactorStrategy; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.GeneralizedCostParametersMapper; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.transit.model.network.RoutingTripPattern; @@ -241,4 +244,34 @@ public RaptorConstrainedBoardingSearch transferConstraintsReverseS } return new ConstrainedBoardingSearch(false, toStopTransfers, fromStopTransfers); } + + /*-- HACK SØRLANDSBANEN :: BEGIN --*/ + + private RaptorRoutingRequestTransitData( + RaptorRoutingRequestTransitData original, + Function mapFactors + ) { + this.transitLayer = original.transitLayer; + this.transitSearchTimeZero = original.transitSearchTimeZero; + this.activeTripPatternsPerStop = original.activeTripPatternsPerStop; + this.patternIndex = original.patternIndex; + this.transferIndex = original.transferIndex; + this.transferService = original.transferService; + this.constrainedTransfers = original.constrainedTransfers; + this.validTransitDataStartTime = original.validTransitDataStartTime; + this.validTransitDataEndTime = original.validTransitDataEndTime; + this.generalizedCostCalculator = + new DefaultCostCalculator<>( + (DefaultCostCalculator) original.generalizedCostCalculator, + mapFactors + ); + this.slackProvider = original.slackProvider(); + } + + public RaptorTransitDataProvider enturHackSorlandsbanen( + Function mapFactors + ) { + return new RaptorRoutingRequestTransitData(this, mapFactors); + } + /*-- HACK SØRLANDSBANEN :: END --*/ } diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java index 7da4516d782..96945693cdd 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java @@ -31,6 +31,7 @@ public final class TransitPreferences implements Serializable { private final boolean includePlannedCancellations; private final boolean includeRealtimeCancellations; private final RaptorPreferences raptor; + private final double extraSearchCoachReluctance; private TransitPreferences() { this.boardSlack = this.alightSlack = DurationForEnum.of(TransitMode.class).build(); @@ -41,6 +42,7 @@ private TransitPreferences() { this.includePlannedCancellations = false; this.includeRealtimeCancellations = false; this.raptor = RaptorPreferences.DEFAULT; + this.extraSearchCoachReluctance = 0.0; } private TransitPreferences(Builder builder) { @@ -53,6 +55,7 @@ private TransitPreferences(Builder builder) { this.includePlannedCancellations = builder.includePlannedCancellations; this.includeRealtimeCancellations = builder.includeRealtimeCancellations; this.raptor = requireNonNull(builder.raptor); + this.extraSearchCoachReluctance = builder.extraSearchCoachReluctance; } public static Builder of() { @@ -153,6 +156,11 @@ public RaptorPreferences raptor() { return raptor; } + /** Zero means turned off. HACK SØRLANDSBANEN */ + public double extraSearchCoachReluctance() { + return extraSearchCoachReluctance; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -167,6 +175,7 @@ public boolean equals(Object o) { alightSlack.equals(that.alightSlack) && reluctanceForMode.equals(that.reluctanceForMode) && unpreferredCost.equals(that.unpreferredCost) && + extraSearchCoachReluctance == that.extraSearchCoachReluctance && raptor.equals(that.raptor) ); } @@ -182,6 +191,7 @@ public int hashCode() { ignoreRealtimeUpdates, includePlannedCancellations, includeRealtimeCancellations, + extraSearchCoachReluctance, raptor ); } @@ -229,6 +239,7 @@ public static class Builder { private boolean includePlannedCancellations; private boolean includeRealtimeCancellations; private RaptorPreferences raptor; + private double extraSearchCoachReluctance; public Builder(TransitPreferences original) { this.original = original; @@ -241,6 +252,7 @@ public Builder(TransitPreferences original) { this.includePlannedCancellations = original.includePlannedCancellations; this.includeRealtimeCancellations = original.includeRealtimeCancellations; this.raptor = original.raptor; + this.extraSearchCoachReluctance = original.extraSearchCoachReluctance; } public TransitPreferences original() { @@ -305,6 +317,11 @@ public Builder withRaptor(Consumer body) { return this; } + public Builder setExtraSearchCoachReluctance(double extraSearchCoachReluctance) { + this.extraSearchCoachReluctance = extraSearchCoachReluctance; + return this; + } + public Builder apply(Consumer body) { body.accept(this); return this; diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index c89670b7e0a..db212bd31d3 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -352,6 +352,13 @@ The board time is added to the time when going from the stop (offboard) to onboa ) .asDoubleOptional() .ifPresent(it::withRelaxGeneralizedCostAtDestination) + ) + .setExtraSearchCoachReluctance( + c + .of("extraSearchCoachReluctance") + .since(V2_1) + .summary("TODO") + .asDouble(dft.extraSearchCoachReluctance()) ); } diff --git a/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java b/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java index 39022da42ca..aeca5a36ebf 100644 --- a/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java +++ b/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java @@ -39,6 +39,9 @@ public class RaptorArchitectureTest { private static final Package RR_STANDARD = RANGE_RAPTOR.subPackage("standard"); private static final Package RR_STD_CONFIGURE = RR_STANDARD.subPackage("configure"); private static final Package RR_CONTEXT = RANGE_RAPTOR.subPackage("context"); + private static final Package EXT_SORLANDSBANAN_HACK = Package.of( + "org.opentripplanner.ext.sorlandsbanen" + ); /** * Packages used by standard-range-raptor and multi-criteria-range-raptor. @@ -200,7 +203,8 @@ void enforcePackageDependenciesInRaptorService() { RAPTOR_UTIL, CONFIGURE, RR_INTERNAL_API, - RR_TRANSIT + RR_TRANSIT, + EXT_SORLANDSBANAN_HACK ) .verify(); } From e1dfd8b507ae6fa439a3f12dc18d3655224df06e Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 15 Nov 2023 17:08:12 +0100 Subject: [PATCH 22/26] Version 2.5.0-entur-8 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5b7108c8ec3..d86cb909e30 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.5.0-SNAPSHOT + 2.5.0-entur-8 jar From 3d16ba570c8496943d894d2924fc6a29f1591704 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Thu, 16 Nov 2023 10:37:59 +0100 Subject: [PATCH 23/26] Update CircleCI image to Java 21 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d01a0c323e9..61df85c0883 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,7 +17,7 @@ aliases: jobs: build: docker: - - image: cimg/openjdk:17.0 + - image: cimg/openjdk:21.0 environment: DEBIAN_FRONTEND: "noninteractive" MAVEN_OPTS: -Xmx6G From 192afaec6a709b8fe6fc5668ca687aad81bd662f Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 19 May 2023 18:35:18 +0200 Subject: [PATCH 24/26] CircleCI config, release scripts, and pom.xml updates. --- .circleci/config.yml | 52 +++++ .circleci/prepare_release | 235 +++++++++++++++++++++++ .circleci/release | 84 ++++++++ .circleci/update_deployment_config | 46 +++++ .gitignore | 1 + pom.xml | 12 +- src/main/java/EnturUpdatePomVersion.java | 135 +++++++++++++ 7 files changed, 559 insertions(+), 6 deletions(-) create mode 100644 .circleci/config.yml create mode 100755 .circleci/prepare_release create mode 100755 .circleci/release create mode 100755 .circleci/update_deployment_config create mode 100644 src/main/java/EnturUpdatePomVersion.java diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000000..61df85c0883 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +aliases: + - &jfrog-login + name: Rename jfrog environment variable for maven setting.xml + command: | + echo "export JFROG_USER=$ARTIFACTORY_USER" >> $BASH_ENV + echo "export JFROG_PASS=$ARTIFACTORY_PASSWORD" >> $BASH_ENV + - &post_build + name: Update deployment with OTP version + command: | + sudo apt-get update + sudo apt-get install libxml2-utils + chmod u+x .circleci/update_deployment_config + .circleci/update_deployment_config + +jobs: + build: + docker: + - image: cimg/openjdk:21.0 + environment: + DEBIAN_FRONTEND: "noninteractive" + MAVEN_OPTS: -Xmx6G + resource_class: large + steps: + - checkout + - restore_cache: + keys: + - dep-cache-{{ checksum "pom.xml" }} + # fallback to the most recent cache if there is no exact match for this pom.xml + - dep-cache- + - run: wget https://raw.githubusercontent.com/entur/circleci-toolbox-image-java11/master/tools/m2/settings.xml -O .circleci/settings.xml + - run: *jfrog-login + - run: mvn org.apache.maven.plugins:maven-dependency-plugin:3.1.0:go-offline -s .circleci/settings.xml + - save_cache: + paths: + - ~/.m2 + key: dep-cache-{{ checksum "pom.xml" }} + - run: mvn install -PprettierSkip org.apache.maven.plugins:maven-deploy-plugin:2.8.2:deploy -s .circleci/settings.xml -DaltDeploymentRepository=snapshots::default::https://entur2.jfrog.io/entur2/libs-release-local + - run: *post_build + +workflows: + version: 2 + release: + jobs: + - build: + name: build-release + context: global + filters: + branches: + only: + - otp2_entur_develop \ No newline at end of file diff --git a/.circleci/prepare_release b/.circleci/prepare_release new file mode 100755 index 00000000000..5b2ad0e23c4 --- /dev/null +++ b/.circleci/prepare_release @@ -0,0 +1,235 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ENTUR_DEVELOP=otp2_entur_develop +REMOTE_REPO="`git remote -v | grep "entur/OpenTripPlanner" | grep "push" | awk '{print $1;}'`" +STATUS_FILE=".prepare_release.tmp" +STATUS="" +DRY_RUN="" +OTP_BASE="" + +function main() { + setup "$@" + resumePreviousExecution + resetEnturDevelop + rebaseAndMergeExtBranch otp2_ext_config + rebaseAndMergeExtBranch otp2_ext_hack_sorlandsbanen + logSuccess +} + +function setup() { + if [[ $# -eq 2 && "$1" == "--dryRun" ]] ; then + DRY_RUN="--dryRun" + OTP_BASE="$2" + elif [[ $# -eq 1 ]] ; then + OTP_BASE="$1" + else + printHelp + exit 1 + fi + + echo "" + echo "Options: ${DRY_RUN}" + echo "Git base branch/commit: ${OTP_BASE}" + echo "Entur develop branch: ${ENTUR_DEVELOP}" + echo "Entur remote repo(pull/push): ${REMOTE_REPO}" + echo "" + + if git diff-index --quiet HEAD --; then + echo "" + echo "OK - No local changes, prepare to checkout '${ENTUR_DEVELOP}'" + echo "" + else + echo "" + echo "You have local modification, the script will abort. Nothing done!" + exit 2 + fi + + git fetch ${REMOTE_REPO} +} + +# This script create a status file '.prepare_release.tmp'. This file is used to resume the +# script in the same spot as where is left when the error occurred. This allow us to fix the +# problem (merge conflict or compile error) and re-run the script to complete the proses. +function resumePreviousExecution() { + readStatus + + if [[ -n "${STATUS}" ]] ; then + echo "" + echo "Resume: ${STATUS}?" + echo "" + echo " If all problems are resolved you may continue." + echo " Exit to clear status and start over." + echo "" + + ANSWER="" + while [[ ! "$ANSWER" =~ [yx] ]]; do + echo "Do you want to resume: [y:Yes, x:Exit]" + read ANSWER + done + + if [[ "${ANSWER}" == "x" ]] ; then + exit 0 + fi + fi +} + +function resetEnturDevelop() { + echo "" + echo "## ------------------------------------------------------------------------------------- ##" + echo "## RESET '${ENTUR_DEVELOP}' TO '${OTP_BASE}'" + echo "## ------------------------------------------------------------------------------------- ##" + echo "" + echo "Would you like to reset the '${ENTUR_DEVELOP}' to '${OTP_BASE}'? " + echo "" + + whatDoYouWant + + if [[ "${ANSWER}" == "y" ]] ; then + echo "" + echo "Checkout '${ENTUR_DEVELOP}'" + git checkout ${ENTUR_DEVELOP} + + echo "" + echo "Reset '${ENTUR_DEVELOP}' branch to '${OTP_BASE}' (hard)" + git reset --hard "${OTP_BASE}" + echo "" + fi +} + +function rebaseAndMergeExtBranch() { + EXT_BRANCH="$1" + EXT_STATUS_REBASE="Rebase '${EXT_BRANCH}'" + EXT_STATUS_COMPILE="Compile '${EXT_BRANCH}'" + + echo "" + echo "## ------------------------------------------------------------------------------------- ##" + echo "## REBASE AND MERGE '${EXT_BRANCH}' INTO '${ENTUR_DEVELOP}'" + echo "## ------------------------------------------------------------------------------------- ##" + echo "" + echo "You are about to rebase and merge '${EXT_BRANCH}' into '${ENTUR_DEVELOP}'. Any local" + echo "modification in the '${EXT_BRANCH}' will be lost." + echo "" + + whatDoYouWant + + if [[ "${ANSWER}" == "y" ]] ; then + echo "" + echo "Checkout '${EXT_BRANCH}'" + git checkout "${EXT_BRANCH}" + + echo "" + echo "Reset to '${REMOTE_REPO}/${EXT_BRANCH}'" + git reset --hard "${REMOTE_REPO}/${EXT_BRANCH}" + + echo "" + echo "Top 2 commits in '${EXT_BRANCH}'" + echo "-------------------------------------------------------------------------------------------" + git --no-pager log -2 + echo "-------------------------------------------------------------------------------------------" + echo "" + echo "You are about to rebase the TOP COMMIT ONLY(see above). Check that the " + echo "'${EXT_BRANCH}' only have ONE commit that you want to keep." + echo "" + + whatDoYouWant + + if [[ "${ANSWER}" == "y" ]] ; then + echo "" + echo "Rebase '${EXT_BRANCH}' onto '${ENTUR_DEVELOP}'" + setStatus "${EXT_STATUS_REBASE}" + git rebase --onto ${ENTUR_DEVELOP} HEAD~1 + fi + fi + + if [[ "${STATUS}" == "${EXT_STATUS_REBASE}" || "${STATUS}" == "${EXT_STATUS_COMPILE}" ]] ; then + # Reset status in case the test-compile fails. We need to do this because the status file + # is deleted after reading the status in the setup() function. + setStatus "${EXT_STATUS_COMPILE}" + + mvn clean test-compile + clearStatus + + echo "" + echo "Push '${EXT_BRANCH}'" + if [[ -z "${DRY_RUN}" ]] ; then + git push -f + else + echo "Skip: git push -f (--dryRun)" + fi + + echo "" + echo "Checkout '${ENTUR_DEVELOP}' and merge in '${EXT_BRANCH}'" + git checkout "${ENTUR_DEVELOP}" + git merge "${EXT_BRANCH}" + fi +} + +function logSuccess() { + echo "" + echo "## ------------------------------------------------------------------------------------- ##" + echo "## PREPARE RELEASE DONE -- SUCCESS" + echo "## ------------------------------------------------------------------------------------- ##" + echo " - '${REMOTE_REPO}/${ENTUR_DEVELOP}' reset to '${OTP_BASE}'" + echo " - 'otp2_ext_config' merged" + echo " - 'otp2_ext_hack_sorlandsbanen' merged" + echo "" + echo "" +} + +function whatDoYouWant() { + echo "" + ANSWER="" + + if [[ -n "${STATUS}" ]] ; then + # Skip until process is resumed + ANSWER="s" + else + while [[ ! "$ANSWER" =~ [ysx] ]]; do + echo "Do you want to continue: [y:Yes, s:Skip, x:Exit]" + read ANSWER + done + + if [[ "${ANSWER}" == "x" ]] ; then + exit 0 + fi + fi +} + +function setStatus() { + STATUS="$1" + echo "$STATUS" > "${STATUS_FILE}" +} + +function readStatus() { + if [[ -f "${STATUS_FILE}" ]] ; then + STATUS=`cat $STATUS_FILE` + rm "$STATUS_FILE" + else + STATUS="" + fi +} + +function clearStatus() { + STATUS="" + rm "${STATUS_FILE}" +} + +function printHelp() { + echo "" + echo "This script take ONE argument , the base **branch** or **commit** to use for the" + echo "release. The '${ENTUR_DEVELOP}' branch is reset to this commit and then the extension" + echo "branches is rebased onto that. The 'release' script is used to complete the release." + echo "It tag and push all changes to remote git repo." + echo "" + echo "Options:" + echo " --dryRun : Run script locally, nothing is pushed to remote server." + echo "" + echo "Usage:" + echo " $ .circleci/prepare_release otp/dev-2.x" + echo " $ .circleci/prepare_release --dryRun otp/dev-2.x" + echo "" +} + +main "$@" diff --git a/.circleci/release b/.circleci/release new file mode 100755 index 00000000000..9cac0d97958 --- /dev/null +++ b/.circleci/release @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +set -euo pipefail + +GIT_REMOTE_REPO="`git remote -v | grep "entur/OpenTripPlanner" | grep "push" | awk '{print $1;}'`" +MASTER_BRANCH=otp2_entur_develop +TAGS_FILE=target/git-entur-tags.txt +DRY_RUN="" + +function main() { + setup "$@" + listAllTags + mergeInOldReleaseWithNoChanges + setPomVersion + tagRelease + pushToRemote +} + +function setup() { + echo "" + echo "git fetch ${GIT_REMOTE_REPO}" + git fetch ${GIT_REMOTE_REPO} + + echo "Verify current branch is ${MASTER_BRANCH} " + git status | grep -q "On branch ${MASTER_BRANCH}" + + if [[ "${1+x}" == "--dryRun" ]] ; then + DRY_RUN="--dryRun" + fi +} + +function listAllTags() { + ## List all Entur tags to allow the UpdatePomVersion java program find the next version number + echo "" + echo "Dump all entur tags to ${TAGS_FILE}" + git tag -l | grep entur > ${TAGS_FILE} +} + +function setPomVersion() { + echo "" + echo "Update pom.xml with new version" + javac -d target/classes src/main/java/EnturUpdatePomVersion.java + + VERSION="`java -cp target/classes EnturUpdatePomVersion ${TAGS_FILE}`" + echo "" + echo "New version set: ${VERSION}" + echo "" + + ## Verify everything builds and tests run + echo "" + mvn clean test + + ## Add [ci skip] here before moving this to the CI server + echo "" + echo "Add and commit pom.xml" + git commit -m "Version ${VERSION}" pom.xml +} + +function mergeInOldReleaseWithNoChanges() { + echo "" + echo "Merge the old version of '${GIT_REMOTE_REPO}' into the new version. This only keep " + echo "a reference to the old version, the resulting tree of the merge is that of the new" + echo "branch head, effectively ignoring all changes from the old release." + git merge -s ours "${GIT_REMOTE_REPO}/${MASTER_BRANCH}" -m "Merge old release into '${MASTER_BRANCH}' - NO CHANGES COPIED OVER" +} + + +function tagRelease() { + echo "" + echo "Tag version ${VERSION}" + git tag -a v${VERSION} -m "Version ${VERSION}" +} + +function pushToRemote() { + echo "" + echo "Push pom.xml and new tag" + if [[ -z "${DRY_RUN}" ]] ; then + git push -f ${GIT_REMOTE_REPO} "v${VERSION}" ${MASTER_BRANCH} + else + echo "Skip: push -f ${GIT_REMOTE_REPO} "v${VERSION}" ${MASTER_BRANCH} (--dryRun)" + fi +} + +main "$@" diff --git a/.circleci/update_deployment_config b/.circleci/update_deployment_config new file mode 100755 index 00000000000..64550f19797 --- /dev/null +++ b/.circleci/update_deployment_config @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## +## This script run AFTER OTP is build on the CI Server. It checkout the +## deployment config and update the version number, commit and push. This +## trigger the deployment config build witch create and deploy a new OTP2 +## docker image. +## +## Note! There is no need to run this script locally, unless you want to debug it. +## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## + + +set -euo pipefail + +OTP_DEPLOYMENT_CONFIG=target/otp-deployment-config +DOCKER_FILE=Dockerfile + +VERSION=$(xmllint --xpath "//*[local-name()='project']/*[local-name()='version']/text()" pom.xml) + +echo "Checkout otp-deplyment-config in ${OTP_DEPLOYMENT_CONFIG}" +git clone -n https://github.com/entur/otp-deployment-config.git --depth 1 ${OTP_DEPLOYMENT_CONFIG} + +pushd ${OTP_DEPLOYMENT_CONFIG} + +echo "Checkout latest version of master Dockerfile" +git checkout master ${DOCKER_FILE} + +echo "Update OTP version number in ${DOCKER_FILE}" +if [[ "$OSTYPE" == "darwin"* ]]; then + sed -E -i '' "s/OTP_VERSION=[\.0-9]+-entur-[0-9]+/OTP_VERSION=${VERSION}/" ${DOCKER_FILE} +else + sed -E -i "s/OTP_VERSION=[\.0-9]+-entur-[0-9]+/OTP_VERSION=${VERSION}/" ${DOCKER_FILE} +fi + +if [[ "$CIRCLE_USERNAME" != "" ]]; then + git config user.email "circleci@entur.no" + git config user.name "circleci ($CIRCLE_USERNAME)" +fi + +echo "Add and commit Dockerfile" +git commit -m "New OTP2 Version ${VERSION}" ${DOCKER_FILE} + +echo "Push otp-deployment-config to GitHub" +git push + +popd \ No newline at end of file diff --git a/.gitignore b/.gitignore index a90f06cdcb0..3bc96654225 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .project .pydevproject .settings +.prepare_release.tmp .sonar .nvmrc *~ diff --git a/pom.xml b/pom.xml index 622577868e2..728e4532aa5 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 128 + EN-0056 30.0 2.48.1 @@ -84,11 +84,11 @@ - - github - OpenTripPlanner Maven Repository on Github Packages - https://maven.pkg.github.com/${GITHUB_REPOSITORY}/ - + + snapshots + entur2-snapshots + https://entur2.jfrog.io/entur2/libs-snapshot-local + diff --git a/src/main/java/EnturUpdatePomVersion.java b/src/main/java/EnturUpdatePomVersion.java new file mode 100644 index 00000000000..97bd72b3c6e --- /dev/null +++ b/src/main/java/EnturUpdatePomVersion.java @@ -0,0 +1,135 @@ +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.regex.Pattern; + +public class EnturUpdatePomVersion { + + private static final String VERSION_SEP = "-"; + private static final String ENTUR_PREFIX = "entur" + VERSION_SEP; + private static final Path POM_FILE = Path.of("pom.xml"); + + private final List tags = new ArrayList<>(); + private final List pomFile = new ArrayList<>(); + private String mainVersion; + private int versionNumber = 0; + + public static void main(String[] args) { + try { + new EnturUpdatePomVersion().withArgs(args).run(); + } catch (Exception e) { + System.err.println(e.getMessage()); + e.printStackTrace(System.err); + System.exit(10); + } + } + + private EnturUpdatePomVersion withArgs(String[] args) throws IOException { + if (args.length != 1 || args[0].matches("(?i)-h|--help")) { + printHelp(); + System.exit(1); + } + String arg = args[0]; + + if (arg.matches("\\d+")) { + versionNumber = resolveVersionFromNumericString(arg); + } else { + tags.addAll(readTagsFromFile(arg)); + } + return this; + } + + private void run() throws IOException { + readAndReplaceVersion(); + replacePomFile(); + } + + public void readAndReplaceVersion() throws IOException { + var pattern = Pattern.compile( + "(\\s*)(\\d+.\\d+.\\d+)" + + VERSION_SEP + + "(" + + ENTUR_PREFIX + + "\\d+|SNAPSHOT)(\\s*)" + ); + boolean found = false; + int i = 0; + + for (String line : Files.readAllLines(POM_FILE, UTF_8)) { + // Look for the version in the 25 first lines + if (!found) { + var m = pattern.matcher(line); + if (m.matches()) { + mainVersion = m.group(2); + String newVersion = mainVersion + VERSION_SEP + ENTUR_PREFIX + resolveVersionNumber(); + line = m.group(1) + newVersion + m.group(4); + System.out.println(newVersion); + found = true; + } + if (++i == 25) { + throw new IllegalStateException( + "Version not found in first 25 lines of the pom.xml file." + ); + } + } + pomFile.add(line); + } + if (!found) { + throw new IllegalStateException( + "Version not found in 'pom.xml'. Nothing matching pattern: " + pattern + ); + } + } + + public void replacePomFile() throws IOException { + Files.delete(POM_FILE); + Files.write(POM_FILE, pomFile, UTF_8); + } + + private static void printHelp() { + System.err.println( + "Use this small program to replace the OTP version '2.1.0-SNAPSHOT' \n" + + "with a new version number with a Entur qualifier like '2.1.0-entur-1'.\n" + + "\n" + + "Usage:\n" + + " $ java -cp .circleci UpdatePomVersion \n" + ); + } + + private int resolveVersionNumber() { + var pattern = Pattern.compile("v" + mainVersion + VERSION_SEP + ENTUR_PREFIX + "(\\d+)"); + int maxTagVersion = tags + .stream() + .mapToInt(tag -> { + var m = pattern.matcher(tag); + return m.matches() ? Integer.parseInt(m.group(1)) : -999; + }) + .max() + .orElse(-999); + + return 1 + Math.max(maxTagVersion, versionNumber); + } + + public static int resolveVersionFromNumericString(String arg) { + try { + return Integer.parseInt(arg); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Unable to parse input, decimal number expected: '" + arg + "'" + ); + } + } + + private static Collection readTagsFromFile(String arg) throws IOException { + var tags = Files.readAllLines(Path.of(arg)); + if (tags.isEmpty()) { + throw new IllegalStateException("Unable to load git tags from file: " + arg); + } + return tags; + } +} From 17c323d3a5b15aab5064edb574d2c3c4196ffeca Mon Sep 17 00:00:00 2001 From: Hannes Junnila Date: Fri, 9 Dec 2022 09:38:06 +0200 Subject: [PATCH 25/26] =?UTF-8?q?hack:=20Add=20train=20option=20for=20S?= =?UTF-8?q?=C3=B8rlandsbanen.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/Configuration.md | 1 + docs/RouteRequest.md | 1 + src/ext/graphql/transmodelapi/schema.graphql | 2 + .../ConcurrentCompositeWorker.java | 53 +++++++ .../sorlandsbanen/EnturHackSorlandsBanen.java | 141 ++++++++++++++++++ .../ext/sorlandsbanen/PathKey.java | 51 +++++++ .../RaptorWorkerResultComposite.java | 88 +++++++++++ .../transmodelapi/model/plan/TripQuery.java | 8 + .../common/RequestToPreferencesMapper.java | 1 + .../api/common/RoutingResource.java | 3 + .../framework/application/OTPFeature.java | 1 + .../raptor/api/request/RaptorRequest.java | 4 + .../api/request/RaptorRequestBuilder.java | 6 + .../service/RangeRaptorDynamicSearch.java | 10 +- .../raptoradapter/router/TransitRouter.java | 3 +- .../transit/cost/DefaultCostCalculator.java | 16 ++ .../transit/cost/FactorStrategy.java | 2 +- .../cost/IndexBasedFactorStrategy.java | 4 +- .../transit/mappers/RaptorRequestMapper.java | 16 +- .../RaptorRoutingRequestTransitData.java | 33 ++++ .../preference/TransitPreferences.java | 17 +++ .../routerequest/RouteRequestConfig.java | 7 + .../raptor/RaptorArchitectureTest.java | 6 +- 23 files changed, 464 insertions(+), 10 deletions(-) create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java create mode 100644 src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java diff --git a/docs/Configuration.md b/docs/Configuration.md index bfd168d5955..ebe2ec24327 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -228,6 +228,7 @@ Here is a list of all features which can be toggled on/off and their default val | `ConsiderPatternsForDirectTransfers` | Enable limiting transfers so that there is only a single transfer to each pattern. | ✓️ | | | `DebugClient` | Enable the debug web client located at the root of the web server. | ✓️ | | | `FloatingBike` | Enable floating bike routing. | ✓️ | | +| `HackSorlandsbanen` | Includ Sørlandsbanen | | ✓️ | | `GtfsGraphQlApi` | Enable GTFS GraphQL API. | ✓️ | | | `GtfsGraphQlApiRentalStationFuzzyMatching` | Does vehicleRentalStation query also allow ids that are not feed scoped. | | | | `MinimumTransferTimeIsDefinitive` | If the minimum transfer time is a lower bound (default) or the definitive time for the transfer. Set this to `true` if you want to set a transfer time lower than what OTP derives from OSM data. | | | diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index f814753df4a..b6284d22e4d 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -46,6 +46,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe | elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 | | elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | | escalatorReluctance | `double` | A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time | *Optional* | `1.5` | 2.4 | +| extraSearchCoachReluctance | `double` | TODO | *Optional* | `0.0` | 2.1 | | geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | | ignoreRealtimeUpdates | `boolean` | When true, realtime updates are ignored during this search. | *Optional* | `false` | 2.0 | | [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql index a207d8d0d6f..e3d8f60cf7f 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/ext/graphql/transmodelapi/schema.graphql @@ -793,6 +793,8 @@ type QueryType { dateTime: DateTime, "Debug the itinerary-filter-chain. OTP will attach a system notice to itineraries instead of removing them. This is very convenient when tuning the filters." debugItineraryFilter: Boolean = false @deprecated(reason : "Use `itineraryFilter.debug` instead."), + "FOR TESTING ONLY" + extraSearchCoachReluctance: Float, "A list of filters for which trips should be included. A trip will be included if it matches with at least one filter. An empty list of filters means that all trips should be included. If a search include this parameter, \"whiteListed\", \"banned\" & \"modes.transportModes\" filters will be ignored." filters: [TripFilterInput!], "The start location" diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java new file mode 100644 index 00000000000..e80e106ce1e --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java @@ -0,0 +1,53 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.opentripplanner.framework.application.OTPFeature; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; +import org.opentripplanner.raptor.api.response.StopArrivals; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.multicriteria.McRaptorWorkerResult; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TripScheduleWithOffset; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class ConcurrentCompositeWorker implements RaptorWorker { + + private static final Logger LOG = LoggerFactory.getLogger(ConcurrentCompositeWorker.class); + + private final RaptorWorker mainWorker; + private final RaptorWorker alternativeWorker; + + ConcurrentCompositeWorker(RaptorWorker mainWorker, RaptorWorker alternativeWorker) { + this.mainWorker = mainWorker; + this.alternativeWorker = alternativeWorker; + } + + @Override + public RaptorWorkerResult route() { + if (OTPFeature.ParallelRouting.isOn()) { + var mainResultFuture = CompletableFuture.supplyAsync(mainWorker::route); + var alternativeResultFuture = CompletableFuture.supplyAsync(alternativeWorker::route); + + try { + return new RaptorWorkerResultComposite<>( + mainResultFuture.get(), + alternativeResultFuture.get() + ); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } else { + var mainResult = mainWorker.route(); + var alternativeResult = alternativeWorker.route(); + return new RaptorWorkerResultComposite<>(mainResult, alternativeResult); + } + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java new file mode 100644 index 00000000000..2b52e850f42 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java @@ -0,0 +1,141 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.function.Function; +import org.opentripplanner.framework.geometry.SphericalDistanceLibrary; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.model.GenericLocation; +import org.opentripplanner.raptor.api.model.RaptorAccessEgress; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.request.RaptorRequest; +import org.opentripplanner.raptor.api.request.SearchParams; +import org.opentripplanner.raptor.configure.RaptorConfig; +import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; +import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.FactorStrategy; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.IndexBasedFactorStrategy; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.RaptorRoutingRequestTransitData; +import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.site.StopLocation; + +public class EnturHackSorlandsBanen { + + private static final double SOUTH_BOARDER_LIMIT = 59.1; + private static final int MIN_DISTANCE_LIMIT = 120_000; + + public static boolean match(RaptorRequest mcRequest) { + return mcRequest.extraSearchCoachReluctance > 0.1; + } + + public static RaptorWorker worker( + RaptorConfig config, + RaptorTransitDataProvider transitData, + RaptorRequest mcRequest, + Heuristics destinationHeuristics + ) { + //noinspection unchecked + RaptorTransitDataProvider altTransitData = (RaptorTransitDataProvider) ( + (RaptorRoutingRequestTransitData) transitData + ).enturHackSorlandsbanen(mapFactors(mcRequest.extraSearchCoachReluctance)); + + return new ConcurrentCompositeWorker<>( + config.createMcWorker(transitData, mcRequest, destinationHeuristics), + config.createMcWorker(altTransitData, mcRequest, destinationHeuristics) + ); + } + + public static RaptorRequest enableHack( + RaptorRequest raptorRequest, + RouteRequest request, + TransitLayer transitLayer + ) { + if (request.preferences().transit().extraSearchCoachReluctance() < 0.1) { + return raptorRequest; + } + + SearchParams params = raptorRequest.searchParams(); + + WgsCoordinate from = findStopCoordinate(request.from(), params.accessPaths(), transitLayer); + WgsCoordinate to = findStopCoordinate(request.to(), params.egressPaths(), transitLayer); + + if (from.latitude() > SOUTH_BOARDER_LIMIT && to.latitude() > SOUTH_BOARDER_LIMIT) { + return raptorRequest; + } + + double distanceMeters = SphericalDistanceLibrary.distance( + from.latitude(), + from.longitude(), + to.latitude(), + to.longitude() + ); + + if (distanceMeters < MIN_DISTANCE_LIMIT) { + return raptorRequest; + } + + raptorRequest.extraSearchCoachReluctance = + request.preferences().transit().extraSearchCoachReluctance(); + return raptorRequest; + } + + /* private methods */ + + private static Function mapFactors( + final double extraSearchCoachReluctance + ) { + return (FactorStrategy originalFactors) -> { + int[] modeReluctance = new int[TransitMode.values().length]; + for (TransitMode mode : TransitMode.values()) { + int index = mode.ordinal(); + int originalFactor = originalFactors.factor(index); + modeReluctance[index] = + mode == TransitMode.COACH + ? (int) (extraSearchCoachReluctance * originalFactor + 0.5) + : originalFactor; + } + return new IndexBasedFactorStrategy(modeReluctance); + }; + } + + /** + * Find a coordinate matching the given location, in order: + * - First return the coordinate of the location if it exists. + * - Then loop through the access/egress stops and try to find the + * stop or station given by the location id, return the stop/station coordinate. + * - Return the fist stop in the access/egress list coordinate. + */ + @SuppressWarnings("ConstantConditions") + private static WgsCoordinate findStopCoordinate( + GenericLocation location, + Collection accessEgress, + TransitLayer transitLayer + ) { + if (location.lat != null) { + return new WgsCoordinate(location.lat, location.lng); + } + + StopLocation firstStop = null; + for (RaptorAccessEgress it : accessEgress) { + StopLocation stop = transitLayer.getStopByIndex(it.stop()); + if (stop.getId().equals(location.stopId)) { + return stop.getCoordinate(); + } + if (idIsParentStation(stop, location.stopId)) { + return stop.getParentStation().getCoordinate(); + } + if (firstStop == null) { + firstStop = stop; + } + } + return firstStop.getCoordinate(); + } + + private static boolean idIsParentStation(StopLocation stop, FeedScopedId pId) { + return stop.getParentStation() != null && stop.getParentStation().getId().equals(pId); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java new file mode 100644 index 00000000000..94267b3e7c2 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java @@ -0,0 +1,51 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; + +final class PathKey { + + private final int hash; + + PathKey(RaptorPath path) { + this.hash = hash(path); + } + + private static int hash(RaptorPath path) { + if (path == null) { + return 0; + } + int result = 1; + + PathLeg leg = path.accessLeg(); + + while (!leg.isEgressLeg()) { + result = 31 * result + leg.toStop(); + result = 31 * result + leg.toTime(); + + if (leg.isTransitLeg()) { + result = 31 * result + leg.asTransitLeg().trip().pattern().debugInfo().hashCode(); + } + leg = leg.nextLeg(); + } + result = 31 * result + leg.toTime(); + + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o.getClass() != PathKey.class) { + return false; + } + return hash == ((PathKey) o).hash; + } + + @Override + public int hashCode() { + return hash; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java new file mode 100644 index 00000000000..094e652fc4c --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java @@ -0,0 +1,88 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.internalapi.SingleCriteriaStopArrivals; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TripScheduleWithOffset; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RaptorWorkerResultComposite + implements RaptorWorkerResult { + + private static final Logger LOG = LoggerFactory.getLogger(RaptorWorkerResultComposite.class); + + private RaptorWorkerResult mainResult; + private RaptorWorkerResult alternativeResult; + + public RaptorWorkerResultComposite( + RaptorWorkerResult mainResult, + RaptorWorkerResult alternativeResult + ) { + this.mainResult = mainResult; + this.alternativeResult = alternativeResult; + } + + @Override + public Collection> extractPaths() { + Map> paths = new HashMap<>(); + addAll(paths, mainResult.extractPaths()); + addExtraRail(paths, alternativeResult.extractPaths()); + return paths.values(); + } + + @Override + public SingleCriteriaStopArrivals extractBestOverallArrivals() { + return mainResult.extractBestOverallArrivals(); + } + + @Override + public SingleCriteriaStopArrivals extractBestTransitArrivals() { + return mainResult.extractBestTransitArrivals(); + } + + @Override + public SingleCriteriaStopArrivals extractBestNumberOfTransfers() { + return mainResult.extractBestNumberOfTransfers(); + } + + @Override + public boolean isDestinationReached() { + return mainResult.isDestinationReached(); + } + + private void addExtraRail(Map> map, Collection> paths) { + paths.forEach(p -> { + if (hasRail(p)) { + var v = map.put(new PathKey(p), p); + LOG.debug("Ex.Rail {} : {}", (v == null ? "ADD " : "SKIP"), p); + } else { + LOG.debug("Ex. NOT Rail : {}", p); + } + }); + } + + private void addAll(Map> map, Collection> paths) { + paths.forEach(p -> { + var v = map.put(new PathKey(p), p); + LOG.debug("Normal {} : {}", (v == null ? "ADD " : "SKIP"), p); + }); + } + + private static boolean hasRail(RaptorPath path) { + return path + .legStream() + .filter(PathLeg::isTransitLeg) + .anyMatch(leg -> { + var trip = (TripScheduleWithOffset) leg.asTransitLeg().trip(); + var mode = trip.getOriginalTripPattern().getMode(); + return mode == TransitMode.RAIL; + }); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java index 829015c37d7..b749d9e4491 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java @@ -564,6 +564,14 @@ public static GraphQLFieldDefinition create( ) .build() ) + .argument( + GraphQLArgument + .newArgument() + .name("extraSearchCoachReluctance") + .description("FOR TESTING ONLY") + .type(Scalars.GraphQLFloat) + .build() + ) .dataFetcher(environment -> new TransmodelGraphQLPlanner().plan(environment)) .build(); } diff --git a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java index 20ca8054308..ff61f201624 100644 --- a/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/api/common/RequestToPreferencesMapper.java @@ -89,6 +89,7 @@ private BoardAndAlightSlack mapTransit() { req.relaxTransitSearchGeneralizedCostAtDestination, value -> tr.withRaptor(it -> it.withRelaxGeneralizedCostAtDestination(value)) ); + setIfNotNull(req.extraSearchCoachReluctance, tr::setExtraSearchCoachReluctance); }); return new BoardAndAlightSlack( diff --git a/src/main/java/org/opentripplanner/api/common/RoutingResource.java b/src/main/java/org/opentripplanner/api/common/RoutingResource.java index e60bfe41b82..f214f0126c6 100644 --- a/src/main/java/org/opentripplanner/api/common/RoutingResource.java +++ b/src/main/java/org/opentripplanner/api/common/RoutingResource.java @@ -200,6 +200,9 @@ public abstract class RoutingResource { @QueryParam("carReluctance") protected Double carReluctance; + @QueryParam("extraSearchCoachReluctance") + protected Double extraSearchCoachReluctance; + /** * How much worse is waiting for a transit vehicle than being on a transit vehicle, as a * multiplier. The default value treats wait and on-vehicle time as the same. diff --git a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 9c730b94216..61d41c296f2 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -29,6 +29,7 @@ public enum OTPFeature { ), DebugClient(true, false, "Enable the debug web client located at the root of the web server."), FloatingBike(true, false, "Enable floating bike routing."), + HackSorlandsbanen(false, true, "Includ Sørlandsbanen"), GtfsGraphQlApi(true, false, "Enable GTFS GraphQL API."), GtfsGraphQlApiRentalStationFuzzyMatching( false, diff --git a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java index 1f0621b8d36..6b99e7ae238 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java @@ -29,6 +29,9 @@ public class RaptorRequest { private final DebugRequest debug; private final RaptorTimers performanceTimers; + // HACK SØRLANDSBANEN + public double extraSearchCoachReluctance = 0.0; + private RaptorRequest() { searchParams = SearchParams.defaults(); profile = RaptorProfile.MULTI_CRITERIA; @@ -49,6 +52,7 @@ private RaptorRequest() { this.multiCriteria = builder.multiCriteria(); this.performanceTimers = builder.performanceTimers(); this.debug = builder.debug().build(); + this.extraSearchCoachReluctance = builder.extraSearchCoachReluctance; verify(); } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java index ed2aab3de20..7bb8b538843 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java @@ -34,6 +34,9 @@ public class RaptorRequestBuilder { // Performance monitoring private RaptorTimers performanceTimers; + /** HACK SØRLANDSBANEN */ + public double extraSearchCoachReluctance; + // Algorithm private RaptorProfile profile; @@ -60,6 +63,9 @@ public RaptorRequestBuilder() { // Debug this.debug = new DebugRequestBuilder(defaults.debug()); + + // HACK SØRLANDSBANEN + this.extraSearchCoachReluctance = defaults.extraSearchCoachReluctance; } public SearchParamsBuilder searchParams() { diff --git a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java index fe8bb884912..86b10f7d96f 100644 --- a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java +++ b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java @@ -11,6 +11,8 @@ import java.util.concurrent.Future; import java.util.stream.Collectors; import javax.annotation.Nullable; +import org.opentripplanner.ext.sorlandsbanen.EnturHackSorlandsBanen; +import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.raptor.RaptorService; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; @@ -134,7 +136,13 @@ private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request) // Create worker if (request.profile().is(MULTI_CRITERIA)) { - raptorWorker = config.createMcWorker(transitData, request, getDestinationHeuristics()); + // HACK SØRLANDSBANEN + if (OTPFeature.HackSorlandsbanen.isOn() && EnturHackSorlandsBanen.match(request)) { + raptorWorker = + EnturHackSorlandsBanen.worker(config, transitData, request, getDestinationHeuristics()); + } else { + raptorWorker = config.createMcWorker(transitData, request, getDestinationHeuristics()); + } } else { raptorWorker = config.createStdWorker(transitData, request); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index b9f9685341c..e59c4d1673c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -128,7 +128,8 @@ private TransitRouterResult route() { accessEgresses.getAccesses(), accessEgresses.getEgresses(), accessEgresses.calculateMaxAccessTimePenalty(), - serverContext.meterRegistry() + serverContext.meterRegistry(), + transitLayer ); // Route transit diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java index b738d63054e..4f39b2631fe 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java @@ -1,5 +1,6 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit.cost; +import java.util.function.Function; import javax.annotation.Nullable; import org.opentripplanner.model.transfer.TransferConstraint; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; @@ -211,4 +212,19 @@ private int boardingCostConstrainedTransfer( // fallback to regular transfer return boardingCostRegularTransfer(firstBoarding, prevArrivalTime, boardStop, boardTime); } + + /*-- HACK SØRLANDSBANEN :: BEGIN --*/ + + public DefaultCostCalculator( + DefaultCostCalculator original, + Function modeReluctanceMapper + ) { + this.boardCostOnly = original.boardCostOnly; + this.boardAndTransferCost = original.boardAndTransferCost; + this.waitFactor = original.waitFactor; + this.transferCostOnly = original.transferCostOnly; + this.transitFactors = modeReluctanceMapper.apply(original.transitFactors); + this.stopTransferCost = original.stopTransferCost; + } + /*-- HACK SØRLANDSBANEN :: END --*/ } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java index fba2a7cfe38..8a14c27566d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java @@ -6,7 +6,7 @@ *

* The class and methods are {@code final} to help the JIT compiler optimize the use of this class. */ -interface FactorStrategy { +public interface FactorStrategy { /** * Return the factor for the given index. */ diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java index 152f199248c..4c66b5b452e 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java @@ -6,12 +6,12 @@ * This class keep a facto for each index and the minimum factor for fast retrieval during Raptor * search. */ -final class IndexBasedFactorStrategy implements FactorStrategy { +public final class IndexBasedFactorStrategy implements FactorStrategy { private final int[] factors; private final int minFactor; - private IndexBasedFactorStrategy(int[] factors) { + public IndexBasedFactorStrategy(int[] factors) { this.factors = factors; this.minFactor = findMinimumFactor(factors); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index 8e6121edffe..5b0de00bc6c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -8,6 +8,7 @@ import java.time.ZonedDateTime; import java.util.Collection; import java.util.List; +import org.opentripplanner.ext.sorlandsbanen.EnturHackSorlandsBanen; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; @@ -17,6 +18,7 @@ import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; import org.opentripplanner.raptor.rangeraptor.SystemErrDebugLogger; import org.opentripplanner.routing.algorithm.raptoradapter.router.performance.PerformanceTimersForRaptor; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.transit.model.site.StopLocation; @@ -31,6 +33,8 @@ public class RaptorRequestMapper { private final boolean isMultiThreadedEnbled; private final MeterRegistry meterRegistry; + private final TransitLayer transitLayer; + private RaptorRequestMapper( RouteRequest request, boolean isMultiThreaded, @@ -38,13 +42,15 @@ private RaptorRequestMapper( Collection egressPaths, Duration searchWindowAccessSlack, long transitSearchTimeZeroEpocSecond, - MeterRegistry meterRegistry + MeterRegistry meterRegistry, + TransitLayer transitLayer ) { this.request = request; this.isMultiThreadedEnbled = isMultiThreaded; this.accessPaths = accessPaths; this.egressPaths = egressPaths; this.searchWindowAccessSlack = searchWindowAccessSlack; + this.transitLayer = transitLayer; this.transitSearchTimeZeroEpocSecond = transitSearchTimeZeroEpocSecond; this.meterRegistry = meterRegistry; } @@ -56,7 +62,8 @@ public static RaptorRequest mapRequest( Collection accessPaths, Collection egressPaths, Duration searchWindowAccessSlack, - MeterRegistry meterRegistry + MeterRegistry meterRegistry, + TransitLayer transitLayer ) { return new RaptorRequestMapper( request, @@ -65,7 +72,8 @@ public static RaptorRequest mapRequest( egressPaths, searchWindowAccessSlack, transitSearchTimeZero.toEpochSecond(), - meterRegistry + meterRegistry, + transitLayer ) .doMap(); } @@ -171,7 +179,7 @@ private RaptorRequest doMap() { ) ); - return builder.build(); + return EnturHackSorlandsBanen.enableHack(builder.build(), request, transitLayer); } private List mapPassThroughPoints() { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java index a0b62414181..df5996009ec 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java @@ -4,6 +4,7 @@ import java.util.BitSet; import java.util.Iterator; import java.util.List; +import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.opentripplanner.framework.application.OTPFeature; @@ -27,6 +28,8 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.constrainedtransfer.ConstrainedBoardingSearch; import org.opentripplanner.routing.algorithm.raptoradapter.transit.constrainedtransfer.ConstrainedTransfersForPatterns; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.CostCalculatorFactory; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.DefaultCostCalculator; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.FactorStrategy; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.GeneralizedCostParametersMapper; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.transit.model.network.RoutingTripPattern; @@ -241,4 +244,34 @@ public RaptorConstrainedBoardingSearch transferConstraintsReverseS } return new ConstrainedBoardingSearch(false, toStopTransfers, fromStopTransfers); } + + /*-- HACK SØRLANDSBANEN :: BEGIN --*/ + + private RaptorRoutingRequestTransitData( + RaptorRoutingRequestTransitData original, + Function mapFactors + ) { + this.transitLayer = original.transitLayer; + this.transitSearchTimeZero = original.transitSearchTimeZero; + this.activeTripPatternsPerStop = original.activeTripPatternsPerStop; + this.patternIndex = original.patternIndex; + this.transferIndex = original.transferIndex; + this.transferService = original.transferService; + this.constrainedTransfers = original.constrainedTransfers; + this.validTransitDataStartTime = original.validTransitDataStartTime; + this.validTransitDataEndTime = original.validTransitDataEndTime; + this.generalizedCostCalculator = + new DefaultCostCalculator<>( + (DefaultCostCalculator) original.generalizedCostCalculator, + mapFactors + ); + this.slackProvider = original.slackProvider(); + } + + public RaptorTransitDataProvider enturHackSorlandsbanen( + Function mapFactors + ) { + return new RaptorRoutingRequestTransitData(this, mapFactors); + } + /*-- HACK SØRLANDSBANEN :: END --*/ } diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java index 7da4516d782..96945693cdd 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java @@ -31,6 +31,7 @@ public final class TransitPreferences implements Serializable { private final boolean includePlannedCancellations; private final boolean includeRealtimeCancellations; private final RaptorPreferences raptor; + private final double extraSearchCoachReluctance; private TransitPreferences() { this.boardSlack = this.alightSlack = DurationForEnum.of(TransitMode.class).build(); @@ -41,6 +42,7 @@ private TransitPreferences() { this.includePlannedCancellations = false; this.includeRealtimeCancellations = false; this.raptor = RaptorPreferences.DEFAULT; + this.extraSearchCoachReluctance = 0.0; } private TransitPreferences(Builder builder) { @@ -53,6 +55,7 @@ private TransitPreferences(Builder builder) { this.includePlannedCancellations = builder.includePlannedCancellations; this.includeRealtimeCancellations = builder.includeRealtimeCancellations; this.raptor = requireNonNull(builder.raptor); + this.extraSearchCoachReluctance = builder.extraSearchCoachReluctance; } public static Builder of() { @@ -153,6 +156,11 @@ public RaptorPreferences raptor() { return raptor; } + /** Zero means turned off. HACK SØRLANDSBANEN */ + public double extraSearchCoachReluctance() { + return extraSearchCoachReluctance; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -167,6 +175,7 @@ public boolean equals(Object o) { alightSlack.equals(that.alightSlack) && reluctanceForMode.equals(that.reluctanceForMode) && unpreferredCost.equals(that.unpreferredCost) && + extraSearchCoachReluctance == that.extraSearchCoachReluctance && raptor.equals(that.raptor) ); } @@ -182,6 +191,7 @@ public int hashCode() { ignoreRealtimeUpdates, includePlannedCancellations, includeRealtimeCancellations, + extraSearchCoachReluctance, raptor ); } @@ -229,6 +239,7 @@ public static class Builder { private boolean includePlannedCancellations; private boolean includeRealtimeCancellations; private RaptorPreferences raptor; + private double extraSearchCoachReluctance; public Builder(TransitPreferences original) { this.original = original; @@ -241,6 +252,7 @@ public Builder(TransitPreferences original) { this.includePlannedCancellations = original.includePlannedCancellations; this.includeRealtimeCancellations = original.includeRealtimeCancellations; this.raptor = original.raptor; + this.extraSearchCoachReluctance = original.extraSearchCoachReluctance; } public TransitPreferences original() { @@ -305,6 +317,11 @@ public Builder withRaptor(Consumer body) { return this; } + public Builder setExtraSearchCoachReluctance(double extraSearchCoachReluctance) { + this.extraSearchCoachReluctance = extraSearchCoachReluctance; + return this; + } + public Builder apply(Consumer body) { body.accept(this); return this; diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index c89670b7e0a..db212bd31d3 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -352,6 +352,13 @@ The board time is added to the time when going from the stop (offboard) to onboa ) .asDoubleOptional() .ifPresent(it::withRelaxGeneralizedCostAtDestination) + ) + .setExtraSearchCoachReluctance( + c + .of("extraSearchCoachReluctance") + .since(V2_1) + .summary("TODO") + .asDouble(dft.extraSearchCoachReluctance()) ); } diff --git a/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java b/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java index 39022da42ca..aeca5a36ebf 100644 --- a/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java +++ b/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java @@ -39,6 +39,9 @@ public class RaptorArchitectureTest { private static final Package RR_STANDARD = RANGE_RAPTOR.subPackage("standard"); private static final Package RR_STD_CONFIGURE = RR_STANDARD.subPackage("configure"); private static final Package RR_CONTEXT = RANGE_RAPTOR.subPackage("context"); + private static final Package EXT_SORLANDSBANAN_HACK = Package.of( + "org.opentripplanner.ext.sorlandsbanen" + ); /** * Packages used by standard-range-raptor and multi-criteria-range-raptor. @@ -200,7 +203,8 @@ void enforcePackageDependenciesInRaptorService() { RAPTOR_UTIL, CONFIGURE, RR_INTERNAL_API, - RR_TRANSIT + RR_TRANSIT, + EXT_SORLANDSBANAN_HACK ) .verify(); } From 63a8c50e6518ab732965633667fb6a6b5ee608ae Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 20 Nov 2023 10:47:09 +0100 Subject: [PATCH 26/26] Version 2.5.0-entur-9 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 728e4532aa5..1a240061c26 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.5.0-SNAPSHOT + 2.5.0-entur-9 jar