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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -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 +3 -7
- 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 +1 -1
- 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/concurrent/GeoJsonStreamingMerger.h +51 -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/version.rb +1 -1
- data/lib/contrek.rb +1 -0
- metadata +5 -1
|
@@ -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
|
+
};
|
data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/GeoJsonStreamingMerger.h
CHANGED
|
@@ -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
|
-
|
|
99
|
+
write_point(points[i]);
|
|
59
100
|
if (i < points_size - 1) *stream << ",";
|
|
60
101
|
}
|
|
61
|
-
*stream << ",
|
|
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
|
-
|
|
114
|
+
write_point(inner_points[i]);
|
|
72
115
|
if (i < inner_size - 1) *stream << ",";
|
|
73
116
|
}
|
|
74
|
-
*stream << ",
|
|
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 = [
|
|
@@ -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
|
data/lib/contrek/version.rb
CHANGED
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.
|
|
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
|