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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -0
  3. data/Gemfile.lock +3 -1
  4. data/README.md +28 -28
  5. data/contrek.gemspec +1 -0
  6. data/ext/cpp_polygon_finder/PolygonFinder/src/ContrekApi.h +2 -0
  7. data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +0 -2
  8. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/FinderUtils.cpp +11 -5
  9. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.cpp +8 -10
  10. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.h +4 -1
  11. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.cpp +114 -42
  12. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.h +1 -1
  13. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +2 -1
  14. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.cpp +1 -1
  15. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +66 -116
  16. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.h +1 -1
  17. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +1 -0
  18. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.cpp +2 -87
  19. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.h +1 -13
  20. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/PartPool.cpp +0 -1
  21. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.cpp +1 -57
  22. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.h +1 -4
  23. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Queueable.h +31 -17
  24. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Sequence.cpp +0 -16
  25. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Sequence.h +0 -9
  26. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.cpp +1 -1
  27. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/DouglasPeuckerReducer.cpp +177 -0
  28. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/DouglasPeuckerReducer.h +52 -0
  29. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/RasterReducer.cpp +109 -0
  30. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/RasterReducer.h +32 -0
  31. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/VisvalingamReducer.h +6 -1
  32. data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +4 -0
  33. data/lib/contrek/bitmaps/painting.rb +27 -10
  34. data/lib/contrek/bitmaps/png_bitmap.rb +3 -0
  35. data/lib/contrek/bitmaps/rendering.rb +75 -0
  36. data/lib/contrek/finder/concurrent/cursor.rb +62 -84
  37. data/lib/contrek/finder/concurrent/finder.rb +2 -1
  38. data/lib/contrek/finder/concurrent/part.rb +4 -77
  39. data/lib/contrek/finder/concurrent/partitionable.rb +1 -58
  40. data/lib/contrek/finder/concurrent/queueable.rb +23 -5
  41. data/lib/contrek/finder/concurrent/sequence.rb +0 -15
  42. data/lib/contrek/finder/concurrent/tile.rb +1 -1
  43. data/lib/contrek/finder/node.rb +26 -8
  44. data/lib/contrek/finder/node_cluster.rb +91 -37
  45. data/lib/contrek/finder/polygon_finder.rb +1 -1
  46. data/lib/contrek/reducers/douglas_peucker_reducer.rb +191 -0
  47. data/lib/contrek/reducers/raster_reducer.rb +72 -0
  48. data/lib/contrek/version.rb +1 -1
  49. data/lib/contrek.rb +3 -0
  50. metadata +23 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14fceba0577a0e8bf3ce358d23099eb7d359dfdaafdbfac3489f853001be47b3
4
- data.tar.gz: 4097a012082df7a8c59dd873c2bac9556b0ac5f40699183cb3de1410a9f34a5c
3
+ metadata.gz: ea7475a8d469db6be267562a802b7b8f09480774e6d3085a35a428f569d2f113
4
+ data.tar.gz: 20370119576e765342797caad9778b7d808f9b6be154eca4e5a744f988d532f6
5
5
  SHA512:
6
- metadata.gz: ed0bf7cbf8f90b6327b8ad2ca990e0f05a59e664a9ea04a20a164f51ad0640a85275c80ff34b21e66c2611226448c0c9c7fbc7effeb1c4f51c02c3550267fbdd
7
- data.tar.gz: 9e9677f9fddbd0746a594a26315eb93773f078ed78035cb2b3b36030036079382e7bea31fc4cbc040d7e7d21f1e8fd158a7609f11d1a71aa257592a963af4eb6
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.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=>[{:x=>5, :y=>4}, {:x=>5, :y=>5}, {:x=>8, :y=>5}, {:x=>8, :y=>4}], :inner=>[]}]
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.816 secs with image load (0.37 secs)).
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: 23.83,
201
- init: 313.179,
202
- inner: 6.16,
203
- outer: 78.791,
204
- total: 337.013
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, &not_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
- STRICT_BOUNDS };
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
- : start_point(min_x, y),
24
- end_point(max_x, y),
25
- cluster(cluster) {
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.end_point;
111
+ return node_up.se_point();
114
112
  } else {
115
113
  enter_to->track |= TURNER[tracking][OMIN - 1];
116
- return node_up.start_point;
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.start_point;
120
+ return node_down.nw_point();
123
121
  } else {
124
122
  enter_to->track |= TURNER[tracking][OMAX - 1];
125
- return node_down.end_point;
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.compress_visvalingam)) return;
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 < (int)vert_nodes.size(); 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
- if ((root_node)->tangs_sequence.size() > 0) // front() or back() on empty list is undefined
119
- { versus == Node::A ?
120
- next_node = root_node->get_tangent_node_by_virtual_index(root_node->tangs_sequence.back()) :
121
- next_node = root_node->get_tangent_node_by_virtual_index(root_node->tangs_sequence.front());
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 p = next_node->coords_entering_to(root_node, versus_inverter[versus], Node::OUTER);
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 (poly.outer.size() >= 2)
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
- void NodeCluster::plot_inner_node(std::vector<Point>& sequence_coords, Node *node, int versus, Node *stop_at, Node *start_node) {
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
- bool strict_bounds = this->options->strict_bounds;
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
- sequence_coords.push_back(last_node->coords_entering_to(current_node, versus_inverter[versus], Node::INNER));
275
- if (current_node != start_node) {
276
- if (last_node->y == next_node->y) {
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
- } else if (strict_bounds) {
281
- sequence_coords.push_back(Point{(first_is_max ? last_node->max_x : last_node->min_x), current_node->y});
282
- sequence_coords.push_back(Point{(first_is_max ? next_node->min_x : next_node->max_x), current_node->y});
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 p = last_node->coords_entering_to(current_node, versus, Node::OUTER);
318
- bounds.expand(p.x, p.y);
319
- sequence_coords.push_back(p);
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
- } else if (strict_bounds) {
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
- if (current_node->track_complete()) break;
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
- void plot_inner_node(std::vector<Point>& sequence_coords, Node *node, int versus, Node *stop_at, Node *start_node);
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;
@@ -20,7 +20,7 @@
20
20
  Cluster::Cluster(Finder *finder, int height)
21
21
  : finder_(finder)
22
22
  { tiles_.reserve(2); // only two (left|right)
23
- this->hub_ = new Hub(height);
23
+ this->hub_ = new Hub(height + 1);
24
24
  }
25
25
 
26
26
  Cluster::~Cluster() {