contrek 1.4.2 → 1.4.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 +8 -0
- data/Gemfile.lock +1 -1
- data/README.md +11 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +13 -18
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.cpp +16 -6
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.h +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/AsciiSource.cpp +10 -33
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/AsciiSource.h +4 -9
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/GeoLocalization.h +32 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/PngSource.cpp +6 -11
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/RasterStreamer.h +5 -10
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/TiffSource.cpp +301 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/TiffSource.h +51 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +9 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/GeoJsonStreamingMerger.h +51 -6
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.cpp +6 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/VerticalMerger.cpp +24 -6
- data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +39 -2
- data/ext/cpp_polygon_finder/extconf.rb +9 -0
- data/lib/contrek/bitmaps/streaming/raster_streamer.rb +1 -1
- data/lib/contrek/bitmaps/streaming/sources/tiff.rb +349 -0
- data/lib/contrek/finder/concurrent/geo_json_streaming_merger.rb +19 -2
- data/lib/contrek/finder/concurrent/merger.rb +6 -1
- data/lib/contrek/finder/concurrent/vertical_merger.rb +24 -4
- data/lib/contrek/finder/result.rb +6 -0
- data/lib/contrek/shared/opencv_converter.rb +144 -0
- data/lib/contrek/version.rb +1 -1
- data/lib/contrek.rb +2 -0
- metadata +6 -1
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
|
|
5
|
+
module Contrek
|
|
6
|
+
module Bitmaps
|
|
7
|
+
module Streaming
|
|
8
|
+
module Sources
|
|
9
|
+
class Tiff
|
|
10
|
+
attr_reader :width, :height, :geo_localization
|
|
11
|
+
|
|
12
|
+
module LibTiff
|
|
13
|
+
extend FFI::Library
|
|
14
|
+
|
|
15
|
+
ffi_lib "tiff"
|
|
16
|
+
|
|
17
|
+
TIFFTAG_IMAGEWIDTH = 256
|
|
18
|
+
TIFFTAG_IMAGELENGTH = 257
|
|
19
|
+
TIFFTAG_BITSPERSAMPLE = 258
|
|
20
|
+
TIFFTAG_PHOTOMETRIC = 262
|
|
21
|
+
TIFFTAG_SAMPLESPERPIXEL = 277
|
|
22
|
+
TIFFTAG_PLANARCONFIG = 284
|
|
23
|
+
TIFFTAG_SAMPLEFORMAT = 339
|
|
24
|
+
TIFFTAG_MODELPIXELSCALE = 33550
|
|
25
|
+
TIFFTAG_MODELTIEPOINT = 33922
|
|
26
|
+
TIFFTAG_MODELTRANSFORMATION = 34264
|
|
27
|
+
|
|
28
|
+
PHOTOMETRIC_MINISWHITE = 0
|
|
29
|
+
PHOTOMETRIC_MINISBLACK = 1
|
|
30
|
+
PHOTOMETRIC_RGB = 2
|
|
31
|
+
|
|
32
|
+
PLANARCONFIG_CONTIG = 1
|
|
33
|
+
|
|
34
|
+
SAMPLEFORMAT_UINT = 1
|
|
35
|
+
|
|
36
|
+
attach_function :TIFFOpen, [:string, :string], :pointer
|
|
37
|
+
attach_function :TIFFClose, [:pointer], :void
|
|
38
|
+
attach_function :TIFFGetField, [:pointer, :uint32, :varargs], :int
|
|
39
|
+
attach_function :TIFFGetFieldDefaulted, [:pointer, :uint32, :varargs], :int
|
|
40
|
+
attach_function :TIFFScanlineSize64, [:pointer], :uint64
|
|
41
|
+
attach_function :TIFFReadScanline, [:pointer, :pointer, :uint32, :uint16], :int
|
|
42
|
+
attach_function :TIFFIsTiled, [:pointer], :int
|
|
43
|
+
attach_function :TIFFSetWarningHandler, [:pointer], :pointer
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
module LibGeoTiff
|
|
47
|
+
extend FFI::Library
|
|
48
|
+
|
|
49
|
+
ffi_lib "geotiff"
|
|
50
|
+
|
|
51
|
+
GTMODELTYPEGEOKEY = 1024
|
|
52
|
+
GEOGRAPHICTYPEGEOKEY = 2048
|
|
53
|
+
PROJECTEDCSTYPEGEOKEY = 3072
|
|
54
|
+
|
|
55
|
+
MODELTYPE_PROJECTED = 1
|
|
56
|
+
MODELTYPE_GEOGRAPHIC = 2
|
|
57
|
+
|
|
58
|
+
attach_function :XTIFFOpen, [:string, :string], :pointer
|
|
59
|
+
attach_function :GTIFNew, [:pointer], :pointer
|
|
60
|
+
attach_function :GTIFFree, [:pointer], :void
|
|
61
|
+
attach_function :GTIFKeyGetSHORT, [:pointer, :int, :pointer, :int, :int], :int
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def initialize(filepath, suppress_warnings: false)
|
|
65
|
+
@suppress_warnings = suppress_warnings
|
|
66
|
+
disable_warnings! if @suppress_warnings
|
|
67
|
+
|
|
68
|
+
@tiff = LibGeoTiff.XTIFFOpen(filepath, "r")
|
|
69
|
+
raise "Unable to open TIFF: #{filepath}" if @tiff.null?
|
|
70
|
+
|
|
71
|
+
@width = read_uint32(LibTiff::TIFFTAG_IMAGEWIDTH)
|
|
72
|
+
@height = read_uint32(LibTiff::TIFFTAG_IMAGELENGTH)
|
|
73
|
+
@bits_per_sample = read_uint16(LibTiff::TIFFTAG_BITSPERSAMPLE, defaulted: true)
|
|
74
|
+
@samples_per_pixel = read_uint16(LibTiff::TIFFTAG_SAMPLESPERPIXEL, defaulted: true)
|
|
75
|
+
@photometric = read_uint16(LibTiff::TIFFTAG_PHOTOMETRIC)
|
|
76
|
+
@planar_config = read_uint16(LibTiff::TIFFTAG_PLANARCONFIG, defaulted: true)
|
|
77
|
+
@sample_format = read_uint16(LibTiff::TIFFTAG_SAMPLEFORMAT, defaulted: true)
|
|
78
|
+
|
|
79
|
+
validate_format
|
|
80
|
+
|
|
81
|
+
@source_row_size = LibTiff.TIFFScanlineSize64(@tiff)
|
|
82
|
+
@source_row = FFI::MemoryPointer.new(:uint8, @source_row_size)
|
|
83
|
+
@current_row = 0
|
|
84
|
+
@geo_localization = read_coordinates
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def get_bytes_per_pixel
|
|
88
|
+
4
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def read_next_row(destination, row_size)
|
|
92
|
+
return false if @current_row >= @height
|
|
93
|
+
|
|
94
|
+
expected_row_size = @width * get_bytes_per_pixel
|
|
95
|
+
raise "Invalid TIFF destination row size: #{row_size}" unless row_size == expected_row_size
|
|
96
|
+
|
|
97
|
+
ret = LibTiff.TIFFReadScanline(@tiff, @source_row, @current_row, 0)
|
|
98
|
+
raise "TIFFReadScanline failed at row #{@current_row}" if ret < 0
|
|
99
|
+
|
|
100
|
+
convert_row(destination)
|
|
101
|
+
|
|
102
|
+
@current_row += 1
|
|
103
|
+
true
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def close
|
|
107
|
+
unless @gtif.nil? || @gtif.null?
|
|
108
|
+
LibGeoTiff.GTIFFree(@gtif)
|
|
109
|
+
@gtif = nil
|
|
110
|
+
end
|
|
111
|
+
unless @tiff.nil? || @tiff.null?
|
|
112
|
+
LibTiff.TIFFClose(@tiff)
|
|
113
|
+
@tiff = nil
|
|
114
|
+
end
|
|
115
|
+
if @suppress_warnings
|
|
116
|
+
LibTiff.TIFFSetWarningHandler(@previous_warning_handler)
|
|
117
|
+
@previous_warning_handler = nil
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def validate_format
|
|
124
|
+
if LibTiff.TIFFIsTiled(@tiff) != 0
|
|
125
|
+
raise "Tiled TIFF is not supported yet"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
unless @planar_config == LibTiff::PLANARCONFIG_CONTIG
|
|
129
|
+
raise "Unsupported TIFF planar configuration: #{@planar_config}"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
unless [8, 16].include?(@bits_per_sample)
|
|
133
|
+
raise "Unsupported TIFF bits per sample: #{@bits_per_sample}"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
unless [1, 3, 4].include?(@samples_per_pixel)
|
|
137
|
+
raise "Unsupported TIFF samples per pixel: #{@samples_per_pixel}"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
unless @sample_format == LibTiff::SAMPLEFORMAT_UINT
|
|
141
|
+
raise "Unsupported TIFF sample format: #{@sample_format}"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
unless [
|
|
145
|
+
LibTiff::PHOTOMETRIC_MINISWHITE,
|
|
146
|
+
LibTiff::PHOTOMETRIC_MINISBLACK,
|
|
147
|
+
LibTiff::PHOTOMETRIC_RGB
|
|
148
|
+
].include?(@photometric)
|
|
149
|
+
raise "Unsupported TIFF photometric: #{@photometric}"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def convert_row(destination)
|
|
154
|
+
case [@samples_per_pixel, @bits_per_sample]
|
|
155
|
+
when [1, 8]
|
|
156
|
+
convert_gray8_row(destination)
|
|
157
|
+
when [1, 16]
|
|
158
|
+
convert_gray16_row(destination)
|
|
159
|
+
when [3, 8]
|
|
160
|
+
convert_rgb8_row(destination)
|
|
161
|
+
when [3, 16]
|
|
162
|
+
convert_rgb16_row(destination)
|
|
163
|
+
when [4, 8]
|
|
164
|
+
convert_rgba8_row(destination)
|
|
165
|
+
when [4, 16]
|
|
166
|
+
convert_rgba16_row(destination)
|
|
167
|
+
else
|
|
168
|
+
raise "Unsupported TIFF pixel format"
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def convert_rgba8_row(dest)
|
|
173
|
+
dest.put_bytes(0, @source_row.read_bytes(@width * 4))
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def convert_rgb8_row(dest)
|
|
177
|
+
@width.times do |x|
|
|
178
|
+
source_offset = x * 3
|
|
179
|
+
dest_offset = x * 4
|
|
180
|
+
dest.put_uint8(dest_offset, @source_row.get_uint8(source_offset))
|
|
181
|
+
dest.put_uint8(dest_offset + 1, @source_row.get_uint8(source_offset + 1))
|
|
182
|
+
dest.put_uint8(dest_offset + 2, @source_row.get_uint8(source_offset + 2))
|
|
183
|
+
dest.put_uint8(dest_offset + 3, 255)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def convert_gray8_row(dest)
|
|
188
|
+
@width.times do |x|
|
|
189
|
+
value = @source_row.get_uint8(x)
|
|
190
|
+
value = 255 - value if @photometric == LibTiff::PHOTOMETRIC_MINISWHITE
|
|
191
|
+
dest_offset = x * 4
|
|
192
|
+
dest.put_uint8(dest_offset, value)
|
|
193
|
+
dest.put_uint8(dest_offset + 1, value)
|
|
194
|
+
dest.put_uint8(dest_offset + 2, value)
|
|
195
|
+
dest.put_uint8(dest_offset + 3, 255)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def convert_gray16_row(dest)
|
|
200
|
+
@width.times do |x|
|
|
201
|
+
value = @source_row.get_uint16(x * 2) >> 8
|
|
202
|
+
value = 255 - value if @photometric == LibTiff::PHOTOMETRIC_MINISWHITE
|
|
203
|
+
dest_offset = x * 4
|
|
204
|
+
dest.put_uint8(dest_offset, value)
|
|
205
|
+
dest.put_uint8(dest_offset + 1, value)
|
|
206
|
+
dest.put_uint8(dest_offset + 2, value)
|
|
207
|
+
dest.put_uint8(dest_offset + 3, 255)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def convert_rgb16_row(dest)
|
|
212
|
+
@width.times do |x|
|
|
213
|
+
source_offset = x * 6
|
|
214
|
+
dest_offset = x * 4
|
|
215
|
+
dest.put_uint8(dest_offset, @source_row.get_uint16(source_offset) >> 8)
|
|
216
|
+
dest.put_uint8(dest_offset + 1, @source_row.get_uint16(source_offset + 2) >> 8)
|
|
217
|
+
dest.put_uint8(dest_offset + 2, @source_row.get_uint16(source_offset + 4) >> 8)
|
|
218
|
+
dest.put_uint8(dest_offset + 3, 255)
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def convert_rgba16_row(dest)
|
|
223
|
+
@width.times do |x|
|
|
224
|
+
source_offset = x * 8
|
|
225
|
+
dest_offset = x * 4
|
|
226
|
+
dest.put_uint8(dest_offset, @source_row.get_uint16(source_offset) >> 8)
|
|
227
|
+
dest.put_uint8(dest_offset + 1, @source_row.get_uint16(source_offset + 2) >> 8)
|
|
228
|
+
dest.put_uint8(dest_offset + 2, @source_row.get_uint16(source_offset + 4) >> 8)
|
|
229
|
+
dest.put_uint8(dest_offset + 3, @source_row.get_uint16(source_offset + 6) >> 8)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def read_uint32(tag, defaulted: false)
|
|
234
|
+
value = FFI::MemoryPointer.new(:uint32)
|
|
235
|
+
ret = if defaulted
|
|
236
|
+
LibTiff.TIFFGetFieldDefaulted(@tiff, tag, :pointer, value)
|
|
237
|
+
else
|
|
238
|
+
LibTiff.TIFFGetField(@tiff, tag, :pointer, value)
|
|
239
|
+
end
|
|
240
|
+
raise "Unable to read TIFF tag: #{tag}" unless ret == 1
|
|
241
|
+
value.read_uint32
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def read_uint16(tag, defaulted: false)
|
|
245
|
+
value = FFI::MemoryPointer.new(:uint16)
|
|
246
|
+
ret = if defaulted
|
|
247
|
+
LibTiff.TIFFGetFieldDefaulted(@tiff, tag, :pointer, value)
|
|
248
|
+
else
|
|
249
|
+
LibTiff.TIFFGetField(@tiff, tag, :pointer, value)
|
|
250
|
+
end
|
|
251
|
+
raise "Unable to read TIFF tag: #{tag}" unless ret == 1
|
|
252
|
+
value.read_uint16
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def read_coordinates
|
|
256
|
+
transform = read_model_transformation || read_pixel_scale_and_tiepoint
|
|
257
|
+
return nil unless transform
|
|
258
|
+
@gtif = LibGeoTiff.GTIFNew(@tiff)
|
|
259
|
+
|
|
260
|
+
{transform: transform,
|
|
261
|
+
crs: read_crs}
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def read_pixel_scale_and_tiepoint
|
|
265
|
+
scale_count = FFI::MemoryPointer.new(:uint16)
|
|
266
|
+
scale_data = FFI::MemoryPointer.new(:pointer)
|
|
267
|
+
ret = LibTiff.TIFFGetField(
|
|
268
|
+
@tiff, LibTiff::TIFFTAG_MODELPIXELSCALE, :pointer, scale_count, :pointer, scale_data
|
|
269
|
+
)
|
|
270
|
+
return nil unless ret == 1
|
|
271
|
+
|
|
272
|
+
tiepoint_count = FFI::MemoryPointer.new(:uint16)
|
|
273
|
+
tiepoint_data = FFI::MemoryPointer.new(:pointer)
|
|
274
|
+
|
|
275
|
+
ret = LibTiff.TIFFGetField(
|
|
276
|
+
@tiff, LibTiff::TIFFTAG_MODELTIEPOINT, :pointer, tiepoint_count, :pointer, tiepoint_data
|
|
277
|
+
)
|
|
278
|
+
return nil unless ret == 1
|
|
279
|
+
|
|
280
|
+
scale_ptr = scale_data.read_pointer
|
|
281
|
+
tiepoint_ptr = tiepoint_data.read_pointer
|
|
282
|
+
x_pixel_size = scale_ptr.get_double(0)
|
|
283
|
+
y_pixel_size = scale_ptr.get_double(8)
|
|
284
|
+
pixel_x = tiepoint_ptr.get_double(0)
|
|
285
|
+
pixel_y = tiepoint_ptr.get_double(8)
|
|
286
|
+
model_x = tiepoint_ptr.get_double(24)
|
|
287
|
+
model_y = tiepoint_ptr.get_double(32)
|
|
288
|
+
|
|
289
|
+
{x_origin: model_x - pixel_x * x_pixel_size,
|
|
290
|
+
y_origin: model_y + pixel_y * y_pixel_size,
|
|
291
|
+
x_pixel_size: x_pixel_size,
|
|
292
|
+
y_pixel_size: -y_pixel_size,
|
|
293
|
+
x_row_offset: 0.0,
|
|
294
|
+
y_column_offset: 0.0}
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def read_model_transformation
|
|
298
|
+
count = FFI::MemoryPointer.new(:uint16)
|
|
299
|
+
data = FFI::MemoryPointer.new(:pointer)
|
|
300
|
+
|
|
301
|
+
ret = LibTiff.TIFFGetField(
|
|
302
|
+
@tiff, LibTiff::TIFFTAG_MODELTRANSFORMATION, :pointer, count, :pointer, data
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
return nil unless ret == 1
|
|
306
|
+
ptr = data.read_pointer
|
|
307
|
+
return nil unless count.read_uint16 >= 16
|
|
308
|
+
|
|
309
|
+
{x_origin: ptr.get_double(3 * 8),
|
|
310
|
+
y_origin: ptr.get_double(7 * 8),
|
|
311
|
+
x_pixel_size: ptr.get_double(0 * 8),
|
|
312
|
+
y_column_offset: ptr.get_double(4 * 8),
|
|
313
|
+
x_row_offset: ptr.get_double(1 * 8),
|
|
314
|
+
y_pixel_size: ptr.get_double(5 * 8)}
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def read_crs
|
|
318
|
+
return nil if @gtif.nil? || @gtif.null?
|
|
319
|
+
|
|
320
|
+
model_type = read_geokey_short(LibGeoTiff::GTMODELTYPEGEOKEY)
|
|
321
|
+
code =
|
|
322
|
+
case model_type
|
|
323
|
+
when LibGeoTiff::MODELTYPE_PROJECTED
|
|
324
|
+
read_geokey_short(LibGeoTiff::PROJECTEDCSTYPEGEOKEY)
|
|
325
|
+
when LibGeoTiff::MODELTYPE_GEOGRAPHIC
|
|
326
|
+
read_geokey_short(LibGeoTiff::GEOGRAPHICTYPEGEOKEY)
|
|
327
|
+
end
|
|
328
|
+
return nil unless code
|
|
329
|
+
|
|
330
|
+
{authority: "EPSG",
|
|
331
|
+
code: code}
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def read_geokey_short(key)
|
|
335
|
+
value = FFI::MemoryPointer.new(:uint16)
|
|
336
|
+
ret = LibGeoTiff.GTIFKeyGetSHORT(@gtif, key, value, 0, 1)
|
|
337
|
+
|
|
338
|
+
return nil unless ret == 1
|
|
339
|
+
value.read_uint16
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def disable_warnings!
|
|
343
|
+
LibTiff.TIFFSetWarningHandler(nil)
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
@@ -5,6 +5,8 @@ module Contrek
|
|
|
5
5
|
class GeoJsonStreamingMerger < StreamingMerger
|
|
6
6
|
def initialize(stream_to:, pixel_val:, options: {})
|
|
7
7
|
@pixel_val = pixel_val
|
|
8
|
+
@point_converter = self.class.coordinates_converter(options[:geo_localization])
|
|
9
|
+
|
|
8
10
|
super(stream_to:, options:)
|
|
9
11
|
end
|
|
10
12
|
|
|
@@ -23,11 +25,11 @@ module Contrek
|
|
|
23
25
|
else
|
|
24
26
|
@stream.write(",")
|
|
25
27
|
end
|
|
26
|
-
outer_ring = polygon[:outer].map { |p|
|
|
28
|
+
outer_ring = polygon[:outer].map { |p| @point_converter.call(p) }
|
|
27
29
|
outer_ring << outer_ring.first if outer_ring.first != outer_ring.last
|
|
28
30
|
polygon_coordinates = [outer_ring]
|
|
29
31
|
polygon[:inner].each do |sequence|
|
|
30
|
-
inner_ring = sequence.map { |p|
|
|
32
|
+
inner_ring = sequence.map { |p| @point_converter.call(p) }
|
|
31
33
|
inner_ring << inner_ring.first if inner_ring.first != inner_ring.last
|
|
32
34
|
polygon_coordinates << inner_ring
|
|
33
35
|
end
|
|
@@ -41,6 +43,21 @@ module Contrek
|
|
|
41
43
|
}
|
|
42
44
|
@stream.write(JSON.generate(feature_hash))
|
|
43
45
|
end
|
|
46
|
+
|
|
47
|
+
def self.coordinates_converter(geo_localization)
|
|
48
|
+
return ->(point) { [point[:y], point[:x]] } unless geo_localization
|
|
49
|
+
|
|
50
|
+
crs = geo_localization[:crs]
|
|
51
|
+
raise "Unsupported CRS: #{crs[:authority]}:#{crs[:code]}" unless crs[:authority] == "EPSG" && crs[:code] == 4326
|
|
52
|
+
|
|
53
|
+
transform = geo_localization[:transform]
|
|
54
|
+
lambda do |point|
|
|
55
|
+
x = point[:y]
|
|
56
|
+
y = point[:x]
|
|
57
|
+
[(transform[:x_origin] + x * transform[:x_pixel_size] + y * transform[:x_row_offset]).round(7),
|
|
58
|
+
(transform[:y_origin] + x * transform[:y_column_offset] + y * transform[:y_pixel_size]).round(7)]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
44
61
|
end
|
|
45
62
|
end
|
|
46
63
|
end
|
|
@@ -6,13 +6,18 @@ module Contrek
|
|
|
6
6
|
attr_reader :tiles
|
|
7
7
|
prepend Poolable
|
|
8
8
|
|
|
9
|
+
IGNORE_OPTIONS = [:versus]
|
|
10
|
+
|
|
9
11
|
def initialize(options: {})
|
|
10
12
|
@initialize_time = 0
|
|
11
13
|
@current_x = 0
|
|
12
14
|
@tiles = Queue.new
|
|
13
15
|
@whole_tile = nil
|
|
14
16
|
@user_options = options
|
|
15
|
-
|
|
17
|
+
if (warn_options = @user_options.keys & IGNORE_OPTIONS).any?
|
|
18
|
+
warn "[Contrek WARNING] The given options '#{warn_options.join(",")}' will be ignored."
|
|
19
|
+
end
|
|
20
|
+
@options = {unsafe_mode: false}.merge(@user_options).except(*IGNORE_OPTIONS)
|
|
16
21
|
unless safe?
|
|
17
22
|
warn "[Contrek WARNING] Processing tile with 'unsafe_mode: true'. Incompatible result options might lead to unexpected vector geometry."
|
|
18
23
|
end
|
|
@@ -5,10 +5,10 @@ module Contrek
|
|
|
5
5
|
class VerticalMerger < Merger
|
|
6
6
|
def add_tile(result)
|
|
7
7
|
transpose(result)
|
|
8
|
+
adjust(result)
|
|
8
9
|
if @tiles.size > 0
|
|
9
10
|
translate(result, @current_x)
|
|
10
11
|
end
|
|
11
|
-
adjust(result)
|
|
12
12
|
super
|
|
13
13
|
end
|
|
14
14
|
|
|
@@ -22,12 +22,32 @@ module Contrek
|
|
|
22
22
|
|
|
23
23
|
private
|
|
24
24
|
|
|
25
|
+
# After transposing the polygon, select the correct starting vertex according to its versus.
|
|
26
|
+
# On the topmost scanline, use the rightmost vertex for counterclockwise polygons and the leftmost
|
|
27
|
+
# vertex for clockwise polygons.
|
|
28
|
+
# TODO: is useless the really rotate coords. Probably you can save rotation index here computed
|
|
29
|
+
# using later in partition() to get the starting point sequence parts begins
|
|
25
30
|
def adjust(result)
|
|
31
|
+
versus = result.metadata[:versus]
|
|
32
|
+
tile_width = result.metadata[:width]
|
|
33
|
+
number_of_tiles = @tiles.size
|
|
34
|
+
|
|
26
35
|
result.polygons.each do |polygon|
|
|
27
|
-
polygon[:
|
|
28
|
-
|
|
29
|
-
|
|
36
|
+
bounds = polygon[:bounds]
|
|
37
|
+
needs_left = number_of_tiles > 0 && bounds[:min_x] == 0
|
|
38
|
+
needs_right = bounds[:max_x] == tile_width
|
|
39
|
+
next unless needs_left || needs_right
|
|
40
|
+
sequence = polygon[:outer]
|
|
41
|
+
index = nil
|
|
42
|
+
best_x = nil
|
|
43
|
+
sequence.each_with_index do |point, i|
|
|
44
|
+
next unless point[:y] == bounds[:min_y]
|
|
45
|
+
if index.nil? || ((versus == :a) ? point[:x] > best_x : point[:x] < best_x)
|
|
46
|
+
index = i
|
|
47
|
+
best_x = point[:x]
|
|
48
|
+
end
|
|
30
49
|
end
|
|
50
|
+
sequence.rotate!(index) if index && index != 0
|
|
31
51
|
end
|
|
32
52
|
end
|
|
33
53
|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
module Contrek
|
|
2
|
+
module Shared
|
|
3
|
+
class OpencvConverter
|
|
4
|
+
CHAIN_DIR = [
|
|
5
|
+
[3, 4, 5],
|
|
6
|
+
[2, -1, 6],
|
|
7
|
+
[1, 0, 7]
|
|
8
|
+
].freeze
|
|
9
|
+
|
|
10
|
+
CORNER_X = [0, 1, 1, 0].freeze
|
|
11
|
+
CORNER_Y = [1, 1, 0, 0].freeze
|
|
12
|
+
|
|
13
|
+
def contour_to_cell_boundary(contour, rect)
|
|
14
|
+
raw = []
|
|
15
|
+
n = contour.size
|
|
16
|
+
|
|
17
|
+
return raw if n == 0
|
|
18
|
+
if n == 1
|
|
19
|
+
x = contour[0][:x]
|
|
20
|
+
y = contour[0][:y]
|
|
21
|
+
return [
|
|
22
|
+
{x: x, y: y + 1},
|
|
23
|
+
{x: x + 1, y: y + 1},
|
|
24
|
+
{x: x + 1, y: y},
|
|
25
|
+
{x: x, y: y}
|
|
26
|
+
]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
n.times do |i|
|
|
30
|
+
previous = contour[(i - 1) % n]
|
|
31
|
+
current = contour[i]
|
|
32
|
+
next_p = contour[(i + 1) % n]
|
|
33
|
+
|
|
34
|
+
incoming = chain_direction(previous, current)
|
|
35
|
+
outgoing = chain_direction(current, next_p)
|
|
36
|
+
|
|
37
|
+
corner = incoming >> 1
|
|
38
|
+
last_corner = ((outgoing + 1) >> 1) & 3
|
|
39
|
+
|
|
40
|
+
loop do
|
|
41
|
+
raw << {
|
|
42
|
+
x: current[:x] + CORNER_X[corner],
|
|
43
|
+
y: current[:y] + CORNER_Y[corner]
|
|
44
|
+
}
|
|
45
|
+
break if corner == last_corner
|
|
46
|
+
corner = (corner + 1) & 3
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# build path parts
|
|
51
|
+
edges = []
|
|
52
|
+
raw.size.times do |i|
|
|
53
|
+
a = raw[i]
|
|
54
|
+
b = raw[(i + 1) % raw.size]
|
|
55
|
+
next if a == b
|
|
56
|
+
|
|
57
|
+
key = [
|
|
58
|
+
[a[:x], a[:y]],
|
|
59
|
+
[b[:x], b[:y]]
|
|
60
|
+
].sort
|
|
61
|
+
edges << {a: a, b: b, key: key}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
counts = Hash.new(0)
|
|
65
|
+
edges.each do |edge|
|
|
66
|
+
counts[edge[:key]] += 1
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
kept = {}
|
|
70
|
+
edges.select! do |edge|
|
|
71
|
+
key = edge[:key]
|
|
72
|
+
next false if counts[key].even?
|
|
73
|
+
next false if kept[key]
|
|
74
|
+
|
|
75
|
+
kept[key] = true
|
|
76
|
+
true
|
|
77
|
+
end
|
|
78
|
+
return [] if edges.empty?
|
|
79
|
+
|
|
80
|
+
outgoing = Hash.new { |h, k| h[k] = [] }
|
|
81
|
+
edges.each_with_index do |edge, i|
|
|
82
|
+
outgoing[[edge[:a][:x], edge[:a][:y]]] << i
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
used = Array.new(edges.size, false)
|
|
86
|
+
result = []
|
|
87
|
+
current = 0
|
|
88
|
+
|
|
89
|
+
loop do
|
|
90
|
+
break if used.all?
|
|
91
|
+
if used[current]
|
|
92
|
+
current = used.index(false)
|
|
93
|
+
break unless current
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
edge = edges[current]
|
|
97
|
+
used[current] = true
|
|
98
|
+
result << edge[:a]
|
|
99
|
+
finish = edge[:b]
|
|
100
|
+
candidates = outgoing[[finish[:x], finish[:y]]]
|
|
101
|
+
next_edge = candidates.find { |index| !used[index] }
|
|
102
|
+
|
|
103
|
+
if next_edge
|
|
104
|
+
current = next_edge
|
|
105
|
+
else
|
|
106
|
+
current = used.index(false)
|
|
107
|
+
break unless current
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
rotate_to_scanline_start(result, rect)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def rect_to_bounds(rect)
|
|
114
|
+
{min_x: rect[:x],
|
|
115
|
+
max_x: rect[:x] + rect[:width],
|
|
116
|
+
min_y: rect[:y],
|
|
117
|
+
max_y: rect[:y] + rect[:height]}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
def rotate_to_scanline_start(points, rect)
|
|
123
|
+
return points if points.empty?
|
|
124
|
+
|
|
125
|
+
min_y = rect[:y]
|
|
126
|
+
min_x = points.select { |p| p[:y] == min_y }.map { |p| p[:x] }.min
|
|
127
|
+
index = points.index { |p| p[:x] == min_x && p[:y] == min_y }
|
|
128
|
+
(index && index > 0) ? points.rotate(index) : points
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def chain_direction(from, to)
|
|
132
|
+
dx = to[:x] - from[:x]
|
|
133
|
+
dy = to[:y] - from[:y]
|
|
134
|
+
|
|
135
|
+
raise ArgumentError, "Zero-length contour step" if dx == 0 && dy == 0
|
|
136
|
+
if dx.abs > 1 || dy.abs > 1
|
|
137
|
+
raise ArgumentError, "Non-adjacent contour step"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
CHAIN_DIR[dx + 1][dy + 1]
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
data/lib/contrek/version.rb
CHANGED
data/lib/contrek.rb
CHANGED
|
@@ -15,7 +15,9 @@ require "contrek/bitmaps/streaming/raster_streamer"
|
|
|
15
15
|
require "contrek/bitmaps/streaming/sources/base"
|
|
16
16
|
require "contrek/bitmaps/streaming/sources/ascii"
|
|
17
17
|
require "contrek/bitmaps/streaming/sources/png"
|
|
18
|
+
require "contrek/bitmaps/streaming/sources/tiff"
|
|
18
19
|
require "contrek/shared/result"
|
|
20
|
+
require "contrek/shared/opencv_converter"
|
|
19
21
|
require "contrek/finder/bounds"
|
|
20
22
|
require "contrek/finder/list"
|
|
21
23
|
require "contrek/finder/list_entry"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: contrek
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.4.
|
|
4
|
+
version: 1.4.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Emanuele Cesaroni
|
|
@@ -168,10 +168,13 @@ files:
|
|
|
168
168
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/spng.h
|
|
169
169
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/AsciiSource.cpp
|
|
170
170
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/AsciiSource.h
|
|
171
|
+
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/GeoLocalization.h
|
|
171
172
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/PngSource.cpp
|
|
172
173
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/PngSource.h
|
|
173
174
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/RasterSource.h
|
|
174
175
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/RasterStreamer.h
|
|
176
|
+
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/TiffSource.cpp
|
|
177
|
+
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/TiffSource.h
|
|
175
178
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/FinderUtils.cpp
|
|
176
179
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/FinderUtils.h
|
|
177
180
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/List.cpp
|
|
@@ -278,6 +281,7 @@ files:
|
|
|
278
281
|
- lib/contrek/bitmaps/streaming/sources/ascii.rb
|
|
279
282
|
- lib/contrek/bitmaps/streaming/sources/base.rb
|
|
280
283
|
- lib/contrek/bitmaps/streaming/sources/png.rb
|
|
284
|
+
- lib/contrek/bitmaps/streaming/sources/tiff.rb
|
|
281
285
|
- lib/contrek/cpp/cpp_concurrent_finder.rb
|
|
282
286
|
- lib/contrek/cpp/cpp_concurrent_horizontal_merger.rb
|
|
283
287
|
- lib/contrek/cpp/cpp_concurrent_merger.rb
|
|
@@ -329,6 +333,7 @@ files:
|
|
|
329
333
|
- lib/contrek/reducers/reducer.rb
|
|
330
334
|
- lib/contrek/reducers/uniq_reducer.rb
|
|
331
335
|
- lib/contrek/reducers/visvalingam_reducer.rb
|
|
336
|
+
- lib/contrek/shared/opencv_converter.rb
|
|
332
337
|
- lib/contrek/shared/result.rb
|
|
333
338
|
- lib/contrek/version.rb
|
|
334
339
|
homepage: https://github.com/runout77/contrek
|