Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/src/netedit/elements/moving/GNEMoveElementLaneDouble.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 GNEMoveElementLaneDouble.cpp
15
/// @author Pablo Alvarez Lopez
16
/// @date Sep 2025
17
///
18
// Class used for elements that can be moved over a lane with two positions
19
/****************************************************************************/
20
#include <config.h>
21
22
#include <netedit/changes/GNEChange_Attribute.h>
23
#include <netedit/changes/GNEChange_Connection.h>
24
#include <netedit/elements/network/GNEConnection.h>
25
#include <netedit/frames/common/GNEMoveFrame.h>
26
#include <netedit/GNENet.h>
27
#include <netedit/GNETagProperties.h>
28
#include <netedit/GNEUndoList.h>
29
#include <netedit/GNEViewParent.h>
30
#include <utils/vehicle/SUMORouteHandler.h>
31
32
#include "GNEMoveElementLaneDouble.h"
33
#include "GNEMoveElementLaneSingle.h"
34
35
// ===========================================================================
36
// static members
37
// ===========================================================================
38
39
const double GNEMoveElementLaneDouble::defaultSize = 10;
40
41
// ===========================================================================
42
// member method definitions
43
// ===========================================================================
44
45
GNEMoveElementLaneDouble::GNEMoveElementLaneDouble(GNEAttributeCarrier* element,
46
SumoXMLAttr startPosAttr, double& startPosValue, SumoXMLAttr endPosAttr,
47
double& endPosValue, bool& friendlyPosition) :
48
GNEMoveElement(element),
49
myStartPos(new GNEMoveElementLaneSingle(element, startPosAttr, startPosValue, friendlyPosition,
50
GNEMoveElementLaneSingle::PositionType::STARPOS)),
51
myEndPos(new GNEMoveElementLaneSingle(element, endPosAttr, endPosValue, friendlyPosition,
52
GNEMoveElementLaneSingle::PositionType::ENDPOS)) {
53
}
54
55
56
GNEMoveElementLaneDouble::~GNEMoveElementLaneDouble() {
57
delete myStartPos;
58
delete myEndPos;
59
}
60
61
62
GNEMoveOperation*
63
GNEMoveElementLaneDouble::getMoveOperation() {
64
const auto& parentLanes = myMovedElement->getHierarchicalElement()->getParentLanes();
65
// get allow change lane
66
const bool allowChangeLane = myMovedElement->getNet()->getViewParent()->getMoveFrame()->getCommonMoveOptions()->getAllowChangeLane();
67
// fist check if we're moving only extremes
68
if (myMovedElement->drawMovingGeometryPoints()) {
69
// get geometry points under cursor
70
const auto& geometryPoints = gViewObjectsHandler.getSelectedGeometryPoints(myMovedElement->getGUIGlObject());
71
// continue depending of clicked geometry point
72
if (geometryPoints.empty()) {
73
return nullptr;
74
} else {
75
if (geometryPoints.front() == 0) {
76
// move start position
77
return myStartPos->getMoveOperation();
78
} else {
79
// move end position
80
return myEndPos->getMoveOperation();
81
}
82
}
83
} else if ((myStartPos->myPosOverLane != INVALID_DOUBLE) && (myEndPos->myPosOverLane != INVALID_DOUBLE)) {
84
// move both start and end positions depending of number of lanes
85
if (parentLanes.size() > 1) {
86
if (gViewObjectsHandler.isObjectSelected(parentLanes.front())) {
87
return new GNEMoveOperation(myMovedElement->getMoveElement(), parentLanes.front(), myStartPos->myPosOverLane, parentLanes.back(), myEndPos->myPosOverLane, true, false);
88
} else if (gViewObjectsHandler.isObjectSelected(parentLanes.back())) {
89
return new GNEMoveOperation(myMovedElement->getMoveElement(), parentLanes.front(), myStartPos->myPosOverLane, parentLanes.back(), myEndPos->myPosOverLane, false, false);
90
} else {
91
// temporal, in the future will be allow, clicking in the intermediate lanes
92
return nullptr;
93
}
94
} else {
95
return new GNEMoveOperation(myMovedElement->getMoveElement(), parentLanes.front(), myStartPos->myPosOverLane, myEndPos->myPosOverLane, allowChangeLane);
96
}
97
} else if (myStartPos->myPosOverLane != INVALID_DOUBLE) {
98
// move only start position
99
return myStartPos->getMoveOperation();
100
} else if (myEndPos->myPosOverLane != INVALID_DOUBLE) {
101
// move only end position
102
return myEndPos->getMoveOperation();
103
} else {
104
// start and end positions undefined, then nothing to move
105
return nullptr;
106
}
107
}
108
109
110
std::string
111
GNEMoveElementLaneDouble::getMovingAttribute(SumoXMLAttr key) const {
112
// position attributes
113
if (key == myStartPos->myPosAttr) {
114
return myStartPos->getMovingAttribute(key);
115
} else if (key == myEndPos->myPosAttr) {
116
return myEndPos->getMovingAttribute(key);
117
} else {
118
// other attributes
119
switch (key) {
120
case SUMO_ATTR_LANE:
121
case SUMO_ATTR_LANES:
122
return GNEAttributeCarrier::parseIDs(myMovedElement->getHierarchicalElement()->getParentLanes());
123
case SUMO_ATTR_FRIENDLY_POS:
124
return myStartPos->getMovingAttribute(key);
125
case GNE_ATTR_SHIFTLANEINDEX:
126
return "";
127
case SUMO_ATTR_LENGTH:
128
case GNE_ATTR_SIZE:
129
if (myMovedElement->isTemplate()) {
130
return toString(myTemplateSize);
131
} else {
132
return toString(getMovingAttributeDouble(GNE_ATTR_SIZE));
133
}
134
case GNE_ATTR_FORCESIZE:
135
return toString(myTemplateForceSize);
136
case GNE_ATTR_REFERENCE:
137
return SUMOXMLDefinitions::ReferencePositions.getString(myReferencePosition);
138
default:
139
return myMovedElement->getCommonAttribute(key);
140
}
141
}
142
}
143
144
145
double
146
GNEMoveElementLaneDouble::getMovingAttributeDouble(SumoXMLAttr key) const {
147
// position attributes
148
if (key == myStartPos->myPosAttr) {
149
return myStartPos->myPosOverLane;
150
} else if (key == myEndPos->myPosAttr) {
151
return myEndPos->myPosOverLane;
152
} else {
153
// other attributes
154
switch (key) {
155
case SUMO_ATTR_CENTER:
156
return (getStartFixedPositionOverLane(false) + getEndFixedPositionOverLane(false)) * 0.5;
157
case SUMO_ATTR_LENGTH:
158
case GNE_ATTR_SIZE:
159
if (myMovedElement->isTemplate()) {
160
return myTemplateSize;
161
} else {
162
return (getEndFixedPositionOverLane(false) - getStartFixedPositionOverLane(false));
163
}
164
default:
165
throw InvalidArgument(myMovedElement->getTagStr() + " doesn't have a moving attribute of type '" + toString(key) + "'");
166
}
167
}
168
}
169
170
171
Position
172
GNEMoveElementLaneDouble::getMovingAttributePosition(SumoXMLAttr key) const {
173
return myMovedElement->getCommonAttributePosition(key);
174
}
175
176
177
PositionVector
178
GNEMoveElementLaneDouble::getMovingAttributePositionVector(SumoXMLAttr key) const {
179
return myMovedElement->getCommonAttributePositionVector(key);
180
}
181
182
183
void
184
GNEMoveElementLaneDouble::setMovingAttribute(SumoXMLAttr key, const std::string& value, GNEUndoList* undoList) {
185
// position attributes
186
if (key == myStartPos->myPosAttr) {
187
GNEChange_Attribute::changeAttribute(myMovedElement, key, value, undoList);
188
} else if (key == myEndPos->myPosAttr) {
189
GNEChange_Attribute::changeAttribute(myMovedElement, key, value, undoList);
190
} else {
191
// other attributes
192
switch (key) {
193
case SUMO_ATTR_LANE:
194
case SUMO_ATTR_LANES:
195
case SUMO_ATTR_FRIENDLY_POS:
196
case GNE_ATTR_SHIFTLANEINDEX:
197
case GNE_ATTR_REFERENCE:
198
case GNE_ATTR_FORCESIZE:
199
GNEChange_Attribute::changeAttribute(myMovedElement, key, value, undoList);
200
break;
201
case SUMO_ATTR_LENGTH:
202
if (myMovedElement->isTemplate()) {
203
// use size value
204
GNEChange_Attribute::changeAttribute(myMovedElement, key, value, undoList);
205
} else {
206
// change end position
207
GNEChange_Attribute::changeAttribute(myMovedElement, myEndPos->myPosAttr, toString(getStartFixedPositionOverLane(true) + GNEAttributeCarrier::parse<double>(value)), undoList);
208
}
209
break;
210
case GNE_ATTR_SIZE:
211
if (myMovedElement->isTemplate()) {
212
GNEChange_Attribute::changeAttribute(myMovedElement, key, value, undoList);
213
} else {
214
setSize(value, undoList);
215
}
216
break;
217
default:
218
myMovedElement->setCommonAttribute(key, value, undoList);
219
break;
220
}
221
}
222
}
223
224
225
bool
226
GNEMoveElementLaneDouble::isMovingAttributeValid(SumoXMLAttr key, const std::string& value) const {
227
// position attributes
228
if (key == myStartPos->myPosAttr) {
229
return myStartPos->isMovingAttributeValid(key, value);
230
} else if (key == myEndPos->myPosAttr) {
231
return myEndPos->isMovingAttributeValid(key, value);
232
} else {
233
// other attributes
234
switch (key) {
235
case SUMO_ATTR_LANE:
236
case SUMO_ATTR_LANES:
237
if (value.empty()) {
238
return false;
239
} else {
240
return GNEAttributeCarrier::canParse<std::vector<GNELane*> >(myMovedElement->getNet(), value, true);
241
}
242
case SUMO_ATTR_FRIENDLY_POS:
243
return myStartPos->isMovingAttributeValid(key, value);
244
case GNE_ATTR_SHIFTLANEINDEX:
245
return true;
246
case SUMO_ATTR_LENGTH:
247
case GNE_ATTR_SIZE:
248
if (value.empty()) {
249
return false;
250
} else {
251
return GNEAttributeCarrier::canParse<double>(value) && GNEAttributeCarrier::parse<double>(value) >= POSITION_EPS;
252
}
253
case GNE_ATTR_FORCESIZE:
254
return GNEAttributeCarrier::canParse<bool>(value);
255
case GNE_ATTR_REFERENCE:
256
return SUMOXMLDefinitions::ReferencePositions.hasString(value);
257
default:
258
return myMovedElement->isCommonAttributeValid(key, value);
259
}
260
}
261
}
262
263
264
void
265
GNEMoveElementLaneDouble::setMovingAttribute(SumoXMLAttr key, const std::string& value) {
266
// position attributes
267
if (key == myStartPos->myPosAttr) {
268
myStartPos->setMovingAttribute(key, value);
269
} else if (key == myEndPos->myPosAttr) {
270
myEndPos->setMovingAttribute(key, value);
271
} else {
272
// other attributes
273
switch (key) {
274
case SUMO_ATTR_FRIENDLY_POS:
275
myStartPos->setMovingAttribute(key, value);
276
break;
277
case SUMO_ATTR_LENGTH:
278
case GNE_ATTR_SIZE:
279
if (value.empty()) {
280
myTemplateSize = defaultSize;
281
} else {
282
myTemplateSize = GNEAttributeCarrier::parse<double>(value);
283
}
284
break;
285
case GNE_ATTR_FORCESIZE:
286
myTemplateForceSize = GNEAttributeCarrier::parse<bool>(value);
287
break;
288
case GNE_ATTR_REFERENCE:
289
myReferencePosition = SUMOXMLDefinitions::ReferencePositions.get(value);
290
break;
291
default:
292
return myMovedElement->setCommonAttribute(key, value);
293
}
294
}
295
}
296
297
298
void
299
GNEMoveElementLaneDouble::removeGeometryPoint(const Position /*clickedPosition*/, GNEUndoList* /*undoList*/) {
300
// nothing to do here
301
}
302
303
304
bool
305
GNEMoveElementLaneDouble::isMoveElementValid() const {
306
// first check if lanes are connected
307
if (!GNEAdditional::areLaneConsecutives(myMovedElement->getHierarchicalElement()->getParentLanes())) {
308
return false;
309
} else if (!GNEAdditional::areLaneConnected(myMovedElement->getHierarchicalElement()->getParentLanes())) {
310
return false;
311
} else if (!myStartPos->isMoveElementValid() || !myEndPos->isMoveElementValid()) {
312
return false;
313
} else if ((myMovedElement->getHierarchicalElement()->getParentLanes().size() == 1) &&
314
(myStartPos->getFixedPositionOverLane(false) > (myEndPos->getFixedPositionOverLane(false) - POSITION_EPS))) {
315
return false;
316
} else {
317
return true;
318
}
319
}
320
321
322
std::string
323
GNEMoveElementLaneDouble::getMovingProblem() const {
324
// first check if lanes are connected
325
if (!GNEAdditional::areLaneConsecutives(myMovedElement->getHierarchicalElement()->getParentLanes())) {
326
return TL("Lanes aren't consecutives");
327
} else if (!GNEAdditional::areLaneConnected(myMovedElement->getHierarchicalElement()->getParentLanes())) {
328
return TL("Lanes aren't connected");
329
} else if (!myStartPos->isMoveElementValid()) {
330
return myStartPos->getMovingProblem();
331
} else if (!myEndPos->isMoveElementValid()) {
332
return myEndPos->getMovingProblem();
333
} else if ((myMovedElement->getHierarchicalElement()->getParentLanes().size() == 1) &&
334
(myStartPos->getFixedPositionOverLane(false) > (myEndPos->getFixedPositionOverLane(false) - POSITION_EPS))) {
335
return TL("starPos > (endPos - EPS)");
336
} else {
337
return "";
338
}
339
}
340
341
342
void
343
GNEMoveElementLaneDouble::fixMovingProblem() {
344
const auto undolist = myMovedElement->getNet()->getUndoList();
345
// iterate over all lanes and build connections
346
for (int i = 1; i < (int)myMovedElement->getHierarchicalElement()->getParentLanes().size(); i++) {
347
// get lanes
348
const auto firstLane = myMovedElement->getHierarchicalElement()->getParentLanes().at(i - 1);
349
const auto secondLane = myMovedElement->getHierarchicalElement()->getParentLanes().at(i);
350
// search connection
351
bool foundConnection = false;
352
for (const auto& connection : firstLane->getParentEdge()->getGNEConnections()) {
353
if ((connection->getLaneFrom() == firstLane) && (connection->getLaneTo() == secondLane)) {
354
foundConnection = true;
355
break;
356
}
357
}
358
// check if connection exist
359
if (!foundConnection) {
360
// create new connection manually
361
NBEdge::Connection newCon(firstLane->getIndex(), secondLane->getParentEdge()->getNBEdge(), secondLane->getIndex());
362
// allow to undo creation of new lane
363
undolist->add(new GNEChange_Connection(firstLane->getParentEdge(), newCon, false, true), true);
364
}
365
}
366
// Fix both position
367
myStartPos->fixMovingProblem();
368
myEndPos->fixMovingProblem();
369
if (myMovedElement->getHierarchicalElement()->getParentLanes().size() == 1) {
370
// extra if starPos > endPos (endPos is dominant)
371
const double finalLenght = myMovedElement->getHierarchicalElement()->getParentLanes().back()->getParentEdge()->getNBEdge()->getFinalLength();
372
const double maxStartPos = (myEndPos->myPosOverLane == INVALID_DOUBLE) ? finalLenght : (myEndPos->getFixedPositionOverLane(false) - POSITION_EPS);
373
if (maxStartPos < POSITION_EPS) {
374
myMovedElement->setAttribute(myStartPos->myPosAttr, "0", undolist);
375
myMovedElement->setAttribute(myEndPos->myPosAttr, toString(POSITION_EPS), undolist);
376
} else if (myStartPos->getFixedPositionOverLane(false) > maxStartPos) {
377
// use truncate to avoid problem precission under certain conditions
378
const double newStartPos = (std::trunc(maxStartPos * 1000) / 1000);
379
myMovedElement->setAttribute(myStartPos->myPosAttr, toString(newStartPos), undolist);
380
}
381
}
382
}
383
384
385
void
386
GNEMoveElementLaneDouble::writeMoveAttributes(OutputDevice& device, const bool writeLength) const {
387
// lane/s
388
if (myMovedElement->getTagProperty()->hasAttribute(SUMO_ATTR_LANE)) {
389
device.writeAttr(SUMO_ATTR_LANE, myMovedElement->getAttribute(SUMO_ATTR_LANE));
390
} else {
391
device.writeAttr(SUMO_ATTR_LANES, myMovedElement->getAttribute(SUMO_ATTR_LANES));
392
}
393
// write start position
394
if (myStartPos->myPosOverLane != myMovedElement->getTagProperty()->getDefaultDoubleValue(myStartPos->myPosAttr)) {
395
device.writeAttr(myStartPos->myPosAttr, myStartPos->myPosOverLane);
396
}
397
// write end position depending of lenght
398
if (writeLength) {
399
device.writeAttr(SUMO_ATTR_LENGTH, (myEndPos->myPosOverLane - myStartPos->myPosOverLane));
400
} else if (myEndPos->myPosOverLane != myMovedElement->getTagProperty()->getDefaultDoubleValue(myEndPos->myPosAttr)) {
401
device.writeAttr(myEndPos->myPosAttr, myEndPos->myPosOverLane);
402
}
403
// friendly position (only if true)
404
if (myStartPos->myFriendlyPos) {
405
device.writeAttr(SUMO_ATTR_FRIENDLY_POS, myStartPos->myFriendlyPos);
406
}
407
}
408
409
410
double
411
GNEMoveElementLaneDouble::getStartFixedPositionOverLane(const bool adjustGeometryFactor) const {
412
if (myStartPos->getFixedPositionOverLane(adjustGeometryFactor) < 0) {
413
return 0;
414
} else if (myStartPos->getFixedPositionOverLane(adjustGeometryFactor) > (myEndPos->getFixedPositionOverLane(adjustGeometryFactor) - POSITION_EPS)) {
415
return (myEndPos->getFixedPositionOverLane(adjustGeometryFactor) - POSITION_EPS);
416
} else {
417
return myStartPos->getFixedPositionOverLane(adjustGeometryFactor);
418
}
419
}
420
421
422
double
423
GNEMoveElementLaneDouble::getEndFixedPositionOverLane(const bool adjustGeometryFactor) const {
424
if (myEndPos->getFixedPositionOverLane(adjustGeometryFactor) < POSITION_EPS) {
425
return POSITION_EPS;
426
} else if (myStartPos->getFixedPositionOverLane(adjustGeometryFactor) > (myEndPos->getFixedPositionOverLane(adjustGeometryFactor) - POSITION_EPS)) {
427
return myEndPos->getFixedPositionOverLane(adjustGeometryFactor);
428
} else {
429
return myEndPos->getFixedPositionOverLane(adjustGeometryFactor);
430
}
431
}
432
433
434
void
435
GNEMoveElementLaneDouble::setMoveShape(const GNEMoveResult& moveResult) {
436
// check if we're moving both points
437
if ((moveResult.newFirstPos != INVALID_DOUBLE) && (moveResult.newLastPos != INVALID_DOUBLE)) {
438
// change both position
439
myStartPos->setMoveShape(moveResult);
440
myEndPos->setMoveShape(moveResult);
441
// set lateral offset
442
myMovingLateralOffset = moveResult.firstLaneOffset;
443
} else if (moveResult.newFirstPos != INVALID_DOUBLE) {
444
// change only start position
445
myStartPos->setMoveShape(moveResult);
446
} else if (moveResult.newLastPos != INVALID_DOUBLE) {
447
myEndPos->setMoveShape(moveResult);
448
}
449
// update geometry
450
myMovedElement->updateGeometry();
451
}
452
453
454
void
455
GNEMoveElementLaneDouble::commitMoveShape(const GNEMoveResult& moveResult, GNEUndoList* undoList) {
456
// begin change attribute
457
undoList->begin(myMovedElement, TLF("position of %", myMovedElement->getTagStr()));
458
// check if we're moving both points
459
if ((moveResult.newFirstPos != INVALID_DOUBLE) && (moveResult.newLastPos != INVALID_DOUBLE)) {
460
// set both positions
461
myStartPos->commitMoveShape(moveResult, undoList);
462
myEndPos->commitMoveShape(moveResult, undoList);
463
} else if (moveResult.newFirstPos != INVALID_DOUBLE) {
464
// set only start position
465
myStartPos->commitMoveShape(moveResult, undoList);
466
} else if (moveResult.newLastPos != INVALID_DOUBLE) {
467
// set only end position
468
myEndPos->commitMoveShape(moveResult, undoList);
469
}
470
// end change attribute
471
undoList->end();
472
}
473
474
475
void
476
GNEMoveElementLaneDouble::setSize(const std::string& value, GNEUndoList* undoList) {
477
const auto laneLength = myMovedElement->getHierarchicalElement()->getParentLanes().front()->getLaneShapeLength();
478
const double newSize = GNEAttributeCarrier::parse<double>(value);
479
// continue depending of values of start und end position
480
if ((myStartPos->myPosOverLane != INVALID_DOUBLE) && (myEndPos->myPosOverLane != INVALID_DOUBLE)) {
481
// get middle lengths
482
const double center = (getStartFixedPositionOverLane(false) + getEndFixedPositionOverLane(false)) * 0.5;
483
// calculate new lenghts
484
double newStartPos = center - (newSize * 0.5);
485
double newEndPos = center + (newSize * 0.5);
486
// adjust positions
487
if (newStartPos < 0) {
488
newStartPos = 0;
489
}
490
if (newEndPos > laneLength) {
491
newEndPos = laneLength;
492
}
493
// set new start und end positions
494
undoList->begin(myMovedElement, TLF(" %'s size", myMovedElement->getTagStr()));
495
GNEChange_Attribute::changeAttribute(myMovedElement, myStartPos->myPosAttr, toString(newStartPos), undoList);
496
GNEChange_Attribute::changeAttribute(myMovedElement, myEndPos->myPosAttr, toString(newEndPos), undoList);
497
undoList->end();
498
} else if (myStartPos->myPosOverLane != INVALID_DOUBLE) {
499
double newStartPos = laneLength - newSize;
500
// adjust new StartPos
501
if (newStartPos < 0) {
502
newStartPos = 0;
503
}
504
undoList->begin(myMovedElement, TLF(" %'s size", myMovedElement->getTagStr()));
505
GNEChange_Attribute::changeAttribute(myMovedElement, myStartPos->myPosAttr, toString(newStartPos), undoList);
506
undoList->end();
507
} else if (myEndPos->myPosOverLane != INVALID_DOUBLE) {
508
double newEndPos = newSize;
509
// adjust endPos
510
if (newEndPos > laneLength) {
511
newEndPos = laneLength;
512
}
513
undoList->begin(myMovedElement, TLF(" %'s size", myMovedElement->getTagStr()));
514
GNEChange_Attribute::changeAttribute(myMovedElement, myEndPos->myPosAttr, toString(newEndPos), undoList);
515
undoList->end();
516
}
517
}
518
519
/****************************************************************************/
520
521