contrek 1.4.5 → 1.4.6
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 +5 -9
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +2 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/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/Tile.cpp +19 -21
- data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +2 -0
- data/lib/contrek/finder/concurrent/merger.rb +6 -25
- data/lib/contrek/finder/concurrent/merger_validator.rb +96 -0
- data/lib/contrek/finder/concurrent/tile.rb +0 -1
- data/lib/contrek/version.rb +1 -1
- data/lib/contrek.rb +1 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a6ed7af7c2661396d55b6ddacd89dfc6c08c4c54744d1443d03a6ba533605ed4
|
|
4
|
+
data.tar.gz: 3308bedea2f5e89fe8031a65c404c250490542142101dba32fd09f8510cb8cfb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 250b0ed1bce589beaae764e03226206853f9e9c3bb356fe3bf827cd25c69d72f33ad820f3afa5d9d31aa83e628e67dd8c4500782dcdb668f44b0e098932f6c4f
|
|
7
|
+
data.tar.gz: 7cf17a172afd9ec9b1788f2abe1779421d204886b99fd54e4729fbf9b616cc3968bb7586cafa5b17da6b5cbcf9641e3f210ee6d3d320e4be67e945022964af13
|
data/CHANGELOG.md
CHANGED
|
@@ -170,3 +170,6 @@ All notable changes to this project will be documented in this file.
|
|
|
170
170
|
|
|
171
171
|
## [1.4.5] - 2026-10-11
|
|
172
172
|
- Added tile boundary validation for external geometries, with support for bypassing checks via `unsafe_mode: true`.
|
|
173
|
+
|
|
174
|
+
## [1.4.6] - 2026-10-18
|
|
175
|
+
- Added tile metadata, polygon bounds, ordering, and shared boundary validation for Ruby and C++ mergers, with `unsafe_mode: true` to bypass validation.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -726,17 +726,13 @@ extractors, provided that the input geometry follows the requirements below.
|
|
|
726
726
|
|
|
727
727
|
### Boundary Validation
|
|
728
728
|
|
|
729
|
-
By default, Contrek validates the shared boundaries of adjacent tiles when
|
|
730
|
-
merging externally supplied geometries. Boundary coordinates must match
|
|
731
|
-
exactly, including intermediate vertices. Otherwise, the merger raises a
|
|
732
|
-
`Tile boundary mismatch` error.
|
|
729
|
+
By default, Contrek validates the shared boundaries of adjacent tiles when merging externally supplied geometries. Boundary coordinates must match exactly, including intermediate vertices. Otherwise, the merger raises a `Tile boundary mismatch` error.
|
|
733
730
|
|
|
734
|
-
This validation can be disabled by setting `unsafe_mode: true`, avoiding
|
|
735
|
-
validation overhead when the input geometries are known to be valid.
|
|
731
|
+
This validation can be disabled by setting `unsafe_mode: true`, avoiding validation overhead when the input geometries are known to be valid.
|
|
736
732
|
|
|
737
|
-
**Important:** Boundary validation does not verify all the Laws of the Tiles.
|
|
738
|
-
|
|
739
|
-
|
|
733
|
+
**Important:** Boundary validation does not verify all the Laws of the Tiles. Users supplying external geometries remain responsible for satisfying every requirement listed above.
|
|
734
|
+
|
|
735
|
+
**Important:** Boundary validation prioritizes correctness over performance and introduce additional processing overhead. Experienced users who can guarantee that their input geometries satisfy the Laws of the Tiles can enable `unsafe_mode: true` to bypass validation entirely and achieve maximum merging performance. This option can also be useful during debugging to isolate merging issues from validation errors.
|
|
740
736
|
|
|
741
737
|
# Results
|
|
742
738
|
|
|
@@ -413,7 +413,7 @@ void stream_progressive_png_image(const std::string& filepath, uint32_t stripe_h
|
|
|
413
413
|
|
|
414
414
|
SvgStreamingMerger merger(
|
|
415
415
|
0,
|
|
416
|
-
{{"bounds", true},{"unsafe_mode", true}},
|
|
416
|
+
{{"bounds", true}, {"unsafe_mode", true}},
|
|
417
417
|
&stream,
|
|
418
418
|
source.width(),
|
|
419
419
|
source.height());
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
#include <vector>
|
|
13
13
|
#include <algorithm>
|
|
14
14
|
#include <list>
|
|
15
|
+
#include <memory>
|
|
15
16
|
#include "Merger.h"
|
|
16
17
|
|
|
17
18
|
Merger::Merger(int number_of_threads, const Options& options)
|
|
@@ -19,7 +20,9 @@ Merger::Merger(int number_of_threads, const Options& options)
|
|
|
19
20
|
if (options.contains("versus")) {
|
|
20
21
|
std::cerr << "[Contrek WARNING] The given options 'versus' will be ignored." << std::endl;
|
|
21
22
|
}
|
|
22
|
-
if (
|
|
23
|
+
if (safe()) {
|
|
24
|
+
validator_ = std::make_unique<MergerValidator>(*this);
|
|
25
|
+
} else {
|
|
23
26
|
std::cerr << "[Contrek WARNING] Processing tile with 'unsafe_mode: true'. "
|
|
24
27
|
<< "Incompatible result options might lead to unexpected vector geometry.\n";
|
|
25
28
|
}
|
|
@@ -30,7 +33,10 @@ bool Merger::safe() {
|
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
void Merger::add_tile(ProcessResult& result)
|
|
33
|
-
{ if (
|
|
36
|
+
{ if (validator_) {
|
|
37
|
+
validator_->validate_metadata(result);
|
|
38
|
+
}
|
|
39
|
+
if (this->height == 0) {
|
|
34
40
|
this->height = result.height;
|
|
35
41
|
} else {
|
|
36
42
|
if (result.height != this->height && safe()) {
|
|
@@ -51,17 +57,15 @@ void Merger::add_tile(ProcessResult& result)
|
|
|
51
57
|
}
|
|
52
58
|
}
|
|
53
59
|
}
|
|
54
|
-
if (safe()) validate(result);
|
|
55
60
|
|
|
56
61
|
int end_x = this->current_x + result.width;
|
|
62
|
+
if (validator_) {
|
|
63
|
+
validator_->validate(result, this->current_x, end_x);
|
|
64
|
+
}
|
|
57
65
|
Tile* tile = new Tile(this, this->current_x, end_x, std::to_string(tiles_.size()), tiles_.size(), Benchmarks {0, 0});
|
|
58
66
|
tile->assign_raw_polygons(result.polygons, result.treemap);
|
|
59
67
|
tiles_.queue_push(tile);
|
|
60
68
|
|
|
61
|
-
if (safe()) {
|
|
62
|
-
previous_boundary_ = boundary_coordinates(result.polygons, end_x);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
69
|
this->maximum_width_ = end_x;
|
|
66
70
|
this->current_x = end_x - 1;
|
|
67
71
|
}
|
|
@@ -84,26 +88,3 @@ void Merger::translate(ProcessResult& result, int offset) {
|
|
|
84
88
|
}
|
|
85
89
|
}
|
|
86
90
|
|
|
87
|
-
std::vector<int> Merger::boundary_coordinates(const std::list<Polygon>& polygons, int boundary_x) {
|
|
88
|
-
std::vector<int> coordinates;
|
|
89
|
-
|
|
90
|
-
for (const auto& polygon : polygons) {
|
|
91
|
-
for (const auto& point : polygon.outer) {
|
|
92
|
-
if (point.x == boundary_x) {
|
|
93
|
-
coordinates.push_back(point.y);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
std::sort(coordinates.begin(), coordinates.end());
|
|
99
|
-
return coordinates;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
void Merger::validate(const ProcessResult& result) {
|
|
103
|
-
if (tiles_.size() == 0) return;
|
|
104
|
-
|
|
105
|
-
const auto current = boundary_coordinates(result.polygons, current_x);
|
|
106
|
-
if (previous_boundary_ != current) {
|
|
107
|
-
throw std::invalid_argument("Tile boundary mismatch: adjacent tiles have different edge coordinates");
|
|
108
|
-
}
|
|
109
|
-
}
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
#include <string>
|
|
12
12
|
#include <vector>
|
|
13
13
|
#include <list>
|
|
14
|
+
#include <memory>
|
|
14
15
|
#include "Finder.h"
|
|
16
|
+
#include "MergerValidator.h"
|
|
15
17
|
#include "../Options.h"
|
|
16
18
|
|
|
17
19
|
class Merger : public Finder {
|
|
@@ -24,7 +26,5 @@ class Merger : public Finder {
|
|
|
24
26
|
protected:
|
|
25
27
|
void translate(ProcessResult& result, int offset);
|
|
26
28
|
int current_x = 0;
|
|
27
|
-
|
|
28
|
-
static std::vector<int> boundary_coordinates(const std::list<Polygon>& polygons, int boundary_x);
|
|
29
|
-
std::vector<int> previous_boundary_;
|
|
29
|
+
std::unique_ptr<MergerValidator> validator_;
|
|
30
30
|
};
|
|
@@ -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
|
+
};
|
|
@@ -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.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"
|
|
@@ -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
|
|
@@ -92,7 +92,6 @@ module Contrek
|
|
|
92
92
|
@shapes = []
|
|
93
93
|
shapes_map = {}
|
|
94
94
|
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
95
|
shape = Shape.new.tap do |shape|
|
|
97
96
|
shape.outer_polyline = Polyline.new(tile: self, polygon: raw_polyline[:outer], shape: shape, bounds: raw_polyline[:bounds])
|
|
98
97
|
shape.inner_polylines = raw_polyline[:inner].map { |raw| InnerPolyline.new(shape: shape, raw_coordinates: raw) }
|
data/lib/contrek/version.rb
CHANGED
data/lib/contrek.rb
CHANGED
|
@@ -45,6 +45,7 @@ require "contrek/finder/concurrent/polyline"
|
|
|
45
45
|
require "contrek/finder/concurrent/cluster"
|
|
46
46
|
require "contrek/finder/concurrent/finder"
|
|
47
47
|
require "contrek/finder/concurrent/merger"
|
|
48
|
+
require "contrek/finder/concurrent/merger_validator"
|
|
48
49
|
require "contrek/finder/concurrent/horizontal_merger"
|
|
49
50
|
require "contrek/finder/concurrent/vertical_merger"
|
|
50
51
|
require "contrek/finder/concurrent/streaming_merger"
|
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.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Emanuele Cesaroni
|
|
@@ -215,6 +215,8 @@ files:
|
|
|
215
215
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/InnerPolyline.h
|
|
216
216
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.cpp
|
|
217
217
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.h
|
|
218
|
+
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/MergerValidator.cpp
|
|
219
|
+
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/MergerValidator.h
|
|
218
220
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.cpp
|
|
219
221
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.h
|
|
220
222
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/PartPool.cpp
|
|
@@ -305,6 +307,7 @@ files:
|
|
|
305
307
|
- lib/contrek/finder/concurrent/inner_polyline.rb
|
|
306
308
|
- lib/contrek/finder/concurrent/listable.rb
|
|
307
309
|
- lib/contrek/finder/concurrent/merger.rb
|
|
310
|
+
- lib/contrek/finder/concurrent/merger_validator.rb
|
|
308
311
|
- lib/contrek/finder/concurrent/part.rb
|
|
309
312
|
- lib/contrek/finder/concurrent/partitionable.rb
|
|
310
313
|
- lib/contrek/finder/concurrent/polyline.rb
|