File indexing completed on 2024-11-24 04:15:38

0001 /*
0002     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "marblegeometryassembler_p.h"
0008 #include "reassembly-logging.h"
0009 
0010 #include <cassert>
0011 
0012 using namespace KOSMIndoorMap;
0013 
0014 enum { NodeMatchDistance = 2 }; // in 1e7th of a degree, 46 for the old Marble data, very small for the new one
0015 
0016 /** Compare two coordinate while accounting for floating point noise. */
0017 static bool fuzzyEquals(OSM::Coordinate lhs, OSM::Coordinate rhs)
0018 {
0019     return std::abs((int32_t)lhs.latitude - (int32_t)rhs.latitude) <= NodeMatchDistance && std::abs((int32_t)lhs.longitude - (int32_t)rhs.longitude) <= NodeMatchDistance;
0020 }
0021 
0022 OSM::Id MarbleGeometryAssembler::s_nextInternalId = -(1ll << 32); // try to stay out of the way of the number space used by Marble tiles
0023 
0024 MarbleGeometryAssembler::MarbleGeometryAssembler() = default;
0025 MarbleGeometryAssembler::~MarbleGeometryAssembler() = default;
0026 
0027 void MarbleGeometryAssembler::setDataSet(OSM::DataSet* dataSet)
0028 {
0029     assert(dataSet);
0030     m_dataSet = dataSet;
0031     m_mxoidKey = m_dataSet->makeTagKey("mx:oid");
0032     m_typeKey = m_dataSet->makeTagKey("type");
0033 }
0034 
0035 void MarbleGeometryAssembler::merge(OSM::DataSetMergeBuffer *mergeBuffer)
0036 {
0037     assert(m_dataSet);
0038     m_nodeIdMap.clear();
0039     m_wayIdMap.clear();
0040     m_relIdMap.clear();
0041 
0042     std::vector<OSM::Way> prevPendingWays;
0043     std::swap(m_pendingWays, prevPendingWays);
0044 
0045     mergeNodes(mergeBuffer);
0046     deduplicateWays(mergeBuffer->ways);
0047     remapWayNodes(mergeBuffer->ways);
0048     mergeWays(mergeBuffer->ways);
0049     mergeWays(prevPendingWays);
0050     mergeRelations(mergeBuffer);
0051 
0052     mergeBuffer->clear();
0053 }
0054 
0055 void MarbleGeometryAssembler::finalize()
0056 {
0057     m_dataSet->ways.reserve(m_dataSet->ways.size() + m_pendingWays.size());
0058     for (auto &way : m_pendingWays) {
0059         if (!std::binary_search(m_dataSet->ways.begin(), m_dataSet->ways.end(), way)) {
0060             m_dataSet->ways.push_back(std::move(way));
0061         }
0062     }
0063     std::sort(m_dataSet->ways.begin(), m_dataSet->ways.end());
0064 }
0065 
0066 void MarbleGeometryAssembler::mergeNodes(OSM::DataSetMergeBuffer *mergeBuffer)
0067 {
0068     // nodes of the first batch are just taken as-is
0069     if (m_dataSet->nodes.empty()) {
0070         m_dataSet->nodes = std::move(mergeBuffer->nodes);
0071         std::sort(m_dataSet->nodes.begin(), m_dataSet->nodes.end());
0072         return;
0073     }
0074 
0075     // find nodes we already know
0076     // - for synthetic nodes those are collisions we need to remap
0077     // - for normal nodes, those are real duplicates and can be omitted
0078     // we do this in-place in the merge buffer, to avoid O(n^2) removal or insertion ops
0079     // (which matters here as this grows with the number of tiles we load)
0080     for (auto &node : mergeBuffer->nodes) {
0081         const auto it = std::lower_bound(m_dataSet->nodes.begin(), m_dataSet->nodes.end(), node);
0082         if (it != m_dataSet->nodes.end() && (*it).id == node.id) {
0083             if (node.id < 0) { // synthetic id collision, remap that
0084                 node.id = s_nextInternalId++;
0085                 m_nodeIdMap[(*it).id] = node.id;
0086             } else {
0087                 node.id = 0;
0088             }
0089         }
0090     }
0091 
0092     // append the new nodes (those not marked with id == 0), then sort the result
0093     m_dataSet->nodes.reserve(m_dataSet->nodes.size() + mergeBuffer->nodes.size());
0094     for (auto &node : mergeBuffer->nodes) {
0095         if (node.id) {
0096             m_dataSet->nodes.push_back(std::move(node));
0097         }
0098     }
0099     std::sort(m_dataSet->nodes.begin(), m_dataSet->nodes.end());
0100 }
0101 
0102 void MarbleGeometryAssembler::mergeWays(std::vector<OSM::Way> &ways)
0103 {
0104     // for ways we do:
0105     // 1. restore the original id
0106     // 2. if a way with that id already exists, we merge with the geometry of the existing one
0107 
0108     for (auto &way : ways) {
0109         if (way.id > 0 || way.nodes.empty()) { // not a synthetic id
0110             m_dataSet->addWay(std::move(way));
0111             continue;
0112         }
0113 
0114         const OSM::Id mxoid = takeMxOid(way);
0115         if (mxoid <= 0) { // shouldn't happen?
0116             m_dataSet->addWay(std::move(way));
0117             continue;
0118         }
0119 
0120         const auto syntheticId = way.id;
0121         way.id = mxoid;
0122 
0123         const auto it = std::lower_bound(m_dataSet->ways.begin(), m_dataSet->ways.end(), way);
0124         if (it != m_dataSet->ways.end() && (*it).id == way.id) {
0125             mergeWay(*it, way);
0126 
0127             if (way.nodes.empty()) {
0128                 // way was fully merged
0129                 m_wayIdMap[syntheticId] = mxoid;
0130             } else {
0131                 // defer to later (ie. more tiles loaded)
0132                 way.id = syntheticId;
0133                 OSM::setTagValue(way, m_mxoidKey, QByteArray::number((qlonglong)mxoid));
0134                 m_pendingWays.push_back(std::move(way));
0135             }
0136 
0137         } else {
0138             m_wayIdMap[syntheticId] = mxoid;
0139             m_dataSet->ways.insert(it, std::move(way));
0140         }
0141     }
0142 }
0143 
0144 static bool isDuplicateWay(const OSM::Way &lhs, const OSM::Way &rhs)
0145 {
0146     if (lhs.nodes.size() != rhs.nodes.size()) {
0147         return false;
0148     }
0149     for (std::size_t i = 0; i < lhs.nodes.size(); ++i) {
0150         if (lhs.nodes[i] != rhs.nodes[i] && (lhs.nodes[i] > 0 || rhs.nodes[i] > 0)) {
0151             return false;
0152         }
0153     }
0154     return true;
0155 }
0156 
0157 void MarbleGeometryAssembler::deduplicateWays(std::vector<OSM::Way>& ways)
0158 {
0159     for (auto it = ways.begin(); it != ways.end();) {
0160         if ((*it).id > 0) {
0161             ++it;
0162             continue;
0163         }
0164         const OSM::Id mxoid = OSM::tagValue(*it, m_mxoidKey).toLongLong();
0165         if (mxoid == 0) {
0166             ++it;
0167             continue;
0168         }
0169 
0170         const auto duplIt = m_duplicateWays.find(mxoid);
0171         if (duplIt != m_duplicateWays.end()) {
0172             bool found = false;
0173             for (auto it2 = (*duplIt).second.begin(); it2 != (*duplIt).second.end(); ++it2) {
0174                 if (isDuplicateWay(*it, ways[*it2])) {
0175                     m_wayIdMap[(*it).id] = mxoid;
0176                     qCDebug(ReassemblyLog) << "removing duplicate way:" << (*it).id << (*it).url();
0177                     it = ways.erase(it);
0178                     found = true;
0179                     break;
0180                 }
0181             }
0182             if (!found) {
0183                 m_duplicateWays[mxoid].push_back(std::distance(ways.begin(), it));
0184                 ++it;
0185             }
0186         } else {
0187             m_duplicateWays[mxoid] = {(std::size_t)std::distance(ways.begin(), it)};
0188             ++it;
0189         }
0190     }
0191     m_duplicateWays.clear();
0192 }
0193 
0194 void MarbleGeometryAssembler::remapWayNodes(std::vector<OSM::Way> &ways) const
0195 {
0196     if (m_nodeIdMap.empty()) {
0197         return;
0198     }
0199 
0200     for (auto &way : ways) {
0201         for (auto &id : way.nodes) {
0202             if (id > 0) {
0203                 continue;
0204             }
0205             const auto it = m_nodeIdMap.find(id);
0206             if (it != m_nodeIdMap.end()) {
0207                 id = (*it).second;
0208             }
0209         }
0210     }
0211 }
0212 
0213 void MarbleGeometryAssembler::mergeWay(OSM::Way &way, OSM::Way &otherWay) const
0214 {
0215     // for merging two ways:
0216     // - non-synthetic nodes remain unchanged, ways can only be merged on synthetic nodes
0217     // - synthetic nodes are duplicated in both sets, we need to merge them by coordinate comparison
0218     // - synthetic nodes can be removed, and so can edges between two adjacent synthetic nodes
0219     // - closed polygons at least have one shared edge (possibly multiple adjacent or non-adjacent ones)
0220     // - lines can only be merged at the beginning or the end, a line crossing the same boundary multiple times would be split at every boundary intersection
0221     // - we can assume polygon orientation is preserved by the splitting
0222 
0223     // in some cases the Marble tile generator produces both ways and areas for the same polygon
0224     // e.g. for something way-like itself used in a multipolygon relations, with both being tagged
0225     // we ignore the way in that case and rely solely on area merging
0226 
0227     if (!way.isClosed() && !otherWay.isClosed()) {
0228         mergeLine(way, otherWay);
0229     } else if (way.isClosed() && !otherWay.isClosed()) {
0230         return;
0231     } else if (!way.isClosed() && otherWay.isClosed()) {
0232         std::swap(way, otherWay);
0233         return;
0234     } else {
0235         way.nodes = mergeArea(way, otherWay);
0236     }
0237 }
0238 
0239 void MarbleGeometryAssembler::mergeLine(OSM::Way &way, OSM::Way &otherWay) const
0240 {
0241     const auto begin1 = m_dataSet->node(way.nodes.front());
0242     const auto end1 = m_dataSet->node(way.nodes.back());
0243     const auto begin2 = m_dataSet->node(otherWay.nodes.front());
0244     const auto end2 = m_dataSet->node(otherWay.nodes.back());
0245     if (!begin1 || !end1 || !begin2 || !end2) {
0246         qDebug() << "failed to find way nodes!?" << begin1 << end1 << begin2 << end2;;
0247         return;
0248     }
0249 
0250     // only merge ways on synthetic nodes (the < 0 checks)
0251     // it is possible to end up here with mergable non-synthetic nodes, in case of a circular
0252     // path that the Marble tile generator classified as a line rather than an area
0253     // in this case we do not want to merge the nodes as that would eliminate that node
0254 
0255     way.nodes.reserve(way.nodes.size() + otherWay.nodes.size() - 2);
0256     if (end1->id < 0 && begin2->id < 0 && fuzzyEquals(end1->coordinate, begin2->coordinate)) {
0257         way.nodes.pop_back();
0258         std::copy(std::next(otherWay.nodes.begin()), otherWay.nodes.end(), std::back_inserter(way.nodes));
0259     } else if (end1->id < 0 && end2->id < 0 && fuzzyEquals(end1->coordinate, end2->coordinate)) {
0260         way.nodes.pop_back();
0261         std::copy(std::next(otherWay.nodes.rbegin()), otherWay.nodes.rend(), std::back_inserter(way.nodes));
0262     } else if (begin1->id < 0 && end2->id < 0 && fuzzyEquals(begin1->coordinate, end2->coordinate)) {
0263         way.nodes.erase(way.nodes.begin());
0264         way.nodes.insert(way.nodes.begin(), otherWay.nodes.begin(), std::prev(otherWay.nodes.end()));
0265     } else if (begin1->id < 0 && begin2->id < 0 && fuzzyEquals(begin1->coordinate, begin2->coordinate)) {
0266         way.nodes.erase(way.nodes.begin());
0267         way.nodes.insert(way.nodes.begin(), otherWay.nodes.rbegin(), std::prev(otherWay.nodes.rend()));
0268     } else {
0269         //qDebug() << "unable to merge line:" << begin1->coordinate << end1->coordinate << begin2->coordinate << end2->coordinate;
0270         return;
0271     }
0272 
0273     otherWay.nodes.clear(); // for the caller to notice we succeeded here
0274 }
0275 
0276 std::vector<OSM::Id> MarbleGeometryAssembler::mergeArea(OSM::Way &way, OSM::Way &otherWay) const
0277 {
0278     // sanity checks for assumptions below
0279     if (way.nodes.size() < 3 || otherWay.nodes.size() < 3 || !way.isClosed() || !otherWay.isClosed()) {
0280         qCWarning(ReassemblyLog()) << "got invalid polygons!" << way.url() << way.nodes.size() << way.isClosed() << otherWay.url() << otherWay.nodes.size() << otherWay.isClosed();
0281         return way.nodes.empty() ? std::move(way.nodes) : std::move(otherWay.nodes);
0282     }
0283 
0284     // "open" the closed polygons (otherwise we have to special-case the closing edges in both ways below)
0285     way.nodes.pop_back();
0286     otherWay.nodes.pop_back();
0287 
0288     std::vector<OSM::Id> nodes;
0289     mergeAreaSection(nodes, way.nodes, way.nodes.begin(), otherWay.nodes);
0290 
0291     // re-close the polygon
0292     if (!nodes.empty()) {
0293         nodes.push_back(nodes.front());
0294     }
0295 
0296     // if otherWay is still populated we failed to find a connecting node
0297     // this can happen in a number of cases, such as the same area entering a tile
0298     // twice but its connecting part still being in an not yet loaded tile
0299     // we defer processing those to later, so just re-close the polygon here
0300     if (!otherWay.nodes.empty()) {
0301         otherWay.nodes.push_back(otherWay.nodes.front());
0302     }
0303     return nodes;
0304 }
0305 
0306 bool MarbleGeometryAssembler::mergeAreaSection(std::vector<OSM::Id> &assembledPath, std::vector<OSM::Id> &path, const std::vector<OSM::Id>::iterator &pathBegin, std::vector<OSM::Id> &otherPath) const
0307 {
0308     for (auto nodeIt = pathBegin; nodeIt != path.end(); ++nodeIt) {
0309         if ((*nodeIt) >= 0) { // not synthetic
0310             continue;
0311         }
0312         const auto node = m_dataSet->node(*nodeIt);
0313         if (!node) { // should not happen?
0314             qCDebug(ReassemblyLog) << "could not find node" << (*nodeIt);
0315             continue;
0316         }
0317 
0318         // TODO orientation change?
0319         // synthetic node, find a matching one in the other way and splice in that way
0320         for (auto otherNodeIt = otherPath.begin(); otherNodeIt != otherPath.end(); ++otherNodeIt) {
0321             if ((*otherNodeIt) >= 0) {
0322                 continue;
0323             }
0324 
0325             const auto otherNode = m_dataSet->node(*otherNodeIt);
0326             if (!otherNode) {
0327                 qCDebug(ReassemblyLog) << "could not find node" << (*otherNodeIt);
0328                 continue;
0329             }
0330 
0331             if (!fuzzyEquals(node->coordinate, otherNode->coordinate)) {
0332                 continue;
0333             }
0334 
0335             // found a matching synthetic node, continue in the other path
0336             std::copy(pathBegin, nodeIt, std::back_inserter(assembledPath));
0337             nodeIt = path.erase(pathBegin, ++nodeIt);
0338             if (nodeIt == path.end()) {
0339                 nodeIt = path.begin();
0340             }
0341             otherNodeIt = otherPath.erase(otherNodeIt);
0342             if (otherNodeIt == otherPath.end()) {
0343                 otherNodeIt = otherPath.begin();
0344             }
0345             // if otherPath was completely consumed, it would have switched back to us with its closing edge
0346             // so add the remaining part of path here
0347             if (mergeAreaSection(assembledPath, otherPath, otherNodeIt, path)) {
0348 //                 qDebug() << "      processing trailing path";
0349                 std::copy(nodeIt, path.end(), std::back_inserter(assembledPath));
0350                 std::copy(path.begin(), nodeIt, std::back_inserter(assembledPath));
0351                 path.clear();
0352             }
0353             return false;
0354         }
0355 
0356     }
0357 
0358     // copy the final segment
0359     std::copy(pathBegin, path.end(), std::back_inserter(assembledPath));
0360     path.erase(pathBegin, path.end());
0361 
0362     // wrap around when starting in the middle (can happen on the secondary path)
0363     if (!path.empty()) {
0364         return mergeAreaSection(assembledPath, path, path.begin(), otherPath);
0365     }
0366 
0367     return path.empty();
0368 }
0369 
0370 void MarbleGeometryAssembler::mergeRelations(OSM::DataSetMergeBuffer *mergeBuffer)
0371 {
0372     // for relations we do:
0373     // 1. restore the original id
0374     // 2. replace all member ids with the restored ids for ways/relations
0375     // 3. if a relations with the restored id already exists, merge with its content
0376 
0377     for (auto &rel : mergeBuffer->relations) {
0378         const OSM::Id mxoid = takeMxOid(rel);
0379         if (mxoid <= 0) { // shouldn't happen?
0380             m_dataSet->addRelation(std::move(rel));
0381             continue;
0382         }
0383 
0384         m_relIdMap[rel.id] = mxoid;
0385         rel.id = mxoid;
0386 
0387         for (auto &member : rel.members) {
0388             if (member.id >= 0) { // not a synthetic id
0389                 continue;
0390             }
0391 
0392             if (member.type() == OSM::Type::Node) {
0393                 const auto it = m_nodeIdMap.find(member.id);
0394                 if (it != m_nodeIdMap.end()) {
0395                     member.id = (*it).second;
0396                 }
0397             } else if (member.type() == OSM::Type::Way) {
0398                 const auto it = m_wayIdMap.find(member.id);
0399                 if (it != m_wayIdMap.end()) {
0400                     member.id = (*it).second;
0401                 }
0402             } else if (member.type() == OSM::Type::Relation) {
0403                 const auto it = m_relIdMap.find(member.id);
0404                 if (it != m_relIdMap.end()) {
0405                     member.id = (*it).second;
0406                 }
0407             }
0408         }
0409 
0410         const auto it = std::lower_bound(m_dataSet->relations.begin(), m_dataSet->relations.end(), rel);
0411         if (it != m_dataSet->relations.end() && (*it).id == rel.id) {
0412             mergeRelation(*it, rel);
0413         } else {
0414             m_dataSet->relations.insert(it, std::move(rel));
0415         }
0416     }
0417 }
0418 
0419 void MarbleGeometryAssembler::mergeRelation(OSM::Relation& relation, const OSM::Relation& otherRelation) const
0420 {
0421     for (auto &otherMember : otherRelation.members) {
0422         const auto it = std::find(relation.members.begin(), relation.members.end(), otherMember);
0423         if (it == relation.members.end()) {
0424             relation.members.push_back(otherMember);
0425         }
0426     }
0427 
0428     // multi-polygons can exist entirely out of synthetic polygons, e.g. if the source was a set of lines rather than closed polygons
0429     // merging those would not have happened before (as we wouldn't know what to merge it with), so we need to do this here
0430     if (OSM::tagValue(relation, m_typeKey) == "multipolygon") {
0431         for (auto it = relation.members.begin(); it != relation.members.end();) {
0432             if ((*it).id > 0 || (*it).type() != OSM::Type::Way) {
0433                 ++it;
0434                 continue;
0435             }
0436             // TODO look up role names once via DataSet::role
0437             if (std::strcmp((*it).role().name(), "outer") != 0 && std::strcmp((*it).role().name(), "inner") != 0) {
0438                 ++it;
0439                 continue;
0440             }
0441 
0442             auto way = m_dataSet->way((*it).id);
0443             if (!way || !way->isClosed()) {
0444                 ++it;
0445                 continue;
0446             }
0447 
0448             bool merged = false;
0449             for (auto it2 = std::next(it); it2 != relation.members.end(); ++it2) {
0450                 if ((*it2).id > 0 || (*it2).type() != OSM::Type::Way || (*it2).role() != (*it).role()) {
0451                     continue;
0452                 }
0453 
0454                 auto otherWay = m_dataSet->way((*it2).id);
0455                 if (!otherWay || !otherWay->isClosed()) {
0456                     continue;
0457                 }
0458 
0459                 way->nodes = mergeArea(*way, *otherWay);
0460                 if (otherWay->nodes.empty()) {
0461                     relation.members.erase(it2);
0462                     merged = true;
0463                     break;
0464                 }
0465             }
0466             if (!merged) {
0467                 ++it;
0468             }
0469         }
0470     }
0471 }
0472 
0473 template<typename Elem>
0474 OSM::Id MarbleGeometryAssembler::takeMxOid(Elem &elem) const
0475 {
0476     const auto it = std::lower_bound(elem.tags.begin(), elem.tags.end(), m_mxoidKey, [](const auto &lhs, const auto &rhs) { return lhs.key < rhs; });
0477     if (it != elem.tags.end() && (*it).key == m_mxoidKey) {
0478         bool result = false;
0479         const OSM::Id id = (*it).value.toLongLong(&result);
0480         if (result) {
0481             elem.tags.erase(it);
0482             return id;
0483         }
0484     }
0485     return {};
0486 }