File indexing completed on 2024-12-01 10:29:54
0001 /* 0002 SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "xmlparser.h" 0008 #include "datatypes.h" 0009 0010 #include <QDebug> 0011 #include <QIODevice> 0012 #include <QXmlStreamReader> 0013 0014 #include <cmath> 0015 0016 using namespace OSM; 0017 0018 XmlParser::XmlParser(DataSet* dataSet) 0019 : AbstractReader(dataSet) 0020 { 0021 } 0022 0023 void XmlParser::readFromIODevice(QIODevice *io) 0024 { 0025 QXmlStreamReader reader(io); 0026 while (!reader.atEnd() && !reader.hasError()) { 0027 const auto token = reader.readNext(); 0028 if (token != QXmlStreamReader::StartElement) { 0029 continue; 0030 } 0031 0032 if (reader.name() == QLatin1String("node")) { 0033 parseNode(reader); 0034 } else if (reader.name() == QLatin1String("way")) { 0035 parseWay(reader); 0036 } else if (reader.name() == QLatin1String("relation")) { 0037 parseRelation(reader); 0038 } else if (reader.name() == QLatin1String("remark")) { 0039 m_error = reader.readElementText(); 0040 return; 0041 } 0042 } 0043 0044 if (reader.hasError()) { 0045 m_error = reader.errorString(); 0046 } 0047 } 0048 0049 // parse double coordinate value without actually doing floating point computations 0050 // this avoids any loss in precision we can other get heret 0051 uint32_t parseCoordinateValue(QStringView s, int offset) 0052 { 0053 const auto idx = s.indexOf(QLatin1Char('.')); 0054 if (idx < 0) { 0055 return s.toUInt() * 10'000'000; 0056 } 0057 uint32_t result = (uint32_t)(s.left(idx).toInt() + offset) * 10'000'000; 0058 const auto decimals = s.mid(idx + 1); 0059 if (decimals.size() >= 7) { 0060 result += decimals.left(7).toUInt(); 0061 } else { 0062 result += decimals.toUInt() * std::pow(10, 7 - decimals.size()); 0063 } 0064 return result; 0065 } 0066 0067 void XmlParser::parseNode(QXmlStreamReader &reader) 0068 { 0069 Node node; 0070 node.id = reader.attributes().value(QLatin1String("id")).toLongLong(); 0071 node.coordinate = Coordinate(parseCoordinateValue(reader.attributes().value(QLatin1String("lat")), 90), parseCoordinateValue(reader.attributes().value(QLatin1String("lon")), 180)); 0072 0073 while (!reader.atEnd() && reader.readNext() != QXmlStreamReader::EndElement) { 0074 if (reader.tokenType() != QXmlStreamReader::StartElement) { 0075 continue; 0076 } 0077 if (reader.name() == QLatin1String("tag")) { 0078 parseTag(reader, node); 0079 } 0080 reader.skipCurrentElement(); 0081 } 0082 0083 addNode(std::move(node)); 0084 } 0085 0086 void XmlParser::parseWay(QXmlStreamReader &reader) 0087 { 0088 Way way; 0089 way.id = reader.attributes().value(QLatin1String("id")).toLongLong(); 0090 0091 while (!reader.atEnd() && reader.readNext() != QXmlStreamReader::EndElement) { 0092 if (reader.tokenType() != QXmlStreamReader::StartElement) { 0093 continue; 0094 } 0095 if (reader.name() == QLatin1String("nd")) { 0096 OSM::Id node; 0097 node = reader.attributes().value(QLatin1String("ref")).toLongLong(); 0098 way.nodes.push_back(node); 0099 } else if (reader.name() == QLatin1String("tag")) { 0100 parseTagOrBounds(reader, way); 0101 } else if (reader.name() == QLatin1String("bounds")) { 0102 parseBounds(reader, way); 0103 } 0104 reader.skipCurrentElement(); 0105 } 0106 0107 addWay(std::move(way)); 0108 } 0109 0110 void XmlParser::parseRelation(QXmlStreamReader &reader) 0111 { 0112 Relation rel; 0113 rel.id = reader.attributes().value(QLatin1String("id")).toLongLong(); 0114 0115 while (!reader.atEnd() && reader.readNext() != QXmlStreamReader::EndElement) { 0116 if (reader.tokenType() != QXmlStreamReader::StartElement) { 0117 continue; 0118 } 0119 if (reader.name() == QLatin1String("tag")) { 0120 parseTagOrBounds(reader, rel); 0121 } else if (reader.name() == QLatin1String("bounds")) { // Overpass style bounding box 0122 parseBounds(reader, rel); 0123 } else if (reader.name() == QLatin1String("member")) { 0124 Member member; 0125 member.id = reader.attributes().value(QLatin1String("ref")).toLongLong(); 0126 const auto type = reader.attributes().value(QLatin1String("type")); 0127 if (type == QLatin1String("node")) { 0128 member.setType(Type::Node); 0129 } else if (type == QLatin1String("way")) { 0130 member.setType(Type::Way); 0131 } else { 0132 member.setType(Type::Relation); 0133 } 0134 member.setRole(m_dataSet->makeRole(reader.attributes().value(QLatin1String("role")).toUtf8().constData(), DataSet::StringIsTransient)); 0135 rel.members.push_back(std::move(member)); 0136 } 0137 reader.skipCurrentElement(); 0138 } 0139 0140 addRelation(std::move(rel)); 0141 } 0142 0143 template <typename T> 0144 void XmlParser::parseTag(QXmlStreamReader &reader, T &elem) 0145 { 0146 const auto key = m_dataSet->makeTagKey(reader.attributes().value(QLatin1String("k")).toString().toUtf8().constData(), OSM::DataSet::StringIsTransient); 0147 OSM::setTagValue(elem, key, reader.attributes().value(QLatin1String("v")).toUtf8()); 0148 } 0149 0150 template <typename T> 0151 void XmlParser::parseTagOrBounds(QXmlStreamReader &reader, T &elem) 0152 { 0153 if (reader.attributes().value(QLatin1String("k")) == QLatin1String("bBox")) { // osmconvert style bounding box 0154 const auto v = reader.attributes().value(QLatin1String("v")).split(QLatin1Char(',')); 0155 if (v.size() == 4) { 0156 elem.bbox.min = Coordinate(v[1].toDouble(), v[0].toDouble()); 0157 elem.bbox.max = Coordinate(v[3].toDouble(), v[2].toDouble()); 0158 } 0159 } else { 0160 parseTag(reader, elem); 0161 } 0162 } 0163 0164 template<typename T> 0165 void XmlParser::parseBounds(QXmlStreamReader &reader, T &elem) 0166 { 0167 // overpass style bounding box 0168 elem.bbox.min = Coordinate(reader.attributes().value(QLatin1String("minlat")).toDouble(), reader.attributes().value(QLatin1String("minlon")).toDouble()); 0169 elem.bbox.max = Coordinate(reader.attributes().value(QLatin1String("maxlat")).toDouble(), reader.attributes().value(QLatin1String("maxlon")).toDouble()); 0170 }