Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
geom_algorithms_test_simplification.cc
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
32#include <cmath>
33
34using namespace Aleph;
35
36// Helper: check that result is a subsequence of original
38 const Array<Point> & sub)
39{
40 size_t j = 0;
41 for (size_t i = 0; i < original.size() and j < sub.size(); ++i)
42 if (original(i) == sub(j))
43 ++j;
44 return j == sub.size();
45}
46
47// Helper: extract polygon vertices into Array
48static Array<Point> poly_verts(const Polygon & poly)
49{
51}
52
53// Helper: make a circle approximation polygon
54static Polygon make_circle(size_t n, double radius = 10.0,
55 double cx = 0.0, double cy = 0.0)
56{
57 Polygon poly;
58 for (size_t i = 0; i < n; ++i)
59 {
60 double angle = 2.0 * M_PI * i / n;
61 poly.add_vertex(Point(cx + radius * cos(angle),
62 cy + radius * sin(angle)));
63 }
64 poly.close();
65 return poly;
66}
67
68// ===================== Douglas-Peucker tests =====================
69
71{
72 // Collinear points → only endpoints kept
73 Array<Point> line;
74 line.append(Point(0, 0));
75 line.append(Point(1, 0));
76 line.append(Point(2, 0));
77 line.append(Point(3, 0));
78 line.append(Point(4, 0));
79
81 auto result = dp(line, Geom_Number(1, 10)); // epsilon = 0.1
82
83 EXPECT_EQ(result.size(), 2u);
84 EXPECT_EQ(result(0), Point(0, 0));
85 EXPECT_EQ(result(1), Point(4, 0));
86}
87
89{
90 // L-shape: all 3 points must be kept
92 pts.append(Point(0, 0));
93 pts.append(Point(5, 0));
94 pts.append(Point(5, 5));
95
97 auto result = dp(pts, Geom_Number(1, 10));
98
99 EXPECT_EQ(result.size(), 3u);
100 EXPECT_EQ(result(0), Point(0, 0));
101 EXPECT_EQ(result(1), Point(5, 0));
102 EXPECT_EQ(result(2), Point(5, 5));
103}
104
106{
108 pts.append(Point(0, 0));
109 pts.append(Point(1, 2));
110 pts.append(Point(2, 0));
111 pts.append(Point(3, 2));
112 pts.append(Point(4, 0));
113
115 // Small epsilon: keep all
116 auto result = dp(pts, Geom_Number(1, 100));
117 EXPECT_EQ(result.size(), 5u);
118
119 // Large epsilon: only endpoints
120 auto result2 = dp(pts, Geom_Number(10));
121 EXPECT_EQ(result2.size(), 2u);
122}
123
125{
127 rect.add_vertex(Point(0, 0));
128 rect.add_vertex(Point(10, 0));
129 rect.add_vertex(Point(10, 5));
130 rect.add_vertex(Point(0, 5));
131 rect.close();
132
134 Polygon result = dp.simplify_polygon(rect, Geom_Number(1, 10));
135
136 auto rv = poly_verts(result);
137 EXPECT_EQ(rv.size(), 4u); // Rectangle should be preserved
138}
139
141{
143 pts.append(Point(0, 0));
144 pts.append(Point(1, 1));
145 pts.append(Point(2, 0));
146 pts.append(Point(3, 1));
147 pts.append(Point(4, 0));
148
150 auto result = dp(pts, Geom_Number(0));
151 EXPECT_EQ(result.size(), pts.size());
152}
153
155{
157 pts.append(Point(0, 0));
158 pts.append(Point(1, 1));
159 pts.append(Point(2, 0));
160 pts.append(Point(3, 1));
161 pts.append(Point(4, 0));
162
164 auto result = dp(pts, Geom_Number(100));
165 EXPECT_EQ(result.size(), 2u); // Only endpoints
166}
167
169{
171 pts.append(Point(0, 0));
172 pts.append(Point(5, 5));
173
175 auto result = dp(pts, Geom_Number(1));
176 EXPECT_EQ(result.size(), 2u);
177 EXPECT_EQ(result(0), Point(0, 0));
178 EXPECT_EQ(result(1), Point(5, 5));
179}
180
182{
183 Polygon circle = make_circle(40);
184
186 Polygon mild = dp.simplify_polygon(circle, Geom_Number(1, 10));
188
189 auto mv = poly_verts(mild);
190 auto av = poly_verts(aggressive);
191
192 EXPECT_LE(av.size(), mv.size());
193 EXPECT_LE(mv.size(), 40u);
194 EXPECT_GE(av.size(), 3u);
195}
196
197// ===================== Visvalingam-Whyatt tests =====================
198
200{
201 // Nearly collinear interior point → removed
203 pts.append(Point(0, 0));
204 pts.append(Point(5, Geom_Number(1, 100))); // tiny deviation
205 pts.append(Point(10, 0));
206
208 auto result = vw(pts, Geom_Number(1)); // area threshold = 1
209 EXPECT_EQ(result.size(), 2u);
210}
211
213{
215 pts.append(Point(0, 0));
216 pts.append(Point(1, 0));
217 pts.append(Point(1, 1));
218 pts.append(Point(2, 1));
219 pts.append(Point(2, 2));
220 pts.append(Point(3, 2));
221
223 // Small threshold keeps all
224 auto result = vw(pts, Geom_Number(1, 1000));
225 EXPECT_EQ(result.size(), pts.size());
226
227 // Large threshold: only endpoints
228 auto result2 = vw(pts, Geom_Number(100));
229 EXPECT_EQ(result2.size(), 2u);
230}
231
233{
235 rect.add_vertex(Point(0, 0));
236 rect.add_vertex(Point(10, 0));
237 rect.add_vertex(Point(10, 5));
238 rect.add_vertex(Point(0, 5));
239 rect.close();
240
242 Polygon result = vw.simplify_polygon(rect, Geom_Number(1));
243
244 auto rv = poly_verts(result);
245 EXPECT_EQ(rv.size(), 4u); // Rectangle corners have area=25, should stay
246}
247
249{
251 pts.append(Point(0, 0));
252 pts.append(Point(1, 1));
253 pts.append(Point(2, 0));
254 pts.append(Point(3, 1));
255 pts.append(Point(4, 0));
256
258 auto result = vw(pts, Geom_Number(0));
259 EXPECT_EQ(result.size(), pts.size());
260}
261
263{
265 pts.append(Point(0, 0));
266 pts.append(Point(1, 1));
267 pts.append(Point(2, 0));
268 pts.append(Point(3, 1));
269 pts.append(Point(4, 0));
270
272 auto result = vw(pts, Geom_Number(10000));
273 EXPECT_EQ(result.size(), 2u);
274}
275
277{
279 pts.append(Point(0, 0));
280 pts.append(Point(5, 5));
281
283 auto result = vw(pts, Geom_Number(1));
284 EXPECT_EQ(result.size(), 2u);
285}
286
288{
289 Polygon circle = make_circle(40);
290
292 Polygon mild = vw.simplify_polygon(circle, Geom_Number(1, 10));
293 Polygon aggressive = vw.simplify_polygon(circle, Geom_Number(50));
294
295 auto mv = poly_verts(mild);
296 auto av = poly_verts(aggressive);
297
298 EXPECT_LE(av.size(), mv.size());
299 EXPECT_LE(mv.size(), 40u);
300 EXPECT_GE(av.size(), 3u);
301}
302
303// ===================== Property checks (both) =====================
304
306{
308 pts.append(Point(0, 0));
309 pts.append(Point(1, 2));
310 pts.append(Point(2, -1));
311 pts.append(Point(3, 3));
312 pts.append(Point(4, 0));
313 pts.append(Point(5, 2));
314 pts.append(Point(6, -1));
315 pts.append(Point(7, 0));
316
318 auto result = dp(pts, Geom_Number(1));
319
321 EXPECT_LE(result.size(), pts.size());
322 EXPECT_EQ(result(0), pts(0));
323 EXPECT_EQ(result(result.size() - 1), pts(pts.size() - 1));
324}
325
327{
329 pts.append(Point(0, 0));
330 pts.append(Point(1, 2));
331 pts.append(Point(2, -1));
332 pts.append(Point(3, 3));
333 pts.append(Point(4, 0));
334 pts.append(Point(5, 2));
335 pts.append(Point(6, -1));
336 pts.append(Point(7, 0));
337
339 auto result = vw(pts, Geom_Number(1));
340
342 EXPECT_LE(result.size(), pts.size());
343 EXPECT_EQ(result(0), pts(0));
344 EXPECT_EQ(result(result.size() - 1), pts(pts.size() - 1));
345}
346
348{
349 Polygon circle = make_circle(30);
350 auto orig = poly_verts(circle);
351
353 auto dp_result = dp.simplify_polygon(circle, Geom_Number(1));
354 auto dpv = poly_verts(dp_result);
355 EXPECT_LE(dpv.size(), orig.size());
356
358 auto vw_result = vw.simplify_polygon(circle, Geom_Number(1));
359 auto vwv = poly_verts(vw_result);
360 EXPECT_LE(vwv.size(), orig.size());
361}
362
364{
366 pts.append(Point(0, 0));
367 pts.append(Point(1, 5));
368 pts.append(Point(2, 0));
369 pts.append(Point(3, 5));
370 pts.append(Point(4, 0));
371
373 for (int e = 1; e <= 10; ++e)
374 {
375 auto result = dp(pts, Geom_Number(e));
376 EXPECT_GE(result.size(), 2u);
377 EXPECT_EQ(result(0), pts(0));
378 EXPECT_EQ(result(result.size() - 1), pts(pts.size() - 1));
379 }
380}
381
383{
385 pts.append(Point(0, 0));
386 pts.append(Point(1, 5));
387 pts.append(Point(2, 0));
388 pts.append(Point(3, 5));
389 pts.append(Point(4, 0));
390
392 for (int e = 1; e <= 10; ++e)
393 {
394 auto result = vw(pts, Geom_Number(e));
395 EXPECT_GE(result.size(), 2u);
396 EXPECT_EQ(result(0), pts(0));
397 EXPECT_EQ(result(result.size() - 1), pts(pts.size() - 1));
398 }
399}
400
402{
404 pts.append(Point(0, 0));
405 pts.append(Point(1, 2));
406 pts.append(Point(2, 0));
407 pts.append(Point(3, 2));
408 pts.append(Point(4, 0));
409
411 auto result = dp(pts, Geom_Number(100));
412 EXPECT_EQ(result.size(), 2u);
413}
414
416{
418 pts.append(Point(0, 0));
419 pts.append(Point(1, 2));
420 pts.append(Point(2, 0));
421 pts.append(Point(3, 2));
422 pts.append(Point(4, 0));
423
425 auto result = vw(pts, Geom_Number(100));
426 EXPECT_EQ(result.size(), 2u);
427}
428
429// ===================== Chaikin smoothing tests =====================
430
432{
433 Polygon tri;
434 tri.add_vertex(Point(0, 0));
435 tri.add_vertex(Point(10, 0));
436 tri.add_vertex(Point(5, 10));
437 tri.close();
438
440 auto rv = poly_verts(result);
441 EXPECT_EQ(rv.size(), 6u); // 3 * 2^1 = 6
442}
443
445{
446 Polygon sq;
447 sq.add_vertex(Point(0, 0));
448 sq.add_vertex(Point(4, 0));
449 sq.add_vertex(Point(4, 4));
450 sq.add_vertex(Point(0, 4));
451 sq.close();
452
454 auto rv = poly_verts(result);
455 EXPECT_EQ(rv.size(), 8u); // 4 * 2 = 8
456
457 // First edge (0,0)->(4,0): 1/4 point = (1,0), 3/4 point = (3,0)
458 EXPECT_EQ(rv(0), Point(1, 0));
459 EXPECT_EQ(rv(1), Point(3, 0));
460 // Second edge (4,0)->(4,4): 1/4 point = (4,1), 3/4 point = (4,3)
461 EXPECT_EQ(rv(2), Point(4, 1));
462 EXPECT_EQ(rv(3), Point(4, 3));
463}
464
466{
467 Polygon hex;
468 for (int i = 0; i < 6; ++i)
469 {
470 double angle = 2.0 * M_PI * i / 6;
471 hex.add_vertex(Point(10 * cos(angle), 10 * sin(angle)));
472 }
473 hex.close();
474
475 for (size_t k = 1; k <= 4; ++k)
476 {
478 auto rv = poly_verts(result);
479 size_t expected = 6;
480 for (size_t j = 0; j < k; ++j)
481 expected *= 2;
482 EXPECT_EQ(rv.size(), expected);
483 }
484}
485
487{
489 pts.append(Point(0, 0));
490 pts.append(Point(2, 3));
491 pts.append(Point(4, 1));
492 pts.append(Point(6, 4));
493 pts.append(Point(8, 0));
494
496
497 // 1 iteration: first + last kept, 4 edges * 2 inner = 8, plus 2 = 10
498 auto r1 = cs(pts, 1);
499 EXPECT_EQ(r1.size(), 10u); // (5-1)*2 + 2 = 10
500
501 // Verify first and last are preserved
502 EXPECT_EQ(r1(0), Point(0, 0));
503 EXPECT_EQ(r1(r1.size() - 1), Point(8, 0));
504}
505
507{
508 Polygon tri;
509 tri.add_vertex(Point(0, 0));
510 tri.add_vertex(Point(10, 0));
511 tri.add_vertex(Point(5, 10));
512 tri.close();
513
514 for (size_t k = 1; k <= 5; ++k)
515 {
517 auto rv = poly_verts(result);
518 EXPECT_GE(rv.size(), 3u);
519 }
520}
521
523{
524 // Coarse integer octagon approximation of a circle-like shape.
525 // Integer coordinates avoid trigonometric conversion instability across
526 // toolchains/libstdc++ versions.
527 Polygon oct;
528 oct.add_vertex(Point(10, 0));
529 oct.add_vertex(Point(7, 7));
530 oct.add_vertex(Point(0, 10));
531 oct.add_vertex(Point(-7, 7));
532 oct.add_vertex(Point(-10, 0));
533 oct.add_vertex(Point(-7, -7));
534 oct.add_vertex(Point(0, -10));
535 oct.add_vertex(Point(7, -7));
536 oct.close();
537
538 auto compute_area = [](const Polygon & p)
539 {
541 if (verts.size() < 3)
542 return 0.0;
543
544 double sum = 0.0;
545 for (size_t i = 0; i < verts.size(); ++i)
546 {
547 const auto & a = verts(i);
548 const auto & b = verts((i + 1) % verts.size());
549 sum += geom_number_to_double(a.get_x()) * geom_number_to_double(b.get_y()) -
550 geom_number_to_double(a.get_y()) * geom_number_to_double(b.get_x());
551 }
552 return std::abs(sum) * 0.5;
553 };
554
555 const double oct_area = compute_area(oct);
558 const double smooth1_area = compute_area(smooth1);
559 const double smooth_area = compute_area(smooth4);
560
561 // Chaikin corner cutting on a convex polygon shrinks area monotonically.
565}
566
568{
569 Polygon sq;
570 sq.add_vertex(Point(0, 0));
571 sq.add_vertex(Point(4, 0));
572 sq.add_vertex(Point(4, 4));
573 sq.add_vertex(Point(0, 4));
574 sq.close();
575
576 // ratio = 1/3: cut points at 1/3 and 2/3
578 auto rv = poly_verts(result);
579 EXPECT_EQ(rv.size(), 8u);
580
581 // First edge (0,0)->(4,0): 1/3 point ~ (4/3, 0), 2/3 point ~ (8/3, 0)
582 EXPECT_EQ(rv(0), Point(Geom_Number(4, 3), 0));
583 EXPECT_EQ(rv(1), Point(Geom_Number(8, 3), 0));
584}
585
587{
588 Polygon sq;
589 sq.add_vertex(Point(0, 0));
590 sq.add_vertex(Point(4, 0));
591 sq.add_vertex(Point(4, 4));
592 sq.add_vertex(Point(0, 4));
593 sq.close();
594
595 // iterations = 0 should throw
596 EXPECT_THROW(ChaikinSmoothing::smooth_polygon(sq, 0), std::domain_error);
597
598 // ratio = 0 should throw
600 std::domain_error);
601
602 // ratio = 1/2 should throw (boundary)
604 std::domain_error);
605
606 // negative ratio should throw
608 std::domain_error);
609}
610
612{
614 pts.append(Point(0, 0));
615 pts.append(Point(2, 3));
616 pts.append(Point(4, 1));
617 pts.append(Point(6, 0));
618
620 auto result = cs(pts, 1);
621
622 // (4-1)*2 + 2 = 8
623 EXPECT_EQ(result.size(), 8u);
624 EXPECT_EQ(result(0), Point(0, 0));
625 EXPECT_EQ(result(result.size() - 1), Point(6, 0));
626}
627
629{
631 pts.append(Point(0, 0));
632 pts.append(Point(10, 0));
633
635 auto result = cs(pts, 1);
636
637 // 1 edge * 2 inner + 2 endpoints = 4
638 EXPECT_EQ(result.size(), 4u);
639 EXPECT_EQ(result(0), Point(0, 0));
640 EXPECT_EQ(result(1), Point(Geom_Number(5, 2), 0)); // 2.5
641 EXPECT_EQ(result(2), Point(Geom_Number(15, 2), 0)); // 7.5
642 EXPECT_EQ(result(3), Point(10, 0));
643}
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:139
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:245
Chaikin corner-cutting subdivision for polygon smoothing.
static Polygon smooth_polygon(const Polygon &poly, size_t iterations, const Geom_Number &ratio=Geom_Number(1, 4))
Smooth a closed polygon.
Douglas-Peucker polyline simplification.
Polygon simplify_polygon(const Polygon &poly, const Geom_Number &epsilon) const
Simplify a closed polygon.
Dynamic singly linked list with functional programming support.
Definition htlist.H:1155
T & append(const T &item)
Definition htlist.H:1271
static Array< Point > extract_vertices(const Polygon &poly)
Extract vertices from a polygon into an array for indexed access.
Represents a point with rectangular coordinates in a 2D plane.
Definition point.H:229
A general (irregular) 2D polygon defined by a sequence of vertices.
Definition polygon.H:246
void add_vertex(const Point &point)
Add a vertex to the polygon.
Definition polygon.H:677
void close()
Close the polygon.
Definition polygon.H:840
Visvalingam-Whyatt polyline simplification.
static Polygon simplify_polygon(const Polygon &poly, const Geom_Number &area_threshold)
Simplify a closed polygon.
static Polygon make_circle(size_t n, double radius=10.0, double cx=0.0, double cy=0.0)
static bool is_subsequence(const Array< Point > &original, const Array< Point > &sub)
static Array< Point > poly_verts(const Polygon &poly)
TEST_F(GeomAlgorithmsTest, DP_StraightLine)
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_cos_function > > cos(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4069
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_sin_function > > sin(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4070
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
double geom_number_to_double(const Geom_Number &n)
Converts a Geom_Number to its double precision representation.
Definition point.H:122
mpq_class Geom_Number
Numeric type used by the geometry module.
Definition point.H:115
T sum(const Container &container, const T &init=T{})
Compute sum of all elements.
static int * k