contrek 1.4.2 → 1.4.3

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.
@@ -0,0 +1,51 @@
1
+ /*
2
+ * TiffSource.h
3
+ *
4
+ * Copyright (c) 2025-2026 Emanuele Cesaroni
5
+ *
6
+ * Licensed under the GNU Affero General Public License v3 (AGPLv3).
7
+ * See the LICENSE file in this directory for the full license text.
8
+ */
9
+
10
+ #pragma once
11
+
12
+ #include "RasterSource.h"
13
+ #include "GeoLocalization.h"
14
+
15
+ #include <cstddef>
16
+ #include <cstdint>
17
+ #include <string>
18
+ #include <vector>
19
+
20
+ struct tiff;
21
+ typedef struct tiff TIFF;
22
+
23
+ class TiffSource : public RasterSource {
24
+ public:
25
+ explicit TiffSource(const std::string& filepath, bool suppress_warnings = false);
26
+ ~TiffSource() override;
27
+ uint32_t width() const override;
28
+ uint32_t height() const override;
29
+ uint32_t get_bytes_per_pixel() const override;
30
+ bool read_next_row(unsigned char* destination, std::size_t row_size) override;
31
+ const GeoLocalization& geo_localization() const;
32
+
33
+ private:
34
+ void read_image_info();
35
+ void read_geo_localization();
36
+ void read_pixel_scale_and_tiepoint();
37
+ void read_model_transformation(double* matrix, uint16_t count);
38
+ void read_crs();
39
+ void convert_row(unsigned char* destination);
40
+ TIFF* tiff_;
41
+ uint32_t width_;
42
+ uint32_t height_;
43
+ uint16_t bits_per_sample_;
44
+ uint16_t samples_per_pixel_;
45
+ uint16_t photometric_;
46
+ uint16_t planar_config_;
47
+ uint16_t sample_format_;
48
+ uint32_t current_row_;
49
+ std::vector<unsigned char> source_row_;
50
+ GeoLocalization geo_localization_;
51
+ };
@@ -10,12 +10,51 @@
10
10
  #pragma once
11
11
  #include <vector>
12
12
  #include <string>
13
+ #include <functional>
14
+ #include <stdexcept>
15
+ #include <iomanip>
16
+ #include <limits>
13
17
  #include "StreamingMerger.h"
14
18
 
