File indexing completed on 2024-05-12 04:42:18

0001 /*
0002     SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org>
0003     SPDX-License-Identifier: LGPL-2.0-or-later
0004 */
0005 
0006 #include "o5mwriter.h"
0007 #include "o5m.h"
0008 #include "datatypes.h"
0009 
0010 #include <QBuffer>
0011 #include <QIODevice>
0012 
0013 using namespace OSM;
0014 
0015 static void writeByte(uint8_t b, QIODevice *io)
0016 {
0017     io->write(reinterpret_cast<const char*>(&b), 1);
0018 }
0019 
0020 static void writeUnsigned(uint64_t n, QIODevice *io)
0021 {
0022     do {
0023         uint8_t b = ((n >> 7) > 0 ? O5M_NUMBER_CONTINUATION : 0) | (n & O5M_NUMBER_MASK);
0024         writeByte(b, io);
0025         n >>= 7;
0026     } while (n > 0);
0027 }
0028 
0029 static void writeSigned(int64_t n, QIODevice *io)
0030 {
0031     uint64_t u = n < 0 ? (-n - 1) : n;
0032     u <<= 1;
0033     u |= n < 0 ? O5M_NUMBER_SIGNED_BIT : 0;
0034     writeUnsigned(u, io);
0035 }
0036 
0037 static void writeHeader(QIODevice *io)
0038 {
0039     writeByte(O5M_BLOCK_RESET, io);
0040     writeByte(O5M_BLOCK_HEADER, io);
0041     writeByte(4, io);
0042     io->write(O5M_HEADER, 4);
0043 }
0044 
0045 static void writeTrailer(QIODevice *io)
0046 {
0047     writeByte(O5M_TRAILER, io);
0048 }
0049 
0050 void O5mWriter::writeToIODevice(const OSM::DataSet& dataSet, QIODevice* io)
0051 {
0052     writeHeader(io);
0053     writeNodes(dataSet, io);
0054     writeWays(dataSet, io);
0055     writeRelations(dataSet, io);
0056     writeTrailer(io);
0057 
0058     m_stringTable.clear();
0059 }
0060 
0061 void O5mWriter::writeNodes(const OSM::DataSet &dataSet, QIODevice *io)
0062 {
0063     if (dataSet.nodes.empty()) {
0064         return;
0065     }
0066 
0067     writeByte(O5M_BLOCK_RESET, io);
0068     m_stringTable.clear();
0069 
0070     OSM::Id prevId = 0;
0071     int64_t prevLat = 900'000'000ll;
0072     int64_t prevLon = 1'800'000'000ll;
0073 
0074     QByteArray bufferData;
0075     QBuffer buffer(&bufferData);
0076     for(auto const &node: dataSet.nodes) {
0077         bufferData.clear();
0078         buffer.open(QIODevice::WriteOnly);
0079         writeByte(O5M_BLOCK_NODE, io);
0080 
0081         writeSigned((int64_t)node.id - (int64_t)prevId, &buffer);
0082         prevId = node.id;
0083 
0084         writeByte(0x0, &buffer);
0085         writeSigned((int64_t)node.coordinate.longitude - prevLon, &buffer);
0086         prevLon = node.coordinate.longitude;
0087         writeSigned((int64_t)node.coordinate.latitude - prevLat, &buffer);
0088         prevLat = node.coordinate.latitude;
0089 
0090         writeTags(node, &buffer);
0091 
0092         buffer.close();
0093         writeUnsigned(bufferData.size(), io);
0094         io->write(bufferData.constData(), bufferData.size());
0095     }
0096 }
0097 
0098 void O5mWriter::writeWays(const OSM::DataSet &dataSet, QIODevice *io)
0099 {
0100     if (dataSet.ways.empty()) {
0101         return;
0102     }
0103 
0104     writeByte(O5M_BLOCK_RESET, io);
0105     m_stringTable.clear();
0106     OSM::Id prevId = 0;
0107     OSM::Id prevNodeId = 0;
0108 
0109     QByteArray bufferData;
0110     QBuffer buffer(&bufferData);
0111     QByteArray referencesBufferData;
0112     QBuffer referencesBuffer(&referencesBufferData);
0113 
0114     for (auto const &way: dataSet.ways) {
0115         writeByte(O5M_BLOCK_WAY, io);
0116 
0117         bufferData.clear();
0118         buffer.open(QIODevice::WriteOnly);
0119         writeSigned((int64_t)way.id - (int64_t)prevId, &buffer);
0120         prevId = way.id;
0121         writeByte(0x0, &buffer);
0122 
0123         referencesBufferData.clear();
0124         referencesBuffer.open(QIODevice::WriteOnly);
0125         for (const auto &node : way.nodes) {
0126             writeSigned((int64_t)node - (int64_t)prevNodeId, &referencesBuffer);
0127             prevNodeId = node;
0128         }
0129         referencesBuffer.close();
0130         writeUnsigned(referencesBufferData.size(), &buffer);
0131         buffer.write(referencesBufferData.constData(), referencesBufferData.size());
0132         writeTags(way, &buffer);
0133         buffer.close();
0134         writeUnsigned(bufferData.size(), io);
0135         io->write(bufferData.constData(), bufferData.size());
0136     }
0137 }
0138 
0139 void O5mWriter::writeRelations(const OSM::DataSet &dataSet, QIODevice *io)
0140 {
0141     if (dataSet.relations.empty()) {
0142         return;
0143     }
0144 
0145     writeByte(O5M_BLOCK_RESET, io);
0146     m_stringTable.clear();
0147     OSM::Id prevId = 0;
0148     OSM::Id prevMemberId[3] = { 0, 0, 0 };
0149 
0150     QByteArray bufferData;
0151     QBuffer buffer(&bufferData);
0152     QByteArray referencesBufferData;
0153     QBuffer referencesBuffer(&referencesBufferData);
0154     QByteArray role;
0155 
0156     for (auto const &relation: dataSet.relations) {
0157         writeByte(O5M_BLOCK_RELATION, io);
0158 
0159         bufferData.clear();
0160         buffer.open(QIODevice::WriteOnly);
0161 
0162         writeSigned((int64_t)relation.id - (int64_t)prevId, &buffer);
0163         prevId = relation.id;
0164         writeByte(0x0, &buffer);
0165 
0166         referencesBufferData.clear();
0167         referencesBuffer.open(QIODevice::WriteOnly);
0168 
0169         for (const auto &member : relation.members) {
0170             role.clear();
0171             switch (member.type()) {
0172                 case OSM::Type::Node:
0173                     writeSigned((int64_t)member.id - (int64_t)prevMemberId[0], &referencesBuffer);
0174                     prevMemberId[0] = member.id;
0175                     role += (const char)O5M_MEMTYPE_NODE;
0176                     role += member.role().name();
0177                     writeStringPair(role.constData(), nullptr, &referencesBuffer);
0178                     break;
0179                 case OSM::Type::Way:
0180                     writeSigned((int64_t)member.id - (int64_t)prevMemberId[1], &referencesBuffer);
0181                     prevMemberId[1] = member.id;
0182                     role += (const char)O5M_MEMTYPE_WAY;
0183                     role += member.role().name();
0184                     writeStringPair(role.constData(), nullptr, &referencesBuffer);
0185                     break;
0186                 case OSM::Type::Relation:
0187                     writeSigned((int64_t)member.id - (int64_t)prevMemberId[2], &referencesBuffer);
0188                     prevMemberId[2] = member.id;
0189                     role += (const char)O5M_MEMTYPE_RELATION;
0190                     role += member.role().name();
0191                     writeStringPair(role.constData(), nullptr, &referencesBuffer);
0192                     break;
0193                 case OSM::Type::Null:
0194                     assert(false);
0195                     break;
0196             }
0197         }
0198 
0199         referencesBuffer.close();
0200         writeUnsigned(referencesBufferData.size(), &buffer);
0201         buffer.write(referencesBufferData.constData(), referencesBufferData.size());
0202         writeTags(relation, &buffer);
0203         buffer.close();
0204         writeUnsigned(bufferData.size(), io);
0205         io->write(bufferData.constData(), bufferData.size());
0206     }
0207 }
0208 
0209 template <typename T>
0210 void O5mWriter::writeTags(const T &elem, QIODevice *io)
0211 {
0212     for (auto &tag : elem.tags) {
0213         writeStringPair(tag.key.name(), tag.value.constData(), io);
0214     }
0215 }
0216 
0217 void O5mWriter::writeStringPair(const char *s1, const char *s2, QIODevice *io)
0218 {
0219     assert(s1);
0220     O5mStringPair p;
0221     p.s1 = s1;
0222     if (s2) {
0223         p.s2 = s2;
0224     }
0225     const auto it = m_stringTable.find(p);
0226     if (it != m_stringTable.end()) {
0227         writeUnsigned(m_stringTable.size() - it->second, io);
0228     } else {
0229         writeByte(0x0, io);
0230         io->write(s1);
0231         writeByte(0x0, io);
0232         if (s2) {
0233             io->write(s2);
0234             writeByte(0x0, io);
0235         }
0236         if ((std::strlen(s1) + (s2 ? std::strlen(s2) : 0) <= O5M_STRING_TABLE_MAXLEN) && (m_stringTable.size() <= O5M_STRING_TABLE_SIZE)) {
0237             m_stringTable[p] = m_stringTable.size();
0238         }
0239     }
0240 }