2
2
3
3
import it .unimi .dsi .fastutil .ints .IntArrayList ;
4
4
import it .unimi .dsi .fastutil .ints .IntList ;
5
+ import it .unimi .dsi .fastutil .ints .IntOpenHashSet ;
6
+ import it .unimi .dsi .fastutil .ints .IntSet ;
5
7
import it .unimi .dsi .fastutil .objects .Object2IntLinkedOpenHashMap ;
6
8
import it .unimi .dsi .fastutil .objects .Object2IntMap ;
7
9
import it .unimi .dsi .fastutil .objects .Object2LongMap ;
15
17
import org .locationtech .jts .geom .Geometry ;
16
18
import org .locationtech .jts .geom .GeometryFactory ;
17
19
import org .locationtech .jts .geom .Point ;
20
+ import org .matsim .application .ApplicationUtils ;
18
21
import org .matsim .application .CommandSpec ;
19
22
import org .matsim .application .MATSimAppCommand ;
20
23
import org .matsim .application .options .CsvOptions ;
32
35
import java .math .BigDecimal ;
33
36
import java .math .RoundingMode ;
34
37
import java .nio .file .Files ;
38
+ import java .nio .file .Path ;
35
39
import java .util .*;
36
40
import java .util .stream .IntStream ;
37
41
43
47
produces = {
44
48
"mode_share.csv" , "mode_share_per_dist.csv" , "mode_users.csv" , "trip_stats.csv" ,
45
49
"mode_share_per_%s.csv" , "population_trip_stats.csv" , "trip_purposes_by_hour.csv" ,
46
- "mode_share_distance_distribution.csv" ,
50
+ "mode_share_distance_distribution.csv" , "mode_shift.csv" ,
47
51
"mode_choices.csv" , "mode_choice_evaluation.csv" , "mode_choice_evaluation_per_mode.csv" ,
48
52
"mode_confusion_matrix.csv" , "mode_prediction_error.csv"
49
53
}
50
54
)
51
55
public class TripAnalysis implements MATSimAppCommand {
52
56
53
- private static final Logger log = LogManager .getLogger (TripAnalysis .class );
54
-
55
57
/**
56
58
* Attributes which relates this person to a reference person.
57
59
*/
58
- public static String ATTR_REF_ID = "ref_id" ;
60
+ public static final String ATTR_REF_ID = "ref_id" ;
59
61
/**
60
62
* Person attribute that contains the reference modes of a person. Multiple modes are delimited by "-".
61
63
*/
62
- public static String ATTR_REF_MODES = "ref_modes" ;
64
+ public static final String ATTR_REF_MODES = "ref_modes" ;
63
65
/**
64
66
* Person attribute containing its weight for analysis purposes.
65
67
*/
66
- public static String ATTR_REF_WEIGHT = "ref_weight" ;
67
-
68
+ public static final String ATTR_REF_WEIGHT = "ref_weight" ;
69
+ private static final Logger log = LogManager .getLogger (TripAnalysis .class );
70
+ @ CommandLine .Option (names = "--person-filter" , description = "Define which persons should be included into trip analysis. Map like: Attribute name (key), attribute value (value). " +
71
+ "The attribute needs to be contained by output_persons.csv. Persons who do not match all filters are filtered out." , split = "," )
72
+ private final Map <String , String > personFilters = new HashMap <>();
68
73
@ CommandLine .Mixin
69
74
private InputOptions input = InputOptions .ofCommand (TripAnalysis .class );
70
75
@ CommandLine .Mixin
71
76
private OutputOptions output = OutputOptions .ofCommand (TripAnalysis .class );
72
-
73
77
@ CommandLine .Option (names = "--input-ref-data" , description = "Optional path to reference data" , required = false )
74
78
private String refData ;
75
-
76
79
@ CommandLine .Option (names = "--match-id" , description = "Pattern to filter agents by id" )
77
80
private String matchId ;
78
-
79
81
@ CommandLine .Option (names = "--dist-groups" , split = "," , description = "List of distances for binning" , defaultValue = "0,1000,2000,5000,10000,20000" )
80
82
private List <Long > distGroups ;
81
-
82
83
@ CommandLine .Option (names = "--modes" , split = "," , description = "List of considered modes, if not set all will be used" )
83
84
private List <String > modeOrder ;
84
-
85
85
@ CommandLine .Option (names = "--shp-filter" , description = "Define how the shp file filtering should work" , defaultValue = "home" )
86
86
private LocationFilter filter ;
87
-
88
87
@ CommandLine .Mixin
89
88
private ShpOptions shp ;
90
89
@@ -131,6 +130,20 @@ private static double[] calcHistogram(double[] data, double[] bins) {
131
130
return hist ;
132
131
}
133
132
133
+ private static Map <String , ColumnType > getColumnTypes () {
134
+ Map <String , ColumnType > columnTypes = new HashMap <>(Map .of ("person" , ColumnType .TEXT ,
135
+ "trav_time" , ColumnType .STRING , "wait_time" , ColumnType .STRING , "dep_time" , ColumnType .STRING ,
136
+ "longest_distance_mode" , ColumnType .STRING , "main_mode" , ColumnType .STRING ,
137
+ "start_activity_type" , ColumnType .TEXT , "end_activity_type" , ColumnType .TEXT ,
138
+ "first_pt_boarding_stop" , ColumnType .TEXT , "last_pt_egress_stop" , ColumnType .TEXT ));
139
+
140
+ // Map.of only has 10 argument max
141
+ columnTypes .put ("traveled_distance" , ColumnType .LONG );
142
+ columnTypes .put ("euclidean_distance" , ColumnType .LONG );
143
+
144
+ return columnTypes ;
145
+ }
146
+
134
147
@ Override
135
148
public Integer call () throws Exception {
136
149
@@ -146,6 +159,43 @@ public Integer call() throws Exception {
146
159
persons = persons .where (persons .textColumn ("person" ).matchesRegex (matchId ));
147
160
}
148
161
162
+ // filter persons according to person (attribute) filter
163
+ if (!personFilters .isEmpty ()) {
164
+ IntSet generalFilteredRowIds = null ;
165
+ for (Map .Entry <String , String > entry : personFilters .entrySet ()) {
166
+ if (!persons .containsColumn (entry .getKey ())) {
167
+ log .warn ("Persons table does not contain column for filter attribute {}. Filter on {} will not be applied." , entry .getKey (), entry .getValue ());
168
+ continue ;
169
+ }
170
+ log .info ("Using person filter for attribute {} and value {}" , entry .getKey (), entry .getValue ());
171
+
172
+ IntSet filteredRowIds = new IntOpenHashSet ();
173
+
174
+ for (int i = 0 ; i < persons .rowCount (); i ++) {
175
+ Row row = persons .row (i );
176
+ String value = row .getString (entry .getKey ());
177
+ // only add value once
178
+ if (value .equals (entry .getValue ())) {
179
+ filteredRowIds .add (i );
180
+ }
181
+ }
182
+
183
+ if (generalFilteredRowIds == null ) {
184
+ // If generalFilteredRowIds is empty, add all elements from filteredRowIds to generalFilteredRowIds
185
+ generalFilteredRowIds = filteredRowIds ;
186
+ } else {
187
+ // If generalFilteredRowIds is not empty, retain only the elements that are also in filteredRowIds
188
+ generalFilteredRowIds .retainAll (filteredRowIds );
189
+ }
190
+ }
191
+
192
+ if (generalFilteredRowIds != null ) {
193
+ persons = persons .where (Selection .with (generalFilteredRowIds .intStream ().toArray ()));
194
+ }
195
+ }
196
+
197
+ log .info ("Filtered {} out of {} persons" , persons .rowCount (), total );
198
+
149
199
// Home filter by standard attribute
150
200
if (shp .isDefined () && filter == LocationFilter .home ) {
151
201
Geometry geometry = shp .getGeometry ();
@@ -166,18 +216,8 @@ public Integer call() throws Exception {
166
216
167
217
log .info ("Filtered {} out of {} persons" , persons .rowCount (), total );
168
218
169
- Map <String , ColumnType > columnTypes = new HashMap <>(Map .of ("person" , ColumnType .TEXT ,
170
- "trav_time" , ColumnType .STRING , "wait_time" , ColumnType .STRING , "dep_time" , ColumnType .STRING ,
171
- "longest_distance_mode" , ColumnType .STRING , "main_mode" , ColumnType .STRING ,
172
- "start_activity_type" , ColumnType .TEXT , "end_activity_type" , ColumnType .TEXT ,
173
- "first_pt_boarding_stop" , ColumnType .TEXT , "last_pt_egress_stop" , ColumnType .TEXT ));
174
-
175
- // Map.of only has 10 argument max
176
- columnTypes .put ("traveled_distance" , ColumnType .LONG );
177
- columnTypes .put ("euclidean_distance" , ColumnType .LONG );
178
-
179
219
Table trips = Table .read ().csv (CsvReadOptions .builder (IOUtils .getBufferedReader (input .getPath ("trips.csv" )))
180
- .columnTypesPartial (columnTypes )
220
+ .columnTypesPartial (getColumnTypes () )
181
221
.sample (false )
182
222
.separator (CsvOptions .detectDelimiter (input .getPath ("trips.csv" ))).build ());
183
223
@@ -271,6 +311,8 @@ public Integer call() throws Exception {
271
311
272
312
writeTripDistribution (joined );
273
313
314
+ writeModeShift (joined );
315
+
274
316
return 0 ;
275
317
}
276
318
@@ -540,6 +582,34 @@ private void writeTripDistribution(Table trips) throws IOException {
540
582
}
541
583
}
542
584
585
+ private void writeModeShift (Table trips ) throws IOException {
586
+ Path path ;
587
+ try {
588
+ Path dir = Path .of (input .getPath ("trips.csv" )).getParent ().resolve ("ITERS" ).resolve ("it.0" );
589
+ path = ApplicationUtils .matchInput ("trips.csv" , dir );
590
+ } catch (Exception e ) {
591
+ log .error ("Could not find trips from 0th iteration." , e );
592
+ return ;
593
+ }
594
+
595
+ Table originalTrips = Table .read ().csv (CsvReadOptions .builder (IOUtils .getBufferedReader (path .toString ()))
596
+ .columnTypesPartial (getColumnTypes ())
597
+ .sample (false )
598
+ .separator (CsvOptions .detectDelimiter (path .toString ())).build ());
599
+
600
+ // Use longest_distance_mode where main_mode is not present
601
+ originalTrips .stringColumn ("main_mode" )
602
+ .set (originalTrips .stringColumn ("main_mode" ).isMissing (),
603
+ originalTrips .stringColumn ("longest_distance_mode" ));
604
+
605
+ originalTrips .column ("main_mode" ).setName ("original_mode" );
606
+
607
+ Table joined = new DataFrameJoiner (trips , "trip_id" ).inner (true , originalTrips );
608
+ Table aggr = joined .summarize ("trip_id" , count ).by ("original_mode" , "main_mode" );
609
+
610
+ aggr .write ().csv (output .getPath ("mode_shift.csv" ).toFile ());
611
+ }
612
+
543
613
/**
544
614
* How shape file filtering should be applied.
545
615
*/
0 commit comments