Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/src/netedit/elements/moving/GNEMoveElementEdge.cpp
185790 views
1
/****************************************************************************/
2
// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
// Copyright (C) 2001-2025 German Aerospace Center (DLR) and others.
4
// This program and the accompanying materials are made available under the
5
// terms of the Eclipse Public License 2.0 which is available at
6
// https://www.eclipse.org/legal/epl-2.0/
7
// This Source Code may also be made available under the following Secondary
8
// Licenses when the conditions for such availability set forth in the Eclipse
9
// Public License 2.0 are satisfied: GNU General Public License, version 2
10
// or later which is available at
11
// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13
/****************************************************************************/
14
/// @file GNEMoveElementEdge.cpp
15
/// @author Pablo Alvarez Lopez
16
/// @date Oct 2025
17
///
18
// Class used for moving edge shapes
19
/****************************************************************************/
20
#include <config.h>
21
22
#include <netedit/changes/GNEChange_Attribute.h>
23
#include <netedit/GNENet.h>
24
#include <netedit/GNEUndoList.h>
25
26
#include "GNEMoveElementEdge.h"
27
28
// ===========================================================================
29
// defines
30
// ===========================================================================
31
32
#define ENDPOINT_TOLERANCE 2
33
34
// ===========================================================================
35
// Method definitions
36
// ===========================================================================
37
38
GNEMoveElementEdge::GNEMoveElementEdge(GNEEdge* edge) :
39
GNEMoveElement(edge),
40
myEdge(edge) {
41
}
42
43
44
GNEMoveElementEdge::~GNEMoveElementEdge() {}
45
46
47
GNEEdge*
48
GNEMoveElementEdge::getEdge() const {
49
return myEdge;
50
}
51
52
53
GNEMoveOperation*
54
GNEMoveElementEdge::getMoveOperation() {
55
// get geometry point radius
56
const double geometryPointRadius = myEdge->getGeometryPointRadius();
57
// check if edge is selected
58
if (myEdge->isAttributeCarrierSelected()) {
59
// check if both junctions are selected
60
if (myEdge->getFromJunction()->isAttributeCarrierSelected() && myEdge->getToJunction()->isAttributeCarrierSelected()) {
61
return processMoveBothJunctionSelected();
62
} else if (myEdge->getFromJunction()->isAttributeCarrierSelected()) {
63
return processMoveFromJunctionSelected(myEdge->getNBEdge()->getGeometry(), myEdge->getNet()->getViewNet()->getPositionInformation(), geometryPointRadius);
64
} else if (myEdge->getToJunction()->isAttributeCarrierSelected()) {
65
return processMoveToJunctionSelected(myEdge->getNBEdge()->getGeometry(), myEdge->getNet()->getViewNet()->getPositionInformation(), geometryPointRadius);
66
} else if (myEdge->getNet()->getViewNet()->getMoveMultipleElementValues().isMovingSelectedEdge()) {
67
if (myEdge->getNet()->getAttributeCarriers()->getNumberOfSelectedEdges() == 1) {
68
// special case: when only a single edge is selected, move all shape points (including custom end points)
69
return processMoveBothJunctionSelected();
70
} else {
71
// synchronized movement of a single point
72
return processNoneJunctionSelected(geometryPointRadius);
73
}
74
} else {
75
// calculate move shape operation (because there are only an edge selected)
76
return getEditShapeOperation(myEdge, myEdge->getNBEdge()->getGeometry(), false);
77
}
78
} else {
79
// calculate move shape operation
80
return getEditShapeOperation(myEdge, myEdge->getNBEdge()->getGeometry(), false);
81
}
82
}
83
84
85
std::string
86
GNEMoveElementEdge::getMovingAttribute(SumoXMLAttr key) const {
87
return myMovedElement->getCommonAttribute(key);
88
}
89
90
91
double
92
GNEMoveElementEdge::getMovingAttributeDouble(SumoXMLAttr key) const {
93
return myMovedElement->getCommonAttributeDouble(key);
94
}
95
96
97
Position
98
GNEMoveElementEdge::getMovingAttributePosition(SumoXMLAttr key) const {
99
return myMovedElement->getCommonAttributePosition(key);
100
}
101
102
103
PositionVector
104
GNEMoveElementEdge::getMovingAttributePositionVector(SumoXMLAttr key) const {
105
return myMovedElement->getCommonAttributePositionVector(key);
106
}
107
108
109
void
110
GNEMoveElementEdge::setMovingAttribute(SumoXMLAttr key, const std::string& value, GNEUndoList* undoList) {
111
myMovedElement->setCommonAttribute(key, value, undoList);
112
}
113
114
115
bool
116
GNEMoveElementEdge::isMovingAttributeValid(SumoXMLAttr key, const std::string& value) const {
117
return myMovedElement->isCommonAttributeValid(key, value);
118
}
119
120
121
void
122
GNEMoveElementEdge::setMovingAttribute(SumoXMLAttr key, const std::string& value) {
123
myMovedElement->setCommonAttribute(key, value);
124
}
125
126
127
void
128
GNEMoveElementEdge::removeGeometryPoint(const Position clickedPosition, GNEUndoList* undoList) {
129
// get geometry point radius
130
const double geometryPointRadius = myEdge->getGeometryPointRadius();
131
// declare shape to move
132
PositionVector shape = myEdge->getNBEdge()->getGeometry();
133
// obtain flags for start and end positions
134
const bool customStartPosition = (myEdge->getNBEdge()->getGeometry().front().distanceSquaredTo2D(myEdge->getFromJunction()->getNBNode()->getPosition()) > ENDPOINT_TOLERANCE);
135
const bool customEndPosition = (myEdge->getNBEdge()->getGeometry().back().distanceSquaredTo2D(myEdge->getToJunction()->getNBNode()->getPosition()) > ENDPOINT_TOLERANCE);
136
// get variable for last index
137
const int lastIndex = (int)myEdge->getNBEdge()->getGeometry().size() - 1;
138
// flag to enable/disable remove geometry point
139
bool removeGeometryPoint = true;
140
// obtain index
141
const int index = myEdge->getNBEdge()->getGeometry().indexOfClosest(clickedPosition, true);
142
// check index
143
if (index == -1) {
144
removeGeometryPoint = false;
145
}
146
// check distance
147
if (shape[index].distanceSquaredTo2D(clickedPosition) > (geometryPointRadius * geometryPointRadius)) {
148
removeGeometryPoint = false;
149
}
150
// check custom start position
151
if (!customStartPosition && (index == 0)) {
152
removeGeometryPoint = false;
153
}
154
// check custom end position
155
if (!customEndPosition && (index == lastIndex)) {
156
removeGeometryPoint = false;
157
}
158
// check if we can remove geometry point
159
if (removeGeometryPoint) {
160
// check if we're removing first geometry proint
161
if (index == 0) {
162
// commit new geometry start
163
undoList->begin(myEdge, TLF("remove first geometry point of %", myEdge->getTagStr()));
164
GNEChange_Attribute::changeAttribute(myEdge, GNE_ATTR_SHAPE_START, "", undoList);
165
undoList->end();
166
} else if (index == lastIndex) {
167
// commit new geometry end
168
undoList->begin(myEdge, TLF("remove last geometry point of %", myEdge->getTagStr()));
169
GNEChange_Attribute::changeAttribute(myEdge, GNE_ATTR_SHAPE_END, "", undoList);
170
undoList->end();
171
} else {
172
// remove geometry point
173
shape.erase(shape.begin() + index);
174
// get innen shape
175
shape.pop_front();
176
shape.pop_back();
177
// remove double points
178
shape.removeDoublePoints((geometryPointRadius * geometryPointRadius));
179
// commit new shape
180
undoList->begin(myEdge, TLF("remove geometry point of %", myEdge->getTagStr()));
181
GNEChange_Attribute::changeAttribute(myEdge, SUMO_ATTR_SHAPE, toString(shape), undoList);
182
undoList->end();
183
}
184
}
185
}
186
187
188
void
189
GNEMoveElementEdge::setMoveShape(const GNEMoveResult& moveResult) {
190
// get start and end points
191
const Position shapeStart = moveResult.shapeToUpdate.front();
192
const Position shapeEnd = moveResult.shapeToUpdate.back();
193
// get innen shape
194
PositionVector innenShape = moveResult.shapeToUpdate;
195
innenShape.pop_front();
196
innenShape.pop_back();
197
// set shape start
198
if (std::find(moveResult.geometryPointsToMove.begin(), moveResult.geometryPointsToMove.end(), 0) != moveResult.geometryPointsToMove.end()) {
199
myEdge->setShapeStartPos(shapeStart);
200
}
201
// set innen geometry
202
myEdge->setGeometry(innenShape, true);
203
// set shape end
204
if (std::find(moveResult.geometryPointsToMove.begin(), moveResult.geometryPointsToMove.end(), ((int)moveResult.shapeToUpdate.size() - 1)) != moveResult.geometryPointsToMove.end()) {
205
myEdge->setShapeEndPos(shapeEnd);
206
}
207
}
208
209
210
void
211
GNEMoveElementEdge::commitMoveShape(const GNEMoveResult& moveResult, GNEUndoList* undoList) {
212
// make sure that newShape isn't empty
213
if (moveResult.shapeToUpdate.size() > 1) {
214
// get innen shape
215
PositionVector innenShapeToUpdate = moveResult.shapeToUpdate;
216
innenShapeToUpdate.pop_front();
217
innenShapeToUpdate.pop_back();
218
// commit new shape
219
undoList->begin(myEdge, TLF("moving shape of %", myEdge->getTagStr()));
220
// update start position
221
if (std::find(moveResult.geometryPointsToMove.begin(), moveResult.geometryPointsToMove.end(), 0) != moveResult.geometryPointsToMove.end()) {
222
GNEChange_Attribute::changeAttribute(myEdge, GNE_ATTR_SHAPE_START, toString(moveResult.shapeToUpdate.front()), undoList);
223
}
224
// check if update shape
225
if (innenShapeToUpdate.size() > 0) {
226
GNEChange_Attribute::changeAttribute(myEdge, SUMO_ATTR_SHAPE, toString(innenShapeToUpdate), undoList);
227
}
228
// update end position
229
if (std::find(moveResult.geometryPointsToMove.begin(), moveResult.geometryPointsToMove.end(), ((int)moveResult.shapeToUpdate.size() - 1)) != moveResult.geometryPointsToMove.end()) {
230
GNEChange_Attribute::changeAttribute(myEdge, GNE_ATTR_SHAPE_END, toString(moveResult.shapeToUpdate.back()), undoList);
231
}
232
undoList->end();
233
}
234
}
235
236
237
GNEMoveOperation*
238
GNEMoveElementEdge::processMoveFromJunctionSelected(const PositionVector originalShape, const Position mousePosition, const double snapRadius) {
239
// calculate squared snapRadius
240
const double squaredSnapRadius = (snapRadius * snapRadius);
241
// declare shape to move
242
PositionVector shapeToMove = originalShape;
243
// obtain nearest index
244
const int nearestIndex = originalShape.indexOfClosest(mousePosition);
245
// obtain nearest position
246
const Position nearestPosition = originalShape.positionAtOffset2D(originalShape.nearest_offset_to_point2D(mousePosition));
247
// generate indexes
248
std::vector<int> indexes;
249
// check conditions
250
if (nearestIndex == -1) {
251
return nullptr;
252
} else if (nearestPosition == Position::INVALID) {
253
// special case for extremes
254
if (mousePosition.distanceSquaredTo2D(shapeToMove[nearestIndex]) <= squaredSnapRadius) {
255
for (int i = 1; i <= nearestIndex; i++) {
256
indexes.push_back(i);
257
}
258
// move extrem without creating new geometry point
259
return new GNEMoveOperation(this, originalShape, indexes, shapeToMove, indexes);
260
} else {
261
return nullptr;
262
}
263
} else if (nearestPosition.distanceSquaredTo2D(shapeToMove[nearestIndex]) <= squaredSnapRadius) {
264
for (int i = 1; i <= nearestIndex; i++) {
265
indexes.push_back(i);
266
}
267
// move geometry point without creating new geometry point
268
return new GNEMoveOperation(this, originalShape, indexes, shapeToMove, indexes);
269
} else {
270
// create new geometry point and keep new index (if we clicked near of shape)
271
const int newIndex = shapeToMove.insertAtClosest(nearestPosition, true);
272
for (int i = 1; i <= newIndex; i++) {
273
indexes.push_back(i);
274
}
275
// move after setting new geometry point in shapeToMove
276
return new GNEMoveOperation(this, originalShape, indexes, shapeToMove, indexes);
277
}
278
}
279
280
281
GNEMoveOperation*
282
GNEMoveElementEdge::processMoveToJunctionSelected(const PositionVector originalShape, const Position mousePosition, const double snapRadius) {
283
// calculate squared snapRadius
284
const double squaredSnapRadius = (snapRadius * snapRadius);
285
// declare shape to move
286
PositionVector shapeToMove = originalShape;
287
// obtain nearest index
288
const int nearestIndex = originalShape.indexOfClosest(mousePosition);
289
// obtain nearest position
290
const Position nearestPosition = originalShape.positionAtOffset2D(originalShape.nearest_offset_to_point2D(mousePosition));
291
// generate indexes
292
std::vector<int> indexes;
293
// check conditions
294
if (nearestIndex == -1) {
295
return nullptr;
296
} else if (nearestPosition == Position::INVALID) {
297
// special case for extremes
298
if (mousePosition.distanceSquaredTo2D(shapeToMove[nearestIndex]) <= squaredSnapRadius) {
299
for (int i = nearestIndex; i < ((int)originalShape.size() - 1); i++) {
300
indexes.push_back(i);
301
}
302
// move extrem without creating new geometry point
303
return new GNEMoveOperation(this, originalShape, indexes, shapeToMove, indexes);
304
} else {
305
return nullptr;
306
}
307
} else if (nearestPosition.distanceSquaredTo2D(shapeToMove[nearestIndex]) <= squaredSnapRadius) {
308
for (int i = nearestIndex; i < ((int)originalShape.size() - 1); i++) {
309
indexes.push_back(i);
310
}
311
// move geometry point without creating new geometry point
312
return new GNEMoveOperation(this, originalShape, indexes, shapeToMove, indexes);
313
} else {
314
// create new geometry point and keep new index (if we clicked near of shape)
315
const int newIndex = shapeToMove.insertAtClosest(nearestPosition, true);
316
for (int i = newIndex; i < ((int)originalShape.size() - 1); i++) {
317
indexes.push_back(i);
318
}
319
// move after setting new geometry point in shapeToMove
320
return new GNEMoveOperation(this, originalShape, indexes, shapeToMove, indexes);
321
}
322
}
323
324
325
GNEMoveOperation*
326
GNEMoveElementEdge::processMoveBothJunctionSelected() {
327
std::vector<int> geometryPointsToMove;
328
for (int i = 0; i < (int)myEdge->getNBEdge()->getGeometry().size(); i++) {
329
geometryPointsToMove.push_back(i);
330
}
331
// move entire shape (including extremes)
332
return new GNEMoveOperation(this, myEdge->getNBEdge()->getGeometry(), geometryPointsToMove, myEdge->getNBEdge()->getGeometry(), geometryPointsToMove);
333
}
334
335
336
GNEMoveOperation*
337
GNEMoveElementEdge::processNoneJunctionSelected(const double snapRadius) {
338
// get move multiple element values
339
const auto& moveMultipleElementValues = myEdge->getNet()->getViewNet()->getMoveMultipleElementValues();
340
// declare shape to move
341
PositionVector shapeToMove = myEdge->getNBEdge()->getGeometry();
342
// first check if kept offset is larger than geometry
343
if (shapeToMove.length2D() < moveMultipleElementValues.getEdgeOffset()) {
344
return nullptr;
345
}
346
// declare offset
347
double offset = 0;
348
// set offset depending of convex angle
349
if (myEdge->isConvexAngle()) {
350
offset = moveMultipleElementValues.getEdgeOffset();
351
} else {
352
offset = shapeToMove.length2D() - moveMultipleElementValues.getEdgeOffset();
353
}
354
// obtain offset position
355
const Position offsetPosition = myEdge->getNBEdge()->getGeometry().positionAtOffset2D(offset);
356
// obtain nearest index to offset position
357
const int nearestIndex = myEdge->getNBEdge()->getGeometry().indexOfClosest(offsetPosition);
358
// check conditions
359
if ((nearestIndex == -1) || (offsetPosition == Position::INVALID)) {
360
return nullptr;
361
} else if (offsetPosition.distanceSquaredTo2D(shapeToMove[nearestIndex]) <= (snapRadius * snapRadius)) {
362
// move geometry point without creating new geometry point
363
return new GNEMoveOperation(this, myEdge->getNBEdge()->getGeometry(), {nearestIndex}, shapeToMove, {nearestIndex});
364
} else {
365
// create new geometry point and keep new index (if we clicked near of shape)
366
const int newIndex = shapeToMove.insertAtClosest(offsetPosition, true);
367
// move after setting new geometry point in shapeToMove
368
return new GNEMoveOperation(this, myEdge->getNBEdge()->getGeometry(), {nearestIndex}, shapeToMove, {newIndex});
369
}
370
}
371
372
373
/****************************************************************************/
374
375