diff --git a/src/lib/marble/geodata/data/GeoDataRelation.cpp b/src/lib/marble/geodata/data/GeoDataRelation.cpp index c5b36de09..e0556b8cf 100644 --- a/src/lib/marble/geodata/data/GeoDataRelation.cpp +++ b/src/lib/marble/geodata/data/GeoDataRelation.cpp @@ -1,143 +1,153 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2017 Dennis Nienhüser #include "GeoDataRelation.h" #include "GeoDataTypes.h" #include "OsmPlacemarkData.h" #include namespace Marble { class GeoDataRelationPrivate { public: QSet m_features; OsmPlacemarkData m_osmData; QSet m_memberIds; static QHash s_relationTypes; }; QHash GeoDataRelationPrivate::s_relationTypes; GeoDataRelation::GeoDataRelation() : GeoDataFeature(), d_ptr(new GeoDataRelationPrivate) { // nothing to do } GeoDataRelation::~GeoDataRelation() { delete d_ptr; } GeoDataRelation::GeoDataRelation(const GeoDataRelation &other) : GeoDataFeature(other), d_ptr(new GeoDataRelationPrivate) { Q_D(GeoDataRelation); d->m_features = other.d_func()->m_features; d->m_osmData = other.d_func()->m_osmData; } GeoDataRelation &GeoDataRelation::operator=(GeoDataRelation other) // passed by value { GeoDataFeature::operator=(other); std::swap(*this->d_ptr, *other.d_ptr); return *this; } const char *GeoDataRelation::nodeType() const { return GeoDataTypes::GeoDataRelationType; } GeoDataFeature *GeoDataRelation::clone() const { return new GeoDataRelation(*this); } void GeoDataRelation::addMember(const GeoDataFeature *feature, qint64 id, const QString &role) { Q_D(GeoDataRelation); d->m_features << feature; d->m_osmData.addRelation(id, role); d->m_memberIds << id; } QSet GeoDataRelation::members() const { Q_D(const GeoDataRelation); return d->m_features; } OsmPlacemarkData &GeoDataRelation::osmData() { Q_D(GeoDataRelation); return d->m_osmData; } const OsmPlacemarkData &GeoDataRelation::osmData() const { Q_D(const GeoDataRelation); return d->m_osmData; } GeoDataRelation::RelationType GeoDataRelation::relationType() const { if (GeoDataRelationPrivate::s_relationTypes.isEmpty()) { auto &map = GeoDataRelationPrivate::s_relationTypes; map["road"] = RouteRoad; map["detour"] = RouteDetour; map["ferry"] = RouteFerry; map["train"] = RouteTrain; map["subway"] = RouteSubway; map["tram"] = RouteTram; map["bus"] = RouteBus; map["trolleybus"] = RouteTrolleyBus; map["bicycle"] = RouteBicycle; map["mtb"] = RouteMountainbike; map["foot"] = RouteFoot; map["hiking"] = GeoDataRelation::RouteHiking; map["horse"] = RouteHorse; map["inline_skates"] = RouteInlineSkates; } Q_D(const GeoDataRelation); if (d->m_osmData.containsTag(QStringLiteral("type"), QStringLiteral("route"))) { auto const route = d->m_osmData.tagValue(QStringLiteral("route")); if (route == QStringLiteral("piste")) { auto const piste = d->m_osmData.tagValue(QStringLiteral("piste:type")); if (piste == QStringLiteral("downhill")) { return RouteSkiDownhill; } else if (piste == QStringLiteral("nordic")) { return RouteSkiNordic; } else if (piste == QStringLiteral("skitour")) { return RouteSkitour; } else if (piste == QStringLiteral("sled")) { return RouteSled; } } return GeoDataRelationPrivate::s_relationTypes.value(route, UnknownType); } return UnknownType; } QSet GeoDataRelation::memberIds() const { Q_D(const GeoDataRelation); return d->m_memberIds; } +bool GeoDataRelation::containsAnyOf(const QSet &memberIds) const +{ + Q_D(const GeoDataRelation); +#if QT_VERSION >= 0x050600 // intersects was introduced in Qt 5.6. + return d->m_memberIds.intersects(memberIds); +#else + return !(d->m_memberIds.intersect(memberIds).isEmpty()); +#endif +} + } diff --git a/src/lib/marble/geodata/data/GeoDataRelation.h b/src/lib/marble/geodata/data/GeoDataRelation.h index 6205bda0a..68e3cbad7 100644 --- a/src/lib/marble/geodata/data/GeoDataRelation.h +++ b/src/lib/marble/geodata/data/GeoDataRelation.h @@ -1,71 +1,72 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2017 Dennis Nienhüser #ifndef MARBLE_GEODATARELATION_H #define MARBLE_GEODATARELATION_H #include "GeoDataCoordinates.h" #include "GeoDataPlacemark.h" #include "geodata_export.h" namespace Marble { class GeoDataRelationPrivate; class GEODATA_EXPORT GeoDataRelation: public GeoDataFeature { public: enum RelationType { UnknownType, RouteRoad, RouteDetour, RouteFerry, RouteTrain, RouteSubway, RouteTram, RouteBus, RouteTrolleyBus, RouteBicycle, RouteMountainbike, RouteFoot, RouteHiking, RouteHorse, RouteInlineSkates, RouteSkiDownhill, RouteSkiNordic, RouteSkitour, RouteSled, }; GeoDataRelation(); ~GeoDataRelation(); GeoDataRelation(const GeoDataRelation &other); GeoDataRelation & operator=(GeoDataRelation other); const char* nodeType() const override; GeoDataFeature * clone() const override; void addMember(const GeoDataFeature* feature, qint64 id, const QString &role); QSet members() const; OsmPlacemarkData &osmData(); const OsmPlacemarkData &osmData() const; RelationType relationType() const; QSet memberIds() const; + bool containsAnyOf(const QSet &memberIds) const; private: GeoDataRelationPrivate* d_ptr; Q_DECLARE_PRIVATE(GeoDataRelation) }; } #endif diff --git a/tools/vectorosm-tilecreator/VectorClipper.cpp b/tools/vectorosm-tilecreator/VectorClipper.cpp index 07d82691c..b78e7d86e 100644 --- a/tools/vectorosm-tilecreator/VectorClipper.cpp +++ b/tools/vectorosm-tilecreator/VectorClipper.cpp @@ -1,343 +1,337 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2016 Dennis Nienhüser // Copyright 2016 David Kolozsvari // #include "VectorClipper.h" #include "TileId.h" #include "GeoDataTypes.h" #include "GeoDataLatLonAltBox.h" #include "GeoDataPolygon.h" #include "GeoDataPlacemark.h" #include "GeoDataRelation.h" #include "OsmPlacemarkData.h" #include "OsmObjectManager.h" #include "TileCoordsPyramid.h" #include "clipper/clipper.hpp" #include #include #include #include namespace Marble { VectorClipper::VectorClipper(GeoDataDocument* document, int maxZoomLevel) : m_maxZoomLevel(maxZoomLevel) { for (auto feature: document->featureList()) { if (feature->nodeType() == GeoDataTypes::GeoDataPlacemarkType) { GeoDataPlacemark* placemark = static_cast(feature); // Select zoom level such that the placemark fits in a single tile int zoomLevel; qreal north, south, east, west; placemark->geometry()->latLonAltBox().boundaries(north, south, east, west); for (zoomLevel = maxZoomLevel; zoomLevel >= 0; --zoomLevel) { if (TileId::fromCoordinates(GeoDataCoordinates(west, north), zoomLevel) == TileId::fromCoordinates(GeoDataCoordinates(east, south), zoomLevel)) { break; } } TileId const key = TileId::fromCoordinates(GeoDataCoordinates(west, north), zoomLevel); m_items[key] << placemark; } else if (feature->nodeType() == GeoDataTypes::GeoDataRelationType) { m_relations << static_cast(feature); } else { Q_ASSERT(false && "only placemark variants are supported so far"); } } } GeoDataDocument *VectorClipper::clipTo(const GeoDataLatLonBox &tileBoundary, int zoomLevel) { bool const filterSmallAreas = zoomLevel > 10 && zoomLevel < 17; GeoDataDocument* tile = new GeoDataDocument(); auto const clip = clipPath(tileBoundary, zoomLevel); GeoDataLinearRing ring; ring << GeoDataCoordinates(tileBoundary.west(), tileBoundary.north()); ring << GeoDataCoordinates(tileBoundary.east(), tileBoundary.north()); ring << GeoDataCoordinates(tileBoundary.east(), tileBoundary.south()); ring << GeoDataCoordinates(tileBoundary.west(), tileBoundary.south()); qreal const minArea = filterSmallAreas ? 0.01 * area(ring) : 0.0; QSet osmIds; foreach (GeoDataPlacemark const * placemark, potentialIntersections(tileBoundary)) { GeoDataGeometry const * const geometry = placemark ? placemark->geometry() : nullptr; if (geometry && tileBoundary.intersects(geometry->latLonAltBox())) { if(geometry->nodeType() == GeoDataTypes::GeoDataPolygonType) { clipPolygon(placemark, clip, minArea, tile, osmIds); } else if (geometry->nodeType() == GeoDataTypes::GeoDataLineStringType) { clipString(placemark, clip, minArea, tile, osmIds); } else if (geometry->nodeType() == GeoDataTypes::GeoDataLinearRingType) { clipString(placemark, clip, minArea, tile, osmIds); } else { tile->append(placemark->clone()); osmIds << placemark->osmData().id(); } } } for (auto relation: m_relations) { - bool const hasMember = -#if QT_VERSION >= 0x050600 // intersects was introduced in Qt 5.6. - relation->memberIds().intersects(osmIds); -#else - !relation->memberIds().intersect(osmIds).isEmpty(); -#endif - if (hasMember) { + if (relation->containsAnyOf(osmIds)) { GeoDataRelation* multi = new GeoDataRelation; multi->osmData() = relation->osmData(); tile->append(multi); } } return tile; } QVector VectorClipper::potentialIntersections(const GeoDataLatLonBox &box) const { qreal north, south, east, west; box.boundaries(north, south, east, west); TileId const topLeft = TileId::fromCoordinates(GeoDataCoordinates(west, north), m_maxZoomLevel); TileId const bottomRight = TileId::fromCoordinates(GeoDataCoordinates(east, south), m_maxZoomLevel); QRect rect; rect.setCoords(topLeft.x(), topLeft.y(), bottomRight.x(), bottomRight.y()); TileCoordsPyramid pyramid(0, m_maxZoomLevel); pyramid.setBottomLevelCoords(rect); QVector result; for (int level = pyramid.topLevel(), maxLevel = pyramid.bottomLevel(); level <= maxLevel; ++level) { int x1, y1, x2, y2; pyramid.coords(level).getCoords(&x1, &y1, &x2, &y2); for (int x = x1; x <= x2; ++x) { for (int y = y1; y <= y2; ++y) { result << m_items.value(TileId(0, level, x, y)); } } } return result; } GeoDataDocument *VectorClipper::clipTo(unsigned int zoomLevel, unsigned int tileX, unsigned int tileY) { const GeoDataLatLonBox tileBoundary = m_tileProjection.geoCoordinates(zoomLevel, tileX, tileY); GeoDataDocument *tile = clipTo(tileBoundary, zoomLevel); QString tileName = QString("%1/%2/%3").arg(zoomLevel).arg(tileX).arg(tileY); tile->setName(tileName); return tile; } ClipperLib::Path VectorClipper::clipPath(const GeoDataLatLonBox &box, int zoomLevel) const { using namespace ClipperLib; Path path; int const steps = qMax(1, 22 - 2 * zoomLevel); qreal const scale = IntPoint::scale; double x = box.west() * scale; double const horizontalStep = (box.east() * scale - x) / steps; double y = box.north() * scale; double const verticalStep = (box.south() * scale - y) / steps; for (int i=0; i= GeoDataPlacemark::HighwaySteps && visualCategory <= GeoDataPlacemark::HighwayMotorway) { return false; } if (visualCategory >= GeoDataPlacemark::RailwayRail && visualCategory <= GeoDataPlacemark::RailwayFunicular) { return false; } if (visualCategory >= GeoDataPlacemark::AdminLevel1 && visualCategory <= GeoDataPlacemark::AdminLevel11) { return false; } if (visualCategory == GeoDataPlacemark::BoundaryMaritime || visualCategory == GeoDataPlacemark::InternationalDateLine) { return false; } return true; } qreal VectorClipper::area(const GeoDataLinearRing &ring) { auto const iter = m_areas.find(&ring); if (iter != m_areas.end()) { return *iter; } int const n = ring.size(); qreal area = 0; if (n<3) { return area; } for (int i = 1; i < n; ++i ){ area += (ring[i].longitude() - ring[i-1].longitude() ) * ( ring[i].latitude() + ring[i-1].latitude()); } area += (ring[0].longitude() - ring[n-1].longitude() ) * (ring[0].latitude() + ring[n-1].latitude()); qreal const result = EARTH_RADIUS * EARTH_RADIUS * qAbs(area * 0.5); m_areas.insert(&ring, result); return result; } void VectorClipper::getBounds(const ClipperLib::Path &path, ClipperLib::cInt &minX, ClipperLib::cInt &maxX, ClipperLib::cInt &minY, ClipperLib::cInt &maxY) const { Q_ASSERT(!path.empty()); minX = path[0].X; maxX = minX; minY = path[0].Y; maxY = minY; for (auto const & point: path) { if (point.X < minX) { minX = point.X; } else if (point.X > maxX) { maxX = point.X; } if (point.Y < minY) { minY = point.Y; } else if (point.Y > maxY) { maxY = point.Y; } } } void VectorClipper::clipPolygon(const GeoDataPlacemark *placemark, const ClipperLib::Path &tileBoundary, qreal minArea, GeoDataDocument *document, QSet &osmIds) { const GeoDataPolygon* polygon = static_cast(placemark->geometry()); if (minArea > 0.0 && area(polygon->outerBoundary()) < minArea) { return; } using namespace ClipperLib; Path path; foreach(auto const & node, polygon->outerBoundary()) { path << IntPoint(&node); } cInt minX, maxX, minY, maxY; getBounds(tileBoundary, minX, maxX, minY, maxY); Clipper clipper; clipper.PreserveCollinear(true); clipper.AddPath(tileBoundary, ptClip, true); clipper.AddPath(path, ptSubject, true); Paths paths; clipper.Execute(ctIntersection, paths); foreach(const auto &path, paths) { GeoDataPlacemark* newPlacemark = new GeoDataPlacemark; newPlacemark->setVisible(placemark->isVisible()); newPlacemark->setVisualCategory(placemark->visualCategory()); GeoDataLinearRing outerRing; OsmPlacemarkData const & placemarkOsmData = placemark->osmData(); OsmPlacemarkData & newPlacemarkOsmData = newPlacemark->osmData(); int index = -1; OsmPlacemarkData const & outerRingOsmData = placemarkOsmData.memberReference(index); OsmPlacemarkData & newOuterRingOsmData = newPlacemarkOsmData.memberReference(index); int nodeIndex = 0; foreach(const auto &point, path) { GeoDataCoordinates const coordinates = point.coordinates(); outerRing << coordinates; auto const originalOsmData = outerRingOsmData.nodeReference(coordinates); if (originalOsmData.id() > 0) { newOuterRingOsmData.addNodeReference(coordinates, originalOsmData); } ++nodeIndex; } GeoDataPolygon* newPolygon = new GeoDataPolygon; newPolygon->setOuterBoundary(outerRing); newPlacemark->setGeometry(newPolygon); if (placemarkOsmData.id() > 0) { newPlacemarkOsmData.addTag(QStringLiteral("mx:oid"), QString::number(placemarkOsmData.id())); } copyTags(placemarkOsmData, newPlacemarkOsmData); copyTags(outerRingOsmData, newOuterRingOsmData); if (outerRingOsmData.id() > 0) { newOuterRingOsmData.addTag(QStringLiteral("mx:oid"), QString::number(outerRingOsmData.id())); } auto const & innerBoundaries = polygon->innerBoundaries(); for (index = 0; index < innerBoundaries.size(); ++index) { auto const & innerBoundary = innerBoundaries.at(index); if (minArea > 0.0 && area(innerBoundary) < minArea) { continue; } auto const & innerRingOsmData = placemarkOsmData.memberReference(index); clipper.Clear(); clipper.AddPath(path, ptClip, true); Path innerPath; foreach(auto const & node, innerBoundary) { innerPath << IntPoint(&node); } clipper.AddPath(innerPath, ptSubject, true); Paths innerPaths; clipper.Execute(ctIntersection, innerPaths); foreach(auto const &innerPath, innerPaths) { int const newIndex = newPolygon->innerBoundaries().size(); auto & newInnerRingOsmData = newPlacemarkOsmData.memberReference(newIndex); GeoDataLinearRing innerRing; nodeIndex = 0; foreach(const auto &point, innerPath) { GeoDataCoordinates const coordinates = point.coordinates(); innerRing << coordinates; auto const originalOsmData = innerRingOsmData.nodeReference(coordinates); if (originalOsmData.id() > 0) { newInnerRingOsmData.addNodeReference(coordinates, originalOsmData); } ++nodeIndex; } newPolygon->appendInnerBoundary(innerRing); if (innerRingOsmData.id() > 0) { newInnerRingOsmData.addTag(QStringLiteral("mx:oid"), QString::number(innerRingOsmData.id())); } copyTags(innerRingOsmData, newInnerRingOsmData); } } OsmObjectManager::initializeOsmData(newPlacemark); document->append(newPlacemark); osmIds << placemark->osmData().id(); } } void VectorClipper::copyTags(const GeoDataPlacemark &source, GeoDataPlacemark &target) const { copyTags(source.osmData(), target.osmData()); } void VectorClipper::copyTags(const OsmPlacemarkData &originalPlacemarkData, OsmPlacemarkData &targetOsmData) const { for (auto iter=originalPlacemarkData.tagsBegin(), end=originalPlacemarkData.tagsEnd(); iter != end; ++iter) { targetOsmData.addTag(iter.key(), iter.value()); } } }