Path: blob/main/src/netedit/elements/moving/GNEMoveElementLaneDouble.cpp
185790 views
/****************************************************************************/1// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo2// Copyright (C) 2001-2025 German Aerospace Center (DLR) and others.3// This program and the accompanying materials are made available under the4// terms of the Eclipse Public License 2.0 which is available at5// https://www.eclipse.org/legal/epl-2.0/6// This Source Code may also be made available under the following Secondary7// Licenses when the conditions for such availability set forth in the Eclipse8// Public License 2.0 are satisfied: GNU General Public License, version 29// or later which is available at10// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html11// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later12/****************************************************************************/13/// @file GNEMoveElementLaneDouble.cpp14/// @author Pablo Alvarez Lopez15/// @date Sep 202516///17// Class used for elements that can be moved over a lane with two positions18/****************************************************************************/19#include <config.h>2021#include <netedit/changes/GNEChange_Attribute.h>22#include <netedit/changes/GNEChange_Connection.h>23#include <netedit/elements/network/GNEConnection.h>24#include <netedit/frames/common/GNEMoveFrame.h>25#include <netedit/GNENet.h>26#include <netedit/GNETagProperties.h>27#include <netedit/GNEUndoList.h>28#include <netedit/GNEViewParent.h>29#include <utils/vehicle/SUMORouteHandler.h>3031#include "GNEMoveElementLaneDouble.h"32#include "GNEMoveElementLaneSingle.h"3334// ===========================================================================35// static members36// ===========================================================================3738const double GNEMoveElementLaneDouble::defaultSize = 10;3940// ===========================================================================41// member method definitions42// ===========================================================================4344GNEMoveElementLaneDouble::GNEMoveElementLaneDouble(GNEAttributeCarrier* element,45SumoXMLAttr startPosAttr, double& startPosValue, SumoXMLAttr endPosAttr,46double& endPosValue, bool& friendlyPosition) :47GNEMoveElement(element),48myStartPos(new GNEMoveElementLaneSingle(element, startPosAttr, startPosValue, friendlyPosition,49GNEMoveElementLaneSingle::PositionType::STARPOS)),50myEndPos(new GNEMoveElementLaneSingle(element, endPosAttr, endPosValue, friendlyPosition,51GNEMoveElementLaneSingle::PositionType::ENDPOS)) {52}535455GNEMoveElementLaneDouble::~GNEMoveElementLaneDouble() {56delete myStartPos;57delete myEndPos;58}596061GNEMoveOperation*62GNEMoveElementLaneDouble::getMoveOperation() {63const auto& parentLanes = myMovedElement->getHierarchicalElement()->getParentLanes();64// get allow change lane65const bool allowChangeLane = myMovedElement->getNet()->getViewParent()->getMoveFrame()->getCommonMoveOptions()->getAllowChangeLane();66// fist check if we're moving only extremes67if (myMovedElement->drawMovingGeometryPoints()) {68// get geometry points under cursor69const auto& geometryPoints = gViewObjectsHandler.getSelectedGeometryPoints(myMovedElement->getGUIGlObject());70// continue depending of clicked geometry point71if (geometryPoints.empty()) {72return nullptr;73} else {74if (geometryPoints.front() == 0) {75// move start position76return myStartPos->getMoveOperation();77} else {78// move end position79return myEndPos->getMoveOperation();80}81}82} else if ((myStartPos->myPosOverLane != INVALID_DOUBLE) && (myEndPos->myPosOverLane != INVALID_DOUBLE)) {83// move both start and end positions depending of number of lanes84if (parentLanes.size() > 1) {85if (gViewObjectsHandler.isObjectSelected(parentLanes.front())) {86return new GNEMoveOperation(myMovedElement->getMoveElement(), parentLanes.front(), myStartPos->myPosOverLane, parentLanes.back(), myEndPos->myPosOverLane, true, false);87} else if (gViewObjectsHandler.isObjectSelected(parentLanes.back())) {88return new GNEMoveOperation(myMovedElement->getMoveElement(), parentLanes.front(), myStartPos->myPosOverLane, parentLanes.back(), myEndPos->myPosOverLane, false, false);89} else {90// temporal, in the future will be allow, clicking in the intermediate lanes91return nullptr;92}93} else {94return new GNEMoveOperation(myMovedElement->getMoveElement(), parentLanes.front(), myStartPos->myPosOverLane, myEndPos->myPosOverLane, allowChangeLane);95}96} else if (myStartPos->myPosOverLane != INVALID_DOUBLE) {97// move only start position98return myStartPos->getMoveOperation();99} else if (myEndPos->myPosOverLane != INVALID_DOUBLE) {100// move only end position101return myEndPos->getMoveOperation();102} else {103// start and end positions undefined, then nothing to move104return nullptr;105}106}107108109std::string110GNEMoveElementLaneDouble::getMovingAttribute(SumoXMLAttr key) const {111// position attributes112if (key == myStartPos->myPosAttr) {113return myStartPos->getMovingAttribute(key);114} else if (key == myEndPos->myPosAttr) {115return myEndPos->getMovingAttribute(key);116} else {117// other attributes118switch (key) {119case SUMO_ATTR_LANE:120case SUMO_ATTR_LANES:121return GNEAttributeCarrier::parseIDs(myMovedElement->getHierarchicalElement()->getParentLanes());122case SUMO_ATTR_FRIENDLY_POS:123return myStartPos->getMovingAttribute(key);124case GNE_ATTR_SHIFTLANEINDEX:125return "";126case SUMO_ATTR_LENGTH:127case GNE_ATTR_SIZE:128if (myMovedElement->isTemplate()) {129return toString(myTemplateSize);130} else {131return toString(getMovingAttributeDouble(GNE_ATTR_SIZE));132}133case GNE_ATTR_FORCESIZE:134return toString(myTemplateForceSize);135case GNE_ATTR_REFERENCE:136return SUMOXMLDefinitions::ReferencePositions.getString(myReferencePosition);137default:138return myMovedElement->getCommonAttribute(key);139}140}141}142143144double145GNEMoveElementLaneDouble::getMovingAttributeDouble(SumoXMLAttr key) const {146// position attributes147if (key == myStartPos->myPosAttr) {148return myStartPos->myPosOverLane;149} else if (key == myEndPos->myPosAttr) {150return myEndPos->myPosOverLane;151} else {152// other attributes153switch (key) {154case SUMO_ATTR_CENTER:155return (getStartFixedPositionOverLane(false) + getEndFixedPositionOverLane(false)) * 0.5;156case SUMO_ATTR_LENGTH:157case GNE_ATTR_SIZE:158if (myMovedElement->isTemplate()) {159return myTemplateSize;160} else {161return (getEndFixedPositionOverLane(false) - getStartFixedPositionOverLane(false));162}163default:164throw InvalidArgument(myMovedElement->getTagStr() + " doesn't have a moving attribute of type '" + toString(key) + "'");165}166}167}168169170Position171GNEMoveElementLaneDouble::getMovingAttributePosition(SumoXMLAttr key) const {172return myMovedElement->getCommonAttributePosition(key);173}174175176PositionVector177GNEMoveElementLaneDouble::getMovingAttributePositionVector(SumoXMLAttr key) const {178return myMovedElement->getCommonAttributePositionVector(key);179}180181182void183GNEMoveElementLaneDouble::setMovingAttribute(SumoXMLAttr key, const std::string& value, GNEUndoList* undoList) {184// position attributes185if (key == myStartPos->myPosAttr) {186GNEChange_Attribute::changeAttribute(myMovedElement, key, value, undoList);187} else if (key == myEndPos->myPosAttr) {188GNEChange_Attribute::changeAttribute(myMovedElement, key, value, undoList);189} else {190// other attributes191switch (key) {192case SUMO_ATTR_LANE:193case SUMO_ATTR_LANES:194case SUMO_ATTR_FRIENDLY_POS:195case GNE_ATTR_SHIFTLANEINDEX:196case GNE_ATTR_REFERENCE:197case GNE_ATTR_FORCESIZE:198GNEChange_Attribute::changeAttribute(myMovedElement, key, value, undoList);199break;200case SUMO_ATTR_LENGTH:201if (myMovedElement->isTemplate()) {202// use size value203GNEChange_Attribute::changeAttribute(myMovedElement, key, value, undoList);204} else {205// change end position206GNEChange_Attribute::changeAttribute(myMovedElement, myEndPos->myPosAttr, toString(getStartFixedPositionOverLane(true) + GNEAttributeCarrier::parse<double>(value)), undoList);207}208break;209case GNE_ATTR_SIZE:210if (myMovedElement->isTemplate()) {211GNEChange_Attribute::changeAttribute(myMovedElement, key, value, undoList);212} else {213setSize(value, undoList);214}215break;216default:217myMovedElement->setCommonAttribute(key, value, undoList);218break;219}220}221}222223224bool225GNEMoveElementLaneDouble::isMovingAttributeValid(SumoXMLAttr key, const std::string& value) const {226// position attributes227if (key == myStartPos->myPosAttr) {228return myStartPos->isMovingAttributeValid(key, value);229} else if (key == myEndPos->myPosAttr) {230return myEndPos->isMovingAttributeValid(key, value);231} else {232// other attributes233switch (key) {234case SUMO_ATTR_LANE:235case SUMO_ATTR_LANES:236if (value.empty()) {237return false;238} else {239return GNEAttributeCarrier::canParse<std::vector<GNELane*> >(myMovedElement->getNet(), value, true);240}241case SUMO_ATTR_FRIENDLY_POS:242return myStartPos->isMovingAttributeValid(key, value);243case GNE_ATTR_SHIFTLANEINDEX:244return true;245case SUMO_ATTR_LENGTH:246case GNE_ATTR_SIZE:247if (value.empty()) {248return false;249} else {250return GNEAttributeCarrier::canParse<double>(value) && GNEAttributeCarrier::parse<double>(value) >= POSITION_EPS;251}252case GNE_ATTR_FORCESIZE:253return GNEAttributeCarrier::canParse<bool>(value);254case GNE_ATTR_REFERENCE:255return SUMOXMLDefinitions::ReferencePositions.hasString(value);256default:257return myMovedElement->isCommonAttributeValid(key, value);258}259}260}261262263void264GNEMoveElementLaneDouble::setMovingAttribute(SumoXMLAttr key, const std::string& value) {265// position attributes266if (key == myStartPos->myPosAttr) {267myStartPos->setMovingAttribute(key, value);268} else if (key == myEndPos->myPosAttr) {269myEndPos->setMovingAttribute(key, value);270} else {271// other attributes272switch (key) {273case SUMO_ATTR_FRIENDLY_POS:274myStartPos->setMovingAttribute(key, value);275break;276case SUMO_ATTR_LENGTH:277case GNE_ATTR_SIZE:278if (value.empty()) {279myTemplateSize = defaultSize;280} else {281myTemplateSize = GNEAttributeCarrier::parse<double>(value);282}283break;284case GNE_ATTR_FORCESIZE:285myTemplateForceSize = GNEAttributeCarrier::parse<bool>(value);286break;287case GNE_ATTR_REFERENCE:288myReferencePosition = SUMOXMLDefinitions::ReferencePositions.get(value);289break;290default:291return myMovedElement->setCommonAttribute(key, value);292}293}294}295296297void298GNEMoveElementLaneDouble::removeGeometryPoint(const Position /*clickedPosition*/, GNEUndoList* /*undoList*/) {299// nothing to do here300}301302303bool304GNEMoveElementLaneDouble::isMoveElementValid() const {305// first check if lanes are connected306if (!GNEAdditional::areLaneConsecutives(myMovedElement->getHierarchicalElement()->getParentLanes())) {307return false;308} else if (!GNEAdditional::areLaneConnected(myMovedElement->getHierarchicalElement()->getParentLanes())) {309return false;310} else if (!myStartPos->isMoveElementValid() || !myEndPos->isMoveElementValid()) {311return false;312} else if ((myMovedElement->getHierarchicalElement()->getParentLanes().size() == 1) &&313(myStartPos->getFixedPositionOverLane(false) > (myEndPos->getFixedPositionOverLane(false) - POSITION_EPS))) {314return false;315} else {316return true;317}318}319320321std::string322GNEMoveElementLaneDouble::getMovingProblem() const {323// first check if lanes are connected324if (!GNEAdditional::areLaneConsecutives(myMovedElement->getHierarchicalElement()->getParentLanes())) {325return TL("Lanes aren't consecutives");326} else if (!GNEAdditional::areLaneConnected(myMovedElement->getHierarchicalElement()->getParentLanes())) {327return TL("Lanes aren't connected");328} else if (!myStartPos->isMoveElementValid()) {329return myStartPos->getMovingProblem();330} else if (!myEndPos->isMoveElementValid()) {331return myEndPos->getMovingProblem();332} else if ((myMovedElement->getHierarchicalElement()->getParentLanes().size() == 1) &&333(myStartPos->getFixedPositionOverLane(false) > (myEndPos->getFixedPositionOverLane(false) - POSITION_EPS))) {334return TL("starPos > (endPos - EPS)");335} else {336return "";337}338}339340341void342GNEMoveElementLaneDouble::fixMovingProblem() {343const auto undolist = myMovedElement->getNet()->getUndoList();344// iterate over all lanes and build connections345for (int i = 1; i < (int)myMovedElement->getHierarchicalElement()->getParentLanes().size(); i++) {346// get lanes347const auto firstLane = myMovedElement->getHierarchicalElement()->getParentLanes().at(i - 1);348const auto secondLane = myMovedElement->getHierarchicalElement()->getParentLanes().at(i);349// search connection350bool foundConnection = false;351for (const auto& connection : firstLane->getParentEdge()->getGNEConnections()) {352if ((connection->getLaneFrom() == firstLane) && (connection->getLaneTo() == secondLane)) {353foundConnection = true;354break;355}356}357// check if connection exist358if (!foundConnection) {359// create new connection manually360NBEdge::Connection newCon(firstLane->getIndex(), secondLane->getParentEdge()->getNBEdge(), secondLane->getIndex());361// allow to undo creation of new lane362undolist->add(new GNEChange_Connection(firstLane->getParentEdge(), newCon, false, true), true);363}364}365// Fix both position366myStartPos->fixMovingProblem();367myEndPos->fixMovingProblem();368if (myMovedElement->getHierarchicalElement()->getParentLanes().size() == 1) {369// extra if starPos > endPos (endPos is dominant)370const double finalLenght = myMovedElement->getHierarchicalElement()->getParentLanes().back()->getParentEdge()->getNBEdge()->getFinalLength();371const double maxStartPos = (myEndPos->myPosOverLane == INVALID_DOUBLE) ? finalLenght : (myEndPos->getFixedPositionOverLane(false) - POSITION_EPS);372if (maxStartPos < POSITION_EPS) {373myMovedElement->setAttribute(myStartPos->myPosAttr, "0", undolist);374myMovedElement->setAttribute(myEndPos->myPosAttr, toString(POSITION_EPS), undolist);375} else if (myStartPos->getFixedPositionOverLane(false) > maxStartPos) {376// use truncate to avoid problem precission under certain conditions377const double newStartPos = (std::trunc(maxStartPos * 1000) / 1000);378myMovedElement->setAttribute(myStartPos->myPosAttr, toString(newStartPos), undolist);379}380}381}382383384void385GNEMoveElementLaneDouble::writeMoveAttributes(OutputDevice& device, const bool writeLength) const {386// lane/s387if (myMovedElement->getTagProperty()->hasAttribute(SUMO_ATTR_LANE)) {388device.writeAttr(SUMO_ATTR_LANE, myMovedElement->getAttribute(SUMO_ATTR_LANE));389} else {390device.writeAttr(SUMO_ATTR_LANES, myMovedElement->getAttribute(SUMO_ATTR_LANES));391}392// write start position393if (myStartPos->myPosOverLane != myMovedElement->getTagProperty()->getDefaultDoubleValue(myStartPos->myPosAttr)) {394device.writeAttr(myStartPos->myPosAttr, myStartPos->myPosOverLane);395}396// write end position depending of lenght397if (writeLength) {398device.writeAttr(SUMO_ATTR_LENGTH, (myEndPos->myPosOverLane - myStartPos->myPosOverLane));399} else if (myEndPos->myPosOverLane != myMovedElement->getTagProperty()->getDefaultDoubleValue(myEndPos->myPosAttr)) {400device.writeAttr(myEndPos->myPosAttr, myEndPos->myPosOverLane);401}402// friendly position (only if true)403if (myStartPos->myFriendlyPos) {404device.writeAttr(SUMO_ATTR_FRIENDLY_POS, myStartPos->myFriendlyPos);405}406}407408409double410GNEMoveElementLaneDouble::getStartFixedPositionOverLane(const bool adjustGeometryFactor) const {411if (myStartPos->getFixedPositionOverLane(adjustGeometryFactor) < 0) {412return 0;413} else if (myStartPos->getFixedPositionOverLane(adjustGeometryFactor) > (myEndPos->getFixedPositionOverLane(adjustGeometryFactor) - POSITION_EPS)) {414return (myEndPos->getFixedPositionOverLane(adjustGeometryFactor) - POSITION_EPS);415} else {416return myStartPos->getFixedPositionOverLane(adjustGeometryFactor);417}418}419420421double422GNEMoveElementLaneDouble::getEndFixedPositionOverLane(const bool adjustGeometryFactor) const {423if (myEndPos->getFixedPositionOverLane(adjustGeometryFactor) < POSITION_EPS) {424return POSITION_EPS;425} else if (myStartPos->getFixedPositionOverLane(adjustGeometryFactor) > (myEndPos->getFixedPositionOverLane(adjustGeometryFactor) - POSITION_EPS)) {426return myEndPos->getFixedPositionOverLane(adjustGeometryFactor);427} else {428return myEndPos->getFixedPositionOverLane(adjustGeometryFactor);429}430}431432433void434GNEMoveElementLaneDouble::setMoveShape(const GNEMoveResult& moveResult) {435// check if we're moving both points436if ((moveResult.newFirstPos != INVALID_DOUBLE) && (moveResult.newLastPos != INVALID_DOUBLE)) {437// change both position438myStartPos->setMoveShape(moveResult);439myEndPos->setMoveShape(moveResult);440// set lateral offset441myMovingLateralOffset = moveResult.firstLaneOffset;442} else if (moveResult.newFirstPos != INVALID_DOUBLE) {443// change only start position444myStartPos->setMoveShape(moveResult);445} else if (moveResult.newLastPos != INVALID_DOUBLE) {446myEndPos->setMoveShape(moveResult);447}448// update geometry449myMovedElement->updateGeometry();450}451452453void454GNEMoveElementLaneDouble::commitMoveShape(const GNEMoveResult& moveResult, GNEUndoList* undoList) {455// begin change attribute456undoList->begin(myMovedElement, TLF("position of %", myMovedElement->getTagStr()));457// check if we're moving both points458if ((moveResult.newFirstPos != INVALID_DOUBLE) && (moveResult.newLastPos != INVALID_DOUBLE)) {459// set both positions460myStartPos->commitMoveShape(moveResult, undoList);461myEndPos->commitMoveShape(moveResult, undoList);462} else if (moveResult.newFirstPos != INVALID_DOUBLE) {463// set only start position464myStartPos->commitMoveShape(moveResult, undoList);465} else if (moveResult.newLastPos != INVALID_DOUBLE) {466// set only end position467myEndPos->commitMoveShape(moveResult, undoList);468}469// end change attribute470undoList->end();471}472473474void475GNEMoveElementLaneDouble::setSize(const std::string& value, GNEUndoList* undoList) {476const auto laneLength = myMovedElement->getHierarchicalElement()->getParentLanes().front()->getLaneShapeLength();477const double newSize = GNEAttributeCarrier::parse<double>(value);478// continue depending of values of start und end position479if ((myStartPos->myPosOverLane != INVALID_DOUBLE) && (myEndPos->myPosOverLane != INVALID_DOUBLE)) {480// get middle lengths481const double center = (getStartFixedPositionOverLane(false) + getEndFixedPositionOverLane(false)) * 0.5;482// calculate new lenghts483double newStartPos = center - (newSize * 0.5);484double newEndPos = center + (newSize * 0.5);485// adjust positions486if (newStartPos < 0) {487newStartPos = 0;488}489if (newEndPos > laneLength) {490newEndPos = laneLength;491}492// set new start und end positions493undoList->begin(myMovedElement, TLF(" %'s size", myMovedElement->getTagStr()));494GNEChange_Attribute::changeAttribute(myMovedElement, myStartPos->myPosAttr, toString(newStartPos), undoList);495GNEChange_Attribute::changeAttribute(myMovedElement, myEndPos->myPosAttr, toString(newEndPos), undoList);496undoList->end();497} else if (myStartPos->myPosOverLane != INVALID_DOUBLE) {498double newStartPos = laneLength - newSize;499// adjust new StartPos500if (newStartPos < 0) {501newStartPos = 0;502}503undoList->begin(myMovedElement, TLF(" %'s size", myMovedElement->getTagStr()));504GNEChange_Attribute::changeAttribute(myMovedElement, myStartPos->myPosAttr, toString(newStartPos), undoList);505undoList->end();506} else if (myEndPos->myPosOverLane != INVALID_DOUBLE) {507double newEndPos = newSize;508// adjust endPos509if (newEndPos > laneLength) {510newEndPos = laneLength;511}512undoList->begin(myMovedElement, TLF(" %'s size", myMovedElement->getTagStr()));513GNEChange_Attribute::changeAttribute(myMovedElement, myEndPos->myPosAttr, toString(newEndPos), undoList);514undoList->end();515}516}517518/****************************************************************************/519520521