rgeo 2.3.0 → 3.0.0.pre.rc.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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -0
  3. data/README.md +1 -0
  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 +251 -182
  10. data/ext/geos_c_impl/factory.h +43 -62
  11. data/ext/geos_c_impl/geometry.c +56 -24
  12. data/ext/geos_c_impl/geometry.h +8 -3
  13. data/ext/geos_c_impl/geometry_collection.c +41 -148
  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 +15 -51
  23. data/ext/geos_c_impl/polygon.h +1 -3
  24. data/ext/geos_c_impl/preface.h +8 -0
  25. data/lib/rgeo/cartesian/analysis.rb +2 -2
  26. data/lib/rgeo/cartesian/calculations.rb +54 -17
  27. data/lib/rgeo/cartesian/factory.rb +0 -7
  28. data/lib/rgeo/cartesian/feature_classes.rb +66 -46
  29. data/lib/rgeo/cartesian/feature_methods.rb +56 -20
  30. data/lib/rgeo/cartesian/interface.rb +0 -6
  31. data/lib/rgeo/cartesian/planar_graph.rb +379 -0
  32. data/lib/rgeo/cartesian/sweepline_intersector.rb +149 -0
  33. data/lib/rgeo/cartesian/valid_op.rb +71 -0
  34. data/lib/rgeo/cartesian.rb +3 -0
  35. data/lib/rgeo/coord_sys/cs/wkt_parser.rb +6 -6
  36. data/lib/rgeo/error.rb +15 -0
  37. data/lib/rgeo/feature/curve.rb +12 -2
  38. data/lib/rgeo/feature/geometry.rb +38 -28
  39. data/lib/rgeo/feature/geometry_collection.rb +13 -5
  40. data/lib/rgeo/feature/line_string.rb +3 -3
  41. data/lib/rgeo/feature/multi_curve.rb +6 -1
  42. data/lib/rgeo/feature/multi_surface.rb +3 -3
  43. data/lib/rgeo/feature/point.rb +4 -4
  44. data/lib/rgeo/feature/surface.rb +3 -3
  45. data/lib/rgeo/geographic/factory.rb +0 -7
  46. data/lib/rgeo/geographic/interface.rb +4 -18
  47. data/lib/rgeo/geographic/proj4_projector.rb +0 -2
  48. data/lib/rgeo/geographic/projected_feature_classes.rb +21 -9
  49. data/lib/rgeo/geographic/projected_feature_methods.rb +63 -30
  50. data/lib/rgeo/geographic/simple_mercator_projector.rb +0 -2
  51. data/lib/rgeo/geographic/spherical_feature_classes.rb +29 -9
  52. data/lib/rgeo/geographic/spherical_feature_methods.rb +68 -2
  53. data/lib/rgeo/geos/capi_factory.rb +21 -31
  54. data/lib/rgeo/geos/capi_feature_classes.rb +64 -11
  55. data/lib/rgeo/geos/ffi_factory.rb +0 -28
  56. data/lib/rgeo/geos/ffi_feature_classes.rb +34 -10
  57. data/lib/rgeo/geos/ffi_feature_methods.rb +53 -10
  58. data/lib/rgeo/geos/interface.rb +18 -10
  59. data/lib/rgeo/geos/zm_factory.rb +0 -12
  60. data/lib/rgeo/geos/zm_feature_methods.rb +30 -5
  61. data/lib/rgeo/impl_helper/basic_geometry_collection_methods.rb +18 -8
  62. data/lib/rgeo/impl_helper/basic_geometry_methods.rb +1 -1
  63. data/lib/rgeo/impl_helper/basic_line_string_methods.rb +37 -26
  64. data/lib/rgeo/impl_helper/basic_point_methods.rb +13 -3
  65. data/lib/rgeo/impl_helper/basic_polygon_methods.rb +8 -3
  66. data/lib/rgeo/impl_helper/valid_op.rb +354 -0
  67. data/lib/rgeo/impl_helper/validity_check.rb +138 -0
  68. data/lib/rgeo/impl_helper.rb +1 -0
  69. data/lib/rgeo/version.rb +1 -1
  70. data/lib/rgeo/wkrep/wkb_generator.rb +1 -1
  71. data/lib/rgeo/wkrep/wkt_generator.rb +6 -6
  72. metadata +30 -7
@@ -42,7 +42,6 @@ module RGeo
42
42
  end
43
43
  srid ||= @coord_sys.authority_code if @coord_sys
44
44
  @srid = srid.to_i
