File indexing completed on 2024-07-21 09:27:11

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2011 Thibaut Gridel <tgridel@free.fr>
0004 // SPDX-FileCopyrightText: 2011 Konstantin Oblaukhov <oblaukhov.konstantin@gmail.com>
0005 // SPDX-FileCopyrightText: 2014 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
0006 // SPDX-FileCopyrightText: 2015 Dennis Nienhüser <nienhueser@kde.org>
0007 //
0008 
0009 #include "OsmParser.h"
0010 #include "OsmElementDictionary.h"
0011 #include "osm/OsmObjectManager.h"
0012 #include "GeoDataDocument.h"
0013 #include "GeoDataPoint.h"
0014 #include "GeoDataStyle.h"
0015 #include "GeoDataPolyStyle.h"
0016 #include <MarbleZipReader.h>
0017 #include "o5mreader.h"
0018 #include "OsmPbfParser.h"
0019 
0020 #include <QColor>
0021 #include <QFile>
0022 #include <QFileInfo>
0023 #include <QBuffer>
0024 #include <QSet>
0025 
0026 namespace Marble {
0027 
0028 GeoDataDocument *OsmParser::parse(const QString &filename, QString &error)
0029 {
0030     QFileInfo const fileInfo(filename);
0031     if (!fileInfo.exists() || !fileInfo.isReadable()) {
0032         error = QString("Cannot read file %1").arg(filename);
0033         return nullptr;
0034     }
0035 
0036     if (fileInfo.suffix() == QLatin1String("o5m")) {
0037         return parseO5m(filename, error);
0038     } else if (filename.endsWith(QLatin1String(".osm.pbf"))) {
0039         return parseOsmPbf(filename, error);
0040     } else {
0041         return parseXml(filename, error);
0042     }
0043 }
0044 
0045 GeoDataDocument* OsmParser::parseO5m(const QString &filename, QString &error)
0046 {
0047     O5mreader* reader;
0048     O5mreaderDataset data;
0049     O5mreaderIterateRet outerState, innerState;
0050     char *key, *value;
0051     // share string data on the heap at least for this file
0052     QSet<QString> stringPool;
0053 
0054     OsmNodes nodes;
0055     OsmWays ways;
0056     OsmRelations relations;
0057     QHash<uint8_t, QString> relationTypes;
0058     relationTypes[O5MREADER_DS_NODE] = QStringLiteral("node");
0059     relationTypes[O5MREADER_DS_WAY] = QStringLiteral("way");
0060     relationTypes[O5MREADER_DS_REL] = QStringLiteral("relation");
0061 
0062     auto file = fopen(filename.toStdString().c_str(), "rb");
0063     o5mreader_open(&reader, file);
0064 
0065     while( (outerState = o5mreader_iterateDataSet(reader, &data)) == O5MREADER_ITERATE_RET_NEXT) {
0066         switch (data.type) {
0067         case O5MREADER_DS_NODE:
0068         {
0069             OsmNode& node = nodes[data.id];
0070             node.osmData().setId(data.id);
0071             node.setCoordinates(GeoDataCoordinates(data.lon*1.0e-7, data.lat*1.0e-7,
0072                                                    0.0, GeoDataCoordinates::Degree));
0073             while ((innerState = o5mreader_iterateTags(reader, &key, &value)) == O5MREADER_ITERATE_RET_NEXT) {
0074                 const QString keyString = *stringPool.insert(QString::fromUtf8(key));
0075                 const QString valueString = *stringPool.insert(QString::fromUtf8(value));
0076                 node.osmData().addTag(keyString, valueString);
0077             }
0078         }
0079             break;
0080         case O5MREADER_DS_WAY:
0081         {
0082             OsmWay &way = ways[data.id];
0083             way.osmData().setId(data.id);
0084             uint64_t nodeId;
0085             while ((innerState = o5mreader_iterateNds(reader, &nodeId)) == O5MREADER_ITERATE_RET_NEXT) {
0086                 way.addReference(nodeId);
0087             }
0088             while ((innerState = o5mreader_iterateTags(reader, &key, &value)) == O5MREADER_ITERATE_RET_NEXT) {
0089                 const QString keyString = *stringPool.insert(QString::fromUtf8(key));
0090                 const QString valueString = *stringPool.insert(QString::fromUtf8(value));
0091                 way.osmData().addTag(keyString, valueString);
0092             }
0093         }
0094             break;
0095         case O5MREADER_DS_REL:
0096         {
0097             OsmRelation &relation = relations[data.id];
0098             relation.osmData().setId(data.id);
0099             char *role;
0100             uint8_t type;
0101             uint64_t refId;
0102             while ((innerState = o5mreader_iterateRefs(reader, &refId, &type, &role)) == O5MREADER_ITERATE_RET_NEXT) {
0103                 const QString roleString = *stringPool.insert(QString::fromUtf8(role));
0104                 relation.addMember(refId, roleString, relationTypes[type]);
0105             }
0106             while ((innerState = o5mreader_iterateTags(reader, &key, &value)) == O5MREADER_ITERATE_RET_NEXT) {
0107                 const QString keyString = *stringPool.insert(QString::fromUtf8(key));
0108                 const QString valueString = *stringPool.insert(QString::fromUtf8(value));
0109                 relation.osmData().addTag(keyString, valueString);
0110             }
0111         }
0112             break;
0113         }
0114     }
0115 
0116     fclose(file);
0117     error = reader->errMsg;
0118     o5mreader_close(reader);
0119     return createDocument(nodes, ways, relations);
0120 }
0121 
0122 GeoDataDocument* OsmParser::parseXml(const QString &filename, QString &error)
0123 {
0124     QXmlStreamReader parser;
0125     QFile file;
0126     QBuffer buffer;
0127     QFileInfo fileInfo(filename);
0128     if (fileInfo.completeSuffix() == QLatin1String("osm.zip")) {
0129         MarbleZipReader zipReader(filename);
0130         if (zipReader.fileInfoList().size() != 1) {
0131             int const fileNumber = zipReader.fileInfoList().size();
0132             error = QStringLiteral("Unexpected number of files (%1) in %2").arg(fileNumber).arg(filename);
0133             return nullptr;
0134         }
0135         QByteArray const data = zipReader.fileData(zipReader.fileInfoList().first().filePath);
0136         buffer.setData(data);
0137         buffer.open(QBuffer::ReadOnly);
0138         parser.setDevice(&buffer);
0139     } else {
0140         file.setFileName(filename);
0141         if (!file.open(QFile::ReadOnly)) {
0142             error = QStringLiteral("Cannot open file %1").arg(filename);
0143             return nullptr;
0144         }
0145         parser.setDevice(&file);
0146     }
0147 
0148     OsmPlacemarkData* osmData(nullptr);
0149     QString parentTag;
0150     qint64 parentId(0);
0151     // share string data on the heap at least for this file
0152     QSet<QString> stringPool;
0153 
0154     OsmNodes m_nodes;
0155     OsmWays m_ways;
0156     OsmRelations m_relations;
0157 
0158     while (!parser.atEnd()) {
0159         parser.readNext();
0160         if (!parser.isStartElement()) {
0161             continue;
0162         }
0163 
0164         QStringRef const tagName = parser.name();
0165         if (tagName == osm::osmTag_node || tagName == osm::osmTag_way || tagName == osm::osmTag_relation) {
0166             parentTag = parser.name().toString();
0167             parentId = parser.attributes().value(QLatin1String("id")).toLongLong();
0168 
0169             if (tagName == osm::osmTag_node) {
0170                 m_nodes[parentId].osmData() = OsmPlacemarkData::fromParserAttributes(parser.attributes());
0171                 m_nodes[parentId].parseCoordinates(parser.attributes());
0172                 osmData = &m_nodes[parentId].osmData();
0173             } else if (tagName == osm::osmTag_way) {
0174                 m_ways[parentId].osmData() = OsmPlacemarkData::fromParserAttributes(parser.attributes());
0175                 osmData = &m_ways[parentId].osmData();
0176             } else {
0177                 Q_ASSERT(tagName == osm::osmTag_relation);
0178                 m_relations[parentId].osmData() = OsmPlacemarkData::fromParserAttributes(parser.attributes());
0179                 osmData = &m_relations[parentId].osmData();
0180             }
0181         } else if (osmData && tagName == osm::osmTag_tag) {
0182             const QXmlStreamAttributes &attributes = parser.attributes();
0183             const QString keyString = *stringPool.insert(attributes.value(QLatin1String("k")).toString());
0184             const QString valueString = *stringPool.insert(attributes.value(QLatin1String("v")).toString());
0185             osmData->addTag(keyString, valueString);
0186         } else if (tagName == osm::osmTag_nd && parentTag == osm::osmTag_way) {
0187             m_ways[parentId].addReference(parser.attributes().value(QLatin1String("ref")).toLongLong());
0188         } else if (tagName == osm::osmTag_member && parentTag == osm::osmTag_relation) {
0189             m_relations[parentId].parseMember(parser.attributes());
0190         } // other tags like osm, bounds ignored
0191     }
0192 
0193     if (parser.hasError()) {
0194         error = parser.errorString();
0195         return nullptr;
0196     }
0197 
0198     return createDocument(m_nodes, m_ways, m_relations);
0199 }
0200 
0201 GeoDataDocument* OsmParser::parseOsmPbf(const QString &filename, QString &error)
0202 {
0203     QFile f(filename);
0204     if (!f.open(QFile::ReadOnly)) {
0205         error = f.errorString();
0206         return nullptr;
0207     }
0208 
0209     const auto data = f.map(0, f.size());
0210     OsmPbfParser p;
0211     p.parse(data, f.size());
0212     return createDocument(p.m_nodes, p.m_ways, p.m_relations);
0213 }
0214 
0215 GeoDataDocument *OsmParser::createDocument(OsmNodes &nodes, OsmWays &ways, OsmRelations &relations)
0216 {
0217     GeoDataDocument* document = new GeoDataDocument;
0218     GeoDataPolyStyle backgroundPolyStyle;
0219     backgroundPolyStyle.setFill( true );
0220     backgroundPolyStyle.setOutline( false );
0221     backgroundPolyStyle.setColor(QStringLiteral("#f1eee8"));
0222     GeoDataStyle::Ptr backgroundStyle(new GeoDataStyle);
0223     backgroundStyle->setPolyStyle( backgroundPolyStyle );
0224     backgroundStyle->setId(QStringLiteral("background"));
0225     document->addStyle( backgroundStyle );
0226 
0227     QSet<qint64> usedNodes, usedWays;
0228     for(auto const &relation: relations) {
0229         relation.createMultipolygon(document, ways, nodes, usedNodes, usedWays);
0230     }
0231     for(auto id: usedWays) {
0232         ways.remove(id);
0233     }
0234 
0235     QHash<qint64, GeoDataPlacemark*> placemarks;
0236     for (auto iter=ways.constBegin(), end=ways.constEnd(); iter != end; ++iter) {
0237         auto placemark = iter.value().create(nodes, usedNodes);
0238         if (placemark) {
0239             document->append(placemark);
0240             placemarks[placemark->osmData().oid()] = placemark;
0241         }
0242     }
0243 
0244     for(auto id: usedNodes) {
0245         if (nodes[id].osmData().isEmpty()) {
0246             nodes.remove(id);
0247         }
0248     }
0249 
0250     for(auto const &node: nodes) {
0251         auto placemark = node.create();
0252         if (placemark) {
0253             document->append(placemark);
0254             placemarks[placemark->osmData().oid()] = placemark;
0255         }
0256     }
0257 
0258     for(auto const &relation: relations) {
0259         relation.createRelation(document, placemarks);
0260     }
0261 
0262     return document;
0263 }
0264 
0265 }