Skip to content

Commit

Permalink
Two new tools and extending an existing one (#187)
Browse files Browse the repository at this point in the history
* feat: ExportActivitiesToShapeFile

* feat: ExportPopulationToCSV

* feat: extending ExportTransitLinesToShapefile

Two features:
- It is now possible to specify which items are exported by one of transit line id, transit route id or mode.
- The line name is now included in the exported shapefile

* fix: renamed ExportActivitiesToShapefile to have consistent naming of tools
  • Loading branch information
tkchouaki authored Dec 19, 2023
1 parent 401852e commit e23f643
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package org.eqasim.core.tools;

import org.locationtech.jts.geom.Coordinate;
import org.matsim.api.core.v01.Scenario;
import org.matsim.api.core.v01.population.Activity;
import org.matsim.api.core.v01.population.Person;
import org.matsim.api.core.v01.population.PlanElement;
import org.matsim.api.core.v01.population.Population;
import org.matsim.core.config.CommandLine;
import org.matsim.core.config.ConfigUtils;
import org.matsim.core.population.io.PopulationReader;
import org.matsim.core.scenario.ScenarioUtils;
import org.matsim.core.utils.geometry.geotools.MGC;
import org.matsim.core.utils.gis.PointFeatureFactory;
import org.matsim.core.utils.gis.ShapeFileWriter;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

import java.util.*;
import java.util.stream.Collectors;

public class ExportActivitiesToShapefile {

public static void exportActivitiesToShapeFile(Population population, String crsString, Set<String> ignoredActivityTypesSet, String outputPath) {

CoordinateReferenceSystem crs = MGC.getCRS(crsString);

PointFeatureFactory pointFactory = new PointFeatureFactory.Builder() //
.setCrs(crs).setName("id") //
.addAttribute("personId", String.class)
.addAttribute("activityIndex", Integer.class)
.addAttribute("type", String.class)//
.addAttribute("linkId", String.class)
.addAttribute("facilityId", String.class)
.addAttribute("startTime", Double.class)
.addAttribute("endTime", Double.class)//
.create();

Collection<SimpleFeature> features = new LinkedList<>();

for(Person person: population.getPersons().values()) {
if(person.getSelectedPlan() == null) {
continue;
}
int activityIndex = -1;
for(PlanElement planElement: person.getSelectedPlan().getPlanElements()) {
if (!(planElement instanceof Activity)) {
continue;
}
Activity a = (Activity) planElement;
activityIndex++;
if(ignoredActivityTypesSet.contains(a.getType())) {
continue;
}
Coordinate coordinate = new Coordinate(a.getCoord().getX(), a.getCoord().getY());
SimpleFeature feature = pointFactory.createPoint(coordinate,
new Object[] {
person.getId().toString(),
activityIndex,
a.getType(),
a.getLinkId().toString(),
a.getFacilityId() == null ? null : a.getFacilityId().toString(),
a.getStartTime().orElse(Double.NaN),
a.getEndTime().orElse(Double.NaN)
},
null);
features.add(feature);
}
}
ShapeFileWriter.writeGeometries(features, outputPath);
}


public static void main(String[] args) throws CommandLine.ConfigurationException {
CommandLine commandLine = new CommandLine.Builder(args).requireOptions("plans-path", "output-path", "crs")
.allowOptions("ignored-activity-types").build();

String plansPath = commandLine.getOptionStrict("plans-path");
String outputPath = commandLine.getOptionStrict("output-path");
String crs = commandLine.getOptionStrict("crs");
Set<String> ignoredActivityTypes = Arrays.stream(commandLine.getOption("ignored-activity-types").orElse("").split(","))
.map(String::trim)
.filter(s -> s.length()>0)
.collect(Collectors.toSet());

Scenario scenario = ScenarioUtils.createScenario(ConfigUtils.createConfig());
PopulationReader populationReader = new PopulationReader(scenario);
populationReader.readFile(plansPath);

exportActivitiesToShapeFile(scenario.getPopulation(), crs, ignoredActivityTypes, outputPath);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.eqasim.core.tools;

import org.matsim.api.core.v01.Scenario;
import org.matsim.api.core.v01.population.Person;
import org.matsim.api.core.v01.population.Population;
import org.matsim.core.config.CommandLine;
import org.matsim.core.config.ConfigUtils;
import org.matsim.core.population.io.PopulationReader;
import org.matsim.core.scenario.ScenarioUtils;

import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;

public class ExportPopulationToCSV {

public static final String[] IGNORED_ATTRIBUTES = new String[]{"vehicles"};

public static void exportPopulationToCSV(Population population, String filePath) {

List<String> ignoredAttributes = List.of(IGNORED_ATTRIBUTES);

List<String> attributes = population.getPersons().values().stream()
.flatMap(p -> p.getAttributes().getAsMap().keySet().stream())
.distinct()
.filter(attribute -> !ignoredAttributes.contains(attribute))
.collect(Collectors.toList());

String[] header = new String[attributes.size()+1];
header[0] = "person_id";
for(int i=0; i<attributes.size(); i++) {
header[i+1] = attributes.get(i);
}

try {
FileWriter fileWriter = new FileWriter(filePath);
fileWriter.write(String.join(";", header) + "\n");
for(Person person: population.getPersons().values()) {
String[] line = new String[attributes.size()+1];
line[0] = person.getId().toString();
for(int i=0; i<attributes.size(); i++) {
line[i+1] = String.valueOf(person.getAttributes().getAsMap().getOrDefault(attributes.get(i), null));
}
fileWriter.write(String.join(";", line) + "\n");
}
fileWriter.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}


public static void main(String[] args) throws CommandLine.ConfigurationException {
CommandLine commandLine = new CommandLine.Builder(args).requireOptions("plans-path", "output-path").build();

String plansPath = commandLine.getOptionStrict("plans-path");
Scenario scenario = ScenarioUtils.createScenario(ConfigUtils.createConfig());

new PopulationReader(scenario).readFile(plansPath);

exportPopulationToCSV(scenario.getPopulation(), commandLine.getOptionStrict("output-path"));
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package org.eqasim.core.tools;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;

import org.apache.commons.lang3.BooleanUtils;
import org.locationtech.jts.geom.Coordinate;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.IdSet;
import org.matsim.api.core.v01.Scenario;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
Expand All @@ -27,11 +28,19 @@
public class ExportTransitLinesToShapefile {
public static void main(String[] args) throws Exception {
CommandLine cmd = new CommandLine.Builder(args) //
.requireOptions("schedule-path", "network-path", "output-path", "crs") //
.requireOptions("schedule-path", "network-path", "output-path", "crs")
.allowOptions("modes", "transit-lines", "transit-routes")
.build();

String schedulePath = cmd.getOptionStrict("schedule-path");
String networkPath = cmd.getOptionStrict("network-path");
Optional<String> modesOption = cmd.getOption("modes");
Optional<String> transitLinesOption = cmd.getOption("transit-lines");
Optional<String> transitRoutesOption = cmd.getOption("transit-routes");

if(BooleanUtils.toInteger(modesOption.isPresent()) + BooleanUtils.toInteger(transitLinesOption.isPresent()) + BooleanUtils.toInteger(transitRoutesOption.isPresent()) > 1) {
throw new IllegalStateException("Only one of the options 'modes', 'transit-lines' and 'transit-routes' can be used");
}

Config config = ConfigUtils.createConfig();
Scenario scenario = ScenarioUtils.createScenario(config);
Expand All @@ -45,14 +54,34 @@ public static void main(String[] args) throws Exception {
PolylineFeatureFactory linkFactory = new PolylineFeatureFactory.Builder() //
.setCrs(crs).setName("line") //
.addAttribute("line_id", String.class) //
.addAttribute("line_name", String.class)//
.addAttribute("route_id", String.class) //
.addAttribute("mode", String.class) //
.create();

Network network = scenario.getNetwork();

Set<String> modes = new HashSet<>();
IdSet<TransitLine> transitLineIdSet = new IdSet<>(TransitLine.class);
IdSet<TransitRoute> transitRouteIdSet = new IdSet<>(TransitRoute.class);

transitLinesOption.ifPresent(value -> Arrays.stream(value.split(",")).map(String::trim).map(s -> Id.create(s, TransitLine.class)).forEach(transitLineIdSet::add));
transitRoutesOption.ifPresent(value -> Arrays.stream(value.split(",")).map(String::trim).map(s -> Id.create(s, TransitRoute.class)).forEach(transitRouteIdSet::add));
if(modesOption.isPresent()) {
modes = Arrays.stream(modesOption.get().split(",")).map(String::trim).collect(Collectors.toSet());
}

for (TransitLine transitLine : scenario.getTransitSchedule().getTransitLines().values()) {
if(transitLineIdSet.size() > 0 && !transitLineIdSet.contains(transitLine.getId())) {
continue;
}
for (TransitRoute transitRoute : transitLine.getRoutes().values()) {
if(transitRouteIdSet.size() > 0 && !transitRouteIdSet.contains(transitRoute.getId())) {
continue;
}
if(modes.size() > 0 && !modes.contains(transitRoute.getTransportMode())) {
continue;
}
NetworkRoute networkRoute = transitRoute.getRoute();
List<Link> links = new ArrayList<>(networkRoute.getLinkIds().size() + 2);
links.add(network.getLinks().get(networkRoute.getStartLinkId()));
Expand All @@ -76,9 +105,10 @@ public static void main(String[] args) throws Exception {
SimpleFeature feature = linkFactory.createPolyline( //
coordinates, //
new Object[] { //
transitLine.getId().toString(), //
transitLine.getId().toString(),
transitLine.getName(),//
transitRoute.getId().toString(), //
transitRoute.getTransportMode() //
transitRoute.getTransportMode(),//
}, null);

features.add(feature);
Expand Down
51 changes: 42 additions & 9 deletions core/src/test/java/org/eqasim/TestSimulationPipeline.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
import org.eqasim.core.simulation.mode_choice.AbstractEqasimExtension;
import org.eqasim.core.simulation.mode_choice.EqasimModeChoiceModule;
import org.eqasim.core.simulation.mode_choice.parameters.ModeParameters;
import org.eqasim.core.tools.ExportNetworkToShapefile;
import org.eqasim.core.tools.ExportTransitLinesToShapefile;
import org.eqasim.core.tools.ExportTransitStopsToShapefile;
import org.eqasim.core.tools.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
Expand All @@ -37,7 +35,7 @@ public class TestSimulationPipeline {
public void setUp() throws IOException {
URL fixtureUrl = getClass().getClassLoader().getResource("melun");
FileUtils.copyDirectory(new File(fixtureUrl.getPath()), new File("melun_test/input"));
FileUtils.forceMkdir(new File("melun_test/shp"));
FileUtils.forceMkdir(new File("melun_test/exports"));
}

@After
Expand Down Expand Up @@ -109,31 +107,66 @@ private void runAnalyses() throws CommandLine.ConfigurationException, IOExceptio
assert CRCChecksum.getCRCFromFile("melun_test/output/eqasim_pt.csv") == CRCChecksum.getCRCFromFile("melun_test/output/eqasim_pt_post_sim.csv");
}

private void runShapefileExports() throws Exception {
private void runExports() throws Exception {
ExportTransitLinesToShapefile.main(new String[] {
"--schedule-path", "melun_test/input/transit_schedule.xml.gz",
"--network-path", "melun_test/input/network.xml.gz",
"--crs", "EPSG:2154",
"--output-path", "melun_test/shp/lines.shp"
"--output-path", "melun_test/exports/lines.shp"
});

ExportTransitLinesToShapefile.main(new String[] {
"--schedule-path", "melun_test/input/transit_schedule.xml.gz",
"--network-path", "melun_test/input/network.xml.gz",
"--crs", "EPSG:2154",
"--modes", "rail",
"--output-path", "melun_test/exports/lines_rail.shp"
});

ExportTransitLinesToShapefile.main(new String[] {
"--schedule-path", "melun_test/input/transit_schedule.xml.gz",
"--network-path", "melun_test/input/network.xml.gz",
"--crs", "EPSG:2154",
"--transit-lines", "IDFM:C02364,IDFM:C00879",
"--output-path", "melun_test/exports/lines_line_ids.shp"
});

ExportTransitLinesToShapefile.main(new String[] {
"--schedule-path", "melun_test/input/transit_schedule.xml.gz",
"--network-path", "melun_test/input/network.xml.gz",
"--crs", "EPSG:2154",
"--transit-routes", "IDFM:TRANSDEV_AMV:27719-C00637-14017001,IDFM:SNCF:42048-C01728-9e8c577f-7ff9-4fe7-93e7-3c3854aa5ecf",
"--output-path", "melun_test/exports/lines_route_ids.shp"
});

ExportTransitStopsToShapefile.main(new String[] {
"--schedule-path", "melun_test/input/transit_schedule.xml.gz",
"--crs", "EPSG:2154",
"--output-path", "melun_test/shp/stops.shp"
"--output-path", "melun_test/exports/stops.shp"
});

ExportNetworkToShapefile.main(new String[] {
"--network-path", "melun_test/input/network.xml.gz",
"--crs", "EPSG:2154",
"--output-path", "melun_test/shp/network.shp"
"--output-path", "melun_test/exports/network.shp"
});

ExportActivitiesToShapefile.main(new String[]{
"--plans-path", "melun_test/input/population.xml.gz",
"--output-path", "melun_test/exports/activities.shp",
"--crs", "EPSG:2154"
});

ExportPopulationToCSV.main(new String[]{
"--plans-path", "melun_test/input/population.xml.gz",
"--output-path", "melun_test/exports/persons.csv"
});
}

@Test
public void testPipeline() throws Exception {
runMelunSimulation();
runAnalyses();
runShapefileExports();
runExports();
}
}

0 comments on commit e23f643

Please sign in to comment.