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 }