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.
Merge remote-tracking branch 'entur/otp2_sorlandsbanen' into otp2_ent…
…ur_develop
- Loading branch information
Showing
32 changed files
with
718 additions
and
345 deletions.
There are no files selected for viewing
79 changes: 79 additions & 0 deletions
79
application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.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,79 @@ | ||
package org.opentripplanner.ext.sorlandsbanen; | ||
|
||
import org.opentripplanner.raptor.api.model.RaptorAccessEgress; | ||
import org.opentripplanner.raptor.api.model.RaptorTransferConstraint; | ||
import org.opentripplanner.raptor.spi.RaptorCostCalculator; | ||
import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; | ||
import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter; | ||
import org.opentripplanner.transit.model.basic.TransitMode; | ||
|
||
|
||
/** | ||
* This cost calculator increases the cost on mode coach by adding an extra reluctance. The | ||
* reluctance is hardcoded in this class and cannot be configured. | ||
*/ | ||
class CoachCostCalculator<T extends TripSchedule> implements RaptorCostCalculator<T> { | ||
|
||
private static final int EXTRA_RELUCTANCE_ON_COACH = RaptorCostConverter.toRaptorCost(0.6); | ||
|
||
private final RaptorCostCalculator<T> delegate; | ||
|
||
CoachCostCalculator(RaptorCostCalculator<T> delegate) { | ||
this.delegate = delegate; | ||
} | ||
|
||
@Override | ||
public int boardingCost( | ||
boolean firstBoarding, | ||
int prevArrivalTime, | ||
int boardStop, | ||
int boardTime, | ||
T trip, | ||
RaptorTransferConstraint transferConstraints | ||
) { | ||
return delegate.boardingCost( | ||
firstBoarding, | ||
prevArrivalTime, | ||
boardStop, | ||
boardTime, | ||
trip, | ||
transferConstraints | ||
); | ||
} | ||
|
||
@Override | ||
public int onTripRelativeRidingCost(int boardTime, T tripScheduledBoarded) { | ||
return delegate.onTripRelativeRidingCost(boardTime, tripScheduledBoarded); | ||
} | ||
|
||
@Override | ||
public int transitArrivalCost( | ||
int boardCost, | ||
int alightSlack, | ||
int transitTime, | ||
T trip, | ||
int toStop | ||
) { | ||
int cost = delegate.transitArrivalCost(boardCost, alightSlack, transitTime, trip, toStop); | ||
if(trip.transitReluctanceFactorIndex() == TransitMode.COACH.ordinal()) { | ||
cost += transitTime * EXTRA_RELUCTANCE_ON_COACH; | ||
} | ||
return cost; | ||
} | ||
|
||
@Override | ||
public int waitCost(int waitTimeInSeconds) { | ||
return delegate.waitCost(waitTimeInSeconds); | ||
} | ||
|
||
@Override | ||
public int calculateRemainingMinCost(int minTravelTime, int minNumTransfers, int fromStop) { | ||
return delegate.calculateRemainingMinCost(minTravelTime, minNumTransfers, fromStop); | ||
} | ||
|
||
@Override | ||
public int costEgress(RaptorAccessEgress egress) { | ||
return delegate.costEgress(egress); | ||
} | ||
|
||
} |
103 changes: 103 additions & 0 deletions
103
...ication/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturSorlandsbanenService.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,103 @@ | ||
package org.opentripplanner.ext.sorlandsbanen; | ||
|
||
import java.util.Collection; | ||
import java.util.function.BiFunction; | ||
import org.opentripplanner.framework.geometry.WgsCoordinate; | ||
import org.opentripplanner.model.GenericLocation; | ||
import org.opentripplanner.raptor.api.path.RaptorPath; | ||
import org.opentripplanner.raptor.spi.ExtraMcRouterSearch; | ||
import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; | ||
import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgresses; | ||
import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress; | ||
import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; | ||
import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; | ||
import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.RaptorRoutingRequestTransitData; | ||
import org.opentripplanner.routing.api.request.RouteRequest; | ||
import org.opentripplanner.transit.model.framework.FeedScopedId; | ||
import org.opentripplanner.transit.model.site.StopLocation; | ||
|
||
/** | ||
* This is basically a big hack to produce results containing "Sørlandsbanen" in Norway. This | ||
* railroad line is slow and goes inland fare from where people live. Despite this, people and the | ||
* operator want to show it in the results for log travel along the southern part of Norway where | ||
* ii is an option. Tuning the search has proven to be challenging. It is solved here by doing | ||
* two searches. One normal search and one where the rail is given a big cost advantage over coach. | ||
* If train results are found in the second search, then it is added to the results of the first | ||
* search. Everything found in the first search is always returned. | ||
*/ | ||
public class EnturSorlandsbanenService { | ||
|
||
private static final double SOUTH_BOARDER_LIMIT = 59.1; | ||
private static final int MIN_DISTANCE_LIMIT = 120_000; | ||
|
||
|
||
public ExtraMcRouterSearch<TripSchedule> createMcRouterFactory(RouteRequest request, AccessEgresses accessEgresses, TransitLayer transitLayer) { | ||
WgsCoordinate from = findStopCoordinate( | ||
request.from(), | ||
accessEgresses.getAccesses(), | ||
transitLayer | ||
); | ||
WgsCoordinate to = findStopCoordinate(request.to(), accessEgresses.getEgresses(), transitLayer); | ||
|
||
if (from.latitude() > SOUTH_BOARDER_LIMIT && to.latitude() > SOUTH_BOARDER_LIMIT) { | ||
return null; | ||
} | ||
|
||
double distance = from.distanceTo(to); | ||
if (distance < MIN_DISTANCE_LIMIT) { | ||
return null; | ||
} | ||
|
||
return new ExtraMcRouterSearch<>() { | ||
@Override | ||
public RaptorTransitDataProvider<TripSchedule> createTransitDataAlternativeSearch(RaptorTransitDataProvider<TripSchedule> transitDataMainSearch) { | ||
return new RaptorRoutingRequestTransitData( | ||
(RaptorRoutingRequestTransitData)transitDataMainSearch, | ||
new CoachCostCalculator<>(transitDataMainSearch.multiCriteriaCostCalculator()) | ||
); | ||
} | ||
|
||
@Override | ||
public BiFunction<Collection<RaptorPath<TripSchedule>>, Collection<RaptorPath<TripSchedule>>, Collection<RaptorPath<TripSchedule>>> merger() { | ||
return new MergePaths<>(); | ||
} | ||
}; | ||
} | ||
|
||
/** | ||
* 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<? extends RoutingAccessEgress> accessEgress, | ||
TransitLayer transitLayer | ||
) { | ||
if (location.lat != null) { | ||
return new WgsCoordinate(location.lat, location.lng); | ||
} | ||
|
||
StopLocation firstStop = null; | ||
for (RoutingAccessEgress 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); | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/MergePaths.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,52 @@ | ||
package org.opentripplanner.ext.sorlandsbanen; | ||
|
||
import java.util.Collection; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.function.BiFunction; | ||
import org.opentripplanner.raptor.api.model.RaptorTripSchedule; | ||
import org.opentripplanner.raptor.api.path.PathLeg; | ||
import org.opentripplanner.raptor.api.path.RaptorPath; | ||
import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TripScheduleWithOffset; | ||
import org.opentripplanner.transit.model.basic.TransitMode; | ||
|
||
/** | ||
* Strategy for merging the main results and the extra rail results from Sorlandsbanen. | ||
* Everything from the main result is kept, and any additional rail results from the alternative | ||
* search are added. | ||
*/ | ||
class MergePaths<T extends RaptorTripSchedule> implements BiFunction<Collection<RaptorPath<T>>, Collection<RaptorPath<T>>, Collection<RaptorPath<T>>> { | ||
|
||
@Override | ||
public Collection<RaptorPath<T>> apply(Collection<RaptorPath<T>> main, Collection<RaptorPath<T>> railAlternatives) { | ||
Map<PathKey, RaptorPath<T>> result = new HashMap<>(); | ||
addAllToMap(result, main); | ||
addRailToMap(result, railAlternatives); | ||
return result.values(); | ||
} | ||
|
||
private void addAllToMap(Map<PathKey, RaptorPath<T>> map, Collection<RaptorPath<T>> paths) { | ||
for (var it : paths) { | ||
map.put(new PathKey(it), it); | ||
} | ||
} | ||
|
||
private void addRailToMap(Map<PathKey, RaptorPath<T>> map, Collection<RaptorPath<T>> paths) { | ||
for (var it : paths) { | ||
if (hasRail(it)) { | ||
map.put(new PathKey(it), it); | ||
} | ||
} | ||
} | ||
|
||
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; | ||
}); | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
application/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,58 @@ | ||
package org.opentripplanner.ext.sorlandsbanen; | ||
|
||
import org.opentripplanner.raptor.api.path.PathLeg; | ||
import org.opentripplanner.raptor.api.path.RaptorPath; | ||
|
||
|
||
/** | ||
* Uses a hash to create a key for access, egress and transit legs in a path. Transfers | ||
* are not included. The key is used to exclude duplicates. This approach may drop valid results | ||
* when there is a hash collision, but this whole sandbox feature is a hack - so we can tolerate | ||
* this here. | ||
*/ | ||
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; | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
...rc/ext/java/org/opentripplanner/ext/sorlandsbanen/configure/EnturSorlandsbanenModule.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,17 @@ | ||
package org.opentripplanner.ext.sorlandsbanen.configure; | ||
|
||
import dagger.Module; | ||
import dagger.Provides; | ||
import javax.annotation.Nullable; | ||
import org.opentripplanner.ext.sorlandsbanen.EnturSorlandsbanenService; | ||
import org.opentripplanner.framework.application.OTPFeature; | ||
|
||
@Module | ||
public class EnturSorlandsbanenModule { | ||
|
||
@Provides | ||
@Nullable | ||
EnturSorlandsbanenService providesEnturSorlandsbanenService() { | ||
return OTPFeature.Sorlandsbanen.isOn() ? new EnturSorlandsbanenService() : null; | ||
} | ||
} |
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.