CARLA
Loading...
Searching...
No Matches
Signals.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma
2// de Barcelona (UAB).
3//
4// This work is licensed under the terms of the MIT license.
5// For a copy, see <https://opensource.org/licenses/MIT>.
6
7#include "Signals.h"
8
9#include <algorithm>
10#include <cmath>
11#include <limits>
12#include <map>
13#include <set>
14#include <sstream>
15
16#include "Profiles.h"
17
18namespace twinmodel {
19
20 namespace {
21
22 std::string MakeId(const char *prefix, std::size_t index) {
23 std::ostringstream out;
24 out << prefix << index;
25 return out.str();
26 }
27
28
29 /// Rank of a highway class; the junction's major road is the highest rank
30 /// among its approaches and gets the priority plate, the rest give way.
31 int ClassRank(const std::string &highway) {
32 if (highway == "motorway" || highway == "motorway_link") return 7;
33 if (highway == "trunk" || highway == "trunk_link") return 6;
34 if (highway == "primary" || highway == "primary_link") return 5;
35 if (highway == "secondary" || highway == "secondary_link") return 4;
36 if (highway == "tertiary" || highway == "tertiary_link") return 3;
37 if (highway == "unclassified" || highway == "residential") return 2;
38 if (highway == "living_street") return 1;
39 return 0;
40 }
41
42 /// The road whose reference line passes closest to @a point, with the s of
43 /// the projection. Connecting roads are skipped: a sign governs approach.
44 const Road *NearestRoad(const TwinModelData &model, const geom::Point &point,
45 double limit, double &out_s, double &out_t) {
46 const Road *best = nullptr;
47 double best_distance = limit;
48 for (const Road &road : model.roads) {
49 if (road.junction_id.has_value() || road.reference_line.size() < 2u) {
50 continue;
51 }
52 const geom::LineString flat = geom::To2D(road.reference_line);
53 const double s = geom::Project(flat, point);
54 const geom::Point on = geom::Interpolate(flat, s);
55 const double distance = geom::Hypot(on, point);
56 if (distance < best_distance) {
57 best_distance = distance;
58 best = &road;
59 out_s = s;
60 // signed offset: positive to the left of the reference line
61 const double heading = geom::HeadingAt(flat, s);
62 const double dx = geom::bg::get<0>(point) - geom::bg::get<0>(on);
63 const double dy = geom::bg::get<1>(point) - geom::bg::get<1>(on);
64 out_t = -std::sin(heading) * dx + std::cos(heading) * dy;
65 }
66 }
67 return best;
68 }
69
70 void AddSignal(TwinModelData &model, SignalKind kind, const Road &road,
71 double s, double t, const geom::Point &position, double heading,
72 std::optional<double> value, long long node_id) {
73 Signal signal;
74 signal.id = MakeId("signal_", model.signals.size());
75 signal.kind = kind;
76 signal.road_id = road.id;
77 signal.s = s;
78 signal.t = t;
79 signal.position = position;
80 signal.heading = heading;
81 signal.value = value;
82 signal.osm_node_id = node_id;
83 signal.orientation = (t < 0.0) ? "-" : "+";
84 model.signals.push_back(std::move(signal));
85 }
86
87 } // namespace
88
89 void BuildSignals(const OsmData &osm, const LocalFrame &frame,
90 TwinModelData &model, const SignalOptions &options) {
91
92 auto to_local = [&](const OsmNode &node) {
93 double x = 0.0;
94 double y = 0.0;
95 frame.ToLocal(node.lon, node.lat, x, y);
96 return geom::Point(x, y);
97 };
98
99 // 1. node-tagged signs and lights
100 for (const auto &entry : osm.nodes) {
101 const OsmNode &node = entry.second;
102 const std::string *highway = FindTag(node.tags, "highway");
103 if (highway == nullptr) {
104 continue;
105 }
107 bool keep = false;
108 if (*highway == "traffic_signals") {
111 keep = true;
112 }
113 } else if (*highway == "stop") {
114 kind = SignalKind::Stop;
115 keep = true;
116 } else if (*highway == "give_way") {
117 kind = SignalKind::Yield;
118 keep = true;
119 } else if (*highway == "crossing") {
121 keep = true;
122 }
123 if (!keep) {
124 continue;
125 }
126 const geom::Point point = to_local(node);
127 double s = 0.0;
128 double t = 0.0;
129 const Road *road = NearestRoad(model, point, options.snap_m, s, t);
130 if (road == nullptr) {
131 continue;
132 }
133 const geom::LineString flat = geom::To2D(road->reference_line);
134 AddSignal(model, kind, *road, s, t, point, geom::HeadingAt(flat, s),
135 std::nullopt, node.id);
136 }
137
138 // 2. a speed limit sign at the start of every way that carries maxspeed
139 std::set<std::string> signed_roads;
140 for (const Road &road : model.roads) {
141 if (road.junction_id.has_value() || road.lanes.empty()) {
142 continue;
143 }
144 const std::string *maxspeed = FindTag(road.tags, "maxspeed");
145 if (maxspeed == nullptr) {
146 continue;
147 }
148 const std::optional<double> speed = ParseMaxSpeed(*maxspeed);
149 if (!speed.has_value() || signed_roads.count(road.id) != 0u) {
150 continue;
151 }
152 signed_roads.insert(road.id);
153 const geom::LineString flat = geom::To2D(road.reference_line);
154 const geom::Point at = geom::Interpolate(flat, 0.0);
155 AddSignal(model, SignalKind::SpeedLimit, road, 0.0, -1.0, at,
156 geom::HeadingAt(flat, 0.0), speed, 0);
157 }
158
159 // 3. a light on every junction approach when the caller asked for them on
160 // all junctions, with one controller per junction holding its signals
161 if (options.all_junctions_traffic_lights) {
162 for (const Junction &junction : model.junctions) {
163 std::set<std::string> approaches;
164 for (const Connection &connection : junction.connections) {
165 approaches.insert(connection.incoming_road);
166 }
167 for (const std::string &road_id : approaches) {
168 const Road *road = model.FindRoad(road_id);
169 if (road == nullptr || road->reference_line.size() < 2u) {
170 continue;
171 }
172 const geom::LineString flat = geom::To2D(road->reference_line);
173 const double total = geom::Length(flat);
174 const geom::Point at = geom::Interpolate(flat, total);
175 AddSignal(model, SignalKind::TrafficLight, *road, total, -1.0, at,
176 geom::HeadingAt(flat, total), std::nullopt, 0);
177 }
178 }
179 }
180
181 // 4. sign the unsignalised junctions: the highest highway class through
182 // the junction keeps priority, every lower approach gives way
183 {
184 std::set<std::string> signalised;
185 for (const Signal &signal : model.signals) {
186 if (signal.kind == SignalKind::TrafficLight ||
187 signal.kind == SignalKind::TrafficLightPedestrian) {
188 signalised.insert(signal.road_id);
189 }
190 }
191 for (const Junction &junction : model.junctions) {
192 std::set<std::string> approaches;
193 for (const Connection &connection : junction.connections) {
194 approaches.insert(connection.incoming_road);
195 }
196 if (approaches.size() < 2u) {
197 continue;
198 }
199 int best = -1;
200 bool any_signalised = false;
201 for (const std::string &road_id : approaches) {
202 const Road *road = model.FindRoad(road_id);
203 if (road == nullptr) {
204 continue;
205 }
206 best = std::max(best, ClassRank(road->highway));
207 any_signalised = any_signalised || (signalised.count(road_id) != 0u);
208 }
209 if (any_signalised || best < 0) {
210 continue;
211 }
212 bool mixed = false;
213 for (const std::string &road_id : approaches) {
214 const Road *road = model.FindRoad(road_id);
215 if (road != nullptr && ClassRank(road->highway) != best) {
216 mixed = true;
217 }
218 }
219 for (const std::string &road_id : approaches) {
220 const Road *road = model.FindRoad(road_id);
221 if (road == nullptr || road->reference_line.size() < 2u) {
222 continue;
223 }
224 // Where every approach is the same class there is no major road to
225 // plate, so the junction is uncontrolled and each approach gives way.
226 const bool major = mixed && ClassRank(road->highway) == best;
227 const geom::LineString flat = geom::To2D(road->reference_line);
228 const double total = geom::Length(flat);
229 const double at = std::max(0.0, total - 1.0);
230 AddSignal(model, major ? SignalKind::PriorityRoad : SignalKind::Yield,
231 *road, at, -1.0, geom::Interpolate(flat, at),
232 geom::HeadingAt(flat, at), std::nullopt, 0);
233 }
234 }
235 }
236
237 // 5. one controller per junction over the lights on its approaches
238 std::map<std::string, std::vector<std::string>> by_junction;
239 for (const Signal &signal : model.signals) {
240 if (signal.kind != SignalKind::TrafficLight &&
241 signal.kind != SignalKind::TrafficLightPedestrian) {
242 continue;
243 }
244 for (const Junction &junction : model.junctions) {
245 bool touches = false;
246 for (const Connection &connection : junction.connections) {
247 if (connection.incoming_road == signal.road_id) {
248 touches = true;
249 break;
250 }
251 }
252 if (touches) {
253 by_junction[junction.id].push_back(signal.id);
254 break;
255 }
256 }
257 }
258 for (const auto &entry : by_junction) {
259 Controller controller;
260 controller.id = MakeId("controller_", model.controllers.size());
261 controller.junction_id = entry.first;
262 controller.signal_ids = entry.second;
263 controller.sequence = 0;
264 for (Signal &signal : model.signals) {
265 if (std::find(entry.second.begin(), entry.second.end(), signal.id) !=
266 entry.second.end()) {
267 signal.controller_id = controller.id;
268 }
269 }
270 model.controllers.push_back(std::move(controller));
271 }
272 }
273
274} // namespace twinmodel
float distance
float y
float x
std::string highway
Definition LaneGraph.cpp:24
long long node
Definition LaneGraph.cpp:43
geom::Point point
Definition LaneGraph.cpp:41
double heading
Definition LaneGraph.cpp:42
std::string road_id
Definition LaneGraph.cpp:39
std::map< std::string, unsigned int > road
std::map< std::string, unsigned int > junction
WGS84 <-> local ENU metres, a transverse Mercator centred on the origin.
Definition Frame.h:15
void ToLocal(double lon, double lat, double &x, double &y) const
Definition Frame.cpp:34
double Length(const LineString &line)
Point Interpolate(const LineString &line, double s)
Point at distance s along line, shapely's LineString.interpolate.
double HeadingAt(const LineString &line, double s)
Heading of line at distance s, in radians.
bg::model::linestring< Point > LineString
double Hypot(const Point &a, const Point &b)
bg::model::d2::point_xy< double > Point
Point To2D(const Point3 &p)
double Project(const LineString &line, const Point &p)
Distance along line of the projection of p, shapely's LineString.project.
SignalKind
OpenDRIVE type/subtype follow road/SignalType.h; the crosswalk is not a signal in CARLA but an <objec...
Definition Model.h:46
std::optional< double > ParseMaxSpeed(const std::string &value)
Parse an OSM maxspeed value into m/s; "50", "50 km/h" and "30 mph".
Definition Profiles.cpp:84
const std::string * FindTag(const Tags &tags, const std::string &key)
Definition Model.h:20
void BuildSignals(const OsmData &osm, const LocalFrame &frame, TwinModelData &model, const SignalOptions &options)
Read traffic lights, stop and give-way signs, speed limits and crossings out of the OSM tags and atta...
Definition Signals.cpp:89
std::string id
Definition Model.h:143
std::vector< std::string > signal_ids
Definition Model.h:145
std::string junction_id
Definition Model.h:144
std::map< long long, OsmNode > nodes
Definition OsmIngest.h:51
bool all_junctions_traffic_lights
Definition Signals.h:17
double snap_m
A signal node is bound to the nearest road end within this distance.
Definition Signals.h:20
Everything the OpenDRIVE writer reads.
Definition Model.h:158
std::vector< Signal > signals
Definition Model.h:164
std::vector< Road > roads
Definition Model.h:162
const Road * FindRoad(const std::string &id) const
Definition Model.h:168
std::vector< Controller > controllers
Definition Model.h:165
std::vector< Junction > junctions
Definition Model.h:163