Fishnet 2.0.1
Framework for Graph-Based Analysis of GIS Vector Data
Loading...
Searching...
No Matches
Ring.hpp
Go to the documentation of this file.
1#pragma once
2#include <ranges>
3#include <algorithm>
4#include <numeric>
5#include <sstream>
6#include <vector>
10#include <fishnet/Ray.hpp>
11#include <fishnet/Segment.hpp>
15
16namespace fishnet::geometry{
17
18template<fishnet::math::Number T>
19class Rectangle;
20
24
30template<fishnet::math::Number T>
31class Ring{
32private:
33 std::vector<Segment<T>> segments;
34
41 constexpr static inline std::vector<Segment<T>> toSegments(util::random_access_range_of<Vec2D<T>> auto const & points) noexcept {
42 std::vector<Segment<T>> segments {};
43 if(points.size()==0)
44 return segments;
45 segments.reserve(points.size());
46 for(size_t i = 0; i < points.size()-1 ; ++i){
47 if(points[i]==points[i+1])
48 continue; // skip 0-length segments
49 segments.emplace_back(points[i],points[i+1]);
50 }
51 auto const& first = points[0];
52 auto const& last = points[points.size()-1];
53 if(first != last){
54 segments.emplace_back(last,first);
55 }
56 return segments;
57 }
58
63 constexpr void makeValid() noexcept {
64 for(size_t i =0; i < segments.size(); ++i){
65 if(segments[i].q() != segments[(i+1)%segments.size()].p()){
66 auto & s = segments[(i+1)%segments.size()];
67 s = s.flip();
68 }
69 }
70 }
71protected:
72
80 constexpr PointLocation getPointLocation(IPoint auto const & point) const noexcept {
81 u_int16_t intersectionCounter = 0;
82 Ray<T> horizontalRay = Ray<T>::right(point);
83 for(const auto & segment: segments){
84 if(point==segment.p() or point==segment.q() or segment.contains(point)) //point is part of any segment on the boundary
86 std::optional<Vec2DReal> interOpt = segment.intersection(horizontalRay);
87 if (not interOpt)
88 continue; // no intersection, go to next segment
89 const Vec2DReal & inter = interOpt.value();
90 if (inter == segment.lowerEndpoint())
91 continue; //prevent counting a vertex twice -> count only upperEndpoints
92 ++intersectionCounter;
93 }
94 return intersectionCounter%2 == 1 ? PointLocation::INSIDE : PointLocation::OUTSIDE;
95 }
96
97public:
98 using numeric_type = T;
99 constexpr static GeometryType type = GeometryType::RING;
100
101 Ring(util::random_access_range_of<Vec2D<T>> auto const& points, bool checked = false):segments(toSegments(points)){
102 if(not checked){
103 verifyPolygonalRing<T>(this->segments);
104 }
105 }
106
107 Ring(util::random_access_range_of<Segment<T>> auto && segments, bool checked = false):segments(std::forward<decltype(segments)>(segments)){
108 makeValid();
109 if(not checked){
110 verifyPolygonalRing<T>(this->segments);
111 }
112 }
113
114 Ring(std::initializer_list<Vec2D<T>> && points){
115 std::vector<Vec2D<T>> pointsInVector {points};
116 this->segments = std::move(toSegments(pointsInVector));
117 verifyPolygonalRing<T>(this->segments);
118 }
119
120 template<fishnet::math::Number U>
121 constexpr operator Ring<U> () const noexcept {
122 std::vector<Vec2D<U>> points {};
123 std::ranges::transform(getPoints(),std::back_inserter(points),[](const auto & p){
124 return static_cast<Vec2D<U>>(p);
125 });
126 return Ring<U>(points);
127 }
128
129 constexpr const Ring<T> & getBoundary() const noexcept {
130 return *this;
131 }
132
133 constexpr auto getHoles() const noexcept {
134 return std::ranges::empty_view<Ring<T>>();
135 }
136
137 constexpr util::view_of<Segment<T>> auto getSegments() const noexcept{
138 return std::views::all(segments);
139 }
140
141 constexpr util::view_of<Vec2D<T>> auto getPoints() const noexcept {
142 return getSegments()
143 | std::views::transform([](const auto & s){return s.p();});
144 }
145
151 constexpr fishnet::math::DEFAULT_FLOATING_POINT area() const noexcept {
153 for(size_t i = 0; i < segments.size(); i++){
154 area += segments[i].p().cross(segments[(i+1)%segments.size()].p());
155 }
156 return 0.5 * fabs(area);
157 }
158
164 constexpr Vec2DReal centroid() const noexcept {
165 Vec2DReal sum {0,0};
166 for(const auto & s : segments){
167 sum = sum + s.p();
168 }
169 return sum / (fishnet::math::DEFAULT_FLOATING_POINT)segments.size();
170 }
171
177 constexpr Rectangle<T> aaBB() const noexcept {
178 T high = this->segments.at(0).p().y;
179 T low = high;
180 T right = this->segments.at(0).p().x;
181 T left = right;
182 for(const auto s : this->getSegments()){
183 auto p = s.p();
184 if(p.y > high)
185 high = p.y;
186 if(p.y < low)
187 low = p.y;
188 if(p.x > right)
189 right = p.x;
190 if(p.x < left)
191 left = p.x;
192 }
193 return Rectangle<T>(left,high,right,low);
194 }
195
196 constexpr bool contains(IPoint auto const & point) const noexcept {
198 }
199
200 constexpr bool isInside(IPoint auto const & point) const noexcept {
202 }
203
204 constexpr bool isOnBoundary(IPoint auto const & point) const noexcept {
206 }
207
208 constexpr bool isOutside(IPoint auto const & point) const noexcept {
210 }
211
220 template<LinearGeometry L>
221 constexpr bool intersects( const L & linearFeature) const noexcept {
222 return ringIntersects(*this, linearFeature);
223 }
224
225 constexpr std::unordered_set<Vec2DReal> intersections(LinearGeometry auto const& linearFeature) const noexcept{
226 std::unordered_set<Vec2D<double>> intersectionSet {};
227 auto intersectionView = this->getSegments()
228 | std::views::transform([linearFeature](const auto & segment){return segment.intersection(linearFeature);})
229 | std::views::filter([](const auto & optIntersection){return optIntersection.has_value();});
230 for(auto && optInter : intersectionView){
231 intersectionSet.insert(optInter.value());
232 }
233 return intersectionSet;
234 }
235
243 constexpr bool contains(ISegment auto const & segment) const noexcept {
244 [[unlikely]] if(not segment.isValid())
245 return contains(segment.p()); // or segment.q()
246 std::vector<Vec2DReal> splittingPoints;
247 splittingPoints.push_back(segment.p());
248 for(const auto & s : segments){
249 [[unlikely]] if (s.contains(segment))
250 return true;
251 auto inter = s.intersection(segment);
252 if(inter and not s.isEndpoint(inter.value()) and not segment.isEndpoint(inter.value())){ // splitting points are must not be vertices of the ring or endpoint of the segment
253 splittingPoints.push_back(inter.value());
254 }
255 }
256 splittingPoints.push_back(segment.q()); // insert at least on splitting point, q() beeing the opposite endpoint of the segment
257 std::ranges::sort(splittingPoints,[segment](const Vec2DReal & a, const Vec2DReal & b ){
258 return segment.p().distance(a) < segment.p().distance(b); // sort endpoints according to distance from p()
259 });
260 for(size_t i = 0; i < splittingPoints.size()-1; i++){
261 auto middlePointOfPartialSegment = splittingPoints[i] + (splittingPoints[i+1]-splittingPoints[i]) * 0.5;
262 if(not contains(middlePointOfPartialSegment))
263 return false;
264 }
265 return true;
266 }
267
268
269 template<fishnet::math::Number U>
270 constexpr bool operator==(const Ring<U> & other) const noexcept {
271 if(this->segments.size() != other.getSegments().size())
272 return false;
273 size_t size = segments.size();
274 // Find common segment to start comparision
275 Segment<T> const & start = this->segments.front();
276 auto segmentViewOther = other.getSegments();
277 size_t indexOfStart = segmentViewOther.size();
278 for(size_t i = 0; i < segmentViewOther.size(); ++i){
279 if(segmentViewOther[i] == start){
280 indexOfStart = i;
281 break;
282 }
283 }
284 if(indexOfStart == segmentViewOther.size())
285 return false; // no common segment found -> not equal
286
287 auto nextIndex = [size,indexOfStart](size_t index){return (indexOfStart+index) % size;};
288 bool allMatch = true;
289 for(size_t i = 0; i < this->segments.size(); ++i){
290 if(this->segments[i] != segmentViewOther[nextIndex(i)])
291 allMatch = false;
292 }
293 if(allMatch) return true;
294 // Opposite direction
295 auto prevIndex = [size,indexOfStart](size_t index) {
296 return (indexOfStart-index+size) % size;
297 };
298 for(size_t i = 0; i < this->segments.size(); ++i){
299 if(this->segments[i]!=segmentViewOther[prevIndex(i)]) return false;
300 }
301 return true;
302 }
303
304 constexpr bool crosses(IRing auto const & other) const noexcept {
305 if(not this->aaBB().overlap(other.aaBB()))
306 return false; // rings whose bounding boxes are apart cannot share a single point
307 return std::ranges::any_of(segments,[&other](const auto & s){return other.intersects(s);})
308 || std::ranges::any_of(other.getSegments(),[this](const auto & s){return this->intersects(s);});
309 }
310
311 constexpr bool contains(IRing auto const & other) const noexcept {
312 return std::ranges::all_of(other.getSegments(), [this](const auto & s){
313 return this->contains(s);
314 });
315 }
316
317 constexpr bool touches(IRing auto const & other) const noexcept {
318 if(not this->aaBB().overlap(other.aaBB()))
319 return false; // rings whose bounding boxes are apart cannot touch
320 if(this->crosses(other)) return false;
321 if(this->contains(other) || other.contains(*this)) return false;
322 for(const auto & p : other.getPoints()){
323 if(this->isOnBoundary(p)) return true;
324 }
325 return false;
326 }
327
333 constexpr fishnet::math::DEFAULT_FLOATING_POINT distance(IRing auto const & other) const noexcept {
334 if(this->aaBB().overlap(other.aaBB()) && (this->contains(other) or other.contains(*this) or this->crosses(other)))
335 return 0;
336 return shapeDistance(*this,other);
337 }
338
339 constexpr size_t hash() const noexcept {
340 auto segmentsHashView = this->getSegments() | std::views::transform([](const auto & segment){return segment.hash();});
341 auto centroidHash = this->centroid().hash();
342 return std::accumulate(std::ranges::begin(segmentsHashView),std::ranges::end(segmentsHashView),centroidHash);
343 }
344
345 constexpr std::string toString() const noexcept {
346 std::ostringstream oss;
347 bool first = true;
348 for (const auto & s: this->segments){
349 if(!first) oss << ",";
350 oss << s.toString();
351 first = false;
352 }
353 return oss.str();
354 }
355};
356
357//Deduction guides
358template<std::ranges::random_access_range R>
360
361template<typename T>
362Ring(std::initializer_list<Vec2D<T>> && points)->Ring<T>;
363
364} // namespace fishnet::geometry
365
366// NOTE: the explicit instantiation of Ring<DEFAULT_NUMERIC> lives in Rectangle.hpp, not here:
367// aaBB() returns Rectangle<T> by value, so instantiating Ring<T> requires Rectangle<T> to be
368// complete. Whichever of Ring.hpp/Rectangle.hpp is included first, by the time Rectangle.hpp
369// reaches its own explicit instantiation both types are guaranteed complete; placing it here
370// instead is order-dependent (fails when something includes Rectangle.hpp before Ring.hpp).
371#include "Rectangle.hpp"
372
Implementation of a ray A ray consist of an origin and the direction of the ray.
Definition Ray.hpp:13
static constexpr Ray< T > right(Vec2D< T > origin) noexcept
Definition Ray.hpp:29
Implementation of a rectangle Stores top,right,bottom and left point of the rectangle.
Definition Rectangle.hpp:15
Implementation of a ring.
Definition Ring.hpp:31
constexpr bool contains(IPoint auto const &point) const noexcept
Definition Ring.hpp:196
constexpr std::unordered_set< Vec2DReal > intersections(LinearGeometry auto const &linearFeature) const noexcept
Definition Ring.hpp:225
constexpr Vec2DReal centroid() const noexcept
Calculate centroid point of the ring https://en.wikipedia.org/wiki/Centroid.
Definition Ring.hpp:164
constexpr PointLocation getPointLocation(IPoint auto const &point) const noexcept
Get the location of a point with regard to the ring (INSIDE | OUTSIDE | BOUNDARY) Uses the Ray-Castin...
Definition Ring.hpp:80
static constexpr GeometryType type
Definition Ring.hpp:99
constexpr std::string toString() const noexcept
Definition Ring.hpp:345
constexpr fishnet::math::DEFAULT_FLOATING_POINT area() const noexcept
Calculate the area of the ring Uses Shoelace formula: https://en.wikipedia.org/wiki/Shoelace_formula.
Definition Ring.hpp:151
constexpr util::view_of< Vec2D< T > > auto getPoints() const noexcept
Definition Ring.hpp:141
T numeric_type
Definition Ring.hpp:98
constexpr bool isInside(IPoint auto const &point) const noexcept
Definition Ring.hpp:200
constexpr bool intersects(const L &linearFeature) const noexcept
test whether a linear feature intersects the ring
Definition Ring.hpp:221
Ring(util::random_access_range_of< Vec2D< T > > auto const &points, bool checked=false)
Definition Ring.hpp:101
constexpr Rectangle< T > aaBB() const noexcept
Computes the axis-aligned bounding box of the ring Calculated be computing the extreme points in ever...
Definition Ring.hpp:177
constexpr bool contains(IRing auto const &other) const noexcept
Definition Ring.hpp:311
constexpr bool contains(ISegment auto const &segment) const noexcept
Test whether a segment is fully contained inside the boundary of the ring Collect all intersections o...
Definition Ring.hpp:243
constexpr size_t hash() const noexcept
Definition Ring.hpp:339
Ring(std::initializer_list< Vec2D< T > > &&points)
Definition Ring.hpp:114
constexpr util::view_of< Segment< T > > auto getSegments() const noexcept
Definition Ring.hpp:137
constexpr bool isOnBoundary(IPoint auto const &point) const noexcept
Definition Ring.hpp:204
constexpr bool operator==(const Ring< U > &other) const noexcept
Definition Ring.hpp:270
constexpr fishnet::math::DEFAULT_FLOATING_POINT distance(IRing auto const &other) const noexcept
Distance between the two rings.
Definition Ring.hpp:333
constexpr auto getHoles() const noexcept
Definition Ring.hpp:133
constexpr bool touches(IRing auto const &other) const noexcept
Definition Ring.hpp:317
constexpr bool isOutside(IPoint auto const &point) const noexcept
Definition Ring.hpp:208
constexpr bool crosses(IRing auto const &other) const noexcept
Definition Ring.hpp:304
Ring(util::random_access_range_of< Segment< T > > auto &&segments, bool checked=false)
Definition Ring.hpp:107
constexpr const Ring< T > & getBoundary() const noexcept
Definition Ring.hpp:129
Implementation of a segment.
Definition Segment.hpp:14
Implementation of a two-dimensional vector.
Definition Vec2D.hpp:16
constexpr size_t hash() const noexcept
Definition Vec2D.hpp:179
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
Definition IGeometry.hpp:13
Definition CollectionConcepts.hpp:39
Definition CollectionConcepts.hpp:45
Definition BoundingBoxWrapper.hpp:5
PointLocation
Definition Ring.hpp:21
GeometryType
Definition GeometryBase.hpp:6
Rectangle(const S &) -> Rectangle< typename S::numeric_type >
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 constexpr bool ringIntersects(const R &ring, const L &linearFeature) noexcept
test whether a linear feature intersects a ring Check every intersection of the linear feature with t...
Definition RingIntersection.hpp:28
double DEFAULT_FLOATING_POINT
Definition NumericConcepts.hpp:12
STL namespace.