15
19
  class GeoJsonStreamingMerger : public StreamingMerger {
16
20
  private:
17
21
  unsigned int target_value;
18
22
  bool is_first_feature = true;
23
+ std::function<void(const Point&)> point_writer;
24
+
25
+ void configure_point_writer(const Options& options) {
26
+ const Options* geo_localization = options.get_options("geo_localization");
27
+ if (!geo_localization) {
28
+ point_writer = [this](const Point& point) { *stream << "[" << point.y << "," << point.x << "]"; };
29
+ return;
30
+ }
31
+
32
+ const Options* transform = geo_localization->get_options("transform");
33
+ const Options* crs = geo_localization->get_options("crs");
34
+ if (!transform || !crs) throw std::invalid_argument("Invalid geo_localization.");
35
+
36
+ const std::string authority = crs->get<std::string>("authority", "");
37
+ const auto code = crs->get("code", 0L);
38
+ if (authority != "EPSG" || code != 4326) throw std::invalid_argument("Unsupported CRS: " + authority + ":" + std::to_string(code));
39
+ const double x_origin = transform->get<double>("x_origin", 0.0);
40
+ const double y_origin = transform->get<double>("y_origin", 0.0);
41
+ const double x_pixel_size = transform->get<double>("x_pixel_size", 1.0);
42
+ const double y_pixel_size = transform->get<double>("y_pixel_size", 1.0);
43
+ const double x_row_offset = transform->get<double>("x_row_offset", 0.0);
44
+ const double y_column_offset = transform->get<double>("y_column_offset", 0.0);
45
+
46
+ *stream << std::fixed << std::setprecision(7);
47
+
48
+ point_writer = [this, x_origin, y_origin, x_pixel_size, y_pixel_size, x_row_offset, y_column_offset](const Point& point) {
49
+ const double x = point.y;
50
+ const double y = point.x;
51
+ *stream << "[" << x_origin + x * x_pixel_size + y * x_row_offset << "," << y_origin + x * y_column_offset + y * y_pixel_size << "]";
52
+ };
53
+ }
54
+
55
+ void write_point(const Point& point) {
56
+ point_writer(point);
57
+ }
19
58
 
20
59
  protected:
21
60
  void write_header() override {
@@ -40,7 +79,9 @@ class GeoJsonStreamingMerger : public StreamingMerger {
40
79
  std::ofstream* stream_to,
41
80
  unsigned int pixel_value)
42
81
  : StreamingMerger(number_of_threads, options, stream_to),
43
- target_value(pixel_value) {}
82
+ target_value(pixel_value) {
83
+ configure_point_writer(options);
84
+ }
44
85
 
45
86
  void stream_raw_polygon(const Polygon& polygon) override {
46
87
  if (!stream) return;
@@ -55,12 +96,14 @@ class GeoJsonStreamingMerger : public StreamingMerger {
55
96
  const size_t points_size = points.size();
56
97
  if (points_size > 0) {
57
98
  for (size_t i = 0; i < points_size; ++i) {
58
- *stream << "[" << points[i].y << "," << points[i].x << "]";
99
+ write_point(points[i]);
59
100
  if (i < points_size - 1) *stream << ",";
60
101
  }
61
- *stream << ",[" << points[0].y << "," << points[0].x << "]]";
102
+ *stream << ",";
103
+ write_point(points[0]);
104
+ *stream << "]";
62
105
  } else {
63
- *stream << "]]";
106
+ *stream << "]";
64
107
  }
65
108
 
66
109
  for (const std::vector<Point>& inner_points : polygon.inner) {
@@ -68,10 +111,12 @@ class GeoJsonStreamingMerger : public StreamingMerger {
68
111
  if (inner_size > 0) {
69
112
  *stream << ",[";
70
113
  for (size_t i = 0; i < inner_size; ++i) {
71
- *stream << "[" << inner_points[i].y << "," << inner_points[i].x << "]";
114
+ write_point(inner_points[i]);
72
115
  if (i < inner_size - 1) *stream << ",";
73
116
  }
74
- *stream << ",[" << inner_points[0].y << "," << inner_points[0].x << "]]";
117
+ *stream << ",";
118
+ write_point(inner_points[0]);
119
+ *stream << "]";
75
120
  }
76
121
  }
77
122
  *stream << "]}}";
@@ -40,6 +40,10 @@
40
40
  #include "PolygonFinder/src/polygon/bitmaps/streaming/RasterSource.h"
41
41
  #include "PolygonFinder/src/polygon/bitmaps/streaming/AsciiSource.h"
42
42
  #include "PolygonFinder/src/polygon/bitmaps/streaming/AsciiSource.cpp"
43
+ #ifdef CONTREK_HAS_TIFF
44
+ #include "PolygonFinder/src/polygon/bitmaps/streaming/TiffSource.h"
45
+ #include "PolygonFinder/src/polygon/bitmaps/streaming/TiffSource.cpp"
46
+ #endif
43
47
  #include "PolygonFinder/src/polygon/bitmaps/streaming/PngSource.h"
44
48
  #include "PolygonFinder/src/polygon/bitmaps/streaming/PngSource.cpp"
45
49
  #include "PolygonFinder/src/polygon/bitmaps/streaming/RasterStreamer.h"
@@ -455,6 +459,38 @@ void Init_cpp_polygon_finder() {
455
459
  .define_method("height",&PngSource::height)
456
460
  .define_method("get_bytes_per_pixel",&PngSource::get_bytes_per_pixel);
457
461
 
462
+ #ifdef CONTREK_HAS_TIFF
463
+ Data_Type<TiffSource> rb_cTiffSource =
464
+ define_class<TiffSource, RasterSource>("CPPTiffSource")
465
+ .define_constructor(Constructor<TiffSource, std::string, bool>(),Arg("filepath"),Arg("suppress_warnings") = false)
466
+ .define_method("width",&TiffSource::width)
467
+ .define_method("height",&TiffSource::height)
468
+ .define_method("get_bytes_per_pixel",&TiffSource::get_bytes_per_pixel)
469
+ .define_method("geo_localization",[](TiffSource& self) {
470
+ const GeoLocalization& geo = self.geo_localization();
471
+
472
+ if (!geo.valid) return Object(Qnil);
473
+
474
+ Hash transform;
475
+ transform[Symbol("x_origin")] = geo.transform.x_origin;
476
+ transform[Symbol("y_origin")] = geo.transform.y_origin;
477
+ transform[Symbol("x_pixel_size")] = geo.transform.x_pixel_size;
478
+ transform[Symbol("y_pixel_size")] = geo.transform.y_pixel_size;
479
+ transform[Symbol("x_row_offset")] = geo.transform.x_row_offset;
480
+ transform[Symbol("y_column_offset")] = geo.transform.y_column_offset;
481
+
482
+ Hash crs;
483
+ crs[Symbol("authority")] = geo.crs.authority;
484
+ crs[Symbol("code")] = geo.crs.code;
485
+
486
+ Hash result;
487
+ result[Symbol("transform")] = transform;
488
+ result[Symbol("crs")] = crs;
489
+
490
+ return Object(result);
491
+ });
492
+ #endif
493
+
458
494
  Data_Type<RasterStreamer> rb_cRasterStreamer =
459
495
  define_class<RasterStreamer>("CPPRasterStreamer")
460
496
  .define_constructor(Constructor<RasterStreamer, RasterSource&, uint32_t>(),Arg("source"),Arg("stripe_height"))
@@ -463,13 +499,14 @@ void Init_cpp_polygon_finder() {
463
499
  VALUE block = rb_block_proc();
464
500
  self.each(
465
501
  buffer,
466
- [block](Bitmap& bitmap,uint32_t buffer_rows,std::size_t buffer_size) {
502
+ [block](Bitmap& bitmap,uint32_t buffer_rows,std::size_t buffer_size, std::size_t buffer_read) {
467
503
  Rice::Data_Object<Bitmap> rb_bitmap(&bitmap);
468
504
  Rice::Object(block).call(
469
505
  "call",
470
506
  rb_bitmap,
471
507
  buffer_rows,
472
- buffer_size
508
+ buffer_size,
509
+ buffer_read
473
510
  );
474
511
  }
475
512
  );
@@ -3,6 +3,8 @@
3
3
  require "mkmf-rice"
4
4
 
5
5
  has_tcmalloc = find_library("tcmalloc", "malloc")
6
+ has_tiff = have_library("tiff", "TIFFOpen", "tiffio.h")
7
+ has_geotiff = has_tiff && have_library("geotiff", "XTIFFOpen", "geotiff/xtiffio.h")
6
8
 
7
9
  # rubocop:disable Style/GlobalVars
8
10
 
@@ -16,6 +18,13 @@ else
16
18
  puts "tcmalloc not found; standard malloc will be used."
17
19
  end
18
20
 
21
+ if has_tiff && has_geotiff
22
+ $CXXFLAGS << " -DCONTREK_HAS_TIFF"
23
+ puts "TIFF/GeoTIFF support enabled."
24
+ else
25
+ puts "TIFF/GeoTIFF support disabled."
26
+ end
27
+
19
28
  $LDFLAGS << " -lz -lstdc++ -flto -pthread"
20
29
 
21
30
  $objs = [
@@ -55,7 +55,7 @@ module Contrek
55
55
  buffer_rows = overlap_rows + rows_read
56
56
  break if buffer_rows == 0
57
57
 
58
- yield buffer, buffer_rows, buffer_rows * row_size
58
+ yield buffer, buffer_rows, buffer_rows * row_size, rows_read
59
59
 
60
60
  first_stripe = false
61
61
  end
@@ -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| [p[:y], p[:x]] }
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| [p[:y], p[:x]] }
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Contrek
4
- VERSION = "1.4.2"
4
+ VERSION = "1.4.3"
5
5
  end
data/lib/contrek.rb CHANGED
@@ -15,6 +15,7 @@ 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"
19
20
  require "contrek/finder/bounds"
20
21
  require "contrek/finder/list"
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.2
4
+ version: 1.4.3
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