contrek 1.3.3 → 1.3.5
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 +3 -1
- data/README.md +28 -28
- data/contrek.gemspec +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/ContrekApi.h +2 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +0 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/FinderUtils.cpp +11 -5
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.cpp +8 -10
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.h +4 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.cpp +114 -42
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.h +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/Cluster.cpp +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +66 -116
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.h +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.cpp +2 -87
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.h +1 -13
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/PartPool.cpp +0 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.cpp +1 -57
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.h +1 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Queueable.h +31 -17
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Sequence.cpp +0 -16
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Sequence.h +0 -9
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.cpp +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/DouglasPeuckerReducer.cpp +177 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/DouglasPeuckerReducer.h +52 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/RasterReducer.cpp +109 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/RasterReducer.h +32 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/VisvalingamReducer.h +6 -1
- data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +4 -0
- data/lib/contrek/bitmaps/painting.rb +27 -10
- data/lib/contrek/bitmaps/png_bitmap.rb +3 -0
- data/lib/contrek/bitmaps/rendering.rb +75 -0
- data/lib/contrek/finder/concurrent/cursor.rb +62 -84
- data/lib/contrek/finder/concurrent/finder.rb +2 -1
- data/lib/contrek/finder/concurrent/part.rb +4 -77
- data/lib/contrek/finder/concurrent/partitionable.rb +1 -58
- data/lib/contrek/finder/concurrent/queueable.rb +23 -5
- data/lib/contrek/finder/concurrent/sequence.rb +0 -15
- data/lib/contrek/finder/concurrent/tile.rb +1 -1
- data/lib/contrek/finder/node.rb +26 -8
- data/lib/contrek/finder/node_cluster.rb +91 -37
- data/lib/contrek/finder/polygon_finder.rb +1 -1
- data/lib/contrek/reducers/douglas_peucker_reducer.rb +191 -0
- data/lib/contrek/reducers/raster_reducer.rb +72 -0
- data/lib/contrek/version.rb +1 -1
- data/lib/contrek.rb +3 -0
- metadata +23 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ea7475a8d469db6be267562a802b7b8f09480774e6d3085a35a428f569d2f113
|
|
4
|
+
data.tar.gz: 20370119576e765342797caad9778b7d808f9b6be154eca4e5a744f988d532f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b09fddc2bc46bb644f3024de98aa2db5155856981aaf3ce3a5dfaf5a8ef5449f36ad4fb784654bb12368fc8d2b2c0741a492c1f70ec4e620b4282d28268d770e
|
|
7
|
+
data.tar.gz: 6240cd18e6145a3f47504decfab3d74decce51c0ddc91084d7ac7cf95e020cc7165bfca4d8d7831c760ca3ee3aaa63ae3b50fa1c75b5ed60ed13ae87544b9d36
|
data/CHANGELOG.md
CHANGED
|
@@ -135,3 +135,6 @@ All notable changes to this project will be documented in this file.
|
|
|
135
135
|
|
|
136
136
|
## [1.3.3] - 2026-06-28
|
|
137
137
|
- **GeoJsonStreamingMerger:** Fixes both Ruby and CPP side.
|
|
138
|
+
|
|
139
|
+
## [1.3.4] - 2026-07-19
|
|
140
|
+
- Refactored core topology from a top-left point model to a 4-vertex pixel-cell model, natively eliminating diagonal ambiguities and rewriting part of the stripe-merging algorithm to ensure perfect geometric stitching on shared nodes. Removed Strict Bounds mode. Added a Douglas-Peucker and a custom raster-compression algorithm that shifts top/right pixel boundaries by 1px to ensure flawless surface rendering as bitmap image.
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
contrek (1.3.
|
|
4
|
+
contrek (1.3.4)
|
|
5
5
|
chunky_png (~> 1.4)
|
|
6
6
|
concurrent-ruby (~> 1.3.5)
|
|
7
7
|
rice (= 4.5.0)
|
|
@@ -61,6 +61,7 @@ GEM
|
|
|
61
61
|
ruby-prof (1.7.2)
|
|
62
62
|
base64
|
|
63
63
|
ruby-progressbar (1.13.0)
|
|
64
|
+
ruby2d (0.12.1)
|
|
64
65
|
standard (1.51.1)
|
|
65
66
|
language_server-protocol (~> 3.17.0.2)
|
|
66
67
|
lint_roller (~> 1.0)
|
|
@@ -86,6 +87,7 @@ DEPENDENCIES
|
|
|
86
87
|
curses (~> 1.5, >= 1.5.3)
|
|
87
88
|
rspec (~> 3.12)
|
|
88
89
|
ruby-prof (~> 1.7, >= 1.7.2)
|
|
90
|
+
ruby2d (~> 0.12)
|
|
89
91
|
standard (~> 1.51)
|
|
90
92
|
|
|
91
93
|
BUNDLED WITH
|
data/README.md
CHANGED
|
@@ -155,7 +155,7 @@ You can process from a raw stream
|
|
|
155
155
|
result = CPPPolygonFinder.new(raw_bitmap, not_matcher, nil,{compress: {uniq: true, linear: true}}).process_info
|
|
156
156
|
puts result.points.inspect
|
|
157
157
|
=>
|
|
158
|
-
[{:outer=>[{:
|
|
158
|
+
[{:outer=>[{x: 5, y: 4}, {x: 5, y: 6}, {x: 9, y: 6}, {x: 9, y: 4}], :inner=>[]}]
|
|
159
159
|
```
|
|
160
160
|
|
|
161
161
|
Multithreaded contour processing is supported by both the native C++ and pure Ruby implementations. When using the C++ engine (default), multithreading works as expected and fully utilizes all available cores.
|
|
@@ -183,7 +183,7 @@ Regarding multithreading:
|
|
|
183
183
|
|
|
184
184
|
- The algorithm splits the contour-detection workflow into multiple phases that can be executed in parallel. The initial contour extraction on each band and the subsequent merging of coordinates between adjacent bands—performed pairwise, recursively, and in a non-deterministic order—results in a final output that is not idempotent. Idempotence is guaranteed only when the exact same merging sequence is repeated.
|
|
185
185
|
|
|
186
|
-
By not declaring native option CPP Multithreading optimized code is used. In the above example a [105 MP image](spec/files/images/sample_10240x10240.png) is examined by 4 threads working on 4 tiles (total compute time about 0.
|
|
186
|
+
By not declaring native option CPP Multithreading optimized code is used. In the above example a [105 MP image](spec/files/images/sample_10240x10240.png) is examined by 4 threads working on 4 tiles (total compute time about 0.328 secs with image load).
|
|
187
187
|
|
|
188
188
|
```ruby
|
|
189
189
|
result = Contrek.contour!(
|
|
@@ -197,15 +197,36 @@ result = Contrek.contour!(
|
|
|
197
197
|
)
|
|
198
198
|
puts result.metadata[:benchmarks].inspect
|
|
199
199
|
|
|
200
|
-
{ compress:
|
|
201
|
-
init:
|
|
202
|
-
inner:
|
|
203
|
-
outer:
|
|
204
|
-
total:
|
|
200
|
+
{ compress: 5.3933,
|
|
201
|
+
init: 322.749,
|
|
202
|
+
inner: 4.795,
|
|
203
|
+
outer: 81.546,
|
|
204
|
+
total: 328.142
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
```
|
|
208
208
|
|
|
209
|
+
## Tracking & Topology Schema
|
|
210
|
+
|
|
211
|
+
Contrek utilizes a **Dual-Grid / Cell-Based Topology** instead of a classic point-pixel model. Each pixel is treated as a solid 2D cell defined by **4 distinct vertices** (nodes).
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
### Pixel-Cell Geometry
|
|
215
|
+
|
|
216
|
+
```text
|
|
217
|
+
(x, y) Top Edge (x+1, y)
|
|
218
|
+
O--------------------------------O
|
|
219
|
+
| |
|
|
220
|
+
| |
|
|
221
|
+
Left | PIXEL | Right
|
|
222
|
+
Edge | (x, y) | Edge
|
|
223
|
+
| |
|
|
224
|
+
| |
|
|
225
|
+
O--------------------------------O
|
|
226
|
+
(x, y+1) Bottom Edge (x+1, y+1)
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
|
|
209
230
|
### Connectivity Modes
|
|
210
231
|
|
|
211
232
|
Contrek supports different pixel connectivity rules to define how polygons are traced and merged. This is controlled by the `:connectivity` option.
|
|
@@ -233,27 +254,6 @@ result = Contrek.contour!(
|
|
|
233
254
|
)
|
|
234
255
|
```
|
|
235
256
|
|
|
236
|
-
## Tracing Modes
|
|
237
|
-
|
|
238
|
-
Contrek provides two levels of precision for polygon generation to balance data density and topological accuracy.
|
|
239
|
-
|
|
240
|
-
### **Standard Mode** (Default)
|
|
241
|
-
Optimized for **speed** and **lightweight** output.
|
|
242
|
-
* **Behavior:** Traces outer boundaries while simplifying collinear segments.
|
|
243
|
-
* **Result:** Fewer vertices and smaller memory footprint.
|
|
244
|
-
|
|
245
|
-
### **Strict Mode** (`strict_bounds: true`)
|
|
246
|
-
Engineered for **Pixel-Perfect** precision.
|
|
247
|
-
* **Behavior:** Preserves every junction and internal connectivity change.
|
|
248
|
-
* **Result:** 100% topologically faithful geometry with no micro-gaps between adjacent polygons.
|
|
249
|
-
|
|
250
|
-
Below are two images illustrating the difference in tracing modes. In the first case, with **strict_bounds ON**, the anti-clockwise sequence includes two additional points, **H** and **I**, which trace the shape more accurately. In the second case, the transition between **G** and **H** is approximated, omitting the indentation.
|
|
251
|
-
|
|
252
|
-
| Strict Bounds ON | Strict Bounds OFF |
|
|
253
|
-
|:---:|:---:|
|
|
254
|
-
| <img src="./docs/images/strict_bounds_on.png" alt="Originale" width="60%"/> | <img src="./docs/images/strict_bounds_off.png" alt="Poligoni" width="60%"/> |
|
|
255
|
-
|
|
256
|
-
|
|
257
257
|
## Result
|
|
258
258
|
|
|
259
259
|
The result structure contains polygon coordinates and a set of metadata. Polygon coordinates can be accessed via:
|
data/contrek.gemspec
CHANGED
|
@@ -29,6 +29,7 @@ Gem::Specification.new do |s|
|
|
|
29
29
|
s.add_development_dependency "standard", "~> 1.51"
|
|
30
30
|
s.add_development_dependency "curses", "~> 1.5", ">= 1.5.3"
|
|
31
31
|
s.add_development_dependency "ruby-prof", "~> 1.7", ">= 1.7.2"
|
|
32
|
+
s.add_development_dependency "ruby2d", "~> 0.12"
|
|
32
33
|
|
|
33
34
|
s.add_dependency "chunky_png", "~> 1.4"
|
|
34
35
|
s.add_dependency "concurrent-ruby", "~> 1.3.5"
|
|
@@ -37,6 +37,8 @@ struct Config {
|
|
|
37
37
|
int tiles = 2;
|
|
38
38
|
bool compress_unique = false;
|
|
39
39
|
bool compress_linear = false;
|
|
40
|
+
bool compress_raster = false;
|
|
41
|
+
bool compress_douglas_peucker = false;
|
|
40
42
|
bool compress_visvalingam = false;
|
|
41
43
|
bool treemap = false;
|
|
42
44
|
int32_t target_color = -1;
|
|
@@ -342,7 +342,6 @@ void stream_png_image(const std::string& filepath, uint32_t stripe_height, bool
|
|
|
342
342
|
// stripe contour tracing
|
|
343
343
|
std::vector<std::string> finder_arguments = {
|
|
344
344
|
"--versus=a",
|
|
345
|
-
"--strict_bounds" // <- this option is strictly needed when working with vertical merger
|
|
346
345
|
};
|
|
347
346
|
PolygonFinder polygon_finder(&stripe_bitmap, ¬_matcher, nullptr, &finder_arguments);
|
|
348
347
|
ProcessResult *result = polygon_finder.process_info();
|
|
@@ -470,7 +469,6 @@ void stream_progressive_png_image(const std::string& filepath, uint32_t stripe_h
|
|
|
470
469
|
std::vector<std::string> finder_arguments = {
|
|
471
470
|
"--versus=a",
|
|
472
471
|
"--bounds",
|
|
473
|
-
"--strict_bounds", // <- this option is strictly needed when working with vertical merger
|
|
474
472
|
"--compress_uniq",
|
|
475
473
|
"--compress_linear"
|
|
476
474
|
};
|
|
@@ -24,18 +24,19 @@ void FinderUtils::sanitize_options(pf_Options& options, std::vector<std::string>
|
|
|
24
24
|
|
|
25
25
|
enum optionIndex { COMPRESS_UNIQ, VERSUS, COMPRESS_VISVALINGAM, COMPRESS_LINEAR, NUMBER_OF_TILES,
|
|
26
26
|
COMPRESS_VISVALINGAM_TOLERANCE, TREEMAP, NAMED_SEQUENCES, BOUNDS, CONNECTIVITY,
|
|
27
|
-
|
|
27
|
+
COMPRESS_RASTER, COMPRESS_DOUGLAS_PEUCKER };
|
|
28
28
|
const option::Descriptor usage[] = {
|
|
29
29
|
// {UNKNOWN, 0,"" , "" ,option::Arg::None, 0},
|
|
30
30
|
{COMPRESS_VISVALINGAM, 0, "" , "compress_visvalingam", option::Arg::None, 0},
|
|
31
31
|
{COMPRESS_LINEAR, 0, "" , "compress_linear", option::Arg::None, 0},
|
|
32
32
|
{COMPRESS_VISVALINGAM_TOLERANCE, 0, "" , "compress_visvalingam_tolerance", option::Arg::Optional, 0},
|
|
33
33
|
{COMPRESS_UNIQ, 0, "", "compress_uniq", option::Arg::None, 0},
|
|
34
|
+
{COMPRESS_RASTER, 0, "", "compress_raster", option::Arg::None, 0},
|
|
35
|
+
{COMPRESS_DOUGLAS_PEUCKER, 0, "", "compress_douglas_peucker", option::Arg::None, 0},
|
|
34
36
|
{NUMBER_OF_TILES, 0, "", "number_of_tiles", option::Arg::Optional, 0},
|
|
35
37
|
{TREEMAP, 0, "", "treemap", option::Arg::None, 0},
|
|
36
38
|
{NAMED_SEQUENCES, 0, "", "named_sequences", option::Arg::None, 0},
|
|
37
39
|
{BOUNDS, 0, "", "bounds", option::Arg::None, 0},
|
|
38
|
-
{STRICT_BOUNDS, 0, "", "strict_bounds", option::Arg::None, 0},
|
|
39
40
|
{VERSUS, 0, "v", "versus", option::Arg::Optional, 0},
|
|
40
41
|
{CONNECTIVITY, 0, "c", "connectivity", option::Arg::Optional, 0},
|
|
41
42
|
{0, 0, 0, 0, 0, 0}
|
|
@@ -89,9 +90,6 @@ void FinderUtils::sanitize_options(pf_Options& options, std::vector<std::string>
|
|
|
89
90
|
if (ioptions[BOUNDS].count() > 0)
|
|
90
91
|
{ options.bounds = true;
|
|
91
92
|
}
|
|
92
|
-
if (ioptions[STRICT_BOUNDS].count() > 0)
|
|
93
|
-
{ options.strict_bounds = true;
|
|
94
|
-
}
|
|
95
93
|
// COMPRESS LINEAR
|
|
96
94
|
if (ioptions[COMPRESS_LINEAR].count() > 0)
|
|
97
95
|
{ options.compress_linear = true;
|
|
@@ -108,6 +106,14 @@ void FinderUtils::sanitize_options(pf_Options& options, std::vector<std::string>
|
|
|
108
106
|
break;
|
|
109
107
|
}
|
|
110
108
|
}
|
|
109
|
+
// COMPRESS RASTER
|
|
110
|
+
if (ioptions[COMPRESS_RASTER].count() > 0)
|
|
111
|
+
{ options.compress_raster = true;
|
|
112
|
+
}
|
|
113
|
+
// COMPRESS DOUGLAS PEUCKER
|
|
114
|
+
if (ioptions[COMPRESS_DOUGLAS_PEUCKER].count() > 0)
|
|
115
|
+
{ options.compress_douglas_peucker = true;
|
|
116
|
+
}
|
|
111
117
|
/* std::cout << "-----------" << std::endl;
|
|
112
118
|
std::cout << "versus " << options.versus << std::endl;
|
|
113
119
|
std::cout << "bounds " << options.bounds << std::endl;
|
|
@@ -20,13 +20,11 @@
|
|
|
20
20
|
static const int TURNER[2][2] = {{Node::OMAX, Node::OMIN}, {Node::TURN_MAX, Node::TURN_MIN}};
|
|
21
21
|
|
|
22
22
|
Node::Node(int min_x, int max_x, int y, NodeCluster* cluster, char name)
|
|
23
|
-
:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
this->name = name;
|
|
27
|
-
this->min_x = min_x;
|
|
28
|
-
this->max_x = max_x;
|
|
23
|
+
: cluster(cluster),
|
|
24
|
+
min_x(min_x),
|
|
25
|
+
max_x(max_x + 1) {
|
|
29
26
|
this->y = y;
|
|
27
|
+
this->name = name;
|
|
30
28
|
this->tangs_count = 0;
|
|
31
29
|
this->abs_x_index = 0;
|
|
32
30
|
this->down_indexer = 0;
|
|
@@ -110,19 +108,19 @@ Point Node::coords_entering_to(Node *enter_to, int mode, int tracking) {
|
|
|
110
108
|
Node& node_up = cluster->vert_nodes[y + T_UP][-(tg_index + 1)];
|
|
111
109
|
if (mode == Node::A) {
|
|
112
110
|
enter_to->track |= TURNER[tracking][OMAX - 1];
|
|
113
|
-
return node_up.
|
|
111
|
+
return node_up.se_point();
|
|
114
112
|
} else {
|
|
115
113
|
enter_to->track |= TURNER[tracking][OMIN - 1];
|
|
116
|
-
return node_up.
|
|
114
|
+
return node_up.sw_point();
|
|
117
115
|
}
|
|
118
116
|
} else {
|
|
119
117
|
Node& node_down = cluster->vert_nodes[y + T_DOWN][tg_index];
|
|
120
118
|
if (mode == Node::A) {
|
|
121
119
|
enter_to->track |= TURNER[tracking][OMIN - 1];
|
|
122
|
-
return node_down.
|
|
120
|
+
return node_down.nw_point();
|
|
123
121
|
} else {
|
|
124
122
|
enter_to->track |= TURNER[tracking][OMAX - 1];
|
|
125
|
-
return node_down.
|
|
123
|
+
return node_down.ne_point();
|
|
126
124
|
}
|
|
127
125
|
}
|
|
128
126
|
}
|
|
@@ -94,7 +94,6 @@ class Node : public Listable {
|
|
|
94
94
|
int upper_end = -1;
|
|
95
95
|
int lower_start = std::numeric_limits<int>::max();
|
|
96
96
|
int lower_end = -1;
|
|
97
|
-
Point start_point, end_point;
|
|
98
97
|
NodeCluster* cluster;
|
|
99
98
|
void add_intersection(Node& other_node, int other_node_index);
|
|
100
99
|
SmallVec tangs_sequence;
|
|
@@ -105,6 +104,10 @@ class Node : public Listable {
|
|
|
105
104
|
bool track_uncomplete();
|
|
106
105
|
bool track_complete();
|
|
107
106
|
bool get_trackmax();
|
|
107
|
+
Point nw_point() { return {min_x, y}; }
|
|
108
|
+
Point ne_point() { return {max_x, y}; }
|
|
109
|
+
Point se_point() { return {max_x, y + 1}; }
|
|
110
|
+
Point sw_point() { return {min_x, y + 1}; }
|
|
108
111
|
|
|
109
112
|
public:
|
|
110
113
|
int min_x, max_x;
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
#include "RectBounds.h"
|
|
21
21
|
#include "../reducers/UniqReducer.h"
|
|
22
22
|
#include "../reducers/LinearReducer.h"
|
|
23
|
+
#include "../reducers/RasterReducer.h"
|
|
24
|
+
#include "../reducers/DouglasPeuckerReducer.h"
|
|
23
25
|
#include "../reducers/VisvalingamReducer.h"
|
|
24
26
|
|
|
25
27
|
NodeCluster::NodeCluster(int h, int w, pf_Options *options) {
|
|
@@ -41,7 +43,8 @@ NodeCluster::~NodeCluster() {
|
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
void NodeCluster::compress_coords(std::list<Polygon>& polygons, pf_Options options) {
|
|
44
|
-
if (!(options.compress_linear || options.compress_uniq || options.
|
|
46
|
+
if (!(options.compress_linear || options.compress_uniq || options.compress_raster ||
|
|
47
|
+
options.compress_douglas_peucker || options.compress_visvalingam)) return;
|
|
45
48
|
|
|
46
49
|
auto compress_sequence = [&](std::vector<Point>& points_vec) {
|
|
47
50
|
if (points_vec.empty()) return;
|
|
@@ -54,10 +57,18 @@ void NodeCluster::compress_coords(std::list<Polygon>& polygons, pf_Options optio
|
|
|
54
57
|
LinearReducer linear_reducer(points_vec);
|
|
55
58
|
linear_reducer.reduce();
|
|
56
59
|
}
|
|
60
|
+
if (options.compress_raster) {
|
|
61
|
+
RasterReducer raster_reducer(points_vec, this->options->versus);
|
|
62
|
+
raster_reducer.reduce();
|
|
63
|
+
}
|
|
57
64
|
if (options.compress_visvalingam) {
|
|
58
65
|
VisvalingamReducer vis_reducer(points_vec, options.compress_visvalingam_tolerance);
|
|
59
66
|
vis_reducer.reduce();
|
|
60
67
|
}
|
|
68
|
+
if (options.compress_douglas_peucker) {
|
|
69
|
+
DouglasPeuckerReducer douglas_peucker_reducer(points_vec);
|
|
70
|
+
douglas_peucker_reducer.reduce();
|
|
71
|
+
}
|
|
61
72
|
};
|
|
62
73
|
for (auto &poly : polygons) {
|
|
63
74
|
compress_sequence(poly.outer);
|
|
@@ -68,7 +79,7 @@ void NodeCluster::compress_coords(std::list<Polygon>& polygons, pf_Options optio
|
|
|
68
79
|
}
|
|
69
80
|
|
|
70
81
|
void NodeCluster::build_tangs_sequence() {
|
|
71
|
-
for (int y = 0; y < (
|
|
82
|
+
for (int y = 0; y < static_cast<int>(vert_nodes.size()); y++) {
|
|
72
83
|
for (Node& node : vert_nodes[y]) {
|
|
73
84
|
node.precalc_tangs_sequences(*this);
|
|
74
85
|
}
|
|
@@ -89,11 +100,11 @@ Node* NodeCluster::add_node(int min_x, int max_x, int y, char name, int offset)
|
|
|
89
100
|
if (!up_nodes.empty()) {
|
|
90
101
|
auto it = std::lower_bound(up_nodes.begin(), up_nodes.end(), node.min_x,
|
|
91
102
|
[&](const Node& a, int val) {
|
|
92
|
-
return ((a.max_x + offset) < val);
|
|
103
|
+
return ((a.max_x - 1 + offset) < val);
|
|
93
104
|
});
|
|
94
105
|
|
|
95
106
|
while (it != up_nodes.end()) {
|
|
96
|
-
if ((it->min_x - offset) > node.max_x) break;
|
|
107
|
+
if ((it->min_x - offset) > (node.max_x - 1)) break;
|
|
97
108
|
node.add_intersection(*it, it->abs_x_index);
|
|
98
109
|
it->add_intersection(node, node.abs_x_index);
|
|
99
110
|
++it;
|
|
@@ -106,7 +117,6 @@ Node* NodeCluster::add_node(int min_x, int max_x, int y, char name, int offset)
|
|
|
106
117
|
void NodeCluster::plot(int versus) {
|
|
107
118
|
int inner_v = versus_inverter[versus];
|
|
108
119
|
int index_order = 0;
|
|
109
|
-
Node *next_node;
|
|
110
120
|
|
|
111
121
|
while (root_nodes->size() > 0)
|
|
112
122
|
{ Node *root_node = reinterpret_cast<Node*>(root_nodes->shift());
|
|
@@ -114,22 +124,50 @@ void NodeCluster::plot(int versus) {
|
|
|
114
124
|
this->plot_sequence.clear();
|
|
115
125
|
this->plot_sequence.push_back(root_node);
|
|
116
126
|
Polygon poly;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
127
|
+
Node *next_node = nullptr;
|
|
128
|
+
if ((root_node)->tangs_sequence.size() > 0) // front() or back() on empty list is undefined
|
|
129
|
+
{ versus == Node::A ?
|
|
130
|
+
next_node = root_node->get_tangent_node_by_virtual_index(root_node->tangs_sequence.back()) :
|
|
131
|
+
next_node = root_node->get_tangent_node_by_virtual_index(root_node->tangs_sequence.front());
|
|
132
|
+
}
|
|
123
133
|
if (next_node != nullptr)
|
|
124
|
-
{ Point
|
|
134
|
+
{ Point beginning_point;
|
|
135
|
+
beginning_point = versus == Node::A ? root_node->nw_point() : root_node->ne_point();
|
|
136
|
+
poly.bounds.expand(beginning_point.x, beginning_point.y);
|
|
137
|
+
poly.outer.push_back(beginning_point);
|
|
138
|
+
|
|
139
|
+
Point p = next_node->coords_entering_to(root_node, versus_inverter[versus], Node::OUTER);
|
|
125
140
|
poly.bounds.expand(p.x, p.y);
|
|
126
141
|
poly.outer.push_back(p);
|
|
127
142
|
}
|
|
143
|
+
|
|
128
144
|
if ((this->nodes > 0) && (next_node != nullptr))
|
|
129
145
|
{ plot_node(poly.outer, next_node, root_node, versus, poly.bounds);
|
|
130
146
|
}
|
|
131
147
|
|
|
132
|
-
if (
|
|
148
|
+
if (next_node == nullptr) {
|
|
149
|
+
if (versus == Node::A) {
|
|
150
|
+
Point nw_point = root_node->nw_point();
|
|
151
|
+
poly.bounds.expand(nw_point.x, nw_point.y);
|
|
152
|
+
poly.outer.push_back(nw_point);
|
|
153
|
+
poly.outer.push_back(root_node->sw_point());
|
|
154
|
+
Point se_point = root_node->se_point();
|
|
155
|
+
poly.bounds.expand(se_point.x, se_point.y);
|
|
156
|
+
poly.outer.push_back(se_point);
|
|
157
|
+
poly.outer.push_back(root_node->ne_point());
|
|
158
|
+
} else {
|
|
159
|
+
Point ne_point = root_node->ne_point();
|
|
160
|
+
poly.bounds.expand(ne_point.x, ne_point.y);
|
|
161
|
+
poly.outer.push_back(ne_point);
|
|
162
|
+
poly.outer.push_back(root_node->se_point());
|
|
163
|
+
Point sw_point = root_node->sw_point();
|
|
164
|
+
poly.bounds.expand(sw_point.x, sw_point.y);
|
|
165
|
+
poly.outer.push_back(sw_point);
|
|
166
|
+
poly.outer.push_back(root_node->nw_point());
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (poly.outer.size() > 0)
|
|
133
171
|
{ this->polygons.push_back(poly);
|
|
134
172
|
this->sequences.push_back(std::move(this->plot_sequence));
|
|
135
173
|
|
|
@@ -174,7 +212,11 @@ void NodeCluster::plot(int versus) {
|
|
|
174
212
|
|
|
175
213
|
if (next_node != nullptr)
|
|
176
214
|
{ inner_sequence.push_back(next_node->coords_entering_to(first, inner_v, Node::INNER));
|
|
177
|
-
plot_inner_node(inner_sequence, next_node, inner_v, first, root_node);
|
|
215
|
+
Node* last_node = plot_inner_node(inner_sequence, next_node, inner_v, first, root_node);
|
|
216
|
+
Point last_coord = last_node->coords_entering_to(first, versus_inverter[inner_v], Node::INNER);
|
|
217
|
+
if (inner_sequence.back() != last_coord) {
|
|
218
|
+
inner_sequence.push_back(last_coord);
|
|
219
|
+
}
|
|
178
220
|
}
|
|
179
221
|
this->polygons.back().inner.push_back(inner_sequence);
|
|
180
222
|
this->inner_plot->grab(this->inner_new);
|
|
@@ -187,9 +229,6 @@ void NodeCluster::plot(int versus) {
|
|
|
187
229
|
{ this->treemap.push_back(versus == Node::A ? this->test_in_hole_a(root_node) : this->test_in_hole_o(root_node));
|
|
188
230
|
}
|
|
189
231
|
index_order++;
|
|
190
|
-
} else {
|
|
191
|
-
this->plot_sequence.clear();
|
|
192
|
-
}
|
|
193
232
|
}
|
|
194
233
|
}
|
|
195
234
|
|
|
@@ -242,9 +281,9 @@ std::pair<int, int> NodeCluster::test_in_hole_o(Node* node)
|
|
|
242
281
|
return {-1, -1};
|
|
243
282
|
}
|
|
244
283
|
|
|
245
|
-
|
|
284
|
+
Node* NodeCluster::plot_inner_node(std::vector<Point>& sequence_coords, Node *node, int versus, Node *stop_at, Node *start_node) {
|
|
246
285
|
Node *current_node = node;
|
|
247
|
-
|
|
286
|
+
|
|
248
287
|
while (current_node != nullptr) {
|
|
249
288
|
current_node->outer_index = start_node->outer_index;
|
|
250
289
|
root_nodes->remove(current_node);
|
|
@@ -271,16 +310,29 @@ void NodeCluster::plot_inner_node(std::vector<Point>& sequence_coords, Node *nod
|
|
|
271
310
|
plot = (n == next_node);
|
|
272
311
|
}
|
|
273
312
|
if (plot) {
|
|
274
|
-
|
|
275
|
-
if (
|
|
276
|
-
|
|
277
|
-
sequence_coords.push_back(next_node->coords_entering_to(current_node, versus, Node::INNER));
|
|
278
|
-
}
|
|
313
|
+
Point first_point = last_node->coords_entering_to(current_node, versus_inverter[versus], Node::INNER);
|
|
314
|
+
if (sequence_coords.back() != first_point) {
|
|
315
|
+
sequence_coords.push_back(first_point);
|
|
279
316
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
317
|
+
if (next_node->y == last_node->y) {
|
|
318
|
+
Point pt_a, pt_b;
|
|
319
|
+
if (next_node->y < current_node->y) {
|
|
320
|
+
pt_a = current_node->sw_point();
|
|
321
|
+
pt_b = current_node->se_point();
|
|
322
|
+
} else {
|
|
323
|
+
pt_a = current_node->ne_point();
|
|
324
|
+
pt_b = current_node->nw_point();
|
|
325
|
+
}
|
|
326
|
+
if (versus == Node::O) {
|
|
327
|
+
sequence_coords.push_back(pt_a);
|
|
328
|
+
sequence_coords.push_back(pt_b);
|
|
329
|
+
} else {
|
|
330
|
+
sequence_coords.push_back(pt_b);
|
|
331
|
+
sequence_coords.push_back(pt_a);
|
|
332
|
+
}
|
|
283
333
|
}
|
|
334
|
+
sequence_coords.push_back(next_node->coords_entering_to(current_node, versus, Node::INNER));
|
|
335
|
+
}
|
|
284
336
|
|
|
285
337
|
if (current_node->track_uncomplete()) {
|
|
286
338
|
this->inner_new->push_back(current_node);
|
|
@@ -290,11 +342,11 @@ void NodeCluster::plot_inner_node(std::vector<Point>& sequence_coords, Node *nod
|
|
|
290
342
|
if (next_node == stop_at) break;
|
|
291
343
|
current_node = next_node;
|
|
292
344
|
}
|
|
345
|
+
return current_node;
|
|
293
346
|
}
|
|
294
347
|
|
|
295
348
|
void NodeCluster::plot_node(std::vector<Point>& sequence_coords, Node *node, Node *start_node, int versus, RectBounds& bounds) {
|
|
296
349
|
Node *current_node = node;
|
|
297
|
-
bool strict_bounds = this->options->strict_bounds;
|
|
298
350
|
|
|
299
351
|
while (current_node != nullptr) {
|
|
300
352
|
root_nodes->remove(current_node);
|
|
@@ -314,27 +366,47 @@ void NodeCluster::plot_node(std::vector<Point>& sequence_coords, Node *node, Nod
|
|
|
314
366
|
plot = (n == next_node);
|
|
315
367
|
}
|
|
316
368
|
if (plot) {
|
|
317
|
-
Point
|
|
318
|
-
|
|
319
|
-
|
|
369
|
+
Point start_coord = last_node->coords_entering_to(current_node, versus, Node::OUTER);
|
|
370
|
+
if (sequence_coords.back() != start_coord) {
|
|
371
|
+
bounds.expand(start_coord.x, start_coord.y);
|
|
372
|
+
sequence_coords.push_back(start_coord);
|
|
373
|
+
}
|
|
374
|
+
bool final_step = current_node == start_node && current_node->track_complete();
|
|
375
|
+
if (next_node->y == last_node->y) {
|
|
376
|
+
Point pt_a, pt_b;
|
|
377
|
+
if (next_node->y < current_node->y) {
|
|
378
|
+
pt_a = current_node->se_point();
|
|
379
|
+
pt_b = current_node->sw_point();
|
|
380
|
+
} else {
|
|
381
|
+
pt_a = current_node->nw_point();
|
|
382
|
+
pt_b = current_node->ne_point();
|
|
383
|
+
}
|
|
384
|
+
Point first = (versus == Node::O) ? pt_a : pt_b;
|
|
385
|
+
Point second = (versus == Node::O) ? pt_b : pt_a;
|
|
386
|
+
sequence_coords.push_back(first);
|
|
387
|
+
bounds.expand(first.x, first.y);
|
|
388
|
+
if (!final_step) {
|
|
389
|
+
sequence_coords.push_back(second);
|
|
390
|
+
bounds.expand(second.x, second.y);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (!final_step) {
|
|
395
|
+
Point end_coord = next_node->coords_entering_to(current_node, versus_inverter[versus], Node::OUTER);
|
|
396
|
+
sequence_coords.push_back(end_coord);
|
|
397
|
+
bounds.expand(end_coord.x, end_coord.y);
|
|
398
|
+
}
|
|
399
|
+
|
|
320
400
|
if (current_node != start_node) {
|
|
321
401
|
inner_plot->contains(current_node) ? inner_plot->remove(current_node) : inner_plot->push_back(current_node);
|
|
322
402
|
if (last_node->y == next_node->y) {
|
|
323
|
-
Point p1 = next_node->coords_entering_to(current_node, versus_inverter[versus], Node::OUTER);
|
|
324
|
-
bounds.expand(p1.x, p1.y);
|
|
325
|
-
sequence_coords.push_back(p1);
|
|
326
403
|
inner_plot->contains(current_node) ? inner_plot->remove(current_node) : inner_plot->push_back(current_node);
|
|
327
404
|
}
|
|
328
405
|
}
|
|
329
|
-
}
|
|
330
|
-
bool is_down = current_node->y > last_node->y;
|
|
331
|
-
bool is_a = (versus == Node::A);
|
|
332
|
-
sequence_coords.push_back(Point{(is_down == is_a ? last_node->min_x : last_node->max_x), current_node->y});
|
|
333
|
-
sequence_coords.push_back(Point{(is_down == is_a ? next_node->max_x : next_node->min_x), current_node->y});
|
|
334
|
-
}
|
|
406
|
+
}
|
|
335
407
|
|
|
336
|
-
if (current_node == start_node) {
|
|
337
|
-
|
|
408
|
+
if (current_node == start_node && current_node->track_complete()) {
|
|
409
|
+
break;
|
|
338
410
|
}
|
|
339
411
|
current_node = next_node;
|
|
340
412
|
}
|
|
@@ -25,7 +25,7 @@ struct pf_Options;
|
|
|
25
25
|
class NodeCluster {
|
|
26
26
|
private:
|
|
27
27
|
void plot_node(std::vector<Point>& sequence_coords, Node *node, Node *start_node, int versus, RectBounds& bounds);
|
|
28
|
-
|
|
28
|
+
Node* plot_inner_node(std::vector<Point>& sequence_coords, Node *node, int versus, Node *stop_at, Node *start_node);
|
|
29
29
|
std::vector<Node*> plot_sequence;
|
|
30
30
|
List *inner_plot;
|
|
31
31
|
List *inner_new;
|
|
@@ -47,9 +47,10 @@ struct pf_Options {
|
|
|
47
47
|
bool compress_uniq = false;
|
|
48
48
|
bool compress_linear = false;
|
|
49
49
|
bool compress_visvalingam = false;
|
|
50
|
+
bool compress_raster = false;
|
|
51
|
+
bool compress_douglas_peucker = false;
|
|
50
52
|
bool named_sequences = false;
|
|
51
53
|
bool bounds = false;
|
|
52
|
-
bool strict_bounds = false;
|
|
53
54
|
int connectivity_offset = 0;
|
|
54
55
|
float compress_visvalingam_tolerance = 10.0;
|
|
55
56
|
int number_of_tiles = 1;
|