File indexing completed on 2024-11-24 04:15:37
0001 /* 0002 SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "boundarysearch_p.h" 0008 0009 #include <osm/datatypes.h> 0010 #include <osm/element.h> 0011 #include <osm/geomath.h> 0012 0013 #include <QDebug> 0014 0015 using namespace KOSMIndoorMap; 0016 0017 enum { 0018 BoundingBoxMargin = 100, // margin around the final result, in meters 0019 BoundingBoxMaxSize = 2000, // maximum width/height of the bounding box, in meters 0020 }; 0021 0022 void BoundarySearch::init(OSM::Coordinate coord) 0023 { 0024 m_center = coord; 0025 0026 m_bbox = {coord, coord}; 0027 m_relevantIds.clear(); 0028 } 0029 0030 static OSM::Id actualId(OSM::Element e, OSM::TagKey mxoidTag) 0031 { 0032 const auto mxoid = e.tagValue(mxoidTag); 0033 if (!mxoid.isEmpty()) { 0034 return mxoid.toLongLong(); 0035 } 0036 return e.id(); 0037 } 0038 0039 static bool isPolygon(OSM::Element e) 0040 { 0041 return (e.type() == OSM::Type::Way && e.way()->isClosed()) 0042 || (e.type() == OSM::Type::Relation && e.tagValue("type") == "multipolygon"); 0043 } 0044 0045 void BoundarySearch::resolveTagKeys(const OSM::DataSet& dataSet) 0046 { 0047 // cache tag keys for fast lookup 0048 m_tag.mxoid = dataSet.tagKey("mx:oid"); 0049 m_tag.building = dataSet.tagKey("building"); 0050 m_tag.railway = dataSet.tagKey("railway"); 0051 m_tag.aeroway = dataSet.tagKey("aeroway"); 0052 m_tag.public_transport = dataSet.tagKey("public_transport"); 0053 m_tag.amenity = dataSet.tagKey("amenity"); 0054 m_tag.tourism = dataSet.tagKey("tourism"); 0055 m_tag.leisure = dataSet.tagKey("leisure"); 0056 m_tag.shop = dataSet.tagKey("shop"); 0057 } 0058 0059 bool BoundarySearch::isRelevantPolygon(const OSM::Element e) const 0060 { 0061 if (!isPolygon(e) || !OSM::contains(e.boundingBox(), m_center)) { 0062 return false; 0063 } 0064 0065 return !e.tagValue(m_tag.amenity).isEmpty() 0066 || !e.tagValue(m_tag.tourism).isEmpty() 0067 || !e.tagValue(m_tag.leisure).isEmpty() 0068 || !e.tagValue(m_tag.shop).isEmpty(); 0069 } 0070 0071 /* There's a number of critieria being considered here: 0072 * - a certain minimum radius around center (see BoundingBoxMargin) 0073 * - an upper limit (BoundingBoxMaxSize), to avoid this growing out of control 0074 * - buildings, stations or airports containing the center position 0075 * -- for now with manual geometry re-assmbly from Marble vector tiles, ideally this will happen in a general step beforehand 0076 * - relevant elements (e.g. platforms or terminal buildings) in the vicinity of center (TODO) 0077 */ 0078 OSM::BoundingBox BoundarySearch::boundingBox(const OSM::DataSet& dataSet) 0079 { 0080 resolveTagKeys(dataSet); 0081 0082 if (m_relevantIds.empty()) { // first pass over the center tile 0083 OSM::for_each(dataSet, [this, &dataSet](OSM::Element e) { 0084 if (!e.boundingBox().isValid()) { 0085 e.recomputeBoundingBox(dataSet); 0086 } 0087 0088 const bool isRelevant = !e.tagValue(m_tag.building).isEmpty() 0089 || !e.tagValue(m_tag.railway).isEmpty() 0090 || !e.tagValue(m_tag.aeroway).isEmpty() 0091 || isRelevantPolygon(e); 0092 0093 if (!isRelevant) { 0094 return; 0095 } 0096 0097 m_relevantIds.insert(actualId(e, m_tag.mxoid)); 0098 m_bbox = OSM::unite(m_bbox, e.boundingBox()); 0099 }, OSM::IncludeRelations | OSM::IncludeWays); 0100 } 0101 0102 OSM::BoundingBox bbox = m_bbox; 0103 OSM::for_each(dataSet, [&](OSM::Element e) { 0104 const auto railwayValue = e.tagValue(m_tag.railway); 0105 const bool isStation = railwayValue == "station" 0106 || railwayValue == "platform" 0107 || e.tagValue(m_tag.building) == "train_station" 0108 || e.tagValue(m_tag.public_transport) == "platform"; 0109 const bool isAirport = (e.tagValue(m_tag.aeroway) == "aerodrome"); 0110 const bool isRelevant = isRelevantPolygon(e); 0111 if (!isStation && !isAirport && !isRelevant) { 0112 return; 0113 } 0114 0115 e.recomputeBoundingBox(dataSet); // unconditionally as this obviously grows as we load more data 0116 0117 if (m_relevantIds.count(actualId(e, m_tag.mxoid))) { 0118 m_bbox = OSM::unite(m_bbox, e.boundingBox()); 0119 bbox = OSM::unite(m_bbox, bbox); 0120 } else if (!isRelevant && OSM::intersects(e.boundingBox(), m_bbox)) { 0121 bbox = OSM::unite(bbox, e.boundingBox()); 0122 } 0123 }, OSM::IncludeRelations | OSM::IncludeWays); 0124 0125 return clampBoundingBox(growBoundingBox(bbox, BoundingBoxMargin), BoundingBoxMaxSize); 0126 } 0127 0128 OSM::BoundingBox BoundarySearch::growBoundingBox(const OSM::BoundingBox &bbox, double meters) const 0129 { 0130 const auto dlon = meters / OSM::distance(m_center.latF(), 0.0, m_center.latF(), 1.0); 0131 const auto dlat = meters / OSM::distance(0.0, m_center.lonF(), 1.0, m_center.lonF()); 0132 return OSM::BoundingBox(OSM::Coordinate(bbox.min.latF() - dlat, bbox.min.lonF() - dlon), 0133 OSM::Coordinate(bbox.max.latF() + dlat, bbox.max.lonF() + dlon)); 0134 } 0135 0136 OSM::BoundingBox BoundarySearch::clampBoundingBox(const OSM::BoundingBox &bbox, double meters) const 0137 { 0138 // TODO don'T do this around the bbox center, but biased towards m_center 0139 const auto dlon = std::max(0.0, bbox.widthF() - (meters / OSM::distance(m_center.latF(), 0.0, m_center.latF(), 1.0))) / 2.0; 0140 const auto dlat = std::max(0.0, bbox.heightF() - (meters / OSM::distance(0.0, m_center.lonF(), 1.0, m_center.lonF()))) / 2.0; 0141 return OSM::BoundingBox(OSM::Coordinate(bbox.min.latF() + dlat, bbox.min.lonF() + dlon), 0142 OSM::Coordinate(bbox.max.latF() - dlat, bbox.max.lonF() - dlon)); 0143 }