winding-polygon 0.0.2 → 0.0.3

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.
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