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 }