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 001/121] 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 002/121] =?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 003/121] 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 004/121] 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 From ab1f994093fd9e6acfc788677d697c1f9f477625 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 005/121] 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 5880d9dcc62..585b93f8848 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 150 + EN-0067 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 8fbef65d210f700091c858c691c8d4e1baa84aa5 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 19 Dec 2023 21:26:49 +0100 Subject: [PATCH 006/121] =?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 0fd9ec47f89..790984ac02c 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 f9ea136fbe2..df1ee7bb0eb 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -316,13 +316,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)`. @@ -332,10 +333,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 e7d68a2e780..bcb28bddb90 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 54d522f32761aedd61ea016508f41c0fd06fc4a8 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Thu, 6 Jun 2024 10:30:49 +0200 Subject: [PATCH 007/121] Version 2.6.0-entur-12 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 585b93f8848..cf35bb2f0e2 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-12 jar From 6b2b04aa9a9b027eb4d92cca002bd7bb9f5ddd4d Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Thu, 6 Jun 2024 12:09:36 +0200 Subject: [PATCH 008/121] Update serialization id --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cf35bb2f0e2..ea34d933b2c 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - EN-0067 + EN-0068 31.1 2.51.1 From 2ff5fcaaead5ddbf8faa31780c1d9abc5b549fc1 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Thu, 6 Jun 2024 12:23:44 +0200 Subject: [PATCH 009/121] Version 2.6.0-entur-13 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ea34d933b2c..43294b72518 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.6.0-entur-12 + 2.6.0-entur-13 jar From b5f602e83e771d5535776bd26a3864714831120e Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Fri, 7 Jun 2024 08:35:16 +0200 Subject: [PATCH 010/121] Version 2.6.0-entur-14 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cf8125c3cfb..08e8dd52490 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.6.0-entur-13 + 2.6.0-entur-14 jar From a00116e9c18dd6f3155355bab1d9bd5de970439c Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Fri, 14 Jun 2024 09:27:04 +0200 Subject: [PATCH 011/121] Fix too large response cancellation --- .../restapi/resources/PlannerResource.java | 5 ++-- .../opentripplanner/api/common/Message.java | 2 +- .../MaxFieldsInResultInstrumentation.java | 3 +-- .../transmodel/ResponseTooLargeException.java | 11 ++++++++ .../apis/transmodel/TransmodelGraph.java | 6 +++-- ...nprocessableRequestExecutionStrategy.java} | 22 ++++++++++------ .../support/ExecutionResultMapper.java | 26 ++++++++++++++----- src/main/resources/Message.properties | 2 +- src/main/resources/Message_de.properties | 2 +- src/main/resources/Message_es.properties | 2 +- src/main/resources/Message_fr.properties | 2 +- src/main/resources/Message_hu.properties | 2 +- src/main/resources/Message_nl.properties | 2 +- .../support/ExecutionResultMapperTest.java | 22 ++++++++++++++++ 14 files changed, 81 insertions(+), 28 deletions(-) create mode 100644 src/main/java/org/opentripplanner/apis/transmodel/ResponseTooLargeException.java rename src/main/java/org/opentripplanner/apis/transmodel/support/{AbortOnTimeoutExecutionStrategy.java => AbortOnUnprocessableRequestExecutionStrategy.java} (60%) diff --git a/src/ext/java/org/opentripplanner/ext/restapi/resources/PlannerResource.java b/src/ext/java/org/opentripplanner/ext/restapi/resources/PlannerResource.java index 38c70851b76..9c58c1c719d 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/PlannerResource.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/PlannerResource.java @@ -12,6 +12,7 @@ import org.opentripplanner.api.common.Message; import org.opentripplanner.api.error.PlannerError; import org.opentripplanner.apis.support.mapping.PlannerErrorMapper; +import org.opentripplanner.apis.transmodel.ResponseTooLargeException; import org.opentripplanner.ext.restapi.mapping.TripPlanMapper; import org.opentripplanner.ext.restapi.mapping.TripSearchMetadataMapper; import org.opentripplanner.ext.restapi.model.ElevationMetadata; @@ -104,8 +105,8 @@ public Response plan(@Context UriInfo uriInfo, @Context Request grizzlyRequest) LOG.error("System error - unhandled error case?", e); response.setError(new PlannerError(Message.SYSTEM_ERROR)); } - } catch (OTPRequestTimeoutException e) { - response.setError(new PlannerError(Message.PROCESSING_TIMEOUT)); + } catch (OTPRequestTimeoutException | ResponseTooLargeException e) { + response.setError(new PlannerError(Message.UNPROCESSABLE_REQUEST)); } catch (Exception e) { LOG.error("System error", e); response.setError(new PlannerError(Message.SYSTEM_ERROR)); diff --git a/src/main/java/org/opentripplanner/api/common/Message.java b/src/main/java/org/opentripplanner/api/common/Message.java index 9a07d8c33d6..3e568d57940 100644 --- a/src/main/java/org/opentripplanner/api/common/Message.java +++ b/src/main/java/org/opentripplanner/api/common/Message.java @@ -15,7 +15,7 @@ public enum Message { PLAN_OK(200), SYSTEM_ERROR(500), - PROCESSING_TIMEOUT(422), + UNPROCESSABLE_REQUEST(422), GRAPH_UNAVAILABLE(503), diff --git a/src/main/java/org/opentripplanner/apis/transmodel/MaxFieldsInResultInstrumentation.java b/src/main/java/org/opentripplanner/apis/transmodel/MaxFieldsInResultInstrumentation.java index 6d22f18783c..7cc356e5f6b 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/MaxFieldsInResultInstrumentation.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/MaxFieldsInResultInstrumentation.java @@ -3,7 +3,6 @@ import static graphql.execution.instrumentation.SimpleInstrumentationContext.noOp; import graphql.ExecutionResult; -import graphql.execution.AbortExecutionException; import graphql.execution.instrumentation.Instrumentation; import graphql.execution.instrumentation.InstrumentationContext; import graphql.execution.instrumentation.InstrumentationState; @@ -47,7 +46,7 @@ public InstrumentationContext beginFieldFetch( if (fetched % 10000 == 0) { LOG.debug("Fetched {} fields", fetched); if (fetched > maxFieldFetch) { - throw new AbortExecutionException( + throw new ResponseTooLargeException( "The number of fields in the GraphQL result exceeds the maximum allowed: " + maxFieldFetch ); } diff --git a/src/main/java/org/opentripplanner/apis/transmodel/ResponseTooLargeException.java b/src/main/java/org/opentripplanner/apis/transmodel/ResponseTooLargeException.java new file mode 100644 index 00000000000..a6b070e8fdd --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/transmodel/ResponseTooLargeException.java @@ -0,0 +1,11 @@ +package org.opentripplanner.apis.transmodel; + +/** + * Exception thrown when the API response exceeds a configurable limit. + */ +public class ResponseTooLargeException extends RuntimeException { + + public ResponseTooLargeException(String message) { + super(message); + } +} diff --git a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraph.java b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraph.java index 1bff91638fd..d755c509989 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraph.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraph.java @@ -16,7 +16,7 @@ import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import org.opentripplanner.apis.transmodel.support.AbortOnTimeoutExecutionStrategy; +import org.opentripplanner.apis.transmodel.support.AbortOnUnprocessableRequestExecutionStrategy; import org.opentripplanner.apis.transmodel.support.ExecutionResultMapper; import org.opentripplanner.ext.actuator.MicrometerGraphQLInstrumentation; import org.opentripplanner.framework.application.OTPFeature; @@ -50,7 +50,7 @@ Response executeGraphQL( int maxNumberOfResultFields, Iterable tracingTags ) { - try (var executionStrategy = new AbortOnTimeoutExecutionStrategy()) { + try (var executionStrategy = new AbortOnUnprocessableRequestExecutionStrategy()) { variables = ObjectUtils.ifNotNull(variables, new HashMap<>()); var instrumentation = createInstrumentation(maxNumberOfResultFields, tracingTags); var transmodelRequestContext = createRequestContext(serverContext); @@ -69,6 +69,8 @@ Response executeGraphQL( return ExecutionResultMapper.okResponse(result); } catch (OTPRequestTimeoutException te) { return ExecutionResultMapper.timeoutResponse(); + } catch (ResponseTooLargeException rtle) { + return ExecutionResultMapper.tooLargeResponse(rtle.getMessage()); } catch (CoercingParseValueException | UnknownOperationException e) { return ExecutionResultMapper.badRequestResponse(e.getMessage()); } catch (Exception systemError) { diff --git a/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnTimeoutExecutionStrategy.java b/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnUnprocessableRequestExecutionStrategy.java similarity index 60% rename from src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnTimeoutExecutionStrategy.java rename to src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnUnprocessableRequestExecutionStrategy.java index d143d65421c..4d7b31d5ce4 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnTimeoutExecutionStrategy.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnUnprocessableRequestExecutionStrategy.java @@ -4,22 +4,28 @@ import graphql.schema.DataFetchingEnvironment; import java.io.Closeable; import java.util.concurrent.CompletableFuture; +import org.opentripplanner.apis.transmodel.ResponseTooLargeException; import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.framework.logging.ProgressTracker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * To abort fetching data when a timeout occurs we have to rethrow the time-out-exception. + * To abort fetching data when a request is unprocessable (either because the execution times + * out or because the response is too large) we have to rethrow the exception. * This will prevent unresolved data-fetchers to be called. The exception is not handled * gracefully. */ -public class AbortOnTimeoutExecutionStrategy extends AsyncExecutionStrategy implements Closeable { +public class AbortOnUnprocessableRequestExecutionStrategy + extends AsyncExecutionStrategy + implements Closeable { - private static final Logger LOG = LoggerFactory.getLogger(AbortOnTimeoutExecutionStrategy.class); + private static final Logger LOG = LoggerFactory.getLogger( + AbortOnUnprocessableRequestExecutionStrategy.class + ); public static final int LOG_STEPS = 25_000; private final ProgressTracker timeoutProgressTracker = ProgressTracker.track( - "TIMEOUT! Abort GraphQL query", + "Unprocessable request. Abort GraphQL query", LOG_STEPS, -1 ); @@ -29,15 +35,15 @@ protected CompletableFuture handleFetchingException( DataFetchingEnvironment environment, Throwable e ) { - if (e instanceof OTPRequestTimeoutException te) { - logTimeoutProgress(); - throw te; + if (e instanceof OTPRequestTimeoutException || e instanceof ResponseTooLargeException) { + logCancellationProgress(); + throw (RuntimeException) e; } return super.handleFetchingException(environment, e); } @SuppressWarnings("Convert2MethodRef") - private void logTimeoutProgress() { + private void logCancellationProgress() { timeoutProgressTracker.startOrStep(m -> LOG.info(m)); } diff --git a/src/main/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapper.java index d0ccb198e16..aac9cb1bb7c 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapper.java @@ -16,6 +16,11 @@ public class ExecutionResultMapper { private static final ErrorClassification API_PROCESSING_TIMEOUT = ErrorClassification.errorClassification( "ApiProcessingTimeout" ); + + private static final ErrorClassification RESPONSE_TOO_LARGE = ErrorClassification.errorClassification( + "ResponseTooLarge" + ); + private static final ErrorClassification BAD_REQUEST_ERROR = ErrorClassification.errorClassification( "BadRequestError" ); @@ -29,13 +34,11 @@ public static Response okResponse(ExecutionResult result) { } public static Response timeoutResponse() { - var error = GraphQLError - .newError() - .errorType(API_PROCESSING_TIMEOUT) - .message(OTPRequestTimeoutException.MESSAGE) - .build(); - var result = ExecutionResult.newExecutionResult().addError(error).build(); - return response(result, OtpHttpStatus.STATUS_UNPROCESSABLE_ENTITY); + return unprocessableResponse(API_PROCESSING_TIMEOUT, OTPRequestTimeoutException.MESSAGE); + } + + public static Response tooLargeResponse(String message) { + return unprocessableResponse(RESPONSE_TOO_LARGE, message); } public static Response badRequestResponse(String message) { @@ -56,4 +59,13 @@ public static Response response(ExecutionResult result, Response.StatusType stat .entity(GraphQLResponseSerializer.serialize(result)) .build(); } + + private static Response unprocessableResponse( + ErrorClassification errorClassification, + String message + ) { + var error = GraphQLError.newError().errorType(errorClassification).message(message).build(); + var result = ExecutionResult.newExecutionResult().addError(error).build(); + return response(result, OtpHttpStatus.STATUS_UNPROCESSABLE_ENTITY); + } } diff --git a/src/main/resources/Message.properties b/src/main/resources/Message.properties index 5c6011e3d3d..931b25c8b3b 100644 --- a/src/main/resources/Message.properties +++ b/src/main/resources/Message.properties @@ -8,7 +8,7 @@ SYSTEM_ERROR = We're sorry. The trip planner is temporarily unavailable. Please GRAPH_UNAVAILABLE = We're sorry. The trip planner is temporarily unavailable. Please try again later. OUTSIDE_BOUNDS = Trip is not possible. You might be trying to plan a trip outside the map data boundary. -PROCESSING_TIMEOUT = The trip planner is taking too long to process your request. +UNPROCESSABLE_REQUEST = The trip planner is taking too long time or too much resources to process your request. BOGUS_PARAMETER = The request has errors that the server is not willing or able to process. LOCATION_NOT_ACCESSIBLE = The location was found, but no stops could be found within the search radius. PATH_NOT_FOUND = No trip found. There may be no transit service within the maximum specified distance or at the specified time, or your start or end point might not be safely accessible. diff --git a/src/main/resources/Message_de.properties b/src/main/resources/Message_de.properties index 1b0d8a2322e..6862a993c71 100644 --- a/src/main/resources/Message_de.properties +++ b/src/main/resources/Message_de.properties @@ -2,7 +2,7 @@ PLAN_OK = Success SYSTEM_ERROR = Es tut uns leid, leider steht der Trip-Planer momentan nicht zur Verfügung. Bitte versuchen Sie es zu einem späteren Zeitpunkt nochmal. OUTSIDE_BOUNDS = Planung nicht möglich. Vielleicht versuchen sie einen Plan außerhalb der Kartengrenzen zu planen. -PROCESSING_TIMEOUT = Der Trip-Planner braucht zu lange um die Anfrage zu bearbeiten +UNPROCESSABLE_REQUEST = Der Trip-Planner braucht zu lange um die Anfrage zu bearbeiten BOGUS_PARAMETER = Die Anfrage ist fehlerhaft so dass sie der Server nicht bearbeiten möchte oder kann. PATH_NOT_FOUND = Planung nicht möglich. Ihr Start- oder Endpunkt könnte nicht erreichbar sein. Bitte stellen sie sicher, dass ihre Anfrage innerhalb der Kartendaten ist. NO_TRANSIT_TIMES = Keine Fahrzeiten verfügbar. Das Datum kann zu weit in der Vergangenheit oder zu weit in der Zukunft liegen oder es gibt keinen Verkehrsbetrieb zu dem von Ihnen gewählten Zeitpunkt. diff --git a/src/main/resources/Message_es.properties b/src/main/resources/Message_es.properties index 0c5fe7a9395..88b7c849acb 100644 --- a/src/main/resources/Message_es.properties +++ b/src/main/resources/Message_es.properties @@ -8,7 +8,7 @@ SYSTEM_ERROR = ES-We're sorry. The trip planner is temporarily unavailable. Plea GRAPH_UNAVAILABLE = ES-We're sorry. The trip planner is temporarily unavailable. Please try again later. OUTSIDE_BOUNDS = ES-los trip is not possible. You might be trying to plan a trip outside the map data boundary. -PROCESSING_TIMEOUT = ES-los trip planner is taking too long to process your request. +UNPROCESSABLE_REQUEST = ES-los trip planner is taking too long time or too much resources to process your request. BOGUS_PARAMETER = ES-los request has errors that the server is not willing or able to process. PATH_NOT_FOUND = ES-los trip is not possible. Please check that you plan is within the bound of the map. NO_TRANSIT_TIMES = ES-Non transit times available. The date may be past or too far in the future or there may not be transit service for your trip at the time you chose. diff --git a/src/main/resources/Message_fr.properties b/src/main/resources/Message_fr.properties index 6f7b73c1997..66ff82bbc59 100644 --- a/src/main/resources/Message_fr.properties +++ b/src/main/resources/Message_fr.properties @@ -8,7 +8,7 @@ SYSTEM_ERROR = Désolé: le calculateur d'itinéraires est temporairement indisp GRAPH_UNAVAILABLE = Désolé: le calculateur d'itinéraires est temporairement indisponible. Veuillez recommencer ultérieurement. OUTSIDE_BOUNDS = Impossible de calculer un itinéraire. Vous essayez de planifier un itinéraire hors des limites de la zone couverte. -PROCESSING_TIMEOUT = Le calculateur d'itinéraires prend trop de temps pour gérer votre demande. +UNPROCESSABLE_REQUEST = Le calculateur d'itinéraires prend trop de temps ou de resources pour gérer votre demande. BOGUS_PARAMETER = Le serveur ne peut pas prendre en compte la requête, elle contient des paramètres erronés. PATH_NOT_FOUND = Impossible de calculer un itinéraire. Veuillez vérifier que le trajet demandé est inclus dans la zone couverte. NO_TRANSIT_TIMES = Aucun voyage disponible. Soit la date est trop loin dans le futur, soit il n'y a pas de service ce jour. diff --git a/src/main/resources/Message_hu.properties b/src/main/resources/Message_hu.properties index eca5fad9b10..f72f2295581 100644 --- a/src/main/resources/Message_hu.properties +++ b/src/main/resources/Message_hu.properties @@ -8,7 +8,7 @@ SYSTEM_ERROR = Sajn\u00E1ljuk. Az \u00FAtvonaltervez\u0151 \u00E1tmenetileg GRAPH_UNAVAILABLE = Sajn\u00E1ljuk. Az \u00FAtvonaltervez\u0151 \u00E1tmenetileg nem el\u00E9rhet\u0151. K\u00E9rj\u00FCk, pr\u00F3b\u00E1lja \u00FAjra k\u00E9s\u0151bb. OUTSIDE_BOUNDS = Az utaz\u00E1s nem lehets\u00E9ges. Lehet, hogy a t\u00E9rk\u00E9padat-hat\u00E1ron k\u00EDv\u00FCli utat pr\u00F3b\u00E1l megtervezni. -PROCESSING_TIMEOUT = Az utaz\u00E1stervez\u0151 t\u00FAl sok\u00E1ig tart a k\u00E9r\u00E9s feldolgoz\u00E1s\u00E1hoz. +UNPROCESSABLE_REQUEST = Az utaz\u00E1stervez\u0151 t\u00FAl sok\u00E1ig tart a k\u00E9r\u00E9s feldolgoz\u00E1s\u00E1hoz. BOGUS_PARAMETER = A k\u00E9r\u00E9s olyan hib\u00E1kat tartalmaz, amelyeket a szerver nem hajland\u00F3 vagy nem k\u00E9pes feldolgozni. LOCATION_NOT_ACCESSIBLE = A helyek megtal\u00E1lhat\u00F3ak, de meg\u00E1ll\u00F3k nem tal\u00E1lhat\u00F3 a keres\u00E9si k\u00F6rzetben. PATH_NOT_FOUND = Nem tal\u00E1lhat\u00F3 utaz\u00E1s. El\u0151fordulhat, hogy a megadott maxim\u00E1lis t\u00E1vols\u00E1gon bel\u00FCl vagy a megadott id\u0151pontban nincs t\u00F6megk\u00F6zleked\u00E9si szolg\u00E1ltat\u00E1s, vagy a kezd\u0151- vagy v\u00E9gpont nem \u00E9rhet\u0151 el biztons\u00E1gosan. diff --git a/src/main/resources/Message_nl.properties b/src/main/resources/Message_nl.properties index 1e18c998264..2a7a1b7700b 100644 --- a/src/main/resources/Message_nl.properties +++ b/src/main/resources/Message_nl.properties @@ -8,7 +8,7 @@ SYSTEM_ERROR = Onze excuses. De routeplanner is momenteel niet beschikbaar. Prob GRAPH_UNAVAILABLE = Onze excuses. De routeplanner is momenteel niet beschikbaar. Probeer later opnieuw. OUTSIDE_BOUNDS = Deze reis is niet mogelijk. Mogelijk probeert u een reis te plannen buiten het beschikbare gebied -PROCESSING_TIMEOUT = De routeplanner is te lang bezig met uw verzoek. +UNPROCESSABLE_REQUEST = De routeplanner is te lang bezig met uw verzoek. BOGUS_PARAMETER = Uw verzoek bevat fouten die de server niet wil or kan verwerken. PATH_NOT_FOUND = Reis is niet mogelijk. Misschien is het startpunt of de bestemming niet veilig toegankelijk. Bijvoorbeeld een straat alleen verbonden met een snelweg. NO_TRANSIT_TIMES = Geen OV-informatie beschikbaar. De datum is mogelijk te ver in het verleden of te ver in de toekomst. Of er is geen dienst voor uw reis op het moment dat u wilt. diff --git a/src/test/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapperTest.java index bce971e9f1e..b697adc9288 100644 --- a/src/test/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapperTest.java @@ -34,6 +34,21 @@ class ExecutionResultMapperTest { "}" ); + public static final String TOO_LARGE_MESSAGE = + "The number of fields in the GraphQL result exceeds the maximum allowed: 100000"; + + private static final String TOO_LARGE_RESPONSE = quoteReplace( + "{'" + + "errors':[{" + + "'message':'" + + TOO_LARGE_MESSAGE + + "'," + + "'locations':[]," + + "'extensions':{'classification':'ResponseTooLarge'}" + + "}]" + + "}" + ); + public static final String SYSTEM_ERROR_MESSAGE = "A system error!"; public static final String SYSTEM_ERROR_RESPONSE = quoteReplace( @@ -62,6 +77,13 @@ void timeoutResponse() { assertEquals(TIMEOUT_RESPONSE, response.getEntity().toString()); } + @Test + void tooLargeResponse() { + var response = ExecutionResultMapper.tooLargeResponse(TOO_LARGE_MESSAGE); + assertEquals(422, response.getStatus()); + assertEquals(TOO_LARGE_RESPONSE, response.getEntity().toString()); + } + @Test void systemErrorResponse() { var response = ExecutionResultMapper.systemErrorResponse(SYSTEM_ERROR_MESSAGE); From a501413ffce66908470a4ca90be3d30bc64fb9d7 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Fri, 14 Jun 2024 10:10:16 +0200 Subject: [PATCH 012/121] Version 2.6.0-entur-15 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 08e8dd52490..6096fe12113 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.6.0-entur-14 + 2.6.0-entur-15 jar From d78225c593d801b4d814206e1b4b67844f80f6a4 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 013/121] 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 6f202b592f8..11818765947 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 ae3b16e77bb6cc36bc6c26e51320ce9cc4fd1e0d Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 19 Dec 2023 21:26:49 +0100 Subject: [PATCH 014/121] =?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 05611e23628..2f34b598fa7 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 0fd9ec47f89..790984ac02c 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 3ae029fdb2d..b4912aacb1d 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java @@ -203,6 +203,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 5ed3264a4fd..26676d96c7b 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 @@ -616,6 +616,14 @@ Normally this is when the search is performed (now), plus a small grace period t ) .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 29489de19f2..d7bd15922fc 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 6a1404c3039..eaab9741570 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 f9ea136fbe2..df1ee7bb0eb 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -316,13 +316,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)`. @@ -332,10 +333,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 fbaa2418d7e..3462c67e458 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -805,6 +805,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 12885db7c5409182e9bafb6f81389f847c82e885 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Fri, 14 Jun 2024 09:27:04 +0200 Subject: [PATCH 015/121] Fix too large response cancellation --- .../restapi/resources/PlannerResource.java | 5 ++-- .../opentripplanner/api/common/Message.java | 2 +- .../MaxFieldsInResultInstrumentation.java | 3 +-- .../transmodel/ResponseTooLargeException.java | 11 ++++++++ .../apis/transmodel/TransmodelGraph.java | 6 +++-- ...nprocessableRequestExecutionStrategy.java} | 22 ++++++++++------ .../support/ExecutionResultMapper.java | 26 ++++++++++++++----- src/main/resources/Message.properties | 2 +- src/main/resources/Message_de.properties | 2 +- src/main/resources/Message_es.properties | 2 +- src/main/resources/Message_fr.properties | 2 +- src/main/resources/Message_hu.properties | 2 +- src/main/resources/Message_nl.properties | 2 +- .../support/ExecutionResultMapperTest.java | 22 ++++++++++++++++ 14 files changed, 81 insertions(+), 28 deletions(-) create mode 100644 src/main/java/org/opentripplanner/apis/transmodel/ResponseTooLargeException.java rename src/main/java/org/opentripplanner/apis/transmodel/support/{AbortOnTimeoutExecutionStrategy.java => AbortOnUnprocessableRequestExecutionStrategy.java} (62%) diff --git a/src/ext/java/org/opentripplanner/ext/restapi/resources/PlannerResource.java b/src/ext/java/org/opentripplanner/ext/restapi/resources/PlannerResource.java index 38c70851b76..9c58c1c719d 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/PlannerResource.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/PlannerResource.java @@ -12,6 +12,7 @@ import org.opentripplanner.api.common.Message; import org.opentripplanner.api.error.PlannerError; import org.opentripplanner.apis.support.mapping.PlannerErrorMapper; +import org.opentripplanner.apis.transmodel.ResponseTooLargeException; import org.opentripplanner.ext.restapi.mapping.TripPlanMapper; import org.opentripplanner.ext.restapi.mapping.TripSearchMetadataMapper; import org.opentripplanner.ext.restapi.model.ElevationMetadata; @@ -104,8 +105,8 @@ public Response plan(@Context UriInfo uriInfo, @Context Request grizzlyRequest) LOG.error("System error - unhandled error case?", e); response.setError(new PlannerError(Message.SYSTEM_ERROR)); } - } catch (OTPRequestTimeoutException e) { - response.setError(new PlannerError(Message.PROCESSING_TIMEOUT)); + } catch (OTPRequestTimeoutException | ResponseTooLargeException e) { + response.setError(new PlannerError(Message.UNPROCESSABLE_REQUEST)); } catch (Exception e) { LOG.error("System error", e); response.setError(new PlannerError(Message.SYSTEM_ERROR)); diff --git a/src/main/java/org/opentripplanner/api/common/Message.java b/src/main/java/org/opentripplanner/api/common/Message.java index 9a07d8c33d6..3e568d57940 100644 --- a/src/main/java/org/opentripplanner/api/common/Message.java +++ b/src/main/java/org/opentripplanner/api/common/Message.java @@ -15,7 +15,7 @@ public enum Message { PLAN_OK(200), SYSTEM_ERROR(500), - PROCESSING_TIMEOUT(422), + UNPROCESSABLE_REQUEST(422), GRAPH_UNAVAILABLE(503), diff --git a/src/main/java/org/opentripplanner/apis/transmodel/MaxFieldsInResultInstrumentation.java b/src/main/java/org/opentripplanner/apis/transmodel/MaxFieldsInResultInstrumentation.java index 6d22f18783c..7cc356e5f6b 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/MaxFieldsInResultInstrumentation.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/MaxFieldsInResultInstrumentation.java @@ -3,7 +3,6 @@ import static graphql.execution.instrumentation.SimpleInstrumentationContext.noOp; import graphql.ExecutionResult; -import graphql.execution.AbortExecutionException; import graphql.execution.instrumentation.Instrumentation; import graphql.execution.instrumentation.InstrumentationContext; import graphql.execution.instrumentation.InstrumentationState; @@ -47,7 +46,7 @@ public InstrumentationContext beginFieldFetch( if (fetched % 10000 == 0) { LOG.debug("Fetched {} fields", fetched); if (fetched > maxFieldFetch) { - throw new AbortExecutionException( + throw new ResponseTooLargeException( "The number of fields in the GraphQL result exceeds the maximum allowed: " + maxFieldFetch ); } diff --git a/src/main/java/org/opentripplanner/apis/transmodel/ResponseTooLargeException.java b/src/main/java/org/opentripplanner/apis/transmodel/ResponseTooLargeException.java new file mode 100644 index 00000000000..a6b070e8fdd --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/transmodel/ResponseTooLargeException.java @@ -0,0 +1,11 @@ +package org.opentripplanner.apis.transmodel; + +/** + * Exception thrown when the API response exceeds a configurable limit. + */ +public class ResponseTooLargeException extends RuntimeException { + + public ResponseTooLargeException(String message) { + super(message); + } +} diff --git a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraph.java b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraph.java index 1bff91638fd..d755c509989 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraph.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraph.java @@ -16,7 +16,7 @@ import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import org.opentripplanner.apis.transmodel.support.AbortOnTimeoutExecutionStrategy; +import org.opentripplanner.apis.transmodel.support.AbortOnUnprocessableRequestExecutionStrategy; import org.opentripplanner.apis.transmodel.support.ExecutionResultMapper; import org.opentripplanner.ext.actuator.MicrometerGraphQLInstrumentation; import org.opentripplanner.framework.application.OTPFeature; @@ -50,7 +50,7 @@ Response executeGraphQL( int maxNumberOfResultFields, Iterable tracingTags ) { - try (var executionStrategy = new AbortOnTimeoutExecutionStrategy()) { + try (var executionStrategy = new AbortOnUnprocessableRequestExecutionStrategy()) { variables = ObjectUtils.ifNotNull(variables, new HashMap<>()); var instrumentation = createInstrumentation(maxNumberOfResultFields, tracingTags); var transmodelRequestContext = createRequestContext(serverContext); @@ -69,6 +69,8 @@ Response executeGraphQL( return ExecutionResultMapper.okResponse(result); } catch (OTPRequestTimeoutException te) { return ExecutionResultMapper.timeoutResponse(); + } catch (ResponseTooLargeException rtle) { + return ExecutionResultMapper.tooLargeResponse(rtle.getMessage()); } catch (CoercingParseValueException | UnknownOperationException e) { return ExecutionResultMapper.badRequestResponse(e.getMessage()); } catch (Exception systemError) { diff --git a/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnTimeoutExecutionStrategy.java b/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnUnprocessableRequestExecutionStrategy.java similarity index 62% rename from src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnTimeoutExecutionStrategy.java rename to src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnUnprocessableRequestExecutionStrategy.java index a925aff0a1d..a8a664fc18d 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnTimeoutExecutionStrategy.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnUnprocessableRequestExecutionStrategy.java @@ -5,22 +5,28 @@ import graphql.schema.DataFetchingEnvironment; import java.io.Closeable; import java.util.concurrent.CompletableFuture; +import org.opentripplanner.apis.transmodel.ResponseTooLargeException; import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.framework.logging.ProgressTracker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * To abort fetching data when a timeout occurs we have to rethrow the time-out-exception. + * To abort fetching data when a request is unprocessable (either because the execution times + * out or because the response is too large) we have to rethrow the exception. * This will prevent unresolved data-fetchers to be called. The exception is not handled * gracefully. */ -public class AbortOnTimeoutExecutionStrategy extends AsyncExecutionStrategy implements Closeable { +public class AbortOnUnprocessableRequestExecutionStrategy + extends AsyncExecutionStrategy + implements Closeable { - private static final Logger LOG = LoggerFactory.getLogger(AbortOnTimeoutExecutionStrategy.class); + private static final Logger LOG = LoggerFactory.getLogger( + AbortOnUnprocessableRequestExecutionStrategy.class + ); public static final int LOG_STEPS = 25_000; private final ProgressTracker timeoutProgressTracker = ProgressTracker.track( - "TIMEOUT! Abort GraphQL query", + "Unprocessable request. Abort GraphQL query", LOG_STEPS, -1 ); @@ -31,15 +37,15 @@ protected CompletableFuture handleFetchingException( ExecutionStrategyParameters params, Throwable e ) { - if (e instanceof OTPRequestTimeoutException te) { - logTimeoutProgress(); - throw te; + if (e instanceof OTPRequestTimeoutException || e instanceof ResponseTooLargeException) { + logCancellationProgress(); + throw (RuntimeException) e; } return super.handleFetchingException(environment, params, e); } @SuppressWarnings("Convert2MethodRef") - private void logTimeoutProgress() { + private void logCancellationProgress() { timeoutProgressTracker.startOrStep(m -> LOG.info(m)); } diff --git a/src/main/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapper.java index d0ccb198e16..aac9cb1bb7c 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapper.java @@ -16,6 +16,11 @@ public class ExecutionResultMapper { private static final ErrorClassification API_PROCESSING_TIMEOUT = ErrorClassification.errorClassification( "ApiProcessingTimeout" ); + + private static final ErrorClassification RESPONSE_TOO_LARGE = ErrorClassification.errorClassification( + "ResponseTooLarge" + ); + private static final ErrorClassification BAD_REQUEST_ERROR = ErrorClassification.errorClassification( "BadRequestError" ); @@ -29,13 +34,11 @@ public static Response okResponse(ExecutionResult result) { } public static Response timeoutResponse() { - var error = GraphQLError - .newError() - .errorType(API_PROCESSING_TIMEOUT) - .message(OTPRequestTimeoutException.MESSAGE) - .build(); - var result = ExecutionResult.newExecutionResult().addError(error).build(); - return response(result, OtpHttpStatus.STATUS_UNPROCESSABLE_ENTITY); + return unprocessableResponse(API_PROCESSING_TIMEOUT, OTPRequestTimeoutException.MESSAGE); + } + + public static Response tooLargeResponse(String message) { + return unprocessableResponse(RESPONSE_TOO_LARGE, message); } public static Response badRequestResponse(String message) { @@ -56,4 +59,13 @@ public static Response response(ExecutionResult result, Response.StatusType stat .entity(GraphQLResponseSerializer.serialize(result)) .build(); } + + private static Response unprocessableResponse( + ErrorClassification errorClassification, + String message + ) { + var error = GraphQLError.newError().errorType(errorClassification).message(message).build(); + var result = ExecutionResult.newExecutionResult().addError(error).build(); + return response(result, OtpHttpStatus.STATUS_UNPROCESSABLE_ENTITY); + } } diff --git a/src/main/resources/Message.properties b/src/main/resources/Message.properties index 5c6011e3d3d..931b25c8b3b 100644 --- a/src/main/resources/Message.properties +++ b/src/main/resources/Message.properties @@ -8,7 +8,7 @@ SYSTEM_ERROR = We're sorry. The trip planner is temporarily unavailable. Please GRAPH_UNAVAILABLE = We're sorry. The trip planner is temporarily unavailable. Please try again later. OUTSIDE_BOUNDS = Trip is not possible. You might be trying to plan a trip outside the map data boundary. -PROCESSING_TIMEOUT = The trip planner is taking too long to process your request. +UNPROCESSABLE_REQUEST = The trip planner is taking too long time or too much resources to process your request. BOGUS_PARAMETER = The request has errors that the server is not willing or able to process. LOCATION_NOT_ACCESSIBLE = The location was found, but no stops could be found within the search radius. PATH_NOT_FOUND = No trip found. There may be no transit service within the maximum specified distance or at the specified time, or your start or end point might not be safely accessible. diff --git a/src/main/resources/Message_de.properties b/src/main/resources/Message_de.properties index 1b0d8a2322e..6862a993c71 100644 --- a/src/main/resources/Message_de.properties +++ b/src/main/resources/Message_de.properties @@ -2,7 +2,7 @@ PLAN_OK = Success SYSTEM_ERROR = Es tut uns leid, leider steht der Trip-Planer momentan nicht zur Verfügung. Bitte versuchen Sie es zu einem späteren Zeitpunkt nochmal. OUTSIDE_BOUNDS = Planung nicht möglich. Vielleicht versuchen sie einen Plan außerhalb der Kartengrenzen zu planen. -PROCESSING_TIMEOUT = Der Trip-Planner braucht zu lange um die Anfrage zu bearbeiten +UNPROCESSABLE_REQUEST = Der Trip-Planner braucht zu lange um die Anfrage zu bearbeiten BOGUS_PARAMETER = Die Anfrage ist fehlerhaft so dass sie der Server nicht bearbeiten möchte oder kann. PATH_NOT_FOUND = Planung nicht möglich. Ihr Start- oder Endpunkt könnte nicht erreichbar sein. Bitte stellen sie sicher, dass ihre Anfrage innerhalb der Kartendaten ist. NO_TRANSIT_TIMES = Keine Fahrzeiten verfügbar. Das Datum kann zu weit in der Vergangenheit oder zu weit in der Zukunft liegen oder es gibt keinen Verkehrsbetrieb zu dem von Ihnen gewählten Zeitpunkt. diff --git a/src/main/resources/Message_es.properties b/src/main/resources/Message_es.properties index 0c5fe7a9395..88b7c849acb 100644 --- a/src/main/resources/Message_es.properties +++ b/src/main/resources/Message_es.properties @@ -8,7 +8,7 @@ SYSTEM_ERROR = ES-We're sorry. The trip planner is temporarily unavailable. Plea GRAPH_UNAVAILABLE = ES-We're sorry. The trip planner is temporarily unavailable. Please try again later. OUTSIDE_BOUNDS = ES-los trip is not possible. You might be trying to plan a trip outside the map data boundary. -PROCESSING_TIMEOUT = ES-los trip planner is taking too long to process your request. +UNPROCESSABLE_REQUEST = ES-los trip planner is taking too long time or too much resources to process your request. BOGUS_PARAMETER = ES-los request has errors that the server is not willing or able to process. PATH_NOT_FOUND = ES-los trip is not possible. Please check that you plan is within the bound of the map. NO_TRANSIT_TIMES = ES-Non transit times available. The date may be past or too far in the future or there may not be transit service for your trip at the time you chose. diff --git a/src/main/resources/Message_fr.properties b/src/main/resources/Message_fr.properties index 6f7b73c1997..66ff82bbc59 100644 --- a/src/main/resources/Message_fr.properties +++ b/src/main/resources/Message_fr.properties @@ -8,7 +8,7 @@ SYSTEM_ERROR = Désolé: le calculateur d'itinéraires est temporairement indisp GRAPH_UNAVAILABLE = Désolé: le calculateur d'itinéraires est temporairement indisponible. Veuillez recommencer ultérieurement. OUTSIDE_BOUNDS = Impossible de calculer un itinéraire. Vous essayez de planifier un itinéraire hors des limites de la zone couverte. -PROCESSING_TIMEOUT = Le calculateur d'itinéraires prend trop de temps pour gérer votre demande. +UNPROCESSABLE_REQUEST = Le calculateur d'itinéraires prend trop de temps ou de resources pour gérer votre demande. BOGUS_PARAMETER = Le serveur ne peut pas prendre en compte la requête, elle contient des paramètres erronés. PATH_NOT_FOUND = Impossible de calculer un itinéraire. Veuillez vérifier que le trajet demandé est inclus dans la zone couverte. NO_TRANSIT_TIMES = Aucun voyage disponible. Soit la date est trop loin dans le futur, soit il n'y a pas de service ce jour. diff --git a/src/main/resources/Message_hu.properties b/src/main/resources/Message_hu.properties index eca5fad9b10..f72f2295581 100644 --- a/src/main/resources/Message_hu.properties +++ b/src/main/resources/Message_hu.properties @@ -8,7 +8,7 @@ SYSTEM_ERROR = Sajn\u00E1ljuk. Az \u00FAtvonaltervez\u0151 \u00E1tmenetileg GRAPH_UNAVAILABLE = Sajn\u00E1ljuk. Az \u00FAtvonaltervez\u0151 \u00E1tmenetileg nem el\u00E9rhet\u0151. K\u00E9rj\u00FCk, pr\u00F3b\u00E1lja \u00FAjra k\u00E9s\u0151bb. OUTSIDE_BOUNDS = Az utaz\u00E1s nem lehets\u00E9ges. Lehet, hogy a t\u00E9rk\u00E9padat-hat\u00E1ron k\u00EDv\u00FCli utat pr\u00F3b\u00E1l megtervezni. -PROCESSING_TIMEOUT = Az utaz\u00E1stervez\u0151 t\u00FAl sok\u00E1ig tart a k\u00E9r\u00E9s feldolgoz\u00E1s\u00E1hoz. +UNPROCESSABLE_REQUEST = Az utaz\u00E1stervez\u0151 t\u00FAl sok\u00E1ig tart a k\u00E9r\u00E9s feldolgoz\u00E1s\u00E1hoz. BOGUS_PARAMETER = A k\u00E9r\u00E9s olyan hib\u00E1kat tartalmaz, amelyeket a szerver nem hajland\u00F3 vagy nem k\u00E9pes feldolgozni. LOCATION_NOT_ACCESSIBLE = A helyek megtal\u00E1lhat\u00F3ak, de meg\u00E1ll\u00F3k nem tal\u00E1lhat\u00F3 a keres\u00E9si k\u00F6rzetben. PATH_NOT_FOUND = Nem tal\u00E1lhat\u00F3 utaz\u00E1s. El\u0151fordulhat, hogy a megadott maxim\u00E1lis t\u00E1vols\u00E1gon bel\u00FCl vagy a megadott id\u0151pontban nincs t\u00F6megk\u00F6zleked\u00E9si szolg\u00E1ltat\u00E1s, vagy a kezd\u0151- vagy v\u00E9gpont nem \u00E9rhet\u0151 el biztons\u00E1gosan. diff --git a/src/main/resources/Message_nl.properties b/src/main/resources/Message_nl.properties index 1e18c998264..2a7a1b7700b 100644 --- a/src/main/resources/Message_nl.properties +++ b/src/main/resources/Message_nl.properties @@ -8,7 +8,7 @@ SYSTEM_ERROR = Onze excuses. De routeplanner is momenteel niet beschikbaar. Prob GRAPH_UNAVAILABLE = Onze excuses. De routeplanner is momenteel niet beschikbaar. Probeer later opnieuw. OUTSIDE_BOUNDS = Deze reis is niet mogelijk. Mogelijk probeert u een reis te plannen buiten het beschikbare gebied -PROCESSING_TIMEOUT = De routeplanner is te lang bezig met uw verzoek. +UNPROCESSABLE_REQUEST = De routeplanner is te lang bezig met uw verzoek. BOGUS_PARAMETER = Uw verzoek bevat fouten die de server niet wil or kan verwerken. PATH_NOT_FOUND = Reis is niet mogelijk. Misschien is het startpunt of de bestemming niet veilig toegankelijk. Bijvoorbeeld een straat alleen verbonden met een snelweg. NO_TRANSIT_TIMES = Geen OV-informatie beschikbaar. De datum is mogelijk te ver in het verleden of te ver in de toekomst. Of er is geen dienst voor uw reis op het moment dat u wilt. diff --git a/src/test/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapperTest.java index bce971e9f1e..b697adc9288 100644 --- a/src/test/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/support/ExecutionResultMapperTest.java @@ -34,6 +34,21 @@ class ExecutionResultMapperTest { "}" ); + public static final String TOO_LARGE_MESSAGE = + "The number of fields in the GraphQL result exceeds the maximum allowed: 100000"; + + private static final String TOO_LARGE_RESPONSE = quoteReplace( + "{'" + + "errors':[{" + + "'message':'" + + TOO_LARGE_MESSAGE + + "'," + + "'locations':[]," + + "'extensions':{'classification':'ResponseTooLarge'}" + + "}]" + + "}" + ); + public static final String SYSTEM_ERROR_MESSAGE = "A system error!"; public static final String SYSTEM_ERROR_RESPONSE = quoteReplace( @@ -62,6 +77,13 @@ void timeoutResponse() { assertEquals(TIMEOUT_RESPONSE, response.getEntity().toString()); } + @Test + void tooLargeResponse() { + var response = ExecutionResultMapper.tooLargeResponse(TOO_LARGE_MESSAGE); + assertEquals(422, response.getStatus()); + assertEquals(TOO_LARGE_RESPONSE, response.getEntity().toString()); + } + @Test void systemErrorResponse() { var response = ExecutionResultMapper.systemErrorResponse(SYSTEM_ERROR_MESSAGE); From 675e0b5216b29a37000ddf5b92243b4455146c63 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Fri, 14 Jun 2024 13:15:23 +0200 Subject: [PATCH 016/121] Version 2.6.0-entur-16 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 11818765947..05748f6d4b7 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-16 jar From cc058d8ee752a032bcbebe1ebc579a0f8942b9f0 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 14 Jun 2024 15:54:26 +0200 Subject: [PATCH 017/121] Add timePenalty to Transmodel API --- .../ext/restapi/mapping/ItineraryMapper.java | 5 +- .../transmodel/TransmodelGraphQLSchema.java | 9 ++- .../plan/TripPatternTimePenaltyType.java | 79 +++++++++++++++++++ .../model/plan/TripPatternType.java | 20 ++++- .../model/plan/TripPlanTimePenaltyDto.java | 32 ++++++++ .../apis/transmodel/schema.graphql | 38 +++++++++ .../plan/TripPlanTimePenaltyDtoTest.java | 67 ++++++++++++++++ 7 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternTimePenaltyType.java create mode 100644 src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPlanTimePenaltyDto.java create mode 100644 src/test/java/org/opentripplanner/apis/transmodel/model/plan/TripPlanTimePenaltyDtoTest.java diff --git a/src/ext/java/org/opentripplanner/ext/restapi/mapping/ItineraryMapper.java b/src/ext/java/org/opentripplanner/ext/restapi/mapping/ItineraryMapper.java index d87cf33d71c..720d02fdd44 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/mapping/ItineraryMapper.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/mapping/ItineraryMapper.java @@ -37,7 +37,10 @@ public ApiItinerary mapItinerary(Itinerary domain) { api.transitTime = domain.getTransitDuration().toSeconds(); api.waitingTime = domain.getWaitingDuration().toSeconds(); api.walkDistance = domain.getNonTransitDistanceMeters(); - api.generalizedCost = domain.getGeneralizedCost(); + // We list only the generalizedCostIncludingPenalty, this is the least confusing. We intend to + // delete this endpoint soon, so we will not make the proper change and add the + // generalizedCostIncludingPenalty to the response and update the debug client to show it. + api.generalizedCost = domain.getGeneralizedCostIncludingPenalty(); api.elevationLost = domain.getElevationLost(); api.elevationGained = domain.getElevationGained(); api.transfers = domain.getNumberOfTransfers(); diff --git a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java index 638d7783e9a..a88c36ac039 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java @@ -70,6 +70,7 @@ import org.opentripplanner.apis.transmodel.model.plan.PathGuidanceType; import org.opentripplanner.apis.transmodel.model.plan.PlanPlaceType; import org.opentripplanner.apis.transmodel.model.plan.RoutingErrorType; +import org.opentripplanner.apis.transmodel.model.plan.TripPatternTimePenaltyType; import org.opentripplanner.apis.transmodel.model.plan.TripPatternType; import org.opentripplanner.apis.transmodel.model.plan.TripQuery; import org.opentripplanner.apis.transmodel.model.plan.TripType; @@ -314,6 +315,7 @@ private GraphQLSchema create() { gqlUtil ); + GraphQLObjectType tripPatternTimePenaltyType = TripPatternTimePenaltyType.create(); GraphQLObjectType tripMetadataType = TripMetadataType.create(gqlUtil); GraphQLObjectType placeType = PlanPlaceType.create( bikeRentalStationType, @@ -339,7 +341,12 @@ private GraphQLSchema create() { elevationStepType, gqlUtil ); - GraphQLObjectType tripPatternType = TripPatternType.create(systemNoticeType, legType, gqlUtil); + GraphQLObjectType tripPatternType = TripPatternType.create( + systemNoticeType, + legType, + tripPatternTimePenaltyType, + gqlUtil + ); GraphQLObjectType routingErrorType = RoutingErrorType.create(); GraphQLOutputType tripType = TripType.create( diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternTimePenaltyType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternTimePenaltyType.java new file mode 100644 index 00000000000..1a6e7310697 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternTimePenaltyType.java @@ -0,0 +1,79 @@ +package org.opentripplanner.apis.transmodel.model.plan; + +import graphql.Scalars; +import graphql.schema.DataFetchingEnvironment; +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLObjectType; +import org.opentripplanner.framework.time.DurationUtils; + +public class TripPatternTimePenaltyType { + + public static GraphQLObjectType create() { + return GraphQLObjectType + .newObject() + .name("TimePenalty") + .description( + """ + The time-penalty is applied to either the access-legs and/or egress-legs. Both access and + egress may contain more than one leg; Hence, the penalty is not a field on leg. + + Note! This is for debugging only. This type can change without notice. + """ + ) + .field( + GraphQLFieldDefinition + .newFieldDefinition() + .name("appliedTo") + .description( + """ + The time-penalty is applied to either the access-legs and/or egress-legs. Both access + and egress may contain more than one leg; Hence, the penalty is not a field on leg. The + `appliedTo` describe witch part of the itinerary that this instance applies to. + """ + ) + .type(Scalars.GraphQLString) + .dataFetcher(environment -> penalty(environment).appliesTo()) + .build() + ) + .field( + GraphQLFieldDefinition + .newFieldDefinition() + .name("timePenalty") + .description( + """ + The time-penalty added to the actual time/duration when comparing the itinerary with + other itineraries. This is used to decide witch is the best option, but is not visible + - the actual departure and arrival-times are not modified. + """ + ) + .type(Scalars.GraphQLString) + .dataFetcher(environment -> + DurationUtils.durationToStr(penalty(environment).penalty().time()) + ) + .build() + ) + .field( + GraphQLFieldDefinition + .newFieldDefinition() + .name("generalizedCostPenalty") + .description( + """ + The time-penalty does also propagate to the `generalizedCost` But, while the + arrival-/departure-times listed is not affected, the generalized-cost is. In some cases + the time-penalty-cost is excluded when comparing itineraries - that happens if one of + the itineraries is a "direct/street-only" itinerary. Time-penalty can not be set for + direct searches, so it needs to be excluded from such comparison to be fair. The unit + is transit-seconds. + """ + ) + .type(Scalars.GraphQLInt) + .dataFetcher(environment -> penalty(environment).penalty().cost().toSeconds()) + .build() + ) + .build(); + } + + static TripPlanTimePenaltyDto penalty(DataFetchingEnvironment environment) { + return environment.getSource(); + } +} diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternType.java index 2238a39c139..c903016b91b 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternType.java @@ -16,6 +16,7 @@ public class TripPatternType { public static GraphQLObjectType create( GraphQLOutputType systemNoticeType, GraphQLObjectType legType, + GraphQLObjectType timePenaltyType, GqlUtil gqlUtil ) { return GraphQLObjectType @@ -189,7 +190,7 @@ public static GraphQLObjectType create( .name("generalizedCost") .description("Generalized cost or weight of the itinerary. Used for debugging.") .type(Scalars.GraphQLInt) - .dataFetcher(env -> itinerary(env).getGeneralizedCost()) + .dataFetcher(env -> itinerary(env).getGeneralizedCostIncludingPenalty()) .build() ) .field( @@ -228,6 +229,23 @@ public static GraphQLObjectType create( .dataFetcher(env -> itinerary(env).getTransferPriorityCost()) .build() ) + .field( + GraphQLFieldDefinition + .newFieldDefinition() + .name("timePenalty") + .description( + """ + A time and cost penalty applied to access and egress to favor regular scheduled + transit over potentially faster options with FLEX, Car, bike and scooter. + + Note! This field is meant for debugging only. The field can be removed without notice + in the future. + """ + ) + .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(timePenaltyType)))) + .dataFetcher(env -> TripPlanTimePenaltyDto.of(itinerary(env))) + .build() + ) .build(); } diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPlanTimePenaltyDto.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPlanTimePenaltyDto.java new file mode 100644 index 00000000000..b834b711327 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPlanTimePenaltyDto.java @@ -0,0 +1,32 @@ +package org.opentripplanner.apis.transmodel.model.plan; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; +import org.opentripplanner.framework.model.TimeAndCost; +import org.opentripplanner.model.plan.Itinerary; + +/** + * A simple data-transfer-object used to map from an itinerary to the API specific + * type. It is needed because we need to pass in the "appliedTo" field, which does not + * exist in the domain model. + */ +public record TripPlanTimePenaltyDto(String appliesTo, TimeAndCost penalty) { + static List of(Itinerary itinerary) { + // This check for null to be robust - in case of a mistake in the future. + // The check is redundant on purpose. + if (itinerary == null) { + return List.of(); + } + return Stream + .of(of("access", itinerary.getAccessPenalty()), of("egress", itinerary.getEgressPenalty())) + .filter(Objects::nonNull) + .toList(); + } + + static TripPlanTimePenaltyDto of(String appliedTo, TimeAndCost penalty) { + return penalty == null || penalty.isZero() + ? null + : new TripPlanTimePenaltyDto(appliedTo, penalty); + } +} diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index 1b01b2c2276..5f4768f247f 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -1217,6 +1217,36 @@ type TimeAndDayOffset { time: Time } +""" +The time-penalty is applied to either the access-legs and/or egress-legs. Both access and +egress may contain more than one leg; Hence, the penalty is not a field on leg. + +Note! This is for debugging only. This type can change without notice. +""" +type TimePenalty { + """ + The time-penalty is applied to either the access-legs and/or egress-legs. Both access + and egress may contain more than one leg; Hence, the penalty is not a field on leg. The + `appliedTo` describe witch part of the itinerary that this instance applies to. + """ + appliedTo: String + """ + The time-penalty does also propagate to the `generalizedCost` But, while the + arrival-/departure-times listed is not affected, the generalized-cost is. In some cases + the time-penalty-cost is excluded when comparing itineraries - that happens if one of + the itineraries is a "direct/street-only" itinerary. Time-penalty can not be set for + direct searches, so it needs to be excluded from such comparison to be fair. The unit + is transit-seconds. + """ + generalizedCostPenalty: Int + """ + The time-penalty added to the actual time/duration when comparing the itinerary with + other itineraries. This is used to decide witch is the best option, but is not visible + - the actual departure and arrival-times are not modified. + """ + timePenalty: String +} + "Scheduled passing times. These are not affected by real time updates." type TimetabledPassingTime { "Scheduled time of arrival at quay" @@ -1311,6 +1341,14 @@ type TripPattern { streetDistance: Float "Get all system notices." systemNotices: [SystemNotice!]! + """ + A time and cost penalty applied to access and egress to favor regular scheduled + transit over potentially faster options with FLEX, Car, bike and scooter. + + Note! This field is meant for debugging only. The field can be removed without notice + in the future. + """ + timePenalty: [TimePenalty!]! "A cost calculated to favor transfer with higher priority. This field is meant for debugging only." transferPriorityCost: Int "A cost calculated to distribute wait-time and avoid very short transfers. This field is meant for debugging only." diff --git a/src/test/java/org/opentripplanner/apis/transmodel/model/plan/TripPlanTimePenaltyDtoTest.java b/src/test/java/org/opentripplanner/apis/transmodel/model/plan/TripPlanTimePenaltyDtoTest.java new file mode 100644 index 00000000000..9ea6016324b --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/transmodel/model/plan/TripPlanTimePenaltyDtoTest.java @@ -0,0 +1,67 @@ +package org.opentripplanner.apis.transmodel.model.plan; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.model.Cost; +import org.opentripplanner.framework.model.TimeAndCost; +import org.opentripplanner.framework.time.DurationUtils; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.Place; +import org.opentripplanner.model.plan.TestItineraryBuilder; +import org.opentripplanner.transit.model._data.TransitModelForTest; + +class TripPlanTimePenaltyDtoTest { + + private static final TimeAndCost PENALTY = new TimeAndCost( + DurationUtils.duration("20m30s"), + Cost.costOfSeconds(21) + ); + + private final TransitModelForTest testModel = TransitModelForTest.of(); + private final Place placeA = Place.forStop(testModel.stop("A").build()); + private final Place placeB = Place.forStop(testModel.stop("B").build()); + + @Test + void testCreateFromSingeEntry() { + assertNull(TripPlanTimePenaltyDto.of("access", null)); + assertNull(TripPlanTimePenaltyDto.of("access", TimeAndCost.ZERO)); + assertEquals( + new TripPlanTimePenaltyDto("access", PENALTY), + TripPlanTimePenaltyDto.of("access", PENALTY) + ); + } + + @Test + void testCreateFromItineraryWithNoPenalty() { + var i = itinerary(); + assertEquals(List.of(), TripPlanTimePenaltyDto.of(null)); + assertEquals(List.of(), TripPlanTimePenaltyDto.of(i)); + } + + @Test + void testCreateFromItineraryWithAccess() { + var i = itinerary(); + i.setAccessPenalty(PENALTY); + assertEquals( + List.of(new TripPlanTimePenaltyDto("access", PENALTY)), + TripPlanTimePenaltyDto.of(i) + ); + } + + @Test + void testCreateFromItineraryWithEgress() { + var i = itinerary(); + i.setEgressPenalty(PENALTY); + assertEquals( + List.of(new TripPlanTimePenaltyDto("egress", PENALTY)), + TripPlanTimePenaltyDto.of(i) + ); + } + + private Itinerary itinerary() { + return TestItineraryBuilder.newItinerary(placeA).drive(100, 200, placeB).build(); + } +} From 78337524288da18f9d2ed3f551c336b8c5560693 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 14 Jun 2024 16:27:07 +0200 Subject: [PATCH 018/121] Version 2.6.0-entur-17 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a2ff4ae435c..0427891f01d 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.6.0-entur-11 + 2.6.0-entur-17 jar From 49d52da007ae0244d7ddf96ea6e18472723a4dec Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 14 Jun 2024 15:54:26 +0200 Subject: [PATCH 019/121] Add timePenalty to Transmodel API --- .../ext/restapi/mapping/ItineraryMapper.java | 5 +- .../transmodel/TransmodelGraphQLSchema.java | 9 ++- .../plan/TripPatternTimePenaltyType.java | 79 +++++++++++++++++++ .../model/plan/TripPatternType.java | 20 ++++- .../model/plan/TripPlanTimePenaltyDto.java | 32 ++++++++ .../apis/transmodel/schema.graphql | 38 +++++++++ .../plan/TripPlanTimePenaltyDtoTest.java | 67 ++++++++++++++++ 7 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternTimePenaltyType.java create mode 100644 src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPlanTimePenaltyDto.java create mode 100644 src/test/java/org/opentripplanner/apis/transmodel/model/plan/TripPlanTimePenaltyDtoTest.java diff --git a/src/ext/java/org/opentripplanner/ext/restapi/mapping/ItineraryMapper.java b/src/ext/java/org/opentripplanner/ext/restapi/mapping/ItineraryMapper.java index d87cf33d71c..720d02fdd44 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/mapping/ItineraryMapper.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/mapping/ItineraryMapper.java @@ -37,7 +37,10 @@ public ApiItinerary mapItinerary(Itinerary domain) { api.transitTime = domain.getTransitDuration().toSeconds(); api.waitingTime = domain.getWaitingDuration().toSeconds(); api.walkDistance = domain.getNonTransitDistanceMeters(); - api.generalizedCost = domain.getGeneralizedCost(); + // We list only the generalizedCostIncludingPenalty, this is the least confusing. We intend to + // delete this endpoint soon, so we will not make the proper change and add the + // generalizedCostIncludingPenalty to the response and update the debug client to show it. + api.generalizedCost = domain.getGeneralizedCostIncludingPenalty(); api.elevationLost = domain.getElevationLost(); api.elevationGained = domain.getElevationGained(); api.transfers = domain.getNumberOfTransfers(); diff --git a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java index 638d7783e9a..a88c36ac039 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java @@ -70,6 +70,7 @@ import org.opentripplanner.apis.transmodel.model.plan.PathGuidanceType; import org.opentripplanner.apis.transmodel.model.plan.PlanPlaceType; import org.opentripplanner.apis.transmodel.model.plan.RoutingErrorType; +import org.opentripplanner.apis.transmodel.model.plan.TripPatternTimePenaltyType; import org.opentripplanner.apis.transmodel.model.plan.TripPatternType; import org.opentripplanner.apis.transmodel.model.plan.TripQuery; import org.opentripplanner.apis.transmodel.model.plan.TripType; @@ -314,6 +315,7 @@ private GraphQLSchema create() { gqlUtil ); + GraphQLObjectType tripPatternTimePenaltyType = TripPatternTimePenaltyType.create(); GraphQLObjectType tripMetadataType = TripMetadataType.create(gqlUtil); GraphQLObjectType placeType = PlanPlaceType.create( bikeRentalStationType, @@ -339,7 +341,12 @@ private GraphQLSchema create() { elevationStepType, gqlUtil ); - GraphQLObjectType tripPatternType = TripPatternType.create(systemNoticeType, legType, gqlUtil); + GraphQLObjectType tripPatternType = TripPatternType.create( + systemNoticeType, + legType, + tripPatternTimePenaltyType, + gqlUtil + ); GraphQLObjectType routingErrorType = RoutingErrorType.create(); GraphQLOutputType tripType = TripType.create( diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternTimePenaltyType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternTimePenaltyType.java new file mode 100644 index 00000000000..1a6e7310697 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternTimePenaltyType.java @@ -0,0 +1,79 @@ +package org.opentripplanner.apis.transmodel.model.plan; + +import graphql.Scalars; +import graphql.schema.DataFetchingEnvironment; +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLObjectType; +import org.opentripplanner.framework.time.DurationUtils; + +public class TripPatternTimePenaltyType { + + public static GraphQLObjectType create() { + return GraphQLObjectType + .newObject() + .name("TimePenalty") + .description( + """ + The time-penalty is applied to either the access-legs and/or egress-legs. Both access and + egress may contain more than one leg; Hence, the penalty is not a field on leg. + + Note! This is for debugging only. This type can change without notice. + """ + ) + .field( + GraphQLFieldDefinition + .newFieldDefinition() + .name("appliedTo") + .description( + """ + The time-penalty is applied to either the access-legs and/or egress-legs. Both access + and egress may contain more than one leg; Hence, the penalty is not a field on leg. The + `appliedTo` describe witch part of the itinerary that this instance applies to. + """ + ) + .type(Scalars.GraphQLString) + .dataFetcher(environment -> penalty(environment).appliesTo()) + .build() + ) + .field( + GraphQLFieldDefinition + .newFieldDefinition() + .name("timePenalty") + .description( + """ + The time-penalty added to the actual time/duration when comparing the itinerary with + other itineraries. This is used to decide witch is the best option, but is not visible + - the actual departure and arrival-times are not modified. + """ + ) + .type(Scalars.GraphQLString) + .dataFetcher(environment -> + DurationUtils.durationToStr(penalty(environment).penalty().time()) + ) + .build() + ) + .field( + GraphQLFieldDefinition + .newFieldDefinition() + .name("generalizedCostPenalty") + .description( + """ + The time-penalty does also propagate to the `generalizedCost` But, while the + arrival-/departure-times listed is not affected, the generalized-cost is. In some cases + the time-penalty-cost is excluded when comparing itineraries - that happens if one of + the itineraries is a "direct/street-only" itinerary. Time-penalty can not be set for + direct searches, so it needs to be excluded from such comparison to be fair. The unit + is transit-seconds. + """ + ) + .type(Scalars.GraphQLInt) + .dataFetcher(environment -> penalty(environment).penalty().cost().toSeconds()) + .build() + ) + .build(); + } + + static TripPlanTimePenaltyDto penalty(DataFetchingEnvironment environment) { + return environment.getSource(); + } +} diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternType.java index 2238a39c139..c903016b91b 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPatternType.java @@ -16,6 +16,7 @@ public class TripPatternType { public static GraphQLObjectType create( GraphQLOutputType systemNoticeType, GraphQLObjectType legType, + GraphQLObjectType timePenaltyType, GqlUtil gqlUtil ) { return GraphQLObjectType @@ -189,7 +190,7 @@ public static GraphQLObjectType create( .name("generalizedCost") .description("Generalized cost or weight of the itinerary. Used for debugging.") .type(Scalars.GraphQLInt) - .dataFetcher(env -> itinerary(env).getGeneralizedCost()) + .dataFetcher(env -> itinerary(env).getGeneralizedCostIncludingPenalty()) .build() ) .field( @@ -228,6 +229,23 @@ public static GraphQLObjectType create( .dataFetcher(env -> itinerary(env).getTransferPriorityCost()) .build() ) + .field( + GraphQLFieldDefinition + .newFieldDefinition() + .name("timePenalty") + .description( + """ + A time and cost penalty applied to access and egress to favor regular scheduled + transit over potentially faster options with FLEX, Car, bike and scooter. + + Note! This field is meant for debugging only. The field can be removed without notice + in the future. + """ + ) + .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(timePenaltyType)))) + .dataFetcher(env -> TripPlanTimePenaltyDto.of(itinerary(env))) + .build() + ) .build(); } diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPlanTimePenaltyDto.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPlanTimePenaltyDto.java new file mode 100644 index 00000000000..b834b711327 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripPlanTimePenaltyDto.java @@ -0,0 +1,32 @@ +package org.opentripplanner.apis.transmodel.model.plan; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; +import org.opentripplanner.framework.model.TimeAndCost; +import org.opentripplanner.model.plan.Itinerary; + +/** + * A simple data-transfer-object used to map from an itinerary to the API specific + * type. It is needed because we need to pass in the "appliedTo" field, which does not + * exist in the domain model. + */ +public record TripPlanTimePenaltyDto(String appliesTo, TimeAndCost penalty) { + static List of(Itinerary itinerary) { + // This check for null to be robust - in case of a mistake in the future. + // The check is redundant on purpose. + if (itinerary == null) { + return List.of(); + } + return Stream + .of(of("access", itinerary.getAccessPenalty()), of("egress", itinerary.getEgressPenalty())) + .filter(Objects::nonNull) + .toList(); + } + + static TripPlanTimePenaltyDto of(String appliedTo, TimeAndCost penalty) { + return penalty == null || penalty.isZero() + ? null + : new TripPlanTimePenaltyDto(appliedTo, penalty); + } +} diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index 3462c67e458..e22ded472a6 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -1217,6 +1217,36 @@ type TimeAndDayOffset { time: Time } +""" +The time-penalty is applied to either the access-legs and/or egress-legs. Both access and +egress may contain more than one leg; Hence, the penalty is not a field on leg. + +Note! This is for debugging only. This type can change without notice. +""" +type TimePenalty { + """ + The time-penalty is applied to either the access-legs and/or egress-legs. Both access + and egress may contain more than one leg; Hence, the penalty is not a field on leg. The + `appliedTo` describe witch part of the itinerary that this instance applies to. + """ + appliedTo: String + """ + The time-penalty does also propagate to the `generalizedCost` But, while the + arrival-/departure-times listed is not affected, the generalized-cost is. In some cases + the time-penalty-cost is excluded when comparing itineraries - that happens if one of + the itineraries is a "direct/street-only" itinerary. Time-penalty can not be set for + direct searches, so it needs to be excluded from such comparison to be fair. The unit + is transit-seconds. + """ + generalizedCostPenalty: Int + """ + The time-penalty added to the actual time/duration when comparing the itinerary with + other itineraries. This is used to decide witch is the best option, but is not visible + - the actual departure and arrival-times are not modified. + """ + timePenalty: String +} + "Scheduled passing times. These are not affected by real time updates." type TimetabledPassingTime { "Scheduled time of arrival at quay" @@ -1311,6 +1341,14 @@ type TripPattern { streetDistance: Float "Get all system notices." systemNotices: [SystemNotice!]! + """ + A time and cost penalty applied to access and egress to favor regular scheduled + transit over potentially faster options with FLEX, Car, bike and scooter. + + Note! This field is meant for debugging only. The field can be removed without notice + in the future. + """ + timePenalty: [TimePenalty!]! "A cost calculated to favor transfer with higher priority. This field is meant for debugging only." transferPriorityCost: Int "A cost calculated to distribute wait-time and avoid very short transfers. This field is meant for debugging only." diff --git a/src/test/java/org/opentripplanner/apis/transmodel/model/plan/TripPlanTimePenaltyDtoTest.java b/src/test/java/org/opentripplanner/apis/transmodel/model/plan/TripPlanTimePenaltyDtoTest.java new file mode 100644 index 00000000000..9ea6016324b --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/transmodel/model/plan/TripPlanTimePenaltyDtoTest.java @@ -0,0 +1,67 @@ +package org.opentripplanner.apis.transmodel.model.plan; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.model.Cost; +import org.opentripplanner.framework.model.TimeAndCost; +import org.opentripplanner.framework.time.DurationUtils; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.Place; +import org.opentripplanner.model.plan.TestItineraryBuilder; +import org.opentripplanner.transit.model._data.TransitModelForTest; + +class TripPlanTimePenaltyDtoTest { + + private static final TimeAndCost PENALTY = new TimeAndCost( + DurationUtils.duration("20m30s"), + Cost.costOfSeconds(21) + ); + + private final TransitModelForTest testModel = TransitModelForTest.of(); + private final Place placeA = Place.forStop(testModel.stop("A").build()); + private final Place placeB = Place.forStop(testModel.stop("B").build()); + + @Test + void testCreateFromSingeEntry() { + assertNull(TripPlanTimePenaltyDto.of("access", null)); + assertNull(TripPlanTimePenaltyDto.of("access", TimeAndCost.ZERO)); + assertEquals( + new TripPlanTimePenaltyDto("access", PENALTY), + TripPlanTimePenaltyDto.of("access", PENALTY) + ); + } + + @Test + void testCreateFromItineraryWithNoPenalty() { + var i = itinerary(); + assertEquals(List.of(), TripPlanTimePenaltyDto.of(null)); + assertEquals(List.of(), TripPlanTimePenaltyDto.of(i)); + } + + @Test + void testCreateFromItineraryWithAccess() { + var i = itinerary(); + i.setAccessPenalty(PENALTY); + assertEquals( + List.of(new TripPlanTimePenaltyDto("access", PENALTY)), + TripPlanTimePenaltyDto.of(i) + ); + } + + @Test + void testCreateFromItineraryWithEgress() { + var i = itinerary(); + i.setEgressPenalty(PENALTY); + assertEquals( + List.of(new TripPlanTimePenaltyDto("egress", PENALTY)), + TripPlanTimePenaltyDto.of(i) + ); + } + + private Itinerary itinerary() { + return TestItineraryBuilder.newItinerary(placeA).drive(100, 200, placeB).build(); + } +} From ab35129e0bef3575b25d241d8f74a545eabe5104 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 18 Jun 2024 13:50:42 +0200 Subject: [PATCH 020/121] Version 2.6.0-entur-18 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 05748f6d4b7..eddeeded25b 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.6.0-entur-16 + 2.6.0-entur-18 jar From 8e8af1b0e075bbd093f8aed1e307fef644cc88a6 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 021/121] 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 62f6155fd10..720176e6332 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 151 + EN-0069 31.2 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 becd86528f5058762909bd07988f8f8837e7903b Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 19 Dec 2023 21:26:49 +0100 Subject: [PATCH 022/121] =?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 05611e23628..2f34b598fa7 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 0fd9ec47f89..790984ac02c 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 3ae029fdb2d..b4912aacb1d 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java @@ -203,6 +203,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 5ed3264a4fd..26676d96c7b 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 @@ -616,6 +616,14 @@ Normally this is when the search is performed (now), plus a small grace period t ) .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 29489de19f2..d7bd15922fc 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 6a1404c3039..eaab9741570 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 f9ea136fbe2..df1ee7bb0eb 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -316,13 +316,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)`. @@ -332,10 +333,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 fbaa2418d7e..3462c67e458 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -805,6 +805,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 185fef2ce3ea001b5e24a9c760f048f955770558 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 20 Jun 2024 10:18:18 +0200 Subject: [PATCH 023/121] Version 2.6.0-entur-19 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 720176e6332..207d7e7969e 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-19 jar From 1de0f40c302ebd5f02a8bef2706d9df4407982ae Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 20 Jun 2024 16:42:23 +0200 Subject: [PATCH 024/121] debug: Log undos from KeepItinerariesWithFewestTransfers --- .../transit/KeepItinerariesWithFewestTransfers.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/KeepItinerariesWithFewestTransfers.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/KeepItinerariesWithFewestTransfers.java index 3d1e6301786..deb368959fc 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/KeepItinerariesWithFewestTransfers.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/KeepItinerariesWithFewestTransfers.java @@ -8,6 +8,8 @@ import org.opentripplanner.model.SystemNotice; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryListFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This filter makes sure that the itinerary with the fewest transfers is not removed. @@ -16,6 +18,7 @@ * keys are removed. Itineraries with other system notices are ignored. */ public class KeepItinerariesWithFewestTransfers implements ItineraryListFilter { + private static final Logger LOG = LoggerFactory.getLogger(KeepItinerariesWithFewestTransfers.class); private final Set filterKeys; @@ -31,7 +34,10 @@ public List filter(List itineraries) { .filter(it -> filterKeys.containsAll(it.getSystemNotices().stream().map(SystemNotice::tag).toList()) ) - .ifPresent(it -> it.removeDeletionFlags(filterKeys)); + .ifPresent(it -> { + LOG.debug("Include TxMin Itinerary. Remove {} from {}.", filterKeys, it); + it.removeDeletionFlags(filterKeys); + }); return itineraries; } From 926851ba59be695436dd3041feba9f7263178ab0 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 20 Jun 2024 16:44:31 +0200 Subject: [PATCH 025/121] Version 2.6.0-entur-20 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 207d7e7969e..e288820233a 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.6.0-entur-19 + 2.6.0-entur-20 jar From 70cb03ebc1f65ee631e9205826bb9108fbb71d73 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 24 Jun 2024 12:05:00 +0200 Subject: [PATCH 026/121] Update Siri Google PubSub updater documentation --- .../sandbox/siri/SiriGooglePubSubUpdater.md | 29 +++++ docs/RouterConfiguration.md | 11 ++ docs/examples/entur/router-config.json | 3 +- docs/sandbox/VehicleParking.md | 18 +-- docs/sandbox/siri/SiriGooglePubSubUpdater.md | 118 ++++++++++++++++++ .../SiriETGooglePubsubUpdaterParameters.java | 20 +-- .../SiriETGooglePubsubUpdaterConfig.java | 79 ++++++++++-- .../doc/SiriGooglePubSubConfigDocTest.java | 101 +++++++++++++++ .../generate/doc/UpdaterConfigDocTest.java | 1 + .../standalone/config/router-config.json | 12 ++ 10 files changed, 353 insertions(+), 39 deletions(-) create mode 100644 doc-templates/sandbox/siri/SiriGooglePubSubUpdater.md create mode 100644 docs/sandbox/siri/SiriGooglePubSubUpdater.md create mode 100644 src/test/java/org/opentripplanner/generate/doc/SiriGooglePubSubConfigDocTest.java diff --git a/doc-templates/sandbox/siri/SiriGooglePubSubUpdater.md b/doc-templates/sandbox/siri/SiriGooglePubSubUpdater.md new file mode 100644 index 00000000000..09fd3996bef --- /dev/null +++ b/doc-templates/sandbox/siri/SiriGooglePubSubUpdater.md @@ -0,0 +1,29 @@ +# Siri-ET Google PubSub Updater + +Support for consuming SIRI-ET messages over a Google Cloud PubSub subscription. +Similarly to the SIRI-ET HTTP updater, this updater is developed to support the Nordic SIRI profile +which is a subset of the SIRI specification. + +## Contact Info +Entur, Norway +https://entur.no/ + +## Documentation + +This updater consumes SIRI real time information over an asynchronous publisher/subscriber feed +provided by a Google Cloud PubSub topic. + +For more documentation see +the [Entur Real-Time Data](https://developer.entur.org/pages-real-time-intro) documentation and +the [Norwegian SIRI profile](https://enturas.atlassian.net/wiki/spaces/PUBLIC/pages/637370420/Norwegian+SIRI+profile) +. + +## Configuration + +To enable the SIRI-ET Google PubSub updater you need to add it to the updaters section +of the `router-config.json`. + +### Siri-ET via Google PubSub + + + diff --git a/docs/RouterConfiguration.md b/docs/RouterConfiguration.md index 19a70c44d0f..0891bf6d20d 100644 --- a/docs/RouterConfiguration.md +++ b/docs/RouterConfiguration.md @@ -859,6 +859,17 @@ Used to group requests when monitoring OTP. "timeout" : 300000 } }, + { + "type" : "siri-et-google-pubsub-updater", + "feedId" : "feed_id", + "reconnectPeriod" : "5s", + "initialGetDataTimeout" : "1m20s", + "topicProjectName" : "google_pubsub_topic_project_name", + "subscriptionProjectName" : "google_pubsub_subscription_project_name", + "topicName" : "estimated_timetables", + "dataInitializationUrl" : "https://example.com/some/path", + "fuzzyTripMatching" : true + }, { "type" : "vehicle-parking", "feedId" : "bikeep", diff --git a/docs/examples/entur/router-config.json b/docs/examples/entur/router-config.json index 0cbded8bc85..a54e40eb44d 100644 --- a/docs/examples/entur/router-config.json +++ b/docs/examples/entur/router-config.json @@ -123,8 +123,9 @@ { "type": "siri-et-google-pubsub-updater", "feedId": "EN", - "projectName": "entur-ror", + "topicProjectName": "entur-anshar", "topicName": "estimated_timetables", + "subscriptionProjectName": "entur-otp2", "dataInitializationUrl": "https://example.com" }, // SIRI ET updater diff --git a/docs/sandbox/VehicleParking.md b/docs/sandbox/VehicleParking.md index 8d9e4942440..a5adde1d4c2 100644 --- a/docs/sandbox/VehicleParking.md +++ b/docs/sandbox/VehicleParking.md @@ -320,36 +320,36 @@ HTTP headers to add to the request. Any header key, value can be inserted. | Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | |----------------------------------|:---------------:|------------------------------------------------------------------------------|:----------:|---------------|:-----:| | type = "vehicle-parking" | `enum` | The type of the updater. | *Required* | | 1.5 | -| [feedId](#u__13__feedId) | `string` | The id of the data source, which will be the prefix of the parking lot's id. | *Required* | | 2.2 | +| [feedId](#u__14__feedId) | `string` | The id of the data source, which will be the prefix of the parking lot's id. | *Required* | | 2.2 | | frequency | `duration` | How often to update the source. | *Optional* | `"PT1M"` | 2.6 | -| [sourceType](#u__13__sourceType) | `enum` | The source of the vehicle updates. | *Required* | | 2.2 | +| [sourceType](#u__14__sourceType) | `enum` | The source of the vehicle updates. | *Required* | | 2.2 | | url | `uri` | URL of the locations endpoint. | *Required* | | 2.6 | -| [headers](#u__13__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.6 | +| [headers](#u__14__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.6 | #### Details -

feedId

+

feedId

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

sourceType

+

sourceType

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

headers

+

headers

**Since version:** `2.6` ∙ **Type:** `map of string` ∙ **Cardinality:** `Optional` -**Path:** /updaters/[13] +**Path:** /updaters/[14] HTTP headers to add to the request. Any header key, value can be inserted. diff --git a/docs/sandbox/siri/SiriGooglePubSubUpdater.md b/docs/sandbox/siri/SiriGooglePubSubUpdater.md new file mode 100644 index 00000000000..9eee17c5900 --- /dev/null +++ b/docs/sandbox/siri/SiriGooglePubSubUpdater.md @@ -0,0 +1,118 @@ +# Siri-ET Google PubSub Updater + +Support for consuming SIRI-ET messages over a Google Cloud PubSub subscription. +Similarly to the SIRI-ET HTTP updater, this updater is developed to support the Nordic SIRI profile +which is a subset of the SIRI specification. + +## Contact Info +Entur, Norway +https://entur.no/ + +## Documentation + +This updater consumes SIRI real time information over an asynchronous publisher/subscriber feed +provided by a Google Cloud PubSub topic. + +For more documentation see +the [Entur Real-Time Data](https://developer.entur.org/pages-real-time-intro) documentation and +the [Norwegian SIRI profile](https://enturas.atlassian.net/wiki/spaces/PUBLIC/pages/637370420/Norwegian+SIRI+profile) +. + +## Configuration + +To enable the SIRI-ET Google PubSub updater you need to add it to the updaters section +of the `router-config.json`. + +### Siri-ET via Google PubSub + + + + +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|------------------------------------------------------------|:----------:|----------------------------------------------------------------------------------|:----------:|---------------|:-----:| +| type = "siri-et-google-pubsub-updater" | `enum` | The type of the updater. | *Required* | | 1.5 | +| [dataInitializationUrl](#u__13__dataInitializationUrl) | `string` | URL used to download over HTTP the recent history of SIRI-ET messages. | *Optional* | | na | +| feedId | `string` | The ID of the feed to apply the updates to. | *Optional* | | na | +| fuzzyTripMatching | `boolean` | If the trips should be matched fuzzily. | *Optional* | `false` | na | +| [initialGetDataTimeout](#u__13__initialGetDataTimeout) | `duration` | Timeout for retrieving the recent history of SIRI-ET messages. | *Optional* | `"PT30S"` | na | +| [reconnectPeriod](#u__13__reconnectPeriod) | `duration` | Wait this amount of time before trying to reconnect to the PubSub subscription. | *Optional* | `"PT30S"` | na | +| [subscriptionProjectName](#u__13__subscriptionProjectName) | `string` | The Google Cloud project that hosts the PubSub subscription. | *Required* | | na | +| topicName | `string` | The name of the PubSub topic that publishes the updates. | *Required* | | na | +| topicProjectName | `string` | The Google Cloud project that hosts the PubSub topic that publishes the updates. | *Required* | | na | + + +##### Parameter details + +

dataInitializationUrl

+ +**Since version:** `na` ∙ **Type:** `string` ∙ **Cardinality:** `Optional` +**Path:** /updaters/[13] + +URL used to download over HTTP the recent history of SIRI-ET messages. + +Optionally the updater can download the recent history of SIRI-ET messages from this URL. +If this parameter is set, the updater will be marked as initialized (primed) only when +the message history is fully downloaded and applied. + + +

initialGetDataTimeout

+ +**Since version:** `na` ∙ **Type:** `duration` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"PT30S"` +**Path:** /updaters/[13] + +Timeout for retrieving the recent history of SIRI-ET messages. + +When trying to fetch the message history over HTTP, the updater will wait this amount +of time for the connection to be established. +If the connection times out, the updater will retry indefinitely with exponential backoff. + + +

reconnectPeriod

+ +**Since version:** `na` ∙ **Type:** `duration` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"PT30S"` +**Path:** /updaters/[13] + +Wait this amount of time before trying to reconnect to the PubSub subscription. + +In case of a network error, the updater will try periodically to reconnect to the +Google PubSub subscription. + + +

subscriptionProjectName

+ +**Since version:** `na` ∙ **Type:** `string` ∙ **Cardinality:** `Required` +**Path:** /updaters/[13] + +The Google Cloud project that hosts the PubSub subscription. + +During startup, the updater creates a PubSub subscription that listens +to the PubSub topic that publishes SIRI-ET updates. +This parameter specifies in which Google Cloud project the subscription will be created. +The topic and the subscription can be hosted in two different projects. + + + + +##### Example configuration + +```JSON +// router-config.json +{ + "updaters" : [ + { + "type" : "siri-et-google-pubsub-updater", + "feedId" : "feed_id", + "reconnectPeriod" : "5s", + "initialGetDataTimeout" : "1m20s", + "topicProjectName" : "google_pubsub_topic_project_name", + "subscriptionProjectName" : "google_pubsub_subscription_project_name", + "topicName" : "estimated_timetables", + "dataInitializationUrl" : "https://example.com/some/path", + "fuzzyTripMatching" : true + } + ] +} +``` + + + diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java index 24ff12f6bd4..c9e070311c6 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java @@ -10,15 +10,12 @@ public record SiriETGooglePubsubUpdaterParameters( @Nonnull String configRef, @Nullable String feedId, - String type, - @Deprecated String projectName, String subscriptionProjectName, String topicProjectName, String topicName, @Nullable String dataInitializationUrl, Duration reconnectPeriod, Duration initialGetDataTimeout, - boolean purgeExpiredData, boolean fuzzyTripMatching ) implements UrlUpdaterParameters { @@ -26,16 +23,6 @@ public record SiriETGooglePubsubUpdaterParameters( public static Duration INITIAL_GET_DATA_TIMEOUT = Duration.ofSeconds(30); public SiriETGooglePubsubUpdaterParameters { - Objects.requireNonNull(type); - - if (subscriptionProjectName == null && topicProjectName == null) { - // New config-parameters not yet in use - // TODO: Remove deprecated `projectName` when config is updated - Objects.requireNonNull(projectName); - subscriptionProjectName = projectName; - topicProjectName = projectName; - } - Objects.requireNonNull(subscriptionProjectName); Objects.requireNonNull(topicProjectName); Objects.requireNonNull(topicName); @@ -50,14 +37,11 @@ public String toString() { .of(SiriETGooglePubsubUpdaterParameters.class) .addObj("configRef", configRef, null) .addObj("feedId", feedId, null) - .addObj("type", type) - .addObj("projectName", projectName) - .addObj("subscriptionProjectName", subscriptionProjectName, projectName) - .addObj("topicProjectName", topicProjectName, projectName) + .addObj("subscriptionProjectName", subscriptionProjectName) + .addObj("topicProjectName", topicProjectName) .addObj("topicName", topicName) .addDuration("reconnectPeriod", reconnectPeriod, RECONNECT_PERIOD) .addDuration("initialGetDataTimeout", initialGetDataTimeout, INITIAL_GET_DATA_TIMEOUT) - .addBoolIfTrue("purgeExpiredData", purgeExpiredData) .addBoolIfTrue("fuzzyTripMatching", fuzzyTripMatching) .addObj("dataInitializationUrl", dataInitializationUrl, null) .toString(); diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java index 45221785109..25fbd235cd3 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java @@ -12,17 +12,74 @@ public class SiriETGooglePubsubUpdaterConfig { public static SiriETGooglePubsubUpdaterParameters create(String configRef, NodeAdapter c) { return new SiriETGooglePubsubUpdaterParameters( configRef, - c.of("feedId").since(NA).summary("TODO").asString(null), - c.of("type").since(NA).summary("TODO").asString(), - c.of("projectName").since(NA).summary("TODO").asString(null), // TODO: Remove (deprecated) - c.of("subscriptionProjectName").since(NA).summary("TODO").asString(null), // TODO: Set as required - c.of("topicProjectName").since(NA).summary("TODO").asString(null), // TODO: Set as required - c.of("topicName").since(NA).summary("TODO").asString(), - c.of("dataInitializationUrl").since(NA).summary("TODO").asString(null), - c.of("reconnectPeriod").since(NA).summary("TODO").asDuration(RECONNECT_PERIOD), - c.of("initialGetDataTimeout").since(NA).summary("TODO").asDuration(INITIAL_GET_DATA_TIMEOUT), - c.of("purgeExpiredData").since(NA).summary("TODO").asBoolean(false), - c.of("fuzzyTripMatching").since(NA).summary("TODO").asBoolean(false) + c + .of("feedId") + .since(NA) + .summary("The ID of the feed to apply the updates to.") + .asString(null), + c + .of("subscriptionProjectName") + .since(NA) + .summary("The Google Cloud project that hosts the PubSub subscription.") + .description( + """ + During startup, the updater creates a PubSub subscription that listens + to the PubSub topic that publishes SIRI-ET updates. + This parameter specifies in which Google Cloud project the subscription will be created. + The topic and the subscription can be hosted in two different projects. + """ + ) + .asString(), + c + .of("topicProjectName") + .since(NA) + .summary("The Google Cloud project that hosts the PubSub topic that publishes the updates.") + .asString(), + c + .of("topicName") + .since(NA) + .summary("The name of the PubSub topic that publishes the updates.") + .asString(), + c + .of("dataInitializationUrl") + .since(NA) + .summary("URL used to download over HTTP the recent history of SIRI-ET messages.") + .description( + """ + Optionally the updater can download the recent history of SIRI-ET messages from this URL. + If this parameter is set, the updater will be marked as initialized (primed) only when + the message history is fully downloaded and applied. + """ + ) + .asString(null), + c + .of("reconnectPeriod") + .since(NA) + .summary("Wait this amount of time before trying to reconnect to the PubSub subscription.") + .description( + """ + In case of a network error, the updater will try periodically to reconnect to the + Google PubSub subscription. + """ + ) + .asDuration(RECONNECT_PERIOD), + c + .of("initialGetDataTimeout") + .since(NA) + .summary("Timeout for retrieving the recent history of SIRI-ET messages.") + .description( + """ + When trying to fetch the message history over HTTP, the updater will wait this amount + of time for the connection to be established. + If the connection times out, the updater will retry indefinitely with exponential backoff. + """ + ) + .asDuration(INITIAL_GET_DATA_TIMEOUT), + c + .of("fuzzyTripMatching") + .since(NA) + .summary("If the trips should be matched fuzzily.") + .asBoolean(false) ); } } diff --git a/src/test/java/org/opentripplanner/generate/doc/SiriGooglePubSubConfigDocTest.java b/src/test/java/org/opentripplanner/generate/doc/SiriGooglePubSubConfigDocTest.java new file mode 100644 index 00000000000..f5aa3e130ed --- /dev/null +++ b/src/test/java/org/opentripplanner/generate/doc/SiriGooglePubSubConfigDocTest.java @@ -0,0 +1,101 @@ +package org.opentripplanner.generate.doc; + +import static org.opentripplanner.framework.application.OtpFileNames.ROUTER_CONFIG_FILENAME; +import static org.opentripplanner.framework.io.FileUtils.assertFileEquals; +import static org.opentripplanner.framework.io.FileUtils.readFile; +import static org.opentripplanner.framework.io.FileUtils.writeFile; +import static org.opentripplanner.framework.text.MarkdownFormatter.HEADER_4; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceSection; +import static org.opentripplanner.standalone.config.framework.json.JsonSupport.jsonNodeFromResource; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.File; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.opentripplanner.generate.doc.framework.DocBuilder; +import org.opentripplanner.generate.doc.framework.GeneratesDocumentation; +import org.opentripplanner.generate.doc.framework.ParameterDetailsList; +import org.opentripplanner.generate.doc.framework.ParameterSummaryTable; +import org.opentripplanner.generate.doc.framework.SkipNodes; +import org.opentripplanner.standalone.config.RouterConfig; +import org.opentripplanner.standalone.config.framework.json.NodeAdapter; + +@GeneratesDocumentation +public class SiriGooglePubSubConfigDocTest { + + private static final File TEMPLATE = new File( + TEMPLATE_ROOT, + "sandbox/siri/SiriGooglePubSubUpdater.md" + ); + private static final File OUT_FILE = new File( + DOCS_ROOT, + "sandbox/siri/SiriGooglePubSubUpdater.md" + ); + + private static final String ROUTER_CONFIG_PATH = "standalone/config/" + ROUTER_CONFIG_FILENAME; + private static final Set INCLUDE_UPDATERS = Set.of("siri-et-google-pubsub-updater"); + private static final SkipNodes SKIP_NODES = SkipNodes.of().build(); + public static final ObjectMapper mapper = new ObjectMapper(); + + /** + * NOTE! This test updates the {@code docs/sandbox/SiriGooglePubSubUpdater.md} document based on the latest + * version of the code. + */ + @Test + public void updateSiriDoc() { + NodeAdapter node = readUpdaterConfig(); + + // Read and close input file (same as output file) + String template = readFile(TEMPLATE); + String original = readFile(OUT_FILE); + + for (String childName : node.listChildrenByName()) { + var child = node.child(childName); + var type = child.typeQualifier(); + + if (INCLUDE_UPDATERS.contains(type)) { + template = replaceSection(template, type, updaterDoc(child)); + } + } + + writeFile(OUT_FILE, template); + assertFileEquals(original, OUT_FILE); + } + + private NodeAdapter readUpdaterConfig() { + var json = jsonNodeFromResource(ROUTER_CONFIG_PATH); + var conf = new RouterConfig(json, ROUTER_CONFIG_PATH, false); + return conf.asNodeAdapter().child("updaters"); + } + + private String updaterDoc(NodeAdapter node) { + DocBuilder buf = new DocBuilder(); + addParameterSummaryTable(buf, node); + addDetailsSection(buf, node); + addExample(buf, node); + return buf.toString(); + } + + private void addParameterSummaryTable(DocBuilder buf, NodeAdapter node) { + buf.addSection(new ParameterSummaryTable(SKIP_NODES).createTable(node).toMarkdownTable()); + } + + private void addDetailsSection(DocBuilder buf, NodeAdapter node) { + String details = getParameterDetailsTable(node); + + if (!details.isBlank()) { + buf.header(5, "Parameter details", null).addSection(details); + } + } + + private String getParameterDetailsTable(NodeAdapter node) { + return ParameterDetailsList.listParametersWithDetails(node, SKIP_NODES, HEADER_4); + } + + private void addExample(DocBuilder buf, NodeAdapter node) { + buf.addSection("##### Example configuration"); + buf.addUpdaterExample(ROUTER_CONFIG_FILENAME, node.rawNode()); + } +} diff --git a/src/test/java/org/opentripplanner/generate/doc/UpdaterConfigDocTest.java b/src/test/java/org/opentripplanner/generate/doc/UpdaterConfigDocTest.java index 4bdfe782615..264d0d6850a 100644 --- a/src/test/java/org/opentripplanner/generate/doc/UpdaterConfigDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/UpdaterConfigDocTest.java @@ -34,6 +34,7 @@ public class UpdaterConfigDocTest { "siri-azure-et-updater", "vehicle-parking", "siri-et-updater", + "siri-et-google-pubsub-updater", "siri-sx-updater" ); private static final SkipNodes SKIP_NODES = SkipNodes.of().build(); diff --git a/src/test/resources/standalone/config/router-config.json b/src/test/resources/standalone/config/router-config.json index cee86fafa2e..e6285a77d41 100644 --- a/src/test/resources/standalone/config/router-config.json +++ b/src/test/resources/standalone/config/router-config.json @@ -421,6 +421,18 @@ "timeout": 300000 } }, + // SIRI ET Google Pubsub updater + { + "type": "siri-et-google-pubsub-updater", + "feedId": "feed_id", + "reconnectPeriod": "5s", + "initialGetDataTimeout": "1m20s", + "topicProjectName": "google_pubsub_topic_project_name", + "subscriptionProjectName": "google_pubsub_subscription_project_name", + "topicName": "estimated_timetables", + "dataInitializationUrl": "https://example.com/some/path", + "fuzzyTripMatching": true + }, { "type": "vehicle-parking", "feedId": "bikeep", From 4e13672bb501832f2e644421d506fd7b45f5caef 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 027/121] 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 62f6155fd10..720176e6332 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 151 + EN-0069 31.2 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 28bc46080bbe684d8298f1a03a1062370caa66f8 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 19 Dec 2023 21:26:49 +0100 Subject: [PATCH 028/121] =?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 05611e23628..2f34b598fa7 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 0fd9ec47f89..790984ac02c 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 3ae029fdb2d..b4912aacb1d 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java @@ -203,6 +203,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 5ed3264a4fd..26676d96c7b 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 @@ -616,6 +616,14 @@ Normally this is when the search is performed (now), plus a small grace period t ) .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 29489de19f2..d7bd15922fc 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 06f4ff1cf45..13be34d5791 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 f9ea136fbe2..df1ee7bb0eb 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -316,13 +316,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)`. @@ -332,10 +333,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 fbaa2418d7e..3462c67e458 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -805,6 +805,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 5d57d378390c3653cd9b651e0dcfa4f778348256 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 24 Jun 2024 13:02:13 +0200 Subject: [PATCH 029/121] Fix documentation --- docs/Configuration.md | 63 ++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index 2f34b598fa7..26cdfb7c5b0 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -219,37 +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. | ✓️ | | -| `APIUpdaterStatus` | Enable endpoint for graph updaters status. | ✓️ | | -| `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. | | | -| `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). Turning this _off_ will increase the routing performance a little. | ✓️ | | -| `TransmodelGraphQlApi` | Enable the [Transmodel (NeTEx) GraphQL API](apis/TransmodelApi.md). | ✓️ | ✓️ | -| `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. | | ✓️ | -| `LegacyRestApi` | Enable legacy REST API. This API will be removed in the future. | ✓️ | ✓️ | -| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with real-time data | | ✓️ | -| `ReportApi` | Enable the report API. | | ✓️ | -| `RestAPIPassInDefaultConfigAsJson` | Enable a default RouteRequest to be passed in as JSON on the REST API - FOR DEBUGGING ONLY! | | | -| `SandboxAPIGeocoder` | Enable the Geocoder API. | | ✓️ | -| `SandboxAPIMapboxVectorTilesApi` | Enable Mapbox vector tiles API. | | ✓️ | -| `SandboxAPIParkAndRideApi` | Enable park-and-ride endpoint. | | ✓️ | -| `TransferAnalyzer` | Analyze transfers during graph build. | | ✓️ | +| Feature | Description | Enabled by default | Sandbox | +|--------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------:|:-------:| +| `APIBikeRental` | Enable the bike rental endpoint. | ✓️ | | +| `APIServerInfo` | Enable the server info endpoint. | ✓️ | | +| `APIUpdaterStatus` | Enable endpoint for graph updaters status. | ✓️ | | +| `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. | | | +| `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). Turning this _off_ will increase the routing performance a little. | ✓️ | | +| `TransmodelGraphQlApi` | Enable the [Transmodel (NeTEx) GraphQL API](apis/TransmodelApi.md). | ✓️ | ✓️ | +| `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. | | ✓️ | +| `LegacyRestApi` | Enable legacy REST API. This API will be removed in the future. | ✓️ | ✓️ | +| `MultiCriteriaGroupMaxFilter` | Keep the best itinerary with respect to each criteria used in the transit-routing search. For example the itinerary with the lowest cost, fewest transfers, and each unique transit-group (transit-group-priority) is kept, even if the max-limit is exceeded. This is turned off by default for now, until this feature is well tested. | | | +| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with real-time data | | ✓️ | +| `ReportApi` | Enable the report API. | | ✓️ | +| `RestAPIPassInDefaultConfigAsJson` | Enable a default RouteRequest to be passed in as JSON on the REST API - FOR DEBUGGING ONLY! | | | +| `SandboxAPIGeocoder` | Enable the Geocoder API. | | ✓️ | +| `SandboxAPIMapboxVectorTilesApi` | Enable Mapbox vector tiles API. | | ✓️ | +| `SandboxAPIParkAndRideApi` | Enable park-and-ride endpoint. | | ✓️ | +| `TransferAnalyzer` | Analyze transfers during graph build. | | ✓️ | From ed81f4c6f5f76691892cdff2b95e0ee1710ea7fb Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 24 Jun 2024 13:08:57 +0200 Subject: [PATCH 030/121] Version 2.6.0-entur-21 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 720176e6332..d27f81ff8da 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-21 jar From ed16338182948800ac83dedb8df8487da2795d41 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 25 Jun 2024 17:00:45 +0200 Subject: [PATCH 031/121] Version 2.6.0-entur-22 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d27f81ff8da..7d09e378e6c 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.6.0-entur-21 + 2.6.0-entur-22 jar From f8f854daa79491916445b46bca737bf93f767ed0 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 26 Jun 2024 16:13:18 +0200 Subject: [PATCH 032/121] refactor: Add logging for critical error in GroupByFilter --- .../system/SingeCriteriaComparator.java | 21 +++++++++++++++++-- .../framework/filter/GroupByFilter.java | 6 +++++- .../framework/filter/RemoveFilter.java | 5 +++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingeCriteriaComparator.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingeCriteriaComparator.java index 528542d40df..2e36743307c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingeCriteriaComparator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingeCriteriaComparator.java @@ -45,11 +45,28 @@ static SingeCriteriaComparator compareGeneralizedCost() { @SuppressWarnings("OptionalGetWithoutIsPresent") static SingeCriteriaComparator compareTransitGroupsPriority() { - return (left, right) -> - TransitGroupPriority32n.dominate( + return (left, right) -> { + if (left.getGeneralizedCost2().isEmpty()) { + throw new IllegalArgumentException( + "C2 used in context where it does not exist. Itinerary: " + + left.toStr() + + " compared with: " + + right.toStr() + ); + } + if (right.getGeneralizedCost2().isEmpty()) { + throw new IllegalArgumentException( + "C2 used in context where it does not exist. Itinerary: " + + right.toStr() + + " compared with: " + + left.toStr() + ); + } + return TransitGroupPriority32n.dominate( left.getGeneralizedCost2().get(), right.getGeneralizedCost2().get() ); + }; } static SingeCriteriaComparator compareLessThan(final ToIntFunction op) { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/GroupByFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/GroupByFilter.java index 73f84625750..e136bf67223 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/GroupByFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/GroupByFilter.java @@ -57,7 +57,11 @@ public final List filter(List itineraries) { for (Entry e : groups) { List groupResult = e.itineraries; for (ItineraryListFilter filter : nestedFilters) { - groupResult = filter.filter(groupResult); + try { + groupResult = filter.filter(groupResult); + } catch (RuntimeException ex) { + throw new IllegalStateException("Error in filter '" + filter.toString() + "'", ex); + } } result.addAll(groupResult); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/RemoveFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/RemoveFilter.java index e83aa237cab..0ef9b8893fb 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/RemoveFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/filter/RemoveFilter.java @@ -49,4 +49,9 @@ public List filter(List itineraries) { return itineraries; } + + @Override + public String toString() { + return name(); + } } From 20bfd472961d5180d0eb0d3c6ff72739e83ac0ac Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 26 Jun 2024 16:15:13 +0200 Subject: [PATCH 033/121] Version 2.6.0-entur-23 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7d09e378e6c..fbe370a271c 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.6.0-entur-22 + 2.6.0-entur-23 jar From e193e7e763c0039bedd776f1827c3ee606481aff Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Thu, 27 Jun 2024 10:24:41 +0200 Subject: [PATCH 034/121] Version 2.6.0-entur-24 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index aea1820e240..0735ceaf7d0 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.6.0-entur-18 + 2.6.0-entur-24 jar From cc28a610aafeb115dc5526ae3d7f8ec8f08a5338 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 28 Jun 2024 17:20:43 +0200 Subject: [PATCH 035/121] Add group priority to direct search #5945 (squashed cf87f165..de7c6989) --- .../flex/trip/ScheduledDeviatedTripTest.java | 6 +- .../model/TransitGroupPriorityReport.java | 6 +- .../opentripplanner/model/plan/Itinerary.java | 203 +++++++++--------- .../org/opentripplanner/model/plan/Leg.java | 1 + ...ransitGroupPriorityItineraryDecorator.java | 49 +++++ .../api/request/MultiCriteriaRequest.java | 10 +- ...RaptorTransitGroupPriorityCalculator.java} | 2 +- .../configure/McRangeRaptorConfig.java | 4 +- .../c2/TransitGroupPriorityRideFactory.java | 6 +- .../routing/algorithm/RoutingWorker.java | 13 ++ .../raptoradapter/router/TransitRouter.java | 7 + .../transit/mappers/RaptorRequestMapper.java | 5 +- .../request/PriorityGroupConfigurator.java | 144 ------------- .../RaptorRoutingRequestTransitData.java | 19 +- ...aptorRoutingRequestTransitDataCreator.java | 9 +- .../grouppriority}/BinarySetOperator.java | 2 +- ...DefaultTransitGroupPriorityCalculator.java | 26 +++ .../network/grouppriority/EntityAdapter.java | 16 ++ .../model/network/grouppriority/Matcher.java | 9 + .../network/grouppriority/Matchers.java} | 112 +++++----- .../TransitGroupPriority32n.java | 53 ++--- .../TransitGroupPriorityService.java | 176 +++++++++++++++ .../network/grouppriority/TripAdapter.java | 34 +++ .../grouppriority/TripPatternAdapter.java | 34 +++ .../moduletests/K01_TransitPriorityTest.java | 4 +- .../K02_TransitPriorityDestinationTest.java | 4 +- .../support/TestGroupPriorityCalculator.java | 6 +- .../TransitGroupPriority32nTest.java | 67 ------ ...rRoutingRequestTransitDataCreatorTest.java | 6 +- ...ultTransitGroupPriorityCalculatorTest.java | 32 +++ .../network/grouppriority/MatchersTest.java} | 52 ++--- .../TransitGroupPriority32nTest.java | 68 ++++++ .../TransitGroupPriorityServiceTest.java} | 29 ++- .../grouppriority/TripAdapterTest.java | 34 +++ 34 files changed, 760 insertions(+), 488 deletions(-) create mode 100644 src/main/java/org/opentripplanner/model/plan/grouppriority/TransitGroupPriorityItineraryDecorator.java rename src/main/java/org/opentripplanner/raptor/api/request/{RaptorTransitGroupCalculator.java => RaptorTransitGroupPriorityCalculator.java} (93%) delete mode 100644 src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java rename src/main/java/org/opentripplanner/{routing/algorithm/raptoradapter/transit/request => transit/model/network/grouppriority}/BinarySetOperator.java (79%) create mode 100644 src/main/java/org/opentripplanner/transit/model/network/grouppriority/DefaultTransitGroupPriorityCalculator.java create mode 100644 src/main/java/org/opentripplanner/transit/model/network/grouppriority/EntityAdapter.java create mode 100644 src/main/java/org/opentripplanner/transit/model/network/grouppriority/Matcher.java rename src/main/java/org/opentripplanner/{routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java => transit/model/network/grouppriority/Matchers.java} (54%) rename src/main/java/org/opentripplanner/{routing/algorithm/raptoradapter/transit/cost => transit/model/network}/grouppriority/TransitGroupPriority32n.java (51%) create mode 100644 src/main/java/org/opentripplanner/transit/model/network/grouppriority/TransitGroupPriorityService.java create mode 100644 src/main/java/org/opentripplanner/transit/model/network/grouppriority/TripAdapter.java create mode 100644 src/main/java/org/opentripplanner/transit/model/network/grouppriority/TripPatternAdapter.java delete mode 100644 src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitGroupPriority32nTest.java create mode 100644 src/test/java/org/opentripplanner/transit/model/network/grouppriority/DefaultTransitGroupPriorityCalculatorTest.java rename src/test/java/org/opentripplanner/{routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcherTest.java => transit/model/network/grouppriority/MatchersTest.java} (70%) create mode 100644 src/test/java/org/opentripplanner/transit/model/network/grouppriority/TransitGroupPriority32nTest.java rename src/test/java/org/opentripplanner/{routing/algorithm/raptoradapter/transit/request/PriorityGroupConfiguratorTest.java => transit/model/network/grouppriority/TransitGroupPriorityServiceTest.java} (77%) create mode 100644 src/test/java/org/opentripplanner/transit/model/network/grouppriority/TripAdapterTest.java diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java index 2883394f837..1115ee3dbbd 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java @@ -42,14 +42,15 @@ import org.opentripplanner.street.search.request.StreetSearchRequest; import org.opentripplanner.street.search.state.State; import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService; import org.opentripplanner.transit.model.site.AreaStop; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; /** * This tests that the feed for the Cobb County Flex service is processed correctly. This service - * contains both flex zones but also scheduled stops. Inside the zone passengers can get on or off - * anywhere so there it works more like a taxi. + * contains both flex zones but also scheduled stops. Inside the zone, passengers can get on or off + * anywhere, so there it works more like a taxi. *

* Read about the details at: https://www.cobbcounty.org/transportation/cobblinc/routes-and-schedules/flex */ @@ -222,6 +223,7 @@ private static List getItineraries( var result = TransitRouter.route( request, serverContext, + TransitGroupPriorityService.empty(), transitStartOfTime, additionalSearchDays, new DebugTimingAggregator() diff --git a/src/ext/java/org/opentripplanner/ext/reportapi/model/TransitGroupPriorityReport.java b/src/ext/java/org/opentripplanner/ext/reportapi/model/TransitGroupPriorityReport.java index 635469cb3a2..66ad29fad56 100644 --- a/src/ext/java/org/opentripplanner/ext/reportapi/model/TransitGroupPriorityReport.java +++ b/src/ext/java/org/opentripplanner/ext/reportapi/model/TransitGroupPriorityReport.java @@ -4,9 +4,9 @@ import java.util.TreeMap; import java.util.TreeSet; import java.util.stream.Collectors; -import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.PriorityGroupConfigurator; import org.opentripplanner.routing.api.request.request.TransitRequest; import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService; /** * This class is used to report all transit-groups used for transit-group-priority. The report is @@ -17,14 +17,14 @@ public class TransitGroupPriorityReport { public static String build(Collection patterns, TransitRequest request) { - var c = PriorityGroupConfigurator.of( + var service = new TransitGroupPriorityService( request.priorityGroupsByAgency(), request.priorityGroupsGlobal() ); var map = new TreeMap(); for (var it : patterns) { - int groupId = c.lookupTransitGroupPriorityId(it); + int groupId = service.lookupTransitGroupPriorityId(it); var de = map.computeIfAbsent(groupId, DebugEntity::new); de.add( it.getRoute().getAgency().getId().toString(), diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index cb14227e83d..dee80addd91 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -177,11 +177,6 @@ public Leg lastLeg() { return getLegs().get(getLegs().size() - 1); } - /** Get the first transit leg if one exist */ - public Optional firstTransitLeg() { - return getLegs().stream().filter(TransitLeg.class::isInstance).findFirst(); - } - /** * An itinerary can be flagged for removal with a system notice. *

@@ -225,105 +220,6 @@ public Itinerary withTimeShiftToStartAt(ZonedDateTime afterTime) { return newItin; } - /** @see #equals(Object) */ - @Override - public final int hashCode() { - return super.hashCode(); - } - - /** - * Return {@code true} it the other object is the same object using the {@link - * Object#equals(Object)}. An itinerary is a temporary object and the equals method should not be - * used for comparision of 2 instances, only to check that to objects are the same instance. - */ - @Override - public final boolean equals(Object o) { - return super.equals(o); - } - - /** - * Used to convert a list of itineraries to a SHORT human-readable string. - * - * @see #toStr() - *

- * It is great for comparing lists of itineraries in a test: {@code - * assertEquals(toStr(List.of(it1)), toStr(result))}. - */ - public static String toStr(List list) { - return list.stream().map(Itinerary::toStr).collect(Collectors.joining(", ")); - } - - @Override - public String toString() { - return ToStringBuilder - .of(Itinerary.class) - .addStr("from", firstLeg().getFrom().toStringShort()) - .addStr("to", lastLeg().getTo().toStringShort()) - .addTime("start", firstLeg().getStartTime()) - .addTime("end", lastLeg().getEndTime()) - .addNum("nTransfers", numberOfTransfers) - .addDuration("duration", duration) - .addDuration("nonTransitTime", nonTransitDuration) - .addDuration("transitTime", transitDuration) - .addDuration("waitingTime", waitingDuration) - .addNum("generalizedCost", generalizedCost, UNKNOWN) - .addNum("generalizedCost2", generalizedCost2) - .addNum("waitTimeOptimizedCost", waitTimeOptimizedCost, UNKNOWN) - .addNum("transferPriorityCost", transferPriorityCost, UNKNOWN) - .addNum("nonTransitDistance", nonTransitDistanceMeters, "m") - .addBool("tooSloped", tooSloped) - .addNum("elevationLost", elevationLost, 0.0) - .addNum("elevationGained", elevationGained, 0.0) - .addCol("legs", legs) - .addObj("fare", fare) - .addObj("emissionsPerPerson", emissionsPerPerson) - .toString(); - } - - /** - * Used to convert an itinerary to a SHORT human readable string - including just a few of the - * most important fields. It is much shorter and easier to read then the {@link - * Itinerary#toString()}. - *

- * It is great for comparing to itineraries in a test: {@code assertEquals(toStr(it1), - * toStr(it2))}. - *

- * Example: {@code A ~ Walk 2m ~ B ~ BUS 55 12:04 12:14 ~ C [cost: 1066]} - *

- * Reads: Start at A, walk 2 minutes to stop B, take bus 55, board at 12:04 and alight at 12:14 - * ... - */ - public String toStr() { - // No translater needed, stop indexes are never passed to the builder - PathStringBuilder buf = new PathStringBuilder(null); - buf.stop(firstLeg().getFrom().name.toString()); - - for (Leg leg : legs) { - if (leg.isWalkingLeg()) { - buf.walk((int) leg.getDuration().toSeconds()); - } else if (leg instanceof TransitLeg transitLeg) { - buf.transit( - transitLeg.getMode().name(), - transitLeg.getTrip().logName(), - transitLeg.getStartTime(), - transitLeg.getEndTime() - ); - } else if (leg instanceof StreetLeg streetLeg) { - buf.street(streetLeg.getMode().name(), leg.getStartTime(), leg.getEndTime()); - } - buf.stop(leg.getTo().name.toString()); - } - - // The generalizedCost2 is printed as is, it is a special cost and the scale depends on the - // use-case. - buf.summary( - RaptorCostConverter.toRaptorCost(generalizedCost), - getGeneralizedCost2().orElse(RaptorConstants.NOT_SET) - ); - - return buf.toString(); - } - /** Total duration of the itinerary in seconds */ public Duration getDuration() { return duration; @@ -698,6 +594,105 @@ public Duration walkDuration() { return walkDuration; } + /** @see #equals(Object) */ + @Override + public final int hashCode() { + return super.hashCode(); + } + + /** + * Return {@code true} it the other object is the same object using the {@link + * Object#equals(Object)}. An itinerary is a temporary object and the equals method should not be + * used for comparision of 2 instances, only to check that to objects are the same instance. + */ + @Override + public final boolean equals(Object o) { + return super.equals(o); + } + + @Override + public String toString() { + return ToStringBuilder + .of(Itinerary.class) + .addStr("from", firstLeg().getFrom().toStringShort()) + .addStr("to", lastLeg().getTo().toStringShort()) + .addTime("start", firstLeg().getStartTime()) + .addTime("end", lastLeg().getEndTime()) + .addNum("nTransfers", numberOfTransfers) + .addDuration("duration", duration) + .addDuration("nonTransitTime", nonTransitDuration) + .addDuration("transitTime", transitDuration) + .addDuration("waitingTime", waitingDuration) + .addNum("generalizedCost", generalizedCost, UNKNOWN) + .addNum("generalizedCost2", generalizedCost2) + .addNum("waitTimeOptimizedCost", waitTimeOptimizedCost, UNKNOWN) + .addNum("transferPriorityCost", transferPriorityCost, UNKNOWN) + .addNum("nonTransitDistance", nonTransitDistanceMeters, "m") + .addBool("tooSloped", tooSloped) + .addNum("elevationLost", elevationLost, 0.0) + .addNum("elevationGained", elevationGained, 0.0) + .addCol("legs", legs) + .addObj("fare", fare) + .addObj("emissionsPerPerson", emissionsPerPerson) + .toString(); + } + + /** + * Used to convert a list of itineraries to a SHORT human-readable string. + * + * @see #toStr() + *

+ * It is great for comparing lists of itineraries in a test: {@code + * assertEquals(toStr(List.of(it1)), toStr(result))}. + */ + public static String toStr(List list) { + return list.stream().map(Itinerary::toStr).collect(Collectors.joining(", ")); + } + + /** + * Used to convert an itinerary to a SHORT human readable string - including just a few of the + * most important fields. It is much shorter and easier to read then the {@link + * Itinerary#toString()}. + *

+ * It is great for comparing to itineraries in a test: {@code assertEquals(toStr(it1), + * toStr(it2))}. + *

+ * Example: {@code A ~ Walk 2m ~ B ~ BUS 55 12:04 12:14 ~ C [cost: 1066]} + *

+ * Reads: Start at A, walk 2 minutes to stop B, take bus 55, board at 12:04 and alight at 12:14 + * ... + */ + public String toStr() { + // No translater needed, stop indexes are never passed to the builder + PathStringBuilder buf = new PathStringBuilder(null); + buf.stop(firstLeg().getFrom().name.toString()); + + for (Leg leg : legs) { + if (leg.isWalkingLeg()) { + buf.walk((int) leg.getDuration().toSeconds()); + } else if (leg instanceof TransitLeg transitLeg) { + buf.transit( + transitLeg.getMode().name(), + transitLeg.getTrip().logName(), + transitLeg.getStartTime(), + transitLeg.getEndTime() + ); + } else if (leg instanceof StreetLeg streetLeg) { + buf.street(streetLeg.getMode().name(), leg.getStartTime(), leg.getEndTime()); + } + buf.stop(leg.getTo().name.toString()); + } + + // The generalizedCost2 is printed as is, it is a special cost and the scale depends on the + // use-case. + buf.summary( + RaptorCostConverter.toRaptorCost(generalizedCost), + getGeneralizedCost2().orElse(RaptorConstants.NOT_SET) + ); + + return buf.toString(); + } + private static int penaltyCost(TimeAndCost penalty) { return penalty.cost().toSeconds(); } diff --git a/src/main/java/org/opentripplanner/model/plan/Leg.java b/src/main/java/org/opentripplanner/model/plan/Leg.java index 1ee72761d66..2a0b6726560 100644 --- a/src/main/java/org/opentripplanner/model/plan/Leg.java +++ b/src/main/java/org/opentripplanner/model/plan/Leg.java @@ -184,6 +184,7 @@ default Route getRoute() { /** * For transit legs, the trip. For non-transit legs, null. */ + @Nullable default Trip getTrip() { return null; } diff --git a/src/main/java/org/opentripplanner/model/plan/grouppriority/TransitGroupPriorityItineraryDecorator.java b/src/main/java/org/opentripplanner/model/plan/grouppriority/TransitGroupPriorityItineraryDecorator.java new file mode 100644 index 00000000000..80b7b39bd0c --- /dev/null +++ b/src/main/java/org/opentripplanner/model/plan/grouppriority/TransitGroupPriorityItineraryDecorator.java @@ -0,0 +1,49 @@ +package org.opentripplanner.model.plan.grouppriority; + +import java.util.Collection; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.Leg; +import org.opentripplanner.raptor.api.request.RaptorTransitGroupPriorityCalculator; +import org.opentripplanner.transit.model.network.grouppriority.DefaultTransitGroupPriorityCalculator; +import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService; + +/** + * This class will set the {@link Itinerary#getGeneralizedCost2()} value if the feature is + * enabled and no such value is set. The AStar router does not produce itineraries with this, + * so we decorate itineraries with this here to make sure the `c2` is set correct and can be + * used in the itinerary-filter-chain. + */ +public class TransitGroupPriorityItineraryDecorator { + + private final TransitGroupPriorityService priorityGroupConfigurator; + private final RaptorTransitGroupPriorityCalculator transitGroupCalculator; + + public TransitGroupPriorityItineraryDecorator( + TransitGroupPriorityService priorityGroupConfigurator + ) { + this.priorityGroupConfigurator = priorityGroupConfigurator; + this.transitGroupCalculator = new DefaultTransitGroupPriorityCalculator(); + } + + public void decorate(Collection itineraries) { + if (!priorityGroupConfigurator.isEnabled()) { + return; + } + for (Itinerary it : itineraries) { + decorate(it); + } + } + + public void decorate(Itinerary itinerary) { + if (itinerary.getGeneralizedCost2().isEmpty() && priorityGroupConfigurator.isEnabled()) { + int c2 = priorityGroupConfigurator.baseGroupId(); + for (Leg leg : itinerary.getLegs()) { + if (leg.getTrip() != null) { + int newGroupId = priorityGroupConfigurator.lookupTransitGroupPriorityId(leg.getTrip()); + c2 = transitGroupCalculator.mergeGroupIds(c2, newGroupId); + } + } + itinerary.setGeneralizedCost2(c2); + } + } +} diff --git a/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java b/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java index 368b4660922..683c9807af5 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java @@ -18,7 +18,7 @@ public class MultiCriteriaRequest { private final RelaxFunction relaxC1; @Nullable - private final RaptorTransitGroupCalculator transitPriorityCalculator; + private final RaptorTransitGroupPriorityCalculator transitPriorityCalculator; private final List passThroughPoints; @@ -63,7 +63,7 @@ public RelaxFunction relaxC1() { return relaxC1; } - public Optional transitPriorityCalculator() { + public Optional transitPriorityCalculator() { return Optional.ofNullable(transitPriorityCalculator); } @@ -140,7 +140,7 @@ public static class Builder { private final MultiCriteriaRequest original; private RelaxFunction relaxC1; - private RaptorTransitGroupCalculator transitPriorityCalculator; + private RaptorTransitGroupPriorityCalculator transitPriorityCalculator; private List passThroughPoints; private Double relaxCostAtDestination; @@ -163,11 +163,11 @@ public Builder withRelaxC1(RelaxFunction relaxC1) { } @Nullable - public RaptorTransitGroupCalculator transitPriorityCalculator() { + public RaptorTransitGroupPriorityCalculator transitPriorityCalculator() { return transitPriorityCalculator; } - public Builder withTransitPriorityCalculator(RaptorTransitGroupCalculator value) { + public Builder withTransitPriorityCalculator(RaptorTransitGroupPriorityCalculator value) { transitPriorityCalculator = value; return this; } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/RaptorTransitGroupCalculator.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorTransitGroupPriorityCalculator.java similarity index 93% rename from src/main/java/org/opentripplanner/raptor/api/request/RaptorTransitGroupCalculator.java rename to src/main/java/org/opentripplanner/raptor/api/request/RaptorTransitGroupPriorityCalculator.java index b5f0598415e..06c10b51daf 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/RaptorTransitGroupCalculator.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorTransitGroupPriorityCalculator.java @@ -2,7 +2,7 @@ import org.opentripplanner.raptor.api.model.DominanceFunction; -public interface RaptorTransitGroupCalculator { +public interface RaptorTransitGroupPriorityCalculator { /** * Merge in the transit group id with an existing set. Note! Both the set * and the group id type is {@code int}. diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java index 8eef90950dd..3673e78ee47 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java @@ -6,7 +6,7 @@ import org.opentripplanner.raptor.api.model.DominanceFunction; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.request.MultiCriteriaRequest; -import org.opentripplanner.raptor.api.request.RaptorTransitGroupCalculator; +import org.opentripplanner.raptor.api.request.RaptorTransitGroupPriorityCalculator; import org.opentripplanner.raptor.rangeraptor.context.SearchContext; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetCost; @@ -201,7 +201,7 @@ private DominanceFunction dominanceFunctionC2() { return null; } - private RaptorTransitGroupCalculator getTransitGroupPriorityCalculator() { + private RaptorTransitGroupPriorityCalculator getTransitGroupPriorityCalculator() { return mcRequest().transitPriorityCalculator().orElseThrow(); } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c2/TransitGroupPriorityRideFactory.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c2/TransitGroupPriorityRideFactory.java index 5d65c40d021..79ae1558836 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c2/TransitGroupPriorityRideFactory.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/ride/c2/TransitGroupPriorityRideFactory.java @@ -2,7 +2,7 @@ import org.opentripplanner.raptor.api.model.RaptorTripPattern; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; -import org.opentripplanner.raptor.api.request.RaptorTransitGroupCalculator; +import org.opentripplanner.raptor.api.request.RaptorTransitGroupPriorityCalculator; import org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.McStopArrival; import org.opentripplanner.raptor.rangeraptor.multicriteria.ride.PatternRide; import org.opentripplanner.raptor.rangeraptor.multicriteria.ride.PatternRideFactory; @@ -15,10 +15,10 @@ public class TransitGroupPriorityRideFactory implements PatternRideFactory> { private int currentPatternGroupPriority; - private final RaptorTransitGroupCalculator transitGroupPriorityCalculator; + private final RaptorTransitGroupPriorityCalculator transitGroupPriorityCalculator; public TransitGroupPriorityRideFactory( - RaptorTransitGroupCalculator transitGroupPriorityCalculator + RaptorTransitGroupPriorityCalculator transitGroupPriorityCalculator ) { this.transitGroupPriorityCalculator = transitGroupPriorityCalculator; } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java b/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java index 06ceaeebeb2..7ac3dd1caf6 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java @@ -16,6 +16,7 @@ import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.framework.time.ServiceDateUtils; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.grouppriority.TransitGroupPriorityItineraryDecorator; import org.opentripplanner.model.plan.paging.cursor.PageCursorInput; import org.opentripplanner.raptor.api.request.RaptorTuningParameters; import org.opentripplanner.raptor.api.request.SearchParams; @@ -36,6 +37,7 @@ import org.opentripplanner.routing.framework.DebugTimingAggregator; import org.opentripplanner.service.paging.PagingService; import org.opentripplanner.standalone.api.OtpServerRequestContext; +import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,6 +66,7 @@ public class RoutingWorker { */ private final ZonedDateTime transitSearchTimeZero; private final AdditionalSearchDays additionalSearchDays; + private final TransitGroupPriorityService transitGroupPriorityService; private SearchParams raptorSearchParamsUsed = null; private PageCursorInput pageCursorInput = null; @@ -79,6 +82,12 @@ public RoutingWorker(OtpServerRequestContext serverContext, RouteRequest request this.transitSearchTimeZero = ServiceDateUtils.asStartOfService(request.dateTime(), zoneId); this.additionalSearchDays = createAdditionalSearchDays(serverContext.raptorTuningParameters(), zoneId, request); + this.transitGroupPriorityService = + TransitGroupPriorityService.of( + request.preferences().transit().relaxTransitGroupPriority(), + request.journey().transit().priorityGroupsByAgency(), + request.journey().transit().priorityGroupsGlobal() + ); } public RoutingResponse route() { @@ -122,6 +131,9 @@ public RoutingResponse route() { routeTransit(itineraries, routingErrors); } + // Set C2 value for Street and FLEX if transit-group-priority is used + new TransitGroupPriorityItineraryDecorator(transitGroupPriorityService).decorate(itineraries); + debugTimingAggregator.finishedRouting(); // Filter itineraries @@ -258,6 +270,7 @@ private Void routeTransit(List itineraries, Collection var transitResults = TransitRouter.route( request, serverContext, + transitGroupPriorityService, transitSearchTimeZero, additionalSearchDays, debugTimingAggregator 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 13be34d5791..ea0274a139d 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 @@ -41,6 +41,7 @@ import org.opentripplanner.routing.framework.DebugTimingAggregator; import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.street.search.TemporaryVerticesContainer; +import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService; public class TransitRouter { @@ -48,6 +49,7 @@ public class TransitRouter { private final RouteRequest request; private final OtpServerRequestContext serverContext; + private final TransitGroupPriorityService transitGroupPriorityService; private final DebugTimingAggregator debugTimingAggregator; private final ZonedDateTime transitSearchTimeZero; private final AdditionalSearchDays additionalSearchDays; @@ -56,12 +58,14 @@ public class TransitRouter { private TransitRouter( RouteRequest request, OtpServerRequestContext serverContext, + TransitGroupPriorityService transitGroupPriorityService, ZonedDateTime transitSearchTimeZero, AdditionalSearchDays additionalSearchDays, DebugTimingAggregator debugTimingAggregator ) { this.request = request; this.serverContext = serverContext; + this.transitGroupPriorityService = transitGroupPriorityService; this.transitSearchTimeZero = transitSearchTimeZero; this.additionalSearchDays = additionalSearchDays; this.debugTimingAggregator = debugTimingAggregator; @@ -71,6 +75,7 @@ private TransitRouter( public static TransitRouterResult route( RouteRequest request, OtpServerRequestContext serverContext, + TransitGroupPriorityService priorityGroupConfigurator, ZonedDateTime transitSearchTimeZero, AdditionalSearchDays additionalSearchDays, DebugTimingAggregator debugTimingAggregator @@ -78,6 +83,7 @@ public static TransitRouterResult route( TransitRouter transitRouter = new TransitRouter( request, serverContext, + priorityGroupConfigurator, transitSearchTimeZero, additionalSearchDays, debugTimingAggregator @@ -310,6 +316,7 @@ private RaptorRoutingRequestTransitData createRequestTransitDataProvider( ) { return new RaptorRoutingRequestTransitData( transitLayer, + transitGroupPriorityService, transitSearchTimeZero, additionalSearchDays.additionalSearchDaysInPast(), additionalSearchDays.additionalSearchDaysInFuture(), 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 17b96790a5f..6474a1ab31a 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 @@ -22,12 +22,11 @@ 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; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; +import org.opentripplanner.transit.model.network.grouppriority.DefaultTransitGroupPriorityCalculator; import org.opentripplanner.transit.model.site.StopLocation; public class RaptorRequestMapper { @@ -128,7 +127,7 @@ private RaptorRequest doMap() { mcBuilder.withPassThroughPoints(mapPassThroughPoints()); r.relaxGeneralizedCostAtDestination().ifPresent(mcBuilder::withRelaxCostAtDestination); } else if (!pt.relaxTransitGroupPriority().isNormal()) { - mcBuilder.withTransitPriorityCalculator(TransitGroupPriority32n.priorityCalculator()); + mcBuilder.withTransitPriorityCalculator(new DefaultTransitGroupPriorityCalculator()); mcBuilder.withRelaxC1(mapRelaxCost(pt.relaxTransitGroupPriority())); } }); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java deleted file mode 100644 index 6ef82786b99..00000000000 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfigurator.java +++ /dev/null @@ -1,144 +0,0 @@ -package org.opentripplanner.routing.algorithm.raptoradapter.transit.request; - -import gnu.trove.impl.Constants; -import gnu.trove.map.TObjectIntMap; -import gnu.trove.map.hash.TObjectIntHashMap; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.stream.Stream; -import org.opentripplanner.framework.lang.ArrayUtils; -import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitGroupPriority32n; -import org.opentripplanner.routing.api.request.request.filter.TransitGroupSelect; -import org.opentripplanner.transit.model.framework.FeedScopedId; -import org.opentripplanner.transit.model.network.TripPattern; - -/** - * This class dynamically builds an index of transit-group-ids from the - * provided {@link TransitGroupSelect}s while serving the caller with - * group-ids for each requested pattern. It is made for optimal - * performance, since it is used in request scope. - *

- * THIS CLASS IS NOT THREAD-SAFE. - */ -public class PriorityGroupConfigurator { - - /** - * There are two ways we can treat the base (local-traffic) transit priority group: - *

    - *
  1. We can assign group id 1 (one) to the base group and it will be treated as any other group. - *
  2. We can assign group id 0 (zero) to the base and it will not be added to the set of groups - * a given path has. - *
- * When we compare paths we compare sets of group ids. A set is dominating another set if it is - * a smaller subset or different from the other set. - *

- * Example - base-group-id = 0 (zero) - *

- * Let B be the base and G be concrete group. Then: (B) dominates (G), (G) dominates (B), (B) - * dominates (BG), but (G) does not dominate (BG). In other words, paths with only agency - * X (group G) is not given an advantage in the routing over paths with a combination of agency - * X (group G) and local traffic (group B). - *

- * TODO: Experiment with base-group-id=0 and make it configurable. - */ - private static final int GROUP_INDEX_COUNTER_START = 1; - - private final int baseGroupId = TransitGroupPriority32n.groupId(GROUP_INDEX_COUNTER_START); - private int groupIndexCounter = GROUP_INDEX_COUNTER_START; - private final boolean enabled; - private final PriorityGroupMatcher[] agencyMatchers; - private final PriorityGroupMatcher[] globalMatchers; - - // Index matchers and ids - private final List agencyMatchersIds; - private final List globalMatchersIds; - - private PriorityGroupConfigurator() { - this.enabled = false; - this.agencyMatchers = null; - this.globalMatchers = null; - this.agencyMatchersIds = List.of(); - this.globalMatchersIds = List.of(); - } - - private PriorityGroupConfigurator( - Collection byAgency, - Collection global - ) { - this.agencyMatchers = PriorityGroupMatcher.of(byAgency); - this.globalMatchers = PriorityGroupMatcher.of(global); - this.enabled = Stream.of(agencyMatchers, globalMatchers).anyMatch(ArrayUtils::hasContent); - this.globalMatchersIds = - Arrays.stream(globalMatchers).map(m -> new MatcherAndId(m, nextGroupId())).toList(); - // We need to populate this dynamically - this.agencyMatchersIds = Arrays.stream(agencyMatchers).map(MatcherAgencyAndIds::new).toList(); - } - - public static PriorityGroupConfigurator empty() { - return new PriorityGroupConfigurator(); - } - - public static PriorityGroupConfigurator of( - Collection byAgency, - Collection global - ) { - if (Stream.of(byAgency, global).allMatch(Collection::isEmpty)) { - return empty(); - } - return new PriorityGroupConfigurator(byAgency, global); - } - - /** - * Fetch/lookup the transit-group-id for the given pattern. - *

- * @throws IllegalArgumentException if more than 32 group-ids are requested. - */ - public int lookupTransitGroupPriorityId(TripPattern tripPattern) { - if (!enabled || tripPattern == null) { - return baseGroupId; - } - - for (var it : agencyMatchersIds) { - if (it.matcher().match(tripPattern)) { - var agencyId = tripPattern.getRoute().getAgency().getId(); - int groupId = it.ids().get(agencyId); - - if (groupId < 0) { - groupId = nextGroupId(); - it.ids.put(agencyId, groupId); - } - return groupId; - } - } - - for (var it : globalMatchersIds) { - if (it.matcher.match(tripPattern)) { - return it.groupId(); - } - } - // Fallback to base-group-id - return baseGroupId; - } - - public int baseGroupId() { - return baseGroupId; - } - - private int nextGroupId() { - return TransitGroupPriority32n.groupId(++groupIndexCounter); - } - - /** Pair of matcher and groupId. Used only inside this class. */ - record MatcherAndId(PriorityGroupMatcher matcher, int groupId) {} - - /** Matcher with map of ids by agency. */ - record MatcherAgencyAndIds(PriorityGroupMatcher matcher, TObjectIntMap ids) { - MatcherAgencyAndIds(PriorityGroupMatcher matcher) { - this( - matcher, - new TObjectIntHashMap<>(Constants.DEFAULT_CAPACITY, Constants.DEFAULT_LOAD_FACTOR, -1) - ); - } - } -} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java index d4785dc6f5b..eea21ba50d2 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 @@ -33,10 +33,11 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.GeneralizedCostParametersMapper; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.transit.model.network.RoutingTripPattern; +import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService; /** * This is the data provider for the Range Raptor search engine. It uses data from the TransitLayer, - * but filters it by dates and modes per request. Transfers durations are pre-calculated per request + * but filters it by dates and modes per request. Transfer durations are pre-calculated per request * based on walk speed. */ public class RaptorRoutingRequestTransitData implements RaptorTransitDataProvider { @@ -74,6 +75,7 @@ public class RaptorRoutingRequestTransitData implements RaptorTransitDataProvide public RaptorRoutingRequestTransitData( TransitLayer transitLayer, + TransitGroupPriorityService transitGroupPriorityService, ZonedDateTime transitSearchTimeZero, int additionalPastSearchDays, int additionalFutureSearchDays, @@ -85,7 +87,7 @@ public RaptorRoutingRequestTransitData( this.transitSearchTimeZero = transitSearchTimeZero; // Delegate to the creator to construct the needed data structures. The code is messy so - // it is nice to NOT have it in the class. It isolate this code to only be available at + // it is nice to NOT have it in the class. It isolates this code to only be available at // the time of construction var transitDataCreator = new RaptorRoutingRequestTransitDataCreator( transitLayer, @@ -95,7 +97,7 @@ public RaptorRoutingRequestTransitData( additionalPastSearchDays, additionalFutureSearchDays, filter, - createTransitGroupPriorityConfigurator(request) + transitGroupPriorityService ); this.patternIndex = transitDataCreator.createPatternIndex(tripPatterns); this.activeTripPatternsPerStop = transitDataCreator.createTripPatternsPerStop(tripPatterns); @@ -246,17 +248,6 @@ public RaptorConstrainedBoardingSearch transferConstraintsReverseS return new ConstrainedBoardingSearch(false, toStopTransfers, fromStopTransfers); } - private PriorityGroupConfigurator createTransitGroupPriorityConfigurator(RouteRequest request) { - if (request.preferences().transit().relaxTransitGroupPriority().isNormal()) { - return PriorityGroupConfigurator.empty(); - } - var transitRequest = request.journey().transit(); - return PriorityGroupConfigurator.of( - transitRequest.priorityGroupsByAgency(), - transitRequest.priorityGroupsGlobal() - ); - } - /*-- HACK SØRLANDSBANEN :: BEGIN --*/ private RaptorRoutingRequestTransitData( diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java index f987e4f7a21..815bf839e31 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java @@ -19,6 +19,7 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripPatternForDate; import org.opentripplanner.transit.model.network.RoutingTripPattern; +import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService; import org.opentripplanner.transit.model.timetable.TripTimes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -93,7 +94,7 @@ static List merge( ZonedDateTime transitSearchTimeZero, List patternForDateList, TransitDataProviderFilter filter, - PriorityGroupConfigurator priorityGroupConfigurator + TransitGroupPriorityService transitGroupPriorityService ) { // Group TripPatternForDate objects by TripPattern. // This is done in a loop to increase performance. @@ -147,7 +148,7 @@ static List merge( tripPattern.getAlightingPossible(), BoardAlight.ALIGHT ), - priorityGroupConfigurator.lookupTransitGroupPriorityId(tripPattern.getPattern()) + transitGroupPriorityService.lookupTransitGroupPriorityId(tripPattern.getPattern()) ) ); } @@ -159,7 +160,7 @@ List createTripPatterns( int additionalPastSearchDays, int additionalFutureSearchDays, TransitDataProviderFilter filter, - PriorityGroupConfigurator priorityGroupConfigurator + TransitGroupPriorityService transitGroupPriorityService ) { List tripPatternForDates = getTripPatternsForDateRange( additionalPastSearchDays, @@ -167,7 +168,7 @@ List createTripPatterns( filter ); - return merge(transitSearchTimeZero, tripPatternForDates, filter, priorityGroupConfigurator); + return merge(transitSearchTimeZero, tripPatternForDates, filter, transitGroupPriorityService); } private static List filterActiveTripPatterns( diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/BinarySetOperator.java b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/BinarySetOperator.java similarity index 79% rename from src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/BinarySetOperator.java rename to src/main/java/org/opentripplanner/transit/model/network/grouppriority/BinarySetOperator.java index 35e5b8c0918..4d8ce2c8fd7 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/BinarySetOperator.java +++ b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/BinarySetOperator.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.raptoradapter.transit.request; +package org.opentripplanner.transit.model.network.grouppriority; /** * Used to concatenate matches with either the logical "AND" or "OR" operator. diff --git a/src/main/java/org/opentripplanner/transit/model/network/grouppriority/DefaultTransitGroupPriorityCalculator.java b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/DefaultTransitGroupPriorityCalculator.java new file mode 100644 index 00000000000..df5d2abef8a --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/DefaultTransitGroupPriorityCalculator.java @@ -0,0 +1,26 @@ +package org.opentripplanner.transit.model.network.grouppriority; + +import org.opentripplanner.raptor.api.model.DominanceFunction; +import org.opentripplanner.raptor.api.request.RaptorTransitGroupPriorityCalculator; + +/** + * Implement {@link RaptorTransitGroupPriorityCalculator}. + */ +public final class DefaultTransitGroupPriorityCalculator + implements RaptorTransitGroupPriorityCalculator { + + @Override + public int mergeGroupIds(int currentGroupIds, int boardingGroupId) { + return TransitGroupPriority32n.mergeInGroupId(currentGroupIds, boardingGroupId); + } + + @Override + public DominanceFunction dominanceFunction() { + return TransitGroupPriority32n::dominate; + } + + @Override + public String toString() { + return "DefaultTransitGroupCalculator{Using TGP32n}"; + } +} diff --git a/src/main/java/org/opentripplanner/transit/model/network/grouppriority/EntityAdapter.java b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/EntityAdapter.java new file mode 100644 index 00000000000..c6802562283 --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/EntityAdapter.java @@ -0,0 +1,16 @@ +package org.opentripplanner.transit.model.network.grouppriority; + +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +/** + * This is the keys used to group transit trips and trip-patterns. This is used to calculate a + * unique groupId based on the request config. We use the adapter pattern to be able to generate + * the groupId based on different input types (TripPattern and Trip). + */ +interface EntityAdapter { + TransitMode mode(); + String subMode(); + FeedScopedId agencyId(); + FeedScopedId routeId(); +} diff --git a/src/main/java/org/opentripplanner/transit/model/network/grouppriority/Matcher.java b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/Matcher.java new file mode 100644 index 00000000000..bb5b4075364 --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/Matcher.java @@ -0,0 +1,9 @@ +package org.opentripplanner.transit.model.network.grouppriority; + +interface Matcher { + boolean match(EntityAdapter entity); + + default boolean isEmpty() { + return false; + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/Matchers.java similarity index 54% rename from src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java rename to src/main/java/org/opentripplanner/transit/model/network/grouppriority/Matchers.java index c017f2862ab..7e1e7e6853a 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcher.java +++ b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/Matchers.java @@ -1,7 +1,7 @@ -package org.opentripplanner.routing.algorithm.raptoradapter.transit.request; +package org.opentripplanner.transit.model.network.grouppriority; -import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.BinarySetOperator.AND; -import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.BinarySetOperator.OR; +import static org.opentripplanner.transit.model.network.grouppriority.BinarySetOperator.AND; +import static org.opentripplanner.transit.model.network.grouppriority.BinarySetOperator.OR; import java.util.ArrayList; import java.util.Arrays; @@ -18,7 +18,6 @@ import org.opentripplanner.routing.api.request.request.filter.TransitGroupSelect; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.FeedScopedId; -import org.opentripplanner.transit.model.network.TripPattern; /** * This class turns a {@link TransitGroupSelect} into a matcher. @@ -28,49 +27,38 @@ * a `CompositeMatcher`. So, a new matcher is only created if the field in the * select is present. */ -public abstract class PriorityGroupMatcher { +final class Matchers { - private static final PriorityGroupMatcher NOOP = new PriorityGroupMatcher() { - @Override - boolean match(TripPattern pattern) { - return false; - } + private static final Matcher NOOP = new EmptyMatcher(); - @Override - boolean isEmpty() { - return true; - } - }; - - public static PriorityGroupMatcher of(TransitGroupSelect select) { + static Matcher of(TransitGroupSelect select) { if (select.isEmpty()) { return NOOP; } - List list = new ArrayList<>(); + List list = new ArrayList<>(); if (!select.modes().isEmpty()) { list.add(new ModeMatcher(select.modes())); } if (!select.subModeRegexp().isEmpty()) { - list.add( - new RegExpMatcher("SubMode", select.subModeRegexp(), p -> p.getNetexSubmode().name()) - ); + list.add(new RegExpMatcher("SubMode", select.subModeRegexp(), EntityAdapter::subMode)); } if (!select.agencyIds().isEmpty()) { - list.add(new IdMatcher("Agency", select.agencyIds(), p -> p.getRoute().getAgency().getId())); + list.add(new IdMatcher("Agency", select.agencyIds(), EntityAdapter::agencyId)); } if (!select.routeIds().isEmpty()) { - list.add(new IdMatcher("Route", select.routeIds(), p -> p.getRoute().getId())); + list.add(new IdMatcher("Route", select.routeIds(), EntityAdapter::routeId)); } return andOf(list); } - static PriorityGroupMatcher[] of(Collection selectors) { + @SuppressWarnings("unchecked") + static Matcher[] of(Collection selectors) { return selectors .stream() - .map(PriorityGroupMatcher::of) - .filter(Predicate.not(PriorityGroupMatcher::isEmpty)) - .toArray(PriorityGroupMatcher[]::new); + .map(Matchers::of) + .filter(Predicate.not(Matcher::isEmpty)) + .toArray(Matcher[]::new); } private static String arrayToString(BinarySetOperator op, T[] values) { @@ -81,9 +69,9 @@ private static String colToString(BinarySetOperator op, Collection values return values.stream().map(Objects::toString).collect(Collectors.joining(" " + op + " ")); } - private static PriorityGroupMatcher andOf(List list) { + private static Matcher andOf(List list) { // Remove empty/noop matchers - list = list.stream().filter(Predicate.not(PriorityGroupMatcher::isEmpty)).toList(); + list = list.stream().filter(Predicate.not(Matcher::isEmpty)).toList(); if (list.isEmpty()) { return NOOP; @@ -94,13 +82,25 @@ private static PriorityGroupMatcher andOf(List list) { return new AndMatcher(list); } - abstract boolean match(TripPattern pattern); + private static final class EmptyMatcher implements Matcher { + + @Override + public boolean match(EntityAdapter entity) { + return false; + } + + @Override + public boolean isEmpty() { + return true; + } - boolean isEmpty() { - return false; + @Override + public String toString() { + return "Empty"; + } } - private static final class ModeMatcher extends PriorityGroupMatcher { + private static final class ModeMatcher implements Matcher { private final Set modes; @@ -109,8 +109,8 @@ public ModeMatcher(List modes) { } @Override - boolean match(TripPattern pattern) { - return modes.contains(pattern.getMode()); + public boolean match(EntityAdapter entity) { + return modes.contains(entity.mode()); } @Override @@ -119,26 +119,26 @@ public String toString() { } } - private static final class RegExpMatcher extends PriorityGroupMatcher { + private static final class RegExpMatcher implements Matcher { private final String typeName; - private final Pattern[] subModeRegexp; - private final Function toValue; + private final Pattern[] patterns; + private final Function toValue; public RegExpMatcher( String typeName, - List subModeRegexp, - Function toValue + List regexps, + Function toValue ) { this.typeName = typeName; - this.subModeRegexp = subModeRegexp.stream().map(Pattern::compile).toArray(Pattern[]::new); + this.patterns = regexps.stream().map(Pattern::compile).toArray(Pattern[]::new); this.toValue = toValue; } @Override - boolean match(TripPattern pattern) { - var value = toValue.apply(pattern); - for (Pattern p : subModeRegexp) { + public boolean match(EntityAdapter entity) { + var value = toValue.apply(entity); + for (Pattern p : patterns) { if (p.matcher(value).matches()) { return true; } @@ -148,20 +148,20 @@ boolean match(TripPattern pattern) { @Override public String toString() { - return typeName + "Regexp(" + arrayToString(OR, subModeRegexp) + ')'; + return typeName + "Regexp(" + arrayToString(OR, patterns) + ')'; } } - private static final class IdMatcher extends PriorityGroupMatcher { + private static final class IdMatcher implements Matcher { private final String typeName; private final Set ids; - private final Function idProvider; + private final Function idProvider; public IdMatcher( String typeName, List ids, - Function idProvider + Function idProvider ) { this.typeName = typeName; this.ids = new HashSet<>(ids); @@ -169,8 +169,8 @@ public IdMatcher( } @Override - boolean match(TripPattern pattern) { - return ids.contains(idProvider.apply(pattern)); + public boolean match(EntityAdapter entity) { + return ids.contains(idProvider.apply(entity)); } @Override @@ -183,18 +183,18 @@ public String toString() { * Takes a list of matchers and provide a single interface. All matchers in the list must match * for the composite matcher to return a match. */ - private static final class AndMatcher extends PriorityGroupMatcher { + private static final class AndMatcher implements Matcher { - private final PriorityGroupMatcher[] matchers; + private final Matcher[] matchers; - public AndMatcher(List matchers) { - this.matchers = matchers.toArray(PriorityGroupMatcher[]::new); + public AndMatcher(List matchers) { + this.matchers = matchers.toArray(Matcher[]::new); } @Override - boolean match(TripPattern pattern) { + public boolean match(EntityAdapter entity) { for (var m : matchers) { - if (!m.match(pattern)) { + if (!m.match(entity)) { return false; } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitGroupPriority32n.java b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/TransitGroupPriority32n.java similarity index 51% rename from src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitGroupPriority32n.java rename to src/main/java/org/opentripplanner/transit/model/network/grouppriority/TransitGroupPriority32n.java index feb3f6f7b3a..32423070e09 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitGroupPriority32n.java +++ b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/TransitGroupPriority32n.java @@ -1,55 +1,31 @@ -package org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority; - -import org.opentripplanner.raptor.api.model.DominanceFunction; -import org.opentripplanner.raptor.api.request.RaptorTransitGroupCalculator; +package org.opentripplanner.transit.model.network.grouppriority; /** - * This is a "BitSet" implementation for groupId. It can store upto 32 groups, + * This is a "BitSet" implementation for groupId. It can store up to 31 groups, * a set with few elements does NOT dominate a set with more elements. */ -public class TransitGroupPriority32n { +class TransitGroupPriority32n { private static final int GROUP_ZERO = 0; private static final int MIN_SEQ_NO = 0; private static final int MAX_SEQ_NO = 32; - public static RaptorTransitGroupCalculator priorityCalculator() { - return new RaptorTransitGroupCalculator() { - @Override - public int mergeGroupIds(int currentGroupIds, int boardingGroupId) { - return mergeInGroupId(currentGroupIds, boardingGroupId); - } - - @Override - public DominanceFunction dominanceFunction() { - return TransitGroupPriority32n::dominate; - } - - @Override - public String toString() { - return "TransitGroupPriority32nCalculator{}"; - } - }; - } - /** - * Left dominate right, if right contains a group which does not exist in left. Left - * do NOT dominate right if they are equals or left is a super set of right. + * Left dominates right: + * - if right contains a group which does not exist in the left. + * Left do NOT dominate right: + * - if they are equals or + * - left is a superset of right. */ - public static boolean dominate(int left, int right) { + static boolean dominate(int left, int right) { return ((left ^ right) & right) != 0; } - @Override - public String toString() { - return "TransitGroupPriority32n{}"; - } - /** * Use this method to map from a continuous group index [0..32) to the groupId used - * during routing. The ID is implementation specific and optimized for performance. + * during routing. The ID is implementation-specific and optimized for performance. */ - public static int groupId(final int priorityGroupIndex) { + static int groupId(final int priorityGroupIndex) { assertValidGroupSeqNo(priorityGroupIndex); return priorityGroupIndex == MIN_SEQ_NO ? GROUP_ZERO : 0x01 << (priorityGroupIndex - 1); } @@ -57,10 +33,15 @@ public static int groupId(final int priorityGroupIndex) { /** * Merge a groupId into a set of groupIds. */ - public static int mergeInGroupId(final int currentSetOfGroupIds, final int newGroupId) { + static int mergeInGroupId(final int currentSetOfGroupIds, final int newGroupId) { return currentSetOfGroupIds | newGroupId; } + @Override + public String toString() { + return "TransitGroupPriority32n{}"; + } + private static void assertValidGroupSeqNo(int priorityGroupIndex) { if (priorityGroupIndex < MIN_SEQ_NO) { throw new IllegalArgumentException( diff --git a/src/main/java/org/opentripplanner/transit/model/network/grouppriority/TransitGroupPriorityService.java b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/TransitGroupPriorityService.java new file mode 100644 index 00000000000..048b2279a88 --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/TransitGroupPriorityService.java @@ -0,0 +1,176 @@ +package org.opentripplanner.transit.model.network.grouppriority; + +import gnu.trove.impl.Constants; +import gnu.trove.map.TObjectIntMap; +import gnu.trove.map.hash.TObjectIntHashMap; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; +import org.opentripplanner.framework.lang.ArrayUtils; +import org.opentripplanner.routing.api.request.framework.CostLinearFunction; +import org.opentripplanner.routing.api.request.request.filter.TransitGroupSelect; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.timetable.Trip; + +/** + * This class dynamically builds an index of transit-group-ids from the provided + * {@link TransitGroupSelect}s while serving the caller with group-ids for each requested + * trip/pattern. It is made for optimal performance, since it is used in request scope. + *

+ * THIS CLASS IS NOT THREAD-SAFE. + */ +public class TransitGroupPriorityService { + + /** + * IMPLEMENTATION DETAILS + * + * There are two ways we can treat the base (local-traffic) transit priority group: + *

    + *
  1. + * We can assign group id 1 (one) to the base group and it will be treated as any other group. + *
  2. + *
  3. + * We can assign group id 0 (zero) to the base and it will not be added to the set of groups + * a given path has. + *
  4. + *
+ * When we compare paths, we compare sets of group ids. A set is dominating another set if it is + * a smaller subset or different from the other set. + */ + private static final int GROUP_INDEX_COUNTER_START = 1; + + private final int baseGroupId = TransitGroupPriority32n.groupId(GROUP_INDEX_COUNTER_START); + private int groupIndexCounter = GROUP_INDEX_COUNTER_START; + private final boolean enabled; + private final Matcher[] agencyMatchers; + private final Matcher[] globalMatchers; + + // Index matchers and ids + private final List agencyMatchersIds; + private final List globalMatchersIds; + + private TransitGroupPriorityService() { + this.enabled = false; + this.agencyMatchers = null; + this.globalMatchers = null; + this.agencyMatchersIds = List.of(); + this.globalMatchersIds = List.of(); + } + + public TransitGroupPriorityService( + Collection byAgency, + Collection global + ) { + this.agencyMatchers = Matchers.of(byAgency); + this.globalMatchers = Matchers.of(global); + this.enabled = Stream.of(agencyMatchers, globalMatchers).anyMatch(ArrayUtils::hasContent); + this.globalMatchersIds = + Arrays.stream(globalMatchers).map(m -> new MatcherAndId(m, nextGroupId())).toList(); + // We need to populate this dynamically + this.agencyMatchersIds = Arrays.stream(agencyMatchers).map(MatcherAgencyAndIds::new).toList(); + } + + public static TransitGroupPriorityService empty() { + return new TransitGroupPriorityService(); + } + + public static TransitGroupPriorityService of( + CostLinearFunction relaxTransitGroupPriority, + List groupByAgency, + List groupGlobal + ) { + if (relaxTransitGroupPriority.isNormal()) { + return TransitGroupPriorityService.empty(); + } else if (Stream.of(groupByAgency, groupGlobal).allMatch(Collection::isEmpty)) { + return TransitGroupPriorityService.empty(); + } else { + return new TransitGroupPriorityService(groupByAgency, groupGlobal); + } + } + + /** + * Return true is the feature is configured and the request a {@code relaxTransitGroupPriority} + * function. + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Fetch/lookup the transit-group-id for the given pattern. + *

+ * @throws IllegalArgumentException if more than 32 group-ids are requested. + */ + public int lookupTransitGroupPriorityId(TripPattern tripPattern) { + return tripPattern == null + ? baseGroupId + : lookupTransitGroupPriorityId(new TripPatternAdapter(tripPattern)); + } + + /** + * Fetch/lookup the transit-group-id for the given trip. + *

+ * @throws IllegalArgumentException if more than 32 group-ids are requested. + */ + public int lookupTransitGroupPriorityId(Trip trip) { + return trip == null ? baseGroupId : lookupTransitGroupPriorityId(new TripAdapter(trip)); + } + + /** + * Fetch/lookup the transit-group-id for the given entity. + *

+ * @throws IllegalArgumentException if more than 32 group-ids are requested. + */ + private int lookupTransitGroupPriorityId(EntityAdapter entity) { + if (!enabled) { + return baseGroupId; + } + for (var it : agencyMatchersIds) { + if (it.matcher().match(entity)) { + var agencyId = entity.agencyId(); + int groupId = it.ids().get(agencyId); + + if (groupId < 0) { + groupId = nextGroupId(); + it.ids.put(agencyId, groupId); + } + return groupId; + } + } + + for (var it : globalMatchersIds) { + if (it.matcher.match(entity)) { + return it.groupId(); + } + } + // Fallback to base-group-id + return baseGroupId; + } + + /** + * This is the group-id assigned to all transit trips/patterns witch does not match a + * specific group. + */ + public int baseGroupId() { + return baseGroupId; + } + + private int nextGroupId() { + return TransitGroupPriority32n.groupId(++groupIndexCounter); + } + + /** Pair of matcher and groupId. Used only inside this class. */ + private record MatcherAndId(Matcher matcher, int groupId) {} + + /** Matcher with a map of ids by agency. */ + private record MatcherAgencyAndIds(Matcher matcher, TObjectIntMap ids) { + MatcherAgencyAndIds(Matcher matcher) { + this( + matcher, + new TObjectIntHashMap<>(Constants.DEFAULT_CAPACITY, Constants.DEFAULT_LOAD_FACTOR, -1) + ); + } + } +} diff --git a/src/main/java/org/opentripplanner/transit/model/network/grouppriority/TripAdapter.java b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/TripAdapter.java new file mode 100644 index 00000000000..7ff1d158e62 --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/TripAdapter.java @@ -0,0 +1,34 @@ +package org.opentripplanner.transit.model.network.grouppriority; + +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.timetable.Trip; + +class TripAdapter implements EntityAdapter { + + private final Trip trip; + + public TripAdapter(Trip trip) { + this.trip = trip; + } + + @Override + public TransitMode mode() { + return trip.getMode(); + } + + @Override + public String subMode() { + return trip.getNetexSubMode().name(); + } + + @Override + public FeedScopedId agencyId() { + return trip.getRoute().getAgency().getId(); + } + + @Override + public FeedScopedId routeId() { + return trip.getRoute().getId(); + } +} diff --git a/src/main/java/org/opentripplanner/transit/model/network/grouppriority/TripPatternAdapter.java b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/TripPatternAdapter.java new file mode 100644 index 00000000000..223f163f535 --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/TripPatternAdapter.java @@ -0,0 +1,34 @@ +package org.opentripplanner.transit.model.network.grouppriority; + +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.network.TripPattern; + +class TripPatternAdapter implements EntityAdapter { + + private final TripPattern tripPattern; + + public TripPatternAdapter(TripPattern tripPattern) { + this.tripPattern = tripPattern; + } + + @Override + public TransitMode mode() { + return tripPattern.getMode(); + } + + @Override + public String subMode() { + return tripPattern.getNetexSubmode().name(); + } + + @Override + public FeedScopedId agencyId() { + return tripPattern.getRoute().getAgency().getId(); + } + + @Override + public FeedScopedId routeId() { + return tripPattern.getRoute().getId(); + } +} diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/K01_TransitPriorityTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/K01_TransitPriorityTest.java index 00b1d528e7e..f03eb5335a0 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/K01_TransitPriorityTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/K01_TransitPriorityTest.java @@ -22,7 +22,7 @@ import org.opentripplanner.raptor._data.transit.TestTripSchedule; import org.opentripplanner.raptor.api.request.RaptorProfile; import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; -import org.opentripplanner.raptor.api.request.RaptorTransitGroupCalculator; +import org.opentripplanner.raptor.api.request.RaptorTransitGroupPriorityCalculator; import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.raptor.moduletests.support.TestGroupPriorityCalculator; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter; @@ -34,7 +34,7 @@ */ public class K01_TransitPriorityTest { - private static final RaptorTransitGroupCalculator PRIORITY_GROUP_CALCULATOR = + private static final RaptorTransitGroupPriorityCalculator PRIORITY_GROUP_CALCULATOR = TestGroupPriorityCalculator.PRIORITY_CALCULATOR; private static final int C1_SLACK_90s = RaptorCostConverter.toRaptorCost(90); diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/K02_TransitPriorityDestinationTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/K02_TransitPriorityDestinationTest.java index c6ab8e337ea..fb21128728c 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/K02_TransitPriorityDestinationTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/K02_TransitPriorityDestinationTest.java @@ -25,7 +25,7 @@ import org.opentripplanner.raptor._data.transit.TestTripSchedule; import org.opentripplanner.raptor.api.request.RaptorProfile; import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; -import org.opentripplanner.raptor.api.request.RaptorTransitGroupCalculator; +import org.opentripplanner.raptor.api.request.RaptorTransitGroupPriorityCalculator; import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.raptor.moduletests.support.TestGroupPriorityCalculator; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter; @@ -37,7 +37,7 @@ */ public class K02_TransitPriorityDestinationTest { - private static final RaptorTransitGroupCalculator PRIORITY_GROUP_CALCULATOR = + private static final RaptorTransitGroupPriorityCalculator PRIORITY_GROUP_CALCULATOR = TestGroupPriorityCalculator.PRIORITY_CALCULATOR; private static final int C1_SLACK_90s = RaptorCostConverter.toRaptorCost(90); diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/support/TestGroupPriorityCalculator.java b/src/test/java/org/opentripplanner/raptor/moduletests/support/TestGroupPriorityCalculator.java index cdbe82f18a6..3234dc126fb 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/support/TestGroupPriorityCalculator.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/support/TestGroupPriorityCalculator.java @@ -5,11 +5,11 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.raptor.api.model.DominanceFunction; -import org.opentripplanner.raptor.api.request.RaptorTransitGroupCalculator; +import org.opentripplanner.raptor.api.request.RaptorTransitGroupPriorityCalculator; -public class TestGroupPriorityCalculator implements RaptorTransitGroupCalculator { +public class TestGroupPriorityCalculator implements RaptorTransitGroupPriorityCalculator { - public static final RaptorTransitGroupCalculator PRIORITY_CALCULATOR = new TestGroupPriorityCalculator(); + public static final RaptorTransitGroupPriorityCalculator PRIORITY_CALCULATOR = new TestGroupPriorityCalculator(); public static final int GROUP_A = 0x01; public static final int GROUP_B = 0x02; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitGroupPriority32nTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitGroupPriority32nTest.java deleted file mode 100644 index 2713a190dbf..00000000000 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/grouppriority/TransitGroupPriority32nTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; -import org.opentripplanner.raptor.api.request.RaptorTransitGroupCalculator; - -class TransitGroupPriority32nTest { - - private static final int GROUP_INDEX_0 = 0; - private static final int GROUP_INDEX_1 = 1; - private static final int GROUP_INDEX_2 = 2; - private static final int GROUP_INDEX_30 = 30; - private static final int GROUP_INDEX_31 = 31; - - private static final int GROUP_0 = TransitGroupPriority32n.groupId(GROUP_INDEX_0); - private static final int GROUP_1 = TransitGroupPriority32n.groupId(GROUP_INDEX_1); - private static final int GROUP_2 = TransitGroupPriority32n.groupId(GROUP_INDEX_2); - private static final int GROUP_30 = TransitGroupPriority32n.groupId(GROUP_INDEX_30); - private static final int GROUP_31 = TransitGroupPriority32n.groupId(GROUP_INDEX_31); - private static final RaptorTransitGroupCalculator subjct = TransitGroupPriority32n.priorityCalculator(); - - @Test - void groupId() { - assertEqualsHex(0x00_00_00_00, TransitGroupPriority32n.groupId(0)); - assertEqualsHex(0x00_00_00_01, TransitGroupPriority32n.groupId(1)); - assertEqualsHex(0x00_00_00_02, TransitGroupPriority32n.groupId(2)); - assertEqualsHex(0x00_00_00_04, TransitGroupPriority32n.groupId(3)); - assertEqualsHex(0x40_00_00_00, TransitGroupPriority32n.groupId(31)); - assertEqualsHex(0x80_00_00_00, TransitGroupPriority32n.groupId(32)); - - assertThrows(IllegalArgumentException.class, () -> TransitGroupPriority32n.groupId(-1)); - assertThrows(IllegalArgumentException.class, () -> TransitGroupPriority32n.groupId(33)); - } - - @Test - void mergeTransitGroupPriorityIds() { - assertEqualsHex(GROUP_0, subjct.mergeGroupIds(GROUP_0, GROUP_0)); - assertEqualsHex(GROUP_1, subjct.mergeGroupIds(GROUP_1, GROUP_1)); - assertEqualsHex(GROUP_0 | GROUP_1, subjct.mergeGroupIds(GROUP_0, GROUP_1)); - assertEqualsHex(GROUP_30 | GROUP_31, subjct.mergeGroupIds(GROUP_30, GROUP_31)); - assertEqualsHex( - GROUP_0 | GROUP_1 | GROUP_2 | GROUP_30 | GROUP_31, - subjct.mergeGroupIds(GROUP_0 | GROUP_1 | GROUP_2 | GROUP_30, GROUP_31) - ); - } - - @Test - void dominanceFunction() { - assertFalse(subjct.dominanceFunction().leftDominateRight(GROUP_0, GROUP_0)); - assertFalse(subjct.dominanceFunction().leftDominateRight(GROUP_31, GROUP_31)); - assertFalse(subjct.dominanceFunction().leftDominateRight(GROUP_1 | GROUP_2, GROUP_1 | GROUP_2)); - - assertTrue(subjct.dominanceFunction().leftDominateRight(GROUP_0, GROUP_1)); - assertFalse(subjct.dominanceFunction().leftDominateRight(GROUP_1, GROUP_0)); - - assertTrue(subjct.dominanceFunction().leftDominateRight(GROUP_1, GROUP_1 | GROUP_2)); - assertFalse(subjct.dominanceFunction().leftDominateRight(GROUP_1 | GROUP_2, GROUP_1)); - } - - static void assertEqualsHex(int expected, int actual) { - assertEquals(expected, actual, "%08x == %08x".formatted(expected, actual)); - } -} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java index ed71b3de400..ea815a2f47f 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java @@ -19,6 +19,7 @@ import org.opentripplanner.transit.model.network.RoutingTripPattern; import org.opentripplanner.transit.model.network.StopPattern; import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService; import org.opentripplanner.transit.model.timetable.ScheduledTripTimes; import org.opentripplanner.transit.model.timetable.TripTimes; @@ -60,12 +61,13 @@ public void testMergeTripPatterns() { tripPatternsForDates.add(new TripPatternForDate(tripPattern1, tripTimes, List.of(), third)); tripPatternsForDates.add(new TripPatternForDate(tripPattern3, tripTimes, List.of(), third)); - // Patterns containing trip schedules for all 3 days. Trip schedules for later days are offset in time when requested. + // Patterns containing trip schedules for all 3 days. Trip schedules for later days are offset + // in time when requested. List combinedTripPatterns = RaptorRoutingRequestTransitDataCreator.merge( startOfTime, tripPatternsForDates, new TestTransitDataProviderFilter(), - PriorityGroupConfigurator.empty() + TransitGroupPriorityService.empty() ); // Get the results diff --git a/src/test/java/org/opentripplanner/transit/model/network/grouppriority/DefaultTransitGroupPriorityCalculatorTest.java b/src/test/java/org/opentripplanner/transit/model/network/grouppriority/DefaultTransitGroupPriorityCalculatorTest.java new file mode 100644 index 00000000000..745442e3e7b --- /dev/null +++ b/src/test/java/org/opentripplanner/transit/model/network/grouppriority/DefaultTransitGroupPriorityCalculatorTest.java @@ -0,0 +1,32 @@ +package org.opentripplanner.transit.model.network.grouppriority; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class DefaultTransitGroupPriorityCalculatorTest { + + private final DefaultTransitGroupPriorityCalculator subject = new DefaultTransitGroupPriorityCalculator(); + + @Test + void mergeGroupIds() { + // Smoke test, should not fail + subject.mergeGroupIds(1, 2); + } + + @Test + void dominanceFunction() { + // This is assuming 1 & 2 represent different transit-groups - this just a smoke test to + // see that the delegation works as expected. The 'leftDominateRight' is unit-tested elsewhere. + assertTrue(subject.dominanceFunction().leftDominateRight(1, 2)); + assertTrue(subject.dominanceFunction().leftDominateRight(2, 1)); + assertFalse(subject.dominanceFunction().leftDominateRight(1, 1)); + } + + @Test + void testToString() { + assertEquals("DefaultTransitGroupCalculator{Using TGP32n}", subject.toString()); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcherTest.java b/src/test/java/org/opentripplanner/transit/model/network/grouppriority/MatchersTest.java similarity index 70% rename from src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcherTest.java rename to src/test/java/org/opentripplanner/transit/model/network/grouppriority/MatchersTest.java index 91d0142f9ef..cdda3755d37 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupMatcherTest.java +++ b/src/test/java/org/opentripplanner/transit/model/network/grouppriority/MatchersTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.raptoradapter.transit.request; +package org.opentripplanner.transit.model.network.grouppriority; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -6,12 +6,12 @@ import java.util.List; import org.junit.jupiter.api.Test; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TestRouteData; import org.opentripplanner.routing.api.request.request.filter.TransitGroupSelect; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.FeedScopedId; -import org.opentripplanner.transit.model.network.TripPattern; -class PriorityGroupMatcherTest { +class MatchersTest { private final TestRouteData r1 = TestRouteData .rail("R1") @@ -25,16 +25,24 @@ class PriorityGroupMatcherTest { .withSubmode("localFerry") .build(); - private final TripPattern rail1 = r1.getTripPattern(); - private final TripPattern bus = b1.getTripPattern(); - private final TripPattern ferry = f1.getTripPattern(); - private final FeedScopedId r1agencyId = rail1.getRoute().getAgency().getId(); - private final FeedScopedId r1routeId = rail1.getRoute().getId(); + private final EntityAdapter rail1 = new TripPatternAdapter(r1.getTripPattern()); + private final EntityAdapter bus = new TripPatternAdapter(b1.getTripPattern()); + private final EntityAdapter ferry = new TripPatternAdapter(f1.getTripPattern()); + private final FeedScopedId r1agencyId = rail1.agencyId(); + private final FeedScopedId r1routeId = rail1.routeId(); private final FeedScopedId anyId = new FeedScopedId("F", "ANY"); + @Test + void testEmptySelect() { + var m = Matchers.of(TransitGroupSelect.of().build()); + assertEquals("Empty", m.toString()); + assertTrue(m.isEmpty()); + assertFalse(m.match(bus)); + } + @Test void testMode() { - var m = PriorityGroupMatcher.of( + var m = Matchers.of( TransitGroupSelect.of().addModes(List.of(TransitMode.BUS, TransitMode.TRAM)).build() ); assertEquals("Mode(BUS | TRAM)", m.toString()); @@ -46,18 +54,14 @@ void testMode() { @Test void testAgencyIds() { - var m1 = PriorityGroupMatcher.of( - TransitGroupSelect.of().addAgencyIds(List.of(r1agencyId)).build() - ); - var m2 = PriorityGroupMatcher.of( - TransitGroupSelect.of().addAgencyIds(List.of(r1agencyId, anyId)).build() - ); + var m1 = Matchers.of(TransitGroupSelect.of().addAgencyIds(List.of(r1agencyId)).build()); + var m2 = Matchers.of(TransitGroupSelect.of().addAgencyIds(List.of(r1agencyId, anyId)).build()); var matchers = List.of(m1, m2); assertEquals("AgencyId(F:A1)", m1.toString()); assertEquals("AgencyId(F:A1 | F:ANY)", m2.toString()); - for (PriorityGroupMatcher m : matchers) { + for (Matcher m : matchers) { assertFalse(m.isEmpty()); assertTrue(m.match(rail1)); assertTrue(m.match(ferry)); @@ -67,18 +71,14 @@ void testAgencyIds() { @Test void routeIds() { - var m1 = PriorityGroupMatcher.of( - TransitGroupSelect.of().addRouteIds(List.of(r1routeId)).build() - ); - var m2 = PriorityGroupMatcher.of( - TransitGroupSelect.of().addRouteIds(List.of(r1routeId, anyId)).build() - ); + var m1 = Matchers.of(TransitGroupSelect.of().addRouteIds(List.of(r1routeId)).build()); + var m2 = Matchers.of(TransitGroupSelect.of().addRouteIds(List.of(r1routeId, anyId)).build()); var matchers = List.of(m1, m2); assertEquals("RouteId(F:R1)", m1.toString()); assertEquals("RouteId(F:R1 | F:ANY)", m2.toString()); - for (PriorityGroupMatcher m : matchers) { + for (Matcher m : matchers) { assertFalse(m.isEmpty()); assertTrue(m.match(rail1)); assertFalse(m.match(ferry)); @@ -88,7 +88,7 @@ void routeIds() { @Test void testSubMode() { - var subject = PriorityGroupMatcher.of( + var subject = Matchers.of( TransitGroupSelect.of().addSubModeRegexp(List.of(".*local.*")).build() ); @@ -102,7 +102,7 @@ void testSubMode() { @Test void testAnd() { - var subject = PriorityGroupMatcher.of( + var subject = Matchers.of( TransitGroupSelect .of() .addSubModeRegexp(List.of("express")) @@ -124,7 +124,7 @@ void testAnd() { @Test void testToString() { - var subject = PriorityGroupMatcher.of( + var subject = Matchers.of( TransitGroupSelect .of() .addModes(List.of(TransitMode.BUS, TransitMode.TRAM)) diff --git a/src/test/java/org/opentripplanner/transit/model/network/grouppriority/TransitGroupPriority32nTest.java b/src/test/java/org/opentripplanner/transit/model/network/grouppriority/TransitGroupPriority32nTest.java new file mode 100644 index 00000000000..9f681dc16c6 --- /dev/null +++ b/src/test/java/org/opentripplanner/transit/model/network/grouppriority/TransitGroupPriority32nTest.java @@ -0,0 +1,68 @@ +package org.opentripplanner.transit.model.network.grouppriority; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriority32n.dominate; +import static org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriority32n.groupId; +import static org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriority32n.mergeInGroupId; + +import org.junit.jupiter.api.Test; + +class TransitGroupPriority32nTest { + + private static final int GROUP_INDEX_0 = 0; + private static final int GROUP_INDEX_1 = 1; + private static final int GROUP_INDEX_2 = 2; + private static final int GROUP_INDEX_30 = 30; + private static final int GROUP_INDEX_31 = 31; + + private static final int GROUP_0 = groupId(GROUP_INDEX_0); + private static final int GROUP_1 = groupId(GROUP_INDEX_1); + private static final int GROUP_2 = groupId(GROUP_INDEX_2); + private static final int GROUP_30 = groupId(GROUP_INDEX_30); + private static final int GROUP_31 = groupId(GROUP_INDEX_31); + + @Test + void testGroupId() { + assertEqualsHex(0x00_00_00_00, groupId(0)); + assertEqualsHex(0x00_00_00_01, groupId(1)); + assertEqualsHex(0x00_00_00_02, groupId(2)); + assertEqualsHex(0x00_00_00_04, groupId(3)); + assertEqualsHex(0x40_00_00_00, groupId(31)); + assertEqualsHex(0x80_00_00_00, groupId(32)); + + assertThrows(IllegalArgumentException.class, () -> groupId(-1)); + assertThrows(IllegalArgumentException.class, () -> groupId(33)); + } + + @Test + void mergeTransitGroupPriorityIds() { + assertEqualsHex(GROUP_0, mergeInGroupId(GROUP_0, GROUP_0)); + assertEqualsHex(GROUP_1, mergeInGroupId(GROUP_1, GROUP_1)); + assertEqualsHex(GROUP_0 | GROUP_1, mergeInGroupId(GROUP_0, GROUP_1)); + assertEqualsHex(GROUP_30 | GROUP_31, mergeInGroupId(GROUP_30, GROUP_31)); + assertEqualsHex( + GROUP_0 | GROUP_1 | GROUP_2 | GROUP_30 | GROUP_31, + mergeInGroupId(GROUP_0 | GROUP_1 | GROUP_2 | GROUP_30, GROUP_31) + ); + } + + @Test + void dominanceFunction() { + assertFalse(dominate(GROUP_0, GROUP_0)); + assertFalse(dominate(GROUP_31, GROUP_31)); + assertFalse(dominate(GROUP_1 | GROUP_2, GROUP_1 | GROUP_2)); + + assertTrue(dominate(GROUP_0, GROUP_1)); + assertFalse(dominate(GROUP_1, GROUP_0)); + + assertTrue(dominate(GROUP_1, GROUP_1 | GROUP_2)); + assertFalse(dominate(GROUP_1 | GROUP_2, GROUP_1)); + } + + static void assertEqualsHex(int expected, int actual) { + assertEquals(expected, actual, "%08x == %08x".formatted(expected, actual)); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfiguratorTest.java b/src/test/java/org/opentripplanner/transit/model/network/grouppriority/TransitGroupPriorityServiceTest.java similarity index 77% rename from src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfiguratorTest.java rename to src/test/java/org/opentripplanner/transit/model/network/grouppriority/TransitGroupPriorityServiceTest.java index 7f974927c1b..993eb14c424 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/PriorityGroupConfiguratorTest.java +++ b/src/test/java/org/opentripplanner/transit/model/network/grouppriority/TransitGroupPriorityServiceTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.raptoradapter.transit.request; +package org.opentripplanner.transit.model.network.grouppriority; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TestTransitCaseData.STOP_A; @@ -7,12 +7,14 @@ import java.util.List; import org.junit.jupiter.api.Test; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TestRouteData; import org.opentripplanner.routing.api.request.request.filter.TransitGroupSelect; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.site.RegularStop; +import org.opentripplanner.transit.model.timetable.Trip; -class PriorityGroupConfiguratorTest { +class TransitGroupPriorityServiceTest { private static final String AGENCY_A1 = "A1"; private static final String AGENCY_A2 = "A2"; @@ -65,13 +67,15 @@ class PriorityGroupConfiguratorTest { private final TripPattern railR3 = routeR3.getTripPattern(); private final TripPattern ferryF3 = routeF3.getTripPattern(); private final TripPattern busB3 = routeB3.getTripPattern(); + private final TripPattern nullTripPattern = null; + private final Trip nullTrip = null; @Test void emptyConfigurationShouldReturnGroupZero() { - var subject = PriorityGroupConfigurator.of(List.of(), List.of()); + var subject = TransitGroupPriorityService.empty(); assertEquals(subject.baseGroupId(), subject.lookupTransitGroupPriorityId(railR1)); assertEquals(subject.baseGroupId(), subject.lookupTransitGroupPriorityId(busB2)); - assertEquals(subject.baseGroupId(), subject.lookupTransitGroupPriorityId(null)); + assertEquals(subject.baseGroupId(), subject.lookupTransitGroupPriorityId(nullTripPattern)); } @Test @@ -82,21 +86,26 @@ void lookupTransitGroupIdByAgency() { .build(); // Add matcher `byAgency` for bus and real - var subject = PriorityGroupConfigurator.of(List.of(select), List.of()); + var subject = new TransitGroupPriorityService(List.of(select), List.of()); // Agency groups are indexed (group-id set) at request time - assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitGroupPriorityId(null)); + assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitGroupPriorityId(nullTripPattern)); assertEquals(EXP_GROUP_1, subject.lookupTransitGroupPriorityId(busB2)); assertEquals(EXP_GROUP_2, subject.lookupTransitGroupPriorityId(railR3)); assertEquals(EXP_GROUP_3, subject.lookupTransitGroupPriorityId(railR1)); assertEquals(EXP_GROUP_2, subject.lookupTransitGroupPriorityId(busB3)); assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitGroupPriorityId(ferryF3)); + + // Verify we get the same result with using the trip, not trip-pattern + assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitGroupPriorityId(nullTrip)); + assertEquals(EXP_GROUP_1, subject.lookupTransitGroupPriorityId(busB2.getTrip(0))); + assertEquals(EXP_GROUP_2, subject.lookupTransitGroupPriorityId(railR3.getTrip(0))); } @Test void lookupTransitPriorityGroupIdByGlobalMode() { // Global groups are indexed (group-id set) at construction time - var subject = PriorityGroupConfigurator.of( + var subject = new TransitGroupPriorityService( List.of(), List.of( TransitGroupSelect.of().addModes(List.of(TransitMode.BUS)).build(), @@ -104,12 +113,16 @@ void lookupTransitPriorityGroupIdByGlobalMode() { ) ); - assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitGroupPriorityId(null)); + assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitGroupPriorityId(nullTripPattern)); assertEquals(EXP_GROUP_2, subject.lookupTransitGroupPriorityId(railR1)); assertEquals(EXP_GROUP_1, subject.lookupTransitGroupPriorityId(busB2)); assertEquals(EXP_GROUP_2, subject.lookupTransitGroupPriorityId(railR3)); assertEquals(EXP_GROUP_1, subject.lookupTransitGroupPriorityId(busB3)); assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitGroupPriorityId(ferryF3)); + + // Verify we get the same result with using the trip, not trip-pattern + assertEquals(EXP_GROUP_ID_BASE, subject.lookupTransitGroupPriorityId(nullTrip)); + assertEquals(EXP_GROUP_2, subject.lookupTransitGroupPriorityId(railR1.getTrip(0))); } private static TestRouteData route( diff --git a/src/test/java/org/opentripplanner/transit/model/network/grouppriority/TripAdapterTest.java b/src/test/java/org/opentripplanner/transit/model/network/grouppriority/TripAdapterTest.java new file mode 100644 index 00000000000..c1aeeea1a59 --- /dev/null +++ b/src/test/java/org/opentripplanner/transit/model/network/grouppriority/TripAdapterTest.java @@ -0,0 +1,34 @@ +package org.opentripplanner.transit.model.network.grouppriority; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.timetable.Trip; + +class TripAdapterTest { + + private final Trip trip = TransitModelForTest.trip("Trip").build(); + + private final TripAdapter subject = new TripAdapter(trip); + + @Test + void mode() { + assertEquals(trip.getMode(), subject.mode()); + } + + @Test + void subMode() { + assertEquals(trip.getNetexSubMode().name(), subject.subMode()); + } + + @Test + void agencyId() { + assertEquals(trip.getRoute().getAgency().getId(), subject.agencyId()); + } + + @Test + void routeId() { + assertEquals(trip.getRoute().getId(), subject.routeId()); + } +} From 5a50d6393c14a5fc87183131702abf3e68f429a9 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 17 Jun 2024 17:11:58 +0200 Subject: [PATCH 036/121] Keep at least one result for min-transfers and each transit-group in itinerary-group-filter #5919 (squashed 4d0dc4c7..9b4b7fe8) --- docs/Configuration.md | 62 +++--- .../framework/application/OTPFeature.java | 8 + .../ItineraryListFilterChainBuilder.java | 36 +++- .../system/SingleCriteriaComparator.java | 68 ++++++ .../filters/system/mcmax/Group.java | 55 +++++ .../filters/system/mcmax/Item.java | 47 ++++ .../system/mcmax/McMaxLimitFilter.java | 105 +++++++++ .../filters/system/mcmax/State.java | 201 ++++++++++++++++++ .../RouteRequestToFilterChainMapper.java | 4 + .../datastore/file/ZipFileDataSourceTest.java | 1 - .../ZipStreamDataSourceDecoratorTest.java | 1 - .../model/plan/TestItineraryBuilder.java | 50 +++-- .../system/SingleCriteriaComparatorTest.java | 109 ++++++++++ .../filters/system/mcmax/ItemTest.java | 56 +++++ .../system/mcmax/McMaxLimitFilterTest.java | 192 +++++++++++++++++ 15 files changed, 943 insertions(+), 52 deletions(-) create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingleCriteriaComparator.java create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/Group.java create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/Item.java create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/McMaxLimitFilter.java create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/State.java create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingleCriteriaComparatorTest.java create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/ItemTest.java create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/McMaxLimitFilterTest.java diff --git a/docs/Configuration.md b/docs/Configuration.md index 2f34b598fa7..71048f08f82 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -219,37 +219,37 @@ 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. | ✓️ | | -| `APIUpdaterStatus` | Enable endpoint for graph updaters status. | ✓️ | | -| `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. | | | -| `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). Turning this _off_ will increase the routing performance a little. | ✓️ | | -| `TransmodelGraphQlApi` | Enable the [Transmodel (NeTEx) GraphQL API](apis/TransmodelApi.md). | ✓️ | ✓️ | -| `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. | | ✓️ | -| `LegacyRestApi` | Enable legacy REST API. This API will be removed in the future. | ✓️ | ✓️ | -| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with real-time data | | ✓️ | -| `ReportApi` | Enable the report API. | | ✓️ | -| `RestAPIPassInDefaultConfigAsJson` | Enable a default RouteRequest to be passed in as JSON on the REST API - FOR DEBUGGING ONLY! | | | -| `SandboxAPIGeocoder` | Enable the Geocoder API. | | ✓️ | -| `SandboxAPIMapboxVectorTilesApi` | Enable Mapbox vector tiles API. | | ✓️ | -| `SandboxAPIParkAndRideApi` | Enable park-and-ride endpoint. | | ✓️ | -| `TransferAnalyzer` | Analyze transfers during graph build. | | ✓️ | +| Feature | Description | Enabled by default | Sandbox | +|--------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------:|:-------:| +| `APIBikeRental` | Enable the bike rental endpoint. | ✓️ | | +| `APIServerInfo` | Enable the server info endpoint. | ✓️ | | +| `APIUpdaterStatus` | Enable endpoint for graph updaters status. | ✓️ | | +| `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. | ✓️ | | +| `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. | | | +| `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). Turning this _off_ will increase the routing performance a little. | ✓️ | | +| `TransmodelGraphQlApi` | Enable the [Transmodel (NeTEx) GraphQL API](apis/TransmodelApi.md). | ✓️ | ✓️ | +| `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. | | ✓️ | +| `LegacyRestApi` | Enable legacy REST API. This API will be removed in the future. | ✓️ | ✓️ | +| `MultiCriteriaGroupMaxFilter` | Keep the best itinerary with respect to each criteria used in the transit-routing search. For example the itinerary with the lowest cost, fewest transfers, and each unique transit-group (transit-group-priority) is kept, even if the max-limit is exceeded. This is turned off by default for now, until this feature is well tested. | | | +| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with real-time data | | ✓️ | +| `ReportApi` | Enable the report API. | | ✓️ | +| `RestAPIPassInDefaultConfigAsJson` | Enable a default RouteRequest to be passed in as JSON on the REST API - FOR DEBUGGING ONLY! | | | +| `SandboxAPIGeocoder` | Enable the Geocoder API. | | ✓️ | +| `SandboxAPIMapboxVectorTilesApi` | Enable Mapbox vector tiles API. | | ✓️ | +| `SandboxAPIParkAndRideApi` | Enable park-and-ride endpoint. | | ✓️ | +| `TransferAnalyzer` | Analyze transfers during graph build. | | ✓️ | diff --git a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index d7bd15922fc..58d4b7b4cb3 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -91,6 +91,14 @@ public enum OTPFeature { FlexRouting(false, true, "Enable FLEX routing."), GoogleCloudStorage(false, true, "Enable Google Cloud Storage integration."), LegacyRestApi(true, true, "Enable legacy REST API. This API will be removed in the future."), + MultiCriteriaGroupMaxFilter( + false, + false, + "Keep the best itinerary with respect to each criteria used in the transit-routing search. " + + "For example the itinerary with the lowest cost, fewest transfers, and each unique transit-group " + + "(transit-group-priority) is kept, even if the max-limit is exceeded. This is turned off by default " + + "for now, until this feature is well tested." + ), RealtimeResolver( false, true, diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java index e8b8ed43c1c..071814a7abf 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java @@ -12,6 +12,7 @@ import java.util.function.Function; import javax.annotation.Nullable; import org.opentripplanner.ext.accessibilityscore.DecorateWithAccessibilityScore; +import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.collection.ListSection; import org.opentripplanner.framework.lang.Sandbox; import org.opentripplanner.model.plan.Itinerary; @@ -27,6 +28,8 @@ import org.opentripplanner.routing.algorithm.filterchain.filters.system.NumItinerariesFilter; import org.opentripplanner.routing.algorithm.filterchain.filters.system.OutsideSearchWindowFilter; import org.opentripplanner.routing.algorithm.filterchain.filters.system.PagingFilter; +import org.opentripplanner.routing.algorithm.filterchain.filters.system.SingleCriteriaComparator; +import org.opentripplanner.routing.algorithm.filterchain.filters.system.mcmax.McMaxLimitFilter; import org.opentripplanner.routing.algorithm.filterchain.filters.transit.DecorateTransitAlert; import org.opentripplanner.routing.algorithm.filterchain.filters.transit.KeepItinerariesWithFewestTransfers; import org.opentripplanner.routing.algorithm.filterchain.filters.transit.RemoveItinerariesWithShortStreetLeg; @@ -64,7 +67,6 @@ public class ItineraryListFilterChainBuilder { private static final int NOT_SET = -1; private final SortOrder sortOrder; private final List groupBySimilarity = new ArrayList<>(); - private ItineraryFilterDebugProfile debug = ItineraryFilterDebugProfile.OFF; private int maxNumberOfItineraries = NOT_SET; private ListSection maxNumberOfItinerariesCropSection = ListSection.TAIL; @@ -86,6 +88,7 @@ public class ItineraryListFilterChainBuilder { private double minBikeParkingDistance; private boolean removeTransitIfWalkingIsBetter = true; private ItinerarySortKey itineraryPageCut; + private boolean transitGroupPriorityUsed = false; /** * Sandbox filters which decorate the itineraries with extra information. @@ -292,6 +295,15 @@ public ItineraryListFilterChainBuilder withPagingDeduplicationFilter( return this; } + /** + * Adjust filters to include multi-criteria parameter c2 and treat it as the + * transit-group. + */ + public ItineraryListFilterChainBuilder withTransitGroupPriority() { + this.transitGroupPriorityUsed = true; + return this; + } + /** * If set, walk-all-the-way itineraries are removed. This happens AFTER e.g. the group-by and * remove-transit-with-higher-cost-than-best-on-street-only filter. This make sure that poor @@ -531,7 +543,7 @@ private ItineraryListFilter buildGroupBySameRoutesAndStopsFilter() { GroupBySameRoutesAndStops::new, List.of( new SortingFilter(SortOrderComparator.comparator(sortOrder)), - new RemoveFilter(new MaxLimit(GroupBySameRoutesAndStops.TAG, 1)) + new RemoveFilter(createMaxLimitFilter(GroupBySameRoutesAndStops.TAG, 1)) ) ); } @@ -574,7 +586,7 @@ private List buildGroupByTripIdAndDistanceFilters() { GroupByAllSameStations::new, List.of( new SortingFilter(generalizedCostComparator()), - new RemoveFilter(new MaxLimit(innerGroupName, 1)) + new RemoveFilter(createMaxLimitFilter(innerGroupName, 1)) ) ) ); @@ -587,7 +599,7 @@ private List buildGroupByTripIdAndDistanceFilters() { } addSort(nested, generalizedCostComparator()); - addRemoveFilter(nested, new MaxLimit(tag, group.maxNumOfItinerariesPerGroup)); + addRemoveFilter(nested, createMaxLimitFilter(tag, group.maxNumOfItinerariesPerGroup)); nested.add(new KeepItinerariesWithFewestTransfers(sysTags)); @@ -620,4 +632,20 @@ private static void addDecorateFilter( ) { filters.add(new DecorateFilter(decorator)); } + + private RemoveItineraryFlagger createMaxLimitFilter(String filterName, int maxLimit) { + if (OTPFeature.MultiCriteriaGroupMaxFilter.isOn()) { + List comparators = new ArrayList<>(); + comparators.add(SingleCriteriaComparator.compareGeneralizedCost()); + comparators.add(SingleCriteriaComparator.compareNumTransfers()); + if (transitGroupPriorityUsed) { + comparators.add(SingleCriteriaComparator.compareTransitGroupsPriority()); + } + return new McMaxLimitFilter(filterName, maxLimit, comparators); + } + // Default is to just use a "hard" max limit + else { + return new MaxLimit(filterName, maxLimit); + } + } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingleCriteriaComparator.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingleCriteriaComparator.java new file mode 100644 index 00000000000..ee814213157 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingleCriteriaComparator.java @@ -0,0 +1,68 @@ +package org.opentripplanner.routing.algorithm.filterchain.filters.system; + +import java.util.Comparator; +import java.util.function.ToIntFunction; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitGroupPriority32n; + +/** + * Comparator used to compare a SINGLE criteria for dominance. The difference between this and the + * {@link org.opentripplanner.raptor.util.paretoset.ParetoComparator} is that: + *

    + *
  1. This applies to one criteria, not multiple.
  2. + *
  3. This interface applies to itineraries; It is not generic.
  4. + *
+ * A set of instances of this interface can be used to create a pareto-set. See + * {@link org.opentripplanner.raptor.util.paretoset.ParetoSet} and + * {@link org.opentripplanner.raptor.util.paretoset.ParetoComparator}. + *

+ * This interface extends {@link Comparator} so elements can be sorted as well. Not all criteria + * can be sorted, if so the {@link #strictOrder()} should return false (this is the default). + */ +@FunctionalInterface +public interface SingleCriteriaComparator { + /** + * The left criteria dominates the right criteria. Note! The right criteria may dominate + * the left criteria if there is no {@link #strictOrder()}. If left and right are equals, then + * there is no dominance. + */ + boolean leftDominanceExist(Itinerary left, Itinerary right); + + /** + * Return true if the criteria can be deterministically sorted. + */ + default boolean strictOrder() { + return false; + } + + static SingleCriteriaComparator compareNumTransfers() { + return compareLessThan(Itinerary::getNumberOfTransfers); + } + + static SingleCriteriaComparator compareGeneralizedCost() { + return compareLessThan(Itinerary::getGeneralizedCost); + } + + @SuppressWarnings("OptionalGetWithoutIsPresent") + static SingleCriteriaComparator compareTransitGroupsPriority() { + return (left, right) -> + TransitGroupPriority32n.dominate( + left.getGeneralizedCost2().get(), + right.getGeneralizedCost2().get() + ); + } + + static SingleCriteriaComparator compareLessThan(final ToIntFunction op) { + return new SingleCriteriaComparator() { + @Override + public boolean leftDominanceExist(Itinerary left, Itinerary right) { + return op.applyAsInt(left) < op.applyAsInt(right); + } + + @Override + public boolean strictOrder() { + return true; + } + }; + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/Group.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/Group.java new file mode 100644 index 00000000000..7bfdad83e8f --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/Group.java @@ -0,0 +1,55 @@ +package org.opentripplanner.routing.algorithm.filterchain.filters.system.mcmax; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * The purpose of a group is to maintain a list of items, all optimal for a single + * criteria/comparator. After the group is created, then the criteria is no longer needed, so we do + * not keep a reference to the original criteria. + */ +class Group implements Iterable { + + private final List items = new ArrayList<>(); + + public Group(Item firstItem) { + add(firstItem); + } + + Item first() { + return items.getFirst(); + } + + boolean isEmpty() { + return items.isEmpty(); + } + + boolean isSingleItemGroup() { + return items.size() == 1; + } + + void add(Item item) { + item.incGroupCount(); + items.add(item); + } + + void removeAllItems() { + items.forEach(Item::decGroupCount); + items.clear(); + } + + void addNewDominantItem(Item item) { + removeAllItems(); + add(item); + } + + boolean contains(Item item) { + return this.items.contains(item); + } + + @Override + public Iterator iterator() { + return items.iterator(); + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/Item.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/Item.java new file mode 100644 index 00000000000..36c3d662493 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/Item.java @@ -0,0 +1,47 @@ +package org.opentripplanner.routing.algorithm.filterchain.filters.system.mcmax; + +import org.opentripplanner.model.plan.Itinerary; + +/** + * An item is a decorated itinerary. The extra information added is the index in the input list + * (sort order) and a groupCount. The sort order is used to break ties, while the group-count is + * used to select the itinerary witch exist in the highest number of groups. The group dynamically + * updates the group-count; The count is incremented when an item is added to a group, and + * decremented when the group is removed from the State. + */ +class Item { + + private final Itinerary item; + private final int index; + private int groupCount = 0; + + Item(Itinerary item, int index) { + this.item = item; + this.index = index; + } + + /** + * An item is better than another if the groupCount is higher, and in case of a tie, if the sort + * index is lower. + */ + public boolean betterThan(Item o) { + return groupCount != o.groupCount ? groupCount > o.groupCount : index < o.index; + } + + Itinerary item() { + return item; + } + + void incGroupCount() { + ++this.groupCount; + } + + void decGroupCount() { + --this.groupCount; + } + + @Override + public String toString() { + return "Item #%d {count:%d, %s}".formatted(index, groupCount, item.toStr()); + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/McMaxLimitFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/McMaxLimitFilter.java new file mode 100644 index 00000000000..c0b07e7400a --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/McMaxLimitFilter.java @@ -0,0 +1,105 @@ +package org.opentripplanner.routing.algorithm.filterchain.filters.system.mcmax; + +import java.util.List; +import java.util.function.Predicate; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.routing.algorithm.filterchain.filters.system.SingleCriteriaComparator; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; + +/** + * This filter is used to reduce a set of itineraries down to the specified limit, if possible. + * The filter is guaranteed to keep at least the given {@code minNumItineraries} and/or the best + * itinerary for each criterion. The criterion is defined using the list of {@code comparators}. + *

+ * The main usage of this filter is to combine it with a transit grouping filter and for each group + * make sure there is at least {@code minNumItineraries} and that the best itinerary with respect + * to each criterion is kept. So, if the grouping is based on time and riding common trips, then + * this filter will use the remaining criterion (transfers, generalized-cost, + * [transit-group-priority]) to filter the grouped set of itineraries. DO NOT INCLUDE CRITERIA + * USED TO GROUP THE ITINERARIES, ONLY THE REMAINING CRITERION USED IN THE RAPTOR SEARCH. + *

+ * IMPLEMENTATION DETAILS + *

+ * This is not a trivial problem. In most cases, the best itinerary for a given criteria is unique, + * but there might be ties - same number of transfers, same cost, and/or different priority groups. + * In case of a tie, we will look if an itinerary is "best-in-group" for more than one criterion, + * if so we pick the one which is best in the highest number of groups. Again, if there is a tie + * (best in the same number of groups), then we fall back to the given itinerary sorting order. + *

+ * This filter will use the order of the input itineraries to break ties. So, make sure to call the + * appropriate sort function before this filter is invoked. + *

+ * Note! For criteria like num-of-transfers or generalized-cost, there is only one set of "best" + * itineraries, and usually there are only one or a few itineraries. In case there is more than one, + * picking just one is fine. But, for transit-group-priority there might be more than one optimal + * set of itineraries. For each set, we need to pick one itinerary for the final result. Each of + * these sets may or may not have more than one itinerary. If you group by agency, then there will + * be at least one itinerary for each agency present in the result (simplified, an itinerary may + * consist of legs with different agencies). The transit-group-priority pareto-function used by + * Raptor is reused, so we do not need to worry about the logic here. + *

+ * Let's discuss an example (this example also exists as a unit-test case): + *

+ *   minNumItineraries = 4
+ *   comparators = [ generalized-cost, min-num-transfers, transit-group-priority ]
+ *   itineraries: [
+ *    #0 : [ 1000, 2, (a) ]
+ *    #1 : [ 1000, 3, (a,b) ]
+ *    #2 : [ 1000, 3, (b) ]
+ *    #3 : [ 1200, 1, (a,b) ]
+ *    #4 : [ 1200, 1, (a) ]
+ *    #5 : [ 1300, 2, (c) ]
+ *    #6 : [ 1300, 3, (c) ]
+ *   ]
+ * 
+ * The best itineraries by generalized-cost are (#0, #1, #2). The best itineraries by + * min-num-transfers are (#3, #4). The best itineraries by transit-group-priority are + * (a:(#0, #4), b:(#2), c:(#5, #6)). + *

+ * So we need to pick one from each group (#0, #1, #2), (#3, #4), (#0, #4), (#2), and (#5, #6). + * Since #2 is a single, we pick it first. Itinerary #2 is also one of the best + * generalized-cost itineraries - so we are done with generalized-cost itineraries as well. The two + * groups left are (#3, #4), (#0, #4), and (#5, #6). #4 exists in 2 groups, so we pick it next. Now + * we are left with (#5, #6). To break the tie, we look at the sort-order. We pick + * itinerary #5. Result: #2, #4, and #5. + *

+ * The `minNumItineraries` limit is not met, so we need to pick another itinerary, we use the + * sort-order again and add itinerary #0. The result returned is: [#0, #2, #4, #5] + */ +public class McMaxLimitFilter implements RemoveItineraryFlagger { + + private final String name; + private final int minNumItineraries; + private final List comparators; + + public McMaxLimitFilter( + String name, + int minNumItineraries, + List comparators + ) { + this.name = name; + this.minNumItineraries = minNumItineraries; + this.comparators = comparators; + } + + @Override + public String name() { + return name; + } + + @Override + public List flagForRemoval(List itineraries) { + if (itineraries.size() <= minNumItineraries) { + return List.of(); + } + var state = new State(itineraries, comparators); + state.findAllSingleItemGroupsAndAddTheItemToTheResult(); + state.findTheBestItemsUntilAllGroupsAreRepresentedInTheResult(); + state.fillUpTheResultWithMinimumNumberOfItineraries(minNumItineraries); + + // We now have the itineraries we want, but we must invert this and return the + // list of itineraries to drop - keeping the original order + var ok = state.getResult(); + return itineraries.stream().filter(Predicate.not(ok::contains)).toList(); + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/State.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/State.java new file mode 100644 index 00000000000..93b8b1097c9 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/State.java @@ -0,0 +1,201 @@ +package org.opentripplanner.routing.algorithm.filterchain.filters.system.mcmax; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import javax.annotation.Nullable; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.routing.algorithm.filterchain.filters.system.SingleCriteriaComparator; + +/** + * Keep a list of items, groups and the result in progress. This is just a class for + * simple bookkeeping for the state of the filter. + */ +class State { + + private final List items; + private final List groups; + private final List result = new ArrayList<>(); + + /** + * Initialize the state by wrapping each itinerary in an item (with index) and create groups for + * each criterion with the best itineraries (can be more than one with, for example, the same + * cost). There should be at least one itinerary from each group surviving the filtering process. + * The same itinerary can exist in multiple groups. + */ + State(List itineraries, List comparators) { + this.items = createListOfItems(itineraries); + this.groups = createGroups(items, comparators); + } + + List getResult() { + return result.stream().map(Item::item).toList(); + } + + /** + * Find and add all groups with a single item in them and add them to the result + */ + void findAllSingleItemGroupsAndAddTheItemToTheResult() { + var item = findItemInFirstSingleItemGroup(groups); + while (item != null) { + addToResult(item); + item = findItemInFirstSingleItemGroup(groups); + } + } + + /** + * Find the items with the highest group count and the lowest index. Theoretically, there might be + * a smaller set of itineraries that TOGETHER represent all groups than what we achieve here, but + * it is far more complicated to compute - so this is probably good enough. + */ + void findTheBestItemsUntilAllGroupsAreRepresentedInTheResult() { + while (!groups.isEmpty()) { + addToResult(findBestItem(groups)); + } + } + + /** + * Fill up with itineraries until the minimum number of itineraries is reached + */ + void fillUpTheResultWithMinimumNumberOfItineraries(int minNumItineraries) { + int end = Math.min(items.size(), minNumItineraries); + for (int i = 0; result.size() < end; ++i) { + var it = items.get(i); + if (!result.contains(it)) { + result.add(it); + } + } + } + + private void addToResult(Item item) { + result.add(item); + removeGroupsWitchContainsItem(item); + } + + /** + * If an itinerary is accepted into the final result, then all groups that contain that itinerary + * can be removed. In addition, the item groupCount should be decremented if a group is dropped. + * This makes sure that the groups represented in the final result do not count when selecting the + * next item. + */ + private void removeGroupsWitchContainsItem(Item item) { + for (Group group : groups) { + if (group.contains(item)) { + group.removeAllItems(); + } + } + groups.removeIf(Group::isEmpty); + } + + /** + * The best item is the one which exists in most groups, and in case of a tie, the sort order/ + * itinerary index is used. + */ + private static Item findBestItem(List groups) { + var candidate = groups.getFirst().first(); + for (Group group : groups) { + for (Item item : group) { + if (item.betterThan(candidate)) { + candidate = item; + } + } + } + return candidate; + } + + /** + * Search through all groups and return all items witch comes from groups with only one item. + */ + @Nullable + private static Item findItemInFirstSingleItemGroup(List groups) { + return groups + .stream() + .filter(Group::isSingleItemGroup) + .findFirst() + .map(Group::first) + .orElse(null); + } + + private static ArrayList createListOfItems(List itineraries) { + var items = new ArrayList(); + for (int i = 0; i < itineraries.size(); i++) { + items.add(new Item(itineraries.get(i), i)); + } + return items; + } + + private static List createGroups( + Collection items, + List comparators + ) { + List groups = new ArrayList<>(); + for (SingleCriteriaComparator comparator : comparators) { + if (comparator.strictOrder()) { + groups.add(createOrderedGroup(items, comparator)); + } else { + groups.addAll(createUnorderedGroups(items, comparator)); + } + } + return groups; + } + + /** + * In a strict ordered group only one optimal value exist for the criteria defined by the given + * {@code comparator}. All items that have this value should be included in the group created. + */ + private static Group createOrderedGroup( + Collection items, + SingleCriteriaComparator comparator + ) { + Group group = null; + for (Item item : items) { + if (group == null) { + group = new Group(item); + continue; + } + var current = group.first(); + if (comparator.leftDominanceExist(item.item(), current.item())) { + group.addNewDominantItem(item); + } else if (!comparator.leftDominanceExist(current.item(), item.item())) { + group.add(item); + } + } + return group; + } + + /** + * For a none strict ordered criteria, multiple optimal values exist. The criterion is defined by + * the given {@code comparator}. This method will create a group for each optimal value found in + * the given set of items. + * + * @see #createOrderedGroup(Collection, SingleCriteriaComparator) + */ + private static Collection createUnorderedGroups( + Collection items, + SingleCriteriaComparator comparator + ) { + List result = new ArrayList<>(); + + for (Item item : items) { + int groupCount = result.size(); + for (Group group : result) { + var groupItem = group.first().item(); + if (comparator.leftDominanceExist(groupItem, item.item())) { + if (comparator.leftDominanceExist(item.item(), groupItem)) { + // Mutual dominance => the item belong in another group + --groupCount; + } + } else { + if (comparator.leftDominanceExist(item.item(), groupItem)) { + group.removeAllItems(); + } + group.add(item); + } + } + if (groupCount == 0) { + result.add(new Group(item)); + } + } + return result; + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java index 651d94b4eac..c1fab68f999 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java @@ -94,6 +94,10 @@ public static ItineraryListFilterChain createFilterChain( .withRemoveTransitIfWalkingIsBetter(true) .withDebugEnabled(params.debug()); + if (!request.preferences().transit().relaxTransitGroupPriority().isNormal()) { + builder.withTransitGroupPriority(); + } + var fareService = context.graph().getFareService(); if (fareService != null) { builder.withFareDecorator(new DecorateWithFare(fareService)); diff --git a/src/test/java/org/opentripplanner/datastore/file/ZipFileDataSourceTest.java b/src/test/java/org/opentripplanner/datastore/file/ZipFileDataSourceTest.java index da46fc430b6..b5b8f797eb3 100644 --- a/src/test/java/org/opentripplanner/datastore/file/ZipFileDataSourceTest.java +++ b/src/test/java/org/opentripplanner/datastore/file/ZipFileDataSourceTest.java @@ -74,7 +74,6 @@ public void testIO() throws IOException { Collection content = subject.content(); Collection names = content.stream().map(DataSource::name).toList(); - //System.out.println(names); assertTrue( names.containsAll(List.of("agency.txt", "stops.txt", "trips.txt")), names.toString() diff --git a/src/test/java/org/opentripplanner/datastore/file/ZipStreamDataSourceDecoratorTest.java b/src/test/java/org/opentripplanner/datastore/file/ZipStreamDataSourceDecoratorTest.java index 09479d85478..21d4bc6bed0 100644 --- a/src/test/java/org/opentripplanner/datastore/file/ZipStreamDataSourceDecoratorTest.java +++ b/src/test/java/org/opentripplanner/datastore/file/ZipStreamDataSourceDecoratorTest.java @@ -100,7 +100,6 @@ void testIO() throws IOException { Collection content = subject.content(); Collection names = content.stream().map(DataSource::name).toList(); - System.out.println(names); assertTrue(names.containsAll(EXPECTED_ZIP_ENTRIES), names.toString()); DataSource entry = subject.entry("agency.txt"); diff --git a/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java b/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java index 28be5b3a7e2..edaafabd753 100644 --- a/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java +++ b/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java @@ -55,6 +55,8 @@ */ public class TestItineraryBuilder implements PlanTestConstants { + private static final int NOT_SET = -1_999_999; + public static final LocalDate SERVICE_DAY = LocalDate.of(2020, Month.FEBRUARY, 2); public static final Route BUS_ROUTE = route("1").withMode(TransitMode.BUS).build(); public static final Route RAIL_ROUTE = route("2").withMode(TransitMode.RAIL).build(); @@ -69,7 +71,8 @@ public class TestItineraryBuilder implements PlanTestConstants { private final List legs = new ArrayList<>(); private Place lastPlace; private int lastEndTime; - private int cost = 0; + private int c1 = 0; + private int c2 = NOT_SET; private TestItineraryBuilder(Place origin, int startTime) { this.lastPlace = origin; @@ -241,7 +244,7 @@ public TestItineraryBuilder flex(int start, int end, Place to) { FlexibleTransitLeg leg = new FlexibleTransitLeg(edge, newTime(start), newTime(end), legCost); legs.add(leg); - cost += legCost; + c1 += legCost; // Setup for adding another leg lastEndTime = end; @@ -330,17 +333,6 @@ public TestItineraryBuilder faresV2Rail( ); } - public Itinerary egress(int walkDuration) { - walk(walkDuration, null); - return build(); - } - - public Itinerary build() { - Itinerary itinerary = new Itinerary(legs); - itinerary.setGeneralizedCost(cost); - return itinerary; - } - public TestItineraryBuilder frequencyBus(int tripId, int startTime, int endTime, Place to) { return transit( RAIL_ROUTE, @@ -401,6 +393,34 @@ public TestItineraryBuilder carHail(int duration, Place to) { return this; } + public TestItineraryBuilder withGeneralizedCost2(int c2) { + this.c2 = c2; + return this; + } + + public Itinerary egress(int walkDuration) { + walk(walkDuration, null); + return build(); + } + + /** + * Override any value set for c1. The given value will be assigned to the itinerary + * independent of any values set on the legs. + */ + public Itinerary build(int c1) { + this.c1 = c1; + return build(); + } + + public Itinerary build() { + Itinerary itinerary = new Itinerary(legs); + itinerary.setGeneralizedCost(c1); + if (c2 != NOT_SET) { + itinerary.setGeneralizedCost2(c2); + } + return itinerary; + } + /* private methods */ /** Create a dummy trip */ @@ -506,7 +526,7 @@ public TestItineraryBuilder transit( leg.setDistanceMeters(speed(leg.getMode()) * (end - start)); legs.add(leg); - cost += legCost; + c1 += legCost; // Setup for adding another leg lastEndTime = end; @@ -536,7 +556,7 @@ private StreetLeg streetLeg( .build(); legs.add(leg); - cost += legCost; + c1 += legCost; // Setup for adding another leg lastEndTime = endTime; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingleCriteriaComparatorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingleCriteriaComparatorTest.java new file mode 100644 index 00000000000..ebcf441370f --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingleCriteriaComparatorTest.java @@ -0,0 +1,109 @@ +package org.opentripplanner.routing.algorithm.filterchain.filters.system; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.Place; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitGroupPriority32n; +import org.opentripplanner.transit.model._data.TransitModelForTest; + +class SingleCriteriaComparatorTest { + + private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of(); + + private static final Place A = TEST_MODEL.place("A", 10, 11); + private static final Place B = TEST_MODEL.place("B", 10, 13); + private static final Place C = TEST_MODEL.place("C", 10, 14); + private static final Place D = TEST_MODEL.place("D", 10, 15); + + private static final int START = 1000; + private static final int TX_AT = 1500; + private static final int END_LOW = 2000; + + // [Tx, Cost] => [0, 1240] + private static final Itinerary zeroTransferLowCost = newItinerary(A) + .bus(1, START, END_LOW, B) + .walk(60, C) + .build(); + // [Tx, Cost] => [0, 1360] + private static final Itinerary zeroTransferHighCost = newItinerary(A) + .bus(1, START, END_LOW, B) + .walk(120, C) + .build(); + // [Tx, Cost] => [1, 1240] + private static final Itinerary oneTransferLowCost = newItinerary(A) + .bus(1, START, TX_AT, B) + .bus(2, TX_AT, END_LOW, C) + .build(); + + @BeforeAll + static void setUp() { + assertEquals(0, zeroTransferLowCost.getNumberOfTransfers()); + assertEquals(0, zeroTransferHighCost.getNumberOfTransfers()); + assertEquals(1, oneTransferLowCost.getNumberOfTransfers()); + + int expectedCost = zeroTransferLowCost.getGeneralizedCost(); + assertTrue(expectedCost < zeroTransferHighCost.getGeneralizedCost()); + assertEquals(expectedCost, oneTransferLowCost.getGeneralizedCost()); + } + + @Test + void strictOrder() { + assertTrue(SingleCriteriaComparator.compareNumTransfers().strictOrder()); + assertTrue(SingleCriteriaComparator.compareGeneralizedCost().strictOrder()); + assertFalse(SingleCriteriaComparator.compareTransitGroupsPriority().strictOrder()); + } + + @Test + void compareNumTransfers() { + var subject = SingleCriteriaComparator.compareNumTransfers(); + + // leftDominanceExist + assertFalse(subject.leftDominanceExist(zeroTransferHighCost, zeroTransferLowCost)); + assertTrue(subject.leftDominanceExist(zeroTransferLowCost, oneTransferLowCost)); + assertFalse(subject.leftDominanceExist(oneTransferLowCost, zeroTransferLowCost)); + + // strict order expected + assertTrue(subject.strictOrder()); + } + + @Test + void compareGeneralizedCost() { + var subject = SingleCriteriaComparator.compareGeneralizedCost(); + + // leftDominanceExist + assertFalse(subject.leftDominanceExist(zeroTransferHighCost, zeroTransferLowCost)); + assertTrue(subject.leftDominanceExist(zeroTransferLowCost, zeroTransferHighCost)); + assertFalse(subject.leftDominanceExist(zeroTransferLowCost, oneTransferLowCost)); + + // strict order expected + assertTrue(subject.strictOrder()); + } + + @Test + void compareTransitPriorityGroups() { + var group1 = newItinerary(A).bus(1, START, END_LOW, C).withGeneralizedCost2(1).build(); + var group2 = newItinerary(A).bus(1, START, END_LOW, C).withGeneralizedCost2(2).build(); + var group1And2 = newItinerary(A) + .bus(1, START, END_LOW, C) + .withGeneralizedCost2(TransitGroupPriority32n.mergeInGroupId(1, 2)) + .build(); + + var subject = SingleCriteriaComparator.compareTransitGroupsPriority(); + + assertTrue(subject.leftDominanceExist(group1, group2)); + assertTrue(subject.leftDominanceExist(group2, group1)); + assertTrue(subject.leftDominanceExist(group1, group1And2)); + assertTrue(subject.leftDominanceExist(group2, group1And2)); + assertFalse(subject.leftDominanceExist(group1And2, group1)); + assertFalse(subject.leftDominanceExist(group1And2, group1)); + + // Cannot be ordered => compare will fail + assertFalse(subject.strictOrder()); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/ItemTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/ItemTest.java new file mode 100644 index 00000000000..c8cd04212e5 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/ItemTest.java @@ -0,0 +1,56 @@ +package org.opentripplanner.routing.algorithm.filterchain.filters.system.mcmax; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.Place; +import org.opentripplanner.transit.model._data.TransitModelForTest; + +class ItemTest { + + private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of(); + private static final Place A = TEST_MODEL.place("A", 10, 11); + private static final Place B = TEST_MODEL.place("B", 10, 11); + private static final Itinerary ITINERARY = newItinerary(A).bus(1, 1, 2, B).build(); + + @Test + void betterThan() { + var i1 = new Item(ITINERARY, 3); + var i2 = new Item(ITINERARY, 7); + + // i1 is better than i2 because the index is lower + assertTrue(i1.betterThan(i2)); + assertFalse(i2.betterThan(i1)); + + // Incrementing both does not change anything + i1.incGroupCount(); + i2.incGroupCount(); + assertTrue(i1.betterThan(i2)); + assertFalse(i2.betterThan(i1)); + + // Incrementing i2 make it better + i2.incGroupCount(); + assertFalse(i1.betterThan(i2)); + assertTrue(i2.betterThan(i1)); + } + + @Test + void item() { + assertSame(ITINERARY, new Item(ITINERARY, 7).item()); + } + + @Test + void testToString() { + Item item = new Item(ITINERARY, 7); + assertEquals("Item #7 {count:0, A ~ BUS 1 0:00:01 0:00:02 ~ B [C₁121]}", item.toString()); + item.incGroupCount(); + assertEquals("Item #7 {count:1, A ~ BUS 1 0:00:01 0:00:02 ~ B [C₁121]}", item.toString()); + item.decGroupCount(); + assertEquals("Item #7 {count:0, A ~ BUS 1 0:00:01 0:00:02 ~ B [C₁121]}", item.toString()); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/McMaxLimitFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/McMaxLimitFilterTest.java new file mode 100644 index 00000000000..ca7a1bb5336 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/McMaxLimitFilterTest.java @@ -0,0 +1,192 @@ +package org.opentripplanner.routing.algorithm.filterchain.filters.system.mcmax; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; + +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.Place; +import org.opentripplanner.routing.algorithm.filterchain.filters.system.SingleCriteriaComparator; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitGroupPriority32n; +import org.opentripplanner.transit.model._data.TransitModelForTest; + +class McMaxLimitFilterTest { + + private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of(); + + private static final Place A = TEST_MODEL.place("A", 10, 11); + private static final Place B = TEST_MODEL.place("B", 10, 13); + private static final Place C = TEST_MODEL.place("C", 10, 14); + private static final Place D = TEST_MODEL.place("D", 10, 15); + private static final Place E = TEST_MODEL.place("E", 10, 15); + private static final Place[] PLACES = { A, B, C, D, E }; + + private static final int START = 3600 * 10; + private static final int GROUP_A = TransitGroupPriority32n.groupId(1); + private static final int GROUP_B = TransitGroupPriority32n.groupId(2); + private static final int GROUP_C = TransitGroupPriority32n.groupId(3); + private static final int GROUP_AB = TransitGroupPriority32n.mergeInGroupId(GROUP_A, GROUP_B); + private static final int GROUP_BC = TransitGroupPriority32n.mergeInGroupId(GROUP_B, GROUP_C); + private static final int GROUP_ABC = TransitGroupPriority32n.mergeInGroupId(GROUP_AB, GROUP_C); + + private static final boolean EXP_KEEP = true; + private static final boolean EXP_DROP = false; + + private static final int COST_LOW = 1000; + private static final int COST_MED = 1200; + private static final int COST_HIGH = 1500; + + private static final int TX_0 = 0; + private static final int TX_1 = 1; + private static final int TX_2 = 2; + + private final McMaxLimitFilter subject = new McMaxLimitFilter( + "test", + 2, + List.of( + SingleCriteriaComparator.compareGeneralizedCost(), + SingleCriteriaComparator.compareNumTransfers(), + SingleCriteriaComparator.compareTransitGroupsPriority() + ) + ); + + static TestRow row( + boolean expected, + int c1, + int nTransfers, + int transitGroups, + String description + ) { + return new TestRow(expected, c1, nTransfers, transitGroups); + } + + static List> filterTestCases() { + return List.of( + List.of(/* Should not fail for an empty list of itineraries*/), + List.of( + // Test minNumItinerariesLimit = 2 + row(EXP_KEEP, COST_LOW, TX_1, GROUP_A, "Best in everything"), + row(EXP_KEEP, COST_HIGH, TX_2, GROUP_AB, "Worse, kept because minNumItinerariesLimit is 2") + ), + List.of( + // Test minNumItinerariesLimit, first is added + row(EXP_KEEP, COST_HIGH, TX_2, GROUP_ABC, "Worst, kept because of minNumItinerariesLimit"), + row(EXP_KEEP, COST_LOW, TX_0, GROUP_A, "Best in everything"), + row(EXP_DROP, COST_HIGH, TX_1, GROUP_AB, "Dropped because not better than #2.") + ), + List.of( + // The minNumItinerariesLimit is met, so no extra itinerary(#0) is added + row(EXP_DROP, COST_HIGH, TX_2, GROUP_AB, "First element is dropped"), + row(EXP_KEEP, COST_LOW, TX_1, GROUP_B, "Best cost and group B"), + row(EXP_KEEP, COST_MED, TX_0, GROUP_A, "Best nTransfers and group A") + ), + List.of( + row(EXP_KEEP, COST_LOW, TX_2, GROUP_A, "Best: c1 and group A"), + row(EXP_DROP, COST_LOW, TX_1, GROUP_AB, "Best compromise: c1, Tx, and group AB"), + row(EXP_KEEP, COST_LOW, TX_2, GROUP_C, "Best: c1 and group C"), + row(EXP_KEEP, COST_MED, TX_0, GROUP_BC, "Best: num-of-transfers") + ), + /** + * This is the example explained in JavaDoc {@link McMaxLimitFilter} + */ + List.of( + row(EXP_DROP, COST_LOW, TX_1, GROUP_A, ""), + row(EXP_DROP, COST_LOW, TX_2, GROUP_AB, ""), + row(EXP_KEEP, COST_LOW, TX_2, GROUP_B, "Kept -> Only one in group B"), + row(EXP_DROP, COST_MED, TX_0, GROUP_AB, ""), + row(EXP_KEEP, COST_MED, TX_0, GROUP_A, "Kept -> Best transfer and group A"), + row(EXP_KEEP, COST_HIGH, TX_1, GROUP_C, "Kept -> Best group C, tie with #6"), + row(EXP_DROP, COST_HIGH, TX_2, GROUP_C, "") + ) + ); + } + + @ParameterizedTest + @MethodSource("filterTestCases") + void filterTest(List rows) { + var input = rows.stream().map(TestRow::create).toList(); + var expected = rows.stream().filter(TestRow::expected).map(TestRow::create).toList(); + + var result = subject.removeMatchesForTest(input); + + assertEquals(toStr(expected), toStr(result)); + } + + @Test + void testName() { + assertEquals("test", subject.name()); + } + + /** + * Make sure the test setup is correct - this does not test anything in src/main + */ + @Test + void testGroupsToString() { + assertEquals("A", groupsToString(GROUP_A)); + assertEquals("B", groupsToString(GROUP_B)); + assertEquals("C", groupsToString(GROUP_C)); + assertEquals("AB", groupsToString(GROUP_AB)); + assertEquals("BC", groupsToString(GROUP_BC)); + assertEquals("ABC", groupsToString(GROUP_ABC)); + } + + private static String groupsToString(int groups) { + var buf = new StringBuilder(); + char ch = 'A'; + // Check for 5 groups - the test does not use so many, but it does not matter + for (int i = 0; i < 5; ++i) { + int mask = 1 << i; + if ((groups & mask) != 0) { + buf.append(ch); + } + ch = (char) (ch + 1); + } + return buf.toString(); + } + + private static String toStr(List list) { + return list + .stream() + .map(i -> + "[ %d %d %s ]".formatted( + i.getGeneralizedCost(), + i.getNumberOfTransfers(), + groupsToString(i.getGeneralizedCost2().orElse(-1)) + ) + ) + .collect(Collectors.joining(", ")); + } + + record TestRow(boolean expected, int c1, int nTransfers, int transitGroupIds) { + Itinerary create() { + int start = START; + var builder = newItinerary(A); + + if (nTransfers < 0) { + builder.drive(start, ++start, E); + } else { + builder.bus(1, ++start, ++start, PLACES[1]); + for (int i = 0; i < nTransfers; i++) { + builder.bus(1, ++start, ++start, PLACES[i + 2]); + } + builder.withGeneralizedCost2(transitGroupIds); + } + return builder.build(c1); + } + + @Override + public String toString() { + // The red-x is a unicode character(U+274C) and should be visible in most IDEs. + return "%s %d %d %s".formatted( + expected ? "" : "❌", + c1, + nTransfers, + groupsToString(transitGroupIds) + ); + } + } +} From d424574428e4ad4a3ecdc3cca5eae9b26e6993f8 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 4 Jul 2024 16:47:51 +0200 Subject: [PATCH 037/121] fix: Fix imports accessability issues after merging --- docs/Configuration.md | 1 + .../filters/system/SingleCriteriaComparator.java | 2 +- .../network/grouppriority/TransitGroupPriority32n.java | 8 ++++---- .../filters/system/SingleCriteriaComparatorTest.java | 2 +- .../filters/system/mcmax/McMaxLimitFilterTest.java | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index 71048f08f82..26cdfb7c5b0 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/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingleCriteriaComparator.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingleCriteriaComparator.java index ee814213157..b4dd790f31f 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingleCriteriaComparator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingleCriteriaComparator.java @@ -3,7 +3,7 @@ import java.util.Comparator; import java.util.function.ToIntFunction; import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitGroupPriority32n; +import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriority32n; /** * Comparator used to compare a SINGLE criteria for dominance. The difference between this and the diff --git a/src/main/java/org/opentripplanner/transit/model/network/grouppriority/TransitGroupPriority32n.java b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/TransitGroupPriority32n.java index 32423070e09..42c3cedf47f 100644 --- a/src/main/java/org/opentripplanner/transit/model/network/grouppriority/TransitGroupPriority32n.java +++ b/src/main/java/org/opentripplanner/transit/model/network/grouppriority/TransitGroupPriority32n.java @@ -4,7 +4,7 @@ * This is a "BitSet" implementation for groupId. It can store up to 31 groups, * a set with few elements does NOT dominate a set with more elements. */ -class TransitGroupPriority32n { +public class TransitGroupPriority32n { private static final int GROUP_ZERO = 0; private static final int MIN_SEQ_NO = 0; @@ -17,7 +17,7 @@ class TransitGroupPriority32n { * - if they are equals or * - left is a superset of right. */ - static boolean dominate(int left, int right) { + public static boolean dominate(int left, int right) { return ((left ^ right) & right) != 0; } @@ -25,7 +25,7 @@ static boolean dominate(int left, int right) { * Use this method to map from a continuous group index [0..32) to the groupId used * during routing. The ID is implementation-specific and optimized for performance. */ - static int groupId(final int priorityGroupIndex) { + public static int groupId(final int priorityGroupIndex) { assertValidGroupSeqNo(priorityGroupIndex); return priorityGroupIndex == MIN_SEQ_NO ? GROUP_ZERO : 0x01 << (priorityGroupIndex - 1); } @@ -33,7 +33,7 @@ static int groupId(final int priorityGroupIndex) { /** * Merge a groupId into a set of groupIds. */ - static int mergeInGroupId(final int currentSetOfGroupIds, final int newGroupId) { + public static int mergeInGroupId(final int currentSetOfGroupIds, final int newGroupId) { return currentSetOfGroupIds | newGroupId; } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingleCriteriaComparatorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingleCriteriaComparatorTest.java index ebcf441370f..601fdb67042 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingleCriteriaComparatorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/SingleCriteriaComparatorTest.java @@ -9,8 +9,8 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Place; -import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitGroupPriority32n; import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriority32n; class SingleCriteriaComparatorTest { diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/McMaxLimitFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/McMaxLimitFilterTest.java index ca7a1bb5336..a3c0a240687 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/McMaxLimitFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/mcmax/McMaxLimitFilterTest.java @@ -11,8 +11,8 @@ import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Place; import org.opentripplanner.routing.algorithm.filterchain.filters.system.SingleCriteriaComparator; -import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitGroupPriority32n; import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriority32n; class McMaxLimitFilterTest { From d2d1b116869351ca2326af15b42e8ba4ee9c0558 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 4 Jul 2024 16:49:11 +0200 Subject: [PATCH 038/121] Version 2.6.0-entur-25 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0735ceaf7d0..90859d2eb07 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.6.0-entur-24 + 2.6.0-entur-25 jar From a61da3f3e355e0c2ff263bbec27a02fec1e15a8d Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 3 Jul 2024 11:07:00 +0200 Subject: [PATCH 039/121] Fix NullPointerException in stop transfer priority cost vector generation #5943 (squashed d6c9f6ff..0c914c4b) --- docs/BuildConfiguration.md | 4 +-- docs/RouterConfiguration.md | 2 +- .../gtfs/graphbuilder/GtfsFeedParameters.java | 3 -- .../GtfsFeedParametersBuilder.java | 2 +- .../transit/mappers/TransfersMapper.java | 5 ++++ .../transit/mappers/TransitLayerMapper.java | 15 ++++++++-- .../transit/model/site/RegularStop.java | 4 ++- .../transit/model/site/Station.java | 5 ++-- .../transit/model/site/StopLocation.java | 2 +- .../model/site/StopTransferPriority.java | 30 +++++++++++-------- .../transit/service/StopModel.java | 1 + .../transit/service/StopModelIndex.java | 26 ++++++++++++++++ 12 files changed, 71 insertions(+), 28 deletions(-) diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index 7d2d90ea41b..cefd8d2cf2f 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -731,7 +731,7 @@ but we want to calculate the transfers always from OSM data. **Since version:** `2.3` ∙ **Type:** `enum` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"allowed"` **Path:** /gtfsDefaults -**Enum values:** `discouraged` | `allowed` | `recommended` | `preferred` +**Enum values:** `preferred` | `recommended` | `allowed` | `discouraged` Should there be some preference or aversion for transfers at stops that are part of a station. @@ -980,7 +980,7 @@ but we want to calculate the transfers always from OSM data. **Since version:** `2.3` ∙ **Type:** `enum` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"allowed"` **Path:** /transitFeeds/[0] -**Enum values:** `discouraged` | `allowed` | `recommended` | `preferred` +**Enum values:** `preferred` | `recommended` | `allowed` | `discouraged` Should there be some preference or aversion for transfers at stops that are part of a station. Overrides the value specified in `gtfsDefaults`. diff --git a/docs/RouterConfiguration.md b/docs/RouterConfiguration.md index 19a70c44d0f..5f588cf99c9 100644 --- a/docs/RouterConfiguration.md +++ b/docs/RouterConfiguration.md @@ -363,7 +363,7 @@ for more info." **Since version:** `2.0` ∙ **Type:** `enum map of integer` ∙ **Cardinality:** `Optional` **Path:** /transit -**Enum keys:** `discouraged` | `allowed` | `recommended` | `preferred` +**Enum keys:** `preferred` | `recommended` | `allowed` | `discouraged` Costs for boarding and alighting during transfers at stops with a given transfer priority. diff --git a/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsFeedParameters.java b/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsFeedParameters.java index fa23e02bac5..68b8874d5e6 100644 --- a/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsFeedParameters.java +++ b/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsFeedParameters.java @@ -24,9 +24,6 @@ public record GtfsFeedParameters( implements DataSourceConfig { public static final boolean DEFAULT_REMOVE_REPEATED_STOPS = true; - public static final StopTransferPriority DEFAULT_STATION_TRANSFER_PREFERENCE = - StopTransferPriority.ALLOWED; - public static final boolean DEFAULT_DISCARD_MIN_TRANSFER_TIMES = false; public static final boolean DEFAULT_BLOCK_BASED_INTERLINING = true; diff --git a/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsFeedParametersBuilder.java b/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsFeedParametersBuilder.java index 9dd3ad3a25e..1e799dbecbb 100644 --- a/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsFeedParametersBuilder.java +++ b/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsFeedParametersBuilder.java @@ -18,7 +18,7 @@ public class GtfsFeedParametersBuilder { public GtfsFeedParametersBuilder() { this.removeRepeatedStops = GtfsFeedParameters.DEFAULT_REMOVE_REPEATED_STOPS; - this.stationTransferPreference = GtfsFeedParameters.DEFAULT_STATION_TRANSFER_PREFERENCE; + this.stationTransferPreference = StopTransferPriority.defaultValue(); this.discardMinTransferTimes = GtfsFeedParameters.DEFAULT_DISCARD_MIN_TRANSFER_TIMES; this.blockBasedInterlining = GtfsFeedParameters.DEFAULT_BLOCK_BASED_INTERLINING; this.maxInterlineDistance = GtfsFeedParameters.DEFAULT_MAX_INTERLINE_DISTANCE; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransfersMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransfersMapper.java index 3cb259d3a67..74a5d7a6352 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransfersMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransfersMapper.java @@ -19,6 +19,11 @@ static List> mapTransfers(StopModel stopModel, TransitModel trans for (int i = 0; i < stopModel.stopIndexSize(); ++i) { var stop = stopModel.stopByIndex(i); + + if (stop == null) { + continue; + } + ArrayList list = new ArrayList<>(); for (PathTransfer pathTransfer : transitModel.getTransfersByStop(stop)) { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java index 210c8b42066..33a076bb8d6 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java @@ -190,12 +190,21 @@ static int[] createStopBoardAlightTransferCosts( if (!tuningParams.enableStopTransferPriority()) { return null; } + int defaultCost = RaptorCostConverter.toRaptorCost( + tuningParams.stopBoardAlightDuringTransferCost(StopTransferPriority.defaultValue()) + ); int[] stopBoardAlightTransferCosts = new int[stops.stopIndexSize()]; for (int i = 0; i < stops.stopIndexSize(); ++i) { - StopTransferPriority priority = stops.stopByIndex(i).getPriority(); - int domainCost = tuningParams.stopBoardAlightDuringTransferCost(priority); - stopBoardAlightTransferCosts[i] = RaptorCostConverter.toRaptorCost(domainCost); + // There can be holes in the stop index, so we need to account for 'null' here. + var stop = stops.stopByIndex(i); + if (stop == null) { + stopBoardAlightTransferCosts[i] = defaultCost; + } else { + var priority = stop.getPriority(); + int domainCost = tuningParams.stopBoardAlightDuringTransferCost(priority); + stopBoardAlightTransferCosts[i] = RaptorCostConverter.toRaptorCost(domainCost); + } } return stopBoardAlightTransferCosts; } diff --git a/src/main/java/org/opentripplanner/transit/model/site/RegularStop.java b/src/main/java/org/opentripplanner/transit/model/site/RegularStop.java index 4c1638e7bae..9f3327beff9 100644 --- a/src/main/java/org/opentripplanner/transit/model/site/RegularStop.java +++ b/src/main/java/org/opentripplanner/transit/model/site/RegularStop.java @@ -127,7 +127,9 @@ public Point getGeometry() { @Override @Nonnull public StopTransferPriority getPriority() { - return isPartOfStation() ? getParentStation().getPriority() : StopTransferPriority.ALLOWED; + return isPartOfStation() + ? getParentStation().getPriority() + : StopTransferPriority.defaultValue(); } @Override diff --git a/src/main/java/org/opentripplanner/transit/model/site/Station.java b/src/main/java/org/opentripplanner/transit/model/site/Station.java index 3bfbcb7594e..68ce2d6d5a2 100644 --- a/src/main/java/org/opentripplanner/transit/model/site/Station.java +++ b/src/main/java/org/opentripplanner/transit/model/site/Station.java @@ -30,8 +30,6 @@ public class Station extends AbstractTransitEntity implements StopLocationsGroup, LogInfo { - public static final StopTransferPriority DEFAULT_PRIORITY = StopTransferPriority.ALLOWED; - private final I18NString name; private final String code; private final I18NString description; @@ -52,7 +50,8 @@ public class Station // Required fields this.name = Objects.requireNonNull(builder.getName()); this.coordinate = Objects.requireNonNull(builder.getCoordinate()); - this.priority = Objects.requireNonNullElse(builder.getPriority(), DEFAULT_PRIORITY); + this.priority = + Objects.requireNonNullElse(builder.getPriority(), StopTransferPriority.defaultValue()); this.transfersNotAllowed = builder.isTransfersNotAllowed(); // Optional fields diff --git a/src/main/java/org/opentripplanner/transit/model/site/StopLocation.java b/src/main/java/org/opentripplanner/transit/model/site/StopLocation.java index a3cbd1a8014..9cb4411437c 100644 --- a/src/main/java/org/opentripplanner/transit/model/site/StopLocation.java +++ b/src/main/java/org/opentripplanner/transit/model/site/StopLocation.java @@ -142,7 +142,7 @@ default ZoneId getTimeZone() { @Nonnull default StopTransferPriority getPriority() { - return StopTransferPriority.ALLOWED; + return StopTransferPriority.defaultValue(); } boolean isPartOfSameStationAs(StopLocation alternativeStop); diff --git a/src/main/java/org/opentripplanner/transit/model/site/StopTransferPriority.java b/src/main/java/org/opentripplanner/transit/model/site/StopTransferPriority.java index 1bc214fd47e..0e072c19ed6 100644 --- a/src/main/java/org/opentripplanner/transit/model/site/StopTransferPriority.java +++ b/src/main/java/org/opentripplanner/transit/model/site/StopTransferPriority.java @@ -9,31 +9,35 @@ */ public enum StopTransferPriority { /** - * Block transfers from/to this stop. In OTP this is not a definitive block, just a huge penalty - * is added to the cost function. + * Preferred place to transfer, strongly recommended. *

- * NeTEx equivalent is NO_INTERCHANGE. + * NeTEx equivalent is PREFERRED_INTERCHANGE. */ - DISCOURAGED, - + PREFERRED, + /** + * Recommended stop place. + *

+ * NeTEx equivalent is RECOMMENDED_INTERCHANGE. + */ + RECOMMENDED, /** * Allow transfers from/to this stop. This is the default. *

* NeTEx equivalent is INTERCHANGE_ALLOWED. */ ALLOWED, - /** - * Recommended stop place. + * Block transfers from/to this stop. In OTP this is not a definitive block, just a huge penalty + * is added to the cost function. *

- * NeTEx equivalent is RECOMMENDED_INTERCHANGE. + * NeTEx equivalent is NO_INTERCHANGE. */ - RECOMMENDED, + DISCOURAGED; /** - * Preferred place to transfer, strongly recommended. - *

- * NeTEx equivalent is PREFERRED_INTERCHANGE. + * The {@link #ALLOWED} is used as default value in cases where the value is not set. */ - PREFERRED, + public static StopTransferPriority defaultValue() { + return ALLOWED; + } } diff --git a/src/main/java/org/opentripplanner/transit/service/StopModel.java b/src/main/java/org/opentripplanner/transit/service/StopModel.java index 763aa504c40..4f55ca25b5b 100644 --- a/src/main/java/org/opentripplanner/transit/service/StopModel.java +++ b/src/main/java/org/opentripplanner/transit/service/StopModel.java @@ -162,6 +162,7 @@ public Collection listGroupStops() { return groupStopById.values(); } + @Nullable public StopLocation stopByIndex(int stopIndex) { return index.stopByIndex(stopIndex); } diff --git a/src/main/java/org/opentripplanner/transit/service/StopModelIndex.java b/src/main/java/org/opentripplanner/transit/service/StopModelIndex.java index 16337d980a1..c12c6f715f7 100644 --- a/src/main/java/org/opentripplanner/transit/service/StopModelIndex.java +++ b/src/main/java/org/opentripplanner/transit/service/StopModelIndex.java @@ -1,8 +1,11 @@ package org.opentripplanner.transit.service; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Objects; +import javax.annotation.Nullable; import org.locationtech.jts.geom.Envelope; import org.opentripplanner.framework.collection.CollectionsView; import org.opentripplanner.framework.geometry.HashGridSpatialIndex; @@ -12,6 +15,9 @@ import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.Station; import org.opentripplanner.transit.model.site.StopLocation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.event.Level; /** * Indexed access to Stop entities. @@ -20,6 +26,8 @@ */ class StopModelIndex { + private static final Logger LOG = LoggerFactory.getLogger(StopModelIndex.class); + private final HashGridSpatialIndex regularStopSpatialIndex = new HashGridSpatialIndex<>(); private final Map multiModalStationForStations = new HashMap<>(); private final HashGridSpatialIndex locationIndex = new HashGridSpatialIndex<>(); @@ -58,6 +66,8 @@ class StopModelIndex { // Trim the sizes of the indices regularStopSpatialIndex.compact(); locationIndex.compact(); + + logHolesInIndex(); } /** @@ -71,6 +81,7 @@ MultiModalStation getMultiModalStationForStation(Station station) { return multiModalStationForStations.get(station); } + @Nullable StopLocation stopByIndex(int index) { return stopsByIndex[index]; } @@ -82,4 +93,19 @@ int stopIndexSize() { Collection findAreaStops(Envelope envelope) { return locationIndex.query(envelope); } + + /** + * A small number of holes in the stop-index is ok, but if there are many, it will affect + * the Raptor performance. + */ + private void logHolesInIndex() { + int c = (int) Arrays.stream(stopsByIndex).filter(Objects::isNull).count(); + if (c > 0) { + double p = (100.0 * c) / stopsByIndex.length; + // Log this as warning if more than 5% of the space is null + LOG + .atLevel(p >= 5.0 ? Level.WARN : Level.INFO) + .log("The stop index contains holes in it. {} of {} is null.", c, stopsByIndex.length); + } + } } From f67bc76632dcf16ceb71c24f3a4d8b270a48585b Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 4 Jul 2024 17:58:58 +0200 Subject: [PATCH 040/121] Version 2.6.0-entur-26 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 90859d2eb07..c757927c930 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.6.0-entur-25 + 2.6.0-entur-26 jar From cc7e191e283153d2515a71dcb6515fd63dbb2fd8 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 041/121] 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 2f007cbb214..bee3e72b846 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 155 + EN-0070 31.3 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 216f73874868555a44f565765e0ce3a5535cc3c9 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 19 Dec 2023 21:26:49 +0100 Subject: [PATCH 042/121] =?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 a4e8b5100a4..742301e0d11 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 14d937eeb63..ac3debca3c5 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 51b2fae86b5..a2a3d079d13 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 3ae029fdb2d..b4912aacb1d 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java @@ -203,6 +203,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 b67b26b90b6..0f18f6577a8 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 @@ -616,6 +616,14 @@ Normally this is when the search is performed (now), plus a small grace period t ) .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 8ee5d100b94..07500ff0643 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 37bb7270902..ea0274a139d 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 @@ -133,7 +133,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 948b132e408..3c3f789918d 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.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; @@ -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 5b9d81fa6c3..087b5a51e55 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; @@ -244,4 +247,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 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 6fa33aac267..72a7dcd8d59 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -332,13 +332,14 @@ A related parameter (transferSlack) also helps avoid missed connections when the } // 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)`. @@ -348,10 +349,17 @@ A related parameter (transferSlack) also helps avoid missed connections when the 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 64440780f78..1ad654a1397 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -805,6 +805,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 38eb3d15367bef6b5475c7e6b2f2127e9bb5585a Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 23 Jul 2024 17:19:53 +0200 Subject: [PATCH 043/121] Fix documentation --- docs/Configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Configuration.md b/docs/Configuration.md index 7e48dab5344..65d3394edfd 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. | | | From 9d2029bfa3dfb6c6188c58ebad41c2f469c04ccf Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 23 Jul 2024 17:23:49 +0200 Subject: [PATCH 044/121] Version 2.6.0-entur-27 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bee3e72b846..56c0c4cd615 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-27 jar From daf5216a848b21f2daebb1e4ff4782f2d22a6578 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 045/121] 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 73ef86f3be9..afa25f9e15d 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 156 + EN-0071 31.3 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 5a86e8d5fcdb5a2f9225a2baf13d39542ae82f57 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 19 Dec 2023 21:26:49 +0100 Subject: [PATCH 046/121] =?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 7e48dab5344..65d3394edfd 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 a8baaf1603b..6e17bf40bd7 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 51b2fae86b5..a2a3d079d13 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 3ae029fdb2d..b4912aacb1d 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java @@ -203,6 +203,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 b67b26b90b6..0f18f6577a8 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 @@ -616,6 +616,14 @@ Normally this is when the search is performed (now), plus a small grace period t ) .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 749772fae12..591b0376f59 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 37bb7270902..ea0274a139d 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 @@ -133,7 +133,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 948b132e408..3c3f789918d 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.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; @@ -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 5b9d81fa6c3..087b5a51e55 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; @@ -244,4 +247,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 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 dc91388458a..72fc4c2c6f9 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -332,13 +332,14 @@ A related parameter (transferSlack) also helps avoid missed connections when the } // 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)`. @@ -348,10 +349,17 @@ A related parameter (transferSlack) also helps avoid missed connections when the 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 0d2bf71dcc6..4563a3ba60a 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -805,6 +805,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 e9caf91c61d1e109d02bcf566987dc82c5422fc2 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 5 Aug 2024 09:49:16 +0200 Subject: [PATCH 047/121] Version 2.6.0-entur-28 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index afa25f9e15d..546743e7c36 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-28 jar From 9ff28f6ab0fd0396da15084f885e7d33302bb6ba 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 048/121] 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 f5a7972ab8e..45f5b73880b 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 156 + EN-0071 31.3 2.52 @@ -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 0a32c1cd5033363d8fccc98697485da9201fdd18 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 19 Dec 2023 21:26:49 +0100 Subject: [PATCH 049/121] =?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 7e48dab5344..65d3394edfd 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 a8baaf1603b..6e17bf40bd7 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 51b2fae86b5..a2a3d079d13 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 3ae029fdb2d..b4912aacb1d 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java @@ -203,6 +203,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 b67b26b90b6..0f18f6577a8 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 @@ -616,6 +616,14 @@ Normally this is when the search is performed (now), plus a small grace period t ) .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 749772fae12..591b0376f59 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 37bb7270902..ea0274a139d 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 @@ -133,7 +133,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 948b132e408..3c3f789918d 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.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; @@ -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 5b9d81fa6c3..087b5a51e55 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; @@ -244,4 +247,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 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 dc91388458a..72fc4c2c6f9 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -332,13 +332,14 @@ A related parameter (transferSlack) also helps avoid missed connections when the } // 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)`. @@ -348,10 +349,17 @@ A related parameter (transferSlack) also helps avoid missed connections when the 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 0d2bf71dcc6..4563a3ba60a 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -805,6 +805,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 1d37dea7941af6969292780fd1dc92da0731fd25 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 20 Aug 2024 13:38:31 +0200 Subject: [PATCH 050/121] Version 2.6.0-entur-29 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 45f5b73880b..84b77cdafd6 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-29 jar From 226927233de06d0984a60e37cdef73ff3430dc02 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 20 Aug 2024 14:06:52 +0200 Subject: [PATCH 051/121] Update GTFS unit tests --- .../ext/siri/SiriTimetableSnapshotSourceTest.java | 4 ++-- .../trip/moduletests/addition/AddedTest.java | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java b/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java index b34f47205b6..b484103cbab 100644 --- a/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java @@ -93,7 +93,7 @@ void testAddJourneyWithNewRoute() { .withEstimatedCalls(builder -> builder.call(env.stopD1).arriveAimedExpected("00:03", "00:04")) .buildEstimatedTimetableDeliveries(); - assertEquals(1, env.getTransitService().getAllRoutes().size()); + int numRoutes = env.getTransitService().getAllRoutes().size(); var result = env.applyEstimatedTimetable(updates); assertEquals(1, result.successful()); @@ -103,7 +103,7 @@ void testAddJourneyWithNewRoute() { env.getScheduledTimetable("newJourney") ); TransitService transitService = env.getTransitService(); - assertEquals(2, transitService.getAllRoutes().size()); + assertEquals(numRoutes + 1, transitService.getAllRoutes().size()); FeedScopedId newRouteId = TransitModelForTest.id(newRouteRef); Route newRoute = transitService.getRouteForId(newRouteId); assertNotNull(newRoute); diff --git a/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java b/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java index 8371c5dda3a..2e0b9d3d88e 100644 --- a/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java +++ b/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java @@ -16,9 +16,12 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.opentripplanner.model.PickDrop; +import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.RealTimeState; +import org.opentripplanner.transit.model.timetable.Trip; +import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.spi.UpdateSuccess; import org.opentripplanner.updater.trip.RealtimeTestEnvironment; import org.opentripplanner.updater.trip.TripUpdateBuilder; @@ -62,8 +65,12 @@ void addedTripWithNewRoute() { assertEquals(TripUpdateBuilder.ROUTE_NAME, route.getName()); assertEquals(TransitMode.RAIL, route.getMode()); - var fromTransitModel = env.getTransitService().getRouteForId(route.getId()); + TransitService transitService = env.getTransitService(); + var fromTransitModel = transitService.getRouteForId(route.getId()); assertEquals(fromTransitModel, route); + var patternsForRoute = transitService.getPatternsForRoute(route); + assertEquals(1, patternsForRoute.size()); + assertEquals(pattern, patternsForRoute.stream().findFirst().orElseThrow()); assertEquals(PickDrop.CALL_AGENCY, pattern.getBoardType(0)); assertEquals(PickDrop.CALL_AGENCY, pattern.getAlightType(0)); @@ -122,6 +129,12 @@ void repeatedlyAddedTripWithNewRoute() { private TripPattern assertAddedTrip(String tripId, RealtimeTestEnvironment env) { var snapshot = env.getTimetableSnapshot(); + + TransitService transitService = env.getTransitService(); + Trip trip = transitService.getTripForId(TransitModelForTest.id(ADDED_TRIP_ID)); + assertNotNull(trip); + assertNotNull(transitService.getPatternForTrip(trip)); + var stopA = env.transitModel.getStopModel().getRegularStop(env.stopA1.getId()); // Get the trip pattern of the added trip which goes through stopA var patternsAtA = env.getTimetableSnapshot().getPatternsForStop(stopA); From ac11cb84554037f4994137b40e09743896ec0e0c Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 20 Aug 2024 15:31:01 +0200 Subject: [PATCH 052/121] Ensure null-safe lookup on immutable map --- .../model/TimetableSnapshot.java | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opentripplanner/model/TimetableSnapshot.java b/src/main/java/org/opentripplanner/model/TimetableSnapshot.java index e6dcdd2eee1..08fe929a8f4 100644 --- a/src/main/java/org/opentripplanner/model/TimetableSnapshot.java +++ b/src/main/java/org/opentripplanner/model/TimetableSnapshot.java @@ -223,7 +223,7 @@ public boolean hasRealtimeAddedTripPatterns() { */ @Nullable public Route getRealtimeAddedRoute(FeedScopedId id) { - return realtimeAddedRoutes.get(id); + return getByNullableKey(id, realtimeAddedRoutes); } public Collection getAllRealTimeAddedRoutes() { @@ -235,7 +235,7 @@ public Collection getAllRealTimeAddedRoutes() { */ @Nullable public Trip getRealTimeAddedTrip(FeedScopedId id) { - return realTimeAddedTrips.get(id); + return getByNullableKey(id, realTimeAddedTrips); } public Collection getAllRealTimeAddedTrips() { @@ -247,7 +247,7 @@ public Collection getAllRealTimeAddedTrips() { */ @Nullable public TripPattern getRealTimeAddedPatternForTrip(Trip trip) { - return realTimeAddedPatternForTrip.get(trip); + return getByNullableKey(trip, realTimeAddedPatternForTrip); } /** @@ -262,7 +262,7 @@ public Collection getRealTimeAddedPatternForRoute(Route route) { */ @Nullable public TripOnServiceDate getRealTimeAddedTripOnServiceDateById(FeedScopedId id) { - return realTimeAddedTripOnServiceDateById.get(id); + return getByNullableKey(id, realTimeAddedTripOnServiceDateById); } /** @@ -272,7 +272,7 @@ public TripOnServiceDate getRealTimeAddedTripOnServiceDateById(FeedScopedId id) public TripOnServiceDate getRealTimeAddedTripOnServiceDateForTripAndDay( TripIdAndServiceDate tripIdAndServiceDate ) { - return realTimeAddedTripOnServiceDateForTripAndDay.get(tripIdAndServiceDate); + return getByNullableKey(tripIdAndServiceDate, realTimeAddedTripOnServiceDateForTripAndDay); } public Collection getAllRealTimeAddedTripOnServiceDate() { @@ -611,6 +611,20 @@ private Timetable copyTimetable(TripPattern pattern, LocalDate serviceDate, Time return tt; } + /** + * Look up the given key in a Map, return null if the key is null. + * This prevents a NullPointerException if the underlying implementation of the map does not + * accept querying with null keys (e.g. ImmutableMap). + * + **/ + @Nullable + private static V getByNullableKey(K key, Map map) { + if (key == null) { + return null; + } + return map.get(key); + } + protected static class SortedTimetableComparator implements Comparator { @Override From 6d44fe71fab3f00ab2ee5ee3b604979a56561a7b Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 20 Aug 2024 15:47:11 +0200 Subject: [PATCH 053/121] Version 2.6.0-entur-30 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 84b77cdafd6..c6dc236c2b6 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.6.0-entur-29 + 2.6.0-entur-30 jar From 99caef29989331648d049defd5119f0093748bc3 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Fri, 23 Aug 2024 12:05:08 +0200 Subject: [PATCH 054/121] Inject updated TransitService in real-time updaters --- .../RealtimeResolverTest.java | 3 +- .../ext/siri/SiriAlertsUpdateHandlerTest.java | 4 +- .../layers/stops/RealtimeStopsLayerTest.java | 3 +- .../ext/siri/SiriAlertsUpdateHandler.java | 5 +- .../ext/siri/SiriTimetableSnapshotSource.java | 8 ++- .../ext/siri/updater/SiriETUpdater.java | 7 +- .../ext/siri/updater/SiriSXUpdater.java | 11 ++-- .../azure/AbstractAzureSiriUpdater.java | 8 +-- .../updater/azure/SiriAzureETUpdater.java | 6 +- .../updater/azure/SiriAzureSXUpdater.java | 10 +-- .../google/SiriETGooglePubsubUpdater.java | 7 +- .../routing/impl/TransitAlertServiceImpl.java | 12 ++-- .../alert/GtfsRealtimeAlertsUpdater.java | 9 ++- .../configure/UpdaterConfigurator.java | 64 ++++++++++++++++--- .../updater/trip/MqttGtfsRealtimeUpdater.java | 8 +-- .../updater/trip/PollingTripUpdater.java | 8 +-- .../updater/trip/TimetableSnapshotSource.java | 10 +++ .../PollingVehiclePositionUpdater.java | 22 +------ .../java/org/opentripplanner/GtfsTest.java | 3 +- .../apis/gtfs/GraphQLIntegrationTest.java | 2 +- .../transit/TransitAlertFilterTest.java | 5 +- .../alert/AlertsUpdateHandlerTest.java | 5 +- 22 files changed, 131 insertions(+), 89 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java b/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java index af415b5e883..92b971f62ad 100644 --- a/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java @@ -176,8 +176,9 @@ private static TransitService makeTransitService( transitModel.updateCalendarServiceData(true, calendarServiceData, DataImportIssueStore.NOOP); transitModel.index(); - var alertService = new TransitAlertServiceImpl(transitModel); return new DefaultTransitService(transitModel) { + final TransitAlertService alertService = new TransitAlertServiceImpl(this); + @Override public TransitAlertService getTransitAlertService() { return alertService; diff --git a/src/ext-test/java/org/opentripplanner/ext/siri/SiriAlertsUpdateHandlerTest.java b/src/ext-test/java/org/opentripplanner/ext/siri/SiriAlertsUpdateHandlerTest.java index 7c603ea7b2e..dcaac1ee5f3 100644 --- a/src/ext-test/java/org/opentripplanner/ext/siri/SiriAlertsUpdateHandlerTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/siri/SiriAlertsUpdateHandlerTest.java @@ -83,11 +83,11 @@ public void setUp() throws Exception { transitAlertService.getAllAlerts().clear(); } if (alertsUpdateHandler == null) { - transitAlertService = new TransitAlertServiceImpl(transitModel); + transitAlertService = new TransitAlertServiceImpl(transitService); alertsUpdateHandler = new SiriAlertsUpdateHandler( FEED_ID, - transitModel, + transitService, transitAlertService, SiriFuzzyTripMatcher.of(transitService), Duration.ZERO diff --git a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/RealtimeStopsLayerTest.java b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/RealtimeStopsLayerTest.java index bb258beb76f..8208351dd3d 100644 --- a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/RealtimeStopsLayerTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/RealtimeStopsLayerTest.java @@ -65,8 +65,9 @@ void realtimeStopLayer() { var transitModel = new TransitModel(new StopModel(), deduplicator); transitModel.initTimeZone(ZoneIds.HELSINKI); transitModel.index(); - var alertService = new TransitAlertServiceImpl(transitModel); var transitService = new DefaultTransitService(transitModel) { + final TransitAlertService alertService = new TransitAlertServiceImpl(this); + @Override public TransitAlertService getTransitAlertService() { return alertService; diff --git a/src/ext/java/org/opentripplanner/ext/siri/SiriAlertsUpdateHandler.java b/src/ext/java/org/opentripplanner/ext/siri/SiriAlertsUpdateHandler.java index 8a747e765da..bc6c3c3102d 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/SiriAlertsUpdateHandler.java +++ b/src/ext/java/org/opentripplanner/ext/siri/SiriAlertsUpdateHandler.java @@ -20,8 +20,6 @@ import org.opentripplanner.routing.alertpatch.TransitAlertBuilder; import org.opentripplanner.routing.services.TransitAlertService; import org.opentripplanner.transit.model.framework.FeedScopedId; -import org.opentripplanner.transit.service.DefaultTransitService; -import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.transit.service.TransitService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -67,7 +65,7 @@ public class SiriAlertsUpdateHandler { */ public SiriAlertsUpdateHandler( String feedId, - TransitModel transitModel, + TransitService transitService, TransitAlertService transitAlertService, SiriFuzzyTripMatcher siriFuzzyTripMatcher, Duration earlyStart @@ -76,7 +74,6 @@ public SiriAlertsUpdateHandler( this.transitAlertService = transitAlertService; this.earlyStart = earlyStart; - TransitService transitService = new DefaultTransitService(transitModel); this.affectsMapper = new AffectsMapper(feedId, siriFuzzyTripMatcher, transitService); } diff --git a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java index 9e2dfaff76b..597bf4d3e9f 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java +++ b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java @@ -133,7 +133,13 @@ public TimetableSnapshot getTimetableSnapshot() { return snapshotManager.getTimetableSnapshot(); } - private TimetableSnapshot getTimetableSnapshotBuffer() { + /** + * @return the current timetable snapshot buffer that contains pending changes (not yet published + * in a snapshot). + * This should be used in the context of an updater to build a TransitEditorService that sees all + * the changes applied so far by real-time updates. + */ + public TimetableSnapshot getTimetableSnapshotBuffer() { return snapshotManager.getTimetableSnapshotBuffer(); } diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETUpdater.java b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETUpdater.java index c811d3ee5d8..247e7787d73 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriETUpdater.java @@ -3,8 +3,7 @@ import java.util.List; import java.util.function.Consumer; import org.opentripplanner.ext.siri.SiriTimetableSnapshotSource; -import org.opentripplanner.transit.service.DefaultTransitService; -import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.spi.PollingGraphUpdater; import org.opentripplanner.updater.spi.ResultLogger; import org.opentripplanner.updater.spi.UpdateResult; @@ -41,7 +40,7 @@ public class SiriETUpdater extends PollingGraphUpdater { public SiriETUpdater( SiriETUpdaterParameters config, - TransitModel transitModel, + TransitService transitService, SiriTimetableSnapshotSource timetableSnapshotSource ) { super(config); @@ -62,7 +61,7 @@ public SiriETUpdater( new EstimatedTimetableHandler( timetableSnapshotSource, config.fuzzyTripMatching(), - new DefaultTransitService(transitModel), + transitService, feedId ); diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriSXUpdater.java b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriSXUpdater.java index 5ededbb3bf0..58939a43117 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/SiriSXUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/SiriSXUpdater.java @@ -11,8 +11,7 @@ import org.opentripplanner.framework.retry.OtpRetryBuilder; import org.opentripplanner.routing.impl.TransitAlertServiceImpl; import org.opentripplanner.routing.services.TransitAlertService; -import org.opentripplanner.transit.service.DefaultTransitService; -import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.alert.TransitAlertProvider; import org.opentripplanner.updater.spi.PollingGraphUpdater; import org.opentripplanner.updater.spi.WriteToGraphCallback; @@ -45,7 +44,7 @@ public class SiriSXUpdater extends PollingGraphUpdater implements TransitAlertPr private final SiriHttpLoader siriHttpLoader; private final OtpRetry retry; - public SiriSXUpdater(SiriSXUpdaterParameters config, TransitModel transitModel) { + public SiriSXUpdater(SiriSXUpdaterParameters config, TransitService transitService) { super(config); // TODO: add options to choose different patch services this.url = config.url(); @@ -58,13 +57,13 @@ public SiriSXUpdater(SiriSXUpdaterParameters config, TransitModel transitModel) //Keeping original requestorRef use as base for updated requestorRef to be used in retries this.originalRequestorRef = requestorRef; this.blockReadinessUntilInitialized = config.blockReadinessUntilInitialized(); - this.transitAlertService = new TransitAlertServiceImpl(transitModel); + this.transitAlertService = new TransitAlertServiceImpl(transitService); this.updateHandler = new SiriAlertsUpdateHandler( config.feedId(), - transitModel, + transitService, transitAlertService, - SiriFuzzyTripMatcher.of(new DefaultTransitService(transitModel)), + SiriFuzzyTripMatcher.of(transitService), config.earlyStart() ); siriHttpLoader = new SiriHttpLoader(url, config.timeout(), config.requestHeaders()); diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java index a72c1797d38..f7c15bcc538 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java @@ -26,8 +26,6 @@ import org.opentripplanner.ext.siri.SiriFuzzyTripMatcher; import org.opentripplanner.framework.application.ApplicationShutdownSupport; import org.opentripplanner.framework.io.OtpHttpClientFactory; -import org.opentripplanner.transit.service.DefaultTransitService; -import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.spi.GraphUpdater; import org.opentripplanner.updater.spi.HttpHeaders; @@ -69,7 +67,10 @@ public abstract class AbstractAzureSiriUpdater implements GraphUpdater { */ protected final int timeout; - public AbstractAzureSiriUpdater(SiriAzureUpdaterParameters config, TransitModel transitModel) { + public AbstractAzureSiriUpdater( + SiriAzureUpdaterParameters config, + TransitService transitService + ) { this.configRef = config.configRef(); this.authenticationType = config.getAuthenticationType(); this.fullyQualifiedNamespace = config.getFullyQualifiedNamespace(); @@ -80,7 +81,6 @@ public AbstractAzureSiriUpdater(SiriAzureUpdaterParameters config, TransitModel this.feedId = config.feedId(); this.autoDeleteOnIdle = config.getAutoDeleteOnIdle(); this.prefetchCount = config.getPrefetchCount(); - TransitService transitService = new DefaultTransitService(transitModel); this.entityResolver = new EntityResolver(transitService, feedId); this.fuzzyTripMatcher = config.isFuzzyTripMatching() ? SiriFuzzyTripMatcher.of(transitService) : null; diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureETUpdater.java b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureETUpdater.java index 2b8c48dce11..6a95c0ae780 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureETUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureETUpdater.java @@ -16,7 +16,7 @@ import javax.xml.stream.XMLStreamException; import org.apache.hc.core5.net.URIBuilder; import org.opentripplanner.ext.siri.SiriTimetableSnapshotSource; -import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.spi.ResultLogger; import org.opentripplanner.updater.spi.UpdateResult; import org.opentripplanner.updater.trip.UpdateIncrementality; @@ -40,10 +40,10 @@ public class SiriAzureETUpdater extends AbstractAzureSiriUpdater { public SiriAzureETUpdater( SiriAzureETUpdaterParameters config, - TransitModel transitModel, + TransitService transitService, SiriTimetableSnapshotSource snapshotSource ) { - super(config, transitModel); + super(config, transitService); this.fromDateTime = config.getFromDateTime(); this.snapshotSource = snapshotSource; this.recordMetrics = TripUpdateMetrics.streaming(config); diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureSXUpdater.java b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureSXUpdater.java index b04149a44bf..e8181e0e9b9 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureSXUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureSXUpdater.java @@ -18,7 +18,7 @@ import org.opentripplanner.ext.siri.SiriAlertsUpdateHandler; import org.opentripplanner.routing.impl.TransitAlertServiceImpl; import org.opentripplanner.routing.services.TransitAlertService; -import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.alert.TransitAlertProvider; import org.rutebanken.siri20.util.SiriXml; import org.slf4j.Logger; @@ -35,15 +35,15 @@ public class SiriAzureSXUpdater extends AbstractAzureSiriUpdater implements Tran private final LocalDate fromDateTime; private final LocalDate toDateTime; - public SiriAzureSXUpdater(SiriAzureSXUpdaterParameters config, TransitModel transitModel) { - super(config, transitModel); + public SiriAzureSXUpdater(SiriAzureSXUpdaterParameters config, TransitService transitService) { + super(config, transitService); this.fromDateTime = config.getFromDateTime(); this.toDateTime = config.getToDateTime(); - this.transitAlertService = new TransitAlertServiceImpl(transitModel); + this.transitAlertService = new TransitAlertServiceImpl(transitService); this.updateHandler = new SiriAlertsUpdateHandler( feedId, - transitModel, + transitService, transitAlertService, fuzzyTripMatcher(), Duration.ZERO diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/google/SiriETGooglePubsubUpdater.java b/src/ext/java/org/opentripplanner/ext/siri/updater/google/SiriETGooglePubsubUpdater.java index 58c01815230..87f52393d5b 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/google/SiriETGooglePubsubUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/google/SiriETGooglePubsubUpdater.java @@ -5,8 +5,7 @@ import org.opentripplanner.ext.siri.updater.AsyncEstimatedTimetableProcessor; import org.opentripplanner.ext.siri.updater.AsyncEstimatedTimetableSource; import org.opentripplanner.ext.siri.updater.EstimatedTimetableHandler; -import org.opentripplanner.transit.service.DefaultTransitService; -import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.spi.GraphUpdater; import org.opentripplanner.updater.spi.UpdateResult; import org.opentripplanner.updater.spi.WriteToGraphCallback; @@ -27,7 +26,7 @@ public class SiriETGooglePubsubUpdater implements GraphUpdater { public SiriETGooglePubsubUpdater( SiriETGooglePubsubUpdaterParameters config, - TransitModel transitModel, + TransitService transitService, SiriTimetableSnapshotSource timetableSnapshotSource ) { configRef = config.configRef(); @@ -46,7 +45,7 @@ public SiriETGooglePubsubUpdater( new EstimatedTimetableHandler( timetableSnapshotSource, config.fuzzyTripMatching(), - new DefaultTransitService(transitModel), + transitService, config.feedId() ); diff --git a/src/main/java/org/opentripplanner/routing/impl/TransitAlertServiceImpl.java b/src/main/java/org/opentripplanner/routing/impl/TransitAlertServiceImpl.java index 0cc24c691e9..b4d75345de9 100644 --- a/src/main/java/org/opentripplanner/routing/impl/TransitAlertServiceImpl.java +++ b/src/main/java/org/opentripplanner/routing/impl/TransitAlertServiceImpl.java @@ -13,7 +13,7 @@ import org.opentripplanner.routing.services.TransitAlertService; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.timetable.Direction; -import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.transit.service.TransitService; /** * This is the primary implementation of TransitAlertService, which actually retains its own set @@ -32,12 +32,12 @@ */ public class TransitAlertServiceImpl implements TransitAlertService { - private final TransitModel transitModel; + private final TransitService transitService; private Multimap alerts = HashMultimap.create(); - public TransitAlertServiceImpl(TransitModel transitModel) { - this.transitModel = transitModel; + public TransitAlertServiceImpl(TransitService transitService) { + this.transitService = transitService; } @Override @@ -85,8 +85,8 @@ public Collection getStopAlerts( } if (result.isEmpty()) { // Search for alerts on parent-stop - if (transitModel != null) { - var quay = transitModel.getStopModel().getRegularStop(stopId); + if (transitService != null) { + var quay = transitService.getRegularStop(stopId); if (quay != null) { // TODO - SIRI: Add alerts from parent- and multimodal-stops /* diff --git a/src/main/java/org/opentripplanner/updater/alert/GtfsRealtimeAlertsUpdater.java b/src/main/java/org/opentripplanner/updater/alert/GtfsRealtimeAlertsUpdater.java index 79e248a22cf..b8a71352e1f 100644 --- a/src/main/java/org/opentripplanner/updater/alert/GtfsRealtimeAlertsUpdater.java +++ b/src/main/java/org/opentripplanner/updater/alert/GtfsRealtimeAlertsUpdater.java @@ -7,8 +7,7 @@ import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.routing.impl.TransitAlertServiceImpl; import org.opentripplanner.routing.services.TransitAlertService; -import org.opentripplanner.transit.service.DefaultTransitService; -import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.GtfsRealtimeFuzzyTripMatcher; import org.opentripplanner.updater.spi.HttpHeaders; import org.opentripplanner.updater.spi.PollingGraphUpdater; @@ -33,15 +32,15 @@ public class GtfsRealtimeAlertsUpdater extends PollingGraphUpdater implements Tr public GtfsRealtimeAlertsUpdater( GtfsRealtimeAlertsUpdaterParameters config, - TransitModel transitModel + TransitService transitService ) { super(config); this.url = config.url(); this.headers = HttpHeaders.of().acceptProtobuf().add(config.headers()).build(); - TransitAlertService transitAlertService = new TransitAlertServiceImpl(transitModel); + TransitAlertService transitAlertService = new TransitAlertServiceImpl(transitService); var fuzzyTripMatcher = config.fuzzyTripMatching() - ? new GtfsRealtimeFuzzyTripMatcher(new DefaultTransitService(transitModel)) + ? new GtfsRealtimeFuzzyTripMatcher(transitService) : null; this.transitAlertService = transitAlertService; diff --git a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java index 83e0bd0fe85..8cce1ca6e46 100644 --- a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java +++ b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java @@ -16,7 +16,9 @@ import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; import org.opentripplanner.service.vehiclerental.VehicleRentalRepository; +import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.GraphUpdaterManager; import org.opentripplanner.updater.UpdatersParameters; import org.opentripplanner.updater.alert.GtfsRealtimeAlertsUpdater; @@ -156,32 +158,50 @@ private List createUpdatersFromConfig() { } } for (var configItem : updatersParameters.getGtfsRealtimeAlertsUpdaterParameters()) { - updaters.add(new GtfsRealtimeAlertsUpdater(configItem, transitModel)); + updaters.add(new GtfsRealtimeAlertsUpdater(configItem, provideGtfsTransitService())); } for (var configItem : updatersParameters.getPollingStoptimeUpdaterParameters()) { updaters.add( - new PollingTripUpdater(configItem, transitModel, provideGtfsTimetableSnapshot()) + new PollingTripUpdater( + configItem, + provideGtfsTransitService(), + provideGtfsTimetableSnapshot() + ) ); } for (var configItem : updatersParameters.getVehiclePositionsUpdaterParameters()) { updaters.add( - new PollingVehiclePositionUpdater(configItem, realtimeVehicleRepository, transitModel) + new PollingVehiclePositionUpdater( + configItem, + realtimeVehicleRepository, + provideGtfsTransitService() + ) ); } for (var configItem : updatersParameters.getSiriETUpdaterParameters()) { - updaters.add(new SiriETUpdater(configItem, transitModel, provideSiriTimetableSnapshot())); + updaters.add( + new SiriETUpdater(configItem, provideSiriTransitService(), provideSiriTimetableSnapshot()) + ); } for (var configItem : updatersParameters.getSiriETGooglePubsubUpdaterParameters()) { updaters.add( - new SiriETGooglePubsubUpdater(configItem, transitModel, provideSiriTimetableSnapshot()) + new SiriETGooglePubsubUpdater( + configItem, + provideSiriTransitService(), + provideSiriTimetableSnapshot() + ) ); } for (var configItem : updatersParameters.getSiriSXUpdaterParameters()) { - updaters.add(new SiriSXUpdater(configItem, transitModel)); + updaters.add(new SiriSXUpdater(configItem, provideSiriTransitService())); } for (var configItem : updatersParameters.getMqttGtfsRealtimeUpdaterParameters()) { updaters.add( - new MqttGtfsRealtimeUpdater(configItem, transitModel, provideGtfsTimetableSnapshot()) + new MqttGtfsRealtimeUpdater( + configItem, + provideGtfsTransitService(), + provideGtfsTimetableSnapshot() + ) ); } for (var configItem : updatersParameters.getVehicleParkingUpdaterParameters()) { @@ -214,11 +234,15 @@ private List createUpdatersFromConfig() { } for (var configItem : updatersParameters.getSiriAzureETUpdaterParameters()) { updaters.add( - new SiriAzureETUpdater(configItem, transitModel, provideSiriTimetableSnapshot()) + new SiriAzureETUpdater( + configItem, + provideSiriTransitService(), + provideSiriTimetableSnapshot() + ) ); } for (var configItem : updatersParameters.getSiriAzureSXUpdaterParameters()) { - updaters.add(new SiriAzureSXUpdater(configItem, transitModel)); + updaters.add(new SiriAzureSXUpdater(configItem, provideSiriTransitService())); } return updaters; @@ -236,6 +260,17 @@ private SiriTimetableSnapshotSource provideSiriTimetableSnapshot() { return siriTimetableSnapshotSource; } + /** + * Provide a TransitService aware of the latest (uncommitted) SIRI real-time updates. + * Should be injected only in SIRI updaters. + */ + private TransitService provideSiriTransitService() { + return new DefaultTransitService( + transitModel, + provideSiriTimetableSnapshot().getTimetableSnapshotBuffer() + ); + } + private TimetableSnapshotSource provideGtfsTimetableSnapshot() { if (gtfsTimetableSnapshotSource == null) { this.gtfsTimetableSnapshotSource = @@ -244,6 +279,17 @@ private TimetableSnapshotSource provideGtfsTimetableSnapshot() { return gtfsTimetableSnapshotSource; } + /** + * Provide a TransitService aware of the latest (uncommitted) GTFS real-time updates. + * Should be injected only in GTFS-RT updaters. + */ + private TransitService provideGtfsTransitService() { + return new DefaultTransitService( + transitModel, + provideGtfsTimetableSnapshot().getTimetableSnapshotBuffer() + ); + } + /** * If SIRI or GTFS real-time updaters are in use, configure a periodic flush of the timetable * snapshot. diff --git a/src/main/java/org/opentripplanner/updater/trip/MqttGtfsRealtimeUpdater.java b/src/main/java/org/opentripplanner/updater/trip/MqttGtfsRealtimeUpdater.java index 3ba7af0305b..0e3996f16ce 100644 --- a/src/main/java/org/opentripplanner/updater/trip/MqttGtfsRealtimeUpdater.java +++ b/src/main/java/org/opentripplanner/updater/trip/MqttGtfsRealtimeUpdater.java @@ -17,8 +17,7 @@ import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import org.opentripplanner.framework.tostring.ToStringBuilder; -import org.opentripplanner.transit.service.DefaultTransitService; -import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.GtfsRealtimeFuzzyTripMatcher; import org.opentripplanner.updater.spi.GraphUpdater; import org.opentripplanner.updater.spi.UpdateResult; @@ -66,7 +65,7 @@ public class MqttGtfsRealtimeUpdater implements GraphUpdater { public MqttGtfsRealtimeUpdater( MqttGtfsRealtimeUpdaterParameters parameters, - TransitModel transitModel, + TransitService transitService, TimetableSnapshotSource snapshotSource ) { this.configRef = parameters.configRef(); @@ -78,8 +77,7 @@ public MqttGtfsRealtimeUpdater( this.snapshotSource = snapshotSource; // Set properties of realtime data snapshot source if (parameters.getFuzzyTripMatching()) { - this.fuzzyTripMatcher = - new GtfsRealtimeFuzzyTripMatcher(new DefaultTransitService(transitModel)); + this.fuzzyTripMatcher = new GtfsRealtimeFuzzyTripMatcher(transitService); } this.recordMetrics = TripUpdateMetrics.streaming(parameters); LOG.info("Creating streaming GTFS-RT TripUpdate updater subscribing to MQTT broker at {}", url); diff --git a/src/main/java/org/opentripplanner/updater/trip/PollingTripUpdater.java b/src/main/java/org/opentripplanner/updater/trip/PollingTripUpdater.java index 8af27d9818c..3cce5f2092b 100644 --- a/src/main/java/org/opentripplanner/updater/trip/PollingTripUpdater.java +++ b/src/main/java/org/opentripplanner/updater/trip/PollingTripUpdater.java @@ -4,8 +4,7 @@ import java.util.List; import java.util.function.Consumer; import org.opentripplanner.framework.tostring.ToStringBuilder; -import org.opentripplanner.transit.service.DefaultTransitService; -import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.GtfsRealtimeFuzzyTripMatcher; import org.opentripplanner.updater.spi.PollingGraphUpdater; import org.opentripplanner.updater.spi.UpdateResult; @@ -47,7 +46,7 @@ public class PollingTripUpdater extends PollingGraphUpdater { public PollingTripUpdater( PollingTripUpdaterParameters parameters, - TransitModel transitModel, + TransitService transitService, TimetableSnapshotSource snapshotSource ) { super(parameters); @@ -57,8 +56,7 @@ public PollingTripUpdater( this.backwardsDelayPropagationType = parameters.backwardsDelayPropagationType(); this.snapshotSource = snapshotSource; if (parameters.fuzzyTripMatching()) { - this.fuzzyTripMatcher = - new GtfsRealtimeFuzzyTripMatcher(new DefaultTransitService(transitModel)); + this.fuzzyTripMatcher = new GtfsRealtimeFuzzyTripMatcher(transitService); } this.recordMetrics = BatchTripUpdateMetrics.batch(parameters); diff --git a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index 56e3c380b67..fe04d413607 100644 --- a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -333,6 +333,16 @@ public TimetableSnapshot getTimetableSnapshot() { return snapshotManager.getTimetableSnapshot(); } + /** + * @return the current timetable snapshot buffer that contains pending changes (not yet published + * in a snapshot). + * This should be used in the context of an updater to build a TransitEditorService that sees all + * the changes applied so far by real-time updates. + */ + public TimetableSnapshot getTimetableSnapshotBuffer() { + return snapshotManager.getTimetableSnapshotBuffer(); + } + private static void logUpdateResult( String feedId, Map failuresByRelationship, diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java index f1355ca0fa4..17e059e3d6d 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java @@ -1,14 +1,9 @@ package org.opentripplanner.updater.vehicle_position; import com.google.transit.realtime.GtfsRealtime.VehiclePosition; -import java.time.LocalDate; import java.util.List; import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; -import org.opentripplanner.transit.model.network.TripPattern; -import org.opentripplanner.transit.model.timetable.Trip; -import org.opentripplanner.transit.service.DefaultTransitService; -import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.GtfsRealtimeFuzzyTripMatcher; import org.opentripplanner.updater.spi.PollingGraphUpdater; @@ -40,14 +35,11 @@ public class PollingVehiclePositionUpdater extends PollingGraphUpdater { public PollingVehiclePositionUpdater( VehiclePositionsUpdaterParameters params, RealtimeVehicleRepository realtimeVehicleRepository, - TransitModel transitModel + TransitService transitService ) { super(params); this.vehiclePositionSource = new GtfsRealtimeHttpVehiclePositionSource(params.url(), params.headers()); - // TODO Inject TransitService, do not create it here. We currently do not - // support dagger injection in updaters, so this is ok for now. - TransitService transitService = new DefaultTransitService(transitModel); var fuzzyTripMatcher = params.fuzzyTripMatching() ? new GtfsRealtimeFuzzyTripMatcher(transitService) : null; @@ -56,7 +48,7 @@ public PollingVehiclePositionUpdater( params.feedId(), transitService::getTripForId, transitService::getPatternForTrip, - (trip, date) -> getPatternIncludingRealtime(transitModel, trip, date), + transitService::getPatternForTrip, realtimeVehicleRepository, transitService.getTimeZone(), fuzzyTripMatcher, @@ -95,14 +87,4 @@ public void runPolling() { public String toString() { return ToStringBuilder.of(this.getClass()).addObj("source", vehiclePositionSource).toString(); } - - private static TripPattern getPatternIncludingRealtime( - TransitModel transitModel, - Trip trip, - LocalDate sd - ) { - // a new instance of DefaultTransitService must be created to retrieve - // the current TimetableSnapshot - return (new DefaultTransitService(transitModel)).getPatternForTrip(trip, sd); - } } diff --git a/src/test/java/org/opentripplanner/GtfsTest.java b/src/test/java/org/opentripplanner/GtfsTest.java index 074e31147a3..2ca5e3c5734 100644 --- a/src/test/java/org/opentripplanner/GtfsTest.java +++ b/src/test/java/org/opentripplanner/GtfsTest.java @@ -39,6 +39,7 @@ import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.StopModel; import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.updater.TimetableSnapshotSourceParameters; @@ -214,7 +215,7 @@ protected void setUp() throws Exception { .withMaxSnapshotFrequency(Duration.ZERO), transitModel ); - alertPatchServiceImpl = new TransitAlertServiceImpl(transitModel); + alertPatchServiceImpl = new TransitAlertServiceImpl(new DefaultTransitService(transitModel)); alertsUpdateHandler.setTransitAlertService(alertPatchServiceImpl); alertsUpdateHandler.setFeedId(feedId.getId()); diff --git a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java index 79590ca2775..64560d17a21 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -198,7 +198,7 @@ static void setup() { var busRoute = routes.stream().filter(r -> r.getMode().equals(BUS)).findFirst().get(); TransitEditorService transitService = new DefaultTransitService(transitModel) { - private final TransitAlertService alertService = new TransitAlertServiceImpl(transitModel); + private final TransitAlertService alertService = new TransitAlertServiceImpl(this); @Override public List getModesOfStopLocation(StopLocation stop) { diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/TransitAlertFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/TransitAlertFilterTest.java index 379eb207155..cc3e46050d0 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/TransitAlertFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/transit/TransitAlertFilterTest.java @@ -12,6 +12,7 @@ import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.routing.impl.TransitAlertServiceImpl; import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; class TransitAlertFilterTest implements PlanTestConstants { @@ -20,7 +21,9 @@ class TransitAlertFilterTest implements PlanTestConstants { @Test void testFilter() { - var transitAlertService = new TransitAlertServiceImpl(new TransitModel()); + var transitAlertService = new TransitAlertServiceImpl( + new DefaultTransitService(new TransitModel()) + ); transitAlertService.setAlerts( List.of( TransitAlert diff --git a/src/test/java/org/opentripplanner/updater/alert/AlertsUpdateHandlerTest.java b/src/test/java/org/opentripplanner/updater/alert/AlertsUpdateHandlerTest.java index 0474c328162..01128629bb0 100644 --- a/src/test/java/org/opentripplanner/updater/alert/AlertsUpdateHandlerTest.java +++ b/src/test/java/org/opentripplanner/updater/alert/AlertsUpdateHandlerTest.java @@ -25,13 +25,16 @@ import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.routing.impl.TransitAlertServiceImpl; import org.opentripplanner.routing.services.TransitAlertService; +import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; public class AlertsUpdateHandlerTest { private AlertsUpdateHandler handler; - private final TransitAlertService service = new TransitAlertServiceImpl(new TransitModel()); + private final TransitAlertService service = new TransitAlertServiceImpl( + new DefaultTransitService(new TransitModel()) + ); @BeforeEach public void setUp() { From 642272af112a8df049d0ab4dc6d4456732a713cb Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Fri, 23 Aug 2024 12:05:08 +0200 Subject: [PATCH 055/121] Add unit test for SIRI added trip --- .../siri/SiriTimetableSnapshotSourceTest.java | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java b/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java index 45e7979b353..60e9a9af0a0 100644 --- a/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java @@ -3,12 +3,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertFailure; -import java.util.Set; +import java.util.List; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.opentripplanner.transit.model.timetable.RealTimeState; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.trip.RealtimeTestEnvironment; +import uk.org.siri.siri20.EstimatedTimetableDeliveryStructure; class SiriTimetableSnapshotSourceTest { @@ -32,16 +33,7 @@ void testCancelTrip() { @Test void testAddJourney() { var env = RealtimeTestEnvironment.siri(); - - var updates = new SiriEtBuilder(env.getDateTimeHelper()) - .withEstimatedVehicleJourneyCode("newJourney") - .withIsExtraJourney(true) - .withOperatorRef(env.operator1Id.getId()) - .withLineRef(env.route1Id.getId()) - .withRecordedCalls(builder -> builder.call(env.stopC1).departAimedActual("00:01", "00:02")) - .withEstimatedCalls(builder -> builder.call(env.stopD1).arriveAimedExpected("00:03", "00:04")) - .buildEstimatedTimetableDeliveries(); - + var updates = createValidAddedJourney(env); var result = env.applyEstimatedTimetable(updates); assertEquals(1, result.successful()); @@ -52,6 +44,20 @@ void testAddJourney() { ); } + @Test + void testAddJourneyMultipleTimes() { + var env = RealtimeTestEnvironment.siri(); + var updates = createValidAddedJourney(env); + + int numTrips = env.getTransitService().getAllTrips().size(); + var result1 = env.applyEstimatedTimetable(updates); + assertEquals(1, result1.successful()); + assertEquals(numTrips + 1, env.getTransitService().getAllTrips().size()); + var result2 = env.applyEstimatedTimetable(updates); + assertEquals(1, result2.successful()); + assertEquals(numTrips + 1, env.getTransitService().getAllTrips().size()); + } + @Test void testAddedJourneyWithInvalidScheduledData() { var env = RealtimeTestEnvironment.siri(); @@ -415,6 +421,19 @@ void testExtraUnknownStop() { assertFailure(UpdateError.UpdateErrorType.INVALID_STOP_SEQUENCE, result); } + private static List createValidAddedJourney( + RealtimeTestEnvironment env + ) { + return new SiriEtBuilder(env.getDateTimeHelper()) + .withEstimatedVehicleJourneyCode("newJourney") + .withIsExtraJourney(true) + .withOperatorRef(env.operator1Id.getId()) + .withLineRef(env.route1Id.getId()) + .withRecordedCalls(builder -> builder.call(env.stopC1).departAimedActual("00:01", "00:02")) + .withEstimatedCalls(builder -> builder.call(env.stopD1).arriveAimedExpected("00:03", "00:04")) + .buildEstimatedTimetableDeliveries(); + } + private static SiriEtBuilder updatedJourneyBuilder(RealtimeTestEnvironment env) { return new SiriEtBuilder(env.getDateTimeHelper()) .withEstimatedCalls(builder -> From 42247e9b3798daac6bca685e2d8823b69d555738 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Sat, 24 Aug 2024 17:43:30 +0200 Subject: [PATCH 056/121] Version 2.6.0-entur-31 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3979cf9a827..c9b64fc01fa 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.6.0-entur-30 + 2.6.0-entur-31 jar From 3a3d32fdad6f2a36484cc5829083688908842e85 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 057/121] 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 6fac28d2178..4df8f460dbe 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 940d8de5f94..eca1208a98c 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 156 + EN-0071 31.3 2.52 @@ -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 3f5b3bd0699ec0f3a311ac358587f6ca088cc673 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 19 Dec 2023 21:26:49 +0100 Subject: [PATCH 058/121] =?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 --- doc/user/Configuration.md | 1 + doc/user/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/doc/user/Configuration.md b/doc/user/Configuration.md index 8e366e476a9..bd35d681eb6 100644 --- a/doc/user/Configuration.md +++ b/doc/user/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/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index 674ab238888..dabbca4fe94 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/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 51b2fae86b5..a2a3d079d13 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 3ae029fdb2d..b4912aacb1d 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java @@ -203,6 +203,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 b67b26b90b6..0f18f6577a8 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 @@ -616,6 +616,14 @@ Normally this is when the search is performed (now), plus a small grace period t ) .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 749772fae12..591b0376f59 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 37bb7270902..ea0274a139d 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 @@ -133,7 +133,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 948b132e408..3c3f789918d 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.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; @@ -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 5b9d81fa6c3..087b5a51e55 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; @@ -244,4 +247,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 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 dc91388458a..72fc4c2c6f9 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -332,13 +332,14 @@ A related parameter (transferSlack) also helps avoid missed connections when the } // 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)`. @@ -348,10 +349,17 @@ A related parameter (transferSlack) also helps avoid missed connections when the 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 0d2bf71dcc6..4563a3ba60a 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -805,6 +805,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 0532928d67d7b8d71416ece74a1c8466eec702b1 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Thu, 29 Aug 2024 14:39:03 +0200 Subject: [PATCH 059/121] Fix unit test --- .../siri/SiriTimetableSnapshotSourceTest.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java b/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java index e8f33080cb6..647b772aa69 100644 --- a/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java @@ -5,7 +5,6 @@ import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertFailure; import static org.opentripplanner.updater.trip.RealtimeTestEnvironment.SERVICE_DATE; -import java.util.List; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.opentripplanner.transit.model._data.TransitModelForTest; @@ -17,7 +16,6 @@ import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.trip.RealtimeTestEnvironment; -import uk.org.siri.siri20.EstimatedTimetableDeliveryStructure; class SiriTimetableSnapshotSourceTest { @@ -46,7 +44,7 @@ void testAddJourneyWithExistingRoute() { int numPatternForRoute = env.getTransitService().getPatternsForRoute(route).size(); String newJourneyId = "newJourney"; - var updates = createValidAddedJourney(env); + var updates = createValidAddedJourney(env).buildEstimatedTimetableDeliveries(); var result = env.applyEstimatedTimetable(updates); @@ -79,7 +77,9 @@ void testAddJourneyWithNewRoute() { var env = RealtimeTestEnvironment.siri(); String newRouteRef = "new route ref"; - var updates = createValidAddedJourney(env); + var updates = createValidAddedJourney(env) + .withLineRef(newRouteRef) + .buildEstimatedTimetableDeliveries(); int numRoutes = env.getTransitService().getAllRoutes().size(); var result = env.applyEstimatedTimetable(updates); @@ -101,7 +101,7 @@ void testAddJourneyWithNewRoute() { @Test void testAddJourneyMultipleTimes() { var env = RealtimeTestEnvironment.siri(); - var updates = createValidAddedJourney(env); + var updates = createValidAddedJourney(env).buildEstimatedTimetableDeliveries(); int numTrips = env.getTransitService().getAllTrips().size(); var result1 = env.applyEstimatedTimetable(updates); @@ -263,7 +263,7 @@ void testUpdateJourneyWithFuzzyMatchingAndMissingAimedDepartureTime() { var updates = new SiriEtBuilder(env.getDateTimeHelper()) .withFramedVehicleJourneyRef(builder -> - builder.withServiceDate(SERVICE_DATE).withVehicleJourneyRef("XXX") + builder.withServiceDate(RealtimeTestEnvironment.SERVICE_DATE).withVehicleJourneyRef("XXX") ) .withEstimatedCalls(builder -> builder @@ -473,17 +473,15 @@ void testExtraUnknownStop() { assertFailure(UpdateError.UpdateErrorType.INVALID_STOP_SEQUENCE, result); } - private static List createValidAddedJourney( - RealtimeTestEnvironment env - ) { + private static SiriEtBuilder createValidAddedJourney(RealtimeTestEnvironment env) { return new SiriEtBuilder(env.getDateTimeHelper()) .withEstimatedVehicleJourneyCode("newJourney") .withIsExtraJourney(true) .withOperatorRef(env.operator1Id.getId()) .withLineRef(env.route1Id.getId()) .withRecordedCalls(builder -> builder.call(env.stopC1).departAimedActual("00:01", "00:02")) - .withEstimatedCalls(builder -> builder.call(env.stopD1).arriveAimedExpected("00:03", "00:04")) - .buildEstimatedTimetableDeliveries(); + .withEstimatedCalls(builder -> builder.call(env.stopD1).arriveAimedExpected("00:03", "00:04") + ); } private static SiriEtBuilder updatedJourneyBuilder(RealtimeTestEnvironment env) { From c751ca50f31b821064b04b95dabde587e8b30ded Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Thu, 29 Aug 2024 14:42:58 +0200 Subject: [PATCH 060/121] Version 2.6.0-entur-32 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index eca1208a98c..34d4e9893fe 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-32 jar From 1cb8b2ad09813c725131b6c0baa44bfd00f51c77 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 061/121] 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 6fac28d2178..4df8f460dbe 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 33b74ac5551..d97cb1043d3 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 156 + EN-0071 31.3 2.52 @@ -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 d39dffdc733ee874b9316172a2811252ec5f8914 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 19 Dec 2023 21:26:49 +0100 Subject: [PATCH 062/121] =?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 --- doc/user/Configuration.md | 1 + doc/user/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/doc/user/Configuration.md b/doc/user/Configuration.md index 8e366e476a9..bd35d681eb6 100644 --- a/doc/user/Configuration.md +++ b/doc/user/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/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index 674ab238888..dabbca4fe94 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/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 51b2fae86b5..a2a3d079d13 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 3ae029fdb2d..b4912aacb1d 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java @@ -203,6 +203,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 b67b26b90b6..0f18f6577a8 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 @@ -616,6 +616,14 @@ Normally this is when the search is performed (now), plus a small grace period t ) .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 749772fae12..591b0376f59 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 37bb7270902..ea0274a139d 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 @@ -133,7 +133,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 948b132e408..3c3f789918d 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.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; @@ -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 5b9d81fa6c3..087b5a51e55 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; @@ -244,4 +247,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 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 dc91388458a..72fc4c2c6f9 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -332,13 +332,14 @@ A related parameter (transferSlack) also helps avoid missed connections when the } // 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)`. @@ -348,10 +349,17 @@ A related parameter (transferSlack) also helps avoid missed connections when the 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 0d2bf71dcc6..4563a3ba60a 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -805,6 +805,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 834f502b93db4d2c15fc6276ff472766fff1b527 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Thu, 12 Sep 2024 08:54:58 +0200 Subject: [PATCH 063/121] Version 2.6.0-entur-33 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d97cb1043d3..9bf5dcf5ad7 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-33 jar From a6d16700b5436c54d1835d55acd14de094b98c0e 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 064/121] 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 6fac28d2178..4df8f460dbe 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 8eb3e64d897..1c8d478f0f3 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 157 + EN-0072 31.3 2.52 @@ -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 b6d336972edf70783ba3f2187a3cf0ff11083b73 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 19 Dec 2023 21:26:49 +0100 Subject: [PATCH 065/121] =?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 --- doc/user/Configuration.md | 1 + doc/user/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/doc/user/Configuration.md b/doc/user/Configuration.md index 8e366e476a9..bd35d681eb6 100644 --- a/doc/user/Configuration.md +++ b/doc/user/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/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index 674ab238888..dabbca4fe94 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/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 51b2fae86b5..a2a3d079d13 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 3ae029fdb2d..b4912aacb1d 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java @@ -203,6 +203,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 b67b26b90b6..0f18f6577a8 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 @@ -616,6 +616,14 @@ Normally this is when the search is performed (now), plus a small grace period t ) .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 749772fae12..591b0376f59 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 37bb7270902..ea0274a139d 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 @@ -133,7 +133,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 948b132e408..3c3f789918d 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.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; @@ -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 5b9d81fa6c3..087b5a51e55 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; @@ -244,4 +247,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 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 dc91388458a..72fc4c2c6f9 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -332,13 +332,14 @@ A related parameter (transferSlack) also helps avoid missed connections when the } // 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)`. @@ -348,10 +349,17 @@ A related parameter (transferSlack) also helps avoid missed connections when the 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 0d2bf71dcc6..4563a3ba60a 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -805,6 +805,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 baaf57f7f2cccf61190e0e024d25ef1a0fdc60be Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Thu, 12 Sep 2024 13:43:12 +0200 Subject: [PATCH 066/121] Version 2.6.0-entur-34 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1c8d478f0f3..686bba636d0 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-34 jar From 53dbb839be4a4b64bd17c3bd989d118753a67cc8 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 067/121] 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 6fac28d2178..4df8f460dbe 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 706db0cd2c9..6ea2ee79edd 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 158 + EN-0073 32.0 2.52 @@ -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 be4822255f0d0fe49fae891f43a402be6c663161 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 19 Dec 2023 21:26:49 +0100 Subject: [PATCH 068/121] =?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 --- doc/user/Configuration.md | 1 + doc/user/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/doc/user/Configuration.md b/doc/user/Configuration.md index 58cc31c0213..116c9f9de48 100644 --- a/doc/user/Configuration.md +++ b/doc/user/Configuration.md @@ -228,6 +228,7 @@ Here is a list of all features which can be toggled on/off and their default val | `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. | ✓️ | | | `ExtraTransferLegOnSameStop` | Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated. | | | | `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/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index 674ab238888..dabbca4fe94 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/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 51b2fae86b5..a2a3d079d13 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 3ae029fdb2d..b4912aacb1d 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java @@ -203,6 +203,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 b67b26b90b6..0f18f6577a8 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 @@ -616,6 +616,14 @@ Normally this is when the search is performed (now), plus a small grace period t ) .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 29c2a452448..2af00c92091 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -37,6 +37,7 @@ public enum OTPFeature { "Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated." ), 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 37bb7270902..ea0274a139d 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 @@ -133,7 +133,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 948b132e408..3c3f789918d 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.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; @@ -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 5b9d81fa6c3..087b5a51e55 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; @@ -244,4 +247,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 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 dc91388458a..72fc4c2c6f9 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -332,13 +332,14 @@ A related parameter (transferSlack) also helps avoid missed connections when the } // 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)`. @@ -348,10 +349,17 @@ A related parameter (transferSlack) also helps avoid missed connections when the 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 0d2bf71dcc6..4563a3ba60a 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -805,6 +805,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 20d2b7886b99db1d71d37ac4ccb08a666b0b5853 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 23 Sep 2024 10:18:07 +0200 Subject: [PATCH 069/121] Version 2.7.0-entur-1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6ea2ee79edd..36e67afd527 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.7.0-SNAPSHOT + 2.7.0-entur-1 jar From f40e16def54d94a3e85f995331d095752fbf6d30 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 23 Sep 2024 10:48:16 +0200 Subject: [PATCH 070/121] Bump otp.serialization.version.id to EN-0074 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 36e67afd527..69f9bf939a8 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - EN-0073 + EN-0074 32.0 2.52 From 222092561aa8901184799f255525917fd4945251 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 23 Sep 2024 12:11:04 +0200 Subject: [PATCH 071/121] Version 2.7.0-entur-2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 69f9bf939a8..6ff08f6b2ae 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.7.0-entur-1 + 2.7.0-entur-2 jar From f52117c9c92734f98c2f7ca6106ed10b4fafc506 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 072/121] 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 6fac28d2178..4df8f460dbe 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 9e40d4b513b..301316016a8 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 158 + EN-0073 32.0 2.52 @@ -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 9e43e32ff4933428be58940bc08e3d39abc64dda Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 19 Dec 2023 21:26:49 +0100 Subject: [PATCH 073/121] =?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 --- doc/user/Configuration.md | 1 + doc/user/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/doc/user/Configuration.md b/doc/user/Configuration.md index bca974f8617..f48e7813bee 100644 --- a/doc/user/Configuration.md +++ b/doc/user/Configuration.md @@ -228,6 +228,7 @@ Here is a list of all features which can be toggled on/off and their default val | `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. | ✓️ | | | `ExtraTransferLegOnSameStop` | Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated. | | | | `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/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index c00502b2726..8b9183218e3 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/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 51b2fae86b5..a2a3d079d13 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 3ae029fdb2d..b4912aacb1d 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java @@ -203,6 +203,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 b67b26b90b6..0f18f6577a8 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 @@ -616,6 +616,14 @@ Normally this is when the search is performed (now), plus a small grace period t ) .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 29c2a452448..2af00c92091 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -37,6 +37,7 @@ public enum OTPFeature { "Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated." ), 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 37bb7270902..ea0274a139d 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 @@ -133,7 +133,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 948b132e408..3c3f789918d 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.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; @@ -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 5b9d81fa6c3..087b5a51e55 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; @@ -244,4 +247,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 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 dc91388458a..72fc4c2c6f9 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -332,13 +332,14 @@ A related parameter (transferSlack) also helps avoid missed connections when the } // 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)`. @@ -348,10 +349,17 @@ A related parameter (transferSlack) also helps avoid missed connections when the 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 a3d0c013955..da8c768575a 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -805,6 +805,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 17a735230d3aef8f940b7d64d969071787f5fccf Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 8 Oct 2024 13:09:10 +0200 Subject: [PATCH 074/121] Version 2.7.0-entur-3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 301316016a8..bc64c218168 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.7.0-SNAPSHOT + 2.7.0-entur-3 jar From 8cce58bba580d7762b853ab320bc81756ae9513d 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 075/121] 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 6fac28d2178..4df8f460dbe 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 34997513f54..f7d91c62d0c 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 160 + EN-0074 32.0 2.52 @@ -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 2f2c8fb8901d32de0c4903119715d289a7e318cf Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 19 Dec 2023 21:26:49 +0100 Subject: [PATCH 076/121] =?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 --- doc/user/Configuration.md | 1 + doc/user/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/doc/user/Configuration.md b/doc/user/Configuration.md index bca974f8617..f48e7813bee 100644 --- a/doc/user/Configuration.md +++ b/doc/user/Configuration.md @@ -228,6 +228,7 @@ Here is a list of all features which can be toggled on/off and their default val | `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. | ✓️ | | | `ExtraTransferLegOnSameStop` | Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated. | | | | `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/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index c00502b2726..8b9183218e3 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/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 51b2fae86b5..a2a3d079d13 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 3ae029fdb2d..b4912aacb1d 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java @@ -203,6 +203,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 b67b26b90b6..0f18f6577a8 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 @@ -616,6 +616,14 @@ Normally this is when the search is performed (now), plus a small grace period t ) .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 324f5397673..60c35e6fd1a 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -38,6 +38,7 @@ public enum OTPFeature { "Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated." ), 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 1a44e275521..93ff0bd3e59 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 @@ -133,7 +133,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 948b132e408..3c3f789918d 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.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; @@ -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 9717404d370..dd7a131dd43 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.Nullable; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.time.ServiceDateUtils; @@ -26,6 +27,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; @@ -242,4 +245,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 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 dc91388458a..72fc4c2c6f9 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -332,13 +332,14 @@ A related parameter (transferSlack) also helps avoid missed connections when the } // 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)`. @@ -348,10 +349,17 @@ A related parameter (transferSlack) also helps avoid missed connections when the 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 a3d0c013955..da8c768575a 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -805,6 +805,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 1b097e12664710345d907dd090bb166d5f9efc02 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 8 Oct 2024 17:40:10 +0200 Subject: [PATCH 077/121] Version 2.7.0-entur-4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f7d91c62d0c..b5770f00845 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.7.0-SNAPSHOT + 2.7.0-entur-4 jar From ee9e18cf1920eae6fd5fa2149d6c15c10b20b2d2 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 078/121] 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 6fac28d2178..4df8f460dbe 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 7994b112642..c93a47bf321 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 161 + EN-0075 32.0 2.52 @@ -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 ac9a5add7c25f21827023f8bacffb1a58fb0acfe Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 19 Dec 2023 21:26:49 +0100 Subject: [PATCH 079/121] =?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 --- doc/user/Configuration.md | 1 + doc/user/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/doc/user/Configuration.md b/doc/user/Configuration.md index bca974f8617..f48e7813bee 100644 --- a/doc/user/Configuration.md +++ b/doc/user/Configuration.md @@ -228,6 +228,7 @@ Here is a list of all features which can be toggled on/off and their default val | `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. | ✓️ | | | `ExtraTransferLegOnSameStop` | Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated. | | | | `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/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index c00502b2726..8b9183218e3 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/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 51b2fae86b5..a2a3d079d13 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 3ae029fdb2d..b4912aacb1d 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java @@ -203,6 +203,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 b67b26b90b6..0f18f6577a8 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 @@ -616,6 +616,14 @@ Normally this is when the search is performed (now), plus a small grace period t ) .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 324f5397673..60c35e6fd1a 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -38,6 +38,7 @@ public enum OTPFeature { "Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated." ), 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 1a44e275521..93ff0bd3e59 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 @@ -133,7 +133,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 948b132e408..3c3f789918d 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.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; @@ -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 9717404d370..dd7a131dd43 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.Nullable; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.time.ServiceDateUtils; @@ -26,6 +27,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; @@ -242,4 +245,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 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 dc91388458a..72fc4c2c6f9 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -332,13 +332,14 @@ A related parameter (transferSlack) also helps avoid missed connections when the } // 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)`. @@ -348,10 +349,17 @@ A related parameter (transferSlack) also helps avoid missed connections when the 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 a3d0c013955..da8c768575a 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -805,6 +805,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 bd08e7284ba7a30975814121d3f47f9ccdc135f3 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Wed, 9 Oct 2024 16:54:02 +0200 Subject: [PATCH 080/121] Version 2.7.0-entur-5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c93a47bf321..603f7c625cb 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.7.0-SNAPSHOT + 2.7.0-entur-5 jar From fd58fa49acebd6eecc8f17225bf1c3367ef21f5b 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 081/121] 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 6fac28d2178..4df8f460dbe 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 4bc3110c397..8ad55169268 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 164 + EN-0076 32.0 2.52 @@ -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 9f5d8c8bbd008c975b0606f4e1a4b5f47fa78b37 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 19 Dec 2023 21:26:49 +0100 Subject: [PATCH 082/121] =?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 --- doc/user/Configuration.md | 1 + doc/user/RouteRequest.md | 1 + .../resources/RequestToPreferencesMapper.java | 1 + .../restapi/resources/RoutingResource.java | 3 + .../ConcurrentCompositeWorker.java | 40 +++++ .../sorlandsbanen/EnturHackSorlandsBanen.java | 137 ++++++++++++++++++ .../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 + .../raptor/configure/RaptorConfig.java | 43 +++++- .../raptor/service/HeuristicSearchTask.java | 3 +- .../service/RangeRaptorDynamicSearch.java | 10 +- .../service/ViaRangeRaptorDynamicSearch.java | 9 +- .../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 | 30 +++- .../mappers/RaptorRequestMapperTest.java | 3 +- 27 files changed, 524 insertions(+), 39 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/doc/user/Configuration.md b/doc/user/Configuration.md index bca974f8617..f48e7813bee 100644 --- a/doc/user/Configuration.md +++ b/doc/user/Configuration.md @@ -228,6 +228,7 @@ Here is a list of all features which can be toggled on/off and their default val | `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. | ✓️ | | | `ExtraTransferLegOnSameStop` | Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated. | | | | `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/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index ea3d0d12c74..6cf450836dc 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/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 51b2fae86b5..a2a3d079d13 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 3ae029fdb2d..b4912aacb1d 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java @@ -203,6 +203,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..57f3cbdbdaa --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java @@ -0,0 +1,40 @@ +package org.opentripplanner.ext.sorlandsbanen; + +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.rangeraptor.internalapi.RaptorRouter; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; + +class ConcurrentCompositeWorker implements RaptorRouter { + + private final RaptorRouter mainWorker; + private final RaptorRouter alternativeWorker; + + ConcurrentCompositeWorker(RaptorRouter mainWorker, RaptorRouter alternativeWorker) { + this.mainWorker = mainWorker; + this.alternativeWorker = alternativeWorker; + } + + @Override + public RaptorRouterResult 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..68f6188b965 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java @@ -0,0 +1,137 @@ +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.rangeraptor.internalapi.RaptorRouter; +import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; +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(double extraSearchCoachReluctance) { + return extraSearchCoachReluctance > 0.1; + } + + public static RaptorRouter router( + RaptorRequest mcRequest, + RaptorTransitDataProvider transitData, + Function, RaptorRouter> raptorRouterFactory + ) { + //noinspection unchecked + RaptorTransitDataProvider altTransitData = (RaptorTransitDataProvider) ( + (RaptorRoutingRequestTransitData) transitData + ).enturHackSorlandsbanen(mapFactors(mcRequest.extraSearchCoachReluctance)); + + return new ConcurrentCompositeWorker<>( + raptorRouterFactory.apply(transitData), + raptorRouterFactory.apply(altTransitData) + ); + } + + 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..28ccfd5ff13 --- /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.RaptorRouterResult; +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 RaptorRouterResult { + + private static final Logger LOG = LoggerFactory.getLogger(RaptorWorkerResultComposite.class); + + private final RaptorRouterResult mainResult; + private final RaptorRouterResult alternativeResult; + + public RaptorWorkerResultComposite( + RaptorRouterResult mainResult, + RaptorRouterResult 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 5b1bbd84373..27ea61568e0 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 @@ -632,6 +632,14 @@ Normally this is when the search is performed (now), plus a small grace period t ) .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 324f5397673..60c35e6fd1a 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -38,6 +38,7 @@ public enum OTPFeature { "Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated." ), 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/configure/RaptorConfig.java b/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java index cc488448304..20bab1e0c8c 100644 --- a/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java +++ b/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java @@ -3,6 +3,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.annotation.Nullable; +import org.opentripplanner.ext.sorlandsbanen.EnturHackSorlandsBanen; +import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.concurrent.OtpRequestThreadFactory; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.request.RaptorRequest; @@ -15,6 +17,7 @@ import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; import org.opentripplanner.raptor.rangeraptor.internalapi.PassThroughPointsService; import org.opentripplanner.raptor.rangeraptor.internalapi.RangeRaptorWorker; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouter; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerState; import org.opentripplanner.raptor.rangeraptor.internalapi.RoutingStrategy; @@ -58,7 +61,7 @@ public SearchContext context(RaptorTransitDataProvider transit, RaptorRequ return SearchContext.of(request, tuningParameters, transit, acceptC2AtDestination).build(); } - public RangeRaptor createRangeRaptorWithStdWorker( + public RaptorRouter createRangeRaptorWithStdWorker( RaptorTransitDataProvider transitData, RaptorRequest request ) { @@ -66,11 +69,12 @@ public RangeRaptor createRangeRaptorWithStdWorker( var stdConfig = new StdRangeRaptorConfig<>(context); return createRangeRaptor( context, + context.transit(), createWorker(context.legs().getFirst(), stdConfig.state(), stdConfig.strategy()) ); } - public RangeRaptor createRangeRaptorWithMcWorker( + public RaptorRouter createRangeRaptorWithMcWorker( RaptorTransitDataProvider transitData, RaptorRequest request, Heuristics heuristics @@ -93,11 +97,10 @@ public RangeRaptor createRangeRaptorWithMcWorker( var c = new McRangeRaptorConfig<>(leg, passThroughPointsService).withHeuristics(heuristics); worker = createWorker(leg, c.state(), c.strategy()); } - - return createRangeRaptor(context, worker); + return createRaptorRouter(request, context, worker); } - public RangeRaptor createRangeRaptorWithHeuristicSearch( + public RaptorRouter createRangeRaptorWithHeuristicSearch( RaptorTransitDataProvider transitData, RaptorRequest request ) { @@ -137,7 +140,7 @@ private static PassThroughPointsService createPassThroughPointsService(RaptorReq return McRangeRaptorConfig.passThroughPointsService(request.multiCriteria()); } - private RangeRaptorWorker createWorker( + public RangeRaptorWorker createWorker( SearchContextViaLeg ctxLeg, RaptorWorkerState workerState, RoutingStrategy routingStrategy @@ -156,10 +159,34 @@ private RangeRaptorWorker createWorker( ); } - private RangeRaptor createRangeRaptor(SearchContext ctx, RangeRaptorWorker worker) { + // HACK SØRLANDSBANEN + private RaptorRouter createRaptorRouter( + RaptorRequest request, + SearchContext ctx, + RangeRaptorWorker worker + ) { + if ( + OTPFeature.HackSorlandsbanen.isOn() && + EnturHackSorlandsBanen.match(request.extraSearchCoachReluctance) + ) { + return EnturHackSorlandsBanen.router( + request, + ctx.transit(), + td -> createRangeRaptor(ctx, td, worker) + ); + } else { + return createRangeRaptor(ctx, ctx.transit(), worker); + } + } + + private RaptorRouter createRangeRaptor( + SearchContext ctx, + RaptorTransitDataProvider data, + RangeRaptorWorker worker + ) { return new RangeRaptor<>( worker, - ctx.transit(), + data, ctx.legs().getFirst().accessPaths(), ctx.roundTracker(), ctx.calculator(), diff --git a/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java b/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java index 7c0158d2192..0de84283af8 100644 --- a/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java +++ b/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java @@ -10,6 +10,7 @@ import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.raptor.rangeraptor.RangeRaptor; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouter; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; import org.slf4j.Logger; @@ -33,7 +34,7 @@ public class HeuristicSearchTask { private final RaptorTransitDataProvider transitData; private boolean run = false; - private RangeRaptor search = null; + private RaptorRouter search = null; private RaptorRequest originalRequest; private RaptorRequest heuristicRequest; private RaptorRouterResult result = null; diff --git a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java index 5353804f414..3af049a55ec 100644 --- a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java +++ b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java @@ -19,8 +19,8 @@ import org.opentripplanner.raptor.api.request.SearchParamsBuilder; import org.opentripplanner.raptor.api.response.RaptorResponse; import org.opentripplanner.raptor.configure.RaptorConfig; -import org.opentripplanner.raptor.rangeraptor.RangeRaptor; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouter; import org.opentripplanner.raptor.rangeraptor.transit.RaptorSearchWindowCalculator; import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; import org.slf4j.Logger; @@ -129,18 +129,18 @@ private void runHeuristics() { private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request) { LOG.debug("Main request: {}", request); - RangeRaptor rangeRaptorRouter; + RaptorRouter raptorRouter; // Create worker if (request.profile().is(MULTI_CRITERIA)) { - rangeRaptorRouter = + raptorRouter = config.createRangeRaptorWithMcWorker(transitData, request, getDestinationHeuristics()); } else { - rangeRaptorRouter = config.createRangeRaptorWithStdWorker(transitData, request); + raptorRouter = config.createRangeRaptorWithStdWorker(transitData, request); } // Route - var result = rangeRaptorRouter.route(); + var result = raptorRouter.route(); // create and return response return new RaptorResponse<>( diff --git a/src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java b/src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java index 4476e40464f..96a55793d6a 100644 --- a/src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java +++ b/src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java @@ -21,6 +21,7 @@ import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.raptor.rangeraptor.RangeRaptor; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouter; import org.opentripplanner.raptor.rangeraptor.transit.RaptorSearchWindowCalculator; import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; import org.slf4j.Logger; @@ -129,18 +130,18 @@ private void runHeuristics() { private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request) { LOG.debug("Main request: {}", request); - RangeRaptor rangeRaptorRouter; + RaptorRouter raptorRouter; // Create worker if (request.profile().is(MULTI_CRITERIA)) { - rangeRaptorRouter = + raptorRouter = config.createRangeRaptorWithMcWorker(transitData, request, getDestinationHeuristics()); } else { - rangeRaptorRouter = config.createRangeRaptorWithStdWorker(transitData, request); + raptorRouter = config.createRangeRaptorWithStdWorker(transitData, request); } // Route - var result = rangeRaptorRouter.route(); + var result = raptorRouter.route(); // create and return response return new RaptorResponse<>( 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 e72d8ee1427..7afb4eaf6ff 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 @@ -138,7 +138,8 @@ private TransitRouterResult route() { accessEgresses.getAccesses(), accessEgresses.getEgresses(), serverContext.meterRegistry(), - this::listStopIndexes + this::listStopIndexes, + 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 1074724a90c..12c9cad2bfc 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.util.Collection; import java.util.List; import java.util.function.Predicate; +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; @@ -22,6 +23,8 @@ import org.opentripplanner.raptor.api.request.RaptorViaLocation; 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.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; @@ -39,6 +42,8 @@ public class RaptorRequestMapper { private final MeterRegistry meterRegistry; private final LookupStopIndexCallback lookUpStopIndex; + private final TransitLayer transitLayer; + private RaptorRequestMapper( RouteRequest request, boolean isMultiThreaded, @@ -46,7 +51,8 @@ private RaptorRequestMapper( Collection egressPaths, long transitSearchTimeZeroEpocSecond, MeterRegistry meterRegistry, - LookupStopIndexCallback lookUpStopIndex + LookupStopIndexCallback lookUpStopIndex, + TransitLayer transitLayer ) { this.request = request; this.isMultiThreadedEnbled = isMultiThreaded; @@ -55,6 +61,7 @@ private RaptorRequestMapper( this.transitSearchTimeZeroEpocSecond = transitSearchTimeZeroEpocSecond; this.meterRegistry = meterRegistry; this.lookUpStopIndex = lookUpStopIndex; + this.transitLayer = transitLayer; } public static RaptorRequest mapRequest( @@ -64,7 +71,8 @@ public static RaptorRequest mapRequest( Collection accessPaths, Collection egressPaths, MeterRegistry meterRegistry, - LookupStopIndexCallback lookUpStopIndex + LookupStopIndexCallback lookUpStopIndex, + TransitLayer transitLayer ) { return new RaptorRequestMapper( request, @@ -73,7 +81,8 @@ public static RaptorRequest mapRequest( egressPaths, transitSearchTimeZero.toEpochSecond(), meterRegistry, - lookUpStopIndex + lookUpStopIndex, + transitLayer ) .doMap(); } @@ -195,7 +204,7 @@ private RaptorRequest doMap() { ) ); } - return builder.build(); + return EnturHackSorlandsBanen.enableHack(builder.build(), request, transitLayer); } private boolean hasPassThroughOnly() { 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 0182598ca35..12910d277b5 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.Nullable; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.time.ServiceDateUtils; @@ -26,6 +27,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; @@ -242,4 +245,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 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 dc91388458a..72fc4c2c6f9 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -332,13 +332,14 @@ A related parameter (transferSlack) also helps avoid missed connections when the } // 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)`. @@ -348,10 +349,17 @@ A related parameter (transferSlack) also helps avoid missed connections when the 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 9aa743fd42d..78dae18cd07 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -805,6 +805,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 657e060c30b..64c65a4c60d 100644 --- a/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java +++ b/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java @@ -44,6 +44,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. @@ -224,7 +227,8 @@ void enforcePackageDependenciesInConfigure() { RR_CONTEXT, RR_STD_CONFIGURE, RR_MC_CONFIGURE, - FRAMEWORK_UTILS + FRAMEWORK_UTILS, + EXT_SORLANDSBANAN_HACK ) .verify(); } @@ -238,3 +242,27 @@ void enforceNoCyclicDependencies() { .check(ArchComponent.OTP_CLASSES); } } +/* +[ERROR] RaptorArchitectureTest.enforcePackageDependenciesInConfigure:233 Architecture Violation [Priority: MEDIUM] +- Rule 'classes that reside in a package 'org.opentripplanner.raptor.configure' and do not implement dagger.internal.Factory should only depend on classes that reside in any package [ +'org.opentripplanner.raptor.rangeraptor.internalapi', +'org.opentripplanner.raptor.rangeraptor', +'org.opentripplanner.raptor.rangeraptor.multicriteria.configure', +'org.opentripplanner.raptor.api..', +'org.opentripplanner.framework.i18n', 'org.opentripplanner.raptor.rangeraptor.context', +'org.opentripplanner.framework.doc', 'org.opentripplanner.raptor.rangeraptor.transit', +'org.opentripplanner.framework.application', 'org.opentripplanner.raptor.spi', +'org.opentripplanner.framework.logging', 'org.opentripplanner.framework.error', +'org.opentripplanner.framework.concurrent', 'org.opentripplanner.framework.text', +'org.opentripplanner.raptor.rangeraptor.standard.configure', +'org.opentripplanner.framework.lang', 'org.opentripplanner.framework.time', +'org.opentripplanner.framework.tostring', 'java..', 'javax.(*)..', 'jakarta.(*)..', 'org.slf4j', 'org.opentripplanner.raptor.configure', '']' was violated (2 times): +Method calls method in (RaptorConfig.java:170) +Method calls method in (RaptorConfig.java:172) + + +*/ 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 87e90e61b2e..22ec1d21023 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 @@ -114,7 +114,8 @@ private static RaptorRequest map(RouteRequest request) { ACCESS, EGRESS, null, - id -> IntStream.of(STOPS_MAP.get(id).getIndex()) + id -> IntStream.of(STOPS_MAP.get(id).getIndex()), + null ); } } From abfe4ed569780eb1f54dcec4b9ad6b42fe4cc32e Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 14 Oct 2024 13:47:32 +0200 Subject: [PATCH 083/121] Version 2.7.0-entur-6 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8ad55169268..1294afd383c 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.7.0-SNAPSHOT + 2.7.0-entur-6 jar From efee1b90e626b10f805f967803ce11055926aa1c 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 084/121] 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 + .../src/main/java/EnturUpdatePomVersion.java | 135 ++++++++++ pom.xml | 12 +- 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 application/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 6fac28d2178..4df8f460dbe 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .project .pydevproject .settings +.prepare_release.tmp .sonar .nvmrc *~ diff --git a/application/src/main/java/EnturUpdatePomVersion.java b/application/src/main/java/EnturUpdatePomVersion.java new file mode 100644 index 00000000000..97bd72b3c6e --- /dev/null +++ b/application/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; + } +} diff --git a/pom.xml b/pom.xml index 34a76961872..e9b184880d3 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ - 164 + EN-0076 32.0 2.52 @@ -86,11 +86,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 + From ac86540907b431f38deaf908721476a19a64689d Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 19 Dec 2023 21:26:49 +0100 Subject: [PATCH 085/121] =?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 --- .../resources/RequestToPreferencesMapper.java | 1 + .../restapi/resources/RoutingResource.java | 3 + .../ConcurrentCompositeWorker.java | 40 +++++ .../sorlandsbanen/EnturHackSorlandsBanen.java | 137 ++++++++++++++++++ .../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 + .../raptor/configure/RaptorConfig.java | 43 +++++- .../raptor/service/HeuristicSearchTask.java | 3 +- .../service/RangeRaptorDynamicSearch.java | 10 +- .../service/ViaRangeRaptorDynamicSearch.java | 9 +- .../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 | 30 +++- .../mappers/RaptorRequestMapperTest.java | 3 +- doc/user/Configuration.md | 1 + doc/user/RouteRequest.md | 1 + 27 files changed, 524 insertions(+), 39 deletions(-) create mode 100644 application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java create mode 100644 application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java create mode 100644 application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java create mode 100644 application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java diff --git a/application/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java b/application/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java index 51b2fae86b5..a2a3d079d13 100644 --- a/application/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java +++ b/application/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/application/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java b/application/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java index 3ae029fdb2d..b4912aacb1d 100644 --- a/application/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java +++ b/application/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java @@ -203,6 +203,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/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java new file mode 100644 index 00000000000..57f3cbdbdaa --- /dev/null +++ b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java @@ -0,0 +1,40 @@ +package org.opentripplanner.ext.sorlandsbanen; + +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.rangeraptor.internalapi.RaptorRouter; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; + +class ConcurrentCompositeWorker implements RaptorRouter { + + private final RaptorRouter mainWorker; + private final RaptorRouter alternativeWorker; + + ConcurrentCompositeWorker(RaptorRouter mainWorker, RaptorRouter alternativeWorker) { + this.mainWorker = mainWorker; + this.alternativeWorker = alternativeWorker; + } + + @Override + public RaptorRouterResult 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/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java new file mode 100644 index 00000000000..68f6188b965 --- /dev/null +++ b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java @@ -0,0 +1,137 @@ +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.rangeraptor.internalapi.RaptorRouter; +import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; +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(double extraSearchCoachReluctance) { + return extraSearchCoachReluctance > 0.1; + } + + public static RaptorRouter router( + RaptorRequest mcRequest, + RaptorTransitDataProvider transitData, + Function, RaptorRouter> raptorRouterFactory + ) { + //noinspection unchecked + RaptorTransitDataProvider altTransitData = (RaptorTransitDataProvider) ( + (RaptorRoutingRequestTransitData) transitData + ).enturHackSorlandsbanen(mapFactors(mcRequest.extraSearchCoachReluctance)); + + return new ConcurrentCompositeWorker<>( + raptorRouterFactory.apply(transitData), + raptorRouterFactory.apply(altTransitData) + ); + } + + 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/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java new file mode 100644 index 00000000000..94267b3e7c2 --- /dev/null +++ b/application/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/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java new file mode 100644 index 00000000000..28ccfd5ff13 --- /dev/null +++ b/application/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.RaptorRouterResult; +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 RaptorRouterResult { + + private static final Logger LOG = LoggerFactory.getLogger(RaptorWorkerResultComposite.class); + + private final RaptorRouterResult mainResult; + private final RaptorRouterResult alternativeResult; + + public RaptorWorkerResultComposite( + RaptorRouterResult mainResult, + RaptorRouterResult 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/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java index 5b1bbd84373..27ea61568e0 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java @@ -632,6 +632,14 @@ Normally this is when the search is performed (now), plus a small grace period t ) .build() ) + .argument( + GraphQLArgument + .newArgument() + .name("extraSearchCoachReluctance") + .description("FOR TESTING ONLY") + .type(Scalars.GraphQLFloat) + .build() + ) .dataFetcher(environment -> new TransmodelGraphQLPlanner().plan(environment)) .build(); } diff --git a/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 324f5397673..60c35e6fd1a 100644 --- a/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -38,6 +38,7 @@ public enum OTPFeature { "Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated." ), 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/application/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java b/application/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java index 010bff4bc08..c53f0f90ae5 100644 --- a/application/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java +++ b/application/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/application/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java b/application/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java index ed2aab3de20..7bb8b538843 100644 --- a/application/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java +++ b/application/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/application/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java b/application/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java index cc488448304..20bab1e0c8c 100644 --- a/application/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java +++ b/application/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java @@ -3,6 +3,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.annotation.Nullable; +import org.opentripplanner.ext.sorlandsbanen.EnturHackSorlandsBanen; +import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.concurrent.OtpRequestThreadFactory; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.request.RaptorRequest; @@ -15,6 +17,7 @@ import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; import org.opentripplanner.raptor.rangeraptor.internalapi.PassThroughPointsService; import org.opentripplanner.raptor.rangeraptor.internalapi.RangeRaptorWorker; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouter; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerState; import org.opentripplanner.raptor.rangeraptor.internalapi.RoutingStrategy; @@ -58,7 +61,7 @@ public SearchContext context(RaptorTransitDataProvider transit, RaptorRequ return SearchContext.of(request, tuningParameters, transit, acceptC2AtDestination).build(); } - public RangeRaptor createRangeRaptorWithStdWorker( + public RaptorRouter createRangeRaptorWithStdWorker( RaptorTransitDataProvider transitData, RaptorRequest request ) { @@ -66,11 +69,12 @@ public RangeRaptor createRangeRaptorWithStdWorker( var stdConfig = new StdRangeRaptorConfig<>(context); return createRangeRaptor( context, + context.transit(), createWorker(context.legs().getFirst(), stdConfig.state(), stdConfig.strategy()) ); } - public RangeRaptor createRangeRaptorWithMcWorker( + public RaptorRouter createRangeRaptorWithMcWorker( RaptorTransitDataProvider transitData, RaptorRequest request, Heuristics heuristics @@ -93,11 +97,10 @@ public RangeRaptor createRangeRaptorWithMcWorker( var c = new McRangeRaptorConfig<>(leg, passThroughPointsService).withHeuristics(heuristics); worker = createWorker(leg, c.state(), c.strategy()); } - - return createRangeRaptor(context, worker); + return createRaptorRouter(request, context, worker); } - public RangeRaptor createRangeRaptorWithHeuristicSearch( + public RaptorRouter createRangeRaptorWithHeuristicSearch( RaptorTransitDataProvider transitData, RaptorRequest request ) { @@ -137,7 +140,7 @@ private static PassThroughPointsService createPassThroughPointsService(RaptorReq return McRangeRaptorConfig.passThroughPointsService(request.multiCriteria()); } - private RangeRaptorWorker createWorker( + public RangeRaptorWorker createWorker( SearchContextViaLeg ctxLeg, RaptorWorkerState workerState, RoutingStrategy routingStrategy @@ -156,10 +159,34 @@ private RangeRaptorWorker createWorker( ); } - private RangeRaptor createRangeRaptor(SearchContext ctx, RangeRaptorWorker worker) { + // HACK SØRLANDSBANEN + private RaptorRouter createRaptorRouter( + RaptorRequest request, + SearchContext ctx, + RangeRaptorWorker worker + ) { + if ( + OTPFeature.HackSorlandsbanen.isOn() && + EnturHackSorlandsBanen.match(request.extraSearchCoachReluctance) + ) { + return EnturHackSorlandsBanen.router( + request, + ctx.transit(), + td -> createRangeRaptor(ctx, td, worker) + ); + } else { + return createRangeRaptor(ctx, ctx.transit(), worker); + } + } + + private RaptorRouter createRangeRaptor( + SearchContext ctx, + RaptorTransitDataProvider data, + RangeRaptorWorker worker + ) { return new RangeRaptor<>( worker, - ctx.transit(), + data, ctx.legs().getFirst().accessPaths(), ctx.roundTracker(), ctx.calculator(), diff --git a/application/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java b/application/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java index 7c0158d2192..0de84283af8 100644 --- a/application/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java +++ b/application/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java @@ -10,6 +10,7 @@ import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.raptor.rangeraptor.RangeRaptor; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouter; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; import org.slf4j.Logger; @@ -33,7 +34,7 @@ public class HeuristicSearchTask { private final RaptorTransitDataProvider transitData; private boolean run = false; - private RangeRaptor search = null; + private RaptorRouter search = null; private RaptorRequest originalRequest; private RaptorRequest heuristicRequest; private RaptorRouterResult result = null; diff --git a/application/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java b/application/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java index 5353804f414..3af049a55ec 100644 --- a/application/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java +++ b/application/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java @@ -19,8 +19,8 @@ import org.opentripplanner.raptor.api.request.SearchParamsBuilder; import org.opentripplanner.raptor.api.response.RaptorResponse; import org.opentripplanner.raptor.configure.RaptorConfig; -import org.opentripplanner.raptor.rangeraptor.RangeRaptor; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouter; import org.opentripplanner.raptor.rangeraptor.transit.RaptorSearchWindowCalculator; import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; import org.slf4j.Logger; @@ -129,18 +129,18 @@ private void runHeuristics() { private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request) { LOG.debug("Main request: {}", request); - RangeRaptor rangeRaptorRouter; + RaptorRouter raptorRouter; // Create worker if (request.profile().is(MULTI_CRITERIA)) { - rangeRaptorRouter = + raptorRouter = config.createRangeRaptorWithMcWorker(transitData, request, getDestinationHeuristics()); } else { - rangeRaptorRouter = config.createRangeRaptorWithStdWorker(transitData, request); + raptorRouter = config.createRangeRaptorWithStdWorker(transitData, request); } // Route - var result = rangeRaptorRouter.route(); + var result = raptorRouter.route(); // create and return response return new RaptorResponse<>( diff --git a/application/src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java b/application/src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java index 4476e40464f..96a55793d6a 100644 --- a/application/src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java +++ b/application/src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java @@ -21,6 +21,7 @@ import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.raptor.rangeraptor.RangeRaptor; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouter; import org.opentripplanner.raptor.rangeraptor.transit.RaptorSearchWindowCalculator; import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; import org.slf4j.Logger; @@ -129,18 +130,18 @@ private void runHeuristics() { private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request) { LOG.debug("Main request: {}", request); - RangeRaptor rangeRaptorRouter; + RaptorRouter raptorRouter; // Create worker if (request.profile().is(MULTI_CRITERIA)) { - rangeRaptorRouter = + raptorRouter = config.createRangeRaptorWithMcWorker(transitData, request, getDestinationHeuristics()); } else { - rangeRaptorRouter = config.createRangeRaptorWithStdWorker(transitData, request); + raptorRouter = config.createRangeRaptorWithStdWorker(transitData, request); } // Route - var result = rangeRaptorRouter.route(); + var result = raptorRouter.route(); // create and return response return new RaptorResponse<>( diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index e72d8ee1427..7afb4eaf6ff 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -138,7 +138,8 @@ private TransitRouterResult route() { accessEgresses.getAccesses(), accessEgresses.getEgresses(), serverContext.meterRegistry(), - this::listStopIndexes + this::listStopIndexes, + transitLayer ); // Route transit diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java index 43faa3f0c40..c3bde3e1cc7 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java +++ b/application/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/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java index fba2a7cfe38..8a14c27566d 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java +++ b/application/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/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java index 152f199248c..4c66b5b452e 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java +++ b/application/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/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index 1074724a90c..12c9cad2bfc 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -8,6 +8,7 @@ import java.util.Collection; import java.util.List; import java.util.function.Predicate; +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; @@ -22,6 +23,8 @@ import org.opentripplanner.raptor.api.request.RaptorViaLocation; 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.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; @@ -39,6 +42,8 @@ public class RaptorRequestMapper { private final MeterRegistry meterRegistry; private final LookupStopIndexCallback lookUpStopIndex; + private final TransitLayer transitLayer; + private RaptorRequestMapper( RouteRequest request, boolean isMultiThreaded, @@ -46,7 +51,8 @@ private RaptorRequestMapper( Collection egressPaths, long transitSearchTimeZeroEpocSecond, MeterRegistry meterRegistry, - LookupStopIndexCallback lookUpStopIndex + LookupStopIndexCallback lookUpStopIndex, + TransitLayer transitLayer ) { this.request = request; this.isMultiThreadedEnbled = isMultiThreaded; @@ -55,6 +61,7 @@ private RaptorRequestMapper( this.transitSearchTimeZeroEpocSecond = transitSearchTimeZeroEpocSecond; this.meterRegistry = meterRegistry; this.lookUpStopIndex = lookUpStopIndex; + this.transitLayer = transitLayer; } public static RaptorRequest mapRequest( @@ -64,7 +71,8 @@ public static RaptorRequest mapRequest( Collection accessPaths, Collection egressPaths, MeterRegistry meterRegistry, - LookupStopIndexCallback lookUpStopIndex + LookupStopIndexCallback lookUpStopIndex, + TransitLayer transitLayer ) { return new RaptorRequestMapper( request, @@ -73,7 +81,8 @@ public static RaptorRequest mapRequest( egressPaths, transitSearchTimeZero.toEpochSecond(), meterRegistry, - lookUpStopIndex + lookUpStopIndex, + transitLayer ) .doMap(); } @@ -195,7 +204,7 @@ private RaptorRequest doMap() { ) ); } - return builder.build(); + return EnturHackSorlandsBanen.enableHack(builder.build(), request, transitLayer); } private boolean hasPassThroughOnly() { diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java index 0182598ca35..12910d277b5 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java +++ b/application/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.Nullable; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.time.ServiceDateUtils; @@ -26,6 +27,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; @@ -242,4 +245,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/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java b/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java index 78f30277e72..94a4a458915 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java +++ b/application/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/application/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index dc91388458a..72fc4c2c6f9 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -332,13 +332,14 @@ A related parameter (transferSlack) also helps avoid missed connections when the } // 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)`. @@ -348,10 +349,17 @@ A related parameter (transferSlack) also helps avoid missed connections when the 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/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index 9aa743fd42d..78dae18cd07 100644 --- a/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -805,6 +805,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/application/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java b/application/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java index 657e060c30b..64c65a4c60d 100644 --- a/application/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java +++ b/application/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java @@ -44,6 +44,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. @@ -224,7 +227,8 @@ void enforcePackageDependenciesInConfigure() { RR_CONTEXT, RR_STD_CONFIGURE, RR_MC_CONFIGURE, - FRAMEWORK_UTILS + FRAMEWORK_UTILS, + EXT_SORLANDSBANAN_HACK ) .verify(); } @@ -238,3 +242,27 @@ void enforceNoCyclicDependencies() { .check(ArchComponent.OTP_CLASSES); } } +/* +[ERROR] RaptorArchitectureTest.enforcePackageDependenciesInConfigure:233 Architecture Violation [Priority: MEDIUM] +- Rule 'classes that reside in a package 'org.opentripplanner.raptor.configure' and do not implement dagger.internal.Factory should only depend on classes that reside in any package [ +'org.opentripplanner.raptor.rangeraptor.internalapi', +'org.opentripplanner.raptor.rangeraptor', +'org.opentripplanner.raptor.rangeraptor.multicriteria.configure', +'org.opentripplanner.raptor.api..', +'org.opentripplanner.framework.i18n', 'org.opentripplanner.raptor.rangeraptor.context', +'org.opentripplanner.framework.doc', 'org.opentripplanner.raptor.rangeraptor.transit', +'org.opentripplanner.framework.application', 'org.opentripplanner.raptor.spi', +'org.opentripplanner.framework.logging', 'org.opentripplanner.framework.error', +'org.opentripplanner.framework.concurrent', 'org.opentripplanner.framework.text', +'org.opentripplanner.raptor.rangeraptor.standard.configure', +'org.opentripplanner.framework.lang', 'org.opentripplanner.framework.time', +'org.opentripplanner.framework.tostring', 'java..', 'javax.(*)..', 'jakarta.(*)..', 'org.slf4j', 'org.opentripplanner.raptor.configure', '']' was violated (2 times): +Method calls method in (RaptorConfig.java:170) +Method calls method in (RaptorConfig.java:172) + + +*/ diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java index 87e90e61b2e..22ec1d21023 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java @@ -114,7 +114,8 @@ private static RaptorRequest map(RouteRequest request) { ACCESS, EGRESS, null, - id -> IntStream.of(STOPS_MAP.get(id).getIndex()) + id -> IntStream.of(STOPS_MAP.get(id).getIndex()), + null ); } } diff --git a/doc/user/Configuration.md b/doc/user/Configuration.md index bca974f8617..f48e7813bee 100644 --- a/doc/user/Configuration.md +++ b/doc/user/Configuration.md @@ -228,6 +228,7 @@ Here is a list of all features which can be toggled on/off and their default val | `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. | ✓️ | | | `ExtraTransferLegOnSameStop` | Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated. | | | | `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/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index ea3d0d12c74..6cf450836dc 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/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 | From 8f7aabbacea61e7e4fdab5e29788c4cb7a08ada5 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 14 Oct 2024 16:01:12 +0200 Subject: [PATCH 086/121] fix release script --- .circleci/release | 8 +++-- .../src/main/java/EnturUpdatePomVersion.java | 29 ++++++++++++++----- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/.circleci/release b/.circleci/release index 9cac0d97958..3f83e4df7b1 100755 --- a/.circleci/release +++ b/.circleci/release @@ -4,6 +4,7 @@ set -euo pipefail GIT_REMOTE_REPO="`git remote -v | grep "entur/OpenTripPlanner" | grep "push" | awk '{print $1;}'`" MASTER_BRANCH=otp2_entur_develop +APPLICATION_MODULE=application TAGS_FILE=target/git-entur-tags.txt DRY_RUN="" @@ -33,15 +34,16 @@ 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}" + mkdir target 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 + javac -d ${APPLICATION_MODULE}/target/classes src/main/java/EnturUpdatePomVersion.java - VERSION="`java -cp target/classes EnturUpdatePomVersion ${TAGS_FILE}`" + VERSION="`java -cp ${APPLICATION_MODULE}/target/classes EnturUpdatePomVersion ${TAGS_FILE}`" echo "" echo "New version set: ${VERSION}" echo "" @@ -53,7 +55,7 @@ function setPomVersion() { ## 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 + git commit -m "Version ${VERSION}" **/pom.xml } function mergeInOldReleaseWithNoChanges() { diff --git a/application/src/main/java/EnturUpdatePomVersion.java b/application/src/main/java/EnturUpdatePomVersion.java index 97bd72b3c6e..424b71f0ff2 100644 --- a/application/src/main/java/EnturUpdatePomVersion.java +++ b/application/src/main/java/EnturUpdatePomVersion.java @@ -3,16 +3,18 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.regex.Pattern; +import java.util.stream.Stream; 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 static final String POM_FILE_NAME = "pom.xml"; private final List tags = new ArrayList<>(); private final List pomFile = new ArrayList<>(); @@ -45,11 +47,13 @@ private EnturUpdatePomVersion withArgs(String[] args) throws IOException { } private void run() throws IOException { - readAndReplaceVersion(); - replacePomFile(); + for (Path pom : listPomFiles()) { + readAndReplaceVersion(pom); + replacePomFile(pom); + } } - public void readAndReplaceVersion() throws IOException { + public void readAndReplaceVersion(Path pom) throws IOException { var pattern = Pattern.compile( "(\\s*)(\\d+.\\d+.\\d+)" + VERSION_SEP + @@ -60,7 +64,7 @@ public void readAndReplaceVersion() throws IOException { boolean found = false; int i = 0; - for (String line : Files.readAllLines(POM_FILE, UTF_8)) { + for (String line : Files.readAllLines(pom, UTF_8)) { // Look for the version in the 25 first lines if (!found) { var m = pattern.matcher(line); @@ -86,9 +90,9 @@ public void readAndReplaceVersion() throws IOException { } } - public void replacePomFile() throws IOException { - Files.delete(POM_FILE); - Files.write(POM_FILE, pomFile, UTF_8); + public void replacePomFile(Path pom) throws IOException { + Files.delete(pom); + Files.write(pom, pomFile, UTF_8); } private static void printHelp() { @@ -132,4 +136,13 @@ private static Collection readTagsFromFile(String arg) throws IOExceptio } return tags; } + + private List listPomFiles() throws IOException { + try (Stream stream = Files.walk(Paths.get(""))) { + return stream + .filter(Files::isRegularFile) + .filter(path -> path.getFileName().toString().equals(POM_FILE_NAME)) + .toList(); + } + } } From 3ce0b4de8e7b21a874d5a66460a08925fb7e8fde Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 14 Oct 2024 16:03:16 +0200 Subject: [PATCH 087/121] fix release script --- .circleci/release | 6 +++--- application/src/main/java/EnturUpdatePomVersion.java | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.circleci/release b/.circleci/release index 3f83e4df7b1..a1bca4677be 100755 --- a/.circleci/release +++ b/.circleci/release @@ -34,14 +34,14 @@ 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}" - mkdir target + mkdir -p target git tag -l | grep entur > ${TAGS_FILE} } function setPomVersion() { echo "" echo "Update pom.xml with new version" - javac -d ${APPLICATION_MODULE}/target/classes src/main/java/EnturUpdatePomVersion.java + javac -d ${APPLICATION_MODULE}/target/classes ${APPLICATION_MODULE}/src/main/java/EnturUpdatePomVersion.java VERSION="`java -cp ${APPLICATION_MODULE}/target/classes EnturUpdatePomVersion ${TAGS_FILE}`" echo "" @@ -55,7 +55,7 @@ function setPomVersion() { ## 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 + git commit -m "Version ${VERSION}" pom.xml application/pom.xml } function mergeInOldReleaseWithNoChanges() { diff --git a/application/src/main/java/EnturUpdatePomVersion.java b/application/src/main/java/EnturUpdatePomVersion.java index 424b71f0ff2..cca6cd4925d 100644 --- a/application/src/main/java/EnturUpdatePomVersion.java +++ b/application/src/main/java/EnturUpdatePomVersion.java @@ -20,6 +20,7 @@ public class EnturUpdatePomVersion { private final List pomFile = new ArrayList<>(); private String mainVersion; private int versionNumber = 0; + private String newVersion; public static void main(String[] args) { try { @@ -51,9 +52,11 @@ private void run() throws IOException { readAndReplaceVersion(pom); replacePomFile(pom); } + System.out.println(newVersion); } public void readAndReplaceVersion(Path pom) throws IOException { + pomFile.clear(); var pattern = Pattern.compile( "(\\s*)(\\d+.\\d+.\\d+)" + VERSION_SEP + @@ -70,9 +73,8 @@ public void readAndReplaceVersion(Path pom) throws IOException { var m = pattern.matcher(line); if (m.matches()) { mainVersion = m.group(2); - String newVersion = mainVersion + VERSION_SEP + ENTUR_PREFIX + resolveVersionNumber(); + newVersion = mainVersion + VERSION_SEP + ENTUR_PREFIX + resolveVersionNumber(); line = m.group(1) + newVersion + m.group(4); - System.out.println(newVersion); found = true; } if (++i == 25) { From e5290389390fc2e70f65d5c606223c8f00040923 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 14 Oct 2024 16:46:24 +0200 Subject: [PATCH 088/121] Version 2.7.0-entur-7 --- application/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index 4a228829c70..04a17fcee9e 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-SNAPSHOT + 2.7.0-entur-7 otp OpenTripPlanner - Application diff --git a/pom.xml b/pom.xml index e9b184880d3..5db9f3f1e52 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.opentripplanner otp-root - 2.7.0-SNAPSHOT + 2.7.0-entur-7 pom OpenTripPlanner - Root From 452dc736d5b5f5dd7a1552349d8cd8a22c4e79f9 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 15 Oct 2024 10:14:17 +0200 Subject: [PATCH 089/121] Update release script for Maven mdule support --- .circleci/release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/release b/.circleci/release index a1bca4677be..40f7e5af3c7 100755 --- a/.circleci/release +++ b/.circleci/release @@ -55,7 +55,7 @@ function setPomVersion() { ## 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 application/pom.xml + git commit -m "Version ${VERSION}" "**pom.xml" } function mergeInOldReleaseWithNoChanges() { From c1857d5486cb1dc05fe75a260f2dbfc350257a2c Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 15 Oct 2024 10:17:20 +0200 Subject: [PATCH 090/121] Version 2.7.0-entur-8 --- application/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index 04a17fcee9e..17a9cb91561 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-entur-7 + 2.7.0-entur-8 otp OpenTripPlanner - Application diff --git a/pom.xml b/pom.xml index 5db9f3f1e52..d8f77afb748 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.opentripplanner otp-root - 2.7.0-entur-7 + 2.7.0-entur-8 pom OpenTripPlanner - Root From d7615312715e5afc59c443d60f53f7b139e0e017 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 15 Oct 2024 20:24:11 +0200 Subject: [PATCH 091/121] Fix: Regression error in passThroughPoints in the Transmodel API The list of ids inside passThroughPoints is allowed to be empty or null. We cannot change this - that would be a breaking change. So, when the via search enforced this, the API was not backward compatible anymore. This commit reverts the behavior and just ignores the passThroughPoints if the list of ids is null or empty. This bug was introduced in PR #6084. --- .../mapping/TripViaLocationMapper.java | 23 +++- .../model/plan/ViaLocationInputType.java | 9 +- .../request/via/PassThroughViaLocation.java | 3 +- .../api/request/via/VisitViaLocation.java | 3 +- .../apis/transmodel/schema.graphql | 4 +- .../mapping/TripViaLocationMapperTest.java | 125 ++++++++++++++++-- 6 files changed, 144 insertions(+), 23 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripViaLocationMapper.java b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripViaLocationMapper.java index 50845aecef7..5f572c89da0 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripViaLocationMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripViaLocationMapper.java @@ -6,6 +6,8 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Objects; +import javax.annotation.Nullable; import org.opentripplanner.apis.transmodel.model.plan.TripQuery; import org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType; import org.opentripplanner.apis.transmodel.support.OneOfInputValidator; @@ -30,6 +32,7 @@ static List toLegacyPassThroughLocations( return passThroughPoints .stream() .map(TripViaLocationMapper::mapLegacyPassThroughViaLocation) + .filter(Objects::nonNull) .collect(toList()); } @@ -65,6 +68,13 @@ private static PassThroughViaLocation mapPassThroughViaLocation(Map mapStopLocationIds(Map map) { var c = (Collection) map.get(ViaLocationInputType.FIELD_STOP_LOCATION_IDS); + + // When coordinates are added, we need to accept null here... + if (c == null) { + throw new IllegalArgumentException( + "'" + ViaLocationInputType.FIELD_STOP_LOCATION_IDS + "' is not set!" + ); + } return c.stream().map(TransitIdMapper::mapIDToDomain).toList(); } @@ -72,12 +82,17 @@ private static List mapStopLocationIds(Map map) { * @deprecated Legacy passThrough, use via instead */ @Deprecated + @Nullable private static ViaLocation mapLegacyPassThroughViaLocation(Map inputMap) { final String name = (String) inputMap.get("name"); - final List stopLocationIds = - ((List) inputMap.get("placeIds")).stream() - .map(TransitIdMapper::mapIDToDomain) - .toList(); + List placeIds = (List) inputMap.get("placeIds"); + if (placeIds == null || placeIds.isEmpty()) { + return null; + } + final List stopLocationIds = placeIds + .stream() + .map(TransitIdMapper::mapIDToDomain) + .toList(); return new PassThroughViaLocation(name, stopLocationIds); } } diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java index a808d934eed..ef13f8db18e 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java @@ -5,6 +5,7 @@ import graphql.language.StringValue; import graphql.schema.GraphQLInputObjectType; +import graphql.schema.GraphQLInputType; import graphql.schema.GraphQLList; import graphql.schema.GraphQLNonNull; import java.time.Duration; @@ -84,7 +85,7 @@ be accepted. To visit a coordinate, the traveler must walk(bike or drive) to the b .name(FIELD_STOP_LOCATION_IDS) .description(DOC_STOP_LOCATION_IDS) - .type(gqlListOfNonNullStrings()) + .type(requiredListOfNonNullStrings()) ) /* TODO: Add support for coordinates @@ -101,7 +102,7 @@ be accepted. To visit a coordinate, the traveler must walk(bike or drive) to the b .name(FIELD_STOP_LOCATION_IDS) .description(DOC_STOP_LOCATION_IDS) - .type(gqlListOfNonNullStrings()) + .type(requiredListOfNonNullStrings()) ) .build(); @@ -119,7 +120,7 @@ be accepted. To visit a coordinate, the traveler must walk(bike or drive) to the ) .build(); - private static GraphQLList gqlListOfNonNullStrings() { - return new GraphQLList(new GraphQLNonNull(GraphQLString)); + private static GraphQLInputType requiredListOfNonNullStrings() { + return new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLString))); } } diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/via/PassThroughViaLocation.java b/application/src/main/java/org/opentripplanner/routing/api/request/via/PassThroughViaLocation.java index 1116031cec6..aa75a634004 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/via/PassThroughViaLocation.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/via/PassThroughViaLocation.java @@ -16,7 +16,8 @@ public PassThroughViaLocation(@Nullable String label, Collection s super(label, stopLocationIds); if (stopLocationIds.isEmpty()) { throw new IllegalArgumentException( - "A pass through via location must have at least one stop location. Label: " + label + "A pass through via location must have at least one stop location." + + (label == null ? "" : " Label: " + label) ); } } diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/via/VisitViaLocation.java b/application/src/main/java/org/opentripplanner/routing/api/request/via/VisitViaLocation.java index 2b59a749a1d..096fbfabc0b 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/via/VisitViaLocation.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/via/VisitViaLocation.java @@ -41,7 +41,8 @@ public VisitViaLocation( if (stopLocationIds().isEmpty() && coordinates().isEmpty()) { throw new IllegalArgumentException( - "A via location must have at least one stop location or a coordinate. Label: " + label + "A via location must have at least one stop location or a coordinate." + + (label == null ? "" : " Label: " + label) ); } } diff --git a/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index 9aa743fd42d..2f3aef2d9a2 100644 --- a/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -2204,7 +2204,7 @@ input TripPassThroughViaLocationInput { stop place or a group of stop places. It is enough to visit ONE of the locations listed. """ - stopLocationIds: [String!] + stopLocationIds: [String!]! } """ @@ -2239,7 +2239,7 @@ input TripVisitViaLocationInput { stop place or a group of stop places. It is enough to visit ONE of the locations listed. """ - stopLocationIds: [String!] + stopLocationIds: [String!]! } "Input format for specifying a location through either a place reference (id), coordinates or both. If both place and coordinates are provided the place ref will be used if found, coordinates will only be used if place is not known. The location also contain information about the minimum and maximum time the user is willing to stay at the via location." diff --git a/application/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripViaLocationMapperTest.java b/application/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripViaLocationMapperTest.java index 49304ee1e05..9c33a149b49 100644 --- a/application/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripViaLocationMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripViaLocationMapperTest.java @@ -21,10 +21,15 @@ class TripViaLocationMapperTest { - public static final String LABEL = "TestLabel"; - public static final Duration MIN_WAIT_TIME = Duration.ofMinutes(5); - public static final List LIST_IDS_INPUT = List.of("F:ID1", "F:ID2"); - public static final String EXPECTED_IDS_AS_STRING = "[F:ID1, F:ID2]"; + private static final String LABEL = "TestLabel"; + private static final Duration MIN_WAIT_TIME = Duration.ofMinutes(5); + private static final List LIST_IDS_INPUT = List.of("F:ID1", "F:ID2"); + private static final String EXPECTED_IDS_AS_STRING = "[F:ID1, F:ID2]"; + private static final String REASON_EMPTY_IDS_ALLOWED_PASS_THROUGH = + """ + Unfortunately the 'placeIds' is not required. Making it required would be a breaking change, + so wee just ignore it." + """; @BeforeEach void setup() { @@ -52,10 +57,7 @@ void testMapToVisitViaLocations() { @Test void testMapToVisitViaLocationsWithBareMinimum() { - Map input = Map.of( - FIELD_VISIT, - Map.of(FIELD_STOP_LOCATION_IDS, List.of("F:1")) - ); + Map input = mapOf(FIELD_VISIT, mapOf(FIELD_STOP_LOCATION_IDS, List.of("F:1"))); var result = TripViaLocationMapper.mapToViaLocations(List.of(input)); var via = result.getFirst(); @@ -66,9 +68,32 @@ void testMapToVisitViaLocationsWithBareMinimum() { assertFalse(via.isPassThroughLocation()); } + @Test + void testMapToVisitViaLocationsWithoutIds() { + Map input = mapOf(FIELD_VISIT, mapOf(FIELD_STOP_LOCATION_IDS, null)); + var ex = assertThrows( + IllegalArgumentException.class, + () -> TripViaLocationMapper.mapToViaLocations(List.of(input)) + ); + assertEquals("'stopLocationIds' is not set!", ex.getMessage()); + } + + @Test + void testMapToVisitViaLocationsWithAnEmptyListOfIds() { + Map input = mapOf(FIELD_VISIT, mapOf(FIELD_STOP_LOCATION_IDS, List.of())); + var ex = assertThrows( + IllegalArgumentException.class, + () -> TripViaLocationMapper.mapToViaLocations(List.of(input)) + ); + assertEquals( + "A via location must have at least one stop location or a coordinate.", + ex.getMessage() + ); + } + @Test void tetMapToPassThrough() { - Map input = Map.of(FIELD_PASS_THROUGH, passThroughInput(LABEL, LIST_IDS_INPUT)); + Map input = mapOf(FIELD_PASS_THROUGH, passThroughInput(LABEL, LIST_IDS_INPUT)); var result = TripViaLocationMapper.mapToViaLocations(List.of(input)); var via = result.getFirst(); @@ -83,9 +108,9 @@ void tetMapToPassThrough() { @Test void tetMapToPassThroughWithBareMinimum() { - Map input = Map.of( + Map input = mapOf( FIELD_PASS_THROUGH, - Map.of(FIELD_STOP_LOCATION_IDS, List.of("F:1")) + mapOf(FIELD_STOP_LOCATION_IDS, List.of("F:1")) ); var result = TripViaLocationMapper.mapToViaLocations(List.of(input)); var via = result.getFirst(); @@ -95,6 +120,32 @@ void tetMapToPassThroughWithBareMinimum() { assertTrue(via.isPassThroughLocation()); } + @Test + void tetMapToPassThroughWithoutIds() { + Map input = mapOf(FIELD_PASS_THROUGH, mapOf(FIELD_STOP_LOCATION_IDS, null)); + var ex = assertThrows( + IllegalArgumentException.class, + () -> TripViaLocationMapper.mapToViaLocations(List.of(input)) + ); + assertEquals("'stopLocationIds' is not set!", ex.getMessage()); + } + + @Test + void tetMapToPassThroughWithAnEmptyListOfIds() { + Map input = mapOf( + FIELD_PASS_THROUGH, + mapOf(FIELD_STOP_LOCATION_IDS, List.of()) + ); + var ex = assertThrows( + IllegalArgumentException.class, + () -> TripViaLocationMapper.mapToViaLocations(List.of(input)) + ); + assertEquals( + "A pass through via location must have at least one stop location.", + ex.getMessage() + ); + } + @Test void testOneOf() { Map input = Map.ofEntries( @@ -121,6 +172,48 @@ void testOneOf() { ); } + @Test + void testToLegacyPassThroughLocations() { + Map input = Map.of("name", LABEL, "placeIds", LIST_IDS_INPUT); + var result = TripViaLocationMapper.toLegacyPassThroughLocations(List.of(input)); + var via = result.getFirst(); + + assertEquals(LABEL, via.label()); + assertEquals(EXPECTED_IDS_AS_STRING, via.stopLocationIds().toString()); + assertTrue(via.isPassThroughLocation()); + assertEquals( + "PassThroughViaLocation{label: TestLabel, stopLocationIds: [F:ID1, F:ID2]}", + via.toString() + ); + } + + @Test + void testToLegacyPassThroughLocationsWithBareMinimum() { + Map input = mapOf("placeIds", LIST_IDS_INPUT); + var result = TripViaLocationMapper.toLegacyPassThroughLocations(List.of(input)); + var via = result.getFirst(); + + assertNull(via.label()); + assertEquals(EXPECTED_IDS_AS_STRING, via.stopLocationIds().toString()); + assertTrue(via.isPassThroughLocation()); + assertEquals("PassThroughViaLocation{stopLocationIds: [F:ID1, F:ID2]}", via.toString()); + } + + @Test + void testToLegacyPassThroughLocationsWithoutIds() { + var result = TripViaLocationMapper.toLegacyPassThroughLocations( + List.of(mapOf("placeIds", null)) + ); + assertTrue(result.isEmpty(), REASON_EMPTY_IDS_ALLOWED_PASS_THROUGH); + } + + @Test + void testToLegacyPassThroughLocationsWithEmptyList() { + Map input = Map.ofEntries(entry("name", LABEL), entry("placeIds", List.of())); + var result = TripViaLocationMapper.toLegacyPassThroughLocations(List.of(input)); + assertTrue(result.isEmpty(), REASON_EMPTY_IDS_ALLOWED_PASS_THROUGH); + } + private Map visitInput(String label, Duration minWaitTime, List ids) { var map = new HashMap(); if (label != null) { @@ -138,4 +231,14 @@ private Map visitInput(String label, Duration minWaitTime, List< private Map passThroughInput(String label, List ids) { return visitInput(label, null, ids); } + + /** + * Create a new HashMap with the {@code key} and {@code value}, the value may be {@code null}. + * The {@link Map#of(Object, Object)} does not support {@code null} values. + */ + private static Map mapOf(String key, Object value) { + var map = new HashMap(); + map.put(key, value); + return map; + } } From c134e30f1d71e41675f100e02d9a8caf064e8b21 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 092/121] CircleCI config, release scripts, and pom.xml updates. --- .circleci/config.yml | 52 ++++ .circleci/prepare_release | 235 ++++++++++++++++++ .circleci/release | 86 +++++++ .circleci/update_deployment_config | 46 ++++ .gitignore | 1 + .../src/main/java/EnturUpdatePomVersion.java | 135 ++++++++++ pom.xml | 12 +- 7 files changed, 561 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 application/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..40f7e5af3c7 --- /dev/null +++ b/.circleci/release @@ -0,0 +1,86 @@ +#!/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 +APPLICATION_MODULE=application +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}" + mkdir -p target + git tag -l | grep entur > ${TAGS_FILE} +} + +function setPomVersion() { + echo "" + echo "Update pom.xml with new version" + javac -d ${APPLICATION_MODULE}/target/classes ${APPLICATION_MODULE}/src/main/java/EnturUpdatePomVersion.java + + VERSION="`java -cp ${APPLICATION_MODULE}/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 6fac28d2178..4df8f460dbe 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .project .pydevproject .settings +.prepare_release.tmp .sonar .nvmrc *~ diff --git a/application/src/main/java/EnturUpdatePomVersion.java b/application/src/main/java/EnturUpdatePomVersion.java new file mode 100644 index 00000000000..97bd72b3c6e --- /dev/null +++ b/application/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; + } +} diff --git a/pom.xml b/pom.xml index 6737acd89cf..8bdb2f9c75f 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ - 164 + EN-0076 32.0 2.52 @@ -86,11 +86,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 + From 4a24a38257c9b7f2b9b816e9f2932c61f9ec22c2 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 19 Dec 2023 21:26:49 +0100 Subject: [PATCH 093/121] =?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 --- .../resources/RequestToPreferencesMapper.java | 1 + .../restapi/resources/RoutingResource.java | 3 + .../ConcurrentCompositeWorker.java | 40 +++++ .../sorlandsbanen/EnturHackSorlandsBanen.java | 137 ++++++++++++++++++ .../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 + .../raptor/configure/RaptorConfig.java | 43 +++++- .../raptor/service/HeuristicSearchTask.java | 3 +- .../service/RangeRaptorDynamicSearch.java | 10 +- .../service/ViaRangeRaptorDynamicSearch.java | 9 +- .../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 | 30 +++- .../mappers/RaptorRequestMapperTest.java | 3 +- doc/user/Configuration.md | 1 + doc/user/RouteRequest.md | 1 + 27 files changed, 524 insertions(+), 39 deletions(-) create mode 100644 application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java create mode 100644 application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java create mode 100644 application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java create mode 100644 application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java diff --git a/application/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java b/application/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java index 51b2fae86b5..a2a3d079d13 100644 --- a/application/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java +++ b/application/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/application/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java b/application/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java index 3ae029fdb2d..b4912aacb1d 100644 --- a/application/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java +++ b/application/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java @@ -203,6 +203,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/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java new file mode 100644 index 00000000000..57f3cbdbdaa --- /dev/null +++ b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java @@ -0,0 +1,40 @@ +package org.opentripplanner.ext.sorlandsbanen; + +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.rangeraptor.internalapi.RaptorRouter; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; + +class ConcurrentCompositeWorker implements RaptorRouter { + + private final RaptorRouter mainWorker; + private final RaptorRouter alternativeWorker; + + ConcurrentCompositeWorker(RaptorRouter mainWorker, RaptorRouter alternativeWorker) { + this.mainWorker = mainWorker; + this.alternativeWorker = alternativeWorker; + } + + @Override + public RaptorRouterResult 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/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java new file mode 100644 index 00000000000..68f6188b965 --- /dev/null +++ b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java @@ -0,0 +1,137 @@ +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.rangeraptor.internalapi.RaptorRouter; +import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; +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(double extraSearchCoachReluctance) { + return extraSearchCoachReluctance > 0.1; + } + + public static RaptorRouter router( + RaptorRequest mcRequest, + RaptorTransitDataProvider transitData, + Function, RaptorRouter> raptorRouterFactory + ) { + //noinspection unchecked + RaptorTransitDataProvider altTransitData = (RaptorTransitDataProvider) ( + (RaptorRoutingRequestTransitData) transitData + ).enturHackSorlandsbanen(mapFactors(mcRequest.extraSearchCoachReluctance)); + + return new ConcurrentCompositeWorker<>( + raptorRouterFactory.apply(transitData), + raptorRouterFactory.apply(altTransitData) + ); + } + + 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/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java new file mode 100644 index 00000000000..94267b3e7c2 --- /dev/null +++ b/application/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/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java new file mode 100644 index 00000000000..28ccfd5ff13 --- /dev/null +++ b/application/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.RaptorRouterResult; +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 RaptorRouterResult { + + private static final Logger LOG = LoggerFactory.getLogger(RaptorWorkerResultComposite.class); + + private final RaptorRouterResult mainResult; + private final RaptorRouterResult alternativeResult; + + public RaptorWorkerResultComposite( + RaptorRouterResult mainResult, + RaptorRouterResult 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/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java index 5b1bbd84373..27ea61568e0 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java @@ -632,6 +632,14 @@ Normally this is when the search is performed (now), plus a small grace period t ) .build() ) + .argument( + GraphQLArgument + .newArgument() + .name("extraSearchCoachReluctance") + .description("FOR TESTING ONLY") + .type(Scalars.GraphQLFloat) + .build() + ) .dataFetcher(environment -> new TransmodelGraphQLPlanner().plan(environment)) .build(); } diff --git a/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 324f5397673..60c35e6fd1a 100644 --- a/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -38,6 +38,7 @@ public enum OTPFeature { "Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated." ), 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/application/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java b/application/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java index 010bff4bc08..c53f0f90ae5 100644 --- a/application/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java +++ b/application/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/application/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java b/application/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java index ed2aab3de20..7bb8b538843 100644 --- a/application/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java +++ b/application/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/application/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java b/application/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java index cc488448304..20bab1e0c8c 100644 --- a/application/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java +++ b/application/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java @@ -3,6 +3,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.annotation.Nullable; +import org.opentripplanner.ext.sorlandsbanen.EnturHackSorlandsBanen; +import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.concurrent.OtpRequestThreadFactory; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.request.RaptorRequest; @@ -15,6 +17,7 @@ import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; import org.opentripplanner.raptor.rangeraptor.internalapi.PassThroughPointsService; import org.opentripplanner.raptor.rangeraptor.internalapi.RangeRaptorWorker; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouter; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerState; import org.opentripplanner.raptor.rangeraptor.internalapi.RoutingStrategy; @@ -58,7 +61,7 @@ public SearchContext context(RaptorTransitDataProvider transit, RaptorRequ return SearchContext.of(request, tuningParameters, transit, acceptC2AtDestination).build(); } - public RangeRaptor createRangeRaptorWithStdWorker( + public RaptorRouter createRangeRaptorWithStdWorker( RaptorTransitDataProvider transitData, RaptorRequest request ) { @@ -66,11 +69,12 @@ public RangeRaptor createRangeRaptorWithStdWorker( var stdConfig = new StdRangeRaptorConfig<>(context); return createRangeRaptor( context, + context.transit(), createWorker(context.legs().getFirst(), stdConfig.state(), stdConfig.strategy()) ); } - public RangeRaptor createRangeRaptorWithMcWorker( + public RaptorRouter createRangeRaptorWithMcWorker( RaptorTransitDataProvider transitData, RaptorRequest request, Heuristics heuristics @@ -93,11 +97,10 @@ public RangeRaptor createRangeRaptorWithMcWorker( var c = new McRangeRaptorConfig<>(leg, passThroughPointsService).withHeuristics(heuristics); worker = createWorker(leg, c.state(), c.strategy()); } - - return createRangeRaptor(context, worker); + return createRaptorRouter(request, context, worker); } - public RangeRaptor createRangeRaptorWithHeuristicSearch( + public RaptorRouter createRangeRaptorWithHeuristicSearch( RaptorTransitDataProvider transitData, RaptorRequest request ) { @@ -137,7 +140,7 @@ private static PassThroughPointsService createPassThroughPointsService(RaptorReq return McRangeRaptorConfig.passThroughPointsService(request.multiCriteria()); } - private RangeRaptorWorker createWorker( + public RangeRaptorWorker createWorker( SearchContextViaLeg ctxLeg, RaptorWorkerState workerState, RoutingStrategy routingStrategy @@ -156,10 +159,34 @@ private RangeRaptorWorker createWorker( ); } - private RangeRaptor createRangeRaptor(SearchContext ctx, RangeRaptorWorker worker) { + // HACK SØRLANDSBANEN + private RaptorRouter createRaptorRouter( + RaptorRequest request, + SearchContext ctx, + RangeRaptorWorker worker + ) { + if ( + OTPFeature.HackSorlandsbanen.isOn() && + EnturHackSorlandsBanen.match(request.extraSearchCoachReluctance) + ) { + return EnturHackSorlandsBanen.router( + request, + ctx.transit(), + td -> createRangeRaptor(ctx, td, worker) + ); + } else { + return createRangeRaptor(ctx, ctx.transit(), worker); + } + } + + private RaptorRouter createRangeRaptor( + SearchContext ctx, + RaptorTransitDataProvider data, + RangeRaptorWorker worker + ) { return new RangeRaptor<>( worker, - ctx.transit(), + data, ctx.legs().getFirst().accessPaths(), ctx.roundTracker(), ctx.calculator(), diff --git a/application/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java b/application/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java index 7c0158d2192..0de84283af8 100644 --- a/application/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java +++ b/application/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java @@ -10,6 +10,7 @@ import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.raptor.rangeraptor.RangeRaptor; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouter; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; import org.slf4j.Logger; @@ -33,7 +34,7 @@ public class HeuristicSearchTask { private final RaptorTransitDataProvider transitData; private boolean run = false; - private RangeRaptor search = null; + private RaptorRouter search = null; private RaptorRequest originalRequest; private RaptorRequest heuristicRequest; private RaptorRouterResult result = null; diff --git a/application/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java b/application/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java index 5353804f414..3af049a55ec 100644 --- a/application/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java +++ b/application/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java @@ -19,8 +19,8 @@ import org.opentripplanner.raptor.api.request.SearchParamsBuilder; import org.opentripplanner.raptor.api.response.RaptorResponse; import org.opentripplanner.raptor.configure.RaptorConfig; -import org.opentripplanner.raptor.rangeraptor.RangeRaptor; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouter; import org.opentripplanner.raptor.rangeraptor.transit.RaptorSearchWindowCalculator; import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; import org.slf4j.Logger; @@ -129,18 +129,18 @@ private void runHeuristics() { private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request) { LOG.debug("Main request: {}", request); - RangeRaptor rangeRaptorRouter; + RaptorRouter raptorRouter; // Create worker if (request.profile().is(MULTI_CRITERIA)) { - rangeRaptorRouter = + raptorRouter = config.createRangeRaptorWithMcWorker(transitData, request, getDestinationHeuristics()); } else { - rangeRaptorRouter = config.createRangeRaptorWithStdWorker(transitData, request); + raptorRouter = config.createRangeRaptorWithStdWorker(transitData, request); } // Route - var result = rangeRaptorRouter.route(); + var result = raptorRouter.route(); // create and return response return new RaptorResponse<>( diff --git a/application/src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java b/application/src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java index 4476e40464f..96a55793d6a 100644 --- a/application/src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java +++ b/application/src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java @@ -21,6 +21,7 @@ import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.raptor.rangeraptor.RangeRaptor; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouter; import org.opentripplanner.raptor.rangeraptor.transit.RaptorSearchWindowCalculator; import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; import org.slf4j.Logger; @@ -129,18 +130,18 @@ private void runHeuristics() { private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request) { LOG.debug("Main request: {}", request); - RangeRaptor rangeRaptorRouter; + RaptorRouter raptorRouter; // Create worker if (request.profile().is(MULTI_CRITERIA)) { - rangeRaptorRouter = + raptorRouter = config.createRangeRaptorWithMcWorker(transitData, request, getDestinationHeuristics()); } else { - rangeRaptorRouter = config.createRangeRaptorWithStdWorker(transitData, request); + raptorRouter = config.createRangeRaptorWithStdWorker(transitData, request); } // Route - var result = rangeRaptorRouter.route(); + var result = raptorRouter.route(); // create and return response return new RaptorResponse<>( diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index e72d8ee1427..7afb4eaf6ff 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -138,7 +138,8 @@ private TransitRouterResult route() { accessEgresses.getAccesses(), accessEgresses.getEgresses(), serverContext.meterRegistry(), - this::listStopIndexes + this::listStopIndexes, + transitLayer ); // Route transit diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java index 43faa3f0c40..c3bde3e1cc7 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java +++ b/application/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/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java index fba2a7cfe38..8a14c27566d 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java +++ b/application/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/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java index 152f199248c..4c66b5b452e 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java +++ b/application/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/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index 1074724a90c..12c9cad2bfc 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -8,6 +8,7 @@ import java.util.Collection; import java.util.List; import java.util.function.Predicate; +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; @@ -22,6 +23,8 @@ import org.opentripplanner.raptor.api.request.RaptorViaLocation; 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.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; @@ -39,6 +42,8 @@ public class RaptorRequestMapper { private final MeterRegistry meterRegistry; private final LookupStopIndexCallback lookUpStopIndex; + private final TransitLayer transitLayer; + private RaptorRequestMapper( RouteRequest request, boolean isMultiThreaded, @@ -46,7 +51,8 @@ private RaptorRequestMapper( Collection egressPaths, long transitSearchTimeZeroEpocSecond, MeterRegistry meterRegistry, - LookupStopIndexCallback lookUpStopIndex + LookupStopIndexCallback lookUpStopIndex, + TransitLayer transitLayer ) { this.request = request; this.isMultiThreadedEnbled = isMultiThreaded; @@ -55,6 +61,7 @@ private RaptorRequestMapper( this.transitSearchTimeZeroEpocSecond = transitSearchTimeZeroEpocSecond; this.meterRegistry = meterRegistry; this.lookUpStopIndex = lookUpStopIndex; + this.transitLayer = transitLayer; } public static RaptorRequest mapRequest( @@ -64,7 +71,8 @@ public static RaptorRequest mapRequest( Collection accessPaths, Collection egressPaths, MeterRegistry meterRegistry, - LookupStopIndexCallback lookUpStopIndex + LookupStopIndexCallback lookUpStopIndex, + TransitLayer transitLayer ) { return new RaptorRequestMapper( request, @@ -73,7 +81,8 @@ public static RaptorRequest mapRequest( egressPaths, transitSearchTimeZero.toEpochSecond(), meterRegistry, - lookUpStopIndex + lookUpStopIndex, + transitLayer ) .doMap(); } @@ -195,7 +204,7 @@ private RaptorRequest doMap() { ) ); } - return builder.build(); + return EnturHackSorlandsBanen.enableHack(builder.build(), request, transitLayer); } private boolean hasPassThroughOnly() { diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java index 0182598ca35..12910d277b5 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java +++ b/application/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.Nullable; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.time.ServiceDateUtils; @@ -26,6 +27,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; @@ -242,4 +245,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/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java b/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java index 78f30277e72..94a4a458915 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java +++ b/application/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/application/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index dc91388458a..72fc4c2c6f9 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -332,13 +332,14 @@ A related parameter (transferSlack) also helps avoid missed connections when the } // 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)`. @@ -348,10 +349,17 @@ A related parameter (transferSlack) also helps avoid missed connections when the 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/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index 9aa743fd42d..78dae18cd07 100644 --- a/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -805,6 +805,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/application/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java b/application/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java index 657e060c30b..64c65a4c60d 100644 --- a/application/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java +++ b/application/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java @@ -44,6 +44,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. @@ -224,7 +227,8 @@ void enforcePackageDependenciesInConfigure() { RR_CONTEXT, RR_STD_CONFIGURE, RR_MC_CONFIGURE, - FRAMEWORK_UTILS + FRAMEWORK_UTILS, + EXT_SORLANDSBANAN_HACK ) .verify(); } @@ -238,3 +242,27 @@ void enforceNoCyclicDependencies() { .check(ArchComponent.OTP_CLASSES); } } +/* +[ERROR] RaptorArchitectureTest.enforcePackageDependenciesInConfigure:233 Architecture Violation [Priority: MEDIUM] +- Rule 'classes that reside in a package 'org.opentripplanner.raptor.configure' and do not implement dagger.internal.Factory should only depend on classes that reside in any package [ +'org.opentripplanner.raptor.rangeraptor.internalapi', +'org.opentripplanner.raptor.rangeraptor', +'org.opentripplanner.raptor.rangeraptor.multicriteria.configure', +'org.opentripplanner.raptor.api..', +'org.opentripplanner.framework.i18n', 'org.opentripplanner.raptor.rangeraptor.context', +'org.opentripplanner.framework.doc', 'org.opentripplanner.raptor.rangeraptor.transit', +'org.opentripplanner.framework.application', 'org.opentripplanner.raptor.spi', +'org.opentripplanner.framework.logging', 'org.opentripplanner.framework.error', +'org.opentripplanner.framework.concurrent', 'org.opentripplanner.framework.text', +'org.opentripplanner.raptor.rangeraptor.standard.configure', +'org.opentripplanner.framework.lang', 'org.opentripplanner.framework.time', +'org.opentripplanner.framework.tostring', 'java..', 'javax.(*)..', 'jakarta.(*)..', 'org.slf4j', 'org.opentripplanner.raptor.configure', '']' was violated (2 times): +Method calls method in (RaptorConfig.java:170) +Method calls method in (RaptorConfig.java:172) + + +*/ diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java index 87e90e61b2e..22ec1d21023 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java @@ -114,7 +114,8 @@ private static RaptorRequest map(RouteRequest request) { ACCESS, EGRESS, null, - id -> IntStream.of(STOPS_MAP.get(id).getIndex()) + id -> IntStream.of(STOPS_MAP.get(id).getIndex()), + null ); } } diff --git a/doc/user/Configuration.md b/doc/user/Configuration.md index bca974f8617..f48e7813bee 100644 --- a/doc/user/Configuration.md +++ b/doc/user/Configuration.md @@ -228,6 +228,7 @@ Here is a list of all features which can be toggled on/off and their default val | `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. | ✓️ | | | `ExtraTransferLegOnSameStop` | Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated. | | | | `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/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index ea3d0d12c74..6cf450836dc 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/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 | From f726d69a74dc42c08d4ddbb126f396045ae35da9 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Wed, 16 Oct 2024 10:02:24 +0200 Subject: [PATCH 094/121] Update release script for Maven mdule support --- .../src/main/java/EnturUpdatePomVersion.java | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/application/src/main/java/EnturUpdatePomVersion.java b/application/src/main/java/EnturUpdatePomVersion.java index 97bd72b3c6e..cca6cd4925d 100644 --- a/application/src/main/java/EnturUpdatePomVersion.java +++ b/application/src/main/java/EnturUpdatePomVersion.java @@ -3,21 +3,24 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.regex.Pattern; +import java.util.stream.Stream; 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 static final String POM_FILE_NAME = "pom.xml"; private final List tags = new ArrayList<>(); private final List pomFile = new ArrayList<>(); private String mainVersion; private int versionNumber = 0; + private String newVersion; public static void main(String[] args) { try { @@ -45,11 +48,15 @@ private EnturUpdatePomVersion withArgs(String[] args) throws IOException { } private void run() throws IOException { - readAndReplaceVersion(); - replacePomFile(); + for (Path pom : listPomFiles()) { + readAndReplaceVersion(pom); + replacePomFile(pom); + } + System.out.println(newVersion); } - public void readAndReplaceVersion() throws IOException { + public void readAndReplaceVersion(Path pom) throws IOException { + pomFile.clear(); var pattern = Pattern.compile( "(\\s*)(\\d+.\\d+.\\d+)" + VERSION_SEP + @@ -60,15 +67,14 @@ public void readAndReplaceVersion() throws IOException { boolean found = false; int i = 0; - for (String line : Files.readAllLines(POM_FILE, UTF_8)) { + for (String line : Files.readAllLines(pom, 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(); + newVersion = mainVersion + VERSION_SEP + ENTUR_PREFIX + resolveVersionNumber(); line = m.group(1) + newVersion + m.group(4); - System.out.println(newVersion); found = true; } if (++i == 25) { @@ -86,9 +92,9 @@ public void readAndReplaceVersion() throws IOException { } } - public void replacePomFile() throws IOException { - Files.delete(POM_FILE); - Files.write(POM_FILE, pomFile, UTF_8); + public void replacePomFile(Path pom) throws IOException { + Files.delete(pom); + Files.write(pom, pomFile, UTF_8); } private static void printHelp() { @@ -132,4 +138,13 @@ private static Collection readTagsFromFile(String arg) throws IOExceptio } return tags; } + + private List listPomFiles() throws IOException { + try (Stream stream = Files.walk(Paths.get(""))) { + return stream + .filter(Files::isRegularFile) + .filter(path -> path.getFileName().toString().equals(POM_FILE_NAME)) + .toList(); + } + } } From 5116102bc178b68d60d89aff31a952b0aeb3e69f Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Wed, 16 Oct 2024 10:17:00 +0200 Subject: [PATCH 095/121] Version 2.7.0-entur-9 --- application/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index 4a228829c70..b6db9f70ade 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-SNAPSHOT + 2.7.0-entur-9 otp OpenTripPlanner - Application diff --git a/pom.xml b/pom.xml index 8bdb2f9c75f..56281a876e6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.opentripplanner otp-root - 2.7.0-SNAPSHOT + 2.7.0-entur-9 pom OpenTripPlanner - Root From fb1a8366c45ae942d9189a5a45f4d39563a7ddc1 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 096/121] CircleCI config, release scripts, and pom.xml updates. --- .circleci/config.yml | 52 ++++ .circleci/prepare_release | 235 ++++++++++++++++++ .circleci/release | 86 +++++++ .circleci/update_deployment_config | 46 ++++ .gitignore | 1 + .../src/main/java/EnturUpdatePomVersion.java | 150 +++++++++++ pom.xml | 12 +- 7 files changed, 576 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 application/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..40f7e5af3c7 --- /dev/null +++ b/.circleci/release @@ -0,0 +1,86 @@ +#!/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 +APPLICATION_MODULE=application +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}" + mkdir -p target + git tag -l | grep entur > ${TAGS_FILE} +} + +function setPomVersion() { + echo "" + echo "Update pom.xml with new version" + javac -d ${APPLICATION_MODULE}/target/classes ${APPLICATION_MODULE}/src/main/java/EnturUpdatePomVersion.java + + VERSION="`java -cp ${APPLICATION_MODULE}/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 6fac28d2178..4df8f460dbe 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .project .pydevproject .settings +.prepare_release.tmp .sonar .nvmrc *~ diff --git a/application/src/main/java/EnturUpdatePomVersion.java b/application/src/main/java/EnturUpdatePomVersion.java new file mode 100644 index 00000000000..cca6cd4925d --- /dev/null +++ b/application/src/main/java/EnturUpdatePomVersion.java @@ -0,0 +1,150 @@ +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +public class EnturUpdatePomVersion { + + private static final String VERSION_SEP = "-"; + private static final String ENTUR_PREFIX = "entur" + VERSION_SEP; + private static final String POM_FILE_NAME = "pom.xml"; + + private final List tags = new ArrayList<>(); + private final List pomFile = new ArrayList<>(); + private String mainVersion; + private int versionNumber = 0; + private String newVersion; + + 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 { + for (Path pom : listPomFiles()) { + readAndReplaceVersion(pom); + replacePomFile(pom); + } + System.out.println(newVersion); + } + + public void readAndReplaceVersion(Path pom) throws IOException { + pomFile.clear(); + 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, 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); + newVersion = mainVersion + VERSION_SEP + ENTUR_PREFIX + resolveVersionNumber(); + line = m.group(1) + newVersion + m.group(4); + 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(Path pom) throws IOException { + Files.delete(pom); + Files.write(pom, 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; + } + + private List listPomFiles() throws IOException { + try (Stream stream = Files.walk(Paths.get(""))) { + return stream + .filter(Files::isRegularFile) + .filter(path -> path.getFileName().toString().equals(POM_FILE_NAME)) + .toList(); + } + } +} diff --git a/pom.xml b/pom.xml index 0b4dd2ea10c..3edec161b73 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ - 165 + EN-0077 32.0 2.52 @@ -86,11 +86,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 + From 720e3695e1b167ab2ed9a5d45b9a51fea4b9405c Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 21 Oct 2024 13:09:07 +0200 Subject: [PATCH 097/121] Version 2.7.0-entur-10 --- application/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index 49c738a251e..ecf0e0bd41a 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-SNAPSHOT + 2.7.0-entur-10 otp OpenTripPlanner - Application diff --git a/pom.xml b/pom.xml index 3edec161b73..866692ec8ec 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.opentripplanner otp-root - 2.7.0-SNAPSHOT + 2.7.0-entur-10 pom OpenTripPlanner - Root From 49eb22f7a86830747a3d3509fc19b01216b64a38 Mon Sep 17 00:00:00 2001 From: eibakke Date: Mon, 28 Oct 2024 16:09:34 +0100 Subject: [PATCH 098/121] Adds provider / datasource of updates as a dimension to metrics. --- .../model/RealTimeTripUpdate.java | 43 +++++++++++--- .../model/TimetableSnapshot.java | 3 +- .../SiriETGooglePubsubUpdaterConfig.java | 5 ++ .../updaters/SiriETUpdaterConfig.java | 7 ++- .../transit/model/framework/FeedScopedId.java | 2 +- .../updater/siri/AddedTripBuilder.java | 26 +++++--- .../updater/siri/ModifiedTripBuilder.java | 18 ++++-- .../siri/SiriTimetableSnapshotSource.java | 16 ++--- .../updater/siri/TripUpdate.java | 26 +++++--- .../siri/updater/SiriETUpdaterParameters.java | 3 +- .../SiriETGooglePubsubUpdaterParameters.java | 3 +- .../spi/DataValidationExceptionMapper.java | 11 +++- .../updater/spi/UpdateError.java | 20 ++++++- .../updater/spi/UpdateResult.java | 35 ++++++++--- .../updater/spi/UpdateSuccess.java | 21 +++---- .../updater/trip/UrlUpdaterParameters.java | 4 ++ .../metrics/StreamingTripUpdateMetrics.java | 59 +++++++++++++++++++ .../updater/siri/AddedTripBuilderTest.java | 27 ++++++--- .../updater/siri/ModifiedTripBuilderTest.java | 24 +++++--- .../updater/siri/SiriEtBuilder.java | 1 + .../sandbox/siri/SiriGooglePubSubUpdater.md | 1 + doc/user/sandbox/siri/SiriUpdater.md | 1 + 22 files changed, 273 insertions(+), 83 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/model/RealTimeTripUpdate.java b/application/src/main/java/org/opentripplanner/model/RealTimeTripUpdate.java index e5bcc6c0322..d369feaf620 100644 --- a/application/src/main/java/org/opentripplanner/model/RealTimeTripUpdate.java +++ b/application/src/main/java/org/opentripplanner/model/RealTimeTripUpdate.java @@ -9,12 +9,19 @@ /** * Represents the real-time update of a single trip. - * @param pattern the pattern to which belongs the updated trip. This can be a new pattern created in real-time. - * @param updatedTripTimes the new trip times for the updated trip. - * @param serviceDate the service date for which this update applies (updates are valid only for one service date) - * @param addedTripOnServiceDate optionally if this trip update adds a new trip, the TripOnServiceDate corresponding to this new trip. - * @param tripCreation true if this update creates a new trip, not present in scheduled data. - * @param routeCreation true if an added trip cannot be registered under an existing route and a new route must be created. + * + * @param pattern the pattern to which belongs the updated trip. This can be a new + * pattern created in real-time. + * @param updatedTripTimes the new trip times for the updated trip. + * @param serviceDate the service date for which this update applies (updates are valid + * only for one service date) + * @param addedTripOnServiceDate optionally if this trip update adds a new trip, the + * TripOnServiceDate corresponding to this new trip. + * @param tripCreation true if this update creates a new trip, not present in scheduled + * data. + * @param routeCreation true if an added trip cannot be registered under an existing route + * and a new route must be created. + * @param provider the provider of the real-time update. */ public record RealTimeTripUpdate( TripPattern pattern, @@ -22,7 +29,8 @@ public record RealTimeTripUpdate( LocalDate serviceDate, @Nullable TripOnServiceDate addedTripOnServiceDate, boolean tripCreation, - boolean routeCreation + boolean routeCreation, + String provider ) { public RealTimeTripUpdate { Objects.requireNonNull(pattern); @@ -38,6 +46,25 @@ public RealTimeTripUpdate( TripTimes updatedTripTimes, LocalDate serviceDate ) { - this(pattern, updatedTripTimes, serviceDate, null, false, false); + this(pattern, updatedTripTimes, serviceDate, null, false, false, null); + } + + public RealTimeTripUpdate( + TripPattern pattern, + TripTimes updatedTripTimes, + LocalDate serviceDate, + @Nullable TripOnServiceDate addedTripOnServiceDate, + boolean tripCreation, + boolean routeCreation + ) { + this( + pattern, + updatedTripTimes, + serviceDate, + addedTripOnServiceDate, + tripCreation, + routeCreation, + null + ); } } diff --git a/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java b/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java index 0a54fc964d8..9f91c06bdc7 100644 --- a/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java +++ b/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java @@ -347,8 +347,7 @@ public Result update(RealTimeTripUpdate realTimeTrip } // The time tables are finished during the commit - - return Result.success(UpdateSuccess.noWarnings()); + return Result.success(UpdateSuccess.noWarnings(realTimeTripUpdate.provider())); } /** diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java index 65fb7bb2c11..b6227b3b0bd 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java @@ -79,6 +79,11 @@ If this parameter is set, the updater will be marked as initialized (primed) onl .of("fuzzyTripMatching") .since(V2_1) .summary("If the trips should be matched fuzzily.") + .asBoolean(false), + c + .of("detailedMetrics") + .since(V2_1) + .summary("If detailed metrics should be collected.") .asBoolean(false) ); } diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java index 4402ba83b7e..e5107607758 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java @@ -43,7 +43,12 @@ public static SiriETUpdaterParameters create(String configRef, NodeAdapter c) { .since(V2_0) .summary("If the fuzzy trip matcher should be used to match trips.") .asBoolean(false), - HttpHeadersConfig.headers(c, V2_3) + HttpHeadersConfig.headers(c, V2_3), + c + .of("detailedMetrics") + .since(V2_0) + .summary("If detailed metrics should be collected.") + .asBoolean(false) ); } } diff --git a/application/src/main/java/org/opentripplanner/transit/model/framework/FeedScopedId.java b/application/src/main/java/org/opentripplanner/transit/model/framework/FeedScopedId.java index be12be83548..0b269220ae1 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/framework/FeedScopedId.java +++ b/application/src/main/java/org/opentripplanner/transit/model/framework/FeedScopedId.java @@ -78,7 +78,7 @@ public static boolean isValidString(String value) throws IllegalArgumentExceptio /** * Concatenate feedId and id into a string. */ - public static String concatenateId(String feedId, String id) { + private static String concatenateId(String feedId, String id) { return feedId + ID_SEPARATOR + id; } diff --git a/application/src/main/java/org/opentripplanner/updater/siri/AddedTripBuilder.java b/application/src/main/java/org/opentripplanner/updater/siri/AddedTripBuilder.java index a1ca89f5c83..33d3fb960a4 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/AddedTripBuilder.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/AddedTripBuilder.java @@ -56,6 +56,7 @@ class AddedTripBuilder { private final Function getTripPatternId; private final FeedScopedId tripId; private final Operator operator; + private final String dataSource; private final String lineRef; private final Route replacedRoute; private final LocalDate serviceDate; @@ -81,11 +82,15 @@ class AddedTripBuilder { Objects.requireNonNull(newServiceJourneyRef, "EstimatedVehicleJourneyCode is required"); tripId = entityResolver.resolveId(newServiceJourneyRef); - //OperatorRef of added trip + // OperatorRef of added trip Objects.requireNonNull(estimatedVehicleJourney.getOperatorRef(), "OperatorRef is required"); String operatorRef = estimatedVehicleJourney.getOperatorRef().getValue(); operator = entityResolver.resolveOperator(operatorRef); + // DataSource of added trip + Objects.requireNonNull(estimatedVehicleJourney.getDataSource(), "DataSource is required"); + dataSource = estimatedVehicleJourney.getDataSource(); + // LineRef of added trip Objects.requireNonNull(estimatedVehicleJourney.getLineRef(), "LineRef is required"); lineRef = estimatedVehicleJourney.getLineRef().getValue(); @@ -135,7 +140,8 @@ class AddedTripBuilder { boolean cancellation, String shortName, String headsign, - List replacedTrips + List replacedTrips, + String dataSource ) { this.transitService = transitService; this.entityResolver = entityResolver; @@ -155,20 +161,21 @@ class AddedTripBuilder { this.shortName = shortName; this.headsign = headsign; this.replacedTrips = replacedTrips; + this.dataSource = dataSource; } Result build() { if (calls.size() < 2) { - return UpdateError.result(tripId, TOO_FEW_STOPS); + return UpdateError.result(tripId, TOO_FEW_STOPS, dataSource); } if (serviceDate == null) { - return UpdateError.result(tripId, NO_START_DATE); + return UpdateError.result(tripId, NO_START_DATE, dataSource); } FeedScopedId calServiceId = transitService.getOrCreateServiceIdForDate(serviceDate); if (calServiceId == null) { - return UpdateError.result(tripId, NO_START_DATE); + return UpdateError.result(tripId, NO_START_DATE, dataSource); } boolean isAddedRoute = false; @@ -176,7 +183,7 @@ Result build() { if (route == null) { Agency agency = resolveAgency(); if (agency == null) { - return UpdateError.result(tripId, CANNOT_RESOLVE_AGENCY); + return UpdateError.result(tripId, CANNOT_RESOLVE_AGENCY, dataSource); } route = createRoute(agency); isAddedRoute = true; @@ -201,7 +208,7 @@ Result build() { // Drop this update if the call refers to an unknown stop (not present in the site repository). if (stopTime == null) { - return UpdateError.result(tripId, NO_VALID_STOPS); + return UpdateError.result(tripId, NO_VALID_STOPS, dataSource); } aimedStopTimes.add(stopTime); @@ -256,7 +263,7 @@ Result build() { try { updatedTripTimes.validateNonIncreasingTimes(); } catch (DataValidationException e) { - return DataValidationExceptionMapper.toResult(e); + return DataValidationExceptionMapper.toResult(e, dataSource); } var tripOnServiceDate = TripOnServiceDate @@ -273,7 +280,8 @@ Result build() { serviceDate, tripOnServiceDate, pattern, - isAddedRoute + isAddedRoute, + dataSource ) ); } diff --git a/application/src/main/java/org/opentripplanner/updater/siri/ModifiedTripBuilder.java b/application/src/main/java/org/opentripplanner/updater/siri/ModifiedTripBuilder.java index 0f1475f7b50..06768087c74 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/ModifiedTripBuilder.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/ModifiedTripBuilder.java @@ -45,6 +45,7 @@ public class ModifiedTripBuilder { private final boolean cancellation; private final OccupancyEnumeration occupancy; private final boolean predictionInaccurate; + private final String dataSource; public ModifiedTripBuilder( TripTimes existingTripTimes, @@ -64,6 +65,7 @@ public ModifiedTripBuilder( cancellation = TRUE.equals(journey.isCancellation()); predictionInaccurate = TRUE.equals(journey.isPredictionInaccurate()); occupancy = journey.getOccupancy(); + dataSource = journey.getDataSource(); } /** @@ -78,7 +80,8 @@ public ModifiedTripBuilder( List calls, boolean cancellation, OccupancyEnumeration occupancy, - boolean predictionInaccurate + boolean predictionInaccurate, + String dataSource ) { this.existingTripTimes = existingTripTimes; this.pattern = pattern; @@ -89,6 +92,7 @@ public ModifiedTripBuilder( this.cancellation = cancellation; this.occupancy = occupancy; this.predictionInaccurate = predictionInaccurate; + this.dataSource = dataSource; } /** @@ -103,7 +107,9 @@ public Result build() { if (cancellation || stopPattern.isAllStopsNonRoutable()) { LOG.debug("Trip is cancelled"); newTimes.cancelTrip(); - return Result.success(new TripUpdate(pattern.getStopPattern(), newTimes, serviceDate)); + return Result.success( + new TripUpdate(pattern.getStopPattern(), newTimes, serviceDate, dataSource) + ); } applyUpdates(newTimes); @@ -116,7 +122,7 @@ public Result build() { newTimes.setRealTimeState(RealTimeState.MODIFIED); } - // TODO - Handle DataValidationException at the outemost level(pr trip) + // TODO - Handle DataValidationException at the outermost level (pr trip) try { newTimes.validateNonIncreasingTimes(); } catch (DataValidationException e) { @@ -125,7 +131,7 @@ public Result build() { newTimes.getTrip().getId(), e.getMessage() ); - return DataValidationExceptionMapper.toResult(e); + return DataValidationExceptionMapper.toResult(e, dataSource); } int numStopsInUpdate = newTimes.getNumStops(); @@ -137,11 +143,11 @@ public Result build() { numStopsInUpdate, numStopsInPattern ); - return UpdateError.result(existingTripTimes.getTrip().getId(), TOO_FEW_STOPS); + return UpdateError.result(existingTripTimes.getTrip().getId(), TOO_FEW_STOPS, dataSource); } LOG.debug("A valid TripUpdate object was applied using the Timetable class update method."); - return Result.success(new TripUpdate(stopPattern, newTimes, serviceDate)); + return Result.success(new TripUpdate(stopPattern, newTimes, serviceDate, dataSource)); } /** diff --git a/application/src/main/java/org/opentripplanner/updater/siri/SiriTimetableSnapshotSource.java b/application/src/main/java/org/opentripplanner/updater/siri/SiriTimetableSnapshotSource.java index b6aa5310d83..33ca220ca7d 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/SiriTimetableSnapshotSource.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/SiriTimetableSnapshotSource.java @@ -174,7 +174,7 @@ private Result apply( /* commit */ return addTripToGraphAndBuffer(result.successValue()); } catch (DataValidationException e) { - return DataValidationExceptionMapper.toResult(e); + return DataValidationExceptionMapper.toResult(e, journey.getDataSource()); } catch (Exception e) { LOG.warn( "{} EstimatedJourney {} failed.", @@ -217,6 +217,7 @@ private Result handleModifiedTrip( EstimatedVehicleJourney estimatedVehicleJourney ) { Trip trip = entityResolver.resolveTrip(estimatedVehicleJourney); + String dataSource = estimatedVehicleJourney.getDataSource(); // Check if EstimatedVehicleJourney is reported as NOT monitored, ignore the notMonitored-flag // if the journey is NOT monitored because it has been cancelled @@ -224,13 +225,13 @@ private Result handleModifiedTrip( !TRUE.equals(estimatedVehicleJourney.isMonitored()) && !TRUE.equals(estimatedVehicleJourney.isCancellation()) ) { - return UpdateError.result(trip != null ? trip.getId() : null, NOT_MONITORED); + return UpdateError.result(trip != null ? trip.getId() : null, NOT_MONITORED, dataSource); } LocalDate serviceDate = entityResolver.resolveServiceDate(estimatedVehicleJourney); if (serviceDate == null) { - return UpdateError.result(trip != null ? trip.getId() : null, NO_START_DATE); + return UpdateError.result(trip != null ? trip.getId() : null, NO_START_DATE, dataSource); } TripPattern pattern; @@ -252,20 +253,20 @@ private Result handleModifiedTrip( "No trips found for EstimatedVehicleJourney. {}", DebugString.of(estimatedVehicleJourney) ); - return UpdateError.result(null, NO_FUZZY_TRIP_MATCH); + return UpdateError.result(null, NO_FUZZY_TRIP_MATCH, dataSource); } trip = tripAndPattern.trip(); pattern = tripAndPattern.tripPattern(); } else { - return UpdateError.result(null, TRIP_NOT_FOUND); + return UpdateError.result(null, TRIP_NOT_FOUND, dataSource); } Timetable currentTimetable = getCurrentTimetable(pattern, serviceDate); TripTimes existingTripTimes = currentTimetable.getTripTimes(trip); if (existingTripTimes == null) { LOG.debug("tripId {} not found in pattern.", trip.getId()); - return UpdateError.result(trip.getId(), TRIP_NOT_FOUND_IN_PATTERN); + return UpdateError.result(trip.getId(), TRIP_NOT_FOUND_IN_PATTERN, dataSource); } var updateResult = new ModifiedTripBuilder( existingTripTimes, @@ -315,7 +316,8 @@ private Result addTripToGraphAndBuffer(TripUpdate tr serviceDate, tripUpdate.addedTripOnServiceDate(), tripUpdate.tripCreation(), - tripUpdate.routeCreation() + tripUpdate.routeCreation(), + tripUpdate.dataSource() ); var result = snapshotManager.updateBuffer(realTimeTripUpdate); LOG.debug("Applied real-time data for trip {} on {}", trip, serviceDate); diff --git a/application/src/main/java/org/opentripplanner/updater/siri/TripUpdate.java b/application/src/main/java/org/opentripplanner/updater/siri/TripUpdate.java index 71c521b76fb..4ef5980af42 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/TripUpdate.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/TripUpdate.java @@ -11,12 +11,18 @@ /** * Represents the SIRI real-time update of a single trip. - * @param stopPattern the stop pattern to which belongs the updated trip. - * @param tripTimes the new trip times for the updated trip. - * @param serviceDate the service date for which this update applies (updates are valid only for one service date) - * @param addedTripOnServiceDate optionally if this trip update adds a new trip, the TripOnServiceDate corresponding to this new trip. - * @param addedTripPattern optionally if this trip update adds a new trip pattern , the new trip pattern to this new trip. - * @param routeCreation true if an added trip cannot be registered under an existing route and a new route must be created. + * + * @param stopPattern the stop pattern to which belongs the updated trip. + * @param tripTimes the new trip times for the updated trip. + * @param serviceDate the service date for which this update applies (updates are valid + * only for one service date) + * @param addedTripOnServiceDate optionally if this trip update adds a new trip, the + * TripOnServiceDate corresponding to this new trip. + * @param addedTripPattern optionally if this trip update adds a new trip pattern , the new + * trip pattern to this new trip. + * @param routeCreation true if an added trip cannot be registered under an existing route + * and a new route must be created. + * @param dataSource the dataSource of the real-time update. */ record TripUpdate( StopPattern stopPattern, @@ -24,7 +30,8 @@ record TripUpdate( LocalDate serviceDate, @Nullable TripOnServiceDate addedTripOnServiceDate, @Nullable TripPattern addedTripPattern, - boolean routeCreation + boolean routeCreation, + String dataSource ) { public TripUpdate { Objects.requireNonNull(stopPattern); @@ -38,9 +45,10 @@ record TripUpdate( public TripUpdate( StopPattern stopPattern, RealTimeTripTimes updatedTripTimes, - LocalDate serviceDate + LocalDate serviceDate, + String dataSource ) { - this(stopPattern, updatedTripTimes, serviceDate, null, null, false); + this(stopPattern, updatedTripTimes, serviceDate, null, null, false, dataSource); } /** diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java index 8bbe66559c6..d2d42e50374 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java @@ -15,7 +15,8 @@ public record SiriETUpdaterParameters( Duration timeout, Duration previewInterval, boolean fuzzyTripMatching, - HttpHeaders httpRequestHeaders + HttpHeaders httpRequestHeaders, + boolean detailedMetrics ) implements PollingGraphUpdaterParameters, UrlUpdaterParameters, SiriETHttpTripUpdateSource.Parameters { diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java index 2970ac114c0..6f68c8002b4 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java @@ -15,7 +15,8 @@ public record SiriETGooglePubsubUpdaterParameters( @Nullable String dataInitializationUrl, Duration reconnectPeriod, Duration initialGetDataTimeout, - boolean fuzzyTripMatching + boolean fuzzyTripMatching, + boolean detailedMetrics ) implements UrlUpdaterParameters { public static Duration RECONNECT_PERIOD = Duration.ofSeconds(30); diff --git a/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java b/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java index dee103bc385..c376c70b832 100644 --- a/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java @@ -15,13 +15,20 @@ public class DataValidationExceptionMapper { private static final Logger LOG = LoggerFactory.getLogger(DataValidationExceptionMapper.class); public static Result toResult(DataValidationException error) { + return toResult(error, null); + } + + public static Result toResult( + DataValidationException error, + String provider + ) { if (error.error() instanceof TimetableValidationError tt) { return Result.failure( - new UpdateError(tt.trip().getId(), mapTimeTableError(tt.code()), tt.stopIndex()) + new UpdateError(tt.trip().getId(), mapTimeTableError(tt.code()), tt.stopIndex(), provider) ); } // The mapper should handle all possible errors - LOG.error("Unhandled error: " + error.getMessage(), error); + LOG.error("Unhandled error: {}", error.getMessage(), error); return Result.failure(UpdateError.noTripId(UpdateError.UpdateErrorType.UNKNOWN)); } diff --git a/application/src/main/java/org/opentripplanner/updater/spi/UpdateError.java b/application/src/main/java/org/opentripplanner/updater/spi/UpdateError.java index 1f568ba99a4..ac723a0d1e3 100644 --- a/application/src/main/java/org/opentripplanner/updater/spi/UpdateError.java +++ b/application/src/main/java/org/opentripplanner/updater/spi/UpdateError.java @@ -11,10 +11,18 @@ public record UpdateError( @Nullable FeedScopedId tripId, UpdateErrorType errorType, - @Nullable Integer stopIndex + @Nullable Integer stopIndex, + @Nullable String provider ) { public UpdateError(@Nullable FeedScopedId tripId, UpdateErrorType errorType) { - this(tripId, errorType, null); + this(tripId, errorType, null, null); + } + + public UpdateError(@Nullable FeedScopedId tripId, UpdateErrorType errorType, Integer stopIndex) { + this(tripId, errorType, stopIndex, null); + } + public UpdateError(@Nullable FeedScopedId tripId, UpdateErrorType errorType, String provider) { + this(tripId, errorType, null, provider); } public String debugId() { @@ -56,6 +64,14 @@ public static Result result(FeedScopedId tripId, UpdateError return Result.failure(new UpdateError(tripId, errorType)); } + public static Result result( + FeedScopedId tripId, + UpdateErrorType errorType, + String provider + ) { + return Result.failure(new UpdateError(tripId, errorType, provider)); + } + public static UpdateError noTripId(UpdateErrorType errorType) { return new UpdateError(null, errorType); } diff --git a/application/src/main/java/org/opentripplanner/updater/spi/UpdateResult.java b/application/src/main/java/org/opentripplanner/updater/spi/UpdateResult.java index 643703f2252..0e47d8d202f 100644 --- a/application/src/main/java/org/opentripplanner/updater/spi/UpdateResult.java +++ b/application/src/main/java/org/opentripplanner/updater/spi/UpdateResult.java @@ -1,6 +1,7 @@ package org.opentripplanner.updater.spi; import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import java.util.List; @@ -15,23 +16,43 @@ public record UpdateResult( int successful, int failed, Multimap failures, - List warnings + List warnings, + List successes, + List errors ) { /** * Create an empty result. */ public static UpdateResult empty() { - return new UpdateResult(0, 0, ArrayListMultimap.create(), List.of()); + return new UpdateResult(0, 0, ArrayListMultimap.create(), List.of(), List.of(), List.of()); } /** * Aggregate a list of results into an instance of {@link UpdateResult}. */ public static UpdateResult ofResults(List> results) { - var errors = results.stream().filter(Result::isFailure).map(Result::failureValue).toList(); - var successes = results.stream().filter(Result::isSuccess).map(Result::successValue).toList(); - var warnings = successes.stream().flatMap(s -> s.warnings().stream()).toList(); - var errorIndex = Multimaps.index(errors, UpdateError::errorType); - return new UpdateResult(successes.size(), errors.size(), errorIndex, warnings); + List errors = results + .stream() + .filter(Result::isFailure) + .map(Result::failureValue) + .toList(); + List successes = results + .stream() + .filter(Result::isSuccess) + .map(Result::successValue) + .toList(); + List warnings = successes + .stream() + .flatMap(s -> s.warnings().stream()) + .toList(); + ImmutableListMultimap errorIndex = Multimaps.index(errors, UpdateError::errorType); + return new UpdateResult( + successes.size(), + errors.size(), + errorIndex, + warnings, + successes, + errors + ); } } diff --git a/application/src/main/java/org/opentripplanner/updater/spi/UpdateSuccess.java b/application/src/main/java/org/opentripplanner/updater/spi/UpdateSuccess.java index 6d797a9a650..385ae8eb17a 100644 --- a/application/src/main/java/org/opentripplanner/updater/spi/UpdateSuccess.java +++ b/application/src/main/java/org/opentripplanner/updater/spi/UpdateSuccess.java @@ -1,34 +1,35 @@ package org.opentripplanner.updater.spi; -import java.util.Arrays; import java.util.Collection; import java.util.List; import org.opentripplanner.utils.collection.ListUtils; /** * The result of a successful application of a realtime update, for example for trips or - * vehicle positions. Its only extra information is a collection of possible warnings that - * ought to be looked at but didn't prevent the application of the update. + * vehicle positions. Its extra information is a collection of possible warnings that + * ought to be looked at but didn't prevent the application of the update and the provider of the + * update. */ -public record UpdateSuccess(List warnings) { +public record UpdateSuccess(List warnings, String provider) { /** - * Create an instance with no warnings. + * Create an instance with no warnings and no provider. */ public static UpdateSuccess noWarnings() { - return new UpdateSuccess(List.of()); + return new UpdateSuccess(List.of(), null); } + /** - * Create an instance with the provided warnings. + * Create an instance with no warnings, but a provider. */ - public static UpdateSuccess ofWarnings(WarningType... warnings) { - return new UpdateSuccess(Arrays.asList(warnings)); + public static UpdateSuccess noWarnings(String provider) { + return new UpdateSuccess(List.of(), provider); } /** * Return a copy of the instance with the provided warnings added. */ public UpdateSuccess addWarnings(Collection addedWarnings) { - return new UpdateSuccess(ListUtils.combine(this.warnings, addedWarnings)); + return new UpdateSuccess(ListUtils.combine(this.warnings, addedWarnings), this.provider); } public enum WarningType { diff --git a/application/src/main/java/org/opentripplanner/updater/trip/UrlUpdaterParameters.java b/application/src/main/java/org/opentripplanner/updater/trip/UrlUpdaterParameters.java index 2070aedbbd6..2df600bfa6e 100644 --- a/application/src/main/java/org/opentripplanner/updater/trip/UrlUpdaterParameters.java +++ b/application/src/main/java/org/opentripplanner/updater/trip/UrlUpdaterParameters.java @@ -4,4 +4,8 @@ public interface UrlUpdaterParameters { String url(); String configRef(); String feedId(); + + default boolean detailedMetrics() { + return false; + } } diff --git a/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java b/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java index c53a398f3dc..b02a907d206 100644 --- a/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java +++ b/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java @@ -8,6 +8,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; +import org.apache.commons.lang3.tuple.Pair; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.spi.UpdateResult; import org.opentripplanner.updater.spi.UpdateSuccess; @@ -31,11 +33,16 @@ public class StreamingTripUpdateMetrics extends TripUpdateMetrics { private final Map failuresByType = new HashMap<>(); private final Map warningsByType = new HashMap<>(); + private final boolean detailedMetrics; + private final Map successesByProvider = new HashMap<>(); + private final Map, Counter> failuresByTypeAndProvider = new HashMap<>(); + public StreamingTripUpdateMetrics(UrlUpdaterParameters parameters) { super(parameters); this.successfulCounter = getCounter("successful", "Total successfully applied trip updates"); this.failureCounter = getCounter("failed", "Total failed trip updates"); this.warningsCounter = getCounter("warnings", "Total warnings for successful trip updates"); + this.detailedMetrics = parameters.detailedMetrics(); } public void setCounters(UpdateResult result) { @@ -45,6 +52,10 @@ public void setCounters(UpdateResult result) { setFailures(result); setWarnings(result); + if (detailedMetrics) { + setFailuresByTypeAndProvider(result); + setSuccessesByProvider(result); + } } private void setWarnings(UpdateResult result) { @@ -79,6 +90,54 @@ private void setFailures(UpdateResult result) { } } + private void setFailuresByTypeAndProvider(UpdateResult result) { + Map, Long> failureCountByTypeAndProvider = result + .errors() + .stream() + .collect( + Collectors.groupingBy( + error -> Pair.of(error.provider(), error.errorType()), + Collectors.counting() + ) + ); + + for (var entry : failureCountByTypeAndProvider.entrySet()) { + Counter counter = failuresByTypeAndProvider.get(entry.getKey()); + if (Objects.isNull(counter)) { + counter = + getCounter( + "failures_by_type_and_provider", + "Total failed trip updates by type and provider", + Tag.of("provider", entry.getKey().getLeft()), + Tag.of("errorType", entry.getKey().getRight().name()) + ); + failuresByTypeAndProvider.put(entry.getKey(), counter); + } + counter.increment(entry.getValue()); + } + } + + private void setSuccessesByProvider(UpdateResult result) { + Map successCountByProvider = result + .successes() + .stream() + .collect(Collectors.groupingBy(UpdateSuccess::provider, Collectors.counting())); + + for (var entry : successCountByProvider.entrySet()) { + Counter counter = successesByProvider.get(entry.getKey()); + if (Objects.isNull(counter)) { + counter = + getCounter( + "successes_by_provider", + "Total successful trip updates by provider", + Tag.of("provider", entry.getKey()) + ); + successesByProvider.put(entry.getKey(), counter); + } + counter.increment(entry.getValue()); + } + } + private Counter getCounter(String name, String description, Tag... tags) { var finalTags = Tags.concat(Arrays.stream(tags).toList(), baseTags); return Counter diff --git a/application/src/test/java/org/opentripplanner/updater/siri/AddedTripBuilderTest.java b/application/src/test/java/org/opentripplanner/updater/siri/AddedTripBuilderTest.java index 0c01e847148..0ca64a18aa1 100644 --- a/application/src/test/java/org/opentripplanner/updater/siri/AddedTripBuilderTest.java +++ b/application/src/test/java/org/opentripplanner/updater/siri/AddedTripBuilderTest.java @@ -135,7 +135,8 @@ void testAddedTrip() { false, SHORT_NAME, HEADSIGN, - List.of() + List.of(), + "TST" ) .build(); @@ -247,7 +248,8 @@ void testAddedTripOnAddedRoute() { false, SHORT_NAME, HEADSIGN, - List.of() + List.of(), + "TST" ) .build(); @@ -275,7 +277,8 @@ void testAddedTripOnAddedRoute() { false, SHORT_NAME, HEADSIGN, - List.of() + List.of(), + "TST" ) .build(); @@ -317,7 +320,8 @@ void testAddedTripOnExistingRoute() { false, SHORT_NAME, HEADSIGN, - List.of() + List.of(), + "TST" ) .build(); @@ -351,7 +355,8 @@ void testAddedTripWithoutReplacedRoute() { false, SHORT_NAME, HEADSIGN, - List.of() + List.of(), + "TST" ) .build(); @@ -395,7 +400,8 @@ void testAddedTripFailOnMissingServiceId() { false, SHORT_NAME, HEADSIGN, - List.of() + List.of(), + "TST" ) .build(); @@ -450,7 +456,8 @@ void testAddedTripFailOnNonIncreasingDwellTime() { false, SHORT_NAME, HEADSIGN, - List.of() + List.of(), + "TST" ) .build(); @@ -489,7 +496,8 @@ void testAddedTripFailOnTooFewCalls() { false, SHORT_NAME, HEADSIGN, - List.of() + List.of(), + "TST" ) .build(); @@ -536,7 +544,8 @@ void testAddedTripFailOnUnknownStop() { false, SHORT_NAME, HEADSIGN, - List.of() + List.of(), + "TST" ) .build(); diff --git a/application/src/test/java/org/opentripplanner/updater/siri/ModifiedTripBuilderTest.java b/application/src/test/java/org/opentripplanner/updater/siri/ModifiedTripBuilderTest.java index ad3ed749fb9..6923aa731c1 100644 --- a/application/src/test/java/org/opentripplanner/updater/siri/ModifiedTripBuilderTest.java +++ b/application/src/test/java/org/opentripplanner/updater/siri/ModifiedTripBuilderTest.java @@ -174,7 +174,8 @@ void testUpdateNoCalls() { List.of(), false, null, - false + false, + "TST" ) .build(); @@ -203,7 +204,8 @@ void testUpdateCancellation() { List.of(), true, null, - false + false, + "TST" ) .build(); @@ -247,7 +249,8 @@ void testUpdateSameStops() { ), false, null, - false + false, + "TST" ) .build(); @@ -297,7 +300,8 @@ void testUpdateValidationFailure() { ), false, null, - false + false, + "TST" ) .build(); @@ -345,7 +349,8 @@ void testUpdateSameStopsDepartEarly() { ), false, null, - false + false, + "TST" ) .build(); @@ -395,7 +400,8 @@ void testUpdateUpdatedStop() { ), false, null, - false + false, + "TST" ) .build(); @@ -451,7 +457,8 @@ void testUpdateCascading() { ), false, null, - false + false, + "TST" ) .build(); @@ -496,7 +503,8 @@ void testUpdateCascading() { ), false, null, - false + false, + "TST" ) .build(); diff --git a/application/src/test/java/org/opentripplanner/updater/siri/SiriEtBuilder.java b/application/src/test/java/org/opentripplanner/updater/siri/SiriEtBuilder.java index b9517e987c4..bc51ac0f9a7 100644 --- a/application/src/test/java/org/opentripplanner/updater/siri/SiriEtBuilder.java +++ b/application/src/test/java/org/opentripplanner/updater/siri/SiriEtBuilder.java @@ -39,6 +39,7 @@ public SiriEtBuilder(DateTimeHelper dateTimeHelper) { // Set default values evj.setMonitored(true); + evj.setDataSource("TST"); } public List buildEstimatedTimetableDeliveries() { diff --git a/doc/user/sandbox/siri/SiriGooglePubSubUpdater.md b/doc/user/sandbox/siri/SiriGooglePubSubUpdater.md index 58d05d5490e..0895b2e4dbf 100644 --- a/doc/user/sandbox/siri/SiriGooglePubSubUpdater.md +++ b/doc/user/sandbox/siri/SiriGooglePubSubUpdater.md @@ -32,6 +32,7 @@ of the `router-config.json`. |------------------------------------------------------------|:----------:|----------------------------------------------------------------------------------|:----------:|---------------|:-----:| | type = "siri-et-google-pubsub-updater" | `enum` | The type of the updater. | *Required* | | 1.5 | | [dataInitializationUrl](#u__12__dataInitializationUrl) | `string` | URL used to download over HTTP the recent history of SIRI-ET messages. | *Optional* | | 2.1 | +| detailedMetrics | `boolean` | If detailed metrics should be collected. | *Optional* | `false` | 2.1 | | feedId | `string` | The ID of the feed to apply the updates to. | *Optional* | | 2.1 | | fuzzyTripMatching | `boolean` | If the trips should be matched fuzzily. | *Optional* | `false` | 2.1 | | [initialGetDataTimeout](#u__12__initialGetDataTimeout) | `duration` | Timeout for retrieving the recent history of SIRI-ET messages. | *Optional* | `"PT30S"` | 2.1 | diff --git a/doc/user/sandbox/siri/SiriUpdater.md b/doc/user/sandbox/siri/SiriUpdater.md index f6c4c3f999f..f63316974f4 100644 --- a/doc/user/sandbox/siri/SiriUpdater.md +++ b/doc/user/sandbox/siri/SiriUpdater.md @@ -31,6 +31,7 @@ To enable the SIRI updater you need to add it to the updaters section of the `ro |--------------------------------|:---------------:|--------------------------------------------------------------------------------------------------------|:----------:|---------------|:-----:| | type = "siri-et-updater" | `enum` | The type of the updater. | *Required* | | 1.5 | | blockReadinessUntilInitialized | `boolean` | Whether catching up with the updates should block the readiness check from returning a 'ready' result. | *Optional* | `false` | 2.0 | +| detailedMetrics | `boolean` | If detailed metrics should be collected. | *Optional* | `false` | 2.0 | | feedId | `string` | The ID of the feed to apply the updates to. | *Required* | | 2.0 | | frequency | `duration` | How often the updates should be retrieved. | *Optional* | `"PT1M"` | 2.0 | | fuzzyTripMatching | `boolean` | If the fuzzy trip matcher should be used to match trips. | *Optional* | `false` | 2.0 | From 31e3bbddf8770864ce4e122527fa566b1ae17f0f Mon Sep 17 00:00:00 2001 From: eibakke Date: Tue, 5 Nov 2024 13:33:57 +0100 Subject: [PATCH 099/121] Changes provider to producer and addresses smaller comments in PR. --- .../model/RealTimeTripUpdate.java | 4 ++-- .../model/TimetableSnapshot.java | 2 +- .../spi/DataValidationExceptionMapper.java | 4 ++-- .../updater/spi/UpdateError.java | 10 +++++----- .../updater/spi/UpdateSuccess.java | 8 ++++---- .../metrics/StreamingTripUpdateMetrics.java | 8 ++++---- .../updater/siri/AddedTripBuilderTest.java | 18 +++++++++--------- .../updater/siri/ModifiedTripBuilderTest.java | 16 ++++++++-------- .../updater/siri/SiriEtBuilder.java | 2 +- 9 files changed, 36 insertions(+), 36 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/model/RealTimeTripUpdate.java b/application/src/main/java/org/opentripplanner/model/RealTimeTripUpdate.java index d369feaf620..28fc16a98ac 100644 --- a/application/src/main/java/org/opentripplanner/model/RealTimeTripUpdate.java +++ b/application/src/main/java/org/opentripplanner/model/RealTimeTripUpdate.java @@ -21,7 +21,7 @@ * data. * @param routeCreation true if an added trip cannot be registered under an existing route * and a new route must be created. - * @param provider the provider of the real-time update. + * @param producer the producer of the real-time update. */ public record RealTimeTripUpdate( TripPattern pattern, @@ -30,7 +30,7 @@ public record RealTimeTripUpdate( @Nullable TripOnServiceDate addedTripOnServiceDate, boolean tripCreation, boolean routeCreation, - String provider + @Nullable String producer ) { public RealTimeTripUpdate { Objects.requireNonNull(pattern); diff --git a/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java b/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java index 9f91c06bdc7..94b490c48a0 100644 --- a/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java +++ b/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java @@ -347,7 +347,7 @@ public Result update(RealTimeTripUpdate realTimeTrip } // The time tables are finished during the commit - return Result.success(UpdateSuccess.noWarnings(realTimeTripUpdate.provider())); + return Result.success(UpdateSuccess.noWarnings(realTimeTripUpdate.producer())); } /** diff --git a/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java b/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java index c376c70b832..c37660cf9bc 100644 --- a/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java @@ -20,11 +20,11 @@ public static Result toResult(DataValidationException error) public static Result toResult( DataValidationException error, - String provider + String producer ) { if (error.error() instanceof TimetableValidationError tt) { return Result.failure( - new UpdateError(tt.trip().getId(), mapTimeTableError(tt.code()), tt.stopIndex(), provider) + new UpdateError(tt.trip().getId(), mapTimeTableError(tt.code()), tt.stopIndex(), producer) ); } // The mapper should handle all possible errors diff --git a/application/src/main/java/org/opentripplanner/updater/spi/UpdateError.java b/application/src/main/java/org/opentripplanner/updater/spi/UpdateError.java index ac723a0d1e3..4e81cf3c2a7 100644 --- a/application/src/main/java/org/opentripplanner/updater/spi/UpdateError.java +++ b/application/src/main/java/org/opentripplanner/updater/spi/UpdateError.java @@ -12,7 +12,7 @@ public record UpdateError( @Nullable FeedScopedId tripId, UpdateErrorType errorType, @Nullable Integer stopIndex, - @Nullable String provider + @Nullable String producer ) { public UpdateError(@Nullable FeedScopedId tripId, UpdateErrorType errorType) { this(tripId, errorType, null, null); @@ -21,8 +21,8 @@ public UpdateError(@Nullable FeedScopedId tripId, UpdateErrorType errorType) { public UpdateError(@Nullable FeedScopedId tripId, UpdateErrorType errorType, Integer stopIndex) { this(tripId, errorType, stopIndex, null); } - public UpdateError(@Nullable FeedScopedId tripId, UpdateErrorType errorType, String provider) { - this(tripId, errorType, null, provider); + public UpdateError(@Nullable FeedScopedId tripId, UpdateErrorType errorType, String producer) { + this(tripId, errorType, null, producer); } public String debugId() { @@ -67,9 +67,9 @@ public static Result result(FeedScopedId tripId, UpdateError public static Result result( FeedScopedId tripId, UpdateErrorType errorType, - String provider + String producer ) { - return Result.failure(new UpdateError(tripId, errorType, provider)); + return Result.failure(new UpdateError(tripId, errorType, producer)); } public static UpdateError noTripId(UpdateErrorType errorType) { diff --git a/application/src/main/java/org/opentripplanner/updater/spi/UpdateSuccess.java b/application/src/main/java/org/opentripplanner/updater/spi/UpdateSuccess.java index 385ae8eb17a..861f4c5edff 100644 --- a/application/src/main/java/org/opentripplanner/updater/spi/UpdateSuccess.java +++ b/application/src/main/java/org/opentripplanner/updater/spi/UpdateSuccess.java @@ -10,7 +10,7 @@ * ought to be looked at but didn't prevent the application of the update and the provider of the * update. */ -public record UpdateSuccess(List warnings, String provider) { +public record UpdateSuccess(List warnings, String producer) { /** * Create an instance with no warnings and no provider. */ @@ -21,15 +21,15 @@ public static UpdateSuccess noWarnings() { /** * Create an instance with no warnings, but a provider. */ - public static UpdateSuccess noWarnings(String provider) { - return new UpdateSuccess(List.of(), provider); + public static UpdateSuccess noWarnings(String producer) { + return new UpdateSuccess(List.of(), producer); } /** * Return a copy of the instance with the provided warnings added. */ public UpdateSuccess addWarnings(Collection addedWarnings) { - return new UpdateSuccess(ListUtils.combine(this.warnings, addedWarnings), this.provider); + return new UpdateSuccess(ListUtils.combine(this.warnings, addedWarnings), this.producer); } public enum WarningType { diff --git a/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java b/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java index b02a907d206..39c639f491a 100644 --- a/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java +++ b/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java @@ -96,7 +96,7 @@ private void setFailuresByTypeAndProvider(UpdateResult result) { .stream() .collect( Collectors.groupingBy( - error -> Pair.of(error.provider(), error.errorType()), + error -> Pair.of(error.producer(), error.errorType()), Collectors.counting() ) ); @@ -121,7 +121,7 @@ private void setSuccessesByProvider(UpdateResult result) { Map successCountByProvider = result .successes() .stream() - .collect(Collectors.groupingBy(UpdateSuccess::provider, Collectors.counting())); + .collect(Collectors.groupingBy(UpdateSuccess::producer, Collectors.counting())); for (var entry : successCountByProvider.entrySet()) { Counter counter = successesByProvider.get(entry.getKey()); @@ -129,8 +129,8 @@ private void setSuccessesByProvider(UpdateResult result) { counter = getCounter( "successes_by_provider", - "Total successful trip updates by provider", - Tag.of("provider", entry.getKey()) + "Total successful trip updates by producer", + Tag.of("producer", entry.getKey()) ); successesByProvider.put(entry.getKey(), counter); } diff --git a/application/src/test/java/org/opentripplanner/updater/siri/AddedTripBuilderTest.java b/application/src/test/java/org/opentripplanner/updater/siri/AddedTripBuilderTest.java index 0ca64a18aa1..abed2144982 100644 --- a/application/src/test/java/org/opentripplanner/updater/siri/AddedTripBuilderTest.java +++ b/application/src/test/java/org/opentripplanner/updater/siri/AddedTripBuilderTest.java @@ -136,7 +136,7 @@ void testAddedTrip() { SHORT_NAME, HEADSIGN, List.of(), - "TST" + "DATASOURCE" ) .build(); @@ -249,7 +249,7 @@ void testAddedTripOnAddedRoute() { SHORT_NAME, HEADSIGN, List.of(), - "TST" + "DATASOURCE" ) .build(); @@ -278,7 +278,7 @@ void testAddedTripOnAddedRoute() { SHORT_NAME, HEADSIGN, List.of(), - "TST" + "DATASOURCE" ) .build(); @@ -321,7 +321,7 @@ void testAddedTripOnExistingRoute() { SHORT_NAME, HEADSIGN, List.of(), - "TST" + "DATASOURCE" ) .build(); @@ -356,7 +356,7 @@ void testAddedTripWithoutReplacedRoute() { SHORT_NAME, HEADSIGN, List.of(), - "TST" + "DATASOURCE" ) .build(); @@ -401,7 +401,7 @@ void testAddedTripFailOnMissingServiceId() { SHORT_NAME, HEADSIGN, List.of(), - "TST" + "DATASOURCE" ) .build(); @@ -457,7 +457,7 @@ void testAddedTripFailOnNonIncreasingDwellTime() { SHORT_NAME, HEADSIGN, List.of(), - "TST" + "DATASOURCE" ) .build(); @@ -497,7 +497,7 @@ void testAddedTripFailOnTooFewCalls() { SHORT_NAME, HEADSIGN, List.of(), - "TST" + "DATASOURCE" ) .build(); @@ -545,7 +545,7 @@ void testAddedTripFailOnUnknownStop() { SHORT_NAME, HEADSIGN, List.of(), - "TST" + "DATASOURCE" ) .build(); diff --git a/application/src/test/java/org/opentripplanner/updater/siri/ModifiedTripBuilderTest.java b/application/src/test/java/org/opentripplanner/updater/siri/ModifiedTripBuilderTest.java index 6923aa731c1..6c1797a24a2 100644 --- a/application/src/test/java/org/opentripplanner/updater/siri/ModifiedTripBuilderTest.java +++ b/application/src/test/java/org/opentripplanner/updater/siri/ModifiedTripBuilderTest.java @@ -175,7 +175,7 @@ void testUpdateNoCalls() { false, null, false, - "TST" + "DATASOURCE" ) .build(); @@ -205,7 +205,7 @@ void testUpdateCancellation() { true, null, false, - "TST" + "DATASOURCE" ) .build(); @@ -250,7 +250,7 @@ void testUpdateSameStops() { false, null, false, - "TST" + "DATASOURCE" ) .build(); @@ -301,7 +301,7 @@ void testUpdateValidationFailure() { false, null, false, - "TST" + "DATASOURCE" ) .build(); @@ -350,7 +350,7 @@ void testUpdateSameStopsDepartEarly() { false, null, false, - "TST" + "DATASOURCE" ) .build(); @@ -401,7 +401,7 @@ void testUpdateUpdatedStop() { false, null, false, - "TST" + "DATASOURCE" ) .build(); @@ -458,7 +458,7 @@ void testUpdateCascading() { false, null, false, - "TST" + "DATASOURCE" ) .build(); @@ -504,7 +504,7 @@ void testUpdateCascading() { false, null, false, - "TST" + "DATASOURCE" ) .build(); diff --git a/application/src/test/java/org/opentripplanner/updater/siri/SiriEtBuilder.java b/application/src/test/java/org/opentripplanner/updater/siri/SiriEtBuilder.java index bc51ac0f9a7..3656d9da2f0 100644 --- a/application/src/test/java/org/opentripplanner/updater/siri/SiriEtBuilder.java +++ b/application/src/test/java/org/opentripplanner/updater/siri/SiriEtBuilder.java @@ -39,7 +39,7 @@ public SiriEtBuilder(DateTimeHelper dateTimeHelper) { // Set default values evj.setMonitored(true); - evj.setDataSource("TST"); + evj.setDataSource("DATASOURCE"); } public List buildEstimatedTimetableDeliveries() { From b6ac221f7a60f073b656ba8299a50fd8f36c9aa7 Mon Sep 17 00:00:00 2001 From: eibakke Date: Tue, 5 Nov 2024 15:03:26 +0100 Subject: [PATCH 100/121] Updates how counters are incremented and kept track of in StreamingTripUpdateMetrics. Also adds more details to the new producerMetrics flag, and renames it from detailedMetrics. --- .../updaters/SiriETUpdaterConfig.java | 4 +- .../siri/updater/SiriETUpdaterParameters.java | 2 +- .../SiriETGooglePubsubUpdaterParameters.java | 2 +- .../updater/trip/UrlUpdaterParameters.java | 2 +- .../metrics/StreamingTripUpdateMetrics.java | 141 +++++------------- doc/user/sandbox/siri/SiriUpdater.md | 2 +- 6 files changed, 42 insertions(+), 111 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java index e5107607758..cb38bf0f4b7 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java @@ -45,9 +45,9 @@ public static SiriETUpdaterParameters create(String configRef, NodeAdapter c) { .asBoolean(false), HttpHeadersConfig.headers(c, V2_3), c - .of("detailedMetrics") + .of("producerMetrics") .since(V2_0) - .summary("If detailed metrics should be collected.") + .summary("If failure, success, and warning metrics should be collected per producer.") .asBoolean(false) ); } diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java index d2d42e50374..dc479c034e1 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java @@ -16,7 +16,7 @@ public record SiriETUpdaterParameters( Duration previewInterval, boolean fuzzyTripMatching, HttpHeaders httpRequestHeaders, - boolean detailedMetrics + boolean producerMetrics ) implements PollingGraphUpdaterParameters, UrlUpdaterParameters, SiriETHttpTripUpdateSource.Parameters { diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java index 6f68c8002b4..d7fb064966a 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java @@ -16,7 +16,7 @@ public record SiriETGooglePubsubUpdaterParameters( Duration reconnectPeriod, Duration initialGetDataTimeout, boolean fuzzyTripMatching, - boolean detailedMetrics + boolean producerMetrics ) implements UrlUpdaterParameters { public static Duration RECONNECT_PERIOD = Duration.ofSeconds(30); diff --git a/application/src/main/java/org/opentripplanner/updater/trip/UrlUpdaterParameters.java b/application/src/main/java/org/opentripplanner/updater/trip/UrlUpdaterParameters.java index 2df600bfa6e..4918be84e45 100644 --- a/application/src/main/java/org/opentripplanner/updater/trip/UrlUpdaterParameters.java +++ b/application/src/main/java/org/opentripplanner/updater/trip/UrlUpdaterParameters.java @@ -5,7 +5,7 @@ public interface UrlUpdaterParameters { String configRef(); String feedId(); - default boolean detailedMetrics() { + default boolean producerMetrics() { return false; } } diff --git a/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java b/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java index 39c639f491a..233099e19f0 100644 --- a/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java +++ b/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java @@ -5,11 +5,7 @@ import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tags; import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; -import org.apache.commons.lang3.tuple.Pair; +import java.util.List; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.spi.UpdateResult; import org.opentripplanner.updater.spi.UpdateSuccess; @@ -27,123 +23,58 @@ public class StreamingTripUpdateMetrics extends TripUpdateMetrics { protected static final String METRICS_PREFIX = "streaming_trip_updates"; - private final Counter successfulCounter; - private final Counter failureCounter; - private final Counter warningsCounter; - private final Map failuresByType = new HashMap<>(); - private final Map warningsByType = new HashMap<>(); - - private final boolean detailedMetrics; - private final Map successesByProvider = new HashMap<>(); - private final Map, Counter> failuresByTypeAndProvider = new HashMap<>(); + private final boolean producerMetrics; public StreamingTripUpdateMetrics(UrlUpdaterParameters parameters) { super(parameters); - this.successfulCounter = getCounter("successful", "Total successfully applied trip updates"); - this.failureCounter = getCounter("failed", "Total failed trip updates"); - this.warningsCounter = getCounter("warnings", "Total warnings for successful trip updates"); - this.detailedMetrics = parameters.detailedMetrics(); + this.producerMetrics = parameters.producerMetrics(); } public void setCounters(UpdateResult result) { - this.successfulCounter.increment(result.successful()); - this.failureCounter.increment(result.failed()); - this.warningsCounter.increment(result.warnings().size()); - - setFailures(result); - setWarnings(result); - if (detailedMetrics) { - setFailuresByTypeAndProvider(result); - setSuccessesByProvider(result); - } + incrementWarningCounts(result); + incrementFailureCounts(result); + incrementSuccessCounts(result); } - private void setWarnings(UpdateResult result) { + private void incrementWarningCounts(UpdateResult result) { for (var warningType : result.warnings()) { - var counter = warningsByType.get(warningType); - if (Objects.isNull(counter)) { - counter = - getCounter( - "warning_type", - "Total warnings by type generated by successful trip updates", - Tag.of("warningType", warningType.name()) - ); - warningsByType.put(warningType, counter); - } - counter.increment(); - } - } - - private void setFailures(UpdateResult result) { - for (var errorType : result.failures().keySet()) { - var counter = failuresByType.get(errorType); - if (Objects.isNull(counter)) { - counter = - getCounter( - "failure_type", - "Total failed trip updates by type", - Tag.of("errorType", errorType.name()) - ); - failuresByType.put(errorType, counter); - } - counter.increment(result.failures().get(errorType).size()); + Tags tags = Tags.concat(baseTags, Tags.of("warningType", warningType.name())); + Counter + .builder(METRICS_PREFIX + "." + "warnings") + .description("Total warnings by type generated by successful trip updates") + .tags(tags) + .register(Metrics.globalRegistry) + .increment(); } } - private void setFailuresByTypeAndProvider(UpdateResult result) { - Map, Long> failureCountByTypeAndProvider = result - .errors() - .stream() - .collect( - Collectors.groupingBy( - error -> Pair.of(error.producer(), error.errorType()), - Collectors.counting() - ) - ); - - for (var entry : failureCountByTypeAndProvider.entrySet()) { - Counter counter = failuresByTypeAndProvider.get(entry.getKey()); - if (Objects.isNull(counter)) { - counter = - getCounter( - "failures_by_type_and_provider", - "Total failed trip updates by type and provider", - Tag.of("provider", entry.getKey().getLeft()), - Tag.of("errorType", entry.getKey().getRight().name()) - ); - failuresByTypeAndProvider.put(entry.getKey(), counter); + private void incrementFailureCounts(UpdateResult result) { + for (UpdateError error : result.errors()) { + Tags tags = Tags.concat(baseTags, Tags.of("errorType", error.errorType().name())); + if (producerMetrics) { + tags = tags.and(Tag.of("producer", error.producer())); } - counter.increment(entry.getValue()); + Counter + .builder(METRICS_PREFIX + "." + "failed") + .description("Total failed trip updates") + .tags(tags) + .register(Metrics.globalRegistry) + .increment(); } } - private void setSuccessesByProvider(UpdateResult result) { - Map successCountByProvider = result - .successes() - .stream() - .collect(Collectors.groupingBy(UpdateSuccess::producer, Collectors.counting())); - - for (var entry : successCountByProvider.entrySet()) { - Counter counter = successesByProvider.get(entry.getKey()); - if (Objects.isNull(counter)) { - counter = - getCounter( - "successes_by_provider", - "Total successful trip updates by producer", - Tag.of("producer", entry.getKey()) - ); - successesByProvider.put(entry.getKey(), counter); + private void incrementSuccessCounts(UpdateResult result) { + for (UpdateSuccess success : result.successes()) { + Tags tags = Tags.of(baseTags); + if (producerMetrics) { + tags = tags.and(Tag.of("producer", success.producer())); } - counter.increment(entry.getValue()); + Counter + .builder(METRICS_PREFIX + "." + "successful") + .description("Total successfully applied trip updates") + .tags(tags) + .register(Metrics.globalRegistry) + .increment(); } } - - private Counter getCounter(String name, String description, Tag... tags) { - var finalTags = Tags.concat(Arrays.stream(tags).toList(), baseTags); - return Counter - .builder(METRICS_PREFIX + "." + name) - .description(description) - .tags(finalTags) - .register(Metrics.globalRegistry); - } } diff --git a/doc/user/sandbox/siri/SiriUpdater.md b/doc/user/sandbox/siri/SiriUpdater.md index f63316974f4..5666f41885c 100644 --- a/doc/user/sandbox/siri/SiriUpdater.md +++ b/doc/user/sandbox/siri/SiriUpdater.md @@ -31,11 +31,11 @@ To enable the SIRI updater you need to add it to the updaters section of the `ro |--------------------------------|:---------------:|--------------------------------------------------------------------------------------------------------|:----------:|---------------|:-----:| | type = "siri-et-updater" | `enum` | The type of the updater. | *Required* | | 1.5 | | blockReadinessUntilInitialized | `boolean` | Whether catching up with the updates should block the readiness check from returning a 'ready' result. | *Optional* | `false` | 2.0 | -| detailedMetrics | `boolean` | If detailed metrics should be collected. | *Optional* | `false` | 2.0 | | feedId | `string` | The ID of the feed to apply the updates to. | *Required* | | 2.0 | | frequency | `duration` | How often the updates should be retrieved. | *Optional* | `"PT1M"` | 2.0 | | fuzzyTripMatching | `boolean` | If the fuzzy trip matcher should be used to match trips. | *Optional* | `false` | 2.0 | | previewInterval | `duration` | TODO | *Optional* | | 2.0 | +| producerMetrics | `boolean` | If failure, success, and warning metrics should be collected per producer. | *Optional* | `false` | 2.0 | | requestorRef | `string` | The requester reference. | *Optional* | | 2.0 | | timeout | `duration` | The HTTP timeout to download the updates. | *Optional* | `"PT15S"` | 2.0 | | [url](#u__8__url) | `string` | The URL to send the HTTP requests to. | *Required* | | 2.0 | From b77d591456c6726cac2f548a7e3af23214cc51c4 Mon Sep 17 00:00:00 2001 From: eibakke Date: Tue, 5 Nov 2024 15:12:38 +0100 Subject: [PATCH 101/121] Addresses comments from PR. --- .../config/routerconfig/updaters/SiriETUpdaterConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java index cb38bf0f4b7..1b0eb2e4420 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java @@ -2,6 +2,7 @@ import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_0; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_3; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7; import java.time.Duration; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; @@ -46,7 +47,7 @@ public static SiriETUpdaterParameters create(String configRef, NodeAdapter c) { HttpHeadersConfig.headers(c, V2_3), c .of("producerMetrics") - .since(V2_0) + .since(V2_7) .summary("If failure, success, and warning metrics should be collected per producer.") .asBoolean(false) ); From 0dc13ba3fa2ec07409650ac6381b678d40027375 Mon Sep 17 00:00:00 2001 From: eibakke Date: Tue, 5 Nov 2024 15:13:46 +0100 Subject: [PATCH 102/121] Addresses comments from PR. --- .../updaters/SiriETGooglePubsubUpdaterConfig.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java index b6227b3b0bd..3396e56f6e3 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java @@ -1,6 +1,7 @@ package org.opentripplanner.standalone.config.routerconfig.updaters; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_1; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7; import static org.opentripplanner.updater.siri.updater.google.SiriETGooglePubsubUpdaterParameters.INITIAL_GET_DATA_TIMEOUT; import static org.opentripplanner.updater.siri.updater.google.SiriETGooglePubsubUpdaterParameters.RECONNECT_PERIOD; @@ -81,9 +82,9 @@ If this parameter is set, the updater will be marked as initialized (primed) onl .summary("If the trips should be matched fuzzily.") .asBoolean(false), c - .of("detailedMetrics") - .since(V2_1) - .summary("If detailed metrics should be collected.") + .of("producerMetrics") + .since(V2_7) + .summary("If failure, success, and warning metrics should be collected per producer.") .asBoolean(false) ); } From c35537a191d35e43cbf738b47de092225de081aa Mon Sep 17 00:00:00 2001 From: eibakke Date: Tue, 5 Nov 2024 15:27:37 +0100 Subject: [PATCH 103/121] Addresses comments from PR and regenerates autogenerated documentation. --- .../updater/spi/DataValidationExceptionMapper.java | 3 ++- .../updater/trip/metrics/StreamingTripUpdateMetrics.java | 4 ++-- doc/user/sandbox/siri/SiriGooglePubSubUpdater.md | 2 +- doc/user/sandbox/siri/SiriUpdater.md | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java b/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java index c37660cf9bc..2a650f684f9 100644 --- a/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java @@ -1,5 +1,6 @@ package org.opentripplanner.updater.spi; +import com.beust.jcommander.internal.Nullable; import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.timetable.TimetableValidationError; @@ -20,7 +21,7 @@ public static Result toResult(DataValidationException error) public static Result toResult( DataValidationException error, - String producer + @Nullable String producer ) { if (error.error() instanceof TimetableValidationError tt) { return Result.failure( diff --git a/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java b/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java index 233099e19f0..0660f8d801b 100644 --- a/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java +++ b/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java @@ -51,7 +51,7 @@ private void incrementWarningCounts(UpdateResult result) { private void incrementFailureCounts(UpdateResult result) { for (UpdateError error : result.errors()) { Tags tags = Tags.concat(baseTags, Tags.of("errorType", error.errorType().name())); - if (producerMetrics) { + if (producerMetrics && error.producer() != null) { tags = tags.and(Tag.of("producer", error.producer())); } Counter @@ -66,7 +66,7 @@ private void incrementFailureCounts(UpdateResult result) { private void incrementSuccessCounts(UpdateResult result) { for (UpdateSuccess success : result.successes()) { Tags tags = Tags.of(baseTags); - if (producerMetrics) { + if (producerMetrics && success.producer() != null) { tags = tags.and(Tag.of("producer", success.producer())); } Counter diff --git a/doc/user/sandbox/siri/SiriGooglePubSubUpdater.md b/doc/user/sandbox/siri/SiriGooglePubSubUpdater.md index 0895b2e4dbf..5232696ad9b 100644 --- a/doc/user/sandbox/siri/SiriGooglePubSubUpdater.md +++ b/doc/user/sandbox/siri/SiriGooglePubSubUpdater.md @@ -32,10 +32,10 @@ of the `router-config.json`. |------------------------------------------------------------|:----------:|----------------------------------------------------------------------------------|:----------:|---------------|:-----:| | type = "siri-et-google-pubsub-updater" | `enum` | The type of the updater. | *Required* | | 1.5 | | [dataInitializationUrl](#u__12__dataInitializationUrl) | `string` | URL used to download over HTTP the recent history of SIRI-ET messages. | *Optional* | | 2.1 | -| detailedMetrics | `boolean` | If detailed metrics should be collected. | *Optional* | `false` | 2.1 | | feedId | `string` | The ID of the feed to apply the updates to. | *Optional* | | 2.1 | | fuzzyTripMatching | `boolean` | If the trips should be matched fuzzily. | *Optional* | `false` | 2.1 | | [initialGetDataTimeout](#u__12__initialGetDataTimeout) | `duration` | Timeout for retrieving the recent history of SIRI-ET messages. | *Optional* | `"PT30S"` | 2.1 | +| producerMetrics | `boolean` | If failure, success, and warning metrics should be collected per producer. | *Optional* | `false` | 2.7 | | [reconnectPeriod](#u__12__reconnectPeriod) | `duration` | Wait this amount of time before trying to reconnect to the PubSub subscription. | *Optional* | `"PT30S"` | 2.1 | | [subscriptionProjectName](#u__12__subscriptionProjectName) | `string` | The Google Cloud project that hosts the PubSub subscription. | *Required* | | 2.1 | | topicName | `string` | The name of the PubSub topic that publishes the updates. | *Required* | | 2.1 | diff --git a/doc/user/sandbox/siri/SiriUpdater.md b/doc/user/sandbox/siri/SiriUpdater.md index 5666f41885c..28f2f9a85db 100644 --- a/doc/user/sandbox/siri/SiriUpdater.md +++ b/doc/user/sandbox/siri/SiriUpdater.md @@ -35,7 +35,7 @@ To enable the SIRI updater you need to add it to the updaters section of the `ro | frequency | `duration` | How often the updates should be retrieved. | *Optional* | `"PT1M"` | 2.0 | | fuzzyTripMatching | `boolean` | If the fuzzy trip matcher should be used to match trips. | *Optional* | `false` | 2.0 | | previewInterval | `duration` | TODO | *Optional* | | 2.0 | -| producerMetrics | `boolean` | If failure, success, and warning metrics should be collected per producer. | *Optional* | `false` | 2.0 | +| producerMetrics | `boolean` | If failure, success, and warning metrics should be collected per producer. | *Optional* | `false` | 2.7 | | requestorRef | `string` | The requester reference. | *Optional* | | 2.0 | | timeout | `duration` | The HTTP timeout to download the updates. | *Optional* | `"PT15S"` | 2.0 | | [url](#u__8__url) | `string` | The URL to send the HTTP requests to. | *Required* | | 2.0 | From 5867d699b277cf89b090bd91debbcf3fab105956 Mon Sep 17 00:00:00 2001 From: eibakke Date: Wed, 6 Nov 2024 11:10:27 +0100 Subject: [PATCH 104/121] Allows for dataSource to be null in AddedTripBuilder. --- .../java/org/opentripplanner/updater/siri/AddedTripBuilder.java | 1 - .../main/java/org/opentripplanner/updater/siri/TripUpdate.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/updater/siri/AddedTripBuilder.java b/application/src/main/java/org/opentripplanner/updater/siri/AddedTripBuilder.java index 33d3fb960a4..aff1659653b 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/AddedTripBuilder.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/AddedTripBuilder.java @@ -88,7 +88,6 @@ class AddedTripBuilder { operator = entityResolver.resolveOperator(operatorRef); // DataSource of added trip - Objects.requireNonNull(estimatedVehicleJourney.getDataSource(), "DataSource is required"); dataSource = estimatedVehicleJourney.getDataSource(); // LineRef of added trip diff --git a/application/src/main/java/org/opentripplanner/updater/siri/TripUpdate.java b/application/src/main/java/org/opentripplanner/updater/siri/TripUpdate.java index 4ef5980af42..8432ed06d54 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/TripUpdate.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/TripUpdate.java @@ -31,7 +31,7 @@ record TripUpdate( @Nullable TripOnServiceDate addedTripOnServiceDate, @Nullable TripPattern addedTripPattern, boolean routeCreation, - String dataSource + @Nullable String dataSource ) { public TripUpdate { Objects.requireNonNull(stopPattern); From 51f8e203f1ccc92ce1fbfbb47869770db954f4f2 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 105/121] CircleCI config, release scripts, and pom.xml updates. --- .circleci/config.yml | 52 ++++ .circleci/prepare_release | 235 ++++++++++++++++++ .circleci/release | 86 +++++++ .circleci/update_deployment_config | 46 ++++ .gitignore | 1 + .../src/main/java/EnturUpdatePomVersion.java | 150 +++++++++++ pom.xml | 12 +- 7 files changed, 576 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 application/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..40f7e5af3c7 --- /dev/null +++ b/.circleci/release @@ -0,0 +1,86 @@ +#!/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 +APPLICATION_MODULE=application +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}" + mkdir -p target + git tag -l | grep entur > ${TAGS_FILE} +} + +function setPomVersion() { + echo "" + echo "Update pom.xml with new version" + javac -d ${APPLICATION_MODULE}/target/classes ${APPLICATION_MODULE}/src/main/java/EnturUpdatePomVersion.java + + VERSION="`java -cp ${APPLICATION_MODULE}/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 d67b620f7b1..ff2ed7ae7bb 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .project .pydevproject .settings +.prepare_release.tmp .sonar .nvmrc *~ diff --git a/application/src/main/java/EnturUpdatePomVersion.java b/application/src/main/java/EnturUpdatePomVersion.java new file mode 100644 index 00000000000..cca6cd4925d --- /dev/null +++ b/application/src/main/java/EnturUpdatePomVersion.java @@ -0,0 +1,150 @@ +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +public class EnturUpdatePomVersion { + + private static final String VERSION_SEP = "-"; + private static final String ENTUR_PREFIX = "entur" + VERSION_SEP; + private static final String POM_FILE_NAME = "pom.xml"; + + private final List tags = new ArrayList<>(); + private final List pomFile = new ArrayList<>(); + private String mainVersion; + private int versionNumber = 0; + private String newVersion; + + 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 { + for (Path pom : listPomFiles()) { + readAndReplaceVersion(pom); + replacePomFile(pom); + } + System.out.println(newVersion); + } + + public void readAndReplaceVersion(Path pom) throws IOException { + pomFile.clear(); + 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, 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); + newVersion = mainVersion + VERSION_SEP + ENTUR_PREFIX + resolveVersionNumber(); + line = m.group(1) + newVersion + m.group(4); + 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(Path pom) throws IOException { + Files.delete(pom); + Files.write(pom, 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; + } + + private List listPomFiles() throws IOException { + try (Stream stream = Files.walk(Paths.get(""))) { + return stream + .filter(Files::isRegularFile) + .filter(path -> path.getFileName().toString().equals(POM_FILE_NAME)) + .toList(); + } + } +} diff --git a/pom.xml b/pom.xml index abc70e5dc8c..dcef12a0eba 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ - 170 + EN-0078 32.0 2.52 @@ -86,11 +86,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 + From 620938d8bfdf0117f97a72e53400d12d37493ec5 Mon Sep 17 00:00:00 2001 From: eibakke Date: Thu, 7 Nov 2024 10:27:57 +0100 Subject: [PATCH 106/121] Adds Configuration.md autogen --- doc/user/Configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/Configuration.md b/doc/user/Configuration.md index 533404fe3ea..21976218db3 100644 --- a/doc/user/Configuration.md +++ b/doc/user/Configuration.md @@ -224,7 +224,7 @@ Here is a list of all features which can be toggled on/off and their default val | `APIBikeRental` | Enable the bike rental endpoint. | ✓️ | | | `APIServerInfo` | Enable the server info endpoint. | ✓️ | | | `APIUpdaterStatus` | Enable endpoint for graph updaters status. | ✓️ | | -| `IncludeEmptyRailStopsInTransfers` | Turning this on make sure that Rail stops without scheduled patterns still get included when generating transfers using `ConsiderPatternsForDirectTransfers`. It is common for stops to be assign at realtime for Rail, if so turning this on will help to avoid dropping transfers witch is needed, when the stop become in use later. Turning this on, if ConsiderPatternsForDirectTransfers is off have no effect. | | | +| `IncludeEmptyRailStopsInTransfers` | Turning this on make sure that Rail stops without scheduled patterns still get included when generating transfers using `ConsiderPatternsForDirectTransfers`. It is common for stops to be assign at realtime for Rail, if so turning this on will help to avoid dropping transfers which are needed, when the stop become in use later. Turning this on, if ConsiderPatternsForDirectTransfers is off has no effect. | | | | `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. | ✓️ | | | `ExtraTransferLegOnSameStop` | Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated. | | | From 24f743317c811e894d80a17053b758cae84f91a9 Mon Sep 17 00:00:00 2001 From: eibakke Date: Thu, 7 Nov 2024 10:29:29 +0100 Subject: [PATCH 107/121] Version 2.7.0-entur-11 --- application/pom.xml | 2 +- gtfs-realtime-protobuf/pom.xml | 4 ++-- pom.xml | 2 +- raptor/pom.xml | 2 +- utils/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index 3fc0193d0af..457d01baf62 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-SNAPSHOT + 2.7.0-entur-11 otp OpenTripPlanner - Application diff --git a/gtfs-realtime-protobuf/pom.xml b/gtfs-realtime-protobuf/pom.xml index e4465a4d366..647c5cc2877 100644 --- a/gtfs-realtime-protobuf/pom.xml +++ b/gtfs-realtime-protobuf/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-SNAPSHOT + 2.7.0-entur-11 gtfs-realtime-protobuf OpenTripPlanner - GTFS Realtime (protobuf) @@ -51,4 +51,4 @@ - \ No newline at end of file + diff --git a/pom.xml b/pom.xml index dcef12a0eba..82d1c94b388 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.opentripplanner otp-root - 2.7.0-SNAPSHOT + 2.7.0-entur-11 pom OpenTripPlanner - Root diff --git a/raptor/pom.xml b/raptor/pom.xml index d2d93e36f9d..87422555929 100644 --- a/raptor/pom.xml +++ b/raptor/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-SNAPSHOT + 2.7.0-entur-11 otp-raptor diff --git a/utils/pom.xml b/utils/pom.xml index 7a223488dd0..6a2209de9cb 100644 --- a/utils/pom.xml +++ b/utils/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-SNAPSHOT + 2.7.0-entur-11 otp-utils From 3b5db5a2a6d57e2da8859820b0606465c1f47aa5 Mon Sep 17 00:00:00 2001 From: eibakke Date: Thu, 7 Nov 2024 10:39:08 +0100 Subject: [PATCH 108/121] Comments out go-offline command in .circlci config. --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8931a1bb608..77b94685fb2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,7 +31,7 @@ jobs: - 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 +# - run: mvn org.apache.maven.plugins:maven-dependency-plugin:3.1.0:go-offline -s .circleci/settings.xml - save_cache: paths: - ~/.m2 From 2778d8beed498162a763f0a59c19b34cf8694191 Mon Sep 17 00:00:00 2001 From: eibakke Date: Thu, 7 Nov 2024 10:40:23 +0100 Subject: [PATCH 109/121] Version 2.7.0-entur-12 --- application/pom.xml | 2 +- gtfs-realtime-protobuf/pom.xml | 2 +- pom.xml | 2 +- raptor/pom.xml | 2 +- utils/pom.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index 457d01baf62..085472c749b 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-entur-11 + 2.7.0-entur-12 otp OpenTripPlanner - Application diff --git a/gtfs-realtime-protobuf/pom.xml b/gtfs-realtime-protobuf/pom.xml index 647c5cc2877..bcb22c887cc 100644 --- a/gtfs-realtime-protobuf/pom.xml +++ b/gtfs-realtime-protobuf/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-entur-11 + 2.7.0-entur-12 gtfs-realtime-protobuf OpenTripPlanner - GTFS Realtime (protobuf) diff --git a/pom.xml b/pom.xml index 82d1c94b388..3ae62b1a90c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.opentripplanner otp-root - 2.7.0-entur-11 + 2.7.0-entur-12 pom OpenTripPlanner - Root diff --git a/raptor/pom.xml b/raptor/pom.xml index 87422555929..9a0371f0704 100644 --- a/raptor/pom.xml +++ b/raptor/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-entur-11 + 2.7.0-entur-12 otp-raptor diff --git a/utils/pom.xml b/utils/pom.xml index 6a2209de9cb..1bb0d7bceb9 100644 --- a/utils/pom.xml +++ b/utils/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-entur-11 + 2.7.0-entur-12 otp-utils From 5e470c151a0517533b9b089e39cbc146613c7abb Mon Sep 17 00:00:00 2001 From: eibakke Date: Thu, 7 Nov 2024 10:51:28 +0100 Subject: [PATCH 110/121] Tries solving go-offline issue by upgrading maven-dependency-plugin to 3.1.2 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8931a1bb608..0b12e18e8a3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,7 +31,7 @@ jobs: - 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 + - run: mvn org.apache.maven.plugins:maven-dependency-plugin:3.1.2:go-offline -s .circleci/settings.xml - save_cache: paths: - ~/.m2 From 87d2fb0cad0be497843a99b51a44075f17e461d4 Mon Sep 17 00:00:00 2001 From: eibakke Date: Thu, 7 Nov 2024 10:52:39 +0100 Subject: [PATCH 111/121] Version 2.7.0-entur-13 --- application/pom.xml | 2 +- gtfs-realtime-protobuf/pom.xml | 2 +- pom.xml | 2 +- raptor/pom.xml | 2 +- utils/pom.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index 457d01baf62..cb6b688872a 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-entur-11 + 2.7.0-entur-13 otp OpenTripPlanner - Application diff --git a/gtfs-realtime-protobuf/pom.xml b/gtfs-realtime-protobuf/pom.xml index 647c5cc2877..d452acb450c 100644 --- a/gtfs-realtime-protobuf/pom.xml +++ b/gtfs-realtime-protobuf/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-entur-11 + 2.7.0-entur-13 gtfs-realtime-protobuf OpenTripPlanner - GTFS Realtime (protobuf) diff --git a/pom.xml b/pom.xml index 82d1c94b388..c2fd48e3eda 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.opentripplanner otp-root - 2.7.0-entur-11 + 2.7.0-entur-13 pom OpenTripPlanner - Root diff --git a/raptor/pom.xml b/raptor/pom.xml index 87422555929..f7cb0d5a398 100644 --- a/raptor/pom.xml +++ b/raptor/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-entur-11 + 2.7.0-entur-13 otp-raptor diff --git a/utils/pom.xml b/utils/pom.xml index 6a2209de9cb..dc8757e5ffd 100644 --- a/utils/pom.xml +++ b/utils/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-entur-11 + 2.7.0-entur-13 otp-utils From 671dc9b143af404b122615df3b9c2572b9ce0527 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 112/121] CircleCI config, release scripts, and pom.xml updates. --- .circleci/config.yml | 52 ++++ .circleci/prepare_release | 235 ++++++++++++++++++ .circleci/release | 86 +++++++ .circleci/update_deployment_config | 46 ++++ .gitignore | 1 + .../src/main/java/EnturUpdatePomVersion.java | 150 +++++++++++ pom.xml | 12 +- 7 files changed, 576 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 application/src/main/java/EnturUpdatePomVersion.java diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000000..0b12e18e8a3 --- /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.2: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..40f7e5af3c7 --- /dev/null +++ b/.circleci/release @@ -0,0 +1,86 @@ +#!/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 +APPLICATION_MODULE=application +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}" + mkdir -p target + git tag -l | grep entur > ${TAGS_FILE} +} + +function setPomVersion() { + echo "" + echo "Update pom.xml with new version" + javac -d ${APPLICATION_MODULE}/target/classes ${APPLICATION_MODULE}/src/main/java/EnturUpdatePomVersion.java + + VERSION="`java -cp ${APPLICATION_MODULE}/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 d67b620f7b1..ff2ed7ae7bb 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .project .pydevproject .settings +.prepare_release.tmp .sonar .nvmrc *~ diff --git a/application/src/main/java/EnturUpdatePomVersion.java b/application/src/main/java/EnturUpdatePomVersion.java new file mode 100644 index 00000000000..cca6cd4925d --- /dev/null +++ b/application/src/main/java/EnturUpdatePomVersion.java @@ -0,0 +1,150 @@ +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +public class EnturUpdatePomVersion { + + private static final String VERSION_SEP = "-"; + private static final String ENTUR_PREFIX = "entur" + VERSION_SEP; + private static final String POM_FILE_NAME = "pom.xml"; + + private final List tags = new ArrayList<>(); + private final List pomFile = new ArrayList<>(); + private String mainVersion; + private int versionNumber = 0; + private String newVersion; + + 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 { + for (Path pom : listPomFiles()) { + readAndReplaceVersion(pom); + replacePomFile(pom); + } + System.out.println(newVersion); + } + + public void readAndReplaceVersion(Path pom) throws IOException { + pomFile.clear(); + 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, 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); + newVersion = mainVersion + VERSION_SEP + ENTUR_PREFIX + resolveVersionNumber(); + line = m.group(1) + newVersion + m.group(4); + 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(Path pom) throws IOException { + Files.delete(pom); + Files.write(pom, 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; + } + + private List listPomFiles() throws IOException { + try (Stream stream = Files.walk(Paths.get(""))) { + return stream + .filter(Files::isRegularFile) + .filter(path -> path.getFileName().toString().equals(POM_FILE_NAME)) + .toList(); + } + } +} diff --git a/pom.xml b/pom.xml index abc70e5dc8c..dcef12a0eba 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ - 170 + EN-0078 32.0 2.52 @@ -86,11 +86,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 + From 25a93cbf25629e439e4a60a607b15298924704e0 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 7 Nov 2024 14:50:35 +0100 Subject: [PATCH 113/121] Version 2.7.0-entur-14 --- application/pom.xml | 2 +- gtfs-realtime-protobuf/pom.xml | 2 +- pom.xml | 2 +- raptor/pom.xml | 2 +- utils/pom.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index cb6b688872a..889e65ed68e 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-entur-13 + 2.7.0-entur-14 otp OpenTripPlanner - Application diff --git a/gtfs-realtime-protobuf/pom.xml b/gtfs-realtime-protobuf/pom.xml index d452acb450c..9888fd5a1eb 100644 --- a/gtfs-realtime-protobuf/pom.xml +++ b/gtfs-realtime-protobuf/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-entur-13 + 2.7.0-entur-14 gtfs-realtime-protobuf OpenTripPlanner - GTFS Realtime (protobuf) diff --git a/pom.xml b/pom.xml index c2fd48e3eda..3adf80b44e9 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.opentripplanner otp-root - 2.7.0-entur-13 + 2.7.0-entur-14 pom OpenTripPlanner - Root diff --git a/raptor/pom.xml b/raptor/pom.xml index f7cb0d5a398..346453d47d2 100644 --- a/raptor/pom.xml +++ b/raptor/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-entur-13 + 2.7.0-entur-14 otp-raptor diff --git a/utils/pom.xml b/utils/pom.xml index dc8757e5ffd..3ed228eaea4 100644 --- a/utils/pom.xml +++ b/utils/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-entur-13 + 2.7.0-entur-14 otp-utils From 54e67d1d3fb0764e997ab0682270b12dbce9289f Mon Sep 17 00:00:00 2001 From: Zsombor Welker Date: Thu, 14 Nov 2024 10:51:55 +0100 Subject: [PATCH 114/121] Set transit-group-priority for requests without via points --- .../transit/mappers/RaptorRequestMapper.java | 10 ++++-- .../mappers/RaptorRequestMapperTest.java | 32 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index 1074724a90c..7db4c4e6dd1 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -199,11 +199,17 @@ private RaptorRequest doMap() { } private boolean hasPassThroughOnly() { - return request.getViaLocations().stream().allMatch(ViaLocation::isPassThroughLocation); + return ( + request.isViaSearch() && + request.getViaLocations().stream().allMatch(ViaLocation::isPassThroughLocation) + ); } private boolean hasViaLocationsOnly() { - return request.getViaLocations().stream().noneMatch(ViaLocation::isPassThroughLocation); + return ( + request.isViaSearch() && + request.getViaLocations().stream().noneMatch(ViaLocation::isPassThroughLocation) + ); } private boolean hasViaLocationsAndPassThroughLocations() { diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java index a8e00056a3f..9eaf44d7748 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java @@ -1,6 +1,7 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.Duration; @@ -90,6 +91,37 @@ void testPassThroughPoints() { ); } + @Test + void testTransitGroupPriority() { + var req = new RouteRequest(); + + // Set relax transit-group-priority + req.withPreferences(p -> + p.withTransit(t -> t.withRelaxTransitGroupPriority(CostLinearFunction.of("30m + 1.2t"))) + ); + + var result = map(req); + + assertFalse(result.multiCriteria().transitPriorityCalculator().isEmpty()); + } + + @Test + void testVisitViaAllowsTransitGroupPriority() { + var req = new RouteRequest(); + + // Set visit-via and relax transit-group-priority + req.setViaLocations( + List.of(new VisitViaLocation("Via A", null, List.of(STOP_A.getId()), List.of())) + ); + req.withPreferences(p -> + p.withTransit(t -> t.withRelaxTransitGroupPriority(CostLinearFunction.of("30m + 1.2t"))) + ); + + var result = map(req); + + assertFalse(result.multiCriteria().transitPriorityCalculator().isEmpty()); + } + @Test void testPassThroughPointsTurnTransitGroupPriorityOff() { var req = new RouteRequest(); From 12ca8a4484f94c56bc08616a19252484058b533f Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 14 Nov 2024 22:46:27 +0100 Subject: [PATCH 115/121] Only allow the following combinations of transit-group-priority and via-visit, and any combination of via-pass-through and relaxed-cost. --- .../transit/mappers/RaptorRequestMapper.java | 31 ++-- .../routing/api/request/RouteRequest.java | 3 +- .../preference/TransitPreferences.java | 4 + .../mappers/RaptorRequestMapperTest.java | 165 +++++++++++++++--- .../preference/TransitPreferencesTest.java | 6 + 5 files changed, 173 insertions(+), 36 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index 7db4c4e6dd1..da948038828 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -15,6 +15,7 @@ import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.model.RelaxFunction; import org.opentripplanner.raptor.api.request.DebugRequestBuilder; +import org.opentripplanner.raptor.api.request.MultiCriteriaRequest; import org.opentripplanner.raptor.api.request.Optimization; import org.opentripplanner.raptor.api.request.PassThroughPoint; import org.opentripplanner.raptor.api.request.RaptorRequest; @@ -26,6 +27,7 @@ import org.opentripplanner.routing.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; +import org.opentripplanner.routing.api.request.preference.TransitPreferences; import org.opentripplanner.routing.api.request.via.ViaLocation; import org.opentripplanner.transit.model.network.grouppriority.DefaultTransitGroupPriorityCalculator; @@ -128,15 +130,20 @@ private RaptorRequest doMap() { var pt = preferences.transit(); var r = pt.raptor(); - // Note! If a pass-through-point exists, then the transit-group-priority feature is disabled - - // TODO - We need to handle via locations that are not pass-through-points here if (hasPassThroughOnly()) { mcBuilder.withPassThroughPoints(mapPassThroughPoints()); + } else if (hasViaLocationsOnly()) { + builder.searchParams().addViaLocations(mapViaLocations()); + // relax transit group priority can be used with via-visit-stop, but not with pass-through + if (pt.isRelaxTransitGroupPrioritySet()) { + mapRelaxTransitGroupPriority(mcBuilder, pt); + } + } else if (pt.isRelaxTransitGroupPrioritySet()) { + mapRelaxTransitGroupPriority(mcBuilder, pt); + } else { + // The deprecated relaxGeneralizedCostAtDestination is only enabled, if there is no + // via location and the relaxTransitGroupPriority is not used (Normal). r.relaxGeneralizedCostAtDestination().ifPresent(mcBuilder::withRelaxCostAtDestination); - } else if (!pt.relaxTransitGroupPriority().isNormal()) { - mcBuilder.withTransitPriorityCalculator(new DefaultTransitGroupPriorityCalculator()); - mcBuilder.withRelaxC1(mapRelaxCost(pt.relaxTransitGroupPriority())); } }); @@ -160,10 +167,6 @@ private RaptorRequest doMap() { .addAccessPaths(accessPaths) .addEgressPaths(egressPaths); - if (hasViaLocationsOnly()) { - builder.searchParams().addViaLocations(mapViaLocations()); - } - var raptorDebugging = request.journey().transit().raptorDebugging(); if (raptorDebugging.isEnabled()) { @@ -271,6 +274,14 @@ private int relativeTime(Instant time) { return (int) (time.getEpochSecond() - transitSearchTimeZeroEpocSecond); } + private static void mapRelaxTransitGroupPriority( + MultiCriteriaRequest.Builder mcBuilder, + TransitPreferences pt + ) { + mcBuilder.withTransitPriorityCalculator(new DefaultTransitGroupPriorityCalculator()); + mcBuilder.withRelaxC1(mapRelaxCost(pt.relaxTransitGroupPriority())); + } + private static void addLogListenerForEachEventTypeRequested( DebugRequestBuilder target, DebugEventType type, diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index 56ba39d8431..2b0c3fd6b3d 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -289,8 +289,9 @@ public List getViaLocations() { return via; } - public void setViaLocations(final List via) { + public RouteRequest setViaLocations(final List via) { this.via = via; + return this; } /** diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java b/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java index 78f30277e72..464ebd49b97 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java @@ -137,6 +137,10 @@ public CostLinearFunction relaxTransitGroupPriority() { return relaxTransitGroupPriority; } + public boolean isRelaxTransitGroupPrioritySet() { + return !relaxTransitGroupPriority.isNormal(); + } + /** * When true, real-time updates are ignored during this search. */ diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java index 9eaf44d7748..b52628208af 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java @@ -3,12 +3,18 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RaptorRequestMapperTest.RequestFeature.RELAX_COST_DEST; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RaptorRequestMapperTest.RequestFeature.TRANSIT_GROUP_PRIORITY; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RaptorRequestMapperTest.RequestFeature.VIA_PASS_THROUGH; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RaptorRequestMapperTest.RequestFeature.VIA_VISIT; import java.time.Duration; import java.time.ZonedDateTime; import java.util.List; import java.util.Map; import java.util.stream.IntStream; +import javax.annotation.Nullable; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -24,11 +30,22 @@ import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.StopLocation; +import org.opentripplanner.utils.collection.ListUtils; class RaptorRequestMapperTest { private static final TimetableRepositoryForTest TEST_MODEL = TimetableRepositoryForTest.of(); private static final StopLocation STOP_A = TEST_MODEL.stop("Stop:A").build(); + public static final PassThroughViaLocation PASS_THROUGH_VIA_LOCATION = new PassThroughViaLocation( + "Via A", + List.of(STOP_A.getId()) + ); + public static final VisitViaLocation VISIT_VIA_LOCATION = new VisitViaLocation( + "Via A", + null, + List.of(STOP_A.getId()), + List.of() + ); private static final List ACCESS = List.of(TestAccessEgress.walk(12, 45)); private static final List EGRESS = List.of(TestAccessEgress.walk(144, 54)); @@ -37,6 +54,10 @@ class RaptorRequestMapperTest { private static final CostLinearFunction R3 = CostLinearFunction.of("30 + 2.0x"); private static final Map STOPS_MAP = Map.of(STOP_A.getId(), STOP_A); + private static final CostLinearFunction RELAX_TRANSIT_GROUP_PRIORITY = CostLinearFunction.of( + "30m + 1.2t" + ); + public static final double RELAX_GENERALIZED_COST_AT_DESTINATION = 2.0; static List testCasesRelaxedCost() { return List.of( @@ -102,40 +123,67 @@ void testTransitGroupPriority() { var result = map(req); - assertFalse(result.multiCriteria().transitPriorityCalculator().isEmpty()); + assertTrue(result.multiCriteria().transitPriorityCalculator().isPresent()); } - @Test - void testVisitViaAllowsTransitGroupPriority() { - var req = new RouteRequest(); - - // Set visit-via and relax transit-group-priority - req.setViaLocations( - List.of(new VisitViaLocation("Via A", null, List.of(STOP_A.getId()), List.of())) - ); - req.withPreferences(p -> - p.withTransit(t -> t.withRelaxTransitGroupPriority(CostLinearFunction.of("30m + 1.2t"))) + static List testViaAndTransitGroupPriorityCombinationsTestCases() { + return List.of( + Arguments.of(List.of(VIA_VISIT), List.of(VIA_VISIT), null), + Arguments.of(List.of(VIA_PASS_THROUGH), List.of(VIA_PASS_THROUGH), null), + Arguments.of(List.of(TRANSIT_GROUP_PRIORITY), List.of(TRANSIT_GROUP_PRIORITY), null), + Arguments.of(List.of(RELAX_COST_DEST), List.of(RELAX_COST_DEST), null), + Arguments.of( + List.of(VIA_VISIT, TRANSIT_GROUP_PRIORITY), + List.of(VIA_VISIT, TRANSIT_GROUP_PRIORITY), + null + ), + Arguments.of( + List.of(VIA_PASS_THROUGH, TRANSIT_GROUP_PRIORITY, RELAX_COST_DEST), + List.of(VIA_PASS_THROUGH), + null + ), + Arguments.of( + List.of(VIA_VISIT, TRANSIT_GROUP_PRIORITY, RELAX_COST_DEST), + List.of(VIA_VISIT, TRANSIT_GROUP_PRIORITY), + null + ), + Arguments.of( + List.of(TRANSIT_GROUP_PRIORITY, RELAX_COST_DEST), + List.of(TRANSIT_GROUP_PRIORITY), + null + ), + Arguments.of( + List.of(VIA_VISIT, VIA_PASS_THROUGH), + List.of(), + "A mix of via-locations and pass-through is not allowed in this version." + ) ); - - var result = map(req); - - assertFalse(result.multiCriteria().transitPriorityCalculator().isEmpty()); } - @Test - void testPassThroughPointsTurnTransitGroupPriorityOff() { + @ParameterizedTest + @MethodSource("testViaAndTransitGroupPriorityCombinationsTestCases") + void testViaAndTransitGroupPriorityCombinations( + List input, + List expectedEnabledFeatures, + @Nullable String errorMessage + ) { var req = new RouteRequest(); - // Set pass-through and relax transit-group-priority - req.setViaLocations(List.of(new PassThroughViaLocation("Via A", List.of(STOP_A.getId())))); - req.withPreferences(p -> - p.withTransit(t -> t.withRelaxTransitGroupPriority(CostLinearFunction.of("30m + 1.2t"))) - ); + for (RequestFeature it : input) { + req = setFeaturesOnRequest(req, it); + } - var result = map(req); + if (errorMessage == null) { + var result = map(req); - // transit-group-priority CANNOT be used with pass-through and is turned off... - assertTrue(result.multiCriteria().transitPriorityCalculator().isEmpty()); + for (var feature : RequestFeature.values()) { + assertFeatureSet(feature, result, expectedEnabledFeatures.contains(feature)); + } + } else { + var r = req; + var ex = Assertions.assertThrows(IllegalArgumentException.class, () -> map(r)); + assertEquals(errorMessage, ex.getMessage()); + } } private static RaptorRequest map(RouteRequest request) { @@ -149,4 +197,71 @@ private static RaptorRequest map(RouteRequest request) { id -> IntStream.of(STOPS_MAP.get(id).getIndex()) ); } + + private static void assertFeatureSet( + RequestFeature feature, + RaptorRequest result, + boolean expected + ) { + switch (feature) { + case VIA_VISIT: + if (expected) { + assertTrue(result.searchParams().hasViaLocations()); + // One via location exist(no NPE), but it does not allow pass-through + assertEquals( + "Via{label: Via A, connections: [0]}", + result.searchParams().viaLocations().get(0).toString() + ); + } + break; + case VIA_PASS_THROUGH: + if (expected) { + assertTrue(result.multiCriteria().hasPassThroughPoints()); + assertEquals( + "(Via A, stops: 0)", + result.multiCriteria().passThroughPoints().get(0).toString() + ); + } + break; + case TRANSIT_GROUP_PRIORITY: + assertEquals(expected, result.multiCriteria().transitPriorityCalculator().isPresent()); + if (expected) { + assertFalse(result.multiCriteria().hasPassThroughPoints()); + } + break; + case RELAX_COST_DEST: + assertEquals(expected, result.multiCriteria().relaxCostAtDestination() != null); + if (expected) { + assertFalse(result.multiCriteria().hasPassThroughPoints()); + assertFalse(result.searchParams().hasViaLocations()); + } + break; + } + } + + private static RouteRequest setFeaturesOnRequest(RouteRequest req, RequestFeature feature) { + return switch (feature) { + case VIA_VISIT -> req.setViaLocations(List.of(VISIT_VIA_LOCATION)); + case VIA_PASS_THROUGH -> req.setViaLocations( + ListUtils.combine(req.getViaLocations(), List.of(PASS_THROUGH_VIA_LOCATION)) + ); + case TRANSIT_GROUP_PRIORITY -> req.withPreferences(p -> + p.withTransit(t -> t.withRelaxTransitGroupPriority(RELAX_TRANSIT_GROUP_PRIORITY)) + ); + case RELAX_COST_DEST -> req.withPreferences(p -> + p.withTransit(t -> + t.withRaptor(r -> + r.withRelaxGeneralizedCostAtDestination(RELAX_GENERALIZED_COST_AT_DESTINATION) + ) + ) + ); + }; + } + + enum RequestFeature { + VIA_VISIT, + VIA_PASS_THROUGH, + TRANSIT_GROUP_PRIORITY, + RELAX_COST_DEST, + } } diff --git a/application/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java b/application/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java index b77a7d9085b..bcdfcfc545c 100644 --- a/application/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java +++ b/application/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java @@ -81,6 +81,12 @@ void relaxTransitGroupPriority() { assertEquals(TRANSIT_GROUP_PRIORITY_RELAX, subject.relaxTransitGroupPriority()); } + @Test + void isRelaxTransitGroupPrioritySet() { + assertTrue(subject.isRelaxTransitGroupPrioritySet()); + assertFalse(TransitPreferences.DEFAULT.isRelaxTransitGroupPrioritySet()); + } + @Test void ignoreRealtimeUpdates() { assertFalse(TransitPreferences.DEFAULT.ignoreRealtimeUpdates()); From 7fce88686b7cea2925de1cf2f06d3296346e8268 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 15 Nov 2024 10:38:08 +0100 Subject: [PATCH 116/121] Version 2.7.0-entur-15 --- application/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index ecf0e0bd41a..ddc0fa3d656 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-entur-10 + 2.7.0-entur-15 otp OpenTripPlanner - Application diff --git a/pom.xml b/pom.xml index 866692ec8ec..ffe241e18f4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.opentripplanner otp-root - 2.7.0-entur-10 + 2.7.0-entur-15 pom OpenTripPlanner - Root From 9c5ad29bc591e55a43978a8284380d99f651bb20 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 14 Nov 2024 22:46:27 +0100 Subject: [PATCH 117/121] Only allow the following combinations of transit-group-priority and via-visit, and any combination of via-pass-through and relaxed-cost. --- .../transit/mappers/RaptorRequestMapper.java | 31 ++-- .../routing/api/request/RouteRequest.java | 3 +- .../preference/TransitPreferences.java | 4 + .../mappers/RaptorRequestMapperTest.java | 166 +++++++++++++++--- .../preference/TransitPreferencesTest.java | 6 + 5 files changed, 174 insertions(+), 36 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index 7db4c4e6dd1..da948038828 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -15,6 +15,7 @@ import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.model.RelaxFunction; import org.opentripplanner.raptor.api.request.DebugRequestBuilder; +import org.opentripplanner.raptor.api.request.MultiCriteriaRequest; import org.opentripplanner.raptor.api.request.Optimization; import org.opentripplanner.raptor.api.request.PassThroughPoint; import org.opentripplanner.raptor.api.request.RaptorRequest; @@ -26,6 +27,7 @@ import org.opentripplanner.routing.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; +import org.opentripplanner.routing.api.request.preference.TransitPreferences; import org.opentripplanner.routing.api.request.via.ViaLocation; import org.opentripplanner.transit.model.network.grouppriority.DefaultTransitGroupPriorityCalculator; @@ -128,15 +130,20 @@ private RaptorRequest doMap() { var pt = preferences.transit(); var r = pt.raptor(); - // Note! If a pass-through-point exists, then the transit-group-priority feature is disabled - - // TODO - We need to handle via locations that are not pass-through-points here if (hasPassThroughOnly()) { mcBuilder.withPassThroughPoints(mapPassThroughPoints()); + } else if (hasViaLocationsOnly()) { + builder.searchParams().addViaLocations(mapViaLocations()); + // relax transit group priority can be used with via-visit-stop, but not with pass-through + if (pt.isRelaxTransitGroupPrioritySet()) { + mapRelaxTransitGroupPriority(mcBuilder, pt); + } + } else if (pt.isRelaxTransitGroupPrioritySet()) { + mapRelaxTransitGroupPriority(mcBuilder, pt); + } else { + // The deprecated relaxGeneralizedCostAtDestination is only enabled, if there is no + // via location and the relaxTransitGroupPriority is not used (Normal). r.relaxGeneralizedCostAtDestination().ifPresent(mcBuilder::withRelaxCostAtDestination); - } else if (!pt.relaxTransitGroupPriority().isNormal()) { - mcBuilder.withTransitPriorityCalculator(new DefaultTransitGroupPriorityCalculator()); - mcBuilder.withRelaxC1(mapRelaxCost(pt.relaxTransitGroupPriority())); } }); @@ -160,10 +167,6 @@ private RaptorRequest doMap() { .addAccessPaths(accessPaths) .addEgressPaths(egressPaths); - if (hasViaLocationsOnly()) { - builder.searchParams().addViaLocations(mapViaLocations()); - } - var raptorDebugging = request.journey().transit().raptorDebugging(); if (raptorDebugging.isEnabled()) { @@ -271,6 +274,14 @@ private int relativeTime(Instant time) { return (int) (time.getEpochSecond() - transitSearchTimeZeroEpocSecond); } + private static void mapRelaxTransitGroupPriority( + MultiCriteriaRequest.Builder mcBuilder, + TransitPreferences pt + ) { + mcBuilder.withTransitPriorityCalculator(new DefaultTransitGroupPriorityCalculator()); + mcBuilder.withRelaxC1(mapRelaxCost(pt.relaxTransitGroupPriority())); + } + private static void addLogListenerForEachEventTypeRequested( DebugRequestBuilder target, DebugEventType type, diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index 56ba39d8431..2b0c3fd6b3d 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -289,8 +289,9 @@ public List getViaLocations() { return via; } - public void setViaLocations(final List via) { + public RouteRequest setViaLocations(final List via) { this.via = via; + return this; } /** diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java b/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java index 78f30277e72..464ebd49b97 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java @@ -137,6 +137,10 @@ public CostLinearFunction relaxTransitGroupPriority() { return relaxTransitGroupPriority; } + public boolean isRelaxTransitGroupPrioritySet() { + return !relaxTransitGroupPriority.isNormal(); + } + /** * When true, real-time updates are ignored during this search. */ diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java index 9eaf44d7748..f0fa58b152a 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java @@ -3,16 +3,23 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RaptorRequestMapperTest.RequestFeature.RELAX_COST_DEST; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RaptorRequestMapperTest.RequestFeature.TRANSIT_GROUP_PRIORITY; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RaptorRequestMapperTest.RequestFeature.VIA_PASS_THROUGH; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RaptorRequestMapperTest.RequestFeature.VIA_VISIT; import java.time.Duration; import java.time.ZonedDateTime; import java.util.List; import java.util.Map; import java.util.stream.IntStream; +import javax.annotation.Nullable; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.framework.collection.ListUtils; import org.opentripplanner.raptor._data.transit.TestAccessEgress; import org.opentripplanner.raptor._data.transit.TestTripSchedule; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; @@ -25,10 +32,21 @@ import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.StopLocation; + class RaptorRequestMapperTest { private static final TimetableRepositoryForTest TEST_MODEL = TimetableRepositoryForTest.of(); private static final StopLocation STOP_A = TEST_MODEL.stop("Stop:A").build(); + public static final PassThroughViaLocation PASS_THROUGH_VIA_LOCATION = new PassThroughViaLocation( + "Via A", + List.of(STOP_A.getId()) + ); + public static final VisitViaLocation VISIT_VIA_LOCATION = new VisitViaLocation( + "Via A", + null, + List.of(STOP_A.getId()), + List.of() + ); private static final List ACCESS = List.of(TestAccessEgress.walk(12, 45)); private static final List EGRESS = List.of(TestAccessEgress.walk(144, 54)); @@ -37,6 +55,10 @@ class RaptorRequestMapperTest { private static final CostLinearFunction R3 = CostLinearFunction.of("30 + 2.0x"); private static final Map STOPS_MAP = Map.of(STOP_A.getId(), STOP_A); + private static final CostLinearFunction RELAX_TRANSIT_GROUP_PRIORITY = CostLinearFunction.of( + "30m + 1.2t" + ); + public static final double RELAX_GENERALIZED_COST_AT_DESTINATION = 2.0; static List testCasesRelaxedCost() { return List.of( @@ -102,40 +124,67 @@ void testTransitGroupPriority() { var result = map(req); - assertFalse(result.multiCriteria().transitPriorityCalculator().isEmpty()); + assertTrue(result.multiCriteria().transitPriorityCalculator().isPresent()); } - @Test - void testVisitViaAllowsTransitGroupPriority() { - var req = new RouteRequest(); - - // Set visit-via and relax transit-group-priority - req.setViaLocations( - List.of(new VisitViaLocation("Via A", null, List.of(STOP_A.getId()), List.of())) - ); - req.withPreferences(p -> - p.withTransit(t -> t.withRelaxTransitGroupPriority(CostLinearFunction.of("30m + 1.2t"))) + static List testViaAndTransitGroupPriorityCombinationsTestCases() { + return List.of( + Arguments.of(List.of(VIA_VISIT), List.of(VIA_VISIT), null), + Arguments.of(List.of(VIA_PASS_THROUGH), List.of(VIA_PASS_THROUGH), null), + Arguments.of(List.of(TRANSIT_GROUP_PRIORITY), List.of(TRANSIT_GROUP_PRIORITY), null), + Arguments.of(List.of(RELAX_COST_DEST), List.of(RELAX_COST_DEST), null), + Arguments.of( + List.of(VIA_VISIT, TRANSIT_GROUP_PRIORITY), + List.of(VIA_VISIT, TRANSIT_GROUP_PRIORITY), + null + ), + Arguments.of( + List.of(VIA_PASS_THROUGH, TRANSIT_GROUP_PRIORITY, RELAX_COST_DEST), + List.of(VIA_PASS_THROUGH), + null + ), + Arguments.of( + List.of(VIA_VISIT, TRANSIT_GROUP_PRIORITY, RELAX_COST_DEST), + List.of(VIA_VISIT, TRANSIT_GROUP_PRIORITY), + null + ), + Arguments.of( + List.of(TRANSIT_GROUP_PRIORITY, RELAX_COST_DEST), + List.of(TRANSIT_GROUP_PRIORITY), + null + ), + Arguments.of( + List.of(VIA_VISIT, VIA_PASS_THROUGH), + List.of(), + "A mix of via-locations and pass-through is not allowed in this version." + ) ); - - var result = map(req); - - assertFalse(result.multiCriteria().transitPriorityCalculator().isEmpty()); } - @Test - void testPassThroughPointsTurnTransitGroupPriorityOff() { + @ParameterizedTest + @MethodSource("testViaAndTransitGroupPriorityCombinationsTestCases") + void testViaAndTransitGroupPriorityCombinations( + List input, + List expectedEnabledFeatures, + @Nullable String errorMessage + ) { var req = new RouteRequest(); - // Set pass-through and relax transit-group-priority - req.setViaLocations(List.of(new PassThroughViaLocation("Via A", List.of(STOP_A.getId())))); - req.withPreferences(p -> - p.withTransit(t -> t.withRelaxTransitGroupPriority(CostLinearFunction.of("30m + 1.2t"))) - ); + for (RequestFeature it : input) { + req = setFeaturesOnRequest(req, it); + } - var result = map(req); + if (errorMessage == null) { + var result = map(req); - // transit-group-priority CANNOT be used with pass-through and is turned off... - assertTrue(result.multiCriteria().transitPriorityCalculator().isEmpty()); + for (var feature : RequestFeature.values()) { + assertFeatureSet(feature, result, expectedEnabledFeatures.contains(feature)); + } + } else { + var r = req; + var ex = Assertions.assertThrows(IllegalArgumentException.class, () -> map(r)); + assertEquals(errorMessage, ex.getMessage()); + } } private static RaptorRequest map(RouteRequest request) { @@ -149,4 +198,71 @@ private static RaptorRequest map(RouteRequest request) { id -> IntStream.of(STOPS_MAP.get(id).getIndex()) ); } + + private static void assertFeatureSet( + RequestFeature feature, + RaptorRequest result, + boolean expected + ) { + switch (feature) { + case VIA_VISIT: + if (expected) { + assertTrue(result.searchParams().hasViaLocations()); + // One via location exist(no NPE), but it does not allow pass-through + assertEquals( + "Via{label: Via A, connections: [0]}", + result.searchParams().viaLocations().get(0).toString() + ); + } + break; + case VIA_PASS_THROUGH: + if (expected) { + assertTrue(result.multiCriteria().hasPassThroughPoints()); + assertEquals( + "(Via A, stops: 0)", + result.multiCriteria().passThroughPoints().get(0).toString() + ); + } + break; + case TRANSIT_GROUP_PRIORITY: + assertEquals(expected, result.multiCriteria().transitPriorityCalculator().isPresent()); + if (expected) { + assertFalse(result.multiCriteria().hasPassThroughPoints()); + } + break; + case RELAX_COST_DEST: + assertEquals(expected, result.multiCriteria().relaxCostAtDestination() != null); + if (expected) { + assertFalse(result.multiCriteria().hasPassThroughPoints()); + assertFalse(result.searchParams().hasViaLocations()); + } + break; + } + } + + private static RouteRequest setFeaturesOnRequest(RouteRequest req, RequestFeature feature) { + return switch (feature) { + case VIA_VISIT -> req.setViaLocations(List.of(VISIT_VIA_LOCATION)); + case VIA_PASS_THROUGH -> req.setViaLocations( + ListUtils.combine(req.getViaLocations(), List.of(PASS_THROUGH_VIA_LOCATION)) + ); + case TRANSIT_GROUP_PRIORITY -> req.withPreferences(p -> + p.withTransit(t -> t.withRelaxTransitGroupPriority(RELAX_TRANSIT_GROUP_PRIORITY)) + ); + case RELAX_COST_DEST -> req.withPreferences(p -> + p.withTransit(t -> + t.withRaptor(r -> + r.withRelaxGeneralizedCostAtDestination(RELAX_GENERALIZED_COST_AT_DESTINATION) + ) + ) + ); + }; + } + + enum RequestFeature { + VIA_VISIT, + VIA_PASS_THROUGH, + TRANSIT_GROUP_PRIORITY, + RELAX_COST_DEST, + } } diff --git a/application/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java b/application/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java index b77a7d9085b..bcdfcfc545c 100644 --- a/application/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java +++ b/application/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java @@ -81,6 +81,12 @@ void relaxTransitGroupPriority() { assertEquals(TRANSIT_GROUP_PRIORITY_RELAX, subject.relaxTransitGroupPriority()); } + @Test + void isRelaxTransitGroupPrioritySet() { + assertTrue(subject.isRelaxTransitGroupPrioritySet()); + assertFalse(TransitPreferences.DEFAULT.isRelaxTransitGroupPrioritySet()); + } + @Test void ignoreRealtimeUpdates() { assertFalse(TransitPreferences.DEFAULT.ignoreRealtimeUpdates()); From 60896f8177c8c332732a220f449f55d308473b1d Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 15 Nov 2024 11:05:15 +0100 Subject: [PATCH 118/121] Version 2.7.0-entur-16 --- application/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index ecf0e0bd41a..9d0f88c8222 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-entur-10 + 2.7.0-entur-16 otp OpenTripPlanner - Application diff --git a/pom.xml b/pom.xml index 866692ec8ec..1e3202e6fc7 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.opentripplanner otp-root - 2.7.0-entur-10 + 2.7.0-entur-16 pom OpenTripPlanner - Root From c844b376083cb4282052b415f7b9c034bba9c996 Mon Sep 17 00:00:00 2001 From: Zsombor Welker Date: Thu, 14 Nov 2024 10:51:55 +0100 Subject: [PATCH 119/121] Set transit-group-priority for requests without via points --- .../transit/mappers/RaptorRequestMapper.java | 10 ++++-- .../mappers/RaptorRequestMapperTest.java | 32 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index ff0855f0128..8de3a16553d 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -199,11 +199,17 @@ private RaptorRequest doMap() { } private boolean hasPassThroughOnly() { - return request.getViaLocations().stream().allMatch(ViaLocation::isPassThroughLocation); + return ( + request.isViaSearch() && + request.getViaLocations().stream().allMatch(ViaLocation::isPassThroughLocation) + ); } private boolean hasViaLocationsOnly() { - return request.getViaLocations().stream().noneMatch(ViaLocation::isPassThroughLocation); + return ( + request.isViaSearch() && + request.getViaLocations().stream().noneMatch(ViaLocation::isPassThroughLocation) + ); } private boolean hasViaLocationsAndPassThroughLocations() { diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java index 03027b219d1..e9b235c1c3e 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java @@ -1,6 +1,7 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.Duration; @@ -90,6 +91,37 @@ void testPassThroughPoints() { ); } + @Test + void testTransitGroupPriority() { + var req = new RouteRequest(); + + // Set relax transit-group-priority + req.withPreferences(p -> + p.withTransit(t -> t.withRelaxTransitGroupPriority(CostLinearFunction.of("30m + 1.2t"))) + ); + + var result = map(req); + + assertFalse(result.multiCriteria().transitPriorityCalculator().isEmpty()); + } + + @Test + void testVisitViaAllowsTransitGroupPriority() { + var req = new RouteRequest(); + + // Set visit-via and relax transit-group-priority + req.setViaLocations( + List.of(new VisitViaLocation("Via A", null, List.of(STOP_A.getId()), List.of())) + ); + req.withPreferences(p -> + p.withTransit(t -> t.withRelaxTransitGroupPriority(CostLinearFunction.of("30m + 1.2t"))) + ); + + var result = map(req); + + assertFalse(result.multiCriteria().transitPriorityCalculator().isEmpty()); + } + @Test void testPassThroughPointsTurnTransitGroupPriorityOff() { var req = new RouteRequest(); From 1ee0553e6abafbb8f7b366dbdceb3bac9ae14e4d Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 14 Nov 2024 22:46:27 +0100 Subject: [PATCH 120/121] Only allow the following combinations of transit-group-priority and via-visit, and any combination of via-pass-through and relaxed-cost. --- .../transit/mappers/RaptorRequestMapper.java | 31 ++-- .../routing/api/request/RouteRequest.java | 3 +- .../preference/TransitPreferences.java | 4 + .../mappers/RaptorRequestMapperTest.java | 165 +++++++++++++++--- .../preference/TransitPreferencesTest.java | 6 + 5 files changed, 173 insertions(+), 36 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index 8de3a16553d..c5a75b02bbc 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -16,6 +16,7 @@ import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.model.RelaxFunction; import org.opentripplanner.raptor.api.request.DebugRequestBuilder; +import org.opentripplanner.raptor.api.request.MultiCriteriaRequest; import org.opentripplanner.raptor.api.request.Optimization; import org.opentripplanner.raptor.api.request.PassThroughPoint; import org.opentripplanner.raptor.api.request.RaptorRequest; @@ -26,6 +27,7 @@ import org.opentripplanner.routing.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; +import org.opentripplanner.routing.api.request.preference.TransitPreferences; import org.opentripplanner.routing.api.request.via.ViaLocation; import org.opentripplanner.transit.model.network.grouppriority.DefaultTransitGroupPriorityCalculator; @@ -128,15 +130,20 @@ private RaptorRequest doMap() { var pt = preferences.transit(); var r = pt.raptor(); - // Note! If a pass-through-point exists, then the transit-group-priority feature is disabled - - // TODO - We need to handle via locations that are not pass-through-points here if (hasPassThroughOnly()) { mcBuilder.withPassThroughPoints(mapPassThroughPoints()); + } else if (hasViaLocationsOnly()) { + builder.searchParams().addViaLocations(mapViaLocations()); + // relax transit group priority can be used with via-visit-stop, but not with pass-through + if (pt.isRelaxTransitGroupPrioritySet()) { + mapRelaxTransitGroupPriority(mcBuilder, pt); + } + } else if (pt.isRelaxTransitGroupPrioritySet()) { + mapRelaxTransitGroupPriority(mcBuilder, pt); + } else { + // The deprecated relaxGeneralizedCostAtDestination is only enabled, if there is no + // via location and the relaxTransitGroupPriority is not used (Normal). r.relaxGeneralizedCostAtDestination().ifPresent(mcBuilder::withRelaxCostAtDestination); - } else if (!pt.relaxTransitGroupPriority().isNormal()) { - mcBuilder.withTransitPriorityCalculator(new DefaultTransitGroupPriorityCalculator()); - mcBuilder.withRelaxC1(mapRelaxCost(pt.relaxTransitGroupPriority())); } }); @@ -160,10 +167,6 @@ private RaptorRequest doMap() { .addAccessPaths(accessPaths) .addEgressPaths(egressPaths); - if (hasViaLocationsOnly()) { - builder.searchParams().addViaLocations(mapViaLocations()); - } - var raptorDebugging = request.journey().transit().raptorDebugging(); if (raptorDebugging.isEnabled()) { @@ -271,6 +274,14 @@ private int relativeTime(Instant time) { return (int) (time.getEpochSecond() - transitSearchTimeZeroEpocSecond); } + private static void mapRelaxTransitGroupPriority( + MultiCriteriaRequest.Builder mcBuilder, + TransitPreferences pt + ) { + mcBuilder.withTransitPriorityCalculator(new DefaultTransitGroupPriorityCalculator()); + mcBuilder.withRelaxC1(mapRelaxCost(pt.relaxTransitGroupPriority())); + } + private static void addLogListenerForEachEventTypeRequested( DebugRequestBuilder target, DebugEventType type, diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index 8e649b3be49..4e36b1b58db 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -292,8 +292,9 @@ public List getViaLocations() { return via; } - public void setViaLocations(final List via) { + public RouteRequest setViaLocations(final List via) { this.via = via; + return this; } /** diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java b/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java index f15b47216e5..a237dd53527 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java @@ -137,6 +137,10 @@ public CostLinearFunction relaxTransitGroupPriority() { return relaxTransitGroupPriority; } + public boolean isRelaxTransitGroupPrioritySet() { + return !relaxTransitGroupPriority.isNormal(); + } + /** * When true, real-time updates are ignored during this search. */ diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java index e9b235c1c3e..ab97262f16f 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java @@ -3,12 +3,18 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RaptorRequestMapperTest.RequestFeature.RELAX_COST_DEST; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RaptorRequestMapperTest.RequestFeature.TRANSIT_GROUP_PRIORITY; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RaptorRequestMapperTest.RequestFeature.VIA_PASS_THROUGH; +import static org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RaptorRequestMapperTest.RequestFeature.VIA_VISIT; import java.time.Duration; import java.time.ZonedDateTime; import java.util.List; import java.util.Map; import java.util.stream.IntStream; +import javax.annotation.Nullable; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -24,11 +30,22 @@ import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.StopLocation; +import org.opentripplanner.utils.collection.ListUtils; class RaptorRequestMapperTest { private static final TimetableRepositoryForTest TEST_MODEL = TimetableRepositoryForTest.of(); private static final StopLocation STOP_A = TEST_MODEL.stop("Stop:A").build(); + public static final PassThroughViaLocation PASS_THROUGH_VIA_LOCATION = new PassThroughViaLocation( + "Via A", + List.of(STOP_A.getId()) + ); + public static final VisitViaLocation VISIT_VIA_LOCATION = new VisitViaLocation( + "Via A", + null, + List.of(STOP_A.getId()), + List.of() + ); private static final List ACCESS = List.of(TestAccessEgress.walk(12, 45)); private static final List EGRESS = List.of(TestAccessEgress.walk(144, 54)); @@ -37,6 +54,10 @@ class RaptorRequestMapperTest { private static final CostLinearFunction R3 = CostLinearFunction.of("30 + 2.0x"); private static final Map STOPS_MAP = Map.of(STOP_A.getId(), STOP_A); + private static final CostLinearFunction RELAX_TRANSIT_GROUP_PRIORITY = CostLinearFunction.of( + "30m + 1.2t" + ); + public static final double RELAX_GENERALIZED_COST_AT_DESTINATION = 2.0; static List testCasesRelaxedCost() { return List.of( @@ -102,40 +123,67 @@ void testTransitGroupPriority() { var result = map(req); - assertFalse(result.multiCriteria().transitPriorityCalculator().isEmpty()); + assertTrue(result.multiCriteria().transitPriorityCalculator().isPresent()); } - @Test - void testVisitViaAllowsTransitGroupPriority() { - var req = new RouteRequest(); - - // Set visit-via and relax transit-group-priority - req.setViaLocations( - List.of(new VisitViaLocation("Via A", null, List.of(STOP_A.getId()), List.of())) - ); - req.withPreferences(p -> - p.withTransit(t -> t.withRelaxTransitGroupPriority(CostLinearFunction.of("30m + 1.2t"))) + static List testViaAndTransitGroupPriorityCombinationsTestCases() { + return List.of( + Arguments.of(List.of(VIA_VISIT), List.of(VIA_VISIT), null), + Arguments.of(List.of(VIA_PASS_THROUGH), List.of(VIA_PASS_THROUGH), null), + Arguments.of(List.of(TRANSIT_GROUP_PRIORITY), List.of(TRANSIT_GROUP_PRIORITY), null), + Arguments.of(List.of(RELAX_COST_DEST), List.of(RELAX_COST_DEST), null), + Arguments.of( + List.of(VIA_VISIT, TRANSIT_GROUP_PRIORITY), + List.of(VIA_VISIT, TRANSIT_GROUP_PRIORITY), + null + ), + Arguments.of( + List.of(VIA_PASS_THROUGH, TRANSIT_GROUP_PRIORITY, RELAX_COST_DEST), + List.of(VIA_PASS_THROUGH), + null + ), + Arguments.of( + List.of(VIA_VISIT, TRANSIT_GROUP_PRIORITY, RELAX_COST_DEST), + List.of(VIA_VISIT, TRANSIT_GROUP_PRIORITY), + null + ), + Arguments.of( + List.of(TRANSIT_GROUP_PRIORITY, RELAX_COST_DEST), + List.of(TRANSIT_GROUP_PRIORITY), + null + ), + Arguments.of( + List.of(VIA_VISIT, VIA_PASS_THROUGH), + List.of(), + "A mix of via-locations and pass-through is not allowed in this version." + ) ); - - var result = map(req); - - assertFalse(result.multiCriteria().transitPriorityCalculator().isEmpty()); } - @Test - void testPassThroughPointsTurnTransitGroupPriorityOff() { + @ParameterizedTest + @MethodSource("testViaAndTransitGroupPriorityCombinationsTestCases") + void testViaAndTransitGroupPriorityCombinations( + List input, + List expectedEnabledFeatures, + @Nullable String errorMessage + ) { var req = new RouteRequest(); - // Set pass-through and relax transit-group-priority - req.setViaLocations(List.of(new PassThroughViaLocation("Via A", List.of(STOP_A.getId())))); - req.withPreferences(p -> - p.withTransit(t -> t.withRelaxTransitGroupPriority(CostLinearFunction.of("30m + 1.2t"))) - ); + for (RequestFeature it : input) { + req = setFeaturesOnRequest(req, it); + } - var result = map(req); + if (errorMessage == null) { + var result = map(req); - // transit-group-priority CANNOT be used with pass-through and is turned off... - assertTrue(result.multiCriteria().transitPriorityCalculator().isEmpty()); + for (var feature : RequestFeature.values()) { + assertFeatureSet(feature, result, expectedEnabledFeatures.contains(feature)); + } + } else { + var r = req; + var ex = Assertions.assertThrows(IllegalArgumentException.class, () -> map(r)); + assertEquals(errorMessage, ex.getMessage()); + } } private static RaptorRequest map(RouteRequest request) { @@ -149,4 +197,71 @@ private static RaptorRequest map(RouteRequest request) { id -> IntStream.of(STOPS_MAP.get(id).getIndex()) ); } + + private static void assertFeatureSet( + RequestFeature feature, + RaptorRequest result, + boolean expected + ) { + switch (feature) { + case VIA_VISIT: + if (expected) { + assertTrue(result.searchParams().hasViaLocations()); + // One via location exist(no NPE), but it does not allow pass-through + assertEquals( + "Via{label: Via A, connections: [0]}", + result.searchParams().viaLocations().get(0).toString() + ); + } + break; + case VIA_PASS_THROUGH: + if (expected) { + assertTrue(result.multiCriteria().hasPassThroughPoints()); + assertEquals( + "(Via A, stops: 0)", + result.multiCriteria().passThroughPoints().get(0).toString() + ); + } + break; + case TRANSIT_GROUP_PRIORITY: + assertEquals(expected, result.multiCriteria().transitPriorityCalculator().isPresent()); + if (expected) { + assertFalse(result.multiCriteria().hasPassThroughPoints()); + } + break; + case RELAX_COST_DEST: + assertEquals(expected, result.multiCriteria().relaxCostAtDestination() != null); + if (expected) { + assertFalse(result.multiCriteria().hasPassThroughPoints()); + assertFalse(result.searchParams().hasViaLocations()); + } + break; + } + } + + private static RouteRequest setFeaturesOnRequest(RouteRequest req, RequestFeature feature) { + return switch (feature) { + case VIA_VISIT -> req.setViaLocations(List.of(VISIT_VIA_LOCATION)); + case VIA_PASS_THROUGH -> req.setViaLocations( + ListUtils.combine(req.getViaLocations(), List.of(PASS_THROUGH_VIA_LOCATION)) + ); + case TRANSIT_GROUP_PRIORITY -> req.withPreferences(p -> + p.withTransit(t -> t.withRelaxTransitGroupPriority(RELAX_TRANSIT_GROUP_PRIORITY)) + ); + case RELAX_COST_DEST -> req.withPreferences(p -> + p.withTransit(t -> + t.withRaptor(r -> + r.withRelaxGeneralizedCostAtDestination(RELAX_GENERALIZED_COST_AT_DESTINATION) + ) + ) + ); + }; + } + + enum RequestFeature { + VIA_VISIT, + VIA_PASS_THROUGH, + TRANSIT_GROUP_PRIORITY, + RELAX_COST_DEST, + } } diff --git a/application/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java b/application/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java index b77a7d9085b..bcdfcfc545c 100644 --- a/application/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java +++ b/application/src/test/java/org/opentripplanner/routing/api/request/preference/TransitPreferencesTest.java @@ -81,6 +81,12 @@ void relaxTransitGroupPriority() { assertEquals(TRANSIT_GROUP_PRIORITY_RELAX, subject.relaxTransitGroupPriority()); } + @Test + void isRelaxTransitGroupPrioritySet() { + assertTrue(subject.isRelaxTransitGroupPrioritySet()); + assertFalse(TransitPreferences.DEFAULT.isRelaxTransitGroupPrioritySet()); + } + @Test void ignoreRealtimeUpdates() { assertFalse(TransitPreferences.DEFAULT.ignoreRealtimeUpdates()); From 2ee785c124752c4a4d660f72dd07655be08b1c71 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 15 Nov 2024 11:20:51 +0100 Subject: [PATCH 121/121] Version 2.7.0-entur-17 --- application/pom.xml | 2 +- gtfs-realtime-protobuf/pom.xml | 2 +- pom.xml | 2 +- raptor/pom.xml | 2 +- utils/pom.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index 889e65ed68e..13e72a38b5a 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-entur-14 + 2.7.0-entur-17 otp OpenTripPlanner - Application diff --git a/gtfs-realtime-protobuf/pom.xml b/gtfs-realtime-protobuf/pom.xml index 9888fd5a1eb..6bf26824a1e 100644 --- a/gtfs-realtime-protobuf/pom.xml +++ b/gtfs-realtime-protobuf/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-entur-14 + 2.7.0-entur-17 gtfs-realtime-protobuf OpenTripPlanner - GTFS Realtime (protobuf) diff --git a/pom.xml b/pom.xml index 3adf80b44e9..a7a5d541922 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.opentripplanner otp-root - 2.7.0-entur-14 + 2.7.0-entur-17 pom OpenTripPlanner - Root diff --git a/raptor/pom.xml b/raptor/pom.xml index 346453d47d2..61f7eb9574a 100644 --- a/raptor/pom.xml +++ b/raptor/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-entur-14 + 2.7.0-entur-17 otp-raptor diff --git a/utils/pom.xml b/utils/pom.xml index 3ed228eaea4..aefceaa8054 100644 --- a/utils/pom.xml +++ b/utils/pom.xml @@ -6,7 +6,7 @@ org.opentripplanner otp-root - 2.7.0-entur-14 + 2.7.0-entur-17 otp-utils