Fishnet 2.0.1
Framework for Graph-Based Analysis of GIS Vector Data
Loading...
Searching...
No Matches
PolygonDistance.hpp
Go to the documentation of this file.
1#pragma once
2#include <fishnet/Vec2D.hpp>
5#include "SweepLine.hpp"
6#include <fishnet/Segment.hpp>
7
8namespace fishnet::geometry {
9
17static Vec2DReal closestPointOnSegment(ISegment auto const & segment, IPoint auto const & point) noexcept {
18 auto orthogonal = segment.direction().orthogonal();
19 auto line = Line(point,point+orthogonal);
20 auto intersection = segment.intersection(line); // intersection with the orthogonal line from the segment to the point
21 if(intersection)
22 return intersection.value(); // return intersection with orthogonal line trough point if present
23 return segment.p().distance(point) < segment.q().distance(point)? segment.p():segment.q(); // return endpoint of segment closest to point
24}
25
26
34static std::pair<Vec2DReal,Vec2DReal> closestPoints(ISegment auto const & lhs, ISegment auto const & rhs) noexcept {
35 std::array<std::pair<Vec2DReal,Vec2DReal>,4> points {{
36 {closestPointOnSegment(lhs,rhs.p()),rhs.p()},
37 {closestPointOnSegment(lhs,rhs.q()),rhs.q()},
38 {lhs.p(),closestPointOnSegment(rhs,lhs.p())},
39 {lhs.q(),closestPointOnSegment(rhs,lhs.q())}
40 }};
41 return *std::ranges::min_element(points,[](const auto & left, const auto & right){
42 const auto & [lFrom,lTo] = left;
43 const auto & [rFrom,rTo] = right;
44 return lFrom.distance(lTo) < rFrom.distance(rTo);
45 });
46}
47
48namespace __impl{
49
50class ClosestPointsResult;
51
56class PolygonReference{
57private:
58 friend class ClosestPointsResult;
59 enum class Type{
60 THIS,OTHER,NONE
61 };
62 Type type;
63
64 PolygonReference():type(Type::NONE){}
65 PolygonReference(Type type):type(type){}
66
67 constexpr bool inline isNone() const noexcept {
68 return this->type==Type::NONE;
69 }
70public:
71 static inline PolygonReference THIS() {
72 return PolygonReference(Type::THIS);
73 }
74
75 static PolygonReference OTHER() {
76 return PolygonReference(Type::OTHER);
77 }
78
79 constexpr bool inline operator<(const PolygonReference & other) const noexcept {
80 return type < other.type;
81 }
82
83 constexpr bool inline operator==(const PolygonReference & other) const noexcept {
84 return type == other.type;
85 }
86
87 PolygonReference other() const noexcept {
88 if(type == Type::THIS)
89 return PolygonReference::OTHER();
90 else
91 return PolygonReference::THIS();
92 }
93
94};
95
102template<bool isXOrdered>
103struct PolygonPoint {
104 constexpr static LexicographicOrder xOrdering {};
105 constexpr static YLexicographicOrder yOrdering{};
106 Segment<fishnet::math::DEFAULT_FLOATING_POINT> segment;
107 PolygonReference polygonRef;
108
109 PolygonPoint(Segment<fishnet::math::DEFAULT_FLOATING_POINT> && segment, PolygonReference polygonReference)
110 :segment(std::move(segment)),polygonRef(polygonReference){}
111
112 constexpr const auto & getPoint() const noexcept {
113 return segment.p();
114 }
115
116 constexpr const auto & getSegment() const noexcept {
117 return segment;
118 }
119
120 constexpr bool inline operator<(const PolygonPoint& other ) const noexcept {
121 if(polygonRef==other.polygonRef){
122 if constexpr(isXOrdered)
123 return xOrdering(this->getPoint(),other.getPoint());
124 else
125 return yOrdering(this->getPoint(),other.getPoint());
126 }
127 return polygonRef < other.polygonRef;
128 }
129};
130
137class ClosestPointsResult{
138private:
139 bool foundPointOfOtherPolygon = false;
140 PolygonReference polyRef;
141public:
142 Vec2DReal thisPoint;
143 Vec2DReal otherPoint;
144 size_t thisCounter = 0;
145 size_t otherCounter = 0;
147
148 ClosestPointsResult(const Vec2DReal & thisInitial, const Vec2DReal & otherInitial,size_t thisCounter, size_t otherCounter)
149 :thisPoint(thisInitial),otherPoint(otherInitial),thisCounter(thisCounter),otherCounter(otherCounter),minDistance(thisInitial.distance(otherInitial)){}
150
151 bool startSearch(PolygonReference polygonRef){
152 if(foundPointOfOtherPolygon){
153 return true;
154 }
155 [[unlikely]] if(polyRef.isNone()){// only the case for the first element
156 polyRef = polygonRef;
157 }
158 if(polyRef != polygonRef){
159 foundPointOfOtherPolygon = true;
160 }
161 return foundPointOfOtherPolygon;
162 }
163
170 template<bool O>
171 void inline visit(const PolygonPoint<O> & point) noexcept {
172 if(point.polygonRef == PolygonReference::THIS())
173 thisCounter--;
174 else
175 otherCounter--;
176 }
177
185 template<bool O>
186 const Vec2DReal & getNeighbour(const PolygonPoint<O> & point) const noexcept {
187 if(point.polygonRef == PolygonReference::THIS()){
188 return otherPoint;
189 }
190 return thisPoint;
191 }
192};
193
194template<bool isXOrdered>
195using PolygonPointSweepLine = SweepLine<PolygonPoint<isXOrdered>,ClosestPointsResult,std::less<PolygonPoint<isXOrdered>>,false,std::less<fishnet::math::DEFAULT_NUMERIC>>;
196
197template<bool isXOrdered>
198struct PolygonSegmentSweepEvent : public PolygonPointSweepLine<isXOrdered>::InsertEvent {
199
200 PolygonSegmentSweepEvent(const PolygonPoint<isXOrdered> & point):PolygonPointSweepLine<isXOrdered>::InsertEvent(point){}
201
202 virtual fishnet::math::DEFAULT_NUMERIC eventPoint() const noexcept {
203 if constexpr(isXOrdered)
204 return this->obj->getPoint().x;
205 else
206 return this->obj->getPoint().y;
207 }
208
217 bool inline stop(PolygonPointSweepLine<isXOrdered> & sweepLine, ClosestPointsResult & status) const {
218 if constexpr(isXOrdered)
219 return (status.thisCounter==0 || status.otherCounter==0) && this->obj->segment.leftEndpoint().x - status.minDistance > status.getNeighbour(*this->obj).x;
220 else
221 return (status.thisCounter==0 || status.otherCounter==0) && this->obj->segment.lowerEndpoint().x - status.minDistance > status.getNeighbour(*this->obj).x;
222 }
223
230 PolygonPoint<isXOrdered> lowerBoundQuery(ClosestPointsResult & status) const noexcept {
231 if constexpr(isXOrdered)
232 return PolygonPoint<isXOrdered>(Segment<fishnet::math::DEFAULT_FLOATING_POINT>(this->obj->segment.leftEndpoint() - Vec2DReal(status.minDistance,0),this->obj->getPoint()),this->obj->polygonRef.other());
233 else
234 return PolygonPoint<isXOrdered>(Segment<fishnet::math::DEFAULT_FLOATING_POINT>(this->obj->segment.lowerEndpoint() - Vec2DReal(0,status.minDistance),this->obj->getPoint()),this->obj->polygonRef.other());
235 }
236
243 PolygonPoint<isXOrdered> upperBoundQuery(ClosestPointsResult & status) const noexcept {
244 if constexpr(isXOrdered)
245 return PolygonPoint<isXOrdered>({this->obj->segment.rightEndpoint() + Vec2DReal(status.minDistance,0),this->obj->getPoint()},this->obj->polygonRef.other());
246 else
247 return PolygonPoint<isXOrdered>({this->obj->segment.upperEndpoint() + Vec2DReal(0,status.minDistance),this->obj->getPoint()},this->obj->polygonRef.other());
248 }
249
250 virtual void process(PolygonPointSweepLine<isXOrdered> & sweepLine, ClosestPointsResult & status) const {
251 const auto & polygonPoint = *this->obj;
252 const auto & point = polygonPoint.getPoint();
253 const auto & segment = polygonPoint.getSegment();
254 sweepLine.addSLS(this->obj);
255 status.visit(polygonPoint);
256 if(stop(sweepLine,status)){
257 /*Clear Event Queue since no closer distances can be found*/
258 while(sweepLine.getEventQueue().size() > 1)
259 sweepLine.getEventQueue().pop();
260 return;
261 }
262 if(not status.startSearch(polygonPoint.polygonRef))
263 return;
264 auto leftQuery = lowerBoundQuery(status);
265 auto rightQuery = upperBoundQuery(status);
266 auto lowerBound = sweepLine.lowerBound(leftQuery);
267 auto upperBound = sweepLine.upperBound(rightQuery);
268 auto & sweepLineStatus = sweepLine.getSLS();
269 for(auto it= lowerBound; it != upperBound;){
270 const auto & currentSegment = (*it)->getSegment();
271 /*Remove elements out of range from the sweep line*/
272 if constexpr(isXOrdered){
273 if(currentSegment.rightEndpoint().x + status.minDistance < point.x){
274 it = sweepLineStatus.erase(it);
275 continue;
276 }
277 }else {
278 if(currentSegment.upperEndpoint().y + status.minDistance < point.y){
279 it = sweepLineStatus.erase(it);
280 continue;
281 }
282 }
283 auto [l,r] = closestPoints(segment,currentSegment);
284 auto distance = l.distance(r);
285 if(distance < status.minDistance){
286 status.minDistance = distance;
287 /*Ensure the points in status are set for the correct polygon*/
288 if(polygonPoint.polygonRef == PolygonReference::THIS()){
289 status.thisPoint = l;
290 status.otherPoint = r;
291 } else {
292 status.thisPoint = r;
293 status.otherPoint = l;
294 }
295 }
296 ++it;
297 }
298 }
299};
300
309template<bool xSweep>
310static std::pair<Vec2DReal,Vec2DReal> closestPointsSweep(SegmentRange auto && lhs, SegmentRange auto && rhs) noexcept {
311 auto lInit = Vec2DReal((*std::ranges::begin(lhs)).p());
312 auto rInit = Vec2DReal((*std::ranges::begin(rhs)).p());
313 PolygonPointSweepLine<xSweep> sweepLine;
314 /*Initialize status with two "random" points of the segment ranges*/
315 ClosestPointsResult status {lInit,rInit,fishnet::util::size(lhs),fishnet::util::size(rhs)};
316 std::vector<PolygonPoint<xSweep>> segments;
317 std::ranges::for_each(lhs,[&segments,&status](const auto & segmentLeft){
318 if constexpr(xSweep){
319 segments.push_back(PolygonPoint<xSweep>(Segment<fishnet::math::DEFAULT_FLOATING_POINT>(Vec2DReal(segmentLeft.leftEndpoint()),Vec2DReal(segmentLeft.rightEndpoint())),PolygonReference::THIS()));
320 segments.push_back(PolygonPoint<xSweep>(Segment<fishnet::math::DEFAULT_FLOATING_POINT>(Vec2DReal(segmentLeft.rightEndpoint()),Vec2DReal(segmentLeft.leftEndpoint())),PolygonReference::THIS()));
321 }else {
322 segments.push_back(PolygonPoint<xSweep>(Segment<fishnet::math::DEFAULT_FLOATING_POINT>(Vec2DReal(segmentLeft.lowerEndpoint()),Vec2DReal(segmentLeft.upperEndpoint())),PolygonReference::THIS()));
323 segments.push_back(PolygonPoint<xSweep>(Segment<fishnet::math::DEFAULT_FLOATING_POINT>(Vec2DReal(segmentLeft.upperEndpoint()),Vec2DReal(segmentLeft.lowerEndpoint())),PolygonReference::THIS()));
324 }
325 status.thisCounter+=2; // keep track of points to be processed -> allows early return
326 });
327 std::ranges::for_each(rhs,[&segments,&status](const auto & segmentRight){
328 if constexpr(xSweep){
329 segments.push_back(PolygonPoint<xSweep>(Segment<fishnet::math::DEFAULT_FLOATING_POINT>(Vec2DReal(segmentRight.leftEndpoint()),Vec2DReal(segmentRight.rightEndpoint())),PolygonReference::OTHER()));
330 segments.push_back(PolygonPoint<xSweep>(Segment<fishnet::math::DEFAULT_FLOATING_POINT>(Vec2DReal(segmentRight.rightEndpoint()),Vec2DReal(segmentRight.leftEndpoint())),PolygonReference::OTHER()));
331 }else {
332 segments.push_back(PolygonPoint<xSweep>(Segment<fishnet::math::DEFAULT_FLOATING_POINT>(Vec2DReal(segmentRight.lowerEndpoint()),Vec2DReal(segmentRight.upperEndpoint())),PolygonReference::OTHER()));
333 segments.push_back(PolygonPoint<xSweep>(Segment<fishnet::math::DEFAULT_FLOATING_POINT>(Vec2DReal(segmentRight.upperEndpoint()),Vec2DReal(segmentRight.lowerEndpoint())),PolygonReference::OTHER()));
334 }
335 status.otherCounter+=2;
336 });
337 std::ranges::for_each(segments,[&sweepLine](const auto & segment){
338 sweepLine.addEvent(std::make_unique<PolygonSegmentSweepEvent<xSweep>>(segment));
339 });
340 auto result = sweepLine.sweep(status);
341 return {result.thisPoint,result.otherPoint};
342}
343
344
345static std::pair<Vec2DReal,Vec2DReal> closestPointsSweep(SegmentRange auto && lhs, SegmentRange auto && rhs) noexcept {
346 auto lInit = Vec2DReal((*std::ranges::begin(lhs)).p());
347 auto rInit = Vec2DReal((*std::ranges::begin(rhs)).p());
348 auto dirVector = rInit - lInit;
349 if(fabs(dirVector.x) > fabs(dirVector.y)){
350 return closestPointsSweep<true>(lhs,rhs);
351 }
352 return closestPointsSweep<false>(lhs,rhs);
353}
354
355
363static std::pair<Vec2DReal,Vec2DReal> closestPointsBruteForce(SegmentRange auto && lhs,SegmentRange auto && rhs) noexcept {
364 fishnet::math::DEFAULT_FLOATING_POINT minDistance = std::numeric_limits<fishnet::math::DEFAULT_FLOATING_POINT>::max();
365 Vec2DReal bestLeft;
366 Vec2DReal bestRight;
367 auto leftPoints = lhs | std::views::transform([](const auto & s){return s.p();});
368 auto rightPoints = rhs | std::views::transform([](const auto & s){return s.p();});
369 for(const auto & s : lhs){
370 for(const auto & p : rightPoints){
371 auto pointOnSegment = closestPointOnSegment(s,p);
372 if(pointOnSegment.distance(p) < minDistance){
373 bestLeft = pointOnSegment;
374 bestRight = p;
375 minDistance = pointOnSegment.distance(p);
376 }
377 }
378 }
379 for(const auto & s : rhs){
380 for(const auto & p: leftPoints){
381 auto pointOnSegment = closestPointOnSegment(s,p);
382 if(pointOnSegment.distance(p) < minDistance){
383 bestLeft = p;
384 bestRight = pointOnSegment;
385 minDistance = pointOnSegment.distance(p);
386 }
387 }
388 }
389 return std::make_pair(bestLeft,bestRight);
390}
391
396struct DefaultDistanceFunction{
397 template<typename T,typename U>
398 static inline fishnet::math::DEFAULT_FLOATING_POINT operator()(const Vec2D<T> & lhs, const Vec2D<U> & rhs) noexcept {
399 return lhs.distance(rhs);
400 }
401};
402
410static std::pair<Vec2DReal,Vec2DReal> closestPoints( SegmentRange auto && lhs, SegmentRange auto && rhs) noexcept {
411 constexpr static size_t SWEEP_LINE_THRESHOLD = 10000;
412 if(fishnet::util::size(lhs) * fishnet::util::size(rhs) > SWEEP_LINE_THRESHOLD){
413 return __impl::closestPointsSweep(lhs,rhs);
414 }
415 return __impl::closestPointsBruteForce(lhs,rhs);
416}
417}
418
426static std::pair<Vec2DReal,Vec2DReal> closestPoints(const IRing auto & lhs, const IRing auto & rhs) noexcept {
427 return __impl::closestPoints(lhs.getSegments(),rhs.getSegments());
428}
429
437template <Shape S, Shape T, typename = std::enable_if_t<IPolygon<S> || IPolygon<T>>>
438static std::pair<Vec2DReal,Vec2DReal> closestPoints(const S & lhs, const T & rhs) noexcept {
439 return closestPoints(lhs.getBoundary(),rhs.getBoundary());
440}
441
449static std::pair<Vec2DReal,Vec2DReal> closestPoints(const IMultiPolygon auto & lhs, const IMultiPolygon auto & rhs) noexcept {
450 return __impl::closestPoints(
451 lhs.getPolygons()|std::views::transform([](const auto & polygon){return polygon.getBoundary().getSegments();}) | std::views::join,
452 rhs.getPolygons()|std::views::transform([](const auto & polygon){return polygon.getBoundary().getSegments();}) | std::views::join
453 );
454}
455
456
466 const auto & [l,r] = closestPoints(lhs,rhs);
467 return distanceFunction(l,r);
468}
469
477static fishnet::math::DEFAULT_FLOATING_POINT shapeDistance(Shape auto const & lhs, Shape auto const & rhs) noexcept {
478 return shapeDistance(lhs,rhs,__impl::DefaultDistanceFunction());
479}
480}
Implementation of a line.
Definition Line.hpp:14
virtual fishnet::math::DEFAULT_NUMERIC eventPoint() const noexcept=0
const Input * obj
Definition SweepLine.hpp:38
virtual void process(SweepLine< Input, Output, SLSLess, InsertEventsFirst, EventQueueGreater > &sweepLine, Output &output) const =0
Implementation of a two-dimensional vector.
Definition Vec2D.hpp:16
constexpr Vec2D< T > orthogonal() const noexcept
Definition Vec2D.hpp:150
Interface for a multi-polygon A multi-polygon is a collection of polygons.
Definition IMultiPolygon.hpp:93
Interface for a point.
Definition IPoint.hpp:92
Interface for a ring A ring is a closed loop of segments, forming a boundary of a shape.
Definition IRing.hpp:143
Interface for a segment A segment is formed between two points p and q, containing only the points on...
Definition ISegment.hpp:105
A geometry enclosing an area: a ring, a polygon or a multi-polygon.
Definition IGeometry.hpp:23
Definition FunctionalConcepts.hpp:15
Definition BoundingBoxWrapper.hpp:5
static std::pair< Vec2DReal, Vec2DReal > closestPoints(ISegment auto const &lhs, ISegment auto const &rhs) noexcept
Compute the pair of nearest points for two segments.
Definition PolygonDistance.hpp:34
Vec2D< fishnet::math::DEFAULT_FLOATING_POINT > Vec2DReal
Definition Vec2D.hpp:220
static fishnet::math::DEFAULT_FLOATING_POINT shapeDistance(Shape auto const &lhs, Shape auto const &rhs, util::BiFunction< Vec2DReal, Vec2DReal, fishnet::math::DEFAULT_FLOATING_POINT > auto const &distanceFunction) noexcept
Compute the distance between two shapes, with custom distance function.
Definition PolygonDistance.hpp:465
static Vec2DReal closestPointOnSegment(ISegment auto const &segment, IPoint auto const &point) noexcept
Compute the point on the segment, closest to the query point.
Definition PolygonDistance.hpp:17
double DEFAULT_NUMERIC
Definition NumericConcepts.hpp:13
double DEFAULT_FLOATING_POINT
Definition NumericConcepts.hpp:12
constexpr size_t size(std::ranges::range auto &&range) noexcept
Definition CollectionConcepts.hpp:50
STL namespace.
InsertEvent(const Input &obj)
Definition SweepLine.hpp:57