Skip to content

Commit

Permalink
Improved Scenario Cutout (#3746)
Browse files Browse the repository at this point in the history
* added person network link checker which looks at all routes and activities

* add log warning

* check for null

* remove left over todo

* add option for beeline check in scenario cutout

* better error message for inconsistent links

* skip writing change event that do not change the value

* improve api of person network link check

* don't generate change events for pt links

* add input alias

* extra option for network cleaning
  • Loading branch information
rakow authored Feb 26, 2025
1 parent 4348eb8 commit 0837a20
Show file tree
Hide file tree
Showing 5 changed files with 2,967 additions and 17,540 deletions.
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

0 comments on commit 0837a20

Please sign in to comment.