Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/osmWebWizard.py
193674 views
1
#!/usr/bin/env python
2
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
# Copyright (C) 2014-2026 German Aerospace Center (DLR) and others.
4
# This program and the accompanying materials are made available under the
5
# terms of the Eclipse Public License 2.0 which is available at
6
# https://www.eclipse.org/legal/epl-2.0/
7
# This Source Code may also be made available under the following Secondary
8
# Licenses when the conditions for such availability set forth in the Eclipse
9
# Public License 2.0 are satisfied: GNU General Public License, version 2
10
# or later which is available at
11
# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13
14
# @file osmWebWizard.py
15
# @author Jakob Stigloher
16
# @author Jakob Erdmann
17
# @author Michael Behrisch
18
# @author Mirko Barthauer
19
# @date 2014-14-10
20
21
from __future__ import absolute_import
22
from __future__ import print_function
23
24
import os
25
import sys
26
import stat
27
import traceback
28
import webbrowser
29
import datetime
30
import json
31
import threading
32
import subprocess
33
import tempfile
34
import shutil
35
from zipfile import ZipFile
36
import base64
37
import ssl
38
import collections
39
from http.server import HTTPServer, SimpleHTTPRequestHandler
40
import time
41
42
import osmGet
43
import osmBuild
44
import randomTrips
45
import ptlines2flows
46
import tileGet
47
import sumolib
48
from webWizard.SimpleWebSocketServer import SimpleWebSocketServer, WebSocket
49
50
SUMO_HOME = os.environ.get("SUMO_HOME", os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
51
DEFAULT_PORT = 8010
52
53
try:
54
basestring
55
# Allows isinstance(foo, basestring) to work in Python 3
56
except NameError:
57
basestring = str
58
59
typemapdir = os.path.join("${SUMO_HOME}" if "SUMO_HOME" in os.environ else SUMO_HOME, "data", "typemap")
60
typemaps = {
61
"net": os.path.join(typemapdir, "osmNetconvert.typ.xml"),
62
"poly": os.path.join(typemapdir, "osmPolyconvert.typ.xml"),
63
"urban": os.path.join(typemapdir, "osmNetconvertUrbanDe.typ.xml"),
64
"pedestrians": os.path.join(typemapdir, "osmNetconvertPedestrians.typ.xml"),
65
"ships": os.path.join(typemapdir, "osmNetconvertShips.typ.xml"),
66
"bicycles": os.path.join(typemapdir, "osmNetconvertBicycle.typ.xml"),
67
"aerialway": os.path.join(typemapdir, "osmNetconvertAerialway.typ.xml"),
68
}
69
70
# common parameters
71
CP = ["--trip-attributes", 'departLane="best"',
72
"--fringe-start-attributes", 'departSpeed="max"',
73
"--validate", "--remove-loops",
74
"--via-edge-types", ','.join(["highway.motorway",
75
"highway.motorway_link",
76
"highway.trunk_link",
77
"highway.primary_link",
78
"highway.secondary_link",
79
"highway.tertiary_link"])
80
]
81
82
# pedestrian parameters
83
PP = ["--vehicle-class", "pedestrian", "--prefix", "ped", ]
84
85
86
def getParams(vClass, prefix=None):
87
if prefix is None:
88
prefix = vClass
89
return ["--vehicle-class", vClass, "--vclass", vClass, "--prefix", prefix]
90
91
92
vehicleParameters = {
93
"passenger": CP + getParams("passenger", "veh") + ["--min-distance", "300", "--min-distance.fringe", "10",
94
"--allow-fringe.min-length", "1000", "--lanes"],
95
"truck": CP + getParams("truck") + ["--min-distance", "600", "--min-distance.fringe", "10"], # noqa
96
"bus": CP + getParams("bus") + ["--min-distance", "600", "--min-distance.fringe", "10"], # noqa
97
"motorcycle": CP + getParams("motorcycle") + ["--max-distance", "1200"], # noqa
98
"bicycle": CP + getParams("bicycle", "bike") + ["--max-distance", "8000"], # noqa
99
"tram": CP + getParams("tram") + ["--min-distance", "1200", "--min-distance.fringe", "10"], # noqa
100
"rail_urban": CP + getParams("rail_urban") + ["--min-distance", "1800", "--min-distance.fringe", "10"], # noqa
101
"rail": CP + getParams("rail") + ["--min-distance", "2400", "--min-distance.fringe", "10"], # noqa
102
"ship": getParams("ship") + ["--fringe-start-attributes", 'departSpeed="max"', "--validate"],
103
"pedestrian": PP + ["--pedestrians", "--max-distance", "2000"],
104
"persontrips": PP + ["--persontrips", "--trip-attributes", 'modes="public"'],
105
}
106
107
vehicleNames = {
108
"passenger": "Cars",
109
"truck": "Trucks",
110
"bus": "Bus",
111
"motorcycle": "Motorcycles",
112
"bicycle": "Bicycles",
113
"pedestrian": "Pedestrians",
114
"tram": "Trams",
115
"rail_urban": "Urban Trains",
116
"rail": "Trains",
117
"ship": "Ships"
118
}
119
120
121
# all can read and execute, only user can read batch files
122
BATCH_MODE = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH
123
BATCH_MODE |= stat.S_IWUSR
124
BATCH_MODE |= stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
125
126
127
def quoted_str(s):
128
if isinstance(s, float):
129
return "%.6f" % s
130
elif not isinstance(s, str):
131
return str(s)
132
elif '"' in s or ' ' in s:
133
return '"' + s.replace('"', '\\"') + '"'
134
else:
135
return s
136
137
138
class Builder(object):
139
prefix = "osm"
140
141
def __init__(self, data, local):
142
self.files = {}
143
self.files_relative = {}
144
self.data = data
145
146
self.tmp = None
147
if local:
148
now = data.get("outputDir", datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"))
149
for base in ['', os.path.expanduser('~/Sumo')]:
150
try:
151
self.tmp = os.path.abspath(os.path.join(base, now))
152
os.makedirs(self.tmp, exist_ok=data.get("outputDirExistOk", False))
153
break
154
except Exception:
155
print("Cannot create directory '%s'." % self.tmp, file=sys.stderr)
156
self.tmp = None
157
if self.tmp is None:
158
self.tmp = tempfile.mkdtemp()
159
160
self.origDir = os.getcwd()
161
print("Building scenario in '%s'." % self.tmp)
162
163
def report(self, message):
164
pass
165
166
def filename(self, use, name, usePrefix=True):
167
prefix = self.prefix if usePrefix else ''
168
self.files_relative[use] = prefix + name
169
self.files[use] = os.path.join(self.tmp, prefix + name)
170
171
def getRelative(self, options):
172
result = []
173
dirname = self.tmp
174
ld = len(dirname)
175
for o in options:
176
if isinstance(o, basestring) and o[:ld] == dirname:
177
remove = o[:ld+1]
178
result.append(o.replace(remove, ''))
179
else:
180
result.append(o)
181
return result
182
183
def build(self):
184
# output name for the osm file, will be used by osmBuild, can be
185
# deleted after the process (but should generally be kept for rebuilding)
186
self.filename("osm", "_bbox.osm.xml.gz")
187
# configuration for re-downloading the same area from OSM
188
self.filename("ogc", ".ogcfg")
189
# output name for the net file, will be used by osmBuild, randomTrips and sumo-gui
190
self.filename("net", ".net.xml.gz")
191
192
if self.data.get("coords") is None:
193
# fixed input testing mode
194
self.files["osm"] = self.data['osm']
195
else:
196
self.report("Downloading map data")
197
osmArgs = ["-b=" + (",".join(map(str, self.data["coords"]))), "-p", self.prefix, "-d", self.tmp, "-z"]
198
if self.data["poly"]:
199
osmArgs.append("--shapes")
200
if 'osmMirror' in self.data:
201
osmArgs += ["-u", self.data["osmMirror"]]
202
if 'roadTypes' in self.data:
203
osmArgs += ["-r", json.dumps(self.data["roadTypes"])]
204
# cannot write config by calling osmGet.get because saving config triggers sys.exit()
205
subprocess.call([sys.executable, osmGet.__file__] + osmArgs + ['-C', self.files['ogc']])
206
osmGet.get(osmArgs)
207
208
if not os.path.exists(self.files["osm"]):
209
raise RuntimeError("Download failed")
210
211
options = ["-f", self.files["osm"], "-p", self.prefix, "-d", self.tmp, "-z"]
212
213
self.additionalFiles = []
214
self.routenames = []
215
216
if self.data["poly"]:
217
# output name for the poly file, will be used by osmBuild and sumo-gui
218
self.filename("poly", ".poly.xml.gz")
219
220
options += ["-m", typemaps["poly"]]
221
self.additionalFiles.append(self.files["poly"])
222
223
typefiles = [typemaps["net"]]
224
# leading space ensures that arguments starting with -- are not
225
# misinterpreted as options
226
netconvertOptions = " " + osmBuild.DEFAULT_NETCONVERT_OPTS
227
if self.data.get("options"):
228
netconvertOptions += "," + self.data["options"]
229
netconvertOptions += ",--tls.default-type,actuated"
230
# netconvertOptions += ",--default.spreadtype,roadCenter"
231
if "pedestrian" in self.data["vehicles"]:
232
# sidewalks are already included via typefile
233
netconvertOptions += ",--crossings.guess"
234
netconvertOptions += ",--osm.sidewalks"
235
typefiles.append(typemaps["urban"])
236
typefiles.append(typemaps["pedestrians"])
237
# if ferry PT mode is requested, aslo include ships typemap for netconvert
238
if ("ship" in self.data["vehicles"] or
239
(self.data.get("publicTransport")
240
and self.data.get("ptModes")
241
and self.data.get("ptModes").get("ferry"))):
242
typefiles.append(typemaps["ships"])
243
if "bicycle" in self.data["vehicles"]:
244
typefiles.append(typemaps["bicycles"])
245
netconvertOptions += ",--osm.bike-access"
246
# special treatment for public transport
247
if self.data["publicTransport"]:
248
self.filename("stops", "_stops.add.xml")
249
netconvertOptions += ",--ptstop-output,%s" % self.files["stops"]
250
netconvertOptions += ",--ptline-clean-up"
251
self.filename("ptlines", "_ptlines.xml")
252
self.filename("ptroutes", "_pt.rou.xml")
253
netconvertOptions += ",--ptline-output,%s" % self.files["ptlines"]
254
self.additionalFiles.append(self.files["stops"])
255
netconvertOptions += ",--railway.topology.repair"
256
typefiles.append(typemaps["aerialway"])
257
if self.data["leftHand"]:
258
netconvertOptions += ",--lefthand"
259
if self.data.get("verbose"):
260
netconvertOptions += ",--verbose"
261
if self.data["decal"]:
262
# change projection to web-mercator to match the background image projection
263
netconvertOptions += ",--proj,+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs" # noqa
264
265
if self.data["carOnlyNetwork"]:
266
if self.data["publicTransport"]:
267
options += ["--vehicle-classes", "publicTransport"]
268
else:
269
options += ["--vehicle-classes", "passenger"]
270
271
options += ["--netconvert-typemap", ','.join(typefiles)]
272
options += ["--netconvert-options", netconvertOptions]
273
options += ["--polyconvert-options", " -v,--osm.keep-full-type,--osm.merge-relations,1"]
274
275
self.report("Converting map data")
276
osmBuild.build(options)
277
ptOptions = None
278
hasPTFlows = False
279
begin = self.data.get("begin", 0)
280
if self.data["publicTransport"]:
281
self.report("Generating public transport schedule")
282
self.filename("pt_stopinfos", "stopinfos.xml", False)
283
self.filename("pt_vehroutes", "vehroutes.xml", False)
284
self.filename("pt_trips", "trips.trips.xml", False)
285
ptOptions = [
286
"-n", self.files["net"],
287
"-b", begin,
288
"-e", begin + self.data["duration"],
289
"-p", "600",
290
"--random-begin",
291
"--seed", "42",
292
"--ptstops", self.files["stops"],
293
"--ptlines", self.files["ptlines"],
294
"-o", self.files["ptroutes"],
295
"--ignore-errors",
296
# "--no-vtypes",
297
"--vtype-prefix", "pt_",
298
"--stopinfos-file", self.files["pt_stopinfos"],
299
"--routes-file", self.files["pt_vehroutes"],
300
"--trips-file", self.files["pt_trips"],
301
"--min-stops", "0",
302
"--extend-to-fringe",
303
"--verbose",
304
]
305
# If ptModes is present and none are selected, do NOT call ptlines2flows
306
if self.data.get("ptModes") is not None:
307
types = [t for t, v in self.data.get("ptModes").items() if v]
308
if types:
309
ptOptions += ["--types", ','.join(types)]
310
ptlines2flows.main(ptlines2flows.get_options(ptOptions))
311
hasPTFlows = True
312
313
if self.data["decal"]:
314
self.report("Downloading background images")
315
tileOptions = [
316
"-n", self.files["net"],
317
"-t", "100",
318
"-d", "background_images",
319
"-l", "-300",
320
"-a", "Mozilla/5.0 (X11; Linux x86_64) osmWebWizard.py/1.0 (+https://github.com/eclipse-sumo/sumo)",
321
]
322
try:
323
os.chdir(self.tmp)
324
os.mkdir("background_images")
325
tileGet.get(tileOptions)
326
self.report("Success.")
327
self.decalError = False
328
except Exception as e:
329
os.chdir(self.tmp)
330
shutil.rmtree("background_images", ignore_errors=True)
331
self.report("Error while downloading background images: %s" % e)
332
self.decalError = True
333
334
if self.data["vehicles"] or hasPTFlows:
335
# routenames stores all routefiles and will join the items later, will
336
# be used by sumo-gui
337
randomTripsCalls = []
338
339
self.edges = sumolib.net.readNet(os.path.join(self.tmp, self.files["net"])).getEdges()
340
341
seed = 42
342
for vehicle in sorted(self.data["vehicles"].keys()):
343
options = self.data["vehicles"][vehicle]
344
self.report("Processing %s" % vehicleNames[vehicle])
345
346
self.filename("route", ".%s.rou.xml" % vehicle)
347
self.filename("trips", ".%s.trips.xml" % vehicle)
348
349
try:
350
options = self.parseTripOpts(vehicle, options, self.data["publicTransport"])
351
except ZeroDivisionError:
352
continue
353
354
if vehicle == "pedestrian" and self.data["publicTransport"]:
355
addFiles = [self.files["stops"]]
356
if hasPTFlows:
357
addFiles.append(self.files["ptroutes"])
358
options += ["--additional-files", ",".join(addFiles)]
359
options += ["--persontrip.walk-opposite-factor", "0.8"]
360
options += ["--duarouter-weights.tls-penalty", "20"]
361
362
options += ["--seed", str(seed)]
363
seed += 1
364
365
try:
366
randomTrips.main(randomTrips.get_options(options))
367
except ValueError:
368
print("Could not generate %s traffic" % vehicle, file=sys.stderr)
369
continue
370
371
randomTripsCalls.append(options)
372
373
# --validate is not called for pedestrians
374
if vehicle == "pedestrian":
375
self.routenames.append(self.files["route"])
376
else:
377
self.routenames.append(self.files["trips"])
378
# clean up unused route file (was only used for validation)
379
os.remove(self.files["route"])
380
381
# add generated PT flows to route list only if at least one pt mode was selected
382
if hasPTFlows:
383
self.routenames.append(self.files["ptroutes"])
384
385
# create a batch file for reproducing calls to randomTrips.py
386
if os.name == "posix":
387
SUMO_HOME_VAR = "$SUMO_HOME"
388
else:
389
SUMO_HOME_VAR = "%SUMO_HOME%"
390
391
randomTripsPath = os.path.join(SUMO_HOME_VAR, "tools", "randomTrips.py")
392
ptlines2flowsPath = os.path.join(SUMO_HOME_VAR, "tools", "ptlines2flows.py")
393
394
self.filename("build.bat", "build.bat", False)
395
batchFile = self.files["build.bat"]
396
with open(batchFile, 'w') as f:
397
if os.name == "posix":
398
f.write("#!/bin/bash\n")
399
if ptOptions is not None:
400
f.write('python "%s" %s\n' %
401
(ptlines2flowsPath, " ".join(map(quoted_str, self.getRelative(ptOptions)))))
402
for opts in randomTripsCalls:
403
f.write('python "%s" %s\n' %
404
(randomTripsPath, " ".join(map(quoted_str, self.getRelative(opts)))))
405
os.chmod(batchFile, BATCH_MODE)
406
407
def parseTripOpts(self, vehicle, options, publicTransport):
408
"Return an option list for randomTrips.py for a given vehicle"
409
410
begin = self.data.get("begin", 0)
411
opts = ["-n", self.files["net"], "--fringe-factor", options.get("fringeFactor", "1"),
412
"--insertion-density", options["count"],
413
"-o", self.files["trips"],
414
"-r", self.files["route"],
415
"-b", begin,
416
"-e", begin + self.data["duration"]]
417
if vehicle == "pedestrian" and publicTransport:
418
opts += vehicleParameters["persontrips"]
419
else:
420
opts += vehicleParameters[vehicle]
421
422
return opts
423
424
def makeConfigFile(self):
425
"Save the configuration for SUMO in a file"
426
427
self.report("Generating configuration file")
428
429
self.filename("guisettings", ".view.xml")
430
with open(self.files["guisettings"], 'w') as f:
431
if self.data["decal"] and not self.decalError:
432
f.write("""
433
<viewsettings>
434
<scheme name="real world"/>
435
<delay value="20"/>
436
<include href="background_images/settings.xml"/>
437
</viewsettings>
438
""")
439
else:
440
f.write("""
441
<viewsettings>
442
<scheme name="real world"/>
443
<delay value="20"/>
444
</viewsettings>
445
""")
446
sumo = sumolib.checkBinary("sumo")
447
448
self.filename("config", ".sumocfg")
449
opts = [sumo, "-n", self.files_relative["net"], "--gui-settings-file", self.files_relative["guisettings"],
450
"--duration-log.statistics",
451
"--device.rerouting.adaptation-interval", "10",
452
"--device.rerouting.adaptation-steps", "18",
453
"--tls.actuated.jam-threshold", "30",
454
"-v", "--no-step-log", "--save-configuration", self.files_relative["config"], "--ignore-route-errors"]
455
456
if self.routenames:
457
opts += ["-r", ",".join(self.getRelative(self.routenames))]
458
459
# extra output if the scenario contains traffic
460
opts += ["--tripinfo-output", "tripinfos.xml"]
461
opts += ["--statistic-output", "stats.xml"]
462
463
self.filename("outadd", "output.add.xml", False)
464
with open(self.files["outadd"], 'w') as fadd:
465
sumolib.writeXMLHeader(fadd, "$Id$", "additional")
466
fadd.write(' <edgeData id="wizard_example" period="3600" file="edgeData.xml"/>\n')
467
fadd.write("</additional>\n")
468
self.additionalFiles.append(self.files["outadd"])
469
470
if self.data["publicTransport"]:
471
opts += ["--stop-output", "stopinfos.xml"]
472
473
if len(self.additionalFiles) > 0:
474
opts += ["-a", ",".join(self.getRelative(self.additionalFiles))]
475
476
subprocess.call(opts, cwd=self.tmp)
477
478
def createBatch(self):
479
"Create a batch / bash file "
480
481
# use bat as extension, as only Windows needs the extension .bat
482
self.filename("run.bat", "run.bat", False)
483
484
with open(self.files["run.bat"], "w") as batchfile:
485
batchfile.write("sumo-gui -c " + self.files_relative["config"])
486
487
os.chmod(self.files["run.bat"], BATCH_MODE)
488
489
def openSUMO(self):
490
self.report("Calling SUMO")
491
492
sumogui = sumolib.checkBinary("sumo-gui")
493
494
subprocess.Popen([sumogui, "-c", self.files["config"]], cwd=self.tmp)
495
496
def createZip(self):
497
"Create a zip file with everything inside which SUMO GUI needs, returns it base64 encoded"
498
499
self.report("Building zip file")
500
501
self.filename("zip", ".zip")
502
503
with ZipFile(self.files["zip"], "w") as zipfile:
504
files = ["net", "guisettings", "config", "run.bat"]
505
506
if self.data["vehicles"] or self.data["publicTransport"]:
507
files += ["build.bat"]
508
509
if self.data["poly"]:
510
files += ["poly"]
511
512
# translate the pseudo file names to real file names
513
files = list(map(lambda name: self.files[name], files))
514
515
if self.data["vehicles"]:
516
files += self.routenames
517
518
# add the files to the zip
519
for name in files:
520
zipfile.write(name)
521
522
# now open the zip file as raw
523
with open(self.files["zip"], "rb") as zipfile:
524
content = zipfile.read()
525
526
return base64.b64encode(content)
527
528
def finalize(self):
529
try:
530
shutil.rmtree(self.tmp)
531
except Exception:
532
pass
533
534
535
class OSMImporterWebSocket(WebSocket):
536
537
local = False
538
outputDir = None
539
540
def report(self, message):
541
print(message)
542
self.sendMessage(u"report " + message)
543
# number of remaining steps
544
self.steps -= 1
545
546
def handleMessage(self):
547
data = json.loads(self.data)
548
549
thread = threading.Thread(target=self.build, args=(data,))
550
thread.start()
551
552
def build(self, data):
553
if self.outputDir is not None:
554
data['outputDir'] = self.outputDir
555
builder = Builder(data, self.local)
556
builder.report = self.report
557
558
self.steps = len(data["vehicles"]) + 4
559
self.sendMessage(u"steps %s" % self.steps)
560
561
try:
562
builder.build()
563
builder.makeConfigFile()
564
builder.createBatch()
565
566
if self.local:
567
builder.openSUMO()
568
else:
569
data = builder.createZip()
570
builder.finalize()
571
572
self.sendMessage(b"zip " + data)
573
except ssl.CertificateError:
574
self.report("Error with SSL certificate, try 'pip install -U certifi'.")
575
except Exception as e:
576
print(traceback.format_exc())
577
# reset 'Generate Scenario' button
578
while self.steps > 0:
579
self.report(str(e) + ", recovering.")
580
if os.path.isdir(builder.tmp) and not os.listdir(builder.tmp):
581
os.rmdir(builder.tmp)
582
os.chdir(builder.origDir)
583
584
585
class OSMImporterHTTPHandler(SimpleHTTPRequestHandler):
586
587
web_root = os.path.join(os.path.dirname(os.path.realpath(__file__)), "webWizard")
588
589
def __init__(self, *args, **kwargs):
590
super().__init__(*args, directory=self.web_root, **kwargs)
591
592
def report(self, message):
593
print(message)
594
self.server.messages.append(u"report " + message)
595
self.server.last_seen = time.time()
596
self.steps -= 1
597
598
def build(self, data):
599
if self.server.output is not None:
600
data['outputDir'] = self.server.output
601
builder = Builder(data, self.server.local)
602
builder.report = self.report
603
604
self.steps = len(data["vehicles"]) + 4
605
self.server.messages.append(u"steps %s" % self.steps)
606
607
try:
608
builder.build()
609
builder.makeConfigFile()
610
builder.createBatch()
611
612
if self.server.local:
613
builder.openSUMO()
614
else:
615
self.server.messages.append(b"zip " + builder.createZip())
616
builder.finalize()
617
except ssl.CertificateError:
618
self.report("Error with SSL certificate, try 'pip install -U certifi'.")
619
except Exception as e:
620
print(traceback.format_exc())
621
# reset 'Generate Scenario' button
622
while self.steps > 0:
623
self.report(str(e) + ", recovering.")
624
if os.path.isdir(builder.tmp) and not os.listdir(builder.tmp):
625
os.rmdir(builder.tmp)
626
os.chdir(builder.origDir)
627
628
def log_message(self, format, *args):
629
pass # disable default logging
630
631
def do_POST(self):
632
if self.path == "/build":
633
length = int(self.headers.get("Content-Length", 0))
634
data = self.rfile.read(length)
635
threading.Thread(target=self.build, args=(json.loads(data),)).start()
636
self.send_response(200)
637
self.send_header("Content-Type", "application/json")
638
self.end_headers()
639
self.wfile.write(json.dumps({"status": "started"}).encode())
640
else:
641
self.send_error(404)
642
643
def do_GET(self):
644
if self.path == "/progress":
645
self.send_response(200)
646
self.send_header("Content-Type", "application/json")
647
self.end_headers()
648
self.wfile.write(json.dumps({"data": self.server.messages[0] if self.server.messages else ""}).encode())
649
del self.server.messages[:1]
650
else:
651
super().do_GET()
652
653
654
def watchdog(server, timeout=3600):
655
while True:
656
time.sleep(60)
657
if time.time() - server.last_seen > timeout:
658
print("No request, shutting down server.")
659
server.shutdown()
660
break
661
662
663
def get_options(args=None):
664
parser = sumolib.options.ArgumentParser(description="OSM Web Wizard for SUMO")
665
parser.add_argument("--web-socket", action="store_true", default=False,
666
help="Create a (legacy) websocket instead of running a http server.")
667
parser.add_argument("--remote", action="store_true", default=False,
668
help="In remote mode, SUMO GUI will not be automatically opened instead a zip file " +
669
"will be generated.")
670
parser.add_argument("--osm-file", default="osm_bbox.osm.xml", dest="osmFile", help="use input file from path.")
671
parser.add_argument("--test-output", dest="testOutputDir",
672
help="Run with pre-defined options on file 'osm_bbox.osm.xml' and " +
673
"write output to the given directory.")
674
parser.add_argument("--bbox", help="bounding box to retrieve in geo coordinates west,south,east,north.")
675
parser.add_argument("-o", "--output", dest="outputDir",
676
help="Write output to the given folder rather than creating a name based on the timestamp")
677
parser.add_argument("--address", default="", help="Address for the web socket or server.")
678
parser.add_argument("--port", type=int,
679
help="Port for the web socket or server. By default a random port is chosen.")
680
parser.add_argument("-v", "--verbose", action="store_true", default=False, help="tell me what you are doing")
681
parser.add_argument("-b", "--begin", default=0, type=sumolib.miscutils.parseTime,
682
help="Defines the begin time for the scenario.")
683
parser.add_argument("-e", "--end", default=900, type=sumolib.miscutils.parseTime,
684
help="Defines the end time for the scenario.")
685
parser.add_argument("-n", "--netconvert-options", help="additional comma-separated options for netconvert")
686
parser.add_argument("--demand", default="passenger:6f5,bicycle:2f2,pedestrian:4,ship:1f40",
687
help="Traffic demand definition for non-interactive mode.")
688
return parser.parse_args(args)
689
690
691
def main(options):
692
if options.port is None:
693
options.port = DEFAULT_PORT if options.remote else 0
694
if options.testOutputDir is not None:
695
demand = collections.defaultdict(dict)
696
for mode in options.demand.split(","):
697
k, v = mode.split(":")
698
if "f" in v:
699
demand[k]['count'], demand[k]['fringeFactor'] = v.split("f")
700
else:
701
demand[k]['count'] = v
702
data = {u'begin': options.begin,
703
u'duration': options.end - options.begin,
704
u'vehicles': demand,
705
u'osm': os.path.abspath(options.osmFile),
706
u'poly': options.bbox is None, # reduce download size
707
u'publicTransport': True,
708
u'ptModes': {'bus': True,
709
'tram': True,
710
'train': True,
711
'subway': True,
712
'light_rail': True,
713
'monorail': True,
714
'trolleybus': True,
715
'minibus': True,
716
'share_taxi': True,
717
'aerialway': True,
718
'ferry': True},
719
u'leftHand': False,
720
u'decal': False,
721
u'verbose': options.verbose,
722
u'carOnlyNetwork': False,
723
u'outputDir': options.testOutputDir,
724
u'coords': options.bbox.split(",") if options.bbox else None,
725
u'options': options.netconvert_options
726
}
727
builder = Builder(data, True)
728
builder.build()
729
builder.makeConfigFile()
730
builder.createBatch()
731
if not options.remote:
732
subprocess.call([sumolib.checkBinary("sumo"), "-c", builder.files["config"]])
733
elif options.web_socket:
734
# everything concerning web sockets is considered legacy
735
OSMImporterWebSocket.local = options.testOutputDir is not None or not options.remote
736
OSMImporterWebSocket.outputDir = options.outputDir
737
port = DEFAULT_PORT
738
if os.name != "nt":
739
port = options.port if options.port else sumolib.miscutils.getFreeSocketPort()
740
server = SimpleWebSocketServer(options.address, port, OSMImporterWebSocket)
741
if not options.remote:
742
path = os.path.dirname(os.path.realpath(__file__))
743
# on Linux Firefox refuses to open files in /usr/ #16086
744
if os.name != "nt" and not path.startswith(os.path.expanduser('~')):
745
new_path = os.path.expanduser('~/Sumo')
746
wizard_path = os.path.join(new_path, 'webWizard')
747
if not os.path.exists(wizard_path):
748
os.makedirs(new_path, exist_ok=True)
749
shutil.copytree(os.path.join(path, "webWizard"), wizard_path, dirs_exist_ok=True)
750
path = new_path
751
os.chdir(path)
752
url = "file://" + os.path.join(path, "webWizard", "index.html")
753
if os.name != "nt":
754
# on Windows the webbrowser module uses os.startfile which cannot handle parameters
755
url += "?port=%s" % port
756
webbrowser.open(url)
757
server.serveforever()
758
else:
759
port = options.port if options.port else sumolib.miscutils.getFreeSocketPort()
760
httpd = HTTPServer((options.address, port), OSMImporterHTTPHandler)
761
httpd.local = options.testOutputDir is not None or not options.remote
762
httpd.output = options.outputDir
763
httpd.messages = []
764
httpd.last_seen = time.time()
765
if options.remote:
766
print("Web server listening on http://%s:%s/ in remote mode." % (httpd.server_name, port))
767
else:
768
webbrowser.open("http://127.0.0.1:%s/" % port)
769
threading.Thread(target=watchdog, args=(httpd,), daemon=True).start()
770
httpd.serve_forever()
771
772
773
if __name__ == "__main__":
774
main(get_options())
775
776