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