From 941082389414f6bf1e37bb15d759e29e90ce3807 Mon Sep 17 00:00:00 2001 From: Chengqi Lu <43133404+luchengqi7@users.noreply.github.com> Date: Mon, 1 Apr 2024 14:18:29 +0200 Subject: [PATCH] Work in progress --- .../drt_post_simulation/DetourAnalysis.java | 202 ++++++++++++++++++ .../ExtractDrtTripsForPostSimulation.java | 67 ++++++ .../RunDrtPostSimulation.java | 62 ++++++ .../RunDrtPostSimulationFleetSizing.java | 180 ++++++++++++++++ .../matsim/run/prepare/PrepareNetwork.java | 2 +- 5 files changed, 512 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/matsim/run/drt_post_simulation/DetourAnalysis.java create mode 100644 src/main/java/org/matsim/run/drt_post_simulation/ExtractDrtTripsForPostSimulation.java create mode 100644 src/main/java/org/matsim/run/drt_post_simulation/RunDrtPostSimulation.java create mode 100644 src/main/java/org/matsim/run/drt_post_simulation/RunDrtPostSimulationFleetSizing.java diff --git a/src/main/java/org/matsim/run/drt_post_simulation/DetourAnalysis.java b/src/main/java/org/matsim/run/drt_post_simulation/DetourAnalysis.java new file mode 100644 index 0000000..5d81999 --- /dev/null +++ b/src/main/java/org/matsim/run/drt_post_simulation/DetourAnalysis.java @@ -0,0 +1,202 @@ +package org.matsim.run.drt_post_simulation; + +import org.apache.commons.collections.ArrayStack; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.api.core.v01.population.Person; +import org.matsim.contrib.drt.util.DrtEventsReaders; +import org.matsim.contrib.dvrp.passenger.*; +import org.matsim.contrib.dvrp.path.VrpPaths; +import org.matsim.contrib.dvrp.router.TimeAsTravelDisutility; +import org.matsim.contrib.dvrp.trafficmonitoring.QSimFreeSpeedTravelTime; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.events.EventsUtils; +import org.matsim.core.events.MatsimEventsReader; +import org.matsim.core.network.NetworkUtils; +import org.matsim.core.router.speedy.SpeedyALTFactory; +import org.matsim.core.router.util.LeastCostPathCalculator; +import org.matsim.core.router.util.TravelTime; +import org.matsim.core.utils.geometry.CoordUtils; + +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.util.*; + +import static org.matsim.application.ApplicationUtils.globFile; + +public class DetourAnalysis { + public static void main(String[] args) throws IOException { + Path outputDirectory = Path.of(args[0]); + DetourAnalysisEventsHandler detourAnalysis = new DetourAnalysisEventsHandler(outputDirectory); + detourAnalysis.readEvents(); + detourAnalysis.writeAnalysis(); + } + + public static class DetourAnalysisEventsHandler implements PassengerRequestSubmittedEventHandler, + PassengerRequestScheduledEventHandler, PassengerRequestRejectedEventHandler, PassengerPickedUpEventHandler, + PassengerDroppedOffEventHandler { + private final Path outputDirectory; + private final Network network; + private final TravelTime travelTime; + private final LeastCostPathCalculator router; + private final Map, Double> submissionTimeMap = new LinkedHashMap<>(); + private final Map, Double> directTripTimeMap = new HashMap<>(); + private final Map, Double> scheduledPickupTimeMap = new HashMap<>(); + private final Map, Double> actualPickupTimeMap = new HashMap<>(); + private final Map, Double> arrivalTimeMap = new HashMap<>(); + private final List> rejectedPersons = new ArrayList<>(); + private final Map, Double> waitTimeMap = new HashMap<>(); + private final Map, Double> euclideanDistanceMap = new HashMap<>(); + + public DetourAnalysisEventsHandler(Path outputDirectory) { + this.outputDirectory = outputDirectory; + Path networkPath = globFile(outputDirectory, "*output_network.xml.gz*"); + this.network = NetworkUtils.readNetwork(networkPath.toString()); + this.travelTime = new QSimFreeSpeedTravelTime(1); + this.router = new SpeedyALTFactory().createPathCalculator(network, new TimeAsTravelDisutility(travelTime), travelTime); + } + + @Override + public void handleEvent(PassengerRequestSubmittedEvent event) { + double submissionTime = event.getTime(); + Link fromLink = network.getLinks().get(event.getFromLinkId()); + Link toLink = network.getLinks().get(event.getToLinkId()); + double directTripTime = VrpPaths.calcAndCreatePath + (fromLink, toLink, submissionTime, router, travelTime).getTravelTime(); + double euclideanDistance = CoordUtils.calcEuclideanDistance(fromLink.getToNode().getCoord(), toLink.getToNode().getCoord()); + submissionTimeMap.put(event.getPersonIds().get(0), submissionTime); + directTripTimeMap.put(event.getPersonIds().get(0), directTripTime); + euclideanDistanceMap.put(event.getPersonIds().get(0), euclideanDistance); + } + + @Override + public void handleEvent(PassengerRequestScheduledEvent event) { + double scheduledPickupTime = Math.ceil(event.getPickupTime()); + scheduledPickupTimeMap.put(event.getPersonIds().get(0), scheduledPickupTime); + } + + @Override + public void handleEvent(PassengerRequestRejectedEvent event) { + rejectedPersons.add(event.getPersonIds().get(0)); + } + + @Override + public void handleEvent(PassengerPickedUpEvent event) { + double actualPickupTime = event.getTime(); + actualPickupTimeMap.put(event.getPersonId(), actualPickupTime); + double waitTime = actualPickupTime - submissionTimeMap.get(event.getPersonId()); + waitTimeMap.put(event.getPersonId(), waitTime); + } + + @Override + public void handleEvent(PassengerDroppedOffEvent event) { + double arrivalTime = event.getTime(); + arrivalTimeMap.put(event.getPersonId(), arrivalTime); + } + + @Override + public void reset(int iteration) { + PassengerRequestScheduledEventHandler.super.reset(iteration); + } + + public void writeAnalysis() throws IOException { + // Write information about simulated trips. (In the lists below, we only store completed trips) + List directTripDurations = new ArrayList<>(); + List actualRideDurations = new ArrayList<>(); + List waitTimes = new ArrayList<>(); + List euclideanDistances = new ArrayList<>(); + + { + String detourAnalysisOutput = outputDirectory.toString() + "/simulated-drt-trips.tsv"; + CSVPrinter csvPrinter = new CSVPrinter(new FileWriter(detourAnalysisOutput), CSVFormat.TDF); + csvPrinter.printRecord(Arrays.asList("person_id", "submission", "scheduled_pickup", "actual_pickup", + "arrival", "direct_trip_duration", "total_wait_time", "delay_since_scheduled_pickup", + "actual_ride_duration", "total_travel_time", "euclidean_distance")); + + for (Id personId : submissionTimeMap.keySet()) { + if (rejectedPersons.contains(personId)) { + continue; + } + + double submissionTime = submissionTimeMap.get(personId); + double scheduledPickupTime = scheduledPickupTimeMap.get(personId); + double actualPickupTime = actualPickupTimeMap.get(personId); + double arrivalTime = arrivalTimeMap.get(personId); + double directTripDuration = directTripTimeMap.get(personId); + double euclideanDistance = euclideanDistanceMap.get(personId); + + double waitTime = actualPickupTime - submissionTime; + double delay = actualPickupTime - scheduledPickupTime; + double actualRideDuration = arrivalTime - actualPickupTime; + double actualTotalTravelTime = arrivalTime - submissionTime; + + csvPrinter.printRecord(Arrays.asList( + personId.toString(), + Double.toString(submissionTime), + Double.toString(scheduledPickupTime), + Double.toString(actualPickupTime), + Double.toString(arrivalTime), + Double.toString(directTripDuration), + Double.toString(waitTime), + Double.toString(delay), + Double.toString(actualRideDuration), + Double.toString(actualTotalTravelTime), + Double.toString(euclideanDistance) + )); + + actualRideDurations.add(actualRideDuration); + directTripDurations.add(directTripDuration); + waitTimes.add(waitTime); + euclideanDistances.add(euclideanDistance); + } + csvPrinter.close(); + } + + // Write out summary data + { + String tripsSummary = outputDirectory + "/simulated-drt-summary.tsv"; + CSVPrinter csvPrinter = new CSVPrinter(new FileWriter(tripsSummary), CSVFormat.TDF); + csvPrinter.printRecord(Arrays.asList("direct_trip_duration", "actual_ride_duration", "wait_time", + "total_travel_time", "euclidean_distance")); + + double meanDirectTripDuration = directTripDurations.stream().mapToDouble(v -> v).average().orElseThrow(); + double meanActualRideDuration = actualRideDurations.stream().mapToDouble(v -> v).average().orElseThrow(); + double meanWaitTime = waitTimes.stream().mapToDouble(v -> v).average().orElseThrow(); + double meanTotalTravelTime = meanActualRideDuration + meanWaitTime; + double meanEuclideanDistance = euclideanDistances.stream().mapToDouble(v -> v).average().orElseThrow(); + + csvPrinter.printRecord(Arrays.asList( + Double.toString(meanDirectTripDuration), + Double.toString(meanActualRideDuration), + Double.toString(meanWaitTime), + Double.toString(meanTotalTravelTime), + Double.toString(meanEuclideanDistance) + )); + csvPrinter.close(); + } + } + + public List> getRejectedPersons() { + return rejectedPersons; + } + + public void readEvents() { + Path outputEventsPath = globFile(outputDirectory, "*output_events.xml.gz*"); + EventsManager eventManager = EventsUtils.createEventsManager(); + eventManager.addHandler(this); + eventManager.initProcessing(); + MatsimEventsReader matsimEventsReader = DrtEventsReaders.createEventsReader(eventManager); + matsimEventsReader.readFile(outputEventsPath.toString()); + } + + public double get95pctWaitTime() { + List waitingTimes = waitTimeMap.values().stream().sorted().toList(); + int idx = (int) Math.min(Math.ceil(waitingTimes.size() * 0.95), waitingTimes.size() - 1); + return waitingTimes.get(idx); + } + } +} diff --git a/src/main/java/org/matsim/run/drt_post_simulation/ExtractDrtTripsForPostSimulation.java b/src/main/java/org/matsim/run/drt_post_simulation/ExtractDrtTripsForPostSimulation.java new file mode 100644 index 0000000..29bb803 --- /dev/null +++ b/src/main/java/org/matsim/run/drt_post_simulation/ExtractDrtTripsForPostSimulation.java @@ -0,0 +1,67 @@ +package org.matsim.run.drt_post_simulation; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.population.*; +import org.matsim.application.MATSimAppCommand; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.population.PopulationUtils; +import picocli.CommandLine; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.matsim.application.ApplicationUtils.globFile; + +public class ExtractDrtTripsForPostSimulation implements MATSimAppCommand { + @CommandLine.Option(names = "--directory", description = "path to the directory of the simulation output", required = true) + private Path directory; + + @CommandLine.Option(names = "--drt-operators", description = "path to the directory of the simulation output", arity = "1..*", defaultValue = "drt") + private String[] drtOperators; + + public static void main(String[] args) { + new ExtractDrtTripsForPostSimulation().execute(args); + } + + @Override + public Integer call() throws Exception { + Population outputDrtPlans = PopulationUtils.createPopulation(ConfigUtils.createConfig()); + PopulationFactory populationFactory = outputDrtPlans.getFactory(); + int counter = 0; + + for (String drtOperator : drtOperators) { + Path drtLegsFile = globFile(directory, "*output_drt_legs_" + drtOperator + ".csv*"); + try (CSVParser parser = new CSVParser(Files.newBufferedReader(drtLegsFile), + CSVFormat.DEFAULT.withDelimiter(';').withFirstRecordAsHeader())) { + for (CSVRecord row : parser.getRecords()) { + double departureTime = Double.parseDouble(row.get("departureTime")); + Id fromLinkId = Id.createLinkId(row.get("fromLinkId")); + Id toLinkId = Id.createLinkId(row.get("toLinkId")); + + Activity fromAct = populationFactory.createActivityFromLinkId("dummy", fromLinkId); + fromAct.setEndTime(departureTime); + Activity toAct = populationFactory.createActivityFromLinkId("dummy", toLinkId); + Leg leg = populationFactory.createLeg(drtOperator); + leg.setMode(TransportMode.drt); + + Plan plan = populationFactory.createPlan(); + Person person = populationFactory.createPerson(Id.createPersonId("dummy_person_" + counter)); + plan.addActivity(fromAct); + plan.addLeg(leg); + plan.addActivity(toAct); + person.addPlan(plan); + outputDrtPlans.addPerson(person); + counter++; + } + } + } + + new PopulationWriter(outputDrtPlans).write(directory.toString() + "/extracted-drt-plans.xml.gz"); + return 0; + } +} diff --git a/src/main/java/org/matsim/run/drt_post_simulation/RunDrtPostSimulation.java b/src/main/java/org/matsim/run/drt_post_simulation/RunDrtPostSimulation.java new file mode 100644 index 0000000..fa10e26 --- /dev/null +++ b/src/main/java/org/matsim/run/drt_post_simulation/RunDrtPostSimulation.java @@ -0,0 +1,62 @@ +package org.matsim.run.drt_post_simulation; + +import org.matsim.api.core.v01.Scenario; +import org.matsim.application.MATSimApplication; +import org.matsim.application.options.ShpOptions; +import org.matsim.contrib.drt.routing.DrtRoute; +import org.matsim.contrib.drt.routing.DrtRouteFactory; +import org.matsim.contrib.drt.run.DrtConfigs; +import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup; +import org.matsim.contrib.drt.run.MultiModeDrtModule; +import org.matsim.contrib.dvrp.run.DvrpConfigGroup; +import org.matsim.contrib.dvrp.run.DvrpModule; +import org.matsim.contrib.dvrp.run.DvrpQSimComponents; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.controler.Controler; +import org.matsim.run.prepare.PrepareNetwork; +import picocli.CommandLine; + +import javax.annotation.Nullable; +import java.nio.file.Path; + +@CommandLine.Command(header = ":: Run DRT post-simulation ::", version = RunDrtPostSimulation.VERSION) +public class RunDrtPostSimulation extends MATSimApplication { + @CommandLine.Option(names = "--drt-area", description = "Path to SHP file specifying where DRT mode is allowed") + private Path drtArea; + + static final String VERSION = "1.0"; + + public static void main(String[] args) { + MATSimApplication.run(RunDrtPostSimulation.class, args); + } + + @Nullable + @Override + protected Config prepareConfig(Config config) { + ConfigUtils.addOrGetModule(config, DvrpConfigGroup.class); + MultiModeDrtConfigGroup multiModeDrtConfig = ConfigUtils.addOrGetModule(config, MultiModeDrtConfigGroup.class); + DrtConfigs.adjustMultiModeDrtConfig(multiModeDrtConfig, config.scoring(), config.routing()); + return config; + } + + @Override + protected void prepareScenario(Scenario scenario) { + scenario.getPopulation() + .getFactory() + .getRouteFactories() + .setRouteFactory(DrtRoute.class, new DrtRouteFactory()); + // Prepare network by adding DRT mode to service area + PrepareNetwork.prepareDRT(scenario.getNetwork(), new ShpOptions(drtArea, null, null)); + + } + + @Override + protected void prepareControler(Controler controler) { + Config config = controler.getConfig(); + MultiModeDrtConfigGroup multiModeDrtConfig = ConfigUtils.addOrGetModule(config, MultiModeDrtConfigGroup.class); + controler.addOverridingModule(new DvrpModule()); + controler.addOverridingModule(new MultiModeDrtModule()); + controler.configureQSimComponents(DvrpQSimComponents.activateAllModes(multiModeDrtConfig)); + } +} diff --git a/src/main/java/org/matsim/run/drt_post_simulation/RunDrtPostSimulationFleetSizing.java b/src/main/java/org/matsim/run/drt_post_simulation/RunDrtPostSimulationFleetSizing.java new file mode 100644 index 0000000..3b29c54 --- /dev/null +++ b/src/main/java/org/matsim/run/drt_post_simulation/RunDrtPostSimulationFleetSizing.java @@ -0,0 +1,180 @@ +package org.matsim.run.drt_post_simulation; + +import org.apache.commons.io.FileUtils; +import org.matsim.application.MATSimAppCommand; +import org.matsim.application.options.ShpOptions; +import org.matsim.contrib.drt.passenger.DrtOfferAcceptor; +import org.matsim.contrib.drt.passenger.MaxDetourOfferAcceptor; +import org.matsim.contrib.drt.run.DrtConfigGroup; +import org.matsim.contrib.drt.run.DrtControlerCreator; +import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup; +import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule; +import org.matsim.contrib.dvrp.run.DvrpConfigGroup; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.controler.Controler; +import org.matsim.run.prepare.PrepareNetwork; +import picocli.CommandLine; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class RunDrtPostSimulationFleetSizing implements MATSimAppCommand { + @CommandLine.Option(names = "--config", description = "path to config file", required = true) + private Path configPath; + + @CommandLine.Option(names = "--drt-plans", description = "path to extracted DRT trips file (plans)", required = true) + private String plans; + + @CommandLine.Option(names = "--output", description = "root output folder", required = true) + private String output; + + @CommandLine.Option(names = "--fleet-size", description = "fleet size from", defaultValue = "250") + private int fleetSize; + + @CommandLine.Option(names = "--steps", description = "maximum number of runs", defaultValue = "50") + private int steps; + + @CommandLine.Option(names = "--step-size", description = "number of vehicles increased for each step (phase 1)", defaultValue = "5") + private int stepSizeStageOne; + + @CommandLine.Option(names = "--step-size-2", description = "number of vehicles increased for each step (phase 2)", defaultValue = "1") + private int stepSizeStageTwo; + + @CommandLine.Option(names = "--seats", description = "fleet size", defaultValue = "8") + private int seats; + + @CommandLine.Option(names = "--wait-time", description = "max wait time", defaultValue = "300") + private double maxWaitTime; + + @CommandLine.Option(names = "--detour-alpha", description = "max detour alpha", defaultValue = "1.5") + private double detourAlpha; + + @CommandLine.Option(names = "--detour-beta", description = "max detour beta", defaultValue = "300") + private double detourBeta; + + @CommandLine.Option(names = "--pickup-delay", description = "max pickup delay", defaultValue = "120") + private double maxPickupDelay; + + @CommandLine.Option(names = "--max-abs-detour", description = "max pickup delay", defaultValue = "7200") + private double maxAbsDetour; + + @CommandLine.Option(names = "--allow-rejection", description = "allow rejection") + private boolean allowRejection; + + @CommandLine.Option(names = "--stop-duration", description = "max pickup delay", defaultValue = "60") + private double stopDuration; + + @CommandLine.Option(names = "--drt-area", description = "Path to SHP file specifying where DRT mode is allowed") + private Path drtArea; + + public static void main(String[] args) { + new RunDrtPostSimulationFleetSizing().execute(args); + } + + @Override + public Integer call() throws Exception { + // Create a temporary config file in the same folder, so that multiple runs can be run in the cluster at the same time + String temporaryConfig = createTemporaryConfig(configPath, output); + + // Stage 1 + int maxFleetSize = fleetSize + stepSizeStageOne * (steps - 1); + while (fleetSize <= maxFleetSize) { + String outputDirectory = runSimulation(temporaryConfig); + boolean requirementFulfilled = analyzeResult(outputDirectory); + if (requirementFulfilled) { + break; + } + fleetSize += stepSizeStageOne; + } + + // Stage 2 + if (fleetSize < maxFleetSize && stepSizeStageTwo < stepSizeStageOne && steps > 1) { + // Move back to previous step (large step) and move forward for one small step + fleetSize = fleetSize - stepSizeStageOne + stepSizeStageTwo; + while (true) { + String outputDirectory = runSimulation(temporaryConfig); + boolean requirementFulfilled = analyzeResult(outputDirectory); + if (requirementFulfilled) { + break; + } + fleetSize += stepSizeStageTwo; + } + } + + // Delete the temporary config file for the current run + Files.delete(Path.of(temporaryConfig)); + return 0; + } + + private String runSimulation(String temporaryConfig) { + String outputDirectory = output + "/fleet-size-" + fleetSize; + Config config = ConfigUtils.loadConfig(temporaryConfig, new MultiModeDrtConfigGroup(), new DvrpConfigGroup()); + config.controller().setOutputDirectory(outputDirectory); + config.plans().setInputFile(plans); + + // Assume single DRT operator + DrtConfigGroup drtConfigGroup = DrtConfigGroup.getSingleModeDrtConfig(config); + drtConfigGroup.vehiclesFile = "drt-vehicles-whole-city/" + fleetSize + "-" + seats + "_seater-drt-vehicles.xml"; + + drtConfigGroup.maxAllowedPickupDelay = maxPickupDelay; + drtConfigGroup.maxDetourAlpha = detourAlpha; + drtConfigGroup.maxDetourBeta = detourBeta; + drtConfigGroup.maxAbsoluteDetour = maxAbsDetour; + + drtConfigGroup.maxWaitTime = maxWaitTime; + + drtConfigGroup.maxTravelTimeAlpha = 10; + drtConfigGroup.maxTravelTimeBeta = 7200; + + drtConfigGroup.rejectRequestIfMaxWaitOrTravelTimeViolated = allowRejection; + drtConfigGroup.stopDuration = stopDuration; + + Controler controler = DrtControlerCreator.createControler(config, false); + controler.addOverridingQSimModule(new AbstractDvrpModeQSimModule(drtConfigGroup.mode) { + @Override + protected void configureQSim() { + bindModal(DrtOfferAcceptor.class).toProvider(modalProvider(getter -> + new MaxDetourOfferAcceptor(drtConfigGroup.maxAllowedPickupDelay))); + } + }); + + // Prepare network by adding DRT mode to service area + PrepareNetwork.prepareDRT(controler.getScenario().getNetwork(), new ShpOptions(drtArea, null, null)); + + controler.run(); + return outputDirectory; + } + + + private String createTemporaryConfig(Path configPath, String output) throws IOException { + int taskId = (int) (System.currentTimeMillis() / 1000); + File originalConfig = new File(configPath.toString()); + String temporaryConfig = configPath.getParent().toString() + "/temporary_" + taskId + ".config.xml"; + File copy = new File(temporaryConfig); + try { + FileUtils.copyFile(originalConfig, copy); + } catch (IOException e) { + e.printStackTrace(); + } + + if (!Files.exists(Path.of(output))) { + Files.createDirectory(Path.of(output)); + } + return temporaryConfig; + } + + private boolean analyzeResult(String outputDirectory) throws IOException { + Path outputDirectoryPath = Path.of(outputDirectory); + DetourAnalysis.DetourAnalysisEventsHandler analysisEventsHandler = + new DetourAnalysis.DetourAnalysisEventsHandler(outputDirectoryPath); + analysisEventsHandler.readEvents(); + analysisEventsHandler.writeAnalysis(); + if (allowRejection) { + return analysisEventsHandler.getRejectedPersons().size() == 0; + } + return analysisEventsHandler.get95pctWaitTime() <= maxWaitTime; + } +} diff --git a/src/main/java/org/matsim/run/prepare/PrepareNetwork.java b/src/main/java/org/matsim/run/prepare/PrepareNetwork.java index fb12e18..1492cbd 100644 --- a/src/main/java/org/matsim/run/prepare/PrepareNetwork.java +++ b/src/main/java/org/matsim/run/prepare/PrepareNetwork.java @@ -59,7 +59,7 @@ public Integer call() throws Exception { * Adapt network to one or more drt service areas. Therefore, a shape file of the wished service area + a list * of drt modes are needed. */ - static void prepareDRT(Network network, ShpOptions shp) { + public static void prepareDRT(Network network, ShpOptions shp) { List features = shp.readFeatures(); Map modeGeoms = new HashMap<>();