File indexing completed on 2024-05-19 03:53:14
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2016 Dennis Nienhüser <nienhueser@kde.org> 0004 // 0005 0006 #include "O5mWriter.h" 0007 0008 #include "GeoDataDocument.h" 0009 #include "GeoDataLineString.h" 0010 #include "GeoDataLinearRing.h" 0011 #include "GeoDataPlacemark.h" 0012 #include "GeoDataRelation.h" 0013 #include "GeoDataPolygon.h" 0014 #include "GeoDataBuilding.h" 0015 #include "GeoDataMultiGeometry.h" 0016 #include "GeoWriter.h" 0017 #include "osm/OsmPlacemarkData.h" 0018 0019 #include <QDataStream> 0020 #include <QBuffer> 0021 0022 namespace Marble 0023 { 0024 0025 QSet<QString> O5mWriter::m_blacklistedTags; 0026 0027 bool O5mWriter::write(QIODevice *device, const GeoDataDocument &document) 0028 { 0029 if (!device || !device->isWritable()) { 0030 return false; 0031 } 0032 0033 OsmConverter converter; 0034 converter.read(&document); 0035 0036 QDataStream stream(device); 0037 writeHeader(stream); 0038 writeNodes(converter.nodes(), stream); 0039 writeWays(converter.ways(), stream); 0040 writeRelations(converter.relations(), stream); 0041 writeTrailer(stream); 0042 0043 return true; 0044 } 0045 0046 void O5mWriter::writeHeader(QDataStream &stream) const 0047 { 0048 stream << qint8(0xff); // o5m file start indicator 0049 stream << qint8(0xe0); // o5m header block indicator 0050 stream << qint8(0x04) << qint8(0x6f) << qint8(0x35) << qint8(0x6d) << qint8(0x32); // o5m header 0051 } 0052 0053 void O5mWriter::writeNodes(const OsmConverter::Nodes &nodes, QDataStream &stream) const 0054 { 0055 if (nodes.empty()) { 0056 return; 0057 } 0058 0059 stream << qint8(0xff); // reset delta encoding counters 0060 StringTable stringTable; 0061 qint64 lastId = 0; 0062 double lastLon = 0.0; 0063 double lastLat = 0.0; 0064 0065 QByteArray bufferData; 0066 QBuffer buffer(&bufferData); 0067 for(auto const & node: nodes) { 0068 if (node.second.id() == lastId) { 0069 continue; 0070 } 0071 0072 stream << qint8(0x10); // node section start indicator 0073 0074 bufferData.clear(); 0075 buffer.open(QIODevice::WriteOnly); 0076 QDataStream bufferStream(&buffer); 0077 0078 OsmPlacemarkData const & osmData = node.second; 0079 qint64 idDiff = osmData.id() - lastId; 0080 writeSigned(idDiff, bufferStream); 0081 writeVersion(osmData, bufferStream); 0082 GeoDataCoordinates const & coordinates = node.first; 0083 double const lon = coordinates.longitude(GeoDataCoordinates::Degree); 0084 double const lat = coordinates.latitude(GeoDataCoordinates::Degree); 0085 writeSigned(deltaTo(lon, lastLon), bufferStream); 0086 writeSigned(deltaTo(lat, lastLat), bufferStream); 0087 writeTags(osmData, stringTable, bufferStream); 0088 0089 buffer.close(); 0090 writeUnsigned(bufferData.size(), stream); 0091 stream.writeRawData(bufferData.constData(), bufferData.size()); 0092 0093 lastId = osmData.id(); 0094 lastLon = lon; 0095 lastLat = lat; 0096 } 0097 } 0098 0099 void O5mWriter::writeWays(const OsmConverter::Ways &ways, QDataStream &stream) const 0100 { 0101 if (ways.empty()) { 0102 return; 0103 } 0104 0105 stream << qint8(0xff); // reset delta encoding counters 0106 StringTable stringTable; 0107 qint64 lastId = 0; 0108 qint64 lastReferenceId = 0; 0109 0110 QByteArray bufferData; 0111 QBuffer buffer(&bufferData); 0112 QByteArray referencesBufferData; 0113 QBuffer referencesBuffer(&referencesBufferData); 0114 for (auto const & way: ways) { 0115 Q_ASSERT(way.first); 0116 if (way.second.id() == lastId) { 0117 continue; 0118 } 0119 0120 stream << qint8(0x11); // way start indicator 0121 OsmPlacemarkData const & osmData = way.second; 0122 0123 bufferData.clear(); 0124 buffer.open(QIODevice::WriteOnly); 0125 QDataStream bufferStream(&buffer); 0126 0127 qint64 idDiff = osmData.id() - lastId; 0128 writeSigned(idDiff, bufferStream); 0129 lastId = osmData.id(); 0130 writeVersion(osmData, bufferStream); 0131 0132 referencesBufferData.clear(); 0133 referencesBuffer.open(QIODevice::WriteOnly); 0134 QDataStream referencesStream(&referencesBuffer); 0135 writeReferences(*way.first, lastReferenceId, osmData, referencesStream); 0136 referencesBuffer.close(); 0137 writeUnsigned(referencesBufferData.size(), bufferStream); 0138 bufferStream.writeRawData(referencesBufferData.constData(), referencesBufferData.size()); 0139 0140 writeTags(osmData, stringTable, bufferStream); 0141 0142 buffer.close(); 0143 writeUnsigned(bufferData.size(), stream); 0144 stream.writeRawData(bufferData.constData(), bufferData.size()); 0145 } 0146 } 0147 0148 void O5mWriter::writeRelations(const OsmConverter::Relations &relations, QDataStream &stream) const 0149 { 0150 if (relations.empty()) { 0151 return; 0152 } 0153 0154 stream << qint8(0xff); // reset delta encoding counters 0155 StringTable stringTable; 0156 qint64 lastId = 0; 0157 qint64 lastReferenceId[3] = { 0, 0, 0 }; 0158 0159 QByteArray bufferData; 0160 QBuffer buffer(&bufferData); 0161 QByteArray referencesBufferData; 0162 QBuffer referencesBuffer(&referencesBufferData); 0163 for (auto const & relation: relations) { 0164 if (relation.second.id() == lastId) { 0165 continue; 0166 } 0167 0168 stream << qint8(0x12); // relation start indicator 0169 OsmPlacemarkData const & osmData = relation.second; 0170 0171 bufferData.clear(); 0172 buffer.open(QIODevice::WriteOnly); 0173 QDataStream bufferStream(&buffer); 0174 0175 qint64 idDiff = osmData.id() - lastId; 0176 writeSigned(idDiff, bufferStream); 0177 lastId = osmData.id(); 0178 writeVersion(osmData, bufferStream); 0179 0180 referencesBufferData.clear(); 0181 referencesBuffer.open(QIODevice::WriteOnly); 0182 QDataStream referencesStream(&referencesBuffer); 0183 if (const auto placemark = geodata_cast<GeoDataPlacemark>(relation.first)) { 0184 if (const auto building = geodata_cast<GeoDataBuilding>(placemark->geometry())) { 0185 auto polygon = geodata_cast<GeoDataPolygon>(& static_cast<const GeoDataMultiGeometry*>(building->multiGeometry())->at(0)); 0186 Q_ASSERT(polygon); 0187 writeMultipolygonMembers(*polygon, lastReferenceId, osmData, stringTable, referencesStream); 0188 } else { 0189 auto polygon = geodata_cast<GeoDataPolygon>(placemark->geometry()); 0190 Q_ASSERT(polygon); 0191 writeMultipolygonMembers(*polygon, lastReferenceId, osmData, stringTable, referencesStream); 0192 } 0193 } else if (const auto placemark = geodata_cast<GeoDataRelation>(relation.first)) { 0194 writeRelationMembers(placemark, lastReferenceId, osmData, stringTable, referencesStream); 0195 } else { 0196 Q_ASSERT(false); 0197 } 0198 referencesBuffer.close(); 0199 writeUnsigned(referencesBufferData.size(), bufferStream); 0200 bufferStream.writeRawData(referencesBufferData.constData(), referencesBufferData.size()); 0201 0202 writeTags(osmData, stringTable, bufferStream); 0203 0204 buffer.close(); 0205 writeUnsigned(bufferData.size(), stream); 0206 stream.writeRawData(bufferData.constData(), bufferData.size()); 0207 } 0208 } 0209 0210 void O5mWriter::writeTrailer(QDataStream &stream) const 0211 { 0212 stream << qint8(0xfe); // o5m file end indicator 0213 } 0214 0215 void O5mWriter::writeMultipolygonMembers(const GeoDataPolygon &polygon, qint64 (&lastId)[3], const OsmPlacemarkData &osmData, StringTable &stringTable, QDataStream &stream) const 0216 { 0217 qint64 id = osmData.memberReference(-1).id(); 0218 qint64 idDiff = id - lastId[(int)OsmType::Way]; 0219 writeSigned(idDiff, stream); 0220 lastId[(int)OsmType::Way] = id; 0221 writeStringPair(StringPair("1outer", QString()), stringTable, stream); // type=way, role=outer 0222 for (int index = 0; index < polygon.innerBoundaries().size(); ++index) { 0223 id = osmData.memberReference( index ).id(); 0224 qint64 idDiff = id - lastId[(int)OsmType::Way]; 0225 writeSigned(idDiff, stream); 0226 writeStringPair(StringPair("1inner", QString()), stringTable, stream); // type=way, role=inner 0227 lastId[(int)OsmType::Way] = id; 0228 } 0229 } 0230 0231 void O5mWriter::writeRelationMembers(const GeoDataRelation *relation, qint64 (&lastId)[3], const OsmPlacemarkData &osmData, O5mWriter::StringTable &stringTable, QDataStream &stream) const 0232 { 0233 Q_UNUSED(relation); 0234 for (auto iter = osmData.relationReferencesBegin(), end = osmData.relationReferencesEnd(); iter != end; ++iter) { 0235 qint64 id = iter.key().id; 0236 qint64 idDiff = id - lastId[(int)iter.key().type]; 0237 writeSigned(idDiff, stream); 0238 const QString key = QLatin1Char('0' + (int)iter.key().type) + iter.value(); 0239 writeStringPair(StringPair(key, QString()), stringTable, stream); 0240 lastId[(int)iter.key().type] = id; 0241 } 0242 } 0243 0244 void O5mWriter::writeReferences(const GeoDataLineString &lineString, qint64 &lastId, const OsmPlacemarkData &osmData, QDataStream &stream) const 0245 { 0246 QVector<GeoDataCoordinates>::const_iterator it = lineString.constBegin(); 0247 QVector<GeoDataCoordinates>::ConstIterator const end = lineString.constEnd(); 0248 0249 for ( ; it != end; ++it ) { 0250 qint64 id = osmData.nodeReference( *it ).id(); 0251 qint64 idDiff = id - lastId; 0252 writeSigned(idDiff, stream); 0253 lastId = id; 0254 } 0255 0256 if (!lineString.isEmpty() && lineString.isClosed()) { 0257 auto const startId = osmData.nodeReference(lineString.first()).id(); 0258 auto const endId = osmData.nodeReference(lineString.last()).id(); 0259 if (startId != endId) { 0260 qint64 idDiff = startId - lastId; 0261 writeSigned(idDiff, stream); 0262 lastId = startId; 0263 } 0264 } 0265 } 0266 0267 void O5mWriter::writeVersion(const OsmPlacemarkData &, QDataStream &stream) const 0268 { 0269 stream << qint8(0x00); // no version information 0270 /** @todo implement */ 0271 } 0272 0273 void O5mWriter::writeTags(const OsmPlacemarkData &osmData, StringTable &stringTable, QDataStream &stream) const 0274 { 0275 if (m_blacklistedTags.isEmpty()) { 0276 m_blacklistedTags << QStringLiteral("mx:version"); 0277 m_blacklistedTags << QStringLiteral("mx:changeset"); 0278 m_blacklistedTags << QStringLiteral("mx:uid"); 0279 m_blacklistedTags << QStringLiteral("mx:visible"); 0280 m_blacklistedTags << QStringLiteral("mx:user"); 0281 m_blacklistedTags << QStringLiteral("mx:timestamp"); 0282 m_blacklistedTags << QStringLiteral("mx:action"); 0283 } 0284 0285 for (auto iter=osmData.tagsBegin(), end = osmData.tagsEnd(); iter != end; ++iter) { 0286 if (!m_blacklistedTags.contains(iter.key())) { 0287 writeStringPair(StringPair(iter.key(), iter.value()), stringTable, stream); 0288 } 0289 } 0290 } 0291 0292 void O5mWriter::writeStringPair(const StringPair &pair, StringTable &stringTable, QDataStream &stream) const 0293 { 0294 Q_ASSERT(stringTable.size() <= 15000); 0295 auto const iter = stringTable.constFind(pair); 0296 if (iter == stringTable.cend()) { 0297 m_stringPairBuffer.clear(); 0298 m_stringPairBuffer.push_back(char(0x00)); 0299 m_stringPairBuffer.push_back(pair.first.toUtf8()); 0300 if (!pair.second.isEmpty()) { 0301 m_stringPairBuffer.push_back(char(0x00)); 0302 m_stringPairBuffer.push_back(pair.second.toUtf8()); 0303 } 0304 m_stringPairBuffer.push_back(char(0x00)); 0305 stream.writeRawData(m_stringPairBuffer.constData(), m_stringPairBuffer.size()); 0306 bool const tooLong = (m_stringPairBuffer.size() - (pair.second.isEmpty() ? 2 : 3)) > 250; 0307 bool const tableFull = stringTable.size() > 15000; 0308 Q_ASSERT(!tableFull); 0309 if (!tooLong && !tableFull) { 0310 /* When the table is full, old values could be re-used. 0311 * See o5m spec. This is only relevant for large files and would 0312 * need some kind of string popularity to be effective though. */ 0313 stringTable.insert(pair, stringTable.size()); 0314 } 0315 } else { 0316 auto const reference = stringTable.size() - iter.value(); 0317 Q_ASSERT(reference > 0); 0318 Q_ASSERT(reference <= stringTable.size()); 0319 writeUnsigned(reference, stream); 0320 } 0321 } 0322 0323 void O5mWriter::writeSigned(qint64 value, QDataStream &stream) const 0324 { 0325 bool const negative = value < 0; 0326 if (negative) { 0327 value = -value - 1; 0328 } 0329 quint8 word = (value >> 6) > 0 ? (1<<7) : 0; 0330 word |= ( (value << 1) & 0x7e); 0331 if (negative) { 0332 word |= 0x01; 0333 } 0334 value >>= 6; 0335 stream << word; 0336 0337 while (value > 0) { 0338 word = ((value >> 7) > 0 ? 0x80 : 0x00) | (value & 0x7f); 0339 stream << word; 0340 value >>= 7; 0341 } 0342 } 0343 0344 void O5mWriter::writeUnsigned(quint32 value, QDataStream &stream) const 0345 { 0346 do { 0347 quint8 word = ((value >> 7) > 0 ? 0x80 : 0x00) | (value & 0x7f); 0348 stream << word; 0349 value >>= 7; 0350 } while (value > 0); 0351 } 0352 0353 qint32 O5mWriter::deltaTo(double value, double previous) const 0354 { 0355 double const diff = value - previous; 0356 return qRound(diff * 1e7); 0357 } 0358 0359 MARBLE_ADD_WRITER(O5mWriter, "o5m") 0360 0361 }