File indexing completed on 2025-01-05 03:59:29
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 "GeoDataRelation.h" 0009 #include "GeoDataDocument.h" 0010 #include "GeoDataPolygon.h" 0011 #include "GeoDataLatLonAltBox.h" 0012 #include "StyleBuilder.h" 0013 #include "OsmObjectManager.h" 0014 0015 #include "digikam_debug.h" 0016 0017 namespace Marble { 0018 0019 OsmRelation::OsmMember::OsmMember() : 0020 reference(0) 0021 { 0022 // nothing to do 0023 } 0024 0025 OsmPlacemarkData &OsmRelation::osmData() 0026 { 0027 return m_osmData; 0028 } 0029 0030 const OsmPlacemarkData &OsmRelation::osmData() const 0031 { 0032 return m_osmData; 0033 } 0034 0035 void OsmRelation::parseMember(const QXmlStreamAttributes &attributes) 0036 { 0037 addMember(attributes.value(QLatin1String("ref")).toLongLong(), 0038 attributes.value(QLatin1String("role")).toString(), 0039 attributes.value(QLatin1String("type")).toString()); 0040 } 0041 0042 void OsmRelation::addMember(qint64 reference, const QString &role, const QString &type) 0043 { 0044 OsmMember member; 0045 member.reference = reference; 0046 member.role = role; 0047 member.type = type; 0048 m_members << member; 0049 } 0050 0051 void OsmRelation::createMultipolygon(GeoDataDocument *document, OsmWays &ways, const OsmNodes &nodes, QSet<qint64> &usedNodes, QSet<qint64> &usedWays) const 0052 { 0053 if (!m_osmData.containsTag(QStringLiteral("type"), QStringLiteral("multipolygon"))) { 0054 return; 0055 } 0056 0057 QStringList const outerRoles = QStringList() << QStringLiteral("outer") << QString(); 0058 QSet<qint64> outerWays; 0059 QSet<qint64> outerNodes; 0060 OsmRings const outer = rings(outerRoles, ways, nodes, outerNodes, outerWays); 0061 0062 if (outer.isEmpty()) { 0063 return; 0064 } 0065 0066 GeoDataPlacemark::GeoDataVisualCategory outerCategory = StyleBuilder::determineVisualCategory(m_osmData); 0067 if (outerCategory == GeoDataPlacemark::None) { 0068 // Try to determine the visual category from the relation members 0069 GeoDataPlacemark::GeoDataVisualCategory const firstCategory = 0070 StyleBuilder::determineVisualCategory(ways[*outerWays.begin()].osmData()); 0071 0072 bool categoriesAreSame = true; 0073 for (auto wayId: outerWays) { 0074 GeoDataPlacemark::GeoDataVisualCategory const category = 0075 StyleBuilder::determineVisualCategory(ways[wayId].osmData()); 0076 if( category != firstCategory ) { 0077 categoriesAreSame = false; 0078 break; 0079 } 0080 } 0081 0082 if( categoriesAreSame ) { 0083 outerCategory = firstCategory; 0084 } 0085 } 0086 0087 for (auto wayId: outerWays) { 0088 Q_ASSERT(ways.contains(wayId)); 0089 const auto &osmData = ways[wayId].osmData(); 0090 GeoDataPlacemark::GeoDataVisualCategory const category = StyleBuilder::determineVisualCategory(osmData); 0091 if ((category == GeoDataPlacemark::None || category == outerCategory) && osmData.isEmpty()) { 0092 // Schedule way for removal: It's a non-styled way only used to create the outer boundary in this polygon 0093 usedWays << wayId; 0094 } // else we keep it 0095 0096 for(auto nodeId: ways[wayId].references()) { 0097 ways[wayId].osmData().addNodeReference(nodes[nodeId].coordinates(), nodes[nodeId].osmData()); 0098 } 0099 } 0100 0101 QStringList const innerRoles = QStringList() << QStringLiteral("inner"); 0102 QSet<qint64> innerWays; 0103 OsmRings const inner = rings(innerRoles, ways, nodes, usedNodes, innerWays); 0104 0105 bool const hasMultipleOuterRings = outer.size() > 1; 0106 for (int i=0, n=outer.size(); i<n; ++i) { 0107 auto const & outerRing = outer[i]; 0108 0109 GeoDataPolygon *polygon = new GeoDataPolygon; 0110 polygon->setOuterBoundary(outerRing.first); 0111 OsmPlacemarkData osmData = m_osmData; 0112 osmData.addMemberReference(-1, outerRing.second); 0113 0114 int index = 0; 0115 for (auto const &innerRing: inner) { 0116 if (innerRing.first.isEmpty() || !outerRing.first.contains(innerRing.first.first())) { 0117 // Simple check to see if this inner ring is inside the outer ring 0118 continue; 0119 } 0120 0121 if (StyleBuilder::determineVisualCategory(innerRing.second) == GeoDataPlacemark::None) { 0122 // Schedule way for removal: It's a non-styled way only used to create the inner boundary in this polygon 0123 usedWays << innerRing.second.id(); 0124 } 0125 polygon->appendInnerBoundary(innerRing.first); 0126 osmData.addMemberReference(index, innerRing.second); 0127 ++index; 0128 } 0129 0130 if (outerCategory == GeoDataPlacemark::Bathymetry) { 0131 // In case of a bathymetry store elevation info since it is required during styling 0132 // The ele=* tag is present in the outermost way 0133 const QString ele = QStringLiteral("ele"); 0134 const OsmPlacemarkData &outerWayData = outerRing.second; 0135 auto tagIter = outerWayData.findTag(ele); 0136 if (tagIter != outerWayData.tagsEnd()) { 0137 osmData.addTag(ele, tagIter.value()); 0138 } 0139 } 0140 0141 GeoDataPlacemark *placemark = new GeoDataPlacemark; 0142 placemark->setName(m_osmData.tagValue(QStringLiteral("name"))); 0143 placemark->setVisualCategory(outerCategory); 0144 placemark->setOsmData(osmData); 0145 placemark->setZoomLevel(StyleBuilder::minimumZoomLevel(outerCategory)); 0146 placemark->setPopularity(StyleBuilder::popularity(placemark)); 0147 placemark->setVisible(outerCategory != GeoDataPlacemark::None); 0148 placemark->setGeometry(polygon); 0149 if (hasMultipleOuterRings) { 0150 /** @todo Use a GeoDataMultiGeometry to keep the ID? */ 0151 osmData.setId(0); 0152 OsmObjectManager::initializeOsmData(placemark); 0153 } else { 0154 OsmObjectManager::registerId(osmData.id()); 0155 } 0156 usedNodes |= outerNodes; 0157 0158 document->append(placemark); 0159 } 0160 } 0161 0162 static OsmType stringToType(const QString &str) 0163 { 0164 if (str == QLatin1String("relation")) { 0165 return OsmType::Relation; 0166 } 0167 if (str == QLatin1String("node")) { 0168 return OsmType::Node; 0169 } 0170 return OsmType::Way; 0171 } 0172 0173 void OsmRelation::createRelation(GeoDataDocument *document, const QHash<qint64, GeoDataPlacemark*>& placemarks) const 0174 { 0175 if (m_osmData.containsTag(QStringLiteral("type"), QStringLiteral("multipolygon"))) { 0176 return; 0177 } 0178 0179 OsmPlacemarkData osmData = m_osmData; 0180 GeoDataRelation *relation = new GeoDataRelation; 0181 0182 relation->setName(osmData.tagValue(QStringLiteral("name"))); 0183 if (relation->name().isEmpty()) { 0184 relation->setName(osmData.tagValue(QStringLiteral("ref"))); 0185 } 0186 relation->osmData() = osmData; 0187 0188 for (auto const &member: m_members) { 0189 auto const iter = placemarks.find(member.reference); 0190 if (iter != placemarks.constEnd()) { 0191 relation->addMember(*iter, member.reference, stringToType(member.type), member.role); 0192 } 0193 } 0194 0195 if (relation->members().isEmpty()) { 0196 delete relation; 0197 return; 0198 } 0199 0200 OsmObjectManager::registerId(osmData.id()); 0201 relation->setVisible(false); 0202 document->append(relation); 0203 } 0204 0205 OsmRelation::OsmRings OsmRelation::rings(const QStringList &roles, const OsmWays &ways, const OsmNodes &nodes, QSet<qint64> &usedNodes, QSet<qint64> &usedWays) const 0206 { 0207 QSet<qint64> currentWays; 0208 QSet<qint64> currentNodes; 0209 QList<qint64> roleMembers; 0210 for (auto const &member: m_members) { 0211 if (roles.contains(member.role)) { 0212 if (!ways.contains(member.reference)) { 0213 // A way is missing. Return nothing. 0214 return OsmRings(); 0215 } 0216 roleMembers << member.reference; 0217 } 0218 } 0219 0220 OsmRings result; 0221 QList<OsmWay> unclosedWays; 0222 for(auto wayId: roleMembers) { 0223 GeoDataLinearRing ring; 0224 OsmWay const & way = ways[wayId]; 0225 if (way.references().isEmpty()) { 0226 continue; 0227 } 0228 if (way.references().first() != way.references().last()) { 0229 unclosedWays.append(way); 0230 continue; 0231 } 0232 0233 OsmPlacemarkData placemarkData = way.osmData(); 0234 for(auto id: way.references()) { 0235 if (!nodes.contains(id)) { 0236 // A node is missing. Return nothing. 0237 return OsmRings(); 0238 } 0239 const auto &node = nodes[id]; 0240 ring << node.coordinates(); 0241 placemarkData.addNodeReference(node.coordinates(), node.osmData()); 0242 } 0243 Q_ASSERT(ways.contains(wayId)); 0244 currentWays << wayId; 0245 result << OsmRing(GeoDataLinearRing(ring.optimized()), placemarkData); 0246 } 0247 0248 if( !unclosedWays.isEmpty() ) { 0249 //qCDebug(DIGIKAM_MARBLE_LOG) << "Trying to merge non-trivial polygon boundary in relation " << m_osmData.id(); 0250 while( unclosedWays.length() > 0 ) { 0251 GeoDataLinearRing ring; 0252 qint64 firstReference = unclosedWays.first().references().first(); 0253 qint64 lastReference = firstReference; 0254 OsmPlacemarkData placemarkData; 0255 bool ok = true; 0256 while( ok ) { 0257 ok = false; 0258 for(int i = 0; i<unclosedWays.length(); ) { 0259 const OsmWay &nextWay = unclosedWays.at(i); 0260 if( nextWay.references().first() == lastReference 0261 || nextWay.references().last() == lastReference ) { 0262 0263 bool isReversed = nextWay.references().last() == lastReference; 0264 QVector<qint64> v = nextWay.references(); 0265 while( !v.isEmpty() ) { 0266 qint64 id = isReversed ? v.takeLast() : v.takeFirst(); 0267 if (!nodes.contains(id)) { 0268 // A node is missing. Return nothing. 0269 return OsmRings(); 0270 } 0271 if ( id != lastReference ) { 0272 const auto &node = nodes[id]; 0273 ring << node.coordinates(); 0274 placemarkData.addNodeReference(node.coordinates(), node.osmData()); 0275 currentNodes << id; 0276 } 0277 } 0278 lastReference = isReversed ? nextWay.references().first() 0279 : nextWay.references().last(); 0280 Q_ASSERT(ways.contains(nextWay.osmData().id())); 0281 currentWays << nextWay.osmData().id(); 0282 unclosedWays.removeAt(i); 0283 ok = true; 0284 break; 0285 } else { 0286 ++i; 0287 } 0288 } 0289 } 0290 0291 if(lastReference != firstReference) { 0292 return OsmRings(); 0293 } else { 0294 /** @todo Merge tags common to all rings into the new osm data? */ 0295 result << OsmRing(GeoDataLinearRing(ring.optimized()), placemarkData); 0296 } 0297 } 0298 } 0299 0300 usedWays |= currentWays; 0301 usedNodes |= currentNodes; 0302 return result; 0303 } 0304 0305 }