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 }