rgeo 2.3.1 → 3.0.0.pre.rc.2

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -0
  3. data/README.md +11 -10
  4. data/ext/geos_c_impl/analysis.c +8 -6
  5. data/ext/geos_c_impl/analysis.h +1 -3
  6. data/ext/geos_c_impl/errors.c +10 -8
  7. data/ext/geos_c_impl/errors.h +7 -3
  8. data/ext/geos_c_impl/extconf.rb +3 -0
  9. data/ext/geos_c_impl/factory.c +273 -202
  10. data/ext/geos_c_impl/factory.h +51 -63
  11. data/ext/geos_c_impl/geometry.c +124 -22
  12. data/ext/geos_c_impl/geometry.h +8 -3
  13. data/ext/geos_c_impl/geometry_collection.c +81 -185
  14. data/ext/geos_c_impl/geometry_collection.h +1 -14
  15. data/ext/geos_c_impl/globals.c +91 -0
  16. data/ext/geos_c_impl/globals.h +45 -0
  17. data/ext/geos_c_impl/line_string.c +28 -29
  18. data/ext/geos_c_impl/line_string.h +1 -3
  19. data/ext/geos_c_impl/main.c +10 -9
  20. data/ext/geos_c_impl/point.c +9 -8
  21. data/ext/geos_c_impl/point.h +1 -3
  22. data/ext/geos_c_impl/polygon.c +43 -72
  23. data/ext/geos_c_impl/polygon.h +1 -3
  24. data/ext/geos_c_impl/preface.h +12 -0
  25. data/ext/geos_c_impl/ruby_more.c +65 -0
  26. data/ext/geos_c_impl/ruby_more.h +16 -0
  27. data/lib/rgeo/cartesian/calculations.rb +54 -17
  28. data/lib/rgeo/cartesian/factory.rb +6 -14
  29. data/lib/rgeo/cartesian/feature_classes.rb +68 -46
  30. data/lib/rgeo/cartesian/feature_methods.rb +67 -20
  31. data/lib/rgeo/cartesian/interface.rb +0 -36
  32. data/lib/rgeo/cartesian/planar_graph.rb +379 -0
  33. data/lib/rgeo/cartesian/sweepline_intersector.rb +149 -0
  34. data/lib/rgeo/cartesian/valid_op.rb +71 -0
  35. data/lib/rgeo/cartesian.rb +3 -0
  36. data/lib/rgeo/coord_sys/cs/wkt_parser.rb +6 -6
  37. data/lib/rgeo/coord_sys.rb +0 -11
  38. data/lib/rgeo/error.rb +15 -0
  39. data/lib/rgeo/feature/factory_generator.rb +0 -3
  40. data/lib/rgeo/feature/geometry.rb +107 -28
  41. data/lib/rgeo/feature/geometry_collection.rb +13 -5
  42. data/lib/rgeo/feature/line_string.rb +3 -3
  43. data/lib/rgeo/feature/multi_surface.rb +3 -3
  44. data/lib/rgeo/feature/point.rb +4 -4
  45. data/lib/rgeo/feature/surface.rb +3 -3
  46. data/lib/rgeo/geographic/factory.rb +6 -7
  47. data/lib/rgeo/geographic/interface.rb +6 -49
  48. data/lib/rgeo/geographic/proj4_projector.rb +0 -2
  49. data/lib/rgeo/geographic/projected_feature_classes.rb +21 -9
  50. data/lib/rgeo/geographic/projected_feature_methods.rb +67 -28
  51. data/lib/rgeo/geographic/simple_mercator_projector.rb +0 -2
  52. data/lib/rgeo/geographic/spherical_feature_classes.rb +29 -9
  53. data/lib/rgeo/geographic/spherical_feature_methods.rb +79 -2
  54. data/lib/rgeo/geos/capi_factory.rb +21 -38
  55. data/lib/rgeo/geos/capi_feature_classes.rb +54 -11
  56. data/lib/rgeo/geos/ffi_factory.rb +6 -35
  57. data/lib/rgeo/geos/ffi_feature_classes.rb +34 -10
  58. data/lib/rgeo/geos/ffi_feature_methods.rb +39 -5
  59. data/lib/rgeo/geos/interface.rb +0 -24
  60. data/lib/rgeo/geos/zm_factory.rb +0 -19
  61. data/lib/rgeo/geos/zm_feature_methods.rb +16 -0
  62. data/lib/rgeo/geos.rb +6 -3
  63. data/lib/rgeo/impl_helper/basic_geometry_collection_methods.rb +4 -4
  64. data/lib/rgeo/impl_helper/basic_geometry_methods.rb +1 -1
  65. data/lib/rgeo/impl_helper/basic_line_string_methods.rb +15 -19
  66. data/lib/rgeo/impl_helper/basic_point_methods.rb +1 -1
  67. data/lib/rgeo/impl_helper/basic_polygon_methods.rb +1 -1
  68. data/lib/rgeo/impl_helper/valid_op.rb +354 -0
  69. data/lib/rgeo/impl_helper/validity_check.rb +139 -0
  70. data/lib/rgeo/impl_helper.rb +1 -0
  71. data/lib/rgeo/version.rb +1 -1
  72. metadata +45 -9
  73. data/lib/rgeo/coord_sys/srs_database/entry.rb +0 -107
  74. data/lib/rgeo/coord_sys/srs_database/sr_org.rb +0 -64
  75. data/lib/rgeo/coord_sys/srs_database/url_reader.rb +0 -65
