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 }