45
- @lenient_assertions = opts[:uses_lenient_assertions] ? true : false
46
45
  @buffer_resolution = opts[:buffer_resolution].to_i
47
46
  @buffer_resolution = 1 if @buffer_resolution < 1
48
47
 
@@ -103,7 +102,6 @@ module RGeo
103
102
  "wkbg" => @wkb_generator.properties,
104
103
  "wktp" => @wkt_parser.properties,
105
104
  "wkbp" => @wkb_parser.properties,
106
- "lena" => @lenient_assertions,
107
105
  "bufr" => @buffer_resolution
108
106
  }
109
107
  hash_["proj4"] = @proj4.marshal_dump if @proj4
@@ -131,7 +129,6 @@ module RGeo
131
129
  wkb_generator: symbolize_hash(data["wkbg"]),
132
130
  wkt_parser: symbolize_hash(data["wktp"]),
133
131
  wkb_parser: symbolize_hash(data["wkbp"]),
134
- uses_lenient_assertions: data["lena"],
135
132
  buffer_resolution: data["bufr"],
136
133
  proj4: proj4,
137
134
  coord_sys: coord_sys
@@ -144,7 +141,6 @@ module RGeo
144
141
  coder["has_z_coordinate"] = @has_z
145
142
  coder["has_m_coordinate"] = @has_m
146
143
  coder["srid"] = @srid
147
- coder["lenient_assertions"] = @lenient_assertions
148
144
  coder["buffer_resolution"] = @buffer_resolution
149
145
  coder["wkt_generator"] = @wkt_generator.properties
150
146
  coder["wkb_generator"] = @wkb_generator.properties
@@ -180,7 +176,6 @@ module RGeo
180
176
  wkb_generator: symbolize_hash(coder["wkb_generator"]),
181
177
  wkt_parser: symbolize_hash(coder["wkt_parser"]),
182
178
  wkb_parser: symbolize_hash(coder["wkb_parser"]),
183
- uses_lenient_assertions: coder["lenient_assertions"],
184
179
  buffer_resolution: coder["buffer_resolution"],
185
180
  proj4: proj4,
186
181
  coord_sys: coord_sys
@@ -199,8 +194,6 @@ module RGeo
199
194
  @has_z
200
195
  when :has_m_coordinate
201
196
  @has_m
202
- when :uses_lenient_assertions
203
- @lenient_assertions
204
197
  when :buffer_resolution
205
198
  @buffer_resolution
206
199
  when :is_cartesian
@@ -9,76 +9,96 @@
9
9
  module RGeo
10
10
  module Cartesian
11
11
  class PointImpl # :nodoc:
12
- include RGeo::Feature::Point
13
- include RGeo::ImplHelper::BasicGeometryMethods
14
- include RGeo::ImplHelper::BasicPointMethods
15
- include RGeo::Cartesian::GeometryMethods
16
- include RGeo::Cartesian::PointMethods
12
+ include Feature::Point
13
+ include ImplHelper::ValidityCheck
14
+ include ImplHelper::BasicGeometryMethods
15
+ include ImplHelper::BasicPointMethods
16
+ include ImplHelper::ValidOp
17
+ include GeometryMethods
18
+ include PointMethods
17
19
  end
18
20
 
19
21
  class LineStringImpl # :nodoc:
20
- include RGeo::Feature::LineString
21
- include RGeo::ImplHelper::BasicGeometryMethods
22
- include RGeo::ImplHelper::BasicLineStringMethods
23
- include RGeo::Cartesian::GeometryMethods
24
- include RGeo::Cartesian::LineStringMethods
22
+ include Feature::LineString
23
+ include ImplHelper::ValidityCheck
24
+ include ImplHelper::BasicGeometryMethods
25
+ include ImplHelper::BasicLineStringMethods
26
+ include ImplHelper::ValidOp
27
+ include GeometryMethods
28
+ include LineStringMethods
25
29
  end
26
30
 
27
31
  class LineImpl # :nodoc:
28
- include RGeo::Feature::Line
29
- include RGeo::ImplHelper::BasicGeometryMethods
30
- include RGeo::ImplHelper::BasicLineStringMethods
31
- include RGeo::ImplHelper::BasicLineMethods
32
- include RGeo::Cartesian::GeometryMethods
33
- include RGeo::Cartesian::LineStringMethods
32
+ include Feature::Line
33
+ include ImplHelper::ValidityCheck
34
+ include ImplHelper::BasicGeometryMethods
35
+ include ImplHelper::BasicLineStringMethods
36
+ include ImplHelper::BasicLineMethods
37
+ include ImplHelper::ValidOp
38
+ include GeometryMethods
39
+ include LineStringMethods
34
40
  end
