File indexing completed on 2025-01-05 03:59:29

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