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 "osmpbfwriter.h"
0007 #include "datatypes.h"
0008 
0009 #include "fileformat.pb.h"
0010 #include "osmformat.pb.h"
0011 
0012 #include <QIODevice>
0013 #include <QtEndian>
0014 
0015 #include <zlib.h>
0016 
0017 using namespace OSM;
0018 
0019 static constexpr const std::size_t BLOCK_SIZE_LIMIT = 16'777'216;
0020 
0021 OsmPbfWriter::OsmPbfWriter() = default;
0022 OsmPbfWriter::~OsmPbfWriter() = default;
0023 
0024 void OsmPbfWriter::writeToIODevice(const OSM::DataSet &dataSet, QIODevice *io)
0025 {
0026     m_io = io;
0027     writeNodes(dataSet);
0028     writeWays(dataSet);
0029     writeRelations(dataSet);
0030     if (m_block) {
0031         writeBlob();
0032     }
0033     m_io =  nullptr;
0034 }
0035 
0036 void OsmPbfWriter::writeNodes(const OSM::DataSet &dataSet)
0037 {
0038     if (dataSet.nodes.empty()) {
0039         return;
0040     }
0041 
0042     int64_t prevId = 0;
0043     int64_t prevLat = 900'000'000ll;
0044     int64_t prevLon = 1'800'000'000ll;
0045 
0046     OSMPBF::DenseNodes *denseBlock = nullptr;
0047     for(auto const &node: dataSet.nodes) {
0048         createBlockIfNeeded();
0049         if (!denseBlock) {
0050             denseBlock = m_block->add_primitivegroup()->mutable_dense();
0051         }
0052 
0053         denseBlock->add_id(node.id - prevId);
0054         prevId = node.id;
0055 
0056         denseBlock->add_lat((int64_t)node.coordinate.latitude - prevLat);
0057         prevLat = node.coordinate.latitude;
0058         denseBlock->add_lon((int64_t)node.coordinate.longitude - prevLon);
0059         prevLon = node.coordinate.longitude;
0060 
0061         for (const auto &tag : node.tags) {
0062             denseBlock->add_keys_vals(stringTableEntry(tag.key.name()));
0063             denseBlock->add_keys_vals(stringTableEntry(tag.value.constData()));
0064             m_blockSizeEstimate += 2 * sizeof(int32_t);
0065         }
0066 
0067         denseBlock->add_keys_vals(0);
0068         m_blockSizeEstimate += 3 * sizeof(int64_t) + sizeof(int32_t);
0069 
0070         if (blockSizeLimitReached()) {
0071             denseBlock = nullptr;
0072             writeBlob();
0073         }
0074     }
0075 }
0076 
0077 void OsmPbfWriter::writeWays(const OSM::DataSet &dataSet)
0078 {
0079     OSMPBF::PrimitiveGroup *group = nullptr;
0080     for(auto const &way : dataSet.ways) {
0081         createBlockIfNeeded();
0082         if (!group) {
0083             group = m_block->add_primitivegroup();
0084         }
0085 
0086         auto w = group->add_ways();
0087         w->set_id(way.id);
0088         m_blockSizeEstimate += sizeof(int64_t);
0089 
0090         int64_t prevId = 0;
0091         for (const auto &id : way.nodes) {
0092             w->add_refs(id - prevId);
0093             prevId = id;
0094             m_blockSizeEstimate += sizeof(int64_t);
0095         }
0096         for (const auto &tag : way.tags) {
0097             w->add_keys(stringTableEntry(tag.key.name()));
0098             w->add_vals(stringTableEntry(tag.value.constData()));
0099             m_blockSizeEstimate += 2 * sizeof(int32_t);
0100         }
0101 
0102         if (blockSizeLimitReached()) {
0103             group = nullptr;
0104             writeBlob();
0105         }
0106     }
0107 }
0108 
0109 static OSMPBF::Relation_MemberType pbfMemberType(OSM::Type t)
0110 {
0111     switch (t) {
0112         case OSM::Type::Null:
0113             Q_UNREACHABLE();
0114         case OSM::Type::Node:
0115             return OSMPBF::Relation_MemberType::Relation_MemberType_NODE;
0116         case OSM::Type::Way:
0117             return OSMPBF::Relation_MemberType::Relation_MemberType_WAY;
0118         case OSM::Type::Relation:
0119             return OSMPBF::Relation_MemberType::Relation_MemberType_RELATION;
0120     }
0121     return OSMPBF::Relation_MemberType::Relation_MemberType_NODE;
0122 }
0123 
0124 void OsmPbfWriter::writeRelations(const OSM::DataSet &dataSet)
0125 {
0126     OSMPBF::PrimitiveGroup *group = nullptr;
0127     for (const auto &rel :dataSet.relations) {
0128         createBlockIfNeeded();
0129         if (!group) {
0130             group = m_block->add_primitivegroup();
0131         }
0132 
0133         auto r = group->add_relations();
0134         r->set_id(rel.id);
0135         m_blockSizeEstimate += sizeof(int64_t);
0136 
0137         for (const auto &tag : rel.tags) {
0138             r->add_keys(stringTableEntry(tag.key.name()));
0139             r->add_vals(stringTableEntry(tag.value.constData()));
0140             m_blockSizeEstimate += 2 * sizeof(int32_t);
0141         }
0142 
0143         int64_t prevMemId = 0;
0144         for (const auto &mem : rel.members) {
0145             r->add_roles_sid(stringTableEntry(mem.role().name()));
0146             r->add_memids(mem.id - prevMemId);
0147             prevMemId = mem.id;
0148             r->add_types(pbfMemberType(mem.type()));
0149             m_blockSizeEstimate += 2* sizeof(int32_t) + sizeof(int64_t);
0150         }
0151 
0152         if (blockSizeLimitReached()) {
0153             group = nullptr;
0154             writeBlob();
0155         }
0156     }
0157 }
0158 
0159 int32_t OsmPbfWriter::stringTableEntry(const char *s)
0160 {
0161     assert(m_block);
0162     const auto it = m_stringTable.find(s);
0163     if (it == m_stringTable.end()) {
0164         auto st = m_block->mutable_stringtable();
0165         st->add_s(s);
0166         m_stringTable[s] = st->s_size() - 1;
0167         m_blockSizeEstimate += std::strlen(s) + 1 + sizeof(int32_t);
0168         return st->s_size() - 1;
0169     }
0170 
0171     return (*it).second;
0172 }
0173 
0174 void OsmPbfWriter::createBlockIfNeeded()
0175 {
0176     if (!m_block) {
0177         m_block = std::make_unique<OSMPBF::PrimitiveBlock>();
0178         m_blockSizeEstimate = 0;
0179         m_block->mutable_stringtable()->add_s(""); // dense node block tag separation marker
0180     }
0181 }
0182 
0183 bool OsmPbfWriter::blockSizeLimitReached() const
0184 {
0185     return m_blockSizeEstimate >BLOCK_SIZE_LIMIT;
0186 }
0187 
0188 void OsmPbfWriter::writeBlob()
0189 {
0190     OSMPBF::Blob blob;
0191     auto rawBlobData = m_block->SerializeAsString();
0192     auto zlibBlobData = blob.mutable_zlib_data();
0193     zlibBlobData->resize(32 * 1024ul * 1024ul);
0194 
0195     z_stream zStream;
0196     zStream.next_in = (uint8_t*)rawBlobData.data();
0197     zStream.avail_in = rawBlobData.size();
0198     zStream.next_out = (uint8_t*)zlibBlobData->data();
0199     zStream.avail_out = zlibBlobData->size();
0200     zStream.zalloc = nullptr;
0201     zStream.zfree = nullptr;
0202     zStream.opaque = nullptr;
0203     deflateInit(&zStream, Z_DEFAULT_COMPRESSION);
0204     while (true) {
0205         const auto ret = deflate(&zStream, Z_FINISH);
0206         if (ret == Z_STREAM_END) {
0207             break;
0208         }
0209         if (ret != Z_OK) {
0210             qWarning() << "zlib compression error!" << ret;
0211             break;
0212         }
0213         if (zStream.avail_out == 0) {
0214             // we could dynamically resize the output buffer here, but that
0215             // is already about twice the size of the data we want to compress
0216             // so we really shouldn't end up here
0217             qFatal("zlib output buffer underrun");
0218             break;
0219         }
0220     }
0221     zlibBlobData->resize(zlibBlobData->size() - zStream.avail_out);
0222 
0223     deflateEnd(&zStream);
0224     blob.set_raw_size((int32_t)rawBlobData.size());
0225 
0226     OSMPBF::BlobHeader header;
0227     header.set_type("OSMData");
0228     header.set_datasize((int32_t)blob.ByteSizeLong());
0229 
0230     auto blobHeaderSize = (int32_t)header.ByteSizeLong();
0231     blobHeaderSize = qToBigEndian(blobHeaderSize);
0232     m_io->write(reinterpret_cast<const char*>(&blobHeaderSize), sizeof(blobHeaderSize));
0233     m_io->write(header.SerializeAsString().c_str(), header.ByteSizeLong()); // TODO do this copy-free
0234     m_io->write(blob.SerializeAsString().c_str(), blob.ByteSizeLong()); // TODO do this copy-free
0235 
0236     m_block.reset();
0237     m_blockSizeEstimate = 0;
0238     m_stringTable.clear();
0239 }