Skip to content

Commit dcaf5b9

Browse files
Merge pull request #123 from moia-oss/master
Update MATSim CW1
2 parents 514076e + f217386 commit dcaf5b9

File tree

74 files changed

+1553
-814
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+1553
-814
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
package org.matsim.application.analysis.activity;
2+
3+
import it.unimi.dsi.fastutil.objects.Object2IntMap;
4+
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
5+
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
6+
import org.apache.logging.log4j.LogManager;
7+
import org.apache.logging.log4j.Logger;
8+
import org.geotools.api.feature.Property;
9+
import org.geotools.api.feature.simple.SimpleFeature;
10+
import org.locationtech.jts.geom.Geometry;
11+
import org.matsim.api.core.v01.Coord;
12+
import org.matsim.application.CommandSpec;
13+
import org.matsim.application.MATSimAppCommand;
14+
import org.matsim.application.options.*;
15+
import org.matsim.core.utils.io.IOUtils;
16+
import picocli.CommandLine;
17+
import tech.tablesaw.api.*;
18+
import tech.tablesaw.io.csv.CsvReadOptions;
19+
import tech.tablesaw.selection.Selection;
20+
21+
import java.util.*;
22+
import java.util.regex.Pattern;
23+
24+
@CommandSpec(
25+
requires = {"activities.csv"},
26+
produces = {"activities_%s_per_region.csv"}
27+
)
28+
public class ActivityCountAnalysis implements MATSimAppCommand {
29+
30+
private static final Logger log = LogManager.getLogger(ActivityCountAnalysis.class);
31+
32+
@CommandLine.Mixin
33+
private final InputOptions input = InputOptions.ofCommand(ActivityCountAnalysis.class);
34+
@CommandLine.Mixin
35+
private final OutputOptions output = OutputOptions.ofCommand(ActivityCountAnalysis.class);
36+
@CommandLine.Mixin
37+
private ShpOptions shp;
38+
@CommandLine.Mixin
39+
private SampleOptions sample;
40+
@CommandLine.Mixin
41+
private CrsOptions crs;
42+
43+
/**
44+
* Specifies the column in the shapefile used as the region ID.
45+
*/
46+
@CommandLine.Option(names = "--id-column", description = "Column to use as ID for the shapefile", required = true)
47+
private String idColumn;
48+
49+
/**
50+
* Maps patterns to merge activity types into a single category.
51+
* Example: `home;work` can merge activities "home1" and "work1" into categories "home" and "work".
52+
*/
53+
@CommandLine.Option(names = "--activity-mapping", description = "Map of patterns to merge activity types", split = ";")
54+
private Map<String, String> activityMapping;
55+
56+
/**
57+
* Specifies activity types that should be counted only once per agent per region.
58+
*/
59+
@CommandLine.Option(names = "--single-occurrence", description = "Activity types that are only counted once per agent")
60+
private Set<String> singleOccurrence;
61+
62+
public static void main(String[] args) {
63+
new ActivityCountAnalysis().execute(args);
64+
}
65+
66+
/**
67+
* Executes the activity count analysis.
68+
*
69+
* @return Exit code (0 for success).
70+
* @throws Exception if errors occur during execution.
71+
*/
72+
@Override
73+
public Integer call() throws Exception {
74+
75+
// Prepares the activity mappings and reads input data
76+
HashMap<String, Set<String>> formattedActivityMapping = new HashMap<>();
77+
Map<String, Double> regionAreaMap = new HashMap<>();
78+
79+
if (this.activityMapping == null) this.activityMapping = new HashMap<>();
80+
81+
for (Map.Entry<String, String> entry : this.activityMapping.entrySet()) {
82+
String pattern = entry.getKey();
83+
String activity = entry.getValue();
84+
Set<String> activities = new HashSet<>(Arrays.asList(activity.split(",")));
85+
formattedActivityMapping.put(pattern, activities);
86+
}
87+
88+
// Reading the input csv
89+
Table activities = Table.read().csv(CsvReadOptions.builder(IOUtils.getBufferedReader(input.getPath("activities.csv")))
90+
.columnTypesPartial(Map.of("person", ColumnType.TEXT, "activity_type", ColumnType.TEXT))
91+
.sample(false)
92+
.separator(CsvOptions.detectDelimiter(input.getPath("activities.csv"))).build());
93+
94+
// remove the underscore and the number from the activity_type column
95+
TextColumn activityType = activities.textColumn("activity_type");
96+
activityType.set(Selection.withRange(0, activityType.size()), activityType.replaceAll("_[0-9]{2,}$", ""));
97+
98+
ShpOptions.Index index = crs.getInputCRS() == null ? shp.createIndex(idColumn) : shp.createIndex(crs.getInputCRS(), idColumn);
99+
100+
// stores the counts of activities per region
101+
Object2ObjectOpenHashMap<Object, Object2IntMap<String>> regionActivityCounts = new Object2ObjectOpenHashMap<>();
102+
// stores the activities that have been counted for each person in each region
103+
Object2ObjectOpenHashMap<Object, Set<String>> personActivityTracker = new Object2ObjectOpenHashMap<>();
104+
105+
// iterate over the csv rows
106+
for (Row row : activities) {
107+
String person = row.getString("person");
108+
String activity = row.getText("activity_type");
109+
110+
for (Map.Entry<String, Set<String>> entry : formattedActivityMapping.entrySet()) {
111+
String pattern = entry.getKey();
112+
Set<String> activities2 = entry.getValue();
113+
for (String act : activities2) {
114+
if (Pattern.matches(act, activity)) {
115+
activity = pattern;
116+
break;
117+
}
118+
}
119+
}
120+
121+
Coord coord = new Coord(row.getDouble("coord_x"), row.getDouble("coord_y"));
122+
123+
// get the region for the current coordinate
124+
SimpleFeature feature = index.queryFeature(coord);
125+
126+
if (feature == null) {
127+
continue;
128+
}
129+
130+
Geometry geometry = (Geometry) feature.getDefaultGeometry();
131+
132+
Property prop = feature.getProperty(idColumn);
133+
if (prop == null)
134+
throw new IllegalArgumentException("No property found for column %s".formatted(idColumn));
135+
136+
Object region = prop.getValue();
137+
if (region != null && region.toString().length() > 0) {
138+
139+
double area = geometry.getArea();
140+
regionAreaMap.put(region.toString(), area);
141+
142+
// Add region to the activity counts and person activity tracker if not already present
143+
regionActivityCounts.computeIfAbsent(region, k -> new Object2IntOpenHashMap<>());
144+
personActivityTracker.computeIfAbsent(region, k -> new HashSet<>());
145+
146+
Set<String> trackedActivities = personActivityTracker.get(region);
147+
String personActivityKey = person + "_" + activity;
148+
149+
// adding activity only if it has not been counted for the person in the region
150+
if (singleOccurrence == null || !singleOccurrence.contains(activity) || !trackedActivities.contains(personActivityKey)) {
151+
Object2IntMap<String> activityCounts = regionActivityCounts.get(region);
152+
activityCounts.mergeInt(activity, 1, Integer::sum);
153+
154+
// mark the activity as counted for the person in the region
155+
trackedActivities.add(personActivityKey);
156+
}
157+
}
158+
}
159+
160+
Set<String> uniqueActivities = new HashSet<>();
161+
162+
for (Object2IntMap<String> map : regionActivityCounts.values()) {
163+
uniqueActivities.addAll(map.keySet());
164+
}
165+
166+
for (String activity : uniqueActivities) {
167+
Table resultTable = Table.create();
168+
TextColumn regionColumn = TextColumn.create("id");
169+
DoubleColumn activityColumn = DoubleColumn.create("count");
170+
DoubleColumn distributionColumn = DoubleColumn.create("relative_density");
171+
DoubleColumn countRatioColumn = DoubleColumn.create("density");
172+
DoubleColumn areaColumn = DoubleColumn.create("area");
173+
174+
resultTable.addColumns(regionColumn, activityColumn, distributionColumn, countRatioColumn, areaColumn);
175+
for (Map.Entry<Object, Object2IntMap<String>> entry : regionActivityCounts.entrySet()) {
176+
Object region = entry.getKey();
177+
double value = 0;
178+
for (Map.Entry<String, Integer> entry2 : entry.getValue().object2IntEntrySet()) {
179+
String ect = entry2.getKey();
180+
if (Pattern.matches(ect, activity)) {
181+
value = entry2.getValue() * sample.getUpscaleFactor();
182+
break;
183+
}
184+
}
185+
186+
187+
Row row = resultTable.appendRow();
188+
row.setString("id", region.toString());
189+
row.setDouble("count", value);
190+
}
191+
192+
for (Row row : resultTable) {
193+
Double area = regionAreaMap.get(row.getString("id"));
194+
if (area != null) {
195+
row.setDouble("area", area);
196+
row.setDouble("density", row.getDouble("count") / area);
197+
} else {
198+
log.warn("Area for region {} is not found", row.getString("id"));
199+
}
200+
}
201+
202+
Double averageDensity = countRatioColumn.mean();
203+
204+
for (Row row : resultTable) {
205+
Double value = row.getDouble("density");
206+
if (averageDensity != 0) {
207+
row.setDouble("relative_density", value / averageDensity);
208+
} else {
209+
row.setDouble("relative_density", 0.0);
210+
}
211+
}
212+
213+
214+
resultTable.write().csv(output.getPath("activities_%s_per_region.csv", activity).toFile());
215+
log.info("Wrote activity counts for {} to {}", activity, output.getPath("activities_%s_per_region.csv", activity));
216+
}
217+
218+
return 0;
219+
}
220+
}

