generate-shapes/src/pfaedle/osm/OsmBuilder.cpp
Alexander Held | geOps c023bb963d Fix edge splitting fragment being registered with wrong geometry in edge grid
If I'm not mistaken, there is a typo in the OsmBuilder when an edge is split
into two edges to insert a station: one of the new edges is registered with
the wrong geometry in the edge grid. This should fix it.
2020-09-30 16:20:04 +02:00

1952 lines
64 KiB
C++

// Copyright 2018, University of Freiburg,
// Chair of Algorithms and Data Structures.
// Authors: Patrick Brosi <brosi@informatik.uni-freiburg.de>
#include <float.h>
#include <algorithm>
#include <exception>
#include <iostream>
#include <limits>
#include <map>
#include <stack>
#include <string>
#include <utility>
#include <vector>
#include "pfaedle/Def.h"
#include "pfaedle/osm/BBoxIdx.h"
#include "pfaedle/osm/Osm.h"
#include "pfaedle/osm/OsmBuilder.h"
#include "pfaedle/osm/OsmFilter.h"
#include "pfaedle/osm/Restrictor.h"
#include "pfaedle/trgraph/StatGroup.h"
#include "util/Misc.h"
#include "util/Nullable.h"
#include "util/log/Log.h"
#include "xml/pfxml.h"
using util::geo::webMercMeterDist;
using util::geo::Box;
using util::Nullable;
using pfaedle::trgraph::Normalizer;
using pfaedle::trgraph::Graph;
using pfaedle::trgraph::Node;
using pfaedle::trgraph::NodePL;
using pfaedle::trgraph::Edge;
using pfaedle::trgraph::EdgePL;
using pfaedle::trgraph::TransitEdgeLine;
using pfaedle::trgraph::StatInfo;
using pfaedle::trgraph::StatGroup;
using pfaedle::trgraph::Component;
using pfaedle::osm::OsmBuilder;
using pfaedle::osm::OsmWay;
using pfaedle::osm::OsmRel;
using pfaedle::osm::OsmNode;
using pfaedle::osm::EdgeGrid;
using pfaedle::osm::NodeGrid;
using pfaedle::osm::EqSearch;
using pfaedle::osm::BlockSearch;
using ad::cppgtfs::gtfs::Stop;
// _____________________________________________________________________________
bool EqSearch::operator()(const Node* cand, const StatInfo* si) const {
if (orphanSnap && cand->pl().getSI() &&
(!cand->pl().getSI()->getGroup() ||
cand->pl().getSI()->getGroup()->getStops().size() == 0)) {
return true;
}
return cand->pl().getSI() && cand->pl().getSI()->simi(si) > minSimi;
}
// _____________________________________________________________________________
OsmBuilder::OsmBuilder() {}
// _____________________________________________________________________________
void OsmBuilder::read(const std::string& path, const OsmReadOpts& opts,
Graph* g, const BBoxIdx& bbox, size_t gridSize,
router::FeedStops* fs, Restrictor* res) {
if (!bbox.size()) return;
LOG(INFO) << "Reading OSM file " << path << " ... ";
NodeSet orphanStations;
EdgTracks eTracks;
{
OsmIdSet bboxNodes, noHupNodes;
NIdMap nodes;
NIdMultMap multNodes;
RelLst intmRels;
RelMap nodeRels, wayRels;
Restrictions rawRests;
AttrKeySet attrKeys[3] = {};
getKeptAttrKeys(opts, attrKeys);
OsmFilter filter(opts);
pfxml::file xml(path);
// we do four passes of the file here to be as memory creedy as possible:
// - the first pass collects all node IDs which are
// * inside the given bounding box
// * (TODO: maybe more filtering?)
// these nodes are stored on the HD via OsmIdSet (which implements a
// simple bloom filter / base 256 encoded id store
// - the second pass collects filtered relations
// - the third pass collects filtered ways which contain one of the nodes
// from pass 1
// - the forth pass collects filtered nodes which were
// * collected as node ids in pass 1
// * match the filter criteria
// * have been used in a way in pass 3
LOG(VDEBUG) << "Reading bounding box nodes...";
skipUntil(&xml, "node");
pfxml::parser_state nodeBeg = xml.state();
pfxml::parser_state edgesBeg =
readBBoxNds(&xml, &bboxNodes, &noHupNodes, filter, bbox);
LOG(VDEBUG) << "Reading relations...";
skipUntil(&xml, "relation");
readRels(&xml, &intmRels, &nodeRels, &wayRels, filter, attrKeys[2],
&rawRests);
LOG(VDEBUG) << "Reading edges...";
xml.set_state(edgesBeg);
readEdges(&xml, g, intmRels, wayRels, filter, bboxNodes, &nodes, &multNodes,
noHupNodes, attrKeys[1], rawRests, res, intmRels.flat, &eTracks,
opts);
LOG(VDEBUG) << "Reading kept nodes...";
xml.set_state(nodeBeg);
readNodes(&xml, g, intmRels, nodeRels, filter, bboxNodes, &nodes,
&multNodes, &orphanStations, attrKeys[0], intmRels.flat, opts);
}
LOG(VDEBUG) << "OSM ID set lookups: " << osm::OsmIdSet::LOOKUPS
<< ", file lookups: " << osm::OsmIdSet::FLOOKUPS;
LOG(VDEBUG) << "Applying edge track numbers...";
writeEdgeTracks(eTracks);
eTracks.clear();
{
LOG(VDEBUG) << "Fixing gaps...";
NodeGrid ng = buildNodeIdx(g, gridSize, bbox.getFullWebMercBox(), false);
fixGaps(g, &ng);
}
LOG(VDEBUG) << "Writing edge geoms...";
writeGeoms(g);
LOG(VDEBUG) << "Snapping stations...";
snapStats(opts, g, bbox, gridSize, fs, res, orphanStations);
LOG(VDEBUG) << "Deleting orphan nodes...";
deleteOrphNds(g);
LOG(VDEBUG) << "Deleting orphan edges...";
deleteOrphEdgs(g, opts);
LOG(VDEBUG) << "Collapsing edges...";
collapseEdges(g);
LOG(VDEBUG) << "Deleting orphan nodes...";
deleteOrphNds(g);
LOG(VDEBUG) << "Deleting orphan edges...";
deleteOrphEdgs(g, opts);
LOG(VDEBUG) << "Writing graph components...";
// the restrictor is needed here to prevent connections in the graph
// which are not possible in reality
uint32_t comps = writeComps(g);
LOG(VDEBUG) << "Simplifying geometries...";
simplifyGeoms(g);
LOG(VDEBUG) << "Writing other-direction edges...";
writeODirEdgs(g, res);
LOG(VDEBUG) << "Write dummy node self-edges...";
writeSelfEdgs(g);
size_t numEdges = 0;
for (auto* n : *g->getNds()) {
numEdges += n->getAdjListOut().size();
}
LOG(DEBUG) << "Graph has " << g->getNds()->size() << " nodes, " << numEdges
<< " edges and " << comps << " connected component(s)";
}
// _____________________________________________________________________________
void OsmBuilder::overpassQryWrite(std::ostream* out,
const std::vector<OsmReadOpts>& opts,
const BBoxIdx& latLngBox) const {
OsmIdSet bboxNodes, noHupNodes;
MultAttrMap emptyF;
RelLst rels;
OsmIdList ways;
RelMap nodeRels, wayRels;
// TODO(patrick): not needed here!
Restrictions rests;
NIdMap nodes;
// always empty
NIdMultMap multNodes;
util::xml::XmlWriter wr(out, true, 4);
*out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
wr.openComment();
wr.writeText(" - written by pfaedle -");
wr.closeTag();
wr.openTag("osm-script");
OsmFilter filter;
for (const OsmReadOpts& o : opts) {
filter = filter.merge(OsmFilter(o.keepFilter, o.dropFilter));
}
wr.openTag("union");
size_t c = 0;
for (auto box : latLngBox.getLeafs()) {
if (box.getLowerLeft().getX() > box.getUpperRight().getX()) continue;
c++;
wr.openComment();
wr.writeText(std::string("Bounding box #") + std::to_string(c) + " (" +
std::to_string(box.getLowerLeft().getY()) + ", " +
std::to_string(box.getLowerLeft().getX()) + ", " +
std::to_string(box.getUpperRight().getY()) + ", " +
std::to_string(box.getUpperRight().getX()) + ")");
wr.closeTag();
for (auto t : std::vector<std::string>{"way", "node", "relation"}) {
for (auto r : filter.getKeepRules()) {
for (auto val : r.second) {
if (t == "way" && (val.second & OsmFilter::WAY)) continue;
if (t == "relation" && (val.second & OsmFilter::REL)) continue;
if (t == "node" && (val.second & OsmFilter::NODE)) continue;
wr.openTag("query", {{"type", t}});
if (val.first == "*")
wr.openTag("has-kv", {{"k", r.first}});
else
wr.openTag("has-kv", {{"k", r.first}, {"v", val.first}});
wr.closeTag();
wr.openTag("bbox-query",
{{"s", std::to_string(box.getLowerLeft().getY())},
{"w", std::to_string(box.getLowerLeft().getX())},
{"n", std::to_string(box.getUpperRight().getY())},
{"e", std::to_string(box.getUpperRight().getX())}});
wr.closeTag();
wr.closeTag();
}
}
}
}
wr.closeTag();
wr.openTag("union");
wr.openTag("item");
wr.closeTag();
wr.openTag("recurse", {{"type", "down"}});
wr.closeTag();
wr.closeTag();
wr.openTag("print");
wr.closeTags();
}
// _____________________________________________________________________________
void OsmBuilder::filterWrite(const std::string& in, const std::string& out,
const std::vector<OsmReadOpts>& opts,
const BBoxIdx& latLngBox) {
OsmIdSet bboxNodes, noHupNodes;
MultAttrMap emptyF;
RelLst rels;
OsmIdList ways;
RelMap nodeRels, wayRels;
// TODO(patrick): not needed here!
Restrictions rests;
NIdMap nodes;
// always empty
NIdMultMap multNodes;
pfxml::file xml(in);
std::ofstream outstr;
outstr.open(out);
util::xml::XmlWriter wr(&outstr, true, 4);
outstr << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
wr.openTag("osm");
wr.openTag(
"bounds",
{{"minlat", std::to_string(latLngBox.getFullBox().getLowerLeft().getY())},
{"minlon", std::to_string(latLngBox.getFullBox().getLowerLeft().getX())},
{"maxlat",
std::to_string(latLngBox.getFullBox().getUpperRight().getY())},
{"maxlon",
std::to_string(latLngBox.getFullBox().getUpperRight().getX())}});
wr.closeTag();
OsmFilter filter;
AttrKeySet attrKeys[3] = {};
for (const OsmReadOpts& o : opts) {
getKeptAttrKeys(o, attrKeys);
filter = filter.merge(OsmFilter(o.keepFilter, o.dropFilter));
}
skipUntil(&xml, "node");
pfxml::parser_state nodeBeg = xml.state();
pfxml::parser_state edgesBeg =
readBBoxNds(&xml, &bboxNodes, &noHupNodes, filter, latLngBox);
skipUntil(&xml, "relation");
readRels(&xml, &rels, &nodeRels, &wayRels, filter, attrKeys[2], &rests);
xml.set_state(edgesBeg);
readEdges(&xml, wayRels, filter, bboxNodes, attrKeys[1], &ways, &nodes,
rels.flat);
xml.set_state(nodeBeg);
readWriteNds(&xml, &wr, nodeRels, filter, bboxNodes, &nodes, attrKeys[0],
rels.flat);
readWriteWays(&xml, &wr, &ways, attrKeys[1]);
std::sort(ways.begin(), ways.end());
skipUntil(&xml, "relation");
readWriteRels(&xml, &wr, &ways, &nodes, filter, attrKeys[2]);
wr.closeTags();
}
// _____________________________________________________________________________
void OsmBuilder::readWriteRels(pfxml::file* i, util::xml::XmlWriter* o,
OsmIdList* ways, NIdMap* nodes,
const OsmFilter& filter,
const AttrKeySet& keepAttrs) {
OsmRel rel;
while ((rel = nextRel(i, filter, keepAttrs)).id) {
OsmIdList realNodes, realWays;
std::vector<const char *> realNodeRoles, realWayRoles;
for (size_t j = 0; j < rel.ways.size(); j++) {
osmid wid = rel.ways[j];
const auto& i = std::lower_bound(ways->begin(), ways->end(), wid);
if (i != ways->end() && *i == wid) {
realWays.push_back(wid);
realWayRoles.push_back(rel.wayRoles[j].c_str());
}
}
for (size_t j = 0; j < rel.nodes.size(); j++) {
osmid nid = rel.nodes[j];
if (nodes->count(nid)) {
realNodes.push_back(nid);
realNodeRoles.push_back(rel.nodeRoles[j].c_str());
}
}
if (realNodes.size() || realWays.size()) {
o->openTag("relation", "id", std::to_string(rel.id));
for (size_t j = 0; j < realNodes.size(); j++) {
osmid nid = realNodes[j];
std::map<std::string, std::string> attrs;
attrs["type"] = "node";
if (strlen(realNodeRoles[j])) attrs["role"] = realNodeRoles[j];
attrs["ref"] = std::to_string(nid);
o->openTag("member", attrs);
o->closeTag();
}
for (size_t j = 0; j < realWays.size(); j++) {
osmid wid = realWays[j];
std::map<std::string, std::string> attrs;
attrs["type"] = "way";
if (strlen(realWayRoles[j])) attrs["role"] = realWayRoles[j];
attrs["ref"] = std::to_string(wid);
o->openTag("member", attrs);
o->closeTag();
}
for (const auto& kv : rel.attrs) {
std::map<std::string, std::string> attrs = {
{"k", kv.first}, {"v", pfxml::file::decode(kv.second)}};
o->openTag("tag", attrs);
o->closeTag();
}
o->closeTag();
}
}
}
// _____________________________________________________________________________
void OsmBuilder::readWriteWays(pfxml::file* i, util::xml::XmlWriter* o,
OsmIdList* ways,
const AttrKeySet& keepAttrs) const {
OsmWay w;
NIdMultMap empty;
for (auto wid : *ways) {
w = nextWayWithId(i, wid, keepAttrs);
assert(w.id);
o->openTag("way", "id", std::to_string(wid));
for (osmid nid : w.nodes) {
o->openTag("nd", "ref", std::to_string(nid));
o->closeTag();
}
for (const auto& kv : w.attrs) {
std::map<std::string, std::string> attrs;
attrs["k"] = kv.first;
attrs["v"] = pfxml::file::decode(kv.second);
o->openTag("tag", attrs);
o->closeTag();
}
o->closeTag();
}
}
// _____________________________________________________________________________
NodePL OsmBuilder::plFromGtfs(const Stop* s, const OsmReadOpts& ops) {
NodePL ret(
util::geo::latLngToWebMerc<PFAEDLE_PRECISION>(s->getLat(), s->getLng()),
StatInfo(ops.statNormzer(s->getName()),
ops.trackNormzer(s->getPlatformCode()), false));
#ifdef PFAEDLE_STATION_IDS
// debug feature, store station id from GTFS
ret.getSI()->setId(s->getId());
#endif
if (s->getParentStation()) {
ret.getSI()->addAltName(ops.statNormzer(s->getParentStation()->getName()));
}
return ret;
}
// _____________________________________________________________________________
pfxml::parser_state OsmBuilder::readBBoxNds(pfxml::file* xml, OsmIdSet* nodes,
OsmIdSet* nohupNodes,
const OsmFilter& filter,
const BBoxIdx& bbox) const {
bool inNodeBlock = false;
uint64_t curId = 0;
do {
const pfxml::tag& cur = xml->get();
if (inNodeBlock && xml->level() == 3 && curId &&
strcmp(cur.name, "tag") == 0) {
if (filter.nohup(cur.attrs.find("k")->second,
cur.attrs.find("v")->second)) {
nohupNodes->add(curId);
}
}
if (xml->level() != 2) continue;
if (!inNodeBlock && strcmp(cur.name, "node") == 0) inNodeBlock = true;
if (inNodeBlock) {
// block ended
if (strcmp(cur.name, "node")) return xml->state();
double y = util::atof(cur.attrs.find("lat")->second, 7);
double x = util::atof(cur.attrs.find("lon")->second, 7);
if (bbox.contains(Point<double>(x, y))) {
curId = util::atoul(cur.attrs.find("id")->second);
nodes->add(curId);
}
}
} while (xml->next());
return xml->state();
}
// _____________________________________________________________________________
OsmWay OsmBuilder::nextWayWithId(pfxml::file* xml, osmid wid,
const AttrKeySet& keepAttrs) const {
OsmWay w;
do {
const pfxml::tag& cur = xml->get();
if (xml->level() == 2 || xml->level() == 0) {
if (w.id || strcmp(cur.name, "way")) return w;
osmid id = util::atoul(cur.attrs.find("id")->second);
if (id == wid) w.id = id;
}
if (w.id && xml->level() == 3) {
if (strcmp(cur.name, "nd") == 0) {
w.nodes.push_back(util::atoul(cur.attrs.find("ref")->second));
} else if (strcmp(cur.name, "tag") == 0) {
if (keepAttrs.count(cur.attrs.find("k")->second))
w.attrs[cur.attrs.find("k")->second] = cur.attrs.find("v")->second;
}
}
} while (xml->next());
if (w.id) return w;
return OsmWay();
}
// _____________________________________________________________________________
void OsmBuilder::skipUntil(pfxml::file* xml, const std::string& s) const {
while (xml->next() && strcmp(xml->get().name, s.c_str())) {
}
}
// _____________________________________________________________________________
bool OsmBuilder::relKeep(osmid id, const RelMap& rels,
const FlatRels& fl) const {
auto it = rels.find(id);
if (it == rels.end()) return false;
for (osmid relId : it->second) {
// as soon as any of this entities relations is not flat, return true
if (!fl.count(relId)) return true;
}
return false;
}
// _____________________________________________________________________________
OsmWay OsmBuilder::nextWay(pfxml::file* xml, const RelMap& wayRels,
const OsmFilter& filter, const OsmIdSet& bBoxNodes,
const AttrKeySet& keepAttrs,
const FlatRels& fl) const {
OsmWay w;
do {
const pfxml::tag& cur = xml->get();
if (xml->level() == 2 || xml->level() == 0) {
if (keepWay(w, wayRels, filter, bBoxNodes, fl)) return w;
if (strcmp(cur.name, "way")) return OsmWay();
w.id = util::atoul(cur.attrs.find("id")->second);
w.nodes.clear();
w.attrs.clear();
}
if (w.id && xml->level() == 3) {
if (strcmp(cur.name, "nd") == 0) {
osmid nid = util::atoul(cur.attrs.find("ref")->second);
w.nodes.push_back(nid);
} else if (strcmp(cur.name, "tag") == 0) {
if (keepAttrs.count(cur.attrs.find("k")->second))
w.attrs[cur.attrs.find("k")->second] = cur.attrs.find("v")->second;
}
}
} while (xml->next());
if (keepWay(w, wayRels, filter, bBoxNodes, fl)) return w;
return OsmWay();
}
// _____________________________________________________________________________
bool OsmBuilder::keepWay(const OsmWay& w, const RelMap& wayRels,
const OsmFilter& filter, const OsmIdSet& bBoxNodes,
const FlatRels& fl) const {
if (w.id && w.nodes.size() > 1 &&
(relKeep(w.id, wayRels, fl) || filter.keep(w.attrs, OsmFilter::WAY)) &&
!filter.drop(w.attrs, OsmFilter::WAY)) {
for (osmid nid : w.nodes) {
if (bBoxNodes.has(nid)) {
return true;
}
}
}
return false;
}
// _____________________________________________________________________________
void OsmBuilder::readEdges(pfxml::file* xml, const RelMap& wayRels,
const OsmFilter& filter, const OsmIdSet& bBoxNodes,
const AttrKeySet& keepAttrs, OsmIdList* ret,
NIdMap* nodes, const FlatRels& flat) {
OsmWay w;
while ((w = nextWay(xml, wayRels, filter, bBoxNodes, keepAttrs, flat)).id) {
ret->push_back(w.id);
for (auto n : w.nodes) {
(*nodes)[n] = 0;
}
}
}
// _____________________________________________________________________________
void OsmBuilder::readEdges(pfxml::file* xml, Graph* g, const RelLst& rels,
const RelMap& wayRels, const OsmFilter& filter,
const OsmIdSet& bBoxNodes, NIdMap* nodes,
NIdMultMap* multiNodes, const OsmIdSet& noHupNodes,
const AttrKeySet& keepAttrs,
const Restrictions& rawRests, Restrictor* restor,
const FlatRels& fl, EdgTracks* eTracks,
const OsmReadOpts& opts) {
OsmWay w;
while ((w = nextWay(xml, wayRels, filter, bBoxNodes, keepAttrs, fl)).id) {
Node* last = 0;
std::vector<TransitEdgeLine*> lines;
if (wayRels.count(w.id)) {
lines = getLines(wayRels.find(w.id)->second, rels, opts);
}
std::string track =
getAttrByFirstMatch(opts.edgePlatformRules, w.id, w.attrs, wayRels,
rels, opts.trackNormzer);
osmid lastnid = 0;
for (osmid nid : w.nodes) {
Node* n = 0;
if (noHupNodes.has(nid)) {
n = g->addNd();
(*multiNodes)[nid].insert(n);
} else if (!nodes->count(nid)) {
if (!bBoxNodes.has(nid)) continue;
n = g->addNd();
(*nodes)[nid] = n;
} else {
n = (*nodes)[nid];
}
if (last) {
auto e = g->addEdg(last, n, EdgePL());
if (!e) continue;
processRestr(nid, w.id, rawRests, e, n, restor);
processRestr(lastnid, w.id, rawRests, e, last, restor);
e->pl().addLines(lines);
e->pl().setLvl(filter.level(w.attrs));
if (!track.empty()) (*eTracks)[e] = track;
if (filter.oneway(w.attrs)) e->pl().setOneWay(1);
if (filter.onewayrev(w.attrs)) e->pl().setOneWay(2);
}
lastnid = nid;
last = n;
}
}
}
// _____________________________________________________________________________
void OsmBuilder::processRestr(osmid nid, osmid wid,
const Restrictions& rawRests, Edge* e, Node* n,
Restrictor* restor) const {
if (rawRests.pos.count(nid)) {
for (const auto& kv : rawRests.pos.find(nid)->second) {
if (kv.eFrom == wid) {
e->pl().setRestricted();
restor->add(e, kv.eTo, n, true);
} else if (kv.eTo == wid) {
e->pl().setRestricted();
restor->relax(wid, n, e);
}
}
}
if (rawRests.neg.count(nid)) {
for (const auto& kv : rawRests.neg.find(nid)->second) {
if (kv.eFrom == wid) {
e->pl().setRestricted();
restor->add(e, kv.eTo, n, false);
} else if (kv.eTo == wid) {
e->pl().setRestricted();
restor->relax(wid, n, e);
}
}
}
}
// _____________________________________________________________________________
OsmNode OsmBuilder::nextNode(pfxml::file* xml, NIdMap* nodes,
NIdMultMap* multNodes, const RelMap& nodeRels,
const OsmFilter& filter, const OsmIdSet& bBoxNodes,
const AttrKeySet& keepAttrs,
const FlatRels& fl) const {
OsmNode n;
do {
const pfxml::tag& cur = xml->get();
if (xml->level() == 2 || xml->level() == 0) {
if (keepNode(n, *nodes, *multNodes, nodeRels, bBoxNodes, filter, fl))
return n;
// block ended
if (strcmp(cur.name, "node")) return OsmNode();
n.attrs.clear();
n.lat = util::atof(cur.attrs.find("lat")->second, 7);
n.lng = util::atof(cur.attrs.find("lon")->second, 7);
n.id = util::atoul(cur.attrs.find("id")->second);
}
if (xml->level() == 3 && n.id && strcmp(cur.name, "tag") == 0) {
if (keepAttrs.count(cur.attrs.find("k")->second))
n.attrs[cur.attrs.find("k")->second] = cur.attrs.find("v")->second;
}
} while (xml->next());
if (keepNode(n, *nodes, *multNodes, nodeRels, bBoxNodes, filter, fl))
return n;
return OsmNode();
}
// _____________________________________________________________________________
bool OsmBuilder::keepNode(const OsmNode& n, const NIdMap& nodes,
const NIdMultMap& multNodes, const RelMap& nodeRels,
const OsmIdSet& bBoxNodes, const OsmFilter& filter,
const FlatRels& fl) const {
if (n.id &&
(nodes.count(n.id) || multNodes.count(n.id) ||
relKeep(n.id, nodeRels, fl) || filter.keep(n.attrs, OsmFilter::NODE)) &&
(nodes.count(n.id) || bBoxNodes.has(n.id)) &&
(nodes.count(n.id) || multNodes.count(n.id) ||
!filter.drop(n.attrs, OsmFilter::NODE))) {
return true;
}
return false;
}
// _____________________________________________________________________________
void OsmBuilder::readWriteNds(pfxml::file* i, util::xml::XmlWriter* o,
const RelMap& nRels, const OsmFilter& filter,
const OsmIdSet& bBoxNds, NIdMap* nds,
const AttrKeySet& keepAttrs,
const FlatRels& f) const {
OsmNode nd;
NIdMultMap empt;
while (
(nd = nextNode(i, nds, &empt, nRels, filter, bBoxNds, keepAttrs, f)).id) {
(*nds)[nd.id] = 0;
o->openTag("node", {{"id", std::to_string(nd.id)},
{"lat", std::to_string(nd.lat)},
{"lon", std::to_string(nd.lng)}});
for (const auto& kv : nd.attrs) {
o->openTag("tag", {{"k", kv.first}, {"v", pfxml::file::decode(kv.second)}});
o->closeTag();
}
o->closeTag();
}
}
// _____________________________________________________________________________
void OsmBuilder::readNodes(pfxml::file* xml, Graph* g, const RelLst& rels,
const RelMap& nodeRels, const OsmFilter& filter,
const OsmIdSet& bBoxNodes, NIdMap* nodes,
NIdMultMap* multNodes, NodeSet* orphanStations,
const AttrKeySet& keepAttrs, const FlatRels& fl,
const OsmReadOpts& opts) const {
StAttrGroups attrGroups;
OsmNode nd;
while ((nd = nextNode(xml, nodes, multNodes, nodeRels, filter, bBoxNodes,
keepAttrs, fl))
.id) {
Node* n = 0;
auto pos = util::geo::latLngToWebMerc<PFAEDLE_PRECISION>(nd.lat, nd.lng);
if (nodes->count(nd.id)) {
n = (*nodes)[nd.id];
n->pl().setGeom(pos);
if (filter.station(nd.attrs)) {
auto si = getStatInfo(n, nd.id, pos, nd.attrs, &attrGroups, nodeRels,
rels, opts);
if (!si.isNull()) n->pl().setSI(si);
} else if (filter.blocker(nd.attrs)) {
n->pl().setBlocker();
}
} else if ((*multNodes).count(nd.id)) {
for (auto* n : (*multNodes)[nd.id]) {
n->pl().setGeom(pos);
if (filter.station(nd.attrs)) {
auto si = getStatInfo(n, nd.id, pos, nd.attrs, &attrGroups, nodeRels,
rels, opts);
if (!si.isNull()) n->pl().setSI(si);
} else if (filter.blocker(nd.attrs)) {
n->pl().setBlocker();
}
}
} else {
// these are nodes without any connected edges
if (filter.station(nd.attrs)) {
auto tmp = g->addNd(NodePL(pos));
auto si = getStatInfo(tmp, nd.id, pos, nd.attrs, &attrGroups, nodeRels,
rels, opts);
if (!si.isNull()) tmp->pl().setSI(si);
if (tmp->pl().getSI()) {
tmp->pl().getSI()->setIsFromOsm(false);
orphanStations->insert(tmp);
}
}
}
}
}
// _____________________________________________________________________________
OsmRel OsmBuilder::nextRel(pfxml::file* xml, const OsmFilter& filter,
const AttrKeySet& keepAttrs) const {
OsmRel rel;
do {
const pfxml::tag& cur = xml->get();
if (xml->level() == 2 || xml->level() == 0) {
uint64_t keepFlags = 0;
uint64_t dropFlags = 0;
if (rel.id && rel.attrs.size() &&
(keepFlags = filter.keep(rel.attrs, OsmFilter::REL)) &&
!(dropFlags = filter.drop(rel.attrs, OsmFilter::REL))) {
rel.keepFlags = keepFlags;
rel.dropFlags = dropFlags;
return rel;
}
// block ended
if (strcmp(cur.name, "relation")) return OsmRel();
rel.attrs.clear();
rel.nodes.clear();
rel.ways.clear();
rel.nodeRoles.clear();
rel.wayRoles.clear();
rel.keepFlags = 0;
rel.dropFlags = 0;
rel.id = util::atoul(cur.attrs.find("id")->second);
}
if (xml->level() == 3 && rel.id) {
if (strcmp(cur.name, "member") == 0) {
if (strcmp(cur.attrs.find("type")->second, "node") == 0) {
osmid id = util::atoul(cur.attrs.find("ref")->second);
// TODO(patrick): no need to push IDs that have been filtered out by
// the bounding box!!!!
rel.nodes.push_back(id);
if (cur.attrs.count("role")) {
rel.nodeRoles.push_back(cur.attrs.find("role")->second);
} else {
rel.nodeRoles.push_back("");
}
}
if (strcmp(cur.attrs.find("type")->second, "way") == 0) {
osmid id = util::atoul(cur.attrs.find("ref")->second);
rel.ways.push_back(id);
if (cur.attrs.count("role")) {
rel.wayRoles.push_back(cur.attrs.find("role")->second);
} else {
rel.wayRoles.push_back("");
}
}
} else if (strcmp(cur.name, "tag") == 0) {
if (keepAttrs.count(cur.attrs.find("k")->second))
rel.attrs[cur.attrs.find("k")->second] = cur.attrs.find("v")->second;
}
}
} while (xml->next());
// dont forget last relation
uint64_t keepFlags = 0;
uint64_t dropFlags = 0;
if (rel.id && rel.attrs.size() &&
(keepFlags = filter.keep(rel.attrs, OsmFilter::REL)) &&
!(dropFlags = filter.drop(rel.attrs, OsmFilter::REL))) {
rel.keepFlags = keepFlags;
rel.dropFlags = dropFlags;
return rel;
}
return OsmRel();
}
// _____________________________________________________________________________
void OsmBuilder::readRels(pfxml::file* xml, RelLst* rels, RelMap* nodeRels,
RelMap* wayRels, const OsmFilter& filter,
const AttrKeySet& keepAttrs,
Restrictions* rests) const {
OsmRel rel;
while ((rel = nextRel(xml, filter, keepAttrs)).id) {
rels->rels.push_back(rel.attrs);
if (rel.keepFlags & osm::REL_NO_DOWN) {
rels->flat.insert(rels->rels.size() - 1);
}
for (osmid id : rel.nodes) (*nodeRels)[id].push_back(rels->rels.size() - 1);
for (osmid id : rel.ways) (*wayRels)[id].push_back(rels->rels.size() - 1);
// TODO(patrick): this is not needed for the filtering - remove it here!
readRestr(rel, rests, filter);
}
}
// _____________________________________________________________________________
void OsmBuilder::readRestr(const OsmRel& rel, Restrictions* rests,
const OsmFilter& filter) const {
if (!rel.attrs.count("type")) return;
if (rel.attrs.find("type")->second != "restriction") return;
bool pos = filter.posRestr(rel.attrs);
bool neg = filter.negRestr(rel.attrs);
if (!pos && !neg) return;
osmid from = 0;
osmid to = 0;
osmid via = 0;
for (size_t i = 0; i < rel.ways.size(); i++) {
if (rel.wayRoles[i] == "from") {
if (from) return; // only one from member supported
from = rel.ways[i];
}
if (rel.wayRoles[i] == "to") {
if (to) return; // only one to member supported
to = rel.ways[i];
}
}
for (size_t i = 0; i < rel.nodes.size(); i++) {
if (rel.nodeRoles[i] == "via") {
via = rel.nodes[i];
break;
}
}
if (from && to && via) {
if (pos)
rests->pos[via].push_back(Restriction{from, to});
else if (neg)
rests->neg[via].push_back(Restriction{from, to});
}
}
// _____________________________________________________________________________
std::string OsmBuilder::getAttrByFirstMatch(const DeepAttrLst& rule, osmid id,
const AttrMap& attrs,
const RelMap& entRels,
const RelLst& rels,
const Normalizer& norm) const {
std::string ret;
for (const auto& s : rule) {
ret = norm(pfxml::file::decode(getAttr(s, id, attrs, entRels, rels)));
if (!ret.empty()) return ret;
}
return ret;
}
// _____________________________________________________________________________
std::vector<std::string> OsmBuilder::getAttrMatchRanked(
const DeepAttrLst& rule, osmid id, const AttrMap& attrs,
const RelMap& entRels, const RelLst& rels, const Normalizer& norm) const {
std::vector<std::string> ret;
for (const auto& s : rule) {
std::string tmp =
norm(pfxml::file::decode(getAttr(s, id, attrs, entRels, rels)));
if (!tmp.empty()) ret.push_back(tmp);
}
return ret;
}
// _____________________________________________________________________________
std::string OsmBuilder::getAttr(const DeepAttrRule& s, osmid id,
const AttrMap& attrs, const RelMap& entRels,
const RelLst& rels) const {
if (s.relRule.kv.first.empty()) {
if (attrs.find(s.attr) != attrs.end()) {
return attrs.find(s.attr)->second;
}
} else {
if (entRels.count(id)) {
for (const auto& relId : entRels.find(id)->second) {
if (OsmFilter::contained(rels.rels[relId], s.relRule.kv)) {
if (rels.rels[relId].count(s.attr)) {
return rels.rels[relId].find(s.attr)->second;
}
}
}
}
}
return "";
}
// _____________________________________________________________________________
Nullable<StatInfo> OsmBuilder::getStatInfo(Node* node, osmid nid,
const POINT& pos, const AttrMap& m,
StAttrGroups* groups,
const RelMap& nodeRels,
const RelLst& rels,
const OsmReadOpts& ops) const {
std::string platform;
std::vector<std::string> names;
names = getAttrMatchRanked(ops.statAttrRules.nameRule, nid, m, nodeRels, rels,
ops.statNormzer);
platform = getAttrByFirstMatch(ops.statAttrRules.platformRule, nid, m,
nodeRels, rels, ops.trackNormzer);
if (!names.size()) return Nullable<StatInfo>();
auto ret = StatInfo(names[0], platform, true);
#ifdef PFAEDLE_STATION_IDS
ret.setId(getAttrByFirstMatch(ops.statAttrRules.idRule, nid, m, nodeRels,
rels, ops.idNormzer));
#endif
for (size_t i = 1; i < names.size(); i++) ret.addAltName(names[i]);
bool groupFound = false;
for (const auto& rule : ops.statGroupNAttrRules) {
if (groupFound) break;
std::string ruleVal = getAttr(rule.attr, nid, m, nodeRels, rels);
if (!ruleVal.empty()) {
// check if a matching group exists
for (auto* group : (*groups)[rule.attr.attr][ruleVal]) {
if (groupFound) break;
for (const auto* member : group->getNodes()) {
if (webMercMeterDist(*member->pl().getGeom(), pos) <= rule.maxDist) {
// ok, group is matching
groupFound = true;
if (node) group->addNode(node);
ret.setGroup(group);
break;
}
}
}
}
}
if (!groupFound) {
for (const auto& rule : ops.statGroupNAttrRules) {
std::string ruleVal = getAttr(rule.attr, nid, m, nodeRels, rels);
if (!ruleVal.empty()) {
// add new group
StatGroup* g = new StatGroup();
if (node) g->addNode(node);
ret.setGroup(g);
(*groups)[rule.attr.attr][ruleVal].push_back(g);
break;
}
}
}
return ret;
}
// _____________________________________________________________________________
double OsmBuilder::dist(const Node* a, const Node* b) {
return webMercMeterDist(*a->pl().getGeom(), *b->pl().getGeom());
}
// _____________________________________________________________________________
double OsmBuilder::webMercDist(const Node* a, const Node* b) {
return webMercMeterDist(*a->pl().getGeom(), *b->pl().getGeom());
}
// _____________________________________________________________________________
void OsmBuilder::writeGeoms(Graph* g) {
for (auto* n : *g->getNds()) {
for (auto* e : n->getAdjListOut()) {
e->pl().addPoint(*e->getFrom()->pl().getGeom());
e->pl().setLength(dist(e->getFrom(), e->getTo()));
e->pl().addPoint(*e->getTo()->pl().getGeom());
}
}
}
// _____________________________________________________________________________
void OsmBuilder::fixGaps(Graph* g, NodeGrid* ng) {
double METER = 1;
for (auto* n : *g->getNds()) {
if (n->getInDeg() + n->getOutDeg() == 1) {
// get all nodes in distance
std::set<Node*> ret;
double distor = util::geo::webMercDistFactor(*n->pl().getGeom());
ng->get(util::geo::pad(util::geo::getBoundingBox(*n->pl().getGeom()),
METER / distor),
&ret);
for (auto* nb : ret) {
if (nb != n && (nb->getInDeg() + nb->getOutDeg()) == 1 &&
webMercDist(nb, n) <= METER / distor) {
// special case: both node are non-stations, move
// the end point nb to n and delete nb
if (!nb->pl().getSI() && !n->pl().getSI()) {
Node* otherN;
if (nb->getOutDeg())
otherN = (*nb->getAdjListOut().begin())->getOtherNd(nb);
else
otherN = (*nb->getAdjListIn().begin())->getOtherNd(nb);
LINE l;
l.push_back(*otherN->pl().getGeom());
l.push_back(*n->pl().getGeom());
Edge* e;
if (nb->getOutDeg())
e = g->addEdg(otherN, n, (*nb->getAdjListOut().begin())->pl());
else
e = g->addEdg(otherN, n, (*nb->getAdjListIn().begin())->pl());
if (e) {
*e->pl().getGeom() = l;
g->delNd(nb);
ng->remove(nb);
}
} else {
// if one of the nodes is a station, just add an edge between them
if (nb->getOutDeg())
g->addEdg(n, nb, (*nb->getAdjListOut().begin())->pl());
else
g->addEdg(n, nb, (*nb->getAdjListIn().begin())->pl());
}
}
}
}
}
}
// _____________________________________________________________________________
EdgeGrid OsmBuilder::buildEdgeIdx(Graph* g, size_t size,
const BOX& webMercBox) {
EdgeGrid ret(size, size, webMercBox, false);
for (auto* n : *g->getNds()) {
for (auto* e : n->getAdjListOut()) {
assert(e->pl().getGeom());
ret.add(*e->pl().getGeom(), e);
}
}
return ret;
}
// _____________________________________________________________________________
NodeGrid OsmBuilder::buildNodeIdx(Graph* g, size_t size, const BOX& webMercBox,
bool which) {
NodeGrid ret(size, size, webMercBox, false);
for (auto* n : *g->getNds()) {
if (!which && n->getInDeg() + n->getOutDeg() == 1)
ret.add(*n->pl().getGeom(), n);
else if (which && n->pl().getSI())
ret.add(*n->pl().getGeom(), n);
}
return ret;
}
// _____________________________________________________________________________
Node* OsmBuilder::depthSearch(const Edge* e, const StatInfo* si, const POINT& p,
double maxD, int maxFullTurns, double minAngle,
const SearchFunc& sfunc) {
// shortcuts
double dFrom = webMercMeterDist(*e->getFrom()->pl().getGeom(), p);
double dTo = webMercMeterDist(*e->getTo()->pl().getGeom(), p);
if (dFrom > maxD && dTo > maxD) return 0;
if (dFrom <= maxD && sfunc(e->getFrom(), si)) return e->getFrom();
if (dTo <= maxD && sfunc(e->getTo(), si)) return e->getTo();
NodeCandPQ pq;
NodeSet closed;
pq.push(NodeCand{dFrom, e->getFrom(), e, 0});
if (e->getFrom() != e->getTo()) pq.push(NodeCand{dTo, e->getTo(), e, 0});
while (!pq.empty()) {
auto cur = pq.top();
pq.pop();
if (closed.count(cur.node)) continue;
closed.insert(cur.node);
for (size_t i = 0; i < cur.node->getInDeg() + cur.node->getOutDeg(); i++) {
trgraph::Node* cand;
trgraph::Edge* edg;
if (i < cur.node->getInDeg()) {
edg = cur.node->getAdjListIn()[i];
cand = edg->getFrom();
} else {
edg = cur.node->getAdjListOut()[i - cur.node->getInDeg()];
cand = edg->getTo();
}
if (cand == cur.node) continue; // dont follow self edges
int fullTurn = 0;
if (cur.fromEdge &&
cur.node->getInDeg() + cur.node->getOutDeg() >
2) { // only intersection angles
const POINT& toP = *cand->pl().getGeom();
const POINT& fromP =
*cur.fromEdge->getOtherNd(cur.node)->pl().getGeom();
const POINT& nodeP = *cur.node->pl().getGeom();
if (util::geo::innerProd(nodeP, fromP, toP) < minAngle) fullTurn = 1;
}
if ((maxFullTurns < 0 || cur.fullTurns + fullTurn <= maxFullTurns) &&
cur.dist + edg->pl().getLength() < maxD && !closed.count(cand)) {
if (sfunc(cand, si)) {
return cand;
} else {
pq.push(NodeCand{cur.dist + edg->pl().getLength(), cand, edg,
cur.fullTurns + fullTurn});
}
}
}
}
return 0;
}
// _____________________________________________________________________________
bool OsmBuilder::isBlocked(const Edge* e, const StatInfo* si, const POINT& p,
double maxD, int maxFullTurns, double minAngle) {
return depthSearch(e, si, p, maxD, maxFullTurns, minAngle, BlockSearch());
}
// _____________________________________________________________________________
Node* OsmBuilder::eqStatReach(const Edge* e, const StatInfo* si, const POINT& p,
double maxD, int maxFullTurns, double minAngle,
bool orphanSnap) {
return depthSearch(e, si, p, maxD, maxFullTurns, minAngle,
EqSearch(orphanSnap));
}
// _____________________________________________________________________________
void OsmBuilder::getEdgCands(const POINT& geom, EdgeCandPQ* ret, EdgeGrid* eg,
double d) {
double distor = util::geo::webMercDistFactor(geom);
std::set<Edge*> neighs;
BOX box = util::geo::pad(util::geo::getBoundingBox(geom), d / distor);
eg->get(box, &neighs);
for (auto* e : neighs) {
double dist = util::geo::distToSegment(*e->getFrom()->pl().getGeom(),
*e->getTo()->pl().getGeom(), geom);
if (dist * distor <= d) {
ret->push(EdgeCand(-dist, e));
}
}
}
// _____________________________________________________________________________
std::set<Node*> OsmBuilder::getMatchingNds(const NodePL& s, NodeGrid* ng,
double d) {
std::set<Node*> ret;
double distor = util::geo::webMercDistFactor(*s.getGeom());
std::set<Node*> neighs;
BOX box = util::geo::pad(util::geo::getBoundingBox(*s.getGeom()), d / distor);
ng->get(box, &neighs);
for (auto* n : neighs) {
if (n->pl().getSI() && n->pl().getSI()->simi(s.getSI()) > 0.5) {
double dist = webMercMeterDist(*n->pl().getGeom(), *s.getGeom());
if (dist < d) ret.insert(n);
}
}
return ret;
}
// _____________________________________________________________________________
Node* OsmBuilder::getMatchingNd(const NodePL& s, NodeGrid* ng, double d) {
double distor = util::geo::webMercDistFactor(*s.getGeom());
std::set<Node*> neighs;
BOX box = util::geo::pad(util::geo::getBoundingBox(*s.getGeom()), d / distor);
ng->get(box, &neighs);
Node* ret = 0;
double bestD = std::numeric_limits<double>::max();
for (auto* n : neighs) {
if (n->pl().getSI() && n->pl().getSI()->simi(s.getSI()) > 0.5) {
double dist = webMercMeterDist(*n->pl().getGeom(), *s.getGeom());
if (dist < d && dist < bestD) {
bestD = dist;
ret = n;
}
}
}
return ret;
}
// _____________________________________________________________________________
std::set<Node*> OsmBuilder::snapStation(Graph* g, NodePL* s, EdgeGrid* eg,
NodeGrid* sng, const OsmReadOpts& opts,
Restrictor* restor, bool surrHeur,
bool orphSnap, double d) {
assert(s->getSI());
std::set<Node*> ret;
EdgeCandPQ pq;
getEdgCands(*s->getGeom(), &pq, eg, d);
if (pq.empty() && surrHeur) {
// no station found in the first round, try again with the nearest
// surrounding station with matching name
const Node* best = getMatchingNd(*s, sng, opts.maxSnapFallbackHeurDistance);
if (best) {
getEdgCands(*best->pl().getGeom(), &pq, eg, d);
} else {
// if still no luck, get edge cands in fallback snap distance
getEdgCands(*s->getGeom(), &pq, eg, opts.maxSnapFallbackHeurDistance);
}
}
while (!pq.empty()) {
auto* e = pq.top().second;
pq.pop();
auto geom =
util::geo::projectOn(*e->getFrom()->pl().getGeom(), *s->getGeom(),
*e->getTo()->pl().getGeom());
Node* eq = 0;
if (!(eq = eqStatReach(e, s->getSI(), geom, 2 * d, 0,
opts.maxAngleSnapReach, orphSnap))) {
if (e->pl().lvl() > opts.maxSnapLevel) continue;
if (isBlocked(e, s->getSI(), geom, opts.maxBlockDistance, 0,
opts.maxAngleSnapReach)) {
continue;
}
// if the projected position is near (< 2 meters) the end point of this
// way and the endpoint is not already a station, place the station there.
if (!e->getFrom()->pl().getSI() &&
webMercMeterDist(geom, *e->getFrom()->pl().getGeom()) < 2) {
e->getFrom()->pl().setSI(*s->getSI());
if (s->getSI()->getGroup())
s->getSI()->getGroup()->addNode(e->getFrom());
ret.insert(e->getFrom());
} else if (!e->getTo()->pl().getSI() &&
webMercMeterDist(geom, *e->getTo()->pl().getGeom()) < 2) {
e->getTo()->pl().setSI(*s->getSI());
if (s->getSI()->getGroup()) s->getSI()->getGroup()->addNode(e->getTo());
ret.insert(e->getTo());
} else {
s->setGeom(geom);
Node* n = g->addNd(*s);
if (n->pl().getSI()->getGroup())
n->pl().getSI()->getGroup()->addNode(n);
sng->add(geom, n);
auto ne = g->addEdg(e->getFrom(), n, e->pl());
ne->pl().setLength(webMercDist(n, e->getFrom()));
LINE l;
l.push_back(*e->getFrom()->pl().getGeom());
l.push_back(*n->pl().getGeom());
*ne->pl().getGeom() = l;
eg->add(l, ne);
auto nf = g->addEdg(n, e->getTo(), e->pl());
nf->pl().setLength(webMercDist(n, e->getTo()));
LINE ll;
ll.push_back(*n->pl().getGeom());
ll.push_back(*e->getTo()->pl().getGeom());
*nf->pl().getGeom() = ll;
eg->add(ll, nf);
// replace edge in restrictor
restor->replaceEdge(e, ne, nf);
g->delEdg(e->getFrom(), e->getTo());
eg->remove(e);
ret.insert(n);
}
} else {
// if the snapped station is very near to the original OSM station
// write additional info from this snap station to the equivalent stat
if (webMercMeterDist(*s->getGeom(), *eq->pl().getGeom()) <
opts.maxOsmStationDistance) {
if (eq->pl().getSI()->getTrack().empty())
eq->pl().getSI()->setTrack(s->getSI()->getTrack());
}
ret.insert(eq);
}
}
return ret;
}
// _____________________________________________________________________________
StatGroup* OsmBuilder::groupStats(const NodeSet& s) {
if (!s.size()) return 0;
// reference group
StatGroup* ret = new StatGroup();
bool used = false;
for (auto* n : s) {
if (!n->pl().getSI()) continue;
used = true;
if (n->pl().getSI()->getGroup()) {
// this node is already in a group - merge this group with this one
ret->merge(n->pl().getSI()->getGroup());
} else {
ret->addNode(n);
n->pl().getSI()->setGroup(ret);
}
}
if (!used) {
delete ret;
return 0;
}
return ret;
}
// _____________________________________________________________________________
std::vector<TransitEdgeLine*> OsmBuilder::getLines(
const std::vector<size_t>& edgeRels, const RelLst& rels,
const OsmReadOpts& ops) {
std::vector<TransitEdgeLine*> ret;
for (size_t relId : edgeRels) {
TransitEdgeLine* elp = 0;
if (_relLines.count(relId)) {
elp = _relLines[relId];
} else {
TransitEdgeLine el;
bool found = false;
for (const auto& r : ops.relLinerules.sNameRule) {
for (const auto& relAttr : rels.rels[relId]) {
if (relAttr.first == r) {
el.shortName = ops.lineNormzer(pfxml::file::decode(relAttr.second));
if (!el.shortName.empty()) found = true;
}
}
if (found) break;
}
found = false;
for (const auto& r : ops.relLinerules.fromNameRule) {
for (const auto& relAttr : rels.rels[relId]) {
if (relAttr.first == r) {
el.fromStr = ops.statNormzer(pfxml::file::decode(relAttr.second));
if (!el.fromStr.empty()) found = true;
}
}
if (found) break;
}
found = false;
for (const auto& r : ops.relLinerules.toNameRule) {
for (const auto& relAttr : rels.rels[relId]) {
if (relAttr.first == r) {
el.toStr = ops.statNormzer(pfxml::file::decode(relAttr.second));
if (!el.toStr.empty()) found = true;
}
}
if (found) break;
}
if (!el.shortName.size() && !el.fromStr.size() && !el.toStr.size())
continue;
if (_lines.count(el)) {
elp = _lines[el];
_relLines[relId] = elp;
} else {
elp = new TransitEdgeLine(el);
_lines[el] = elp;
_relLines[relId] = elp;
}
}
ret.push_back(elp);
}
return ret;
}
// _____________________________________________________________________________
void OsmBuilder::getKeptAttrKeys(const OsmReadOpts& opts,
AttrKeySet sets[3]) const {
for (const auto& i : opts.statGroupNAttrRules) {
if (i.attr.relRule.kv.first.empty()) {
sets[0].insert(i.attr.attr);
} else {
sets[2].insert(i.attr.relRule.kv.first);
sets[2].insert(i.attr.attr);
}
}
for (const auto& i : opts.keepFilter) {
for (size_t j = 0; j < 3; j++) sets[j].insert(i.first);
}
for (const auto& i : opts.dropFilter) {
for (size_t j = 0; j < 3; j++) sets[j].insert(i.first);
}
for (const auto& i : opts.noHupFilter) {
sets[0].insert(i.first);
}
for (const auto& i : opts.oneWayFilter) {
sets[1].insert(i.first);
}
for (const auto& i : opts.oneWayFilterRev) {
sets[1].insert(i.first);
}
for (const auto& i : opts.twoWayFilter) {
sets[1].insert(i.first);
}
for (const auto& i : opts.stationFilter) {
sets[0].insert(i.first);
sets[2].insert(i.first);
}
for (const auto& i : opts.stationBlockerFilter) {
sets[0].insert(i.first);
}
for (uint8_t j = 0; j < 7; j++) {
for (const auto& kv : *(opts.levelFilters + j)) {
sets[1].insert(kv.first);
}
}
// restriction system
for (const auto& i : opts.restrPosRestr) {
sets[2].insert(i.first);
}
for (const auto& i : opts.restrNegRestr) {
sets[2].insert(i.first);
}
for (const auto& i : opts.noRestrFilter) {
sets[2].insert(i.first);
}
sets[2].insert("from");
sets[2].insert("via");
sets[2].insert("to");
sets[2].insert(opts.relLinerules.toNameRule.begin(),
opts.relLinerules.toNameRule.end());
sets[2].insert(opts.relLinerules.fromNameRule.begin(),
opts.relLinerules.fromNameRule.end());
sets[2].insert(opts.relLinerules.sNameRule.begin(),
opts.relLinerules.sNameRule.end());
for (const auto& i : opts.statAttrRules.nameRule) {
if (i.relRule.kv.first.empty()) {
sets[0].insert(i.attr);
} else {
sets[2].insert(i.relRule.kv.first);
sets[2].insert(i.attr);
}
}
for (const auto& i : opts.edgePlatformRules) {
if (i.relRule.kv.first.empty()) {
sets[1].insert(i.attr);
} else {
sets[2].insert(i.relRule.kv.first);
sets[2].insert(i.attr);
}
}
for (const auto& i : opts.statAttrRules.platformRule) {
if (i.relRule.kv.first.empty()) {
sets[0].insert(i.attr);
} else {
sets[2].insert(i.relRule.kv.first);
sets[2].insert(i.attr);
}
}
for (const auto& i : opts.statAttrRules.idRule) {
if (i.relRule.kv.first.empty()) {
sets[0].insert(i.attr);
} else {
sets[2].insert(i.relRule.kv.first);
sets[2].insert(i.attr);
}
}
}
// _____________________________________________________________________________
void OsmBuilder::deleteOrphEdgs(Graph* g, const OsmReadOpts& opts) {
size_t ROUNDS = 3;
for (size_t c = 0; c < ROUNDS; c++) {
for (auto i = g->getNds()->begin(); i != g->getNds()->end();) {
if ((*i)->getInDeg() + (*i)->getOutDeg() != 1 || (*i)->pl().getSI()) {
++i;
continue;
}
// check if the removal of this edge would transform a steep angle
// full turn at an intersection into a node 2 eligible for contraction
// if so, dont delete
if (keepFullTurn(*i, opts.fullTurnAngle)) {
++i;
continue;
}
i = g->delNd(*i);
continue;
i++;
}
}
}
// _____________________________________________________________________________
void OsmBuilder::deleteOrphNds(Graph* g) {
for (auto i = g->getNds()->begin(); i != g->getNds()->end();) {
if ((*i)->getInDeg() + (*i)->getOutDeg() == 0 &&
!((*i)->pl().getSI() && (*i)->pl().getSI()->getGroup())) {
i = g->delNd(i);
// TODO(patrick): maybe delete from node grid?
} else {
i++;
}
}
}
// _____________________________________________________________________________
bool OsmBuilder::edgesSim(const Edge* a, const Edge* b) {
if (static_cast<bool>(a->pl().oneWay()) ^ static_cast<bool>(b->pl().oneWay()))
return false;
if (a->pl().lvl() != b->pl().lvl()) return false;
if (a->pl().getLines().size() != b->pl().getLines().size()) return false;
if (a->pl().getLines() != b->pl().getLines()) return false;
if (a->pl().oneWay() && b->pl().oneWay()) {
if (a->getFrom() != b->getTo() && a->getTo() != b->getFrom()) return false;
}
if (a->pl().isRestricted() || b->pl().isRestricted()) return false;
return true;
}
// _____________________________________________________________________________
const EdgePL& OsmBuilder::mergeEdgePL(Edge* a, Edge* b) {
const Node* n = 0;
if (a->getFrom() == b->getFrom())
n = a->getFrom();
else if (a->getFrom() == b->getTo())
n = a->getFrom();
else
n = a->getTo();
if (a->getTo() == n && b->getTo() == n) {
// --> n <--
a->pl().getGeom()->insert(a->pl().getGeom()->end(),
b->pl().getGeom()->rbegin(),
b->pl().getGeom()->rend());
} else if (a->getTo() == n && b->getFrom() == n) {
// --> n -->
a->pl().getGeom()->insert(a->pl().getGeom()->end(),
b->pl().getGeom()->begin(),
b->pl().getGeom()->end());
} else if (a->getFrom() == n && b->getTo() == n) {
// <-- n <--
std::reverse(a->pl().getGeom()->begin(), a->pl().getGeom()->end());
a->pl().getGeom()->insert(a->pl().getGeom()->end(),
b->pl().getGeom()->rbegin(),
b->pl().getGeom()->rend());
} else {
// <-- n -->
std::reverse(a->pl().getGeom()->begin(), a->pl().getGeom()->end());
a->pl().getGeom()->insert(a->pl().getGeom()->end(),
b->pl().getGeom()->begin(),
b->pl().getGeom()->end());
}
a->pl().setLength(a->pl().getLength() + b->pl().getLength());
return a->pl();
}
// _____________________________________________________________________________
void OsmBuilder::collapseEdges(Graph* g) {
for (auto* n : *g->getNds()) {
if (n->getOutDeg() + n->getInDeg() != 2 || n->pl().getSI()) continue;
Edge* ea;
Edge* eb;
if (n->getOutDeg() == 2) {
ea = *n->getAdjListOut().begin();
eb = *n->getAdjListOut().rbegin();
} else if (n->getInDeg() == 2) {
ea = *n->getAdjListIn().begin();
eb = *n->getAdjListIn().rbegin();
} else {
ea = *n->getAdjListOut().begin();
eb = *n->getAdjListIn().begin();
}
// important, we don't have a multigraph! if the same edge
// will already exist, leave this node
if (g->getEdg(ea->getOtherNd(n), eb->getOtherNd(n))) continue;
if (g->getEdg(eb->getOtherNd(n), ea->getOtherNd(n))) continue;
if (edgesSim(ea, eb)) {
if (ea->pl().oneWay() && ea->getOtherNd(n) != ea->getFrom()) {
g->addEdg(eb->getOtherNd(n), ea->getOtherNd(n), mergeEdgePL(eb, ea));
} else {
g->addEdg(ea->getOtherNd(n), eb->getOtherNd(n), mergeEdgePL(ea, eb));
}
g->delEdg(ea->getFrom(), ea->getTo());
g->delEdg(eb->getFrom(), eb->getTo());
}
}
}
// _____________________________________________________________________________
void OsmBuilder::simplifyGeoms(Graph* g) {
for (auto* n : *g->getNds()) {
for (auto* e : n->getAdjListOut()) {
(*e->pl().getGeom()) = util::geo::simplify(*e->pl().getGeom(), 0.5);
}
}
}
// _____________________________________________________________________________
uint32_t OsmBuilder::writeComps(Graph* g) {
Component* comp = new Component{7};
uint32_t numC = 0;
for (auto* n : *g->getNds()) {
if (!n->pl().getComp()) {
std::stack<std::pair<Node*, Edge*>> q;
q.push(std::pair<Node*, Edge*>(n, 0));
while (!q.empty()) {
std::pair<Node*, Edge*> cur = q.top();
q.pop();
cur.first->pl().setComp(comp);
for (auto* e : cur.first->getAdjListOut()) {
if (e->pl().lvl() < comp->minEdgeLvl)
comp->minEdgeLvl = e->pl().lvl();
if (!e->getOtherNd(cur.first)->pl().getComp())
q.push(std::pair<Node*, Edge*>(e->getOtherNd(cur.first), e));
}
for (auto* e : cur.first->getAdjListIn()) {
if (e->pl().lvl() < comp->minEdgeLvl)
comp->minEdgeLvl = e->pl().lvl();
if (!e->getOtherNd(cur.first)->pl().getComp())
q.push(std::pair<Node*, Edge*>(e->getOtherNd(cur.first), e));
}
}
numC++;
comp = new Component{7};
}
}
// the last comp was not used
delete comp;
return numC;
}
// _____________________________________________________________________________
void OsmBuilder::writeEdgeTracks(const EdgTracks& tracks) {
for (const auto& tr : tracks) {
if (tr.first->getTo()->pl().getSI() &&
tr.first->getTo()->pl().getSI()->getTrack().empty()) {
tr.first->getTo()->pl().getSI()->setTrack(tr.second);
}
if (tr.first->getFrom()->pl().getSI() &&
tr.first->getFrom()->pl().getSI()->getTrack().empty()) {
tr.first->getFrom()->pl().getSI()->setTrack(tr.second);
}
}
}
// _____________________________________________________________________________
void OsmBuilder::writeODirEdgs(Graph* g, Restrictor* restor) {
for (auto* n : *g->getNds()) {
for (auto* e : n->getAdjListOut()) {
if (g->getEdg(e->getTo(), e->getFrom())) continue;
auto newE = g->addEdg(e->getTo(), e->getFrom(), e->pl().revCopy());
if (e->pl().isRestricted()) restor->duplicateEdge(e, newE);
}
}
}
// _____________________________________________________________________________
void OsmBuilder::writeSelfEdgs(Graph* g) {
for (auto* n : *g->getNds()) {
if (n->pl().getSI() && n->getAdjListOut().size() == 0) {
g->addEdg(n, n);
}
}
}
// _____________________________________________________________________________
bool OsmBuilder::keepFullTurn(const trgraph::Node* n, double ang) {
if (n->getInDeg() + n->getOutDeg() != 1) return false;
const trgraph::Edge* e = 0;
if (n->getOutDeg())
e = n->getAdjListOut().front();
else
e = n->getAdjListIn().front();
auto other = e->getOtherNd(n);
if (other->getInDeg() + other->getOutDeg() == 3) {
const trgraph::Edge* a = 0;
const trgraph::Edge* b = 0;
for (auto f : other->getAdjListIn()) {
if (f != e && !a)
a = f;
else if (f != e && !b)
b = f;
}
for (auto f : other->getAdjListOut()) {
if (f != e && !a)
a = f;
else if (f != e && !b)
b = f;
}
auto ap = a->pl().backHop();
auto bp = b->pl().backHop();
if (a->getTo() != other) ap = a->pl().frontHop();
if (b->getTo() != other) bp = b->pl().frontHop();
return router::angSmaller(ap, *other->pl().getGeom(), bp, ang);
}
return false;
}
// _____________________________________________________________________________
void OsmBuilder::snapStats(const OsmReadOpts& opts, Graph* g,
const BBoxIdx& bbox, size_t gridSize,
router::FeedStops* fs, Restrictor* res,
const NodeSet& orphanStations) {
NodeGrid sng = buildNodeIdx(g, gridSize, bbox.getFullWebMercBox(), true);
EdgeGrid eg = buildEdgeIdx(g, gridSize, bbox.getFullWebMercBox());
LOG(DEBUG) << "Grid size of " << sng.getXWidth() << "x" << sng.getYHeight();
for (double d : opts.maxSnapDistances) {
for (auto s : orphanStations) {
POINT geom = *s->pl().getGeom();
NodePL pl = s->pl();
pl.getSI()->setIsFromOsm(false);
const auto& r =
snapStation(g, &pl, &eg, &sng, opts, res, false, false, d);
groupStats(r);
for (auto n : r) {
// if the snapped station is very near to the original OSM
// station, set is-from-osm to true
if (webMercMeterDist(geom, *n->pl().getGeom()) <
opts.maxOsmStationDistance) {
if (n->pl().getSI()) n->pl().getSI()->setIsFromOsm(true);
}
}
}
}
if (!fs) return;
std::vector<const Stop*> notSnapped;
for (auto& s : *fs) {
bool snapped = false;
auto pl = plFromGtfs(s.first, opts);
for (size_t i = 0; i < opts.maxSnapDistances.size(); i++) {
double d = opts.maxSnapDistances[i];
StatGroup* group = groupStats(
snapStation(g, &pl, &eg, &sng, opts, res,
i == opts.maxSnapDistances.size() - 1, false, d));
if (group) {
group->addStop(s.first);
(*fs)[s.first] = *group->getNodes().begin();
snapped = true;
}
}
if (!snapped) {
LOG(VDEBUG) << "Could not snap station "
<< "(" << pl.getSI()->getName() << ")"
<< " (" << s.first->getLat() << "," << s.first->getLng()
<< ") in normal run, trying again later in orphan mode.";
if (!bbox.contains(*pl.getGeom())) {
LOG(VDEBUG) << "Note: '" << pl.getSI()->getName()
<< "' does not lie within the bounds for this graph and "
"may be a stray station";
}
notSnapped.push_back(s.first);
}
}
if (notSnapped.size())
LOG(VDEBUG) << notSnapped.size() << " stations could not be snapped in "
"normal run, trying again in orphan "
"mode.";
// try again, but aggressively snap to orphan OSM stations which have
// not been assigned to any GTFS stop yet
for (auto& s : notSnapped) {
bool snapped = false;
auto pl = plFromGtfs(s, opts);
for (size_t i = 0; i < opts.maxSnapDistances.size(); i++) {
double d = opts.maxSnapDistances[i];
StatGroup* group = groupStats(
snapStation(g, &pl, &eg, &sng, opts, res,
i == opts.maxSnapDistances.size() - 1, true, d));
if (group) {
group->addStop(s);
// add the added station name as an alt name to ensure future
// similarity
for (auto n : group->getNodes()) {
if (n->pl().getSI())
n->pl().getSI()->addAltName(pl.getSI()->getName());
}
(*fs)[s] = *group->getNodes().begin();
snapped = true;
}
}
if (!snapped) {
// finally give up
// add a group with only this stop in it
StatGroup* dummyGroup = new StatGroup();
Node* dummyNode = g->addNd(pl);
dummyNode->pl().getSI()->setGroup(dummyGroup);
dummyGroup->addNode(dummyNode);
dummyGroup->addStop(s);
(*fs)[s] = dummyNode;
if (!bbox.contains(*pl.getGeom())) {
LOG(VDEBUG) << "Could not snap station "
<< "(" << pl.getSI()->getName() << ")"
<< " (" << s->getLat() << "," << s->getLng() << ")";
LOG(VDEBUG) << "Note: '" << pl.getSI()->getName()
<< "' does not lie within the bounds for this graph and "
"may be a stray station";
} else {
// only warn if it is contained in the BBOX for this graph
LOG(WARN) << "Could not snap station "
<< "(" << pl.getSI()->getName() << ")"
<< " (" << s->getLat() << "," << s->getLng() << ")";
}
}
}
}