rgeo 2.3.1 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -0
  3. data/README.md +23 -14
  4. data/ext/geos_c_impl/analysis.c +30 -25
  5. data/ext/geos_c_impl/analysis.h +8 -7
  6. data/ext/geos_c_impl/coordinates.c +27 -21
  7. data/ext/geos_c_impl/coordinates.h +5 -2
  8. data/ext/geos_c_impl/errors.c +19 -10
  9. data/ext/geos_c_impl/errors.h +11 -4
  10. data/ext/geos_c_impl/extconf.rb +42 -28
  11. data/ext/geos_c_impl/factory.c +540 -451
  12. data/ext/geos_c_impl/factory.h +105 -95
  13. data/ext/geos_c_impl/geometry.c +593 -387
  14. data/ext/geos_c_impl/geometry.h +10 -5
  15. data/ext/geos_c_impl/geometry_collection.c +306 -339
  16. data/ext/geos_c_impl/geometry_collection.h +6 -20
  17. data/ext/geos_c_impl/globals.c +169 -0
  18. data/ext/geos_c_impl/globals.h +46 -0
  19. data/ext/geos_c_impl/line_string.c +271 -231
  20. data/ext/geos_c_impl/line_string.h +5 -8
  21. data/ext/geos_c_impl/main.c +16 -16
  22. data/ext/geos_c_impl/point.c +65 -67
  23. data/ext/geos_c_impl/point.h +4 -7
  24. data/ext/geos_c_impl/polygon.c +137 -135
  25. data/ext/geos_c_impl/polygon.h +11 -11
  26. data/ext/geos_c_impl/preface.h +16 -10
  27. data/ext/geos_c_impl/ruby_more.c +67 -0
  28. data/ext/geos_c_impl/ruby_more.h +25 -0
  29. data/lib/rgeo/cartesian/analysis.rb +5 -3
  30. data/lib/rgeo/cartesian/bounding_box.rb +74 -79
  31. data/lib/rgeo/cartesian/calculations.rb +64 -33
  32. data/lib/rgeo/cartesian/factory.rb +57 -102
  33. data/lib/rgeo/cartesian/feature_classes.rb +68 -46
  34. data/lib/rgeo/cartesian/feature_methods.rb +67 -25
  35. data/lib/rgeo/cartesian/interface.rb +6 -41
  36. data/lib/rgeo/cartesian/planar_graph.rb +373 -0
  37. data/lib/rgeo/cartesian/sweepline_intersector.rb +147 -0
  38. data/lib/rgeo/cartesian/valid_op.rb +69 -0
  39. data/lib/rgeo/cartesian.rb +3 -0
  40. data/lib/rgeo/coord_sys/cs/entities.rb +303 -99
  41. data/lib/rgeo/coord_sys/cs/factories.rb +0 -2
  42. data/lib/rgeo/coord_sys/cs/wkt_parser.rb +90 -42
  43. data/lib/rgeo/coord_sys.rb +1 -20
  44. data/lib/rgeo/error.rb +15 -0
  45. data/lib/rgeo/feature/curve.rb +0 -11
  46. data/lib/rgeo/feature/factory.rb +26 -36
  47. data/lib/rgeo/feature/factory_generator.rb +6 -14
  48. data/lib/rgeo/feature/geometry.rb +146 -66
  49. data/lib/rgeo/feature/geometry_collection.rb +16 -9
  50. data/lib/rgeo/feature/line_string.rb +4 -5
  51. data/lib/rgeo/feature/linear_ring.rb +0 -1
  52. data/lib/rgeo/feature/multi_curve.rb +0 -6
  53. data/lib/rgeo/feature/multi_surface.rb +3 -4
  54. data/lib/rgeo/feature/point.rb +4 -5
  55. data/lib/rgeo/feature/polygon.rb +1 -2
  56. data/lib/rgeo/feature/surface.rb +3 -4
  57. data/lib/rgeo/feature/types.rb +69 -85
  58. data/lib/rgeo/geographic/factory.rb +98 -125
  59. data/lib/rgeo/geographic/interface.rb +69 -166
  60. data/lib/rgeo/geographic/projected_feature_classes.rb +21 -9
  61. data/lib/rgeo/geographic/projected_feature_methods.rb +67 -42
  62. data/lib/rgeo/geographic/projected_window.rb +36 -22
  63. data/lib/rgeo/geographic/{proj4_projector.rb → projector.rb} +3 -5
  64. data/lib/rgeo/geographic/simple_mercator_projector.rb +26 -25
  65. data/lib/rgeo/geographic/spherical_feature_classes.rb +29 -9
  66. data/lib/rgeo/geographic/spherical_feature_methods.rb +86 -9
  67. data/lib/rgeo/geographic/spherical_math.rb +17 -20
  68. data/lib/rgeo/geographic.rb +1 -1
  69. data/lib/rgeo/geos/capi_factory.rb +87 -158
  70. data/lib/rgeo/geos/capi_feature_classes.rb +50 -36
  71. data/lib/rgeo/geos/ffi_factory.rb +105 -173
  72. data/lib/rgeo/geos/ffi_feature_classes.rb +34 -10
  73. data/lib/rgeo/geos/ffi_feature_methods.rb +105 -127
  74. data/lib/rgeo/geos/interface.rb +20 -59
  75. data/lib/rgeo/geos/utils.rb +5 -5
  76. data/lib/rgeo/geos/zm_factory.rb +53 -95
  77. data/lib/rgeo/geos/zm_feature_methods.rb +30 -33
  78. data/lib/rgeo/geos.rb +8 -8
  79. data/lib/rgeo/impl_helper/basic_geometry_collection_methods.rb +9 -22
  80. data/lib/rgeo/impl_helper/basic_geometry_methods.rb +1 -2
  81. data/lib/rgeo/impl_helper/basic_line_string_methods.rb +28 -56
  82. data/lib/rgeo/impl_helper/basic_point_methods.rb +2 -14
  83. data/lib/rgeo/impl_helper/basic_polygon_methods.rb +17 -26
  84. data/lib/rgeo/impl_helper/utils.rb +21 -0
  85. data/lib/rgeo/impl_helper/valid_op.rb +350 -0
  86. data/lib/rgeo/impl_helper/validity_check.rb +139 -0
  87. data/lib/rgeo/impl_helper.rb +1 -0
  88. data/lib/rgeo/version.rb +1 -1
  89. data/lib/rgeo/wkrep/wkb_generator.rb +73 -63
  90. data/lib/rgeo/wkrep/wkb_parser.rb +33 -31
  91. data/lib/rgeo/wkrep/wkt_generator.rb +52 -45
  92. data/lib/rgeo/wkrep/wkt_parser.rb +48 -35
  93. data/lib/rgeo.rb +1 -3
  94. metadata +50 -13
  95. data/lib/rgeo/coord_sys/srs_database/entry.rb +0 -107
  96. data/lib/rgeo/coord_sys/srs_database/sr_org.rb +0 -64
  97. data/lib/rgeo/coord_sys/srs_database/url_reader.rb +0 -65
