Skip to content

Commit

Permalink
feat: Feeder DRT automatically detects DRT service areas and consider…
Browse files Browse the repository at this point in the history
…s them during routing (#248)

* feat: support of drt service area in feeder drt

* fix: handling the absence of a skippedFacilities regex
  • Loading branch information
tkchouaki authored Aug 28, 2024
1 parent 0c9a6c6 commit 49986d3
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
import org.eqasim.core.scenario.cutter.extent.ScenarioExtent;
import org.eqasim.core.scenario.cutter.extent.ShapeScenarioExtent;
import org.eqasim.core.simulation.modes.feeder_drt.config.AccessEgressStopSelectorParams;
import org.eqasim.core.simulation.modes.feeder_drt.config.FeederDrtConfigGroup;
import org.eqasim.core.simulation.modes.feeder_drt.router.FeederDrtRoutingModule;
Expand All @@ -11,15 +13,23 @@
import org.eqasim.core.simulation.modes.feeder_drt.router.access_egress_selector.ClosestAccessEgressStopSelectorParameterSet;
import org.matsim.api.core.v01.network.Network;
import org.matsim.api.core.v01.population.Population;
import org.matsim.contrib.drt.run.DrtConfigGroup;
import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup;
import org.matsim.contrib.dvrp.run.AbstractDvrpModeModule;
import org.matsim.contrib.dvrp.run.DvrpMode;
import org.matsim.contrib.dvrp.run.DvrpModes;
import org.matsim.core.config.ConfigGroup;
import org.matsim.core.config.ReflectiveConfigGroup;
import org.matsim.core.modal.ModalAnnotationCreator;
import org.matsim.core.router.RoutingModule;
import org.matsim.pt.transitSchedule.api.TransitSchedule;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.Optional;


public class FeederDrtModeModule extends AbstractDvrpModeModule {
Expand All @@ -34,6 +44,25 @@ public FeederDrtModeModule(FeederDrtConfigGroup feederDrtConfigGroup) {

@Override
public void install() {
MultiModeDrtConfigGroup multiModeDrtConfigGroup = (MultiModeDrtConfigGroup) getConfig().getModules().get(MultiModeDrtConfigGroup.GROUP_NAME);
DrtConfigGroup coveredDrtConfig = multiModeDrtConfigGroup.getModalElements().stream().filter(drtConfigGroup -> drtConfigGroup.getMode().equals(this.config.accessEgressModeName)).findFirst().get();

ScenarioExtent serviceAreaExtent = null;
if(coveredDrtConfig.operationalScheme.equals(DrtConfigGroup.OperationalScheme.serviceAreaBased)) {
URI extentPath;
try {
extentPath = ConfigGroup.getInputFileURL(getConfig().getContext(), coveredDrtConfig.drtServiceAreaShapeFile).toURI();
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
try {
serviceAreaExtent = new ShapeScenarioExtent.Builder(new File(extentPath), Optional.empty(), Optional.empty()).build();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
ScenarioExtent finalServiceAreaExtent = serviceAreaExtent;

FeederDrtConfigGroup feederDrtConfigGroup = this.config;
ReflectiveConfigGroup reflectiveConfigGroup = feederDrtConfigGroup.getAccessEgressStopSelectorConfig().getAccessEgressStopSelectorParams();
if(reflectiveConfigGroup instanceof ClosestAccessEgressStopSelectorParameterSet closestAccessEgressStopSelectorConfig) {
Expand All @@ -48,7 +77,7 @@ public void install() {
public AccessEgressStopsSelector get() {
ModalAnnotationCreator<DvrpMode> modalAnnotationCreator = DvrpModes::mode;
Provider<Network> networkProvider = injector.getProvider(modalAnnotationCreator.key(Network.class, feederDrtConfigGroup.accessEgressModeName));
return new ClosestAccessEgressStopSelector(closestAccessEgressStopSelectorConfig, networkProvider.get(), transitSchedule);
return new ClosestAccessEgressStopSelector(closestAccessEgressStopSelectorConfig, networkProvider.get(), transitSchedule, finalServiceAreaExtent);
}
}).asEagerSingleton();
} else {
Expand All @@ -71,7 +100,7 @@ public RoutingModule get() {
RoutingModule drtRoutingModule = routingModuleProviders.get(feederDrtConfigGroup.accessEgressModeName).get();
ModalAnnotationCreator<DvrpMode> modalAnnotationCreator = DvrpModes::mode;
Provider<AccessEgressStopsSelector> accessEgressStopsSelectorProvider = injector.getProvider(modalAnnotationCreator.key(AccessEgressStopsSelector.class, feederDrtConfigGroup.mode));
return new FeederDrtRoutingModule(feederDrtConfigGroup.mode, drtRoutingModule, ptRoutingModule, population.getFactory(), accessEgressStopsSelectorProvider.get());
return new FeederDrtRoutingModule(feederDrtConfigGroup.mode, drtRoutingModule, ptRoutingModule, population.getFactory(), accessEgressStopsSelectorProvider.get(), finalServiceAreaExtent);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.eqasim.core.simulation.modes.feeder_drt.router;

import org.eqasim.core.scenario.cutter.extent.ScenarioExtent;
import org.eqasim.core.simulation.modes.feeder_drt.router.access_egress_selector.AccessEgressStopsSelector;
import org.matsim.api.core.v01.population.*;
import org.matsim.core.router.DefaultRoutingRequest;
Expand All @@ -23,14 +24,17 @@ public enum FeederDrtTripSegmentType {MAIN, DRT}

private final String mode;
private final AccessEgressStopsSelector accessEgressStopsSelector;
private final ScenarioExtent drtServiceAreaExtent;

public FeederDrtRoutingModule(String mode,RoutingModule feederRoutingModule, RoutingModule transitRoutingModule,
PopulationFactory populationFactory, AccessEgressStopsSelector accessEgressStopsSelector) {
PopulationFactory populationFactory, AccessEgressStopsSelector accessEgressStopsSelector,
ScenarioExtent drtServiceAreaExtent) {
this.mode = mode;
this.drtRoutingModule = feederRoutingModule;
this.transitRoutingModule = transitRoutingModule;
this.populationFactory = populationFactory;
this.accessEgressStopsSelector = accessEgressStopsSelector;
this.drtServiceAreaExtent = drtServiceAreaExtent;
}

@Override
Expand All @@ -46,19 +50,20 @@ public List<? extends PlanElement> calcRoute(RoutingRequest routingRequest) {
Facility egressFacility = this.accessEgressStopsSelector.getEgressFacility(routingRequest);

List<PlanElement> intermodalRoute = new LinkedList<>();
// Computing the access DRT route
List<? extends PlanElement> drtRoute = null;
List<? extends PlanElement> accessDrtRoute = null;
List<? extends PlanElement> egressDrtRoute = null;

if (accessFacility != null) {
drtRoute = drtRoutingModule.calcRoute(DefaultRoutingRequest.withoutAttributes(fromFacility, accessFacility, departureTime, person));
// Computing the access DRT route if it's possible
if (accessFacility != null && (drtServiceAreaExtent == null || drtServiceAreaExtent.isInside(fromFacility.getCoord()))) {
accessDrtRoute = drtRoutingModule.calcRoute(DefaultRoutingRequest.withoutAttributes(fromFacility, accessFacility, departureTime, person));
}
double accessTime = departureTime;
if (drtRoute == null) {
if (accessDrtRoute == null) {
// if no DRT route, next part of the trip starts from the origin
accessFacility = fromFacility;
} else {
//Otherwise we have already a first part of the trip
intermodalRoute.addAll(drtRoute);
intermodalRoute.addAll(accessDrtRoute);
for (PlanElement element : intermodalRoute) {
if (element instanceof Leg leg) {
accessTime = Math.max(accessTime, leg.getDepartureTime().seconds());
Expand All @@ -71,37 +76,44 @@ public List<? extends PlanElement> calcRoute(RoutingRequest routingRequest) {
intermodalRoute.add(accessInteractionActivity);
}

// Compute the PT part of the route
List<PlanElement> ptRoute = new LinkedList<>(transitRoutingModule.calcRoute(DefaultRoutingRequest.withoutAttributes(accessFacility, egressFacility, accessTime, person)));
double egressTime = accessTime;

for (PlanElement element : ptRoute) {
if (element instanceof Leg leg) {
egressTime = Math.max(egressTime, leg.getDepartureTime().seconds());
egressTime += leg.getTravelTime().seconds();
leg.getAttributes().putAttribute(CURRENT_SEGMENT_TYPE_ATTR, FeederDrtTripSegmentType.MAIN);
}
// We have to check the existence of the egress facility here, the pt router will not support a null value
if (egressFacility == null || (drtServiceAreaExtent != null && !drtServiceAreaExtent.isInside(toFacility.getCoord()))) {
egressFacility = toFacility;
}

if (egressFacility != null) {
drtRoute = drtRoutingModule.calcRoute(DefaultRoutingRequest.withoutAttributes(egressFacility, toFacility, egressTime, person));
} else {
drtRoute = null;
// Compute the PT part of the route towards the egress (or to) facility
List<PlanElement> ptRoute = new LinkedList<>(transitRoutingModule.calcRoute(DefaultRoutingRequest.withoutAttributes(accessFacility, egressFacility, accessTime, person)));
ptRoute.stream().filter(planElement -> planElement instanceof Leg).map(planElement -> (Leg) planElement).forEach(leg -> leg.getAttributes().putAttribute(CURRENT_SEGMENT_TYPE_ATTR, FeederDrtTripSegmentType.MAIN));

// It's ok to compare reference here, we want to check if we assigned toFacility to egressFacility above
if(egressFacility != toFacility) {
double egressTime = accessTime;
for (PlanElement element : ptRoute) {
if (element instanceof Leg leg) {
egressTime = Math.max(egressTime, leg.getDepartureTime().seconds());
egressTime += leg.getTravelTime().seconds();
}
}
egressDrtRoute = drtRoutingModule.calcRoute(DefaultRoutingRequest.withoutAttributes(egressFacility, toFacility, egressTime, person));
}

// If no valid DRT route is found, we recompute a PT route from the access facility to the trip destination
if (drtRoute == null) {
ptRoute = new LinkedList<>(transitRoutingModule.calcRoute(DefaultRoutingRequest.withoutAttributes(accessFacility, toFacility, accessTime, person)));
ptRoute.stream().filter(planElement -> planElement instanceof Leg).map(planElement -> (Leg) planElement).forEach(leg -> leg.getAttributes().putAttribute(CURRENT_SEGMENT_TYPE_ATTR, FeederDrtTripSegmentType.MAIN));
// egressDrtRoute is assigned only in the above if, but it can be null without entering the if block, so we need to do this here, If no valid DRT route is found, we recompute a PT route from the access facility to the trip destination
if (egressDrtRoute == null) {
if(egressFacility != toFacility) {
// In this case, the egressDrtRoute is null because the attempt to compute one wasn't successful, so we need to compute a pt route from the access (or from facility) to the to facility
ptRoute = new LinkedList<>(transitRoutingModule.calcRoute(DefaultRoutingRequest.withoutAttributes(accessFacility, toFacility, accessTime, person)));
ptRoute.stream().filter(planElement -> planElement instanceof Leg).map(planElement -> (Leg) planElement).forEach(leg -> leg.getAttributes().putAttribute(CURRENT_SEGMENT_TYPE_ATTR, FeederDrtTripSegmentType.MAIN));
}
intermodalRoute.addAll(ptRoute);
} else {
// Otherwise we add it as an egress to the whole route
// Here we have a pt route and an egress drt route, we need to propriately concatenate them in the overall route
intermodalRoute.addAll(ptRoute);
Activity egressInteractionActivity = populationFactory.createActivityFromLinkId(this.mode + " interaction", egressFacility.getLinkId());
egressInteractionActivity.setMaximumDuration(0);
drtRoute.stream().filter(planElement -> planElement instanceof Leg).map(planElement -> (Leg) planElement).forEach(leg -> leg.getAttributes().putAttribute(CURRENT_SEGMENT_TYPE_ATTR, FeederDrtTripSegmentType.DRT));
egressDrtRoute.stream().filter(planElement -> planElement instanceof Leg).map(planElement -> (Leg) planElement).forEach(leg -> leg.getAttributes().putAttribute(CURRENT_SEGMENT_TYPE_ATTR, FeederDrtTripSegmentType.DRT));
intermodalRoute.add(egressInteractionActivity);
intermodalRoute.addAll(drtRoute);
intermodalRoute.addAll(egressDrtRoute);
}
return intermodalRoute;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eqasim.core.scenario.cutter.extent.ScenarioExtent;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Network;
import org.matsim.core.network.NetworkUtils;
Expand All @@ -24,6 +25,10 @@ public class ClosestAccessEgressStopSelector implements AccessEgressStopsSelecto
private final Pattern skippedFacilitiesIdPattern;

public ClosestAccessEgressStopSelector(ClosestAccessEgressStopSelectorParameterSet config, Network drtNetwork, TransitSchedule schedule) {
this(config, drtNetwork, schedule, null);
}

public ClosestAccessEgressStopSelector(ClosestAccessEgressStopSelectorParameterSet config, Network drtNetwork, TransitSchedule schedule, ScenarioExtent serviceAreaExtent) {
logger.info("Starting initialization");
if(config.skipAccessAndEgressAtFacilities.length() > 0) {
this.skippedFacilitiesIdPattern = Pattern.compile(config.skipAccessAndEgressAtFacilities);
Expand All @@ -44,6 +49,10 @@ public ClosestAccessEgressStopSelector(ClosestAccessEgressStopSelectorParameterS
if (accessEgressTransitStopModes.size() == 0 || accessEgressTransitStopModes.contains(transitRoute.getTransportMode())) {
for (TransitRouteStop transitRouteStop : transitRoute.getStops()) {
TransitStopFacility transitStopFacility = transitRouteStop.getStopFacility();
if(serviceAreaExtent != null && !serviceAreaExtent.isInside(transitStopFacility.getCoord())) {
logger.warn("skipping this stop because it's outside of the service area: " + transitStopFacility.getName());
continue;
}
if (!processedFacilities.contains(transitStopFacility.getId())) {
processedFacilities.add(transitStopFacility.getId());
Facility interactionFacility = FacilitiesUtils.wrapLink(NetworkUtils.getNearestLink(drtNetwork, transitStopFacility.getCoord()));
Expand All @@ -68,7 +77,7 @@ public ClosestAccessEgressStopSelector(ClosestAccessEgressStopSelectorParameterS
}

private boolean skipFacility(Facility facility) {
if(facility instanceof ActivityFacilityImpl activityFacility) {
if(this.skippedFacilitiesIdPattern != null && facility instanceof ActivityFacilityImpl activityFacility) {
return skippedFacilitiesIdPattern.matcher(activityFacility.getId().toString()).matches();
}
return false;
Expand Down

0 comments on commit 49986d3

Please sign in to comment.