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 }