contrek 1.3.1 → 1.3.2
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 +3 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +23 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/FastPngBitmap.cpp +2 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.cpp +2 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RemoteFastPngBitmap.cpp +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.h +2 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.h +4 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.cpp +6 -6
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.cpp +16 -10
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.h +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +16 -15
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.h +3 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +1 -9
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/GeoJsonStreamingMerger.h +81 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/InnerPolyline.cpp +9 -28
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/InnerPolyline.h +10 -11
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.h +5 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.cpp +4 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.h +7 -5
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Position.cpp +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Queueable.h +2 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Sequence.cpp +0 -14
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Sequence.h +0 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Shape.cpp +5 -5
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/ShapePool.cpp +31 -26
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/ShapePool.h +7 -7
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/StreamingMerger.cpp +12 -31
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/StreamingMerger.h +10 -8
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/SubPool.h +47 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/SvgStreamingMerger.h +43 -0
- data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +2 -1
- data/lib/contrek/finder/concurrent/cluster.rb +6 -5
- data/lib/contrek/finder/concurrent/cursor.rb +2 -1
- data/lib/contrek/finder/concurrent/finder.rb +1 -12
- data/lib/contrek/finder/concurrent/inner_polyline.rb +8 -25
- data/lib/contrek/finder/concurrent/sequence.rb +1 -17
- data/lib/contrek/version.rb +1 -1
- metadata +8 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8fcfc790a324e42855e8b8e816f946da8ea764943a44f4d9999b4cc7a360209d
|
|
4
|
+
data.tar.gz: 8f0d0f42350fc8236a9c2e5ca974ab11d91a5bcfbdd4a7fdef9cf310bf764306
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b7a5c008b6811b36ce143642d6300e89d7975dd0861cfc429880bbd76bf8a4b2eadf6c24351ca6d3de746ddbecaec849b4aed2052e973e34a386a9ae20e4a4a6
|
|
7
|
+
data.tar.gz: 8ff2d2465df0afcd376255bb575a46581d27888ec5e7293d9971e27f624550dc83e1fb47ca11814cbeb220c0b38c51b7179087361fbab9140ca181a728de706e
|
data/CHANGELOG.md
CHANGED
|
@@ -129,3 +129,6 @@ All notable changes to this project will be documented in this file.
|
|
|
129
129
|
|
|
130
130
|
## [1.3.1] - 2026-06-20
|
|
131
131
|
- **Streaming merger:** The progressive streaming extraction mode has now reached new heights of efficiency on the C++ side. This mode allows the data source to be processed in contiguous blocks. All isolated polygons, as well as those extending into the bottom strips, are removed from the list and streamed directly to the SVG file. This drastically reduces RAM requirements. An extreme test on an 81920×81920 pixel image containing a massive number of polygons (20,000,000) was processed using roughly 40 strips of 2000 pixels each in less than 300 seconds, peaking at a RAM usage of just 13GB.
|
|
132
|
+
|
|
133
|
+
## [1.3.2] - 2026-06-27
|
|
134
|
+
- **Streaming merger:** A series of optimizations have been implemented at the C++ memory pool level; using a different strategy, these are now freed as soon as possible depending on the source data content and the morphology of the containing polygons. This has allowed for a further reduction in the peak memory indicated in the previous changelog, specifically from 13GB to 4.3GB, on the same image with 20,000,000 polygons.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Contrek
|
|
2
|
-
Contrek is a
|
|
3
|
-
|
|
2
|
+
**Contrek** is a standalone <u>[C++17 topology-preserving streaming polygonization engine](#-c-standalone-library-usage)</u> for raster-to-vector conversion. It extracts polygonal contours from PNG images and raw memory streams using programmable pixel matchers, representing shapes as connected graphs of shared vertices to guarantee topological consistency. Its streaming architecture supports gigapixel-scale datasets, multithreaded execution, and incremental vector serialization to multiple formats such as SVG while keeping memory consumption bounded. A fully featured Ruby wrapper is also available, exposing the complete C++ API through an idiomatic Ruby interface.
|
|
4
3
|
|
|
5
4
|
## About Contrek library
|
|
6
5
|
|
|
7
6
|
Contrek (**con**tour **trek**king) simply scans your png/raw bitmap and returns shape contour as close polygonal lines, both for the external and internal sides. It can compute the nesting level of the polygons found with a tree structure. It supports various levels and modes of compression and approximation of the found coordinates. It is capable of multithreaded processing, splitting the image into vertical strips and recombining the coordinates in pairs.
|
|
8
7
|
|
|
9
8
|
In the following image all the non-white pixels have been examined and the result is the red polygon for the outer contour and the green one for the inner one
|
|
9
|
+
|
|
10
10
|

|
|
11
11
|
|
|
12
12
|
## 🚀 Why Contrek?
|
|
@@ -58,7 +58,7 @@ The **Stripe-Merging** algorithm has been validated through a dedicated testing
|
|
|
58
58
|
* **Extraction Fidelity:** Verifying polygon precision across both engines.
|
|
59
59
|
|
|
60
60
|
The complete testing suite, source code, and raw benchmarks are available here:
|
|
61
|
-
👉 **[
|
|
61
|
+
👉 **[test_contrek](https://github.com/runout77/test_contrek)**
|
|
62
62
|
|
|
63
63
|
## Prerequisites
|
|
64
64
|
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
#include <cstring>
|
|
19
19
|
#include <algorithm>
|
|
20
20
|
#include <cstdio>
|
|
21
|
+
#include <fstream>
|
|
22
|
+
#include <unistd.h>
|
|
21
23
|
|
|
22
24
|
#include "polygon/finder/PolygonFinder.h"
|
|
23
25
|
#include "polygon/finder/concurrent/ClippedPolygonFinder.h"
|
|
@@ -35,6 +37,8 @@
|
|
|
35
37
|
#include "polygon/finder/concurrent/HorizontalMerger.h"
|
|
36
38
|
#include "polygon/finder/concurrent/VerticalMerger.h"
|
|
37
39
|
#include "polygon/finder/concurrent/StreamingMerger.h"
|
|
40
|
+
#include "polygon/finder/concurrent/SvgStreamingMerger.h"
|
|
41
|
+
#include "polygon/finder/concurrent/GeoJsonStreamingMerger.h"
|
|
38
42
|
#include "polygon/finder/concurrent/Sequence.h"
|
|
39
43
|
#include "polygon/finder/concurrent/Position.h"
|
|
40
44
|
#include "polygon/finder/Polygon.h"
|
|
@@ -316,7 +320,7 @@ void stream_png_image(const std::string& filepath, uint32_t stripe_height, bool
|
|
|
316
320
|
int stripe_count = 0;
|
|
317
321
|
// main stripes loop
|
|
318
322
|
for (uint32_t current_y_offset = 0; current_y_offset < total_height; current_y_offset += stripe_height) {
|
|
319
|
-
|
|
323
|
+
uint32_t uncovered_height = total_height - current_y_offset;
|
|
320
324
|
|
|
321
325
|
// copy previous last line to the next new one (each contigue stripe must share one pixel scanline)
|
|
322
326
|
if (current_y_offset > 0) {
|
|
@@ -384,6 +388,17 @@ void Tests::test_i() {
|
|
|
384
388
|
std::cout << "Memory usage peak: " << get_peak_rss() << " MB" << std::endl;
|
|
385
389
|
}
|
|
386
390
|
|
|
391
|
+
double get_current_rss_mb() {
|
|
392
|
+
std::ifstream statm("/proc/self/statm");
|
|
393
|
+
uint32_t size = 0;
|
|
394
|
+
uint32_t resident = 0;
|
|
395
|
+
|
|
396
|
+
statm >> size >> resident;
|
|
397
|
+
|
|
398
|
+
uint32_t page_size = sysconf(_SC_PAGESIZE);
|
|
399
|
+
return (resident * page_size) / (1024.0 * 1024.0);
|
|
400
|
+
}
|
|
401
|
+
|
|
387
402
|
void stream_progressive_png_image(const std::string& filepath, uint32_t stripe_height) {
|
|
388
403
|
std::vector<ProcessResult*> result_clones;
|
|
389
404
|
std::vector<std::string> varguments = {"--bounds"};
|
|
@@ -422,14 +437,17 @@ void stream_progressive_png_image(const std::string& filepath, uint32_t stripe_h
|
|
|
422
437
|
if (!shared_stream) {
|
|
423
438
|
std::cerr << "Error: Unable creating output streaming file!" << std::endl;
|
|
424
439
|
}
|
|
440
|
+
std::vector<char> buffer(4 * 1024 * 1024); // Buffer (4MB)
|
|
441
|
+
shared_stream.rdbuf()->pubsetbuf(buffer.data(), buffer.size());
|
|
425
442
|
|
|
426
|
-
|
|
443
|
+
SvgStreamingMerger vmerger(0, &varguments, &shared_stream, total_width, total_height);
|
|
444
|
+
// GeoJsonStreamingMerger vmerger(0, &varguments, &shared_stream, total_width, total_height, 4294901760);
|
|
427
445
|
try {
|
|
428
446
|
size_t row_size = static_cast<size_t>(total_width) * 4;
|
|
429
447
|
int stripe_count = 0;
|
|
430
448
|
// main stripes loop
|
|
431
449
|
for (uint32_t current_y_offset = 0; current_y_offset < total_height; current_y_offset += stripe_height) {
|
|
432
|
-
|
|
450
|
+
uint32_t uncovered_height = total_height - current_y_offset;
|
|
433
451
|
|
|
434
452
|
// copy previous last line to the next new one (each contigue stripe must share one pixel scanline)
|
|
435
453
|
if (current_y_offset > 0) {
|
|
@@ -463,6 +481,7 @@ void stream_progressive_png_image(const std::string& filepath, uint32_t stripe_h
|
|
|
463
481
|
std::cout << "stripe " << stripe_count << ": found polygons " << result->groups << std::endl;
|
|
464
482
|
vmerger.add_tile(*result, !(current_y_offset + stripe_height < total_height));
|
|
465
483
|
delete result;
|
|
484
|
+
std::cout << "-> RSS before merge: " << get_current_rss_mb() << " MB" << std::endl;
|
|
466
485
|
}
|
|
467
486
|
stripe_count++;
|
|
468
487
|
}
|
|
@@ -479,5 +498,6 @@ void stream_progressive_png_image(const std::string& filepath, uint32_t stripe_h
|
|
|
479
498
|
|
|
480
499
|
void Tests::test_l() {
|
|
481
500
|
stream_progressive_png_image("../images/mixed_shapes_1024x1024.png", 300);
|
|
501
|
+
// stream_progressive_png_image("../images/test_20480x20480.png", 2000);
|
|
482
502
|
std::cout << "Memory usage peak: " << get_peak_rss() << " MB" << std::endl;
|
|
483
503
|
}
|
|
@@ -32,9 +32,9 @@ FastPngBitmap::FastPngBitmap(std::string filename) : RawBitmap() {
|
|
|
32
32
|
} else {
|
|
33
33
|
spng_ctx *ctx = spng_ctx_new(0);
|
|
34
34
|
spng_set_png_buffer(ctx, file_buffer.data(), file_buffer.size());
|
|
35
|
-
struct spng_ihdr ihdr;
|
|
35
|
+
struct spng_ihdr ihdr = {};
|
|
36
36
|
spng_get_ihdr(ctx, &ihdr);
|
|
37
|
-
size_t out_size;
|
|
37
|
+
size_t out_size = 0;
|
|
38
38
|
spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, &out_size);
|
|
39
39
|
this->define(ihdr.width, ihdr.height, 4, false);
|
|
40
40
|
unsigned char* raw_ptr = this->image.get();
|
|
@@ -29,7 +29,7 @@ int RawBitmap::h() {
|
|
|
29
29
|
return this->height;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
char RawBitmap::value_at(int
|
|
32
|
+
char RawBitmap::value_at(int, int) {
|
|
33
33
|
return(0);
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -48,7 +48,7 @@ unsigned int RawBitmap::rgb_value_at(int x, int y) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
void RawBitmap::draw_pixel(int x, int y, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
|
|
51
|
-
if (x < 0 || x >= width || y < 0 || y >= height) return;
|
|
51
|
+
if (x < 0 || x >= static_cast<int>(width) || y < 0 || y >= static_cast<int>(height)) return;
|
|
52
52
|
unsigned char* p = &image[(static_cast<size_t>(y) * width + x) * bpp];
|
|
53
53
|
p[0] = r;
|
|
54
54
|
if (bpp > 1) p[1] = g;
|
|
@@ -30,7 +30,7 @@ RemoteFastPngBitmap::RemoteFastPngBitmap(std::string *dataurl) : FastPngBitmap("
|
|
|
30
30
|
struct spng_ihdr ihdr;
|
|
31
31
|
int error = spng_get_ihdr(ctx, &ihdr);
|
|
32
32
|
if (!error) {
|
|
33
|
-
size_t out_size; // RGBA8 dimension
|
|
33
|
+
size_t out_size = 0; // RGBA8 dimension
|
|
34
34
|
spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, &out_size);
|
|
35
35
|
this->define(ihdr.width, ihdr.height, 4, false);
|
|
36
36
|
error = spng_decode_image(ctx, image.get(), out_size, SPNG_FMT_RGBA8, SPNG_DECODE_TRNS);
|
|
@@ -43,7 +43,7 @@ struct SmallVec {
|
|
|
43
43
|
ptr = np; cap = n;
|
|
44
44
|
}
|
|
45
45
|
void clear() { sz = 0; ptr = buf; cap = INLINE_CAP; }
|
|
46
|
-
int
|
|
46
|
+
unsigned int size() const { return static_cast<unsigned int>(sz); }
|
|
47
47
|
int& operator[](int i) { return ptr[i]; }
|
|
48
48
|
int operator[](int i) const { return ptr[i]; }
|
|
49
49
|
int* begin() { return ptr; }
|
|
@@ -88,7 +88,6 @@ class Node : public Listable {
|
|
|
88
88
|
int abs_x_index;
|
|
89
89
|
int up_indexer, down_indexer;
|
|
90
90
|
int tangs_count;
|
|
91
|
-
char name;
|
|
92
91
|
int track;
|
|
93
92
|
int outer_index, inner_index;
|
|
94
93
|
int upper_start = std::numeric_limits<int>::max();
|
|
@@ -112,6 +111,7 @@ class Node : public Listable {
|
|
|
112
111
|
Node(int min_x, int max_x, int y, NodeCluster* cluster, char name);
|
|
113
112
|
void precalc_tangs_sequences(NodeCluster& cluster);
|
|
114
113
|
bool processed = false;
|
|
114
|
+
char name;
|
|
115
115
|
int inner_left_index = -1;
|
|
116
116
|
int inner_right_index = -1;
|
|
117
117
|
};
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
#include <map>
|
|
15
15
|
#include <string>
|
|
16
16
|
#include <utility>
|
|
17
|
-
#include "PolygonFinder.h"
|
|
18
17
|
#include "Lists.h"
|
|
19
18
|
#include "RectBounds.h"
|
|
20
19
|
#include "Polygon.h"
|
|
@@ -34,8 +33,10 @@ class NodeCluster {
|
|
|
34
33
|
int count = 0;
|
|
35
34
|
int nodes;
|
|
36
35
|
int width;
|
|
36
|
+
int height;
|
|
37
37
|
|
|
38
38
|
public:
|
|
39
|
+
int get_height() const { return height; }
|
|
39
40
|
pf_Options *options;
|
|
40
41
|
std::vector<std::pair<int, int>> treemap; // [a,b] a = index of parent outer, b = index of inner of parent outer
|
|
41
42
|
std::pair<int, int> test_in_hole_a(Node* node);
|
|
@@ -43,7 +44,6 @@ class NodeCluster {
|
|
|
43
44
|
std::vector<std::deque<Node>> vert_nodes;
|
|
44
45
|
void compress_coords(std::list<Polygon>& polygons, pf_Options options);
|
|
45
46
|
List *root_nodes;
|
|
46
|
-
int height;
|
|
47
47
|
std::list<Polygon> polygons;
|
|
48
48
|
NodeCluster(int h, int w, pf_Options *options);
|
|
49
49
|
virtual ~NodeCluster();
|
|
@@ -55,3 +55,5 @@ class NodeCluster {
|
|
|
55
55
|
std::list<Node*>::iterator exam(std::list<Node*>::iterator inode, Node *node, Node *father, Node *root_node);
|
|
56
56
|
std::vector<std::vector<Node*>> sequences;
|
|
57
57
|
};
|
|
58
|
+
|
|
59
|
+
#include "PolygonFinder.h"
|
|
@@ -29,14 +29,14 @@
|
|
|
29
29
|
|
|
30
30
|
PolygonFinder::PolygonFinder(Bitmap *bitmap,
|
|
31
31
|
Matcher *matcher,
|
|
32
|
-
Bitmap
|
|
32
|
+
Bitmap *,
|
|
33
33
|
std::vector<std::string> *options,
|
|
34
34
|
int start_x,
|
|
35
35
|
int end_x)
|
|
36
|
-
:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
: start_x(start_x),
|
|
37
|
+
end_x(end_x == -1 ? bitmap->w() : end_x),
|
|
38
|
+
source_bitmap(bitmap),
|
|
39
|
+
matcher(matcher)
|
|
40
40
|
{ this->rgb_matcher = dynamic_cast<RGBMatcher*>(matcher);
|
|
41
41
|
if (options != nullptr) FinderUtils::sanitize_options(this->options, options);
|
|
42
42
|
|
|
@@ -70,7 +70,7 @@ PolygonFinder::PolygonFinder(Bitmap *bitmap,
|
|
|
70
70
|
|
|
71
71
|
std::list<ShapeLine*> *PolygonFinder::get_shapelines() {
|
|
72
72
|
std::list<ShapeLine*> *sll = new std::list<ShapeLine*>();
|
|
73
|
-
for (int line = 0; line < this->node_cluster->
|
|
73
|
+
for (int line = 0; line < this->node_cluster->get_height(); line++) {
|
|
74
74
|
for (Node& node : this->node_cluster->vert_nodes[line]) {
|
|
75
75
|
ShapeLine *sl = new ShapeLine({node.min_x, node.max_x, node.y});
|
|
76
76
|
sll->push_back(sl);
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
#include "Cursor.h"
|
|
18
18
|
#include "../../CpuTimer.h"
|
|
19
19
|
|
|
20
|
-
Cluster::Cluster(Finder *finder, int height
|
|
20
|
+
Cluster::Cluster(Finder *finder, int height)
|
|
21
21
|
: finder_(finder)
|
|
22
22
|
{ tiles_.reserve(2); // only two (left|right)
|
|
23
23
|
this->hub_ = new Hub(height);
|
|
@@ -55,6 +55,7 @@ Tile* Cluster::merge_tiles() {
|
|
|
55
55
|
CpuTimer timer;
|
|
56
56
|
|
|
57
57
|
std::vector<Shape*> new_shapes;
|
|
58
|
+
std::vector<Shape*> detach_shapes;
|
|
58
59
|
std::vector<InnerPolyline*> all_new_inner_polylines;
|
|
59
60
|
|
|
60
61
|
timer.start();
|
|
@@ -78,34 +79,34 @@ Tile* Cluster::merge_tiles() {
|
|
|
78
79
|
|
|
79
80
|
if (shape->outer_polyline->any_ancients) {
|
|
80
81
|
Cursor cursor(*this, shape);
|
|
81
|
-
Sequence* new_outer = nullptr;
|
|
82
|
-
|
|
83
82
|
timer.start();
|
|
84
|
-
new_outer = cursor.join_outers();
|
|
83
|
+
Sequence new_outer = cursor.join_outers();
|
|
85
84
|
tot_outer += timer.stop();
|
|
86
85
|
|
|
87
86
|
timer.start();
|
|
88
87
|
std::vector<InnerPolyline*> new_inners = shape->inner_polylines;
|
|
89
|
-
std::vector<InnerPolyline*> new_inner_polylines = cursor.join_inners(
|
|
88
|
+
std::vector<InnerPolyline*> new_inner_polylines = cursor.join_inners(treemap);
|
|
90
89
|
tot_inner += timer.stop();
|
|
91
90
|
|
|
92
91
|
for (InnerPolyline* inner_polyline : new_inner_polylines) {
|
|
93
92
|
new_inners.push_back(inner_polyline);
|
|
94
93
|
if (treemap) {
|
|
95
|
-
inner_polyline->
|
|
94
|
+
inner_polyline->compute_vertical_bounds();
|
|
96
95
|
all_new_inner_polylines.push_back(inner_polyline);
|
|
97
96
|
}
|
|
98
97
|
}
|
|
99
98
|
for (auto s : cursor.orphan_inners()) {
|
|
100
99
|
new_inners.push_back(s);
|
|
101
100
|
}
|
|
102
|
-
|
|
101
|
+
shape->clear_inner();
|
|
102
|
+
|
|
103
|
+
Polyline* polyline = tile->shapes_pool->acquire_polyline(tile, new_outer.to_vector(), std::nullopt);
|
|
103
104
|
Shape* inserting_new_shape = tile->shapes_pool->acquire_shape(polyline, new_inners);
|
|
104
105
|
new_shapes.push_back(inserting_new_shape);
|
|
105
106
|
polyline->shape = inserting_new_shape;
|
|
106
107
|
|
|
107
108
|
for (InnerPolyline* inner_polyline : new_inner_polylines) {
|
|
108
|
-
inner_polyline->
|
|
109
|
+
inner_polyline->shape = inserting_new_shape;
|
|
109
110
|
}
|
|
110
111
|
if (treemap) {
|
|
111
112
|
for (const auto merged_shape : cursor.shapes_sequence()) {
|
|
@@ -116,6 +117,9 @@ Tile* Cluster::merge_tiles() {
|
|
|
116
117
|
assign_ancestry(inserting_new_shape, inside_inner_polyline);
|
|
117
118
|
}
|
|
118
119
|
}
|
|
120
|
+
for (const auto merged_shape : cursor.shapes_sequence()) {
|
|
121
|
+
detach_shapes.push_back(merged_shape);
|
|
122
|
+
}
|
|
119
123
|
} else {
|
|
120
124
|
if (treemap) {
|
|
121
125
|
if (shape->fixed) {
|
|
@@ -134,7 +138,6 @@ Tile* Cluster::merge_tiles() {
|
|
|
134
138
|
|
|
135
139
|
double past_tot_outer = tiles_.front()->benchmarks.outer + tiles_.back()->benchmarks.outer;
|
|
136
140
|
double past_tot_inner = tiles_.front()->benchmarks.inner + tiles_.back()->benchmarks.inner;
|
|
137
|
-
|
|
138
141
|
Benchmarks b{
|
|
139
142
|
tot_outer + past_tot_outer,
|
|
140
143
|
tot_inner + past_tot_inner
|
|
@@ -144,6 +147,9 @@ Tile* Cluster::merge_tiles() {
|
|
|
144
147
|
this->finder_, tiles_.front()->start_x(), tiles_.back()->end_x(), tiles_.front()->name() + tiles_.back()->name(), b);
|
|
145
148
|
|
|
146
149
|
tile->assign_shapes(new_shapes);
|
|
150
|
+
for (const auto shape : detach_shapes) {
|
|
151
|
+
shape->detach_from_pool();
|
|
152
|
+
}
|
|
147
153
|
for (Tile* old_tile : tiles_) {
|
|
148
154
|
tile->adopt(old_tile);
|
|
149
155
|
}
|
|
@@ -151,7 +157,7 @@ Tile* Cluster::merge_tiles() {
|
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
void Cluster::assign_ancestry(Shape *shape, InnerPolyline* inner_polyline)
|
|
154
|
-
{ shape->set_parent_shape(inner_polyline->
|
|
160
|
+
{ shape->set_parent_shape(inner_polyline->shape);
|
|
155
161
|
shape->parent_inner_polyline = inner_polyline;
|
|
156
162
|
shape->fixed = true;
|
|
157
163
|
}
|
|
@@ -25,7 +25,7 @@ class Cluster {
|
|
|
25
25
|
void is_children(Shape* shape, std::vector<InnerPolyline*> inner_polylines);
|
|
26
26
|
|
|
27
27
|
public:
|
|
28
|
-
Cluster(Finder *finder, int height
|
|
28
|
+
Cluster(Finder *finder, int height);
|
|
29
29
|
virtual ~Cluster();
|
|
30
30
|
void add(Tile* tile);
|
|
31
31
|
Tile* merge_tiles();
|
|
@@ -18,20 +18,20 @@ Cursor::Cursor(Cluster& cluster, Shape* shape)
|
|
|
18
18
|
: cluster(cluster), shape(shape) {
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
Sequence
|
|
21
|
+
Sequence Cursor::join_outers()
|
|
22
22
|
{ Polyline* outer_polyline = shape->outer_polyline;
|
|
23
23
|
this->shapes_sequence_.push_back(this->shape);
|
|
24
24
|
this->shapes_sequence_lookup.insert(this->shape);
|
|
25
|
-
Sequence
|
|
25
|
+
Sequence outer_joined_polyline;
|
|
26
26
|
std::vector<Part*> all_parts;
|
|
27
27
|
this->traverse_outer(outer_polyline->parts().front(),
|
|
28
28
|
all_parts,
|
|
29
29
|
this->shapes_sequence_,
|
|
30
30
|
outer_joined_polyline);
|
|
31
31
|
|
|
32
|
-
if (outer_joined_polyline
|
|
32
|
+
if (outer_joined_polyline.head->payload == outer_joined_polyline.tail->payload &&
|
|
33
33
|
this->cluster.tiles().front()->left() &&
|
|
34
|
-
this->cluster.tiles().back()->right()) outer_joined_polyline
|
|
34
|
+
this->cluster.tiles().back()->right()) outer_joined_polyline.pop();
|
|
35
35
|
|
|
36
36
|
for (Shape* shape : shapes_sequence_) {
|
|
37
37
|
shape->outer_polyline->turn_on(Polyline::TRACKED_OUTER);
|
|
@@ -50,7 +50,7 @@ Sequence* Cursor::join_outers()
|
|
|
50
50
|
void Cursor::traverse_outer(Part* act_part,
|
|
51
51
|
std::vector<Part*>& all_parts,
|
|
52
52
|
std::vector<Shape*>& shapes_sequence,
|
|
53
|
-
Sequence
|
|
53
|
+
Sequence& outer_joined_polyline) {
|
|
54
54
|
while (act_part != nullptr) {
|
|
55
55
|
Part* last_part = (!all_parts.empty()) ? all_parts.back() : nullptr;
|
|
56
56
|
if (all_parts.empty() || last_part != act_part) {
|
|
@@ -61,7 +61,7 @@ void Cursor::traverse_outer(Part* act_part,
|
|
|
61
61
|
if (act_part->size == 0) return;
|
|
62
62
|
|
|
63
63
|
while (Position *position = act_part->next_position(nullptr)) {
|
|
64
|
-
outer_joined_polyline
|
|
64
|
+
outer_joined_polyline.add(position);
|
|
65
65
|
}
|
|
66
66
|
} else {
|
|
67
67
|
if (act_part->dead_end &&
|
|
@@ -69,13 +69,13 @@ void Cursor::traverse_outer(Part* act_part,
|
|
|
69
69
|
last_part->is(Part::SEAM) &&
|
|
70
70
|
last_part->polyline() == act_part->polyline()) return;
|
|
71
71
|
while (Position *new_position = static_cast<Position*>(act_part->iterator())) {
|
|
72
|
-
if (outer_joined_polyline
|
|
73
|
-
outer_joined_polyline
|
|
72
|
+
if (outer_joined_polyline.size > 1 &&
|
|
73
|
+
outer_joined_polyline.head->payload == new_position->payload &&
|
|
74
74
|
act_part == all_parts.front()) {
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
77
|
this->cluster.positions_pool.emplace_back(this->cluster.hub(), new_position->payload);
|
|
78
|
-
outer_joined_polyline
|
|
78
|
+
outer_joined_polyline.add(&this->cluster.positions_pool.back());
|
|
79
79
|
new_position->end_point()->tracked_outer = true;
|
|
80
80
|
int versus = act_part->versus();
|
|
81
81
|
auto& q_set = new_position->end_point()->queues();
|
|
@@ -125,7 +125,7 @@ void Cursor::traverse_outer(Part* act_part,
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
std::vector<InnerPolyline*> Cursor::join_inners(
|
|
128
|
+
std::vector<InnerPolyline*> Cursor::join_inners(bool treemap) {
|
|
129
129
|
std::vector<InnerPolyline*> return_inner_polylines;
|
|
130
130
|
std::vector<Shape*> processing_queue = shapes_sequence_;
|
|
131
131
|
for (size_t i = 0; i < shapes_sequence_.size(); ++i)
|
|
@@ -140,10 +140,10 @@ std::vector<InnerPolyline*> Cursor::join_inners(Sequence* outer_seq, bool treema
|
|
|
140
140
|
.max = 0
|
|
141
141
|
};
|
|
142
142
|
traverse_inner(part, all_parts, bounds, tracked_end_points);
|
|
143
|
-
Sequence
|
|
143
|
+
Sequence retme_sequence;
|
|
144
144
|
for (Part* part : all_parts)
|
|
145
145
|
{ part->touch();
|
|
146
|
-
retme_sequence
|
|
146
|
+
retme_sequence.move_from(*part, [&](QNode<Point>* pos) -> bool {
|
|
147
147
|
Position *position = static_cast<Position*>(pos);
|
|
148
148
|
if (part->is(Part::ADDED) &&
|
|
149
149
|
!(position->payload.y >= bounds.min &&
|
|
@@ -153,8 +153,8 @@ std::vector<InnerPolyline*> Cursor::join_inners(Sequence* outer_seq, bool treema
|
|
|
153
153
|
return(!(polyline->tile->tg_border(position->payload) && position->end_point()->tracked_outer));
|
|
154
154
|
});
|
|
155
155
|
}
|
|
156
|
-
if (retme_sequence
|
|
157
|
-
InnerPolyline* inner_polyline = polyline->tile->shapes_pool->acquire_inner_polyline(retme_sequence);
|
|
156
|
+
if (retme_sequence.is_not_vertical()) {
|
|
157
|
+
InnerPolyline* inner_polyline = polyline->tile->shapes_pool->acquire_inner_polyline(&retme_sequence);
|
|
158
158
|
return_inner_polylines.push_back(inner_polyline);
|
|
159
159
|
if (treemap) {
|
|
160
160
|
mark_children(tracked_end_points, polyline, inner_polyline);
|
|
@@ -201,7 +201,7 @@ void Cursor::traverse_inner(Part* act_part, std::vector<Part*>& all_parts, Bound
|
|
|
201
201
|
if (act_part->innerable()) {
|
|
202
202
|
all_parts.push_back(act_part);
|
|
203
203
|
bool jumped = false;
|
|
204
|
-
while (act_part = act_part->circular_next) {
|
|
204
|
+
while ((act_part = act_part->circular_next)) {
|
|
205
205
|
if (act_part->innerable()) {
|
|
206
206
|
all_parts.push_back(act_part);
|
|
207
207
|
} else {
|
|
@@ -233,6 +233,7 @@ void Cursor::traverse_inner(Part* act_part, std::vector<Part*>& all_parts, Bound
|
|
|
233
233
|
orphan_inners_.end(),
|
|
234
234
|
shape->inner_polylines.begin(),
|
|
235
235
|
shape->inner_polylines.end());
|
|
236
|
+
shape->clear_inner();
|
|
236
237
|
}
|
|
237
238
|
dest_part->polyline()->turn_on(Polyline::TRACKED_OUTER);
|
|
238
239
|
if (!dest_part->touched()) {
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
class Cursor {
|
|
21
21
|
public:
|
|
22
22
|
Cursor(Cluster& cluster, Shape* shape);
|
|
23
|
-
Sequence
|
|
24
|
-
std::vector<InnerPolyline*> join_inners(
|
|
23
|
+
Sequence join_outers();
|
|
24
|
+
std::vector<InnerPolyline*> join_inners(bool treemap);
|
|
25
25
|
std::list<InnerPolyline*> orphan_inners() { return orphan_inners_; }
|
|
26
26
|
const std::vector<Shape*>& shapes_sequence() const { return shapes_sequence_; }
|
|
27
27
|
|
|
@@ -34,7 +34,7 @@ class Cursor {
|
|
|
34
34
|
void traverse_outer(Part* act_part,
|
|
35
35
|
std::vector<Part*>& all_parts,
|
|
36
36
|
std::vector<Shape*>& shapes_sequence,
|
|
37
|
-
Sequence
|
|
37
|
+
Sequence& outer_joined_polyline);
|
|
38
38
|
void traverse_inner(Part* act_part, std::vector<Part*> &all_parts, Bounds& bounds, std::vector<EndPoint*>& tracked_end_points);
|
|
39
39
|
std::vector<Shape*> connect_missings(std::vector<Shape*> shapes_sequence, std::vector<Shape*> missing_shapes);
|
|
40
40
|
void mark_children(std::vector<EndPoint*>& end_points, const Polyline* outer_polyline, InnerPolyline* inner_polyline);
|
|
@@ -95,16 +95,8 @@ void Finder::process_tiles() {
|
|
|
95
95
|
|
|
96
96
|
if (it != arriving_tiles.end()) {
|
|
97
97
|
Tile* twin_tile = *it;
|
|
98
|
-
int start_x, end_x;
|
|
99
|
-
if (twin_tile->start_x() == (tile->end_x() - 1)) {
|
|
100
|
-
start_x = tile->start_x();
|
|
101
|
-
end_x = twin_tile->end_x();
|
|
102
|
-
} else {
|
|
103
|
-
start_x = twin_tile->start_x();
|
|
104
|
-
end_x = tile->end_x();
|
|
105
|
-
}
|
|
106
98
|
|
|
107
|
-
Cluster *cluster = new Cluster(this, this->height
|
|
99
|
+
Cluster *cluster = new Cluster(this, this->height);
|
|
108
100
|
|
|
109
101
|
if (twin_tile->start_x() == (tile->end_x() - 1)) {
|
|
110
102
|
cluster->add(tile);
|
data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/GeoJsonStreamingMerger.h
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* GeoJsonStreamingMerger.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
|
+
#include <vector>
|
|
12
|
+
#include <string>
|
|
13
|
+
#include "StreamingMerger.h"
|
|
14
|
+
|
|
15
|
+
class GeoJsonStreamingMerger : public StreamingMerger {
|
|
16
|
+
private:
|
|
17
|
+
unsigned int target_value;
|
|
18
|
+
bool is_first_feature = true;
|
|
19
|
+
|
|
20
|
+
protected:
|
|
21
|
+
void write_header() override {
|
|
22
|
+
if (stream) {
|
|
23
|
+
*stream << "{\"type\":\"FeatureCollection\",\"features\":[\n";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
void write_footer() override {
|
|
28
|
+
if (stream) {
|
|
29
|
+
*stream << "\n]}";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
void write_outer_polygon_start() override {}
|
|
33
|
+
void write_outer_polygon_end() override {}
|
|
34
|
+
void write_inner_polygon_start() override {}
|
|
35
|
+
void write_inner_polygon_end() override {}
|
|
36
|
+
|
|
37
|
+
public:
|
|
38
|
+
GeoJsonStreamingMerger(int number_of_threads,
|
|
39
|
+
std::vector<std::string>* options,
|
|
40
|
+
std::ofstream* stream_to,
|
|
41
|
+
int total_width, int total_height,
|
|
42
|
+
unsigned int pixel_value)
|
|
43
|
+
: StreamingMerger(number_of_threads, options, stream_to, total_width, total_height),
|
|
44
|
+
target_value(pixel_value) {}
|
|
45
|
+
|
|
46
|
+
void stream_raw_polygon(const Shape* shape) override {
|
|
47
|
+
if (!stream) return;
|
|
48
|
+
if (!is_first_feature) {
|
|
49
|
+
*stream << ",\n";
|
|
50
|
+
}
|
|
51
|
+
is_first_feature = false;
|
|
52
|
+
|
|
53
|
+
*stream << "{\"type\":\"Feature\",\"properties\":{\"value\":" << target_value
|
|
54
|
+
<< "},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[ [";
|
|
55
|
+
const std::vector<Point>& points = shape->outer_polyline->raw();
|
|
56
|
+
const size_t points_size = points.size();
|
|
57
|
+
if (points_size > 0) {
|
|
58
|
+
for (size_t i = 0; i < points_size; ++i) {
|
|
59
|
+
*stream << "[" << points[i].y << "," << points[i].x << "]";
|
|
60
|
+
if (i < points_size - 1) *stream << ",";
|
|
61
|
+
}
|
|
62
|
+
*stream << ",[" << points[0].y << "," << points[0].x << "]]";
|
|
63
|
+
} else {
|
|
64
|
+
*stream << "]]";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const auto& inner_polyline : shape->inner_polylines) {
|
|
68
|
+
const std::vector<Point>& inner_points = inner_polyline->raw();
|
|
69
|
+
const size_t inner_size = inner_points.size();
|
|
70
|
+
if (inner_size > 0) {
|
|
71
|
+
*stream << ",[";
|
|
72
|
+
for (size_t i = 0; i < inner_size; ++i) {
|
|
73
|
+
*stream << "[" << inner_points[i].y << "," << inner_points[i].x << "]";
|
|
74
|
+
if (i < inner_size - 1) *stream << ",";
|
|
75
|
+
}
|
|
76
|
+
*stream << ",[" << inner_points[0].y << "," << inner_points[0].x << "]]";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
*stream << "]}}";
|
|
80
|
+
}
|
|
81
|
+
};
|
|
@@ -11,36 +11,18 @@
|
|
|
11
11
|
#include <vector>
|
|
12
12
|
#include "InnerPolyline.h"
|
|
13
13
|
|
|
14
|
-
InnerPolyline::InnerPolyline(std::vector<Point> raw_coordinates, Shape* shape)
|
|
15
|
-
:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
InnerPolyline::InnerPolyline(ShapePool* shape_pool, std::vector<Point> raw_coordinates, Shape* shape)
|
|
15
|
+
: shape(shape),
|
|
16
|
+
shape_pool(shape_pool),
|
|
17
|
+
raw_coordinates_(std::move(raw_coordinates)) {}
|
|
18
|
+
InnerPolyline::InnerPolyline(ShapePool* shape_pool, Sequence* sequence)
|
|
19
|
+
: shape_pool(shape_pool) {
|
|
19
20
|
this->raw_coordinates_ = sequence->to_vector();
|
|
21
|
+
this->shape = sequence->shape;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
Bounds& InnerPolyline::vertical_bounds() {
|
|
27
|
-
if (sequence_ != nullptr) {
|
|
28
|
-
return(sequence_->vertical_bounds);
|
|
29
|
-
} else {
|
|
30
|
-
return(this->raw_vertical_bounds());
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
Shape* InnerPolyline::shape() {
|
|
35
|
-
if (sequence_ != nullptr) {
|
|
36
|
-
return(sequence_->shape);
|
|
37
|
-
} else {
|
|
38
|
-
return(this->shape_ ? this->shape_ : this->assigned_shape);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
Bounds& InnerPolyline::raw_vertical_bounds() {
|
|
43
|
-
if (!raw_coordinates_.empty()) {
|
|
24
|
+
void InnerPolyline::compute_vertical_bounds()
|
|
25
|
+
{ if (!raw_coordinates_.empty()) {
|
|
44
26
|
int min_y = raw_coordinates_[0].y;
|
|
45
27
|
int max_y = raw_coordinates_[0].y;
|
|
46
28
|
for (const auto& pos : raw_coordinates_) {
|
|
@@ -50,5 +32,4 @@ Bounds& InnerPolyline::raw_vertical_bounds() {
|
|
|
50
32
|
vertical_bounds_.min = min_y;
|
|
51
33
|
vertical_bounds_.max = max_y;
|
|
52
34
|
}
|
|
53
|
-
return(vertical_bounds_);
|
|
54
35
|
}
|