#include <config.h>
#include <cassert>
#include <utils/options/OptionsCont.h>
#include <utils/common/MsgHandler.h>
#include <utils/common/ToString.h>
#include <utils/common/StringUtils.h>
#include <utils/iodevices/OutputDevice.h>
#include <utils/iodevices/OutputDevice_String.h>
#include <utils/router/DijkstraRouter.h>
#include "NBNetBuilder.h"
#include "NBAlgorithms.h"
#include "NBNodeCont.h"
#include "NBEdgeCont.h"
#include "NBNode.h"
#include "NBEdge.h"
#include "NBPTStop.h"
#include "NBVehicle.h"
#include "NBAlgorithms_Railway.h"
#define DEBUGNODEID "gneJ34"
#define DEBUGNODEID2 "28842974"
#define DEBUGEDGEID "22820560#0"
#define DEBUGCOND(obj) ((obj != 0 && (obj)->getID() == DEBUGNODEID))
#define SHARP_THRESHOLD_SAMEDIR 100
#define SHARP_THRESHOLD 80
void
NBRailwayTopologyAnalyzer::Track::addSuccessor(Track* track) {
successors.push_back(track);
viaSuccessors.push_back(std::make_pair(track, nullptr));
minPermissions &= track->edge->getPermissions();
}
const std::vector<NBRailwayTopologyAnalyzer::Track*>&
NBRailwayTopologyAnalyzer::Track::getSuccessors(SUMOVehicleClass svc) const {
if ((minPermissions & svc) != 0) {
return successors;
} else {
if (svcSuccessors.count(svc) == 0) {
std::vector<Track*> succ;
for (Track* t : successors) {
if ((t->edge->getPermissions() & svc) != 0) {
succ.push_back(t);
}
}
svcSuccessors[svc] = succ;
}
return svcSuccessors[svc];
}
}
const std::vector<std::pair<const NBRailwayTopologyAnalyzer::Track*, const NBRailwayTopologyAnalyzer::Track*> >&
NBRailwayTopologyAnalyzer::Track::getViaSuccessors(SUMOVehicleClass svc, bool ) const {
if ((minPermissions & svc) != 0) {
return viaSuccessors;
} else {
if (svcViaSuccessors.count(svc) == 0) {
std::vector<std::pair<const Track*, const Track*> >& succ = svcViaSuccessors[svc];
for (const Track* const t : successors) {
if ((t->edge->getPermissions() & svc) != 0) {
succ.push_back(std::make_pair(t, nullptr));
}
}
}
return svcViaSuccessors[svc];
}
}
void
NBRailwayTopologyAnalyzer::analyzeTopology(NBEdgeCont& ec) {
getBrokenRailNodes(ec, true);
}
int
NBRailwayTopologyAnalyzer::repairTopology(NBEdgeCont& ec, NBPTStopCont& sc, NBPTLineCont& lc) {
const bool minimal = OptionsCont::getOptions().getBool("railway.topology.repair.minimal");
int addedBidi = 0;
if (!minimal) {
addedBidi += extendBidiEdges(ec);
addedBidi += reverseEdges(ec, sc);
addedBidi += addBidiEdgesForBufferStops(ec);
addedBidi += addBidiEdgesBetweenSwitches(ec);
}
if (lc.getLines().size() > 0) {
addedBidi += addBidiEdgesForStops(ec, lc, sc, minimal);
}
if (OptionsCont::getOptions().getBool("railway.topology.repair.connect-straight")) {
addedBidi += addBidiEdgesForStraightConnectivity(ec, true);
addedBidi += addBidiEdgesForStraightConnectivity(ec, false);
addedBidi += extendBidiEdges(ec);
}
return addedBidi;
}
int
NBRailwayTopologyAnalyzer::makeAllBidi(NBEdgeCont& ec) {
int numNotCenterEdges = 0;
int numAddedBidiEdges = 0;
std::string inputfile = OptionsCont::getOptions().getString("railway.topology.all-bidi.input-file");
std::vector<NBEdge*> edges;
if (inputfile == "") {
for (NBEdge* edge : ec.getAllEdges()) {
edges.push_back(edge);
}
} else {
std::set<std::string> edgeIDs;
NBHelpers::loadEdgesFromFile(inputfile, edgeIDs);
for (const std::string& edgeID : edgeIDs) {
NBEdge* edge = ec.retrieve(edgeID);
if (edge != nullptr) {
edges.push_back(edge);
}
}
}
for (NBEdge* edge : edges) {
if (hasRailway(edge->getPermissions())) {
edge->invalidateConnections(true);
if (!edge->isBidiRail()) {
if (edge->getLaneSpreadFunction() == LaneSpreadFunction::CENTER) {
NBEdge* e2 = addBidiEdge(ec, edge, false);
if (e2 != nullptr) {
numAddedBidiEdges++;
}
} else {
numNotCenterEdges++;
}
}
}
}
WRITE_MESSAGEF(TL("Added % bidi-edges to ensure that all tracks are usable in both directions."), toString(numAddedBidiEdges));
if (numNotCenterEdges) {
WRITE_WARNINGF(TL("Ignore % edges because they have the wrong spreadType"), toString(numNotCenterEdges));
}
return numAddedBidiEdges;
}
NBEdge*
NBRailwayTopologyAnalyzer::addBidiEdge(NBEdgeCont& ec, NBEdge* edge, bool update) {
assert(edge->getLaneSpreadFunction() == LaneSpreadFunction::CENTER);
assert(!edge->isBidiRail());
const std::string id2 = (edge->getID()[0] == '-'
? edge->getID().substr(1)
: "-" + edge->getID());
if (ec.wasIgnored(id2)) {
return nullptr;
}
if (ec.retrieve(id2) == nullptr) {
NBEdge* e2 = new NBEdge(id2, edge->getToNode(), edge->getFromNode(),
edge, edge->getGeometry().reverse());
if (edge->getParameter(NBTrafficLightDefinition::OSM_DIRECTION) == "forward") {
e2->setParameter(NBTrafficLightDefinition::OSM_DIRECTION, "backward");
}
ec.insert(e2);
if (ec.retrieve(id2) == nullptr) {
WRITE_WARNINGF(TL("Bidi-edge '%' prevented by filtering rules."), id2);
return nullptr;
}
if (update) {
updateTurns(edge);
for (NBEdge* incoming : e2->getFromNode()->getIncomingEdges()) {
if (hasRailway(incoming->getPermissions())) {
incoming->invalidateConnections(true);
}
}
}
return e2;
} else {
WRITE_WARNINGF(TL("Could not add bidi-edge '%'."), id2);
return nullptr;
}
}
void
NBRailwayTopologyAnalyzer::getRailEdges(const NBNode* node,
EdgeVector& inEdges, EdgeVector& outEdges) {
for (NBEdge* e : node->getIncomingEdges()) {
if ((e->getPermissions() & SVC_RAIL_CLASSES) != 0) {
inEdges.push_back(e);
}
}
for (NBEdge* e : node->getOutgoingEdges()) {
if ((e->getPermissions() & SVC_RAIL_CLASSES) != 0) {
outEdges.push_back(e);
}
}
}
std::set<NBNode*>
NBRailwayTopologyAnalyzer::getBrokenRailNodes(NBEdgeCont& ec, bool verbose) {
std::set<NBNode*> brokenNodes;
OutputDevice& device = OutputDevice::getDevice(verbose
? OptionsCont::getOptions().getString("railway.topology.output")
: "/dev/null");
device.writeXMLHeader("railwayTopology", "");
std::set<NBNode*> railNodes = getRailNodes(ec, verbose);
std::map<std::pair<int, int>, std::set<NBNode*, ComparatorIdLess> > types;
std::set<NBEdge*, ComparatorIdLess> bidiEdges;
std::set<NBEdge*, ComparatorIdLess> bufferStops;
for (NBNode* node : railNodes) {
EdgeVector inEdges, outEdges;
getRailEdges(node, inEdges, outEdges);
types[std::make_pair((int)inEdges.size(), (int)outEdges.size())].insert(node);
for (NBEdge* e : outEdges) {
if (e->isBidiRail() && bidiEdges.count(e->getTurnDestination(true)) == 0) {
NBEdge* primary = e;
NBEdge* secondary = e->getTurnDestination(true);
if (e->getID()[0] == '-') {
std::swap(primary, secondary);
} else if (primary->getID()[0] != '-' && secondary->getID()[0] != '-' && secondary->getID() < primary->getID()) {
std::swap(primary, secondary);
}
if (bidiEdges.count(secondary) == 0) {
bidiEdges.insert(primary);
}
}
}
}
int numBrokenA = 0;
int numBrokenB = 0;
int numBrokenC = 0;
int numBrokenD = 0;
int numBufferStops = 0;
if (verbose && types.size() > 0) {
WRITE_MESSAGE(TL("Railway nodes by number of incoming,outgoing edges:"))
}
device.openTag("legend");
device.openTag("error");
device.writeAttr(SUMO_ATTR_ID, "a");
device.writeAttr("meaning", "edge pair angle supports driving but both are outgoing");
device.closeTag();
device.openTag("error");
device.writeAttr(SUMO_ATTR_ID, "b");
device.writeAttr("meaning", "edge pair angle supports driving but both are incoming");
device.closeTag();
device.openTag("error");
device.writeAttr(SUMO_ATTR_ID, "c");
device.writeAttr("meaning", "an incoming edge has a sharp angle to all outgoing edges");
device.closeTag();
device.openTag("error");
device.writeAttr(SUMO_ATTR_ID, "d");
device.writeAttr("meaning", "an outgoing edge has a sharp angle from all incoming edges");
device.closeTag();
device.closeTag();
for (auto it : types) {
int numBrokenType = 0;
device.openTag("railNodeType");
int in = it.first.first;
int out = it.first.second;
device.writeAttr("in", in);
device.writeAttr("out", out);
for (NBNode* n : it.second) {
device.openTag(SUMO_TAG_NODE);
device.writeAttr(SUMO_ATTR_ID, n->getID());
EdgeVector inRail, outRail;
getRailEdges(n, inRail, outRail);
std::string broken = "";
if (in < 2 && hasStraightPair(n, outRail, outRail)) {
broken += "a";
numBrokenA++;
}
if (out < 2 && hasStraightPair(n, inRail, inRail)) {
broken += "b";
numBrokenB++;
}
if (out > 0) {
for (NBEdge* e : inRail) {
EdgeVector tmp;
tmp.push_back(e);
if (allSharp(n, tmp, outRail)) {
broken += "c";
numBrokenC++;
break;
}
}
}
if (in > 0) {
for (NBEdge* e : outRail) {
EdgeVector tmp;
tmp.push_back(e);
if (allSharp(n, inRail, tmp)) {
broken += "d";
numBrokenD++;
break;
}
}
}
if (((in == 1 && out == 1) || (in == 2 && out == 2))
&& allBidi(inRail) && allBidi(outRail)) {
broken = "";
}
if (broken.size() > 0) {
device.writeAttr("broken", broken);
brokenNodes.insert(n);
numBrokenType++;
}
if (StringUtils::toBool(n->getParameter("buffer_stop", "false"))) {
device.writeAttr("buffer_stop", "true");
numBufferStops++;
}
device.closeTag();
}
device.closeTag();
if (verbose) {
WRITE_MESSAGE(" " + toString(it.first.first) + "," + toString(it.first.second)
+ " count: " + toString(it.second.size()) + " broken: " + toString(numBrokenType));
}
}
if (verbose) {
WRITE_MESSAGE("Found " + toString(brokenNodes.size()) + " broken railway nodes "
+ "(A=" + toString(numBrokenA)
+ " B=" + toString(numBrokenB)
+ " C=" + toString(numBrokenC)
+ " D=" + toString(numBrokenD)
+ ")");
WRITE_MESSAGEF(TL("Found % railway nodes marked as buffer_stop"), toString(numBufferStops));
}
for (NBEdge* e : bidiEdges) {
device.openTag("bidiEdge");
device.writeAttr(SUMO_ATTR_ID, e->getID());
device.writeAttr("bidi", e->getTurnDestination(true)->getID());
device.closeTag();
}
if (verbose) {
WRITE_MESSAGEF(TL("Found % bidirectional rail edges"), toString(bidiEdges.size()));
}
device.close();
return brokenNodes;
}
std::set<NBNode*>
NBRailwayTopologyAnalyzer::getRailNodes(NBEdgeCont& ec, bool verbose) {
std::set<NBNode*> railNodes;
int numRailEdges = 0;
for (auto it = ec.begin(); it != ec.end(); it++) {
if (hasRailway(it->second->getPermissions())) {
numRailEdges++;
railNodes.insert(it->second->getFromNode());
railNodes.insert(it->second->getToNode());
}
}
int numRailSignals = 0;
for (const NBNode* const node : railNodes) {
if (node->getType() == SumoXMLNodeType::RAIL_SIGNAL) {
numRailSignals++;
}
}
if (verbose) {
WRITE_MESSAGEF(TL("Found % railway edges and % railway nodes (% signals)."), toString(numRailEdges), toString(railNodes.size()), toString(numRailSignals));
}
return railNodes;
}
bool
NBRailwayTopologyAnalyzer::isStraight(const NBNode* node, const NBEdge* e1, const NBEdge* e2) {
const double relAngle = NBHelpers::normRelAngle(e1->getAngleAtNode(node), e2->getAngleAtNode(node));
if ((e1->getToNode() == node && e2->getFromNode() == node)
|| (e1->getFromNode() == node && e2->getToNode() == node)) {
return fabs(relAngle) < SHARP_THRESHOLD;
} else {
return fabs(relAngle) > SHARP_THRESHOLD_SAMEDIR;
}
}
bool
NBRailwayTopologyAnalyzer::hasStraightPair(const NBNode* node, const EdgeVector& edges,
const EdgeVector& edges2) {
#ifdef DEBUG_SEQSTOREVERSE
#endif
for (NBEdge* e1 : edges) {
for (NBEdge* e2 : edges2) {
if (e1 != e2 && isStraight(node, e1, e2)) {
return true;
}
}
}
return false;
}
bool
NBRailwayTopologyAnalyzer::allBroken(const NBNode* node, NBEdge* candOut, const EdgeVector& in, const EdgeVector& out) {
for (NBEdge* e : in) {
if (e != candOut && isStraight(node, e, candOut)) {
if (gDebugFlag1) {
std::cout << " isStraight e=" << e->getID() << " candOut=" << candOut->getID() << "\n";
}
return false;
}
}
for (NBEdge* e : out) {
if (e != candOut && !isStraight(node, e, candOut)) {
if (gDebugFlag1) {
std::cout << " isSharp e=" << e->getID() << " candOut=" << candOut->getID() << "\n";
}
return false;
}
}
return true;
}
bool
NBRailwayTopologyAnalyzer::allSharp(const NBNode* node, const EdgeVector& in, const EdgeVector& out, bool countBidiAsSharp) {
bool allBidi = true;
for (NBEdge* e1 : in) {
for (NBEdge* e2 : out) {
if (e1 != e2 && isStraight(node, e1, e2)) {
return false;
}
if (!e1->isBidiRail(true)) {
allBidi = false;
}
}
}
return !allBidi || countBidiAsSharp;
}
bool
NBRailwayTopologyAnalyzer::allBidi(const EdgeVector& edges) {
for (NBEdge* e : edges) {
if (!e->isBidiRail()) {
return false;
}
}
return true;
}
int
NBRailwayTopologyAnalyzer::extendBidiEdges(NBEdgeCont& ec) {
int added = 0;
for (auto it = ec.begin(); it != ec.end(); it++) {
NBEdge* e = it->second;
if (e->isBidiRail()) {
added += extendBidiEdges(ec, e->getFromNode(), e->getTurnDestination(true));
added += extendBidiEdges(ec, e->getToNode(), e);
}
}
if (added > 0) {
WRITE_MESSAGEF(TL("Added % bidi-edges as extension of existing bidi edges."), toString(added));
}
return added;
}
int
NBRailwayTopologyAnalyzer::extendBidiEdges(NBEdgeCont& ec, NBNode* node, NBEdge* bidiIn) {
assert(bidiIn->getToNode() == node);
NBEdge* bidiOut = bidiIn->getTurnDestination(true);
if (bidiOut == nullptr) {
WRITE_WARNINGF(TL("Could not find bidi-edge for edge '%'"), bidiIn->getID());
return 0;
}
EdgeVector tmpBidiOut;
tmpBidiOut.push_back(bidiOut);
EdgeVector tmpBidiIn;
tmpBidiIn.push_back(bidiIn);
int added = 0;
EdgeVector inRail, outRail;
getRailEdges(node, inRail, outRail);
for (NBEdge* cand : outRail) {
if (!cand->isBidiRail() && isStraight(node, bidiIn, cand)
&& cand->getLaneSpreadFunction() == LaneSpreadFunction::CENTER
&& allSharp(node, inRail, tmpBidiOut, true)) {
NBEdge* e2 = addBidiEdge(ec, cand);
if (e2 != nullptr) {
added += 1 + extendBidiEdges(ec, cand->getToNode(), cand);
}
}
}
for (NBEdge* cand : inRail) {
if (!cand->isBidiRail() && isStraight(node, cand, bidiOut)
&& cand->getLaneSpreadFunction() == LaneSpreadFunction::CENTER
&& allSharp(node, outRail, tmpBidiIn, true)) {
NBEdge* e2 = addBidiEdge(ec, cand);
if (e2 != nullptr) {
added += 1 + extendBidiEdges(ec, cand->getFromNode(), e2);
}
}
}
return added;
}
int
NBRailwayTopologyAnalyzer::reverseEdges(NBEdgeCont& ec, NBPTStopCont& sc) {
std::set<NBNode*> brokenNodes = getBrokenRailNodes(ec);
std::vector<EdgeVector> seqsToReverse;
for (NBNode* n : brokenNodes) {
EdgeVector inRail, outRail;
getRailEdges(n, inRail, outRail);
for (NBEdge* start : outRail) {
EdgeVector tmp;
tmp.push_back(start);
if (!allBroken(n, start, inRail, outRail)
|| (inRail.size() == 1 && outRail.size() == 1)) {
#ifdef DEBUG_SEQSTOREVERSE
if (n->getID() == DEBUGNODEID) {
std::cout << " abort at start n=" << n->getID() << " (not all broken)\n";
}
#endif
continue;
}
bool forward = true;
EdgeVector seq;
while (forward) {
seq.push_back(start);
NBNode* n2 = start->getToNode();
EdgeVector inRail2, outRail2;
getRailEdges(n2, inRail2, outRail2);
if (brokenNodes.count(n2) != 0) {
EdgeVector tmp2;
tmp2.push_back(start);
if (allBroken(n2, start, outRail2, inRail2)) {
seqsToReverse.push_back(seq);
} else {
#ifdef DEBUG_SEQSTOREVERSE
if (n->getID() == DEBUGNODEID) {
std::cout << " abort at n2=" << n2->getID() << " (not all broken)\n";
}
#endif
}
forward = false;
} else {
if (outRail2.size() == 0) {
forward = false;
#ifdef DEBUG_SEQSTOREVERSE
if (n->getID() == DEBUGNODEID) {
std::cout << " abort at n2=" << n2->getID() << " (border)\n";
}
#endif
} else if (outRail2.size() > 1 || inRail2.size() > 1) {
forward = false;
#ifdef DEBUG_SEQSTOREVERSE
if (n->getID() == DEBUGNODEID) {
std::cout << " abort at n2=" << n2->getID() << " (switch)\n";
}
#endif
} else {
start = outRail2.front();
}
}
}
}
}
if (seqsToReverse.size() > 0) {
WRITE_MESSAGEF(TL("Found % reversible edge sequences between broken rail nodes"), toString(seqsToReverse.size()));
}
std::sort(seqsToReverse.begin(), seqsToReverse.end(),
[](const EdgeVector & a, const EdgeVector & b) {
return a.size() < b.size();
});
int numReversed = 0;
std::set<NBNode*> affectedEndpoints;
std::set<std::string> reversedIDs;
std::map<int, int> seqLengths;
for (EdgeVector& seq : seqsToReverse) {
NBNode* seqStart = seq.front()->getFromNode();
NBNode* seqEnd = seq.back()->getToNode();
if (affectedEndpoints.count(seqStart) == 0
&& affectedEndpoints.count(seqEnd) == 0) {
affectedEndpoints.insert(seqStart);
affectedEndpoints.insert(seqEnd);
for (NBEdge* e : seq) {
e->reinitNodes(e->getToNode(), e->getFromNode());
e->setGeometry(e->getGeometry().reverse());
reversedIDs.insert(e->getID());
if (e->getParameter(NBTrafficLightDefinition::OSM_DIRECTION) == "forward") {
e->setParameter(NBTrafficLightDefinition::OSM_DIRECTION, "backward");
}
}
seqLengths[(int)seq.size()]++;
numReversed++;
}
}
if (numReversed > 0) {
WRITE_MESSAGEF(TL("Reversed % sequences (count by length: %)"), toString(numReversed), joinToString(seqLengths, " ", ":"));
for (auto& item : sc.getStops()) {
if (reversedIDs.count(item.second->getEdgeId())) {
item.second->findLaneAndComputeBusStopExtent(ec);
}
}
}
return numReversed;
}
int
NBRailwayTopologyAnalyzer::addBidiEdgesForBufferStops(NBEdgeCont& ec) {
std::set<NBNode*> brokenNodes = getBrokenRailNodes(ec);
std::set<NBNode*> railNodes = getRailNodes(ec);
int numBufferStops = 0;
int numAddedBidiTotal = 0;
for (NBNode* node : railNodes) {
if (StringUtils::toBool(node->getParameter("buffer_stop", "false"))) {
if (node->getEdges().size() != 1) {
WRITE_WARNINGF(TL("Ignoring buffer stop junction '%' with % edges."), node->getID(), node->getEdges().size());
continue;
}
numBufferStops++;
NBEdge* prev = nullptr;
NBEdge* prev2 = nullptr;
EdgeVector inRail, outRail;
getRailEdges(node, inRail, outRail);
bool addAway = true;
while (prev == nullptr || (inRail.size() + outRail.size()) == 3) {
NBEdge* e = nullptr;
if (prev == nullptr) {
assert(node->getEdges().size() == 1);
e = node->getEdges().front();
addAway = node == e->getToNode();
} else {
if (addAway) {
assert(inRail.size() == 2);
e = inRail.front() == prev2 ? inRail.back() : inRail.front();
} else {
assert(outRail.size() == 2);
e = outRail.front() == prev2 ? outRail.back() : outRail.front();
}
}
e->setLaneSpreadFunction(LaneSpreadFunction::CENTER);
NBNode* e2From = nullptr;
NBNode* e2To = nullptr;
if (addAway) {
e2From = node;
e2To = e->getFromNode();
node = e2To;
} else {
e2From = e->getToNode();
e2To = node;
node = e2From;
}
NBEdge* e2 = addBidiEdge(ec, e);
if (e2 == nullptr) {
break;
}
prev = e;
prev2 = e2;
numAddedBidiTotal++;
inRail.clear();
outRail.clear();
getRailEdges(node, inRail, outRail);
}
}
}
if (numAddedBidiTotal > 0) {
WRITE_MESSAGEF(TL("Added % edges to connect % buffer stops in both directions."), toString(numAddedBidiTotal), toString(numBufferStops));
}
return numAddedBidiTotal;
}
NBEdge*
NBRailwayTopologyAnalyzer::isBidiSwitch(const NBNode* n) {
EdgeVector inRail, outRail;
getRailEdges(n, inRail, outRail);
if (inRail.size() == 2 && outRail.size() == 1 && isStraight(n, inRail.front(), inRail.back())) {
if (isStraight(n, inRail.front(), outRail.front())) {
return inRail.front();
} else if (isStraight(n, inRail.back(), outRail.front())) {
return inRail.back();
}
}
if (inRail.size() == 1 && outRail.size() == 2 && isStraight(n, outRail.front(), outRail.back())) {
if (isStraight(n, outRail.front(), inRail.front())) {
return outRail.front();
} else if (isStraight(n, outRail.back(), inRail.front())) {
return outRail.back();
}
}
return nullptr;
}
int
NBRailwayTopologyAnalyzer::addBidiEdgesBetweenSwitches(NBEdgeCont& ec) {
std::set<NBNode*> brokenNodes = getBrokenRailNodes(ec);
std::map<int, int> seqLengths;
int numAdded = 0;
int numSeqs = 0;
for (NBNode* n : brokenNodes) {
NBEdge* edge = isBidiSwitch(n);
if (edge != nullptr && edge->getLaneSpreadFunction() == LaneSpreadFunction::CENTER) {
std::vector<NBNode*> nodeSeq;
EdgeVector edgeSeq;
NBNode* prev = n;
nodeSeq.push_back(prev);
edgeSeq.push_back(edge);
bool forward = true;
while (forward) {
NBNode* next = edge->getFromNode() == prev ? edge->getToNode() : edge->getFromNode();
EdgeVector allRail;
getRailEdges(next, allRail, allRail);
if (allRail.size() == 2 && isStraight(next, allRail.front(), allRail.back())) {
prev = next;
edge = allRail.front() == edge ? allRail.back() : allRail.front();
nodeSeq.push_back(prev);
edgeSeq.push_back(edge);
} else {
forward = false;
EdgeVector inRail2, outRail2;
getRailEdges(next, inRail2, outRail2);
if (isBidiSwitch(next) == edge) {
for (NBEdge* e : edgeSeq) {
addBidiEdge(ec, e);
}
seqLengths[(int)edgeSeq.size()]++;
numSeqs++;
numAdded += (int)edgeSeq.size();
} else {
}
}
}
}
}
if (seqLengths.size() > 0) {
WRITE_MESSAGEF(TL("Added % bidi-edges between % pairs of railway switches (count by length: %)"), toString(numAdded), toString(numSeqs), joinToString(seqLengths, " ", ":"));
}
return numAdded;
}
std::set<NBPTLine*>
NBRailwayTopologyAnalyzer::findBidiCandidates(NBPTLineCont& lc) {
std::set<NBPTLine*> result;
std::set<std::pair<std::shared_ptr<NBPTStop>, std::shared_ptr<NBPTStop> > > visited;
for (const auto& item : lc.getLines()) {
const std::vector<std::shared_ptr<NBPTStop> >& stops = item.second->getStops();
if (stops.size() > 1) {
for (auto it = stops.begin(); it + 1 != stops.end(); ++it) {
std::shared_ptr<NBPTStop> fromStop = *it;
std::shared_ptr<NBPTStop> toStop = *(it + 1);
visited.insert({fromStop, toStop});
}
}
}
for (const auto& item : lc.getLines()) {
const std::vector<std::shared_ptr<NBPTStop> >& stops = item.second->getStops();
if (stops.size() > 1) {
for (auto it = stops.begin(); it + 1 != stops.end(); ++it) {
std::shared_ptr<NBPTStop> fromStop = *it;
std::shared_ptr<NBPTStop> toStop = *(it + 1);
std::pair<std::shared_ptr<NBPTStop>, std::shared_ptr<NBPTStop> > reverseTrip({toStop, fromStop});
if (visited.count(reverseTrip)) {
result.insert(item.second);
break;
}
}
}
}
return result;
}
int
NBRailwayTopologyAnalyzer::addBidiEdgesForStops(NBEdgeCont& ec, NBPTLineCont& lc, NBPTStopCont& sc, bool minimal) {
const double penalty = OptionsCont::getOptions().getFloat("railway.topology.repair.bidi-penalty");
std::vector<Track*> tracks;
for (NBEdge* edge : ec.getAllEdges()) {
tracks.push_back(new Track(edge));
}
const int numEdges = (int)tracks.size();
for (NBEdge* edge : ec.getAllEdges()) {
tracks.push_back(new Track(edge, (int)tracks.size(), edge->getID() + "_reverse", penalty));
}
std::map<NBEdge*, std::pair<Track*, Track*> > stopTracks;
for (NBEdge* edge : ec.getAllEdges()) {
if ((edge->getPermissions() & SVC_RAIL_CLASSES) != 0) {
Track* start = new Track(edge, (int)tracks.size(), edge->getID() + "_start");
tracks.push_back(start);
Track* end = new Track(edge, (int)tracks.size(), edge->getID() + "_end");
tracks.push_back(end);
stopTracks[edge] = {start, end};
}
}
for (NBNode* node : getRailNodes(ec)) {
EdgeVector railEdges;
getRailEdges(node, railEdges, railEdges);
for (NBEdge* e1 : railEdges) {
for (NBEdge* e2 : railEdges) {
if (e1 != e2 && isStraight(node, e1, e2)) {
int i = e1->getNumericalID();
int i2 = e2->getNumericalID();
if (e1->getToNode() == node) {
if (e2->getFromNode() == node) {
tracks[i]->addSuccessor(tracks[i2]);
tracks[i2 + numEdges]->addSuccessor(tracks[i + numEdges]);
} else {
tracks[i]->addSuccessor(tracks[i2 + numEdges]);
tracks[i2]->addSuccessor(tracks[i + numEdges]);
}
} else {
if (e2->getFromNode() == node) {
tracks[i + numEdges]->addSuccessor(tracks[i2]);
tracks[i2 + numEdges]->addSuccessor(tracks[i]);
} else {
}
}
}
}
}
}
for (auto& item : stopTracks) {
const int index = item.first->getNumericalID();
item.second.first->addSuccessor(tracks[index]);
item.second.first->addSuccessor(tracks[index + numEdges]);
tracks[index]->addSuccessor(item.second.second);
tracks[index + numEdges]->addSuccessor(item.second.second);
}
SUMOAbstractRouter<Track, NBVehicle>* const router = new DijkstraRouter<Track, NBVehicle>(
tracks, true, &NBRailwayTopologyAnalyzer::getTravelTimeStatic, nullptr, true);
int added = 0;
int numDisconnected = 0;
std::set<NBEdge*, ComparatorIdLess> addBidiStops;
std::set<NBEdge*, ComparatorIdLess> addBidiEdges;
std::set<std::pair<std::string, std::string> > visited;
std::set<NBPTLine*> requireBidi = findBidiCandidates(lc);
for (const auto& item : lc.getLines()) {
NBPTLine* line = item.second;
std::vector<std::pair<NBEdge*, std::string> > stops = line->getStopEdges(ec);
std::vector<NBEdge*> stopEdges;
for (auto it : stops) {
stopEdges.push_back(it.first);
}
NBEdge* routeStart = line->getRouteStart(ec);
NBEdge* routeEnd = line->getRouteEnd(ec);
if (routeStart != nullptr) {
stops.insert(stops.begin(), {routeStart, routeStart->getID()});
}
if (routeEnd != nullptr) {
stops.push_back({routeEnd, routeEnd->getID()});
}
if (stops.size() < 2) {
continue;
}
if (!line->isConsistent(stopEdges) && requireBidi.count(line) == 0) {
WRITE_WARNINGF(TL("Edge sequence is not consistent with stop sequence in line '%', not adding bidi edges."), item.first);
continue;
}
for (auto it = stops.begin(); it + 1 != stops.end(); ++it) {
NBEdge* fromEdge = it->first;
NBEdge* toEdge = (it + 1)->first;
const std::string fromStop = it->second;
const std::string toStop = (it + 1)->second;
std::pair<std::string, std::string> trip(fromStop, toStop);
std::pair<std::string, std::string> reverseTrip(toStop, fromStop);
if (visited.count(trip) != 0) {
continue;
} else {
visited.insert(trip);
}
if (stopTracks.count(fromEdge) == 0
|| stopTracks.count(toEdge) == 0) {
continue;
}
const bool needBidi = visited.count(reverseTrip) != 0;
NBVehicle veh(line->getRef(), (SUMOVehicleClass)(fromEdge->getPermissions() & SVC_RAIL_CLASSES));
std::vector<const Track*> route;
router->compute(stopTracks[fromEdge].first, stopTracks[toEdge].second, &veh, 0, route);
if (route.size() > 0) {
assert(route.size() > 2);
for (int i = 1; i < (int)route.size() - 1; ++i) {
if (route[i]->getNumericalID() >= numEdges || needBidi) {
NBEdge* edge = route[i]->edge;
if (addBidiEdges.count(edge) == 0) {
bool isStop = i == 1 || i == (int)route.size() - 2;
if (!edge->isBidiRail(true)) {
if (edge->getLaneSpreadFunction() == LaneSpreadFunction::CENTER) {
addBidiEdges.insert(edge);
if (isStop) {
addBidiStops.insert(edge);
}
} else {
if (isStop) {
WRITE_WARNINGF(TL("Stop on edge '%' can only be reached in reverse but edge has the wrong spreadType."), fromEdge->getID());
}
}
} else if (isStop && needBidi) {
std::shared_ptr<NBPTStop> fs = sc.get(fromStop);
if (fs) {
std::shared_ptr<NBPTStop> fromReverse = sc.getReverseStop(fs, ec);
if (fromReverse) {
sc.insert(fromReverse);
fs->setBidiStop(fromReverse);
}
}
std::shared_ptr<NBPTStop> ts = sc.get(toStop);
if (ts) {
std::shared_ptr<NBPTStop> toReverse = sc.getReverseStop(ts, ec);
if (toReverse) {
sc.insert(toReverse);
ts->setBidiStop(toReverse);
}
}
}
}
}
}
} else {
WRITE_WARNINGF(TL("No connection found between stops on edge '%' and edge '%'."), fromEdge->getID(), toEdge->getID());
numDisconnected++;
}
}
}
for (NBEdge* edge : addBidiEdges) {
if (!edge->isBidiRail()) {
NBEdge* e2 = addBidiEdge(ec, edge);
if (e2 != nullptr) {
added++;
if (!minimal) {
added += extendBidiEdges(ec, edge->getToNode(), edge);
added += extendBidiEdges(ec, edge->getFromNode(), e2);
}
}
}
}
if (addBidiEdges.size() > 0 || numDisconnected > 0) {
WRITE_MESSAGE("Added " + toString(addBidiStops.size()) + " bidi-edges for public transport stops and a total of "
+ toString(added) + " bidi-edges to ensure connectivity of stops ("
+ toString(numDisconnected) + " stops remain disconnected)");
}
for (Track* t : tracks) {
delete t;
}
delete router;
return (int)addBidiEdges.size();
}
int
NBRailwayTopologyAnalyzer::addBidiEdgesForStraightConnectivity(NBEdgeCont& ec, bool geometryLike) {
int added = 0;
std::set<NBNode*> brokenNodes = getBrokenRailNodes(ec);
for (const auto& e : ec) {
if (!hasRailway(e.second->getPermissions())) {
continue;
}
NBNode* const from = e.second->getFromNode();
NBNode* const to = e.second->getToNode();
if (brokenNodes.count(from) == 0 && brokenNodes.count(to) == 0) {
continue;
}
if (e.second->isBidiRail()) {
continue;
}
EdgeVector inRailFrom, outRailFrom, inRailTo, outRailTo;
getRailEdges(from, inRailFrom, outRailFrom);
getRailEdges(to, inRailTo, outRailTo);
bool haveStraight = false;
bool haveStraightReverse = false;
if (!geometryLike || outRailFrom.size() + inRailFrom.size() == 2) {
for (const NBEdge* fromStraightCand : outRailFrom) {
if (fromStraightCand != e.second && isStraight(from, fromStraightCand, e.second)) {
haveStraightReverse = true;
break;
}
}
if (haveStraightReverse) {
for (const NBEdge* fromStraightCand : inRailFrom) {
if (fromStraightCand != e.second && isStraight(from, fromStraightCand, e.second)) {
haveStraight = true;
break;
}
}
}
}
if ((!haveStraightReverse || haveStraight) && (!geometryLike || outRailTo.size() + inRailTo.size() == 2)) {
haveStraight = false;
haveStraightReverse = false;
for (const NBEdge* toStraightCand : inRailTo) {
if (toStraightCand != e.second && isStraight(to, toStraightCand, e.second)) {
haveStraightReverse = true;
break;
}
}
if (haveStraightReverse) {
for (const NBEdge* toStraightCand : outRailTo) {
if (toStraightCand != e.second && isStraight(to, toStraightCand, e.second)) {
haveStraight = true;
break;
}
}
}
}
if (haveStraightReverse && !haveStraight) {
NBEdge* e2 = addBidiEdge(ec, e.second);
if (e2 != nullptr) {
added++;
added += extendBidiEdges(ec, to, e.second);
added += extendBidiEdges(ec, from, e2);
}
}
}
if (added > 0) {
if (geometryLike) {
WRITE_MESSAGEF(TL("Added % bidi-edges to ensure connectivity of straight tracks at geometry-like nodes."), toString(added));
} else {
WRITE_MESSAGEF(TL("Added % bidi-edges to ensure connectivity of straight tracks at switches."), toString(added));
}
}
return added;
}
void
NBRailwayTopologyAnalyzer::updateTurns(NBEdge* edge) {
NBTurningDirectionsComputer::computeTurnDirectionsForNode(edge->getFromNode(), false);
NBTurningDirectionsComputer::computeTurnDirectionsForNode(edge->getToNode(), false);
}
double
NBRailwayTopologyAnalyzer::getTravelTimeStatic(const Track* const track, const NBVehicle* const veh, double time) {
return NBEdge::getTravelTimeStatic(track->edge, veh, time) * track->penalty;
}
void
NBRailwayTopologyAnalyzer::extendDirectionPriority(NBEdgeCont& ec, bool fromUniDir) {
std::set<NBEdge*, ComparatorIdLess> bidi;
EdgeSet uni;
for (NBEdge* edge : ec.getAllEdges()) {
if (hasRailway(edge->getPermissions())) {
if (fromUniDir) {
if (!edge->isBidiRail()) {
edge->setPriority(4);
uni.insert(edge);
} else {
bidi.insert(edge);
}
} else {
if (edge->getPriority() >= 0) {
uni.insert(edge);
} else {
bidi.insert(edge);
}
}
}
}
if (uni.size() == 0) {
if (bidi.size() != 0) {
WRITE_WARNING(TL("Cannot extend track direction priority because there are no track edges with positive priority"));
}
return;
}
EdgeSet seen;
EdgeSet check = uni;
EdgeSet forward;
while (!check.empty()) {
NBEdge* edge = *check.begin();
check.erase(edge);
if (seen.count(edge) != 0) {
continue;
}
seen.insert(edge);
NBEdge* straightOut = edge->getStraightContinuation(edge->getPermissions());
if (straightOut != nullptr && straightOut->getStraightPredecessor(straightOut->getPermissions()) == edge) {
forward.insert(straightOut);
check.insert(straightOut);
}
NBEdge* straightIn = edge->getStraightPredecessor(edge->getPermissions());
if (straightIn != nullptr && straightIn->getStraightContinuation(straightIn->getPermissions()) == edge) {
forward.insert(straightIn);
check.insert(straightIn);
}
#ifdef DEBUG_DIRECTION_PRIORITY
std::cout << "edge=" << edge->getID() << " in=" << Named::getIDSecure(straightIn) << " out=" << Named::getIDSecure(straightOut)
<< " outPred=" << (straightOut != nullptr ? Named::getIDSecure(straightOut->getStraightPredecessor(straightOut->getPermissions())) : "")
<< " inSucc=" << (straightIn != nullptr ? Named::getIDSecure(straightIn->getStraightContinuation(straightIn->getPermissions())) : "")
<< "\n";
#endif
}
for (NBEdge* edge : bidi) {
NBEdge* bidiEdge = const_cast<NBEdge*>(edge->getBidiEdge());
int prio;
int bidiPrio;
if (forward.count(edge) != 0) {
if (forward.count(bidiEdge) == 0) {
prio = 3;
bidiPrio = 0;
} else {
prio = 2;
bidiPrio = 2;
}
} else {
if (forward.count(bidiEdge) != 0) {
prio = 0;
bidiPrio = 3;
} else {
prio = 1;
bidiPrio = 1;
}
}
if (bidiEdge == nullptr) {
WRITE_WARNINGF(TL("Edge '%' was loaded with undefined priority (%) but has unambiguous main direction (no bidi edge)"), edge->getID(), edge->getPriority());
}
if (edge->getPriority() >= 0) {
bidiPrio = 0;
}
if (bidiEdge != nullptr && bidiEdge->getPriority() >= 0) {
prio = 0;
}
if (edge->getPriority() < 0) {
edge->setPriority(prio);
}
if (bidiEdge != nullptr && bidiEdge->getPriority() < 0) {
bidiEdge->setPriority(bidiPrio);
}
}
std::map<int, int> numPrios;
for (NBEdge* edge : bidi) {
numPrios[edge->getPriority()]++;
}
if (fromUniDir) {
WRITE_MESSAGE("Assigned edge priority based on main direction: " + joinToString(numPrios, " ", ":") + ".")
} else {
WRITE_MESSAGE("Extended edge priority based on main direction: " + joinToString(numPrios, " ", ":") + ".")
}
}
int
NBRailwaySignalGuesser::guessRailSignals(NBEdgeCont& ec, NBPTStopCont& sc) {
const OptionsCont& oc = OptionsCont::getOptions();
int addedSignals = 0;
if (oc.exists("railway.signal.guess.by-stops")) {
if (oc.getBool("railway.signal.guess.by-stops")) {
const double minLength = oc.getFloat("osm.stop-output.length.train");
addedSignals += guessByStops(ec, sc, minLength);
}
}
return addedSignals;
}
bool
NBRailwaySignalGuesser::canBeSignal(const NBNode* node) {
return (node->getType() != SumoXMLNodeType::RAIL_SIGNAL && node->geometryLike());
}
int
NBRailwaySignalGuesser::guessByStops(NBEdgeCont& ec, NBPTStopCont& sc, double minLength) {
int addedSignals = 0;
for (auto& item : sc.getStops()) {
const NBEdge* stopEdge = ec.retrieve(item.second->getEdgeId());
if (stopEdge != nullptr && isRailway(stopEdge->getPermissions())) {
NBNode* to = stopEdge->getToNode();
if (canBeSignal(to)) {
to->reinit(to->getPosition(), SumoXMLNodeType::RAIL_SIGNAL);
addedSignals++;
}
NBNode* from = stopEdge->getFromNode();
if (stopEdge->getLoadedLength() >= minLength) {
if (canBeSignal(from)) {
from->reinit(from->getPosition(), SumoXMLNodeType::RAIL_SIGNAL);
addedSignals++;
}
} else {
double searchDist = minLength - stopEdge->getLoadedLength();
while (searchDist > 0 && from->geometryLike()) {
for (const NBEdge* in : from->getIncomingEdges()) {
if (in->getFromNode() != stopEdge->getToNode()) {
stopEdge = in;
break;
}
}
if (stopEdge->getFromNode() == from) {
break;
} else {
from = stopEdge->getFromNode();
}
searchDist -= stopEdge->getLoadedLength();
}
if (searchDist <= 0 && canBeSignal(from)) {
from->reinit(from->getPosition(), SumoXMLNodeType::RAIL_SIGNAL);
addedSignals++;
}
}
}
}
WRITE_MESSAGEF(TL("Added % rail signals at % stops."), addedSignals, sc.getStops().size());
return addedSignals;
}
int
NBRailwayGeometryHelper::straigthenCorrdidor(NBEdgeCont& ec, double maxAngle) {
int moved = 0;
int numCorridors = 0;
std::set<NBNode*> railNodes = NBRailwayTopologyAnalyzer::getRailNodes(ec);
std::set<NBNode*> railGeomNodes;
for (NBNode* n : railNodes) {
if (n->geometryLike()) {
railGeomNodes.insert(n);
}
}
std::set<NBNode*, ComparatorIdLess> kinkNodes;;
for (NBNode* n : railGeomNodes) {
NBEdge* in = n->getIncomingEdges().front();
NBEdge* out = n->getOutgoingEdges().size() == 1 || n->getOutgoingEdges()[1]->isTurningDirectionAt(in) ? n->getOutgoingEdges().front() : n->getOutgoingEdges().back();
const double relAngle = fabs(RAD2DEG(GeomHelper::angleDiff(DEG2RAD(in->getAngleAtNode(n)), DEG2RAD(out->getAngleAtNode(n)))));
if (maxAngle > 0 && relAngle > maxAngle) {
kinkNodes.insert(n);
}
}
while (!kinkNodes.empty()) {
std::vector<NBNode*> corridor;
std::vector<NBEdge*> corridorEdges;
Boundary corridorBox;
double length = 0;
NBNode* n = *kinkNodes.begin();
kinkNodes.erase(kinkNodes.begin());
NBEdge* in = n->getIncomingEdges().front();
NBEdge* out = n->getOutgoingEdges().size() == 1 || n->getOutgoingEdges()[1]->isTurningDirectionAt(in) ? n->getOutgoingEdges().front() : n->getOutgoingEdges().back();
NBEdge* const centerIn = in;
NBEdge* const centerOut = out;
NBNode* up = in->getFromNode();
NBNode* down = out->getToNode();
corridor.push_back(up);
corridor.push_back(n);
corridor.push_back(down);
corridorBox.add(up->getPosition());
corridorBox.add(down->getPosition());
corridorEdges.push_back(in);
corridorEdges.push_back(out);
length += in->getLoadedLength();
length += out->getLoadedLength();
Position cBeg, cEnd, delta;
while (kinkNodes.count(up) != 0) {
NBEdge* const out2 = in;
NBEdge* const in2 = up->getIncomingEdges().size() == 1 || up->getIncomingEdges()[1]->isTurningDirectionAt(out2) ? up->getIncomingEdges().front() : up->getIncomingEdges().back();
length += in2->getLoadedLength();
up = in2->getFromNode();
corridor.insert(corridor.begin(), up);
corridorEdges.insert(corridorEdges.begin(), in2);
kinkNodes.erase(up);
corridorBox.add(up->getPosition());
}
cBeg = up->getPosition();
cEnd = down->getPosition();
delta = cEnd - cBeg;
while (delta.length2D() <= POSITION_EPS * (double)corridor.size() && railGeomNodes.count(up) != 0) {
NBEdge* const out2 = in;
NBEdge* const in2 = up->getIncomingEdges().size() == 1 || up->getIncomingEdges()[1]->isTurningDirectionAt(out2) ? up->getIncomingEdges().front() : up->getIncomingEdges().back();
length += in2->getLoadedLength();
up = in2->getFromNode();
corridor.insert(corridor.begin(), up);
corridorEdges.insert(corridorEdges.begin(), in2);
kinkNodes.erase(up);
corridorBox.add(up->getPosition());
cBeg = up->getPosition();
cEnd = down->getPosition();
delta = cEnd - cBeg;
}
in = centerIn;
out = centerOut;
while (kinkNodes.count(down) != 0) {
NBEdge* const in2 = out;
NBEdge* const out2 = down->getOutgoingEdges().size() == 1 || down->getOutgoingEdges()[1]->isTurningDirectionAt(in2) ? down->getOutgoingEdges().front() : down->getOutgoingEdges().back();
down = out2->getToNode();
length += out2->getLoadedLength();
corridor.push_back(down);
corridorEdges.push_back(out2);
kinkNodes.erase(down);
corridorBox.add(down->getPosition());
}
cBeg = up->getPosition();
cEnd = down->getPosition();
delta = cEnd - cBeg;
while (delta.length2D() <= POSITION_EPS * (double)corridor.size() && railGeomNodes.count(down) != 0) {
NBEdge* const in2 = out;
NBEdge* const out2 = down->getOutgoingEdges().size() == 1 || down->getOutgoingEdges()[1]->isTurningDirectionAt(in2) ? down->getOutgoingEdges().front() : down->getOutgoingEdges().back();
down = out2->getToNode();
length += out2->getLoadedLength();
corridor.push_back(down);
corridorEdges.push_back(out2);
kinkNodes.erase(down);
corridorBox.add(down->getPosition());
cBeg = up->getPosition();
cEnd = down->getPosition();
delta = cEnd - cBeg;
}
std::set<NBNode*> corridorNodes(corridor.begin(), corridor.end());
for (NBNode* n2 : corridorNodes) {
for (NBEdge* e : n2->getEdges()) {
if (corridorNodes.count(e->getFromNode()) != 0
&& corridorNodes.count(e->getToNode()) != 0) {
PositionVector simpleGeom;
simpleGeom.push_back(e->getFromNode()->getPosition());
simpleGeom.push_back(e->getToNode()->getPosition());
e->setGeometry(simpleGeom);
}
}
}
if (delta.length2D() > 0) {
double currLength = 0;
for (int i = 1; i < (int)corridor.size() - 1; i++) {
currLength += corridorEdges[i - 1]->getLoadedLength();
const Position newPos = cBeg + delta * (currLength / length);
NBNode* const n2 = corridor[i];
n2->reinit(newPos, n2->getType());
for (NBEdge* e : n2->getEdges()) {
e->resetEndpointAtNode(n2);
}
moved += 1;
}
numCorridors += 1;
} else {
WRITE_WARNINGF(TL("Could not straighten corridor %."), toString(corridor));
}
}
WRITE_MESSAGEF(TL("Moved % rail junctions for straightening % corridors."), moved, numCorridors);
return moved;
}