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 }