Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved Scenario Cutout #3746

Merged
merged 11 commits into from
Feb 26, 2025
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package org.matsim.application.prepare.population;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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.*;
import org.matsim.application.MATSimAppCommand;
import org.matsim.core.network.NetworkUtils;
import org.matsim.core.population.PopulationUtils;
import org.matsim.core.population.algorithms.ParallelPersonAlgorithmUtils;
import org.matsim.core.population.algorithms.PersonAlgorithm;
import org.matsim.core.population.routes.NetworkRoute;
import picocli.CommandLine;

import java.util.Objects;
import java.util.stream.Stream;

/**
* Checks the plan of a person for non-existing link ids.
* Invalid occurrences will be removed and routes or activities link ids reset if necesarry.
*
* This class can be used from CLI or as a {@link PersonAlgorithm}.
*/
@CommandLine.Command(name = "person-network-link-check", description = "Check the plan of a person for non-existing link ids.")
public final class PersonNetworkLinkCheck implements MATSimAppCommand, PersonAlgorithm {

private static final Logger log = LogManager.getLogger(PersonNetworkLinkCheck.class);

@CommandLine.Option(names = {"--input", "--population"}, description = "Path to population", required = true)
private String populationPath;

@CommandLine.Option(names = "--network", description = "Path to network", required = true)
private String networkPath;

@CommandLine.Option(names = "--output", description = "Path to output population", required = true)
private String output;

private Network network;

@SuppressWarnings("unused")
public PersonNetworkLinkCheck() {
}

private PersonNetworkLinkCheck(Network network) {
this.network = network;
}

/**
* Create an instance of the class used directly as a {@link PersonAlgorithm}.
*/
public static PersonAlgorithm createPersonAlgorithm(Network network) {
return new PersonNetworkLinkCheck(network);
}

public static void main(String[] args) {
new PersonNetworkLinkCheck().execute(args);
}

@Override
public Integer call() throws Exception {

network = NetworkUtils.readNetwork(networkPath);
Population population = PopulationUtils.readPopulation(populationPath);
ParallelPersonAlgorithmUtils.run(population, Runtime.getRuntime().availableProcessors(), this);

PopulationUtils.writePopulation(population, output);

return 0;
}

@Override
public void run(Person person) {

Objects.requireNonNull(network, "Network not set. Make sure to use PersonNetworkLinkCheck.createPersonAlgorithm(network).");

for (Plan plan : person.getPlans()) {

for (PlanElement el : plan.getPlanElements()) {

if (el instanceof Activity act) {
checkActivity(person, act);
} else if (el instanceof Leg leg) {

if (leg.getRoute() instanceof NetworkRoute r)
checkNetworkRoute(leg, r);
else if (leg.getRoute() != null)
checkRoute(leg, leg.getRoute());

}
}
}
}

private void checkActivity(Person person, Activity act) {
// activity link ids are reset, if they are not contained in the network
if (act.getLinkId() != null && !network.getLinks().containsKey(act.getLinkId())) {

act.setLinkId(null);
if (act.getFacilityId() == null && act.getCoord() == null) {
log.warn("Person {} now has activity without link id and no facility id or coordinate.", person.getId());
}
}
}

private void checkRoute(Leg leg, Route route) {
if (!network.getLinks().containsKey(route.getStartLinkId()) || !network.getLinks().containsKey(route.getEndLinkId()))
leg.setRoute(null);
}

private void checkNetworkRoute(Leg leg, NetworkRoute r) {

Stream<Id<Link>> stream = Stream.concat(Stream.of(r.getStartLinkId(), r.getEndLinkId()), r.getLinkIds().stream());

boolean valid = stream.allMatch(l -> {

Link link = network.getLinks().get(l);

// Check if link is present in the network
if (link == null)
return false;

// Check if the link has the needed mode
return link.getAllowedModes().contains(leg.getMode());
});

if (!valid) {
leg.setRoute(null);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
import org.matsim.core.population.PopulationUtils;
import org.matsim.core.population.algorithms.ParallelPersonAlgorithmUtils;
import org.matsim.core.population.algorithms.PersonAlgorithm;
import org.matsim.application.prepare.population.PersonNetworkLinkCheck;
import org.matsim.core.population.routes.NetworkRoute;
import org.matsim.core.router.DefaultAnalysisMainModeIdentifier;
import org.matsim.core.router.TripStructureUtils;
import org.matsim.core.router.TripStructureUtils.Trip;
import org.matsim.core.router.costcalculators.OnlyTimeDependentTravelDisutility;
Expand All @@ -44,9 +44,6 @@

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* Cuts out a part of the Population and Network which is relevant inside the specified shape of the shapefile.<br><br>
Expand Down Expand Up @@ -129,9 +126,15 @@ public class CreateScenarioCutOut implements MATSimAppCommand, PersonAlgorithm {
@CommandLine.Option(names = "--network-modes", description = "Modes to consider when cutting network", defaultValue = "car,bike", split = ",")
private Set<String> modes;

@CommandLine.Option(names = "--keep-modes", description = "Network modes of links that are always kept", defaultValue = TransportMode.pt, split = ",")
@CommandLine.Option(names = "--clean-modes", description = "Additional modes to consider for network cleaning", split = ",")
private Set<String> cleanModes;

@CommandLine.Option(names = "--keep-modes", description = "Network modes of links that are always kept. No change events will be generated for these.", defaultValue = TransportMode.pt, split = ",")
private Set<String> keepModes;

@CommandLine.Option(names = "--check-beeline", description = "Additional check if agents might cross the zone using a direct beeline.")
private boolean checkBeeline;

@CommandLine.Mixin
private CrsOptions crs;

Expand Down Expand Up @@ -307,10 +310,17 @@ public Integer call() throws Exception {
cleaner.run(Set.of(mode));
}

if (cleanModes != null) {
for (String mode : cleanModes) {
log.info("Cleaning mode {}", mode);
cleaner.run(Set.of(mode));
}
}

log.info("number of links after cleaning: {}", scenario.getNetwork().getLinks().size());
log.info("number of nodes after cleaning: {}", scenario.getNetwork().getNodes().size());

ParallelPersonAlgorithmUtils.run(scenario.getPopulation(), Runtime.getRuntime().availableProcessors(), new CleanPersonLinkIds());
ParallelPersonAlgorithmUtils.run(scenario.getPopulation(), Runtime.getRuntime().availableProcessors(), PersonNetworkLinkCheck.createPersonAlgorithm(scenario.getNetwork()));

PopulationUtils.writePopulation(scenario.getPopulation(), outputPopulation);

Expand Down Expand Up @@ -476,19 +486,34 @@ private List<NetworkChangeEvent> generateNetworkChangeEvents(double timeFrameLen
if (geomBuffer.contains(MGC.coord2Point(link.getCoord())))
continue;

// Don't generate events for these fixed modes.
if (link.getAllowedModes().equals(keepModes))
continue;


// Setting capacity outside shapefile (and buffer) to a very large value, not max value, as this causes problem in the qsim
link.setCapacity(1_000_000);

Double prevSpeed = null;

// Do this for the whole simulation run
for (double time = 0; time < changeEventsMaxTime; time += timeFrameLength) {

// Setting freespeed to the link average
double freespeed = link.getLength() / tt.getLinkTravelTimes().getLinkTravelTime(link, time, null, null);

// Skip if the speed is the same as the previous speed
if (prevSpeed != null && Math.abs(freespeed - prevSpeed) < 1e-6) {
continue;
}

NetworkChangeEvent event = new NetworkChangeEvent(time);
event.setFreespeedChange(new NetworkChangeEvent.ChangeValue(NetworkChangeEvent.ChangeType.ABSOLUTE_IN_SI_UNITS, freespeed));
NetworkUtils.addNetworkChangeEvent(scenario.getNetwork(), event);
event.addLink(link);
events.add(event);

prevSpeed = freespeed;
}
}

Expand Down Expand Up @@ -533,13 +558,13 @@ public void run(Person person) {
Coord originCoord = getActivityCoord(trip.getOriginActivity());
Coord destinationCoord = getActivityCoord(trip.getDestinationActivity());

if (originCoord != null && destinationCoord != null) {
// also keep persons traveling through or close to area (beeline)
if (checkBeeline && originCoord != null && destinationCoord != null) {
LineString line = geoFactory.createLineString(new Coordinate[]{
MGC.coord2Coordinate(originCoord),
MGC.coord2Coordinate(destinationCoord)
});

// also keep persons traveling through or close to area (beeline)
if (line.intersects(geom)) {
keepPerson = true;
}
Expand Down Expand Up @@ -599,53 +624,4 @@ public void run(Person person) {
}


private final class CleanPersonLinkIds implements PersonAlgorithm {


@Override
public void run(Person person) {

Plan plan = person.getSelectedPlan();
Network network = scenario.getNetwork();

for (Trip trip : TripStructureUtils.getTrips(plan)) {
// activity link ids are reset, if they are not retained in the cleaned network
if (trip.getOriginActivity().getLinkId() != null) {
if (!network.getLinks().containsKey(trip.getOriginActivity().getLinkId()))
trip.getOriginActivity().setLinkId(null);
}

if (trip.getDestinationActivity().getLinkId() != null) {
if (!network.getLinks().containsKey(trip.getDestinationActivity().getLinkId()))
trip.getDestinationActivity().setLinkId(null);
}
}

for (Leg leg : TripStructureUtils.getLegs(plan)) {

if (!(leg.getRoute() instanceof NetworkRoute r))
continue;

Stream<Id<Link>> stream = Stream.concat(Stream.of(r.getStartLinkId(), r.getEndLinkId()), r.getLinkIds().stream());

boolean valid = stream.allMatch(l -> {

Link link = network.getLinks().get(l);

// Check if link is present in the network
if (link == null)
return false;

// Check if the link has the needed mode
return link.getAllowedModes().contains(leg.getMode());
});

if (!valid) {
PopulationUtils.resetRoutes(plan);
break;
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class CreateScenarioCutOutTest {
*/
@Test
void testBasicCutout() {
// TODO Rueckfrage: Soll die cap auch auf inf gesetzt werden, wenn es keine Events gibt, die die freespeed anpassen?

new CreateScenarioCutOut().execute(
"--buffer", "100",
"--population", utils.getClassInputDirectory() + "plans_without_facilities.xml",
Expand Down
Loading
Loading