rgeo 2.3.1 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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"