winding-polygon 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 652a735fde03e3521d55b52c5179f68d04e5c921
4
- data.tar.gz: ed02311938c4259e15ccd582663d23864aa839ad
3
+ metadata.gz: 4d430cfe91bb9a92623cb8f79b9ad4b7592b5db5
4
+ data.tar.gz: 1bcfa936807da46199b6c7a71b1faa0c64e057c8
5
5
  SHA512:
6
- metadata.gz: 53111234ed9f5682ef74d82a6154db8617ef819aae60a395905b0a218046a3d6c3481803d79a332b081c41fdc1d9ff8809808cbd3e398b5434639d1d8ecbcfef
7
- data.tar.gz: 810e98f60f1aa1d96d34425d0ad87e91cf637dad88543c5e5aed69364b5b94e3c08872167ffcd49be94440bf4dc9b9b730bd94461d723992e96830f4f55ec36d
6
+ metadata.gz: fe5ee6fb05e9f98dd6cf4b2785043d91de374460f66cf35de8b8911e1b541c2f56cbb8b0fc681f533dbcb617304879b53434984bd0d2412411fb93c5d954506d
7
+ data.tar.gz: 06e207c51824273756dc7504826b0e2fb513805b2c4c24d5e398b22ab6376cd08080bae5763837489c2793c7d594aa8d57a87ae4b2ba23347387fc02d7b3fbd4
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ /.idea
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format specdoc
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in winding-polygon.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 TODO: Write your name
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,2 @@
1
+ gem.winding-polygon
2
+ ===================
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,15 @@
1
+ require "winding-polygon/version"
2
+
3
+ module WindingPolygon
4
+
5
+ def self.decompose(input_polygon, output_polygons)
6
+ output_polygons = Array.new if output_polygons.nil?
7
+ result = input_polygon.decompose
8
+ if result.instance_of?(Array) && result.size==1 && !output_polygons.include?(input_polygon)
9
+ output_polygons.concat(result)
10
+ else
11
+ decompose(result[0],output_polygons )
12
+ decompose(result[1],output_polygons )
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,287 @@
1
+ class AVLTree
2
+ attr_accessor :root
3
+ def initialize(array = [])
4
+ #create root
5
+ @root = nil
6
+ array.each do |n|
7
+ insert n
8
+ end
9
+ end
10
+
11
+ def insert(v)
12
+ i_node = create_node(v)
13
+
14
+ prev_node = nil
15
+ curr_node = @root
16
+ while !curr_node.nil?
17
+ prev_node = curr_node
18
+ if i_node.value < curr_node.value
19
+ curr_node = curr_node.left
20
+ else
21
+ curr_node = curr_node.right
22
+ end
23
+ end
24
+
25
+ if prev_node.nil?
26
+ @root = i_node
27
+ else
28
+ i_node.parent = prev_node
29
+ if i_node.value < prev_node.value
30
+ prev_node.left = i_node
31
+ else
32
+ prev_node.right = i_node
33
+ end
34
+ end
35
+ i_node.update_height
36
+ @root = i_node.balance
37
+ i_node
38
+ end
39
+
40
+ def search(v)
41
+ search_node(@root, v)
42
+ end
43
+
44
+ def delete(v)
45
+ d = search(v)
46
+ return nil if d.nil?
47
+
48
+ if d.left == nil or d.right == nil
49
+ n = d
50
+ else #both children exist
51
+ n = d.next
52
+ end
53
+
54
+ if !n.left.nil?
55
+ b = n.left
56
+ else
57
+ b = n.right
58
+ end
59
+
60
+ b.parent = n.parent unless b.nil?
61
+
62
+ if n.parent.nil?
63
+ @root = b
64
+ elsif n == n.parent.left
65
+ n.parent.left = b
66
+ else
67
+ n.parent.right = b
68
+ end
69
+
70
+ d.value = n.value unless n == d
71
+
72
+ if b.nil?
73
+ n.parent.update_height if !n.parent.nil?
74
+ @root = n.balance
75
+ else
76
+ b.update_height
77
+ @root = b.balance
78
+ end
79
+ n
80
+ end
81
+
82
+ def print
83
+ result = sort
84
+ puts result.inspect
85
+ end
86
+
87
+ def sort
88
+ result = Array.new
89
+ sort_node(@root, result )
90
+ result
91
+ end
92
+
93
+ def min
94
+ @root.min
95
+ end
96
+
97
+ def max
98
+ @root.max
99
+ end
100
+
101
+
102
+
103
+ private
104
+ def create_node(v = nil)
105
+ n = AVLNode.new(:value => v)
106
+ end
107
+
108
+ def sort_node(n, array)
109
+ if !n.nil?
110
+ sort_node(n.left, array)
111
+ array.push(n.value)
112
+ sort_node(n.right, array)
113
+ end
114
+ end
115
+
116
+ def search_node(n, v)
117
+ if !n.nil?
118
+ if n.value == v
119
+ return n
120
+ elsif v < n.value
121
+ search_node(n.left,v)
122
+ else
123
+ search_node(n.right,v)
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ class AVLNode
130
+ BAL_H = 1
131
+ attr_accessor :height, :left, :right, :value, :parent
132
+ def initialize args
133
+ @height = 0
134
+ @left = nil
135
+ @right = nil
136
+ @value = nil
137
+ @parent = nil
138
+ args.each do |k,v|
139
+ instance_variable_set("@#{k}", v) unless v.nil?
140
+ end
141
+ end
142
+
143
+ def balance
144
+ rotate if difference.abs > BAL_H
145
+ return self if @parent.nil?
146
+ @parent.balance
147
+ end
148
+
149
+ def update_height
150
+ l_height = @left.nil? ? 0 : @left.height
151
+ r_height = @right.nil? ? 0 : @right.height
152
+ @height = ((l_height > r_height) ? l_height : r_height) + 1
153
+ @parent.update_height unless @parent.nil?
154
+ end
155
+
156
+ def rotate
157
+ if difference >= BAL_H
158
+ #check if children should rotate too
159
+ @right.rotate if @right.difference <= -BAL_H
160
+ rotate_left
161
+ elsif difference <= - BAL_H
162
+ #check if children should rotate too
163
+ @left.rotate if @left.difference >= BAL_H
164
+ rotate_right
165
+ end
166
+ end
167
+
168
+ def rotate_left
169
+ #the old right is now the root
170
+ root = @right
171
+ root.parent = @parent
172
+ #update the parent to point to the new root
173
+ if not @parent.nil?
174
+ if @parent.right == self
175
+ @parent.right = root
176
+ else
177
+ @parent.left = root
178
+ end
179
+ end
180
+
181
+ #this node's right is now the root's left
182
+ @right = root.left
183
+ root.left.parent = self unless root.left.nil?
184
+
185
+ #the root is now the parent of this node
186
+ @parent = root
187
+ #this node is now the root's left
188
+ root.left = self
189
+ root.left.update_height
190
+ root
191
+ end
192
+
193
+ def rotate_right
194
+ root = @left
195
+ root.parent = @parent
196
+ #update the parent to point to the new root
197
+ if not @parent.nil?
198
+ if @parent.right == self
199
+ @parent.right = root
200
+ else
201
+ @parent.left = root
202
+ end
203
+ end
204
+
205
+ @left = root.right
206
+ root.right.parent = self unless root.right.nil?
207
+
208
+ @parent = root
209
+ root.right = self
210
+ root.right.update_height
211
+ root
212
+ end
213
+
214
+ def difference
215
+ r_height = @right.nil? ? 0 : @right.height
216
+ l_height = @left.nil? ? 0 : @left.height
217
+ r_height - l_height
218
+ end
219
+
220
+ def next
221
+ if not @right.nil?
222
+ @right.min
223
+ else
224
+ curr_node = self
225
+ p_node = @parent
226
+ while p_node != nil && curr_node == p_node.right
227
+ curr_node = p_node
228
+ p_node = p_node.parent
229
+ end
230
+ p_node
231
+ end
232
+ end
233
+
234
+ def prev
235
+ if not @left.nil?
236
+ @left.max
237
+ else
238
+ curr_node = self
239
+ p_node = @parent
240
+ while p_node != nil && curr_node == p_node.left
241
+ curr_node = p_node
242
+ p_node = p_node.parent
243
+ end
244
+ p_node
245
+ end
246
+ end
247
+
248
+
249
+ def min
250
+ node = self
251
+ while !node.left.nil?
252
+ node = node.left
253
+ end
254
+ node
255
+ end
256
+
257
+ def max
258
+ node = self
259
+ while !node.right.nil?
260
+ node = node.right
261
+ end
262
+ node
263
+ end
264
+
265
+ def print_node
266
+ out_s = "\t\t\t\t\t\n"
267
+ out_s += "\t\t#{@parent}\t\t\n"
268
+ out_s += "#{@height}\t\t|#{@value}|\t\t\n"
269
+ out_s += "\t#{@left}\t\t#{@right}\t\n"
270
+ out_s += "\t\t\t\t\t\n"
271
+ puts out_s
272
+ end
273
+
274
+ def print
275
+ out_s = "#{@value} - "
276
+ instance_variables.each do |i|
277
+ out_s += "(#{i}: #{instance_variable_get(i.to_sym)})"
278
+ end
279
+ puts out_s
280
+ end
281
+
282
+ def to_s
283
+ @value.to_s
284
+ end
285
+
286
+ end
287
+
@@ -0,0 +1,34 @@
1
+ class EventQueue
2
+ attr_reader :number_of_events
3
+ attr_reader :events
4
+ attr_reader :individual_vertices
5
+
6
+ def initialize(polygon)
7
+ #last vertex in geojson is equal to first vertex
8
+ @individual_vertices = polygon.vertices.length - 1
9
+ #2 per edge - last event looping back to 0 is handled by +1 below
10
+ @number_of_events = 2 * (@individual_vertices)
11
+ @events = []
12
+
13
+ #build up 2 'events' per edge. One for left vertex, one for right.
14
+ for i in 0..@individual_vertices-1
15
+ a = 2*i
16
+ b = 2*i+1
17
+ @events[a] = {:edge=>i}
18
+ @events[b] = {:edge=>i}
19
+ @events[a][:vertex] = polygon.vertices[i]
20
+ @events[b][:vertex] = polygon.vertices[i+1]
21
+ if @events[a][:vertex].compare(@events[b][:vertex]) < 0
22
+ @events[a][:type] = 'left'
23
+ @events[b][:type] = 'right'
24
+ else
25
+ @events[a][:type] = 'right'
26
+ @events[b][:type] = 'left'
27
+ end
28
+ end
29
+
30
+ # sort events lexicographically
31
+ @events.sort!{|a,b| a[:vertex].compare(b[:vertex])}
32
+ end
33
+
34
+ end
@@ -0,0 +1,40 @@
1
+ class Point
2
+
3
+ attr_reader :x
4
+ attr_reader :y
5
+
6
+ def initialize(x,y)
7
+ @x=x
8
+ @y=y
9
+ end
10
+
11
+ # Determines the xy lexicographical order of two points
12
+ def compare other_point
13
+ raise Exception.new("Self is x=#{@x},y=#{@y}, the other_point is nil") if other_point.nil?
14
+ # x-coord first
15
+ return 1 if @x > other_point.x
16
+ return -1 if @x < other_point.x
17
+
18
+ # y-coord second
19
+ return 1 if @y > other_point.y
20
+ return -1 if @y < other_point.y
21
+
22
+ # they are the same point
23
+ return 0
24
+ end
25
+
26
+ def == (other_point)
27
+ @x==other_point.x && @y==other_point.y
28
+ end
29
+
30
+ # tests if point is Left|On|Right of the line P0 to P1.
31
+ #
32
+ # returns:
33
+ # >0 for left of the line
34
+ # 0 for on the line
35
+ # <0 for right of the line
36
+ def is_left p0, p1
37
+ return (p1.x - p0.x) * (@y - p0.y) - (@x - p0.x) * (p1.y - p0.y)
38
+ end
39
+
40
+ end
@@ -0,0 +1,114 @@
1
+ class Polygon
2
+ attr_reader :vertices
3
+ attr_reader :intersection_points
4
+
5
+ def initialize(points)
6
+ @vertices = points
7
+ @intersection_points = Array.new
8
+ end
9
+
10
+ def is_simple
11
+ event_queue = EventQueue.new(self)
12
+ sweep_line = SweepLine.new(self)
13
+
14
+ # This loop processes all events in the sorted queue
15
+ # Events are only left or right vertices
16
+ e = event_queue.events.shift
17
+ while !e.nil? do
18
+
19
+ if e[:type] == 'left'
20
+ s = sweep_line.add(e)
21
+
22
+ return false if !sweep_line.intersect(s, s.above).nil?
23
+ return false if !sweep_line.intersect(s, s.below).nil?
24
+ else
25
+ s = sweep_line.find(e)
26
+ return false if !sweep_line.intersect(s.above, s.below).nil?
27
+ sweep_line.remove(s)
28
+ end
29
+
30
+ e = event_queue.events.shift
31
+ end
32
+
33
+ return true
34
+ end
35
+
36
+ def get_intersection_points
37
+ event_queue = EventQueue.new(self)
38
+ sweep_line = SweepLine.new(self)
39
+
40
+ # This loop processes all events in the sorted queue
41
+ # Events are only left or right vertices
42
+ e = event_queue.events.shift
43
+ while !e.nil? do
44
+
45
+ if e[:type] == 'left'
46
+ s = sweep_line.add(e)
47
+
48
+ add_to_intersection_point_collection(sweep_line.intersect(s, s.above))
49
+ add_to_intersection_point_collection(sweep_line.intersect(s, s.below))
50
+
51
+ else
52
+ s = sweep_line.find(e)
53
+ add_to_intersection_point_collection(sweep_line.intersect(s.above, s.below))
54
+ sweep_line.remove(s)
55
+ end
56
+
57
+ e = event_queue.events.shift
58
+ end
59
+
60
+ return nil if intersection_points.size==0
61
+ @intersection_points
62
+ end
63
+
64
+ def add_to_intersection_point_collection(point)
65
+ @intersection_points << point if !point.nil? && !@intersection_points.any?{|p| p.compare(point)==0}
66
+ end
67
+
68
+ def get_first_intersection_point_hash
69
+ event_queue = EventQueue.new(self)
70
+ sweep_line = SweepLine.new(self)
71
+
72
+ # This loop processes all events in the sorted queue
73
+ # Events are only left or right vertices
74
+ e = event_queue.events.shift
75
+ while !e.nil? do
76
+ if e[:type] == 'left'
77
+ s = sweep_line.add(e)
78
+
79
+ point = sweep_line.intersect(s, s.above)
80
+ return point_hash_with_edge_info(point, s, s.above) if !point.nil?
81
+
82
+ point = sweep_line.intersect(s, s.below)
83
+ return point_hash_with_edge_info(point, s, s.below) if !point.nil?
84
+ else
85
+ s = sweep_line.find(e)
86
+ point = sweep_line.intersect(s.above, s.below)
87
+ return point_hash_with_edge_info(point, s.above, s.below) if !point.nil?
88
+ sweep_line.remove(s)
89
+ end
90
+ e = event_queue.events.shift
91
+ end
92
+ return nil
93
+ end
94
+
95
+ def decompose
96
+ intersection_point = get_first_intersection_point_hash
97
+ return [self] if intersection_point.nil?
98
+ first_polygon = Polygon.new((@vertices[0..intersection_point[:edge1]]<<intersection_point[:point]).concat( @vertices[intersection_point[:edge2]+1,@vertices.length-intersection_point[:edge2]]))
99
+ second_polygon = Polygon.new(@vertices[intersection_point[:edge1]+1..intersection_point[:edge2]].insert(0,intersection_point[:point])<<intersection_point[:point])
100
+ return [first_polygon,second_polygon]
101
+ end
102
+
103
+ def point_hash_with_edge_info(point, s1, s2)
104
+ edges=[s1.edge, s2.edge].sort!
105
+ {:point => point, :edge1 => edges[0], :edge2 => edges[1]}
106
+ end
107
+
108
+ def ==(other_polygon)
109
+ for i in 0..@vertices.size-1
110
+ return false if @vertices[i] != other_polygon.vertices[i]
111
+ end
112
+ return true
113
+ end
114
+ end
@@ -0,0 +1,48 @@
1
+ #A container class for segments (or edges) of the polygon to test
2
+ #Allows storage and retrieval from the Balanced Binary Tree
3
+ class Segment
4
+ attr_reader :edge
5
+ attr_accessor :left_point
6
+ attr_accessor :right_point
7
+ attr_accessor :above
8
+ attr_accessor :below
9
+
10
+
11
+ def initialize(event)
12
+ @edge = event[:edge]
13
+ end
14
+
15
+ def <(other_segment)
16
+ return true if @edge<other_segment.edge
17
+ return false
18
+ end
19
+
20
+ def >(other_segment)
21
+ return true if @edge>other_segment.edge
22
+ return false
23
+ end
24
+
25
+ def == (other_segment)
26
+ @edge == other_segment.edge && @left_point == other_segment.left_point && @right_point == other_segment.right_point
27
+ end
28
+
29
+ def to_s
30
+ return "edge:#{@edge.to_s}"
31
+ end
32
+
33
+ def intersection_point_with(other_segment)
34
+ numerator = (other_segment.left_point.y - @left_point.y) * (other_segment.left_point.x - other_segment.right_point.x) -
35
+ (other_segment.left_point.y - other_segment.right_point.y) * (other_segment.left_point.x - @left_point.x)
36
+
37
+ denominator = (@right_point.y - @left_point.y) * (other_segment.left_point.x - other_segment.right_point.x) -
38
+ (other_segment.left_point.y - other_segment.right_point.y) * (@right_point.x - @left_point.x)
39
+
40
+ t = numerator.to_f / denominator
41
+
42
+ x = @left_point.x + t * (@right_point.x - @left_point.x)
43
+ y = @left_point.y + t * (@right_point.y - @left_point.y)
44
+
45
+ Point.new(x, y)
46
+ end
47
+
48
+ end
@@ -0,0 +1,138 @@
1
+ class SweepLine
2
+ attr_reader :tree
3
+ attr_reader :polygon
4
+
5
+ def initialize(polygon)
6
+ @tree = AVLTree.new
7
+ @polygon = polygon
8
+ end
9
+
10
+ def add(event)
11
+ # build up segment data
12
+ seg = Segment.new(event)
13
+ p1 = @polygon.vertices[seg.edge]
14
+ p2 = @polygon.vertices[seg.edge + 1]
15
+
16
+ # if it is being added, then it must be a LEFT edge event
17
+ # but need to determine which endpoint is the left one first
18
+
19
+ if p1.compare(p2) < 0
20
+ seg.left_point = p1
21
+ seg.right_point = p2
22
+ else
23
+ seg.left_point = p2
24
+ seg.right_point = p1
25
+ end
26
+
27
+ # Add node to tree and setup linkages to "above" and "below"
28
+ # edges as per algorithm
29
+ nd = @tree.insert(seg)
30
+
31
+ nx = nd.next
32
+ np = nd.prev
33
+
34
+ if !nx.nil?
35
+ seg.above = nx.value
36
+ seg.above.below = seg
37
+ end
38
+
39
+ if !np.nil?
40
+ seg.below = np.value
41
+ seg.below.above = seg
42
+ end
43
+ return seg
44
+
45
+ end
46
+
47
+ def find(event)
48
+ # need a segment to find it in the tree
49
+ seg = Segment.new(event)
50
+ p1 = @polygon.vertices[seg.edge]
51
+ p2 = @polygon.vertices[seg.edge + 1]
52
+
53
+ # if it is being added, then it must be a LEFT edge event
54
+ # but need to determine which endpoint is the left one first
55
+ if p1.compare(p2) < 0
56
+ seg.left_point = p1
57
+ seg.right_point = p2
58
+ else
59
+ seg.left_point = p2
60
+ seg.right_point = p1
61
+ end
62
+
63
+ node = @tree.search(seg)
64
+
65
+ return nil if node.nil?
66
+
67
+ node.value
68
+ end
69
+
70
+ def remove(seg)
71
+ nd = @tree.search(seg)
72
+ return if nd.nil?
73
+
74
+ nx = nd.next
75
+ nx.value.below = seg.below if !nx.nil?
76
+
77
+ np = nd.prev
78
+ np.value.above = seg.above if !np.nil?
79
+
80
+ @tree.delete(seg)
81
+ end
82
+
83
+ def switch(s1,s2)
84
+ nd1 = @tree.search(s1)
85
+ return if nd1.nil?
86
+
87
+ nd2 = @tree.search(s2)
88
+ return if nd2.nil?
89
+
90
+ nx1 = nd1.next
91
+ nx1.value.below = nd2.value if !nx1.nil?
92
+
93
+ np1 = nd1.prev
94
+ np1.value.above = nd2.value if !np1.nil?
95
+
96
+ nx2 = nd2.next
97
+ nx2.value.below = nd1.value if !nx2.nil?
98
+
99
+ np2 = nd2.prev
100
+ np2.value.above = nd1.value if !np2.nil?
101
+
102
+ end
103
+
104
+ #test intersect of 2 segments and return: nil when none, point when intersecting
105
+ def intersect(s1,s2)
106
+ # no intersect if either segment doesn't existend
107
+ return nil if s1.nil? || s2.nil?
108
+
109
+ # check for consecutive edges in polygon
110
+ e1 = s1.edge
111
+ e2 = s2.edge
112
+
113
+ # no non-simple intersect since consecutive
114
+ polygon_edges = @polygon.vertices.length-1
115
+ return nil if (((e1+1)%polygon_edges === e2) || (e1 === (e2+1)%polygon_edges))
116
+
117
+
118
+ #test for existence of an intersect point
119
+ #s2 left point sign
120
+ lsign = s2.left_point.is_left(s1.left_point, s1.right_point)
121
+ #s2 right point sign
122
+ rsign = s2.right_point.is_left(s1.left_point, s1.right_point)
123
+
124
+ # s2 endpoints have same sign relative to s1 => on same side => no intersect is possible
125
+ return nil if (lsign * rsign > 0)
126
+
127
+ # s1 left point sign
128
+ lsign = s1.left_point.is_left(s2.left_point, s2.right_point)
129
+ #s1 right point sign
130
+ rsign = s1.right_point.is_left(s2.left_point, s2.right_point)
131
+
132
+ # s1 endpoints have same sign relative to s2 => on same side => no intersect is possible
133
+ return nil if (lsign * rsign > 0)
134
+
135
+ #segments s1 and s2 straddle. Intersect exists.
136
+ s1.intersection_point_with(s2)
137
+ end
138
+ end
@@ -0,0 +1,3 @@
1
+ module WindingPolygon
2
+ VERSION = "0.0.3"
3
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe "EventQueue" do
4
+
5
+ it "test can create an EventQueue" do
6
+
7
+ points = JSON.parse("[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]")
8
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
9
+ event_queue = EventQueue.new(polygon)
10
+
11
+ event_queue.events.length.should == 8
12
+ event_queue.events.length.should == event_queue.number_of_events
13
+
14
+ end
15
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Point" do
4
+
5
+ it 'test less than comparator' do
6
+ p0 = Point.new(1,1)
7
+ p1 = Point.new(3,3)
8
+ p0.compare(p1).should == -1
9
+ end
10
+
11
+ it 'test more than comparator' do
12
+ p0 = Point.new(1,1)
13
+ p1 = Point.new(3,3)
14
+ p1.compare(p0).should == 1
15
+ end
16
+
17
+ it 'test equality' do
18
+ p0 = Point.new(1,1)
19
+ p0.compare(p0).should == 0
20
+ end
21
+
22
+ it 'test left of line' do
23
+ p0 = Point.new(1,1)
24
+ p1 = Point.new(3,3)
25
+ p2 = Point.new(1,3)
26
+ p2.is_left(p0,p1).should > 0
27
+ end
28
+
29
+ it 'test right of line' do
30
+ p0 = Point.new(1,1)
31
+ p1 = Point.new(3,3)
32
+ p2 = Point.new(3,1)
33
+ p2.is_left(p0,p1).should < 0
34
+ end
35
+
36
+ it 'test on of line' do
37
+ p0 = Point.new(1,1)
38
+ p1 = Point.new(3,3)
39
+ p2 = Point.new(2,2)
40
+ p2.is_left(p0,p1).should == 0
41
+ end
42
+
43
+ end
@@ -0,0 +1,189 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Polygon" do
4
+
5
+ it 'test can build a polygon from an array of points' do
6
+
7
+ points = JSON.parse("[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]")
8
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
9
+
10
+ polygon.vertices.length.should == points.length
11
+ polygon.vertices[0].x.should == points[0][0]
12
+
13
+ end
14
+
15
+ it 'test is polygon simple 1' do
16
+
17
+ points = JSON.parse("[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]")
18
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
19
+
20
+ polygon.is_simple.should == true
21
+
22
+ end
23
+
24
+ it 'test is polygon simple 2' do
25
+
26
+ points = JSON.parse("[[2.0, 2.0], [1.0, 2.0], [1.0, 1.0], [2.0, 1.0], [3.0, 1.0], [3.0, 2.0], [2.0, 2.0]]")
27
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
28
+
29
+ polygon.is_simple.should == true
30
+
31
+ end
32
+
33
+ it 'test is polygon simple 3' do
34
+
35
+ points = JSON.parse("[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]")
36
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
37
+
38
+ polygon.is_simple.should == true
39
+
40
+ end
41
+
42
+ it 'test is polygon simple 4' do
43
+
44
+ points = JSON.parse("[[0.0, 0.0], [0.0, 3.0], [3.0, 3.0], [3.0, 0.0], [0.0, 0.0]]")
45
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
46
+
47
+ polygon.is_simple.should == true
48
+
49
+ end
50
+
51
+ it 'test is polygon simple 5' do
52
+
53
+ points = JSON.parse("[[2.0, 2.0], [2.0, 3.0], [3.0, 3.0], [4.0, 3.0], [4.0, 2.0], [2.0, 2.0]]")
54
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
55
+
56
+ polygon.is_simple.should == true
57
+
58
+ end
59
+
60
+ it 'test is practical polygon simple 6 (clockwise)' do
61
+
62
+ points = JSON.parse("[[-110.84096146619905, 32.206464264983815],[-110.8254844632264, 32.206404507969239],[-110.82564474991523, 32.2065185890533],[-110.8278203157768, 32.211149692139429],[-110.82920840010047, 32.217701622983441],[110.82990196985523,32.220975019031748],[-110.84096004697494,32.221011891029775],[-110.84096146619905,32.206464264983815]]")
63
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
64
+
65
+ polygon.is_simple.should == true
66
+
67
+ end
68
+
69
+ it 'test is practical polygon simple 7 (counter clockwise)' do
70
+
71
+ points = JSON.parse("[[-118.845766, 34.013743],[-118.384777,34.013743],[-118.384777, 34.335579],[-118.845766, 34.335579],[-118.845766, 34.013743]]")
72
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
73
+
74
+ polygon.is_simple.should == true
75
+
76
+ end
77
+
78
+
79
+ it 'get_intersection_point_hash test for practical polygon simple 6 (clockwise)' do
80
+
81
+ points = JSON.parse("[[-110.84096146619905, 32.206464264983815],[-110.8254844632264, 32.206404507969239],[-110.82564474991523, 32.2065185890533],[-110.8278203157768, 32.211149692139429],[-110.82920840010047, 32.217701622983441],[110.82990196985523,32.220975019031748],[-110.84096004697494,32.221011891029775],[-110.84096146619905,32.206464264983815]]")
82
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
83
+
84
+ polygon.get_first_intersection_point_hash.should be_nil
85
+
86
+ end
87
+
88
+ it 'get_intersection_point_hash test for practical polygon simple 6 (counter clockwise)' do
89
+
90
+ points = JSON.parse("[[-118.845766, 34.013743],[-118.384777,34.013743],[-118.384777, 34.335579],[-118.845766, 34.335579],[-118.845766, 34.013743]]")
91
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
92
+
93
+ polygon.get_first_intersection_point_hash.should be_nil
94
+
95
+ end
96
+
97
+ it 'test is practical polygon simple 7 (counter clockwise)' do
98
+
99
+ points = JSON.parse("[[-118.845766, 34.013743],[-118.384777,34.013743],[-118.384777, 34.335579],[-118.845766, 34.335579],[-118.845766, 34.013743]]")
100
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
101
+
102
+ polygon.is_simple.should == true
103
+
104
+ end
105
+
106
+
107
+ it 'test is complex polygon 1' do
108
+
109
+ points = JSON.parse("[[100.0, 0.0],[101.0, 1.0],[100.0, 1.0],[101.0, 0.0], [100.0, 0.0]]")
110
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
111
+
112
+ polygon.is_simple.should == false
113
+
114
+ end
115
+
116
+ it 'test is complex polygon 2' do
117
+
118
+ points = JSON.parse("[[2.0, 2.0], [3.0, 2.0], [3.0, 3.0], [4.0, 2.0], [2.0, 2.0]]")
119
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
120
+
121
+ polygon.is_simple.should == false
122
+
123
+ end
124
+
125
+ it 'test is complex polygon 3' do
126
+
127
+ points = JSON.parse("[[0.0, 0.0], [3.0, 3.0], [0.0, 3.0], [3.0, 0.0], [0.0, 0.0]]")
128
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
129
+
130
+ polygon.is_simple.should == false
131
+
132
+ end
133
+
134
+
135
+ it 'test is practical complex polygon 4' do
136
+
137
+ points = JSON.parse("[[-98.4609375000036,40.3051841980949],[-98.4609375000036,38.057277897745],[-96.0878906250017,40.1038062719331],[-96.6152343750031,37.9880405545487],[-98.4609375000036,40.3051841980949]]")
138
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
139
+
140
+ polygon.is_simple.should == false
141
+
142
+ end
143
+
144
+ it 'get complex polygon 3 intersection points' do
145
+
146
+ points = JSON.parse("[[0.0, 0.0], [3.0, 3.0], [0.0, 3.0], [3.0, 0.0], [0.0, 0.0]]")
147
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
148
+
149
+ polygon.get_intersection_points[0].should == Point.new(1.5,1.5)
150
+
151
+ end
152
+
153
+ it 'get practical complex polygon 4 intersection points' do
154
+
155
+ points = JSON.parse("[[-98.4609375000036,40.3051841980949],[-98.4609375000036,38.057277897745],[-96.0878906250017,40.1038062719331],[-96.6152343750031,37.9880405545487],[-98.4609375000036,40.3051841980949]]")
156
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
157
+
158
+ polygon.get_intersection_points[0].should == Point.new(-97.39951856124108,38.97265129300353)
159
+
160
+ end
161
+
162
+ it 'get_intersection_point_hash test for practical complex polygon' do
163
+
164
+ points = JSON.parse("[[-98.4609375000036,40.3051841980949],[-98.4609375000036,38.057277897745],[-96.0878906250017,40.1038062719331],[-96.6152343750031,37.9880405545487],[-98.4609375000036,40.3051841980949]]")
165
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
166
+
167
+ polygon.get_first_intersection_point_hash.should == {:point=>Point.new(-97.39951856124108,38.97265129300353),:edge1=>1,:edge2=>3 }
168
+
169
+ end
170
+
171
+ it 'should decompose a practical complex polygon into two simple polygons' do
172
+
173
+ points = JSON.parse("[[-98.4609375000036,40.3051841980949],[-98.4609375000036,38.057277897745],[-96.0878906250017,40.1038062719331],[-96.6152343750031,37.9880405545487],[-98.4609375000036,40.3051841980949]]")
174
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
175
+
176
+ multi_polygons = polygon.decompose
177
+ multi_polygons.should_not be_nil
178
+
179
+ points1 = JSON.parse("[[-98.4609375000036,40.3051841980949],[-98.4609375000036,38.057277897745],[-97.39951856124108,38.97265129300353],[-98.4609375000036,40.3051841980949]]")
180
+ polygon1 = Polygon.new(points1.map{|item| Point.new(item[0],item[1])})
181
+ multi_polygons[0].should ==polygon1
182
+
183
+ points2 = JSON.parse("[[-97.39951856124108,38.97265129300353],[-96.0878906250017,40.1038062719331],[-96.6152343750031,37.9880405545487],[-97.39951856124108,38.97265129300353]]")
184
+ polygon2 = Polygon.new(points2.map{|item| Point.new(item[0],item[1])})
185
+ multi_polygons[1].should ==polygon2
186
+
187
+ end
188
+
189
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Segment" do
4
+
5
+ it "test_segments_intersect_at_the_endpoint" do
6
+
7
+ segment1 = Segment.new({:edge=>0})
8
+ segment1.left_point = Point.new(0,0)
9
+ segment1.right_point = Point.new(2,2)
10
+
11
+ segment2 = Segment.new({:edge=>1})
12
+ segment2.left_point = Point.new(0,2)
13
+ segment2.right_point = Point.new(2,2)
14
+
15
+ intersection_point = segment1.intersection_point_with(segment2)
16
+ intersection_point.x.should ==2
17
+ intersection_point.y.should ==2
18
+
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ $LOAD_PATH.unshift File.expand_path('../../lib/winding-polygon', __FILE__)
3
+ #Dir["../../lib/winding-polygon/*.rb"].each {|file| require file }
4
+ require 'avltree.rb'
5
+ require 'event_queue'
6
+ require 'point'
7
+ require 'polygon'
8
+ require 'segment'
9
+ require 'sweep_line'
10
+ require 'winding-polygon'
11
+ require 'json'
12
+ require "rspec"
13
+
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe "SweepLine" do
4
+
5
+ it "test can find" do
6
+ points = JSON.parse("[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]")
7
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
8
+ sweep_line = SweepLine.new(polygon)
9
+ event_queue = EventQueue.new(polygon)
10
+
11
+ event = event_queue.events.pop
12
+ while !event.nil?
13
+ sweep_line.add(event) if event[:type]=='left'
14
+ event = event_queue.events.pop
15
+ end
16
+
17
+ for i in 0..3 do
18
+ sweep_line.find({:edge=>i}).should_not be_nil
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe WindingPolygon do
4
+ it 'should have a version number' do
5
+ WindingPolygon::VERSION.should_not be_nil
6
+ end
7
+
8
+ it 'should decompose a practical complex polygon into two simple polygons' do
9
+
10
+ points = JSON.parse("[[-98.4609375000036,40.3051841980949],[-98.4609375000036,38.057277897745],[-96.0878906250017,40.1038062719331],[-96.6152343750031,37.9880405545487],[-98.4609375000036,40.3051841980949]]")
11
+ polygon = Polygon.new(points.map{|item| Point.new(item[0],item[1])})
12
+
13
+ multi_polygons = []
14
+ WindingPolygon.decompose(polygon,multi_polygons)
15
+ multi_polygons.should_not be_nil
16
+
17
+ points1 = JSON.parse("[[-98.4609375000036,40.3051841980949],[-98.4609375000036,38.057277897745],[-97.39951856124108,38.97265129300353],[-98.4609375000036,40.3051841980949]]")
18
+ polygon1 = Polygon.new(points1.map{|item| Point.new(item[0],item[1])})
19
+ multi_polygons[0].should ==polygon1
20
+
21
+ points2 = JSON.parse("[[-97.39951856124108,38.97265129300353],[-96.0878906250017,40.1038062719331],[-96.6152343750031,37.9880405545487],[-97.39951856124108,38.97265129300353]]")
22
+ polygon2 = Polygon.new(points2.map{|item| Point.new(item[0],item[1])})
23
+ multi_polygons[1].should ==polygon2
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,142 @@
1
+ require 'test/unit'
2
+ require '../lib/winding-polygon/avltree'
3
+
4
+ class TestAVLTree < Test::Unit::TestCase
5
+
6
+ def setup
7
+ #
8
+ # 2
9
+ # 1 4
10
+ # 3 5
11
+ #
12
+ @tree = AVLTree.new [1,2,5,4,3]
13
+ #
14
+ # 4
15
+ # 2 6
16
+ # 1 3 5 7
17
+ #
18
+ @rot_tree = AVLTree.new [4,2,6,3,1,5,7]
19
+ end
20
+
21
+ def test_sort
22
+ assert_equal "[1, 2, 3, 4, 5]", @tree.sort.inspect
23
+ end
24
+
25
+ def test_search
26
+ assert_not_nil @tree.search(1)
27
+ assert_not_nil @tree.search(4)
28
+ assert_nil @tree.search(0)
29
+ assert_nil @tree.search(6)
30
+ end
31
+
32
+ def test_min
33
+ assert_equal 1, @tree.min.value
34
+ assert_equal 1, @tree.search(1).min.value
35
+ assert_equal 3, @tree.search(4).min.value
36
+ end
37
+
38
+ def test_max
39
+ assert_equal 5, @tree.max.value
40
+ assert_equal 5, @tree.search(2).max.value
41
+ assert_equal 5, @tree.search(4).max.value
42
+ assert_equal 5, @tree.search(5).max.value
43
+ end
44
+
45
+ def test_next
46
+ assert_equal 2, @tree.search(1).next.value
47
+ assert_equal 3, @tree.search(2).next.value
48
+ assert_nil @tree.search(5).next
49
+ end
50
+
51
+ def test_prev
52
+ assert_nil @tree.search(1).prev
53
+ assert_equal 4, @tree.search(5).prev.value
54
+ assert_equal 3, @tree.search(4).prev.value
55
+ assert_equal 2, @tree.search(3).prev.value
56
+ end
57
+
58
+ def test_delete
59
+
60
+ @tree.delete 2
61
+ assert_equal "[1, 3, 4, 5]", @tree.sort.inspect
62
+ @tree.delete 4
63
+ assert_equal "[1, 3, 5]", @tree.sort.inspect
64
+ @tree.delete 3
65
+ assert_equal "[1, 5]", @tree.sort.inspect
66
+ @tree.delete 1
67
+ assert_equal "[5]", @tree.sort.inspect
68
+
69
+ @rot_tree.delete 1
70
+ assert_equal 4.to_s, @rot_tree.root.to_s
71
+ @rot_tree.delete 2
72
+ assert_equal 4.to_s, @rot_tree.root.to_s
73
+ @rot_tree.delete 3
74
+ #should have to rotate
75
+ assert_equal 6.to_s, @rot_tree.root.to_s
76
+ assert_equal 4.to_s, @rot_tree.root.left.to_s
77
+ assert_equal 7.to_s, @rot_tree.root.right.to_s
78
+ end
79
+
80
+ def test_rotate_left
81
+ root = @rot_tree.root
82
+ t_node = @rot_tree.search(4)
83
+ s_node = @rot_tree.search(6)
84
+
85
+ assert_equal 4.to_s, root.to_s
86
+ assert_equal t_node.to_s, root.to_s
87
+ assert_equal s_node.to_s, t_node.right.to_s
88
+
89
+ t_node.rotate_left
90
+
91
+ t_node = @rot_tree.search(4)
92
+ root = t_node.parent
93
+
94
+ #root
95
+ assert_equal nil.to_s, root.parent.to_s
96
+ assert_equal 7.to_s, root.right.to_s
97
+ assert_equal t_node.to_s, root.left.to_s
98
+
99
+ #t_node
100
+ assert_equal root.to_s, t_node.parent.to_s
101
+ assert_equal 2.to_s, t_node.left.to_s
102
+ assert_equal 5.to_s, t_node.right.to_s
103
+ end
104
+
105
+ def test_rotate_right
106
+ root = @rot_tree.root
107
+ t_node = @rot_tree.search(4)
108
+ s_node = @rot_tree.search(2)
109
+
110
+ #root.print
111
+ #t_node.print
112
+ #s_node.print
113
+
114
+ assert_equal 4.to_s, root.to_s
115
+ assert_equal t_node.to_s, root.to_s
116
+ assert_equal s_node.to_s, t_node.left.to_s
117
+
118
+ t_node.rotate_right
119
+
120
+ t_node = @rot_tree.search(4)
121
+ root = t_node.parent
122
+
123
+ #root
124
+ assert_equal nil.to_s, root.parent.to_s
125
+ assert_equal 4.to_s, root.right.to_s
126
+
127
+ #t_node
128
+ assert_equal root.to_s, t_node.parent.to_s
129
+ assert_equal 3.to_s, t_node.left.to_s
130
+ assert_equal 6.to_s, t_node.right.to_s
131
+ end
132
+
133
+ def test_balance
134
+ #@tree.root.print
135
+ #@tree.search(1).print
136
+ #@tree.search(2).print
137
+ #@tree.search(5).print
138
+ #@tree.search(4).print
139
+ #@tree.search(3).print
140
+ end
141
+
142
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'winding-polygon/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "winding-polygon"
8
+ gem.version = WindingPolygon::VERSION
9
+ gem.authors = ["Martin Xu"]
10
+ gem.email = ["mxu2008@gmail.com"]
11
+ gem.description = %q{Use Bentley-Ottmann algorithm to solve self-intersecting polygon issue }
12
+ gem.summary = %q{Detect intersecting points and decompose it into multi-polygons}
13
+ gem.homepage = ""
14
+ gem.license = "MIT"
15
+
16
+ gem.add_development_dependency "rspec", "~> 2.6"
17
+ gem.add_development_dependency "test-unit"
18
+
19
+ gem.files = `git ls-files`.split($/)
20
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
21
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
22
+ gem.require_paths = ["lib"]
23
+
24
+ gem.add_development_dependency "rake"
25
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: winding-polygon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Xu
@@ -58,7 +58,30 @@ email:
58
58
  executables: []
