File indexing completed on 2024-05-05 03:51:05

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