From df79b0517844d7d24aa9b24968901e7d927751aa Mon Sep 17 00:00:00 2001 From: zmiao Date: Fri, 13 Mar 2020 20:02:18 +0200 Subject: [core] Using TileCoordinates for geometry comparison --- include/mbgl/style/expression/within.hpp | 3 +- src/mbgl/style/expression/within.cpp | 233 +++++++++++++++++++------------ src/mbgl/util/geometry_within.cpp | 92 +++--------- src/mbgl/util/geometry_within.hpp | 20 +-- 4 files changed, 180 insertions(+), 168 deletions(-) diff --git a/include/mbgl/style/expression/within.hpp b/include/mbgl/style/expression/within.hpp index eb23c7d0e0..a8e45f9bf0 100644 --- a/include/mbgl/style/expression/within.hpp +++ b/include/mbgl/style/expression/within.hpp @@ -11,7 +11,7 @@ namespace expression { class Within final : public Expression { public: - explicit Within(GeoJSON geojson, Feature::geometry_type geometries_, const WithinBBox& polygonBBox_); + explicit Within(GeoJSON geojson, Feature::geometry_type geometries_); ~Within() override; @@ -31,7 +31,6 @@ public: private: GeoJSON geoJSONSource; Feature::geometry_type geometries; - WithinBBox polygonBBox; }; } // namespace expression diff --git a/src/mbgl/style/expression/within.cpp b/src/mbgl/style/expression/within.cpp index 49ae8b54a8..0f2f837420 100644 --- a/src/mbgl/style/expression/within.cpp +++ b/src/mbgl/style/expression/within.cpp @@ -9,78 +9,149 @@ #include #include +#include namespace mbgl { namespace { -bool pointsWithinPolygons(const mbgl::GeometryTileFeature& feature, - const mbgl::CanonicalTileID& canonical, - const Feature::geometry_type& polygonGeoSet, - const WithinBBox& polyBBox) { - return polygonGeoSet.match( - [&feature, &canonical, &polyBBox](const mapbox::geometry::multi_polygon& polygons) -> bool { - return convertGeometry(feature, canonical) - .match( - [&polygons, &polyBBox](const mapbox::geometry::point& point) -> bool { - return boxWithinBox(calculateBBox(point), polyBBox) && pointWithinPolygons(point, polygons); - }, - [&polygons, &polyBBox](const mapbox::geometry::multi_point& points) -> bool { - return boxWithinBox(calculateBBox(points), polyBBox) && - std::all_of(points.begin(), points.end(), [&polygons](const auto& p) { - return pointWithinPolygons(p, polygons); - }); - }, - [](const auto&) -> bool { return false; }); - }, - [&feature, &canonical, &polyBBox](const mapbox::geometry::polygon& polygon) -> bool { - return convertGeometry(feature, canonical) - .match( - [&polygon, &polyBBox](const mapbox::geometry::point& point) -> bool { - return boxWithinBox(calculateBBox(point), polyBBox) && pointWithinPolygon(point, polygon); - }, - [&polygon, &polyBBox](const mapbox::geometry::multi_point& points) -> bool { - return boxWithinBox(calculateBBox(points), polyBBox) && - std::all_of(points.begin(), points.end(), [&polygon](const auto& p) { - return pointWithinPolygon(p, polygon); - }); - }, - [](const auto&) -> bool { return false; }); - }, - [](const auto&) -> bool { return false; }); +Point latLonToTileCoodinates(const Point& c, const mbgl::CanonicalTileID& canonical) { + Point p; + + const double size = util::EXTENT * std::pow(2, canonical.z); + + auto x = (c.x + 180.0) * size / 360.0; + p.x = (util::clamp(x, std::numeric_limits::min(), std::numeric_limits::max())); + + auto y = (180 - (std::log(std::tan((c.y + 90) * M_PI / 360.0)) * 180 / M_PI)) * size / 360; + p.y = (util::clamp(y, std::numeric_limits::min(), std::numeric_limits::max())); + + return p; +}; + +Polygon getTilePolygon(const Polygon& polygon, + const mbgl::CanonicalTileID& canonical, + WithinBBox& bbox) { + Polygon result; + result.reserve(polygon.size()); + for (const auto& ring : polygon) { + LinearRing temp; + temp.reserve(ring.size()); + for (const auto p : ring) { + const auto coord = latLonToTileCoodinates(p, canonical); + temp.push_back(coord); + updateBBox(bbox, coord); + } + result.push_back(std::move(temp)); + } + return result; +} + +MultiPolygon getTilePolygons(const MultiPolygon& polygons, + const mbgl::CanonicalTileID& canonical, + WithinBBox& bbox) { + MultiPolygon result; + result.reserve(polygons.size()); + for (const auto& pg : polygons) { + result.push_back(getTilePolygon(pg, canonical, bbox)); + } + return result; +} + +MultiPoint getTilePoints(const GeometryCoordinates& points, + const mbgl::CanonicalTileID& canonical, + WithinBBox& bbox) { + const int64_t x0 = util::EXTENT * canonical.x; + const int64_t y0 = util::EXTENT * canonical.y; + MultiPoint results; + results.reserve(points.size()); + for (const auto& p : points) { + auto point = Point(p.x + x0, p.y + y0); + updateBBox(bbox, point); + results.push_back(point); + } + return results; } -bool linesWithinPolygons(const mbgl::GeometryTileFeature& feature, - const mbgl::CanonicalTileID& canonical, - const Feature::geometry_type& polygonGeoSet, - const WithinBBox& polyBBox) { +MultiLineString getTileLines(const GeometryCollection& lines, + const mbgl::CanonicalTileID& canonical, + WithinBBox& bbox) { + const int64_t x0 = util::EXTENT * canonical.x; + const int64_t y0 = util::EXTENT * canonical.y; + MultiLineString results; + results.reserve(bbox.size()); + for (const auto& line : lines) { + LineString lineString; + lineString.reserve(line.size()); + for (const auto& p : line) { + auto point = Point(p.x + x0, p.y + y0); + updateBBox(bbox, point); + lineString.push_back(point); + } + results.push_back(std::move(lineString)); + } + return results; +} + +bool featureWithinPolygons(const GeometryTileFeature& feature, + const CanonicalTileID& canonical, + const Feature::geometry_type& polygonGeoSet) { return polygonGeoSet.match( - [&feature, &canonical, &polyBBox](const MultiPolygon& polygons) -> bool { - return convertGeometry(feature, canonical) - .match( - [&polygons, &polyBBox](const LineString& line) -> bool { - return boxWithinBox(calculateBBox(line), polyBBox) && lineStringWithinPolygons(line, polygons); - }, - [&polygons, &polyBBox](const MultiLineString& lines) -> bool { - return boxWithinBox(calculateBBox(lines), polyBBox) && - std::all_of(lines.begin(), lines.end(), [&polygons](const auto& line) { - return lineStringWithinPolygons(line, polygons); - }); - }, - [](const auto&) -> bool { return false; }); + [&feature, &canonical](const mapbox::geometry::multi_polygon& polys) -> bool { + WithinBBox polyBBox = DefaultBBox; + auto polygons = getTilePolygons(polys, canonical, polyBBox); + + const GeometryCollection& geometries = feature.getGeometries(); + switch (feature.getType()) { + case FeatureType::Point: { + WithinBBox pointBBox = DefaultBBox; + MultiPoint points = getTilePoints(geometries.at(0), canonical, pointBBox); + if (!boxWithinBox(pointBBox, polyBBox)) return false; + + return std::all_of(points.begin(), points.end(), [&polygons](const auto& p) { + return pointWithinPolygons(p, polygons); + }); + } + case FeatureType::LineString: { + WithinBBox lineBBox = DefaultBBox; + MultiLineString multiLineString = getTileLines(geometries, canonical, lineBBox); + + if (!boxWithinBox(lineBBox, polyBBox)) return false; + + return std::all_of(multiLineString.begin(), multiLineString.end(), [&polygons](const auto& line) { + return lineStringWithinPolygons(line, polygons); + }); + } + default: + return false; + }; }, - [&feature, &canonical, &polyBBox](const Polygon& polygon) -> bool { - return convertGeometry(feature, canonical) - .match( - [&polygon, &polyBBox](const LineString& line) -> bool { - return boxWithinBox(calculateBBox(line), polyBBox) && lineStringWithinPolygon(line, polygon); - }, - [&polygon, &polyBBox](const MultiLineString& lines) -> bool { - return boxWithinBox(calculateBBox(lines), polyBBox) && - std::all_of(lines.begin(), lines.end(), [&polygon](const auto& line) { - return lineStringWithinPolygon(line, polygon); - }); - }, - [](const auto&) -> bool { return false; }); + [&feature, &canonical](const mapbox::geometry::polygon& poly) -> bool { + WithinBBox polyBBox = DefaultBBox; + const auto polygon = getTilePolygon(poly, canonical, polyBBox); + + const GeometryCollection& geometries = feature.getGeometries(); + switch (feature.getType()) { + case FeatureType::Point: { + WithinBBox pointBBox = DefaultBBox; + MultiPoint points = getTilePoints(geometries.at(0), canonical, pointBBox); + if (!boxWithinBox(pointBBox, polyBBox)) return false; + + return std::all_of(points.begin(), points.end(), [&polygon](const auto& p) { + return pointWithinPolygon(p, polygon); + }); + } + case FeatureType::LineString: { + WithinBBox lineBBox = DefaultBBox; + MultiLineString multiLineString = getTileLines(geometries, canonical, lineBBox); + if (!boxWithinBox(lineBBox, polyBBox)) return false; + + return std::all_of(multiLineString.begin(), multiLineString.end(), [&polygon](const auto& line) { + return lineStringWithinPolygon(line, polygon); + }); + } + default: + return false; + }; }, [](const auto&) -> bool { return false; }); } @@ -100,16 +171,11 @@ mbgl::optional parseValue(const mbgl::style::conversion::Converti return nullopt; } -struct PolygonInfo { - PolygonInfo(const Feature::geometry_type& geometry_) : geometry(geometry_), bbox(calculateBBox(geometry)){}; - Feature::geometry_type geometry; - WithinBBox bbox; -}; - -mbgl::optional getPolygonInfo(const Feature& polyFeature, mbgl::style::expression::ParsingContext& ctx) { +mbgl::optional getPolygonInfo(const Feature& polyFeature, + mbgl::style::expression::ParsingContext& ctx) { const auto type = apply_visitor(ToFeatureType(), polyFeature.geometry); if (type == FeatureType::Polygon) { - return PolygonInfo(polyFeature.geometry); + return polyFeature.geometry; } ctx.error("'within' expression requires valid geojson source that contains polygon geometry type."); return nullopt; @@ -119,11 +185,8 @@ mbgl::optional getPolygonInfo(const Feature& polyFeature, mbgl::sty namespace style { namespace expression { -Within::Within(GeoJSON geojson, Feature::geometry_type geometries_, const WithinBBox& polygonBBox_) - : Expression(Kind::Within, type::Boolean), - geoJSONSource(std::move(geojson)), - geometries(std::move(geometries_)), - polygonBBox(polygonBBox_) {} +Within::Within(GeoJSON geojson, Feature::geometry_type geometries_) + : Expression(Kind::Within, type::Boolean), geoJSONSource(std::move(geojson)), geometries(std::move(geometries_)) {} Within::~Within() = default; @@ -134,11 +197,9 @@ EvaluationResult Within::evaluate(const EvaluationContext& params) const { return false; } auto geometryType = params.feature->getType(); - // Currently only support Point/Points in Polygon/Polygons - if (geometryType == FeatureType::Point) { - return pointsWithinPolygons(*params.feature, *params.canonical, geometries, polygonBBox); - } else if (geometryType == FeatureType::LineString) { - return linesWithinPolygons(*params.feature, *params.canonical, geometries, polygonBBox); + // Currently only support Point and LineString types in Polygon/Polygons + if (geometryType == FeatureType::Point || geometryType == FeatureType::LineString) { + return featureWithinPolygons(*params.feature, *params.canonical, geometries); } mbgl::Log::Warning(mbgl::Event::General, "within expression currently only support Point/LineString geometry type."); @@ -163,20 +224,20 @@ ParseResult Within::parse(const Convertible& value, ParsingContext& ctx) { return parsedValue->match( [&parsedValue, &ctx](const mapbox::geometry::geometry& geometrySet) { if (auto ret = getPolygonInfo(mbgl::Feature(geometrySet), ctx)) { - return ParseResult(std::make_unique(*parsedValue, std::move(ret->geometry), ret->bbox)); + return ParseResult(std::make_unique(*parsedValue, std::move(*ret))); } return ParseResult(); }, [&parsedValue, &ctx](const mapbox::feature::feature& feature) { if (auto ret = getPolygonInfo(mbgl::Feature(feature), ctx)) { - return ParseResult(std::make_unique(*parsedValue, std::move(ret->geometry), ret->bbox)); + return ParseResult(std::make_unique(*parsedValue, std::move(*ret))); } return ParseResult(); }, [&parsedValue, &ctx](const mapbox::feature::feature_collection& features) { for (const auto& feature : features) { if (auto ret = getPolygonInfo(mbgl::Feature(feature), ctx)) { - return ParseResult(std::make_unique(*parsedValue, std::move(ret->geometry), ret->bbox)); + return ParseResult(std::make_unique(*parsedValue, std::move(*ret))); } } return ParseResult(); @@ -234,7 +295,7 @@ mbgl::Value Within::serialize() const { bool Within::operator==(const Expression& e) const { if (e.getKind() == Kind::Within) { auto rhs = static_cast(&e); - return geoJSONSource == rhs->geoJSONSource && geometries == rhs->geometries && polygonBBox == rhs->polygonBBox; + return geoJSONSource == rhs->geoJSONSource && geometries == rhs->geometries; } return false; } diff --git a/src/mbgl/util/geometry_within.cpp b/src/mbgl/util/geometry_within.cpp index 4a0a5cce11..3c5e26c940 100644 --- a/src/mbgl/util/geometry_within.cpp +++ b/src/mbgl/util/geometry_within.cpp @@ -5,12 +5,14 @@ namespace mbgl { namespace { -bool rayIntersect(const Point& p, const Point& p1, const Point& p2) { + +bool rayIntersect(const Point& p, const Point& p1, const Point& p2) { return ((p1.y > p.y) != (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x); } // check if point p in on line segment with end points p1 and p2 -bool onBoundary(const Point& p, const Point& p1, const Point& p2) { + +bool onBoundary(const Point& p, const Point& p1, const Point& p2) { // requirements of point p on line segment: // 1. colinear: cross product of vector p->p1(x1, y1) and vector p->p2(x2, y2) equals to 0 // 2. p is between p1 and p2 @@ -22,20 +24,24 @@ bool onBoundary(const Point& p, const Point& p1, const Point& a, const Point& b, const Point& c, const Point& d) { - const auto perp = [](const Point& v1, const Point& v2) { return (v1.x * v2.y - v1.y * v2.x); }; + +bool lineIntersectLine(const Point& a, + const Point& b, + const Point& c, + const Point& d) { + const auto perp = [](const Point& v1, const Point& v2) { return (v1.x * v2.y - v1.y * v2.x); }; // check if two segments are parallel or not // precondition is end point a, b is inside polygon, if line a->b is // parallel to polygon edge c->d, then a->b won't intersect with c->d - auto vectorP = Point(b.x - a.x, b.y - a.y); - auto vectorQ = Point(d.x - c.x, d.y - c.y); + auto vectorP = Point(b.x - a.x, b.y - a.y); + auto vectorQ = Point(d.x - c.x, d.y - c.y); if (perp(vectorQ, vectorP) == 0) return false; // check if p1 and p2 are in different sides of line segment q1->q2 const auto twoSided = - [](const Point& p1, const Point& p2, const Point& q1, const Point& q2) { - double x1, y1, x2, y2, x3, y3; + [](const Point& p1, const Point& p2, const Point& q1, const Point& q2) { + int64_t x1, y1, x2, y2, x3, y3; // q1->p1 (x1, y1), q1->p2 (x2, y2), q1->q2 (x3, y3) x1 = p1.x - q1.x; @@ -55,7 +61,7 @@ bool lineIntersectLine(const Point& a, const Point& b, const Poi return false; } -bool lineIntersectPolygon(const Point& p1, const Point& p2, const Polygon& polygon) { +bool lineIntersectPolygon(const Point& p1, const Point& p2, const Polygon& polygon) { for (auto ring : polygon) { auto length = ring.size(); // loop through every edge of the ring @@ -68,21 +74,16 @@ bool lineIntersectPolygon(const Point& p1, const Point& p2, cons return false; } -void updateBBox(WithinBBox& bbox, const Point& p) { +} // namespace + +void updateBBox(WithinBBox& bbox, const Point& p) { bbox[0] = std::min(p.x, bbox[0]); bbox[1] = std::min(p.y, bbox[1]); bbox[2] = std::max(p.x, bbox[2]); bbox[3] = std::max(p.y, bbox[3]); } -bool isBBoxValid(const WithinBBox& bbox) { - return bbox != DefaultBBox; -} - -} // namespace - bool boxWithinBox(const WithinBBox& bbox1, const WithinBBox& bbox2) { - if (!isBBoxValid(bbox1) || !isBBoxValid(bbox2)) return false; if (bbox1[0] <= bbox2[0]) return false; if (bbox1[2] >= bbox2[2]) return false; if (bbox1[1] <= bbox2[1]) return false; @@ -90,57 +91,8 @@ bool boxWithinBox(const WithinBBox& bbox1, const WithinBBox& bbox2) { return true; } -WithinBBox calculateBBox(const Geometry& geometries) { - WithinBBox result = DefaultBBox; - - return geometries.match( - [&result](const Point& point) { - updateBBox(result, point); - return result; - }, - [&result](const MultiPoint& points) { - for (const auto point : points) { - updateBBox(result, point); - } - return result; - }, - [&result](const LineString& line) { - for (const auto point : line) { - updateBBox(result, point); - } - return result; - }, - [&result](const MultiLineString& lines) { - for (const auto& line : lines) { - for (const auto point : line) { - updateBBox(result, point); - } - } - return result; - }, - [&result](const Polygon& polygon) { - for (const auto& ring : polygon) { - for (const auto point : ring) { - updateBBox(result, point); - } - } - return result; - }, - [&result](const MultiPolygon& polygons) { - for (const auto& polygon : polygons) { - for (const auto& ring : polygon) { - for (const auto point : ring) { - updateBBox(result, point); - } - } - } - return result; - }, - [](const auto&) { return DefaultBBox; }); -} - // ray casting algorithm for detecting if point is in polygon -bool pointWithinPolygon(const Point& point, const Polygon& polygon) { +bool pointWithinPolygon(const Point& point, const Polygon& polygon) { bool within = false; for (const auto& ring : polygon) { const auto length = ring.size(); @@ -155,14 +107,14 @@ bool pointWithinPolygon(const Point& point, const Polygon& polyg return within; } -bool pointWithinPolygons(const Point& point, const MultiPolygon& polygons) { +bool pointWithinPolygons(const Point& point, const MultiPolygon& polygons) { for (const auto& polygon : polygons) { if (pointWithinPolygon(point, polygon)) return true; } return false; } -bool lineStringWithinPolygon(const LineString& line, const Polygon& polygon) { +bool lineStringWithinPolygon(const LineString& line, const Polygon& polygon) { const auto length = line.size(); // First, check if geometry points of line segments are all inside polygon for (std::size_t i = 0; i < length; ++i) { @@ -180,7 +132,7 @@ bool lineStringWithinPolygon(const LineString& line, const Polygon& line, const MultiPolygon& polygons) { +bool lineStringWithinPolygons(const LineString& line, const MultiPolygon& polygons) { for (const auto& polygon : polygons) { if (lineStringWithinPolygon(line, polygon)) return true; } diff --git a/src/mbgl/util/geometry_within.hpp b/src/mbgl/util/geometry_within.hpp index 56f755c7d7..b078783501 100644 --- a/src/mbgl/util/geometry_within.hpp +++ b/src/mbgl/util/geometry_within.hpp @@ -7,23 +7,23 @@ namespace mbgl { // contains minX, minY, maxX, maxY -using WithinBBox = std::array; -const WithinBBox DefaultBBox = WithinBBox{std::numeric_limits::infinity(), - std::numeric_limits::infinity(), - -std::numeric_limits::infinity(), - -std::numeric_limits::infinity()}; +using WithinBBox = std::array; +const WithinBBox DefaultBBox = WithinBBox{std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::min(), + std::numeric_limits::min()}; // check if bbox1 is within bbox2 bool boxWithinBox(const WithinBBox& bbox1, const WithinBBox& bbox2); -WithinBBox calculateBBox(const Geometry& geometries); +void updateBBox(WithinBBox& bbox, const Point& p); -bool pointWithinPolygon(const Point& point, const Polygon& polygon); +bool pointWithinPolygon(const Point& point, const Polygon& polygon); -bool pointWithinPolygons(const Point& point, const MultiPolygon& polygons); +bool pointWithinPolygons(const Point& point, const MultiPolygon& polygons); -bool lineStringWithinPolygon(const LineString& lineString, const Polygon& polygon); +bool lineStringWithinPolygon(const LineString& lineString, const Polygon& polygon); -bool lineStringWithinPolygons(const LineString& line, const MultiPolygon& polygons); +bool lineStringWithinPolygons(const LineString& line, const MultiPolygon& polygons); } // namespace mbgl -- cgit v1.2.1