35
41
 
36
42
  class LinearRingImpl # :nodoc:
37
- include RGeo::Feature::LinearRing
38
- include RGeo::ImplHelper::BasicGeometryMethods
39
- include RGeo::ImplHelper::BasicLineStringMethods
40
- include RGeo::ImplHelper::BasicLinearRingMethods
41
- include RGeo::Cartesian::GeometryMethods
42
- include RGeo::Cartesian::LineStringMethods
43
+ include Feature::LinearRing
44
+ include ImplHelper::ValidityCheck
45
+ include ImplHelper::BasicGeometryMethods
46
+ include ImplHelper::BasicLineStringMethods
47
+ include ImplHelper::BasicLinearRingMethods
48
+ include ImplHelper::ValidOp
49
+ include GeometryMethods
50
+ include LineStringMethods
43
51
  end
44
52
 
45
53
  class PolygonImpl # :nodoc:
46
- include RGeo::Feature::Polygon
47
- include RGeo::ImplHelper::BasicGeometryMethods
48
- include RGeo::ImplHelper::BasicPolygonMethods
49
- include RGeo::Cartesian::GeometryMethods
54
+ include Feature::Polygon
55
+ include ImplHelper::ValidityCheck
56
+ include ImplHelper::BasicGeometryMethods
57
+ include ImplHelper::BasicPolygonMethods
58
+ include ValidOp
59
+ include GeometryMethods
50
60
  end
51
61
 
52
62
  class GeometryCollectionImpl # :nodoc:
53
- include RGeo::Feature::GeometryCollection
54
- include RGeo::ImplHelper::BasicGeometryMethods
55
- include RGeo::ImplHelper::BasicGeometryCollectionMethods
56
- include RGeo::Cartesian::GeometryMethods
63
+ include Feature::GeometryCollection
64
+ include ImplHelper::ValidityCheck
65
+ include ImplHelper::BasicGeometryMethods
66
+ include ImplHelper::BasicGeometryCollectionMethods
67
+ include ImplHelper::ValidOp
68
+ include GeometryMethods
57
69
  end
58
70
 
59
71
  class MultiPointImpl # :nodoc:
60
- include RGeo::Feature::MultiPoint
61
- include RGeo::ImplHelper::BasicGeometryMethods
62
- include RGeo::ImplHelper::BasicGeometryCollectionMethods
63
- include RGeo::ImplHelper::BasicMultiPointMethods
64
- include RGeo::Cartesian::GeometryMethods
72
+ include Feature::MultiPoint
73
+ include ImplHelper::ValidityCheck
74
+ include ImplHelper::BasicGeometryMethods
75
+ include ImplHelper::BasicGeometryCollectionMethods
76
+ include ImplHelper::BasicMultiPointMethods
77
+ include ImplHelper::ValidOp
78
+ include GeometryMethods
65
79
  end
66
80
 
67
81
  class MultiLineStringImpl # :nodoc:
68
- include RGeo::Feature::MultiLineString
69
- include RGeo::ImplHelper::BasicGeometryMethods
70
- include RGeo::ImplHelper::BasicGeometryCollectionMethods
71
- include RGeo::ImplHelper::BasicMultiLineStringMethods
72
- include RGeo::Cartesian::GeometryMethods
73
- include RGeo::Cartesian::MultiLineStringMethods
82
+ include Feature::MultiLineString
83
+ include ImplHelper::ValidityCheck
84
+ include ImplHelper::BasicGeometryMethods
85
+ include ImplHelper::BasicGeometryCollectionMethods
86
+ include ImplHelper::BasicMultiLineStringMethods
87
+ include ImplHelper::ValidOp
88
+ include GeometryMethods
89
+ include MultiLineStringMethods
74
90
  end
75
91
 
76
92
  class MultiPolygonImpl # :nodoc:
77
- include RGeo::Feature::MultiPolygon
78
- include RGeo::ImplHelper::BasicGeometryMethods
79
- include RGeo::ImplHelper::BasicGeometryCollectionMethods
80
- include RGeo::ImplHelper::BasicMultiPolygonMethods
81
- include RGeo::Cartesian::GeometryMethods
93
+ include Feature::MultiPolygon
94
+ include ImplHelper::ValidityCheck
95
+ include ImplHelper::BasicGeometryMethods
96
+ include ImplHelper::BasicGeometryCollectionMethods
97
+ include ImplHelper::BasicMultiPolygonMethods
98
+ include ImplHelper::ValidOp
99
+ include GeometryMethods
82
100
  end