@@ -0,0 +1,373 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RGeo
4
+ module Cartesian
5
+ # A Doubly Connected Edge List (DCEL) implementation of a Planar Graph.
6
+ # It represents geometries as vertices and half-edges.
7
+ #
8
+ # It includes an incident_edges hash that maps vertices to an array
9
+ # of half-edges whose origins are that vertex.
10
+ #
11
+ # Upon instantiation, the graph will compute the intersections using
12
+ # the SweeplineIntersector, populate the incident_edges map, and
13
+ # link all cyclic edges.
14
+ class PlanarGraph
15
+ # HalfEdge represents an edge as 2 directed edges.
16
+ # One half-edge will have it's origin at edge.s, the other
17
+ # at edge.e. Both half-edges will be linked as each other's twins.
18
+ #
19
+ # HalfEdges also contain references to the next and prev half-edges,
20
+ # where next's origin is this half-edges destination. Prev's destination
21
+ # is this half-edge's origin.
22
+ class HalfEdge
23
+ include Comparable
24
+
25
+ # Creates 2 half edges from an edge.
26
+ # They will be assigned as each other's twins.
27
+ # The Half Edges will be returned in the order of points
28
+ # of the edge (start, end).
29
+ #
30
+ # @param edge [RGeo::Cartesian::Segment]
31
+ #
32
+ # @return [Array]
33
+ def self.from_edge(edge)
34
+ e1 = new(edge.s)
35
+ e2 = new(edge.e)
36
+
37
+ e1.twin = e2
38
+ e2.twin = e1
39
+ [e1, e2]
40
+ end
41
+
42
+ def initialize(origin)
43
+ @origin = origin
44
+ @twin = nil
45
+ @next = nil
46
+ @prev = nil
47
+ end
48
+ attr_reader :origin
49
+ attr_accessor :twin, :next, :prev
50
+
51
+ # HalfEdges will be sorted around their shared vertex
52
+ # in a CW fashion. This means that face interiors will be
53
+ # a CCW.
54
+ def <=>(other)
55
+ angle <=> other.angle
56
+ end
57
+
58
+ # Will find attempt to find a cycle starting at this
59
+ # HalfEdge. Will end upon finding the first repeated HalfEdge
60
+ # or a HalfEdge where +next+ is nil.
61
+ #
62
+ # If a block is given, each HalfEdge seen will be yielded to the block.
63
+ #
64
+ # @return [Enumerator]
65
+ def and_connected
66
+ return to_enum(__method__) unless block_given?
67
+
68
+ hedges = Set.new
69
+ yield(self)
70
+ hedges << self
71
+
72
+ n = self.next
73
+ until hedges.include?(n) || n.nil?
74
+ yield(n)
75
+ hedges << n
76
+ n = n.next
77
+ end
78
+ end
79
+
80
+ # Return the destination of the half edge
81
+ #
82
+ # @return [RGeo::Feature::Point]
83
+ def destination
84
+ twin&.origin
85
+ end
86
+
87
+ # Compute the angle from the positive x-axis.
88
+ # Used for sorting at each node.
89
+ #
90
+ # @return [Float]
91
+ def angle
92
+ @angle ||= Math.atan2(destination.y - origin.y, destination.x - origin.x)
93
+ end
94
+
95
+ def inspect
96
+ "#<#{self.class}:0x#{object_id.to_s(16)} #{self}>"
97
+ end
98
+
99
+ def to_s
100
+ dst = destination
101
+ pr = prev&.origin
102
+ n = @next&.origin
103
+ "HalfEdge(#{origin}, #{dst}), Prev: #{pr}, Next: #{n}"
104
+ end
105
+ end
106
+
107
+ # Create a new PlanarGraph
108
+ #
109
+ # @param edges [Array<RGeo::Cartesian::Segment>] of Segments
110
+ def initialize(edges = [])
111
+ @edges = []
112
+ @incident_edges = {}
113
+
114
+ add_edges(edges)
115
+ end
116
+ attr_reader :edges, :incident_edges
117
+
118
+ # Insert an edge into the graph. This will automatically
119
+ # calculate intersections and add new vertices if necessary.
120
+ #
121
+ # @param edge [RGeo::Cartesian::Segment]
122
+ def add_edge(edge)
123
+ @edges << edge
124
+ create_half_edge(edge)
125
+
126
+ # Check if a new intersection was made and handle it.
127
+ intersection_map.each do |seg, ints|
128
+ compute_split_edges(seg, ints)
129
+ end
130
+
131
+ link_half_edges
132
+ end
133
+
134
+ # Insert multiple edges into the graph. Like +add_edge+, this automatically
135
+ # calculates intersections and adds new vertices.
136
+ #
137
+ # @param new_edges [Array<RGeo::Cartesian::Segment>]
138
+ def add_edges(new_edges)
139
+ @edges.concat(new_edges)
140
+ new_edges.each do |edge|
141
+ create_half_edge(edge)
142
+ end
143
+
144
+ intersection_map.each do |seg, ints|
145
+ compute_split_edges(seg, ints)
146
+ end
147
+
148
+ link_half_edges
149
+ end
150
+
151
+ private
152
+
153
+ # Creates a map of +proper_intersections+ for each segment
154
+ # from a sweepline intersector.
155
+ #
156
+ # Can be used to determine which edges need to be split
157
+ # after adding edges.
158
+ def intersection_map
159
+ intersector = SweeplineIntersector.new(edges)
160
+ intersections = intersector.proper_intersections
161
+
162
+ intersection_map = {}
163
+ intersections.each do |int|
164
+ # check the int_point against each edge.
165
+ # if it is not on the boundary of the edge, add it to the
166
+ # list of intersections for that edge.
167
+ s1_intersects = int.point != int.s1.s && int.point != int.s1.e
168
+ if s1_intersects
169
+ intersection_map[int.s1] ||= []
170
+ intersection_map[int.s1] << int.point
171
+ end
172
+
173
+ s2_intersects = int.point != int.s2.s && int.point != int.s2.e
174
+
175
+ next unless s2_intersects
176
+
177
+ intersection_map[int.s2] ||= []
178
+ intersection_map[int.s2] << int.point
179
+ end
180
+ intersection_map
181
+ end
182
+
183
+ def create_half_edges
184
+ @edges.each do |edge|
185
+ create_half_edge(edge)
186
+ end
187
+ end
188
+
189
+ def create_half_edge(edge)
190
+ e1, e2 = HalfEdge.from_edge(edge)
191
+ insert_half_edge(e1)
192
+ insert_half_edge(e2)
193
+ end
194
+
195
+ def insert_half_edge(half_edge)
196
+ @incident_edges[half_edge.origin.coordinates] ||= []
197
+ @incident_edges[half_edge.origin.coordinates] << half_edge
198
+ end
199
+
200
+ # Links all half-edges where possible.
201
+ # Defines +next+ and +prev+ for every half-edge by rotating
202
+ # through all half-edges originating at a vertex.
203
+ #
204
+ # Assuming half-edges are sorted CCW, every sequential pair of
205
+ # half-edges (e1, e2) can be linked by saying e1.prev = e2.twin
206
+ # and e2.twin.next = e1.
207
+ def link_half_edges
208
+ incident_edges.each_value do |hedges|
209
+ hedges.sort!
210
+ [*hedges, hedges.first].each_cons(2) do |e1, e2|
211
+ e1.prev = e2.twin
212
+ e2.twin.next = e1
213
+ end
214
+ end
215
+ end
216
+
217
+ # It is possible that intersections occur when new edges are added.
218
+ # This will split those edges into more half-edges while preserving
219
+ # the existing half-edges when possible since geometries may reference
220
+ # them.
221
+ def compute_split_edges(seg, ints)
222
+ points = ints.uniq.sort do |a, b|
223
+ a.distance(seg.s) <=> b.distance(seg.s)
224
+ end
225
+ points = [seg.s] + points + [seg.e]
226
+
227
+ he_start = incident_edges[points.first.coordinates].find { |he| he.destination == points.last }
228
+ he_end = incident_edges[points.last.coordinates].find { |he| he.destination == points.first }
229
+
230
+ points.each_cons(2) do |s, e|
231
+ edge = Segment.new(s, e)
232
+ if s == he_start.origin
233
+ he = HalfEdge.new(e)
234
+ he_start.twin = he
235
+ he.twin = he_start
236
+ insert_half_edge(he)
237
+ elsif e == he_end.origin
238
+ he = HalfEdge.new(s)
239
+ he_end.twin = he
240
+ he.twin = he_end
241
+ insert_half_edge(he)
242
+ else
243
+ create_half_edge(edge)
244
+ end
245
+ @edges << edge
246
+ end
247
+ @edges.delete(seg)
248
+ end
249
+ end
250
+
251
+ # GeometryGraph is a PlanarGraph that is built by adding
252
+ # geometries instead of edges. The GeometryGraph will
253
+ # hold a reference to an arbitrary HalfEdge on the
254
+ # interior of the geometry for every boundary in the geometry.
255
+ # For example, a polygon will have a reference to a HalfEdge for its
256
+ # exterior shell and one for every hole.
257
+ class GeometryGraph < PlanarGraph
258
+ # GeomEdge will be used to store the references to the HalfEdges
259
+ GeomEdge = Struct.new(:exterior_edge, :interior_edges)
260
+
261
+ def initialize(geom)
262
+ super()
263
+ @parent_geometry = geom
264
+ @geom_edges = []
265
+ add_geometry(geom)
266
+ end
267
+ attr_reader :parent_geometry, :geom_edges
268
+
269
+ private
270
+
271
+ # Adds a geometry to the graph and finds its
272
+ # reference HalfEdge(s).
273
+ #
274
+ # @param geom [RGeo::Feature::Instance]
275
+ def add_geometry(geom)
276
+ case geom
277
+ when Feature::Point
278
+ # Can't handle points yet, so just add an empty entry for them
279
+ @geom_edges << GeomEdge.new
280
+ when Feature::LineString, Feature::LinearRing
281
+ add_line_string(geom)
282
+ when Feature::Polygon
283
+ add_polygon(geom)
284
+ when Feature::GeometryCollection
285
+ add_collection(geom)
286
+ else
287
+ raise Error::RGeoError, "Invalid Geometry Type: #{geom.class}"
288
+ end
289
+ end
290
+
291
+ # Adds a LineString or LinearRing
292
+ # to the graph.
293
+ #
294
+ # @param geom [RGeo::Feature::LineString]
295
+ def add_line_string(geom)
296
+ add_edges(geom.segments)
297
+
298
+ hedge = @incident_edges[geom.start_point.coordinates].first unless geom.empty?
299
+
300
+ @geom_edges << GeomEdge.new(hedge, nil)
301
+ end
302
+
303
+ # Adds a Polygon to the graph.
304
+ #
305
+ # Will add each shell seperately and find a CCW half-edge
306
+ # for the exterior ring and a CW half-edge for interior rings
307
+ # since these are designated as being on the interior of the polygon.
308
+ #
309
+ # It is possible that the half-edge will be nil if a suitable one cannot
310
+ # be found. This indicates the polygon is likely invalid.
311
+ #
312
+ # @param geom [RGeo::Feature::Polygon]
313
+ def add_polygon(geom)
314
+ exterior = geom.exterior_ring
315
+ add_edges(exterior.segments)
316
+
317
+ hedge = find_hedge(exterior)
318
+
319
+ interior_hedges = []
320
+ geom.interior_rings.each do |interior|
321
+ add_edges(interior.segments)
322
+ interior_hedges << find_hedge(interior, ccw: false)
323
+ end
324
+
325
+ @geom_edges << GeomEdge.new(hedge, interior_hedges)
326
+ end
327
+
328
+ # Adds a GeometryCollection to the graph.
329
+ #
330
+ # @param col [RGeo::Feature::GeometryCollection]
331
+ def add_collection(col)
332
+ col.each do |geom|
333
+ add_geometry(geom)
334
+ end
335
+ end
336
+
337
+ # Finds a Half-Edge that is part of a CCW or CW rotation
338
+ # from the input ring. Returns nil if none found.
339
+ #
340
+ # Will only consider half-edges that are colinear with
341
+ # the first or last segments of the ring.
342
+ #
343
+ # @param ring [RGeo::Feature::LinearRing]
344
+ # @param ccw [Boolean] true for CCW, false for CW
345
+ # @return [HalfEdge, nil]
346
+ def find_hedge(ring, ccw: true)
347
+ return nil if ring.num_points == 0
348
+ ccw_target = ccw ? 1 : -1
349
+
350
+ coords = ring.start_point.coordinates
351
+ hedges = incident_edges[coords]
352
+
353
+ # find half-edges that are colinear to the start or end
354
+ # segment of the ring.
355
+ start_seg = Segment.new(ring.start_point, ring.point_n(1))
356
+ end_seg = Segment.new(ring.point_n(ring.num_points - 2), ring.end_point)
357
+ colinear_hedges = hedges.select do |he|
358
+ start_seg.side(he.destination) == 0 || end_seg.side(he.destination) == 0
359
+ end
360
+
361
+ colinear_hedges.find do |hedge|
362
+ pts = []
363
+ hedge.and_connected do |he|
364
+ pts << he.origin
365
+ end
366
+ pts << hedge.origin
367
+ lr = parent_geometry.factory.line_string(pts)
368
+ Analysis.ring_direction(lr) == ccw_target
369
+ end
370
+ end
371
+ end
372
+ end
373
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ # -----------------------------------------------------------------------------
4
+ #
5
+ # Simple Sweepline Intersector Class
6
+ #
7
+ # -----------------------------------------------------------------------------
8
+
9
+ module RGeo
10
+ module Cartesian
11
+ # Implements a Sweepline intersector to find all intersections
12
+ # in a group of segments. The idea is to use a horizontal line starting
13
+ # at y = +Infinity that sweeps down to y = -Infinity and every time it hits
14
+ # a new line, it will check if it intersects with any of the segments
15
+ # the line currently intersects at that y value.
16
+ # This is a more simplistic implementation that uses an array to hold
17
+ # observed segments instead of a sorted BST, so performance may be significantly
18
+ # worse in the case of lots of segments overlapping in y-ranges.
19
+ class SweeplineIntersector
20
+ Event = Struct.new(:point, :segment, :is_start)
21
+ Intersection = Struct.new(:point, :s1, :s2)
22
+
23
+ def initialize(segments)
24
+ @segments = segments
25
+ end
26
+ attr_reader :segments
27
+
28
+ # Returns the "proper" intersections from the list of segments.
29
+ #
30
+ # This will only return intersections that are not the start/end or
31
+ # end/start of the 2 segments. This could be useful for finding intersections
32
+ # in a ring for example, because knowing that segments are connected in a linestring
33
+ # is not always helpful, but those are reported by default.
34
+ #
35
+ # Note: This is not the true definition of a proper intersection. A
36
+ # truly proper intersection does not include colinear intersections and
37
+ # the intersection must lie in the interior of both segments.
38
+ #
39
+ # @return [Array<RGeo::Cartesian::SweeplineIntersector::Intersection>]
40
+ def proper_intersections
41
+ return @proper_intersections if defined?(@proper_intersections)
42
+
43
+ @proper_intersections = []
44
+ intersections.each do |intersection|
45
+ s1 = intersection.s1
46
+ s2 = intersection.s2
47
+ pt = intersection.point
48
+
49
+ @proper_intersections << intersection unless (pt == s1.s && pt == s2.e) || (pt == s1.e && pt == s2.s)
50
+ end
51
+ @proper_intersections
52
+ end
53
+
54
+ # Computes the intersections of the input segments.
55
+ #
56
+ # Creates an event queue from the +events+ and adds segments to the
57
+ # +observed_segments+ array while their ending event has not been popped
58
+ # from the queue.
59
+ #
60
+ # Compares the new segment from the +is_start+ event to each observed segment
61
+ # then adds it to +observed_segments+. Records any intersections in to the
62
+ # returned array.
63
+ #
64
+ # @return [Array<RGeo::Cartesian::SweeplineIntersector::Intersection>]
65
+ def intersections
66
+ return @intersections if defined?(@intersections)
67
+
68
+ @intersections = []
69
+ observed_segments = Set.new
70
+ events.each do |e|
71
+ seg = e.segment
72
+
73
+ if e.is_start
74
+ observed_segments.each do |oseg|
75
+ int_pt = seg.segment_intersection(oseg)
76
+ if int_pt
77
+ intersect = Intersection.new(int_pt, seg, oseg)
78
+ @intersections << intersect
79
+ end
80
+ end
81
+ observed_segments << seg
82
+ else
83
+ observed_segments.delete(seg)
84
+ end
85
+ end
86
+ @intersections
87
+ end
88
+
89
+ # Returns an ordered array of events from the input segments. Events
90
+ # are the start and endpoints of each segment with an is_start tag to
91
+ # indicate if this is the starting or ending event for that segment.
92
+ #
93
+ # Ordering is done by greatest-y -> smallest-x -> is_start = true.
94
+ #
95
+ # @return [Array]
96
+ def events
97
+ return @events if defined?(@events)
98
+
99
+ @events = []
100
+ segments.each do |segment|
101
+ event_pair = create_event_pair(segment)
102
+ @events.concat(event_pair)
103
+ end
104
+
105
+ @events.sort! do |a, b|
106
+ if a.point == b.point
107
+ if a.is_start
108
+ -1
109
+ else
110
+ 1
111
+ end
112
+ elsif a.point.y == b.point.y
113
+ a.point.x <=> b.point.x
114
+ else
115
+ b.point.y <=> a.point.y
116
+ end
117
+ end
118
+ @events
119
+ end
120
+
121
+ private
122
+
123
+ # Creates a pair of events from a segment
124
+ #
125
+ # @param segment [Segment]
126
+ #
127
+ # @return [Array]
128
+ def create_event_pair(segment)
129
+ s = segment.s
130
+ e = segment.e
131
+
132
+ s_event = Event.new(s, segment)
133
+ e_event = Event.new(e, segment)
134
+
135
+ if s.y > e.y || (s.y == e.y && s.x < e.x)
136
+ s_event.is_start = true
137
+ e_event.is_start = false
138
+ else
139
+ s_event.is_start = false
140
+ e_event.is_start = true
141
+ end
142
+
143
+ [s_event, e_event]
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RGeo
4
+ module Cartesian
5
+ module ValidOp
6
+ include ImplHelper::ValidOp
7
+
8
+ def validity_helper
9
+ ValidOpHelpers
10
+ end
11
+ end
12
+
13
+ module ValidOpHelpers
14
+ include ImplHelper::ValidOpHelpers
15
+
16
+ module_function(*ImplHelper::ValidOpHelpers.singleton_methods) # rubocop:disable Style/AccessModifierDeclarations
17
+
18
+ module_function
19
+
20
+ # Checks that there are no invalid intersections between the components
21
+ # of a polygon.
22
+ #
23
+ # @param [RGeo::Feature::Polygon] poly
24
+ #
25
+ # @return [String] invalid_reason
26
+ def check_consistent_area(poly)
27
+ # Get set of unique coords
28
+ pts = poly.exterior_ring.coordinates.to_set
29
+ poly.interior_rings.each do |ring|
30
+ pts += ring.coordinates
31
+ end
32
+ num_points = pts.size
33
+
34
+ # if additional nodes were added, there must be an intersection
35
+ # through a boundary.
36
+ return Error::SELF_INTERSECTION if poly.send(:graph).incident_edges.size > num_points
37
+
38
+ rings = [poly.exterior_ring] + poly.interior_rings
39
+ return Error::SELF_INTERSECTION if rings.uniq.size != rings.size
40
+
41
+ nil
42
+ end
43
+
44
+ # Checks that the interior of a polygon is connected.
45
+ #
46
+ # Process to do this is to walk around an interior cycle of the
47
+ # exterior shell in the polygon's geometry graph. It will keep track
48
+ # of all the nodes it visited and if that set is a superset of the
49
+ # coordinates in the exterior_ring, the interior is connected, otherwise
50
+ # it is split somewhere.
51
+ #
52
+ # @param [RGeo::Feature::Polygon] poly
53
+ #
54
+ # @return [String] invalid_reason
55
+ def check_connected_interiors(poly)
56
+ exterior_coords = poly.exterior_ring.coordinates.to_set
57
+
58
+ visited = Set.new
59
+ poly.send(:graph).geom_edges.first.exterior_edge.and_connected do |hedge|
60
+ visited << hedge.origin.coordinates
61
+ end
62
+
63
+ return Error::DISCONNECTED_INTERIOR unless exterior_coords.subset?(visited)
64
+
65
+ nil
66
+ end
67
+ end
68
+ end
69
+ end
@@ -8,8 +8,11 @@
8
8
 
9
9
  require_relative "cartesian/calculations"
10
10
  require_relative "cartesian/feature_methods"
11
+ require_relative "cartesian/valid_op"
11
12
  require_relative "cartesian/feature_classes"
12
13
  require_relative "cartesian/factory"
13
14
  require_relative "cartesian/interface"
14
15
  require_relative "cartesian/bounding_box"
15
16
  require_relative "cartesian/analysis"
17
+ require_relative "cartesian/sweepline_intersector"
18
+ require_relative "cartesian/planar_graph"