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