contribs/application/src/main/java/org/matsim/application/analysis/traffic/traveltime/TravelTimeComparison.java

+23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.matsim.application.analysis.traffic.traveltime;
22

3+
import org.apache.logging.log4j.LogManager;
4+
import org.apache.logging.log4j.Logger;
35
import org.matsim.api.core.v01.Id;
46
import org.matsim.api.core.v01.TransportMode;
57
import org.matsim.api.core.v01.network.Link;
@@ -47,6 +49,8 @@
4749
)
4850
public class TravelTimeComparison implements MATSimAppCommand {
4951

52+
private static final Logger log = LogManager.getLogger(TravelTimeComparison.class);
53+
5054
@CommandLine.Mixin
5155
private InputOptions input = InputOptions.ofCommand(TravelTimeComparison.class);
5256

@@ -90,6 +94,13 @@ public Integer call() throws Exception {
9094

9195
for (Row row : data) {
9296
LeastCostPathCalculator.Path congested = computePath(network, congestedRouter, row);
97+
98+
// Skip if path is not found
99+
if (congested == null) {
100+
row.setDouble("simulated", Double.NaN);
101+
continue;
102+
}
103+
93104
double dist = congested.links.stream().mapToDouble(Link::getLength).sum();
94105
double speed = 3.6 * dist / congested.travelTime;
95106

@@ -102,6 +113,8 @@ public Integer call() throws Exception {
102113
row.setDouble("free_flow", speed);
103114
}
104115

116+
data = data.dropWhere(data.doubleColumn("simulated").isMissing());
117+
105118
data.addColumns(
106119
data.doubleColumn("simulated").subtract(data.doubleColumn("mean")).setName("bias")
107120
);
@@ -129,6 +142,16 @@ private LeastCostPathCalculator.Path computePath(Network network, LeastCostPathC
129142
Node fromNode = network.getNodes().get(Id.createNodeId(row.getString("from_node")));
130143
Node toNode = network.getNodes().get(Id.createNodeId(row.getString("to_node")));
131144

145+
if (fromNode == null) {
146+
log.error("Node {} not found in network", row.getString("from_node"));
147+
return null;
148+
}
149+
150+
if (toNode == null) {
151+
log.error("Node {} not found in network", row.getString("to_node"));
152+
return null;
153+
}
154+
132155
return router.calcLeastCostPath(fromNode, toNode, row.getInt("hour") * 3600, null, null);
133156
}
134157

contribs/application/src/main/java/org/matsim/application/options/ShpOptions.java

+1
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ public Geometry getGeometry() {
230230

231231
/**
232232
* Return the union of all geometries in the shape file and project it to the target crs.
233+
*
233234
* @param toCRS target coordinate system
234235
*/
235236
public Geometry getGeometry(String toCRS) {

contribs/application/src/main/java/org/matsim/freightDemandGeneration/DemandReaderFromCSV.java

+23-22
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,7 @@ else if (samplingOption.equals("changeNumberOfLocationsWithDemand")) {
655655
createJobId(scenario, newDemandInformationElement, link.getId(), null),
656656
CarrierService.class);
657657
CarrierService thisService = CarrierService.Builder.newInstance(idNewService, link.getId())
658-
.setCapacityDemand(demandForThisLink).setServiceDuration(serviceTime)
658+
.setDemand(demandForThisLink).setServiceDuration(serviceTime)
659659
.setServiceStartTimeWindow(newDemandInformationElement.getFirstJobElementTimeWindow())
660660
.build();
661661
CarriersUtils.getCarriers(scenario).getCarriers()
@@ -696,7 +696,7 @@ else if (samplingOption.equals("changeNumberOfLocationsWithDemand")) {
696696
CarrierService.class);
697697
if (demandToDistribute > 0 && singleDemandForThisLink > 0) {
698698
CarrierService thisService = CarrierService.Builder.newInstance(idNewService, link.getId())
699-
.setCapacityDemand(singleDemandForThisLink).setServiceDuration(serviceTime)
699+
.setDemand(singleDemandForThisLink).setServiceDuration(serviceTime)
700700
.setServiceStartTimeWindow(newDemandInformationElement.getFirstJobElementTimeWindow())
701701
.build();
702702
thisCarrier.getServices().put(thisService.getId(), thisService);
@@ -747,7 +747,7 @@ else if (samplingOption.equals("changeNumberOfLocationsWithDemand")) {
747747
createJobId(scenario, newDemandInformationElement, link.getId(), null), CarrierService.class);
748748
if ((demandToDistribute > 0 && singleDemandForThisLink > 0) || demandToDistribute == 0) {
749749
CarrierService thisService = CarrierService.Builder.newInstance(idNewService, link.getId())
750-
.setCapacityDemand(singleDemandForThisLink).setServiceDuration(serviceTime)
750+
.setDemand(singleDemandForThisLink).setServiceDuration(serviceTime)
751751
.setServiceStartTimeWindow(newDemandInformationElement.getFirstJobElementTimeWindow())
752752
.build();
753753
CarriersUtils.getCarriers(scenario).getCarriers()
@@ -1051,8 +1051,8 @@ private static void createSingleShipment(Scenario scenario, DemandInformationEle
10511051

10521052
CarrierShipment thisShipment = CarrierShipment.Builder
10531053
.newInstance(idNewShipment, linkPickup.getId(), linkDelivery.getId(), singleDemandForThisLink)
1054-
.setPickupServiceTime(serviceTimePickup).setPickupTimeWindow(timeWindowPickup)
1055-
.setDeliveryServiceTime(serviceTimeDelivery).setDeliveryTimeWindow(timeWindowDelivery)
1054+
.setPickupDuration(serviceTimePickup).setPickupStartsTimeWindow(timeWindowPickup)
1055+
.setDeliveryDuration(serviceTimeDelivery).setDeliveryStartsTimeWindow(timeWindowDelivery)
10561056
.build();
10571057
thisCarrier.getShipments().put(thisShipment.getId(), thisShipment);
10581058
if (demandForThisLink == 0)
@@ -1188,29 +1188,30 @@ private static void combineSimilarJobs(Scenario scenario) {
11881188
if (!shipmentsToRemove.containsKey(thisShipmentId)) {
11891189
CarrierShipment thisShipment = thisCarrier.getShipments().get(thisShipmentId);
11901190
if (baseShipment.getId() != thisShipment.getId()
1191-
&& baseShipment.getFrom() == thisShipment.getFrom()
1192-
&& baseShipment.getTo() == thisShipment.getTo()
1193-
&& baseShipment.getPickupTimeWindow() == thisShipment.getPickupTimeWindow()
1194-
&& baseShipment.getDeliveryTimeWindow() == thisShipment.getDeliveryTimeWindow())
1195-
shipmentsToConnect.put(thisShipmentId, thisShipment);
1191+
&& baseShipment.getPickupLinkId() == thisShipment.getPickupLinkId()
1192+
&& baseShipment.getDeliveryLinkId() == thisShipment.getDeliveryLinkId()) {
1193+
if (baseShipment.getPickupStartsTimeWindow() == thisShipment.getPickupStartsTimeWindow()) {
1194+
if (baseShipment.getDeliveryStartsTimeWindow() == thisShipment.getDeliveryStartsTimeWindow()) shipmentsToConnect.put(thisShipmentId, thisShipment);
1195+
}
1196+
}
11961197
}
11971198
}
11981199
Id<CarrierShipment> idNewShipment = baseShipment.getId();
11991200
int demandForThisLink = 0;
12001201
double serviceTimePickup = 0;
12011202
double serviceTimeDelivery = 0;
12021203
for (CarrierShipment carrierShipment : shipmentsToConnect.values()) {
1203-
demandForThisLink = demandForThisLink + carrierShipment.getSize();
1204-
serviceTimePickup = serviceTimePickup + carrierShipment.getPickupServiceTime();
1205-
serviceTimeDelivery = serviceTimeDelivery + carrierShipment.getDeliveryServiceTime();
1204+
demandForThisLink = demandForThisLink + carrierShipment.getDemand();
1205+
serviceTimePickup = serviceTimePickup + carrierShipment.getPickupDuration();
1206+
serviceTimeDelivery = serviceTimeDelivery + carrierShipment.getDeliveryDuration();
12061207
shipmentsToRemove.put(carrierShipment.getId(), carrierShipment);
12071208
}
12081209
CarrierShipment newShipment = CarrierShipment.Builder
1209-
.newInstance(idNewShipment, baseShipment.getFrom(), baseShipment.getTo(), demandForThisLink)
1210-
.setPickupServiceTime(serviceTimePickup)
1211-
.setPickupTimeWindow(baseShipment.getPickupTimeWindow())
1212-
.setDeliveryServiceTime(serviceTimeDelivery)
1213-
.setDeliveryTimeWindow(baseShipment.getDeliveryTimeWindow()).build();
1210+
.newInstance(idNewShipment, baseShipment.getPickupLinkId(), baseShipment.getDeliveryLinkId(), demandForThisLink)
1211+
.setPickupDuration(serviceTimePickup)
1212+
.setPickupStartsTimeWindow(baseShipment.getPickupStartsTimeWindow())
1213+
.setDeliveryDuration(serviceTimeDelivery)
1214+
.setDeliveryStartsTimeWindow(baseShipment.getDeliveryStartsTimeWindow()).build();
12141215
shipmentsToAdd.add(newShipment);
12151216
}
12161217
}
@@ -1236,7 +1237,7 @@ private static void combineSimilarJobs(Scenario scenario) {
12361237
if (!servicesToRemove.containsKey(thisServiceId)) {
12371238
CarrierService thisService = thisCarrier.getServices().get(thisServiceId);
12381239
if (baseService.getId() != thisService.getId()
1239-
&& baseService.getLocationLinkId() == thisService.getLocationLinkId() && baseService
1240+
&& baseService.getServiceLinkId() == thisService.getServiceLinkId() && baseService
12401241
.getServiceStartTimeWindow() == thisService.getServiceStartTimeWindow())
12411242
servicesToConnect.put(thisServiceId, thisService);
12421243
}
@@ -1245,15 +1246,15 @@ private static void combineSimilarJobs(Scenario scenario) {
12451246
int demandForThisLink = 0;
12461247
double serviceTimeService = 0;
12471248
for (CarrierService carrierService : servicesToConnect.values()) {
1248-
demandForThisLink = demandForThisLink + carrierService.getCapacityDemand();
1249+
demandForThisLink = demandForThisLink + carrierService.getDemand();
12491250
serviceTimeService = serviceTimeService + carrierService.getServiceDuration();
12501251
servicesToRemove.put(carrierService.getId(), carrierService);
12511252
}
12521253
CarrierService newService = CarrierService.Builder
1253-
.newInstance(idNewService, baseService.getLocationLinkId())
1254+
.newInstance(idNewService, baseService.getServiceLinkId())
12541255
.setServiceDuration(serviceTimeService)
12551256
.setServiceStartTimeWindow(baseService.getServiceStartTimeWindow())
1256-
.setCapacityDemand(demandForThisLink).build();
1257+
.setDemand(demandForThisLink).build();
12571258
servicesToAdd.add(newService);
12581259
}
12591260
}

0 commit comments

Comments
 (0)