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    GNETAZ.cpp
11 /// @author  Pablo Alvarez Lopez
12 /// @date    Oct 2018
13 /// @version $Id$
14 ///
15 //
16 /****************************************************************************/
17 
18 // ===========================================================================
19 // included modules
20 // ===========================================================================
21 #include <config.h>
22 
23 #include <utils/gui/div/GLHelper.h>
24 #include <netedit/netelements/GNELane.h>
25 #include <netedit/frames/GNETAZFrame.h>
26 #include <netedit/GNEUndoList.h>
27 #include <netedit/GNEViewNet.h>
28 #include <netedit/GNENet.h>
29 #include <netedit/changes/GNEChange_Attribute.h>
30 #include <netedit/GNEViewParent.h>
31 #include <utils/gui/globjects/GLIncludes.h>
32 #include "GNETAZ.h"
33 
34 
35 // ===========================================================================
36 // static members
37 // ===========================================================================
38 const double GNETAZ::myHintSize = 0.8;
39 const double GNETAZ::myHintSizeSquared = 0.64;
40 
41 
42 // ===========================================================================
43 // member method definitions
44 // ===========================================================================
GNETAZ(const std::string & id,GNEViewNet * viewNet,PositionVector shape,RGBColor color,bool blockMovement)45 GNETAZ::GNETAZ(const std::string& id, GNEViewNet* viewNet, PositionVector shape, RGBColor color, bool blockMovement) :
46     GNEAdditional(id, viewNet, GLO_TAZ, SUMO_TAG_TAZ, "", blockMovement, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}),
47               myColor(color),
48               myBlockShape(false),
49               myCurrentMovingVertexIndex(-1),
50               myMaxWeightSource(0),
51               myMinWeightSource(0),
52               myAverageWeightSource(0),
53               myMaxWeightSink(0),
54               myMinWeightSink(0),
55 myAverageWeightSink(0) {
56     // set TAZ shape
57     myGeometry.shape = shape;
58 }
59 
60 
~GNETAZ()61 GNETAZ::~GNETAZ() {}
62 
63 
64 void
updateGeometry(bool)65 GNETAZ::updateGeometry(bool /*updateGrid*/) {
66     // Nothing to do
67 }
68 
69 
70 Position
getPositionInView() const71 GNETAZ::getPositionInView() const {
72     return myGeometry.shape.getCentroid();
73 }
74 
75 
76 void
moveGeometry(const Position & offset)77 GNETAZ::moveGeometry(const Position& offset) {
78     // restore old position, apply offset and update Geometry
79     myGeometry.shape[0] = myMove.originalViewPosition;
80     myGeometry.shape[0].add(offset);
81     // filtern position using snap to active grid
82     myGeometry.shape[0] = myViewNet->snapToActiveGrid(myGeometry.shape[0]);
83     updateGeometry(false);
84 }
85 
86 
87 void
commitGeometryMoving(GNEUndoList * undoList)88 GNETAZ::commitGeometryMoving(GNEUndoList* undoList) {
89     // commit new position allowing undo/redo
90     undoList->p_begin("position of " + getTagStr());
91     undoList->p_add(new GNEChange_Attribute(this, myViewNet->getNet(), SUMO_ATTR_SHAPE, toString(myGeometry.shape[0]), true, toString(myMove.originalViewPosition)));
92     undoList->p_end();
93 }
94 
95 
96 int
moveVertexShape(const int index,const Position & oldPos,const Position & offset)97 GNETAZ::moveVertexShape(const int index, const Position& oldPos, const Position& offset) {
98     // only move shape if block movement block shape are disabled
99     if (!myBlockMovement && !myBlockShape && (index != -1)) {
100         // check that index is correct before change position
101         if (index < (int)myGeometry.shape.size()) {
102             // save current moving Geometry Point
103             myCurrentMovingVertexIndex = index;
104             // if closed shape and cliked is first or last, move both giving more priority to first always
105             if ((index == 0 || index == (int)myGeometry.shape.size() - 1)) {
106                 // Change position of first shape Geometry Point and filtern position using snap to active grid
107                 myGeometry.shape.front() = oldPos;
108                 myGeometry.shape.front().add(offset);
109                 myGeometry.shape.front() = myViewNet->snapToActiveGrid(myGeometry.shape.front());
110                 // Change position of last shape Geometry Point and filtern position using snap to active grid
111                 myGeometry.shape.back() = oldPos;
112                 myGeometry.shape.back().add(offset);
113                 myGeometry.shape.back() = myViewNet->snapToActiveGrid(myGeometry.shape.back());
114             } else {
115                 // change position of Geometry Point and filtern position using snap to active grid
116                 myGeometry.shape[index] = oldPos;
117                 myGeometry.shape[index].add(offset);
118                 myGeometry.shape[index] = myViewNet->snapToActiveGrid(myGeometry.shape[index]);
119             }
120             // return index of moved Geometry Point
121             return index;
122         } else {
123             throw InvalidArgument("Index greater than shape size");
124         }
125     } else {
126         return index;
127     }
128 }
129 
130 
131 void
moveEntireShape(const PositionVector & oldShape,const Position & offset)132 GNETAZ::moveEntireShape(const PositionVector& oldShape, const Position& offset) {
133     // only move shape if block movement is disabled and block shape is enabled
134     if (!myBlockMovement && myBlockShape) {
135         // restore original shape
136         myGeometry.shape = oldShape;
137         // change all points of the shape shape using offset
138         for (auto& i : myGeometry.shape) {
139             i.add(offset);
140         }
141         // update Geometry after moving
142         updateGeometry(true);
143     }
144 }
145 
146 
147 void
commitShapeChange(const PositionVector & oldShape,GNEUndoList * undoList)148 GNETAZ::commitShapeChange(const PositionVector& oldShape, GNEUndoList* undoList) {
149     if (!myBlockMovement) {
150         // disable current moving vertex
151         myCurrentMovingVertexIndex = -1;
152         // restore original shape into shapeToCommit
153         PositionVector shapeToCommit = myGeometry.shape;
154         // restore old shape in polygon (to avoid problems with RTree)
155         myGeometry.shape = oldShape;
156         // first check if double points has to be removed
157         shapeToCommit.removeDoublePoints(myHintSize);
158         if (shapeToCommit.size() != myGeometry.shape.size()) {
159             WRITE_WARNING("Merged shape's point")
160         }
161         // check if polygon has to be closed
162         if (shapeToCommit.size() > 1 && shapeToCommit.front().distanceTo2D(shapeToCommit.back()) < (2 * myHintSize)) {
163             shapeToCommit.pop_back();
164             shapeToCommit.push_back(shapeToCommit.front());
165         }
166         // commit new shape
167         undoList->p_begin("moving " + toString(SUMO_ATTR_SHAPE) + " of " + getTagStr());
168         undoList->p_add(new GNEChange_Attribute(this, myViewNet->getNet(), SUMO_ATTR_SHAPE, toString(shapeToCommit)));
169         undoList->p_end();
170     }
171 }
172 
173 
174 int
getVertexIndex(Position pos,bool createIfNoExist,bool snapToGrid)175 GNETAZ::getVertexIndex(Position pos, bool createIfNoExist, bool snapToGrid) {
176     // check if position has to be snapped to grid
177     if (snapToGrid) {
178         pos = myViewNet->snapToActiveGrid(pos);
179     }
180     // first check if vertex already exists
181     for (auto i : myGeometry.shape) {
182         if (i.distanceTo2D(pos) < myHintSize) {
183             return myGeometry.shape.indexOfClosest(i);
184         }
185     }
186     // if vertex doesn't exist, insert it
187     if (createIfNoExist) {
188         return myGeometry.shape.insertAtClosest(pos);
189     } else {
190         return -1;
191     }
192 }
193 
194 
195 void
deleteGeometryPoint(const Position & pos,bool allowUndo)196 GNETAZ::deleteGeometryPoint(const Position& pos, bool allowUndo) {
197     if (myGeometry.shape.size() > 2) {
198         // obtain index
199         PositionVector modifiedShape = myGeometry.shape;
200         int index = modifiedShape.indexOfClosest(pos);
201         // remove point dependending of
202         if ((index == 0 || index == (int)modifiedShape.size() - 1)) {
203             modifiedShape.erase(modifiedShape.begin());
204             modifiedShape.erase(modifiedShape.end() - 1);
205             modifiedShape.push_back(modifiedShape.front());
206         } else {
207             modifiedShape.erase(modifiedShape.begin() + index);
208         }
209         // set new shape depending of allowUndo
210         if (allowUndo) {
211             myViewNet->getUndoList()->p_begin("delete geometry point");
212             setAttribute(SUMO_ATTR_SHAPE, toString(modifiedShape), myViewNet->getUndoList());
213             myViewNet->getUndoList()->p_end();
214         } else {
215             // first remove object from grid due shape is used for boundary
216             myViewNet->getNet()->removeGLObjectFromGrid(this);
217             // set new shape
218             myGeometry.shape = modifiedShape;
219             // add object into grid again
220             myViewNet->getNet()->addGLObjectIntoGrid(this);
221         }
222     } else {
223         WRITE_WARNING("Number of remaining points insufficient")
224     }
225 }
226 
227 
228 bool
isShapeBlocked() const229 GNETAZ::isShapeBlocked() const {
230     return myBlockShape;
231 }
232 
233 
234 std::string
getParentName() const235 GNETAZ::getParentName() const {
236     return myViewNet->getNet()->getMicrosimID();
237 }
238 
239 
240 void
drawGL(const GUIVisualizationSettings & s) const241 GNETAZ::drawGL(const GUIVisualizationSettings& s) const {
242     if (s.polySize.getExaggeration(s, this) == 0) {
243         return;
244     }
245     Boundary boundary = myGeometry.shape.getBoxBoundary();
246     int circleResolution = GNEAttributeCarrier::getCircleResolution(s);
247     if (s.scale * MAX2(boundary.getWidth(), boundary.getHeight()) < s.polySize.minSize) {
248         return;
249     }
250     glPushName(getGlID());
251     if (myGeometry.shape.size() > 1) {
252         glPushMatrix();
253         glTranslated(0, 0, 128);
254         if (drawUsingSelectColor()) {
255             GLHelper::setColor(s.selectionColor);
256         } else {
257             GLHelper::setColor(myColor);
258         }
259         GLHelper::drawLine(myGeometry.shape);
260         GLHelper::drawBoxLines(myGeometry.shape, 1);
261         glPopMatrix();
262         const Position namePos = myGeometry.shape.getPolygonCenter();
263         drawName(namePos, s.scale, s.polyName, s.angle);
264     }
265     // draw geometry details hints if is not too small and isn't in selecting mode
266     if (s.scale * myHintSize > 1.) {
267         // set values relative to mouse position regarding to shape
268         bool mouseOverVertex = false;
269         bool modeMove = myViewNet->getEditModes().networkEditMode == GNE_NMODE_MOVE;
270         Position mousePosition = myViewNet->getPositionInformation();
271         double distanceToShape = myGeometry.shape.distance2D(mousePosition);
272         // set colors
273         RGBColor invertedColor, darkerColor;
274         if (drawUsingSelectColor()) {
275             invertedColor = s.selectionColor.invertedColor();
276             darkerColor = s.selectionColor.changedBrightness(-32);
277         } else {
278             invertedColor = GLHelper::getColor().invertedColor();
279             darkerColor = GLHelper::getColor().changedBrightness(-32);
280         }
281         // Draw geometry hints if polygon's shape isn't blocked
282         if (myBlockShape == false) {
283             // draw a boundary for moving using darkerColor
284             glPushMatrix();
285             glTranslated(0, 0, GLO_POLYGON + 0.01);
286             GLHelper::setColor(darkerColor);
287             GLHelper::drawBoxLines(myGeometry.shape, (myHintSize / 4) * s.polySize.getExaggeration(s, this));
288             glPopMatrix();
289             // draw points of shape
290             for (auto i : myGeometry.shape) {
291                 if (!s.drawForSelecting || (myViewNet->getPositionInformation().distanceSquaredTo2D(i) <= (myHintSizeSquared + 2))) {
292                     glPushMatrix();
293                     glTranslated(i.x(), i.y(), GLO_POLYGON + 0.02);
294                     // Change color of vertex and flag mouseOverVertex if mouse is over vertex
295                     if (modeMove && (i.distanceTo(mousePosition) < myHintSize)) {
296                         mouseOverVertex = true;
297                         GLHelper::setColor(invertedColor);
298                     } else {
299                         GLHelper::setColor(darkerColor);
300                     }
301                     GLHelper::drawFilledCircle(myHintSize, circleResolution);
302                     glPopMatrix();
303                 }
304             }
305             // check if draw moving hint has to be drawed
306             if (modeMove && (mouseOverVertex == false) && (myBlockMovement == false) && (distanceToShape < myHintSize)) {
307                 // push matrix
308                 glPushMatrix();
309                 Position hintPos = myGeometry.shape.size() > 1 ? myGeometry.shape.positionAtOffset2D(myGeometry.shape.nearest_offset_to_point2D(mousePosition)) : myGeometry.shape[0];
310                 glTranslated(hintPos.x(), hintPos.y(), GLO_POLYGON + 0.04);
311                 GLHelper::setColor(invertedColor);
312                 GLHelper:: drawFilledCircle(myHintSize, circleResolution);
313                 glPopMatrix();
314             }
315         }
316     }
317     // check if dotted contour has to be drawn
318     if ((myViewNet->getDottedAC() == this) || (myViewNet->getViewParent()->getTAZFrame()->getTAZCurrentModul()->getTAZ() == this)) {
319         GLHelper::drawShapeDottedContour(GLO_POLYGON + 1, getShape());
320     }
321     // pop name
322     glPopName();
323 }
324 
325 
326 std::string
getAttribute(SumoXMLAttr key) const327 GNETAZ::getAttribute(SumoXMLAttr key) const {
328     switch (key) {
329         case SUMO_ATTR_ID:
330             return getAdditionalID();
331         case SUMO_ATTR_SHAPE:
332             return toString(myGeometry.shape);
333         case SUMO_ATTR_COLOR:
334             return toString(myColor);
335         case SUMO_ATTR_EDGES: {
336             std::vector<std::string> edgeIDs;
337             for (auto i : getAdditionalChilds()) {
338                 edgeIDs.push_back(i->getAttribute(SUMO_ATTR_EDGE));
339             }
340             return toString(edgeIDs);
341         }
342         case GNE_ATTR_BLOCK_MOVEMENT:
343             return toString(myBlockMovement);
344         case GNE_ATTR_BLOCK_SHAPE:
345             return toString(myBlockShape);
346         case GNE_ATTR_SELECTED:
347             return toString(isAttributeCarrierSelected());
348         case GNE_ATTR_GENERIC:
349             return getGenericParametersStr();
350         case GNE_ATTR_MIN_SOURCE:
351             return toString(myMinWeightSource);
352         case GNE_ATTR_MIN_SINK:
353             return toString(myMinWeightSink);
354         case GNE_ATTR_MAX_SOURCE:
355             return toString(myMaxWeightSource);
356         case GNE_ATTR_MAX_SINK:
357             return toString(myMaxWeightSink);
358         case GNE_ATTR_AVERAGE_SOURCE:
359             return toString(myAverageWeightSource);
360         case GNE_ATTR_AVERAGE_SINK:
361             return toString(myAverageWeightSink);
362         default:
363             throw InvalidArgument(getTagStr() + " doesn't have an attribute of type '" + toString(key) + "'");
364     }
365 }
366 
367 
368 void
setAttribute(SumoXMLAttr key,const std::string & value,GNEUndoList * undoList)369 GNETAZ::setAttribute(SumoXMLAttr key, const std::string& value, GNEUndoList* undoList) {
370     if (value == getAttribute(key)) {
371         return; //avoid needless changes, later logic relies on the fact that attributes have changed
372     }
373     switch (key) {
374         case SUMO_ATTR_ID:
375         case SUMO_ATTR_SHAPE:
376         case SUMO_ATTR_COLOR:
377         case SUMO_ATTR_EDGES:
378         case GNE_ATTR_BLOCK_MOVEMENT:
379         case GNE_ATTR_BLOCK_SHAPE:
380         case GNE_ATTR_SELECTED:
381         case GNE_ATTR_GENERIC:
382             undoList->p_add(new GNEChange_Attribute(this, myViewNet->getNet(), key, value));
383             break;
384         default:
385             throw InvalidArgument(getTagStr() + " doesn't have an attribute of type '" + toString(key) + "'");
386     }
387 }
388 
389 
390 bool
isValid(SumoXMLAttr key,const std::string & value)391 GNETAZ::isValid(SumoXMLAttr key, const std::string& value) {
392     switch (key) {
393         case SUMO_ATTR_ID:
394             return isValidAdditionalID(value);
395         case SUMO_ATTR_SHAPE:
396             return canParse<PositionVector>(value);
397         case SUMO_ATTR_COLOR:
398             return canParse<RGBColor>(value);
399         case SUMO_ATTR_EDGES:
400             if (value.empty()) {
401                 return true;
402             } else {
403                 return SUMOXMLDefinitions::isValidListOfTypeID(value);
404             }
405         case GNE_ATTR_BLOCK_MOVEMENT:
406             return canParse<bool>(value);
407         case GNE_ATTR_BLOCK_SHAPE:
408             return canParse<bool>(value);
409         case GNE_ATTR_SELECTED:
410             return canParse<bool>(value);
411         case GNE_ATTR_GENERIC:
412             return isGenericParametersValid(value);
413         default:
414             throw InvalidArgument(getTagStr() + " doesn't have an attribute of type '" + toString(key) + "'");
415     }
416 }
417 
418 
419 std::string
getPopUpID() const420 GNETAZ::getPopUpID() const {
421     return getTagStr() + ":" + getID();
422 }
423 
424 
425 std::string
getHierarchyName() const426 GNETAZ::getHierarchyName() const {
427     return getTagStr();
428 }
429 
430 
431 void
updateAdditionalParent()432 GNETAZ::updateAdditionalParent() {
433     // reset all stadistic variables
434     myMaxWeightSource = 0;
435     myMinWeightSource = -1;
436     myAverageWeightSource = 0;
437     myMaxWeightSink = 0;
438     myMinWeightSink = -1;
439     myAverageWeightSink = 0;
440     // declare an extra variables for saving number of childs
441     int numberOfSources = 0;
442     int numberOfSinks = 0;
443     // iterate over additional childs
444     for (auto i : getAdditionalChilds()) {
445         if (i->getTagProperty().getTag() == SUMO_TAG_TAZSOURCE) {
446             double weight = parse<double>(i->getAttribute(SUMO_ATTR_WEIGHT));
447             // check max Weight
448             if (myMaxWeightSource < weight) {
449                 myMaxWeightSource = weight;
450             }
451             // check min Weight
452             if ((myMinWeightSource == -1) || (weight < myMinWeightSource)) {
453                 myMinWeightSource = weight;
454             }
455             // update Average
456             myAverageWeightSource += weight;
457             // update number of sources
458             numberOfSources++;
459         } else if (i->getTagProperty().getTag() == SUMO_TAG_TAZSINK) {
460             double weight = parse<double>(i->getAttribute(SUMO_ATTR_WEIGHT));
461             // check max Weight
462             if (myMaxWeightSink < weight) {
463                 myMaxWeightSink = weight;
464             }
465             // check min Weight
466             if ((myMinWeightSink == -1) || (weight < myMinWeightSink)) {
467                 myMinWeightSink = weight;
468             }
469             // update Average
470             myAverageWeightSink += weight;
471             // update number of sinks
472             numberOfSinks++;
473         }
474     }
475     // calculate average
476     myAverageWeightSource /= numberOfSources;
477     myAverageWeightSink /= numberOfSinks;
478 }
479 
480 // ===========================================================================
481 // private
482 // ===========================================================================
483 
484 void
setAttribute(SumoXMLAttr key,const std::string & value)485 GNETAZ::setAttribute(SumoXMLAttr key, const std::string& value) {
486     switch (key) {
487         case SUMO_ATTR_ID:
488             changeAdditionalID(value);
489             break;
490         case SUMO_ATTR_SHAPE:
491             myViewNet->getNet()->removeGLObjectFromGrid(this);
492             myGeometry.shape = parse<PositionVector>(value);
493             myViewNet->getNet()->addGLObjectIntoGrid(this);
494             break;
495         case SUMO_ATTR_COLOR:
496             myColor = parse<RGBColor>(value);
497             break;
498         case SUMO_ATTR_EDGES:
499             break;
500         case GNE_ATTR_BLOCK_MOVEMENT:
501             myBlockMovement = parse<bool>(value);
502             break;
503         case GNE_ATTR_BLOCK_SHAPE:
504             myBlockShape = parse<bool>(value);
505             break;
506         case GNE_ATTR_SELECTED:
507             if (parse<bool>(value)) {
508                 selectAttributeCarrier();
509             } else {
510                 unselectAttributeCarrier();
511             }
512             break;
513         case GNE_ATTR_GENERIC:
514             setGenericParametersStr(value);
515             break;
516         default:
517             throw InvalidArgument(getTagStr() + " doesn't have an attribute of type '" + toString(key) + "'");
518     }
519     // check if updated attribute requieres update geometry
520     if (myTagProperty.hasAttribute(key) && myTagProperty.getAttributeProperties(key).requiereUpdateGeometry()) {
521         updateGeometry(true);
522     }
523 }
524 
525 
526 /****************************************************************************/
527