contrek 1.3.0 → 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 +10 -0
- data/Gemfile.lock +1 -1
- data/README.md +3 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +24 -10
- 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 +2 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +16 -20
- 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.cpp +30 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.h +8 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.cpp +41 -41
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.h +4 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.cpp +5 -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 +32 -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 -4
- data/lib/contrek/finder/concurrent/finder.rb +1 -12
- data/lib/contrek/finder/concurrent/inner_polyline.rb +8 -25
- data/lib/contrek/finder/concurrent/part.rb +25 -1
- data/lib/contrek/finder/concurrent/partitionable.rb +39 -34
- data/lib/contrek/finder/concurrent/sequence.rb +1 -17
- data/lib/contrek/version.rb +1 -1
- metadata +5 -2
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
|
@@ -122,3 +122,13 @@ All notable changes to this project will be documented in this file.
|
|
|
122
122
|
|
|
123
123
|
## [1.2.9] - 2026-06-13
|
|
124
124
|
- **Streaming merger:** The streaming merger class extends VerticalMerger and adds a useful feature: the progressive extraction of contours into a disk buffer (SVG file). In this way, all extracted polygons that are no longer within the junction zone of the next stripe are removed from the system and streamed directly to disk. This incredibly reduces memory consumption, allowing the processing of very large files on machines with low memory availability, at the expense of increased processing times. An example of this technique is available in both C++ and Ruby in the repository.
|
|
125
|
+
|
|
126
|
+
## [1.3.0] - 2026-06-17
|
|
127
|
+
- **Streaming merger:** Improvements and bug fixing.
|
|
128
|
+
- **CPP code:** All structures now own 'Point' instances by value instead of raw pointers. Removed now-redundant `clone()` method; results from `process_info()` are already self-contained since points are owned by value, so the defensive deep copy is no longer needed.
|
|
129
|
+
|
|
130
|
+
## [1.3.1] - 2026-06-20
|
|
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) {
|
|
@@ -461,20 +479,15 @@ void stream_progressive_png_image(const std::string& filepath, uint32_t stripe_h
|
|
|
461
479
|
ProcessResult *result = polygon_finder.process_info();
|
|
462
480
|
if (result) {
|
|
463
481
|
std::cout << "stripe " << stripe_count << ": found polygons " << result->groups << std::endl;
|
|
464
|
-
result_clones.push_back(result);
|
|
465
482
|
vmerger.add_tile(*result, !(current_y_offset + stripe_height < total_height));
|
|
483
|
+
delete result;
|
|
484
|
+
std::cout << "-> RSS before merge: " << get_current_rss_mb() << " MB" << std::endl;
|
|
466
485
|
}
|
|
467
486
|
stripe_count++;
|
|
468
487
|
}
|
|
469
|
-
|
|
470
488
|
ProcessResult *merged_result = vmerger.process_info();
|
|
471
489
|
std::cout << "total found polygons " << merged_result->groups << std::endl;
|
|
472
490
|
delete merged_result;
|
|
473
|
-
|
|
474
|
-
// frees memory
|
|
475
|
-
for (auto c : result_clones) {
|
|
476
|
-
delete c;
|
|
477
|
-
}
|
|
478
491
|
} catch (const std::exception& e) {
|
|
479
492
|
std::cerr << "\n[ERROR] Processing exception: " << e.what() << std::endl;
|
|
480
493
|
if (shared_stream.is_open()) shared_stream.close();
|
|
@@ -485,5 +498,6 @@ void stream_progressive_png_image(const std::string& filepath, uint32_t stripe_h
|
|
|
485
498
|
|
|
486
499
|
void Tests::test_l() {
|
|
487
500
|
stream_progressive_png_image("../images/mixed_shapes_1024x1024.png", 300);
|
|
501
|
+
// stream_progressive_png_image("../images/test_20480x20480.png", 2000);
|
|
488
502
|
std::cout << "Memory usage peak: " << get_peak_rss() << " MB" << std::endl;
|
|
489
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();
|
|
@@ -34,5 +34,5 @@ class Cluster {
|
|
|
34
34
|
static void list_to_string(std::vector<Point> list);
|
|
35
35
|
PartPool parts_pool;
|
|
36
36
|
std::deque<Position> positions_pool;
|
|
37
|
-
const Finder* finder() const { return finder_; }
|
|
37
|
+
const Finder* finder() const { return finder_; }
|
|
38
38
|
};
|
|
@@ -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,12 +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
|
-
|
|
65
|
-
outer_joined_polyline->head->payload == position->payload &&
|
|
66
|
-
act_part == all_parts.front()) {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
outer_joined_polyline->add(position);
|
|
64
|
+
outer_joined_polyline.add(position);
|
|
70
65
|
}
|
|
71
66
|
} else {
|
|
72
67
|
if (act_part->dead_end &&
|
|
@@ -74,13 +69,13 @@ void Cursor::traverse_outer(Part* act_part,
|
|
|
74
69
|
last_part->is(Part::SEAM) &&
|
|
75
70
|
last_part->polyline() == act_part->polyline()) return;
|
|
76
71
|
while (Position *new_position = static_cast<Position*>(act_part->iterator())) {
|
|
77
|
-
if (outer_joined_polyline
|
|
78
|
-
outer_joined_polyline
|
|
72
|
+
if (outer_joined_polyline.size > 1 &&
|
|
73
|
+
outer_joined_polyline.head->payload == new_position->payload &&
|
|
79
74
|
act_part == all_parts.front()) {
|
|
80
75
|
return;
|
|
81
76
|
}
|
|
82
77
|
this->cluster.positions_pool.emplace_back(this->cluster.hub(), new_position->payload);
|
|
83
|
-
outer_joined_polyline
|
|
78
|
+
outer_joined_polyline.add(&this->cluster.positions_pool.back());
|
|
84
79
|
new_position->end_point()->tracked_outer = true;
|
|
85
80
|
int versus = act_part->versus();
|
|
86
81
|
auto& q_set = new_position->end_point()->queues();
|
|
@@ -130,7 +125,7 @@ void Cursor::traverse_outer(Part* act_part,
|
|
|
130
125
|
}
|
|
131
126
|
}
|
|
132
127
|
|
|
133
|
-
std::vector<InnerPolyline*> Cursor::join_inners(
|
|
128
|
+
std::vector<InnerPolyline*> Cursor::join_inners(bool treemap) {
|
|
134
129
|
std::vector<InnerPolyline*> return_inner_polylines;
|
|
135
130
|
std::vector<Shape*> processing_queue = shapes_sequence_;
|
|
136
131
|
for (size_t i = 0; i < shapes_sequence_.size(); ++i)
|
|
@@ -145,10 +140,10 @@ std::vector<InnerPolyline*> Cursor::join_inners(Sequence* outer_seq, bool treema
|
|
|
145
140
|
.max = 0
|
|
146
141
|
};
|
|
147
142
|
traverse_inner(part, all_parts, bounds, tracked_end_points);
|
|
148
|
-
Sequence
|
|
143
|
+
Sequence retme_sequence;
|
|
149
144
|
for (Part* part : all_parts)
|
|
150
145
|
{ part->touch();
|
|
151
|
-
retme_sequence
|
|
146
|
+
retme_sequence.move_from(*part, [&](QNode<Point>* pos) -> bool {
|
|
152
147
|
Position *position = static_cast<Position*>(pos);
|
|
153
148
|
if (part->is(Part::ADDED) &&
|
|
154
149
|
!(position->payload.y >= bounds.min &&
|
|
@@ -158,8 +153,8 @@ std::vector<InnerPolyline*> Cursor::join_inners(Sequence* outer_seq, bool treema
|
|
|
158
153
|
return(!(polyline->tile->tg_border(position->payload) && position->end_point()->tracked_outer));
|
|
159
154
|
});
|
|
160
155
|
}
|
|
161
|
-
if (retme_sequence
|
|
162
|
-
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);
|
|
163
158
|
return_inner_polylines.push_back(inner_polyline);
|
|
164
159
|
if (treemap) {
|
|
165
160
|
mark_children(tracked_end_points, polyline, inner_polyline);
|
|
@@ -206,7 +201,7 @@ void Cursor::traverse_inner(Part* act_part, std::vector<Part*>& all_parts, Bound
|
|
|
206
201
|
if (act_part->innerable()) {
|
|
207
202
|
all_parts.push_back(act_part);
|
|
208
203
|
bool jumped = false;
|
|
209
|
-
while (act_part = act_part->circular_next) {
|
|
204
|
+
while ((act_part = act_part->circular_next)) {
|
|
210
205
|
if (act_part->innerable()) {
|
|
211
206
|
all_parts.push_back(act_part);
|
|
212
207
|
} else {
|
|
@@ -238,6 +233,7 @@ void Cursor::traverse_inner(Part* act_part, std::vector<Part*>& all_parts, Bound
|
|
|
238
233
|
orphan_inners_.end(),
|
|
239
234
|
shape->inner_polylines.begin(),
|
|
240
235
|
shape->inner_polylines.end());
|
|
236
|
+
shape->clear_inner();
|
|
241
237
|
}
|
|
242
238
|
dest_part->polyline()->turn_on(Polyline::TRACKED_OUTER);
|
|
243
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
|
+
};
|