Path: blob/main/src/netimport/NIXMLConnectionsHandler.cpp
169665 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 NIXMLConnectionsHandler.cpp14/// @author Daniel Krajzewicz15/// @author Jakob Erdmann16/// @author Michael Behrisch17/// @author Laura Bieker18/// @date Thu, 17 Oct 200219///20// Importer for edge connections stored in XML21/****************************************************************************/22#include <config.h>2324#include <string>25#include <iostream>26#include <netbuild/NBEdge.h>27#include <netbuild/NBEdgeCont.h>28#include <netbuild/NBNodeCont.h>29#include <netbuild/NBTrafficLightLogicCont.h>30#include <netbuild/NBNode.h>31#include <netbuild/NBNetBuilder.h>32#include <utils/geom/GeoConvHelper.h>33#include <utils/xml/SUMOSAXHandler.h>34#include <utils/xml/SUMOXMLDefinitions.h>35#include <utils/common/StringTokenizer.h>36#include <utils/common/ToString.h>37#include <utils/common/StringUtils.h>38#include <utils/common/UtilExceptions.h>39#include <utils/common/MsgHandler.h>40#include <utils/options/OptionsCont.h>41#include "NIImporter_SUMO.h"42#include "NIXMLConnectionsHandler.h"434445// ===========================================================================46// method definitions47// ===========================================================================48NIXMLConnectionsHandler::NIXMLConnectionsHandler(NBEdgeCont& ec, NBNodeCont& nc, NBTrafficLightLogicCont& tlc) :49SUMOSAXHandler("xml-connection-description"),50myEdgeCont(ec),51myNodeCont(nc),52myTLLogicCont(tlc),53myHaveWarnedAboutDeprecatedLanes(false),54myErrorMsgHandler(OptionsCont::getOptions().getBool("ignore-errors.connections") ?55MsgHandler::getWarningInstance() : MsgHandler::getErrorInstance()),56myLocation(nullptr),57myLastParameterised(nullptr) {58}596061NIXMLConnectionsHandler::~NIXMLConnectionsHandler() {62delete myLocation;63}646566void67NIXMLConnectionsHandler::myStartElement(int element,68const SUMOSAXAttributes& attrs) {69switch (element) {70case SUMO_TAG_CONNECTIONS:71// infer location for legacy networks that don't have location information72myLocation = GeoConvHelper::getLoadedPlain(getFileName(), ".con.xml");73break;74case SUMO_TAG_LOCATION:75delete myLocation;76myLocation = NIImporter_SUMO::loadLocation(attrs, false);77break;78case SUMO_TAG_DEL:79delConnection(attrs);80break;81case SUMO_TAG_CONNECTION:82parseConnection(attrs);83break;84case SUMO_TAG_PROHIBITION:85addProhibition(attrs);86break;87case SUMO_TAG_CROSSING:88addCrossing(attrs);89break;90case SUMO_TAG_WALKINGAREA:91addWalkingArea(attrs);92break;93case SUMO_TAG_PARAM:94if (myLastParameterised != nullptr) {95bool ok = true;96const std::string key = attrs.get<std::string>(SUMO_ATTR_KEY, nullptr, ok);97// circumventing empty string test98const std::string val = attrs.hasAttribute(SUMO_ATTR_VALUE) ? attrs.getString(SUMO_ATTR_VALUE) : "";99myLastParameterised->setParameter(key, val);100}101break;102default:103break;104}105}106107108void109NIXMLConnectionsHandler::myEndElement(int element) {110switch (element) {111case SUMO_TAG_CONNECTION:112case SUMO_TAG_CROSSING:113myLastParameterised = nullptr;114break;115default:116break;117}118}119120121122NBConnection123NIXMLConnectionsHandler::parseConnectionDef(const std::string& defRole, const std::string& def) {124// split from/to125const std::string::size_type div = def.find("->");126if (div == std::string::npos) {127myErrorMsgHandler->inform("Missing connection divider in " + defRole + " '" + def + "'");128return NBConnection::InvalidConnection;129}130std::string fromDef = def.substr(0, div);131std::string toDef = def.substr(div + 2);132133// retrieve them now134NBEdge* fromE = myEdgeCont.retrieve(fromDef);135NBEdge* toE = myEdgeCont.retrieve(toDef);136// check137if (fromE == nullptr) {138myErrorMsgHandler->inform("Could not find edge '" + fromDef + "' in " + defRole + " '" + def + "'");139return NBConnection::InvalidConnection;140}141if (toE == nullptr) {142myErrorMsgHandler->inform("Could not find edge '" + toDef + "' in " + defRole + " '" + def + "'");143return NBConnection::InvalidConnection;144}145return NBConnection(fromE, toE);146}147148149void150NIXMLConnectionsHandler::parseLaneBound(const SUMOSAXAttributes& attrs, NBEdge* from, NBEdge* to) {151if (to == nullptr) {152// do nothing if it's a dead end153return;154}155bool ok = true;156// get the begin and the end lane157int fromLane;158int toLane;159try {160if (!parseLaneInfo(attrs, from, to, &fromLane, &toLane)) {161return;162}163if (fromLane < 0) {164myErrorMsgHandler->informf("Invalid value '%' for " + toString(SUMO_ATTR_FROM_LANE) +165" in connection from '%' to '%'.", fromLane, from->getID(), to->getID());166return;167}168if (toLane < 0) {169myErrorMsgHandler->informf("Invalid value '%' for " + toString(SUMO_ATTR_TO_LANE) +170" in connection from '%' to '%'.", toLane, from->getID(), to->getID());171return;172}173174NBEdge::Connection defaultCon(fromLane, to, toLane);175if (from->getStep() == NBEdge::EdgeBuildingStep::LANES2LANES_USER) {176// maybe we are patching an existing connection177std::vector<NBEdge::Connection> existing = from->getConnectionsFromLane(fromLane, to, toLane);178if (existing.size() > 0) {179assert(existing.size() == 1);180defaultCon = existing.front();181// remove the original so we can insert the replacement182from->removeFromConnections(defaultCon);183} else {184from->getToNode()->invalidateTLS(myTLLogicCont, true, false);185}186}187if (OptionsCont::getOptions().isSet("default.connection.cont-pos")) {188defaultCon.contPos = OptionsCont::getOptions().getFloat("default.connection.cont-pos");189}190const bool mayDefinitelyPass = attrs.getOpt<bool>(SUMO_ATTR_PASS, nullptr, ok, defaultCon.mayDefinitelyPass);191KeepClear keepClear = defaultCon.keepClear;192if (attrs.hasAttribute(SUMO_ATTR_KEEP_CLEAR)) {193keepClear = attrs.get<bool>(SUMO_ATTR_KEEP_CLEAR, nullptr, ok) ? KEEPCLEAR_TRUE : KEEPCLEAR_FALSE;194}195const double contPos = attrs.getOpt<double>(SUMO_ATTR_CONTPOS, nullptr, ok, defaultCon.contPos);196const double visibility = attrs.getOpt<double>(SUMO_ATTR_VISIBILITY_DISTANCE, nullptr, ok, defaultCon.visibility);197const double speed = attrs.getOpt<double>(SUMO_ATTR_SPEED, nullptr, ok, defaultCon.speed);198const double friction = attrs.getOpt<double>(SUMO_ATTR_FRICTION, nullptr, ok, defaultCon.friction);199const double length = attrs.getOpt<double>(SUMO_ATTR_LENGTH, nullptr, ok, defaultCon.customLength);200const bool uncontrolled = attrs.getOpt<bool>(SUMO_ATTR_UNCONTROLLED, nullptr, ok, defaultCon.uncontrolled);201const bool indirectLeft = attrs.getOpt<bool>(SUMO_ATTR_INDIRECT, nullptr, ok, false);202const std::string edgeType = attrs.getOpt<std::string>(SUMO_ATTR_TYPE, nullptr, ok, "");203PositionVector customShape = attrs.getOpt<PositionVector>(SUMO_ATTR_SHAPE, nullptr, ok, defaultCon.customShape);204std::string allow = attrs.getOpt<std::string>(SUMO_ATTR_ALLOW, nullptr, ok, "");205std::string disallow = attrs.getOpt<std::string>(SUMO_ATTR_DISALLOW, nullptr, ok, "");206SVCPermissions permissions;207if (allow == "" && disallow == "") {208permissions = SVC_UNSPECIFIED;209} else {210permissions = parseVehicleClasses(allow, disallow);211}212SVCPermissions changeLeft = SVC_UNSPECIFIED;213SVCPermissions changeRight = SVC_UNSPECIFIED;214if (attrs.hasAttribute(SUMO_ATTR_CHANGE_LEFT)) {215changeLeft = parseVehicleClasses(attrs.get<std::string>(SUMO_ATTR_CHANGE_LEFT, nullptr, ok), "");216}217if (attrs.hasAttribute(SUMO_ATTR_CHANGE_RIGHT)) {218changeRight = parseVehicleClasses(attrs.get<std::string>(SUMO_ATTR_CHANGE_RIGHT, nullptr, ok), "");219}220if (attrs.hasAttribute(SUMO_ATTR_SHAPE) && !NBNetBuilder::transformCoordinates(customShape, true, myLocation)) {221WRITE_ERRORF(TL("Unable to project shape for connection from edge '%' to edge '%'."), from->getID(), to->getID());222}223if (!ok) {224return;225}226if (!from->addLane2LaneConnection(fromLane, to, toLane, NBEdge::Lane2LaneInfoType::USER, true, mayDefinitelyPass,227keepClear, contPos, visibility, speed, friction, length, customShape, uncontrolled, permissions, indirectLeft, edgeType, changeLeft, changeRight)) {228if (OptionsCont::getOptions().getBool("show-errors.connections-first-try")) {229WRITE_WARNINGF(TL("Could not set loaded connection from lane '%' to lane '%'."), from->getLaneID(fromLane), to->getLaneID(toLane));230}231// set as to be re-applied after network processing232myEdgeCont.addPostProcessConnection(from->getID(), fromLane, to->getID(), toLane, mayDefinitelyPass, keepClear, contPos, visibility,233speed, friction, length, customShape, uncontrolled, false, permissions, indirectLeft, edgeType, changeLeft, changeRight);234}235} catch (NumberFormatException&) {236myErrorMsgHandler->inform("At least one of the defined lanes was not numeric");237}238}239240bool241NIXMLConnectionsHandler::parseLaneInfo(const SUMOSAXAttributes& attributes, NBEdge* fromEdge, NBEdge* toEdge,242int* fromLane, int* toLane) {243if (attributes.hasAttribute(SUMO_ATTR_LANE)) {244return parseDeprecatedLaneDefinition(attributes, fromEdge, toEdge, fromLane, toLane);245} else {246return parseLaneDefinition(attributes, fromLane, toLane);247}248}249250251inline bool252NIXMLConnectionsHandler::parseDeprecatedLaneDefinition(const SUMOSAXAttributes& attributes,253NBEdge* from, NBEdge* to,254int* fromLane, int* toLane) {255bool ok = true;256if (!myHaveWarnedAboutDeprecatedLanes) {257myHaveWarnedAboutDeprecatedLanes = true;258WRITE_WARNING("'" + toString(SUMO_ATTR_LANE) + "' is deprecated, please use '" +259toString(SUMO_ATTR_FROM_LANE) + "' and '" + toString(SUMO_ATTR_TO_LANE) +260"' instead.");261}262263std::string laneConn = attributes.get<std::string>(SUMO_ATTR_LANE, nullptr, ok);264StringTokenizer st(laneConn, ':');265if (!ok || st.size() != 2) {266myErrorMsgHandler->inform("Invalid lane to lane connection from '" +267from->getID() + "' to '" + to->getID() + "'.");268return false; // There was an error.269}270271*fromLane = StringUtils::toIntSecure(st.next(), -1);272*toLane = StringUtils::toIntSecure(st.next(), -1);273274return true; // We succeeded.275}276277278inline bool279NIXMLConnectionsHandler::parseLaneDefinition(const SUMOSAXAttributes& attributes,280int* fromLane,281int* toLane) {282bool ok = true;283*fromLane = attributes.get<int>(SUMO_ATTR_FROM_LANE, nullptr, ok);284*toLane = attributes.get<int>(SUMO_ATTR_TO_LANE, nullptr, ok);285return ok;286}287288void289NIXMLConnectionsHandler::delConnection(const SUMOSAXAttributes& attrs) {290bool ok = true;291std::string from = attrs.get<std::string>(SUMO_ATTR_FROM, nullptr, ok);292std::string to = attrs.get<std::string>(SUMO_ATTR_TO, nullptr, ok);293if (!ok) {294return;295}296// these connections were removed when the edge was deleted297if (myEdgeCont.wasRemoved(from) || myEdgeCont.wasRemoved(to)) {298return;299}300NBEdge* fromEdge = myEdgeCont.retrieve(from);301NBEdge* toEdge = myEdgeCont.retrieve(to);302if (fromEdge == nullptr) {303myErrorMsgHandler->informf("The connection-source edge '%' to reset is not known.", from);304return;305}306if (toEdge == nullptr) {307myErrorMsgHandler->informf("The connection-destination edge '%' to reset is not known.", to);308return;309}310if (!fromEdge->isConnectedTo(toEdge) && fromEdge->getStep() >= NBEdge::EdgeBuildingStep::EDGE2EDGES) {311WRITE_WARNINGF(TL("Target edge '%' is not connected with '%'; the connection cannot be reset."), toEdge->getID(), fromEdge->getID());312return;313}314int fromLane = -1; // Assume all lanes are to be reset.315int toLane = -1;316if (attrs.hasAttribute(SUMO_ATTR_LANE)317|| attrs.hasAttribute(SUMO_ATTR_FROM_LANE)318|| attrs.hasAttribute(SUMO_ATTR_TO_LANE)) {319if (!parseLaneInfo(attrs, fromEdge, toEdge, &fromLane, &toLane)) {320return;321}322// we could be trying to reset a connection loaded from a sumo net and which has become obsolete.323// In this case it's ok to encounter invalid lance indices324if (!fromEdge->hasConnectionTo(toEdge, toLane) && fromEdge->getStep() >= NBEdge::EdgeBuildingStep::LANES2EDGES) {325WRITE_WARNINGF(TL("Edge '%' has no connection to lane '%'; the connection cannot be reset."), fromEdge->getID(), toEdge->getLaneID(toLane));326}327}328fromEdge->removeFromConnections(toEdge, fromLane, toLane, true);329}330331void332NIXMLConnectionsHandler::parseConnection(const SUMOSAXAttributes& attrs) {333bool ok = true;334std::string from = attrs.get<std::string>(SUMO_ATTR_FROM, "connection", ok);335std::string to = attrs.getOpt<std::string>(SUMO_ATTR_TO, "connection", ok, "");336if (!ok || myEdgeCont.wasIgnored(from) || myEdgeCont.wasIgnored(to)) {337return;338}339// extract edges340NBEdge* fromEdge = myEdgeCont.retrieve(from);341NBEdge* toEdge = to.length() != 0 ? myEdgeCont.retrieve(to) : nullptr;342// check whether they are valid343if (fromEdge == nullptr) {344myErrorMsgHandler->inform("The connection-source edge '" + from + "' is not known.");345return;346}347if (toEdge == nullptr && to.length() != 0) {348myErrorMsgHandler->inform("The connection-destination edge '" + to + "' is not known.");349return;350}351// parse optional lane information352if (attrs.hasAttribute(SUMO_ATTR_LANE) || attrs.hasAttribute(SUMO_ATTR_FROM_LANE) || attrs.hasAttribute(SUMO_ATTR_TO_LANE)) {353parseLaneBound(attrs, fromEdge, toEdge);354} else {355fromEdge->addEdge2EdgeConnection(toEdge);356fromEdge->getToNode()->invalidateTLS(myTLLogicCont, true, false);357if (attrs.hasAttribute(SUMO_ATTR_PASS)358|| attrs.hasAttribute(SUMO_ATTR_KEEP_CLEAR)359|| attrs.hasAttribute(SUMO_ATTR_CONTPOS)360|| attrs.hasAttribute(SUMO_ATTR_VISIBILITY_DISTANCE)361|| attrs.hasAttribute(SUMO_ATTR_SPEED)362|| attrs.hasAttribute(SUMO_ATTR_LENGTH)363|| attrs.hasAttribute(SUMO_ATTR_UNCONTROLLED)364|| attrs.hasAttribute(SUMO_ATTR_SHAPE)365|| attrs.hasAttribute(SUMO_ATTR_ALLOW)366|| attrs.hasAttribute(SUMO_ATTR_DISALLOW)) {367WRITE_ERROR("No additional connection attributes are permitted in connection from edge '" + fromEdge->getID() + "' unless '"368+ toString(SUMO_ATTR_FROM_LANE) + "' and '" + toString(SUMO_ATTR_TO_LANE) + "' are set.");369}370}371}372373void374NIXMLConnectionsHandler::addCrossing(const SUMOSAXAttributes& attrs) {375bool ok = true;376EdgeVector edges;377const std::string nodeID = attrs.get<std::string>(SUMO_ATTR_NODE, nullptr, ok);378double width = attrs.getOpt<double>(SUMO_ATTR_WIDTH, nodeID.c_str(), ok, NBEdge::UNSPECIFIED_WIDTH, true);379const bool discard = attrs.getOpt<bool>(SUMO_ATTR_DISCARD, nodeID.c_str(), ok, false, true);380int tlIndex = attrs.getOpt<int>(SUMO_ATTR_TLLINKINDEX, nullptr, ok, -1);381int tlIndex2 = attrs.getOpt<int>(SUMO_ATTR_TLLINKINDEX2, nullptr, ok, -1);382NBNode* node = myNodeCont.retrieve(nodeID);383if (node == nullptr) {384if (!discard && myNodeCont.wasRemoved(nodeID)) {385WRITE_ERRORF(TL("Node '%' in crossing is not known."), nodeID);386}387return;388}389if (!attrs.hasAttribute(SUMO_ATTR_EDGES)) {390if (discard) {391node->discardAllCrossings(true);392return;393} else {394WRITE_ERRORF(TL("No edges specified for crossing at node '%'."), nodeID);395return;396}397}398for (const std::string& id : attrs.get<std::vector<std::string> >(SUMO_ATTR_EDGES, nodeID.c_str(), ok)) {399NBEdge* edge = myEdgeCont.retrieve(id);400if (edge == nullptr) {401if (!(discard && myEdgeCont.wasRemoved(id))) {402WRITE_ERRORF(TL("Edge '%' for crossing at node '%' is not known."), id, nodeID);403return;404} else {405edge = myEdgeCont.retrieve(id, true);406}407} else {408if (edge->getToNode() != node && edge->getFromNode() != node) {409if (!discard) {410WRITE_ERRORF(TL("Edge '%' does not touch node '%'."), id, nodeID);411return;412}413}414}415edges.push_back(edge);416}417if (!ok) {418return;419}420bool priority = attrs.getOpt<bool>(SUMO_ATTR_PRIORITY, nodeID.c_str(), ok, node->isTLControlled(), true);421if (node->isTLControlled() && !priority) {422// traffic_light nodes should always have priority crossings423WRITE_WARNINGF(TL("Crossing at controlled node '%' must be prioritized"), nodeID);424priority = true;425}426PositionVector customShape = attrs.getOpt<PositionVector>(SUMO_ATTR_SHAPE, nullptr, ok, PositionVector::EMPTY);427if (!NBNetBuilder::transformCoordinates(customShape, true, myLocation)) {428WRITE_ERRORF(TL("Unable to project shape for crossing at node '%'."), node->getID());429}430if (discard) {431node->removeCrossing(edges);432} else {433if (node->checkCrossingDuplicated(edges)) {434// possibly a diff435NBNode::Crossing* existing = node->getCrossing(edges);436if (!(437(attrs.hasAttribute(SUMO_ATTR_WIDTH) && width != existing->width)438|| (attrs.hasAttribute(SUMO_ATTR_TLLINKINDEX) && tlIndex != existing->customTLIndex)439|| (attrs.hasAttribute(SUMO_ATTR_TLLINKINDEX2) && tlIndex2 != existing->customTLIndex2)440|| (attrs.hasAttribute(SUMO_ATTR_PRIORITY) && priority != existing->priority))) {441WRITE_ERRORF(TL("Crossing with edges '%' already exists at node '%'."), toString(edges), node->getID());442return;443} else {444// replace existing, keep old attributes445if (!attrs.hasAttribute(SUMO_ATTR_WIDTH)) {446width = existing->width;447}448if (!attrs.hasAttribute(SUMO_ATTR_TLLINKINDEX)) {449tlIndex = existing->customTLIndex;450}451if (!attrs.hasAttribute(SUMO_ATTR_TLLINKINDEX2)) {452tlIndex2 = existing->customTLIndex2;453}454if (!attrs.hasAttribute(SUMO_ATTR_PRIORITY)) {455priority = existing->priority;456}457node->removeCrossing(edges);458}459}460NBNode::Crossing* c = node->addCrossing(edges, width, priority, tlIndex, tlIndex2, customShape);461myLastParameterised = c;462}463}464465466void467NIXMLConnectionsHandler::addWalkingArea(const SUMOSAXAttributes& attrs) {468bool ok = true;469NBNode* node = nullptr;470EdgeVector edges;471const std::string nodeID = attrs.get<std::string>(SUMO_ATTR_NODE, nullptr, ok);472std::vector<std::string> edgeIDs;473if (!attrs.hasAttribute(SUMO_ATTR_EDGES)) {474WRITE_ERRORF(TL("No edges specified for walkingArea at node '%'."), nodeID);475return;476}477for (const std::string& id : attrs.get<std::vector<std::string> >(SUMO_ATTR_EDGES, nodeID.c_str(), ok)) {478NBEdge* edge = myEdgeCont.retrieve(id);479if (edge == nullptr) {480WRITE_ERRORF(TL("Edge '%' for walkingArea at node '%' is not known."), id, nodeID);481return;482}483if (node == nullptr) {484if (edge->getToNode()->getID() == nodeID) {485node = edge->getToNode();486} else if (edge->getFromNode()->getID() == nodeID) {487node = edge->getFromNode();488} else {489WRITE_ERRORF(TL("Edge '%' does not touch node '%'."), id, nodeID);490return;491}492} else {493if (edge->getToNode() != node && edge->getFromNode() != node) {494WRITE_ERRORF(TL("Edge '%' does not touch node '%'."), id, nodeID);495return;496}497}498edges.push_back(edge);499}500if (!ok) {501return;502}503PositionVector customShape = attrs.getOpt<PositionVector>(SUMO_ATTR_SHAPE, nullptr, ok, PositionVector::EMPTY);504double customWidth = attrs.getOpt<double>(SUMO_ATTR_WIDTH, nullptr, ok, NBEdge::UNSPECIFIED_WIDTH);505if (!NBNetBuilder::transformCoordinates(customShape, true, myLocation)) {506WRITE_ERRORF(TL("Unable to project shape for walkingArea at node '%'."), node->getID());507}508node->addWalkingAreaShape(edges, customShape, customWidth);509}510511void512NIXMLConnectionsHandler::addProhibition(const SUMOSAXAttributes& attrs) {513bool ok = true;514std::string prohibitor = attrs.getOpt<std::string>(SUMO_ATTR_PROHIBITOR, nullptr, ok, "");515std::string prohibited = attrs.getOpt<std::string>(SUMO_ATTR_PROHIBITED, nullptr, ok, "");516if (!ok) {517return;518}519NBConnection prohibitorC = parseConnectionDef("prohibitor", prohibitor);520NBConnection prohibitedC = parseConnectionDef("prohibited", prohibited);521if (prohibitorC == NBConnection::InvalidConnection || prohibitedC == NBConnection::InvalidConnection) {522// something failed523return;524}525NBNode* n = prohibitorC.getFrom()->getToNode();526n->addSortedLinkFoes(prohibitorC, prohibitedC);527}528529/****************************************************************************/530531532