1 /**********************************************************************
2 *
3 * rttopo - topology library
4 * http://git.osgeo.org/gitea/rttopo/librttopo
5 *
6 * rttopo is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * rttopo is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with rttopo. If not, see <http://www.gnu.org/licenses/>.
18 *
19 **********************************************************************
20 *
21 * Copyright (C) 2012 Sandro Santilli <strk@kbt.io>
22 * Copyright (C) 2001-2006 Refractions Research Inc.
23 *
24 **********************************************************************/
25
26
27
28 /* basic RTPOLY manipulation */
29
30 #include "rttopo_config.h"
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include "librttopo_geom_internal.h"
35 #include "rtgeom_log.h"
36
37
38 #define CHECK_POLY_RINGS_ZM 1
39
40 /* construct a new RTPOLY. arrays (points/points per ring) will NOT be copied
41 * use SRID=SRID_UNKNOWN for unknown SRID (will have 8bit type's S = 0)
42 */
43 RTPOLY*
rtpoly_construct(const RTCTX * ctx,int srid,RTGBOX * bbox,uint32_t nrings,RTPOINTARRAY ** points)44 rtpoly_construct(const RTCTX *ctx, int srid, RTGBOX *bbox, uint32_t nrings, RTPOINTARRAY **points)
45 {
46 RTPOLY *result;
47 int hasz, hasm;
48 #ifdef CHECK_POLY_RINGS_ZM
49 char zm;
50 uint32_t i;
51 #endif
52
53 if ( nrings < 1 ) rterror(ctx, "rtpoly_construct: need at least 1 ring");
54
55 hasz = RTFLAGS_GET_Z(points[0]->flags);
56 hasm = RTFLAGS_GET_M(points[0]->flags);
57
58 #ifdef CHECK_POLY_RINGS_ZM
59 zm = RTFLAGS_GET_ZM(points[0]->flags);
60 for (i=1; i<nrings; i++)
61 {
62 if ( zm != RTFLAGS_GET_ZM(points[i]->flags) )
63 rterror(ctx, "rtpoly_construct: mixed dimensioned rings");
64 }
65 #endif
66
67 result = (RTPOLY*) rtalloc(ctx, sizeof(RTPOLY));
68 result->type = RTPOLYGONTYPE;
69 result->flags = gflags(ctx, hasz, hasm, 0);
70 RTFLAGS_SET_BBOX(result->flags, bbox?1:0);
71 result->srid = srid;
72 result->nrings = nrings;
73 result->maxrings = nrings;
74 result->rings = points;
75 result->bbox = bbox;
76
77 return result;
78 }
79
80 RTPOLY*
rtpoly_construct_empty(const RTCTX * ctx,int srid,char hasz,char hasm)81 rtpoly_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm)
82 {
83 RTPOLY *result = rtalloc(ctx, sizeof(RTPOLY));
84 result->type = RTPOLYGONTYPE;
85 result->flags = gflags(ctx, hasz,hasm,0);
86 result->srid = srid;
87 result->nrings = 0;
88 result->maxrings = 1; /* Allocate room for ring, just in case. */
89 result->rings = rtalloc(ctx, result->maxrings * sizeof(RTPOINTARRAY*));
90 result->bbox = NULL;
91 return result;
92 }
93
rtpoly_free(const RTCTX * ctx,RTPOLY * poly)94 void rtpoly_free(const RTCTX *ctx, RTPOLY *poly)
95 {
96 int t;
97
98 if( ! poly ) return;
99
100 if ( poly->bbox )
101 rtfree(ctx, poly->bbox);
102
103 for (t=0; t<poly->nrings; t++)
104 {
105 if ( poly->rings[t] )
106 ptarray_free(ctx, poly->rings[t]);
107 }
108
109 if ( poly->rings )
110 rtfree(ctx, poly->rings);
111
112 rtfree(ctx, poly);
113 }
114
printRTPOLY(const RTCTX * ctx,RTPOLY * poly)115 void printRTPOLY(const RTCTX *ctx, RTPOLY *poly)
116 {
117 int t;
118 rtnotice(ctx, "RTPOLY {");
119 rtnotice(ctx, " ndims = %i", (int)RTFLAGS_NDIMS(poly->flags));
120 rtnotice(ctx, " SRID = %i", (int)poly->srid);
121 rtnotice(ctx, " nrings = %i", (int)poly->nrings);
122 for (t=0; t<poly->nrings; t++)
123 {
124 rtnotice(ctx, " RING # %i :",t);
125 printPA(ctx, poly->rings[t]);
126 }
127 rtnotice(ctx, "}");
128 }
129
130 /* @brief Clone RTLINE object. Serialized point lists are not copied.
131 *
132 * @see ptarray_clone
133 */
134 RTPOLY *
rtpoly_clone(const RTCTX * ctx,const RTPOLY * g)135 rtpoly_clone(const RTCTX *ctx, const RTPOLY *g)
136 {
137 int i;
138 RTPOLY *ret = rtalloc(ctx, sizeof(RTPOLY));
139 memcpy(ret, g, sizeof(RTPOLY));
140 ret->rings = rtalloc(ctx, sizeof(RTPOINTARRAY *)*g->nrings);
141 for ( i = 0; i < g->nrings; i++ ) {
142 ret->rings[i] = ptarray_clone(ctx, g->rings[i]);
143 }
144 if ( g->bbox ) ret->bbox = gbox_copy(ctx, g->bbox);
145 return ret;
146 }
147
148 /* Deep clone RTPOLY object. RTPOINTARRAY are copied, as is ring array */
149 RTPOLY *
rtpoly_clone_deep(const RTCTX * ctx,const RTPOLY * g)150 rtpoly_clone_deep(const RTCTX *ctx, const RTPOLY *g)
151 {
152 int i;
153 RTPOLY *ret = rtalloc(ctx, sizeof(RTPOLY));
154 memcpy(ret, g, sizeof(RTPOLY));
155 if ( g->bbox ) ret->bbox = gbox_copy(ctx, g->bbox);
156 ret->rings = rtalloc(ctx, sizeof(RTPOINTARRAY *)*g->nrings);
157 for ( i = 0; i < ret->nrings; i++ )
158 {
159 ret->rings[i] = ptarray_clone_deep(ctx, g->rings[i]);
160 }
161 RTFLAGS_SET_READONLY(ret->flags,0);
162 return ret;
163 }
164
165 /**
166 * Add a ring to a polygon. Point array will be referenced, not copied.
167 */
168 int
rtpoly_add_ring(const RTCTX * ctx,RTPOLY * poly,RTPOINTARRAY * pa)169 rtpoly_add_ring(const RTCTX *ctx, RTPOLY *poly, RTPOINTARRAY *pa)
170 {
171 if( ! poly || ! pa )
172 return RT_FAILURE;
173
174 /* We have used up our storage, add some more. */
175 if( poly->nrings >= poly->maxrings )
176 {
177 int new_maxrings = 2 * (poly->nrings + 1);
178 poly->rings = rtrealloc(ctx, poly->rings, new_maxrings * sizeof(RTPOINTARRAY*));
179 poly->maxrings = new_maxrings;
180 }
181
182 /* Add the new ring entry. */
183 poly->rings[poly->nrings] = pa;
184 poly->nrings++;
185
186 return RT_SUCCESS;
187 }
188
189 void
rtpoly_force_clockwise(const RTCTX * ctx,RTPOLY * poly)190 rtpoly_force_clockwise(const RTCTX *ctx, RTPOLY *poly)
191 {
192 int i;
193
194 /* No-op empties */
195 if ( rtpoly_is_empty(ctx, poly) )
196 return;
197
198 /* External ring */
199 if ( ptarray_isccw(ctx, poly->rings[0]) )
200 ptarray_reverse(ctx, poly->rings[0]);
201
202 /* Internal rings */
203 for (i=1; i<poly->nrings; i++)
204 if ( ! ptarray_isccw(ctx, poly->rings[i]) )
205 ptarray_reverse(ctx, poly->rings[i]);
206
207 }
208
209 void
rtpoly_release(const RTCTX * ctx,RTPOLY * rtpoly)210 rtpoly_release(const RTCTX *ctx, RTPOLY *rtpoly)
211 {
212 rtgeom_release(ctx, rtpoly_as_rtgeom(ctx, rtpoly));
213 }
214
215 void
rtpoly_reverse(const RTCTX * ctx,RTPOLY * poly)216 rtpoly_reverse(const RTCTX *ctx, RTPOLY *poly)
217 {
218 int i;
219 if ( rtpoly_is_empty(ctx, poly) ) return;
220 for (i=0; i<poly->nrings; i++)
221 ptarray_reverse(ctx, poly->rings[i]);
222 }
223
224 RTPOLY *
rtpoly_segmentize2d(const RTCTX * ctx,RTPOLY * poly,double dist)225 rtpoly_segmentize2d(const RTCTX *ctx, RTPOLY *poly, double dist)
226 {
227 RTPOINTARRAY **newrings;
228 uint32_t i;
229
230 newrings = rtalloc(ctx, sizeof(RTPOINTARRAY *)*poly->nrings);
231 for (i=0; i<poly->nrings; i++)
232 {
233 newrings[i] = ptarray_segmentize2d(ctx, poly->rings[i], dist);
234 if ( ! newrings[i] ) {
235 while (i--) ptarray_free(ctx, newrings[i]);
236 rtfree(ctx, newrings);
237 return NULL;
238 }
239 }
240 return rtpoly_construct(ctx, poly->srid, NULL,
241 poly->nrings, newrings);
242 }
243
244 /*
245 * check coordinate equality
246 * ring and coordinate order is considered
247 */
248 char
rtpoly_same(const RTCTX * ctx,const RTPOLY * p1,const RTPOLY * p2)249 rtpoly_same(const RTCTX *ctx, const RTPOLY *p1, const RTPOLY *p2)
250 {
251 uint32_t i;
252
253 if ( p1->nrings != p2->nrings ) return 0;
254 for (i=0; i<p1->nrings; i++)
255 {
256 if ( ! ptarray_same(ctx, p1->rings[i], p2->rings[i]) )
257 return 0;
258 }
259 return 1;
260 }
261
262 /*
263 * Construct a polygon from a RTLINE being
264 * the shell and an array of RTLINE (possibly NULL) being holes.
265 * Pointarrays from intput geoms are cloned.
266 * SRID must be the same for each input line.
267 * Input lines must have at least 4 points, and be closed.
268 */
269 RTPOLY *
rtpoly_from_rtlines(const RTCTX * ctx,const RTLINE * shell,uint32_t nholes,const RTLINE ** holes)270 rtpoly_from_rtlines(const RTCTX *ctx, const RTLINE *shell,
271 uint32_t nholes, const RTLINE **holes)
272 {
273 uint32_t nrings;
274 RTPOINTARRAY **rings = rtalloc(ctx, (nholes+1)*sizeof(RTPOINTARRAY *));
275 int srid = shell->srid;
276 RTPOLY *ret;
277
278 if ( shell->points->npoints < 4 )
279 rterror(ctx, "rtpoly_from_rtlines: shell must have at least 4 points");
280 if ( ! ptarray_is_closed_2d(ctx, shell->points) )
281 rterror(ctx, "rtpoly_from_rtlines: shell must be closed");
282 rings[0] = ptarray_clone_deep(ctx, shell->points);
283
284 for (nrings=1; nrings<=nholes; nrings++)
285 {
286 const RTLINE *hole = holes[nrings-1];
287
288 if ( hole->srid != srid )
289 rterror(ctx, "rtpoly_from_rtlines: mixed SRIDs in input lines");
290
291 if ( hole->points->npoints < 4 )
292 rterror(ctx, "rtpoly_from_rtlines: holes must have at least 4 points");
293 if ( ! ptarray_is_closed_2d(ctx, hole->points) )
294 rterror(ctx, "rtpoly_from_rtlines: holes must be closed");
295
296 rings[nrings] = ptarray_clone_deep(ctx, hole->points);
297 }
298
299 ret = rtpoly_construct(ctx, srid, NULL, nrings, rings);
300 return ret;
301 }
302
303 RTGEOM*
rtpoly_remove_repeated_points(const RTCTX * ctx,const RTPOLY * poly,double tolerance)304 rtpoly_remove_repeated_points(const RTCTX *ctx, const RTPOLY *poly, double tolerance)
305 {
306 uint32_t i;
307 RTPOINTARRAY **newrings;
308
309 newrings = rtalloc(ctx, sizeof(RTPOINTARRAY *)*poly->nrings);
310 for (i=0; i<poly->nrings; i++)
311 {
312 newrings[i] = ptarray_remove_repeated_points_minpoints(ctx, poly->rings[i], tolerance, 4);
313 }
314
315 return (RTGEOM*)rtpoly_construct(ctx, poly->srid,
316 poly->bbox ? gbox_copy(ctx, poly->bbox) : NULL,
317 poly->nrings, newrings);
318
319 }
320
321
322 RTPOLY*
rtpoly_force_dims(const RTCTX * ctx,const RTPOLY * poly,int hasz,int hasm)323 rtpoly_force_dims(const RTCTX *ctx, const RTPOLY *poly, int hasz, int hasm)
324 {
325 RTPOLY *polyout;
326
327 /* Return 2D empty */
328 if( rtpoly_is_empty(ctx, poly) )
329 {
330 polyout = rtpoly_construct_empty(ctx, poly->srid, hasz, hasm);
331 }
332 else
333 {
334 RTPOINTARRAY **rings = NULL;
335 int i;
336 rings = rtalloc(ctx, sizeof(RTPOINTARRAY*) * poly->nrings);
337 for( i = 0; i < poly->nrings; i++ )
338 {
339 rings[i] = ptarray_force_dims(ctx, poly->rings[i], hasz, hasm);
340 }
341 polyout = rtpoly_construct(ctx, poly->srid, NULL, poly->nrings, rings);
342 }
343 polyout->type = poly->type;
344 return polyout;
345 }
346
rtpoly_is_empty(const RTCTX * ctx,const RTPOLY * poly)347 int rtpoly_is_empty(const RTCTX *ctx, const RTPOLY *poly)
348 {
349 if ( (poly->nrings < 1) || (!poly->rings) || (!poly->rings[0]) || (poly->rings[0]->npoints < 1) )
350 return RT_TRUE;
351 return RT_FALSE;
352 }
353
rtpoly_count_vertices(const RTCTX * ctx,RTPOLY * poly)354 int rtpoly_count_vertices(const RTCTX *ctx, RTPOLY *poly)
355 {
356 int i = 0;
357 int v = 0; /* vertices */
358 assert(poly);
359 for ( i = 0; i < poly->nrings; i ++ )
360 {
361 v += poly->rings[i]->npoints;
362 }
363 return v;
364 }
365
rtpoly_simplify(const RTCTX * ctx,const RTPOLY * ipoly,double dist,int preserve_collapsed)366 RTPOLY* rtpoly_simplify(const RTCTX *ctx, const RTPOLY *ipoly, double dist, int preserve_collapsed)
367 {
368 int i;
369 RTPOLY *opoly = rtpoly_construct_empty(ctx, ipoly->srid, RTFLAGS_GET_Z(ipoly->flags), RTFLAGS_GET_M(ipoly->flags));
370
371 RTDEBUGF(ctx, 2, "%s: simplifying polygon with %d rings", __func__, ipoly->nrings);
372
373 if ( rtpoly_is_empty(ctx, ipoly) )
374 {
375 rtpoly_free(ctx, opoly);
376 return NULL;
377 }
378
379 for ( i = 0; i < ipoly->nrings; i++ )
380 {
381 RTPOINTARRAY *opts;
382 int minvertices = 0;
383
384 /* We'll still let holes collapse, but if we're preserving */
385 /* and this is a shell, we ensure it is kept */
386 if ( preserve_collapsed && i == 0 )
387 minvertices = 4;
388
389 opts = ptarray_simplify(ctx, ipoly->rings[i], dist, minvertices);
390
391 RTDEBUGF(ctx, 3, "ring%d simplified from %d to %d points", i, ipoly->rings[i]->npoints, opts->npoints);
392
393 /* Less points than are needed to form a closed ring, we can't use this */
394 if ( opts->npoints < 4 )
395 {
396 RTDEBUGF(ctx, 3, "ring%d skipped (% pts)", i, opts->npoints);
397 ptarray_free(ctx, opts);
398 if ( i ) continue;
399 else break; /* Don't scan holes if shell is collapsed */
400 }
401
402 /* Add ring to simplified polygon */
403 if( rtpoly_add_ring(ctx, opoly, opts) == RT_FAILURE )
404 {
405 rtpoly_free(ctx, opoly);
406 return NULL;
407 }
408 }
409
410 RTDEBUGF(ctx, 3, "simplified polygon with %d rings", ipoly->nrings);
411 opoly->type = ipoly->type;
412
413 if( rtpoly_is_empty(ctx, opoly) )
414 {
415 rtpoly_free(ctx, opoly);
416 return NULL;
417 }
418
419 return opoly;
420 }
421
422 /**
423 * Find the area of the outer ring - sum (area of inner rings).
424 */
425 double
rtpoly_area(const RTCTX * ctx,const RTPOLY * poly)426 rtpoly_area(const RTCTX *ctx, const RTPOLY *poly)
427 {
428 double poly_area = 0.0;
429 int i;
430
431 if ( ! poly )
432 rterror(ctx, "rtpoly_area called with null polygon pointer!");
433
434 for ( i=0; i < poly->nrings; i++ )
435 {
436 RTPOINTARRAY *ring = poly->rings[i];
437 double ringarea = 0.0;
438
439 /* Empty or messed-up ring. */
440 if ( ring->npoints < 3 )
441 continue;
442
443 ringarea = fabs(ptarray_signed_area(ctx, ring));
444 if ( i == 0 ) /* Outer ring, positive area! */
445 poly_area += ringarea;
446 else /* Inner ring, negative area! */
447 poly_area -= ringarea;
448 }
449
450 return poly_area;
451 }
452
453
454 /**
455 * Compute the sum of polygon rings length.
456 * Could use a more numerically stable calculator...
457 */
458 double
rtpoly_perimeter(const RTCTX * ctx,const RTPOLY * poly)459 rtpoly_perimeter(const RTCTX *ctx, const RTPOLY *poly)
460 {
461 double result=0.0;
462 int i;
463
464 RTDEBUGF(ctx, 2, "in rtgeom_polygon_perimeter (%d rings)", poly->nrings);
465
466 for (i=0; i<poly->nrings; i++)
467 result += ptarray_length(ctx, poly->rings[i]);
468
469 return result;
470 }
471
472 /**
473 * Compute the sum of polygon rings length (forcing 2d computation).
474 * Could use a more numerically stable calculator...
475 */
476 double
rtpoly_perimeter_2d(const RTCTX * ctx,const RTPOLY * poly)477 rtpoly_perimeter_2d(const RTCTX *ctx, const RTPOLY *poly)
478 {
479 double result=0.0;
480 int i;
481
482 RTDEBUGF(ctx, 2, "in rtgeom_polygon_perimeter (%d rings)", poly->nrings);
483
484 for (i=0; i<poly->nrings; i++)
485 result += ptarray_length_2d(ctx, poly->rings[i]);
486
487 return result;
488 }
489
490 int
rtpoly_is_closed(const RTCTX * ctx,const RTPOLY * poly)491 rtpoly_is_closed(const RTCTX *ctx, const RTPOLY *poly)
492 {
493 int i = 0;
494
495 if ( poly->nrings == 0 )
496 return RT_TRUE;
497
498 for ( i = 0; i < poly->nrings; i++ )
499 {
500 if (RTFLAGS_GET_Z(poly->flags))
501 {
502 if ( ! ptarray_is_closed_3d(ctx, poly->rings[i]) )
503 return RT_FALSE;
504 }
505 else
506 {
507 if ( ! ptarray_is_closed_2d(ctx, poly->rings[i]) )
508 return RT_FALSE;
509 }
510 }
511
512 return RT_TRUE;
513 }
514
515 int
rtpoly_startpoint(const RTCTX * ctx,const RTPOLY * poly,RTPOINT4D * pt)516 rtpoly_startpoint(const RTCTX *ctx, const RTPOLY* poly, RTPOINT4D* pt)
517 {
518 if ( poly->nrings < 1 )
519 return RT_FAILURE;
520 return ptarray_startpoint(ctx, poly->rings[0], pt);
521 }
522
523 int
rtpoly_contains_point(const RTCTX * ctx,const RTPOLY * poly,const RTPOINT2D * pt)524 rtpoly_contains_point(const RTCTX *ctx, const RTPOLY *poly, const RTPOINT2D *pt)
525 {
526 int i;
527
528 if ( rtpoly_is_empty(ctx, poly) )
529 return RT_FALSE;
530
531 if ( ptarray_contains_point(ctx, poly->rings[0], pt) == RT_OUTSIDE )
532 return RT_FALSE;
533
534 for ( i = 1; i < poly->nrings; i++ )
535 {
536 if ( ptarray_contains_point(ctx, poly->rings[i], pt) == RT_INSIDE )
537 return RT_FALSE;
538 }
539 return RT_TRUE;
540 }
541
542
543
rtpoly_grid(const RTCTX * ctx,const RTPOLY * poly,const gridspec * grid)544 RTPOLY* rtpoly_grid(const RTCTX *ctx, const RTPOLY *poly, const gridspec *grid)
545 {
546 RTPOLY *opoly;
547 int ri;
548
549 #if 0
550 /*
551 * TODO: control this assertion
552 * it is assumed that, since the grid size will be a pixel,
553 * a visible ring should show at least a white pixel inside,
554 * thus, for a square, that would be grid_xsize*grid_ysize
555 */
556 double minvisiblearea = grid->xsize * grid->ysize;
557 #endif
558
559 RTDEBUGF(ctx, 3, "rtpoly_grid: applying grid to polygon with %d rings", poly->nrings);
560
561 opoly = rtpoly_construct_empty(ctx, poly->srid, rtgeom_has_z(ctx, (RTGEOM*)poly), rtgeom_has_m(ctx, (RTGEOM*)poly));
562
563 for (ri=0; ri<poly->nrings; ri++)
564 {
565 RTPOINTARRAY *ring = poly->rings[ri];
566 RTPOINTARRAY *newring;
567
568 newring = ptarray_grid(ctx, ring, grid);
569
570 /* Skip ring if not composed by at least 4 pts (3 segments) */
571 if ( newring->npoints < 4 )
572 {
573 ptarray_free(ctx, newring);
574
575 RTDEBUGF(ctx, 3, "grid_polygon3d: ring%d skipped ( <4 pts )", ri);
576
577 if ( ri ) continue;
578 else break; /* this is the external ring, no need to work on holes */
579 }
580
581 if ( ! rtpoly_add_ring(ctx, opoly, newring) )
582 {
583 rterror(ctx, "rtpoly_grid, memory error");
584 return NULL;
585 }
586 }
587
588 RTDEBUGF(ctx, 3, "rtpoly_grid: simplified polygon with %d rings", opoly->nrings);
589
590 if ( ! opoly->nrings )
591 {
592 rtpoly_free(ctx, opoly);
593 return NULL;
594 }
595
596 return opoly;
597 }
598