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