File indexing completed on 2024-05-05 03:51:04
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 }