From 2dc69659b605ab4ac42dc7387d3b26133be5a4b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Erik=20St=C3=B8wer?= Date: Thu, 14 Dec 2023 16:20:23 +0100 Subject: [PATCH 1/4] 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..8931a1bb608 --- /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-node + environment: + DEBIAN_FRONTEND: "noninteractive" + MAVEN_OPTS: -Xmx6G + resource_class: xlarge + 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 c06bfcafeb1..b4e01ac71af 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 150 + EN-0068 31.1 2.51.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 411d5b1bff75aa85e6cc22d283e750428734659f Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 19 Dec 2023 21:26:49 +0100 Subject: [PATCH 2/4] =?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 + .../resources/RequestToPreferencesMapper.java | 1 + .../restapi/resources/RoutingResource.java | 3 + .../ConcurrentCompositeWorker.java | 53 +++++++ .../sorlandsbanen/EnturHackSorlandsBanen.java | 141 ++++++++++++++++++ .../ext/sorlandsbanen/PathKey.java | 51 +++++++ .../RaptorWorkerResultComposite.java | 88 +++++++++++ .../apis/transmodel/model/plan/TripQuery.java | 8 + .../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 | 17 ++- .../RaptorRoutingRequestTransitData.java | 33 ++++ .../preference/TransitPreferences.java | 17 +++ .../routerequest/RouteRequestConfig.java | 30 ++-- .../apis/transmodel/schema.graphql | 2 + .../raptor/RaptorArchitectureTest.java | 6 +- .../mappers/RaptorRequestMapperTest.java | 1 + 24 files changed, 478 insertions(+), 21 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 ed58f13fa6e..ee324f651ff 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -227,6 +227,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. | ✓️ | | | `DebugUi` | Enable the debug GraphQL client and web UI and located at the root of the web server as well as the debug map tiles it uses. Be aware that the map tiles are not a stable API and can change without notice. Use the [vector tiles feature if](sandbox/MapboxVectorTilesApi.md) you want a stable map tiles API. | ✓️ | | | `FloatingBike` | Enable floating bike routing. | ✓️ | | +| `HackSorlandsbanen` | Includ Sørlandsbanen | | ✓️ | | `GtfsGraphQlApi` | Enable the [GTFS GraphQL API](apis/GTFS-GraphQL-API.md). | ✓️ | | | `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 52fd177fbf6..fd3b9b4dc26 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -23,6 +23,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe | elevatorBoardTime | `integer` | How long does it take to get on an elevator, on average. | *Optional* | `90` | 2.0 | | elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 | | elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | +| 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, real-time updates are ignored during this search. | *Optional* | `false` | 2.0 | | [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | diff --git a/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java b/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java index 6248f71e214..f178af9abc0 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java @@ -132,6 +132,7 @@ private BoardAndAlightSlack mapTransit() { v -> tr.withRaptor(r -> r.withRelaxGeneralizedCostAtDestination(v)) ); } + setIfNotNull(req.extraSearchCoachReluctance, tr::setExtraSearchCoachReluctance); }); return new BoardAndAlightSlack( diff --git a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java index 1d985f0e555..7e0f8ea1dab 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java @@ -199,6 +199,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/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..9be309650aa --- /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/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java index bf34d9928e4..c96956bc6b9 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java @@ -599,6 +599,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/framework/application/OTPFeature.java b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 4ae5004cf6b..3395c7261dd 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -32,6 +32,7 @@ public enum OTPFeature { """ ), FloatingBike(true, false, "Enable floating bike routing."), + HackSorlandsbanen(false, true, "Includ Sørlandsbanen"), GtfsGraphQlApi(true, false, "Enable the [GTFS GraphQL API](apis/GTFS-GraphQL-API.md)."), 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 010bff4bc08..c53f0f90ae5 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 fb96b7a8724..2e50dc2042d 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 0a9e46d2fe3..ad2434f4b70 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 @@ -127,7 +127,8 @@ private TransitRouterResult route() { serverContext.raptorConfig().isMultiThreaded(), accessEgresses.getAccesses(), accessEgresses.getEgresses(), - 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 43faa3f0c40..c3bde3e1cc7 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; @@ -230,4 +231,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.stopBoardAlightTransferCosts = original.stopBoardAlightTransferCosts; + } + /*-- 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 879536fdcd0..17b96790a5f 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.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.GeneralizedCostRelaxFunction; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; @@ -20,6 +21,8 @@ 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.algorithm.raptoradapter.transit.cost.RaptorCostConverter; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitGroupPriority32n; import org.opentripplanner.routing.api.request.DebugEventType; @@ -36,13 +39,16 @@ public class RaptorRequestMapper { private final boolean isMultiThreadedEnbled; private final MeterRegistry meterRegistry; + private final TransitLayer transitLayer; + private RaptorRequestMapper( RouteRequest request, boolean isMultiThreaded, Collection accessPaths, Collection egressPaths, long transitSearchTimeZeroEpocSecond, - MeterRegistry meterRegistry + MeterRegistry meterRegistry, + TransitLayer transitLayer ) { this.request = request; this.isMultiThreadedEnbled = isMultiThreaded; @@ -50,6 +56,7 @@ private RaptorRequestMapper( this.egressPaths = egressPaths; this.transitSearchTimeZeroEpocSecond = transitSearchTimeZeroEpocSecond; this.meterRegistry = meterRegistry; + this.transitLayer = transitLayer; } public static RaptorRequest mapRequest( @@ -58,7 +65,8 @@ public static RaptorRequest mapRequest( boolean isMultiThreaded, Collection accessPaths, Collection egressPaths, - MeterRegistry meterRegistry + MeterRegistry meterRegistry, + TransitLayer transitLayer ) { return new RaptorRequestMapper( request, @@ -66,7 +74,8 @@ public static RaptorRequest mapRequest( accessPaths, egressPaths, transitSearchTimeZero.toEpochSecond(), - meterRegistry + meterRegistry, + transitLayer ) .doMap(); } @@ -176,7 +185,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 8c310206a01..d4785dc6f5b 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; @@ -253,4 +256,34 @@ private PriorityGroupConfigurator createTransitGroupPriorityConfigurator(RouteRe transitRequest.priorityGroupsGlobal() ); } + + /*-- 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 78f30277e72..94a4a458915 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(); @@ -42,6 +43,7 @@ private TransitPreferences() { this.includePlannedCancellations = false; this.includeRealtimeCancellations = false; this.raptor = RaptorPreferences.DEFAULT; + this.extraSearchCoachReluctance = 0.0; } private TransitPreferences(Builder builder) { @@ -55,6 +57,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() { @@ -165,6 +168,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; @@ -180,6 +188,7 @@ public boolean equals(Object o) { ignoreRealtimeUpdates == that.ignoreRealtimeUpdates && includePlannedCancellations == that.includePlannedCancellations && includeRealtimeCancellations == that.includeRealtimeCancellations && + extraSearchCoachReluctance == that.extraSearchCoachReluctance && raptor.equals(that.raptor) ); } @@ -196,6 +205,7 @@ public int hashCode() { ignoreRealtimeUpdates, includePlannedCancellations, includeRealtimeCancellations, + extraSearchCoachReluctance, raptor ); } @@ -245,6 +255,7 @@ public static class Builder { private boolean includePlannedCancellations; private boolean includeRealtimeCancellations; private RaptorPreferences raptor; + private double extraSearchCoachReluctance; public Builder(TransitPreferences original) { this.original = original; @@ -258,6 +269,7 @@ public Builder(TransitPreferences original) { this.includePlannedCancellations = original.includePlannedCancellations; this.includeRealtimeCancellations = original.includeRealtimeCancellations; this.raptor = original.raptor; + this.extraSearchCoachReluctance = original.extraSearchCoachReluctance; } public TransitPreferences original() { @@ -327,6 +339,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 628008da220..a73b3c26d76 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -317,13 +317,14 @@ The board time is added to the time when going from the stop (offboard) to onboa } // TODO REMOVE THIS - builder.withRaptor(it -> - c - .of("relaxTransitSearchGeneralizedCostAtDestination") - .since(V2_3) - .summary("Whether non-optimal transit paths at the destination should be returned") - .description( - """ + builder + .withRaptor(it -> + c + .of("relaxTransitSearchGeneralizedCostAtDestination") + .since(V2_3) + .summary("Whether non-optimal transit paths at the destination should be returned") + .description( + """ Let c be the existing minimum pareto optimal generalized cost to beat. Then a trip with cost c' is accepted if the following is true: `c' < Math.round(c * relaxRaptorCostCriteria)`. @@ -333,10 +334,17 @@ The board time is added to the time when going from the stop (offboard) to onboa Values equals or less than zero is not allowed. Values greater than 2.0 are not supported, due to performance reasons. """ - ) - .asDoubleOptional() - .ifPresent(it::withRelaxGeneralizedCostAtDestination) - ); + ) + .asDoubleOptional() + .ifPresent(it::withRelaxGeneralizedCostAtDestination) + ) + .setExtraSearchCoachReluctance( + c + .of("extraSearchCoachReluctance") + .since(V2_1) + .summary("TODO") + .asDouble(dft.extraSearchCoachReluctance()) + ); } private static void mapBikePreferences(NodeAdapter root, BikePreferences.Builder builder) { diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index e6f4208c643..fbda3226515 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -796,6 +796,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/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(); } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java index 47542782884..e3fbbc0df21 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java @@ -90,6 +90,7 @@ private static RaptorRequest map(RouteRequest request) { false, ACCESS, EGRESS, + null, null ); } From 281383234f0692a513ed7bb7324e5cb8f320e18c Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 31 May 2024 11:31:24 +0200 Subject: [PATCH 3/4] Fix spelling in doc --- .../org/opentripplanner/apis/transmodel/schema.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index 4a6f462624f..1b01b2c2276 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -801,7 +801,7 @@ type QueryType { restrictions are applied - all journeys are listed. """ bookingTime: DateTime, - "The date and time for the earliest time the user is willing to start the journey (if `false`or not set) or the latest acceptable time of arriving (`true`). Defaults to now" + "The date and time for the earliest time the user is willing to start the journey (if `false` or not set) or the latest acceptable time of arriving (`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 @deprecated(reason : "Use `itineraryFilter.debug` instead."), From 9742fe617a715fd90d2705f595c0c4684cfefd0d Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 31 May 2024 11:32:47 +0200 Subject: [PATCH 4/4] Version 2.6.0-entur-11 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b4e01ac71af..a2ff4ae435c 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.6.0-SNAPSHOT + 2.6.0-entur-11 jar