Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/src/netedit/elements/moving/GNEMoveElement.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 GNEMoveElement.cpp
15
/// @author Pablo Alvarez Lopez
16
/// @date Mar 2020
17
///
18
// Class used for elements that can be moved
19
/****************************************************************************/
20
#include <config.h>
21
22
#include <netedit/changes/GNEChange_Attribute.h>
23
#include <netedit/GNEViewParent.h>
24
#include <netedit/frames/common/GNEMoveFrame.h>
25
26
#include "GNEMoveElement.h"
27
28
// ===========================================================================
29
// Method definitions
30
// ===========================================================================
31
32
GNEMoveElement::GNEMoveElement(GNEAttributeCarrier* movedElement) :
33
myMovedElement(movedElement) {
34
}
35
36
37
GNEMoveElement::~GNEMoveElement() {}
38
39
40
std::string
41
GNEMoveElement::getMovingAttribute(SumoXMLAttr key) const {
42
throw InvalidArgument(myMovedElement->getTagStr() + " doesn't have a moving attribute of type '" + toString(key) + "'");
43
}
44
45
46
double
47
GNEMoveElement::getMovingAttributeDouble(SumoXMLAttr key) const {
48
throw InvalidArgument(myMovedElement->getTagStr() + " doesn't have a moving attribute of type '" + toString(key) + "'");
49
}
50
51
52
void
53
GNEMoveElement::setMovingAttribute(SumoXMLAttr key, const std::string& /*value*/, GNEUndoList* /*undoList*/) {
54
throw InvalidArgument(myMovedElement->getTagStr() + " doesn't have a moving attribute of type '" + toString(key) + "'");
55
}
56
57
58
bool
59
GNEMoveElement::isMovingAttributeValid(SumoXMLAttr key, const std::string& /*value*/) const {
60
throw InvalidArgument(myMovedElement->getTagStr() + " doesn't have a moving attribute of type '" + toString(key) + "'");
61
}
62
63
64
void
65
GNEMoveElement::setMovingAttribute(SumoXMLAttr key, const std::string& /*value*/) {
66
throw InvalidArgument(myMovedElement->getTagStr() + " doesn't have a moving attribute of type '" + toString(key) + "'");
67
}
68
69
70
GNEMoveOperation*
71
GNEMoveElement::getEditShapeOperation(const GUIGlObject* obj, const PositionVector originalShape,
72
const bool maintainShapeClosed) {
73
// get moved geometry points
74
const auto geometryPoints = gViewObjectsHandler.getSelectedGeometryPoints(obj);
75
// get pos over shape
76
const auto posOverShape = gViewObjectsHandler.getSelectedPositionOverShape(obj);
77
// declare shape to move
78
PositionVector shapeToMove = originalShape;
79
const int lastIndex = (int)shapeToMove.size() - 1;
80
// check if move existent geometry points or create new
81
if (geometryPoints.size() > 0) {
82
// move geometry point without creating new geometry point
83
if (maintainShapeClosed && ((geometryPoints.front() == 0) || (geometryPoints.front() == lastIndex))) {
84
// move first and last point
85
return new GNEMoveOperation(this, originalShape, {0, lastIndex}, shapeToMove, {0, lastIndex});
86
} else {
87
return new GNEMoveOperation(this, originalShape, {geometryPoints.front()}, shapeToMove, {geometryPoints.front()});
88
}
89
} else if (posOverShape != Position::INVALID) {
90
// create new geometry point and keep new index (if we clicked near of shape)
91
const int newIndex = shapeToMove.insertAtClosest(posOverShape, true);
92
return new GNEMoveOperation(this, originalShape, {shapeToMove.indexOfClosest(posOverShape)}, shapeToMove, {newIndex});
93
} else {
94
return nullptr;
95
}
96
}
97
98
99
void
100
GNEMoveElement::moveElement(const GNEViewNet* viewNet, GNEMoveOperation* moveOperation, const GNEMoveOffset& offset) {
101
// declare move result
102
GNEMoveResult moveResult(moveOperation);
103
// set geometry points to move
104
moveResult.geometryPointsToMove = moveOperation->geometryPointsToMove;
105
// check if we're moving over a lane shape, an entire shape or only certain geometry point
106
if (moveOperation->firstLane) {
107
// calculate movement over lane depending if element has more than one lane
108
if (moveOperation->lastLane) {
109
if ((moveOperation->firstPosition != INVALID_DOUBLE) && (moveOperation->lastPosition != INVALID_DOUBLE)) {
110
// move both first and last positions
111
calculateLanePositions(moveResult.newFirstPos, moveResult.newLastPos, viewNet, moveOperation->firstLane,
112
moveOperation->firstPosition, moveOperation->lastLane, moveOperation->lastPosition,
113
moveOperation->clickedFirstLane, offset);
114
} else if (moveOperation->firstPosition != INVALID_DOUBLE) {
115
// move first position
116
calculateLanePosition(moveResult.newFirstPos, viewNet, moveOperation->firstLane, moveOperation->firstPosition, offset);
117
} else if (moveOperation->lastPosition != INVALID_DOUBLE) {
118
// move last position
119
calculateLanePosition(moveResult.newLastPos, viewNet, moveOperation->lastLane, moveOperation->lastPosition, offset);
120
}
121
} else {
122
// continue depending if we're moving both positions
123
if ((moveOperation->firstPosition != INVALID_DOUBLE) && (moveOperation->lastPosition != INVALID_DOUBLE)) {
124
// move both first and last positions in the same lane
125
calculateLanePositions(moveResult.newFirstPos, moveResult.newLastPos, viewNet, moveOperation->firstLane,
126
moveOperation->firstPosition, moveOperation->lastPosition, offset);
127
} else if (moveOperation->firstPosition != INVALID_DOUBLE) {
128
// move first position
129
calculateLanePosition(moveResult.newFirstPos, viewNet, moveOperation->firstLane, moveOperation->firstPosition, offset);
130
} else if (moveOperation->lastPosition != INVALID_DOUBLE) {
131
// move last position
132
calculateLanePosition(moveResult.newLastPos, viewNet, moveOperation->firstLane, moveOperation->lastPosition, offset);
133
}
134
// calculate new lane
135
if (moveOperation->allowChangeLane) {
136
calculateNewLaneChange(viewNet, moveOperation->firstLane, moveResult.newFirstLane, moveResult.firstLaneOffset);
137
}
138
}
139
} else if (moveOperation->geometryPointsToMove.size() > 0) {
140
// set values in moveResult
141
moveResult.shapeToUpdate = moveOperation->shapeToMove;
142
// move geometry points
143
for (const auto& geometryPointIndex : moveOperation->geometryPointsToMove) {
144
if (moveResult.shapeToUpdate[geometryPointIndex] != Position::INVALID) {
145
// add offset
146
moveResult.shapeToUpdate[geometryPointIndex].add(offset.x, offset.y, offset.z);
147
// apply snap to active grid
148
moveResult.shapeToUpdate[geometryPointIndex] = viewNet->snapToActiveGrid(moveResult.shapeToUpdate[geometryPointIndex]);
149
} else {
150
throw ProcessError("trying to move an invalid position");
151
}
152
}
153
} else {
154
// set values in moveResult
155
moveResult.shapeToUpdate = moveOperation->shapeToMove;
156
// move entire shape
157
for (auto& geometryPointIndex : moveResult.shapeToUpdate) {
158
if (geometryPointIndex != Position::INVALID) {
159
// add offset
160
geometryPointIndex.add(offset.x, offset.y, offset.z);
161
// apply snap to active grid
162
geometryPointIndex = viewNet->snapToActiveGrid(geometryPointIndex);
163
} else {
164
throw ProcessError("trying to move an invalid position");
165
}
166
}
167
// check if we're adjusting width or height
168
if ((moveOperation->operationType == GNEMoveOperation::OperationType::WIDTH) ||
169
(moveOperation->operationType == GNEMoveOperation::OperationType::HEIGHT) ||
170
(moveOperation->operationType == GNEMoveOperation::OperationType::LENGTH)) {
171
// calculate extrapolate vector
172
moveResult.shapeToUpdate = calculateExtrapolatedVector(moveOperation, moveResult);
173
}
174
}
175
// move shape element
176
moveOperation->moveElement->setMoveShape(moveResult);
177
}
178
179
180
void
181
GNEMoveElement::commitMove(const GNEViewNet* viewNet, GNEMoveOperation* moveOperation, const GNEMoveOffset& offset, GNEUndoList* undoList) {
182
// declare move result
183
GNEMoveResult moveResult(moveOperation);
184
// check if we're moving over a lane shape, an entire shape or only certain geometry point
185
if (moveOperation->firstLane) {
186
// calculate original move result
187
moveResult.newFirstLane = moveOperation->firstLane;
188
moveResult.newFirstPos = moveOperation->firstPosition;
189
moveResult.newLastLane = moveOperation->lastLane;
190
moveResult.newLastPos = moveOperation->lastPosition;
191
// set original positions in element
192
moveOperation->moveElement->setMoveShape(moveResult);
193
// calculate movement over lane depending if element has more than one lane
194
if (moveOperation->lastLane) {
195
if ((moveOperation->firstPosition != INVALID_DOUBLE) && (moveOperation->lastPosition != INVALID_DOUBLE)) {
196
// move both first and last positions
197
calculateLanePositions(moveResult.newFirstPos, moveResult.newLastPos, viewNet, moveOperation->firstLane,
198
moveOperation->firstPosition, moveOperation->lastLane, moveOperation->lastPosition,
199
moveOperation->clickedFirstLane, offset);
200
} else if (moveOperation->firstPosition != INVALID_DOUBLE) {
201
// move first position
202
calculateLanePosition(moveResult.newFirstPos, viewNet, moveOperation->firstLane, moveOperation->firstPosition, offset);
203
} else if (moveOperation->lastPosition != INVALID_DOUBLE) {
204
// move last position
205
calculateLanePosition(moveResult.newLastPos, viewNet, moveOperation->lastLane, moveOperation->lastPosition, offset);
206
}
207
} else {
208
// continue depending if we're moving both positions
209
if ((moveOperation->firstPosition != INVALID_DOUBLE) && (moveOperation->lastPosition != INVALID_DOUBLE)) {
210
// move both first and last positions in the same lane
211
calculateLanePositions(moveResult.newFirstPos, moveResult.newLastPos, viewNet, moveOperation->firstLane,
212
moveOperation->firstPosition, moveOperation->lastPosition, offset);
213
} else if (moveOperation->firstPosition != INVALID_DOUBLE) {
214
// move first position
215
calculateLanePosition(moveResult.newFirstPos, viewNet, moveOperation->firstLane, moveOperation->firstPosition, offset);
216
} else if (moveOperation->lastPosition != INVALID_DOUBLE) {
217
// move last position
218
calculateLanePosition(moveResult.newLastPos, viewNet, moveOperation->firstLane, moveOperation->lastPosition, offset);
219
}
220
// calculate new lane
221
if (moveOperation->allowChangeLane) {
222
calculateNewLaneChange(viewNet, moveOperation->firstLane, moveResult.newFirstLane, moveResult.firstLaneOffset);
223
}
224
}
225
} else {
226
// set original geometry points to move
227
moveResult.geometryPointsToMove = moveOperation->originalGeometryPoints;
228
// set shapeToUpdate with originalPosOverLanes
229
moveResult.shapeToUpdate = moveOperation->originalShape;
230
// first restore original geometry geometry
231
moveOperation->moveElement->setMoveShape(moveResult);
232
// set new geometry points to move
233
moveResult.geometryPointsToMove = moveOperation->geometryPointsToMove;
234
// set values in moveResult
235
moveResult.shapeToUpdate = moveOperation->shapeToMove;
236
// check if we're moving an entire shape or only certain geometry point
237
if (moveOperation->geometryPointsToMove.size() > 0) {
238
// only move certain geometry points
239
for (const auto& geometryPointIndex : moveOperation->geometryPointsToMove) {
240
if (moveResult.shapeToUpdate[geometryPointIndex] != Position::INVALID) {
241
// add offset
242
moveResult.shapeToUpdate[geometryPointIndex].add(offset.x, offset.y, offset.z);
243
// apply snap to active grid
244
moveResult.shapeToUpdate[geometryPointIndex] = viewNet->snapToActiveGrid(moveResult.shapeToUpdate[geometryPointIndex]);
245
} else {
246
throw ProcessError("trying to move an invalid position");
247
}
248
}
249
// remove double points if merge points is enabled (only in commitMove)
250
if (viewNet->getViewParent()->getMoveFrame()->getCommonMoveOptions()->getMergeGeometryPoints() && (moveResult.shapeToUpdate.size() > 2)) {
251
moveResult.shapeToUpdate.removeDoublePoints(2);
252
}
253
} else {
254
// move entire shape
255
for (auto& geometryPointIndex : moveResult.shapeToUpdate) {
256
if (geometryPointIndex != Position::INVALID) {
257
// add offset
258
geometryPointIndex.add(offset.x, offset.y, offset.z);
259
// apply snap to active grid
260
geometryPointIndex = viewNet->snapToActiveGrid(geometryPointIndex);
261
} else {
262
throw ProcessError("trying to move an invalid position");
263
}
264
}
265
// check if we're adjusting width or height
266
if ((moveOperation->operationType == GNEMoveOperation::OperationType::WIDTH) ||
267
(moveOperation->operationType == GNEMoveOperation::OperationType::HEIGHT) ||
268
(moveOperation->operationType == GNEMoveOperation::OperationType::LENGTH)) {
269
// calculate extrapolate vector
270
moveResult.shapeToUpdate = calculateExtrapolatedVector(moveOperation, moveResult);
271
}
272
}
273
}
274
// commit move shape
275
moveOperation->moveElement->commitMoveShape(moveResult, undoList);
276
}
277
278
279
double
280
GNEMoveElement::calculateLaneOffset(const GNEViewNet* viewNet, const GNELane* lane, const double firstPosition, const double lastPosition,
281
const GNEMoveOffset& offset) {
282
// get lane shape lenght
283
const auto laneShapeLength = lane->getLaneShape().length2D();
284
// declare laneOffset
285
double laneOffset = 0;
286
// calculate central position between two given positions
287
const double offsetCentralPosition = (firstPosition + lastPosition) * 0.5;
288
// calculate middle length between two given positions
289
const double middleLength = std::abs(lastPosition - firstPosition) * 0.5;
290
// calculate lane position at offset given by offsetCentralPosition
291
Position laneCentralPosition = lane->getLaneShape().positionAtOffset2D(offsetCentralPosition);
292
// apply offset to positionAtCentralPosition
293
laneCentralPosition.add(offset.x, offset.y, offset.z);
294
// snap to grid
295
laneCentralPosition = viewNet->snapToActiveGrid(laneCentralPosition);
296
// calculate offset over lane using laneCentralPosition
297
const double offsetLaneCentralPositionPerpendicular = lane->getLaneShape().nearest_offset_to_point2D(laneCentralPosition);
298
// check if offset is within lane shape
299
if (offsetLaneCentralPositionPerpendicular == -1) {
300
// calculate non-perpendicular offset over lane using laneCentralPosition
301
const double offsetLaneCentralPosition = lane->getLaneShape().nearest_offset_to_point2D(laneCentralPosition, false);
302
// due laneCentralPosition is out of lane shape, then place positions in extremes
303
if (offsetLaneCentralPosition == 0) {
304
laneOffset = firstPosition;
305
} else {
306
laneOffset = lastPosition - laneShapeLength;
307
}
308
} else {
309
// laneCentralPosition is within of lane shapen, then calculate offset using middlelength
310
if ((offsetLaneCentralPositionPerpendicular - middleLength) < 0) {
311
laneOffset = firstPosition;
312
} else if ((offsetLaneCentralPositionPerpendicular + middleLength) > laneShapeLength) {
313
laneOffset = lastPosition - laneShapeLength;
314
} else {
315
laneOffset = (offsetCentralPosition - offsetLaneCentralPositionPerpendicular);
316
}
317
}
318
return laneOffset;
319
}
320
321
322
void
323
GNEMoveElement::calculateLanePosition(double& posOverLane, const GNEViewNet* viewNet, const GNELane* lane,
324
const double lanePos, const GNEMoveOffset& offset) {
325
// get lane offset
326
const double laneOffset = calculateLaneOffset(viewNet, lane, lanePos, lanePos, offset);
327
// update lane position
328
posOverLane = (lanePos - laneOffset) / lane->getLengthGeometryFactor();
329
}
330
331
332
void
333
GNEMoveElement::calculateLanePositions(double& starPos, double& endPos, const GNEViewNet* viewNet, const GNELane* lane,
334
const double firstPosOverLane, const double lastPosOverLane, const GNEMoveOffset& offset) {
335
// get lane offset
336
const double laneOffset = calculateLaneOffset(viewNet, lane, firstPosOverLane, lastPosOverLane, offset);
337
// update moveResult
338
starPos = (firstPosOverLane - laneOffset) / lane->getLengthGeometryFactor();
339
endPos = (lastPosOverLane - laneOffset) / lane->getLengthGeometryFactor();
340
}
341
342
343
void
344
GNEMoveElement::calculateLanePositions(double& starPos, double& endPos, const GNEViewNet* viewNet, const GNELane* firstLane,
345
const double firstPosOverLane, const GNELane* lastLane, const double lastPosOverLane,
346
const bool firstLaneClicked, const GNEMoveOffset& offset) {
347
// declare offset
348
double laneOffset = 0;
349
// calculate offset depending if we clicked over the first or over the second lane
350
if (firstLaneClicked) {
351
// calculate lane start position for first lane
352
calculateLanePosition(starPos, viewNet, firstLane, firstPosOverLane, offset);
353
// calculate offset
354
laneOffset = (starPos - firstPosOverLane);
355
// set end position
356
endPos = lastPosOverLane + laneOffset;
357
// adjust offset
358
const double lastLaneLength = lastLane->getLaneShape().length2D();
359
if (endPos > lastLaneLength) {
360
laneOffset = (lastLaneLength - lastPosOverLane);
361
}
362
if (endPos < 0) {
363
laneOffset = (0 - lastPosOverLane);
364
}
365
} else {
366
// calculate lane start position for first lane
367
calculateLanePosition(endPos, viewNet, lastLane, lastPosOverLane, offset);
368
// calculate offset
369
laneOffset = (endPos - lastPosOverLane);
370
// set start position
371
starPos = firstPosOverLane + laneOffset;
372
// adjust offset
373
const double firstLaneLength = firstLane->getLaneShape().length2D();
374
if (starPos > firstLaneLength) {
375
laneOffset = (firstLaneLength - firstPosOverLane);
376
}
377
if (starPos < 0) {
378
laneOffset = (0 - firstPosOverLane);
379
}
380
}
381
// set positions with the adjusted offset
382
starPos = firstPosOverLane + laneOffset;
383
endPos = lastPosOverLane + laneOffset;
384
}
385
386
387
void
388
GNEMoveElement::calculateNewLaneChange(const GNEViewNet* viewNet, const GNELane* originalLane, const GNELane*& newLane, double& laneOffset) {
389
// get cursor position
390
const Position cursorPosition = viewNet->getPositionInformation();
391
// iterate over edge lanes
392
for (const auto& lane : originalLane->getParentEdge()->getChildLanes()) {
393
// avoid moveOperation lane
394
if (lane != originalLane) {
395
// calculate offset over lane shape
396
const double offSet = lane->getLaneShape().nearest_offset_to_point2D(cursorPosition, true);
397
// calculate position over lane shape
398
const Position posOverLane = lane->getLaneShape().positionAtOffset2D(offSet);
399
// check distance
400
if (posOverLane.distanceSquaredTo2D(cursorPosition) < 1) {
401
// update newlane
402
newLane = lane;
403
// calculate offset over moveOperation lane
404
const double offsetMoveOperationLane = originalLane->getLaneShape().nearest_offset_to_point2D(cursorPosition, true);
405
// calculate position over moveOperation lane
406
const Position posOverMoveOperationLane = originalLane->getLaneShape().positionAtOffset2D(offsetMoveOperationLane);
407
// update moveResult of laneOffset
408
laneOffset = posOverLane.distanceTo2D(posOverMoveOperationLane);
409
// change sign of moveResult laneOffset depending of lane index
410
if (originalLane->getIndex() < newLane->getIndex()) {
411
laneOffset *= -1;
412
}
413
}
414
}
415
}
416
}
417
418
419
PositionVector
420
GNEMoveElement::calculateExtrapolatedVector(const GNEMoveOperation* moveOperation, const GNEMoveResult& moveResult) {
421
// get original shape half length
422
const double halfLength = moveOperation->originalShape.length2D() * -0.5;
423
// get original shape and extrapolate
424
PositionVector extendedShape = moveOperation->originalShape;
425
extendedShape.extrapolate2D(10e5);
426
// get geometry point
427
const Position geometryPoint = moveOperation->firstGeometryPoint ? moveResult.shapeToUpdate.front() : moveResult.shapeToUpdate.back();
428
// calculate offsets to first and last positions
429
const double offset = extendedShape.nearest_offset_to_point2D(geometryPoint, false);
430
// calculate extrapolate value
431
double extrapolateValue = (10e5 - offset);
432
// adjust extrapolation
433
if (moveOperation->firstGeometryPoint) {
434
if (extrapolateValue < halfLength) {
435
extrapolateValue = (halfLength - POSITION_EPS);
436
}
437
} else {
438
if (extrapolateValue > halfLength) {
439
extrapolateValue = (halfLength - POSITION_EPS);
440
}
441
}
442
// restore shape in in moveResult
443
PositionVector extrapolatedShape = moveOperation->shapeToMove;
444
// extrapolate
445
extrapolatedShape.extrapolate2D(extrapolateValue);
446
// check if return reverse
447
if (moveOperation->firstGeometryPoint) {
448
return extrapolatedShape;
449
} else {
450
return extrapolatedShape.reverse();
451
}
452
}
453
454
/****************************************************************************/
455
456