Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/contributed/sumopy/coremodules/demand/turnflows.py
169689 views
1
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
2
# Copyright (C) 2016-2025 German Aerospace Center (DLR) and others.
3
# SUMOPy module
4
# Copyright (C) 2012-2021 University of Bologna - DICAM
5
# This program and the accompanying materials are made available under the
6
# terms of the Eclipse Public License 2.0 which is available at
7
# https://www.eclipse.org/legal/epl-2.0/
8
# This Source Code may also be made available under the following Secondary
9
# Licenses when the conditions for such availability set forth in the Eclipse
10
# Public License 2.0 are satisfied: GNU General Public License, version 2
11
# or later which is available at
12
# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
13
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
14
15
# @file turnflows.py
16
# @author Joerg Schweizer
17
# @date 2012
18
19
import os
20
import string
21
import numpy as np
22
from numpy import random
23
import agilepy.lib_base.classman as cm
24
import agilepy.lib_base.arrayman as am
25
import agilepy.lib_base.xmlman as xm
26
from agilepy.lib_base.geometry import *
27
#from coremodules.modules_common import *
28
from coremodules.network.network import SumoIdsConf, MODES
29
from agilepy.lib_base.processes import Process, P, call, CmlMixin
30
import demandbase as db
31
32
33
class Flows(am.ArrayObjman):
34
def __init__(self, ident, parent, edges, **kwargs):
35
self._init_objman(ident, parent=parent,
36
name='Flows',
37
info='Contains the number of vehicles which start on the given edge during a certain time interval.',
38
version=0.2,
39
xmltag=('flows', 'flow', None), **kwargs)
40
41
self._init_attributes(edges)
42
43
def _init_attributes(self, edges=None):
44
if edges is None:
45
# recover edges from already initialized
46
edges = self.ids_edge.get_linktab()
47
48
if self.get_version() < 0.1:
49
# update attrs from previous
50
# IdsArrayConf not yet modifiable interactively, despite perm = 'rw',!!!
51
self.ids_edge.set_perm('rw')
52
if hasattr(self, 'func_delete_row'):
53
self.func_make_row._is_returnval = False
54
self.func_delete_row._is_returnval = False
55
56
self.add_col(am.IdsArrayConf('ids_edge', edges,
57
groupnames=['state'],
58
name='Edge ID',
59
perm='rw',
60
info='Edge ID of flow.',
61
xmltag='from',
62
))
63
64
self.add_col(am.ArrayConf('flows', 0,
65
dtype=np.int32,
66
groupnames=['state'],
67
perm='rw',
68
name='Flow',
69
info='Absolute number of vehicles which start on the given edge during a certain time interval.',
70
xmltag='number',
71
))
72
73
self.add(cm.FuncConf('func_make_row', 'on_add_row', None,
74
groupnames=['rowfunctions', '_private'],
75
name='New flow.',
76
info='Add a new flow.',
77
is_returnval=False,
78
))
79
80
self.add(cm.FuncConf('func_delete_row', 'on_del_row', None,
81
groupnames=['rowfunctions', '_private'],
82
name='Del flow',
83
info='Delete flow.',
84
is_returnval=False,
85
))
86
87
def on_del_row(self, id_row=None):
88
if id_row is not None:
89
# print 'on_del_row', id_row
90
self.del_row(id_row)
91
92
def on_add_row(self, id_row=None):
93
if len(self) > 0:
94
# copy previous
95
flow_last = self.get_row(self.get_ids()[-1])
96
self.add_row(**flow_last)
97
else:
98
self.add_row(self.suggest_id())
99
100
def _init_constants(self):
101
#self.edgeweights_orig = None
102
#self.edgeweights_dest = None
103
pass
104
105
def add_flow(self, id_edge, flow):
106
# print 'Flows.add_flows'
107
return self.add_row(ids_edge=id_edge,
108
flows=flow)
109
110
def get_edges(self):
111
return self.ids_edge.get_linktab()
112
113
def get_demand(self):
114
return self.parent.parent.parent
115
116
def count_left(self, counter):
117
"""
118
Counts the number of vehicles leaving the edge.
119
The counter is an array where the index equals the edge ID
120
and the value represens the number of entered vehicles per edge.
121
"""
122
ids_flow = self.get_ids()
123
counter[self.ids_edge[ids_flow]] += self.flows[ids_flow]
124
125
def export_xml(self, fd, vtype, id_flow, share=1.0, indent=2):
126
"""
127
Generates a line for each edge with a flow.
128
129
id_flow is the flow count and is used to generate a unique flow id
130
131
132
"""
133
# TODO: better handling of mode and vtypes, distributions
134
# DONE with share
135
# <flow id="0" from="edge0" to="edge1" type= "vType" number="100"/>
136
ids_eges = []
137
ids_sumoeges = self.get_edges().ids_sumo
138
i = 0
139
# print 'Flows.export_xml vtypes,id_flow,len(self)',vtype,id_flow,len(self)
140
for id_edge, flow in zip(self.ids_edge.get_value(), share*self.flows.get_value()):
141
# print ' id_edge,flow',id_edge,flow
142
ids_eges.append(id_edge)
143
id_flow += 1
144
fd.write(xm.start('flow'+xm.num('id', id_flow), indent))
145
fd.write(xm.num('from', ids_sumoeges[id_edge]))
146
fd.write(xm.num('type', vtype))
147
fd.write(xm.num('number', int(flow)))
148
fd.write(xm.stopit())
149
i += 1
150
# print ' return ids_eges, id_flow',ids_eges, id_flow
151
return ids_eges, id_flow
152
153
154
class Turns(am.ArrayObjman):
155
def __init__(self, ident, parent, edges, **kwargs):
156
self._init_objman(ident, parent=parent,
157
name='Turn flows',
158
info='The table contains turn probabilities between two edges during a given time interval.',
159
version=0.1,
160
xmltag=('odtrips', 'odtrip', None), **kwargs)
161
162
self._init_attributes(edges)
163
164
def _init_attributes(self, edges=None):
165
if edges is None:
166
# recover edges from already initialized
167
edges = self.ids_fromedge.get_linktab()
168
169
if self.get_version() < 0.1:
170
# update attrs from previous
171
# IdsArrayConf not yet modifiable interactively, despite perm = 'rw',!!!
172
self.ids_fromedge.set_perm('rw')
173
self.ids_toedge.set_perm('rw')
174
if hasattr(self, 'func_delete_row'):
175
self.func_make_row._is_returnval = False
176
self.func_delete_row._is_returnval = False
177
178
self.add_col(am.IdsArrayConf('ids_fromedge', edges,
179
groupnames=['state'],
180
name='Edge ID from',
181
info='Edge ID where turn starts.',
182
xmltag='fromEdge',
183
))
184
185
self.add_col(am.IdsArrayConf('ids_toedge', edges,
186
groupnames=['state'],
187
name='Edge ID to',
188
info='Edge ID where turn ends.',
189
xmltag='toEdge',
190
))
191
192
self.add_col(am.ArrayConf('flows', 0,
193
dtype=np.int32,
194
groupnames=['state'],
195
perm='rw',
196
name='Flow',
197
info='Absolute number of vehicles which pass from "fromedge" to "toedge" during a certain time interval.',
198
xmltag='number',
199
))
200
201
self.add_col(am.ArrayConf('probabilities', 0.0,
202
dtype=np.float32,
203
groupnames=['state'],
204
perm='rw',
205
name='Probab.',
206
info='Probability to make a turn between "Edge ID from" and "Edge ID to" and .',
207
xmltag='probability',
208
))
209
210
self.add(cm.FuncConf('func_make_row', 'on_add_row', None,
211
groupnames=['rowfunctions', '_private'],
212
name='New turns',
213
info='Add a new turnflow.',
214
is_returnval=False,
215
))
216
217
self.add(cm.FuncConf('func_delete_row', 'on_del_row', None,
218
groupnames=['rowfunctions', '_private'],
219
name='Del turns',
220
info='Delete turns.',
221
is_returnval=False,
222
))
223
224
def on_del_row(self, id_row=None):
225
if id_row is not None:
226
# print 'on_del_row', id_row
227
self.del_row(id_row)
228
229
def on_add_row(self, id_row=None):
230
if len(self) > 0:
231
# copy previous
232
flow_last = self.get_row(self.get_ids()[-1])
233
self.add_row(**flow_last)
234
else:
235
self.add_row(self.suggest_id())
236
237
def _init_constants(self):
238
#self.edgeweights_orig = None
239
#self.edgeweights_dest = None
240
pass
241
242
def normalize_turnprobabilities(self):
243
"""
244
Makes sure that sum of turn probabilities from an edge equals 1.
245
"""
246
# TODO: there is a quicker way with picking a set and then select
247
# and sum with vectors
248
#ids_source = set(self.ids_fromedge.get_values())
249
250
flows_total = {}
251
for _id in self.get_ids():
252
id_fromedge = self.ids_fromedge[_id]
253
if not flows_total.has_key(id_fromedge):
254
flows_total[id_fromedge] = 0.0
255
flows_total[id_fromedge] += self.flows[_id]
256
257
for _id in self.get_ids():
258
if flows_total[self.ids_fromedge[_id]] > 0:
259
self.probabilities[_id] = self.flows[_id] / flows_total[self.ids_fromedge[_id]]
260
261
def add_turn(self, id_fromedge, id_toedge, flow):
262
print 'Turns.add_turn'
263
return self.add_row(ids_fromedge=id_fromedge,
264
ids_toedge=id_toedge,
265
flows=flow,
266
)
267
268
def get_edges(self):
269
return self.ids_fromedge.get_linktab()
270
271
def count_entered(self, counter):
272
"""
273
Counts the number of vehicles entered in an edge.
274
The counter is an array where the index equals the edge ID
275
and the value represens the number of entered vehicles per edge.
276
"""
277
ids_turns = self.get_ids()
278
counter[self.ids_toedge[ids_turns]] += self.flows[ids_turns]
279
280
def export_xml(self, fd, indent=0):
281
# <edgeRelations>
282
# <interval begin="0" end="3600">
283
# <edgeRelation from="myEdge0" to="myEdge1" probability="0.2"/>
284
# <edgeRelation from="myEdge0" to="myEdge2" probability="0.7"/>
285
# <edgeRelation from="myEdge0" to="myEdge3" probability="0.1"/>
286
287
# ... any other edges ...
288
289
# </interval>
290
291
# ... some further intervals ...
292
293
# </edgeRelations>
294
295
fromedge_to_turnprobs = {}
296
for _id in self.get_ids():
297
id_fromedge = self.ids_fromedge[_id]
298
if not fromedge_to_turnprobs.has_key(id_fromedge):
299
fromedge_to_turnprobs[id_fromedge] = []
300
fromedge_to_turnprobs[id_fromedge].append((self.ids_toedge[_id], self.probabilities[_id]))
301
302
ids_sumoeges = self.get_edges().ids_sumo
303
304
fd.write(xm.begin('edgeRelations', indent))
305
for id_fromedge in fromedge_to_turnprobs.keys():
306
307
for id_toedge, turnprob in fromedge_to_turnprobs[id_fromedge]:
308
fd.write(xm.start('edgeRelation', indent+2))
309
fd.write(xm.num('from', ids_sumoeges[id_fromedge]))
310
fd.write(xm.num('to', ids_sumoeges[id_toedge]))
311
fd.write(xm.num('probability', turnprob))
312
fd.write(xm.stopit())
313
314
fd.write(xm.end('edgeRelations', indent))
315
316
317
class TurnflowModes(am.ArrayObjman):
318
def __init__(self, ident, parent, modes, edges, **kwargs):
319
self._init_objman(ident, parent=parent,
320
name='Mode OD tables',
321
info='Contains for each transport mode an OD trip table.',
322
xmltag=('modesods', 'modeods', 'ids_mode'), **kwargs)
323
324
print 'TurnflowModes.__init__', modes
325
self.add_col(am.IdsArrayConf('ids_mode', modes,
326
groupnames=['state'],
327
choices=MODES,
328
name='ID mode',
329
is_index=True,
330
#xmltag = 'vClass',
331
info='ID of transport mode.',
332
))
333
print ' self.ids_mode.is_index', self.ids_mode.is_index()
334
335
self.add_col(cm.ObjsConf('flowtables',
336
groupnames=['state'],
337
name='Flows',
338
info='Flow generation per edge for a specific mode.',
339
))
340
341
self.add_col(cm.ObjsConf('turntables',
342
groupnames=['state'],
343
name='Turns',
344
info='Turn probabilities between edges for a specific mode.',
345
))
346
347
self.add(cm.ObjConf(edges, is_child=False, groups=['_private']))
348
349
# def generate_trips(self, demand, time_start, time_end,**kwargs):
350
# for id_od_mode in self.get_ids():
351
# self.odtrips[id_od_mode].generate_trips( demand, time_start, time_end, self.ids_mode[id_od_mode],**kwargs)
352
353
# def generate_odflows(self, odflowtab, time_start, time_end,**kwargs):
354
# for id_od_mode in self.get_ids():
355
# self.odtrips[id_od_mode].generate_odflows( odflowtab, time_start, time_end, self.ids_mode[id_od_mode],**kwargs)
356
357
def get_demand(self):
358
return self.parent.parent
359
360
def normalize_turnprobabilities(self):
361
"""
362
Makes sure that sum of turn probabilities from an edge equals 1.
363
"""
364
for _id in self.get_ids():
365
self.turntables[_id].normalize_turnprobabilities()
366
367
def add_mode(self, id_mode):
368
id_tf_modes = self.add_row(ids_mode=id_mode)
369
print ' add_mode', id_mode, id_tf_modes
370
371
flows = Flows((self.flowtables.attrname, id_tf_modes), self, self.edges.get_value())
372
self.flowtables[id_tf_modes] = flows
373
374
turns = Turns((self.turntables.attrname, id_tf_modes), self, self.edges.get_value())
375
self.turntables[id_tf_modes] = turns
376
377
return id_tf_modes
378
379
def add_flow(self, id_mode, id_edge, flow):
380
"""
381
Sets a demand flows between from-Edge and toEdge pairs for mode where flows is a dictionary
382
with (fromEdgeID,toEdgeID) pair as key and number of trips as values.
383
"""
384
print 'TurnflowModes.add_turnflows', id_mode # ,flows,kwargs
385
print ' self.ids_mode.is_index()', self.ids_mode.is_index()
386
if self.ids_mode.has_index(id_mode):
387
id_tf_modes = self.ids_mode.get_id_from_index(id_mode)
388
else:
389
id_tf_modes = self.add_mode(id_mode)
390
self.flowtables[id_tf_modes].add_flow(id_edge, flow)
391
return self.flowtables[id_tf_modes]
392
393
def add_turn(self, id_mode, id_fromedge, id_toedge, turnflow):
394
"""
395
Sets turn probability between from-edge and to-edge.
396
"""
397
print 'TurnflowModes.add_turn', id_mode # ,turnflow,kwargs
398
# if scale!=1.0:
399
# for od in odm.iterkeys():
400
# odm[od] *= scale
401
# if not self.contains_key(mode):
402
# self._initTurnflowsMode(mode)
403
if self.ids_mode.has_index(id_mode):
404
id_tf_modes = self.ids_mode.get_id_from_index(id_mode)
405
else:
406
id_tf_modes = self.add_mode(id_mode)
407
408
self.turntables[id_tf_modes].add_turn(id_fromedge, id_toedge, turnflow)
409
return self.turntables[id_tf_modes]
410
411
def export_flows_xml(self, fd, id_mode, id_flow=0, indent=0):
412
"""
413
Export flow data of desired mode to xml file.
414
Returns list with edge IDs with non zero flows and a flow ID counter.
415
"""
416
print 'TurnflowModes.export_flows_xml id_mode, id_flow', id_mode, id_flow, self.ids_mode.has_index(id_mode)
417
ids_sourceedge = []
418
419
if not self.ids_mode.has_index(id_mode):
420
return ids_sourceedge, id_flow
421
422
# get vtypes for specified mode
423
vtypes, shares = self.get_demand().vtypes.select_by_mode(
424
id_mode, is_sumoid=True, is_share=True)
425
426
# for vtype in vtypes:
427
# print ' test vtype',vtype
428
# print ' _index_to_id', self.get_demand().vtypes.ids_sumo._index_to_id
429
# print ' id_vtype',self.get_demand().vtypes.ids_sumo.get_id_from_index(vtype)
430
431
# if len(vtypes) > 0:# any vehicles found for this mode?
432
# # TODO: can we put some distributen here?
433
# vtype = vtypes[0]
434
for vtype, share in zip(vtypes, shares):
435
print ' write flows for vtype', vtype, share
436
if self.ids_mode.has_index(id_mode):
437
id_tf_modes = self.ids_mode.get_id_from_index(id_mode)
438
ids_sourceedge, id_flow = self.flowtables[id_tf_modes].export_xml(
439
fd, vtype, id_flow, share=share, indent=indent)
440
441
# print ' return ids_sourceedge, id_flow',ids_sourceedge, id_flow
442
return ids_sourceedge, id_flow
443
444
def export_turns_xml(self, fd, id_mode, indent=0):
445
"""
446
Export flow data of desired mode to xml file.
447
Returns list with edge IDs with non zero flows and a flow ID counter.
448
"""
449
print 'TurnflowModes.export_turns_xml'
450
451
if self.ids_mode.has_index(id_mode):
452
id_tf_modes = self.ids_mode.get_id_from_index(id_mode)
453
self.turntables[id_tf_modes].export_xml(fd, indent=indent)
454
455
def count_entered(self, counter):
456
"""
457
Counts the number of vehicles entered in an edge.
458
The counter is an array where the index equals the edge ID
459
and the value represens the number of entered vehicles per edge.
460
"""
461
for id_tf_modes in self.get_ids():
462
self.turntables[id_tf_modes].count_entered(counter)
463
464
465
class Turnflows(am.ArrayObjman):
466
def __init__(self, ident, demand, net, **kwargs):
467
self._init_objman(ident, parent=demand, # = demand
468
name='Turnflow Demand',
469
info='Contains flows and turn probailities for different modes and time intervals. Here demand data is ordered by time intervals.',
470
xmltag=('turnflows', 'interval', None), **kwargs)
471
472
self.add_col(am.ArrayConf('times_start', 0,
473
groupnames=['state'],
474
perm='rw',
475
name='Start time',
476
unit='s',
477
info='Start time of interval in seconds (no fractional seconds).',
478
xmltag='t_start',
479
))
480
481
self.add_col(am.ArrayConf('times_end', 3600,
482
groupnames=['state'],
483
perm='rw',
484
name='End time',
485
unit='s',
486
info='End time of interval in seconds (no fractional seconds).',
487
xmltag='t_end',
488
))
489
490
self.add_col(cm.ObjsConf('turnflowmodes',
491
groupnames=['state'],
492
is_save=True,
493
name='Turnflows by modes',
494
info='Turnflow transport demand for all transport modes within the respective time interval.',
495
))
496
#self.add( cm.ObjConf( Sinkzones('sinkzones', self, demand.get_scenario().net.edges) ))
497
498
def get_demand(self):
499
return self.parent
500
501
def normalize_turnprobabilities(self):
502
"""
503
Makes sure that sum of turn probabilities from an edge equals 1.
504
"""
505
print 'Turnflows.normalize_turnprobabilities'
506
# for turnflowmode in self.turnflowmodes.get_value():
507
# turnflowmode.normalize_turnprobabilities() # no! it's a dict!!
508
# print ' ',self.turnflowmodes.get_value()
509
for _id in self.get_ids():
510
self.turnflowmodes[_id].normalize_turnprobabilities()
511
512
def clear_turnflows(self):
513
self.clear()
514
515
def add_flow(self, t_start, t_end, id_mode, id_edge, flow):
516
517
# print 'turnflows.add_flow', t_start, t_end, id_mode, id_edge, flow
518
ids_inter = self.select_ids((self.times_start.get_value() == t_start) & (self.times_end.get_value() == t_end))
519
if len(ids_inter) == 0:
520
521
id_inter = self.add_row(times_start=t_start, times_end=t_end,)
522
# print ' create new',id_inter
523
tfmodes = TurnflowModes((self.turnflowmodes.attrname, id_inter),
524
self, self.get_net().modes, self.get_net().edges)
525
526
# NO!! odmodes = OdModes( ('ODMs for modes', id_inter), parent = self, modes = self.get_net().modes, zones = self.get_zones())
527
self.turnflowmodes[id_inter] = tfmodes
528
529
flows = tfmodes.add_flow(id_mode, id_edge, flow)
530
531
else:
532
533
# there should be only one demand table found for a certain interval
534
id_inter = ids_inter[0]
535
# print ' use',id_inter
536
flows = self.turnflowmodes[id_inter].add_flow(id_mode, id_edge, flow)
537
return flows
538
539
def add_turn(self, t_start, t_end, id_mode, id_fromedge, id_toedge, turnflow):
540
541
# print 'turnflows.add_turnflows',t_start, t_end,id_mode,id_fromedge, id_toedge, turnprob
542
ids_inter = self.select_ids((self.times_start.get_value() == t_start) & (self.times_end.get_value() == t_end))
543
if len(ids_inter) == 0:
544
545
id_inter = self.add_row(times_start=t_start, times_end=t_end,)
546
# print ' create new',id_inter
547
tfmodes = TurnflowModes((self.turnflowmodes.attrname, id_inter),
548
self, self.get_net().modes, self.get_net().edges)
549
550
# NO!! odmodes = OdModes( ('ODMs for modes', id_inter), parent = self, modes = self.get_net().modes, zones = self.get_zones())
551
self.turnflowmodes[id_inter] = tfmodes
552
553
turns = tfmodes.add_turn(id_mode, id_fromedge, id_toedge, turnflow)
554
555
else:
556
557
# there should be only one demand table found for a certain interval
558
id_inter = ids_inter[0]
559
# print ' use',id_inter
560
turns = self.turnflowmodes[id_inter].add_turn(id_mode, id_fromedge, id_toedge, turnflow)
561
return turns
562
563
def get_net(self):
564
return self.parent.get_scenario().net
565
566
def get_edges(self):
567
return self.get_net().edges
568
569
def get_modes(self):
570
ids_mode = set()
571
for id_inter in self.get_ids():
572
ids_mode.update(self.turnflowmodes[id_inter].ids_mode.value)
573
return list(ids_mode) # self.get_net().modes
574
575
def get_sinkedges(self):
576
zones = self.parent.get_scenario().landuse.zones
577
ids_sinkedges = set()
578
ids_sinkzone = zones.select_ids(zones.ids_landusetype.get_value() == 7)
579
for ids_edge in zones.ids_edges_inside[ids_sinkzone]:
580
ids_sinkedges.update(ids_edge)
581
#sinkedges = zones.ids_edges_orig.get_value().tolist()
582
# print 'get_sinkedges',sinkedges
583
# print ' sinkedges',np.array(sinkedges,np.object)
584
return ids_sinkedges
585
586
def export_flows_and_turns(self, flowfilepath, turnsfilepath, id_mode, indent=0):
587
"""
588
Create the flow file and turn ratios file for a specific mode.
589
In the SUMOpy tunflow data structure, each mode has its own
590
flow and turnratio data.
591
"""
592
print '\n\n'+79*'_'
593
print 'export_flows_and_turns id_mode=', id_mode, 'ids_vtype=', self.parent.vtypes.select_by_mode(id_mode)
594
print ' write flows', flowfilepath
595
fd = open(flowfilepath, 'w')
596
fd.write(xm.begin('flows', indent))
597
598
# write all possible vtypes for this mode
599
self.parent.vtypes.write_xml(fd, indent=indent,
600
ids=self.parent.vtypes.select_by_mode(id_mode),
601
is_print_begin_end=False)
602
603
id_flow = 0
604
ids_allsourceedges = []
605
time_start_min = +np.inf
606
time_end_max = -np.inf
607
for id_inter in self.get_ids():
608
time_start = self.times_start[id_inter]
609
time_end = self.times_end[id_inter]
610
fd.write(xm.begin('interval'+xm.num('begin', time_start)+xm.num('end', time_end), indent+2))
611
ids_sourceedge, id_flow = self.turnflowmodes[id_inter].export_flows_xml(fd, id_mode, id_flow, indent+4)
612
# print ' got ids_sourceedge, id_flow',ids_sourceedge, id_flow
613
ids_allsourceedges += ids_sourceedge
614
615
if len(ids_sourceedge) > 0:
616
# print ' extend total time interval only for intervals with flow'
617
if time_start < time_start_min:
618
time_start_min = time_start
619
620
if time_end > time_end_max:
621
time_end_max = time_end
622
623
fd.write(xm.end('interval', indent+2))
624
625
fd.write(xm.end('flows', indent))
626
fd.close()
627
628
# print ' write turndefs', turnsfilepath
629
fd = open(turnsfilepath, 'w')
630
fd.write(xm.begin('turns', indent))
631
632
for id_inter in self.get_ids():
633
time_start = self.times_start[id_inter]
634
time_end = self.times_end[id_inter]
635
fd.write(xm.begin('interval'+xm.num('begin', time_start)+xm.num('end', time_end), indent+2))
636
self.turnflowmodes[id_inter].export_turns_xml(fd, id_mode, indent+4)
637
fd.write(xm.end('interval', indent+2))
638
639
# take sink edges from sink zones
640
ids_sinkedge = self.get_sinkedges() # it's a set
641
# ...and remove source edges, otherwise vehicle will be inserted and
642
# immediately removed
643
# print ' ids_sinkedge',ids_sinkedge
644
# print ' ids_allsourceedges',ids_allsourceedges
645
ids_sinkedge = ids_sinkedge.difference(ids_allsourceedges)
646
647
ids_sumoedge = self.get_edges().ids_sumo
648
# print ' determined sink edges',list(ids_sinkedge)
649
if len(ids_sinkedge) > 0:
650
fd.write(xm.start('sink'))
651
fd.write(xm.arr('edges', ids_sumoedge[list(ids_sinkedge)]))
652
fd.write(xm.stopit())
653
654
fd.write(xm.end('turns', indent))
655
fd.close()
656
if len(ids_allsourceedges) == 0:
657
time_start_min = 0
658
time_end_max = 0
659
660
return time_start_min, time_end_max
661
662
def estimate_entered(self):
663
"""
664
Estimates the entered number of vehicles for each edge
665
generated by turnflow definitions. Bases are the only the
666
turnflows, not the generated flows (which are not entering an edge).
667
668
returns ids_edge and entered_vec
669
"""
670
671
counter = np.zeros(np.max(self.get_edges().get_ids())+1, int)
672
673
for id_inter in self.get_ids():
674
self.turnflowmodes[id_inter].count_entered(counter)
675
676
ids_edge = np.flatnonzero(counter)
677
entered_vec = counter[ids_edge].copy()
678
return ids_edge, entered_vec
679
680
def turnflows_to_routes(self, is_clear_trips=True, is_export_network=True,
681
is_make_probabilities=True, cmloptions=None,):
682
# jtrrouter --flow-files=<FLOW_DEFS>
683
# --turn-ratio-files=<TURN_DEFINITIONS> --net-file=<SUMO_NET> \
684
# --output-file=MySUMORoutes.rou.xml --begin <UINT> --end <UINT>
685
686
if is_make_probabilities:
687
self.normalize_turnprobabilities()
688
689
scenario = self.parent.get_scenario()
690
if cmloptions is None:
691
cmloptions = '-v --max-edges-factor 1 --seed 23423 --repair --ignore-vclasses false --ignore-errors --turn-defaults 5,90,5'
692
693
trips = scenario.demand.trips
694
if is_clear_trips:
695
# clear all current trips = routes
696
trips.clear_trips()
697
698
rootfilepath = scenario.get_rootfilepath()
699
netfilepath = scenario.net.get_filepath()
700
flowfilepath = rootfilepath+'.flow.xml'
701
turnfilepath = rootfilepath+'.turn.xml'
702
703
routefilepath = trips.get_routefilepath()
704
705
# first generate xml for net
706
if is_export_network:
707
scenario.net.export_netxml()
708
709
ids_mode = self.get_modes()
710
print 'turnflows_to_routes', ids_mode # scenario.net.modes.get_ids()
711
print ' cmloptions', cmloptions
712
713
# route for all modes and read in routes
714
for id_mode in ids_mode:
715
# write flow and turns xml file for this mode
716
time_start, time_end = self.export_flows_and_turns(flowfilepath, turnfilepath, id_mode)
717
print ' time_start, time_end =', time_start, time_end
718
719
if time_end > time_start: # means there exist some flows for this mode
720
cmd = 'jtrrouter --route-files=%s --turn-ratio-files=%s --net-file=%s --output-file=%s --begin %s --end %s %s'\
721
% (P+flowfilepath+P,
722
P+turnfilepath+P,
723
P+netfilepath+P,
724
P+routefilepath+P,
725
time_start,
726
time_end,
727
cmloptions,
728
)
729
# print '\n Starting command:',cmd
730
if call(cmd):
731
if os.path.isfile(routefilepath):
732
trips.import_routes_xml(routefilepath, is_generate_ids=True)
733
os.remove(routefilepath)
734
735
else:
736
print 'jtrroute: no flows generated for id_mode', id_mode
737
738
# self.simfiles.set_modified_data('rou',True)
739
# self.simfiles.set_modified_data('trip',True)
740
# trips and routes are not yet saved!!
741
742
743
class TurnflowRouter(db.TripoptionMixin, CmlMixin, Process):
744
def __init__(self, turnflows, logger=None, **kwargs):
745
746
self._init_common('turnflowrouter', name='Turnflow Router',
747
parent=turnflows,
748
logger=logger,
749
info='Generates routes from turnflow database using the JTR router.',
750
)
751
752
self.init_cml('') # pass no commad to generate options only
753
754
attrsman = self.get_attrsman()
755
756
self.add_option('turnratio_defaults', '30,50,20',
757
groupnames=['options'],
758
cml='--turn-defaults',
759
name='Default turn ratios',
760
info='Default turn definition. Comma separated string means: "percent right, percent straight, percent left".',
761
)
762
763
self.add_option('max-edges-factor', 2.0,
764
groupnames=['options'],
765
cml='--max-edges-factor',
766
name='Maximum edge factor',
767
info='Routes are cut off when the route edges to net edges ratio is larger than this factor.',
768
)
769
770
self.add_option('is_internal_links', False,
771
groupnames=['options'],
772
cml='--no-internal-links',
773
name='Disable internal links',
774
info='Disable (junction) internal links.',
775
)
776
777
self.add_option('is_randomize_flows', True,
778
groupnames=['options'],
779
cml='--randomize-flows',
780
name='Randomize flows',
781
info='generate random departure times for flow input.',
782
)
783
784
self.add_option('is_ignore_vclasses', False,
785
groupnames=['options'],
786
cml='--ignore-vclasses',
787
name='Ignore mode restrictions',
788
info='Ignore mode restrictions of network edges.',
789
)
790
791
self.add_option('is_remove_loops', True,
792
groupnames=['options'],
793
cml='--remove-loops',
794
name='Remove loops',
795
info='emove loops within the route; Remove turnarounds at start and end of the route.',
796
)
797
798
self.add_option('is_remove_loops', True,
799
groupnames=['options'],
800
cml='--remove-loops',
801
name='Remove loops',
802
info='Remove loops within the route; Remove turnarounds at start and end of the route.',
803
)
804
805
self.add_option('is_weights_interpolate', True,
806
groupnames=['options'],
807
cml='--weights.interpolate',
808
name='Interpolate edge weights',
809
info='Interpolate edge weights at interval boundaries.',
810
)
811
812
self.add_option('weights_minor_penalty', 1.5,
813
groupnames=['options'],
814
cml='--weights.minor-penalty',
815
name='Minor link panelty',
816
info='Apply the given time penalty when computing routing costs for minor-link internal lanes.',
817
)
818
819
self.add_option('n_routing_threads', 0,
820
groupnames=['options'],
821
cml='--routing-threads',
822
name='Number of parallel threads',
823
info='ATTENTION: Numbers greater than 0 may cause errors!. The number of parallel execution threads used for routing.',
824
)
825
self.add_option('seed', 1,
826
groupnames=['options'],
827
cml='--seed',
828
name='Random seed',
829
info='Initialises the random number generator with the given value.',
830
)
831
832
self.add_option('is_repair', True,
833
groupnames=['options'],
834
cml='--repair',
835
name='Repair routes',
836
info='Tries to correct a false route.',
837
)
838
839
self.add_option('is_ignore_errors', True,
840
groupnames=['options'],
841
cml='--ignore-errors',
842
name='Ignore errors',
843
info="""Continue routing, even if errors occur.
844
This option is recommended to avoid abortion if no sink zones are.""",
845
)
846
847
self.is_export_network = attrsman.add(am.AttrConf('is_export_network', True,
848
groupnames=['options'],
849
perm='rw',
850
name='Export network',
851
info='Export network before routing.',
852
))
853
854
self.is_clear_trips = attrsman.add(am.AttrConf('is_clear_trips', True,
855
groupnames=['options'],
856
perm='rw',
857
name='Clear trips',
858
info='Clear all trips in current trips database before routing.',
859
))
860
861
self.add_posoptions()
862
self.add_laneoptions()
863
self.add_speedoptions()
864
# self.add_option('turnratiofilepath', turnratiofilepath,
865
# groupnames = ['_private'],#
866
# cml = '--turn-ratio-files',
867
# perm='r',
868
# name = 'Net file',
869
# wildcards = 'Net XML files (*.net.xml)|*.net.xml',
870
# metatype = 'filepath',
871
# info = 'SUMO Net file in XML format.',
872
# )
873
874
def do(self):
875
print 'do'
876
cml = self.get_cml(is_without_command=True) # only options, not the command #
877
print ' cml=', cml
878
self.parent.turnflows_to_routes(is_clear_trips=self.is_clear_trips,
879
is_export_network=self.is_export_network,
880
is_make_probabilities=True,
881
cmloptions=cml)
882
return True
883
884
885
class TurnflowImporter(Process):
886
def __init__(self, turnflows, rootname=None, rootdirpath=None, tffilepath=None,
887
logger=None, **kwargs):
888
889
self._init_common('turnflowimporter', name='Turnflow Importer',
890
parent=turnflows,
891
logger=logger,
892
info='Reads and imports turnflow data from different file formates.',
893
)
894
895
self._edges = turnflows.get_edges()
896
self._net = self._edges.get_parent()
897
898
if rootname is None:
899
rootname = self._net.parent.get_rootfilename()
900
901
if rootdirpath is None:
902
if self._net.parent is not None:
903
rootdirpath = self._net.parent.get_workdirpath()
904
else:
905
rootdirpath = os.getcwd()
906
907
if tffilepath is None:
908
tffilepath = os.path.join(rootdirpath, rootname+'.net.xml')
909
910
attrsman = self.get_attrsman()
911
912
self.t_start = attrsman.add(am.AttrConf('t_start', 0,
913
groupnames=['options'],
914
perm='rw',
915
name='Start time',
916
unit='s',
917
info='Start time of interval',
918
))
919
920
self.t_end = attrsman.add(am.AttrConf('t_end', 3600,
921
groupnames=['options'],
922
perm='rw',
923
name='End time',
924
unit='s',
925
info='End time of interval',
926
))
927
928
# here we get currently available vehicle classes not vehicle type
929
# specific vehicle type within a class will be generated later
930
self.id_mode = attrsman.add(am.AttrConf('id_mode', MODES['passenger'],
931
groupnames=['options'],
932
choices=turnflows.parent.vtypes.get_modechoices(),
933
name='ID mode',
934
info='ID of transport mode.',
935
))
936
937
self.tffilepath = attrsman.add(am.AttrConf('tffilepath', tffilepath,
938
groupnames=['options'], # this will make it show up in the dialog
939
perm='rw',
940
name='Turnflow file',
941
wildcards="Turnflows CSV files (*.csv)|*.csv|CSV files (*.txt)|*.txt|All files (*.*)|*.*",
942
metatype='filepath',
943
info='CSV file with turnflow information for the specific mode and time interval.',
944
))
945
946
def update_params(self):
947
"""
948
Make all parameters consistent.
949
example: used by import OSM to calculate/update number of tiles
950
from process dialog
951
"""
952
pass
953
#self.workdirpath = os.path.dirname(self.netfilepath)
954
#bn = os.path.basename(self.netfilepath).split('.')
955
# if len(bn)>0:
956
# self.rootname = bn[0]
957
958
def do(self):
959
# self.update_params()
960
if os.path.isfile(self.tffilepath):
961
ids_sumoedge_notexist, pairs_sumoedge_unconnected = self.import_pat_csv()
962
return (len(ids_sumoedge_notexist) == 0) & (len(pairs_sumoedge_unconnected) == 0)
963
964
def import_pat_csv(self, sep=","):
965
966
f = open(self.tffilepath, 'r')
967
# self.attrs.print_attrs()
968
turnflows = self.parent
969
edges = turnflows.get_edges()
970
ids_edge_sumo = edges.ids_sumo
971
972
ids_sumoedge_notexist = []
973
pairs_sumoedge_unconnected = []
974
975
print 'import_pat_csv', self.tffilepath
976
i_line = 1
977
for line in f.readlines():
978
cols = line.split(sep)
979
# print ' cols=',cols
980
if len(cols) >= 2:
981
id_fromedge_sumo = cols[0].strip()
982
if not ids_edge_sumo.has_index(id_fromedge_sumo):
983
ids_sumoedge_notexist.append(id_fromedge_sumo)
984
else:
985
id_fromedge = ids_edge_sumo.get_id_from_index(id_fromedge_sumo)
986
987
if cols[1].strip() != '':
988
flow = int(string.atof(cols[1].strip()))
989
# print ' id_fromedge,flow',id_fromedge,flow
990
if flow > 0:
991
turnflows.add_flow(self.t_start, self.t_end, self.id_mode, id_fromedge, flow)
992
993
if len(cols) >= 4:
994
for i in range(2, len(cols), 2):
995
id_toedge_sumo = cols[i].strip()
996
if not ids_edge_sumo.has_index(id_toedge_sumo):
997
ids_sumoedge_notexist.append(id_toedge_sumo)
998
else:
999
id_toedge = ids_edge_sumo.get_id_from_index(id_toedge_sumo)
1000
if not (id_toedge in edges.get_outgoing(id_fromedge)):
1001
pairs_sumoedge_unconnected.append((id_fromedge_sumo, id_toedge_sumo))
1002
else:
1003
if cols[i+1].strip() != '':
1004
turnflow = int(string.atof(cols[i+1].strip()))
1005
turnflows.add_turn(self.t_start, self.t_end, self.id_mode,
1006
id_fromedge, id_toedge, turnflow)
1007
1008
else:
1009
print 'WARNING: inconsistent row in line %d, file %s' % (i_line, self.tffilepath)
1010
i_line += 1
1011
f.close()
1012
if len(ids_sumoedge_notexist) > 0:
1013
print 'WARNING: inexistant edge IDs:', ids_sumoedge_notexist
1014
if len(pairs_sumoedge_unconnected) > 0:
1015
print 'WARNING: unconnected edge pairs:', pairs_sumoedge_unconnected
1016
1017
return ids_sumoedge_notexist, pairs_sumoedge_unconnected
1018
1019