#include <config.h>
#include <string>
#include <map>
#include <fstream>
#include <utils/common/UtilExceptions.h>
#include <utils/common/MsgHandler.h>
#include <utils/common/ToString.h>
#include <utils/common/StringUtils.h>
#include <utils/common/StdDefs.h>
#include <utils/common/SysUtils.h>
#include <utils/common/RGBColor.h>
#include <utils/geom/GeomHelper.h>
#include <utils/geom/Position.h>
#include <utils/geom/GeoConvHelper.h>
#include <utils/xml/XMLSubSys.h>
#include <utils/geom/GeomConvHelper.h>
#include <utils/common/FileHelpers.h>
#include <utils/options/OptionsCont.h>
#include <utils/options/Option.h>
#include <polyconvert/PCPolyContainer.h>
#include "PCLoaderOSM.h"
const std::set<std::string> PCLoaderOSM::MyKeysToInclude(PCLoaderOSM::initMyKeysToInclude());
std::set<std::string> PCLoaderOSM::initMyKeysToInclude() {
std::set<std::string> result;
result.insert("highway");
result.insert("railway");
result.insert("railway:position");
result.insert("railway:position:exact");
result.insert("waterway");
result.insert("aeroway");
result.insert("aerialway");
result.insert("power");
result.insert("man_made");
result.insert("building");
result.insert("leisure");
result.insert("amenity");
result.insert("shop");
result.insert("tourism");
result.insert("historic");
result.insert("landuse");
result.insert("natural");
result.insert("military");
result.insert("boundary");
result.insert("admin_level");
result.insert("sport");
result.insert("polygon");
result.insert("place");
result.insert("population");
result.insert("barrier");
result.insert("openGeoDB:population");
result.insert("openGeoDB:name");
return result;
}
void
PCLoaderOSM::loadIfSet(OptionsCont& oc, PCPolyContainer& toFill, PCTypeMap& tm) {
if (!oc.isSet("osm-files")) {
return;
}
std::vector<std::string> files = oc.getStringVector("osm-files");
std::map<long long int, PCOSMNode*> nodes;
bool withAttributes = oc.getBool("all-attributes");
MsgHandler* m = OptionsCont::getOptions().getBool("ignore-errors") ? MsgHandler::getWarningInstance() : MsgHandler::getErrorInstance();
NodesHandler nodesHandler(nodes, withAttributes, *m);
for (std::vector<std::string>::const_iterator file = files.begin(); file != files.end(); ++file) {
if (!FileHelpers::isReadable(*file)) {
WRITE_ERRORF(TL("Could not open osm-file '%'."), *file);
return;
}
const long before = PROGRESS_BEGIN_TIME_MESSAGE("Parsing nodes from osm-file '" + *file + "'");
if (!XMLSubSys::runParser(nodesHandler, *file)) {
for (std::map<long long int, PCOSMNode*>::const_iterator i = nodes.begin(); i != nodes.end(); ++i) {
delete (*i).second;
}
throw ProcessError();
}
PROGRESS_TIME_MESSAGE(before);
}
Relations relations;
RelationsMap additionalWays;
std::set<long long int> innerEdges;
RelationsHandler relationsHandler(additionalWays, relations, innerEdges, withAttributes, *m);
for (std::vector<std::string>::const_iterator file = files.begin(); file != files.end(); ++file) {
const long before = PROGRESS_BEGIN_TIME_MESSAGE("Parsing relations from osm-file '" + *file + "'");
XMLSubSys::runParser(relationsHandler, *file);
PROGRESS_TIME_MESSAGE(before);
}
EdgeMap edges;
EdgesHandler edgesHandler(nodes, edges, additionalWays, withAttributes, *m);
for (std::vector<std::string>::const_iterator file = files.begin(); file != files.end(); ++file) {
const long before = PROGRESS_BEGIN_TIME_MESSAGE("Parsing edges from osm-file '" + *file + "'");
XMLSubSys::runParser(edgesHandler, *file);
PROGRESS_TIME_MESSAGE(before);
}
const bool useName = oc.getBool("osm.use-name");
const double mergeRelationsThreshold = OptionsCont::getOptions().getFloat("osm.merge-relations");
if (mergeRelationsThreshold >= 0) {
for (PCOSMRelation* rel : relations) {
if (!rel->keep || rel->myWays.empty()) {
continue;
}
int numNodes = 0;
for (auto it = rel->myWays.begin(); it != rel->myWays.end();) {
if (edges.count(*it) == 0 || edges[*it]->myCurrentNodes.empty()) {
it = rel->myWays.erase(it);
} else if (innerEdges.count(*it) > 0) {
edges[*it]->standalone = true;
it = rel->myWays.erase(it);
} else {
numNodes += (int)edges[*it]->myCurrentNodes.size();
it++;
}
}
if (numNodes == 0) {
WRITE_WARNINGF(TL("Could not import polygon from relation '%' (missing ways)"), rel->id);
continue;
}
PCOSMEdge* e = new PCOSMEdge();
e->id = rel->id;
e->name = rel->name;
e->myAttributes = rel->myAttributes;
e->myIsClosed = false;
e->standalone = true;
std::vector<std::vector<long long int> > snippets;
for (const long long int wayID : rel->myWays) {
PCOSMEdge* edge = edges[wayID];
snippets.push_back(edge->myCurrentNodes);
}
double maxDist = 0.;
bool ok = true;
while (snippets.size() > 1) {
maxDist = MAX2(maxDist, mergeClosest(nodes, snippets));
if (maxDist > mergeRelationsThreshold) {
ok = false;
break;
}
}
if (ok) {
e->myCurrentNodes = snippets.front();
edges[e->id] = e;
double frontBackDist = 0;
if (e->myCurrentNodes.front() != e->myCurrentNodes.back()) {
const Position posFront = convertNodePosition(nodes[e->myCurrentNodes.front()]);
const Position posBack = convertNodePosition(nodes[e->myCurrentNodes.back()]);
frontBackDist = posFront.distanceTo2D(posBack);
if (frontBackDist < mergeRelationsThreshold) {
e->myCurrentNodes.push_back(e->myCurrentNodes.front());
frontBackDist = 0;
}
}
std::string frontBackMsg = "";
if (frontBackDist > 0) {
frontBackMsg = TLF(", (front-to-back dist: %)", frontBackDist);
}
WRITE_MESSAGEF(TL("Assembled polygon from relation '%' (name:%)%"), toString(rel->id), e->name, frontBackMsg);
} else {
WRITE_WARNINGF(TL("Could not import polygon from relation '%' (name:% reason: found gap of %m)."), rel->id, rel->name, maxDist)
delete e;
for (long long int wayID : rel->myWays) {
PCOSMEdge* part = edges[wayID];
part->standalone = true;
}
}
}
}
for (EdgeMap::iterator i = edges.begin(); i != edges.end(); ++i) {
PCOSMEdge* e = (*i).second;
if (e->myAttributes.size() == 0) {
continue;
}
if (!e->standalone && mergeRelationsThreshold >= 0) {
continue;
}
if (e->myCurrentNodes.size() == 0) {
WRITE_WARNINGF(TL("Polygon '%' has no shape."), toString(e->id));
continue;
}
PositionVector vec;
for (std::vector<long long int>::iterator j = e->myCurrentNodes.begin(); j != e->myCurrentNodes.end(); ++j) {
PCOSMNode* n = nodes.find(*j)->second;
Position pos(n->lon, n->lat);
if (!GeoConvHelper::getProcessing().x2cartesian(pos)) {
WRITE_WARNINGF(TL("Unable to project coordinates for polygon '%'."), e->id);
}
vec.push_back_noDoublePos(pos);
}
const bool ignorePruning = OptionsCont::getOptions().isInStringVector("prune.keep-list", toString(e->id));
int index = 0;
std::string unknownPolyType = "";
bool isInner = mergeRelationsThreshold >= 0 && innerEdges.count(e->id) != 0 && tm.has("inner");
for (std::map<std::string, std::string>::iterator it = e->myAttributes.begin(); it != e->myAttributes.end(); ++it) {
const std::string& key = it->first;
const std::string& value = it->second;
const std::string fullType = key + "." + value;
if (tm.has(key + "." + value)) {
auto def = tm.get(isInner ? "inner" : fullType);
index = addPolygon(e, vec, def, fullType, index, useName, toFill, ignorePruning, withAttributes);
} else if (tm.has(key)) {
auto def = tm.get(isInner ? "inner" : key);
index = addPolygon(e, vec, def, fullType, index, useName, toFill, ignorePruning, withAttributes);
} else if (MyKeysToInclude.count(key) > 0) {
unknownPolyType = fullType;
}
}
const PCTypeMap::TypeDef& def = tm.getDefault();
if (index == 0 && !def.discard && unknownPolyType != "") {
addPolygon(e, vec, def, unknownPolyType, index, useName, toFill, ignorePruning, withAttributes);
}
}
for (std::map<long long int, PCOSMNode*>::iterator i = nodes.begin(); i != nodes.end(); ++i) {
PCOSMNode* n = (*i).second;
if (n->myAttributes.size() == 0) {
continue;
}
Position pos(n->lon, n->lat);
if (!GeoConvHelper::getProcessing().x2cartesian(pos)) {
WRITE_WARNINGF(TL("Unable to project coordinates for POI '%'."), n->id);
}
const bool ignorePruning = OptionsCont::getOptions().isInStringVector("prune.keep-list", toString(n->id));
int index = 0;
std::string unKnownPOIType = "";
for (std::map<std::string, std::string>::iterator it = n->myAttributes.begin(); it != n->myAttributes.end(); ++it) {
const std::string& key = it->first;
const std::string& value = it->second;
const std::string fullType = key + "." + value;
if (tm.has(key + "." + value)) {
index = addPOI(n, pos, tm.get(fullType), fullType, index, useName, toFill, ignorePruning, withAttributes);
} else if (tm.has(key)) {
index = addPOI(n, pos, tm.get(key), fullType, index, useName, toFill, ignorePruning, withAttributes);
} else if (MyKeysToInclude.count(key) > 0) {
unKnownPOIType = fullType;
}
}
const PCTypeMap::TypeDef& def = tm.getDefault();
if (index == 0 && !def.discard && unKnownPOIType != "") {
addPOI(n, pos, def, unKnownPOIType, index, useName, toFill, ignorePruning, withAttributes);
}
}
for (std::map<long long int, PCOSMNode*>::const_iterator i = nodes.begin(); i != nodes.end(); ++i) {
delete (*i).second;
}
for (EdgeMap::iterator i = edges.begin(); i != edges.end(); ++i) {
delete (*i).second;
}
for (Relations::iterator i = relations.begin(); i != relations.end(); ++i) {
delete (*i);
}
}
Position
PCLoaderOSM::convertNodePosition(PCOSMNode* n) {
Position pos(n->lon, n->lat);
GeoConvHelper::getProcessing().x2cartesian(pos);
return pos;
}
int
PCLoaderOSM::addPolygon(const PCOSMEdge* edge, const PositionVector& vec, const PCTypeMap::TypeDef& def, const std::string& fullType, int index, bool useName, PCPolyContainer& toFill, bool ignorePruning, bool withAttributes) {
if (def.discard) {
return index;
} else {
const bool closedShape = vec.front() == vec.back();
const std::string idSuffix = (index == 0 ? "" : "#" + toString(index));
const std::string id = def.prefix + (useName && edge->name != "" ? edge->name : toString(edge->id)) + idSuffix;
bool fill = def.allowFill == PCTypeMap::Filltype::FORCE || (def.allowFill == PCTypeMap::Filltype::FILL && closedShape);
SUMOPolygon* poly = new SUMOPolygon(
StringUtils::escapeXML(id),
StringUtils::escapeXML(OptionsCont::getOptions().getBool("osm.keep-full-type") ? fullType : def.id),
def.color, vec, false, fill, 1, def.layer);
if (withAttributes) {
poly->updateParameters(edge->myAttributes);
}
if (!toFill.add(poly, ignorePruning)) {
return index;
} else {
return index + 1;
}
}
}
int
PCLoaderOSM::addPOI(const PCOSMNode* node, const Position& pos, const PCTypeMap::TypeDef& def, const std::string& fullType,
int index, bool useName, PCPolyContainer& toFill, bool ignorePruning, bool withAttributes) {
if (def.discard) {
return index;
} else {
const std::string idSuffix = (index == 0 ? "" : "#" + toString(index));
const std::string id = def.prefix + (useName && node->name != "" ? node->name : toString(node->id)) + idSuffix;
PointOfInterest* poi = new PointOfInterest(
StringUtils::escapeXML(id),
StringUtils::escapeXML(OptionsCont::getOptions().getBool("osm.keep-full-type") ? fullType : def.id),
def.color, pos, false, "", 0, false, 0, def.icon, def.layer);
if (withAttributes) {
poi->updateParameters(node->myAttributes);
}
if (!toFill.add(poi, ignorePruning)) {
return index;
} else {
return index + 1;
}
}
}
PCLoaderOSM::NodesHandler::NodesHandler(std::map<long long int, PCOSMNode*>& toFill,
bool withAttributes, MsgHandler& errorHandler) :
SUMOSAXHandler("osm - file"), myWithAttributes(withAttributes), myErrorHandler(errorHandler),
myToFill(toFill), myLastNodeID(-1) {}
PCLoaderOSM::NodesHandler::~NodesHandler() {}
void
PCLoaderOSM::NodesHandler::myStartElement(int element, const SUMOSAXAttributes& attrs) {
myParentElements.push_back(element);
if (element == SUMO_TAG_NODE) {
bool ok = true;
long long int id = attrs.get<long long int>(SUMO_ATTR_ID, nullptr, ok);
if (!ok) {
return;
}
myLastNodeID = -1;
if (myToFill.find(id) == myToFill.end()) {
myLastNodeID = id;
PCOSMNode* toAdd = new PCOSMNode();
toAdd->id = id;
toAdd->lon = attrs.get<double>(SUMO_ATTR_LON, toString(id).c_str(), ok);
toAdd->lat = attrs.get<double>(SUMO_ATTR_LAT, toString(id).c_str(), ok);
if (!ok) {
delete toAdd;
return;
}
myToFill[toAdd->id] = toAdd;
}
}
if (element == SUMO_TAG_TAG && myParentElements.size() > 2 && myParentElements[myParentElements.size() - 2] == SUMO_TAG_NODE
&& myLastNodeID != -1) {
bool ok = true;
std::string key = attrs.getOpt<std::string>(SUMO_ATTR_K, toString(myLastNodeID).c_str(), ok, "", false);
std::string value = attrs.getOpt<std::string>(SUMO_ATTR_V, toString(myLastNodeID).c_str(), ok, "", false);
if (key == "name") {
myToFill[myLastNodeID]->name = value;
} else if (key == "") {
myErrorHandler.inform("Empty key in a a tag while parsing node '" + toString(myLastNodeID) + "' occurred.");
ok = false;
}
if (!ok) {
return;
}
myToFill[myLastNodeID]->myAttributes[key] = value;
}
}
void
PCLoaderOSM::NodesHandler::myEndElement(int element) {
if (element == SUMO_TAG_NODE) {
myLastNodeID = -1;
}
myParentElements.pop_back();
}
PCLoaderOSM::RelationsHandler::RelationsHandler(RelationsMap& additionalWays,
Relations& relations,
std::set<long long int>& innerEdges,
bool withAttributes,
MsgHandler& errorHandler) :
SUMOSAXHandler("osm - file"),
myAdditionalWays(additionalWays),
myRelations(relations),
myInnerEdges(innerEdges),
myWithAttributes(withAttributes),
myErrorHandler(errorHandler),
myCurrentRelation(nullptr) {
}
PCLoaderOSM::RelationsHandler::~RelationsHandler() {
}
void
PCLoaderOSM::RelationsHandler::myStartElement(int element, const SUMOSAXAttributes& attrs) {
myParentElements.push_back(element);
if (element == SUMO_TAG_RELATION) {
myCurrentWays.clear();
bool ok = true;
const std::string& action = attrs.getOpt<std::string>(SUMO_ATTR_ACTION, nullptr, ok);
if (action == "delete" || !ok) {
myCurrentRelation = nullptr;
} else {
myCurrentRelation = new PCOSMRelation();
myCurrentRelation->keep = false;
myCurrentRelation->id = attrs.get<long long int>(SUMO_ATTR_ID, nullptr, ok);
myRelations.push_back(myCurrentRelation);
}
return;
} else if (myCurrentRelation == nullptr) {
return;
}
if (element == SUMO_TAG_MEMBER) {
bool ok = true;
std::string role = attrs.hasAttribute("role") ? attrs.getStringSecure("role", "") : "";
long long int ref = attrs.get<long long int>(SUMO_ATTR_REF, nullptr, ok);
if (role == "outer" || role == "inner") {
std::string memberType = attrs.get<std::string>(SUMO_ATTR_TYPE, nullptr, ok);
if (memberType == "way") {
myCurrentWays.push_back(ref);
if (role == "inner") {
myInnerEdges.insert(ref);
}
}
}
return;
}
if (element == SUMO_TAG_TAG && myParentElements.size() > 2 && myParentElements[myParentElements.size() - 2] == SUMO_TAG_RELATION
&& myCurrentRelation != nullptr) {
bool ok = true;
std::string key = attrs.getOpt<std::string>(SUMO_ATTR_K, toString(myCurrentRelation).c_str(), ok, "", false);
std::string value = attrs.getOpt<std::string>(SUMO_ATTR_V, toString(myCurrentRelation).c_str(), ok, "", false);
if (key == "") {
myErrorHandler.inform("Empty key in a a tag while parsing way '" + toString(myCurrentRelation) + "' occurred.");
ok = false;
}
if (!ok) {
return;
}
if (key == "name") {
myCurrentRelation->name = value;
} else if (MyKeysToInclude.count(key) > 0) {
myCurrentRelation->keep = true;
for (std::vector<long long int>::iterator it = myCurrentWays.begin(); it != myCurrentWays.end(); ++it) {
myAdditionalWays[*it] = myCurrentRelation;
}
}
myCurrentRelation->myAttributes[key] = value;
}
}
void
PCLoaderOSM::RelationsHandler::myEndElement(int element) {
myParentElements.pop_back();
if (element == SUMO_TAG_RELATION) {
myCurrentRelation->myWays = myCurrentWays;
myCurrentRelation = nullptr;
myCurrentWays.clear();
}
}
PCLoaderOSM::EdgesHandler::EdgesHandler(const std::map<long long int, PCOSMNode*>& osmNodes,
EdgeMap& toFill,
const RelationsMap& additionalWays,
bool withAttributes, MsgHandler& errorHandler) :
SUMOSAXHandler("osm - file"),
myWithAttributes(withAttributes),
myErrorHandler(errorHandler),
myOSMNodes(osmNodes),
myEdgeMap(toFill),
myAdditionalWays(additionalWays) {
}
PCLoaderOSM::EdgesHandler::~EdgesHandler() {
}
void
PCLoaderOSM::EdgesHandler::myStartElement(int element, const SUMOSAXAttributes& attrs) {
myParentElements.push_back(element);
if (element == SUMO_TAG_WAY) {
bool ok = true;
const long long int id = attrs.get<long long int>(SUMO_ATTR_ID, nullptr, ok);
const std::string& action = attrs.getOpt<std::string>(SUMO_ATTR_ACTION, nullptr, ok);
if (action == "delete" || !ok) {
myCurrentEdge = nullptr;
return;
}
myCurrentEdge = new PCOSMEdge();
myCurrentEdge->id = id;
myCurrentEdge->myIsClosed = false;
myCurrentEdge->standalone = false;
myKeep = (myAdditionalWays.find(id) != myAdditionalWays.end());
}
if (element == SUMO_TAG_ND && myCurrentEdge != nullptr) {
bool ok = true;
const long long int ref = attrs.get<long long int>(SUMO_ATTR_REF, nullptr, ok);
if (ok) {
if (myOSMNodes.find(ref) == myOSMNodes.end()) {
WRITE_WARNINGF(TL("The referenced geometry information (ref='%') is not known"), ref);
return;
}
myCurrentEdge->myCurrentNodes.push_back(ref);
}
}
if (element == SUMO_TAG_TAG && myParentElements.size() > 2 && myParentElements[myParentElements.size() - 2] == SUMO_TAG_WAY
&& myCurrentEdge != nullptr) {
bool ok = true;
std::string key = attrs.getOpt<std::string>(SUMO_ATTR_K, toString(myCurrentEdge->id).c_str(), ok, "", false);
std::string value = attrs.getOpt<std::string>(SUMO_ATTR_V, toString(myCurrentEdge->id).c_str(), ok, "", false);
if (key == "") {
myErrorHandler.inform("Empty key in a a tag while parsing way '" + toString(myCurrentEdge->id) + "' occurred.");
ok = false;
}
if (!ok) {
return;
}
if (key == "name") {
myCurrentEdge->name = value;
} else if (MyKeysToInclude.count(key) > 0) {
myKeep = true;
myCurrentEdge->standalone = true;
}
myCurrentEdge->myAttributes[key] = value;
}
}
void
PCLoaderOSM::EdgesHandler::myEndElement(int element) {
myParentElements.pop_back();
if (element == SUMO_TAG_WAY && myCurrentEdge != nullptr) {
if (myKeep) {
RelationsMap::const_iterator it = myAdditionalWays.find(myCurrentEdge->id);
if (it != myAdditionalWays.end()) {
myCurrentEdge->myAttributes.insert((*it).second->myAttributes.begin(), (*it).second->myAttributes.end());
}
myEdgeMap[myCurrentEdge->id] = myCurrentEdge;
} else {
delete myCurrentEdge;
}
myCurrentEdge = nullptr;
}
}
double
PCLoaderOSM::mergeClosest(const std::map<long long int, PCOSMNode*>& nodes, std::vector<std::vector<long long int> >& snippets) {
double best = std::numeric_limits<double>::max();
int best_i = 0;
int best_j = 1;
bool iFW = true;
bool jFW = true;
for (int i = 0; i < (int)snippets.size(); i++) {
for (int j = i + 1; j < (int)snippets.size(); j++) {
Position front1(convertNodePosition(nodes.find(snippets[i].front())->second));
Position back1(convertNodePosition(nodes.find(snippets[i].back())->second));
Position front2(convertNodePosition(nodes.find(snippets[j].front())->second));
Position back2(convertNodePosition(nodes.find(snippets[j].back())->second));
double dist1 = front1.distanceTo2D(front2);
double dist2 = front1.distanceTo2D(back2);
double dist3 = back1.distanceTo2D(front2);
double dist4 = back1.distanceTo2D(back2);
if (dist1 < best) {
best = dist1;
best_i = i;
best_j = j;
iFW = false;
jFW = true;
}
if (dist2 < best) {
best = dist2;
best_i = i;
best_j = j;
iFW = false;
jFW = false;
}
if (dist3 < best) {
best = dist3;
best_i = i;
best_j = j;
iFW = true;
jFW = true;
}
if (dist4 < best) {
best = dist4;
best_i = i;
best_j = j;
iFW = true;
jFW = false;
}
}
}
std::vector<long long int> merged;
if (iFW) {
merged.insert(merged.end(), snippets[best_i].begin(), snippets[best_i].end());
} else {
merged.insert(merged.end(), snippets[best_i].rbegin(), snippets[best_i].rend());
}
if (jFW) {
merged.insert(merged.end(), snippets[best_j].begin(), snippets[best_j].end());
} else {
merged.insert(merged.end(), snippets[best_j].rbegin(), snippets[best_j].rend());
}
snippets.erase(snippets.begin() + best_j);
snippets.erase(snippets.begin() + best_i);
snippets.push_back(merged);
return best;
}