winding-polygon 0.0.7 → 0.0.8
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 +4 -4
- data/lib/winding-polygon.rb +72 -8
- data/lib/winding-polygon/avltree.rb +5 -0
- data/lib/winding-polygon/event_queue.rb +19 -4
- data/lib/winding-polygon/point.rb +12 -0
- data/lib/winding-polygon/polygon.rb +65 -15
- data/lib/winding-polygon/segment.rb +51 -3
- data/lib/winding-polygon/sweep_line.rb +53 -1
- data/lib/winding-polygon/vector.rb +50 -0
- data/lib/winding-polygon/version.rb +1 -1
- data/spec/polygon_spec.rb +14 -11
- data/spec/segment_spec.rb +12 -0
- data/spec/sweep_line_spec.rb +10 -7
- data/spec/winding-polygon_spec.rb +103 -4
- data/test/test_avltree.rb +2 -0
- data/test/vector/arithmetics_test.rb +22 -0
- data/test/vector/collinear_with_test.rb +27 -0
- data/test/vector/cross_product_test.rb +86 -0
- data/test/vector/equals_test.rb +15 -0
- data/test/vector/modulus_test.rb +15 -0
- data/test/vector/scalar_product_test.rb +18 -0
- metadata +23 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7d8c4d4bd5532c0f315bfd93c56b2abeb83d71ba
|
4
|
+
data.tar.gz: 29690ff49b94f6005f0b3b4ab5d43f1467ee7f8a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3270fcf7ab126fb0aee8044905b9595fad6f17d54f47f4216941ccd365adc65bf838b43a3f2524fece01c3789655511271059628ab08bf4d0d0faddee437f30f
|
7
|
+
data.tar.gz: a6e3ea0045b182ae9b18d9f799b94a31ead92806b9d86b20e0a33430ab5005a8634795d345a288f5021bf5449638c2dd260acea6ed0ed4fe6bdbe8353ba08dc6
|
data/lib/winding-polygon.rb
CHANGED
@@ -1,15 +1,79 @@
|
|
1
1
|
require "winding-polygon/version"
|
2
|
+
require "winding-polygon/vector"
|
2
3
|
|
3
4
|
module WindingPolygon
|
4
5
|
|
5
|
-
def self.decompose(input_polygon
|
6
|
-
|
7
|
-
|
8
|
-
if
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
def self.decompose(input_polygon)
|
7
|
+
|
8
|
+
intersection_points = input_polygon.get_intersection_points
|
9
|
+
return input_polygon if intersection_points.nil? || intersection_points.size==0
|
10
|
+
|
11
|
+
input_polygon.simple_segments.sort_by!{|seg| [seg.left_point] }
|
12
|
+
simple_polygons = Array.new
|
13
|
+
while !input_polygon.simple_segments.nil? && input_polygon.simple_segments.size>=3
|
14
|
+
simple_polygons << get_one_simple_polygon(get_first_segment(input_polygon), input_polygon)
|
15
|
+
end
|
16
|
+
simple_polygons
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.get_one_simple_polygon(first_segment, input_polygon)
|
21
|
+
current_simple_polygon = Array.new
|
22
|
+
current_simple_polygon_vertices = Array.new
|
23
|
+
current_simple_polygon << first_segment
|
24
|
+
current_simple_polygon_vertices << first_segment.left_point << first_segment.right_point
|
25
|
+
current_point = first_segment.right_point
|
26
|
+
|
27
|
+
while !input_polygon.simple_segments.nil? && input_polygon.simple_segments.size>=1
|
28
|
+
previous_edge = current_simple_polygon.last.edge
|
29
|
+
next_segment_candidates = input_polygon.simple_segments.select { |seg| seg.edge!=previous_edge && (seg.left_point == current_point ||seg.right_point == current_point) }.dup
|
30
|
+
|
31
|
+
if !next_segment_candidates.nil? && next_segment_candidates.size>=2
|
32
|
+
#determine previous segment vector
|
33
|
+
if current_point == current_simple_polygon.last.left_point
|
34
|
+
v0 = Vector.new(current_simple_polygon.last.right_point.x-current_point.x, current_simple_polygon.last.right_point.y-current_point.y)
|
35
|
+
else
|
36
|
+
v0 = Vector.new(current_simple_polygon.last.left_point.x-current_point.x, current_simple_polygon.last.left_point.y-current_point.y)
|
37
|
+
end
|
38
|
+
|
39
|
+
#determine next segment vector
|
40
|
+
if current_point == next_segment_candidates[0].left_point
|
41
|
+
v1 = Vector.new(next_segment_candidates[0].right_point.x-current_point.x, next_segment_candidates[0].right_point.y-current_point.y)
|
42
|
+
else
|
43
|
+
v1 = Vector.new(next_segment_candidates[0].left_point.x-current_point.x, next_segment_candidates[0].left_point.y-current_point.y)
|
44
|
+
end
|
45
|
+
|
46
|
+
if v1.cross_product(v0) > 0
|
47
|
+
current_simple_polygon << next_segment_candidates[0]
|
48
|
+
else
|
49
|
+
current_simple_polygon << next_segment_candidates[1]
|
50
|
+
end
|
51
|
+
else
|
52
|
+
current_simple_polygon << next_segment_candidates[0]
|
53
|
+
end
|
54
|
+
|
55
|
+
if current_simple_polygon.last.left_point == current_point
|
56
|
+
current_point = current_simple_polygon.last.right_point
|
57
|
+
else
|
58
|
+
current_point = current_simple_polygon.last.left_point
|
59
|
+
end
|
60
|
+
|
61
|
+
current_simple_polygon_vertices << current_point
|
62
|
+
|
63
|
+
input_polygon.simple_segments.delete_if { |seg| seg.left_point==current_simple_polygon.last.left_point && seg.right_point==current_simple_polygon.last.right_point }
|
64
|
+
|
65
|
+
return current_simple_polygon_vertices if current_simple_polygon.first.left_point == current_simple_polygon.last.left_point
|
66
|
+
|
13
67
|
end
|
68
|
+
current_simple_polygon_vertices
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.get_first_segment(input_polygon)
|
72
|
+
start_point = input_polygon.simple_segments[0].left_point
|
73
|
+
first_segment_candidates = input_polygon.simple_segments.select { |seg| seg.left_point == start_point }.dup
|
74
|
+
first_segment_candidates.sort!
|
75
|
+
input_polygon.simple_segments.delete_if { |seg| seg.left_point==first_segment_candidates[0].left_point && seg.right_point==first_segment_candidates[0].right_point }
|
76
|
+
first_segment_candidates[0]
|
14
77
|
end
|
78
|
+
|
15
79
|
end
|
@@ -26,14 +26,29 @@ module WindingPolygon
|
|
26
26
|
@events[a][:type] = 'right'
|
27
27
|
@events[b][:type] = 'left'
|
28
28
|
end
|
29
|
-
|
30
|
-
@events[a][:other_y]=@events[b][:vertex].y
|
31
|
-
@events[b][:other_y]=@events[a][:vertex].y
|
32
29
|
end
|
33
30
|
|
34
31
|
# sort events lexicographically
|
35
|
-
|
32
|
+
#@events.sort!{|a,b| [a[:vertex], b[:type], a[:other_endpoint]] <=> [b[:vertex], a[:type], b[:other_endpoint]] }
|
33
|
+
@events.sort!{|a,b| [a[:vertex], b[:type]] <=> [b[:vertex], a[:type]] }
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
def insert(point_hash)
|
38
|
+
for i in 0..@events.size-1
|
39
|
+
next if @events[i][:vertex].x < point_hash[:point].x
|
40
|
+
next if @events[i][:vertex].x == point_hash[:point].x && @events[i][:vertex].y<point_hash[:point].y
|
41
|
+
@events.insert(i,{:type=>'intersection_point',:vertex=>point_hash[:point],:edge1=>point_hash[:edge1],:edge2=>point_hash[:edge2]})
|
42
|
+
break
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def exist(point)
|
47
|
+
for i in 0..@events.size-1
|
48
|
+
return true if @events[i][:vertex] ==point
|
49
|
+
end
|
36
50
|
|
51
|
+
return false
|
37
52
|
end
|
38
53
|
|
39
54
|
end
|
@@ -42,6 +42,18 @@ module WindingPolygon
|
|
42
42
|
@x==other_point.x && @y==other_point.y
|
43
43
|
end
|
44
44
|
|
45
|
+
def < (other_point)
|
46
|
+
return true if @x < other_point.x
|
47
|
+
return true if @x == other_point.x && @y < other_point.y
|
48
|
+
return false
|
49
|
+
end
|
50
|
+
|
51
|
+
def > (other_point)
|
52
|
+
return true if @x > other_point.x
|
53
|
+
return true if @x == other_point.x && @y > other_point.y
|
54
|
+
return false
|
55
|
+
end
|
56
|
+
|
45
57
|
# tests if point is Left|On|Right of the line P0 to P1.
|
46
58
|
#
|
47
59
|
# returns:
|
@@ -2,10 +2,32 @@ module WindingPolygon
|
|
2
2
|
class Polygon
|
3
3
|
attr_reader :vertices
|
4
4
|
attr_reader :intersection_points
|
5
|
+
attr_accessor :segments
|
6
|
+
attr_accessor :simple_segments
|
5
7
|
|
6
8
|
def initialize(points)
|
7
9
|
@vertices = points
|
8
10
|
@intersection_points = Array.new
|
11
|
+
|
12
|
+
@simple_segments = Array.new
|
13
|
+
|
14
|
+
@segments = Array.new
|
15
|
+
for i in 0..@vertices.length-2
|
16
|
+
|
17
|
+
seg = Segment.new({:edge=>i})
|
18
|
+
|
19
|
+
if @vertices[i]<@vertices[i+1]
|
20
|
+
seg.left_point = @vertices[i]
|
21
|
+
seg.right_point = @vertices[i+1]
|
22
|
+
else
|
23
|
+
seg.left_point = @vertices[i+1]
|
24
|
+
seg.right_point = @vertices[i]
|
25
|
+
end
|
26
|
+
|
27
|
+
@segments << seg
|
28
|
+
|
29
|
+
end
|
30
|
+
|
9
31
|
end
|
10
32
|
|
11
33
|
def is_simple
|
@@ -45,14 +67,41 @@ module WindingPolygon
|
|
45
67
|
|
46
68
|
if e[:type] == 'left'
|
47
69
|
s = sweep_line.add(e)
|
70
|
+
find_intersection_point_between_segments(s,s.above, event_queue, sweep_line)
|
71
|
+
find_intersection_point_between_segments(s,s.below, event_queue, sweep_line)
|
72
|
+
end
|
48
73
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
s = sweep_line.find(e)
|
54
|
-
add_to_intersection_point_collection(sweep_line.intersect(s.above, s.below))
|
74
|
+
if e[:type] == 'right'
|
75
|
+
s = sweep_line.find_segment(@segments[e[:edge]])
|
76
|
+
point = sweep_line.intersect(s.above, s.below)
|
77
|
+
event_queue.insert(point_hash_with_edge_info(point, s.above, s.below)) if !point.nil? && !event_queue.exist(point)
|
55
78
|
sweep_line.remove(s)
|
79
|
+
@simple_segments << s
|
80
|
+
end
|
81
|
+
|
82
|
+
if e[:type] == 'intersection_point'
|
83
|
+
add_to_intersection_point_collection(e[:vertex])
|
84
|
+
|
85
|
+
s1 = sweep_line.find_segment(@segments[e[:edge1]])
|
86
|
+
sweep_line.remove(s1)
|
87
|
+
s11 = s1.dup
|
88
|
+
s11.right_point = e[:vertex]
|
89
|
+
@simple_segments << s11
|
90
|
+
|
91
|
+
|
92
|
+
s2 = sweep_line.find_segment(@segments[e[:edge2]])
|
93
|
+
sweep_line.remove(s2)
|
94
|
+
s22 = s2.dup
|
95
|
+
s22.right_point = e[:vertex]
|
96
|
+
@simple_segments << s22
|
97
|
+
|
98
|
+
@segments[e[:edge1]].left_point=e[:vertex]
|
99
|
+
s1 = sweep_line.add_segment(@segments[e[:edge1]])
|
100
|
+
find_intersection_point_between_segments(s1,s1.below, event_queue, sweep_line)
|
101
|
+
|
102
|
+
@segments[e[:edge2]].left_point=e[:vertex]
|
103
|
+
s2 = sweep_line.add_segment(@segments[e[:edge2]])
|
104
|
+
find_intersection_point_between_segments(s2,s2.above, event_queue, sweep_line)
|
56
105
|
end
|
57
106
|
|
58
107
|
e = event_queue.events.shift
|
@@ -62,6 +111,11 @@ module WindingPolygon
|
|
62
111
|
@intersection_points
|
63
112
|
end
|
64
113
|
|
114
|
+
def find_intersection_point_between_segments(s1,s2, event_queue, sweep_line)
|
115
|
+
point =sweep_line.intersect(s1, s2)
|
116
|
+
event_queue.insert(point_hash_with_edge_info(point, s1, s2)) if !point.nil? && !event_queue.exist(point)
|
117
|
+
end
|
118
|
+
|
65
119
|
def add_to_intersection_point_collection(point)
|
66
120
|
@intersection_points << point if !point.nil? && !@intersection_points.any?{|p| p.compare(point)==0}
|
67
121
|
end
|
@@ -93,16 +147,12 @@ module WindingPolygon
|
|
93
147
|
return nil
|
94
148
|
end
|
95
149
|
|
96
|
-
def decompose
|
97
|
-
intersection_point = get_first_intersection_point_hash
|
98
|
-
return [self] if intersection_point.nil?
|
99
|
-
first_polygon = Polygon.new((@vertices[0..intersection_point[:edge1]]<<intersection_point[:point]).concat( @vertices[intersection_point[:edge2]+1,@vertices.length-intersection_point[:edge2]]))
|
100
|
-
second_polygon = Polygon.new(@vertices[intersection_point[:edge1]+1..intersection_point[:edge2]].insert(0,intersection_point[:point])<<intersection_point[:point])
|
101
|
-
return [first_polygon,second_polygon]
|
102
|
-
end
|
103
|
-
|
104
150
|
def point_hash_with_edge_info(point, s1, s2)
|
105
|
-
|
151
|
+
if s1.right_point.y<s2.right_point.y
|
152
|
+
edges=[s1.edge, s2.edge]
|
153
|
+
else
|
154
|
+
edges=[s2.edge, s1.edge]
|
155
|
+
end
|
106
156
|
{:point => point, :edge1 => edges[0], :edge2 => edges[1]}
|
107
157
|
end
|
108
158
|
|
@@ -14,20 +14,38 @@ module WindingPolygon
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def <(other_segment)
|
17
|
-
|
17
|
+
if @left_point == other_segment.left_point
|
18
|
+
return true if @right_point.is_left(other_segment.left_point,other_segment.right_point)<0
|
19
|
+
end
|
20
|
+
|
21
|
+
return true if @left_point.is_left(other_segment.left_point,other_segment.right_point)<0
|
22
|
+
|
18
23
|
return false
|
19
24
|
end
|
20
25
|
|
21
26
|
def >(other_segment)
|
22
|
-
|
27
|
+
if @left_point == other_segment.left_point
|
28
|
+
return true if @right_point.is_left(other_segment.left_point,other_segment.right_point)>0
|
29
|
+
end
|
30
|
+
|
31
|
+
return true if @left_point.is_left(other_segment.left_point,other_segment.right_point)>0
|
32
|
+
|
23
33
|
return false
|
24
34
|
end
|
25
35
|
|
26
36
|
def == (other_segment)
|
27
|
-
return true if @left_point
|
37
|
+
return true if @left_point == other_segment.left_point && @right_point == other_segment.right_point && @edge==other_segment.edge
|
28
38
|
return false
|
29
39
|
end
|
30
40
|
|
41
|
+
def <=> other_segment
|
42
|
+
raise Exception.new("Self is edge=#{@edge}, the other_segment is nil") if other_segment.nil?
|
43
|
+
|
44
|
+
return 1 if self > other_segment
|
45
|
+
return -1 if self < other_segment
|
46
|
+
return 0 if self == other_segment
|
47
|
+
end
|
48
|
+
|
31
49
|
def to_s
|
32
50
|
return "edge:#{@edge.to_s}"
|
33
51
|
end
|
@@ -48,5 +66,35 @@ module WindingPolygon
|
|
48
66
|
|
49
67
|
Point.new(x, y)
|
50
68
|
end
|
69
|
+
|
70
|
+
def is_intersected_with(other_segment)
|
71
|
+
# no intersect if either segment doesn't existend
|
72
|
+
return false if other_segment.nil?
|
73
|
+
|
74
|
+
#test for existence of an intersect point
|
75
|
+
#other_segment left point sign
|
76
|
+
lsign = other_segment.left_point.is_left(@left_point, @right_point)
|
77
|
+
#other_segment right point sign
|
78
|
+
rsign = other_segment.right_point.is_left(@left_point, @right_point)
|
79
|
+
|
80
|
+
# other_segment endpoints have same sign relative to it => on same side => no intersect is possible
|
81
|
+
return false if (lsign * rsign > 0)
|
82
|
+
|
83
|
+
# its left point sign
|
84
|
+
lsign = @left_point.is_left(other_segment.left_point, other_segment.right_point)
|
85
|
+
#its right point sign
|
86
|
+
rsign = @right_point.is_left(other_segment.left_point, other_segment.right_point)
|
87
|
+
|
88
|
+
# its endpoints have same sign relative to other_segment => on same side => no intersect is possible
|
89
|
+
return false if (lsign * rsign > 0)
|
90
|
+
|
91
|
+
return true
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
def is_on_the_line(point)
|
96
|
+
return true if @left_point.x<point.x && point.x < @right_point.x && (@left_point.y<point.y && point.y < @right_point.y || @right_point.y<point.y && point.y < @left_point.y) && ((point.x - @left_point.x) / (@right_point.x - @left_point.x) - (point.y - @left_point.y) / (@right_point.y - @left_point.y)).abs<0.00000000001
|
97
|
+
false
|
98
|
+
end
|
51
99
|
end
|
52
100
|
end
|
@@ -45,6 +45,29 @@ module WindingPolygon
|
|
45
45
|
|
46
46
|
end
|
47
47
|
|
48
|
+
def add_segment(seg)
|
49
|
+
seg.below=seg.above=nil
|
50
|
+
|
51
|
+
# Add node to tree and setup linkages to "above" and "below"
|
52
|
+
# edges as per algorithm
|
53
|
+
nd = @tree.insert(seg)
|
54
|
+
|
55
|
+
nx = nd.next
|
56
|
+
np = nd.prev
|
57
|
+
|
58
|
+
if !nx.nil?
|
59
|
+
seg.above = nx.value
|
60
|
+
seg.above.below = seg
|
61
|
+
end
|
62
|
+
|
63
|
+
if !np.nil?
|
64
|
+
seg.below = np.value
|
65
|
+
seg.below.above = seg
|
66
|
+
end
|
67
|
+
return seg
|
68
|
+
|
69
|
+
end
|
70
|
+
|
48
71
|
def find(event)
|
49
72
|
# need a segment to find it in the tree
|
50
73
|
seg = Segment.new(event)
|
@@ -68,6 +91,12 @@ module WindingPolygon
|
|
68
91
|
node.value
|
69
92
|
end
|
70
93
|
|
94
|
+
def find_segment(seg)
|
95
|
+
node = @tree.search(seg)
|
96
|
+
return nil if node.nil?
|
97
|
+
node.value
|
98
|
+
end
|
99
|
+
|
71
100
|
def remove(seg)
|
72
101
|
nd = @tree.search(seg)
|
73
102
|
return if nd.nil?
|
@@ -81,13 +110,14 @@ module WindingPolygon
|
|
81
110
|
@tree.delete(seg)
|
82
111
|
end
|
83
112
|
|
84
|
-
def
|
113
|
+
def swap(s1,s2)
|
85
114
|
nd1 = @tree.search(s1)
|
86
115
|
return if nd1.nil?
|
87
116
|
|
88
117
|
nd2 = @tree.search(s2)
|
89
118
|
return if nd2.nil?
|
90
119
|
|
120
|
+
|
91
121
|
nx1 = nd1.next
|
92
122
|
nx1.value.below = nd2.value if !nx1.nil?
|
93
123
|
|
@@ -100,6 +130,28 @@ module WindingPolygon
|
|
100
130
|
np2 = nd2.prev
|
101
131
|
np2.value.above = nd1.value if !np2.nil?
|
102
132
|
|
133
|
+
|
134
|
+
nd1.value = s2
|
135
|
+
nd2.value = s1
|
136
|
+
|
137
|
+
temp = s1
|
138
|
+
s1=s2
|
139
|
+
s2=temp
|
140
|
+
|
141
|
+
|
142
|
+
|
143
|
+
temp_above = s1.above
|
144
|
+
temp_below = s1.below
|
145
|
+
|
146
|
+
s1.above = s2.above
|
147
|
+
s1.below = s2.below
|
148
|
+
|
149
|
+
s2.above = temp_above
|
150
|
+
s2.below = temp_below
|
151
|
+
|
152
|
+
|
153
|
+
[s1,s2]
|
154
|
+
|
103
155
|
end
|
104
156
|
|
105
157
|
#test intersect of 2 segments and return: nil when none, point when intersecting
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module WindingPolygon
|
2
|
+
class Vector < Struct.new(:x, :y)
|
3
|
+
def ==(vector)
|
4
|
+
x === vector.x && y === vector.y
|
5
|
+
end
|
6
|
+
|
7
|
+
# Modulus of vector. Also known as length, size or norm
|
8
|
+
def modulus
|
9
|
+
Math.hypot(x ,y)
|
10
|
+
end
|
11
|
+
|
12
|
+
# z-coordinate of cross product (also known as vector product or outer product)
|
13
|
+
# It is positive if other vector should be turned counter-clockwise in order to superpose them.
|
14
|
+
# It is negetive if other vector should be turned clockwise in order to superpose them.
|
15
|
+
# It is zero when vectors are collinear.
|
16
|
+
# Remark: x- and y- coordinates of plane vectors cross product are always zero
|
17
|
+
def cross_product(vector)
|
18
|
+
x * vector.y - y * vector.x
|
19
|
+
end
|
20
|
+
|
21
|
+
# Scalar product, also known as inner product or dot product
|
22
|
+
def scalar_product(vector)
|
23
|
+
x * vector.x + y * vector.y
|
24
|
+
end
|
25
|
+
|
26
|
+
def collinear_with?(vector)
|
27
|
+
cross_product(vector) === 0
|
28
|
+
end
|
29
|
+
|
30
|
+
def +(vector)
|
31
|
+
Vector.new(x + vector.x, y + vector.y)
|
32
|
+
end
|
33
|
+
|
34
|
+
def -(vector)
|
35
|
+
self + (-1) * vector
|
36
|
+
end
|
37
|
+
|
38
|
+
def *(scalar)
|
39
|
+
Vector.new(x * scalar, y * scalar)
|
40
|
+
end
|
41
|
+
|
42
|
+
def coerce(scalar)
|
43
|
+
if scalar.is_a?(Numeric)
|
44
|
+
[self, scalar]
|
45
|
+
else
|
46
|
+
raise ArgumentError, "Vector: cannot coerce #{scalar.inspect}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/spec/polygon_spec.rb
CHANGED
@@ -174,25 +174,28 @@ describe "Polygon" do
|
|
174
174
|
points = JSON.parse("[[-98.4609375000036,40.3051841980949],[-98.4609375000036,38.057277897745],[-96.0878906250017,40.1038062719331],[-96.6152343750031,37.9880405545487],[-98.4609375000036,40.3051841980949]]")
|
175
175
|
polygon = WindingPolygon::Polygon.new(points.map{|item| WindingPolygon::Point.new(item[0],item[1])})
|
176
176
|
|
177
|
-
polygon.get_first_intersection_point_hash.should == {:point=>WindingPolygon::Point.new(-97.39951856124108,38.97265129300353),:edge1=>
|
177
|
+
polygon.get_first_intersection_point_hash.should == {:point=>WindingPolygon::Point.new(-97.39951856124108,38.97265129300353),:edge1=>3,:edge2=>1 }
|
178
178
|
|
179
179
|
end
|
180
180
|
|
181
|
-
it '
|
181
|
+
it 'get practical complex polygon 5 intersection points' do
|
182
182
|
|
183
|
-
points = JSON.parse("[[-98.4609375000036,40.3051841980949],[-98.4609375000036,38.057277897745],[-96.0878906250017,40.1038062719331],[-96.6152343750031,37.9880405545487],[-98.4609375000036,40.3051841980949]]")
|
183
|
+
points = JSON.parse("[[-98.4609375000036,40.3051841980949],[-98.4609375000036,38.057277897745],[-96.0878906250017,40.1038062719331],[-96.6152343750031,37.9880405545487],[-97.6152343750031,40.9880405545487],[-98.0152343750031,37.9880405545487],[-98.4609375000036,40.3051841980949]]")
|
184
184
|
polygon = WindingPolygon::Polygon.new(points.map{|item| WindingPolygon::Point.new(item[0],item[1])})
|
185
185
|
|
186
|
-
|
187
|
-
|
186
|
+
polygon.get_intersection_points.size.should == 3
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'get practical complex polygon 6 intersection points' do
|
191
|
+
|
192
|
+
points = JSON.parse("[["+"-115.86328125000495 34.15328996737699,-119.818359375002 45.45290147989422,-81.4101562500034 35.021556836103684,-110.50195312500117 46.97322061018078,-92.66015625000608 43.38159203778072,-94.68164062500398 46.18790776409503,-115.07226562500733 41.63237589430056,-104.17382812500229 40.17099871188528,-100.3945312500027 32.46399995947513,-95.82421875000551 37.08201838643584,-88.52929687500257 35.95188058401679,-84.31054687500303 33.64262887366194,-83.16796875000327 34.51617009551454,-82.37695312500296 32.538125275571225,-84.31054687500303 39.36031127050692,-72.79687500000323 42.739443407624286,-115.86328125000495 34.15328996737699".gsub(',','],[').gsub(' ',',')+"]]")
|
193
|
+
polygon = WindingPolygon::Polygon.new(points.map{|item| WindingPolygon::Point.new(item[0],item[1])})
|
188
194
|
|
189
|
-
|
190
|
-
|
191
|
-
|
195
|
+
intersection_points = polygon.get_intersection_points
|
196
|
+
intersection_points.size.should == 8
|
197
|
+
polygon.simple_segments.size == 32
|
192
198
|
|
193
|
-
points2 = JSON.parse("[[-97.39951856124108,38.97265129300353],[-96.0878906250017,40.1038062719331],[-96.6152343750031,37.9880405545487],[-97.39951856124108,38.97265129300353]]")
|
194
|
-
polygon2 = WindingPolygon::Polygon.new(points2.map{|item| WindingPolygon::Point.new(item[0],item[1])})
|
195
|
-
multi_polygons[1].should ==polygon2
|
196
199
|
|
197
200
|
end
|
198
201
|
|
data/spec/segment_spec.rb
CHANGED
@@ -17,4 +17,16 @@ describe "Segment" do
|
|
17
17
|
intersection_point.y.should ==2
|
18
18
|
|
19
19
|
end
|
20
|
+
|
21
|
+
it "point is on the line segment" do
|
22
|
+
|
23
|
+
segment1 = WindingPolygon::Segment.new({:edge=>0})
|
24
|
+
segment1.left_point = WindingPolygon::Point.new(0,0)
|
25
|
+
segment1.right_point = WindingPolygon::Point.new(3,3)
|
26
|
+
|
27
|
+
segment1.is_on_the_line(WindingPolygon::Point.new(1.5,1.5)).should == true
|
28
|
+
segment1.is_on_the_line(WindingPolygon::Point.new(1.5,2.5)).should == false
|
29
|
+
segment1.is_on_the_line(WindingPolygon::Point.new(3.5,3.5)).should == false
|
30
|
+
|
31
|
+
end
|
20
32
|
end
|
data/spec/sweep_line_spec.rb
CHANGED
@@ -8,14 +8,17 @@ describe "SweepLine" do
|
|
8
8
|
sweep_line = WindingPolygon::SweepLine.new(polygon)
|
9
9
|
event_queue = WindingPolygon::EventQueue.new(polygon)
|
10
10
|
|
11
|
-
event = event_queue.events.
|
11
|
+
event = event_queue.events.shift
|
12
12
|
while !event.nil?
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
if event[:type]=='left'
|
14
|
+
sweep_line.add(event)
|
15
|
+
sweep_line.find(event).should_not be_nil
|
16
|
+
else
|
17
|
+
seg = sweep_line.find(event)
|
18
|
+
seg.should_not be_nil
|
19
|
+
sweep_line.remove(seg)
|
20
|
+
end
|
21
|
+
event = event_queue.events.shift
|
19
22
|
end
|
20
23
|
|
21
24
|
end
|
@@ -5,13 +5,18 @@ describe WindingPolygon do
|
|
5
5
|
WindingPolygon::VERSION.should_not be_nil
|
6
6
|
end
|
7
7
|
|
8
|
-
it 'should decompose a practical complex polygon into two simple polygons' do
|
9
8
|
|
9
|
+
=begin
|
10
|
+
it 'should decompose a practical complex polygon into two simple polygons' do
|
11
|
+
puts 'should decompose a practical complex polygon into two simple polygons'
|
10
12
|
points = JSON.parse("[[-98.4609375000036,40.3051841980949],[-98.4609375000036,38.057277897745],[-96.0878906250017,40.1038062719331],[-96.6152343750031,37.9880405545487],[-98.4609375000036,40.3051841980949]]")
|
11
13
|
polygon = WindingPolygon::Polygon.new(points.map{|item| WindingPolygon::Point.new(item[0],item[1])})
|
12
14
|
|
13
15
|
multi_polygons = []
|
16
|
+
t1 = Time.now
|
14
17
|
WindingPolygon.decompose(polygon,multi_polygons)
|
18
|
+
t2 = Time.now
|
19
|
+
puts "elapsed time: #{(t2-t1)*1000.to_i} ms"
|
15
20
|
multi_polygons.should_not be_nil
|
16
21
|
|
17
22
|
points1 = JSON.parse("[[-98.4609375000036,40.3051841980949],[-98.4609375000036,38.057277897745],[-97.39951856124108,38.97265129300353],[-98.4609375000036,40.3051841980949]]")
|
@@ -26,12 +31,15 @@ describe WindingPolygon do
|
|
26
31
|
|
27
32
|
|
28
33
|
it 'should decompose a practical complex polygon into four simple polygons' do
|
29
|
-
|
34
|
+
puts 'should decompose a practical complex polygon into four simple polygons'
|
30
35
|
points = JSON.parse("[[-98.4609375000036,40.3051841980949],[-98.4609375000036,38.057277897745],[-96.0878906250017,40.1038062719331],[-96.6152343750031,37.9880405545487],[-97.6152343750031,40.9880405545487],[-98.0152343750031,37.9880405545487],[-98.4609375000036,40.3051841980949]]")
|
31
36
|
polygon = WindingPolygon::Polygon.new(points.map{|item| WindingPolygon::Point.new(item[0],item[1])})
|
32
37
|
|
33
38
|
multi_polygons = []
|
39
|
+
t1 = Time.now
|
34
40
|
WindingPolygon.decompose(polygon,multi_polygons)
|
41
|
+
t2 = Time.now
|
42
|
+
puts "elapsed time: #{(t2-t1)*1000.to_i} ms"
|
35
43
|
multi_polygons.should_not be_nil
|
36
44
|
|
37
45
|
multi_polygons.size.should == 4
|
@@ -39,12 +47,15 @@ describe WindingPolygon do
|
|
39
47
|
end
|
40
48
|
|
41
49
|
it 'should decompose a production complex polygon 2 into two simple polygons' do
|
42
|
-
|
50
|
+
puts 'should decompose a production complex polygon 2 into two simple polygons'
|
43
51
|
points = JSON.parse("[[-95.6968,29.93101],[-95.6992,29.93101],[-95.7016,29.9319],[-95.70401,29.93309],[-95.7071,29.93428],[-95.7095,29.93488],[-95.7119,29.93547],[-95.71431,29.93607],[-95.7174,29.93666],[-95.72014,29.93726],[-95.72255,29.93785],[-95.72564,29.93875],[-95.72804,29.93994],[-95.72598,29.95094],[-95.72358,29.95303],[-95.72152,29.95481],[-95.71877,29.95659],[-95.71637,29.95778],[-95.71362,29.95838],[-95.71122,29.95838],[-95.70847,29.95838],[-95.70538,29.95808],[-95.70195,29.95778],[-95.69851,29.95719],[-95.69405,29.956],[-95.69165,29.9554],[-95.6889,29.95451],[-95.68615,29.95332],[-95.68307,29.95184],[-95.68066,29.95124],[-95.67723,29.94856],[-95.67483,29.94648],[-95.67242,29.9438],[-95.67448,29.93607],[-95.67689,29.93339],[-95.67895,29.93161],[-95.68101,29.93131],[-95.68341,29.93131],[-95.68615,29.93131],[-95.6889,29.93101],[-95.69165,29.93101],[-95.69439,29.93101],[-95.69748,29.93161],[-95.70023,29.9322],[-95.70435,29.93339],[-95.70641,29.93428],[-95.70984,29.93458],[-95.6968,29.93101]]")
|
44
52
|
polygon = WindingPolygon::Polygon.new(points.map{|item| WindingPolygon::Point.new(item[0].to_f,item[1].to_f)})
|
45
53
|
|
46
54
|
multi_polygons = []
|
55
|
+
t1 = Time.now
|
47
56
|
WindingPolygon.decompose(polygon,multi_polygons)
|
57
|
+
t2 = Time.now
|
58
|
+
puts "elapsed time: #{(t2-t1)*1000.to_i} ms"
|
48
59
|
multi_polygons.should_not be_nil
|
49
60
|
|
50
61
|
multi_polygons.size.should == 3
|
@@ -53,11 +64,15 @@ describe WindingPolygon do
|
|
53
64
|
|
54
65
|
it 'should decompose a production complex polygon 3 into two simple polygons' do
|
55
66
|
|
67
|
+
puts 'should decompose a production complex polygon 3 into two simple polygons'
|
56
68
|
points = JSON.parse("[[-95.6968,29.93101],[-95.6992,29.93101],[-95.7016,29.9319],[-95.70401,29.93309],[-95.7071,29.93428],[-95.70747130044843,29.934372825112106],[-95.70984,29.93458],[-95.6968,29.93101]]")
|
57
69
|
polygon = WindingPolygon::Polygon.new(points.map{|item| WindingPolygon::Point.new(item[0].to_f,item[1].to_f)})
|
58
70
|
|
59
71
|
multi_polygons = []
|
72
|
+
t1 = Time.now
|
60
73
|
WindingPolygon.decompose(polygon,multi_polygons)
|
74
|
+
t2 = Time.now
|
75
|
+
puts "elapsed time: #{(t2-t1)*1000.to_i} ms"
|
61
76
|
multi_polygons.should_not be_nil
|
62
77
|
|
63
78
|
multi_polygons.size.should == 2
|
@@ -65,18 +80,102 @@ describe WindingPolygon do
|
|
65
80
|
end
|
66
81
|
|
67
82
|
it 'should decompose a production complex polygon 4 into two simple polygons' do
|
68
|
-
|
83
|
+
puts 'should decompose a production complex polygon 4 into two simple polygons'
|
69
84
|
points = JSON.parse("[[-87.80356,41.96514],[-87.80115,41.96182],[-87.79909,41.96156],[-87.79635,41.96131],[-87.79394,41.96105],[-87.79188,41.9608],[-87.78914,41.95978],[-87.78708,41.95876],[-87.78467,41.95722],[-87.78193,41.95544],[-87.77987,41.95339],[-87.77781,41.9511],[-87.77541,41.94803],[-87.773,41.94446],[-87.7706,41.94063],[-87.76785,41.9368],[-87.76545,41.93271],[-87.76339,41.92658],[-87.76133,41.91687],[-87.76373,41.87522],[-87.76579,41.87445],[-87.76854,41.87394],[-87.7706,41.87368],[-87.77335,41.87343],[-87.77575,41.87266],[-87.77815,41.8724],[-87.78021,41.87215],[-87.78296,41.87164],[-87.78536,41.87113],[-87.78776,41.87113],[-87.78982,41.87113],[-87.79223,41.87113],[-87.79463,41.87113],[-87.79703,41.87113],[-87.79978,41.87138],[-87.80184,41.87189],[-87.8039,41.8724],[-87.8063,41.87317],[-87.80871,41.87419],[-87.81077,41.87471],[-87.81283,41.87547],[-87.81523,41.87649],[-87.81729,41.87726],[-87.81935,41.87752],[-87.82141,41.87777],[-87.82347,41.87803],[-87.82622,41.87828],[-87.82862,41.87854],[-87.83171,41.87854],[-87.83377,41.8788],[-87.83652,41.87931],[-87.83926,41.88007],[-87.84132,41.88058],[-87.84373,41.88135],[-87.84613,41.88237],[-87.84888,41.88365],[-87.85094,41.88493],[-87.85334,41.88697],[-87.8554,41.89055],[-87.85746,41.89515],[-87.85952,41.90026],[-87.86158,41.90435],[-87.85952,41.92913],[-87.85746,41.93373],[-87.8554,41.94139],[-87.85334,41.94573],[-87.85094,41.94778],[-87.84888,41.94905],[-87.84647,41.95059],[-87.84304,41.95263],[-87.84098,41.95365],[-87.83892,41.95442],[-87.83514,41.95671],[-87.83308,41.95773],[-87.83102,41.95901],[-87.82862,41.96003],[-87.82484,41.96207],[-87.82107,41.9631],[-87.81901,41.9631],[-87.81695,41.9631],[-87.81351,41.9631],[-87.81042,41.96284],[-87.80699,41.96233],[-87.80424,41.96207],[-87.80184,41.96156],[-87.79978,41.96105],[-87.79703,41.95901],[-87.80356,41.96514]]")
|
70
85
|
polygon = WindingPolygon::Polygon.new(points.map{|item| WindingPolygon::Point.new(item[0].to_f,item[1].to_f)})
|
71
86
|
|
72
87
|
multi_polygons = []
|
88
|
+
t1 = Time.now
|
73
89
|
WindingPolygon.decompose(polygon,multi_polygons)
|
90
|
+
t2 = Time.now
|
91
|
+
puts "elapsed time: #{(t2-t1)*1000.to_i} ms"
|
74
92
|
multi_polygons.should_not be_nil
|
75
93
|
|
76
94
|
multi_polygons.size.should == 2
|
77
95
|
|
78
96
|
end
|
79
97
|
|
98
|
+
it 'should decompose a production complex polygon 5 into two simple polygons' do
|
99
|
+
puts 'should decompose a production complex polygon 5 into two simple polygons'
|
100
|
+
points = JSON.parse("[[153.4229,-28.0902],[153.42346,-28.09104],[153.42395,-28.09178],[153.42435,-28.09254],[153.42487,-28.09328],[153.4229,-28.0902]]")
|
101
|
+
polygon = WindingPolygon::Polygon.new(points.map{|item| WindingPolygon::Point.new(item[0].to_f,item[1].to_f)})
|
102
|
+
|
103
|
+
multi_polygons = []
|
104
|
+
t1 = Time.now
|
105
|
+
WindingPolygon.decompose(polygon,multi_polygons)
|
106
|
+
t2 = Time.now
|
107
|
+
puts "elapsed time: #{(t2-t1)*1000.to_i} ms"
|
108
|
+
multi_polygons.should_not be_nil
|
109
|
+
|
110
|
+
multi_polygons.size.should == 2
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should decompose a production complex polygon 6 into three simple polygons' do
|
115
|
+
puts 'should decompose a production complex polygon 6 into three simple polygons'
|
116
|
+
points = JSON.parse("[["+"-78.17273 38.94346,-78.17685 38.93652,-78.18097 38.93171,-78.18578 38.92637,-78.1899 38.91836,-78.19196 38.91622,-78.17273 38.94346".gsub(',','],[').gsub(' ',',')+"]]")
|
117
|
+
polygon = WindingPolygon::Polygon.new(points.map{|item| WindingPolygon::Point.new(item[0].to_f,item[1].to_f)})
|
118
|
+
|
119
|
+
multi_polygons = []
|
120
|
+
t1 = Time.now
|
121
|
+
WindingPolygon.decompose(polygon,multi_polygons)
|
122
|
+
t2 = Time.now
|
123
|
+
puts "elapsed time: #{(t2-t1)*1000.to_i} ms"
|
124
|
+
multi_polygons.should_not be_nil
|
125
|
+
|
126
|
+
multi_polygons.size.should == 3
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'should decompose a production complex polygon 7 into two simple polygons' do
|
131
|
+
puts 'should decompose a production complex polygon 7 into two simple polygons'
|
132
|
+
points = JSON.parse("[["+"-71.77812 41.42488,-71.774 41.42024,-71.76782 41.41818,-71.76301 41.41818,-71.75546 41.41818,-71.74859 41.41818,-71.74172 41.41818,-71.73486 41.41818,-71.7273 41.41818,-71.71906 41.41818,-71.71288 41.41818,-71.70739 41.41818,-71.70327 41.41818,-71.69778 41.41818,-71.69366 41.41612,-71.77812 41.42488".gsub(',','],[').gsub(' ',',')+"]]")
|
133
|
+
polygon = WindingPolygon::Polygon.new(points.map{|item| WindingPolygon::Point.new(item[0].to_f,item[1].to_f)})
|
134
|
+
|
135
|
+
multi_polygons = []
|
136
|
+
t1 = Time.now
|
137
|
+
WindingPolygon.decompose(polygon,multi_polygons)
|
138
|
+
t2 = Time.now
|
139
|
+
puts "elapsed time: #{(t2-t1)*1000.to_i} ms"
|
140
|
+
multi_polygons.should_not be_nil
|
141
|
+
|
142
|
+
multi_polygons.size.should == 2
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'should decompose a production complex polygon 8 into four simple polygons' do
|
147
|
+
puts 'should decompose a production complex polygon 8 into four simple polygons'
|
148
|
+
points = JSON.parse("[["+"-88.69626 32.36626,-88.69697 32.36713,-88.69706 32.36722,-88.69713 32.3673,-88.69719 32.36738,-88.69729 32.36747,-88.69743 32.36758,-88.69749 32.36765,-88.69759 32.36777,-88.69767 32.36791,-88.69769 32.36798,-88.69626 32.36626".gsub(',','],[').gsub(' ',',')+"]]")
|
149
|
+
polygon = WindingPolygon::Polygon.new(points.map{|item| WindingPolygon::Point.new(item[0].to_f,item[1].to_f)})
|
150
|
+
|
151
|
+
multi_polygons = []
|
152
|
+
t1 = Time.now
|
153
|
+
WindingPolygon.decompose(polygon,multi_polygons)
|
154
|
+
t2 = Time.now
|
155
|
+
puts "elapsed time: #{(t2-t1)*1000.to_i} ms"
|
156
|
+
multi_polygons.should_not be_nil
|
157
|
+
|
158
|
+
multi_polygons.size.should == 4
|
159
|
+
|
160
|
+
end
|
161
|
+
=end
|
162
|
+
|
163
|
+
|
164
|
+
it 'should decompose Andy complex polygon into 7 simple polygons' do
|
165
|
+
puts 'should decompose Andy complex polygon into four simple polygons'
|
166
|
+
points = JSON.parse("[["+"-115.86328125000495 34.15328996737699,-119.818359375002 45.45290147989422,-81.4101562500034 35.021556836103684,-110.50195312500117 46.97322061018078,-92.66015625000608 43.38159203778072,-94.68164062500398 46.18790776409503,-115.07226562500733 41.63237589430056,-104.17382812500229 40.17099871188528,-100.3945312500027 32.46399995947513,-95.82421875000551 37.08201838643584,-88.52929687500257 35.95188058401679,-84.31054687500303 33.64262887366194,-83.16796875000327 34.51617009551454,-82.37695312500296 32.538125275571225,-84.31054687500303 39.36031127050692,-72.79687500000323 42.739443407624286,-115.86328125000495 34.15328996737699".gsub(',','],[').gsub(' ',',')+"]]")
|
167
|
+
polygon = WindingPolygon::Polygon.new(points.map{|item| WindingPolygon::Point.new(item[0].to_f,item[1].to_f)})
|
168
|
+
|
169
|
+
t1 = Time.now
|
170
|
+
multi_polygons = WindingPolygon.decompose(polygon)
|
171
|
+
t2 = Time.now
|
172
|
+
puts "elapsed time: #{(t2-t1)*1000.to_i} ms"
|
173
|
+
|
174
|
+
|
175
|
+
multi_polygons.size.should == 7
|
176
|
+
|
177
|
+
end
|
178
|
+
|
80
179
|
|
81
180
|
|
82
181
|
end
|
data/test/test_avltree.rb
CHANGED
@@ -65,6 +65,8 @@ class TestAVLTree < Test::Unit::TestCase
|
|
65
65
|
assert_equal "[1, 5]", @tree.sort.inspect
|
66
66
|
@tree.delete 1
|
67
67
|
assert_equal "[5]", @tree.sort.inspect
|
68
|
+
@tree.delete 5
|
69
|
+
assert_equal nil, @tree.root
|
68
70
|
|
69
71
|
@rot_tree.delete 1
|
70
72
|
assert_equal 4.to_s, @rot_tree.root.to_s
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'winding-polygon/vector'
|
3
|
+
|
4
|
+
class ArithmeticsTest < Test::Unit::TestCase
|
5
|
+
include WindingPolygon
|
6
|
+
|
7
|
+
def test_summation
|
8
|
+
assert_equal Vector.new(4, 6), Vector.new(1, 2) + Vector.new(3, 4)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_subtraction
|
12
|
+
assert_equal Vector.new(-2, -4), Vector.new(1, 0) - Vector.new(3, 4)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_vector_multiplied_by_scalar
|
16
|
+
assert_equal Vector.new(-2, -4), Vector.new(1, 2) * -2
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_scalar_multiplied_by_vector
|
20
|
+
assert_equal Vector.new(-2, -4), -2 * Vector.new(1, 2)
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'winding-polygon/vector'
|
3
|
+
|
4
|
+
class CollinearWithTest < Test::Unit::TestCase
|
5
|
+
include WindingPolygon
|
6
|
+
|
7
|
+
def test_vectors_are_collinear
|
8
|
+
vector1 = Vector.new(1, 2)
|
9
|
+
vector2 = Vector.new(2, 4)
|
10
|
+
|
11
|
+
assert vector1.collinear_with?(vector2)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_vectors_are_not_collinear
|
15
|
+
vector1 = Vector.new(1, 2)
|
16
|
+
vector2 = Vector.new(1, 1)
|
17
|
+
|
18
|
+
assert ! vector1.collinear_with?(vector2)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_vectors_are_oppositely_directed
|
22
|
+
vector1 = Vector.new(2, 2)
|
23
|
+
vector2 = Vector.new(-2, -2)
|
24
|
+
|
25
|
+
assert vector1.collinear_with?(vector2)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'winding-polygon/vector'
|
3
|
+
require 'winding-polygon/point'
|
4
|
+
|
5
|
+
class CrossProductTest < Test::Unit::TestCase
|
6
|
+
include WindingPolygon
|
7
|
+
|
8
|
+
def test_positive
|
9
|
+
assert 1 === Vector.new(1, 0).cross_product(Vector.new(0, 1))
|
10
|
+
assert 1 === Vector.new(-1, 0).cross_product(Vector.new(0, -1))
|
11
|
+
assert 0 === Vector.new(-1, 0).cross_product(Vector.new(1, 0))
|
12
|
+
assert 0 === Vector.new(-1, 0).cross_product(Vector.new(2, 0))
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_positive_for_latlon
|
16
|
+
#current vertex
|
17
|
+
p0= Point.new(-102.52676351947564,36.81219275411459)
|
18
|
+
#previous vertex
|
19
|
+
p1=Point.new(-115.86328125000495,34.15328996737699)
|
20
|
+
#current edge
|
21
|
+
v0 = Vector.new(p1.x-p0.x, p1.y-p0.y)
|
22
|
+
|
23
|
+
#next vertex
|
24
|
+
p2= Point.new(-104.17382812500229,40.17099871188528)
|
25
|
+
#next edge
|
26
|
+
v1 = Vector.new(p2.x-p0.x, p2.y-p0.y)
|
27
|
+
|
28
|
+
assert_true v1.cross_product(v0)>0
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_positive_for_latlon2
|
33
|
+
#current vertex
|
34
|
+
p0=Point.new(-103.69099150583577,44.1751011402773)
|
35
|
+
#previous vertex
|
36
|
+
p1= Point.new(-110.50195312500117,46.97322061018078)
|
37
|
+
#current edge
|
38
|
+
v0 = Vector.new(p1.x-p0.x, p1.y-p0.y)
|
39
|
+
|
40
|
+
#next vertex
|
41
|
+
p2= Point.new(-100.33100481371673,44.925766039368476)
|
42
|
+
#next edge
|
43
|
+
v1 = Vector.new(p2.x-p0.x, p2.y-p0.y)
|
44
|
+
|
45
|
+
assert_true v1.cross_product(v0)>0
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_negative
|
49
|
+
assert(-1 === Vector.new(0, 1).cross_product(Vector.new(1, 0)))
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_negative_for_latlon
|
53
|
+
p0= Point.new(-81.4101562500034,35.021556836103684)
|
54
|
+
p1=Point.new(-115.86328125000495,34.15328996737699)
|
55
|
+
v0 = Vector.new(p1.x-p0.x, p1.y-p0.y)
|
56
|
+
|
57
|
+
p2= Point.new(-100.3945312500027,32.46399995947513)
|
58
|
+
v1 = Vector.new(p2.x-p0.x, p2.y-p0.y)
|
59
|
+
|
60
|
+
assert_true v1.cross_product(v0)<0
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_negative_for_latlon_2
|
64
|
+
#current vertex
|
65
|
+
p0=Point.new(-103.69099150583577,44.1751011402773)
|
66
|
+
#previous vertex
|
67
|
+
p1= Point.new(-110.50195312500117,46.97322061018078)
|
68
|
+
#current edge
|
69
|
+
v0 = Vector.new(p1.x-p0.x, p1.y-p0.y)
|
70
|
+
|
71
|
+
#next vertex
|
72
|
+
p2= Point.new(-109.95811865270818,42.77494310443354)
|
73
|
+
#next edge
|
74
|
+
v1 = Vector.new(p2.x-p0.x, p2.y-p0.y)
|
75
|
+
|
76
|
+
assert_true v1.cross_product(v0)<0
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_zero
|
80
|
+
assert 0 === Vector.new(1, 1).cross_product(Vector.new(-2, -2))
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_unnormalized
|
84
|
+
assert 4 === Vector.new(1, 1).cross_product(Vector.new(-2, 2))
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'winding-polygon/vector'
|
3
|
+
|
4
|
+
class EqualsTest < Test::Unit::TestCase
|
5
|
+
include WindingPolygon
|
6
|
+
|
7
|
+
def test_equal
|
8
|
+
assert_equal Vector.new(1, 3), Vector.new(1, 3)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_not_equal
|
12
|
+
assert_not_equal Vector.new(1, 3), Vector.new(1, 2)
|
13
|
+
assert_not_equal Vector.new(1, 2), Vector.new(0, 2)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'winding-polygon/vector'
|
3
|
+
|
4
|
+
class ModulusTest < Test::Unit::TestCase
|
5
|
+
include WindingPolygon
|
6
|
+
|
7
|
+
def test_parallel_to_axis
|
8
|
+
assert 1 === Vector.new(1, 0).modulus
|
9
|
+
assert 1 === Vector.new(0, 1).modulus
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_inclined
|
13
|
+
assert Math.sqrt(2) === Vector.new(1, 1).modulus
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'winding-polygon/vector'
|
3
|
+
|
4
|
+
class ScalarProductTest < Test::Unit::TestCase
|
5
|
+
include WindingPolygon
|
6
|
+
|
7
|
+
def test_vectors_are_perpendicular
|
8
|
+
assert 0 === Vector.new(1, 1).scalar_product(Vector.new(-1, 1))
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_vectors_are_collinear
|
12
|
+
assert(-4 === Vector.new(1, 1).scalar_product(Vector.new(-2, -2)))
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_vectors_are_inclined
|
16
|
+
assert 1 === Vector.new(1, 1).scalar_product(Vector.new(0, 1))
|
17
|
+
end
|
18
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: winding-polygon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Martin Xu
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-06-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -28,31 +28,31 @@ dependencies:
|
|
28
28
|
name: test-unit
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - '>='
|
31
|
+
- - ! '>='
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - '>='
|
38
|
+
- - ! '>='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - '>='
|
45
|
+
- - ! '>='
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - '>='
|
52
|
+
- - ! '>='
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
-
description: 'Use Bentley-Ottmann algorithm to solve self-intersecting polygon issue '
|
55
|
+
description: ! 'Use Bentley-Ottmann algorithm to solve self-intersecting polygon issue '
|
56
56
|
email:
|
57
57
|
- mxu2008@gmail.com
|
58
58
|
executables: []
|
@@ -72,6 +72,7 @@ files:
|
|
72
72
|
- lib/winding-polygon/polygon.rb
|
73
73
|
- lib/winding-polygon/segment.rb
|
74
74
|
- lib/winding-polygon/sweep_line.rb
|
75
|
+
- lib/winding-polygon/vector.rb
|
75
76
|
- lib/winding-polygon/version.rb
|
76
77
|
- spec/event_queue_spec.rb
|
77
78
|
- spec/point_spec.rb
|
@@ -81,6 +82,12 @@ files:
|
|
81
82
|
- spec/sweep_line_spec.rb
|
82
83
|
- spec/winding-polygon_spec.rb
|
83
84
|
- test/test_avltree.rb
|
85
|
+
- test/vector/arithmetics_test.rb
|
86
|
+
- test/vector/collinear_with_test.rb
|
87
|
+
- test/vector/cross_product_test.rb
|
88
|
+
- test/vector/equals_test.rb
|
89
|
+
- test/vector/modulus_test.rb
|
90
|
+
- test/vector/scalar_product_test.rb
|
84
91
|
- winding-polygon.gemspec
|
85
92
|
homepage: ''
|
86
93
|
licenses:
|
@@ -92,17 +99,17 @@ require_paths:
|
|
92
99
|
- lib
|
93
100
|
required_ruby_version: !ruby/object:Gem::Requirement
|
94
101
|
requirements:
|
95
|
-
- - '>='
|
102
|
+
- - ! '>='
|
96
103
|
- !ruby/object:Gem::Version
|
97
104
|
version: '0'
|
98
105
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
106
|
requirements:
|
100
|
-
- - '>='
|
107
|
+
- - ! '>='
|
101
108
|
- !ruby/object:Gem::Version
|
102
109
|
version: '0'
|
103
110
|
requirements: []
|
104
111
|
rubyforge_project:
|
105
|
-
rubygems_version: 2.0.
|
112
|
+
rubygems_version: 2.0.3
|
106
113
|
signing_key:
|
107
114
|
specification_version: 4
|
108
115
|
summary: Detect intersecting points and decompose it into multi-polygons
|
@@ -115,3 +122,9 @@ test_files:
|
|
115
122
|
- spec/sweep_line_spec.rb
|
116
123
|
- spec/winding-polygon_spec.rb
|
117
124
|
- test/test_avltree.rb
|
125
|
+
- test/vector/arithmetics_test.rb
|
126
|
+
- test/vector/collinear_with_test.rb
|
127
|
+
- test/vector/cross_product_test.rb
|
128
|
+
- test/vector/equals_test.rb
|
129
|
+
- test/vector/modulus_test.rb
|
130
|
+
- test/vector/scalar_product_test.rb
|