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 }