MATSIM
TripsAndLegsWriter.java
Go to the documentation of this file.
1 
2 /* *********************************************************************** *
3  * project: org.matsim.*
4  * TripsCSVWriter.java
5  * *
6  * *********************************************************************** *
7  * *
8  * copyright : (C) 2019 by the members listed in the COPYING, *
9  * LICENSE and WARRANTY file. *
10  * email : info at matsim dot org *
11  * *
12  * *********************************************************************** *
13  * *
14  * This program is free software; you can redistribute it and/or modify *
15  * it under the terms of the GNU General Public License as published by *
16  * the Free Software Foundation; either version 2 of the License, or *
17  * (at your option) any later version. *
18  * See also COPYING, LICENSE and WARRANTY file *
19  * *
20  * *********************************************************************** */
21 
22 package org.matsim.analysis;
23 
24 import jakarta.inject.Inject;
25 import org.apache.commons.csv.CSVFormat;
26 import org.apache.commons.csv.CSVPrinter;
27 import org.apache.commons.lang3.ArrayUtils;
28 import org.apache.commons.lang3.StringUtils;
29 import org.apache.logging.log4j.LogManager;
30 import org.apache.logging.log4j.Logger;
31 import org.matsim.api.core.v01.Coord;
32 import org.matsim.api.core.v01.Id;
33 import org.matsim.api.core.v01.IdMap;
34 import org.matsim.api.core.v01.Scenario;
36 import org.matsim.api.core.v01.population.*;
42 import org.matsim.core.utils.io.IOUtils;
43 import org.matsim.core.utils.misc.Time;
46 import org.matsim.vehicles.Vehicle;
48 
49 import java.io.IOException;
50 import java.util.ArrayList;
51 import java.util.Collections;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.stream.Collectors;
55 
56 
60 public class TripsAndLegsWriter {
61  public static final String[] TRIPSHEADER_BASE = {"person", "trip_number", "trip_id",
62  "dep_time", "trav_time", "wait_time", "traveled_distance", "euclidean_distance",
63  "main_mode", "longest_distance_mode", "modes", "start_activity_type",
64  "end_activity_type", "start_facility_id", "start_link",
65  "start_x", "start_y", "end_facility_id",
66  "end_link", "end_x", "end_y", "first_pt_boarding_stop", "last_pt_egress_stop"};
67 
68  public static final String[] LEGSHEADER_BASE = {"person", "trip_id",
69  "dep_time", "trav_time", "wait_time", "distance", "mode", "network_mode", "start_link",
70  "start_x", "start_y", "end_link", "end_x", "end_y", "access_stop_id", "egress_stop_id", "transit_line", "transit_route", "vehicle_id"};
71  private static final Logger log = LogManager.getLogger(TripsAndLegsWriter.class);
72  private final String[] TRIPSHEADER;
73  private final String[] LEGSHEADER;
75  private final Scenario scenario;
79 
80  @Inject
81  public TripsAndLegsWriter(Scenario scenario, CustomTripsWriterExtension tripsWriterExtension, CustomLegsWriterExtension legWriterExtension,
82  AnalysisMainModeIdentifier mainModeIdentifier, CustomTimeWriter customTimeWriter) {
83  this.scenario = scenario;
84  this.tripsWriterExtension = tripsWriterExtension;
85  this.legsWriterExtension = legWriterExtension;
86  this.mainModeIdentifier = mainModeIdentifier;
87  this.customTimeWriter = customTimeWriter;
88 
89  TRIPSHEADER = ArrayUtils.addAll(TRIPSHEADER_BASE, tripsWriterExtension.getAdditionalTripHeader());
90  LEGSHEADER = ArrayUtils.addAll(LEGSHEADER_BASE, legWriterExtension.getAdditionalLegHeader());
91  }
92 
93  private char getDefaultDelimiter() {
94  return scenario.getConfig().global().getDefaultDelimiter().charAt(0);
95  }
96 
97  public void write(IdMap<Person, Plan> experiencedPlans, String tripsFilename, String legsFilename) {
98  try (CSVPrinter tripsCSVprinter = new CSVPrinter(IOUtils.getBufferedWriter(tripsFilename),
99  CSVFormat.Builder.create().setDelimiter(getDefaultDelimiter()).setHeader(TRIPSHEADER).build());
100  CSVPrinter legsCSVprinter = new CSVPrinter(IOUtils.getBufferedWriter(legsFilename),
101  CSVFormat.Builder.create().setDelimiter(getDefaultDelimiter()).setHeader(LEGSHEADER).build())
102  ) {
103  for (Map.Entry<Id<Person>, Plan> entry : experiencedPlans.entrySet()) {
104  Tuple<Iterable<?>, Iterable<?>> tripsAndLegRecords = getPlanCSVRecords(entry.getValue(), entry.getKey());
105  tripsCSVprinter.printRecords(tripsAndLegRecords.getFirst());
106  legsCSVprinter.printRecords(tripsAndLegRecords.getSecond());
107  }
108  } catch (IOException e) {
109  e.printStackTrace();
110  }
111  }
112 
113  private Tuple<Iterable<?>, Iterable<?>> getPlanCSVRecords(Plan experiencedPlan, Id<Person> personId) {
114  List<List<String>> tripRecords = new ArrayList<>();
115  List<List<String>> legRecords = new ArrayList<>();
116  Tuple<Iterable<?>, Iterable<?>> record = new Tuple<>(tripRecords, legRecords);
117  List<TripStructureUtils.Trip> trips = TripStructureUtils.getTrips(experiencedPlan);
118 
119  for (int i = 0; i < trips.size(); i++) {
120  TripStructureUtils.Trip trip = trips.get(i);
121  List<String> tripRecord = new ArrayList<>();
122  tripRecords.add(tripRecord);
123  tripRecord.add(personId.toString());
124  final String tripNo = Integer.toString(i + 1);
125  tripRecord.add(tripNo); // trip number, numbered starting with 0
126  String tripId = personId + "_" + tripNo;
127  tripRecord.add(tripId);
128  double distance = 0.0;
129  double departureTime = trip.getOriginActivity().getEndTime().orElse(0);
130  double travelTime = trip.getDestinationActivity().getStartTime().orElse(0) - departureTime;
131  //experienced plans have a start time
132 
133  double totalWaitingTime = 0.0;
134  double currentLongestShareDistance = Double.MIN_VALUE;
135  String currentModeWithLongestShare = "";
136  List<String> modes = new ArrayList<>();
137  String lastActivityType = trip.getOriginActivity().getType();
138  String nextActivityType = trip.getDestinationActivity().getType();
139  Id<ActivityFacility> fromFacilityId = trip.getOriginActivity().getFacilityId();
140  Id<ActivityFacility> toFacilityId = trip.getDestinationActivity().getFacilityId();
141  Id<Link> fromLinkId = trip.getOriginActivity().getLinkId();
142  Id<Link> toLinkId = trip.getDestinationActivity().getLinkId();
143  Coord fromCoord = getCoordFromActivity(trip.getOriginActivity());
144  Coord toCoord = getCoordFromActivity(trip.getDestinationActivity());
145  int euclideanDistance = (int) CoordUtils.calcEuclideanDistance(fromCoord, toCoord);
146  String firstPtBoardingStop = null;
147  String lastPtEgressStop = null;
148 
149  String mainMode = "";
150  if (mainModeIdentifier != null) {
151  try {
152  mainMode = mainModeIdentifier.identifyMainMode(trip.getTripElements());
153  if (mainMode == null)
154  mainMode = "";
155  } catch (Exception e) {
156  // leave field empty
157  }
158  }
159 
160  for (Leg leg : trip.getLegsOnly()) {
161  modes.add(leg.getMode());
162  final double legDist = leg.getRoute().getDistance();
163  distance += legDist;
164  Double boardingTime = (Double) leg.getAttributes().getAttribute(EventsToLegs.ENTER_VEHICLE_TIME_ATTRIBUTE_NAME);
165  if (boardingTime != null) {
166  double waitingTime = boardingTime - leg.getDepartureTime().seconds();
167  totalWaitingTime += waitingTime;
168  }
169  if (StringUtils.isBlank(currentModeWithLongestShare) || legDist > currentLongestShareDistance) {
170  currentLongestShareDistance = legDist;
171  currentModeWithLongestShare = leg.getMode();
172 
173  }
174  if (leg.getRoute() instanceof TransitPassengerRoute route) {
175  firstPtBoardingStop = firstPtBoardingStop != null ? firstPtBoardingStop : route.getAccessStopId().toString();
176  lastPtEgressStop = route.getEgressStopId().toString();
177  }
178 
179  }
180 
181  tripRecord.add(customTimeWriter.writeTime(departureTime));
182  tripRecord.add(customTimeWriter.writeTime(travelTime));
183  tripRecord.add(customTimeWriter.writeTime(totalWaitingTime));
184  tripRecord.add(Integer.toString((int) Math.round(distance)));
185  tripRecord.add(Integer.toString(euclideanDistance));
186  tripRecord.add(mainMode);
187  tripRecord.add(currentModeWithLongestShare);
188  tripRecord.add(modes.stream().collect(Collectors.joining("-")));
189  tripRecord.add(lastActivityType);
190  tripRecord.add(nextActivityType);
191  tripRecord.add(String.valueOf(fromFacilityId));
192  tripRecord.add(String.valueOf(fromLinkId));
193  tripRecord.add(Double.toString(fromCoord.getX()));
194  tripRecord.add(Double.toString(fromCoord.getY()));
195 
196  tripRecord.add(String.valueOf(toFacilityId));
197  tripRecord.add(String.valueOf(toLinkId));
198  tripRecord.add(Double.toString(toCoord.getX()));
199  tripRecord.add(Double.toString(toCoord.getY()));
200  tripRecord.add(firstPtBoardingStop != null ? firstPtBoardingStop : "");
201  tripRecord.add(lastPtEgressStop != null ? lastPtEgressStop : "");
202  tripRecord.addAll(tripsWriterExtension.getAdditionalTripColumns(personId, trip));
203 
204  if (TRIPSHEADER.length != tripRecord.size()) {
205  // put the whole error message also into the RuntimeException, so maven shows it on the command line output (log messages are shown incompletely)
206  String errorMessage = getTripErrorMessage(tripRecord).toString();
207  log.error(errorMessage);
208  throw new RuntimeException(errorMessage);
209  }
210 
211  Activity prevAct = null;
212  Leg prevLeg = null;
213  List<PlanElement> allElements = new ArrayList<>();
214  allElements.add(trip.getOriginActivity());
215  allElements.addAll(trip.getTripElements());
216  allElements.add(trip.getDestinationActivity());
217  for (PlanElement pe : allElements) {
218  if (pe instanceof Activity currentAct) {
219  if (prevLeg != null) {
220  List<String> legRecord = getLegRecord(prevLeg, personId.toString(), tripId, prevAct, currentAct, trip);
221  legRecords.add(legRecord);
222  }
223  prevAct = currentAct;
224 
225  } else if (pe instanceof Leg) {
226  prevLeg = (Leg) pe;
227  }
228  }
229  }
230 
231  return record;
232  }
233 
234  private StringBuilder getTripErrorMessage(List<String> tripRecord) {
235  StringBuilder str = new StringBuilder();
236  str.append("Custom CSV Trip Writer Extension does not provide an identical number of additional values and additional columns. Number of columns is ")
237  .append(TRIPSHEADER.length).append(", and number of values is ").append(tripRecord.size())
238  .append(".\n")
239  .append("TripsWriterExtension class was: ").append(tripsWriterExtension.getClass())
240  .append(". Column name to value pairs supplied were:\n");
241  for (int j = 0; j < Math.max(TRIPSHEADER.length, tripRecord.size()); j++) {
242  String columnNameJ;
243  try {
244  columnNameJ = TRIPSHEADER[j];
245  } catch (ArrayIndexOutOfBoundsException e) {
246  columnNameJ = "!COLUMN MISSING!";
247  }
248  String tripRecordJ;
249  try {
250  tripRecordJ = tripRecord.get(j);
251  } catch (IndexOutOfBoundsException e) {
252  tripRecordJ = "!VALUE MISSING!";
253  }
254  str.append(j + ": " + columnNameJ + ": " + tripRecordJ + "\n");
255  }
256  return str;
257  }
258 
259  private List<String> getLegRecord(Leg leg, String personId, String tripId, Activity previousAct, Activity nextAct, TripStructureUtils.Trip trip) {
260  List<String> record = new ArrayList<>();
261  record.add(personId);
262  record.add(tripId);
263  record.add(customTimeWriter.writeTime(leg.getDepartureTime().seconds()));
264  record.add(customTimeWriter.writeTime(leg.getTravelTime().seconds()));
265  Double boardingTime = (Double) leg.getAttributes().getAttribute(EventsToLegs.ENTER_VEHICLE_TIME_ATTRIBUTE_NAME);
267  double waitingTime = 0.;
268  if (boardingTime != null) {
269  waitingTime = boardingTime - leg.getDepartureTime().seconds();
270  }
271  record.add(customTimeWriter.writeTime(waitingTime));
272  record.add(Integer.toString((int) leg.getRoute().getDistance()));
273  record.add(leg.getMode());
274 
275  if (vehicleId != null && scenario.getVehicles().getVehicles().containsKey(vehicleId)) {
276  VehicleType type = scenario.getVehicles().getVehicles().get(vehicleId).getType();
277  record.add(type.hasNetworkMode() ? type.getNetworkMode() : "");
278  } else {
279  record.add("");
280  }
281 
282  record.add(leg.getRoute().getStartLinkId().toString());
283  Coord startCoord = getCoordFromActivity(previousAct);
284  Coord endCoord = getCoordFromActivity(nextAct);
285  record.add(Double.toString(startCoord.getX()));
286  record.add(Double.toString(startCoord.getY()));
287  record.add(leg.getRoute().getEndLinkId().toString());
288  record.add(Double.toString(endCoord.getX()));
289  record.add(Double.toString(endCoord.getY()));
290  String transitLine = "";
291  String transitRoute = "";
292  String ptAccessStop = "";
293  String ptEgressStop = "";
294  if (leg.getRoute() instanceof TransitPassengerRoute route) {
295  transitLine = route.getLineId().toString();
296  transitRoute = route.getRouteId().toString();
297  ptAccessStop = route.getAccessStopId().toString();
298  ptEgressStop = route.getEgressStopId().toString();
299  }
300  record.add(ptAccessStop);
301  record.add(ptEgressStop);
302  record.add(transitLine);
303  record.add(transitRoute);
304  record.add(vehicleId != null ? vehicleId.toString() : "");
305 
306  record.addAll(legsWriterExtension.getAdditionalLegColumns(trip, leg));
307 
308  if (LEGSHEADER.length != record.size()) {
309  // put the whole error message also into the RuntimeException, so maven shows it on the command line output (log messages are shown incompletely)
310  String errorMessage = getLegErrorMessage(record);
311  log.error(errorMessage);
312  throw new RuntimeException(errorMessage);
313  }
314 
315  return record;
316  }
317 
318  private String getLegErrorMessage(List<String> record) {
319  StringBuilder str = new StringBuilder();
320  str.append("Custom CSV Leg Writer Extension does not provide an identical number of additional values and additional columns. Number of columns is ")
321  .append(LEGSHEADER.length)
322  .append(", and number of values is ")
323  .append(record.size())
324  .append(".\n")
325  .append("LegsWriterExtension class was: ")
326  .append(legsWriterExtension.getClass())
327  .append(". Column name to value pairs supplied were:\n");
328  for (int j = 0; j < Math.max(LEGSHEADER.length, record.size()); j++) {
329  String columnNameJ;
330  try {
331  columnNameJ = LEGSHEADER[j];
332  } catch (ArrayIndexOutOfBoundsException e) {
333  columnNameJ = "!COLUMN MISSING!";
334  }
335  String recordJ;
336  try {
337  recordJ = record.get(j);
338  } catch (IndexOutOfBoundsException e) {
339  recordJ = "!VALUE MISSING!";
340  }
341  str.append(j).append(": ").append(columnNameJ).append(": ").append(recordJ).append("\n");
342  }
343  return str.toString();
344  }
345 
346  private Coord getCoordFromActivity(Activity activity) {
347  if (activity.getCoord() != null) {
348  return activity.getCoord();
349  } else if (activity.getFacilityId() != null && scenario.getActivityFacilities().getFacilities().containsKey(activity.getFacilityId())) {
350  Coord coord = scenario.getActivityFacilities().getFacilities().get(activity.getFacilityId()).getCoord();
351  return coord != null ? coord : getCoordFromLink(activity.getLinkId());
352  } else return getCoordFromLink(activity.getLinkId());
353  }
354 
355  //this is the least desirable way
356  private Coord getCoordFromLink(Id<Link> linkId) {
357  return scenario.getNetwork().getLinks().get(linkId).getToNode().getCoord();
358  }
359 
360  public interface CustomTripsWriterExtension {
361 
362  String[] getAdditionalTripHeader();
363 
365 
366  default List<String> getAdditionalTripColumns(Id<Person> personId, TripStructureUtils.Trip trip) {
367  return getAdditionalTripColumns(trip);
368  }
369  }
370 
371  public interface CustomLegsWriterExtension {
372  String[] getAdditionalLegHeader();
373 
374  List<String> getAdditionalLegColumns(TripStructureUtils.Trip experiencedTrip, Leg experiencedLeg);
375  }
376 
377 
378  public interface CustomTimeWriter {
379  String writeTime(double time);
380  }
381 
382  static class SecondsFromMidnightTimeWriter implements CustomTimeWriter {
383  @Override
384  public String writeTime(double time) {
385  return Long.toString((long) time);
386  }
387  }
388 
389  static class DefaultTimeWriter implements CustomTimeWriter {
390  @Override
391  public String writeTime(double time) {
392  return Time.writeTime(time);
393  }
394  }
395 
396  static class NoTripWriterExtension implements CustomTripsWriterExtension {
397  @Override
398  public String[] getAdditionalTripHeader() {
399  return new String[0];
400  }
401 
402  @Override
403  public List<String> getAdditionalTripColumns(TripStructureUtils.Trip trip) {
404  return Collections.EMPTY_LIST;
405  }
406 
407  }
408 
409  static class NoLegsWriterExtension implements CustomLegsWriterExtension {
410  @Override
411  public String[] getAdditionalLegHeader() {
412  return new String[0];
413  }
414 
415  @Override
416  public List<String> getAdditionalLegColumns(TripStructureUtils.Trip experiencedTrip, Leg experiencedLeg) {
417  return Collections.EMPTY_LIST;
418  }
419  }
420 }
List< String > getAdditionalLegColumns(TripStructureUtils.Trip experiencedTrip, Leg experiencedLeg)
Map< Id< ActivityFacility >, ? extends ActivityFacility > getFacilities()
static double calcEuclideanDistance(Coord coord, Coord other)
Set< Map.Entry< Id< T >, V > > entrySet()
Definition: IdMap.java:209
final CustomLegsWriterExtension legsWriterExtension
String identifyMainMode(List<? extends PlanElement > tripElements)
default List< String > getAdditionalTripColumns(Id< Person > personId, TripStructureUtils.Trip trip)
static final String ENTER_VEHICLE_TIME_ATTRIBUTE_NAME
List< String > getLegRecord(Leg leg, String personId, String tripId, Activity previousAct, Activity nextAct, TripStructureUtils.Trip trip)
static BufferedWriter getBufferedWriter(URL url, Charset charset, boolean append)
Definition: IOUtils.java:390
StringBuilder getTripErrorMessage(List< String > tripRecord)
void write(IdMap< Person, Plan > experiencedPlans, String tripsFilename, String legsFilename)
final CustomTripsWriterExtension tripsWriterExtension
final AnalysisMainModeIdentifier mainModeIdentifier
static final String VEHICLE_ID_ATTRIBUTE_NAME
List< String > getAdditionalTripColumns(TripStructureUtils.Trip trip)
static List< Trip > getTrips(final Plan plan)
static final String writeTime(final double seconds, final String timeformat)
Definition: Time.java:80
Map< Id< Link >, ? extends Link > getLinks()
Id< ActivityFacility > getFacilityId()
String getLegErrorMessage(List< String > record)
Map< Id< Vehicle >, Vehicle > getVehicles()
ActivityFacilities getActivityFacilities()
final GlobalConfigGroup global()
Definition: Config.java:395
TripsAndLegsWriter(Scenario scenario, CustomTripsWriterExtension tripsWriterExtension, CustomLegsWriterExtension legWriterExtension, AnalysisMainModeIdentifier mainModeIdentifier, CustomTimeWriter customTimeWriter)
Tuple< Iterable<?>, Iterable<?> > getPlanCSVRecords(Plan experiencedPlan, Id< Person > personId)