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    GNEEdge.cpp
11 /// @author  Jakob Erdmann
12 /// @date    Feb 2011
13 /// @version $Id$
14 ///
15 // A road/street connecting two junctions (netedit-version, adapted from GUIEdge)
16 // Basically a container for an NBEdge with drawing and editing capabilities
17 /****************************************************************************/
18 
19 
20 // ===========================================================================
21 // included modules
22 // ===========================================================================
23 #include <config.h>
24 
25 #include <utils/common/StringTokenizer.h>
26 #include <utils/gui/globjects/GUIGLObjectPopupMenu.h>
27 #include <utils/gui/div/GLHelper.h>
28 #include <netedit/changes/GNEChange_Attribute.h>
29 #include <netedit/changes/GNEChange_Lane.h>
30 #include <netedit/changes/GNEChange_Additional.h>
31 #include <netedit/GNENet.h>
32 #include <netedit/GNEViewNet.h>
33 #include <netedit/GNEUndoList.h>
34 #include <netedit/additionals/GNERouteProbe.h>
35 #include <netedit/additionals/GNEDetectorE2.h>
36 #include <netedit/demandelements/GNEDemandElement.h>
37 #include <utils/options/OptionsCont.h>
38 #include <utils/gui/globjects/GLIncludes.h>
39 
40 #include "GNEConnection.h"
41 #include "GNECrossing.h"
42 #include "GNEJunction.h"
43 #include "GNELane.h"
44 #include "GNEEdge.h"
45 
46 //#define DEBUG_SMOOTH_GEOM
47 //#define DEBUGCOND(obj) (true)
48 
49 // ===========================================================================
50 // static
51 // ===========================================================================
52 const double GNEEdge::SNAP_RADIUS = SUMO_const_halfLaneWidth;
53 
getDummyEdge()54 GNEEdge& GNEEdge::getDummyEdge() {
55     // @note: using local static idiom to avoid static initialization order problem
56     static GNEEdge* dummy = new GNEEdge(); // 'static local variable', this line is called only once
57     return *dummy; // this line gets called with the same 'dummy' every time the function is called
58 }
59 
60 // ===========================================================================
61 // members methods
62 // ===========================================================================
63 
GNEEdge(NBEdge & nbe,GNENet * net,bool wasSplit,bool loaded)64 GNEEdge::GNEEdge(NBEdge& nbe, GNENet* net, bool wasSplit, bool loaded):
65     GNENetElement(net, nbe.getID(), GLO_EDGE, SUMO_TAG_EDGE),
66     myNBEdge(nbe),
67     myGNEJunctionSource(myNet->retrieveJunction(myNBEdge.getFromNode()->getID())),
68     myGNEJunctionDestiny(myNet->retrieveJunction(myNBEdge.getToNode()->getID())),
69     myLanes(0),
70     myAmResponsible(false),
71     myWasSplit(wasSplit),
72     myConnectionStatus(loaded ? FEATURE_LOADED : FEATURE_GUESSED) {
73     // Create lanes
74     int numLanes = myNBEdge.getNumLanes();
75     myLanes.reserve(numLanes);
76     for (int i = 0; i < numLanes; i++) {
77         myLanes.push_back(new GNELane(*this, i));
78         myLanes.back()->incRef("GNEEdge::GNEEdge");
79     }
80     // update Lane geometries
81     for (auto i : myLanes) {
82         i->updateGeometry(true);
83     }
84 }
85 
GNEEdge()86 GNEEdge::GNEEdge() :
87     GNENetElement(nullptr, "DUMMY", GLO_EDGE, SUMO_TAG_NOTHING),
88     myNBEdge(NBEdge::DummyEdge) {
89 }
90 
~GNEEdge()91 GNEEdge::~GNEEdge() {
92     // Delete references to this eddge in lanes
93     for (auto i : myLanes) {
94         i->decRef("GNEEdge::~GNEEdge");
95         if (i->unreferenced()) {
96             // show extra information for tests
97             WRITE_DEBUG("Deleting unreferenced " + i->getTagStr() + " '" + i->getID() + "' in GNEEdge destructor");
98             delete i;
99         }
100     }
101     // delete references to this eddge in connections
102     for (auto i : myGNEConnections) {
103         i->decRef("GNEEdge::~GNEEdge");
104         if (i->unreferenced()) {
105             // show extra information for tests
106             WRITE_DEBUG("Deleting unreferenced " + i->getTagStr() + " '" + i->getID() + "' in GNEEdge destructor");
107             delete i;
108         }
109     }
110     if (myAmResponsible) {
111         delete &myNBEdge;
112     }
113 }
114 
115 
116 std::string
generateChildID(SumoXMLTag)117 GNEEdge::generateChildID(SumoXMLTag /*childTag*/) {
118     // currently unused
119     return "";
120 }
121 
122 
123 void
updateGeometry(bool updateGrid)124 GNEEdge::updateGeometry(bool updateGrid) {
125     // first check if object has to be removed from grid (SUMOTree)
126     if (updateGrid) {
127         myNet->removeGLObjectFromGrid(this);
128     }
129     // Update geometry of lanes
130     for (auto i : myLanes) {
131         i->updateGeometry(updateGrid);
132     }
133     // Update geometry of connections (Only if updateGrid is enabled, because in move mode connections are hidden
134     // (note: only the previous marked as deprecated will be updated)
135     if (updateGrid) {
136         for (auto i : myGNEConnections) {
137             i->updateGeometry(updateGrid);
138         }
139     }
140     // Update geometry of additionals childs vinculated to this edge
141     for (auto i : getAdditionalChilds()) {
142         i->updateGeometry(updateGrid);
143     }
144     // Update geometry of additional parents that have this edge as parent
145     for (auto i : getAdditionalParents()) {
146         i->updateGeometry(updateGrid);
147     }
148     // Update geometry of demand elements childs vinculated to this edge
149     for (auto i : getDemandElementChilds()) {
150         i->updateGeometry(updateGrid);
151     }
152     // Update geometry of demand elements parents that have this edge as parent
153     for (auto i : getDemandElementParents()) {
154         i->updateGeometry(updateGrid);
155     }
156     // last step is to check if object has to be added into grid (SUMOTree) again
157     if (updateGrid) {
158         myNet->addGLObjectIntoGrid(this);
159     }
160 }
161 
162 
163 Position
getPositionInView() const164 GNEEdge::getPositionInView() const {
165     // currently unused
166     return Position(0, 0);
167 }
168 
169 
170 bool
clickedOverShapeStart(const Position & pos)171 GNEEdge::clickedOverShapeStart(const Position& pos) {
172     if (myNBEdge.getGeometry().front() != myGNEJunctionSource->getPositionInView()) {
173         return (myNBEdge.getGeometry().front().distanceTo2D(pos) < SNAP_RADIUS);
174     } else {
175         return false;
176     }
177 }
178 
179 
180 bool
clickedOverShapeEnd(const Position & pos)181 GNEEdge::clickedOverShapeEnd(const Position& pos) {
182     if (myNBEdge.getGeometry().back() != myGNEJunctionDestiny->getPositionInView()) {
183         return (myNBEdge.getGeometry().back().distanceTo2D(pos) < SNAP_RADIUS);
184     } else {
185         return false;
186     }
187 }
188 
189 
190 void
moveShapeStart(const Position & oldPos,const Position & offset)191 GNEEdge::moveShapeStart(const Position& oldPos, const Position& offset) {
192     // change shape startPosition using oldPosition and offset
193     Position shapeStartEdited = oldPos;
194     shapeStartEdited.add(offset);
195     // snap to active grid
196     shapeStartEdited = myNet->getViewNet()->snapToActiveGrid(shapeStartEdited);
197     // make sure that start and end position are different
198     if (shapeStartEdited != myNBEdge.getGeometry().back()) {
199         // set shape start position without updating grid
200         setShapeStartPos(shapeStartEdited, false);
201         updateGeometry(false);
202     }
203 }
204 
205 
206 void
moveShapeEnd(const Position & oldPos,const Position & offset)207 GNEEdge::moveShapeEnd(const Position& oldPos, const Position& offset) {
208     // change shape endPosition using oldPosition and offset
209     Position shapeEndEdited = oldPos;
210     shapeEndEdited.add(offset);
211     // snap to active grid
212     shapeEndEdited = myNet->getViewNet()->snapToActiveGrid(shapeEndEdited);
213     // make sure that start and end position are different
214     if (shapeEndEdited != myNBEdge.getGeometry().front()) {
215         // set shape end position without updating grid
216         setShapeEndPos(shapeEndEdited, false);
217         updateGeometry(false);
218     }
219 }
220 
221 
222 void
commitShapeStartChange(const Position & oldPos,GNEUndoList * undoList)223 GNEEdge::commitShapeStartChange(const Position& oldPos, GNEUndoList* undoList) {
224     // first save current shape start position
225     Position modifiedShapeStartPos = myNBEdge.getGeometry().front();
226     // restore old shape start position
227     setShapeStartPos(oldPos, true);
228     // set attribute using undolist
229     undoList->p_begin("shape start of " + getTagStr());
230     undoList->p_add(new GNEChange_Attribute(this, myNet, GNE_ATTR_SHAPE_START, toString(modifiedShapeStartPos), true, toString(oldPos)));
231     undoList->p_end();
232 }
233 
234 
235 void
commitShapeEndChange(const Position & oldPos,GNEUndoList * undoList)236 GNEEdge::commitShapeEndChange(const Position& oldPos, GNEUndoList* undoList) {
237     // first save current shape end position
238     Position modifiedShapeEndPos = myNBEdge.getGeometry().back();
239     // restore old shape end position
240     setShapeEndPos(oldPos, true);
241     // set attribute using undolist
242     undoList->p_begin("shape end of " + getTagStr());
243     undoList->p_add(new GNEChange_Attribute(this, myNet, GNE_ATTR_SHAPE_END, toString(modifiedShapeEndPos), true, toString(oldPos)));
244     undoList->p_end();
245 }
246 
247 
248 void
startGeometryMoving()249 GNEEdge::startGeometryMoving() {
250     // save current centering boundary
251     myMovingGeometryBoundary = getCenteringBoundary();
252     // Save current centering boundary of lanes (and their childs)
253     for (auto i : myLanes) {
254         i->startGeometryMoving();
255     }
256     // Save current centering boundary of additionals childs vinculated to this edge
257     for (auto i : getAdditionalChilds()) {
258         i->startGeometryMoving();
259     }
260     // Save current centering boundary of additional parents that have this edge as parent
261     for (auto i : getAdditionalParents()) {
262         i->startGeometryMoving();
263     }
264     // Save current centering boundary of demand elements childs vinculated to this edge
265     for (auto i : getDemandElementChilds()) {
266         i->startGeometryMoving();
267     }
268     // Save current centering boundary of demand elements parents that have this edge as parent
269     for (auto i : getDemandElementParents()) {
270         i->startGeometryMoving();
271     }
272 }
273 
274 
275 void
endGeometryMoving()276 GNEEdge::endGeometryMoving() {
277     // check that endGeometryMoving was called only once
278     if (myMovingGeometryBoundary.isInitialised()) {
279         // Remove object from net
280         myNet->removeGLObjectFromGrid(this);
281         // reset myMovingGeometryBoundary
282         myMovingGeometryBoundary.reset();
283         // update geometry without updating grid
284         updateGeometry(false);
285         // Restore centering boundary of lanes (and their childs)
286         for (auto i : myLanes) {
287             i->endGeometryMoving();
288         }
289         // Restore centering boundary of additionals childs vinculated to this edge
290         for (auto i : getAdditionalChilds()) {
291             i->endGeometryMoving();
292         }
293         // Restore centering boundary of additional parents that have this edge as parent
294         for (auto i : getAdditionalParents()) {
295             i->endGeometryMoving();
296         }
297         // Restore centering boundary of demand elements childs vinculated to this edge
298         for (auto i : getDemandElementChilds()) {
299             i->endGeometryMoving();
300         }
301         // Restore centering boundary of demand elements parents that have this edge as parent
302         for (auto i : getDemandElementParents()) {
303             i->endGeometryMoving();
304         }
305         // add object into grid again (using the new centering boundary)
306         myNet->addGLObjectIntoGrid(this);
307     }
308 }
309 
310 
311 int
getVertexIndex(Position pos,bool createIfNoExist,bool snapToGrid)312 GNEEdge::getVertexIndex(Position pos, bool createIfNoExist, bool snapToGrid) {
313     PositionVector entireGeometry = myNBEdge.getGeometry();
314     // check if position has to be snapped to grid
315     if (snapToGrid) {
316         pos = myNet->getViewNet()->snapToActiveGrid(pos);
317     }
318     double offset = entireGeometry.nearest_offset_to_point2D(pos, true);
319     if (offset == GeomHelper::INVALID_OFFSET) {
320         return -1;
321     }
322     Position newPos = entireGeometry.positionAtOffset2D(offset);
323     // first check if vertex already exists in the inner geometry
324     for (int i = 0; i < (int)entireGeometry.size(); i++) {
325         if (entireGeometry[i].distanceTo2D(newPos) < SNAP_RADIUS) {
326             if (i == 0 || i == (int)(entireGeometry.size() - 1)) {
327                 return -1;
328             }
329             // index refers to inner geometry
330             return i - 1;
331         }
332     }
333     // if vertex doesn't exist, insert it
334     if (createIfNoExist) {
335         // check if position has to be snapped to grid
336         if (snapToGrid) {
337             newPos = myNet->getViewNet()->snapToActiveGrid(newPos);
338         }
339         int index = entireGeometry.insertAtClosest(myNet->getViewNet()->snapToActiveGrid(newPos));
340         setGeometry(entireGeometry, false, true);
341         // index refers to inner geometry
342         return (index - 1);
343     } else {
344         return -1;
345     }
346 }
347 
348 
349 int
getVertexIndex(const double offset,bool createIfNoExist,bool snapToGrid)350 GNEEdge::getVertexIndex(const double offset, bool createIfNoExist, bool snapToGrid) {
351     return getVertexIndex(myNBEdge.getGeometry().positionAtOffset2D(offset), createIfNoExist, snapToGrid);
352 }
353 
354 
355 int
moveVertexShape(const int index,const Position & oldPos,const Position & offset)356 GNEEdge::moveVertexShape(const int index, const Position& oldPos, const Position& offset) {
357     // obtain inner geometry of edge
358     PositionVector edgeGeometry = myNBEdge.getInnerGeometry();
359     // Make sure that index is valid AND ins't the first and last index
360     if (index != -1) {
361         // check that index is correct before change position
362         if (index < (int)edgeGeometry.size()) {
363             // change position of vertex
364             edgeGeometry[index] = oldPos;
365             edgeGeometry[index].add(offset);
366             // filtern position using snap to active grid
367             edgeGeometry[index] = myNet->getViewNet()->snapToActiveGrid(edgeGeometry[index]);
368             // update edge's geometry without updating RTree (To avoid unnecesary changes in RTree)
369             setGeometry(edgeGeometry, true, false);
370             return index;
371         } else {
372             throw InvalidArgument("Index greater than shape size");
373         }
374     } else {
375         return index;
376     }
377 }
378 
379 
380 void
moveEntireShape(const PositionVector & oldShape,const Position & offset)381 GNEEdge::moveEntireShape(const PositionVector& oldShape, const Position& offset) {
382     // make a copy of the old shape to change it
383     PositionVector modifiedShape = oldShape;
384     // change all points of the inner geometry using offset
385     for (auto& i : modifiedShape) {
386         i.add(offset);
387     }
388     // restore modified shape
389     setGeometry(modifiedShape, true, false);
390 }
391 
392 
393 void
commitShapeChange(const PositionVector & oldShape,GNEUndoList * undoList)394 GNEEdge::commitShapeChange(const PositionVector& oldShape, GNEUndoList* undoList) {
395     // restore original shape into shapeToCommit
396     PositionVector innerShapeToCommit = myNBEdge.getInnerGeometry();
397     // first check if second and penultimate isn't in Junction's buubles
398     double buubleRadius = GNEJunction::BUBBLE_RADIUS * myNet->getViewNet()->getVisualisationSettings()->junctionSize.exaggeration;
399     if (myNBEdge.getGeometry().size() > 2 && myNBEdge.getGeometry()[0].distanceTo(myNBEdge.getGeometry()[1]) < buubleRadius) {
400         innerShapeToCommit.removeClosest(innerShapeToCommit[0]);
401     }
402     if (myNBEdge.getGeometry().size() > 2 && myNBEdge.getGeometry()[(int)myNBEdge.getGeometry().size() - 2].distanceTo(myNBEdge.getGeometry()[(int)myNBEdge.getGeometry().size() - 1]) < buubleRadius) {
403         innerShapeToCommit.removeClosest(innerShapeToCommit[(int)innerShapeToCommit.size() - 1]);
404     }
405     // second check if double points has to be removed
406     innerShapeToCommit.removeDoublePoints(SNAP_RADIUS);
407     // show warning if some of edge's shape was merged
408     if (innerShapeToCommit.size() != myNBEdge.getInnerGeometry().size()) {
409         WRITE_WARNING("Merged shape's point")
410     }
411     // finish geometry moving
412     endGeometryMoving();
413     updateGeometry(false);
414     // restore old geometry to allow change attribute (And restore shape if during movement a new point was created
415     setGeometry(oldShape, true, true);
416     // commit new shape
417     undoList->p_begin("moving " + toString(SUMO_ATTR_SHAPE) + " of " + getTagStr());
418     undoList->p_add(new GNEChange_Attribute(this, myNet, SUMO_ATTR_SHAPE, toString(innerShapeToCommit)));
419     undoList->p_end();
420 }
421 
422 
423 void
deleteGeometryPoint(const Position & pos,bool allowUndo)424 GNEEdge::deleteGeometryPoint(const Position& pos, bool allowUndo) {
425     // obtain index and remove point
426     PositionVector modifiedShape = myNBEdge.getInnerGeometry();
427     int index = modifiedShape.indexOfClosest(pos);
428     modifiedShape.erase(modifiedShape.begin() + index);
429     // set new shape depending of allowUndo
430     if (allowUndo) {
431         myNet->getViewNet()->getUndoList()->p_begin("delete geometry point");
432         setAttribute(SUMO_ATTR_SHAPE, toString(modifiedShape), myNet->getViewNet()->getUndoList());
433         myNet->getViewNet()->getUndoList()->p_end();
434     } else {
435         // set new shape
436         setGeometry(modifiedShape, true, true);
437     }
438 }
439 
440 
441 void
updateJunctionPosition(GNEJunction * junction,const Position & origPos,bool updateGrid)442 GNEEdge::updateJunctionPosition(GNEJunction* junction, const Position& origPos, bool updateGrid) {
443     Position delta = junction->getNBNode()->getPosition() - origPos;
444     PositionVector geom = myNBEdge.getGeometry();
445     // geometry endpoint need not equal junction position hence we modify it with delta
446     if (junction == myGNEJunctionSource) {
447         geom[0].add(delta);
448     } else {
449         geom[-1].add(delta);
450     }
451     setGeometry(geom, false, updateGrid);
452 }
453 
454 
455 Boundary
getBoundary() const456 GNEEdge::getBoundary() const {
457     Boundary ret;
458     for (auto i : myLanes) {
459         ret.add(i->getBoundary());
460     }
461     // ensure that geometry points are selectable even if the lane geometry is strange
462     for (const Position& pos : myNBEdge.getGeometry()) {
463         ret.add(pos);
464     }
465     ret.grow(10); // !!! magic value
466     return ret;
467 }
468 
469 
470 Boundary
getCenteringBoundary() const471 GNEEdge::getCenteringBoundary() const {
472     // Return Boundary depending if myMovingGeometryBoundary is initialised (important for move geometry)
473     if (myMovingGeometryBoundary.isInitialised()) {
474         return myMovingGeometryBoundary;
475     }  else {
476         Boundary b = getBoundary();
477         b.grow(20);
478         return b;
479     }
480 }
481 
482 const std::string
getOptionalName() const483 GNEEdge::getOptionalName() const {
484     return myNBEdge.getStreetName();
485 }
486 
487 GUIGLObjectPopupMenu*
getPopUpMenu(GUIMainWindow & app,GUISUMOAbstractView & parent)488 GNEEdge::getPopUpMenu(GUIMainWindow& app, GUISUMOAbstractView& parent) {
489     GUIGLObjectPopupMenu* ret = new GUIGLObjectPopupMenu(app, parent, *this);
490     buildPopupHeader(ret, app);
491     buildCenterPopupEntry(ret);
492     buildNameCopyPopupEntry(ret);
493     // build selection and show parameters menu
494     myNet->getViewNet()->buildSelectionACPopupEntry(ret, this);
495     buildShowParamsPopupEntry(ret);
496     // build position copy entry
497     buildPositionCopyEntry(ret, false);
498     return ret;
499 }
500 
501 
502 GNEJunction*
getGNEJunctionSource() const503 GNEEdge::getGNEJunctionSource() const {
504     return myGNEJunctionSource;
505 }
506 
507 
508 GNEJunction*
getGNEJunctionDestiny() const509 GNEEdge::getGNEJunctionDestiny() const {
510     return myGNEJunctionDestiny;
511 }
512 
513 
514 GNEEdge*
getOppositeEdge() const515 GNEEdge::getOppositeEdge() const {
516     return myNet->retrieveEdge(myGNEJunctionDestiny, myGNEJunctionSource, false);
517 }
518 
519 
520 void
drawGL(const GUIVisualizationSettings & s) const521 GNEEdge::drawGL(const GUIVisualizationSettings& s) const {
522     /* do something different for connectors?
523     if (myNBEdge.isMacroscopicConnector()) {
524     }
525     */
526     // obtain circle width
527     double circleWidth = SNAP_RADIUS * MIN2((double)1, s.laneWidthExaggeration);
528     double circleWidthSquared = circleWidth * circleWidth;
529     int circleResolution = GNEAttributeCarrier::getCircleResolution(s);
530     // draw the lanes
531     for (auto i : myLanes) {
532         i->drawGL(s);
533     }
534     // draw geometry points if isnt's too small
535     if (s.scale > 8.0) {
536         RGBColor color = s.junctionColorer.getSchemes()[0].getColor(2);
537         if (drawUsingSelectColor() && s.laneColorer.getActive() != 1) {
538             // override with special colors (unless the color scheme is based on selection)
539             color = s.selectedEdgeColor.changedBrightness(-20);
540         }
541         GLHelper::setColor(color);
542         // recognize full transparency and simply don't draw
543         if (color.alpha() > 0) {
544             // push name
545             glPushName(getGlID());
546             // draw geometry points expect initial and final
547             for (int i = 1; i < (int)myNBEdge.getGeometry().size() - 1; i++) {
548                 Position pos = myNBEdge.getGeometry()[i];
549                 if (!s.drawForSelecting || (myNet->getViewNet()->getPositionInformation().distanceSquaredTo2D(pos) <= (circleWidthSquared + 2))) {
550                     glPushMatrix();
551                     glTranslated(pos.x(), pos.y(), GLO_JUNCTION - 0.01);
552                     // resolution of drawn circle depending of the zoom (To improve smothness)
553                     GLHelper::drawFilledCircle(circleWidth, circleResolution);
554                     glPopMatrix();
555                     // draw elevation or special symbols (Start, End and Block)
556                     if (!s.drawForSelecting && myNet->getViewNet()->getMoveOptions().editingElevation()) {
557                         glPushMatrix();
558                         // Translate to geometry point
559                         glTranslated(pos.x(), pos.y(), GLO_JUNCTION);
560                         // draw Z value
561                         GLHelper::drawText(toString(pos.z()), Position(), GLO_MAX - 5, s.edgeValue.scaledSize(s.scale) / 2, s.edgeValue.color);
562                         glPopMatrix();
563                     }
564                 }
565             }
566             // draw line geometry, start and end points if shapeStart or shape end is edited, and depending of drawForSelecting
567             if (myNet->getViewNet()->getEditModes().networkEditMode == GNE_NMODE_MOVE) {
568                 if ((myNBEdge.getGeometry().front() != myGNEJunctionSource->getPositionInView()) &&
569                         (!s.drawForSelecting || (myNet->getViewNet()->getPositionInformation().distanceSquaredTo2D(myNBEdge.getGeometry().front()) <= (circleWidthSquared + 2)))) {
570                     glPushMatrix();
571                     glTranslated(myNBEdge.getGeometry().front().x(), myNBEdge.getGeometry().front().y(), GLO_JUNCTION + 0.01);
572                     // resolution of drawn circle depending of the zoom (To improve smothness)
573                     GLHelper::drawFilledCircle(circleWidth, circleResolution);
574                     glPopMatrix();
575                     // draw a "s" over last point depending of drawForSelecting
576                     if (!s.drawForSelecting) {
577                         glPushMatrix();
578                         glTranslated(myNBEdge.getGeometry().front().x(), myNBEdge.getGeometry().front().y(), GLO_JUNCTION + 0.02);
579                         GLHelper::drawText("S", Position(), 0, circleWidth, RGBColor::WHITE);
580                         glPopMatrix();
581                         // draw line between Junction and point
582                         glPushMatrix();
583                         glTranslated(0, 0, GLO_JUNCTION - 0.01);
584                         glLineWidth(4);
585                         GLHelper::drawLine(myNBEdge.getGeometry().front(), myGNEJunctionSource->getPositionInView());
586                         // draw line between begin point of last lane shape and the first edge shape point
587                         GLHelper::drawLine(myNBEdge.getGeometry().front(), myNBEdge.getLanes().back().shape.front());
588                         glPopMatrix();
589                     }
590                 }
591                 if ((myNBEdge.getGeometry().back() != myGNEJunctionDestiny->getPositionInView()) &&
592                         (!s.drawForSelecting || (myNet->getViewNet()->getPositionInformation().distanceSquaredTo2D(myNBEdge.getGeometry().back()) <= (circleWidthSquared + 2)))) {
593                     glPushMatrix();
594                     glTranslated(myNBEdge.getGeometry().back().x(), myNBEdge.getGeometry().back().y(), GLO_JUNCTION + 0.01);
595                     // resolution of drawn circle depending of the zoom (To improve smothness)
596                     GLHelper::drawFilledCircle(circleWidth, circleResolution);
597                     glPopMatrix();
598                     // draw a "e" over last point depending of drawForSelecting
599                     if (!s.drawForSelecting) {
600                         glPushMatrix();
601                         glTranslated(myNBEdge.getGeometry().back().x(), myNBEdge.getGeometry().back().y(), GLO_JUNCTION + 0.02);
602                         GLHelper::drawText("E", Position(), 0, circleWidth, RGBColor::WHITE);
603                         glPopMatrix();
604                         // draw line between Junction and point
605                         glPushMatrix();
606                         glTranslated(0, 0, GLO_JUNCTION - 0.01);
607                         glLineWidth(4);
608                         GLHelper::drawLine(myNBEdge.getGeometry().back(), myGNEJunctionDestiny->getPositionInView());
609                         // draw line between last point of first lane shape and the last edge shape point
610                         GLHelper::drawLine(myNBEdge.getGeometry().back(), myNBEdge.getLanes().back().shape.back());
611                         glPopMatrix();
612                     }
613                 }
614             }
615             // pop name
616             glPopName();
617         }
618     }
619 
620     // (optionally) draw the name and/or the street name if isn't being drawn for selecting
621     const bool drawStreetName = s.streetName.show && (myNBEdge.getStreetName() != "");
622     const bool spreadSuperposed = s.spreadSuperposed && myLanes.back()->drawAsRailway(s) && myNBEdge.isBidiRail();
623     if (!s.drawForSelecting && (s.edgeName.show || drawStreetName || s.edgeValue.show)) {
624         glPushName(getGlID());
625         GNELane* lane1 = myLanes[0];
626         GNELane* lane2 = myLanes[myLanes.size() - 1];
627         Position p = lane1->getShape().positionAtOffset(lane1->getShape().length() / (double) 2.);
628         p.add(lane2->getShape().positionAtOffset(lane2->getShape().length() / (double) 2.));
629         p.mul(.5);
630         if (spreadSuperposed) {
631             // move name to the right of the edge and towards its beginning
632             const double dist = 0.6 * s.edgeName.scaledSize(s.scale);
633             const double shiftA = lane1->getShape().rotationAtOffset(lane1->getShape().length() / (double) 2.) - DEG2RAD(135);
634             Position shift(dist * cos(shiftA), dist * sin(shiftA));
635             p.add(shift);
636         }
637         double angle = lane1->getShape().rotationDegreeAtOffset(lane1->getShape().length() / (double) 2.);
638         angle += 90;
639         if (angle > 90 && angle < 270) {
640             angle -= 180;
641         }
642         if (s.edgeName.show) {
643             drawName(p, s.scale, s.edgeName, angle);
644         }
645         if (drawStreetName) {
646             GLHelper::drawTextSettings(s.streetName, myNBEdge.getStreetName(), p, s.scale, angle);
647         }
648         if (s.edgeValue.show) {
649             double value = lane2->getColorValue(s, s.laneColorer.getActive());
650             GLHelper::drawTextSettings(s.edgeValue, toString(value), p, s.scale, angle);
651         }
652         glPopName();
653     }
654     if (!s.drawForSelecting && (myNet->getViewNet()->getDottedAC() == this)) {
655         // draw dotted contor around the first and last lane
656         const double myHalfLaneWidthFront = myNBEdge.getLaneWidth(myLanes.front()->getIndex()) / 2;
657         const double myHalfLaneWidthBack = spreadSuperposed ? 0 : myNBEdge.getLaneWidth(myLanes.back()->getIndex()) / 2;
658         GLHelper::drawShapeDottedContour(GLO_JUNCTION, myLanes.front()->getShape(), myHalfLaneWidthFront, myLanes.back()->getShape(), -1 * myHalfLaneWidthBack);
659     }
660     glPopMatrix();
661 }
662 
663 
664 NBEdge*
getNBEdge()665 GNEEdge::getNBEdge() {
666     return &myNBEdge;
667 }
668 
669 
670 Position
getSplitPos(const Position & clickPos)671 GNEEdge::getSplitPos(const Position& clickPos) {
672     const PositionVector& geom = myNBEdge.getGeometry();
673     int index = geom.indexOfClosest(clickPos);
674     if (geom[index].distanceTo(clickPos) < SNAP_RADIUS) {
675         // split at existing geometry point
676         return geom[index];
677     } else {
678         // split straight between the next two points
679         return geom.positionAtOffset(geom.nearest_offset_to_point2D(clickPos));
680     }
681 }
682 
683 
684 void
editEndpoint(Position pos,GNEUndoList * undoList)685 GNEEdge::editEndpoint(Position pos, GNEUndoList* undoList) {
686     if ((myNBEdge.getGeometry().front() != myGNEJunctionSource->getPositionInView()) && (myNBEdge.getGeometry().front().distanceTo(pos) < SNAP_RADIUS)) {
687         undoList->p_begin("remove endpoint");
688         setAttribute(GNE_ATTR_SHAPE_START, "", undoList);
689         undoList->p_end();
690     } else if ((myNBEdge.getGeometry().back() != myGNEJunctionDestiny->getPositionInView()) && (myNBEdge.getGeometry().back().distanceTo(pos) < SNAP_RADIUS)) {
691         undoList->p_begin("remove endpoint");
692         setAttribute(GNE_ATTR_SHAPE_END, "", undoList);
693         undoList->p_end();
694     } else {
695         // we need to create new Start/End position over Edge shape, not over clicked position
696         double offset = myNBEdge.getGeometry().nearest_offset_to_point2D(myNet->getViewNet()->snapToActiveGrid(pos), true);
697         if (offset != GeomHelper::INVALID_OFFSET) {
698             PositionVector geom = myNBEdge.getGeometry();
699             // calculate position over edge shape relative to clicked positino
700             Position newPos = geom.positionAtOffset2D(offset);
701             // snap new position to grid
702             newPos = myNet->getViewNet()->snapToActiveGrid(newPos);
703             undoList->p_begin("set endpoint");
704             int index = geom.indexOfClosest(pos);
705             // check if snap to existing geometry
706             if (geom[index].distanceTo(pos) < SNAP_RADIUS) {
707                 pos = geom[index];
708             }
709             Position destPos = myGNEJunctionDestiny->getNBNode()->getPosition();
710             Position sourcePos = myGNEJunctionSource->getNBNode()->getPosition();
711             if (pos.distanceTo2D(destPos) < pos.distanceTo2D(sourcePos)) {
712                 setAttribute(GNE_ATTR_SHAPE_END, toString(newPos), undoList);
713                 myGNEJunctionDestiny->invalidateShape();
714             } else {
715                 setAttribute(GNE_ATTR_SHAPE_START, toString(newPos), undoList);
716                 myGNEJunctionSource->invalidateShape();
717             }
718             // possibly existing inner point is no longer needed
719             if (myNBEdge.getInnerGeometry().size() > 0 && getVertexIndex(pos, false, false) != -1) {
720                 deleteGeometryPoint(pos, false);
721             }
722             undoList->p_end();
723         }
724     }
725 }
726 
727 
728 void
resetEndpoint(const Position & pos,GNEUndoList * undoList)729 GNEEdge::resetEndpoint(const Position& pos, GNEUndoList* undoList) {
730     Position destPos = myGNEJunctionDestiny->getNBNode()->getPosition();
731     Position sourcePos = myGNEJunctionSource->getNBNode()->getPosition();
732     if (pos.distanceTo2D(destPos) < pos.distanceTo2D(sourcePos)) {
733         setAttribute(GNE_ATTR_SHAPE_END, toString(destPos), undoList);
734         myGNEJunctionDestiny->invalidateShape();
735     } else {
736         setAttribute(GNE_ATTR_SHAPE_START, toString(sourcePos), undoList);
737         myGNEJunctionSource->invalidateShape();
738     }
739 }
740 
741 
742 void
setGeometry(PositionVector geom,bool inner,bool updateGrid)743 GNEEdge::setGeometry(PositionVector geom, bool inner, bool updateGrid) {
744     if (updateGrid) {
745         // first remove object from net grid
746         myNet->removeGLObjectFromGrid(this);
747     }
748     // set new geometry
749     myNBEdge.setGeometry(geom, inner);
750     if (updateGrid) {
751         // add object into net again
752         myNet->addGLObjectIntoGrid(this);
753     }
754     updateGeometry(updateGrid);
755     myGNEJunctionSource->invalidateShape();
756     myGNEJunctionDestiny->invalidateShape();
757 }
758 
759 
760 void
remakeGNEConnections()761 GNEEdge::remakeGNEConnections() {
762     // create new and removed unused GNEConnectinos
763     const std::vector<NBEdge::Connection>& connections = myNBEdge.getConnections();
764     // create a vector to keep retrieved and created connections
765     std::vector<GNEConnection*> retrievedConnections;
766     // iterate over NBEdge::Connections of GNEEdge
767     for (auto it : connections) {
768         // retrieve existent GNEConnection, or create it
769         GNEConnection* retrievedGNEConnection = retrieveGNEConnection(it.fromLane, it.toEdge, it.toLane);
770         retrievedGNEConnection->updateLinkState();
771         retrievedConnections.push_back(retrievedGNEConnection);
772         // check if previously this GNEConnections exists, and if true, remove it from myGNEConnections
773         std::vector<GNEConnection*>::iterator retrievedExists = std::find(myGNEConnections.begin(), myGNEConnections.end(), retrievedGNEConnection);
774         if (retrievedExists != myGNEConnections.end()) {
775             myGNEConnections.erase(retrievedExists);
776         } else {
777             // include reference to created GNEConnection
778             retrievedGNEConnection->incRef("GNEEdge::remakeGNEConnections");
779         }
780         // mark it as deprecated
781         retrievedGNEConnection->markConnectionGeometryDeprecated();
782     }
783     // delete non retrieved GNEConnections
784     for (auto it : myGNEConnections) {
785         // remove it from Tree
786         myNet->removeGLObjectFromGrid(it);
787         it->decRef();
788         if (it->unreferenced()) {
789             // show extra information for tests
790             WRITE_DEBUG("Deleting unreferenced " + it->getTagStr() + " '" + it->getID() + "' in rebuildGNEConnections()");
791             delete it;
792         }
793     }
794     // copy retrieved (existent and created) GNECrossigns to myGNEConnections
795     myGNEConnections = retrievedConnections;
796 }
797 
798 
799 void
clearGNEConnections()800 GNEEdge::clearGNEConnections() {
801     // Drop all existents connections that aren't referenced anymore
802     for (auto i : myGNEConnections) {
803         // check if connection is selected
804         if (i->isAttributeCarrierSelected()) {
805             i->unselectAttributeCarrier();
806         }
807         // remove it from Tree
808         myNet->removeGLObjectFromGrid(i);
809         // Dec reference of connection
810         i->decRef("GNEEdge::clearGNEConnections");
811         // Delete GNEConnectionToErase if is unreferenced
812         if (i->unreferenced()) {
813             // show extra information for tests
814             WRITE_DEBUG("Deleting unreferenced " + i->getTagStr() + " '" + i->getID() + "' in clearGNEConnections()");
815             delete i;
816         }
817     }
818     myGNEConnections.clear();
819 }
820 
821 
822 int
getRouteProbeRelativePosition(GNERouteProbe * routeProbe) const823 GNEEdge::getRouteProbeRelativePosition(GNERouteProbe* routeProbe) const {
824     std::vector<GNEAdditional*> routeProbes;
825     for (auto i : getAdditionalChilds()) {
826         if (i->getTagProperty().getTag() == routeProbe->getTagProperty().getTag()) {
827             routeProbes.push_back(i);
828         }
829     }
830     // return index of routeProbe in routeProbes vector
831     auto it = std::find(routeProbes.begin(), routeProbes.end(), routeProbe);
832     if (it == routeProbes.end()) {
833         return -1;
834     } else {
835         return (int)(it - routeProbes.begin());
836     }
837 }
838 
839 
840 std::vector<GNECrossing*>
getGNECrossings()841 GNEEdge::getGNECrossings() {
842     std::vector<GNECrossing*> crossings;
843     for (auto i : myGNEJunctionSource->getGNECrossings()) {
844         if (i->checkEdgeBelong(this)) {
845             crossings.push_back(i);
846         }
847     }
848     for (auto i : myGNEJunctionDestiny->getGNECrossings()) {
849         if (i->checkEdgeBelong(this)) {
850             crossings.push_back(i);
851         }
852     }
853     return crossings;
854 }
855 
856 
857 void
copyTemplate(GNEEdge * tpl,GNEUndoList * undoList)858 GNEEdge::copyTemplate(GNEEdge* tpl, GNEUndoList* undoList) {
859     undoList->p_begin("copy template");
860     setAttribute(SUMO_ATTR_NUMLANES,   tpl->getAttribute(SUMO_ATTR_NUMLANES),  undoList);
861     setAttribute(SUMO_ATTR_TYPE,       tpl->getAttribute(SUMO_ATTR_TYPE),     undoList);
862     setAttribute(SUMO_ATTR_PRIORITY,   tpl->getAttribute(SUMO_ATTR_PRIORITY), undoList);
863     setAttribute(SUMO_ATTR_SPREADTYPE, tpl->getAttribute(SUMO_ATTR_SPREADTYPE), undoList);
864     // copy raw values for lane-specific attributes
865     setAttribute(SUMO_ATTR_SPEED,      toString(myNBEdge.getSpeed()), undoList);
866     setAttribute(SUMO_ATTR_WIDTH,      toString(myNBEdge.getLaneWidth()), undoList);
867     setAttribute(SUMO_ATTR_ENDOFFSET,  toString(myNBEdge.getEndOffset()), undoList);
868     // copy lane attributes as well
869     for (int i = 0; i < (int)myLanes.size(); i++) {
870         myLanes[i]->setAttribute(SUMO_ATTR_ALLOW, tpl->myLanes[i]->getAttribute(SUMO_ATTR_ALLOW), undoList);
871         myLanes[i]->setAttribute(SUMO_ATTR_SPEED, tpl->myLanes[i]->getAttribute(SUMO_ATTR_SPEED), undoList);
872         myLanes[i]->setAttribute(SUMO_ATTR_WIDTH, tpl->myLanes[i]->getAttribute(SUMO_ATTR_WIDTH), undoList);
873         myLanes[i]->setAttribute(SUMO_ATTR_ENDOFFSET, tpl->myLanes[i]->getAttribute(SUMO_ATTR_ENDOFFSET), undoList);
874     }
875     undoList->p_end();
876 }
877 
878 
879 std::set<GUIGlID>
getLaneGlIDs()880 GNEEdge::getLaneGlIDs() {
881     std::set<GUIGlID> result;
882     for (auto i : myLanes) {
883         result.insert(i->getGlID());
884     }
885     return result;
886 }
887 
888 
889 const std::vector<GNELane*>&
getLanes()890 GNEEdge::getLanes() {
891     return myLanes;
892 }
893 
894 
895 const std::vector<GNEConnection*>&
getGNEConnections()896 GNEEdge::getGNEConnections() {
897     return myGNEConnections;
898 }
899 
900 
901 bool
wasSplit()902 GNEEdge::wasSplit() {
903     return myWasSplit;
904 }
905 
906 
907 std::string
getAttribute(SumoXMLAttr key) const908 GNEEdge::getAttribute(SumoXMLAttr key) const {
909     switch (key) {
910         case SUMO_ATTR_ID:
911             return getMicrosimID();
912         case SUMO_ATTR_FROM:
913             return myGNEJunctionSource->getMicrosimID();
914         case SUMO_ATTR_TO:
915             return myGNEJunctionDestiny->getMicrosimID();
916         case SUMO_ATTR_NUMLANES:
917             return toString(myNBEdge.getNumLanes());
918         case SUMO_ATTR_PRIORITY:
919             return toString(myNBEdge.getPriority());
920         case SUMO_ATTR_LENGTH:
921             return toString(myNBEdge.getFinalLength());
922         case SUMO_ATTR_TYPE:
923             return myNBEdge.getTypeID();
924         case SUMO_ATTR_SHAPE:
925             return toString(myNBEdge.getInnerGeometry());
926         case SUMO_ATTR_SPREADTYPE:
927             return toString(myNBEdge.getLaneSpreadFunction());
928         case SUMO_ATTR_NAME:
929             return myNBEdge.getStreetName();
930         case SUMO_ATTR_ALLOW:
931             return (getVehicleClassNames(myNBEdge.getPermissions()) + (myNBEdge.hasLaneSpecificPermissions() ? " (combined!)" : ""));
932         case SUMO_ATTR_DISALLOW: {
933             return (getVehicleClassNames(invertPermissions(myNBEdge.getPermissions())) + (myNBEdge.hasLaneSpecificPermissions() ? " (combined!)" : ""));
934         }
935         case SUMO_ATTR_SPEED:
936             if (myNBEdge.hasLaneSpecificSpeed()) {
937                 return "lane specific";
938             } else {
939                 return toString(myNBEdge.getSpeed());
940             }
941         case SUMO_ATTR_WIDTH:
942             if (myNBEdge.hasLaneSpecificWidth()) {
943                 return "lane specific";
944             } else {
945                 return toString(myNBEdge.getLaneWidth());
946             }
947         case SUMO_ATTR_ENDOFFSET:
948             if (myNBEdge.hasLaneSpecificEndOffset()) {
949                 return "lane specific";
950             } else {
951                 return toString(myNBEdge.getEndOffset());
952             }
953         case GNE_ATTR_MODIFICATION_STATUS:
954             return myConnectionStatus;
955         case GNE_ATTR_SHAPE_START:
956             if (myNBEdge.getGeometry().front() == myGNEJunctionSource->getPositionInView()) {
957                 return "";
958             } else {
959                 return toString(myNBEdge.getGeometry().front());
960             }
961         case GNE_ATTR_SHAPE_END:
962             if (myNBEdge.getGeometry().back() == myGNEJunctionDestiny->getPositionInView()) {
963                 return "";
964             } else {
965                 return toString(myNBEdge.getGeometry().back());
966             }
967         case GNE_ATTR_BIDIR:
968             return toString(myNBEdge.isBidiRail());
969         case GNE_ATTR_SELECTED:
970             return toString(isAttributeCarrierSelected());
971         case GNE_ATTR_GENERIC:
972             return getGenericParametersStr();
973         default:
974             throw InvalidArgument(getTagStr() + " doesn't have an attribute of type '" + toString(key) + "'");
975     }
976 }
977 
978 std::string
getAttributeForSelection(SumoXMLAttr key) const979 GNEEdge::getAttributeForSelection(SumoXMLAttr key) const {
980     std::string result = getAttribute(key);
981     if ((key == SUMO_ATTR_ALLOW || key == SUMO_ATTR_DISALLOW) && result.find("all") != std::string::npos) {
982         result += " " + getVehicleClassNames(SVCAll, true);
983     }
984     return result;
985 }
986 
987 void
setAttribute(SumoXMLAttr key,const std::string & value,GNEUndoList * undoList)988 GNEEdge::setAttribute(SumoXMLAttr key, const std::string& value, GNEUndoList* undoList) {
989     switch (key) {
990         case SUMO_ATTR_WIDTH:
991         case SUMO_ATTR_ENDOFFSET:
992         case SUMO_ATTR_SPEED:
993         case SUMO_ATTR_ALLOW:
994         case SUMO_ATTR_DISALLOW: {
995             undoList->p_begin("change " + getTagStr() + " attribute");
996             const std::string origValue = myLanes.at(0)->getAttribute(key); // will have intermediate value of "lane specific"
997             // lane specific attributes need to be changed via lanes to allow undo
998             for (auto it : myLanes) {
999                 it->setAttribute(key, value, undoList);
1000             }
1001             // ensure that the edge value is also changed. Actually this sets the lane attributes again but it does not matter
1002             undoList->p_add(new GNEChange_Attribute(this, myNet, key, value, true, origValue));
1003             undoList->p_end();
1004             break;
1005         }
1006         case SUMO_ATTR_FROM: {
1007             undoList->p_begin("change  " + getTagStr() + "  attribute");
1008             // Remove edge from crossings of junction source
1009             removeEdgeFromCrossings(myGNEJunctionSource, undoList);
1010             // continue changing from junction
1011             GNEJunction* oldGNEJunctionSource = myGNEJunctionSource;
1012             myGNEJunctionSource->setLogicValid(false, undoList);
1013             undoList->p_add(new GNEChange_Attribute(this, myNet, key, value));
1014             myGNEJunctionSource->setLogicValid(false, undoList);
1015             myNet->retrieveJunction(value)->setLogicValid(false, undoList);
1016             setAttribute(GNE_ATTR_SHAPE_START, toString(myGNEJunctionSource->getNBNode()->getPosition()), undoList);
1017             myGNEJunctionSource->invalidateShape();
1018             undoList->p_end();
1019             // update geometries of all implicated junctions
1020             oldGNEJunctionSource->updateGeometry(true);
1021             myGNEJunctionSource->updateGeometry(true);
1022             myGNEJunctionDestiny->updateGeometry(true);
1023             break;
1024         }
1025         case SUMO_ATTR_TO: {
1026             undoList->p_begin("change  " + getTagStr() + "  attribute");
1027             // Remove edge from crossings of junction destiny
1028             removeEdgeFromCrossings(myGNEJunctionDestiny, undoList);
1029             // continue changing destiny junction
1030             GNEJunction* oldGNEJunctionDestiny = myGNEJunctionDestiny;
1031             myGNEJunctionDestiny->setLogicValid(false, undoList);
1032             undoList->p_add(new GNEChange_Attribute(this, myNet, key, value));
1033             myGNEJunctionDestiny->setLogicValid(false, undoList);
1034             myNet->retrieveJunction(value)->setLogicValid(false, undoList);
1035             setAttribute(GNE_ATTR_SHAPE_END, toString(myGNEJunctionDestiny->getNBNode()->getPosition()), undoList);
1036             myGNEJunctionDestiny->invalidateShape();
1037             undoList->p_end();
1038             // update geometries of all implicated junctions
1039             oldGNEJunctionDestiny->updateGeometry(true);
1040             myGNEJunctionDestiny->updateGeometry(true);
1041             myGNEJunctionSource->updateGeometry(true);
1042             break;
1043         }
1044         case SUMO_ATTR_ID:
1045         case SUMO_ATTR_PRIORITY:
1046         case SUMO_ATTR_LENGTH:
1047         case SUMO_ATTR_TYPE:
1048         case SUMO_ATTR_SPREADTYPE:
1049         case GNE_ATTR_MODIFICATION_STATUS:
1050         case GNE_ATTR_SHAPE_START:
1051         case GNE_ATTR_SHAPE_END:
1052         case GNE_ATTR_SELECTED:
1053         case GNE_ATTR_GENERIC:
1054             undoList->p_add(new GNEChange_Attribute(this, myNet, key, value));
1055             break;
1056         case SUMO_ATTR_NAME:
1057             // user cares about street names. Make sure they appear in the output
1058             OptionsCont::getOptions().resetWritable();
1059             OptionsCont::getOptions().set("output.street-names", "true");
1060             undoList->p_add(new GNEChange_Attribute(this, myNet, key, value));
1061             break;
1062         case SUMO_ATTR_NUMLANES:
1063             if (value != getAttribute(key)) {
1064                 // Remove edge from crossings of junction source
1065                 removeEdgeFromCrossings(myGNEJunctionSource, undoList);
1066                 // Remove edge from crossings of junction destiny
1067                 removeEdgeFromCrossings(myGNEJunctionDestiny, undoList);
1068                 // set num lanes
1069                 setNumLanes(parse<int>(value), undoList);
1070             }
1071             break;
1072         case SUMO_ATTR_SHAPE:
1073             // @note: assumes value of inner geometry!
1074             // actually the geometry is already updated (incrementally
1075             // during mouse movement). We set the restore point to the end
1076             // of the last change-set
1077             undoList->p_add(new GNEChange_Attribute(this, myNet, key, value));
1078             break;
1079         case GNE_ATTR_BIDIR:
1080             throw InvalidArgument("Attribute of '" + toString(key) + "' cannot be modified");
1081         default:
1082             throw InvalidArgument(getTagStr() + " doesn't have an attribute of type '" + toString(key) + "'");
1083     }
1084 }
1085 
1086 
1087 bool
isValid(SumoXMLAttr key,const std::string & value)1088 GNEEdge::isValid(SumoXMLAttr key, const std::string& value) {
1089     switch (key) {
1090         case SUMO_ATTR_ID:
1091             return SUMOXMLDefinitions::isValidNetID(value) && (myNet->retrieveEdge(value, false) == nullptr);
1092         case SUMO_ATTR_FROM: {
1093             // check that is a valid ID and is different of ID of junction destiny
1094             if (SUMOXMLDefinitions::isValidNetID(value) && (value != myGNEJunctionDestiny->getMicrosimID())) {
1095                 GNEJunction* junctionFrom = myNet->retrieveJunction(value, false);
1096                 // check that there isn't already another edge with the same From and To Edge
1097                 if ((junctionFrom != nullptr) && (myNet->retrieveEdge(junctionFrom, myGNEJunctionDestiny, false) == nullptr)) {
1098                     return true;
1099                 } else {
1100                     return false;
1101                 }
1102             } else {
1103                 return false;
1104             }
1105         }
1106         case SUMO_ATTR_TO: {
1107             // check that is a valid ID and is different of ID of junction Source
1108             if (SUMOXMLDefinitions::isValidNetID(value) && (value != myGNEJunctionSource->getMicrosimID())) {
1109                 GNEJunction* junctionTo = myNet->retrieveJunction(value, false);
1110                 // check that there isn't already another edge with the same From and To Edge
1111                 if ((junctionTo != nullptr) && (myNet->retrieveEdge(myGNEJunctionSource, junctionTo, false) == nullptr)) {
1112                     return true;
1113                 } else {
1114                     return false;
1115                 }
1116             } else {
1117                 return false;
1118             }
1119         }
1120         case SUMO_ATTR_SPEED:
1121             return canParse<double>(value) && (parse<double>(value) > 0);
1122         case SUMO_ATTR_NUMLANES:
1123             return canParse<int>(value) && (parse<double>(value) > 0);
1124         case SUMO_ATTR_PRIORITY:
1125             return canParse<int>(value);
1126         case SUMO_ATTR_LENGTH:
1127             return canParse<double>(value) && ((parse<double>(value) > 0) || (parse<double>(value) == NBEdge::UNSPECIFIED_LOADED_LENGTH));
1128         case SUMO_ATTR_ALLOW:
1129         case SUMO_ATTR_DISALLOW:
1130             return canParseVehicleClasses(value);
1131         case SUMO_ATTR_TYPE:
1132             return true;
1133         case SUMO_ATTR_SHAPE:
1134             // empty shapes are allowed
1135             return canParse<PositionVector>(value);
1136         case SUMO_ATTR_SPREADTYPE:
1137             return SUMOXMLDefinitions::LaneSpreadFunctions.hasString(value);
1138         case SUMO_ATTR_NAME:
1139             return true;
1140         case SUMO_ATTR_WIDTH:
1141             return canParse<double>(value) && ((parse<double>(value) > 0) || (parse<double>(value) == NBEdge::UNSPECIFIED_WIDTH));
1142         case SUMO_ATTR_ENDOFFSET:
1143             return canParse<double>(value) && parse<double>(value) >= 0 && parse<double>(value) < myNBEdge.getLoadedLength();
1144         case GNE_ATTR_SHAPE_START: {
1145             if (value.empty()) {
1146                 return true;
1147             } else if (canParse<Position>(value)) {
1148                 Position shapeStart = parse<Position>(value);
1149                 return (shapeStart != myNBEdge.getGeometry()[-1]);
1150             } else {
1151                 return false;
1152             }
1153         }
1154         case GNE_ATTR_SHAPE_END: {
1155             if (value.empty()) {
1156                 return true;
1157             } else if (canParse<Position>(value)) {
1158                 Position shapeEnd = parse<Position>(value);
1159                 return (shapeEnd != myNBEdge.getGeometry()[0]);
1160             } else {
1161                 return false;
1162             }
1163         }
1164         case GNE_ATTR_BIDIR:
1165             return false;
1166         case GNE_ATTR_SELECTED:
1167             return canParse<bool>(value);
1168         case GNE_ATTR_GENERIC:
1169             return isGenericParametersValid(value);
1170         default:
1171             throw InvalidArgument(getTagStr() + " doesn't have an attribute of type '" + toString(key) + "'");
1172     }
1173 }
1174 
1175 
1176 std::string
getGenericParametersStr() const1177 GNEEdge::getGenericParametersStr() const {
1178     std::string result;
1179     // Generate an string using the following structure: "key1=value1|key2=value2|...
1180     for (auto i : myNBEdge.getParametersMap()) {
1181         result += i.first + "=" + i.second + "|";
1182     }
1183     // remove the last "|"
1184     if (!result.empty()) {
1185         result.pop_back();
1186     }
1187     return result;
1188 }
1189 
1190 
1191 std::vector<std::pair<std::string, std::string> >
getGenericParameters() const1192 GNEEdge::getGenericParameters() const {
1193     std::vector<std::pair<std::string, std::string> >  result;
1194     // iterate over parameters map and fill result
1195     for (auto i : myNBEdge.getParametersMap()) {
1196         result.push_back(std::make_pair(i.first, i.second));
1197     }
1198     return result;
1199 }
1200 
1201 
1202 void
setGenericParametersStr(const std::string & value)1203 GNEEdge::setGenericParametersStr(const std::string& value) {
1204     // clear parameters
1205     myNBEdge.clearParameter();
1206     // separate value in a vector of string using | as separator
1207     std::vector<std::string> parsedValues;
1208     StringTokenizer stValues(value, "|", true);
1209     while (stValues.hasNext()) {
1210         parsedValues.push_back(stValues.next());
1211     }
1212     // check that parsed values (A=B)can be parsed in generic parameters
1213     for (auto i : parsedValues) {
1214         std::vector<std::string> parsedParameters;
1215         StringTokenizer stParam(i, "=", true);
1216         while (stParam.hasNext()) {
1217             parsedParameters.push_back(stParam.next());
1218         }
1219         // Check that parsed parameters are exactly two and contains valid chracters
1220         if (parsedParameters.size() == 2 && SUMOXMLDefinitions::isValidGenericParameterKey(parsedParameters.front()) && SUMOXMLDefinitions::isValidGenericParameterValue(parsedParameters.back())) {
1221             myNBEdge.setParameter(parsedParameters.front(), parsedParameters.back());
1222         }
1223     }
1224 }
1225 
1226 
1227 void
setResponsible(bool newVal)1228 GNEEdge::setResponsible(bool newVal) {
1229     myAmResponsible = newVal;
1230 }
1231 
1232 // ===========================================================================
1233 // private
1234 // ===========================================================================
1235 
1236 void
setAttribute(SumoXMLAttr key,const std::string & value)1237 GNEEdge::setAttribute(SumoXMLAttr key, const std::string& value) {
1238     switch (key) {
1239         case SUMO_ATTR_ID:
1240             myNet->renameEdge(this, value);
1241             break;
1242         case SUMO_ATTR_FROM:
1243             myNet->changeEdgeEndpoints(this, value, myGNEJunctionDestiny->getMicrosimID());
1244             // update this edge of list of outgoings edges of the old GNEJunctionSource
1245             myGNEJunctionSource->removeOutgoingGNEEdge(this);
1246             // update GNEJunctionSource
1247             myGNEJunctionSource = myNet->retrieveJunction(myNBEdge.getFromNode()->getID());
1248             // update this edge of list of outgoings edges of the new GNEJunctionSource
1249             myGNEJunctionSource->addOutgoingGNEEdge(this);
1250             break;
1251         case SUMO_ATTR_TO:
1252             myNet->changeEdgeEndpoints(this, myGNEJunctionSource->getMicrosimID(), value);
1253             // update this edge of list of incomings edges of the old GNEJunctionDestiny
1254             myGNEJunctionDestiny->removeIncomingGNEEdge(this);
1255             // update GNEJunctionDestiny
1256             myGNEJunctionDestiny = myNet->retrieveJunction(myNBEdge.getToNode()->getID());
1257             // update this edge of list of incomings edges of the new GNEJunctionDestiny
1258             myGNEJunctionDestiny->addIncomingGNEEdge(this);
1259             break;
1260         case SUMO_ATTR_NUMLANES:
1261             throw InvalidArgument("GNEEdge::setAttribute (private) called for attr SUMO_ATTR_NUMLANES. This should never happen");
1262             break;
1263         case SUMO_ATTR_PRIORITY:
1264             myNBEdge.myPriority = parse<int>(value);
1265             break;
1266         case SUMO_ATTR_LENGTH:
1267             myNBEdge.setLoadedLength(parse<double>(value));
1268             break;
1269         case SUMO_ATTR_TYPE:
1270             myNBEdge.myType = value;
1271             break;
1272         case SUMO_ATTR_SHAPE:
1273             setGeometry(parse<PositionVector>(value), true, true);
1274             break;
1275         case SUMO_ATTR_SPREADTYPE:
1276             myNBEdge.setLaneSpreadFunction(SUMOXMLDefinitions::LaneSpreadFunctions.get(value));
1277             break;
1278         case SUMO_ATTR_NAME:
1279             myNBEdge.setStreetName(value);
1280             break;
1281         case SUMO_ATTR_SPEED:
1282             myNBEdge.setSpeed(-1, parse<double>(value));
1283             break;
1284         case SUMO_ATTR_WIDTH:
1285             myNBEdge.setLaneWidth(-1, parse<double>(value));
1286             break;
1287         case SUMO_ATTR_ENDOFFSET:
1288             myNBEdge.setEndOffset(-1, parse<double>(value));
1289             break;
1290         case SUMO_ATTR_ALLOW:
1291             break;  // no edge value
1292         case SUMO_ATTR_DISALLOW:
1293             break; // no edge value
1294         case GNE_ATTR_MODIFICATION_STATUS:
1295             myConnectionStatus = value;
1296             if (value == FEATURE_GUESSED) {
1297                 WRITE_DEBUG("invalidating (removing) connections of edge '" + getID() + "' due it were guessed");
1298                 myNBEdge.invalidateConnections(true);
1299                 clearGNEConnections();
1300             } else if (value != FEATURE_GUESSED) {
1301                 WRITE_DEBUG("declaring connections of edge '" + getID() + "' as loaded (It will not be removed)");
1302                 myNBEdge.declareConnectionsAsLoaded();
1303             }
1304             break;
1305         case GNE_ATTR_SHAPE_START: {
1306             // get geometry of NBEdge, remove FIRST element with the new value (or with the Junction Source position) and set it back to edge
1307             Position newShapeStart;
1308             if (value == "") {
1309                 newShapeStart = myGNEJunctionSource->getPositionInView();
1310             } else {
1311                 newShapeStart = parse<Position>(value);
1312             }
1313             // set shape start position
1314             setShapeStartPos(newShapeStart, true);
1315             break;
1316         }
1317         case GNE_ATTR_SHAPE_END: {
1318             // get geometry of NBEdge, remove LAST element with the new value (or with the Junction Destiny position) and set it back to edge
1319             Position newShapeEnd;
1320             if (value == "") {
1321                 newShapeEnd = myGNEJunctionDestiny->getPositionInView();
1322             } else {
1323                 newShapeEnd = parse<Position>(value);
1324             }
1325             // set shape end position
1326             setShapeEndPos(newShapeEnd, true);
1327             break;
1328         }
1329         case GNE_ATTR_BIDIR:
1330             throw InvalidArgument("Attribute of '" + toString(key) + "' cannot be modified");
1331         case GNE_ATTR_SELECTED:
1332             if (parse<bool>(value)) {
1333                 selectAttributeCarrier();
1334             } else {
1335                 unselectAttributeCarrier();
1336             }
1337             break;
1338         case GNE_ATTR_GENERIC:
1339             setGenericParametersStr(value);
1340             break;
1341         default:
1342             throw InvalidArgument(getTagStr() + " doesn't have an attribute of type '" + toString(key) + "'");
1343     }
1344     // check if updated attribute requieres update geometry
1345     if (myTagProperty.hasAttribute(key) && myTagProperty.getAttributeProperties(key).requiereUpdateGeometry()) {
1346         updateGeometry(true);
1347     }
1348 }
1349 
1350 
1351 void
setNumLanes(int numLanes,GNEUndoList * undoList)1352 GNEEdge::setNumLanes(int numLanes, GNEUndoList* undoList) {
1353     undoList->p_begin("change number of " + toString(SUMO_TAG_LANE) +  "s");
1354     myGNEJunctionSource->setLogicValid(false, undoList);
1355     myGNEJunctionDestiny->setLogicValid(false, undoList);
1356 
1357     const int oldNumLanes = (int)myLanes.size();
1358     for (int i = oldNumLanes; i < numLanes; i++) {
1359         // since the GNELane does not exist yet, it cannot have yet been referenced so we only pass a zero-pointer
1360         undoList->add(new GNEChange_Lane(this, nullptr,
1361                                          myNBEdge.getLaneStruct(oldNumLanes - 1), true), true);
1362     }
1363     for (int i = oldNumLanes - 1; i > numLanes - 1; i--) {
1364         // delete leftmost lane
1365         undoList->add(new GNEChange_Lane(this, myLanes[i], myNBEdge.getLaneStruct(i), false), true);
1366     }
1367     undoList->p_end();
1368 }
1369 
1370 
1371 void
addLane(GNELane * lane,const NBEdge::Lane & laneAttrs,bool recomputeConnections)1372 GNEEdge::addLane(GNELane* lane, const NBEdge::Lane& laneAttrs, bool recomputeConnections) {
1373     // boundary of edge depends of number of lanes. We need to extract if before add or remove lane
1374     myNet->removeGLObjectFromGrid(this);
1375     const int index = lane ? lane->getIndex() : myNBEdge.getNumLanes();
1376     // the laneStruct must be created first to ensure we have some geometry
1377     // unless the connections are fully recomputed, existing indices must be shifted
1378     myNBEdge.addLane(index, true, recomputeConnections, !recomputeConnections);
1379     if (lane) {
1380         // restore a previously deleted lane
1381         myLanes.insert(myLanes.begin() + index, lane);
1382 
1383     } else {
1384         // create a new lane by copying leftmost lane
1385         lane = new GNELane(*this, index);
1386         myLanes.push_back(lane);
1387     }
1388     lane->incRef("GNEEdge::addLane");
1389     // check if lane is selected
1390     if (lane->isAttributeCarrierSelected()) {
1391         lane->selectAttributeCarrier();
1392     }
1393     // we copy all attributes except shape since this is recomputed from edge shape
1394     myNBEdge.setSpeed(lane->getIndex(), laneAttrs.speed);
1395     myNBEdge.setPermissions(laneAttrs.permissions, lane->getIndex());
1396     myNBEdge.setPreferredVehicleClass(laneAttrs.preferred, lane->getIndex());
1397     myNBEdge.setEndOffset(lane->getIndex(), laneAttrs.endOffset);
1398     myNBEdge.setLaneWidth(lane->getIndex(), laneAttrs.width);
1399     // udate indices
1400     for (int i = 0; i < (int)myLanes.size(); ++i) {
1401         myLanes[i]->setIndex(i);
1402     }
1403     /* while technically correct, this looks ugly
1404     myGNEJunctionSource->invalidateShape();
1405     myGNEJunctionDestiny->invalidateShape();
1406     */
1407     // Remake connections for this edge and all edges that target this lane
1408     remakeGNEConnections();
1409     // remake connections of all edges of junction source and destiny
1410     for (auto i : myGNEJunctionSource->getGNEEdges()) {
1411         i->remakeGNEConnections();
1412     }
1413     // remake connections of all edges of junction source and destiny
1414     for (auto i : myGNEJunctionDestiny->getGNEEdges()) {
1415         i->remakeGNEConnections();
1416     }
1417     // add object again
1418     myNet->addGLObjectIntoGrid(this);
1419     // Update geometry with the new lane
1420     updateGeometry(true);
1421 }
1422 
1423 
1424 void
removeLane(GNELane * lane,bool recomputeConnections)1425 GNEEdge::removeLane(GNELane* lane, bool recomputeConnections) {
1426     // boundary of edge depends of number of lanes. We need to extract if before add or remove lane
1427     myNet->removeGLObjectFromGrid(this);
1428     if (myLanes.size() == 0) {
1429         throw ProcessError("Should not remove the last " + toString(SUMO_TAG_LANE) + " from an " + getTagStr());
1430     }
1431     if (lane == nullptr) {
1432         lane = myLanes.back();
1433     }
1434     // check if lane is selected
1435     if (lane->isAttributeCarrierSelected()) {
1436         lane->unselectAttributeCarrier();
1437     }
1438     // Delete lane of edge's container
1439     // unless the connections are fully recomputed, existing indices must be shifted
1440     myNBEdge.deleteLane(lane->getIndex(), recomputeConnections, !recomputeConnections);
1441     lane->decRef("GNEEdge::removeLane");
1442     myLanes.erase(myLanes.begin() + lane->getIndex());
1443     // Delete lane if is unreferenced
1444     if (lane->unreferenced()) {
1445         // show extra information for tests
1446         WRITE_DEBUG("Deleting unreferenced " + lane->getTagStr() + " '" + lane->getID() + "' in removeLane()");
1447         delete lane;
1448     }
1449     // udate indices
1450     for (int i = 0; i < (int)myLanes.size(); ++i) {
1451         myLanes[i]->setIndex(i);
1452     }
1453     /* while technically correct, this looks ugly
1454     myGNEJunctionSource->invalidateShape();
1455     myGNEJunctionDestiny->invalidateShape();
1456     */
1457     // Remake connections of this edge
1458     remakeGNEConnections();
1459     // remake connections of all edges of junction source and destiny
1460     for (auto i : myGNEJunctionSource->getGNEEdges()) {
1461         i->remakeGNEConnections();
1462     }
1463     // remake connections of all edges of junction source and destiny
1464     for (auto i : myGNEJunctionDestiny->getGNEEdges()) {
1465         i->remakeGNEConnections();
1466     }
1467     // add object again
1468     myNet->addGLObjectIntoGrid(this);
1469     // Update element
1470     updateGeometry(true);
1471 }
1472 
1473 
1474 void
addConnection(NBEdge::Connection nbCon,bool selectAfterCreation)1475 GNEEdge::addConnection(NBEdge::Connection nbCon, bool selectAfterCreation) {
1476     // If a new connection was sucesfully created
1477     if (myNBEdge.setConnection(nbCon.fromLane, nbCon.toEdge, nbCon.toLane, NBEdge::L2L_USER, true, nbCon.mayDefinitelyPass,
1478                                nbCon.keepClear, nbCon.contPos, nbCon.visibility,
1479                                nbCon.speed, nbCon.customShape, nbCon.uncontrolled)) {
1480         // Create  or retrieve existent GNEConection
1481         GNEConnection* con = retrieveGNEConnection(nbCon.fromLane, nbCon.toEdge, nbCon.toLane);
1482         // add it to GNEConnection container
1483         myGNEConnections.push_back(con);
1484         // Add reference
1485         myGNEConnections.back()->incRef("GNEEdge::addConnection");
1486         // select GNEConnection if needed
1487         if (selectAfterCreation) {
1488             con->selectAttributeCarrier();
1489         }
1490         // update geometry
1491         con->updateGeometry(true);
1492         // iterate over all additionals from "from" lane and check E2 multilane integrity
1493         for (auto i : con->getLaneFrom()->getAdditionalChilds()) {
1494             if (i->getTagProperty().getTag() == SUMO_TAG_E2DETECTOR_MULTILANE) {
1495                 dynamic_cast<GNEDetectorE2*>(i)->checkE2MultilaneIntegrity();
1496             }
1497         }
1498         // iterate over all additionals from "to" lane and check E2 multilane integrity
1499         for (auto i : con->getLaneTo()->getAdditionalChilds()) {
1500             if (i->getTagProperty().getTag() == SUMO_TAG_E2DETECTOR_MULTILANE) {
1501                 dynamic_cast<GNEDetectorE2*>(i)->checkE2MultilaneIntegrity();
1502             }
1503         }
1504     }
1505     // actually we only do this to force a redraw
1506     updateGeometry(true);
1507 }
1508 
1509 
1510 void
removeConnection(NBEdge::Connection nbCon)1511 GNEEdge::removeConnection(NBEdge::Connection nbCon) {
1512     // check if is a explicit turnaround
1513     if (nbCon.toEdge == myNBEdge.getTurnDestination()) {
1514         myNet->removeExplicitTurnaround(getMicrosimID());
1515     }
1516     // remove NBEdge::connection from NBEdge
1517     myNBEdge.removeFromConnections(nbCon);
1518     // remove their associated GNEConnection
1519     GNEConnection* con = retrieveGNEConnection(nbCon.fromLane, nbCon.toEdge, nbCon.toLane, false);
1520     if (con != nullptr) {
1521         con->decRef("GNEEdge::removeConnection");
1522         myGNEConnections.erase(std::find(myGNEConnections.begin(), myGNEConnections.end(), con));
1523         // iterate over all additionals from "from" lane and check E2 multilane integrity
1524         for (auto i : con->getLaneFrom()->getAdditionalChilds()) {
1525             if (i->getTagProperty().getTag() == SUMO_TAG_E2DETECTOR_MULTILANE) {
1526                 dynamic_cast<GNEDetectorE2*>(i)->checkE2MultilaneIntegrity();
1527             }
1528         }
1529         // iterate over all additionals from "to" lane and check E2 multilane integrity
1530         for (auto i : con->getLaneTo()->getAdditionalChilds()) {
1531             if (i->getTagProperty().getTag() == SUMO_TAG_E2DETECTOR_MULTILANE) {
1532                 dynamic_cast<GNEDetectorE2*>(i)->checkE2MultilaneIntegrity();
1533             }
1534         }
1535         // remove it from Tree
1536         myNet->removeGLObjectFromGrid(con);
1537         // check if connection is selected
1538         if (con->isAttributeCarrierSelected()) {
1539             con->unselectAttributeCarrier();
1540         }
1541         if (con->unreferenced()) {
1542             // show extra information for tests
1543             WRITE_DEBUG("Deleting unreferenced " + con->getTagStr() + " '" + con->getID() + "' in removeConnection()");
1544             delete con;
1545             // actually we only do this to force a redraw
1546             updateGeometry(true);
1547         }
1548     }
1549 }
1550 
1551 
1552 GNEConnection*
retrieveGNEConnection(int fromLane,NBEdge * to,int toLane,bool createIfNoExist)1553 GNEEdge::retrieveGNEConnection(int fromLane, NBEdge* to, int toLane, bool createIfNoExist) {
1554     for (auto i : myGNEConnections) {
1555         if ((i->getFromLaneIndex() == fromLane) && (i->getEdgeTo()->getNBEdge() == to) && (i->getToLaneIndex() == toLane)) {
1556             return i;
1557         }
1558     }
1559     if (createIfNoExist) {
1560         // create new connection. Will be added to the rTree on first geometry computation
1561         GNEConnection* createdConnection = new GNEConnection(myLanes[fromLane], myNet->retrieveEdge(to->getID())->getLanes()[toLane]);
1562         // show extra information for tests
1563         WRITE_DEBUG("Created " + createdConnection->getTagStr() + " '" + createdConnection->getID() + "' in retrieveGNEConnection()");
1564         // insert it in Tree
1565         myNet->addGLObjectIntoGrid(createdConnection);
1566         // iterate over all additionals from "from" lane and check E2 multilane integrity
1567         for (auto i : createdConnection->getLaneFrom()->getAdditionalChilds()) {
1568             if (i->getTagProperty().getTag() == SUMO_TAG_E2DETECTOR_MULTILANE) {
1569                 dynamic_cast<GNEDetectorE2*>(i)->checkE2MultilaneIntegrity();
1570             }
1571         }
1572         // iterate over all additionals from "to" lane and check E2 multilane integrity
1573         for (auto i : createdConnection->getLaneTo()->getAdditionalChilds()) {
1574             if (i->getTagProperty().getTag() == SUMO_TAG_E2DETECTOR_MULTILANE) {
1575                 dynamic_cast<GNEDetectorE2*>(i)->checkE2MultilaneIntegrity();
1576             }
1577         }
1578         return createdConnection;
1579     } else {
1580         return nullptr;
1581     }
1582 }
1583 
1584 
1585 
1586 void
setMicrosimID(const std::string & newID)1587 GNEEdge::setMicrosimID(const std::string& newID) {
1588     GUIGlObject::setMicrosimID(newID);
1589     for (auto i : myLanes) {
1590         i->setMicrosimID(getNBEdge()->getLaneID(i->getIndex()));
1591     }
1592 }
1593 
1594 
1595 bool
hasRestrictedLane(SUMOVehicleClass vclass) const1596 GNEEdge::hasRestrictedLane(SUMOVehicleClass vclass) const {
1597     for (auto i : myLanes) {
1598         if (i->isRestricted(vclass)) {
1599             return true;
1600         }
1601     }
1602     return false;
1603 }
1604 
1605 
1606 void
removeEdgeFromCrossings(GNEJunction * junction,GNEUndoList * undoList)1607 GNEEdge::removeEdgeFromCrossings(GNEJunction* junction, GNEUndoList* undoList) {
1608     // Remove all crossings that contain this edge in parameter "edges"
1609     for (GNECrossing* const i : junction->getGNECrossings()) {
1610         if (i->checkEdgeBelong(this)) {
1611             myNet->deleteCrossing(i, undoList);
1612         }
1613     }
1614 }
1615 
1616 
1617 void
straightenElevation(GNEUndoList * undoList)1618 GNEEdge::straightenElevation(GNEUndoList* undoList) {
1619     PositionVector modifiedShape = myNBEdge.getGeometry().interpolateZ(
1620                                        myNBEdge.getFromNode()->getPosition().z(),
1621                                        myNBEdge.getToNode()->getPosition().z());
1622     PositionVector innerShape(modifiedShape.begin() + 1, modifiedShape.end() - 1);
1623     setAttribute(SUMO_ATTR_SHAPE, toString(innerShape), undoList);
1624 }
1625 
1626 
1627 PositionVector
smoothShape(const PositionVector & old,bool forElevation)1628 GNEEdge::smoothShape(const PositionVector& old, bool forElevation) {
1629     const OptionsCont& oc = OptionsCont::getOptions();
1630     // distinguish 3 cases:
1631     // a) if the edge has exactly 3 or 4 points, use these as control points
1632     // b) if the edge has more than 4 points, use the first 2 and the last 2 as control points
1633     // c) if the edge is straight and both nodes are geometry-like nodes, use geometry of the continuation edges as control points
1634     PositionVector init;
1635 #ifdef DEBUG_SMOOTH_GEOM
1636     if (DEBUGCOND(this)) std::cout << getID()
1637                                        << " forElevation=" << forElevation
1638                                        << " fromGeometryLike=" << myNBEdge.getFromNode()->geometryLike()
1639                                        << " toGeometryLike=" << myNBEdge.getToNode()->geometryLike()
1640                                        << " smoothShape old=" << old << "\n";
1641 #endif
1642     if (old.size() == 3 || old.size() == 4) {
1643         init = old;
1644     } else if (old.size() > 4 && !forElevation) {
1645         // for elevation, the initial segments are not useful
1646         init.push_back(old[0]);
1647         init.push_back(old[1]);
1648         init.push_back(old[-2]);
1649         init.push_back(old[-1]);
1650     } else if (myNBEdge.getFromNode()->geometryLike() && myNBEdge.getToNode()->geometryLike()) {
1651         PositionVector begShape;
1652         PositionVector endShape;
1653         const EdgeVector& incoming = myNBEdge.getFromNode()->getIncomingEdges();
1654         const EdgeVector& outgoing = myNBEdge.getToNode()->getOutgoingEdges();
1655         if (incoming.size() == 1) {
1656             begShape = incoming[0]->getGeometry();
1657         } else {
1658             assert(incoming.size() == 2);
1659             begShape = myNBEdge.isTurningDirectionAt(incoming[0]) ? incoming[1]->getGeometry() : incoming[0]->getGeometry();
1660         }
1661         if (outgoing.size() == 1) {
1662             endShape = outgoing[0]->getGeometry();
1663         } else {
1664             assert(outgoing.size() == 2);
1665             endShape = myNBEdge.isTurningDirectionAt(outgoing[0]) ? outgoing[1]->getGeometry() : outgoing[0]->getGeometry();
1666         }
1667         const double dist = MIN2(old.length2D(), MAX2(old.length2D() / 8, fabs(old[0].z() - old[-1].z()) * OptionsCont::getOptions().getFloat("geometry.max-grade") / 3));
1668         if (forElevation) {
1669             // initialize control point elevation for smooth continuation
1670             init.push_back(old[0]);
1671             init.push_back(old.positionAtOffset2D(dist));
1672             init.push_back(old.positionAtOffset2D(old.length2D() - dist));
1673             init.push_back(old[-1]);
1674             double begZ = begShape.positionAtOffset2D(MAX2(0.0, begShape.length2D() - dist)).z();
1675             double endZ = endShape.positionAtOffset2D(MIN2(begShape.length2D(), dist)).z();
1676             // continue incline
1677             init[1].setz(2 * init[0].z() - begZ);
1678             init[2].setz(2 * init[-1].z() - endZ);
1679         } else {
1680             bool ok = true;
1681             const double straightThresh = DEG2RAD(oc.getFloat("opendrive-output.straight-threshold"));
1682             init = NBNode::bezierControlPoints(begShape, endShape, false, dist, dist, ok, nullptr, straightThresh);
1683         }
1684 #ifdef DEBUG_SMOOTH_GEOM
1685         if (DEBUGCOND(this)) {
1686             std::cout << "   begShape=" << begShape << " endShape=" << endShape << " forElevation=" << forElevation << " dist=" << dist << " ok=" << ok << " init=" << init << "\n";
1687         }
1688 #endif
1689     }
1690     if (init.size() == 0) {
1691         return PositionVector::EMPTY;
1692     } else {
1693         const int numPoints = MAX2(oc.getInt("junctions.internal-link-detail"),
1694                                    int(old.length2D() / oc.getFloat("opendrive.curve-resolution")));
1695         return init.bezier(numPoints);
1696     }
1697 }
1698 
1699 
1700 void
smooth(GNEUndoList * undoList)1701 GNEEdge::smooth(GNEUndoList* undoList) {
1702     PositionVector modifiedShape = smoothShape(myNBEdge.getGeometry(), false);
1703     if (modifiedShape.size() < 2) {
1704         WRITE_WARNING("Could not compute smooth shape for edge '" + getID() + "'");
1705     } else {
1706         PositionVector innerShape(modifiedShape.begin() + 1, modifiedShape.end() - 1);
1707         setAttribute(SUMO_ATTR_SHAPE, toString(innerShape), undoList);
1708     }
1709 }
1710 
1711 
1712 void
smoothElevation(GNEUndoList * undoList)1713 GNEEdge::smoothElevation(GNEUndoList* undoList) {
1714     PositionVector elevationBase;
1715     for (const Position& pos : myNBEdge.getGeometry()) {
1716         if (elevationBase.size() == 0 || elevationBase[-1].z() != pos.z()) {
1717             elevationBase.push_back(pos);
1718         }
1719     }
1720     PositionVector elevation = smoothShape(elevationBase, true);
1721     if (elevation.size() <= 2) {
1722         WRITE_WARNING("Could not compute smooth elevation for edge '" + getID() + "'");
1723     } else {
1724         PositionVector modifiedShape = myNBEdge.getGeometry();
1725         if (modifiedShape.size() < 5) {
1726             modifiedShape = modifiedShape.resample(OptionsCont::getOptions().getFloat("opendrive.curve-resolution"));
1727         }
1728         const double scale = elevation.length2D() / modifiedShape.length2D();
1729         //std::cout << "   elevation=" << elevation << "\n mod1=" << modifiedShape << " scale=" << scale << "\n";
1730         double seen = 0;
1731         for (int i = 1; i < (int)modifiedShape.size(); ++i) {
1732             seen += modifiedShape[i - 1].distanceTo2D(modifiedShape[i]);
1733             modifiedShape[i].setz(elevation.positionAtOffset2D(seen * scale).z());
1734         }
1735         //std::cout << "   mod2=" << modifiedShape << "\n";
1736         PositionVector innerShape(modifiedShape.begin() + 1, modifiedShape.end() - 1);
1737         setAttribute(SUMO_ATTR_SHAPE, toString(innerShape), undoList);
1738     }
1739 }
1740 
1741 
1742 void
setShapeStartPos(const Position & pos,bool updateGrid)1743 GNEEdge::setShapeStartPos(const Position& pos, bool updateGrid) {
1744     // remove start position and add it the new position
1745     PositionVector geom = myNBEdge.getGeometry();
1746     geom.erase(geom.begin());
1747     geom.push_front_noDoublePos(pos);
1748     // restore modified shape
1749     setGeometry(geom, false, updateGrid);
1750 }
1751 
1752 
1753 void
setShapeEndPos(const Position & pos,bool updateGrid)1754 GNEEdge::setShapeEndPos(const Position& pos, bool updateGrid) {
1755     // remove end position and add it the new position
1756     PositionVector geom = myNBEdge.getGeometry();
1757     geom.pop_back();
1758     geom.push_back_noDoublePos(pos);
1759     // restore modified shape
1760     setGeometry(geom, false, updateGrid);
1761 }
1762 
1763 /****************************************************************************/
1764