contrek 1.3.2 → 1.3.4
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/CHANGELOG.md +6 -0
- data/Gemfile.lock +3 -1
- data/README.md +28 -28
- data/contrek.gemspec +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/ContrekApi.h +2 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +2 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/FinderUtils.cpp +11 -5
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.cpp +8 -10
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.h +4 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.cpp +114 -42
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.h +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +2 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.cpp +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +66 -116
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.h +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/GeoJsonStreamingMerger.h +6 -7
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.cpp +2 -87
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.h +3 -13
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/PartPool.cpp +0 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.cpp +1 -57
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.h +1 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Queueable.h +31 -17
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Sequence.cpp +0 -16
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Sequence.h +0 -9
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/StreamingMerger.cpp +2 -6
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/StreamingMerger.h +1 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/SvgStreamingMerger.h +14 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.cpp +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/DouglasPeuckerReducer.cpp +177 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/DouglasPeuckerReducer.h +52 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/RasterReducer.cpp +109 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/RasterReducer.h +32 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/VisvalingamReducer.h +6 -1
- data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +20 -2
- data/lib/contrek/bitmaps/painting.rb +27 -10
- data/lib/contrek/bitmaps/png_bitmap.rb +3 -0
- data/lib/contrek/bitmaps/raw_bitmap.rb +1 -0
- data/lib/contrek/bitmaps/rendering.rb +74 -0
- data/lib/contrek/cpp/cpp_geo_json_concurrent_streaming_merger.rb +11 -0
- data/lib/contrek/cpp/{cpp_concurrent_streaming_merger.rb → cpp_svg_concurrent_streaming_merger.rb} +1 -1
- data/lib/contrek/finder/concurrent/cursor.rb +62 -84
- data/lib/contrek/finder/concurrent/finder.rb +2 -1
- data/lib/contrek/finder/concurrent/geo_json_streaming_merger.rb +46 -0
- data/lib/contrek/finder/concurrent/part.rb +4 -77
- data/lib/contrek/finder/concurrent/partitionable.rb +1 -58
- data/lib/contrek/finder/concurrent/queueable.rb +23 -5
- data/lib/contrek/finder/concurrent/sequence.rb +0 -15
- data/lib/contrek/finder/concurrent/streaming_merger.rb +5 -27
- data/lib/contrek/finder/concurrent/svg_streaming_merger.rb +35 -0
- data/lib/contrek/finder/concurrent/tile.rb +1 -1
- data/lib/contrek/finder/node.rb +26 -8
- data/lib/contrek/finder/node_cluster.rb +91 -37
- data/lib/contrek/finder/polygon_finder.rb +1 -1
- data/lib/contrek/reducers/douglas_peucker_reducer.rb +191 -0
- data/lib/contrek/reducers/raster_reducer.rb +72 -0
- data/lib/contrek/version.rb +1 -1
- data/lib/contrek.rb +7 -1
- metadata +27 -3
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Contrek
|
|
4
|
+
module Cpp
|
|
5
|
+
class CPPGeoJsonConcurrentStreamingMerger < CPPGeoJsonStreamingMerger
|
|
6
|
+
def self.new(stream_to:, number_of_threads: 0, options: nil, pixel_val: 0)
|
|
7
|
+
super(number_of_threads, options, stream_to, pixel_val)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
data/lib/contrek/cpp/{cpp_concurrent_streaming_merger.rb → cpp_svg_concurrent_streaming_merger.rb}
RENAMED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Contrek
|
|
4
4
|
module Cpp
|
|
5
|
-
class
|
|
5
|
+
class CPPSvgConcurrentStreamingMerger < CPPSvgStreamingMerger
|
|
6
6
|
def self.new(stream_to:, total_width:, total_height:, number_of_threads: 0, options: nil)
|
|
7
7
|
super(number_of_threads, options, stream_to, total_width, total_height)
|
|
8
8
|
end
|
|
@@ -51,24 +51,15 @@ module Contrek
|
|
|
51
51
|
if part.innerable?
|
|
52
52
|
all_parts = []
|
|
53
53
|
tracked_end_points = []
|
|
54
|
-
|
|
55
|
-
traverse_inner(part, all_parts, bounds, tracked_end_points)
|
|
56
|
-
range_of_bounds = (bounds[:min]..bounds[:max])
|
|
57
|
-
|
|
54
|
+
traverse_inner(part, all_parts, tracked_end_points)
|
|
58
55
|
retme_sequence = Sequence.new
|
|
59
56
|
all_parts.each do |part|
|
|
60
57
|
part.touch!
|
|
61
|
-
retme_sequence.
|
|
62
|
-
next false if part.is?(Part::ADDED) && !(range_of_bounds === position.payload[:y])
|
|
63
|
-
|
|
64
|
-
!(polyline.tile.tg_border?(position.payload) && position.end_point.tracked_outer)
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
if retme_sequence.is_not_vertical
|
|
68
|
-
inner_polyline = InnerPolyline.new(sequence: retme_sequence)
|
|
69
|
-
return_inner_polylines << inner_polyline
|
|
70
|
-
mark_children(tracked_end_points, polyline, inner_polyline) if treemap
|
|
58
|
+
retme_sequence.append(part)
|
|
71
59
|
end
|
|
60
|
+
inner_polyline = InnerPolyline.new(sequence: retme_sequence)
|
|
61
|
+
return_inner_polylines << inner_polyline
|
|
62
|
+
mark_children(tracked_end_points, polyline, inner_polyline) if treemap
|
|
72
63
|
end
|
|
73
64
|
end
|
|
74
65
|
shape_index += 1
|
|
@@ -78,76 +69,55 @@ module Contrek
|
|
|
78
69
|
|
|
79
70
|
private
|
|
80
71
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# rubocop:disable Lint/NonLocalExitFromIterator
|
|
99
|
-
def traverse_outer(act_part, all_parts, shapes_sequence, outer_joined_polyline)
|
|
100
|
-
last_part = all_parts.last
|
|
101
|
-
all_parts << act_part if last_part != act_part
|
|
102
|
-
if act_part.is?(Part::EXCLUSIVE)
|
|
103
|
-
return if act_part.size == 0
|
|
104
|
-
while (position = act_part.next_position)
|
|
105
|
-
outer_joined_polyline.add(position)
|
|
106
|
-
end
|
|
107
|
-
else
|
|
108
|
-
return if act_part.dead_end && all_parts.size > 1 && last_part.is?(Part::SEAM) && last_part.polyline == act_part.polyline
|
|
109
|
-
while (new_position = act_part.iterator)
|
|
110
|
-
return if outer_joined_polyline.size > 1 &&
|
|
111
|
-
outer_joined_polyline.head.payload == new_position.payload &&
|
|
112
|
-
act_part == all_parts.first
|
|
113
|
-
outer_joined_polyline.add(Position.new(position: new_position.payload, hub: @cluster.hub))
|
|
114
|
-
new_position.end_point.tracked_outer = true
|
|
115
|
-
versus = act_part.versus
|
|
116
|
-
part = new_position.end_point.queues.find do |p|
|
|
117
|
-
(p.mirror || act_part.mirror || p.versus == -versus) && p.polyline.tile != act_part.polyline.tile
|
|
72
|
+
def traverse_outer(start_part, all_parts, shapes_sequence, outer_joined_polyline)
|
|
73
|
+
act_part = start_part
|
|
74
|
+
|
|
75
|
+
while act_part
|
|
76
|
+
last_part = all_parts.last
|
|
77
|
+
all_parts << act_part if last_part != act_part
|
|
78
|
+
jumped_to_new_part = false
|
|
79
|
+
if act_part.is?(Part::EXCLUSIVE)
|
|
80
|
+
return if act_part.size == 0
|
|
81
|
+
while (position = act_part.next_position)
|
|
82
|
+
outer_joined_polyline.add(position)
|
|
118
83
|
end
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
84
|
+
else
|
|
85
|
+
while (new_position = act_part.iterator)
|
|
86
|
+
if outer_joined_polyline.size > 1 &&
|
|
87
|
+
outer_joined_polyline.head.payload == new_position.payload &&
|
|
88
|
+
act_part == all_parts.first
|
|
89
|
+
return
|
|
90
|
+
end
|
|
91
|
+
new_position.end_point.tracked_outer = true
|
|
92
|
+
versus = act_part.versus
|
|
93
|
+
part = new_position.end_point.queues.find do |p|
|
|
94
|
+
(p.versus == -versus) && p.polyline.tile != act_part.polyline.tile
|
|
95
|
+
end
|
|
96
|
+
if part
|
|
97
|
+
if all_parts[-2] != part
|
|
98
|
+
if !shapes_sequence.include?(part.polyline.shape)
|
|
99
|
+
shapes_sequence << part.polyline.shape
|
|
100
|
+
end
|
|
128
101
|
part.next_position(new_position)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
102
|
+
act_part = part
|
|
103
|
+
jumped_to_new_part = true
|
|
104
|
+
break
|
|
132
105
|
end
|
|
133
106
|
end
|
|
107
|
+
act_part.next_position
|
|
134
108
|
end
|
|
135
|
-
act_part.next_position
|
|
136
109
|
end
|
|
110
|
+
next if jumped_to_new_part
|
|
111
|
+
next_part = act_part.circular_next
|
|
112
|
+
next_part.rewind!
|
|
113
|
+
act_part = next_part
|
|
137
114
|
end
|
|
138
|
-
next_part = act_part.circular_next
|
|
139
|
-
next_part.rewind!
|
|
140
|
-
traverse_outer(act_part.circular_next, all_parts, shapes_sequence, outer_joined_polyline)
|
|
141
115
|
end
|
|
142
116
|
|
|
143
|
-
|
|
117
|
+
# rubocop:disable Lint/NonLocalExitFromIterator
|
|
118
|
+
def traverse_inner(act_part, all_parts, tracked_end_points)
|
|
144
119
|
return if act_part == all_parts.first
|
|
145
120
|
|
|
146
|
-
if act_part.size > 0
|
|
147
|
-
min_y, max_y = act_part.to_a.minmax_by { |p| p[:y] }
|
|
148
|
-
bounds[:min] = [bounds[:min], min_y[:y]].min
|
|
149
|
-
bounds[:max] = [bounds[:max], max_y[:y]].max
|
|
150
|
-
end
|
|
151
121
|
if act_part.innerable?
|
|
152
122
|
all_parts << act_part
|
|
153
123
|
while (act_part = act_part.circular_next)
|
|
@@ -161,15 +131,6 @@ module Contrek
|
|
|
161
131
|
next if dest_part_versus != 0 && dest_part_versus == act_part.versus
|
|
162
132
|
|
|
163
133
|
tracked_end_points << act_part.head.end_point
|
|
164
|
-
|
|
165
|
-
link_seq = dest_part.continuum_to?(act_part)
|
|
166
|
-
if link_seq.any?
|
|
167
|
-
ins_part = Part.new(Part::ADDED, act_part.polyline)
|
|
168
|
-
link_seq.each do |pos|
|
|
169
|
-
ins_part.add(Position.new(position: nil, hub: nil, known_endpoint: pos))
|
|
170
|
-
end
|
|
171
|
-
all_parts << ins_part
|
|
172
|
-
end
|
|
173
134
|
shape = dest_part.polyline.shape
|
|
174
135
|
if !dest_part.polyline.on?(Polyline::TRACKED_OUTER)
|
|
175
136
|
@shapes_sequence << shape
|
|
@@ -179,7 +140,7 @@ module Contrek
|
|
|
179
140
|
dest_part.polyline.turn_on(Polyline::TRACKED_OUTER)
|
|
180
141
|
if !dest_part.touched
|
|
181
142
|
dest_part.touch!
|
|
182
|
-
traverse_inner(dest_part.circular_next, all_parts,
|
|
143
|
+
traverse_inner(dest_part.circular_next, all_parts, tracked_end_points)
|
|
183
144
|
return
|
|
184
145
|
end
|
|
185
146
|
end
|
|
@@ -188,10 +149,27 @@ module Contrek
|
|
|
188
149
|
end
|
|
189
150
|
end
|
|
190
151
|
elsif act_part.next
|
|
191
|
-
traverse_inner(act_part.next, all_parts,
|
|
152
|
+
traverse_inner(act_part.next, all_parts, tracked_end_points)
|
|
192
153
|
end
|
|
193
154
|
end
|
|
194
155
|
# rubocop:enable Lint/NonLocalExitFromIterator
|
|
156
|
+
|
|
157
|
+
# finds each part (and relative polyline) inscribed between two end_points and sets the
|
|
158
|
+
# founded inner_polyline which be later used to define in which parent hole is placed.
|
|
159
|
+
def mark_children(end_points, outer_polyline, inner_polyline)
|
|
160
|
+
end_points.each_slice(2) do |a, b|
|
|
161
|
+
range = [a.position[:y], b.position[:y]].sort
|
|
162
|
+
(range[0] + 1).upto(range[1] - 1) do |y|
|
|
163
|
+
if (end_point = @cluster.hub.payloads[y])
|
|
164
|
+
end_point.queues.each do |part|
|
|
165
|
+
if part.polyline != outer_polyline
|
|
166
|
+
part.polyline.inside_inner_polyline = inner_polyline
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
195
173
|
end
|
|
196
174
|
end
|
|
197
175
|
end
|
|
@@ -93,7 +93,8 @@ module Contrek
|
|
|
93
93
|
compress: ((compress_time * 1000).round(3) if @options.has_key?(:compress))
|
|
94
94
|
}.compact,
|
|
95
95
|
width: @maximum_width,
|
|
96
|
-
height: @height
|
|
96
|
+
height: @height,
|
|
97
|
+
versus: options[:versus]
|
|
97
98
|
}
|
|
98
99
|
metadata[:treemap] = @whole_tile.compute_treemap if options[:treemap]
|
|
99
100
|
Contrek::Finder::Result.new(raw_polygons, metadata)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Contrek
|
|
4
|
+
module Concurrent
|
|
5
|
+
class GeoJsonStreamingMerger < StreamingMerger
|
|
6
|
+
def initialize(stream_to:, pixel_val:, options: {})
|
|
7
|
+
@pixel_val = pixel_val
|
|
8
|
+
super(stream_to:, options:)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def write_header
|
|
12
|
+
@first_feature = true
|
|
13
|
+
@stream.write("{\"type\":\"FeatureCollection\",\"features\":[")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def write_footer
|
|
17
|
+
@stream.write("]}")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def stream_raw_polygon(shape)
|
|
21
|
+
if @first_feature
|
|
22
|
+
@first_feature = false
|
|
23
|
+
else
|
|
24
|
+
@stream.write(",")
|
|
25
|
+
end
|
|
26
|
+
outer_ring = shape.outer_polyline.raw.map { |p| [p[:y], p[:x]] }
|
|
27
|
+
outer_ring << outer_ring.first if outer_ring.first != outer_ring.last
|
|
28
|
+
polygon_coordinates = [outer_ring]
|
|
29
|
+
shape.inner_polylines.map(&:raw).each do |sequence|
|
|
30
|
+
inner_ring = sequence.map { |p| [p[:y], p[:x]] }
|
|
31
|
+
inner_ring << inner_ring.first if inner_ring.first != inner_ring.last
|
|
32
|
+
polygon_coordinates << inner_ring
|
|
33
|
+
end
|
|
34
|
+
feature_hash = {
|
|
35
|
+
type: "Feature",
|
|
36
|
+
properties: {PixelVal: @pixel_val},
|
|
37
|
+
geometry: {
|
|
38
|
+
type: "Polygon",
|
|
39
|
+
coordinates: polygon_coordinates
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
@stream.write(JSON.generate(feature_hash))
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -7,25 +7,16 @@ module Contrek
|
|
|
7
7
|
|
|
8
8
|
SEAM = 1
|
|
9
9
|
EXCLUSIVE = 0
|
|
10
|
-
ADDED = 2
|
|
11
10
|
|
|
12
11
|
attr_reader :polyline, :touched
|
|
13
|
-
attr_accessor :next, :circular_next, :
|
|
14
|
-
:trasmuted, :versus, :mirror, :next_seam, :transmutation_skip
|
|
12
|
+
attr_accessor :next, :circular_next, :type, :versus
|
|
15
13
|
def initialize(type, polyline)
|
|
16
14
|
@type = type
|
|
17
15
|
@polyline = polyline
|
|
18
16
|
@next = nil
|
|
19
|
-
@next_seam = nil
|
|
20
17
|
@circular_next = nil
|
|
21
|
-
@prev = nil
|
|
22
|
-
@dead_end = false
|
|
23
18
|
@touched = false
|
|
24
|
-
@inverts = false
|
|
25
|
-
@trasmuted = false
|
|
26
19
|
@versus = 0
|
|
27
|
-
@mirror = false
|
|
28
|
-
@transmutation_skip = false
|
|
29
20
|
end
|
|
30
21
|
|
|
31
22
|
def is?(type)
|
|
@@ -57,12 +48,11 @@ module Contrek
|
|
|
57
48
|
|
|
58
49
|
def name
|
|
59
50
|
{Part::EXCLUSIVE => "EXCLUSIVE",
|
|
60
|
-
Part::SEAM => "SEAM"
|
|
61
|
-
Part::ADDED => "ADDED"}[type]
|
|
51
|
+
Part::SEAM => "SEAM"}[type]
|
|
62
52
|
end
|
|
63
53
|
|
|
64
54
|
def inspect
|
|
65
|
-
"part #{polyline.parts.index(self)} (
|
|
55
|
+
"part #{polyline.parts.index(self)} (versus=#{@versus} touched=#{@touched}, #{size}x) of #{polyline.named} (#{name}) (#{to_a.map { |e| "[#{e[:x]},#{e[:y]}]" }.join})"
|
|
66
56
|
end
|
|
67
57
|
|
|
68
58
|
def innerable?
|
|
@@ -70,80 +60,17 @@ module Contrek
|
|
|
70
60
|
end
|
|
71
61
|
|
|
72
62
|
def orient!
|
|
73
|
-
@versus = if size <= 1
|
|
63
|
+
@versus = if size <= 1
|
|
74
64
|
0
|
|
75
65
|
else
|
|
76
66
|
diff = tail.payload[:y] - head.payload[:y]
|
|
77
67
|
if diff == 0
|
|
78
|
-
@mirror = true
|
|
79
68
|
0
|
|
80
69
|
else
|
|
81
70
|
diff.positive? ? 1 : -1
|
|
82
71
|
end
|
|
83
72
|
end
|
|
84
73
|
end
|
|
85
|
-
|
|
86
|
-
def try_transmutation!
|
|
87
|
-
head_queues = head.end_point.queues
|
|
88
|
-
return if head_queues.size == 1
|
|
89
|
-
other_head_part = head_queues.find { |part| part.polyline.tile == polyline.tile && part != self }
|
|
90
|
-
if other_head_part
|
|
91
|
-
tail_queues = tail.end_point.queues
|
|
92
|
-
if tail_queues.find { |part| part == other_head_part }
|
|
93
|
-
if (other_head_part.tail.payload[:y] == tail.payload[:y] && other_head_part.head.payload[:y] == head.payload[:y]) ||
|
|
94
|
-
(other_head_part.tail.payload[:y] == head.payload[:y] && other_head_part.head.payload[:y] == tail.payload[:y])
|
|
95
|
-
if self.next.nil? && other_head_part.prev.nil?
|
|
96
|
-
self.mirror = true
|
|
97
|
-
end
|
|
98
|
-
else
|
|
99
|
-
self.type = Part::EXCLUSIVE
|
|
100
|
-
self.trasmuted = true
|
|
101
|
-
other_head_part.transmutation_skip = true
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def within?(other)
|
|
108
|
-
self_min, self_max = [head.payload[:y], tail.payload[:y]].minmax
|
|
109
|
-
other_min, other_max = [other.head.payload[:y], other.tail.payload[:y]].minmax
|
|
110
|
-
self_min >= other_min && self_max <= other_max
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def same_length?(other)
|
|
114
|
-
(head.payload[:y] - tail.payload[:y]).abs ==
|
|
115
|
-
(other.head.payload[:y] - other.tail.payload[:y]).abs
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def continuum_to?(other)
|
|
119
|
-
return [] if size <= 2 && inverts && other.size <= 2 && other.inverts
|
|
120
|
-
return [] if other.head.nil?
|
|
121
|
-
|
|
122
|
-
target = other.head.payload
|
|
123
|
-
cursor = tail
|
|
124
|
-
while cursor
|
|
125
|
-
if cursor.payload == target
|
|
126
|
-
s = cursor
|
|
127
|
-
o = other.head
|
|
128
|
-
match = true
|
|
129
|
-
nodes = []
|
|
130
|
-
while s && o
|
|
131
|
-
if s.payload != o.payload
|
|
132
|
-
match = false
|
|
133
|
-
break
|
|
134
|
-
end
|
|
135
|
-
nodes << s.end_point
|
|
136
|
-
s = s.next
|
|
137
|
-
o = o.next
|
|
138
|
-
end
|
|
139
|
-
if match && s.nil?
|
|
140
|
-
return nodes
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
cursor = cursor.prev
|
|
144
|
-
end
|
|
145
|
-
[]
|
|
146
|
-
end
|
|
147
74
|
end
|
|
148
75
|
end
|
|
149
76
|
end
|
|
@@ -8,8 +8,6 @@ module Contrek
|
|
|
8
8
|
def initialize(*args, **kwargs, &block)
|
|
9
9
|
super
|
|
10
10
|
@parts = []
|
|
11
|
-
@first_seam = nil
|
|
12
|
-
@last_seam = nil
|
|
13
11
|
end
|
|
14
12
|
|
|
15
13
|
def add_part(new_part)
|
|
@@ -17,15 +15,7 @@ module Contrek
|
|
|
17
15
|
@parts << new_part
|
|
18
16
|
last.next = last.circular_next = new_part if last
|
|
19
17
|
new_part.circular_next = @parts.first
|
|
20
|
-
new_part.
|
|
21
|
-
if new_part.is?(Part::SEAM)
|
|
22
|
-
@first_seam ||= new_part
|
|
23
|
-
if !@last_seam.nil?
|
|
24
|
-
@last_seam.next_seam = new_part
|
|
25
|
-
end
|
|
26
|
-
@last_seam = new_part
|
|
27
|
-
new_part.orient!
|
|
28
|
-
end
|
|
18
|
+
new_part.orient! if new_part.is?(Part::SEAM)
|
|
29
19
|
end
|
|
30
20
|
|
|
31
21
|
def inspect_parts
|
|
@@ -35,8 +25,6 @@ module Contrek
|
|
|
35
25
|
def partition!
|
|
36
26
|
current_part = nil
|
|
37
27
|
@parts = []
|
|
38
|
-
@first_seam = nil
|
|
39
|
-
@last_seam = nil
|
|
40
28
|
|
|
41
29
|
@raw.each_with_index do |position, n|
|
|
42
30
|
if @tile.tg_border?(position)
|
|
@@ -52,54 +40,9 @@ module Contrek
|
|
|
52
40
|
add_part(current_part)
|
|
53
41
|
current_part = Part.new(Part::EXCLUSIVE, self)
|
|
54
42
|
end
|
|
55
|
-
if n > 0 && @raw[n - 1] == position
|
|
56
|
-
current_part.inverts = true
|
|
57
|
-
end
|
|
58
43
|
current_part.add_position(position)
|
|
59
44
|
end
|
|
60
45
|
add_part(current_part)
|
|
61
|
-
|
|
62
|
-
transmute_parts!
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
private
|
|
66
|
-
|
|
67
|
-
# If there are SEAM parts and one is canceled out by another within the same polyline,
|
|
68
|
-
# meaning that all its points are repeated in another, longer sequence,
|
|
69
|
-
# then the shorter one is converted to EXCLUSIVE and marked as transmuted
|
|
70
|
-
def transmute_parts!
|
|
71
|
-
transpose = tile.cluster.finder.transpose?
|
|
72
|
-
if (current_seam = @first_seam)
|
|
73
|
-
loop do
|
|
74
|
-
if transpose
|
|
75
|
-
transmute_transposed_part(current_seam)
|
|
76
|
-
elsif !current_seam.transmutation_skip
|
|
77
|
-
current_seam.try_transmutation!
|
|
78
|
-
end
|
|
79
|
-
current_seam = current_seam.next_seam
|
|
80
|
-
break if current_seam.nil?
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def transmute_transposed_part(part)
|
|
86
|
-
if (current_seam = @first_seam)
|
|
87
|
-
loop do
|
|
88
|
-
if current_seam != part
|
|
89
|
-
if part.within?(current_seam)
|
|
90
|
-
if !part.same_length?(current_seam)
|
|
91
|
-
part.type = Part::EXCLUSIVE
|
|
92
|
-
part.trasmuted = true
|
|
93
|
-
part.head.end_point.queues.delete(part)
|
|
94
|
-
part.tail.end_point.queues.delete(part)
|
|
95
|
-
return
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
current_seam = current_seam.next_seam
|
|
100
|
-
break if current_seam.nil?
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
46
|
end
|
|
104
47
|
end
|
|
105
48
|
end
|
|
@@ -47,12 +47,30 @@ module Contrek
|
|
|
47
47
|
node.after_add(self)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
def
|
|
51
|
-
queueable.
|
|
52
|
-
|
|
53
|
-
queueable
|
|
54
|
-
|
|
50
|
+
def append(queueable)
|
|
51
|
+
return if queueable.size.zero?
|
|
52
|
+
queueable.each do |node|
|
|
53
|
+
node.before_rem(queueable)
|
|
54
|
+
node.owner = self
|
|
55
55
|
end
|
|
56
|
+
if @tail
|
|
57
|
+
@tail.next = queueable.head
|
|
58
|
+
queueable.head.prev = @tail
|
|
59
|
+
else
|
|
60
|
+
@head = queueable.head
|
|
61
|
+
end
|
|
62
|
+
@tail = queueable.tail
|
|
63
|
+
@size += queueable.size
|
|
64
|
+
queueable.reset!
|
|
65
|
+
|
|
66
|
+
each { |node| node.after_add(self) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def reset!
|
|
70
|
+
@head = nil
|
|
71
|
+
@tail = nil
|
|
72
|
+
@size = 0
|
|
73
|
+
@iterator = 0
|
|
56
74
|
end
|
|
57
75
|
|
|
58
76
|
# from yield: false => stop, true => continue
|
|
@@ -5,21 +5,6 @@ module Contrek
|
|
|
5
5
|
class Sequence
|
|
6
6
|
attr_accessor :shape
|
|
7
7
|
prepend Queueable
|
|
8
|
-
|
|
9
|
-
def is_not_vertical
|
|
10
|
-
return false if size < 2
|
|
11
|
-
x0 = head.payload[:x]
|
|
12
|
-
rewind!
|
|
13
|
-
while (position = iterator)
|
|
14
|
-
return true if position.payload[:x] != x0
|
|
15
|
-
forward!
|
|
16
|
-
end
|
|
17
|
-
false
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def get_vector_cache
|
|
21
|
-
@vector_cache ||= to_a
|
|
22
|
-
end
|
|
23
8
|
end
|
|
24
9
|
end
|
|
25
10
|
end
|
|
@@ -3,10 +3,8 @@
|
|
|
3
3
|
module Contrek
|
|
4
4
|
module Concurrent
|
|
5
5
|
class StreamingMerger < VerticalMerger
|
|
6
|
-
def initialize(stream_to:,
|
|
6
|
+
def initialize(stream_to:, options: {})
|
|
7
7
|
@stream = stream_to
|
|
8
|
-
@total_width = total_width
|
|
9
|
-
@total_height = total_height
|
|
10
8
|
@moved = 0
|
|
11
9
|
super(options: options)
|
|
12
10
|
end
|
|
@@ -48,41 +46,21 @@ module Contrek
|
|
|
48
46
|
|
|
49
47
|
def stream_raw_polygon(shape)
|
|
50
48
|
outer_pts = shape.outer_polyline.raw.map { |p| "#{p[:y]},#{p[:x]}" }.join(" ")
|
|
51
|
-
|
|
49
|
+
write_outer_polygon(outer_pts)
|
|
52
50
|
shape.inner_polylines.map(&:raw).each do |sequence|
|
|
53
51
|
inner_pts = sequence.map { |p| "#{p[:y]},#{p[:x]}" }.join(" ")
|
|
54
|
-
|
|
52
|
+
write_inner_polygon(inner_pts)
|
|
55
53
|
end
|
|
56
54
|
end
|
|
57
55
|
|
|
58
56
|
def ensure_header
|
|
59
57
|
if @stream.pos == 0
|
|
60
|
-
|
|
58
|
+
write_header
|
|
61
59
|
end
|
|
62
60
|
end
|
|
63
61
|
|
|
64
62
|
def ensure_footer
|
|
65
|
-
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def svg_footer
|
|
69
|
-
"</svg>"
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def svg_header
|
|
73
|
-
"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"#{@total_width}\" height=\"#{@total_height}\"><style>#{svg_css}</style>"
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def svg_outer_polygon(outer_pts)
|
|
77
|
-
"<polygon points=\"#{outer_pts}\" class=\"out\"/>"
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def svg_inner_polygon(inner_pts)
|
|
81
|
-
"<polygon points=\"#{inner_pts}\" class=\"in\"/>"
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def svg_css
|
|
85
|
-
".out{fill:none;stroke:red;stroke-width:1;}.in{fill:none;stroke:green;stroke-width:1;}.out:hover{stroke:yellow;}"
|
|
63
|
+
write_footer
|
|
86
64
|
end
|
|
87
65
|
end
|
|
88
66
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Contrek
|
|
4
|
+
module Concurrent
|
|
5
|
+
class SvgStreamingMerger < StreamingMerger
|
|
6
|
+
def initialize(stream_to:, total_width:, total_height:, options: {})
|
|
7
|
+
@total_width = total_width
|
|
8
|
+
@total_height = total_height
|
|
9
|
+
super(stream_to:, options:)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def write_header
|
|
13
|
+
@stream.write("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"#{@total_width}\" height=\"#{@total_height}\"><style>#{svg_css}</style>")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def write_footer
|
|
17
|
+
@stream.write("</svg>")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def write_outer_polygon(outer_pts)
|
|
21
|
+
@stream.write("<polygon points=\"#{outer_pts}\" class=\"out\"/>")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def write_inner_polygon(inner_pts)
|
|
25
|
+
@stream.write("<polygon points=\"#{inner_pts}\" class=\"in\"/>")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def svg_css
|
|
31
|
+
".out{fill:none;stroke:red;stroke-width:1;}.in{fill:none;stroke:green;stroke-width:1;}.out:hover{stroke:yellow;}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|