contrek 1.4.5 → 1.4.7
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 +1 -1
- data/README.md +28 -9
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/FinderUtils.cpp +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.cpp +23 -26
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.h +2 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.cpp +7 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.h +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Polygon.h +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.cpp +24 -11
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +68 -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 +8 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +3 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/GeoJsonStreamingMerger.h +3 -5
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.cpp +11 -30
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.h +3 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/MergerValidator.cpp +104 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/MergerValidator.h +35 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.cpp +3 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.h +3 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Shape.cpp +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/ShapePool.cpp +2 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/ShapePool.h +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.cpp +19 -21
- data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +11 -3
- data/lib/contrek/cpp/cpp_geo_json_concurrent_streaming_merger.rb +2 -2
- data/lib/contrek/cpp/cpp_result.rb +2 -1
- data/lib/contrek/finder/concurrent/cluster.rb +1 -1
- data/lib/contrek/finder/concurrent/cursor.rb +3 -1
- data/lib/contrek/finder/concurrent/finder.rb +2 -1
- data/lib/contrek/finder/concurrent/geo_json_streaming_merger.rb +3 -4
- data/lib/contrek/finder/concurrent/merger.rb +6 -25
- data/lib/contrek/finder/concurrent/merger_validator.rb +96 -0
- data/lib/contrek/finder/concurrent/polyline.rb +3 -2
- data/lib/contrek/finder/concurrent/shape.rb +2 -1
- data/lib/contrek/finder/concurrent/streaming_merger.rb +1 -1
- data/lib/contrek/finder/concurrent/tile.rb +9 -3
- data/lib/contrek/finder/node.rb +13 -23
- data/lib/contrek/finder/node_cluster.rb +17 -5
- data/lib/contrek/finder/polygon_finder.rb +29 -1
- data/lib/contrek/version.rb +1 -1
- data/lib/contrek.rb +1 -0
- metadata +4 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MergerValidator.cpp
|
|
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
|
+
#include "MergerValidator.h"
|
|
11
|
+
#include "Merger.h"
|
|
12
|
+
#include <algorithm>
|
|
13
|
+
#include <stdexcept>
|
|
14
|
+
#include <vector>
|
|
15
|
+
#include <list>
|
|
16
|
+
#include <utility>
|
|
17
|
+
|
|
18
|
+
MergerValidator::MergerValidator(const Merger& merger)
|
|
19
|
+
: merger_(merger) {}
|
|
20
|
+
|
|
21
|
+
void MergerValidator::validate_metadata(const ProcessResult& result) const {
|
|
22
|
+
if (result.width <= 0) {
|
|
23
|
+
throw std::invalid_argument("Invalid result metadata: width must be a positive integer");
|
|
24
|
+
}
|
|
25
|
+
if (result.height <= 0) {
|
|
26
|
+
throw std::invalid_argument("Invalid result metadata: height must be a positive integer");
|
|
27
|
+
}
|
|
28
|
+
if (result.versus != Node::A && result.versus != Node::O) {
|
|
29
|
+
throw std::invalid_argument("Invalid result metadata: unsupported versus option");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
void MergerValidator::validate(const ProcessResult& result, int start_x, int end_x) {
|
|
34
|
+
validate_bounds(result.polygons);
|
|
35
|
+
validate_polygon_order(result.polygons);
|
|
36
|
+
validate_boundary(result, start_x);
|
|
37
|
+
|
|
38
|
+
previous_boundary_ = boundary_coordinates(result.polygons, end_x);
|
|
39
|
+
has_previous_boundary_ = true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
void MergerValidator::validate_bounds(const std::list<Polygon>& polygons) const {
|
|
43
|
+
for (const auto& polygon : polygons) {
|
|
44
|
+
if (polygon.outer.empty()) {
|
|
45
|
+
throw std::invalid_argument("Invalid polygon: empty outer contour");
|
|
46
|
+
}
|
|
47
|
+
RectBounds actual = RectBounds::empty();
|
|
48
|
+
|
|
49
|
+
for (const auto& point : polygon.outer) {
|
|
50
|
+
actual.expand(point.x, point.y);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const auto& bounds = polygon.bounds;
|
|
54
|
+
if (bounds.min_x != actual.min_x ||
|
|
55
|
+
bounds.max_x != actual.max_x ||
|
|
56
|
+
bounds.min_y != actual.min_y ||
|
|
57
|
+
bounds.max_y != actual.max_y) {
|
|
58
|
+
throw std::invalid_argument("Invalid polygon bounds: bounding box does not match outer contour");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
void MergerValidator::validate_polygon_order(const std::list<Polygon>& polygons) const {
|
|
64
|
+
bool has_previous = false;
|
|
65
|
+
std::pair<int, int> previous;
|
|
66
|
+
|
|
67
|
+
for (const auto& polygon : polygons) {
|
|
68
|
+
const auto& bounds = polygon.bounds;
|
|
69
|
+
|
|
70
|
+
const std::pair<int, int> current = merger_.transpose()
|
|
71
|
+
? std::make_pair(bounds.min_x, bounds.min_y)
|
|
72
|
+
: std::make_pair(bounds.min_y, bounds.min_x);
|
|
73
|
+
|
|
74
|
+
if (has_previous && current < previous) {
|
|
75
|
+
throw std::invalid_argument("Invalid polygon order: polygons are not in raster scan order");
|
|
76
|
+
}
|
|
77
|
+
previous = current;
|
|
78
|
+
has_previous = true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
void MergerValidator::validate_boundary(const ProcessResult& result, int start_x) const {
|
|
83
|
+
if (!has_previous_boundary_) return;
|
|
84
|
+
|
|
85
|
+
const auto current = boundary_coordinates(result.polygons, start_x);
|
|
86
|
+
if (previous_boundary_ != current) {
|
|
87
|
+
throw std::invalid_argument("Tile boundary mismatch: adjacent tiles have different edge coordinates");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
std::vector<int> MergerValidator::boundary_coordinates(const std::list<Polygon>& polygons, int boundary_x) {
|
|
92
|
+
std::vector<int> coordinates;
|
|
93
|
+
|
|
94
|
+
for (const auto& polygon : polygons) {
|
|
95
|
+
for (const auto& point : polygon.outer) {
|
|
96
|
+
if (point.x == boundary_x) {
|
|
97
|
+
coordinates.push_back(point.y);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
std::sort(coordinates.begin(), coordinates.end());
|
|
103
|
+
return coordinates;
|
|
104
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MergerValidator.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 <vector>
|
|
13
|
+
#include <list>
|
|
14
|
+
#include "../PolygonFinder.h"
|
|
15
|
+
|
|
16
|
+
class Merger;
|
|
17
|
+
|
|
18
|
+
class MergerValidator {
|
|
19
|
+
public:
|
|
20
|
+
explicit MergerValidator(const Merger& merger);
|
|
21
|
+
|
|
22
|
+
void validate_metadata(const ProcessResult& result) const;
|
|
23
|
+
void validate(const ProcessResult& result, int start_x, int end_x);
|
|
24
|
+
|
|
25
|
+
private:
|
|
26
|
+
const Merger& merger_;
|
|
27
|
+
std::vector<int> previous_boundary_;
|
|
28
|
+
bool has_previous_boundary_ = false;
|
|
29
|
+
|
|
30
|
+
void validate_bounds(const std::list<Polygon>& polygons) const;
|
|
31
|
+
void validate_polygon_order(const std::list<Polygon>& polygons) const;
|
|
32
|
+
void validate_boundary(const ProcessResult& result, int start_x) const;
|
|
33
|
+
|
|
34
|
+
static std::vector<int> boundary_coordinates(const std::list<Polygon>& polygons, int boundary_x);
|
|
35
|
+
};
|
|
@@ -17,10 +17,11 @@
|
|
|
17
17
|
#include "Tile.h"
|
|
18
18
|
#include "Shape.h"
|
|
19
19
|
|
|
20
|
-
Polyline::Polyline(ShapePool* shape_pool, Tile* tile, std::vector<Point> polygon, const std::optional<RectBounds>& bounds)
|
|
20
|
+
Polyline::Polyline(ShapePool* shape_pool, Tile* tile, std::vector<Point> polygon, const std::optional<RectBounds>& bounds, unsigned int class_id)
|
|
21
21
|
: tile(tile),
|
|
22
22
|
shape_pool(shape_pool),
|
|
23
|
-
raw_(std::move(polygon))
|
|
23
|
+
raw_(std::move(polygon)),
|
|
24
|
+
class_id_(class_id)
|
|
24
25
|
{ if (bounds.has_value()) {
|
|
25
26
|
min_x = bounds->min_x;
|
|
26
27
|
max_x_ = bounds->max_x;
|
|
@@ -29,7 +29,7 @@ class InnerPolyline;
|
|
|
29
29
|
class Polyline : public Partitionable {
|
|
30
30
|
public:
|
|
31
31
|
using Partitionable::Partitionable;
|
|
32
|
-
Polyline(ShapePool* shape_pool, Tile* tile, std::vector<Point> polygon, const std::optional<RectBounds>& bounds = std::nullopt);
|
|
32
|
+
Polyline(ShapePool* shape_pool, Tile* tile, std::vector<Point> polygon, const std::optional<RectBounds>& bounds = std::nullopt, unsigned int class_id = 0);
|
|
33
33
|
enum Flags : uint32_t {
|
|
34
34
|
TRACKED_OUTER = 1 << 0
|
|
35
35
|
};
|
|
@@ -47,6 +47,7 @@ class Polyline : public Partitionable {
|
|
|
47
47
|
int max_y() const { return max_y_; }
|
|
48
48
|
int min_y() const { return min_y_; }
|
|
49
49
|
int max_x() const { return max_x_; }
|
|
50
|
+
unsigned int class_id() const { return class_id_; }
|
|
50
51
|
void clear();
|
|
51
52
|
bool is_empty();
|
|
52
53
|
bool any_ancients = false;
|
|
@@ -63,5 +64,6 @@ class Polyline : public Partitionable {
|
|
|
63
64
|
void find_boundary();
|
|
64
65
|
uint32_t flags_ = 0;
|
|
65
66
|
int name;
|
|
67
|
+
unsigned int class_id_;
|
|
66
68
|
std::string named_;
|
|
67
69
|
};
|
|
@@ -49,6 +49,7 @@ std::string Shape::name() {
|
|
|
49
49
|
Polygon Shape::to_raw_polygon(bool bounds) {
|
|
50
50
|
Polygon poly;
|
|
51
51
|
poly.outer = this->outer_polyline->raw();
|
|
52
|
+
poly.class_id = this->outer_polyline->class_id();
|
|
52
53
|
if (bounds) {
|
|
53
54
|
this->outer_polyline->fill_bounds(poly.bounds);
|
|
54
55
|
}
|
|
@@ -19,8 +19,8 @@ Shape* ShapePool::acquire_shape(Polyline* outer_polyline, const std::vector<Inne
|
|
|
19
19
|
return shape;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
Polyline* ShapePool::acquire_polyline(Tile* tile, std::vector<Point> polygon, const std::optional<RectBounds>& bounds = std::nullopt) {
|
|
23
|
-
return this->polylines.acquire(this, tile, std::move(polygon), bounds);
|
|
22
|
+
Polyline* ShapePool::acquire_polyline(Tile* tile, std::vector<Point> polygon, unsigned int class_id, const std::optional<RectBounds>& bounds = std::nullopt) {
|
|
23
|
+
return this->polylines.acquire(this, tile, std::move(polygon), bounds, class_id);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
InnerPolyline* ShapePool::acquire_inner_polyline(std::vector<Point> coords, Shape* shape) {
|
|
@@ -29,7 +29,7 @@ class ShapePool {
|
|
|
29
29
|
Shape* acquire_shape(Polyline* outer_polyline, const std::vector<InnerPolyline*>& inner_polylines);
|
|
30
30
|
InnerPolyline* acquire_inner_polyline(std::vector<Point> coords, Shape* s);
|
|
31
31
|
InnerPolyline* acquire_inner_polyline(Sequence* seq);
|
|
32
|
-
Polyline* acquire_polyline(Tile* tile, std::vector<Point> polygon, const std::optional<RectBounds>& bounds);
|
|
32
|
+
Polyline* acquire_polyline(Tile* tile, std::vector<Point> polygon, unsigned int class_id, const std::optional<RectBounds>& bounds);
|
|
33
33
|
void set_owner(Tile* tile);
|
|
34
34
|
void detach_shape();
|
|
35
35
|
void detach_polyline();
|
|
@@ -60,27 +60,25 @@ void Tile::assign_raw_polygons(const std::list<Polygon>& raw_polylines, const st
|
|
|
60
60
|
std::unordered_map<int, Shape*> shapes_map;
|
|
61
61
|
int polyline_index = 0;
|
|
62
62
|
for (const auto& raw_polyline : raw_polylines)
|
|
63
|
-
{
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
shape->parent_inner_polyline = parent->inner_polylines[treemap_entry.second];
|
|
83
|
-
}
|
|
63
|
+
{ Polyline* polyline = this->shapes_pool->acquire_polyline(this, raw_polyline.outer, raw_polyline.class_id, raw_polyline.bounds);
|
|
64
|
+
std::vector<InnerPolyline*> inner_polylines_list;
|
|
65
|
+
for (auto& raw_points : raw_polyline.inner) {
|
|
66
|
+
inner_polylines_list.push_back(this->shapes_pool->acquire_inner_polyline(raw_points, nullptr));
|
|
67
|
+
}
|
|
68
|
+
Shape* shape = this->shapes_pool->acquire_shape(polyline, inner_polylines_list);
|
|
69
|
+
polyline->shape = shape;
|
|
70
|
+
this->shapes_.push_back(shape);
|
|
71
|
+
|
|
72
|
+
if (treemap.size() > 0)
|
|
73
|
+
{ auto treemap_entry = treemap[polyline_index];
|
|
74
|
+
shapes_map[polyline_index] = shape;
|
|
75
|
+
if (treemap_entry.first != -1 && treemap_entry.second != -1) {
|
|
76
|
+
auto it = shapes_map.find(treemap_entry.first);
|
|
77
|
+
if (it != shapes_map.end()) {
|
|
78
|
+
Shape* parent = it->second;
|
|
79
|
+
shape->set_parent_shape(parent);
|
|
80
|
+
shape->fixed = true;
|
|
81
|
+
shape->parent_inner_polyline = parent->inner_polylines[treemap_entry.second];
|
|
84
82
|
}
|
|
85
83
|
}
|
|
86
84
|
}
|
|
@@ -106,6 +106,8 @@
|
|
|
106
106
|
#include "PolygonFinder/src/polygon/finder/concurrent/PartPool.cpp"
|
|
107
107
|
#include "PolygonFinder/src/polygon/finder/concurrent/Merger.h"
|
|
108
108
|
#include "PolygonFinder/src/polygon/finder/concurrent/Merger.cpp"
|
|
109
|
+
#include "PolygonFinder/src/polygon/finder/concurrent/MergerValidator.h"
|
|
110
|
+
#include "PolygonFinder/src/polygon/finder/concurrent/MergerValidator.cpp"
|
|
109
111
|
#include "PolygonFinder/src/polygon/finder/concurrent/HorizontalMerger.h"
|
|
110
112
|
#include "PolygonFinder/src/polygon/finder/concurrent/HorizontalMerger.cpp"
|
|
111
113
|
#include "PolygonFinder/src/polygon/finder/concurrent/VerticalMerger.h"
|
|
@@ -275,6 +277,9 @@ class To_Ruby<ProcessResult*>
|
|
|
275
277
|
poly_hash[Symbol("bounds")] = bounds_hash;
|
|
276
278
|
}
|
|
277
279
|
|
|
280
|
+
if(pr->options.contains("multiclass")) {
|
|
281
|
+
poly_hash[Symbol("class")] = x.class_id;
|
|
282
|
+
}
|
|
278
283
|
out.push(poly_hash);
|
|
279
284
|
}
|
|
280
285
|
rr->polygons = out;
|
|
@@ -315,6 +320,10 @@ ProcessResult ruby_result_to_process_result(Rice::Object rb_result) {
|
|
|
315
320
|
for (size_t i = 0; i < rb_polygons.size(); ++i) {
|
|
316
321
|
Rice::Hash rb_poly = (Rice::Hash)rb_polygons[i];
|
|
317
322
|
Polygon poly;
|
|
323
|
+
Rice::Object rb_class = rb_poly[Symbol("class")];
|
|
324
|
+
if (!rb_class.is_nil()) {
|
|
325
|
+
poly.class_id = detail::From_Ruby<unsigned int>().convert(rb_class.value());
|
|
326
|
+
}
|
|
318
327
|
// BOUNDS
|
|
319
328
|
Rice::Object rb_bounds_obj = rb_poly[Symbol("bounds")];
|
|
320
329
|
if (!rb_bounds_obj.is_nil()) {
|
|
@@ -380,10 +389,9 @@ SvgStreamingMerger* create_svg_streaming_merger(Object self,
|
|
|
380
389
|
GeoJsonStreamingMerger* create_geo_json_streaming_merger(Object self,
|
|
381
390
|
int number_of_threads,
|
|
382
391
|
Options options,
|
|
383
|
-
Object stream_obj
|
|
384
|
-
int pixel_val) {
|
|
392
|
+
Object stream_obj) {
|
|
385
393
|
OfstreamWrapper* wrapper = Rice::detail::From_Ruby<OfstreamWrapper*>().convert(stream_obj.value());
|
|
386
|
-
return new GeoJsonStreamingMerger(number_of_threads, options, &wrapper->get_stream()
|
|
394
|
+
return new GeoJsonStreamingMerger(number_of_threads, options, &wrapper->get_stream());
|
|
387
395
|
}
|
|
388
396
|
|
|
389
397
|
OfstreamWrapper* create_ofstream(Object self, std::string path) {
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
module Contrek
|
|
4
4
|
module Cpp
|
|
5
5
|
class CPPGeoJsonConcurrentStreamingMerger < CPPGeoJsonStreamingMerger
|
|
6
|
-
def self.new(stream_to:, number_of_threads: 0, options: nil
|
|
7
|
-
super(number_of_threads, options, stream_to
|
|
6
|
+
def self.new(stream_to:, number_of_threads: 0, options: nil)
|
|
7
|
+
super(number_of_threads, options, stream_to)
|
|
8
8
|
end
|
|
9
9
|
end
|
|
10
10
|
end
|
|
@@ -23,7 +23,8 @@ module Contrek
|
|
|
23
23
|
@to_points ||= raw_list.map do |polygon|
|
|
24
24
|
{bounds: (polygon[:bounds] if polygon.key?(:bounds)),
|
|
25
25
|
outer: self.class.to_points(polygon[:outer]),
|
|
26
|
-
inner: polygon[:inner].map { |s| self.class.to_points(s) }
|
|
26
|
+
inner: polygon[:inner].map { |s| self.class.to_points(s) },
|
|
27
|
+
class: (polygon[:class] if polygon.key?(:class))}.compact
|
|
27
28
|
end
|
|
28
29
|
end
|
|
29
30
|
|
|
@@ -69,7 +69,7 @@ module Contrek
|
|
|
69
69
|
end.real
|
|
70
70
|
shape.clear_inner!
|
|
71
71
|
|
|
72
|
-
polyline = Polyline.new(tile: tile, polygon: new_outer.to_a)
|
|
72
|
+
polyline = Polyline.new(tile: tile, polygon: new_outer.to_a, class_id: shape.outer_polyline.class_id)
|
|
73
73
|
inserting_new_shape = Shape.init_by(polyline, new_inners)
|
|
74
74
|
new_shapes << inserting_new_shape
|
|
75
75
|
polyline.shape = inserting_new_shape
|
|
@@ -91,7 +91,9 @@ module Contrek
|
|
|
91
91
|
new_position.end_point.tracked_outer = true
|
|
92
92
|
versus = act_part.versus
|
|
93
93
|
part = new_position.end_point.queues.find do |p|
|
|
94
|
-
(p.versus == -versus) &&
|
|
94
|
+
(p.versus == -versus) &&
|
|
95
|
+
p.polyline.tile != act_part.polyline.tile &&
|
|
96
|
+
p.polyline.class_id == act_part.polyline.class_id
|
|
95
97
|
end
|
|
96
98
|
if part
|
|
97
99
|
if all_parts[-2] != part
|
|
@@ -55,7 +55,8 @@ module Contrek
|
|
|
55
55
|
versus: current_versus,
|
|
56
56
|
bounds: true,
|
|
57
57
|
treemap: @options[:treemap],
|
|
58
|
-
connectivity: @options[:connectivity]
|
|
58
|
+
connectivity: @options[:connectivity],
|
|
59
|
+
multiclass: @options[:multiclass]
|
|
59
60
|
}.compact,
|
|
60
61
|
start_x: payload[:tile_start_x],
|
|
61
62
|
end_x: payload[:tile_end_x]
|
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
module Contrek
|
|
4
4
|
module Concurrent
|
|
5
5
|
class GeoJsonStreamingMerger < StreamingMerger
|
|
6
|
-
def initialize(stream_to:,
|
|
7
|
-
@pixel_val = pixel_val
|
|
6
|
+
def initialize(stream_to:, options: {})
|
|
8
7
|
@point_converter = self.class.coordinates_converter(options[:geo_localization])
|
|
9
8
|
|
|
10
|
-
super
|
|
9
|
+
super
|
|
11
10
|
end
|
|
12
11
|
|
|
13
12
|
def write_header
|
|
@@ -35,7 +34,7 @@ module Contrek
|
|
|
35
34
|
end
|
|
36
35
|
feature_hash = {
|
|
37
36
|
type: "Feature",
|
|
38
|
-
properties: {PixelVal:
|
|
37
|
+
properties: {PixelVal: polygon[:class]},
|
|
39
38
|
geometry: {
|
|
40
39
|
type: "Polygon",
|
|
41
40
|
coordinates: polygon_coordinates
|
|
@@ -13,18 +13,21 @@ module Contrek
|
|
|
13
13
|
@current_x = 0
|
|
14
14
|
@tiles = Queue.new
|
|
15
15
|
@whole_tile = nil
|
|
16
|
-
@previous_boundary = nil
|
|
17
16
|
@user_options = options
|
|
18
17
|
if (warn_options = @user_options.keys & IGNORE_OPTIONS).any?
|
|
19
18
|
warn "[Contrek WARNING] The given options '#{warn_options.join(",")}' will be ignored."
|
|
20
19
|
end
|
|
21
20
|
@options = {unsafe_mode: false}.merge(@user_options).except(*IGNORE_OPTIONS)
|
|
22
|
-
|
|
21
|
+
if safe?
|
|
22
|
+
@validator = MergerValidator.new(self)
|
|
23
|
+
else
|
|
23
24
|
warn "[Contrek WARNING] Processing tile with 'unsafe_mode: true'. Incompatible result options might lead to unexpected vector geometry."
|
|
24
25
|
end
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def add_tile(result)
|
|
29
|
+
@validator&.validate_metadata(result)
|
|
30
|
+
|
|
28
31
|
@height ||= result.metadata[:height]
|
|
29
32
|
raise ArgumentError, "All results must have the same height" if @height != result.metadata[:height] && safe?
|
|
30
33
|
@options[:versus] ||= result.metadata[:versus]
|
|
@@ -35,8 +38,8 @@ module Contrek
|
|
|
35
38
|
end
|
|
36
39
|
end
|
|
37
40
|
|
|
38
|
-
validate(result) if safe?
|
|
39
41
|
end_x = @current_x + result.metadata[:width]
|
|
42
|
+
@validator&.validate(result, @current_x, end_x)
|
|
40
43
|
|
|
41
44
|
tile = Tile.new(
|
|
42
45
|
finder: self,
|
|
@@ -47,9 +50,6 @@ module Contrek
|
|
|
47
50
|
tile.assign_raw_polygons!(result[:polygons])
|
|
48
51
|
|
|
49
52
|
@tiles << tile
|
|
50
|
-
|
|
51
|
-
@previous_boundary = boundary_coordinates(result.polygons, end_x) if safe?
|
|
52
|
-
|
|
53
53
|
@maximum_width = end_x
|
|
54
54
|
@current_x = end_x - 1
|
|
55
55
|
end
|
|
@@ -65,25 +65,6 @@ module Contrek
|
|
|
65
65
|
|
|
66
66
|
private
|
|
67
67
|
|
|
68
|
-
def validate(result)
|
|
69
|
-
return unless @previous_boundary
|
|
70
|
-
|
|
71
|
-
current = boundary_coordinates(result.polygons, @current_x)
|
|
72
|
-
unless @previous_boundary == current
|
|
73
|
-
raise ArgumentError, "Tile boundary mismatch: adjacent tiles have different edge coordinates"
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def boundary_coordinates(polygons, boundary_x)
|
|
78
|
-
coordinates = []
|
|
79
|
-
polygons.each do |polygon|
|
|
80
|
-
polygon[:outer].each do |point|
|
|
81
|
-
coordinates << point[:y] if point[:x] == boundary_x
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
coordinates.sort!
|
|
85
|
-
end
|
|
86
|
-
|
|
87
68
|
def translate(result, offset)
|
|
88
69
|
result.polygons.each do |polygon|
|
|
89
70
|
polygon[:outer].each { |p| p[:x] += offset }
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Contrek
|
|
4
|
+
module Concurrent
|
|
5
|
+
class MergerValidator
|
|
6
|
+
def initialize(merger)
|
|
7
|
+
@merger = merger
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def validate(result, start_x, end_x)
|
|
11
|
+
validate_bounds(result.polygons)
|
|
12
|
+
validate_polygon_order(result.polygons)
|
|
13
|
+
validate_boundary(result, start_x)
|
|
14
|
+
|
|
15
|
+
@previous_boundary = boundary_coordinates(result.polygons, end_x)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def validate_metadata(result)
|
|
19
|
+
metadata = result.metadata
|
|
20
|
+
[:width, :height, :versus].each do |key|
|
|
21
|
+
raise ArgumentError, "Missing result metadata: #{key}" unless metadata.key?(key)
|
|
22
|
+
end
|
|
23
|
+
[:width, :height].each do |key|
|
|
24
|
+
value = metadata[key]
|
|
25
|
+
unless value.is_a?(Integer) && value.positive?
|
|
26
|
+
raise ArgumentError, "Invalid result metadata: #{key} must be a positive integer"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
unless [:a, :o].include?(metadata[:versus])
|
|
30
|
+
raise ArgumentError, "Invalid result metadata: unsupported versus option"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def validate_polygon_order(polygons)
|
|
37
|
+
previous = nil
|
|
38
|
+
polygons.each do |polygon|
|
|
39
|
+
bounds = polygon[:bounds]
|
|
40
|
+
current = if @merger.transpose?
|
|
41
|
+
[bounds[:min_x], bounds[:min_y]]
|
|
42
|
+
else
|
|
43
|
+
[bounds[:min_y], bounds[:min_x]]
|
|
44
|
+
end
|
|
45
|
+
if previous && (current <=> previous) < 0
|
|
46
|
+
raise ArgumentError, "Invalid polygon order: polygons are not in raster scan order"
|
|
47
|
+
end
|
|
48
|
+
previous = current
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def validate_boundary(result, start_x)
|
|
53
|
+
return unless @previous_boundary
|
|
54
|
+
|
|
55
|
+
current = boundary_coordinates(result.polygons, start_x)
|
|
56
|
+
unless @previous_boundary == current
|
|
57
|
+
raise ArgumentError, "Tile boundary mismatch: adjacent tiles have different edge coordinates"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def boundary_coordinates(polygons, boundary_x)
|
|
62
|
+
coordinates = []
|
|
63
|
+
polygons.each do |polygon|
|
|
64
|
+
polygon[:outer].each do |point|
|
|
65
|
+
coordinates << point[:y] if point[:x] == boundary_x
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
coordinates.sort!
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def validate_bounds(polygons)
|
|
72
|
+
polygons.each do |polygon|
|
|
73
|
+
outer = polygon[:outer]
|
|
74
|
+
raise ArgumentError, "Invalid polygon: empty outer contour" if outer.empty?
|
|
75
|
+
bounds = polygon[:bounds]
|
|
76
|
+
min_x = max_x = outer[0][:x]
|
|
77
|
+
min_y = max_y = outer[0][:y]
|
|
78
|
+
outer.each do |point|
|
|
79
|
+
x = point[:x]
|
|
80
|
+
y = point[:y]
|
|
81
|
+
min_x = x if x < min_x
|
|
82
|
+
max_x = x if x > max_x
|
|
83
|
+
min_y = y if y < min_y
|
|
84
|
+
max_y = y if y > max_y
|
|
85
|
+
end
|
|
86
|
+
unless bounds[:min_x] == min_x &&
|
|
87
|
+
bounds[:max_x] == max_x &&
|
|
88
|
+
bounds[:min_y] == min_y &&
|
|
89
|
+
bounds[:max_y] == max_y
|
|
90
|
+
raise ArgumentError, "Invalid polygon bounds: bounding box does not match outer contour"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -7,10 +7,10 @@ module Contrek
|
|
|
7
7
|
|
|
8
8
|
TRACKED_OUTER = 1 << 0
|
|
9
9
|
|
|
10
|
-
attr_reader :raw, :name, :min_y, :max_y
|
|
10
|
+
attr_reader :raw, :name, :min_y, :max_y, :class_id
|
|
11
11
|
attr_accessor :shape, :tile, :any_ancients, :inside_inner_polyline
|
|
12
12
|
|
|
13
|
-
def initialize(tile:, polygon:, shape: nil, bounds: nil, force_named: nil)
|
|
13
|
+
def initialize(tile:, polygon:, shape: nil, bounds: nil, force_named: nil, class_id: 0)
|
|
14
14
|
@tile = tile
|
|
15
15
|
@name = tile.shapes.count
|
|
16
16
|
@named = force_named
|
|
@@ -19,6 +19,7 @@ module Contrek
|
|
|
19
19
|
@flags = 0
|
|
20
20
|
@any_ancients = false
|
|
21
21
|
@inside_inner_polyline = nil
|
|
22
|
+
@class_id = class_id
|
|
22
23
|
|
|
23
24
|
if bounds.nil?
|
|
24
25
|
find_boundary
|
|
@@ -40,8 +40,9 @@ module Contrek
|
|
|
40
40
|
"Shape (outer_polyline = #{outer_polyline.named}, children count = #{@children_shapes.size}"
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
def to_raw_polygon(bounds: false)
|
|
43
|
+
def to_raw_polygon(bounds: false, multiclass: false)
|
|
44
44
|
{bounds: (outer_polyline.get_bounds if bounds),
|
|
45
|
+
class: (outer_polyline.class_id if multiclass),
|
|
45
46
|
outer: outer_polyline.raw,
|
|
46
47
|
inner: inner_polylines.map(&:raw)}.compact
|
|
47
48
|
end
|
|
@@ -57,7 +57,8 @@ module Contrek
|
|
|
57
57
|
|
|
58
58
|
def to_raw_polygons
|
|
59
59
|
bounds = @finder.options[:bounds]
|
|
60
|
-
|
|
60
|
+
multiclass = @finder.options[:multiclass]
|
|
61
|
+
@shapes.map { |s| s.to_raw_polygon(bounds:, multiclass:) }
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
def compute_treemap
|
|
@@ -92,9 +93,14 @@ module Contrek
|
|
|
92
93
|
@shapes = []
|
|
93
94
|
shapes_map = {}
|
|
94
95
|
raw_polylines.each_with_index do |raw_polyline, polyline_index|
|
|
95
|
-
next if raw_polyline[:bounds][:max_x] - raw_polyline[:bounds][:min_x] == 0
|
|
96
96
|
shape = Shape.new.tap do |shape|
|
|
97
|
-
shape.outer_polyline = Polyline.new(
|
|
97
|
+
shape.outer_polyline = Polyline.new(
|
|
98
|
+
tile: self,
|
|
99
|
+
polygon: raw_polyline[:outer],
|
|
100
|
+
shape: shape,
|
|
101
|
+
bounds: raw_polyline[:bounds],
|
|
102
|
+
class_id: raw_polyline[:class] || 0
|
|
103
|
+
)
|
|
98
104
|
shape.inner_polylines = raw_polyline[:inner].map { |raw| InnerPolyline.new(shape: shape, raw_coordinates: raw) }
|
|
99
105
|
end
|
|
100
106
|
@shapes << shape
|