rgeo 2.3.1 → 3.0.0.pre.rc.2

Sign up to get free protection for your applications and to get access to all the features.
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)