forked from HSLdevcom/OpenTripPlanner
-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
hack: Add train option for Sørlandsbanen
- Loading branch information
Showing
23 changed files
with
480 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
145 changes: 145 additions & 0 deletions
145
src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
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 RaptorRequest<TripSchedule> enableHack( | ||
RaptorRequest<TripSchedule> raptorRequest, | ||
RouteRequest request, | ||
TransitLayer transitLayer | ||
) { | ||
if (request.preferences().transit().extraSearchCoachReluctance() < 0.1) { | ||
return raptorRequest; | ||
} | ||
|
||
if (!request.preferences().transit().relaxTransitPriorityGroup().isNormal()) { | ||
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
51
src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
88 changes: 88 additions & 0 deletions
88
src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.