@@ -0,0 +1,379 @@
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
+ unless intersection_map[int.s1]
170
+ intersection_map[int.s1] = []
171
+ end
172
+ intersection_map[int.s1] << int.point
173
+ end
174
+
175
+ s2_intersects = int.point != int.s2.s && int.point != int.s2.e
176
+ next unless s2_intersects
177
+ unless intersection_map[int.s2]
178
+ intersection_map[int.s2] = []
179
+ end
180
+ intersection_map[int.s2] << int.point
181
+ end
182
+ intersection_map
183
+ end
184
+
185
+ def create_half_edges
186
+ @edges.each do |edge|
187
+ create_half_edge(edge)
188
+ end
189
+ end
190
+
191
+ def create_half_edge(edge)
192
+ e1, e2 = HalfEdge.from_edge(edge)
193
+ insert_half_edge(e1)
194
+ insert_half_edge(e2)
195
+ end
196
+
197
+ def insert_half_edge(he)
198
+ unless incident_edges[he.origin.coordinates]
199
+ @incident_edges[he.origin.coordinates] = []
200
+ end
201
+ @incident_edges[he.origin.coordinates] << he
202
+ end
203
+
204
+ # Links all half-edges where possible.
205
+ # Defines +next+ and +prev+ for every half-edge by rotating
206
+ # through all half-edges originating at a vertex.
207
+ #
208
+ # Assuming half-edges are sorted CCW, every sequential pair of
209
+ # half-edges (e1, e2) can be linked by saying e1.prev = e2.twin
210
+ # and e2.twin.next = e1.
211
+ def link_half_edges
212
+ incident_edges.each_value do |hedges|
213
+ hedges.sort!
214
+ [*hedges, hedges.first].each_cons(2) do |e1, e2|
215
+ e1.prev = e2.twin
216
+ e2.twin.next = e1
217
+ end
218
+ end
219
+ end
220
+
221
+ # It is possible that intersections occur when new edges are added.
222
+ # This will split those edges into more half-edges while preserving
223
+ # the existing half-edges when possible since geometries may reference
224
+ # them.
225
+ def compute_split_edges(seg, ints)
226
+ points = ints.uniq.sort do |a, b|
227
+ a.distance(seg.s) <=> b.distance(seg.s)
228
+ end
229
+ points = [seg.s] + points + [seg.e]
230
+
231
+ he_start = incident_edges[points.first.coordinates].find { |he| he.destination == points.last }
232
+ he_end = incident_edges[points.last.coordinates].find { |he| he.destination == points.first }
233
+
234
+ points.each_cons(2) do |s, e|
235
+ edge = Segment.new(s, e)
236
+ if s == he_start.origin
237
+ he = HalfEdge.new(e)
238
+ he_start.twin = he
239
+ he.twin = he_start
240
+ insert_half_edge(he)
241
+ elsif e == he_end.origin
242
+ he = HalfEdge.new(s)
243
+ he_end.twin = he
244
+ he.twin = he_end
245
+ insert_half_edge(he)
246
+ else
247
+ create_half_edge(edge)
248
+ end
249
+ @edges << edge
250
+ end
251
+ @edges.delete(seg)
252
+ end
253
+ end
254
+
255
+ # GeometryGraph is a PlanarGraph that is built by adding
256
+ # geometries instead of edges. The GeometryGraph will
257
+ # hold a reference to an arbitrary HalfEdge on the
258
+ # interior of the geometry for every boundary in the geometry.
259
+ # For example, a polygon will have a reference to a HalfEdge for its
260
+ # exterior shell and one for every hole.
261
+ class GeometryGraph < PlanarGraph
262
+ # GeomEdge will be used to store the references to the HalfEdges
263
+ GeomEdge = Struct.new(:exterior_edge, :interior_edges)
264
+
265
+ def initialize(geom)
266
+ super()
267
+ @parent_geometry = geom
268
+ @geom_edges = []
269
+ add_geometry(geom)
270
+ end
271
+ attr_reader :parent_geometry, :geom_edges
272
+
273
+ private
274
+
275
+ # Adds a geometry to the graph and finds its
276
+ # reference HalfEdge(s).
277
+ #
278
+ # @param geom [RGeo::Feature::Instance]
279
+ def add_geometry(geom)
280
+ case geom
281
+ when Feature::Point
282
+ # Can't handle points yet, so just add an empty entry for them
283
+ @geom_edges << GeomEdge.new
284
+ when Feature::LineString, Feature::LinearRing
285
+ add_line_string(geom)
286
+ when Feature::Polygon
287
+ add_polygon(geom)
288
+ when Feature::GeometryCollection
289
+ add_collection(geom)
290
+ else
291
+ raise Error::RGeoError, "Invalid Geometry Type: #{geom.class}"
292
+ end
293
+ end
294
+
295
+ # Adds a LineString or LinearRing
296
+ # to the graph.
297
+ #
298
+ # @param geom [RGeo::Feature::LineString]
299
+ def add_line_string(geom)
300
+ add_edges(geom.segments)
301
+
302
+ hedge = unless geom.empty?
303
+ @incident_edges[geom.start_point.coordinates].first
304
+ end
305
+
306
+ @geom_edges << GeomEdge.new(hedge, nil)
307
+ end
308
+
309
+ # Adds a Polygon to the graph.
310
+ #
311
+ # Will add each shell seperately and find a CCW half-edge
312
+ # for the exterior ring and a CW half-edge for interior rings
313
+ # since these are designated as being on the interior of the polygon.
314
+ #
315
+ # It is possible that the half-edge will be nil if a suitable one cannot
316
+ # be found. This indicates the polygon is likely invalid.
317
+ #
318
+ # @param geom [RGeo::Feature::Polygon]
319
+ def add_polygon(geom)
320
+ exterior = geom.exterior_ring
321
+ add_edges(exterior.segments)
322
+
323
+ hedge = find_hedge(exterior)
324
+
325
+ interior_hedges = []
326
+ geom.interior_rings.each do |interior|
327
+ add_edges(interior.segments)
328
+ interior_hedges << find_hedge(interior, ccw: false)
329
+ end
330
+
331
+ @geom_edges << GeomEdge.new(hedge, interior_hedges)
332
+ end
333
+
334
+ # Adds a GeometryCollection to the graph.
335
+ #
336
+ # @param col [RGeo::Feature::GeometryCollection]
337
+ def add_collection(col)
338
+ col.each do |geom|
339
+ add_geometry(geom)
340
+ end
341
+ end
342
+
343
+ # Finds a Half-Edge that is part of a CCW or CW rotation
344
+ # from the input ring. Returns nil if none found.
345
+ #
346
+ # Will only consider half-edges that are colinear with
347
+ # the first or last segments of the ring.
348
+ #
349
+ # @param ring [RGeo::Feature::LinearRing]
350
+ # @param ccw [Boolean] true for CCW, false for CW
351
+ # @return [HalfEdge, nil]
352
+ def find_hedge(ring, ccw: true)
353
+ return nil if ring.num_points.zero?
354
+ ccw_target = ccw ? 1 : -1
355
+
356
+ coords = ring.start_point.coordinates
357
+ hedges = incident_edges[coords]
358
+
359
+ # find half-edges that are colinear to the start or end
360
+ # segment of the ring.
361
+ start_seg = Segment.new(ring.start_point, ring.point_n(1))
362
+ end_seg = Segment.new(ring.point_n(ring.num_points - 2), ring.end_point)
363
+ colinear_hedges = hedges.select do |he|
364
+ start_seg.side(he.destination).zero? || end_seg.side(he.destination).zero?
365
+ end
366
+
367
+ colinear_hedges.find do |hedge|
368
+ pts = []
369
+ hedge.and_connected do |he|
370
+ pts << he.origin
371
+ end
372
+ pts << hedge.origin
373
+ lr = parent_geometry.factory.line_string(pts)
374
+ Analysis.ring_direction(lr) == ccw_target
375
+ end
376
+ end
377
+ end
378
+ end
379
+ end
@@ -0,0 +1,149 @@
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
+ unless (pt == s1.s && pt == s2.e) || (pt == s1.e && pt == s2.s)
50
+ @proper_intersections << intersection
51
+ end
52
+ end
53
+ @proper_intersections
54
+ end
55
+
56
+ # Computes the intersections of the input segments.
57
+ #
58
+ # Creates an event queue from the +events+ and adds segments to the
59
+ # +observed_segments+ array while their ending event has not been popped
60
+ # from the queue.
61
+ #
62
+ # Compares the new segment from the +is_start+ event to each observed segment
63
+ # then adds it to +observed_segments+. Records any intersections in to the
64
+ # returned array.
65
+ #
66
+ # @return [Array<RGeo::Cartesian::SweeplineIntersector::Intersection>]
67
+ def intersections
68
+ return @intersections if defined?(@intersections)
69
+
70
+ @intersections = []
71
+ observed_segments = Set.new
72
+ events.each do |e|
73
+ seg = e.segment
74
+
75
+ if e.is_start
76
+ observed_segments.each do |oseg|
77
+ int_pt = seg.segment_intersection(oseg)
78
+ if int_pt
79
+ intersect = Intersection.new(int_pt, seg, oseg)
80
+ @intersections << intersect
81
+ end
82
+ end
83
+ observed_segments << seg
84
+ else
85
+ observed_segments.delete(seg)
86
+ end
87
+ end
88
+ @intersections
89
+ end
90
+
91
+ # Returns an ordered array of events from the input segments. Events
92
+ # are the start and endpoints of each segment with an is_start tag to
93
+ # indicate if this is the starting or ending event for that segment.
94
+ #
95
+ # Ordering is done by greatest-y -> smallest-x -> is_start = true.
96
+ #
97
+ # @return [Array]
98
+ def events
99
+ return @events if defined?(@events)
100
+
101
+ @events = []
102
+ segments.each do |segment|
103
+ event_pair = create_event_pair(segment)
104
+ @events.concat(event_pair)
105
+ end
106
+
107
+ @events.sort! do |a, b|
108
+ if a.point == b.point
109
+ if a.is_start
110
+ -1
111
+ else
112
+ 1
113
+ end
114
+ elsif a.point.y == b.point.y
115
+ a.point.x <=> b.point.x
116
+ else
117
+ b.point.y <=> a.point.y
118
+ end
119
+ end
120
+ @events
121
+ end
122
+
123
+ private
124
+
125
+ # Creates a pair of events from a segment
126
+ #
127
+ # @param segment [Segment]
128
+ #
129
+ # @return [Array]
130
+ def create_event_pair(segment)
131
+ s = segment.s
132
+ e = segment.e
133
+
134
+ s_event = Event.new(s, segment)
135
+ e_event = Event.new(e, segment)
136
+
137
+ if s.y > e.y || (s.y == e.y && s.x < e.x)
138
+ s_event.is_start = true
139
+ e_event.is_start = false
140
+ else
141
+ s_event.is_start = false
142
+ e_event.is_start = true
143
+ end
144
+
145
+ [s_event, e_event]
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,71 @@
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
+ if poly.send(:graph).incident_edges.size > num_points
37
+ return Error::SELF_INTERSECTION
38
+ end
39
+
40
+ rings = [poly.exterior_ring] + poly.interior_rings
41
+ return Error::SELF_INTERSECTION if rings.uniq.size != rings.size
42
+
43
+ nil
44
+ end
45
+
46
+ # Checks that the interior of a polygon is connected.
47
+ #
48
+ # Process to do this is to walk around an interior cycle of the
49
+ # exterior shell in the polygon's geometry graph. It will keep track
50
+ # of all the nodes it visited and if that set is a superset of the
51
+ # coordinates in the exterior_ring, the interior is connected, otherwise
52
+ # it is split somewhere.
53
+ #
54
+ # @param [RGeo::Feature::Polygon] poly
55
+ #
56
+ # @return [String] invalid_reason
57
+ def check_connected_interiors(poly)
58
+ exterior_coords = poly.exterior_ring.coordinates.to_set
59
+
60
+ visited = Set.new
61
+ poly.send(:graph).geom_edges.first.exterior_edge.and_connected do |hedge|
62
+ visited << hedge.origin.coordinates
63
+ end
64
+
65
+ return Error::DISCONNECTED_INTERIOR unless exterior_coords.subset?(visited)
66
+
67
+ nil
68
+ end
69
+ end
70
+ end
71
+ 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"
@@ -24,7 +24,7 @@ module RGeo
24
24
  return value
