File indexing completed on 2024-07-21 09:27:11

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 }