Path: blob/main/src/netedit/elements/moving/GNEMoveElement.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 GNEMoveElement.cpp14/// @author Pablo Alvarez Lopez15/// @date Mar 202016///17// Class used for elements that can be moved18/****************************************************************************/19#include <config.h>2021#include <netedit/changes/GNEChange_Attribute.h>22#include <netedit/GNEViewParent.h>23#include <netedit/frames/common/GNEMoveFrame.h>2425#include "GNEMoveElement.h"2627// ===========================================================================28// Method definitions29// ===========================================================================3031GNEMoveElement::GNEMoveElement(GNEAttributeCarrier* movedElement) :32myMovedElement(movedElement) {33}343536GNEMoveElement::~GNEMoveElement() {}373839std::string40GNEMoveElement::getMovingAttribute(SumoXMLAttr key) const {41throw InvalidArgument(myMovedElement->getTagStr() + " doesn't have a moving attribute of type '" + toString(key) + "'");42}434445double46GNEMoveElement::getMovingAttributeDouble(SumoXMLAttr key) const {47throw InvalidArgument(myMovedElement->getTagStr() + " doesn't have a moving attribute of type '" + toString(key) + "'");48}495051void52GNEMoveElement::setMovingAttribute(SumoXMLAttr key, const std::string& /*value*/, GNEUndoList* /*undoList*/) {53throw InvalidArgument(myMovedElement->getTagStr() + " doesn't have a moving attribute of type '" + toString(key) + "'");54}555657bool58GNEMoveElement::isMovingAttributeValid(SumoXMLAttr key, const std::string& /*value*/) const {59throw InvalidArgument(myMovedElement->getTagStr() + " doesn't have a moving attribute of type '" + toString(key) + "'");60}616263void64GNEMoveElement::setMovingAttribute(SumoXMLAttr key, const std::string& /*value*/) {65throw InvalidArgument(myMovedElement->getTagStr() + " doesn't have a moving attribute of type '" + toString(key) + "'");66}676869GNEMoveOperation*70GNEMoveElement::getEditShapeOperation(const GUIGlObject* obj, const PositionVector originalShape,71const bool maintainShapeClosed) {72// get moved geometry points73const auto geometryPoints = gViewObjectsHandler.getSelectedGeometryPoints(obj);74// get pos over shape75const auto posOverShape = gViewObjectsHandler.getSelectedPositionOverShape(obj);76// declare shape to move77PositionVector shapeToMove = originalShape;78const int lastIndex = (int)shapeToMove.size() - 1;79// check if move existent geometry points or create new80if (geometryPoints.size() > 0) {81// move geometry point without creating new geometry point82if (maintainShapeClosed && ((geometryPoints.front() == 0) || (geometryPoints.front() == lastIndex))) {83// move first and last point84return new GNEMoveOperation(this, originalShape, {0, lastIndex}, shapeToMove, {0, lastIndex});85} else {86return new GNEMoveOperation(this, originalShape, {geometryPoints.front()}, shapeToMove, {geometryPoints.front()});87}88} else if (posOverShape != Position::INVALID) {89// create new geometry point and keep new index (if we clicked near of shape)90const int newIndex = shapeToMove.insertAtClosest(posOverShape, true);91return new GNEMoveOperation(this, originalShape, {shapeToMove.indexOfClosest(posOverShape)}, shapeToMove, {newIndex});92} else {93return nullptr;94}95}969798void99GNEMoveElement::moveElement(const GNEViewNet* viewNet, GNEMoveOperation* moveOperation, const GNEMoveOffset& offset) {100// declare move result101GNEMoveResult moveResult(moveOperation);102// set geometry points to move103moveResult.geometryPointsToMove = moveOperation->geometryPointsToMove;104// check if we're moving over a lane shape, an entire shape or only certain geometry point105if (moveOperation->firstLane) {106// calculate movement over lane depending if element has more than one lane107if (moveOperation->lastLane) {108if ((moveOperation->firstPosition != INVALID_DOUBLE) && (moveOperation->lastPosition != INVALID_DOUBLE)) {109// move both first and last positions110calculateLanePositions(moveResult.newFirstPos, moveResult.newLastPos, viewNet, moveOperation->firstLane,111moveOperation->firstPosition, moveOperation->lastLane, moveOperation->lastPosition,112moveOperation->clickedFirstLane, offset);113} else if (moveOperation->firstPosition != INVALID_DOUBLE) {114// move first position115calculateLanePosition(moveResult.newFirstPos, viewNet, moveOperation->firstLane, moveOperation->firstPosition, offset);116} else if (moveOperation->lastPosition != INVALID_DOUBLE) {117// move last position118calculateLanePosition(moveResult.newLastPos, viewNet, moveOperation->lastLane, moveOperation->lastPosition, offset);119}120} else {121// continue depending if we're moving both positions122if ((moveOperation->firstPosition != INVALID_DOUBLE) && (moveOperation->lastPosition != INVALID_DOUBLE)) {123// move both first and last positions in the same lane124calculateLanePositions(moveResult.newFirstPos, moveResult.newLastPos, viewNet, moveOperation->firstLane,125moveOperation->firstPosition, moveOperation->lastPosition, offset);126} else if (moveOperation->firstPosition != INVALID_DOUBLE) {127// move first position128calculateLanePosition(moveResult.newFirstPos, viewNet, moveOperation->firstLane, moveOperation->firstPosition, offset);129} else if (moveOperation->lastPosition != INVALID_DOUBLE) {130// move last position131calculateLanePosition(moveResult.newLastPos, viewNet, moveOperation->firstLane, moveOperation->lastPosition, offset);132}133// calculate new lane134if (moveOperation->allowChangeLane) {135calculateNewLaneChange(viewNet, moveOperation->firstLane, moveResult.newFirstLane, moveResult.firstLaneOffset);136}137}138} else if (moveOperation->geometryPointsToMove.size() > 0) {139// set values in moveResult140moveResult.shapeToUpdate = moveOperation->shapeToMove;141// move geometry points142for (const auto& geometryPointIndex : moveOperation->geometryPointsToMove) {143if (moveResult.shapeToUpdate[geometryPointIndex] != Position::INVALID) {144// add offset145moveResult.shapeToUpdate[geometryPointIndex].add(offset.x, offset.y, offset.z);146// apply snap to active grid147moveResult.shapeToUpdate[geometryPointIndex] = viewNet->snapToActiveGrid(moveResult.shapeToUpdate[geometryPointIndex]);148} else {149throw ProcessError("trying to move an invalid position");150}151}152} else {153// set values in moveResult154moveResult.shapeToUpdate = moveOperation->shapeToMove;155// move entire shape156for (auto& geometryPointIndex : moveResult.shapeToUpdate) {157if (geometryPointIndex != Position::INVALID) {158// add offset159geometryPointIndex.add(offset.x, offset.y, offset.z);160// apply snap to active grid161geometryPointIndex = viewNet->snapToActiveGrid(geometryPointIndex);162} else {163throw ProcessError("trying to move an invalid position");164}165}166// check if we're adjusting width or height167if ((moveOperation->operationType == GNEMoveOperation::OperationType::WIDTH) ||168(moveOperation->operationType == GNEMoveOperation::OperationType::HEIGHT) ||169(moveOperation->operationType == GNEMoveOperation::OperationType::LENGTH)) {170// calculate extrapolate vector171moveResult.shapeToUpdate = calculateExtrapolatedVector(moveOperation, moveResult);172}173}174// move shape element175moveOperation->moveElement->setMoveShape(moveResult);176}177178179void180GNEMoveElement::commitMove(const GNEViewNet* viewNet, GNEMoveOperation* moveOperation, const GNEMoveOffset& offset, GNEUndoList* undoList) {181// declare move result182GNEMoveResult moveResult(moveOperation);183// check if we're moving over a lane shape, an entire shape or only certain geometry point184if (moveOperation->firstLane) {185// calculate original move result186moveResult.newFirstLane = moveOperation->firstLane;187moveResult.newFirstPos = moveOperation->firstPosition;188moveResult.newLastLane = moveOperation->lastLane;189moveResult.newLastPos = moveOperation->lastPosition;190// set original positions in element191moveOperation->moveElement->setMoveShape(moveResult);192// calculate movement over lane depending if element has more than one lane193if (moveOperation->lastLane) {194if ((moveOperation->firstPosition != INVALID_DOUBLE) && (moveOperation->lastPosition != INVALID_DOUBLE)) {195// move both first and last positions196calculateLanePositions(moveResult.newFirstPos, moveResult.newLastPos, viewNet, moveOperation->firstLane,197moveOperation->firstPosition, moveOperation->lastLane, moveOperation->lastPosition,198moveOperation->clickedFirstLane, offset);199} else if (moveOperation->firstPosition != INVALID_DOUBLE) {200// move first position201calculateLanePosition(moveResult.newFirstPos, viewNet, moveOperation->firstLane, moveOperation->firstPosition, offset);202} else if (moveOperation->lastPosition != INVALID_DOUBLE) {203// move last position204calculateLanePosition(moveResult.newLastPos, viewNet, moveOperation->lastLane, moveOperation->lastPosition, offset);205}206} else {207// continue depending if we're moving both positions208if ((moveOperation->firstPosition != INVALID_DOUBLE) && (moveOperation->lastPosition != INVALID_DOUBLE)) {209// move both first and last positions in the same lane210calculateLanePositions(moveResult.newFirstPos, moveResult.newLastPos, viewNet, moveOperation->firstLane,211moveOperation->firstPosition, moveOperation->lastPosition, offset);212} else if (moveOperation->firstPosition != INVALID_DOUBLE) {213// move first position214calculateLanePosition(moveResult.newFirstPos, viewNet, moveOperation->firstLane, moveOperation->firstPosition, offset);215} else if (moveOperation->lastPosition != INVALID_DOUBLE) {216// move last position217calculateLanePosition(moveResult.newLastPos, viewNet, moveOperation->firstLane, moveOperation->lastPosition, offset);218}219// calculate new lane220if (moveOperation->allowChangeLane) {221calculateNewLaneChange(viewNet, moveOperation->firstLane, moveResult.newFirstLane, moveResult.firstLaneOffset);222}223}224} else {225// set original geometry points to move226moveResult.geometryPointsToMove = moveOperation->originalGeometryPoints;227// set shapeToUpdate with originalPosOverLanes228moveResult.shapeToUpdate = moveOperation->originalShape;229// first restore original geometry geometry230moveOperation->moveElement->setMoveShape(moveResult);231// set new geometry points to move232moveResult.geometryPointsToMove = moveOperation->geometryPointsToMove;233// set values in moveResult234moveResult.shapeToUpdate = moveOperation->shapeToMove;235// check if we're moving an entire shape or only certain geometry point236if (moveOperation->geometryPointsToMove.size() > 0) {237// only move certain geometry points238for (const auto& geometryPointIndex : moveOperation->geometryPointsToMove) {239if (moveResult.shapeToUpdate[geometryPointIndex] != Position::INVALID) {240// add offset241moveResult.shapeToUpdate[geometryPointIndex].add(offset.x, offset.y, offset.z);242// apply snap to active grid243moveResult.shapeToUpdate[geometryPointIndex] = viewNet->snapToActiveGrid(moveResult.shapeToUpdate[geometryPointIndex]);244} else {245throw ProcessError("trying to move an invalid position");246}247}248// remove double points if merge points is enabled (only in commitMove)249if (viewNet->getViewParent()->getMoveFrame()->getCommonMoveOptions()->getMergeGeometryPoints() && (moveResult.shapeToUpdate.size() > 2)) {250moveResult.shapeToUpdate.removeDoublePoints(2);251}252} else {253// move entire shape254for (auto& geometryPointIndex : moveResult.shapeToUpdate) {255if (geometryPointIndex != Position::INVALID) {256// add offset257geometryPointIndex.add(offset.x, offset.y, offset.z);258// apply snap to active grid259geometryPointIndex = viewNet->snapToActiveGrid(geometryPointIndex);260} else {261throw ProcessError("trying to move an invalid position");262}263}264// check if we're adjusting width or height265if ((moveOperation->operationType == GNEMoveOperation::OperationType::WIDTH) ||266(moveOperation->operationType == GNEMoveOperation::OperationType::HEIGHT) ||267(moveOperation->operationType == GNEMoveOperation::OperationType::LENGTH)) {268// calculate extrapolate vector269moveResult.shapeToUpdate = calculateExtrapolatedVector(moveOperation, moveResult);270}271}272}273// commit move shape274moveOperation->moveElement->commitMoveShape(moveResult, undoList);275}276277278double279GNEMoveElement::calculateLaneOffset(const GNEViewNet* viewNet, const GNELane* lane, const double firstPosition, const double lastPosition,280const GNEMoveOffset& offset) {281// get lane shape lenght282const auto laneShapeLength = lane->getLaneShape().length2D();283// declare laneOffset284double laneOffset = 0;285// calculate central position between two given positions286const double offsetCentralPosition = (firstPosition + lastPosition) * 0.5;287// calculate middle length between two given positions288const double middleLength = std::abs(lastPosition - firstPosition) * 0.5;289// calculate lane position at offset given by offsetCentralPosition290Position laneCentralPosition = lane->getLaneShape().positionAtOffset2D(offsetCentralPosition);291// apply offset to positionAtCentralPosition292laneCentralPosition.add(offset.x, offset.y, offset.z);293// snap to grid294laneCentralPosition = viewNet->snapToActiveGrid(laneCentralPosition);295// calculate offset over lane using laneCentralPosition296const double offsetLaneCentralPositionPerpendicular = lane->getLaneShape().nearest_offset_to_point2D(laneCentralPosition);297// check if offset is within lane shape298if (offsetLaneCentralPositionPerpendicular == -1) {299// calculate non-perpendicular offset over lane using laneCentralPosition300const double offsetLaneCentralPosition = lane->getLaneShape().nearest_offset_to_point2D(laneCentralPosition, false);301// due laneCentralPosition is out of lane shape, then place positions in extremes302if (offsetLaneCentralPosition == 0) {303laneOffset = firstPosition;304} else {305laneOffset = lastPosition - laneShapeLength;306}307} else {308// laneCentralPosition is within of lane shapen, then calculate offset using middlelength309if ((offsetLaneCentralPositionPerpendicular - middleLength) < 0) {310laneOffset = firstPosition;311} else if ((offsetLaneCentralPositionPerpendicular + middleLength) > laneShapeLength) {312laneOffset = lastPosition - laneShapeLength;313} else {314laneOffset = (offsetCentralPosition - offsetLaneCentralPositionPerpendicular);315}316}317return laneOffset;318}319320321void322GNEMoveElement::calculateLanePosition(double& posOverLane, const GNEViewNet* viewNet, const GNELane* lane,323const double lanePos, const GNEMoveOffset& offset) {324// get lane offset325const double laneOffset = calculateLaneOffset(viewNet, lane, lanePos, lanePos, offset);326// update lane position327posOverLane = (lanePos - laneOffset) / lane->getLengthGeometryFactor();328}329330331void332GNEMoveElement::calculateLanePositions(double& starPos, double& endPos, const GNEViewNet* viewNet, const GNELane* lane,333const double firstPosOverLane, const double lastPosOverLane, const GNEMoveOffset& offset) {334// get lane offset335const double laneOffset = calculateLaneOffset(viewNet, lane, firstPosOverLane, lastPosOverLane, offset);336// update moveResult337starPos = (firstPosOverLane - laneOffset) / lane->getLengthGeometryFactor();338endPos = (lastPosOverLane - laneOffset) / lane->getLengthGeometryFactor();339}340341342void343GNEMoveElement::calculateLanePositions(double& starPos, double& endPos, const GNEViewNet* viewNet, const GNELane* firstLane,344const double firstPosOverLane, const GNELane* lastLane, const double lastPosOverLane,345const bool firstLaneClicked, const GNEMoveOffset& offset) {346// declare offset347double laneOffset = 0;348// calculate offset depending if we clicked over the first or over the second lane349if (firstLaneClicked) {350// calculate lane start position for first lane351calculateLanePosition(starPos, viewNet, firstLane, firstPosOverLane, offset);352// calculate offset353laneOffset = (starPos - firstPosOverLane);354// set end position355endPos = lastPosOverLane + laneOffset;356// adjust offset357const double lastLaneLength = lastLane->getLaneShape().length2D();358if (endPos > lastLaneLength) {359laneOffset = (lastLaneLength - lastPosOverLane);360}361if (endPos < 0) {362laneOffset = (0 - lastPosOverLane);363}364} else {365// calculate lane start position for first lane366calculateLanePosition(endPos, viewNet, lastLane, lastPosOverLane, offset);367// calculate offset368laneOffset = (endPos - lastPosOverLane);369// set start position370starPos = firstPosOverLane + laneOffset;371// adjust offset372const double firstLaneLength = firstLane->getLaneShape().length2D();373if (starPos > firstLaneLength) {374laneOffset = (firstLaneLength - firstPosOverLane);375}376if (starPos < 0) {377laneOffset = (0 - firstPosOverLane);378}379}380// set positions with the adjusted offset381starPos = firstPosOverLane + laneOffset;382endPos = lastPosOverLane + laneOffset;383}384385386void387GNEMoveElement::calculateNewLaneChange(const GNEViewNet* viewNet, const GNELane* originalLane, const GNELane*& newLane, double& laneOffset) {388// get cursor position389const Position cursorPosition = viewNet->getPositionInformation();390// iterate over edge lanes391for (const auto& lane : originalLane->getParentEdge()->getChildLanes()) {392// avoid moveOperation lane393if (lane != originalLane) {394// calculate offset over lane shape395const double offSet = lane->getLaneShape().nearest_offset_to_point2D(cursorPosition, true);396// calculate position over lane shape397const Position posOverLane = lane->getLaneShape().positionAtOffset2D(offSet);398// check distance399if (posOverLane.distanceSquaredTo2D(cursorPosition) < 1) {400// update newlane401newLane = lane;402// calculate offset over moveOperation lane403const double offsetMoveOperationLane = originalLane->getLaneShape().nearest_offset_to_point2D(cursorPosition, true);404// calculate position over moveOperation lane405const Position posOverMoveOperationLane = originalLane->getLaneShape().positionAtOffset2D(offsetMoveOperationLane);406// update moveResult of laneOffset407laneOffset = posOverLane.distanceTo2D(posOverMoveOperationLane);408// change sign of moveResult laneOffset depending of lane index409if (originalLane->getIndex() < newLane->getIndex()) {410laneOffset *= -1;411}412}413}414}415}416417418PositionVector419GNEMoveElement::calculateExtrapolatedVector(const GNEMoveOperation* moveOperation, const GNEMoveResult& moveResult) {420// get original shape half length421const double halfLength = moveOperation->originalShape.length2D() * -0.5;422// get original shape and extrapolate423PositionVector extendedShape = moveOperation->originalShape;424extendedShape.extrapolate2D(10e5);425// get geometry point426const Position geometryPoint = moveOperation->firstGeometryPoint ? moveResult.shapeToUpdate.front() : moveResult.shapeToUpdate.back();427// calculate offsets to first and last positions428const double offset = extendedShape.nearest_offset_to_point2D(geometryPoint, false);429// calculate extrapolate value430double extrapolateValue = (10e5 - offset);431// adjust extrapolation432if (moveOperation->firstGeometryPoint) {433if (extrapolateValue < halfLength) {434extrapolateValue = (halfLength - POSITION_EPS);435}436} else {437if (extrapolateValue > halfLength) {438extrapolateValue = (halfLength - POSITION_EPS);439}440}441// restore shape in in moveResult442PositionVector extrapolatedShape = moveOperation->shapeToMove;443// extrapolate444extrapolatedShape.extrapolate2D(extrapolateValue);445// check if return reverse446if (moveOperation->firstGeometryPoint) {447return extrapolatedShape;448} else {449return extrapolatedShape.reverse();450}451}452453/****************************************************************************/454455456