Skip to content

Commit

Permalink
hack: Add train option for Sørlandsbanen
Browse files Browse the repository at this point in the history
  • Loading branch information
t2gran authored and vpaturet committed Feb 8, 2024
1 parent c6e0934 commit eeb77e5
Show file tree
Hide file tree
Showing 24 changed files with 687 additions and 375 deletions.
85 changes: 43 additions & 42 deletions docs/Configuration.md

Large diffs are not rendered by default.

481 changes: 169 additions & 312 deletions docs/RouteRequest.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ private BoardAndAlightSlack mapTransit() {
v -> tr.withRaptor(r -> r.withRelaxGeneralizedCostAtDestination(v))
);
}
setIfNotNull(req.extraSearchCoachReluctance, tr::setExtraSearchCoachReluctance);
});

return new BoardAndAlightSlack(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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<T extends RaptorTripSchedule> implements RaptorWorker<T> {

private static final Logger LOG = LoggerFactory.getLogger(ConcurrentCompositeWorker.class);

private final RaptorWorker<T> mainWorker;
private final RaptorWorker<T> alternativeWorker;

ConcurrentCompositeWorker(RaptorWorker<T> mainWorker, RaptorWorker<T> alternativeWorker) {
this.mainWorker = mainWorker;
this.alternativeWorker = alternativeWorker;
}

@Override
public RaptorWorkerResult<T> 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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 <T extends RaptorTripSchedule> boolean match(RaptorRequest<T> mcRequest) {
return mcRequest.extraSearchCoachReluctance > 0.1;
}

public static <T extends RaptorTripSchedule> RaptorWorker<T> worker(
RaptorConfig<T> config,
RaptorTransitDataProvider<T> transitData,
RaptorRequest<T> mcRequest,
Heuristics destinationHeuristics
) {
//noinspection unchecked
RaptorTransitDataProvider<T> altTransitData = (RaptorTransitDataProvider<T>) (
(RaptorRoutingRequestTransitData) transitData
).enturHackSorlandsbanen(mapFactors(mcRequest.extraSearchCoachReluctance));

return new ConcurrentCompositeWorker<>(
config.createMcWorker(transitData, mcRequest, destinationHeuristics),
config.createMcWorker(altTransitData, mcRequest, destinationHeuristics)
);
}

public static <T extends RaptorTripSchedule> RaptorRequest<T> enableHack(
RaptorRequest<T> 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<FactorStrategy, FactorStrategy> 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<RaptorAccessEgress> 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);
}
}
51 changes: 51 additions & 0 deletions src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<T extends RaptorTripSchedule>
implements RaptorWorkerResult<T> {

private static final Logger LOG = LoggerFactory.getLogger(RaptorWorkerResultComposite.class);

private RaptorWorkerResult<T> mainResult;
private RaptorWorkerResult<T> alternativeResult;

public RaptorWorkerResultComposite(
RaptorWorkerResult<T> mainResult,
RaptorWorkerResult<T> alternativeResult
) {
this.mainResult = mainResult;
this.alternativeResult = alternativeResult;
}

@Override
public Collection<RaptorPath<T>> extractPaths() {
Map<PathKey, RaptorPath<T>> 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<PathKey, RaptorPath<T>> map, Collection<RaptorPath<T>> 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<PathKey, RaptorPath<T>> map, Collection<RaptorPath<T>> 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;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public class RaptorRequest<T extends RaptorTripSchedule> {
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;
Expand All @@ -49,6 +52,7 @@ private RaptorRequest() {
this.multiCriteria = builder.multiCriteria();
this.performanceTimers = builder.performanceTimers();
this.debug = builder.debug().build();
this.extraSearchCoachReluctance = builder.extraSearchCoachReluctance;
verify();
}

Expand Down
Loading

0 comments on commit eeb77e5

Please sign in to comment.