diff --git a/input/v1.1/lausitz-pt-case-test_experienced_plans_1person.xml.gz b/input/v1.1/lausitz-pt-case-test_experienced_plans_1person.xml.gz deleted file mode 100644 index 12097b4..0000000 Binary files a/input/v1.1/lausitz-pt-case-test_experienced_plans_1person.xml.gz and /dev/null differ diff --git a/input/v1.1/lausitz-pt-case-test_plans_1person.xml.gz b/input/v1.1/lausitz-pt-case-test_plans_1person.xml.gz new file mode 100644 index 0000000..d716a0f Binary files /dev/null and b/input/v1.1/lausitz-pt-case-test_plans_1person.xml.gz differ diff --git a/src/main/java/org/matsim/run/DrtOptions.java b/src/main/java/org/matsim/run/DrtOptions.java index 9de6cb7..38fe1e9 100644 --- a/src/main/java/org/matsim/run/DrtOptions.java +++ b/src/main/java/org/matsim/run/DrtOptions.java @@ -4,6 +4,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.geotools.api.feature.simple.SimpleFeature; +import org.geotools.api.feature.simple.SimpleFeatureType; +import org.geotools.feature.simple.SimpleFeatureBuilder; +import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.Scenario; import org.matsim.api.core.v01.TransportMode; @@ -24,6 +27,7 @@ import org.matsim.core.utils.gis.GeoFileWriter; import org.matsim.core.utils.io.IOUtils; import org.matsim.pt.config.TransitRouterConfigGroup; +import org.matsim.run.prepare.PrepareDrtScenarioAgents; import org.matsim.run.prepare.PrepareNetwork; import org.matsim.run.prepare.PrepareTransitSchedule; import org.matsim.vehicles.Vehicle; @@ -32,6 +36,10 @@ import org.matsim.vehicles.VehicleUtils; import picocli.CommandLine; +import java.io.File; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -40,6 +48,7 @@ */ public class DrtOptions { private static final Logger log = LogManager.getLogger(DrtOptions.class); + public static final String DRT_DUMMY_ACT_TYPE = "drt-split-trip"; @CommandLine.Option(names = "--drt-shp", description = "Path to shp file for adding drt not network links as an allowed mode.", defaultValue = "./drt-area/nord-bautzen-waiting-times_utm32N.shp") private String drtAreaShp; @@ -65,12 +74,14 @@ public class DrtOptions { @CommandLine.Option(names = "--intermodal", defaultValue = "INTERMODALITY_ACTIVE", description = "enable intermodality for DRT service") private IntermodalityHandling intermodal; + @CommandLine.Option(names = "--manual-trip-conversion", defaultValue = "NOT_CONVERT_TRIPS_MANUALLY", description = "enable manual trip conversion from pt to drt " + + "(for legs with new pt line of LausitzPtScenario).") + private ManualTripConversionHandling manualTripConversion; + /** * a helper method, which makes all necessary config changes to simulate drt. */ public void configureDrtConfig(Config config) { -// check if every feature of shp file has attr typ_wt for drt estimation. Add attr with standard value if not present. - checkServiceAreaShapeFile(config); DvrpConfigGroup dvrpConfigGroup = ConfigUtils.addOrGetModule(config, DvrpConfigGroup.class); dvrpConfigGroup.networkModes = Set.of(TransportMode.drt); @@ -93,6 +104,11 @@ public void configureDrtConfig(Config config) { optimizationConstraintsSet.maxWalkDistance = ConfigUtils.addOrGetModule(config, TransitRouterConfigGroup.class).getSearchRadius(); drtConfigGroup.addParameterSet(optimizationConstraints); drtConfigGroup.addParameterSet(new ExtensiveInsertionSearchParams()); + + // check if every feature of shp file has attr typ_wt for drt estimation. Add attr with standard value if not present +// + set new shp file as drtServiceAreaShapeFile + checkServiceAreaShapeFile(config, drtConfigGroup); + multiModeDrtConfigGroup.addParameterSet(drtConfigGroup); } @@ -144,6 +160,15 @@ public void configureDrtConfig(Config config) { srrConfig.addIntermodalAccessEgress(accessEgressWalkParam); } + + if (manualTripConversion == ManualTripConversionHandling.CONVERT_TRIPS_MANUALLY) { + ScoringConfigGroup.ActivityParams drtDummyScoringParams = new ScoringConfigGroup.ActivityParams(); + drtDummyScoringParams.setTypicalDuration(0.); + drtDummyScoringParams.setActivityType(DRT_DUMMY_ACT_TYPE); + drtDummyScoringParams.setScoringThisActivityAtAll(false); + + scoringConfigGroup.addActivityParams(drtDummyScoringParams); + } } /** @@ -180,31 +205,67 @@ public void configureDrtScenario(Scenario scenario) { drtDummy.getAttributes().putAttribute("serviceEndTime", 86400.); scenario.getVehicles().addVehicle(drtDummy); + } -// tag intermodal pt stops for intermodality between pt and drt - if (intermodal == IntermodalityHandling.INTERMODALITY_ACTIVE) { - PrepareTransitSchedule.tagIntermodalStops(scenario.getTransitSchedule(), new ShpOptions(IOUtils.extendUrl(scenario.getConfig().getContext(), intermodalAreaShp).toString(), null, null)); - } + // tag intermodal pt stops for intermodality between pt and drt + if (intermodal == IntermodalityHandling.INTERMODALITY_ACTIVE) { + PrepareTransitSchedule.tagIntermodalStops(scenario.getTransitSchedule(), new ShpOptions(IOUtils.extendUrl(scenario.getConfig().getContext(), intermodalAreaShp).toString(), null, null)); + } + + if (manualTripConversion == ManualTripConversionHandling.CONVERT_TRIPS_MANUALLY) { + PrepareDrtScenarioAgents.convertVspRegionalTrainLegsToDrt(scenario.getPopulation(), scenario.getNetwork()); } } - private void checkServiceAreaShapeFile(Config config) { - ShpOptions shp = new ShpOptions(getDrtAreaShp(), null, null); + private void checkServiceAreaShapeFile(Config config, DrtConfigGroup drtConfigGroup) { + ShpOptions shp = new ShpOptions(IOUtils.extendUrl(config.getContext(), getDrtAreaShp()).toString(), null, null); List features = shp.readFeatures(); + List newFeatures = new ArrayList<>(); boolean adapted = false; for (SimpleFeature feature : features) { if (feature.getAttribute("typ_wt") == null) { - feature.setAttribute("typ_wt", 10 * 60.); + SimpleFeatureType existingFeatureType = feature.getFeatureType(); + + SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder(); + builder.init(existingFeatureType); + + builder.add("typ_wt", Double.class); + SimpleFeatureType newFeatureType = builder.buildFeatureType(); + + SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(newFeatureType); + + List existingAttributes = feature.getAttributes(); + featureBuilder.addAll(existingAttributes); + featureBuilder.add(10 * 60.); + + // Step 7: Build the new feature with a unique ID (same geometry, updated attributes) + SimpleFeature newFeature = featureBuilder.buildFeature(feature.getID()); + newFeatures.add(newFeature); adapted = true; + } else { + newFeatures.add(feature); } } if (adapted) { + String newServiceAreaPath; + try { + File file = new File(Path.of(IOUtils.extendUrl(config.getContext(), getDrtAreaShp()).toURI()).getParent().toString(), + Path.of(IOUtils.extendUrl(config.getContext(), getDrtAreaShp()).toURI()).getFileName().toString().split(".shp")[0] + "-with-waiting-time.shp"); + newServiceAreaPath = file.getAbsolutePath(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + + + + log.warn("For drt service area shape file {}, at least one feature did not have the obligatory attribute typ_wt. " + - "The attribute is needed for drt estimation. The attribute was added with a standard value of 10min for those features.", getDrtAreaShp()); + "The attribute is needed for drt estimation. The attribute was added with a standard value of 10min for those features " + + "and saved to file {}.", IOUtils.extendUrl(config.getContext(), getDrtAreaShp()), newServiceAreaPath); - GeoFileWriter.writeGeometries(features, IOUtils.extendUrl(config.getContext(), getDrtAreaShp()).toString()); - log.warn("Adapted drt service area shp file written to {}.", IOUtils.extendUrl(config.getContext(), getDrtAreaShp())); + GeoFileWriter.writeGeometries(newFeatures, newServiceAreaPath); + drtConfigGroup.drtServiceAreaShapeFile = newServiceAreaPath; } } @@ -237,4 +298,9 @@ public double getRideTimeStd() { */ enum IntermodalityHandling {INTERMODALITY_ACTIVE, INTERMODALITY_NOT_ACTIVE} + /** + * Defines if pt legs with new pt regional train from LausitzPtScenario are converted to drt legs manually or not. + */ + enum ManualTripConversionHandling {CONVERT_TRIPS_MANUALLY, NOT_CONVERT_TRIPS_MANUALLY} + } diff --git a/src/main/java/org/matsim/run/prepare/PrepareDrtScenarioAgents.java b/src/main/java/org/matsim/run/prepare/PrepareDrtScenarioAgents.java index ff524e9..645b727 100644 --- a/src/main/java/org/matsim/run/prepare/PrepareDrtScenarioAgents.java +++ b/src/main/java/org/matsim/run/prepare/PrepareDrtScenarioAgents.java @@ -1,12 +1,9 @@ package org.matsim.run.prepare; -import com.google.common.collect.Lists; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; -import org.locationtech.jts.geom.Geometry; import org.matsim.api.core.v01.TransportMode; -import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Network; import org.matsim.api.core.v01.population.*; import org.matsim.application.MATSimAppCommand; @@ -16,10 +13,8 @@ import org.matsim.core.network.NetworkUtils; import org.matsim.core.network.filter.NetworkFilterManager; import org.matsim.core.population.PopulationUtils; -import org.matsim.core.router.AnalysisMainModeIdentifier; -import org.matsim.core.router.DefaultAnalysisMainModeIdentifier; import org.matsim.core.router.TripStructureUtils; -import org.matsim.core.utils.geometry.geotools.MGC; +import org.matsim.run.DrtOptions; import picocli.CommandLine; import java.nio.file.Files; @@ -42,7 +37,6 @@ public class PrepareDrtScenarioAgents implements MATSimAppCommand { @CommandLine.Mixin private final ShpOptions shp = new ShpOptions(); - private static final String PLAN_TYPE = "drtPlan"; private static final String PT_INTERACTION = "pt interaction"; public static void main(String[] args) { @@ -64,7 +58,9 @@ public Integer call() throws Exception { Population population = PopulationUtils.readPopulation(input.toString()); Network network = NetworkUtils.readNetwork(networkPath); -// convertPtToDrtTrips(population, network, shp); + // shp needs to include all locations, where the new pt line (from pt policy case) has a station +// thus, lausitz.shp should be chosen as an input + PrepareNetwork.prepareDrtNetwork(network, shp.getShapeFile()); // TODO: try if for 3 and 5 it is enough to delete act locations instead of searching for nearest drt link convertVspRegionalTrainLegsToDrt(population, network); @@ -74,12 +70,12 @@ public Integer call() throws Exception { return 0; } - private void convertVspRegionalTrainLegsToDrt(Population population, Network network) { -// shp needs to include all locations, where the new pt line (from pt policy case) has a station -// thus, lausitz.shp should be chosen as an input - PrepareNetwork.prepareDrtNetwork(network, shp.getShapeFile()); - - NetworkFilterManager manager = new NetworkFilterManager(network, new NetworkConfigGroup()); + /** + * Method to convert agents, which are using the new vsp pt line (see RunLausitzPtScenario) manually to mode DRT. + * The network needs to be including DRT as an allowed mode. + */ + public static void convertVspRegionalTrainLegsToDrt(Population population, Network networkInclDrt) { + NetworkFilterManager manager = new NetworkFilterManager(networkInclDrt, new NetworkConfigGroup()); manager.addLinkFilter(l -> l.getAllowedModes().contains(TransportMode.drt)); Network filtered = manager.applyFilters(); @@ -121,7 +117,7 @@ private void convertVspRegionalTrainLegsToDrt(Population population, Network net } if (selected.getPlanElements().get(i - 2) instanceof Activity prev) { - convertToDrtInteraction(act, prev, network, filtered); + convertToDrtInteractionAndSplitTrip(act, prev, filtered); } else { logWrongPlanElementType(person, i); throw new IllegalStateException(); @@ -133,6 +129,7 @@ private void convertVspRegionalTrainLegsToDrt(Population population, Network net // pt leg with new pt line leg.setRoute(null); leg.setMode(TransportMode.drt); + leg.setRoutingMode(TransportMode.drt); leg.setTravelTimeUndefined(); leg.setDepartureTimeUndefined(); leg.getAttributes().removeAttribute("enterVehicleTime"); @@ -146,7 +143,7 @@ private void convertVspRegionalTrainLegsToDrt(Population population, Network net throw new IllegalStateException(); } if (selected.getPlanElements().get(i + 2) instanceof Activity next) { - convertToDrtInteraction(act, next, network, filtered); + convertToDrtInteractionAndSplitTrip(act, next, filtered); } else { logWrongPlanElementType(person, i); throw new IllegalStateException(); @@ -174,93 +171,19 @@ private static void logWrongPlanElementType(Person person, int i) { "It seems to be a leg. Abort.", person.getId(), i); } - private static void convertToDrtInteraction(Activity act, Activity previous, Network fullNetwork, Network filtered) { + private static void convertToDrtInteractionAndSplitTrip(Activity act, Activity previous, Network filtered) { // TODO: test if it is enough to delete link and facility, but keep coord. Correct link shoulb be found automatically then - if (filtered.getLinks().containsKey(previous.getLinkId())) { - act.setLinkId(previous.getLinkId()); - } else { - act.setLinkId(NetworkUtils.getNearestLink(filtered, fullNetwork.getLinks().get(previous.getLinkId()).getToNode().getCoord()).getId()); +// The original trip has to be split up because MATSim does not allow trips with 2 different routing modes. +// for the drt subtrip, a dummy act, which is not scored, is created. + if (TripStructureUtils.isStageActivityType(previous.getType())) { + previous.setType(DrtOptions.DRT_DUMMY_ACT_TYPE); + previous.setFacilityId(null); + previous.setLinkId(null); } + act.setLinkId(NetworkUtils.getNearestLink(filtered, previous.getCoord()).getId()); act.setFacilityId(null); act.setCoord(null); act.setType("drt interaction"); } - - /** - * This is implemented as a separate method to be able to use it in a scenario run class. - * Additionally, it can be used to write a new output population by calling this class. - */ - public static void convertPtToDrtTrips(Population population, Network network, ShpOptions shp) { - Geometry serviceArea = shp.getGeometry(); - - AnalysisMainModeIdentifier identifier = new DefaultAnalysisMainModeIdentifier(); - - log.info("Starting to iterate through population."); - - int count = 0; - for (Person person : population.getPersons().values()) { - Plan selected = person.getSelectedPlan(); -// remove all unselected plans - for (Plan plan : Lists.newArrayList(person.getPlans())) { - if (plan != selected) - person.removePlan(plan); - } - - for (TripStructureUtils.Trip trip : TripStructureUtils.getTrips(selected)) { - - String tripMode = identifier.identifyMainMode(trip.getTripElements()); - - if (!tripMode.equals(TransportMode.pt)) { - continue; - } - - boolean startInside = isInside(network.getLinks().get(trip.getLegsOnly().getFirst().getRoute().getStartLinkId()), serviceArea); - boolean endInside = isInside(network.getLinks().get(trip.getLegsOnly().getLast().getRoute().getEndLinkId()), serviceArea); - -// we only need to change the mode for trips within the drt service area. -// All others will be handled by intermodal trips between drt and pt. -// "other" would be ending in service area but not starting and vice versa - if (startInside && endInside) { - int oldIndex = selected.getPlanElements().indexOf(trip.getLegsOnly().stream().filter(l -> l.getMode().equals(TransportMode.pt)).toList().getFirst()); - -// TODO: erst plan kopieren dann converten - int index = convertPtTripToLeg(trip, selected, identifier); - -// copy pt plan and create drt plan. Tag it as drtPlan - Plan drtCopy = person.createCopyOfSelectedPlanAndMakeSelected(); - ((Leg) drtCopy.getPlanElements().get(index)).setMode(TransportMode.drt); - drtCopy.setType(PLAN_TYPE); - count++; - } - } - } - log.info("For {} trips, a copy of the selected plan with a drt trip has been created.", count); - } - - private static int convertPtTripToLeg(TripStructureUtils.Trip trip, Plan selected, AnalysisMainModeIdentifier identifier) { - final List planElements = selected.getPlanElements(); - -// TODO: test if new leg is pasted at correct index. -// TODO: index in this method is always -1. fix this - -// TODO: rather use trips2LegsALgo instead of copy paste - final List fullTrip = - planElements.subList( - planElements.indexOf(trip.getOriginActivity()) + 1, - planElements.indexOf(trip.getDestinationActivity())); - final String mode = identifier.identifyMainMode(fullTrip); - fullTrip.clear(); - Leg leg = PopulationUtils.createLeg(mode); - TripStructureUtils.setRoutingMode(leg, mode); - int index = planElements.indexOf(leg); - fullTrip.add(leg); - if ( fullTrip.size() != 1 ) throw new IllegalArgumentException(fullTrip.toString()); - return index; - } - - private static boolean isInside(Link link, Geometry geometry) { - return MGC.coord2Point(link.getFromNode().getCoord()).within(geometry) || - MGC.coord2Point(link.getToNode().getCoord()).within(geometry); - } } diff --git a/src/test/java/org/matsim/run/prepare/PrepareDrtScenarioAgentsTest.java b/src/test/java/org/matsim/run/prepare/PrepareDrtScenarioAgentsTest.java index 61b690a..c8fb8e4 100644 --- a/src/test/java/org/matsim/run/prepare/PrepareDrtScenarioAgentsTest.java +++ b/src/test/java/org/matsim/run/prepare/PrepareDrtScenarioAgentsTest.java @@ -22,7 +22,7 @@ class PrepareDrtScenarioAgentsTest { @Test void testPrepareDrtScenarioAgents() { - String inputPopulationPath = "./input/v1.1/lausitz-pt-case-test_experienced_plans_1person.xml.gz"; + String inputPopulationPath = "./input/v1.1/lausitz-pt-case-test_plans_1person.xml.gz"; Population in = PopulationUtils.readPopulation(inputPopulationPath); String networkPath = URL + String.format("lausitz-v%s-network-with-pt.xml.gz", LausitzScenario.VERSION); String outPath = utils.getOutputDirectory() + "/drt-test-population.xml.gz";