File indexing completed on 2025-01-05 03:59:30

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2015 Dennis Nienhüser <nienhueser@kde.org>
0004 //
0005 
0006 #include "OsmRelation.h"
0007 #include "GeoDataPlacemark.h"
0008 #include "GeoDataLineStyle.h"
0009 #include "GeoDataPolyStyle.h"
0010 #include "GeoDataStyle.h"
0011 #include "GeoDataDocument.h"
0012 #include "OsmObjectManager.h"
0013 #include "MarbleDirs.h"
0014 #include "GeoDataMultiGeometry.h"
0015 
0016 #include "digikam_debug.h"
0017 
0018 namespace Marble {
0019 
0020 QSet<StyleBuilder::OsmTag> OsmWay::s_areaTags;
0021 QSet<StyleBuilder::OsmTag> OsmWay::s_buildingTags;
0022 
0023 GeoDataPlacemark *OsmWay::create(const OsmNodes &nodes, QSet<qint64> &usedNodes) const
0024 {
0025     OsmPlacemarkData osmData = m_osmData;
0026     GeoDataGeometry *geometry = nullptr;
0027 
0028     if (isArea()) {
0029         GeoDataLinearRing linearRing;
0030         linearRing.reserve(m_references.size());
0031         bool const stripLastNode = m_references.first() == m_references.last();
0032         for (int i=0, n=m_references.size() - (stripLastNode ? 1 : 0); i<n; ++i) {
0033             qint64 nodeId = m_references[i];
0034             auto const nodeIter = nodes.constFind(nodeId);
0035             if (nodeIter == nodes.constEnd()) {
0036                 return nullptr;
0037             }
0038 
0039             OsmNode const & node = nodeIter.value();
0040             osmData.addNodeReference(node.coordinates(), node.osmData());
0041             linearRing.append(node.coordinates());
0042             usedNodes << nodeId;
0043         }
0044 
0045         if (isBuilding()) {
0046             GeoDataBuilding building;
0047             building.setName(extractBuildingName());
0048             building.setHeight(extractBuildingHeight());
0049             building.setEntries(extractNamedEntries());
0050             building.multiGeometry()->append(new GeoDataLinearRing(linearRing.optimized()));
0051 
0052             geometry = new GeoDataBuilding(building);
0053         } else {
0054             geometry = new GeoDataLinearRing(linearRing.optimized());
0055         }
0056     } else {
0057         GeoDataLineString lineString;
0058         lineString.reserve(m_references.size());
0059 
0060         for(auto nodeId: m_references) {
0061             auto const nodeIter = nodes.constFind(nodeId);
0062             if (nodeIter == nodes.constEnd()) {
0063                 return nullptr;
0064             }
0065 
0066             OsmNode const & node = nodeIter.value();
0067             osmData.addNodeReference(node.coordinates(), node.osmData());
0068             lineString.append(node.coordinates());
0069             usedNodes << nodeId;
0070         }
0071 
0072         geometry = new GeoDataLineString(lineString.optimized());
0073     }
0074 
0075     Q_ASSERT(geometry != nullptr);
0076 
0077     OsmObjectManager::registerId(m_osmData.id());
0078 
0079     GeoDataPlacemark *placemark = new GeoDataPlacemark;
0080     placemark->setGeometry(geometry);
0081     placemark->setVisualCategory(StyleBuilder::determineVisualCategory(m_osmData));
0082     placemark->setName(m_osmData.tagValue(QStringLiteral("name")));
0083     if (placemark->name().isEmpty()) {
0084         placemark->setName(m_osmData.tagValue(QStringLiteral("ref")));
0085     }
0086     placemark->setOsmData(osmData);
0087     placemark->setZoomLevel(StyleBuilder::minimumZoomLevel(placemark->visualCategory()));
0088     placemark->setPopularity(StyleBuilder::popularity(placemark));
0089     placemark->setVisible(placemark->visualCategory() != GeoDataPlacemark::None);
0090 
0091     return placemark;
0092 }
0093 
0094 const QVector<qint64> &OsmWay::references() const
0095 {
0096     return m_references;
0097 }
0098 
0099 OsmPlacemarkData &OsmWay::osmData()
0100 {
0101     return m_osmData;
0102 }
0103 
0104 const OsmPlacemarkData &OsmWay::osmData() const
0105 {
0106     return m_osmData;
0107 }
0108 
0109 void OsmWay::addReference(qint64 id)
0110 {
0111     m_references << id;
0112 }
0113 
0114 bool OsmWay::isArea() const
0115 {
0116     // @TODO A single OSM way can be both closed and non-closed, e.g. landuse=grass with barrier=fence.
0117     // We need to create two separate ways in cases like that to support this.
0118     // See also https://wiki.openstreetmap.org/wiki/Key:area
0119 
0120     if (m_osmData.containsTag(QStringLiteral("area"), QStringLiteral("yes"))) {
0121         return true;
0122     }
0123 
0124     bool const isLinearFeature =
0125             m_osmData.containsTag(QStringLiteral("area"), QStringLiteral("no")) ||
0126             m_osmData.containsTagKey(QStringLiteral("highway")) ||
0127             m_osmData.containsTagKey(QStringLiteral("barrier"));
0128     if (isLinearFeature) {
0129         return false;
0130     }
0131 
0132     bool const isAreaFeature = m_osmData.containsTagKey(QStringLiteral("landuse"));
0133     if (isAreaFeature) {
0134         return true;
0135     }
0136 
0137     for (auto iter = m_osmData.tagsBegin(), end=m_osmData.tagsEnd(); iter != end; ++iter) {
0138         const auto tag = StyleBuilder::OsmTag(iter.key(), iter.value());
0139         if (isAreaTag(tag)) {
0140             return true;
0141         }
0142     }
0143 
0144     bool const isImplicitlyClosed = m_references.size() > 1 && m_references.front() == m_references.last();
0145     return isImplicitlyClosed;
0146 }
0147 
0148 bool OsmWay::isAreaTag(const StyleBuilder::OsmTag &keyValue)
0149 {
0150     if (s_areaTags.isEmpty()) {
0151         // All these tags can be found updated at
0152         // https://wiki.openstreetmap.org/wiki/Map_Features#Landuse
0153 
0154         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("natural"), QStringLiteral("water")));
0155         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("natural"), QStringLiteral("wood")));
0156         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("natural"), QStringLiteral("beach")));
0157         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("natural"), QStringLiteral("wetland")));
0158         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("natural"), QStringLiteral("glacier")));
0159         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("natural"), QStringLiteral("scrub")));
0160         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("natural"), QStringLiteral("cliff")));
0161         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("area"), QStringLiteral("yes")));
0162         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("waterway"), QStringLiteral("riverbank")));
0163 
0164         for (auto const & tag: StyleBuilder::buildingTags()) {
0165             s_areaTags.insert(tag);
0166         }
0167         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("man_made"), QStringLiteral("bridge")));
0168 
0169         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("amenity"), QStringLiteral("graveyard")));
0170         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("amenity"), QStringLiteral("parking")));
0171         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("amenity"), QStringLiteral("parking_space")));
0172         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("amenity"), QStringLiteral("bicycle_parking")));
0173         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("amenity"), QStringLiteral("college")));
0174         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("amenity"), QStringLiteral("hospital")));
0175         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("amenity"), QStringLiteral("kindergarten")));
0176         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("amenity"), QStringLiteral("school")));
0177         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("amenity"), QStringLiteral("university")));
0178         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("leisure"), QStringLiteral("common")));
0179         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("leisure"), QStringLiteral("garden")));
0180         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("leisure"), QStringLiteral("golf_course")));
0181         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("leisure"), QStringLiteral("marina")));
0182         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("leisure"), QStringLiteral("playground")));
0183         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("leisure"), QStringLiteral("pitch")));
0184         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("leisure"), QStringLiteral("park")));
0185         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("leisure"), QStringLiteral("sports_centre")));
0186         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("leisure"), QStringLiteral("stadium")));
0187         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("leisure"), QStringLiteral("swimming_pool")));
0188         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("leisure"), QStringLiteral("track")));
0189 
0190         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("military"), QStringLiteral("danger_area")));
0191 
0192         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("marble_land"), QStringLiteral("landmass")));
0193         s_areaTags.insert(StyleBuilder::OsmTag(QStringLiteral("settlement"), QStringLiteral("yes")));
0194     }
0195 
0196     return s_areaTags.contains(keyValue);
0197 }
0198 
0199 bool OsmWay::isBuilding() const
0200 {
0201     for (auto iter = m_osmData.tagsBegin(), end=m_osmData.tagsEnd(); iter != end; ++iter) {
0202         const auto tag = StyleBuilder::OsmTag(iter.key(), iter.value());
0203         if (isBuildingTag(tag)) {
0204             return true;
0205         }
0206     }
0207 
0208     return false;
0209 }
0210 
0211 bool OsmWay::isBuildingTag(const StyleBuilder::OsmTag &keyValue)
0212 {
0213     if (s_buildingTags.isEmpty()) {
0214         for (auto const & tag: StyleBuilder::buildingTags()) {
0215             s_buildingTags.insert(tag);
0216         }
0217     }
0218 
0219     return s_buildingTags.contains(keyValue);
0220 }
0221 
0222 QString OsmWay::extractBuildingName() const
0223 {
0224     auto tagIter = m_osmData.findTag(QStringLiteral("addr:housename"));
0225     if (tagIter != m_osmData.tagsEnd()) {
0226         return tagIter.value();
0227     }
0228 
0229     tagIter = m_osmData.findTag(QStringLiteral("addr:housenumber"));
0230     if (tagIter != m_osmData.tagsEnd()) {
0231         return tagIter.value();
0232     }
0233 
0234     return QString();
0235 }
0236 
0237 double OsmWay::extractBuildingHeight() const
0238 {
0239     double height = 8.0;
0240 
0241     QHash<QString, QString>::const_iterator tagIter;
0242     if ((tagIter = m_osmData.findTag(QStringLiteral("height"))) != m_osmData.tagsEnd()) {
0243         height = GeoDataBuilding::parseBuildingHeight(tagIter.value());
0244     } else if ((tagIter = m_osmData.findTag(QStringLiteral("building:levels"))) != m_osmData.tagsEnd()) {
0245         int const levels = tagIter.value().toInt();
0246         int const skipLevels = m_osmData.tagValue(QStringLiteral("building:min_level")).toInt();
0247         /** @todo Is 35 as an upper bound for the number of levels sane? */
0248         height = 3.0 * qBound(1, 1+levels-skipLevels, 35);
0249     }
0250 
0251     return qBound(1.0, height, 1000.0);
0252 }
0253 
0254 QVector<GeoDataBuilding::NamedEntry> OsmWay::extractNamedEntries() const
0255 {
0256     QVector<GeoDataBuilding::NamedEntry> entries;
0257 
0258     QHash< GeoDataCoordinates, OsmPlacemarkData > nref = m_osmData.hRef()->nodeReferences();
0259 
0260 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0261 
0262     QHash< GeoDataCoordinates, OsmPlacemarkData >::const_iterator end = nref.end();
0263 
0264     for (QHash< GeoDataCoordinates, OsmPlacemarkData >::const_iterator iter = nref.begin();
0265          iter != end; ++iter)
0266 
0267 #else
0268 
0269     QHash< GeoDataCoordinates, OsmPlacemarkData >::iterator end = nref.end();
0270 
0271     for (QHash< GeoDataCoordinates, OsmPlacemarkData >::iterator iter = nref.begin();
0272          iter != end; ++iter)
0273 
0274 #endif
0275 
0276     {
0277         auto tagIter = iter.value().findTag(QStringLiteral("addr:housenumber"));
0278 
0279         if (tagIter != iter.value().tagsEnd())
0280         {
0281             GeoDataBuilding::NamedEntry entry;
0282             entry.point = iter.key();
0283             entry.label = tagIter.value();
0284             entries.push_back(entry);
0285         }
0286     }
0287 
0288     return entries;
0289 }
0290 
0291 }