diff --git a/README.md b/README.md index b920872..f583171 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,4 @@ -``` - _-====-__-=-__-===-__-=======-__ - _( _) - OO( ) - . o '===-______-===-____-==-__-=====' - .o - . ______ _______________ - _()_||__|| __o^o___ | [] [] [] [] | - ( | | | |o - /-OO----OO""="OO--OO"="OO---------OO" -############################################################ -``` +![Map-Matched path of a single train through Switzerland](geo/schweiz_mmatched.png?raw=true) # pfaedle @@ -18,7 +7,7 @@ Precise map-matching for public transit schedules (GTFS data). ## Requirements * `cmake` - * `gcc` >= 4.8 + * `gcc` >= 4.8 (may work on lower versions, untested) ## Building and Installation @@ -49,16 +38,29 @@ pfaedle -c -x A shape'd version of the input GTFS feed will be written to `./gtfs-out`. -A default configuration file `pfaedle.cfg` can be found in this repo. - By default, shapes are only calculated for trips that don't have a shape in the input feed. To drop all existing shapes, use the `-D` flag. +For example, you may generate (and replace existing, see -D parameter) shapes for the GTFS dataset for Freiburg like this: + +``` +$ mkdir freiburg_gtfs && cd freiburg_gtfs +$ wget https://fritz.freiburg.de/csv_Downloads/VAGFR.zip +$ unzip VAGFR.zip +$ wget http://download.geofabrik.de/europe/germany/baden-wuerttemberg/freiburg-regbez-latest.osm.bz2 +$ bunzip2 freiburg-regbez-latest.osm.bz2 +$ mkdir gtfs-out +$ pfaedle -D -c pfaedle.cfg -x freiburg-regbez-latest.osm . +``` + +A default configuration file `pfaedle.cfg` can be found in this repo. + + ## Generating shapes for a specific MOT To generate shapes only for a specific mot, use the `-m` option. Possible values are either `tram`, `bus`, `rail`, `subway`, `ferry`, `funicular`, -`gondola`, `all`. +`gondola`, `all` (default). Multiple values can be specified (comma separated). diff --git a/geo/schweiz_mmatched.png b/geo/schweiz_mmatched.png new file mode 100644 index 0000000..d2c6b17 Binary files /dev/null and b/geo/schweiz_mmatched.png differ diff --git a/src/pfaedle/PfaedleMain.cpp b/src/pfaedle/PfaedleMain.cpp index c7d3960..2c54028 100644 --- a/src/pfaedle/PfaedleMain.cpp +++ b/src/pfaedle/PfaedleMain.cpp @@ -154,7 +154,7 @@ int main(int argc, char** argv) { if (cfg.evaluate) ecoll.printStats(&std::cout); if (cfg.feedPaths.size()) { - LOG(INFO) << "Writing output GTFS..."; + LOG(INFO) << "Writing output GTFS to " << cfg.outputPath << " ..."; ad::cppgtfs::Writer w; w.write(>fs, cfg.outputPath); } diff --git a/src/pfaedle/config/PfaedleConfig.h b/src/pfaedle/config/PfaedleConfig.h index 74047b8..ba9a2a0 100644 --- a/src/pfaedle/config/PfaedleConfig.h +++ b/src/pfaedle/config/PfaedleConfig.h @@ -21,6 +21,7 @@ struct Config { : dbgOutputPath("geo"), solveMethod("global"), evalPath("."), + outputPath("gtfs-out"), dropShapes(false), useHMM(false), writeGraph(false), diff --git a/src/util/geo/Box.h b/src/util/geo/Box.h index dfaae15..f2e77de 100644 --- a/src/util/geo/Box.h +++ b/src/util/geo/Box.h @@ -41,10 +41,41 @@ class Box { template class RotatedBox { public: - RotatedBox() : _box() {} - RotatedBox(const Box& box) : _box(box), _deg(0), _center() {} + RotatedBox() : _box(), _deg(0), _center() {} + RotatedBox(const Box& box) + : _box(box), + _deg(0), + _center(Point( + (box.getUpperRight().getX() - box.getLowerLeft().getX()) / T(2), + (box.getUpperRight().getY() - box.getLowerLeft().getY()) / T(2))) {} RotatedBox(const Point& ll, const Point& ur) - : _box(ll, ur), _deg(0), _center() {} + : _box(ll, ur), + _deg(0), + _center(Point((ur.getX() - ll.getX()) / T(2), + (ur.getY() - ll.getY()) / T(2))) {} + RotatedBox(const Box& box, double deg) + : _box(box), + _deg(deg), + _center(Point( + (box.getUpperRight().getX() - box.getLowerLeft().getX()) / T(2), + (box.getUpperRight().getY() - box.getLowerLeft().getY()) / T(2))) {} + RotatedBox(const Point& ll, const Point& ur, double deg) + : _box(ll, ur), + _deg(deg), + _center(Point((ur.getX() - ll.getX()) / T(2), + (ur.getY() - ll.getY()) / T(2))) {} + RotatedBox(const Box& box, double deg, const Point& center) + : _box(box), _deg(deg), _center(center) {} + RotatedBox(const Point& ll, const Point& ur, double deg, + const Point& center) + : _box(ll, ur), _deg(deg), _center(center) {} + + const Box& getBox() const { return _box; } + Box& getBox() { return _box; } + + double getDegree() const { return _deg; } + const Point& getCenter() const { return _center; } + Point& getCenter() { return _center; } private: Box _box; diff --git a/src/util/geo/Geo.h b/src/util/geo/Geo.h index f823296..a020c55 100644 --- a/src/util/geo/Geo.h +++ b/src/util/geo/Geo.h @@ -8,6 +8,8 @@ #define _USE_MATH_DEFINES #include +#include +#include #include #include #include "util/Misc.h" @@ -45,7 +47,7 @@ typedef Polygon DPolygon; typedef Polygon FPolygon; typedef Polygon IPolygon; -const static double EPSILON = 0.0000001; +const static double EPSILON = 0.00000000001; const static double RAD = 0.017453292519943295; // PI/180 // _____________________________________________________________________________ @@ -93,6 +95,14 @@ inline Point centroid(const Box box) { return centroid(LineSegment(box.getLowerLeft(), box.getUpperRight())); } +// _____________________________________________________________________________ +template typename Geometry> +inline Point centroid(std::vector> multigeo) { + Line a; + for (const auto& g : multigeo) a.push_back(centroid(g)); + return centroid(a); +} + // _____________________________________________________________________________ template inline Point rotate(const Point& p, double deg) { @@ -142,12 +152,63 @@ inline Polygon rotate(Polygon geo, double deg, const Point& c) { return geo; } +// _____________________________________________________________________________ +template typename Geometry> +inline std::vector> rotate(std::vector> multigeo, + double deg, const Point& c) { + for (size_t i = 0; i < multigeo.size(); i++) + multigeo[i] = rotate(multigeo[i], deg, c); + return multigeo; +} + +// _____________________________________________________________________________ +template typename Geometry> +inline std::vector> rotate(std::vector> multigeo, + double deg) { + auto c = centroid(multigeo); + for (size_t i = 0; i < multigeo.size(); i++) + multigeo[i] = rotate(multigeo[i], deg, c); + return multigeo; +} + // _____________________________________________________________________________ template inline Point move(const Point& geo, T x, T y) { return Point(geo.getX() + x, geo.getY() + y); } +// _____________________________________________________________________________ +template +inline Line move(Line geo, T x, T y) { + for (size_t i = 0; i < geo.size(); i++) geo[i] = move(geo[i], x, y); + return geo; +} + +// _____________________________________________________________________________ +template +inline LineSegment move(LineSegment geo, T x, T y) { + geo.first = move(geo.first, x, y); + geo.second = move(geo.second, x, y); + return geo; +} + +// _____________________________________________________________________________ +template +inline Polygon move(Polygon geo, T x, T y) { + for (size_t i = 0; i < geo.getOuter().size(); i++) + geo.getOuter()[i] = move(geo.getOuter()[i], x, y); + return geo; +} + +// _____________________________________________________________________________ +template typename Geometry> +inline std::vector> move(std::vector> multigeo, T x, + T y) { + for (size_t i = 0; i < multigeo.size(); i++) + multigeo[i] = move(multigeo[i], x, y); + return multigeo; +} + // _____________________________________________________________________________ template inline Box minbox() { @@ -155,23 +216,20 @@ inline Box minbox() { } // _____________________________________________________________________________ -// template -// inline RotatedBox shrink(const RotatedBox& b, double d) { -// double xd = -// b.b.getUpperRight().getX() - b.b.getLowerLeft().getX(); -// double yd = -// b.b.getUpperRight().getY() - b.b.getLowerLeft().getY(); +template +inline RotatedBox shrink(const RotatedBox& b, double d) { + double xd = b.b.getUpperRight().getX() - b.b.getLowerLeft().getX(); + double yd = b.b.getUpperRight().getY() - b.b.getLowerLeft().getY(); -// if (xd <= 2 * d) d = xd / 2 - 1; -// if (yd <= 2 * d) d = yd / 2 - 1; + if (xd <= 2 * d) d = xd / 2 - 1; + if (yd <= 2 * d) d = yd / 2 - 1; -// Box r(Point(b.b.getLowerLeft().getX() + d, -// b.b.getLowerLeft().getY() + d), -// Point(b.b.getUpperRight().getX() - d, -// b.b.getUpperRight().getY() - d)); + Box r( + Point(b.b.getLowerLeft().getX() + d, b.b.getLowerLeft().getY() + d), + Point(b.b.getUpperRight().getX() - d, b.b.getUpperRight().getY() - d)); -// return RotatedBox(r, b.rotateDeg, b.center); -// } + return RotatedBox(r, b.getDegree(), b.getCenter()); +} // _____________________________________________________________________________ inline bool doubleEq(double a, double b) { return fabs(a - b) < 0.000001; } @@ -199,6 +257,12 @@ inline bool contains(const LineSegment& l, const Box& box) { return contains(l.first, box) && contains(l.second, box); } +// _____________________________________________________________________________ +template +inline bool contains(const Box& b, const Box& box) { + return contains(b.getLowerLeft(), box) && contains(b.getUpperRight(), box); +} + // _____________________________________________________________________________ template inline bool contains(const Point& p, const LineSegment& ls) { @@ -236,13 +300,17 @@ inline int8_t polyContCheck(const Point& a, Point b, Point c) { if (a.getY() == b.getY() && a.getY() == c.getY()) return (!((b.getX() <= a.getX() && a.getX() <= c.getX()) || (c.getX() <= a.getX() && a.getX() <= b.getX()))); - if (a.getY() == b.getY() && a.getX() == b.getX()) return 0; + if (fabs(a.getY() - b.getY()) < EPSILON && + fabs(a.getX() - b.getX()) < EPSILON) + return 0; if (b.getY() > c.getY()) { Point tmp = b; b = c; c = tmp; } - if (a.getY() <= b.getY() || a.getY() > c.getY()) return 1; + if (a.getY() <= b.getY() || a.getY() > c.getY()) { + return 1; + } double d = (b.getX() - a.getX()) * (c.getY() - a.getY()) - (b.getY() - a.getY()) * (c.getX() - a.getX()); @@ -255,7 +323,9 @@ inline int8_t polyContCheck(const Point& a, Point b, Point c) { template inline bool contains(const Polygon& polyC, const Polygon& poly) { for (const auto& p : polyC.getOuter()) { - if (!contains(p, poly)) return false; + if (!contains(p, poly)) { + return false; + } } return true; } @@ -264,7 +334,20 @@ inline bool contains(const Polygon& polyC, const Polygon& poly) { template inline bool contains(const Line& l, const Polygon& poly) { for (const auto& p : l) { - if (!contains(p, poly)) return false; + if (!contains(p, poly)) { + return false; + } + } + return true; +} + +// _____________________________________________________________________________ +template +inline bool contains(const Line& l, const Line& other) { + for (const auto& p : l) { + if (!contains(p, other)) { + return false; + } } return true; } @@ -298,6 +381,16 @@ inline bool contains(const Polygon& poly, const Line& l) { return true; } +// _____________________________________________________________________________ +template typename GeometryA, + template typename GeometryB> +inline bool contains(const std::vector>& multigeo, + const GeometryB& geo) { + for (const auto& g : multigeo) + if (!contains(g, geo)) return false; + return true; +} + // _____________________________________________________________________________ template inline bool intersects(const LineSegment& ls1, const LineSegment& ls2) { @@ -463,6 +556,13 @@ inline Point intersection(const Point& p1, const Point& q1, p2.getY(), q2.getX(), q2.getY()); } +// _____________________________________________________________________________ +template +inline Point intersection(const LineSegment& s1, + const LineSegment& s2) { + return intersection(s1.first, s1.second, s2.first, s2.second); +} + // _____________________________________________________________________________ template inline bool lineIntersects(T p1x, T p1y, T q1x, T q1y, T p2x, T p2y, T q2x, @@ -534,11 +634,10 @@ inline double crossProd(const Point& a, const Point& b) { // _____________________________________________________________________________ template inline double crossProd(const Point& p, const LineSegment& ls) { - LineSegment lss(Point(0, 0), - Point(ls.second.getX() - ls.first.getX(), - ls.second.getY() - ls.first.getY())); - return crossProd(lss.second, Point(p.getX() - ls.first.getX(), - p.getY() - ls.first.getY())); + return crossProd( + Point(ls.second.getX() - ls.first.getX(), + ls.second.getY() - ls.first.getY()), + Point(p.getX() - ls.first.getX(), p.getY() - ls.first.getY())); } // _____________________________________________________________________________ @@ -555,6 +654,19 @@ inline std::string getWKT(const Point& p) { return ss.str(); } +// _____________________________________________________________________________ +template +inline std::string getWKT(const std::vector>& p) { + std::stringstream ss; + ss << "MULTIPOINT ("; + for (size_t i = 0; i < p.size(); i++) { + if (i) ss << ", "; + ss << "(" << p[i].getX() << " " << p[i].getY() << ")"; + } + ss << ")"; + return ss.str(); +} + // _____________________________________________________________________________ template inline std::string getWKT(const Line& l) { @@ -568,6 +680,26 @@ inline std::string getWKT(const Line& l) { return ss.str(); } +// _____________________________________________________________________________ +template +inline std::string getWKT(const std::vector>& ls) { + std::stringstream ss; + ss << "MULTILINESTRING ("; + + for (size_t j = 0; j < ls.size(); j++) { + if (j) ss << ", "; + ss << "("; + for (size_t i = 0; i < ls[j].size(); i++) { + if (i) ss << ", "; + ss << ls[j][i].getX() << " " << ls[j][i].getY(); + } + ss << ")"; + } + + ss << ")"; + return ss.str(); +} + // _____________________________________________________________________________ template inline std::string getWKT(const LineSegment& l) { @@ -594,13 +726,35 @@ inline std::string getWKT(const Polygon& p) { std::stringstream ss; ss << "POLYGON (("; for (size_t i = 0; i < p.getOuter().size(); i++) { - if (i) ss << ", "; - ss << p.getOuter()[i].getX() << " " << p.getOuter()[i].getY(); + ss << p.getOuter()[i].getX() << " " << p.getOuter()[i].getY() << ", "; } + ss << p.getOuter().front().getX() << " " << p.getOuter().front().getY(); ss << "))"; return ss.str(); } +// _____________________________________________________________________________ +template +inline std::string getWKT(const std::vector>& ls) { + std::stringstream ss; + ss << "MULTIPOLYGON ("; + + for (size_t j = 0; j < ls.size(); j++) { + if (j) ss << ", "; + ss << "(("; + for (size_t i = 0; i < ls[j].getOuter().size(); i++) { + ss << ls[j].getOuter()[i].getX() << " " << ls[j].getOuter()[i].getY() + << ", "; + } + ss << ls[j].getOuter().front().getX() << " " + << ls[j].getOuter().front().getY(); + ss << "))"; + } + + ss << ")"; + return ss.str(); +} + // _____________________________________________________________________________ template inline double len(const Point& g) { @@ -633,6 +787,12 @@ inline Box simplify(const Box& g, double d) { return g; } +// _____________________________________________________________________________ +template +inline RotatedBox simplify(const RotatedBox& g, double d) { + return g; +} + // _____________________________________________________________________________ template inline Line simplify(const Line& g, double d) { @@ -658,6 +818,15 @@ inline Line simplify(const Line& g, double d) { return Line{g.front(), g.back()}; } +// _____________________________________________________________________________ +template +inline Polygon simplify(const Polygon& g, double d) { + auto simple = simplify(g, d); + std::rotate(simple.begin(), simple.begin() + simple.size() / 2, simple.end()); + simple = simplify(simple, d); + return Polygon(simple); +} + // _____________________________________________________________________________ inline double distToSegment(double lax, double lay, double lbx, double lby, double px, double py) { @@ -755,43 +924,40 @@ inline double parallelity(const Box& box, const Line& line) { } // _____________________________________________________________________________ -// template -// inline double parallelity(const Box& box, const MultiLine& multiline) { -// double ret = 0; -// for (const Line& l : multiline) { -// ret += parallelity(box, l); -// } +template +inline double parallelity(const Box& box, const MultiLine& multiline) { + double ret = 0; + for (const Line& l : multiline) { + ret += parallelity(box, l); + } -// return ret / multiline.size(); -// } + return ret / multiline.size(); +} // _____________________________________________________________________________ -// template typename Geometry> -// inline RotatedBox getOrientedEnvelope(Geometry pol) { -// // TODO: implement this nicer, works for now, but inefficient -// // see -// // https://geidav.wordpress.com/tag/gift-wrapping/#fn-1057-FreemanShapira1975 -// // for a nicer algorithm +template typename Geometry> +inline RotatedBox getOrientedEnvelope(Geometry pol) { + // TODO: implement this nicer, works for now, but inefficient + // see + // https://geidav.wordpress.com/tag/gift-wrapping/#fn-1057-FreemanShapira1975 + // for a nicer algorithm -// Point center; -// bgeo::centroid(pol, center); + Point center = centroid(pol); + Box tmpBox = getBoundingBox(pol); + double rotateDeg = 0; -// Box tmpBox = getBoundingBox(pol); -// double rotateDeg = 0; + // rotate in 1 deg steps + for (int i = 1; i < 360; i += 1) { + pol = rotate(pol, 1, center); + Box e = getBoundingBox(pol); + if (area(tmpBox) > area(e)) { + tmpBox = e; + rotateDeg = i; + } + } -// // rotate in 5 deg steps -// for (int i = 1; i < 360; i += 1) { -// pol = rotate(pol, 1, center); -// Box e; -// bgeo::envelope(pol, e); -// if (bgeo::area(tmpBox) > bgeo::area(e)) { -// tmpBox = e; -// rotateDeg = i; -// } -// } - -// return RotatedBox(tmpBox, -rotateDeg, center); -// } + return RotatedBox(tmpBox, -rotateDeg, center); +} // _____________________________________________________________________________ template @@ -849,6 +1015,90 @@ inline Box getBoundingBox(const Box& b) { return b; } +// _____________________________________________________________________________ +template typename GeometryA> +inline Box getBoundingBox(const std::vector>& multigeo) { + Box b; + b = extendBox(multigeo, b); + return b; +} + +// _____________________________________________________________________________ +template +inline Polygon convexHull(const Point& p) { + return Polygon({p}); +} + +// _____________________________________________________________________________ +template +inline Polygon convexHull(const Box& b) { + return Polygon(b); +} + +// _____________________________________________________________________________ +template +inline Polygon convexHull(const LineSegment& b) { + return Polygon(Line{b.first, b.second}); +} + +// _____________________________________________________________________________ +template +inline Polygon convexHull(const RotatedBox& b) { + auto p = convexHull(b.getBox()); + p = rotate(p, b.getDegree(), b.getCenter()); + return p; +} + +// _____________________________________________________________________________ +template +inline size_t convexHullImpl(const Line& a, size_t p1, size_t p2, + Line* h, uint8_t d) { + // quickhull by Barber, Dobkin & Huhdanpaa + Point pa; + bool found = false; + double maxDist = 0; + for (const auto& p : a) { + double tmpDist = distToSegment((*h)[p1], (*h)[p2], p); + double cp = crossProd(p, LineSegment((*h)[p1], (*h)[p2])); + if (((cp > 0 && !d) || (cp < 0 && d)) && tmpDist > maxDist) { + pa = p; + found = true; + maxDist = tmpDist; + } + } + + if (!found) return 0; + + h->insert(h->begin() + p2 + !d, pa); + size_t in = 1 + convexHullImpl(a, p1, p2 + !d, h, d); + return in + convexHullImpl(a, p2 + in * d + 1 - 2 * d, p2 + in * d, h, d); +} + +// _____________________________________________________________________________ +template +inline Polygon convexHull(const Line& l) { + if (l.size() == 2) return convexHull(LineSegment(l[0], l[1])); + if (l.size() == 1) return convexHull(l[0]); + + Point left(std::numeric_limits::max(), 0); + Point right(std::numeric_limits::min(), 0); + for (const auto& p : l) { + if (p.getX() <= left.getX()) left = p; + if (p.getX() >= right.getX()) right = p; + } + + Line hull{left, right}; + convexHullImpl(l, 0, 1, &hull, 1); + convexHullImpl(l, 0, hull.size() - 1, &hull, 0); + return Polygon(hull); +} + +// _____________________________________________________________________________ +template +inline Polygon convexHull(const Polygon& p) { + return convexHull(p.getOuter()); +} + // _____________________________________________________________________________ template inline Box extendBox(const Line& l, Box b) { @@ -864,6 +1114,55 @@ inline Box extendBox(const LineSegment& ls, Box b) { return b; } +// _____________________________________________________________________________ +template typename Geometry> +inline Box extendBox(const std::vector>& multigeom, Box b) { + for (const auto& g : multigeom) b = extendBox(g, b); + return b; +} + +// _____________________________________________________________________________ +template +inline double area(const Point& b) { + UNUSED(b); + return 0; +} + +// _____________________________________________________________________________ +template +inline double area(const LineSegment& b) { + UNUSED(b); + return 0; +} + +// _____________________________________________________________________________ +template +inline double area(const Line& b) { + UNUSED(b); + return 0; +} + +// _____________________________________________________________________________ +template +inline double area(const Box& b) { + return (b.getUpperRight().getX() - b.getLowerLeft().getX()) * + (b.getUpperRight().getY() - b.getLowerLeft().getY()); +} + +// _____________________________________________________________________________ +template +inline double area(const Polygon& b) { + double ret = 0; + size_t j = b.getOuter().size() - 1; + for (size_t i = 0; i < b.getOuter().size(); i++) { + ret += (b.getOuter()[j].getX() + b.getOuter()[i].getX()) * + (b.getOuter()[j].getY() - b.getOuter()[i].getY()); + j = i; + } + + return fabs(ret / 2.0); +} + // _____________________________________________________________________________ template inline double commonArea(const Box& ba, const Box& bb) { @@ -877,76 +1176,68 @@ inline double commonArea(const Box& ba, const Box& bb) { } // _____________________________________________________________________________ -// template typename Geometry> -// inline RotatedBox getFullEnvelope(Geometry pol) { -// Point center; -// bgeo::centroid(pol, center); +template typename Geometry> +inline RotatedBox getFullEnvelope(Geometry pol) { + Point center = centroid(pol); + Box tmpBox = getBoundingBox(pol); + double rotateDeg = 0; -// Box tmpBox; -// bgeo::envelope(pol, tmpBox); -// double rotateDeg = 0; + std::vector> ml; -// MultiPolygon ml; + // rotate in 5 deg steps + for (int i = 1; i < 360; i += 1) { + pol = rotate(pol, 1, center); + Polygon hull = convexHull(pol); + ml.push_back(hull); + Box e = getBoundingBox(pol); + if (area(tmpBox) > area(e)) { + tmpBox = e; + rotateDeg = i; + } + } -// // rotate in 5 deg steps -// for (int i = 1; i < 360; i += 1) { -// pol = rotate(pol, 1, center); -// Polygon hull; -// bgeo::convex_hull(pol, hull); -// ml.push_back(hull); -// Box e; -// bgeo::envelope(pol, e); -// if (bgeo::area(tmpBox) > bgeo::area(e)) { -// tmpBox = e; -// rotateDeg = i; -// } -// } + tmpBox = getBoundingBox(ml); -// bgeo::envelope(ml, tmpBox); - -// return RotatedBox(tmpBox, rotateDeg, center); -// } + return RotatedBox(tmpBox, rotateDeg, center); +} // _____________________________________________________________________________ -// template -// inline RotatedBox getOrientedEnvelopeAvg(MultiLine ml) { -// MultiLine orig = ml; -// // get oriented envelope for hull -// RotatedBox rbox = getFullEnvelope(ml); -// Point center; -// bgeo::centroid(rbox.b, center); +template +inline RotatedBox getOrientedEnvelopeAvg(MultiLine ml) { + MultiLine orig = ml; + // get oriented envelope for hull + RotatedBox rbox = getFullEnvelope(ml); + Point center = centroid(rbox.getBox()); -// ml = rotate(ml, -rbox.rotateDeg - 45, center); + ml = rotate(ml, -rbox.getDegree() - 45, center); -// double bestDeg = -45; -// double score = parallelity(rbox.b, ml); + double bestDeg = -45; + double score = parallelity(rbox.b, ml); -// for (double i = -45; i <= 45; i += .5) { -// ml = rotate(ml, -.5, center); -// double p = parallelity(rbox.b, ml); -// if (parallelity(rbox.b, ml) > score) { -// bestDeg = i; -// score = p; -// } -// } + for (double i = -45; i <= 45; i += .5) { + ml = rotate(ml, -.5, center); + double p = parallelity(rbox.b, ml); + if (parallelity(rbox.b, ml) > score) { + bestDeg = i; + score = p; + } + } -// rbox.rotateDeg += bestDeg; + rbox.rotateDeg += bestDeg; -// // move the box along 45deg angles from its origin until it fits the ml -// // = until the intersection of its hull and the box is largest -// Polygon p = rbox.getPolygon(); -// p = rotate(p, -rbox.rotateDeg, rbox.center); + // move the box along 45deg angles from its origin until it fits the ml + // = until the intersection of its hull and the box is largest + Polygon p = convexHull(rbox); + p = rotate(p, -rbox.getDegree(), rbox.getCenter()); -// Polygon hull; -// bgeo::convex_hull(orig, hull); -// hull = rotate(hull, -rbox.rotateDeg, rbox.center); + Polygon hull = convexHull(orig); + hull = rotate(hull, -rbox.getDegree(), rbox.getCenter()); -// Box box; -// bgeo::envelope(hull, box); -// rbox = RotatedBox(box, rbox.rotateDeg, rbox.center); + Box box = getBoundingBox(hull); + rbox = RotatedBox(box, rbox.getDegree(), rbox.getCenter()); -// return rbox; -// } + return rbox; +} // _____________________________________________________________________________ template diff --git a/src/util/geo/Line.h b/src/util/geo/Line.h index d7153d7..dfa29e2 100644 --- a/src/util/geo/Line.h +++ b/src/util/geo/Line.h @@ -11,12 +11,16 @@ namespace util { namespace geo { -template -using Line = std::vector>; +template +class Line : public std::vector> { + using std::vector>::vector; +}; -template +template using LineSegment = std::pair, Point>; +template +using MultiLine = std::vector>; } // namespace geo } // namespace util diff --git a/src/util/geo/Point.h b/src/util/geo/Point.h index df2b9aa..923e9d4 100644 --- a/src/util/geo/Point.h +++ b/src/util/geo/Point.h @@ -39,6 +39,9 @@ class Point { T _x, _y; }; +template +using MultiPoint = std::vector>; + } // namespace geo } // namespace util diff --git a/src/util/geo/PolyLine.h b/src/util/geo/PolyLine.h index 89295cc..b95f038 100644 --- a/src/util/geo/PolyLine.h +++ b/src/util/geo/PolyLine.h @@ -17,6 +17,8 @@ namespace geo { static const double MAX_EQ_DISTANCE = 15; static const double AVERAGING_STEP = 20; +// legacy code, will be removed in the future + template struct LinePoint { LinePoint() : lastIndex(0), totalPos(-1), p() {} @@ -46,7 +48,6 @@ struct SharedSegments { std::vector> segments; }; -// TODO: maybe let this class inherit from a more generic geometry class template class PolyLine { public: diff --git a/src/util/geo/PolyLine.tpp b/src/util/geo/PolyLine.tpp index ec15b43..8d34c14 100644 --- a/src/util/geo/PolyLine.tpp +++ b/src/util/geo/PolyLine.tpp @@ -93,11 +93,11 @@ void PolyLine::offsetPerp(double units) { n1 = n1 / n; n2 = n2 / n; - lastP.template set<0>(lastP.getX() + (n1 * units)); - lastP.template set<1>(lastP.getY() + (n2 * units)); + lastP.setX(lastP.getX() + (n1 * units)); + lastP.setY(lastP.getY() + (n2 * units)); - curP.template set<0>(curP.getX() + (n1 * units)); - curP.template set<1>(curP.getY() + (n2 * units)); + curP.setX(curP.getX() + (n1 * units)); + curP.setY(curP.getY() + (n2 * units)); if (lastIns && befLastIns && lineIntersects(*lastIns, *befLastIns, lastP, curP)) { @@ -460,8 +460,8 @@ bool PolyLine::contains(const PolyLine& rhs, double dmax) const { template void PolyLine::move(double vx, double vy) { for (size_t i = 0; i < _line.size(); i++) { - _line[i].set<0>(_line[i].getX() + vx); - _line[i].set<1>(_line[i].getY() + vy); + _line[i].setX(_line[i].getX() + vx); + _line[i].setY(_line[i].getY() + vy); } } diff --git a/src/util/geo/Polygon.h b/src/util/geo/Polygon.h index 3b1adac..d39c9a5 100644 --- a/src/util/geo/Polygon.h +++ b/src/util/geo/Polygon.h @@ -25,13 +25,16 @@ class Polygon { b.getUpperRight(), Point(b.getLowerLeft().getX(), b.getUpperRight().getY())}) {} - const std::vector>& getOuter() const { return _outer; } - std::vector>& getOuter() { return _outer; } + const Line& getOuter() const { return _outer; } + Line& getOuter() { return _outer; } private: - std::vector> _outer; + Line _outer; }; +template +using MultiPolyon = std::vector>; + } // namespace geo } // namespace util diff --git a/src/util/tests/TestMain.cpp b/src/util/tests/TestMain.cpp index cd43ed3..d5a9220 100644 --- a/src/util/tests/TestMain.cpp +++ b/src/util/tests/TestMain.cpp @@ -272,38 +272,38 @@ CASE("frechet distance") { // ___________________________________________________________________________ CASE("geo box alignment") { - // Line a; - // a.push_back(Point(1, 1)); - // a.push_back(Point(1, 2)); + Line a; + a.push_back(Point(1, 1)); + a.push_back(Point(1, 2)); - // Line b; - // b.push_back(Point(1, 2)); - // b.push_back(Point(2, 2)); + Line b; + b.push_back(Point(1, 2)); + b.push_back(Point(2, 2)); - // Line c; - // c.push_back(Point(2, 2)); - // c.push_back(Point(2, 1)); + Line c; + c.push_back(Point(2, 2)); + c.push_back(Point(2, 1)); - // Line d; - // d.push_back(Point(2, 1)); - // d.push_back(Point(1, 1)); + Line d; + d.push_back(Point(2, 1)); + d.push_back(Point(1, 1)); - // Box box(Point(2, 3), Point(5, 4)); - // MultiLine ml; - // ml.push_back(a); - // ml.push_back(b); - // ml.push_back(c); - // ml.push_back(d); + Box box(Point(2, 3), Point(5, 4)); + MultiLine ml; + ml.push_back(a); + ml.push_back(b); + ml.push_back(c); + ml.push_back(d); - // EXPECT(parallelity(box, ml) == approx(1)); - // ml = rotate(ml, 45); - // EXPECT(parallelity(box, ml) == approx(0)); - // ml = rotate(ml, 45); - // EXPECT(parallelity(box, ml) == approx(1)); - // ml = rotate(ml, 45); - // EXPECT(parallelity(box, ml) == approx(0)); - // ml = rotate(ml, 45); - // EXPECT(parallelity(box, ml) == approx(1)); + EXPECT(parallelity(box, ml) == approx(1)); + ml = rotate(ml, 45); + EXPECT(parallelity(box, ml) == approx(0)); + ml = rotate(ml, 45); + EXPECT(parallelity(box, ml) == approx(1)); + ml = rotate(ml, 45); + EXPECT(parallelity(box, ml) == approx(0)); + ml = rotate(ml, 45); + EXPECT(parallelity(box, ml) == approx(1)); }, // ___________________________________________________________________________ @@ -944,7 +944,7 @@ CASE("geometry") { EXPECT(geo::intersects(Box(Point(0, 0), Point(1.5, 1.5)), boxa)); Polygon poly({Point(1, 1), Point(3, 2), Point(4, 3), Point(6, 3), Point(5, 1)}); - EXPECT(geo::getWKT(poly) == "POLYGON ((1 1, 3 2, 4 3, 6 3, 5 1))"); + EXPECT(geo::getWKT(poly) == "POLYGON ((1 1, 3 2, 4 3, 6 3, 5 1, 1 1))"); EXPECT(geo::contains(Point(4, 2), poly)); EXPECT(!geo::contains(Point(3, 3), poly)); EXPECT(geo::contains(Point(1, 1), poly)); @@ -977,8 +977,215 @@ CASE("geometry") { EXPECT(geo::rotate(rotLine, 90, Point(2, 2))[1].getX() == approx(3)); EXPECT(geo::rotate(rotLine, 90, Point(2, 2))[1].getY() == approx(1)); + MultiLine multiRotLine({{{1, 1}, {3, 3}}, {{1, 3}, {3, 1}}}); + EXPECT(geo::rotate(multiRotLine, 90, Point(2, 2))[0][0].getX() == approx(1)); + EXPECT(geo::rotate(multiRotLine, 90, Point(2, 2))[0][0].getY() == approx(3)); + EXPECT(geo::rotate(multiRotLine, 90, Point(2, 2))[0][1].getX() == approx(3)); + EXPECT(geo::rotate(multiRotLine, 90, Point(2, 2))[0][1].getY() == approx(1)); + EXPECT(geo::rotate(multiRotLine, 90, Point(2, 2))[1][0].getX() == approx(3)); + EXPECT(geo::rotate(multiRotLine, 90, Point(2, 2))[1][0].getY() == approx(3)); + EXPECT(geo::rotate(multiRotLine, 90, Point(2, 2))[1][1].getX() == approx(1)); + EXPECT(geo::rotate(multiRotLine, 90, Point(2, 2))[1][1].getY() == approx(1)); + + EXPECT(geo::getWKT(multiRotLine) == "MULTILINESTRING ((1 1, 3 3), (1 3, 3 1))"); + + EXPECT(geo::contains(multiRotLine[0], geo::move(geo::move(multiRotLine, 1.0, 2.0), -1.0, -2.0)[0])); + EXPECT(geo::contains(multiRotLine, geo::getBoundingBox(Line{{1, 1}, {3, 3}, {1, 3}, {3, 1}}))); + + EXPECT(geo::contains(getBoundingBox(multiRotLine), geo::getBoundingBox(Line{{1, 1}, {3, 3}, {1, 3}, {3, 1}}))); + EXPECT(geo::contains(geo::getBoundingBox(Line{{1, 1}, {3, 3}, {1, 3}, {3, 1}}), getBoundingBox(multiRotLine))); + EXPECT(geo::dist(geo::centroid(rotP), rotP) == approx(0)); EXPECT(geo::dist(geo::centroid(rotLine), rotP) == approx(0)); + EXPECT(geo::dist(geo::centroid(polybox), Point(3.5, 2.5)) == approx(0)); + EXPECT(geo::dist(geo::centroid(Polygon({{0, 0}, {3, 4}, {4,3}})), Point(7.0/3.0,7.0/3.0)) == approx(0)); + + auto polyy = Polygon({{0, 0}, {3, 4}, {4,3}}); + MultiPolyon mpoly{polyy, polyy}; + + EXPECT(geo::getWKT(polyy) == "POLYGON ((0 0, 3 4, 4 3, 0 0))"); + EXPECT(geo::getWKT(mpoly) == "MULTIPOLYGON (((0 0, 3 4, 4 3, 0 0)), ((0 0, 3 4, 4 3, 0 0)))"); + + auto hull = geo::convexHull(Line{{0.1, 3}, {1, 1}, {2, 2}, {4, 4}, {0, 0}, {1, 2}, {3, 1}, {3, 3}}); + EXPECT(hull.getOuter().size() == size_t(4)); + EXPECT(hull.getOuter()[0].getX() == approx(0)); + EXPECT(hull.getOuter()[0].getY() == approx(0)); + EXPECT(hull.getOuter()[1].getX() == approx(3)); + EXPECT(hull.getOuter()[1].getY() == approx(1)); + EXPECT(hull.getOuter()[2].getX() == approx(4)); + EXPECT(hull.getOuter()[2].getY() == approx(4)); + EXPECT(hull.getOuter()[3].getX() == approx(0.1)); + EXPECT(hull.getOuter()[3].getY() == approx(3)); + EXPECT(geo::contains(geo::convexHull(geo::getBoundingBox(poly)), geo::getBoundingBox(poly))); + EXPECT(geo::contains(geo::getBoundingBox(poly), geo::convexHull(geo::getBoundingBox(poly)))); + + auto hull2 = geo::convexHull(Line{{0.1, 3}, {1, 1}, {2, 2}, {4, 4}, {0, 0}, {1, 2}, {3, 1}, {3, 3}, {-0.1, 1}}); + EXPECT(hull2.getOuter().size() == size_t(5)); + EXPECT(hull2.getOuter()[0].getX() == approx(-.1)); + EXPECT(hull2.getOuter()[0].getY() == approx(1)); + EXPECT(hull2.getOuter()[1].getX() == approx(0)); + EXPECT(hull2.getOuter()[1].getY() == approx(0)); + EXPECT(hull2.getOuter()[2].getX() == approx(3)); + EXPECT(hull2.getOuter()[2].getY() == approx(1)); + EXPECT(hull2.getOuter()[3].getX() == approx(4)); + EXPECT(hull2.getOuter()[3].getY() == approx(4)); + EXPECT(hull2.getOuter()[4].getX() == approx(0.1)); + EXPECT(hull2.getOuter()[4].getY() == approx(3)); + + auto hull3 = geo::convexHull(Line{{0.1, 3}, {4, 4}, {0, 0}, {1, 2}, {3, 1}}); + EXPECT(hull3.getOuter().size() == size_t(4)); + EXPECT(hull3.getOuter()[0].getX() == approx(0)); + EXPECT(hull3.getOuter()[0].getY() == approx(0)); + EXPECT(hull3.getOuter()[1].getX() == approx(3)); + EXPECT(hull3.getOuter()[1].getY() == approx(1)); + EXPECT(hull3.getOuter()[2].getX() == approx(4)); + EXPECT(hull3.getOuter()[2].getY() == approx(4)); + EXPECT(hull3.getOuter()[3].getX() == approx(0.1)); + EXPECT(hull3.getOuter()[3].getY() == approx(3)); + + hull3 = geo::convexHull(Line{{0.1, 3}, {4, 4}, {2, 1}, {3, 2}, {0, 0}, {1, 2}, {3, 1}}); + EXPECT(hull3.getOuter().size() == size_t(4)); + EXPECT(hull3.getOuter()[0].getX() == approx(0)); + EXPECT(hull3.getOuter()[0].getY() == approx(0)); + EXPECT(hull3.getOuter()[1].getX() == approx(3)); + EXPECT(hull3.getOuter()[1].getY() == approx(1)); + EXPECT(hull3.getOuter()[2].getX() == approx(4)); + EXPECT(hull3.getOuter()[2].getY() == approx(4)); + EXPECT(hull3.getOuter()[3].getX() == approx(0.1)); + EXPECT(hull3.getOuter()[3].getY() == approx(3)); + + hull3 = geo::convexHull(Line{{4, 4}, {1, 2}, {2, 1}, {3, 2}, {0.1, 3}, {0, 0}, {1, 2}, {3, 1}}); + EXPECT(hull3.getOuter().size() == size_t(4)); + EXPECT(hull3.getOuter()[0].getX() == approx(0)); + EXPECT(hull3.getOuter()[0].getY() == approx(0)); + EXPECT(hull3.getOuter()[1].getX() == approx(3)); + EXPECT(hull3.getOuter()[1].getY() == approx(1)); + EXPECT(hull3.getOuter()[2].getX() == approx(4)); + EXPECT(hull3.getOuter()[2].getY() == approx(4)); + EXPECT(hull3.getOuter()[3].getX() == approx(0.1)); + EXPECT(hull3.getOuter()[3].getY() == approx(3)); + + hull3 = geo::convexHull(Line{{4, 4}, {1, 2}, {3, 1}}); + EXPECT(hull3.getOuter().size() == size_t(3)); + EXPECT(hull3.getOuter()[0].getX() == approx(1)); + EXPECT(hull3.getOuter()[0].getY() == approx(2)); + EXPECT(hull3.getOuter()[1].getX() == approx(3)); + EXPECT(hull3.getOuter()[1].getY() == approx(1)); + EXPECT(hull3.getOuter()[2].getX() == approx(4)); + EXPECT(hull3.getOuter()[2].getY() == approx(4)); + + hull3 = geo::convexHull(Line{{4, 4}, {1, 2}, {3, 10}}); + EXPECT(hull3.getOuter().size() == size_t(3)); + EXPECT(hull3.getOuter()[0].getX() == approx(1)); + EXPECT(hull3.getOuter()[0].getY() == approx(2)); + EXPECT(hull3.getOuter()[1].getX() == approx(4)); + EXPECT(hull3.getOuter()[1].getY() == approx(4)); + EXPECT(hull3.getOuter()[2].getX() == approx(3)); + EXPECT(hull3.getOuter()[2].getY() == approx(10)); + + Line test{{0.3215348546593775, 0.03629583077160248}, + {0.02402358131857918, -0.2356728797179394}, + {0.04590851212470659, -0.4156409924995536}, + {0.3218384001607433, 0.1379850698988746}, + {0.11506479756447, -0.1059521474930943}, + {0.2622539999543261, -0.29702873322836}, + {-0.161920957418085, -0.4055339716426413}, + {0.1905378631228002, 0.3698601009043493}, + {0.2387090918968516, -0.01629827079949742}, + {0.07495888748668034, -0.1659825110491202}, + {0.3319341836794598, -0.1821814101954749}, + {0.07703635755650362, -0.2499430638271785}, + {0.2069242999022122, -0.2232970760420869}, + {0.04604079532068295, -0.1923573186549892}, + {0.05054295812784038, 0.4754929463150845}, + {-0.3900589168910486, 0.2797829520700341}, + {0.3120693385713448, -0.0506329867529059}, + {0.01138812723698857, 0.4002504701728471}, + {0.009645149586391732, 0.1060251100976254}, + {-0.03597933197019559, 0.2953639456959105}, + {0.1818290866742182, 0.001454397571696298}, + {0.444056063372694, 0.2502497166863175}, + {-0.05301752458607545, -0.06553921621808712}, + {0.4823896228171788, -0.4776170002088109}, + {-0.3089226845734964, -0.06356112199235814}, + {-0.271780741188471, 0.1810810595574612}, + {0.4293626522918815, 0.2980897964891882}, + {-0.004796652127799228, 0.382663812844701}, + {0.430695573269106, -0.2995073500084759}, + {0.1799668387323309, -0.2973467472915973}, + {0.4932166845474547, 0.4928094162538735}, + {-0.3521487911717489, 0.4352656197131292}, + {-0.4907368011686362, 0.1865826865533206}, + {-0.1047924716070224, -0.247073392148198}, + {0.4374961861758457, -0.001606279519951237}, + {0.003256207800708899, -0.2729194320486108}, + {0.04310378203457577, 0.4452604050238248}, + {0.4916198379282093, -0.345391701297268}, + {0.001675087028811806, 0.1531837672490476}, + {-0.4404289572876217, -0.2894855991839297} + + }; + hull3 = geo::convexHull(test); + EXPECT(geo::contains(test, hull3)); + EXPECT(hull3.getOuter().size() == size_t(8)); + EXPECT(geo::contains(Polygon({{-0.161920957418085, -0.4055339716426413}, + {0.05054295812784038, 0.4754929463150845}, + {0.4823896228171788, -0.4776170002088109}, + {0.4932166845474547, 0.4928094162538735}, + {-0.3521487911717489, 0.4352656197131292}, + {-0.4907368011686362, 0.1865826865533206}, + {0.4916198379282093, -0.345391701297268}, + {-0.4404289572876217, + -0.2894855991839297}}), hull3)); + EXPECT(geo::contains(hull3, Polygon({{-0.161920957418085, -0.4055339716426413}, + {0.05054295812784038, 0.4754929463150845}, + {0.4823896228171788, -0.4776170002088109}, + {0.4932166845474547, 0.4928094162538735}, + {-0.3521487911717489, 0.4352656197131292}, + {-0.4907368011686362, 0.1865826865533206}, + {0.4916198379282093, -0.345391701297268}, + {-0.4404289572876217, + -0.2894855991839297}}))); + + hull3 = geo::convexHull(Line{{3, 6}, {8, 10}, {3, 5}, {20, -10}, {-4, 5}, {10, 2}, {5, 1}, {45, 1}, {30, -9}, {3, 14}, {25, -5.5}}); + EXPECT(hull3.getOuter().size() == size_t(5)); + EXPECT(hull3.getOuter()[0].getX() == approx(-4)); + EXPECT(hull3.getOuter()[0].getY() == approx(5)); + EXPECT(hull3.getOuter()[1].getX() == approx(20)); + EXPECT(hull3.getOuter()[1].getY() == approx(-10)); + EXPECT(hull3.getOuter()[2].getX() == approx(30)); + EXPECT(hull3.getOuter()[2].getY() == approx(-9)); + EXPECT(hull3.getOuter()[3].getX() == approx(45)); + EXPECT(hull3.getOuter()[3].getY() == approx(1)); + EXPECT(hull3.getOuter()[4].getX() == approx(3)); + EXPECT(hull3.getOuter()[4].getY() == approx(14)); + + hull3 = geo::convexHull(Line{{7, 7}, {7, -7}, {-7, -7}, {-7, 7}, {9, 0}, {-9, 0}, {0, 9}, {0, -9}}); + EXPECT(hull3.getOuter().size() == size_t(8)); + EXPECT(geo::contains(geo::Polygon({{-9, 0}, {-7, -7}, {0, -9}, {7, -7}, {9, 0}, {7, 7}, {0, 9}, {-7, 7}}), hull3)); + EXPECT(geo::contains(hull3, geo::Polygon({{-9, 0}, {-7, -7}, {0, -9}, {7, -7}, {9, 0}, {7, 7}, {0, 9}, {-7, 7}}))); + + hull3 = geo::convexHull(Line{{7, 7}, {7, -7}, {-7, -7}, {-7, 7}, {9, 0}, {-9, 0}, {0, 9}, {0, -9}, {0, 0}, {1, 2}, {-2, 1}, {-1, -1}, {3, 4}, {4, 3}, {-5, 4}, {6, 5}}); + EXPECT(hull3.getOuter().size() == size_t(8)); + EXPECT(geo::contains(geo::Polygon({{-9, 0}, {-7, -7}, {0, -9}, {7, -7}, {9, 0}, {7, 7}, {0, 9}, {-7, 7}}), hull3)); + EXPECT(geo::contains(hull3, geo::Polygon({{-9, 0}, {-7, -7}, {0, -9}, {7, -7}, {9, 0}, {7, 7}, {0, 9}, {-7, 7}}))); + + hull3 = geo::convexHull(Line{{0, 0}, {1, 2}, {-2, 1}, {-1, -1}, {3, 4}, {4, 3}, {-5, 4}, {6, 5}, {7, 7}, {7, -7}, {-7, -7}, {-7, 7}, {9, 0}, {-9, 0}, {0, 9}, {0, -9}, {-8, 0}, {8, 0}, {-7, 0}, {7, 0}, {-6, 0}, {6, 0}, {-5, 0}, {5, 0}, {-4, 0}, {4, 0}, {-3, 0}, {3, 0}, {-2, 0}, {2, 0}, {-1, 0}, {1, 0}, {0, -8}, {0, 8}, {0, -7}, {0, 7}, {0, -6}, {0, 6}, {0, -5}, {0, 5}, {0, -4}, {0, 4}, {0, -3}, {0, 3}, {0, -2}, {0, 2}, {0, -1}, {0, 1}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {1, -1}, {2, -2}, {3, -3}, {4, -4}, {5, -5}, {6, -6}, {-1, 1}, {-2, 2}, {-3, 3}, {-4, 4}, {-5, 5}, {-6, 6}, {-1, -1}, {-2, -2}, {-3, -3}, {-4, -4}, {-5, -5}, {-6, -6}}); + EXPECT(hull3.getOuter().size() == size_t(8)); + EXPECT(geo::contains(geo::Polygon({{-9, 0}, {-7, -7}, {0, -9}, {7, -7}, {9, 0}, {7, 7}, {0, 9}, {-7, 7}}), hull3)); + EXPECT(geo::contains(hull3, geo::Polygon({{-9, 0}, {-7, -7}, {0, -9}, {7, -7}, {9, 0}, {7, 7}, {0, 9}, {-7, 7}}))); + + EXPECT(geo::area(geo::Point(1, 2)) == approx(0)); + EXPECT(geo::area(geo::Line{{1, 2}, {2, 5}}) == approx(0)); + EXPECT(geo::area(geo::Box({0, 0}, {1, 1})) == approx(1)); + EXPECT(geo::area(geo::Box({1, 1}, {1, 1})) == approx(0)); + EXPECT(geo::area(geo::Box({0, 0}, {2, 2})) == approx(4)); + EXPECT(geo::area(geo::Polygon({{0, 0}, {1, 0}, {1, 1}, {0, 1}})) == approx(1)); + EXPECT(geo::area(geo::Polygon({{0, 0}, {1, 0}, {1, 1}})) == approx(0.5)); + + auto obox = geo::getOrientedEnvelope(geo::Line{{0, 0}, {1, 1}, {1.5, 0.5}}); + EXPECT(geo::contains(geo::convexHull(obox), geo::Polygon({{0.0, 0.0}, {1.0, 1.0}, {1.5, 0.5}, {0.5, -0.5}}))); + EXPECT(geo::contains(geo::Polygon({{0.0, 0.0}, {1.0, 1.0}, {1.5, 0.5}, {0.5, -0.5}}), geo::convexHull(obox))); } };