1 /****************************************************************************/
2 // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.org/sumo
3 // Copyright (C) 2001-2019 German Aerospace Center (DLR) and others.
4 // This program and the accompanying materials
5 // are made available under the terms of the Eclipse Public License v2.0
6 // which accompanies this distribution, and is available at
7 // http://www.eclipse.org/legal/epl-v20.html
8 // SPDX-License-Identifier: EPL-2.0
9 /****************************************************************************/
10 /// @file NBTrafficLightDefinition.cpp
11 /// @author Daniel Krajzewicz
12 /// @author Jakob Erdmann
13 /// @author Michael Behrisch
14 /// @date Sept 2002
15 /// @version $Id$
16 ///
17 // The base class for traffic light logic definitions
18 /****************************************************************************/
19
20
21 // ===========================================================================
22 // included modules
23 // ===========================================================================
24 #include <config.h>
25
26 #include <vector>
27 #include <string>
28 #include <algorithm>
29 #include <cassert>
30 #include <iterator>
31 #include <utils/common/MsgHandler.h>
32 #include <utils/common/ToString.h>
33 #include "NBTrafficLightDefinition.h"
34 #include <utils/options/OptionsCont.h>
35 #include "NBLinkPossibilityMatrix.h"
36 #include "NBTrafficLightLogic.h"
37 #include "NBOwnTLDef.h"
38 #include "NBContHelper.h"
39
40 //#define DEBUG_RIGHT_OF_WAY
41 #define DEBUGCOND true
42
43 // ===========================================================================
44 // static members
45 // ===========================================================================
46 const std::string NBTrafficLightDefinition::DefaultProgramID = "0";
47 const std::string NBTrafficLightDefinition::DummyID = "dummy";
48 const SUMOTime NBTrafficLightDefinition::UNSPECIFIED_DURATION(-1);
49
50 // ===========================================================================
51 // method definitions
52 // ===========================================================================
NBTrafficLightDefinition(const std::string & id,const std::vector<NBNode * > & junctions,const std::string & programID,SUMOTime offset,TrafficLightType type)53 NBTrafficLightDefinition::NBTrafficLightDefinition(const std::string& id,
54 const std::vector<NBNode*>& junctions, const std::string& programID,
55 SUMOTime offset, TrafficLightType type) :
56 Named(id),
57 myControlledNodes(junctions),
58 mySubID(programID), myOffset(offset),
59 myType(type),
60 myNeedsContRelationReady(false),
61 myRightOnRedConflictsReady(false) {
62 std::vector<NBNode*>::iterator i = myControlledNodes.begin();
63 while (i != myControlledNodes.end()) {
64 for (std::vector<NBNode*>::iterator j = i + 1; j != myControlledNodes.end();) {
65 if (*i == *j) {
66 j = myControlledNodes.erase(j);
67 } else {
68 j++;
69 }
70 }
71 i++;
72 }
73 std::sort(myControlledNodes.begin(), myControlledNodes.end(), NBNode::nodes_by_id_sorter());
74 for (std::vector<NBNode*>::const_iterator i = junctions.begin(); i != junctions.end(); i++) {
75 (*i)->addTrafficLight(this);
76 }
77 }
78
79
NBTrafficLightDefinition(const std::string & id,NBNode * junction,const std::string & programID,SUMOTime offset,TrafficLightType type)80 NBTrafficLightDefinition::NBTrafficLightDefinition(const std::string& id,
81 NBNode* junction, const std::string& programID, SUMOTime offset, TrafficLightType type) :
82 Named(id),
83 mySubID(programID),
84 myOffset(offset),
85 myType(type),
86 myNeedsContRelationReady(false),
87 myRightOnRedConflictsReady(false) {
88 addNode(junction);
89 }
90
91
NBTrafficLightDefinition(const std::string & id,const std::string & programID,SUMOTime offset,TrafficLightType type)92 NBTrafficLightDefinition::NBTrafficLightDefinition(const std::string& id, const std::string& programID,
93 SUMOTime offset, TrafficLightType type) :
94 Named(id),
95 mySubID(programID),
96 myOffset(offset),
97 myType(type),
98 myNeedsContRelationReady(false),
99 myRightOnRedConflictsReady(false) {
100 }
101
102
~NBTrafficLightDefinition()103 NBTrafficLightDefinition::~NBTrafficLightDefinition() {}
104
105
106 NBTrafficLightLogic*
compute(OptionsCont & oc)107 NBTrafficLightDefinition::compute(OptionsCont& oc) {
108 // it is not really a traffic light if no incoming edge exists
109 if (amInvalid()) {
110 // make a copy of myControlledNodes because it will be modified;
111 std::vector<NBNode*> nodes = myControlledNodes;
112 for (std::vector<NBNode*>::iterator it = nodes.begin(); it != nodes.end(); it++) {
113 (*it)->removeTrafficLight(this);
114 }
115 WRITE_WARNING("The traffic light '" + getID() + "' does not control any links; it will not be build.");
116 return nullptr;
117 }
118 // compute the time needed to brake
119 int brakingTime = computeBrakingTime(oc.getFloat("tls.yellow.min-decel"));
120 // perform the computation depending on whether the traffic light
121 // definition was loaded or shall be computed new completely
122 if (!oc.isDefault("tls.yellow.time")) {
123 brakingTime = oc.getInt("tls.yellow.time");
124 }
125 NBTrafficLightLogic* ret = myCompute(brakingTime);
126 ret->updateParameter(getParametersMap());
127 return ret;
128 }
129
130
131 bool
amInvalid() const132 NBTrafficLightDefinition::amInvalid() const {
133 return myControlledLinks.size() == 0;
134 }
135
136
137 int
computeBrakingTime(double minDecel) const138 NBTrafficLightDefinition::computeBrakingTime(double minDecel) const {
139 if (myIncomingEdges.size() == 0) {
140 // don't crash
141 return 3;
142 }
143 double vmax = NBContHelper::maxSpeed(myIncomingEdges);
144 if (vmax < 71 / 3.6) {
145 // up to 50kmh: 3 seconds , 60km/h: 4, 70kmh: 5
146 // @note: these are German regulations, other countries may differ
147 return 3 + (int)MAX2(0.0, (floor((vmax - 50 / 3.6) * 0.37)));
148 } else {
149 // above 70km/h we use a function that grows according to the "natural"
150 // formula (vmax / 2 * minDecel) but continues smoothly where the german
151 // rules leave of
152 return (int)(1.8 + vmax / 2 / minDecel);
153 }
154 }
155
156
157 void
setParticipantsInformation()158 NBTrafficLightDefinition::setParticipantsInformation() {
159 // collect the information about participating edges and links
160 collectEdges();
161 collectLinks();
162 }
163
164 std::set<NBEdge*>
collectReachable(EdgeVector outer,const EdgeVector & within,bool checkControlled)165 NBTrafficLightDefinition::collectReachable(EdgeVector outer, const EdgeVector& within, bool checkControlled) {
166 std::set<NBEdge*> reachable;
167 while (outer.size() > 0) {
168 NBEdge* from = outer.back();
169 outer.pop_back();
170 std::vector<NBEdge::Connection>& cons = from->getConnections();
171 for (std::vector<NBEdge::Connection>::iterator k = cons.begin(); k != cons.end(); k++) {
172 NBEdge* to = (*k).toEdge;
173 if (reachable.count(to) == 0 &&
174 (find(within.begin(), within.end(), to) != within.end()) &&
175 (!checkControlled || from->mayBeTLSControlled((*k).fromLane, to, (*k).toLane))) {
176 reachable.insert(to);
177 outer.push_back(to);
178 }
179 }
180 }
181 return reachable;
182 }
183
184
185 void
collectEdges()186 NBTrafficLightDefinition::collectEdges() {
187 myIncomingEdges.clear();
188 myEdgesWithin.clear();
189 EdgeVector myOutgoing;
190 // collect the edges from the participating nodes
191 for (std::vector<NBNode*>::iterator i = myControlledNodes.begin(); i != myControlledNodes.end(); i++) {
192 const EdgeVector& incoming = (*i)->getIncomingEdges();
193 copy(incoming.begin(), incoming.end(), back_inserter(myIncomingEdges));
194 const EdgeVector& outgoing = (*i)->getOutgoingEdges();
195 copy(outgoing.begin(), outgoing.end(), back_inserter(myOutgoing));
196 }
197 EdgeVector outer;
198 // check which of the edges are completely within the junction
199 // add them to the list of edges lying within the node
200 for (EdgeVector::iterator j = myIncomingEdges.begin(); j != myIncomingEdges.end(); ++j) {
201 NBEdge* edge = *j;
202 // an edge lies within the logic if it is outgoing as well as incoming
203 EdgeVector::iterator k = std::find(myOutgoing.begin(), myOutgoing.end(), edge);
204 if (k != myOutgoing.end()) {
205 myEdgesWithin.push_back(edge);
206 } else {
207 outer.push_back(edge);
208 }
209 }
210 // collect edges that are reachable from the outside via controlled connections
211 std::set<NBEdge*> reachable = collectReachable(outer, myEdgesWithin, true);
212 // collect edges that are reachable from the outside regardless of controllability
213 std::set<NBEdge*> reachable2 = collectReachable(outer, myEdgesWithin, false);
214
215 const bool uncontrolledWithin = OptionsCont::getOptions().getBool("tls.uncontrolled-within");
216 for (EdgeVector::iterator j = myEdgesWithin.begin(); j != myEdgesWithin.end(); ++j) {
217 NBEdge* edge = *j;
218 // edges that are marked as 'inner' will not get their own phase when
219 // computing traffic light logics (unless they cannot be reached from the outside at all)
220 if (reachable.count(edge) == 1) {
221 edge->setInternal();
222 // legacy behavior
223 if (uncontrolledWithin && myControlledInnerEdges.count(edge->getID()) == 0) {
224 myIncomingEdges.erase(find(myIncomingEdges.begin(), myIncomingEdges.end(), edge));
225 }
226 }
227 if (reachable2.count(edge) == 0 && edge->getFirstNonPedestrianLaneIndex(NBNode::FORWARD, true) >= 0
228 && getID() != DummyID) {
229 WRITE_WARNING("Unreachable edge '" + edge->getID() + "' within tlLogic '" + getID() + "'");
230 }
231 }
232 }
233
234
235 bool
mustBrake(const NBEdge * const from,const NBEdge * const to) const236 NBTrafficLightDefinition::mustBrake(const NBEdge* const from, const NBEdge* const to) const {
237 std::vector<NBNode*>::const_iterator i =
238 find_if(myControlledNodes.begin(), myControlledNodes.end(),
239 NBContHelper::node_with_incoming_finder(from));
240 assert(i != myControlledNodes.end());
241 NBNode* node = *i;
242 if (!node->hasOutgoing(to)) {
243 return true; // !!!
244 }
245 // @todo recheck relevance of lane indices
246 return node->mustBrake(from, to, -1, -1, true);
247 }
248
249
250 bool
mustBrake(const NBEdge * const possProhibitedFrom,const NBEdge * const possProhibitedTo,const NBEdge * const possProhibitorFrom,const NBEdge * const possProhibitorTo,bool regardNonSignalisedLowerPriority) const251 NBTrafficLightDefinition::mustBrake(const NBEdge* const possProhibitedFrom,
252 const NBEdge* const possProhibitedTo,
253 const NBEdge* const possProhibitorFrom,
254 const NBEdge* const possProhibitorTo,
255 bool regardNonSignalisedLowerPriority) const {
256 return forbids(possProhibitorFrom, possProhibitorTo,
257 possProhibitedFrom, possProhibitedTo,
258 regardNonSignalisedLowerPriority);
259 }
260
261
262 bool
mustBrake(const NBConnection & possProhibited,const NBConnection & possProhibitor,bool regardNonSignalisedLowerPriority) const263 NBTrafficLightDefinition::mustBrake(const NBConnection& possProhibited,
264 const NBConnection& possProhibitor,
265 bool regardNonSignalisedLowerPriority) const {
266 return forbids(possProhibitor.getFrom(), possProhibitor.getTo(),
267 possProhibited.getFrom(), possProhibited.getTo(),
268 regardNonSignalisedLowerPriority);
269 }
270
271
272 bool
forbids(const NBEdge * const possProhibitorFrom,const NBEdge * const possProhibitorTo,const NBEdge * const possProhibitedFrom,const NBEdge * const possProhibitedTo,bool regardNonSignalisedLowerPriority,bool sameNodeOnly) const273 NBTrafficLightDefinition::forbids(const NBEdge* const possProhibitorFrom,
274 const NBEdge* const possProhibitorTo,
275 const NBEdge* const possProhibitedFrom,
276 const NBEdge* const possProhibitedTo,
277 bool regardNonSignalisedLowerPriority,
278 bool sameNodeOnly) const {
279 if (possProhibitorFrom == nullptr || possProhibitorTo == nullptr || possProhibitedFrom == nullptr || possProhibitedTo == nullptr) {
280 return false;
281 }
282 // retrieve both nodes
283 std::vector<NBNode*>::const_iterator incoming =
284 find_if(myControlledNodes.begin(), myControlledNodes.end(), NBContHelper::node_with_incoming_finder(possProhibitorFrom));
285 std::vector<NBNode*>::const_iterator outgoing =
286 find_if(myControlledNodes.begin(), myControlledNodes.end(), NBContHelper::node_with_outgoing_finder(possProhibitedTo));
287 assert(incoming != myControlledNodes.end());
288 NBNode* incnode = *incoming;
289 NBNode* outnode = *outgoing;
290 EdgeVector::const_iterator i;
291
292 #ifdef DEBUG_RIGHT_OF_WAY
293 if (DEBUGCOND) {
294 std::cout << "foribds tls=" << getID() << " from=" << possProhibitedFrom->getID() << " to=" << possProhibitedTo->getID() << " foeFrom=" << possProhibitorFrom->getID() << " foeTo=" << possProhibitorTo->getID() << " rnslp=" << regardNonSignalisedLowerPriority << " sameNodeOnly=" << sameNodeOnly;
295 }
296 #endif
297 if (incnode != outnode) {
298 if (sameNodeOnly) {
299 #ifdef DEBUG_RIGHT_OF_WAY
300 if (DEBUGCOND) {
301 std::cout << " differentNodes: allows (no check)\n";
302 }
303 #endif
304 return false;
305 }
306 // the links are located at different nodes
307 const EdgeVector& ev1 = possProhibitedTo->getConnectedEdges();
308 // go through the following edge,
309 // check whether one of these connections is prohibited
310 for (i = ev1.begin(); i != ev1.end(); ++i) {
311 std::vector<NBNode*>::const_iterator outgoing2 =
312 find_if(myControlledNodes.begin(), myControlledNodes.end(), NBContHelper::node_with_outgoing_finder(*i));
313 if (outgoing2 == myControlledNodes.end()) {
314 continue;
315 }
316 NBNode* outnode2 = *outgoing2;
317 if (incnode != outnode2) {
318 continue;
319 }
320 if (incnode->getDirection(possProhibitedTo, *i) != LINKDIR_STRAIGHT) {
321 continue;
322 }
323 bool ret1 = incnode->foes(possProhibitorFrom, possProhibitorTo,
324 possProhibitedTo, *i);
325 bool ret2 = incnode->forbids(possProhibitorFrom, possProhibitorTo,
326 possProhibitedTo, *i,
327 regardNonSignalisedLowerPriority);
328 bool ret = ret1 || ret2;
329 if (ret) {
330 #ifdef DEBUG_RIGHT_OF_WAY
331 if (DEBUGCOND) {
332 std::cout << " differentNodes: forbids\n";
333 }
334 #endif
335 return true;
336 }
337 }
338
339 const EdgeVector& ev2 = possProhibitorTo->getConnectedEdges();
340 // go through the following edge,
341 // check whether one of these connections is prohibited
342 for (i = ev2.begin(); i != ev2.end(); ++i) {
343 std::vector<NBNode*>::const_iterator incoming2 =
344 find_if(myControlledNodes.begin(), myControlledNodes.end(), NBContHelper::node_with_incoming_finder(possProhibitorTo));
345 if (incoming2 == myControlledNodes.end()) {
346 continue;
347 }
348 NBNode* incnode2 = *incoming2;
349 if (incnode2 != outnode) {
350 continue;
351 }
352 if (incnode2->getDirection(possProhibitorTo, *i) != LINKDIR_STRAIGHT) {
353 continue;
354 }
355 bool ret1 = incnode2->foes(possProhibitorTo, *i,
356 possProhibitedFrom, possProhibitedTo);
357 bool ret2 = incnode2->forbids(possProhibitorTo, *i,
358 possProhibitedFrom, possProhibitedTo,
359 regardNonSignalisedLowerPriority);
360 bool ret = ret1 || ret2;
361 if (ret) {
362 #ifdef DEBUG_RIGHT_OF_WAY
363 if (DEBUGCOND) {
364 std::cout << " differentNodes: forbids (2)\n";
365 }
366 #endif
367 return true;
368 }
369 }
370 #ifdef DEBUG_RIGHT_OF_WAY
371 if (DEBUGCOND) {
372 std::cout << " differentNodes: allows\n";
373 }
374 #endif
375 return false;
376 }
377 // both links are located at the same node
378 // check using this node's information
379 const bool result = incnode->forbids(possProhibitorFrom, possProhibitorTo,
380 possProhibitedFrom, possProhibitedTo,
381 regardNonSignalisedLowerPriority);
382 #ifdef DEBUG_RIGHT_OF_WAY
383 if (DEBUGCOND) {
384 std::cout << " sameNodes: " << (result ? "forbids" : "allows") << "\n";
385 }
386 #endif
387 return result;
388 }
389
390
391 bool
foes(const NBEdge * const from1,const NBEdge * const to1,const NBEdge * const from2,const NBEdge * const to2) const392 NBTrafficLightDefinition::foes(const NBEdge* const from1, const NBEdge* const to1,
393 const NBEdge* const from2, const NBEdge* const to2) const {
394 if (to1 == nullptr || to2 == nullptr) {
395 return false;
396 }
397 // retrieve both nodes (it is possible that a connection
398 std::vector<NBNode*>::const_iterator incoming =
399 find_if(myControlledNodes.begin(), myControlledNodes.end(),
400 NBContHelper::node_with_incoming_finder(from1));
401 std::vector<NBNode*>::const_iterator outgoing =
402 find_if(myControlledNodes.begin(), myControlledNodes.end(),
403 NBContHelper::node_with_outgoing_finder(to1));
404 assert(incoming != myControlledNodes.end());
405 NBNode* incnode = *incoming;
406 NBNode* outnode = *outgoing;
407 if (incnode != outnode) {
408 return false;
409 }
410 return incnode->foes(from1, to1, from2, to2);
411 }
412
413
414 void
addNode(NBNode * node)415 NBTrafficLightDefinition::addNode(NBNode* node) {
416 if (std::find(myControlledNodes.begin(), myControlledNodes.end(), node) == myControlledNodes.end()) {
417 myControlledNodes.push_back(node);
418 std::sort(myControlledNodes.begin(), myControlledNodes.end(), NBNode::nodes_by_id_sorter());
419 }
420 node->addTrafficLight(this);
421 }
422
423
424 void
removeNode(NBNode * node)425 NBTrafficLightDefinition::removeNode(NBNode* node) {
426 std::vector<NBNode*>::iterator i = std::find(myControlledNodes.begin(), myControlledNodes.end(), node);
427 if (i != myControlledNodes.end()) {
428 myControlledNodes.erase(i);
429 }
430 // !!! remove in node?
431 }
432
433
434 void
addControlledInnerEdges(const std::vector<std::string> & edges)435 NBTrafficLightDefinition::addControlledInnerEdges(const std::vector<std::string>& edges) {
436 myControlledInnerEdges.insert(edges.begin(), edges.end());
437 }
438
439
440 std::vector<std::string>
getControlledInnerEdges() const441 NBTrafficLightDefinition::getControlledInnerEdges() const {
442 return std::vector<std::string>(myControlledInnerEdges.begin(), myControlledInnerEdges.end());
443 }
444
445
446 const EdgeVector&
getIncomingEdges() const447 NBTrafficLightDefinition::getIncomingEdges() const {
448 return myIncomingEdges;
449 }
450
451
452 void
collectAllLinks()453 NBTrafficLightDefinition::collectAllLinks() {
454 myControlledLinks.clear();
455 int tlIndex = 0;
456 // build the list of links which are controled by the traffic light
457 for (EdgeVector::iterator i = myIncomingEdges.begin(); i != myIncomingEdges.end(); i++) {
458 NBEdge* incoming = *i;
459 int noLanes = incoming->getNumLanes();
460 for (int j = 0; j < noLanes; j++) {
461 std::vector<NBEdge::Connection> connected = incoming->getConnectionsFromLane(j);
462 for (std::vector<NBEdge::Connection>::iterator k = connected.begin(); k != connected.end(); k++) {
463 const NBEdge::Connection& el = *k;
464 if (incoming->mayBeTLSControlled(el.fromLane, el.toEdge, el.toLane)) {
465 if (el.toEdge != nullptr && el.toLane >= (int) el.toEdge->getNumLanes()) {
466 throw ProcessError("Connection '" + incoming->getID() + "_" + toString(j) + "->" + el.toEdge->getID() + "_" + toString(el.toLane) + "' yields in a not existing lane.");
467 }
468 if (incoming->getToNode()->getType() == NODETYPE_RAIL_CROSSING
469 && isRailway(incoming->getPermissions())) {
470 // railways stay uncontrolled at rail crossing but they
471 // must be registered in MSRailCrossing
472 myControlledLinks.push_back(NBConnection(incoming, el.fromLane, el.toEdge, el.toLane, -1));
473 } else if (incoming->getToNode()->getType() == NODETYPE_RAIL_SIGNAL
474 && incoming->getToNode()->getDirection(incoming, el.toEdge) == LINKDIR_TURN) {
475 // turnarounds stay uncontrolled at rail signal
476 } else {
477 myControlledLinks.push_back(NBConnection(incoming, el.fromLane, el.toEdge, el.toLane, tlIndex++));
478 }
479 }
480 }
481 }
482 }
483 if (myControlledLinks.size() > 0 && tlIndex == 0) {
484 WRITE_WARNING("The rail crossing '" + getID() + "' does not have any roads.");
485 }
486 }
487
488
489 bool
needsCont(const NBEdge * fromE,const NBEdge * toE,const NBEdge * otherFromE,const NBEdge * otherToE) const490 NBTrafficLightDefinition::needsCont(const NBEdge* fromE, const NBEdge* toE, const NBEdge* otherFromE, const NBEdge* otherToE) const {
491 if (!myNeedsContRelationReady) {
492 initNeedsContRelation();
493 assert(myNeedsContRelationReady);
494 }
495 return std::find(myNeedsContRelation.begin(), myNeedsContRelation.end(),
496 StreamPair(fromE, toE, otherFromE, otherToE)) != myNeedsContRelation.end();
497 }
498
499
500 void
initNeedsContRelation() const501 NBTrafficLightDefinition::initNeedsContRelation() const {
502 if (!amInvalid()) {
503 NBOwnTLDef dummy(DummyID, myControlledNodes, 0, TLTYPE_STATIC);
504 dummy.initNeedsContRelation();
505 myNeedsContRelation = dummy.myNeedsContRelation;
506 for (std::vector<NBNode*>::const_iterator i = myControlledNodes.begin(); i != myControlledNodes.end(); i++) {
507 (*i)->removeTrafficLight(&dummy);
508 }
509 }
510 myNeedsContRelationReady = true;
511 }
512
513
514 bool
rightOnRedConflict(int index,int foeIndex) const515 NBTrafficLightDefinition::rightOnRedConflict(int index, int foeIndex) const {
516 if (!myRightOnRedConflictsReady) {
517 NBOwnTLDef dummy(DummyID, myControlledNodes, 0, TLTYPE_STATIC);
518 dummy.setParticipantsInformation();
519 NBTrafficLightLogic* tllDummy = dummy.computeLogicAndConts(0, true);
520 delete tllDummy;
521 myRightOnRedConflicts = dummy.myRightOnRedConflicts;
522 for (std::vector<NBNode*>::const_iterator i = myControlledNodes.begin(); i != myControlledNodes.end(); i++) {
523 (*i)->removeTrafficLight(&dummy);
524 }
525 myRightOnRedConflictsReady = true;
526 //std::cout << " rightOnRedConflicts tls=" << getID() << " pro=" << getProgramID() << "\n";
527 //for (RightOnRedConflicts::const_iterator it = myRightOnRedConflicts.begin(); it != myRightOnRedConflicts.end(); ++it) {
528 // std::cout << " " << it->first << ", " << it->second << "\n";
529 //}
530 }
531 return std::find(myRightOnRedConflicts.begin(), myRightOnRedConflicts.end(), std::make_pair(index, foeIndex)) != myRightOnRedConflicts.end();
532 }
533
534 std::string
getDescription() const535 NBTrafficLightDefinition::getDescription() const {
536 return getID() + ':' + getProgramID() + '@' + toString(this);
537 }
538
539 /****************************************************************************/
540
541