diff --git a/src/plugins/runner/osm/OsmParser.cpp b/src/plugins/runner/osm/OsmParser.cpp index da4722244..d179ac5d0 100644 --- a/src/plugins/runner/osm/OsmParser.cpp +++ b/src/plugins/runner/osm/OsmParser.cpp @@ -1,253 +1,253 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011 Thibaut Gridel // Copyright 2011 Konstantin Oblaukhov // Copyright 2014 Bernhard Beschow // Copyright 2015 Dennis Nienhüser // #include "OsmParser.h" #include "OsmElementDictionary.h" #include "osm/OsmObjectManager.h" #include "GeoDataDocument.h" #include "GeoDataPoint.h" #include "GeoDataTypes.h" #include "GeoDataStyle.h" #include "GeoDataPolyStyle.h" #include #include "o5mreader.h" #include #include #include #include namespace Marble { GeoDataDocument *OsmParser::parse(const QString &filename, QString &error) { QFileInfo const fileInfo(filename); if (!fileInfo.exists() || !fileInfo.isReadable()) { error = QString("Cannot read file %1").arg(filename); return 0; } if (fileInfo.completeSuffix() == QLatin1String("o5m")) { return parseO5m(filename, error); } else { return parseXml(filename, error); } } GeoDataDocument* OsmParser::parseO5m(const QString &filename, QString &error) { O5mreader* reader; O5mreaderDataset data; O5mreaderIterateRet outerState, innerState; char *key, *value; // share string data on the heap at least for this file QSet stringPool; OsmNodes nodes; OsmWays ways; OsmRelations relations; QHash relationTypes; relationTypes[O5MREADER_DS_NODE] = QStringLiteral("node"); relationTypes[O5MREADER_DS_WAY] = QStringLiteral("way"); relationTypes[O5MREADER_DS_REL] = QStringLiteral("relation"); auto file = fopen(filename.toStdString().c_str(), "rb"); o5mreader_open(&reader, file); while( (outerState = o5mreader_iterateDataSet(reader, &data)) == O5MREADER_ITERATE_RET_NEXT) { switch (data.type) { case O5MREADER_DS_NODE: { OsmNode& node = nodes[data.id]; node.osmData().setId(data.id); node.setCoordinates(GeoDataCoordinates(data.lon*1.0e-7, data.lat*1.0e-7, 0.0, GeoDataCoordinates::Degree)); while ((innerState = o5mreader_iterateTags(reader, &key, &value)) == O5MREADER_ITERATE_RET_NEXT) { const QString keyString = *stringPool.insert(QString::fromUtf8(key)); const QString valueString = *stringPool.insert(QString::fromUtf8(value)); node.osmData().addTag(keyString, valueString); } } break; case O5MREADER_DS_WAY: { OsmWay &way = ways[data.id]; way.osmData().setId(data.id); uint64_t nodeId; while ((innerState = o5mreader_iterateNds(reader, &nodeId)) == O5MREADER_ITERATE_RET_NEXT) { way.addReference(nodeId); } while ((innerState = o5mreader_iterateTags(reader, &key, &value)) == O5MREADER_ITERATE_RET_NEXT) { const QString keyString = *stringPool.insert(QString::fromUtf8(key)); const QString valueString = *stringPool.insert(QString::fromUtf8(value)); way.osmData().addTag(keyString, valueString); } } break; case O5MREADER_DS_REL: { OsmRelation &relation = relations[data.id]; relation.osmData().setId(data.id); char *role; uint8_t type; uint64_t refId; while ((innerState = o5mreader_iterateRefs(reader, &refId, &type, &role)) == O5MREADER_ITERATE_RET_NEXT) { const QString roleString = *stringPool.insert(QString::fromUtf8(role)); relation.addMember(refId, roleString, relationTypes[type]); } while ((innerState = o5mreader_iterateTags(reader, &key, &value)) == O5MREADER_ITERATE_RET_NEXT) { const QString keyString = *stringPool.insert(QString::fromUtf8(key)); const QString valueString = *stringPool.insert(QString::fromUtf8(value)); relation.osmData().addTag(keyString, valueString); } } break; } } fclose(file); error = reader->errMsg; o5mreader_close(reader); return createDocument(nodes, ways, relations); } GeoDataDocument* OsmParser::parseXml(const QString &filename, QString &error) { QXmlStreamReader parser; QFile file; QBuffer buffer; QFileInfo fileInfo(filename); if (fileInfo.completeSuffix() == QLatin1String("osm.zip")) { MarbleZipReader zipReader(filename); if (zipReader.fileInfoList().size() != 1) { int const fileNumber = zipReader.fileInfoList().size(); error = QStringLiteral("Unexpected number of files (%1) in %2").arg(fileNumber).arg(filename); return nullptr; } QByteArray const data = zipReader.fileData(zipReader.fileInfoList().first().filePath); buffer.setData(data); buffer.open(QBuffer::ReadOnly); parser.setDevice(&buffer); } else { file.setFileName(filename); if (!file.open(QFile::ReadOnly)) { error = QStringLiteral("Cannot open file %1").arg(filename); return nullptr; } parser.setDevice(&file); } OsmPlacemarkData* osmData(0); QString parentTag; qint64 parentId(0); // share string data on the heap at least for this file QSet stringPool; OsmNodes m_nodes; OsmWays m_ways; OsmRelations m_relations; while (!parser.atEnd()) { parser.readNext(); if (!parser.isStartElement()) { continue; } QStringRef const tagName = parser.name(); if (tagName == osm::osmTag_node || tagName == osm::osmTag_way || tagName == osm::osmTag_relation) { parentTag = parser.name().toString(); parentId = parser.attributes().value(QLatin1String("id")).toLongLong(); if (tagName == osm::osmTag_node) { m_nodes[parentId].osmData() = OsmPlacemarkData::fromParserAttributes(parser.attributes()); m_nodes[parentId].parseCoordinates(parser.attributes()); osmData = &m_nodes[parentId].osmData(); } else if (tagName == osm::osmTag_way) { m_ways[parentId].osmData() = OsmPlacemarkData::fromParserAttributes(parser.attributes()); osmData = &m_ways[parentId].osmData(); } else { Q_ASSERT(tagName == osm::osmTag_relation); m_relations[parentId].osmData() = OsmPlacemarkData::fromParserAttributes(parser.attributes()); osmData = &m_relations[parentId].osmData(); } } else if (osmData && tagName == osm::osmTag_tag) { const QXmlStreamAttributes &attributes = parser.attributes(); const QString keyString = *stringPool.insert(attributes.value(QLatin1String("k")).toString()); const QString valueString = *stringPool.insert(attributes.value(QLatin1String("v")).toString()); osmData->addTag(keyString, valueString); } else if (tagName == osm::osmTag_nd && parentTag == osm::osmTag_way) { m_ways[parentId].addReference(parser.attributes().value(QLatin1String("ref")).toLongLong()); } else if (tagName == osm::osmTag_member && parentTag == osm::osmTag_relation) { m_relations[parentId].parseMember(parser.attributes()); } // other tags like osm, bounds ignored } if (parser.hasError()) { error = parser.errorString(); return nullptr; } return createDocument(m_nodes, m_ways, m_relations); } GeoDataDocument *OsmParser::createDocument(OsmNodes &nodes, OsmWays &ways, OsmRelations &relations) { GeoDataDocument* document = new GeoDataDocument; GeoDataPolyStyle backgroundPolyStyle; backgroundPolyStyle.setFill( true ); backgroundPolyStyle.setOutline( false ); backgroundPolyStyle.setColor(QStringLiteral("#f1eee8")); GeoDataStyle::Ptr backgroundStyle(new GeoDataStyle); backgroundStyle->setPolyStyle( backgroundPolyStyle ); backgroundStyle->setId(QStringLiteral("background")); document->addStyle( backgroundStyle ); QSet usedNodes, usedWays; foreach(OsmRelation const &relation, relations) { relation.createMultipolygon(document, ways, nodes, usedNodes, usedWays); } foreach(qint64 id, usedWays) { ways.remove(id); } QHash placemarks; for (auto iter=ways.constBegin(), end=ways.constEnd(); iter != end; ++iter) { auto placemark = iter.value().create(nodes, usedNodes); if (placemark) { document->append(placemark); placemarks[placemark->osmData().oid()] = placemark; } } foreach(qint64 id, usedNodes) { if (nodes[id].osmData().isEmpty()) { nodes.remove(id); } } foreach(OsmNode const &node, nodes) { auto placemark = node.create(); if (placemark) { document->append(placemark); placemarks[placemark->osmData().oid()] = placemark; } } foreach(OsmRelation const &relation, relations) { - relation.createRoute(document, placemarks); + relation.createRelation(document, placemarks); } return document; } } diff --git a/src/plugins/runner/osm/OsmRelation.cpp b/src/plugins/runner/osm/OsmRelation.cpp index a57082c20..8e3b826ba 100644 --- a/src/plugins/runner/osm/OsmRelation.cpp +++ b/src/plugins/runner/osm/OsmRelation.cpp @@ -1,290 +1,290 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2015 Dennis Nienhüser // #include #include #include #include #include #include #include #include #include namespace Marble { OsmRelation::OsmMember::OsmMember() : reference(0) { // nothing to do } OsmPlacemarkData &OsmRelation::osmData() { return m_osmData; } const OsmPlacemarkData &OsmRelation::osmData() const { return m_osmData; } void OsmRelation::parseMember(const QXmlStreamAttributes &attributes) { addMember(attributes.value(QLatin1String("ref")).toLongLong(), attributes.value(QLatin1String("role")).toString(), attributes.value(QLatin1String("type")).toString()); } void OsmRelation::addMember(qint64 reference, const QString &role, const QString &type) { OsmMember member; member.reference = reference; member.role = role; member.type = type; m_members << member; } void OsmRelation::createMultipolygon(GeoDataDocument *document, OsmWays &ways, const OsmNodes &nodes, QSet &usedNodes, QSet &usedWays) const { if (!m_osmData.containsTag(QStringLiteral("type"), QStringLiteral("multipolygon"))) { return; } QStringList const outerRoles = QStringList() << QStringLiteral("outer") << QString(); QSet outerWays; QSet outerNodes; OsmRings const outer = rings(outerRoles, ways, nodes, outerNodes, outerWays); if (outer.isEmpty()) { return; } GeoDataPlacemark::GeoDataVisualCategory outerCategory = StyleBuilder::determineVisualCategory(m_osmData); if (outerCategory == GeoDataPlacemark::None) { // Try to determine the visual category from the relation members GeoDataPlacemark::GeoDataVisualCategory const firstCategory = StyleBuilder::determineVisualCategory(ways[*outerWays.begin()].osmData()); bool categoriesAreSame = true; foreach (auto wayId, outerWays) { GeoDataPlacemark::GeoDataVisualCategory const category = StyleBuilder::determineVisualCategory(ways[wayId].osmData()); if( category != firstCategory ) { categoriesAreSame = false; break; } } if( categoriesAreSame ) { outerCategory = firstCategory; } } foreach(qint64 wayId, outerWays) { Q_ASSERT(ways.contains(wayId)); GeoDataPlacemark::GeoDataVisualCategory const category = StyleBuilder::determineVisualCategory(ways[wayId].osmData()); if (category == GeoDataPlacemark::None || category == outerCategory) { // Schedule way for removal: It's a non-styled way only used to create the outer boundary in this polygon usedWays << wayId; } // else we keep it foreach(qint64 nodeId, ways[wayId].references()) { ways[wayId].osmData().addNodeReference(nodes[nodeId].coordinates(), nodes[nodeId].osmData()); } } QStringList const innerRoles = QStringList() << QStringLiteral("inner"); QSet innerWays; OsmRings const inner = rings(innerRoles, ways, nodes, usedNodes, innerWays); bool const hasMultipleOuterRings = outer.size() > 1; for (int i=0, n=outer.size(); isetOuterBoundary(outerRing.first); OsmPlacemarkData osmData = m_osmData; osmData.addMemberReference(-1, outerRing.second); int index = 0; for (auto const &innerRing: inner) { if (innerRing.first.isEmpty() || !outerRing.first.contains(innerRing.first.first())) { // Simple check to see if this inner ring is inside the outer ring continue; } if (StyleBuilder::determineVisualCategory(innerRing.second) == GeoDataPlacemark::None) { // Schedule way for removal: It's a non-styled way only used to create the inner boundary in this polygon usedWays << innerRing.second.id(); } polygon->appendInnerBoundary(innerRing.first); osmData.addMemberReference(index, innerRing.second); ++index; } if (outerCategory == GeoDataPlacemark::Bathymetry) { // In case of a bathymetry store elevation info since it is required during styling // The ele=* tag is present in the outermost way const QString ele = QStringLiteral("ele"); const OsmPlacemarkData &outerWayData = outerRing.second; auto tagIter = outerWayData.findTag(ele); if (tagIter != outerWayData.tagsEnd()) { osmData.addTag(ele, tagIter.value()); } } GeoDataPlacemark *placemark = new GeoDataPlacemark; placemark->setName(m_osmData.tagValue(QStringLiteral("name"))); placemark->setVisualCategory(outerCategory); placemark->setOsmData(osmData); placemark->setZoomLevel(StyleBuilder::minimumZoomLevel(outerCategory)); placemark->setPopularity(StyleBuilder::popularity(placemark)); placemark->setVisible(outerCategory != GeoDataPlacemark::None); placemark->setGeometry(polygon); if (hasMultipleOuterRings) { /** @TODO Use a GeoDataMultiGeometry to keep the ID? */ osmData.setId(0); OsmObjectManager::initializeOsmData(placemark); } else { OsmObjectManager::registerId(osmData.id()); } usedNodes |= outerNodes; document->append(placemark); } } -void OsmRelation::createRoute(GeoDataDocument *document, const QHash placemarks) const +void OsmRelation::createRelation(GeoDataDocument *document, const QHash placemarks) const { - if (!(m_osmData.containsTag(QStringLiteral("type"), QStringLiteral("route")))) { + if (m_osmData.containsTag(QStringLiteral("type"), QStringLiteral("multipolygon"))) { return; } OsmPlacemarkData osmData = m_osmData; GeoDataRelation *relation = new GeoDataRelation; relation->setName(osmData.tagValue(QStringLiteral("name"))); if (relation->name().isEmpty()) { relation->setName(osmData.tagValue(QStringLiteral("ref"))); } relation->osmData() = osmData; foreach(const OsmMember &member, m_members) { auto const iter = placemarks.find(member.reference); if (iter != placemarks.constEnd()) { relation->addMember(*iter, member.reference, member.role); } } if (relation->members().isEmpty()) { delete relation; return; } OsmObjectManager::registerId(osmData.id()); relation->setVisible(false); document->append(relation); } OsmRelation::OsmRings OsmRelation::rings(const QStringList &roles, const OsmWays &ways, const OsmNodes &nodes, QSet &usedNodes, QSet &usedWays) const { QSet currentWays; QSet currentNodes; QList roleMembers; foreach(const OsmMember &member, m_members) { if (roles.contains(member.role)) { if (!ways.contains(member.reference)) { // A way is missing. Return nothing. return OsmRings(); } roleMembers << member.reference; } } OsmRings result; QList unclosedWays; foreach(qint64 wayId, roleMembers) { GeoDataLinearRing ring; OsmWay const & way = ways[wayId]; if (way.references().isEmpty()) { continue; } if (way.references().first() != way.references().last()) { unclosedWays.append(way); continue; } foreach(qint64 id, way.references()) { if (!nodes.contains(id)) { // A node is missing. Return nothing. return OsmRings(); } ring << nodes[id].coordinates(); } Q_ASSERT(ways.contains(wayId)); currentWays << wayId; result << OsmRing(GeoDataLinearRing(ring.optimized()), way.osmData()); } if( !unclosedWays.isEmpty() ) { //mDebug() << "Trying to merge non-trivial polygon boundary in relation " << m_osmData.id(); while( unclosedWays.length() > 0 ) { GeoDataLinearRing ring; qint64 firstReference = unclosedWays.first().references().first(); qint64 lastReference = firstReference; bool ok = true; while( ok ) { ok = false; for(int i = 0; i v = nextWay.references(); while( !v.isEmpty() ) { qint64 id = isReversed ? v.takeLast() : v.takeFirst(); if (!nodes.contains(id)) { // A node is missing. Return nothing. return OsmRings(); } if ( id != lastReference ) { ring << nodes[id].coordinates(); currentNodes << id; } } lastReference = isReversed ? nextWay.references().first() : nextWay.references().last(); Q_ASSERT(ways.contains(nextWay.osmData().id())); currentWays << nextWay.osmData().id(); unclosedWays.removeAt(i); ok = true; break; } else { ++i; } } } if(lastReference != firstReference) { return OsmRings(); } else { /** @todo Merge tags common to all rings into the new osm data? */ result << OsmRing(GeoDataLinearRing(ring.optimized()), OsmPlacemarkData()); } } } usedWays |= currentWays; usedNodes |= currentNodes; return result; } } diff --git a/src/plugins/runner/osm/OsmRelation.h b/src/plugins/runner/osm/OsmRelation.h index cfcae61e1..d67049f56 100644 --- a/src/plugins/runner/osm/OsmRelation.h +++ b/src/plugins/runner/osm/OsmRelation.h @@ -1,61 +1,61 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2015 Dennis Nienhüser // #ifndef MARBLE_OSMRELATION #define MARBLE_OSMRELATION #include "OsmNode.h" #include "OsmWay.h" #include #include #include #include #include namespace Marble { class GeoDataDocument; class OsmRelation { public: OsmPlacemarkData & osmData(); void parseMember(const QXmlStreamAttributes &attributes); void addMember(qint64 reference, const QString &role, const QString &type); void createMultipolygon(GeoDataDocument* document, OsmWays &ways, const OsmNodes &nodes, QSet &usedNodes, QSet &usedWays) const; - void createRoute(GeoDataDocument* document, const QHash wayPlacemarks) const; + void createRelation(GeoDataDocument* document, const QHash wayPlacemarks) const; const OsmPlacemarkData & osmData() const; private: typedef QPair OsmRing; typedef QVector OsmRings; struct OsmMember { QString type; QString role; qint64 reference; OsmMember(); }; OsmRings rings(const QStringList &roles, const OsmWays &ways, const OsmNodes &nodes, QSet &usedNodes, QSet &usedWays) const; OsmPlacemarkData m_osmData; QVector m_members; }; typedef QHash OsmRelations; } #endif