25
25
  end
26
26
  unless @cur_token.is_a?(TypeString)
27
- raise Error::ParseError("Found token #{@cur_token} when we expected a value")
27
+ raise Error::ParseError, "Found token #{@cur_token} when we expected a value"
28
28
  end
29
29
  type = @cur_token
30
30
  next_token
@@ -48,7 +48,7 @@ module RGeo
48
48
  when "TOWGS84"
49
49
  bursa_wolf_params = args.find_all(Numeric)
50
50
  unless bursa_wolf_params.size == 7
51
- raise Error::ParseError("Expected 7 Bursa Wolf parameters but found #{bursa_wolf_params.size}")
51
+ raise Error::ParseError, "Expected 7 Bursa Wolf parameters but found #{bursa_wolf_params.size}"
52
52
  end
53
53
  obj = WGS84ConversionInfo.create(*bursa_wolf_params)
54
54
  when "UNIT"
@@ -87,7 +87,7 @@ module RGeo
87
87
  unit = args.find_first(Unit)
88
88
  axes = args.find_all(AxisInfo)
89
89
  unless axes.size > 0
90
- raise Error::ParseError("Expected at least one AXIS in a LOCAL_CS")
90
+ raise Error::ParseError, "Expected at least one AXIS in a LOCAL_CS"
91
91
  end
