PostGIS 3.7.0dev-r@@SVN_REVISION@@
Loading...
Searching...
No Matches
cu_geos_cluster.c
Go to the documentation of this file.
1/**********************************************************************
2 *
3 * PostGIS - Spatial Types for PostgreSQL
4 * http://postgis.net
5 *
6 * Copyright 2015 Daniel Baston
7 *
8 * This is free software; you can redistribute and/or modify it under
9 * the terms of the GNU General Public Licence. See the COPYING file.
10 *
11 **********************************************************************/
12
13#include <stdlib.h>
14
15#include "CUnit/Basic.h"
16
17#include "../lwgeom_log.h"
18#include "../lwgeom_geos.h"
19#include "cu_tester.h"
20
21static void assert_all_results_found(LWGEOM** results, size_t num_outputs, LWGEOM** expected, size_t num_expected_outputs)
22{
23 size_t i, j;
24
25 char found_equal = 0;
26 for (i = 0; i < num_outputs; i++)
27 {
28 for (j = 0; j < num_expected_outputs; j++)
29 {
30 if (lwgeom_same(results[i], expected[j]))
31 {
32 found_equal = 1;
33 break;
34 }
35 }
36
37 CU_ASSERT_TRUE(found_equal);
38 }
39}
40
41static GEOSGeometry** LWGEOMARRAY2GEOS(LWGEOM** lw_array, size_t num_geoms)
42{
43 size_t i;
44 GEOSGeometry** geos_geoms = lwalloc(num_geoms * sizeof(GEOSGeometry*));
45
46 for (i = 0; i < num_geoms; i++)
47 {
48 geos_geoms[i] = LWGEOM2GEOS(lw_array[i], 0);
49 }
50
51 return geos_geoms;
52}
53
54static LWGEOM** GEOSARRAY2LWGEOM(GEOSGeometry** geos_array, size_t num_geoms)
55{
56 size_t i;
57 LWGEOM** lw_geoms = lwalloc(num_geoms * sizeof(LWGEOM*));
58
59 for (i = 0; i < num_geoms; i++)
60 {
61 lw_geoms[i] = GEOS2LWGEOM(geos_array[i], 0);
62 }
63
64 return lw_geoms;
65}
66
67static LWGEOM** WKTARRAY2LWGEOM(char** wkt_array, size_t num_geoms)
68{
69 size_t i;
70
71 LWGEOM** lw_geoms = lwalloc(num_geoms * sizeof(LWGEOM*));
72
73 for (i = 0; i < num_geoms; i++)
74 {
75 lw_geoms[i] = lwgeom_from_wkt(wkt_array[i], LW_PARSER_CHECK_NONE);
76 }
77
78 return lw_geoms;
79}
80
81static void perform_cluster_intersecting_test(char** wkt_inputs, uint32_t num_inputs, char** wkt_outputs, uint32_t num_outputs)
82{
83 GEOSGeometry** geos_results;
84 LWGEOM** lw_results;
85 uint32_t num_clusters;
86
87 LWGEOM** expected_outputs = WKTARRAY2LWGEOM(wkt_outputs, num_outputs);
88 LWGEOM** lw_inputs = WKTARRAY2LWGEOM(wkt_inputs, num_inputs);
89 GEOSGeometry** geos_inputs = LWGEOMARRAY2GEOS(lw_inputs, num_inputs);
90
91 cluster_intersecting(geos_inputs, num_inputs, &geos_results, &num_clusters);
92 CU_ASSERT_EQUAL(num_outputs, num_clusters);
93
94 lw_results = GEOSARRAY2LWGEOM(geos_results, num_clusters);
95
96 assert_all_results_found(lw_results, num_clusters, expected_outputs, num_outputs);
97
98 /* Cleanup */
99 uint32_t i;
100 for(i = 0; i < num_clusters; i++)
101 {
102 GEOSGeom_destroy(geos_results[i]);
103 }
104 lwfree(geos_inputs);
105 lwfree(geos_results);
106
107 for(i = 0; i < num_outputs; i++)
108 {
109 lwgeom_free(expected_outputs[i]);
110 lwgeom_free(lw_results[i]);
111 }
112 lwfree(expected_outputs);
113 lwfree(lw_results);
114
115 for(i = 0; i < num_inputs; i++)
116 {
117 lwgeom_free(lw_inputs[i]);
118 }
119 lwfree(lw_inputs);
120}
121
122static void perform_cluster_within_distance_test(double tolerance, char** wkt_inputs, uint32_t num_inputs, char** wkt_outputs, uint32_t num_outputs)
123{
124 LWGEOM** lw_results;
125 uint32_t num_clusters;
126
127 LWGEOM** expected_outputs = WKTARRAY2LWGEOM(wkt_outputs, num_outputs);
128 LWGEOM** lw_inputs = WKTARRAY2LWGEOM(wkt_inputs, num_inputs);
129
130 cluster_within_distance(lw_inputs, num_inputs, tolerance, &lw_results, &num_clusters);
131
132 CU_ASSERT_EQUAL(num_outputs, num_clusters);
133
134 assert_all_results_found(lw_results, num_clusters, expected_outputs, num_outputs);
135
136 /* Cleanup */
137 uint32_t i;
138 for(i = 0; i < num_outputs; i++)
139 {
140 lwgeom_free(expected_outputs[i]);
141 lwgeom_free(lw_results[i]);
142 }
143 lwfree(lw_results);
144 lwfree(expected_outputs);
145 lwfree(lw_inputs);
146}
147
149{
150 initGEOS(lwnotice, lwgeom_geos_error);
151 return 0;
152}
153
155{
156 finishGEOS();
157 return 0;
158}
159
160static void basic_test(void)
161{
162 char* a = "LINESTRING (0 0, 1 1)";
163 char* b = "LINESTRING (1 1, 2 2)";
164 char* c = "LINESTRING (5 5, 6 6)";
165
166 char* wkt_inputs_a[] = {a, b, c};
167 char* wkt_inputs_b[] = {b, c, a};
168 char* wkt_inputs_c[] = {c, a, b};
169
170 char* expected_outputs_a[] = { "GEOMETRYCOLLECTION(LINESTRING (0 0, 1 1), LINESTRING (1 1, 2 2))",
171 "GEOMETRYCOLLECTION(LINESTRING (5 5, 6 6))"
172 };
173
174 char* expected_outputs_b[] = { "GEOMETRYCOLLECTION(LINESTRING (1 1, 2 2), LINESTRING (0 0, 1 1))",
175 "GEOMETRYCOLLECTION(LINESTRING (5 5, 6 6))"
176 };
177
178 char* expected_outputs_c[] = { "GEOMETRYCOLLECTION(LINESTRING (0 0, 1 1), LINESTRING (1 1, 2 2))",
179 "GEOMETRYCOLLECTION(LINESTRING (5 5, 6 6))"
180 };
181
182 perform_cluster_intersecting_test(wkt_inputs_a, 3, expected_outputs_a, 2);
183 perform_cluster_intersecting_test(wkt_inputs_b, 3, expected_outputs_b, 2);
184 perform_cluster_intersecting_test(wkt_inputs_c, 3, expected_outputs_c, 2);
185
186 perform_cluster_within_distance_test(0, wkt_inputs_a, 3, expected_outputs_a, 2);
187 perform_cluster_within_distance_test(0, wkt_inputs_b, 3, expected_outputs_b, 2);
188 perform_cluster_within_distance_test(0, wkt_inputs_c, 3, expected_outputs_c, 2);
189}
190
191static void basic_distance_test(void)
192{
193 char* a = "LINESTRING (0 0, 1 1)";
194 char* b = "LINESTRING (1 1, 2 2)";
195 char* c = "LINESTRING (5 5, 6 6)";
196
197 char* wkt_inputs[] = {a, b, c};
198
199 char* expected_outputs_all[] = {"GEOMETRYCOLLECTION(LINESTRING(0 0, 1 1), LINESTRING(1 1, 2 2), LINESTRING(5 5, 6 6))"};
200 char* expected_outputs_partial[] = {"GEOMETRYCOLLECTION(LINESTRING(0 0, 1 1), LINESTRING(1 1, 2 2))",
201 "GEOMETRYCOLLECTION(LINESTRING(5 5, 6 6))"
202 };
203
204 perform_cluster_within_distance_test(0, wkt_inputs, 3, expected_outputs_partial, 2);
205 perform_cluster_within_distance_test(sqrt(18) - 0.0000001, wkt_inputs, 3, expected_outputs_partial, 2);
206 perform_cluster_within_distance_test(sqrt(18) + 0.0000001, wkt_inputs, 3, expected_outputs_all, 1);
207}
208
209static void nonsequential_test(void)
210{
211 char* wkt_inputs[] = { "LINESTRING (0 0, 1 1)",
212 "LINESTRING (1 1, 2 2)",
213 "LINESTRING (5 5, 6 6)",
214 "LINESTRING (5 5, 4 4)",
215 "LINESTRING (3 3, 2 2)",
216 "LINESTRING (3 3, 4 4)"
217 };
218
219 char* expected_outputs[] = { "GEOMETRYCOLLECTION (LINESTRING (0 0, 1 1), LINESTRING (1 1, 2 2), LINESTRING (5 5, 6 6), LINESTRING (5 5, 4 4), LINESTRING (3 3, 2 2), LINESTRING (3 3, 4 4))" };
220
221 perform_cluster_intersecting_test(wkt_inputs, 6, expected_outputs, 1);
222 perform_cluster_within_distance_test(0, wkt_inputs, 6, expected_outputs, 1);
223}
224
225static void single_input_test(void)
226{
227 char* wkt_inputs[] = { "POINT (0 0)" };
228 char* expected_outputs[] = { "GEOMETRYCOLLECTION (POINT (0 0))" };
229
230 perform_cluster_intersecting_test(wkt_inputs, 1, expected_outputs, 1);
231 perform_cluster_within_distance_test(1, wkt_inputs, 1, expected_outputs, 1);
232}
233
234static void empty_inputs_test(void)
235{
236 char* wkt_inputs[] = { "POLYGON EMPTY", "LINESTRING EMPTY"};
237 char* expected_outputs[] = { "GEOMETRYCOLLECTION( LINESTRING EMPTY )", "GEOMETRYCOLLECTION( POLYGON EMPTY )" };
238
239 perform_cluster_intersecting_test(wkt_inputs, 2, expected_outputs, 2);
240 perform_cluster_within_distance_test(1, wkt_inputs, 2, expected_outputs, 2);
241}
242
243static void multipoint_test(void)
244{
245 /* See #3433 */
246 char* wkt_inputs_mp[] = { "MULTIPOINT ((0 0), (0 1))", "POINT (0 0)"};
247 char* expected_outputs_mp[] = { "GEOMETRYCOLLECTION(MULTIPOINT ((0 0), (0 1)), POINT (0 0))"};
248
249 char* wkt_inputs_gc[] = { "GEOMETRYCOLLECTION (POINT (0 0), POINT (0 1))", "POINT (0 0)"};
250 char* expected_outputs_gc[] = { "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION (POINT (0 0), POINT (0 1)), POINT (0 0))"};
251
252 char* wkt_inputs_pt[] = { "POINT (3 3)", "POINT (3 3)"};
253 char* expected_outputs_pt[] = { "GEOMETRYCOLLECTION(POINT (3 3), POINT (3 3))"};
254
255 perform_cluster_intersecting_test(wkt_inputs_mp, 2, expected_outputs_mp, 1);
256 perform_cluster_intersecting_test(wkt_inputs_gc, 2, expected_outputs_gc, 1);
257 perform_cluster_intersecting_test(wkt_inputs_pt, 2, expected_outputs_pt, 1);
258}
259
261 double eps;
262 uint32_t min_points;
263 uint32_t num_geoms;
265 uint32_t* expected_ids;
267};
268
269static void do_dbscan_test(struct dbscan_test_info test)
270{
271 LWGEOM** geoms = WKTARRAY2LWGEOM(test.wkt_inputs, test.num_geoms);
272 UNIONFIND* uf = UF_create(test.num_geoms);
273 uint32_t* ids;
274 char* in_a_cluster;
275 uint32_t i;
276
277 union_dbscan(geoms, test.num_geoms, uf, test.eps, test.min_points, &in_a_cluster);
278 ids = UF_get_collapsed_cluster_ids(uf, in_a_cluster);
279
280 for (i = 0; i < test.num_geoms; i++)
281 {
282 ASSERT_INT_EQUAL(in_a_cluster[i], test.expected_in_cluster[i]);
283 if (in_a_cluster[i])
284 ASSERT_INT_EQUAL(ids[i], test.expected_ids[i]);
285 }
286
287 UF_destroy(uf);
288 for (i = 0; i < test.num_geoms; i++)
289 {
290 lwgeom_free(geoms[i]);
291 }
292 lwfree(geoms);
293 lwfree(in_a_cluster);
294 lwfree(ids);
295}
296
297static void dbscan_test(void)
298{
299 struct dbscan_test_info test;
300 char* wkt_inputs[] = { "POINT (0 0)", "POINT (-1 0)", "POINT (-1 -0.1)", "POINT (-1 0.1)",
301 "POINT (1 0)",
302 "POINT (2 0)", "POINT (3 0)", "POINT ( 3 -0.1)", "POINT ( 3 0.1)" };
303 /* Although POINT (1 0) and POINT (2 0) are within eps distance of each other,
304 * they do not connect the two clusters because POINT (1 0) is not a core point.
305 * See #3572
306 */
307 test.eps = 1.01;
308 test.min_points = 5;
309 uint32_t expected_ids[] = { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1 };
310 int expected_in_cluster[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
311 test.num_geoms = sizeof(wkt_inputs) / sizeof(char*);
312
314 test.wkt_inputs = wkt_inputs;
316 do_dbscan_test(test);
317}
318
319static void dbscan_test_3612a(void)
320{
321 struct dbscan_test_info test;
322 char* wkt_inputs[] = { "POINT (1 1)" };
323
324 test.eps = 0.0;
325 test.min_points = 5;
326 uint32_t expected_ids[] = { rand() };
327 int expected_in_cluster[] = { 0 };
328 test.num_geoms = sizeof(wkt_inputs) / sizeof(char*);
329
332 test.wkt_inputs = wkt_inputs;
333 do_dbscan_test(test);
334}
335
336static void dbscan_test_3612b(void)
337{
338 struct dbscan_test_info test;
339 char* wkt_inputs[] = { "POINT (1 1)" };
340
341 test.eps = 0.0;
342 test.min_points = 1;
343 uint32_t expected_ids[] = { 0 };
344 int expected_in_cluster[] = { 1 };
345 test.num_geoms = sizeof(wkt_inputs) / sizeof(char*);
346
349 test.wkt_inputs = wkt_inputs;
350 do_dbscan_test(test);
351}
352
353static void dbscan_test_3612c(void)
354{
355 struct dbscan_test_info test;
356 char* wkt_inputs[] = { "POLYGONM((-71.1319 42.2503 1,-71.132 42.2502 3,-71.1323 42.2504 -2,-71.1322 42.2505 1,-71.1319 42.2503 0))",
357 "POLYGONM((-71.1319 42.2512 0,-71.1318 42.2511 20,-71.1317 42.2511 -20,-71.1317 42.251 5,-71.1317 42.2509 4,-71.132 42.2511 6,-71.1319 42.2512 30))" };
358 test.eps = 20.1;
359 test.min_points = 5;
360 uint32_t expected_ids[] = { rand(), rand() };
361 int expected_in_cluster[] = { 0, 0 };
362 test.num_geoms = sizeof(wkt_inputs) / sizeof(char*);
363
366 test.wkt_inputs = wkt_inputs;
367 do_dbscan_test(test);
368}
369
370void geos_cluster_suite_setup(void);
static void basic_distance_test(void)
static void multipoint_test(void)
void geos_cluster_suite_setup(void)
static void dbscan_test_3612c(void)
static void perform_cluster_within_distance_test(double tolerance, char **wkt_inputs, uint32_t num_inputs, char **wkt_outputs, uint32_t num_outputs)
static void single_input_test(void)
static void empty_inputs_test(void)
static int clean_geos_cluster_suite(void)
static void dbscan_test_3612b(void)
static GEOSGeometry ** LWGEOMARRAY2GEOS(LWGEOM **lw_array, size_t num_geoms)
static LWGEOM ** GEOSARRAY2LWGEOM(GEOSGeometry **geos_array, size_t num_geoms)
static void nonsequential_test(void)
static int init_geos_cluster_suite(void)
static void dbscan_test(void)
static void assert_all_results_found(LWGEOM **results, size_t num_outputs, LWGEOM **expected, size_t num_expected_outputs)
static void do_dbscan_test(struct dbscan_test_info test)
static LWGEOM ** WKTARRAY2LWGEOM(char **wkt_array, size_t num_geoms)
static void dbscan_test_3612a(void)
static void perform_cluster_intersecting_test(char **wkt_inputs, uint32_t num_inputs, char **wkt_outputs, uint32_t num_outputs)
static void basic_test(void)
#define ASSERT_INT_EQUAL(o, e)
#define PG_ADD_TEST(suite, testfunc)
GEOSGeometry * LWGEOM2GEOS(const LWGEOM *lwgeom, uint8_t autofix)
void lwgeom_geos_error(const char *fmt,...)
void(*) LWGEOM GEOS2LWGEOM)(const GEOSGeometry *geom, uint8_t want3d)
int cluster_within_distance(LWGEOM **geoms, uint32_t num_geoms, double tolerance, LWGEOM ***clusterGeoms, uint32_t *num_clusters)
Takes an array of LWGEOM* and constructs an array of LWGEOM*, where each element in the constructed a...
int cluster_intersecting(GEOSGeometry **geoms, uint32_t num_geoms, GEOSGeometry ***clusterGeoms, uint32_t *num_clusters)
Takes an array of GEOSGeometry* and constructs an array of GEOSGeometry*, where each element in the c...
int union_dbscan(LWGEOM **geoms, uint32_t num_geoms, UNIONFIND *uf, double eps, uint32_t min_points, char **is_in_cluster_ret)
char lwgeom_same(const LWGEOM *lwgeom1, const LWGEOM *lwgeom2)
geom1 same as geom2 iff
Definition lwgeom.c:619
void lwgeom_free(LWGEOM *geom)
Definition lwgeom.c:1246
#define LW_PARSER_CHECK_NONE
Definition liblwgeom.h:2149
void * lwalloc(size_t size)
Definition lwutil.c:227
void lwfree(void *mem)
Definition lwutil.c:248
LWGEOM * lwgeom_from_wkt(const char *wkt, const char check)
Definition lwin_wkt.c:940
void lwnotice(const char *fmt,...) __attribute__((format(printf
Write a notice out to the notice handler.
void UF_destroy(UNIONFIND *uf)
Definition lwunionfind.c:54
UNIONFIND * UF_create(uint32_t N)
Definition lwunionfind.c:35
uint32_t * UF_get_collapsed_cluster_ids(UNIONFIND *uf, const char *is_in_cluster)
uint32_t * expected_ids