Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/import/dxf/dxf2jupedsim.py
169679 views
1
#!/usr/bin/env python
2
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
# Copyright (C) 2014-2025 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 dxf2jupedsim.py
15
# @author Benjamin Coueraud
16
# @author Michael Behrisch
17
# @date 2023-06-16
18
19
20
import os
21
import sys
22
import warnings
23
import itertools
24
25
import numpy
26
import ezdxf
27
import pyproj
28
from shapely.geometry import Polygon, LineString
29
30
sys.path.append(os.path.join(os.environ["SUMO_HOME"], 'tools'))
31
import sumolib # noqa
32
33
34
def create_test_dxf(args):
35
doc = ezdxf.new(dxfversion='R2000')
36
msp = doc.modelspace()
37
doc.layers.new(name=args.walkable_layer)
38
msp.add_lwpolyline(((0, 0), (10, 0), (10, 10), (0, 10)), dxfattribs={'layer': args.walkable_layer})
39
msp.add_lwpolyline(((100, 100), (110, 100), (110, 110), (100, 110)), dxfattribs={'layer': args.walkable_layer})
40
doc.layers.new(name=args.obstacle_layer)
41
msp.add_lwpolyline(((5, 5), (8, 5), (8, 8), (5, 8)), dxfattribs={'layer': args.obstacle_layer})
42
msp.add_circle((2, 2), 1, dxfattribs={'layer': args.obstacle_layer})
43
doc.saveas(args.file)
44
45
46
def polygon_as_XML_element(polygon, typename, index, color, layer):
47
polygonID = "jps.%s_%s" % (typename[9:], index)
48
49
# Round the coordinates.
50
polygon = [(round(point[0], 9), round(point[1], 9)) for point in polygon]
51
52
# Check for equal consecutive points.
53
cleanPolygon = [polygon[0]]
54
duplicates = []
55
for i in range(1, len(polygon)):
56
if polygon[i][0] != polygon[i-1][0] or polygon[i][1] != polygon[i-1][1]:
57
cleanPolygon.append(polygon[i])
58
else:
59
duplicates.append(polygon[i])
60
if len(cleanPolygon) < len(polygon):
61
duplicates = " ".join(["%.9f,%.9f" % c[:2] for c in duplicates])
62
print("Warning: polygon '%s' had some equal consecutive points removed: %s" % (polygonID, duplicates))
63
64
# Check for simplicity and output hints.
65
if not Polygon(cleanPolygon).is_valid:
66
print("Warning: polygon '%s' is not simple." % polygonID)
67
segments = list(map(LineString, zip(cleanPolygon[:-1], cleanPolygon[1:])))
68
intersect = False
69
for segment1, segment2 in itertools.combinations(segments, 2):
70
if segment1.crosses(segment2):
71
intersect = True
72
print("Hint: segments [(%.9f, %.9f)] and [(%.9f, %.9f)] intersect each other."
73
% (segment1.coords[0][0], segment1.coords[0][1], segment2.coords[1][0], segment2.coords[1][1]))
74
if not intersect:
75
duplicates = {point for point in cleanPolygon[:-1] if cleanPolygon[:-1].count(point) > 1}
76
for point in duplicates:
77
print("Hint: point [(%.9f, %.9f)] appears at least twice." % (point[0], point[1]))
78
79
# Create the XML element.
80
poly = " ".join(["%.9f,%.9f" % c[:2] for c in cleanPolygon])
81
return (' <poly id="%s" type="%s" color="%s" fill="True" layer="%s" shape="%s" geo="True"/>\n' %
82
(polygonID, typename, color, layer, poly))
83
84
85
def generate_circle_vertices(center, radius, nbr_vertices=20):
86
angles = numpy.linspace(0., 2.0*numpy.pi, nbr_vertices)
87
vertices = [(center[0] + radius*numpy.cos(a), center[1] + radius*numpy.sin(a)) for a in angles]
88
return vertices
89
90
91
def apply_inverse_projection(vertices, projection):
92
projection = pyproj.Proj(projection)
93
return [projection(vertex[0], vertex[1], inverse=True) for vertex in vertices]
94
95
96
def main():
97
parser = sumolib.options.ArgumentParser()
98
parser.add_argument('file', help='The DXF file to read from', category="input", type=parser.file)
99
parser.add_argument("-o", "--output", help="Name of the polygon output file", category="output")
100
parser.add_argument("--test", action="store_true", help="Write DXF test file and exit")
101
parser.add_argument("--walkable-layer", default="walkable_areas",
102
help="Name of the DXF layer containing walkable areas")
103
parser.add_argument("--obstacle-layer", default="obstacles",
104
help="Name of the DXF layer containing obstacles")
105
parser.add_argument("--walkable-color", default="179,217,255",
106
help="Color of the polygons defining walkable areas")
107
parser.add_argument("--obstacle-color", default="255,204,204",
108
help="Color of the polygons defining obstacles")
109
parser.add_argument("--sumo-layer", default=0,
110
help="SUMO layer used to render polygons defining walkable areas")
111
parser.add_argument("--projection", default="EPSG:32633",
112
help="EPSG code or projection string used to convert back to geocoordinates")
113
args = parser.parse_args()
114
if args.test:
115
create_test_dxf(args)
116
return
117
118
if args.output is None:
119
args.output = args.file[:-3] + "add.xml"
120
dxf = ezdxf.readfile(args.file)
121
with sumolib.openz(args.output, "w") as add:
122
sumolib.xml.writeHeader(add, root="additional", options=args)
123
for entity in dxf.modelspace().query("LWPOLYLINE CIRCLE"):
124
if entity.dxf.dxftype == "CIRCLE":
125
vertices = generate_circle_vertices(list(entity.dxf.center), entity.dxf.radius)
126
else:
127
vertices = list(entity.vertices())
128
if vertices[-1] != vertices[0]:
129
vertices.append(vertices[0])
130
if args.projection != "none":
131
geoVertices = apply_inverse_projection(vertices, args.projection)
132
else:
133
geoVertices = vertices
134
if entity.dxf.layer == args.walkable_layer:
135
add.write(polygon_as_XML_element(geoVertices, "jupedsim.walkable_area", entity.dxf.handle,
136
args.walkable_color, args.sumo_layer))
137
elif entity.dxf.layer == args.obstacle_layer:
138
add.write(polygon_as_XML_element(geoVertices, "jupedsim.obstacle", entity.dxf.handle,
139
args.obstacle_color, args.sumo_layer+1))
140
else:
141
warnings.warn("Polygon '%s' belonging to unknown layer '%s'." % (entity.dxf.handle, entity.dxf.layer))
142
add.write("</additional>\n")
143
144
145
if __name__ == "__main__":
146
try:
147
main()
148
except IOError as e:
149
print("Input file is not a DXF file or generic I/O error occured: %s" % e, file=sys.stderr)
150
sys.exit(1)
151
except ezdxf.DXFStructureError as e:
152
print("Invalid or corrupted input DXF file: %s" % e, file=sys.stderr)
153
sys.exit(2)
154
155