59
59
  extensions: []
60
60
  extra_rdoc_files: []
61
- files: []
61
+ files:
62
+ - .gitignore
63
+ - .rspec
64
+ - Gemfile
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - lib/winding-polygon.rb
69
+ - lib/winding-polygon/avltree.rb
70
+ - lib/winding-polygon/event_queue.rb
71
+ - lib/winding-polygon/point.rb
72
+ - lib/winding-polygon/polygon.rb
73
+ - lib/winding-polygon/segment.rb
74
+ - lib/winding-polygon/sweep_line.rb
75
+ - lib/winding-polygon/version.rb
76
+ - spec/event_queue_spec.rb
77
+ - spec/point_spec.rb
78
+ - spec/polygon_spec.rb
79
+ - spec/segment_spec.rb
80
+ - spec/spec_helper.rb
81
+ - spec/sweep_line_spec.rb
82
+ - spec/winding-polygon_spec.rb
83
+ - test/test_avltree.rb
84
+ - winding-polygon.gemspec
62
85
  homepage: ''
63
86
  licenses:
64
87
  - MIT
@@ -83,4 +106,12 @@ rubygems_version: 2.0.3
83
106
  signing_key:
84
107
  specification_version: 4
85
108
  summary: Detect intersecting points and decompose it into multi-polygons
86
- test_files: []
109
+ test_files:
110
+ - spec/event_queue_spec.rb
111
+ - spec/point_spec.rb
112
+ - spec/polygon_spec.rb
113
+ - spec/segment_spec.rb
114
+ - spec/spec_helper.rb
115
+ - spec/sweep_line_spec.rb
116
+ - spec/winding-polygon_spec.rb
117
+ - test/test_avltree.rb