92
92
  obj = LocalCoordinateSystem.create(name, local_datum, unit, axes, *args.create_optionals)
93
93
  when "GEOCCS"
@@ -97,7 +97,7 @@ module RGeo
97
97
  linear_unit = args.find_first(LinearUnit)
98
98
  axes = args.find_all(AxisInfo)
99
99
  unless axes.size == 0 || axes.size == 3
100
- raise Error::ParseError("GEOCCS must contain either 0 or 3 AXIS parameters")
100
+ raise Error::ParseError, "GEOCCS must contain either 0 or 3 AXIS parameters"
101
101
  end
102
102
  obj = GeocentricCoordinateSystem.create(name, horizontal_datum, prime_meridian, linear_unit, axes[0], axes[1], axes[2], *args.create_optionals)
103
103
  when "VERT_CS"
@@ -113,7 +113,7 @@ module RGeo
113
113
  angular_unit = args.find_first(AngularUnit)
114
114
  axes = args.find_all(AxisInfo)
115
115
  unless axes.size == 0 || axes.size == 2
116
- raise Error::ParseError("GEOGCS must contain either 0 or 2 AXIS parameters")
116
+ raise Error::ParseError, "GEOGCS must contain either 0 or 2 AXIS parameters"
117
117
  end
118
118
  obj = GeographicCoordinateSystem.create(name, angular_unit, horizontal_datum, prime_meridian, axes[0], axes[1], *args.create_optionals)
