contrek 1.3.2 → 1.3.4
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 +6 -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 +2 -4
- 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/GeoJsonStreamingMerger.h +6 -7
- 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 +3 -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/StreamingMerger.cpp +2 -6
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/StreamingMerger.h +1 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/SvgStreamingMerger.h +14 -1
- 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 +20 -2
- data/lib/contrek/bitmaps/painting.rb +27 -10
- data/lib/contrek/bitmaps/png_bitmap.rb +3 -0
- data/lib/contrek/bitmaps/raw_bitmap.rb +1 -0
- data/lib/contrek/bitmaps/rendering.rb +74 -0
- data/lib/contrek/cpp/cpp_geo_json_concurrent_streaming_merger.rb +11 -0
- data/lib/contrek/cpp/{cpp_concurrent_streaming_merger.rb → cpp_svg_concurrent_streaming_merger.rb} +1 -1
- data/lib/contrek/finder/concurrent/cursor.rb +62 -84
- data/lib/contrek/finder/concurrent/finder.rb +2 -1
- data/lib/contrek/finder/concurrent/geo_json_streaming_merger.rb +46 -0
- 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/streaming_merger.rb +5 -27
- data/lib/contrek/finder/concurrent/svg_streaming_merger.rb +35 -0
- 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 +7 -1
- metadata +27 -3
|
@@ -20,14 +20,5 @@ class Sequence : public Queueable<Point>{
|
|
|
20
20
|
public:
|
|
21
21
|
Sequence() {}
|
|
22
22
|
std::string toString();
|
|
23
|
-
bool is_not_vertical();
|
|
24
23
|
Shape* shape = nullptr;
|
|
25
|
-
const std::vector<Point>& get_vector_cache() {
|
|
26
|
-
if (vector_cache.empty() && this->size > 0) {
|
|
27
|
-
vector_cache = this->to_vector();
|
|
28
|
-
}
|
|
29
|
-
return vector_cache;
|
|
30
|
-
}
|
|
31
|
-
private:
|
|
32
|
-
std::vector<Point> vector_cache;
|
|
33
24
|
};
|
|
@@ -15,15 +15,11 @@
|
|
|
15
15
|
|
|
16
16
|
StreamingMerger::StreamingMerger(int number_of_threads,
|
|
17
17
|
std::vector<std::string>* options,
|
|
18
|
-
std::ofstream* stream_to
|
|
19
|
-
|
|
20
|
-
: VerticalMerger(number_of_threads, options), stream(stream_to), total_width(total_width), total_height(total_height) {
|
|
18
|
+
std::ofstream* stream_to)
|
|
19
|
+
: VerticalMerger(number_of_threads, options), stream(stream_to) {
|
|
21
20
|
if (!stream) {
|
|
22
21
|
throw std::invalid_argument("Streaming requires a valid destination output. stream_to cannot be null.");
|
|
23
22
|
}
|
|
24
|
-
if (total_width <= 0 || total_height <= 0) {
|
|
25
|
-
throw std::invalid_argument("Streaming requires valid canvas dimensions (width and height must be > 0).");
|
|
26
|
-
}
|
|
27
23
|
}
|
|
28
24
|
|
|
29
25
|
void StreamingMerger::add_tile(ProcessResult& result, bool flush)
|
|
@@ -18,8 +18,6 @@
|
|
|
18
18
|
class StreamingMerger : public VerticalMerger {
|
|
19
19
|
protected:
|
|
20
20
|
std::ofstream* stream;
|
|
21
|
-
int total_width;
|
|
22
|
-
int total_height;
|
|
23
21
|
int moved = 0;
|
|
24
22
|
|
|
25
23
|
void stream_polygons(Tile* tile, bool flush = false);
|
|
@@ -36,8 +34,7 @@ class StreamingMerger : public VerticalMerger {
|
|
|
36
34
|
public:
|
|
37
35
|
StreamingMerger(int number_of_threads,
|
|
38
36
|
std::vector<std::string>* options,
|
|
39
|
-
std::ofstream* stream_to
|
|
40
|
-
int total_width, int total_height);
|
|
37
|
+
std::ofstream* stream_to);
|
|
41
38
|
void add_tile(ProcessResult& result, bool flush = false);
|
|
42
39
|
ProcessResult* process_info() override;
|
|
43
40
|
};
|
data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/SvgStreamingMerger.h
CHANGED
|
@@ -9,10 +9,13 @@
|
|
|
9
9
|
|
|
10
10
|
#pragma once
|
|
11
11
|
#include <string>
|
|
12
|
+
#include <vector>
|
|
12
13
|
#include "StreamingMerger.h"
|
|
13
14
|
|
|
14
15
|
class SvgStreamingMerger : public StreamingMerger {
|
|
15
16
|
private:
|
|
17
|
+
int total_width;
|
|
18
|
+
int total_height;
|
|
16
19
|
std::string svg_css() {
|
|
17
20
|
return ".out{fill:none;stroke:red;stroke-width:1;}.in{fill:none;stroke:green;stroke-width:1;}.out:hover{stroke:yellow;}";
|
|
18
21
|
}
|
|
@@ -39,5 +42,15 @@ class SvgStreamingMerger : public StreamingMerger {
|
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
public:
|
|
42
|
-
|
|
45
|
+
SvgStreamingMerger(int number_of_threads,
|
|
46
|
+
std::vector<std::string>* options,
|
|
47
|
+
std::ofstream* stream_to,
|
|
48
|
+
int total_width, int total_height)
|
|
49
|
+
: StreamingMerger(number_of_threads, options, stream_to),
|
|
50
|
+
total_width(total_width),
|
|
51
|
+
total_height(total_height) {
|
|
52
|
+
if (total_width <= 0 || total_height <= 0) {
|
|
53
|
+
throw std::invalid_argument("SVG Streaming requires valid canvas dimensions (width and height must be > 0).");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
43
56
|
};
|
|
@@ -94,7 +94,7 @@ void Tile::assign_shapes(std::vector<Shape*>& shapes) {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
bool Tile::tg_border(const Point& coord) {
|
|
97
|
-
return( coord.x == (this->next == nullptr ? start_x_ : (end_x_
|
|
97
|
+
return( coord.x == (this->next == nullptr ? start_x_ : (end_x_)));
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
void Tile::info() {
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* DouglasPeuckerReducer.cpp
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2025-2026 Emanuele Cesaroni
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the GNU Affero General Public License v3 (AGPLv3).
|
|
7
|
+
* See the LICENSE file in this directory for the full license text.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#include "DouglasPeuckerReducer.h"
|
|
11
|
+
#include <cstdint>
|
|
12
|
+
#include <limits>
|
|
13
|
+
#include <vector>
|
|
14
|
+
|
|
15
|
+
DouglasPeuckerReducer::DouglasPeuckerReducer(std::vector<Point>& list_of_points)
|
|
16
|
+
: Reducer(list_of_points) {
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
void DouglasPeuckerReducer::reduce() {
|
|
20
|
+
const std::size_t count = points.size();
|
|
21
|
+
if (count < 4) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const std::size_t first_index = extremeIndexByX();
|
|
25
|
+
const std::size_t second_index = farthestIndexFrom(first_index);
|
|
26
|
+
if (second_index == first_index) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
std::vector<bool> keep(count, false);
|
|
30
|
+
keep[first_index] = true;
|
|
31
|
+
keep[second_index] = true;
|
|
32
|
+
simplifyArc(first_index, second_index, keep);
|
|
33
|
+
simplifyArc(second_index, first_index, keep);
|
|
34
|
+
compactRing(keep);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
std::size_t DouglasPeuckerReducer::extremeIndexByX() const {
|
|
38
|
+
std::size_t best = 0;
|
|
39
|
+
int best_x = points[0].x;
|
|
40
|
+
for (std::size_t index = 1; index < points.size(); ++index) {
|
|
41
|
+
const int x = points[index].x;
|
|
42
|
+
if (x < best_x) {
|
|
43
|
+
best_x = x;
|
|
44
|
+
best = index;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return best;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
std::size_t DouglasPeuckerReducer::farthestIndexFrom(std::size_t origin_index) const {
|
|
51
|
+
const Point& origin = points[origin_index];
|
|
52
|
+
std::size_t best_index = origin_index;
|
|
53
|
+
std::int64_t best_distance = -1;
|
|
54
|
+
for (std::size_t index = 0; index < points.size(); ++index) {
|
|
55
|
+
const std::int64_t dx = static_cast<std::int64_t>(points[index].x) - origin.x;
|
|
56
|
+
const std::int64_t dy = static_cast<std::int64_t>(points[index].y) - origin.y;
|
|
57
|
+
const std::int64_t distance = dx * dx + dy * dy;
|
|
58
|
+
if (distance > best_distance) {
|
|
59
|
+
best_distance = distance;
|
|
60
|
+
best_index = index;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return best_index;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
void DouglasPeuckerReducer::simplifyArc(std::size_t first_index, std::size_t last_index, std::vector<bool>& keep) const {
|
|
67
|
+
const std::size_t count = points.size();
|
|
68
|
+
std::vector<Arc> stack;
|
|
69
|
+
stack.push_back({first_index, last_index});
|
|
70
|
+
while (!stack.empty()) {
|
|
71
|
+
const Arc arc = stack.back();
|
|
72
|
+
stack.pop_back();
|
|
73
|
+
std::size_t split_index = 0;
|
|
74
|
+
if (!farthestOnArc(
|
|
75
|
+
arc.first,
|
|
76
|
+
arc.last,
|
|
77
|
+
count,
|
|
78
|
+
split_index)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
keep[split_index] = true;
|
|
82
|
+
if (nextIndex(arc.first, count) != split_index) {
|
|
83
|
+
stack.push_back({arc.first, split_index});
|
|
84
|
+
}
|
|
85
|
+
if (nextIndex(split_index, count) != arc.last) {
|
|
86
|
+
stack.push_back({split_index, arc.last});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
bool DouglasPeuckerReducer::farthestOnArc(std::size_t first_index, std::size_t last_index,
|
|
92
|
+
std::size_t count, std::size_t& split_index) const {
|
|
93
|
+
const Point& first = points[first_index];
|
|
94
|
+
const Point& last = points[last_index];
|
|
95
|
+
const double ax = static_cast<double>(first.x);
|
|
96
|
+
const double ay = static_cast<double>(first.y);
|
|
97
|
+
const double bx = static_cast<double>(last.x);
|
|
98
|
+
const double by = static_cast<double>(last.y);
|
|
99
|
+
const double abx = bx - ax;
|
|
100
|
+
const double aby = by - ay;
|
|
101
|
+
const double length_squared = abx * abx + aby * aby;
|
|
102
|
+
double max_distance = TOLERANCE_SQUARED;
|
|
103
|
+
bool found = false;
|
|
104
|
+
std::size_t index = nextIndex(first_index, count);
|
|
105
|
+
|
|
106
|
+
while (index != last_index) {
|
|
107
|
+
const Point& point = points[index];
|
|
108
|
+
double distance;
|
|
109
|
+
if (length_squared == 0.0) {
|
|
110
|
+
const double dx = static_cast<double>(point.x) - ax;
|
|
111
|
+
const double dy = static_cast<double>(point.y) - ay;
|
|
112
|
+
distance = dx * dx + dy * dy;
|
|
113
|
+
} else {
|
|
114
|
+
distance = pointSegmentDistanceSquared(
|
|
115
|
+
static_cast<double>(point.x),
|
|
116
|
+
static_cast<double>(point.y),
|
|
117
|
+
ax,
|
|
118
|
+
ay,
|
|
119
|
+
bx,
|
|
120
|
+
by,
|
|
121
|
+
abx,
|
|
122
|
+
aby,
|
|
123
|
+
length_squared);
|
|
124
|
+
}
|
|
125
|
+
if (distance > max_distance) {
|
|
126
|
+
max_distance = distance;
|
|
127
|
+
split_index = index;
|
|
128
|
+
found = true;
|
|
129
|
+
}
|
|
130
|
+
index = nextIndex(index, count);
|
|
131
|
+
}
|
|
132
|
+
return found;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
double DouglasPeuckerReducer::pointSegmentDistanceSquared(
|
|
136
|
+
double px,
|
|
137
|
+
double py,
|
|
138
|
+
double ax,
|
|
139
|
+
double ay,
|
|
140
|
+
double bx,
|
|
141
|
+
double by,
|
|
142
|
+
double abx,
|
|
143
|
+
double aby,
|
|
144
|
+
double length_squared) {
|
|
145
|
+
const double apx = px - ax;
|
|
146
|
+
const double apy = py - ay;
|
|
147
|
+
const double dot = apx * abx + apy * aby;
|
|
148
|
+
if (dot <= 0.0) {
|
|
149
|
+
return apx * apx + apy * apy;
|
|
150
|
+
}
|
|
151
|
+
if (dot >= length_squared) {
|
|
152
|
+
const double bpx = px - bx;
|
|
153
|
+
const double bpy = py - by;
|
|
154
|
+
return bpx * bpx + bpy * bpy;
|
|
155
|
+
}
|
|
156
|
+
const double cross = apx * aby - apy * abx;
|
|
157
|
+
return (cross * cross) / length_squared;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
std::size_t DouglasPeuckerReducer::nextIndex(std::size_t index, std::size_t count) {
|
|
161
|
+
return index + 1 == count ? 0 : index + 1;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
void DouglasPeuckerReducer::compactRing(const std::vector<bool>& keep) {
|
|
165
|
+
std::size_t write_index = 0;
|
|
166
|
+
for (
|
|
167
|
+
std::size_t read_index = 0;
|
|
168
|
+
read_index < points.size();
|
|
169
|
+
++read_index
|
|
170
|
+
) {
|
|
171
|
+
if (keep[read_index]) {
|
|
172
|
+
points[write_index] = points[read_index];
|
|
173
|
+
++write_index;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
points.resize(write_index);
|
|
177
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* DouglasPeuckerReducer.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 <cstddef>
|
|
12
|
+
#include <vector>
|
|
13
|
+
#include "Reducer.h"
|
|
14
|
+
|
|
15
|
+
struct Point;
|
|
16
|
+
|
|
17
|
+
class DouglasPeuckerReducer : public Reducer {
|
|
18
|
+
public:
|
|
19
|
+
explicit DouglasPeuckerReducer(std::vector<Point>& list_of_points);
|
|
20
|
+
void reduce() override;
|
|
21
|
+
|
|
22
|
+
private:
|
|
23
|
+
static constexpr double TOLERANCE_SQUARED = 1.0;
|
|
24
|
+
struct Arc {
|
|
25
|
+
std::size_t first;
|
|
26
|
+
std::size_t last;
|
|
27
|
+
};
|
|
28
|
+
std::size_t extremeIndexByX() const;
|
|
29
|
+
std::size_t farthestIndexFrom(
|
|
30
|
+
std::size_t origin_index) const;
|
|
31
|
+
void simplifyArc(
|
|
32
|
+
std::size_t first_index,
|
|
33
|
+
std::size_t last_index,
|
|
34
|
+
std::vector<bool>& keep) const;
|
|
35
|
+
bool farthestOnArc(
|
|
36
|
+
std::size_t first_index,
|
|
37
|
+
std::size_t last_index,
|
|
38
|
+
std::size_t count,
|
|
39
|
+
std::size_t& split_index) const;
|
|
40
|
+
static double pointSegmentDistanceSquared(
|
|
41
|
+
double px,
|
|
42
|
+
double py,
|
|
43
|
+
double ax,
|
|
44
|
+
double ay,
|
|
45
|
+
double bx,
|
|
46
|
+
double by,
|
|
47
|
+
double abx,
|
|
48
|
+
double aby,
|
|
49
|
+
double length_squared);
|
|
50
|
+
static std::size_t nextIndex(std::size_t index, std::size_t count);
|
|
51
|
+
void compactRing(const std::vector<bool>& keep);
|
|
52
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* RasterReducer.cpp
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2025-2026 Emanuele Cesaroni
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the GNU Affero General Public License v3 (AGPLv3).
|
|
7
|
+
* See the LICENSE file in this directory for the full license text.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#include "RasterReducer.h"
|
|
11
|
+
#include <stdexcept>
|
|
12
|
+
#include <string>
|
|
13
|
+
|
|
14
|
+
RasterReducer::RasterReducer(std::vector<Point>& points, int versus)
|
|
15
|
+
: Reducer(points),
|
|
16
|
+
versus_(versus) {
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
void RasterReducer::reduce() {
|
|
20
|
+
toRasterPolygon(points, versus_);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
void RasterReducer::toRasterPolygon(std::vector<Point>& points, int versus) {
|
|
24
|
+
const std::size_t n = points.size();
|
|
25
|
+
|
|
26
|
+
if (n == 0) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const bool outer = versus == Node::O;
|
|
31
|
+
const int firstX = points.front().x;
|
|
32
|
+
const int firstY = points.front().y;
|
|
33
|
+
int previousX = points.back().x;
|
|
34
|
+
int previousY = points.back().y;
|
|
35
|
+
|
|
36
|
+
for (std::size_t i = 0; i < n; ++i) {
|
|
37
|
+
const int currentX = points[i].x;
|
|
38
|
+
const int currentY = points[i].y;
|
|
39
|
+
int nextX;
|
|
40
|
+
int nextY;
|
|
41
|
+
if (i + 1 < n) {
|
|
42
|
+
nextX = points[i + 1].x;
|
|
43
|
+
nextY = points[i + 1].y;
|
|
44
|
+
} else {
|
|
45
|
+
nextX = firstX;
|
|
46
|
+
nextY = firstY;
|
|
47
|
+
}
|
|
48
|
+
const Pole incomingSide = segmentPole(
|
|
49
|
+
currentX - previousX,
|
|
50
|
+
currentY - previousY,
|
|
51
|
+
outer);
|
|
52
|
+
const Pole outgoingSide = segmentPole(
|
|
53
|
+
nextX - currentX,
|
|
54
|
+
nextY - currentY,
|
|
55
|
+
outer);
|
|
56
|
+
int x = currentX;
|
|
57
|
+
int y = currentY;
|
|
58
|
+
const auto applySide = [&x, &y](Pole side) {
|
|
59
|
+
switch (side) {
|
|
60
|
+
case Pole::South:
|
|
61
|
+
--y;
|
|
62
|
+
break;
|
|
63
|
+
|
|
64
|
+
case Pole::East:
|
|
65
|
+
--x;
|
|
66
|
+
break;
|
|
67
|
+
|
|
68
|
+
case Pole::North:
|
|
69
|
+
case Pole::West:
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
applySide(incomingSide);
|
|
74
|
+
if (outgoingSide != incomingSide) {
|
|
75
|
+
applySide(outgoingSide);
|
|
76
|
+
}
|
|
77
|
+
points[i].x = x;
|
|
78
|
+
points[i].y = y;
|
|
79
|
+
previousX = currentX;
|
|
80
|
+
previousY = currentY;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
RasterReducer::Pole RasterReducer::segmentPole(int dx, int dy, bool clockwise) {
|
|
85
|
+
const int sx = sign(dx);
|
|
86
|
+
const int sy = sign(dy);
|
|
87
|
+
if (sx == 0 && sy == 1) {
|
|
88
|
+
return clockwise ? Pole::East : Pole::West;
|
|
89
|
+
}
|
|
90
|
+
if (sx == 1 && sy == 0) {
|
|
91
|
+
return clockwise ? Pole::North : Pole::South;
|
|
92
|
+
}
|
|
93
|
+
if (sx == 0 && sy == -1) {
|
|
94
|
+
return clockwise ? Pole::West : Pole::East;
|
|
95
|
+
}
|
|
96
|
+
if (sx == -1 && sy == 0) {
|
|
97
|
+
return clockwise ? Pole::South : Pole::North;
|
|
98
|
+
}
|
|
99
|
+
throw std::invalid_argument(
|
|
100
|
+
"Non-axial segment dx=" +
|
|
101
|
+
std::to_string(dx) +
|
|
102
|
+
" dy=" +
|
|
103
|
+
std::to_string(dy) +
|
|
104
|
+
"!");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
int RasterReducer::sign(int value) {
|
|
108
|
+
return (value > 0) - (value < 0);
|
|
109
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* RasterReducer.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 "Reducer.h"
|
|
13
|
+
|
|
14
|
+
struct Point;
|
|
15
|
+
|
|
16
|
+
class RasterReducer : public Reducer {
|
|
17
|
+
public:
|
|
18
|
+
RasterReducer(std::vector<Point>& points, int versus);
|
|
19
|
+
void reduce() override;
|
|
20
|
+
|
|
21
|
+
private:
|
|
22
|
+
enum class Pole {
|
|
23
|
+
North,
|
|
24
|
+
South,
|
|
25
|
+
East,
|
|
26
|
+
West
|
|
27
|
+
};
|
|
28
|
+
int versus_;
|
|
29
|
+
void toRasterPolygon(std::vector<Point>& points, int versus);
|
|
30
|
+
static Pole segmentPole(int dx, int dy, bool clockwise);
|
|
31
|
+
static int sign(int value);
|
|
32
|
+
};
|
|
@@ -25,7 +25,12 @@ class VisvalingamReducer : public Reducer {
|
|
|
25
25
|
class Triangle {
|
|
26
26
|
public:
|
|
27
27
|
static int area(const Point& a, const Point& b, const Point& c) {
|
|
28
|
-
|
|
28
|
+
const int cross = (c.x - a.x) * (b.y - a.y) - (b.x - a.x) * (c.y - a.y);
|
|
29
|
+
int half = cross / 2;
|
|
30
|
+
if (cross < 0 && cross % 2 != 0) {
|
|
31
|
+
--half;
|
|
32
|
+
}
|
|
33
|
+
return std::abs(half);
|
|
29
34
|
}
|
|
30
35
|
};
|
|
31
36
|
|
|
@@ -52,6 +52,10 @@
|
|
|
52
52
|
#include "PolygonFinder/src/polygon/reducers/LinearReducer.h"
|
|
53
53
|
#include "PolygonFinder/src/polygon/reducers/VisvalingamReducer.cpp"
|
|
54
54
|
#include "PolygonFinder/src/polygon/reducers/VisvalingamReducer.h"
|
|
55
|
+
#include "PolygonFinder/src/polygon/reducers/RasterReducer.cpp"
|
|
56
|
+
#include "PolygonFinder/src/polygon/reducers/RasterReducer.h"
|
|
57
|
+
#include "PolygonFinder/src/polygon/reducers/DouglasPeuckerReducer.cpp"
|
|
58
|
+
#include "PolygonFinder/src/polygon/reducers/DouglasPeuckerReducer.h"
|
|
55
59
|
#include "PolygonFinder/src/polygon/finder/concurrent/Finder.h"
|
|
56
60
|
#include "PolygonFinder/src/polygon/finder/concurrent/Finder.cpp"
|
|
57
61
|
#include "PolygonFinder/src/polygon/finder/concurrent/Poolable.h"
|
|
@@ -97,6 +101,7 @@
|
|
|
97
101
|
#include "PolygonFinder/src/polygon/finder/concurrent/StreamingMerger.h"
|
|
98
102
|
#include "PolygonFinder/src/polygon/finder/concurrent/StreamingMerger.cpp"
|
|
99
103
|
#include "PolygonFinder/src/polygon/finder/concurrent/SvgStreamingMerger.h"
|
|
104
|
+
#include "PolygonFinder/src/polygon/finder/concurrent/GeoJsonStreamingMerger.h"
|
|
100
105
|
#include "PolygonFinder/src/polygon/finder/concurrent/ShapePool.h"
|
|
101
106
|
#include "PolygonFinder/src/polygon/finder/concurrent/ShapePool.cpp"
|
|
102
107
|
extern "C" {
|
|
@@ -309,7 +314,7 @@ struct OfstreamWrapper {
|
|
|
309
314
|
std::ofstream& get_stream() { return stream; }
|
|
310
315
|
};
|
|
311
316
|
|
|
312
|
-
|
|
317
|
+
SvgStreamingMerger* create_svg_streaming_merger(Object self,
|
|
313
318
|
int number_of_threads,
|
|
314
319
|
std::vector<std::string>* options,
|
|
315
320
|
Object stream_obj,
|
|
@@ -319,6 +324,15 @@ StreamingMerger* create_streaming_merger(Object self,
|
|
|
319
324
|
return new SvgStreamingMerger(number_of_threads, options, &wrapper->get_stream(), total_width, total_height);
|
|
320
325
|
}
|
|
321
326
|
|
|
327
|
+
GeoJsonStreamingMerger* create_geo_json_streaming_merger(Object self,
|
|
328
|
+
int number_of_threads,
|
|
329
|
+
std::vector<std::string>* options,
|
|
330
|
+
Object stream_obj,
|
|
331
|
+
int pixel_val) {
|
|
332
|
+
OfstreamWrapper* wrapper = Rice::detail::From_Ruby<OfstreamWrapper*>().convert(stream_obj.value());
|
|
333
|
+
return new GeoJsonStreamingMerger(number_of_threads, options, &wrapper->get_stream(),pixel_val);
|
|
334
|
+
}
|
|
335
|
+
|
|
322
336
|
OfstreamWrapper* create_ofstream(Object self, std::string path) {
|
|
323
337
|
return new OfstreamWrapper(path);
|
|
324
338
|
}
|
|
@@ -437,7 +451,11 @@ void Init_cpp_polygon_finder() {
|
|
|
437
451
|
ProcessResult pr = Rice::detail::ruby_result_to_process_result(rb_result);
|
|
438
452
|
self.add_tile(pr, flush);
|
|
439
453
|
});
|
|
440
|
-
|
|
454
|
+
|
|
455
|
+
Data_Type<SvgStreamingMerger> rb_csvgstreaming_merger = define_class<SvgStreamingMerger, StreamingMerger>("CPPSvgStreamingMerger");
|
|
456
|
+
rb_csvgstreaming_merger.define_singleton_method("new", &create_svg_streaming_merger);
|
|
457
|
+
Data_Type<GeoJsonStreamingMerger> rb_cgeojsonstreaming_merger = define_class<GeoJsonStreamingMerger, StreamingMerger>("CPPGeoJsonStreamingMerger");
|
|
458
|
+
rb_cgeojsonstreaming_merger.define_singleton_method("new", &create_geo_json_streaming_merger);
|
|
441
459
|
|
|
442
460
|
Module mContrek = define_module("Contrek");
|
|
443
461
|
Module mCpp = define_module_under(mContrek, "Cpp");
|
|
@@ -21,23 +21,40 @@ module Contrek
|
|
|
21
21
|
colors.sort_by { |color, count| -color }
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def self.direct_draw_polygons(polygons, png_image)
|
|
24
|
+
def self.direct_draw_polygons(polygons, png_image, zoom: 1)
|
|
25
25
|
polygons.compact.each do |poly|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
if (bounds = poly[:bounds])
|
|
27
|
+
b_factor = 3.to_f / zoom
|
|
28
|
+
bounds_polygon = [
|
|
29
|
+
{x: bounds[:min_x] - b_factor, y: bounds[:min_y] - b_factor},
|
|
30
|
+
{x: bounds[:max_x] + b_factor, y: bounds[:min_y] - b_factor},
|
|
31
|
+
{x: (bounds[:max_x] + b_factor), y: bounds[:max_y] + b_factor},
|
|
32
|
+
{x: (bounds[:min_x] - b_factor), y: bounds[:max_y] + b_factor}
|
|
33
|
+
]
|
|
34
|
+
draw_polygon(bounds_polygon, png_image, ChunkyPNG::Color("blue @ 1.0"), zoom)
|
|
29
35
|
end
|
|
30
|
-
|
|
36
|
+
color = ChunkyPNG::Color("red @ 1.0")
|
|
37
|
+
draw_polygon(poly[:outer], png_image, color, zoom)
|
|
31
38
|
color = ChunkyPNG::Color("green @ 1.0")
|
|
32
39
|
poly[:inner].each do |sequence|
|
|
33
|
-
|
|
34
|
-
sequence.each_cons(2) do |coords|
|
|
35
|
-
png_image.draw_line(coords[0][:x], coords[0][:y], coords[1][:x], coords[1][:y], color)
|
|
36
|
-
end
|
|
37
|
-
png_image.draw_line(sequence[0][:x], sequence[0][:y], sequence[-1][:x], sequence[-1][:y], color)
|
|
40
|
+
draw_polygon(sequence, png_image, color, zoom)
|
|
38
41
|
end
|
|
39
42
|
end
|
|
40
43
|
end
|
|
44
|
+
|
|
45
|
+
def self.draw_polygon(sequence, png_image, color, zoom, margin = 0)
|
|
46
|
+
return if sequence.empty?
|
|
47
|
+
sequence.each_cons(2) do |coords|
|
|
48
|
+
png_image.draw_line(coords[0][:x] * zoom,
|
|
49
|
+
coords[0][:y] * zoom,
|
|
50
|
+
coords[1][:x] * zoom,
|
|
51
|
+
coords[1][:y] * zoom, color)
|
|
52
|
+
end
|
|
53
|
+
png_image.draw_line(sequence[0][:x] * zoom,
|
|
54
|
+
sequence[0][:y] * zoom,
|
|
55
|
+
sequence[-1][:x] * zoom,
|
|
56
|
+
sequence[-1][:y] * zoom, color)
|
|
57
|
+
end
|
|
41
58
|
end
|
|
42
59
|
end
|
|
43
60
|
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Contrek
|
|
4
|
+
module Bitmaps
|
|
5
|
+
module Rendering
|
|
6
|
+
def self.included(base)
|
|
7
|
+
base.extend(ClassMethods)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module ClassMethods
|
|
11
|
+
def to_window(result, zoom: 100, coords: true, window_w: 2000, window_h: 1200, font_h: 20)
|
|
12
|
+
ubuntu_purple = ChunkyPNG::Color.from_hex("#77216F")
|
|
13
|
+
bm = RawBitmap.new(w: window_w, h: window_h, color: ubuntu_purple)
|
|
14
|
+
# grid
|
|
15
|
+
light_purple = ChunkyPNG::Color.from_hex("#95338B")
|
|
16
|
+
(window_w / zoom).times { |x| bm.draw_line(x * zoom, 0, x * zoom, window_h, light_purple) }
|
|
17
|
+
(window_h / zoom).times { |y| bm.draw_line(0, y * zoom, window_w, y * zoom, light_purple) }
|
|
18
|
+
# polygons
|
|
19
|
+
Painting.direct_draw_polygons(result.points, bm, zoom: zoom)
|
|
20
|
+
# windowing
|
|
21
|
+
Tempfile.create do |temp_file|
|
|
22
|
+
bm.image.save(temp_file.path, color_mode: ChunkyPNG::COLOR_TRUECOLOR_ALPHA)
|
|
23
|
+
Window.clear
|
|
24
|
+
Window.set(
|
|
25
|
+
title: "Rendering #{result[:metadata][:groups]} polygons",
|
|
26
|
+
width: window_w,
|
|
27
|
+
height: window_h,
|
|
28
|
+
resizable: true
|
|
29
|
+
)
|
|
30
|
+
Image.new(
|
|
31
|
+
temp_file.path,
|
|
32
|
+
x: 0, y: 0,
|
|
33
|
+
width: window_w,
|
|
34
|
+
height: window_h
|
|
35
|
+
)
|
|
36
|
+
draw_labels(result.points, zoom: zoom, draw_coords: coords, font_h:)
|
|
37
|
+
Window.on :key_down do |event|
|
|
38
|
+
if event.key == "escape"
|
|
39
|
+
Window.close
|
|
40
|
+
end
|
|
41
|
+
if event.key == "s"
|
|
42
|
+
Window.screenshot("window_content.png")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
Window.show
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def draw_labels(polygons, font_h:, zoom: 1.0, draw_coords: true)
|
|
52
|
+
positions = []
|
|
53
|
+
polygons.compact.each do |poly|
|
|
54
|
+
([poly[:outer]] + poly[:inner]).each do |seq|
|
|
55
|
+
seq.each_with_index do |coords, n|
|
|
56
|
+
x = coords[:x] * zoom
|
|
57
|
+
y = coords[:y] * zoom
|
|
58
|
+
y += font_h + 2 if positions.index([x, y])
|
|
59
|
+
t = (n + 1).to_s
|
|
60
|
+
t += " - [#{coords[:x]},#{coords[:y]}]" if draw_coords
|
|
61
|
+
draw_text(x, y, t, font_h) unless draw_coords.nil?
|
|
62
|
+
positions << [x, y]
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def draw_text(x, y, text, font_h)
|
|
69
|
+
Text.new(text, x:, y:, size: font_h, color: "white", z: 10)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|