101
+
102
+ ImplHelper::ValidityCheck.override_classes
83
103
  end
84
104
  end
@@ -16,6 +16,12 @@ module RGeo
16
16
  def envelope
17
17
  BoundingBox.new(factory).add(self).to_geometry
18
18
  end
19
+
20
+ private
21
+
22
+ def graph
23
+ @graph ||= GeometryGraph.new(self)
24
+ end
19
25
  end
20
26
 
21
27
  module PointMethods # :nodoc:
@@ -49,32 +55,62 @@ module RGeo
49
55
  end
50
56
  end
51
57
 
58
+ def simple?
59
+ # Use a SweeplineIntersector to determine if there are any self-intersections
60
+ # in the ring. The GeometryGraph of the ring could be used by comparing the
61
+ # edges to number of segments (graph.incident_edges.length == segments.length),
62
+ # but this adds computational and memory overhead if graph isn't already memoized.
63
+ # Since graph is not used elsewhere in LineStringMethods, we will just use the
64
+ # SweeplineIntersector for now.
65
+ li = SweeplineIntersector.new(segments)
66
+ li.proper_intersections.empty?
67
+ end
68
+
52
69
  def is_simple?
53
- len = segments.length
54
- return false if segments.any?(&:degenerate?)
55
- return true if len == 1
56
- return segments[0].s != segments[1].e if len == 2
57
- segments.each_with_index do |seg, index|
58
- nindex = index + 1
59
- nindex = nil if nindex == len
60
- return false if nindex && seg.contains_point?(segments[nindex].e)
61
- pindex = index - 1
62
- pindex = nil if pindex < 0
63
- return false if pindex && seg.contains_point?(segments[pindex].s)
64
- next unless nindex
65
- oindex = nindex + 1
66
- while oindex < len
67
- oseg = segments[oindex]
68
- return false if !(index == 0 && oindex == len - 1 && seg.s == oseg.e) && seg.intersects_segment?(oseg)
69
- oindex += 1
70
- end
71
- end
72
- true
70
+ warn "The is_simple? method is deprecated, please use the simple? counterpart, will be removed in v3" unless ENV["RGEO_SILENCE_DEPRECATION"]
71
+ simple?
73
72
  end
74
73
 
75
74
  def length
76
75
  segments.inject(0.0) { |sum, seg| sum + seg.length }
77
76
  end
77
+
78
+ def crosses?(rhs)
79
+ case rhs
80
+ when Feature::LineString
81
+ crosses_line_string?(rhs)
82
+ else
83
+ super
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ # Determines if a cross occurs with another linestring.
90
+ # Process is to get the number of proper intersections in each geom
91
+ # then overlay and get the number of proper intersections from that.
92
+ # If the overlaid number is higher than the sum of individual self-ints
93
+ # then there is an intersection. Finally, we need to check the intersection
94
+ # to see that it is not a boundary point of either segment.
95
+ #
96
+ # @param rhs [Feature::LineString]
97
+ #
98
+ # @return [Boolean]
99
+ def crosses_line_string?(rhs)
100
+ self_ints = SweeplineIntersector.new(segments).proper_intersections
101
+ self_ints += SweeplineIntersector.new(rhs.segments).proper_intersections
102
+ overlay_ints = SweeplineIntersector.new(segments + rhs.segments).proper_intersections
103
+
104
+ (overlay_ints - self_ints).each do |int|
105
+ s1s = int.s1.s
106
+ s1e = int.s1.e
107
+ s2s = int.s2.s
108
+ s2e = int.s2.e
109
+ return true unless [s1s, s1e, s2s, s2e].include?(int.point)
110
+ end
111
+
112
+ false
113
+ end
78
114
  end
79
115
 
80
116
  module MultiLineStringMethods # :nodoc:
@@ -78,12 +78,6 @@ module RGeo
78
78
  # Support a Z coordinate. Default is false.
79
79
  # [<tt>:has_m_coordinate</tt>]
80
80
  # Support an M coordinate. Default is false.
81
- # [<tt>:uses_lenient_assertions</tt>]
82
- # If set to true, assertion checking is disabled. This includes
83
- # simplicity checking on LinearRing, and validity checks on
84
- # Polygon and MultiPolygon. This may speed up creation of certain
85
- # objects, at the expense of not doing the proper checking for
86
- # OGC compliance. Default is false.
87
81
  # [<tt>:wkt_parser</tt>]
88
82
  # Configure the parser for WKT. The value is a hash of
89
83
  # configuration parameters for WKRep::WKTParser.new. Default is
@@ -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