119
119
  when "PROJCS"
@@ -125,7 +125,7 @@ module RGeo
125
125
  linear_unit = args.find_first(LinearUnit)
126
126
  axes = args.find_all(AxisInfo)
127
127
  unless axes.size == 0 || axes.size == 2
128
- raise Error::ParseError("PROJCS must contain either 0 or 2 AXIS parameters")
128
+ raise Error::ParseError, "PROJCS must contain either 0 or 2 AXIS parameters"
129
129
  end
130
130
  obj = ProjectedCoordinateSystem.create(name, geographic_coordinate_system, projection, linear_unit, axes[0], axes[1], *args.create_optionals)
131
131
  else
@@ -9,9 +9,6 @@
9
9
  require_relative "coord_sys/cs/factories"
10
10
  require_relative "coord_sys/cs/entities"
11
11
  require_relative "coord_sys/cs/wkt_parser"
12
- require_relative "coord_sys/srs_database/entry"
13
- require_relative "coord_sys/srs_database/url_reader"
14
- require_relative "coord_sys/srs_database/sr_org"
15
12
 
16
13
  module RGeo
17
14
  # This module provides data structures and tools related to coordinate
@@ -26,14 +23,6 @@ module RGeo
26
23
  # This includes classes for representing ellipsoids, datums, coordinate
27
24
  # systems, and other related concepts, as well as a parser for the WKT
28
25
  # format for specifying coordinate systems.
29
- #
30
- # The RGeo::CoordSys::SRSDatabase module contains tools for accessing
31
- # spatial reference databases, from which you can look up coordinate
32
- # system specifications. You can access the <tt>spatial_ref_sys</tt>
33
- # table provided with OGC-compliant spatial databases such as PostGIS,
34
- # read the databases provided with the proj4 library, or access URLs
35
- # such as those provided by spatialreference.org.
36
-
37
26
  module CoordSys
38
27
  # The only valid key is :proj4
39
28
  def self.supported?(key)