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 }