winding-polygon 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|