Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/detector/mapDetectors.py
169674 views
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
4
# Copyright (C) 2013-2025 German Aerospace Center (DLR) and others.
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 mapDetectors.py
16
# @author Jakob Erdmann
17
# @author Mirko Barthauer
18
# @author Davide Guastella
19
# @date 2025-05-31
20
21
"""
22
Create detector definitions by map-matching coordinates to a .net.xml file
23
"""
24
25
from __future__ import print_function
26
from __future__ import absolute_import
27
import os
28
import sys
29
import csv
30
31
SUMO_HOME = os.environ.get('SUMO_HOME',
32
os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..'))
33
sys.path.append(os.path.join(SUMO_HOME, 'tools'))
34
import sumolib # noqa
35
36
37
def get_options(args=None):
38
optParser = sumolib.options.ArgumentParser(
39
description="Map detector locations to a network and write inductionLoop-definitions")
40
optParser.add_argument("-n", "--net-file", dest="netfile", category="input", type=optParser.net_file,
41
help="define the net file (mandatory)")
42
optParser.add_argument("-d", "--detector-file", dest="detfile", category="input", type=optParser.file,
43
help="csv input file with detector ids and coordinates")
44
optParser.add_argument("--delimiter", default=";",
45
help="the field separator of the detector input file")
46
optParser.add_argument("-o", "--output-file", dest="outfile", category="output", type=optParser.file,
47
help="define the output file for generated mapped detectors")
48
optParser.add_argument("-i", "--id-column", default="id", dest="id",
49
help="Read detector ids from the given column")
50
optParser.add_argument("-x", "--longitude-column", default="lon", dest="lon",
51
help="Read detector x-coordinate (lon) from the given column")
52
optParser.add_argument("-y", "--latitude-column", default="lat", dest="lat",
53
help="Read detector y-coordinate (lat) from the given column")
54
optParser.add_argument("--max-radius", type=float, default="100", dest="maxRadius",
55
help="specify maximum distance error when mapping coordinates")
56
optParser.add_argument("--vclass", default="passenger",
57
help="only consider edges that permit the given vClass")
58
optParser.add_argument("--det-output-file", dest="detOut", default="detector.out.xml", category="output",
59
type=optParser.file, help="Define the output file that generated detectors shall write to")
60
optParser.add_argument("--interval", default=3600, type=optParser.time,
61
help="Define the aggregation interval of generated detectors")
62
optParser.add_argument("--write-params", action="store_true", dest="writeParams", default=False,
63
help="Write additional columns as detector parameters")
64
optParser.add_argument('--all-lanes', dest='allLanes', action='store_true',
65
help='If set, an induction loop is placed on each lane of the target edges.')
66
optParser.add_argument("-v", "--verbose", action="store_true", default=False,
67
help="tell me what you are doing")
68
options = optParser.parse_args(args=args)
69
if not options.netfile or not options.detfile or not options.outfile:
70
optParser.print_help()
71
sys.exit(1)
72
73
return options
74
75
76
def main():
77
options = get_options()
78
net = sumolib.net.readNet(options.netfile)
79
seenIDs = set()
80
with sumolib.openz(options.outfile, 'w') as outf:
81
sumolib.writeXMLHeader(outf, root="additional", options=options)
82
inputf = sumolib.openz(options.detfile)
83
reader = csv.DictReader(inputf, delimiter=options.delimiter)
84
checkedFields = False
85
extraCols = []
86
for row in reader:
87
if not checkedFields:
88
checkedFields = True
89
if options.writeParams:
90
extraCols = list(reader.fieldnames)
91
for attr in ["id", "lon", "lat"]:
92
colName = getattr(options, attr)
93
if colName not in row:
94
sys.exit("Required column %s not found. Available columns are %s" % (
95
colName, ",".join(row.keys())))
96
elif extraCols:
97
extraCols.remove(colName)
98
99
detID = row[options.id]
100
lon = float(row[options.lon])
101
lat = float(row[options.lat])
102
x, y = net.convertLonLat2XY(lon, lat)
103
104
lanes = []
105
radius = 0.1
106
while not lanes and radius <= options.maxRadius:
107
lanes = net.getNeighboringLanes(x, y, radius, True)
108
lanes = [(d, lane) for lane, d in lanes if lane.allows(options.vclass)]
109
radius *= 10
110
if not lanes:
111
sys.stderr.write("Could not find road for detector %s within %sm radius\n" % (
112
detID, options.maxRadius))
113
continue
114
lanes.sort(key=lambda x: x[0])
115
best = lanes[0][1]
116
pos = min(best.getLength(),
117
sumolib.geomhelper.polygonOffsetWithMinimumDistanceToPoint((x, y), best.getShape()))
118
119
commentStart, commentEnd = "", ""
120
if detID in seenIDs:
121
commentStart = "!--"
122
commentEnd = "--"
123
endTag = "/" + commentEnd
124
if extraCols:
125
endTag = ""
126
127
usedLanes = [(best, '')]
128
if options.allLanes:
129
usedLanes = [(lane, '_%i' % index) for index, lane in enumerate(best.getEdge().getLanes())]
130
131
for lane, suffix in usedLanes:
132
outf.write(' ' * 4 + '<%sinductionLoop id="%s%s" lane="%s" pos="%.2f" file="%s" freq="%s"%s>\n' % (
133
commentStart,
134
detID, suffix,
135
lane.getID(), pos, options.detOut,
136
options.interval,
137
endTag))
138
if extraCols:
139
for col in extraCols:
140
outf.write(' ' * 8 + '<param key="%s" value="%s"/>\n' % (
141
sumolib.xml.xmlescape(col),
142
sumolib.xml.xmlescape(row[col])))
143
outf.write(' ' * 4 + '</inductionLoop%s>\n' % commentEnd)
144
seenIDs.add(detID)
145
146
outf.write('</additional>\n')
147
inputf.close()
148
149
150
if __name__ == "__main__":
151
main()
152
153