contrek 1.0.0
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 +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +84 -0
- data/LICENSE.md +9 -0
- data/README.md +118 -0
- data/Rakefile +19 -0
- data/contrek.gemspec +23 -0
- data/contrek.png +0 -0
- data/ext/cpp_polygon_finder/PolygonFinder/.cproject +136 -0
- data/ext/cpp_polygon_finder/PolygonFinder/.project +27 -0
- data/ext/cpp_polygon_finder/PolygonFinder/.settings/org.eclipse.ltk.core.refactoring.prefs +2 -0
- data/ext/cpp_polygon_finder/PolygonFinder/images/labyrinth.png +0 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/Main.cpp +41 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +69 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.h +19 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/Bitmap.cpp +52 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/Bitmap.h +32 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/FastPngBitmap.cpp +656 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/FastPngBitmap.h +42 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/PngBitmap.cpp +48 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/PngBitmap.h +32 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RemoteFastPngBitmap.cpp +30 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RemoteFastPngBitmap.h +26 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/X_picopng.cpp +576 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/List.cpp +120 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/List.h +40 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Lists.cpp +36 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Lists.h +30 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.cpp +111 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.h +80 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.cpp +325 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.h +59 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.cpp +206 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +69 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/optionparser.h +2858 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/matchers/Matcher.cpp +23 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/matchers/Matcher.h +23 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/matchers/RGBMatcher.cpp +20 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/matchers/RGBMatcher.h +23 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/matchers/RGBNotMatcher.cpp +20 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/matchers/RGBNotMatcher.h +23 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/matchers/ValueNotMatcher.cpp +20 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/matchers/ValueNotMatcher.h +21 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/LinearReducer.cpp +40 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/LinearReducer.h +23 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/Reducer.cpp +19 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/Reducer.h +25 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/UniqReducer.cpp +30 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/UniqReducer.h +21 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/VisvalingamReducer.cpp +50 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/VisvalingamReducer.h +121 -0
- data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +260 -0
- data/ext/cpp_polygon_finder/extconf.rb +2 -0
- data/lib/contrek/bitmaps/bitmap.rb +21 -0
- data/lib/contrek/bitmaps/chunky_bitmap.rb +33 -0
- data/lib/contrek/bitmaps/painting.rb +40 -0
- data/lib/contrek/bitmaps/png_bitmap.rb +62 -0
- data/lib/contrek/bitmaps/rgb_color.rb +25 -0
- data/lib/contrek/finder/list.rb +132 -0
- data/lib/contrek/finder/list_entry.rb +11 -0
- data/lib/contrek/finder/listable.rb +8 -0
- data/lib/contrek/finder/lists.rb +25 -0
- data/lib/contrek/finder/node.rb +126 -0
- data/lib/contrek/finder/node_cluster.rb +294 -0
- data/lib/contrek/finder/polygon_finder.rb +121 -0
- data/lib/contrek/map/mercator_projection.rb +76 -0
- data/lib/contrek/matchers/matcher.rb +20 -0
- data/lib/contrek/matchers/matcher_hsb.rb +24 -0
- data/lib/contrek/matchers/value_not_matcher.rb +9 -0
- data/lib/contrek/reducers/linear_reducer.rb +25 -0
- data/lib/contrek/reducers/reducer.rb +14 -0
- data/lib/contrek/reducers/uniq_reducer.rb +9 -0
- data/lib/contrek/reducers/visvalingam_reducer.rb +139 -0
- data/lib/contrek/version.rb +3 -0
- data/lib/contrek.rb +58 -0
- metadata +175 -0
@@ -0,0 +1,126 @@
|
|
1
|
+
module Contrek
|
2
|
+
module Finder
|
3
|
+
class Node
|
4
|
+
include Listable
|
5
|
+
attr_reader :min_x, :max_x, :y, :name, :tangs, :tangs_sequence, :tangs_count, :data_pointer
|
6
|
+
attr_accessor :track, :abs_x_index, :outer_index, :inner_index
|
7
|
+
|
8
|
+
T_UP = -1
|
9
|
+
T_DOWN = 1
|
10
|
+
OMAX = 1 << 0
|
11
|
+
OMIN = 1 << 1
|
12
|
+
IMAX = 1 << 2
|
13
|
+
IMIN = 1 << 3
|
14
|
+
OCOMPLETE = OMIN | OMAX
|
15
|
+
|
16
|
+
TURN_MAX = IMAX | OMAX
|
17
|
+
TURN_MIN = IMIN | OMIN
|
18
|
+
|
19
|
+
# 0 = outer
|
20
|
+
# 1 = inner
|
21
|
+
TURNER = [[OMAX, OMIN], [TURN_MAX, TURN_MIN]]
|
22
|
+
|
23
|
+
OUTER = 0
|
24
|
+
INNER = 1
|
25
|
+
|
26
|
+
def initialize(cluster, min_x, max_x, y, name)
|
27
|
+
@name = name
|
28
|
+
@min_x = min_x
|
29
|
+
@max_x = max_x
|
30
|
+
@y = y
|
31
|
+
@tangs = {T_UP => [], T_DOWN => []}
|
32
|
+
@tangs_sequence = nil
|
33
|
+
@tangs_count = 0
|
34
|
+
@track = 0
|
35
|
+
@abs_x_index = 0
|
36
|
+
@data_pointer = cluster.lists.get_data_pointer
|
37
|
+
@up_indexer = 0
|
38
|
+
@down_indexer = 0
|
39
|
+
@outer_index = -1
|
40
|
+
@inner_index = -1
|
41
|
+
cluster.add_node(self)
|
42
|
+
end
|
43
|
+
|
44
|
+
def tangs?(node)
|
45
|
+
@min_x <= node.max_x && node.min_x <= @max_x
|
46
|
+
end
|
47
|
+
|
48
|
+
def my_next(last, versus, mode)
|
49
|
+
last_node_index = if last.y < y
|
50
|
+
last.abs_x_index + @up_indexer
|
51
|
+
else
|
52
|
+
@down_indexer - last.abs_x_index
|
53
|
+
end
|
54
|
+
|
55
|
+
case mode
|
56
|
+
when :outer
|
57
|
+
if versus == :o
|
58
|
+
(last_node_index == tangs_sequence.size - 1) ? last_node_index = 0 : last_node_index += 1
|
59
|
+
else
|
60
|
+
(last_node_index == 0) ? last_node_index = tangs_sequence.size - 1 : last_node_index -= 1
|
61
|
+
end
|
62
|
+
|
63
|
+
when :inner
|
64
|
+
|
65
|
+
if versus == :o
|
66
|
+
(last_node_index == 0) ? last_node_index = tangs_sequence.size - 1 : last_node_index -= 1
|
67
|
+
else
|
68
|
+
(last_node_index == tangs_sequence.size - 1) ? last_node_index = 0 : last_node_index += 1
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
tangs_sequence.at(last_node_index)
|
73
|
+
end
|
74
|
+
|
75
|
+
def coords_entering_to(enter_to, enter_mode, tracking)
|
76
|
+
enter_to_index = if enter_to.y < y
|
77
|
+
enter_to.abs_x_index + @up_indexer
|
78
|
+
else
|
79
|
+
@down_indexer - enter_to.abs_x_index
|
80
|
+
end
|
81
|
+
ds = tangs_sequence[enter_to_index]
|
82
|
+
coords_source = ds.send(enter_mode)
|
83
|
+
enter_to.track |= TURNER[tracking][coords_source[:m] - 1]
|
84
|
+
coords_source[:point]
|
85
|
+
end
|
86
|
+
|
87
|
+
def tangs_with_x?(x)
|
88
|
+
x.between?(@min_x, @max_x)
|
89
|
+
end
|
90
|
+
|
91
|
+
def track_uncomplete
|
92
|
+
(@track & OCOMPLETE) != OCOMPLETE
|
93
|
+
end
|
94
|
+
|
95
|
+
def track_complete
|
96
|
+
(@track & OCOMPLETE) == OCOMPLETE
|
97
|
+
end
|
98
|
+
|
99
|
+
def add_intersection(other_node)
|
100
|
+
if other_node.y < y
|
101
|
+
@tangs[T_UP] << other_node
|
102
|
+
else
|
103
|
+
@tangs[T_DOWN] << other_node
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def precalc_tangs_sequences
|
108
|
+
@tangs_sequence = Array.new(tangs[T_UP].size + tangs[T_DOWN].size)
|
109
|
+
tangs = self.tangs[T_UP].sort_by(&:min_x)
|
110
|
+
n = -1
|
111
|
+
@up_indexer = -tangs[0].abs_x_index if tangs.size > 0
|
112
|
+
tangs.each do |t_node|
|
113
|
+
nd = Contrek::Finder::PolygonFinder::NodeDescriptor.new(t_node, {point: {x: t_node.max_x, y: t_node.y}, m: OMAX}, {point: {x: t_node.min_x, y: t_node.y}, m: OMIN})
|
114
|
+
tangs_sequence[n += 1] = nd
|
115
|
+
end
|
116
|
+
tangs = self.tangs[T_DOWN].sort_by(&:min_x).reverse
|
117
|
+
@down_indexer = (tangs.last.abs_x_index + self.tangs[T_DOWN].size + self.tangs[T_UP].size - 1) if tangs.size > 0
|
118
|
+
tangs.each do |t_node|
|
119
|
+
nd = Contrek::Finder::PolygonFinder::NodeDescriptor.new(t_node, {point: {x: t_node.min_x, y: t_node.y}, m: OMIN}, {point: {x: t_node.max_x, y: t_node.y}, m: OMAX})
|
120
|
+
tangs_sequence[n += 1] = nd
|
121
|
+
end
|
122
|
+
@tangs_count = tangs_sequence.size
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,294 @@
|
|
1
|
+
module Contrek
|
2
|
+
module Finder
|
3
|
+
class NodeCluster
|
4
|
+
attr_reader :root_nodes, :sequences, :polygons, :lists, :treemap, :vert_nodes
|
5
|
+
VERSUS_INVERTER = {a: :o, o: :a}
|
6
|
+
|
7
|
+
def initialize(h, options)
|
8
|
+
@options = options
|
9
|
+
@vert_nodes = Array.new(h) { [] } # per y immetto i nodi
|
10
|
+
@sequences = []
|
11
|
+
@polygons = []
|
12
|
+
@treemap = []
|
13
|
+
@nodes = 0
|
14
|
+
@lists = Contrek::Finder::Lists.new
|
15
|
+
@root_nodes = @lists.add_list
|
16
|
+
@inner_plot = @lists.add_list
|
17
|
+
@inner_new = @lists.add_list
|
18
|
+
end
|
19
|
+
|
20
|
+
def path_sequences
|
21
|
+
@polygons.compact.each do |polygon|
|
22
|
+
yield polygon[:outer]
|
23
|
+
polygon[:inner].each do |sequence|
|
24
|
+
yield sequence
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def compress_coords
|
30
|
+
path_sequences do |seq|
|
31
|
+
Contrek::Reducers::UniqReducer.new(points: seq).reduce! if @options[:compress].has_key?(:uniq)
|
32
|
+
Contrek::Reducers::LinearReducer.new(points: seq, options: @options[:compress][:linear]).reduce! if @options[:compress].has_key?(:linear)
|
33
|
+
Contrek::Reducers::VisvalingamReducer.new(points: seq, options: @options[:compress][:visvalingam]).reduce! if @options[:compress].has_key?(:visvalingam)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# nominal sequence
|
38
|
+
def named_sequence
|
39
|
+
@plot_sequence.map(&:name).join
|
40
|
+
end
|
41
|
+
|
42
|
+
# builds node sequences (with space coordinates) array scanning upper and lower element
|
43
|
+
# needs root_nodes ready
|
44
|
+
def build_tangs_sequence
|
45
|
+
@vert_nodes.each do |line|
|
46
|
+
line.each do |node|
|
47
|
+
node.precalc_tangs_sequences
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def plot(bitmap)
|
53
|
+
versus = @options[:versus]
|
54
|
+
inner_v = VERSUS_INVERTER[versus]
|
55
|
+
index_order = 0
|
56
|
+
while @root_nodes.size > 0
|
57
|
+
root_node = @root_nodes.shift
|
58
|
+
root_node.outer_index = index_order
|
59
|
+
@plot_sequence = []
|
60
|
+
@sequence_coords = []
|
61
|
+
# external polygon
|
62
|
+
@plot_sequence << root_node
|
63
|
+
next_node_nd = if versus == :a
|
64
|
+
root_node.tangs_sequence.last
|
65
|
+
else
|
66
|
+
root_node.tangs_sequence.first
|
67
|
+
end
|
68
|
+
#---------------
|
69
|
+
if !next_node_nd.nil?
|
70
|
+
next_node = next_node_nd.node
|
71
|
+
@sequence_coords << next_node.coords_entering_to(root_node, VERSUS_INVERTER[versus], Contrek::Finder::Node::OUTER)
|
72
|
+
end
|
73
|
+
#---------------
|
74
|
+
plot_node(next_node, root_node, versus) if @nodes > 0 && !next_node_nd.nil?
|
75
|
+
|
76
|
+
draw_sequence(bitmap, "X") unless bitmap.nil?
|
77
|
+
@polygons << {outer: @sequence_coords, inner: []} if @sequence_coords.size >= 2
|
78
|
+
@sequences << @plot_sequence
|
79
|
+
|
80
|
+
@count = 0
|
81
|
+
index_inner = 0
|
82
|
+
while @inner_plot.size > 0
|
83
|
+
@plot_sequence = []
|
84
|
+
@sequence_coords = []
|
85
|
+
# mia test
|
86
|
+
first = @inner_plot.find { |x| x.tangs_count <= 2 } || @inner_plot.first
|
87
|
+
|
88
|
+
@plot_sequence << first
|
89
|
+
@inner_plot.delete(first)
|
90
|
+
@root_nodes.delete(first)
|
91
|
+
|
92
|
+
first.inner_index = index_inner
|
93
|
+
|
94
|
+
# @count += 1
|
95
|
+
# if @count > 10000
|
96
|
+
# puts "Houston, we have a problem!"
|
97
|
+
# break
|
98
|
+
# end
|
99
|
+
|
100
|
+
next_node = if (first.track & Contrek::Finder::Node::OMAX) != 0
|
101
|
+
(inner_v == :a) ? first.tangs[Contrek::Finder::Node::T_UP].first : first.tangs[Contrek::Finder::Node::T_DOWN].first
|
102
|
+
else
|
103
|
+
(inner_v == :a) ? first.tangs[Contrek::Finder::Node::T_DOWN].last : first.tangs[Contrek::Finder::Node::T_UP].last
|
104
|
+
end
|
105
|
+
|
106
|
+
if !next_node.nil?
|
107
|
+
@sequence_coords << next_node.coords_entering_to(first, inner_v, Contrek::Finder::Node::INNER)
|
108
|
+
end
|
109
|
+
|
110
|
+
plot_inner_node(next_node, inner_v, first, root_node) if !next_node.nil?
|
111
|
+
|
112
|
+
draw_sequence(bitmap, "+") unless bitmap.nil?
|
113
|
+
|
114
|
+
@polygons.last[:inner] << @sequence_coords
|
115
|
+
|
116
|
+
@inner_plot.grab(@inner_new)
|
117
|
+
index_inner += 1
|
118
|
+
end
|
119
|
+
# tree
|
120
|
+
@treemap << ((versus == :a) ? test_in_hole_a(root_node) : test_in_hole_o(root_node)) if @options.has_key?(:treemap)
|
121
|
+
index_order += 1
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_in_hole_a(node)
|
126
|
+
if node.outer_index > 0
|
127
|
+
start_left = node.abs_x_index - 1
|
128
|
+
loop do
|
129
|
+
prev = @vert_nodes[node.y][start_left]
|
130
|
+
if ((cindex = prev.outer_index) < node.outer_index) && ((prev.track & Contrek::Finder::Node::IMAX) != 0)
|
131
|
+
start_right = node.abs_x_index
|
132
|
+
while (start_right += 1) != @vert_nodes[node.y].size
|
133
|
+
tnext = @vert_nodes[node.y][start_right]
|
134
|
+
if tnext.outer_index == cindex
|
135
|
+
if (tnext.track & Contrek::Finder::Node::IMIN) != 0
|
136
|
+
return [cindex, prev.inner_index]
|
137
|
+
else
|
138
|
+
return [-1, -1]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
break if (start_left -= 1) < 0
|
144
|
+
end
|
145
|
+
end
|
146
|
+
[-1, -1]
|
147
|
+
end
|
148
|
+
|
149
|
+
def test_in_hole_o(node)
|
150
|
+
if node.outer_index > 0 && @vert_nodes[node.y].last != node
|
151
|
+
start_left = node.abs_x_index + 1
|
152
|
+
loop do
|
153
|
+
prev = @vert_nodes[node.y][start_left]
|
154
|
+
if ((cindex = prev.outer_index) < node.outer_index) && ((prev.track & Contrek::Finder::Node::IMIN) != 0)
|
155
|
+
start_right = node.abs_x_index
|
156
|
+
while (start_right -= 1) >= 0
|
157
|
+
tnext = @vert_nodes[node.y][start_right]
|
158
|
+
if tnext.outer_index == cindex
|
159
|
+
if (tnext.track & Contrek::Finder::Node::IMAX) != 0
|
160
|
+
return [cindex, prev.inner_index]
|
161
|
+
else
|
162
|
+
return [-1, -1]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
break if (start_left += 1) == @vert_nodes[node.y].size
|
168
|
+
end
|
169
|
+
end
|
170
|
+
[-1, -1]
|
171
|
+
end
|
172
|
+
|
173
|
+
def draw_sequence(bitmap, val = nil)
|
174
|
+
count = 1
|
175
|
+
@sequence_coords.each do |coords|
|
176
|
+
bitmap.value_set(coords[:x], coords[:y], val.nil? ? count.alph : val)
|
177
|
+
count += 1
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# inner way
|
182
|
+
# nodes in @plot_sequence
|
183
|
+
# coordinates in @sequence_coords
|
184
|
+
def plot_inner_node(node, versus, stop_at, start_node)
|
185
|
+
node.outer_index = start_node.outer_index
|
186
|
+
node.inner_index = stop_at.inner_index
|
187
|
+
@root_nodes.delete(node)
|
188
|
+
@inner_plot.delete(node)
|
189
|
+
last_node = @plot_sequence.last
|
190
|
+
next_node_nd = node.my_next(last_node, versus, :inner)
|
191
|
+
next_node = next_node_nd.node
|
192
|
+
@plot_sequence << node
|
193
|
+
|
194
|
+
plot = true
|
195
|
+
if next_node.y == last_node.y
|
196
|
+
plot = (node.tangs_sequence.send((versus == :a) ? :first : :last).node == next_node)
|
197
|
+
end
|
198
|
+
if plot
|
199
|
+
@sequence_coords << last_node.coords_entering_to(node, VERSUS_INVERTER[versus], Contrek::Finder::Node::INNER)
|
200
|
+
if node != start_node
|
201
|
+
if last_node.y == next_node.y
|
202
|
+
@sequence_coords << next_node.coords_entering_to(node, versus, Contrek::Finder::Node::INNER)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
if node.track_uncomplete
|
208
|
+
@inner_new << node
|
209
|
+
else
|
210
|
+
@inner_new.delete(node)
|
211
|
+
end
|
212
|
+
|
213
|
+
return if next_node == stop_at
|
214
|
+
plot_inner_node(next_node, versus, stop_at, start_node)
|
215
|
+
end
|
216
|
+
|
217
|
+
# contour tracing core logic loop
|
218
|
+
def plot_node(node, start_node, versus = :a)
|
219
|
+
@root_nodes.delete(node)
|
220
|
+
|
221
|
+
node.outer_index = start_node.outer_index
|
222
|
+
last_node = @plot_sequence.last
|
223
|
+
next_node_nd = node.my_next(last_node, versus, :outer)
|
224
|
+
next_node = next_node_nd.node
|
225
|
+
|
226
|
+
@plot_sequence << node
|
227
|
+
|
228
|
+
plot = true
|
229
|
+
if next_node.y == last_node.y
|
230
|
+
plot = (node.tangs_sequence.send((versus == :a) ? :last : :first).node == next_node)
|
231
|
+
end
|
232
|
+
|
233
|
+
# coord
|
234
|
+
if plot
|
235
|
+
@sequence_coords << last_node.coords_entering_to(node, versus, Contrek::Finder::Node::OUTER)
|
236
|
+
|
237
|
+
if node != start_node
|
238
|
+
@inner_plot.contains(node) ? @inner_plot.delete(node) : @inner_plot << node
|
239
|
+
if last_node.y == next_node.y
|
240
|
+
@sequence_coords << next_node.coords_entering_to(node, VERSUS_INVERTER[versus], Contrek::Finder::Node::OUTER)
|
241
|
+
@inner_plot.contains(node) ? @inner_plot.delete(node) : @inner_plot << node
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
# exit if root_node
|
246
|
+
|
247
|
+
if node == start_node
|
248
|
+
return if node.track_complete
|
249
|
+
end
|
250
|
+
plot_node(next_node, start_node, versus)
|
251
|
+
end
|
252
|
+
|
253
|
+
def add_node(node)
|
254
|
+
@nodes += 1
|
255
|
+
node.abs_x_index = @vert_nodes[node.y].size
|
256
|
+
|
257
|
+
@vert_nodes[node.y] << node
|
258
|
+
@root_nodes << node
|
259
|
+
|
260
|
+
if node.y > 0
|
261
|
+
# all nodes untle up_node.max_x >= node.min_x
|
262
|
+
up_nodes = @vert_nodes[node.y - 1]
|
263
|
+
up_nodes_count = up_nodes.size
|
264
|
+
if up_nodes_count > 0
|
265
|
+
index = 0
|
266
|
+
loop do
|
267
|
+
up_node = up_nodes[index]
|
268
|
+
if up_node.max_x >= node.min_x
|
269
|
+
if up_node.min_x <= node.max_x
|
270
|
+
node.add_intersection(up_node)
|
271
|
+
up_node.add_intersection(node)
|
272
|
+
end
|
273
|
+
return if (index += 1) == up_nodes_count
|
274
|
+
loop do
|
275
|
+
up_node = up_nodes[index]
|
276
|
+
if up_node.min_x <= node.max_x
|
277
|
+
node.add_intersection(up_node)
|
278
|
+
up_node.add_intersection(node)
|
279
|
+
else
|
280
|
+
return
|
281
|
+
end
|
282
|
+
break if (index += 1) == up_nodes_count
|
283
|
+
end
|
284
|
+
return
|
285
|
+
end
|
286
|
+
break if (index += 1) == up_nodes_count
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require "benchmark"
|
2
|
+
# todo
|
3
|
+
# - builtin png decoder which makes blocks to pass vert_nodes
|
4
|
+
# - work only in clockwise (no more :o e :a); finally for coords use push_back on :a and
|
5
|
+
# push_front on :o
|
6
|
+
module Contrek
|
7
|
+
module Finder
|
8
|
+
class PolygonFinder
|
9
|
+
NodeDescriptor = Struct.new(:node, :a, :o)
|
10
|
+
def initialize(bitmap, matcher, test_bitmap = nil, options = {})
|
11
|
+
@options = {versus: :a}.merge(options)
|
12
|
+
sanitize_options
|
13
|
+
@source_bitmap = bitmap
|
14
|
+
@matcher = matcher
|
15
|
+
|
16
|
+
@test_bitmap = test_bitmap
|
17
|
+
@node_cluster = NodeCluster.new(@source_bitmap.h, @options)
|
18
|
+
@reports = {}
|
19
|
+
|
20
|
+
# 1 finds matching blocks
|
21
|
+
@reports[:scan] = Benchmark.measure do
|
22
|
+
scan
|
23
|
+
end
|
24
|
+
|
25
|
+
# 2 builds relational spatially tree map
|
26
|
+
@reports[:build_tangs_sequence] = Benchmark.measure do
|
27
|
+
@node_cluster.build_tangs_sequence
|
28
|
+
end
|
29
|
+
|
30
|
+
# 3 plotting
|
31
|
+
@reports[:plot] = Benchmark.measure do
|
32
|
+
@node_cluster.plot(@test_bitmap)
|
33
|
+
end
|
34
|
+
|
35
|
+
# 4 compress
|
36
|
+
@reports[:compress] = Benchmark.measure do
|
37
|
+
if @options.has_key?(:compress)
|
38
|
+
@node_cluster.compress_coords
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def sanitize_options
|
44
|
+
@options[:versus] = :a unless @options[:versus] == :a || @options[:versus] == :o
|
45
|
+
end
|
46
|
+
|
47
|
+
# infos
|
48
|
+
def process_info
|
49
|
+
{named_sequence: @node_cluster.sequences.map { |list| list.map(&:name).join }.join("-"),
|
50
|
+
groups: @node_cluster.sequences.size,
|
51
|
+
groups_names: @node_cluster.root_nodes.map(&:name).join,
|
52
|
+
polygons: @node_cluster.polygons,
|
53
|
+
benchmarks: format_benchmarks,
|
54
|
+
treemap: (@node_cluster.treemap if @options.has_key?(:treemap))}
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_shapelines
|
58
|
+
shapes = []
|
59
|
+
@node_cluster.vert_nodes.each do |line|
|
60
|
+
line.each do |node|
|
61
|
+
shapes << {start_x: node.min_x, end_x: node.max_x, y: node.y}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
shapes
|
65
|
+
end
|
66
|
+
|
67
|
+
def draw_polygons(png_image)
|
68
|
+
Contrek::Bitmaps::Painting.direct_draw_polygons(@node_cluster.polygons, png_image)
|
69
|
+
end
|
70
|
+
|
71
|
+
def draw_shapelines(png_image)
|
72
|
+
slines = get_shapelines
|
73
|
+
color = ChunkyPNG::Color("blue @ 1.0")
|
74
|
+
slines.each do |sline|
|
75
|
+
png_image.draw_line(sline[:start_x], sline[:y], sline[:end_x], sline[:y], color)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
# image scan
|
82
|
+
def scan
|
83
|
+
last_color = nil
|
84
|
+
matching = false
|
85
|
+
min_x = 0
|
86
|
+
max_x = 0
|
87
|
+
@source_bitmap.scan do |x, y, color|
|
88
|
+
if @matcher.match?(color) && matching == false
|
89
|
+
min_x = x
|
90
|
+
last_color = color
|
91
|
+
matching = true
|
92
|
+
if x == (@source_bitmap.w - 1)
|
93
|
+
max_x = x
|
94
|
+
Contrek::Finder::Node.new(@node_cluster, min_x, max_x, y, last_color)
|
95
|
+
matching = false
|
96
|
+
end
|
97
|
+
elsif @matcher.unmatch?(color) && matching == true
|
98
|
+
max_x = x - 1
|
99
|
+
Contrek::Finder::Node.new(@node_cluster, min_x, max_x, y, last_color)
|
100
|
+
matching = false
|
101
|
+
elsif x == (@source_bitmap.w - 1) && matching == true
|
102
|
+
max_x = x
|
103
|
+
Contrek::Finder::Node.new(@node_cluster, min_x, max_x, y, last_color)
|
104
|
+
matching = false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def format_benchmarks
|
110
|
+
r = {}
|
111
|
+
total = 0
|
112
|
+
@reports.each do |k, v|
|
113
|
+
r[k] = (v.real * 1000).round(3)
|
114
|
+
total += v.real * 1000
|
115
|
+
end
|
116
|
+
r[:total] = total.round(3)
|
117
|
+
r
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Map
|
2
|
+
class MercatorProjection
|
3
|
+
MERCATOR_RANGE = 256
|
4
|
+
|
5
|
+
class LatLng
|
6
|
+
attr_reader :lat, :lng
|
7
|
+
def initialize(lat:, lng:)
|
8
|
+
@lat = lat
|
9
|
+
@lng = lng
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Point
|
14
|
+
attr_accessor :x, :y
|
15
|
+
def initialize(x: 0, y: 0)
|
16
|
+
@x = x
|
17
|
+
@y = y
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@pixel_origin = Point.new(x: MERCATOR_RANGE / 2, y: MERCATOR_RANGE / 2)
|
23
|
+
@pixels_per_lon_degree = MERCATOR_RANGE.to_f / 360
|
24
|
+
@pixels_per_lon_radian = MERCATOR_RANGE.to_f / (2 * Math::PI)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.get_corners(center, zoom, map_width, map_height)
|
28
|
+
scale = 2**zoom
|
29
|
+
proj = Map::MercatorProjection.new
|
30
|
+
center_px = proj.from_lat_lng_to_point(lat_lng: center)
|
31
|
+
sw_point = Point.new(x: center_px.x - ((map_width / 2).to_f / scale), y: center_px.y + ((map_height / 2).to_f / scale))
|
32
|
+
sw_lat_lon = proj.from_point_to_lat_lng(sw_point)
|
33
|
+
ne_point = Point.new(x: center_px.x + ((map_width / 2).to_f / scale), y: center_px.y - ((map_height / 2).to_f / scale))
|
34
|
+
ne_lat_lon = proj.from_point_to_lat_lng(ne_point)
|
35
|
+
{
|
36
|
+
N: ne_lat_lon.lat,
|
37
|
+
E: ne_lat_lon.lng,
|
38
|
+
S: sw_lat_lon.lat,
|
39
|
+
W: sw_lat_lon.lng
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def from_lat_lng_to_point(lat_lng:, opt_point: nil)
|
44
|
+
point = opt_point.nil? ? Point.new(x: 0, y: 0) : opt_point
|
45
|
+
origin = @pixel_origin
|
46
|
+
point.x = origin.x + (lat_lng.lng * @pixels_per_lon_degree)
|
47
|
+
siny = bound(Math.sin(degreesToRadians(lat_lng.lat)), -0.9999, 0.9999)
|
48
|
+
point.y = origin.y + (0.5 * Math.log((1 + siny) / (1 - siny)) * -@pixels_per_lon_radian)
|
49
|
+
point
|
50
|
+
end
|
51
|
+
|
52
|
+
def from_point_to_lat_lng(point)
|
53
|
+
origin = @pixel_origin
|
54
|
+
lng = (point.x - origin.x) / @pixels_per_lon_degree
|
55
|
+
lat_radians = (point.y - origin.y) / -@pixels_per_lon_radian
|
56
|
+
lat = radiansToDegrees(2 * Math.atan(Math.exp(lat_radians)) - (Math::PI / 2))
|
57
|
+
LatLng.new(lat: lat, lng: lng)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def degreesToRadians(deg)
|
63
|
+
deg * (Math::PI / 180)
|
64
|
+
end
|
65
|
+
|
66
|
+
def radiansToDegrees(rad)
|
67
|
+
rad / (Math::PI / 180)
|
68
|
+
end
|
69
|
+
|
70
|
+
def bound(value, opt_min, opt_max)
|
71
|
+
value = [value, opt_min].max if !opt_min.nil?
|
72
|
+
value = [value, opt_max].min if !opt_max.nil?
|
73
|
+
value
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Contrek
|
2
|
+
module Matchers
|
3
|
+
class Matcher
|
4
|
+
attr_reader :values, :counters
|
5
|
+
def initialize(value)
|
6
|
+
@value_is = value
|
7
|
+
@values = []
|
8
|
+
@counters = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def match?(value)
|
12
|
+
@value_is == value
|
13
|
+
end
|
14
|
+
|
15
|
+
def unmatch?(value)
|
16
|
+
!match?(value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Contrek
|
2
|
+
module Matchers
|
3
|
+
class MatcherHsb < Matcher
|
4
|
+
def match?(value)
|
5
|
+
if @values.index(value).nil?
|
6
|
+
@values << value
|
7
|
+
@counters[value] = {count: 1, match: 0}
|
8
|
+
else
|
9
|
+
@counters[value][:count] += 1
|
10
|
+
end
|
11
|
+
|
12
|
+
match = value[0].between?(@value_is[:h][:min], @value_is[:h][:max]) &&
|
13
|
+
value[1].between?(@value_is[:s][:min], @value_is[:s][:max]) &&
|
14
|
+
value[2].between?(@value_is[:b][:min], @value_is[:b][:max])
|
15
|
+
|
16
|
+
if match
|
17
|
+
@counters[value][:match] += 1
|
18
|
+
end
|
19
|
+
|
20
|
+
match
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Contrek
|
2
|
+
module Reducers
|
3
|
+
class LinearReducer < Reducer
|
4
|
+
def reduce!
|
5
|
+
start_p = @points[0]
|
6
|
+
end_p = @points[1]
|
7
|
+
dir = seq_dir(start_p, end_p)
|
8
|
+
@points[2..].map.with_index do |point, i|
|
9
|
+
if (act_seq = seq_dir(end_p, point)) == dir
|
10
|
+
@points.delete_at(@points.index(end_p))
|
11
|
+
else
|
12
|
+
dir = act_seq
|
13
|
+
end
|
14
|
+
end_p = point
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def seq_dir(a, b)
|
21
|
+
[b[:x] - a[:x], b[:y] - a[:y]]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|