/src/postgis/liblwgeom/lwstroke.c
Line | Count | Source |
1 | | /********************************************************************** |
2 | | * |
3 | | * PostGIS - Spatial Types for PostgreSQL |
4 | | * http://postgis.net |
5 | | * |
6 | | * PostGIS 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 | | * PostGIS 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 PostGIS. If not, see <http://www.gnu.org/licenses/>. |
18 | | * |
19 | | ********************************************************************** |
20 | | * |
21 | | * Copyright (C) 2001-2006 Refractions Research Inc. |
22 | | * Copyright (C) 2017 Sandro Santilli <strk@kbt.io> |
23 | | * Copyright (C) 2018 Daniel Baston <dbaston@gmail.com> |
24 | | * |
25 | | **********************************************************************/ |
26 | | |
27 | | |
28 | | #include <stdio.h> |
29 | | #include <stdlib.h> |
30 | | #include <stdarg.h> |
31 | | #include <string.h> |
32 | | |
33 | | #include "../postgis_config.h" |
34 | | |
35 | | /*#define POSTGIS_DEBUG_LEVEL 3*/ |
36 | | |
37 | | #include "lwgeom_log.h" |
38 | | |
39 | | #include "liblwgeom_internal.h" |
40 | | |
41 | | LWGEOM *pta_unstroke(const POINTARRAY *points, int32_t srid); |
42 | | LWGEOM* lwline_unstroke(const LWLINE *line); |
43 | | LWGEOM* lwpolygon_unstroke(const LWPOLY *poly); |
44 | | LWGEOM* lwmline_unstroke(const LWMLINE *mline); |
45 | | LWGEOM* lwmpolygon_unstroke(const LWMPOLY *mpoly); |
46 | | LWGEOM* lwcollection_unstroke(const LWCOLLECTION *c); |
47 | | LWGEOM* lwgeom_unstroke(const LWGEOM *geom); |
48 | | static LWLINE* lwnurbscurve_linearize(const LWNURBSCURVE *curve, double tol, LW_LINEARIZE_TOLERANCE_TYPE tolerance_type, int flags); |
49 | | |
50 | 0 | #define NURBS_MIN_LINEARIZE_SEGMENTS 8 |
51 | 0 | #define NURBS_MAX_LINEARIZE_SEGMENTS 10000 |
52 | | |
53 | | /* |
54 | | * Determines (recursively in the case of collections) whether the geometry |
55 | | * contains at least on arc geometry or segment. |
56 | | */ |
57 | | int |
58 | | lwgeom_has_arc(const LWGEOM *geom) |
59 | 0 | { |
60 | 0 | LWCOLLECTION *col; |
61 | 0 | uint32_t i; |
62 | |
|
63 | 0 | LWDEBUG(2, "lwgeom_has_arc called."); |
64 | |
|
65 | 0 | switch (geom->type) |
66 | 0 | { |
67 | 0 | case POINTTYPE: |
68 | 0 | case LINETYPE: |
69 | 0 | case POLYGONTYPE: |
70 | 0 | case TRIANGLETYPE: |
71 | 0 | case MULTIPOINTTYPE: |
72 | 0 | case MULTILINETYPE: |
73 | 0 | case MULTIPOLYGONTYPE: |
74 | 0 | case POLYHEDRALSURFACETYPE: |
75 | 0 | case TINTYPE: |
76 | 0 | return LW_FALSE; |
77 | 0 | case CIRCSTRINGTYPE: |
78 | 0 | case NURBSCURVETYPE: |
79 | 0 | return LW_TRUE; |
80 | | /* It's a collection that MAY contain an arc */ |
81 | 0 | default: |
82 | 0 | col = (LWCOLLECTION *)geom; |
83 | 0 | for (i=0; i<col->ngeoms; i++) |
84 | 0 | { |
85 | 0 | if (lwgeom_has_arc(col->geoms[i]) == LW_TRUE) |
86 | 0 | return LW_TRUE; |
87 | 0 | } |
88 | 0 | return LW_FALSE; |
89 | 0 | } |
90 | 0 | } |
91 | | |
92 | | int |
93 | | lwgeom_type_arc(const LWGEOM *geom) |
94 | 0 | { |
95 | 0 | switch (geom->type) |
96 | 0 | { |
97 | 0 | case COMPOUNDTYPE: |
98 | 0 | case CIRCSTRINGTYPE: |
99 | 0 | case NURBSCURVETYPE: |
100 | 0 | case CURVEPOLYTYPE: |
101 | 0 | case MULTISURFACETYPE: |
102 | 0 | case MULTICURVETYPE: |
103 | 0 | return LW_TRUE; |
104 | 0 | default: |
105 | 0 | return LW_FALSE; |
106 | 0 | } |
107 | 0 | } |
108 | | |
109 | | /******************************************************************************* |
110 | | * Begin curve segmentize functions |
111 | | ******************************************************************************/ |
112 | | |
113 | | static double interpolate_arc(double angle, double a1, double a2, double a3, double zm1, double zm2, double zm3) |
114 | 0 | { |
115 | 0 | LWDEBUGF(4,"angle %.05g a1 %.05g a2 %.05g a3 %.05g zm1 %.05g zm2 %.05g zm3 %.05g",angle,a1,a2,a3,zm1,zm2,zm3); |
116 | | /* Counter-clockwise sweep */ |
117 | 0 | if ( a1 < a2 ) |
118 | 0 | { |
119 | 0 | if ( angle <= a2 ) |
120 | 0 | return zm1 + (zm2-zm1) * (angle-a1) / (a2-a1); |
121 | 0 | else |
122 | 0 | return zm2 + (zm3-zm2) * (angle-a2) / (a3-a2); |
123 | 0 | } |
124 | | /* Clockwise sweep */ |
125 | 0 | else |
126 | 0 | { |
127 | 0 | if ( angle >= a2 ) |
128 | 0 | return zm1 + (zm2-zm1) * (a1-angle) / (a1-a2); |
129 | 0 | else |
130 | 0 | return zm2 + (zm3-zm2) * (a2-angle) / (a2-a3); |
131 | 0 | } |
132 | 0 | } |
133 | | |
134 | | /* Compute the angle covered by a single segment such that |
135 | | * a given number of segments per quadrant is achieved. */ |
136 | | static double angle_increment_using_segments_per_quad(double tol) |
137 | 0 | { |
138 | 0 | double increment; |
139 | 0 | int perQuad = rint(tol); |
140 | | // error out if tol != perQuad ? (not-round) |
141 | 0 | if ( perQuad != tol ) |
142 | 0 | { |
143 | 0 | lwerror("lwarc_linearize: segments per quadrant must be an integer value, got %.15g", tol); |
144 | 0 | return -1; |
145 | 0 | } |
146 | 0 | if ( perQuad < 1 ) |
147 | 0 | { |
148 | 0 | lwerror("lwarc_linearize: segments per quadrant must be at least 1, got %d", perQuad); |
149 | 0 | return -1; |
150 | 0 | } |
151 | 0 | increment = fabs(M_PI_2 / perQuad); |
152 | 0 | LWDEBUGF(2, "lwarc_linearize: perQuad:%d, increment:%g (%g degrees)", perQuad, increment, increment*180/M_PI); |
153 | |
|
154 | 0 | return increment; |
155 | 0 | } |
156 | | |
157 | | /* Compute the angle covered by a single quadrant such that |
158 | | * the segment deviates from the arc by no more than a given |
159 | | * amount. */ |
160 | | static double angle_increment_using_max_deviation(double max_deviation, double radius) |
161 | 0 | { |
162 | 0 | double increment, halfAngle, maxErr; |
163 | 0 | if ( max_deviation <= 0 ) |
164 | 0 | { |
165 | 0 | lwerror("lwarc_linearize: max deviation must be bigger than 0, got %.15g", max_deviation); |
166 | 0 | return -1; |
167 | 0 | } |
168 | | |
169 | | /* |
170 | | * Ref: https://en.wikipedia.org/wiki/Sagitta_(geometry) |
171 | | * |
172 | | * An arc "sagitta" (distance between middle point of arc and |
173 | | * middle point of corresponding chord) is defined as: |
174 | | * |
175 | | * sagitta = radius * ( 1 - cos( angle ) ); |
176 | | * |
177 | | * We want our sagitta to be at most "tolerance" long, |
178 | | * and we want to find out angle, so we use the inverse |
179 | | * formula: |
180 | | * |
181 | | * tol = radius * ( 1 - cos( angle ) ); |
182 | | * 1 - cos( angle ) = tol/radius |
183 | | * - cos( angle ) = tol/radius - 1 |
184 | | * cos( angle ) = - tol/radius + 1 |
185 | | * angle = acos( 1 - tol/radius ) |
186 | | * |
187 | | * Constraints: 1.0 - tol/radius must be between -1 and 1 |
188 | | * which means tol must be between 0 and 2 times |
189 | | * the radius, which makes sense as you cannot have a |
190 | | * sagitta bigger than twice the radius! |
191 | | * |
192 | | */ |
193 | 0 | maxErr = max_deviation; |
194 | 0 | if ( maxErr > radius * 2 ) |
195 | 0 | { |
196 | 0 | maxErr = radius * 2; |
197 | 0 | LWDEBUGF(2, |
198 | 0 | "lwarc_linearize: tolerance %g is too big, " |
199 | 0 | "using arc-max 2 * radius == %g", |
200 | 0 | max_deviation, |
201 | 0 | maxErr); |
202 | 0 | } |
203 | 0 | do { |
204 | 0 | halfAngle = acos( 1.0 - maxErr / radius ); |
205 | | /* TODO: avoid a loop here, going rather straight to |
206 | | * a minimum angle value */ |
207 | 0 | if ( halfAngle != 0 ) break; |
208 | 0 | LWDEBUGF(2, "lwarc_linearize: tolerance %g is too small for this arc" |
209 | 0 | " to compute approximation angle, doubling it", maxErr); |
210 | 0 | maxErr *= 2; |
211 | 0 | } while(1); |
212 | 0 | increment = 2 * halfAngle; |
213 | 0 | LWDEBUGF(2, |
214 | 0 | "lwarc_linearize: maxDiff:%g, radius:%g, halfAngle:%g, increment:%g (%g degrees)", |
215 | 0 | max_deviation, |
216 | 0 | radius, |
217 | 0 | halfAngle, |
218 | 0 | increment, |
219 | 0 | increment * 180 / M_PI); |
220 | |
|
221 | 0 | return increment; |
222 | 0 | } |
223 | | |
224 | | /* Check that a given angle is positive and, if so, take |
225 | | * it to be the angle covered by a single segment. */ |
226 | | static double angle_increment_using_max_angle(double tol) |
227 | 0 | { |
228 | 0 | if ( tol <= 0 ) |
229 | 0 | { |
230 | 0 | lwerror("lwarc_linearize: max angle must be bigger than 0, got %.15g", tol); |
231 | 0 | return -1; |
232 | 0 | } |
233 | | |
234 | 0 | return tol; |
235 | 0 | } |
236 | | |
237 | | |
238 | | /** |
239 | | * Segmentize an arc |
240 | | * |
241 | | * Does not add the final vertex |
242 | | * |
243 | | * @param to POINTARRAY to append segmentized vertices to |
244 | | * @param p1 first point defining the arc |
245 | | * @param p2 second point defining the arc |
246 | | * @param p3 third point defining the arc |
247 | | * @param tol tolerance, semantic driven by tolerance_type |
248 | | * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE |
249 | | * @param flags LW_LINEARIZE_FLAGS |
250 | | * |
251 | | * @return number of points appended (0 if collinear), |
252 | | * or -1 on error (lwerror would be called). |
253 | | * |
254 | | */ |
255 | | static int |
256 | | lwarc_linearize(POINTARRAY *to, |
257 | | const POINT4D *p1, const POINT4D *p2, const POINT4D *p3, |
258 | | double tol, LW_LINEARIZE_TOLERANCE_TYPE tolerance_type, |
259 | | int flags) |
260 | 0 | { |
261 | 0 | POINT2D center; |
262 | 0 | POINT2D *t1 = (POINT2D*)p1; |
263 | 0 | POINT2D *t2 = (POINT2D*)p2; |
264 | 0 | POINT2D *t3 = (POINT2D*)p3; |
265 | 0 | POINT4D pt; |
266 | 0 | int p2_side = 0; |
267 | 0 | int clockwise = LW_TRUE; |
268 | 0 | double radius; /* Arc radius */ |
269 | 0 | double increment; /* Angle per segment */ |
270 | 0 | double angle_shift = 0; |
271 | 0 | double a1, a2, a3; |
272 | 0 | POINTARRAY *pa; |
273 | 0 | int is_circle = LW_FALSE; |
274 | 0 | int points_added = 0; |
275 | 0 | int reverse = 0; |
276 | 0 | int segments = 0; |
277 | |
|
278 | 0 | LWDEBUG(2, "lwarc_linearize called."); |
279 | |
|
280 | 0 | LWDEBUGF(2, " curve is CIRCULARSTRING(%.15g %.15f, %.15f %.15f, %.15f %15f)", |
281 | 0 | t1->x, t1->y, t2->x, t2->y, t3->x, t3->y); |
282 | |
|
283 | 0 | p2_side = lw_segment_side(t1, t3, t2); |
284 | |
|
285 | 0 | LWDEBUGF(2, " p2 side is %d", p2_side); |
286 | | |
287 | | /* Force counterclockwise scan if SYMMETRIC operation is requested */ |
288 | 0 | if ( p2_side == -1 && flags & LW_LINEARIZE_FLAG_SYMMETRIC ) |
289 | 0 | { |
290 | | /* swap p1-p3 */ |
291 | 0 | t1 = (POINT2D*)p3; |
292 | 0 | t3 = (POINT2D*)p1; |
293 | 0 | p1 = (POINT4D*)t1; |
294 | 0 | p3 = (POINT4D*)t3; |
295 | 0 | p2_side = 1; |
296 | 0 | reverse = 1; |
297 | 0 | } |
298 | |
|
299 | 0 | radius = lw_arc_center(t1, t2, t3, ¢er); |
300 | 0 | LWDEBUGF(2, " center is POINT(%.15g %.15g) - radius:%g", center.x, center.y, radius); |
301 | | |
302 | | /* Matched start/end points imply circle */ |
303 | 0 | if ( p1->x == p3->x && p1->y == p3->y ) |
304 | 0 | is_circle = LW_TRUE; |
305 | | |
306 | | /* Negative radius signals straight line, p1/p2/p3 are collinear */ |
307 | 0 | if ( (radius < 0.0 || p2_side == 0) && ! is_circle ) |
308 | 0 | return 0; |
309 | | |
310 | | /* The side of the p1/p3 line that p2 falls on dictates the sweep |
311 | | direction from p1 to p3. */ |
312 | 0 | if ( p2_side == -1 ) |
313 | 0 | clockwise = LW_TRUE; |
314 | 0 | else |
315 | 0 | clockwise = LW_FALSE; |
316 | | |
317 | | /* Compute the increment (angle per segment) depending on |
318 | | * our tolerance type. */ |
319 | 0 | switch(tolerance_type) |
320 | 0 | { |
321 | 0 | case LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD: |
322 | 0 | increment = angle_increment_using_segments_per_quad(tol); |
323 | 0 | break; |
324 | 0 | case LW_LINEARIZE_TOLERANCE_TYPE_MAX_DEVIATION: |
325 | 0 | increment = angle_increment_using_max_deviation(tol, radius); |
326 | 0 | break; |
327 | 0 | case LW_LINEARIZE_TOLERANCE_TYPE_MAX_ANGLE: |
328 | 0 | increment = angle_increment_using_max_angle(tol); |
329 | 0 | break; |
330 | 0 | default: |
331 | 0 | lwerror("lwarc_linearize: unsupported tolerance type %d", tolerance_type); |
332 | 0 | return -1; |
333 | 0 | } |
334 | | |
335 | 0 | if (increment < 0) |
336 | 0 | { |
337 | | /* Error occurred in increment calculation somewhere |
338 | | * (lwerror already called) |
339 | | */ |
340 | 0 | return -1; |
341 | 0 | } |
342 | | |
343 | | /* Angles of each point that defines the arc section */ |
344 | 0 | a1 = atan2(p1->y - center.y, p1->x - center.x); |
345 | 0 | a2 = atan2(p2->y - center.y, p2->x - center.x); |
346 | 0 | a3 = atan2(p3->y - center.y, p3->x - center.x); |
347 | |
|
348 | 0 | LWDEBUGF(2, "lwarc_linearize A1:%g (%g) A2:%g (%g) A3:%g (%g)", |
349 | 0 | a1, a1*180/M_PI, a2, a2*180/M_PI, a3, a3*180/M_PI); |
350 | | |
351 | | /* Calculate total arc angle, in radians */ |
352 | 0 | double total_angle = clockwise ? a1 - a3 : a3 - a1; |
353 | 0 | if ( total_angle <= 0 ) total_angle += M_PI * 2; |
354 | | |
355 | | /* At extreme tolerance values (very low or very high, depending on |
356 | | * the semantic) we may cause our arc to collapse. In this case, |
357 | | * we want shrink the increment enough so that we get two segments |
358 | | * for a standard arc, or three segments for a complete circle. */ |
359 | 0 | int min_segs = is_circle ? 3 : 2; |
360 | 0 | segments = ceil(total_angle / increment); |
361 | 0 | if (segments < min_segs) |
362 | 0 | { |
363 | 0 | segments = min_segs; |
364 | 0 | increment = total_angle / min_segs; |
365 | 0 | } |
366 | |
|
367 | 0 | if ( flags & LW_LINEARIZE_FLAG_SYMMETRIC ) |
368 | 0 | { |
369 | 0 | LWDEBUGF(2, "lwarc_linearize SYMMETRIC requested - total angle %g deg", total_angle * 180 / M_PI); |
370 | |
|
371 | 0 | if ( flags & LW_LINEARIZE_FLAG_RETAIN_ANGLE ) |
372 | 0 | { |
373 | | /* Number of complete steps */ |
374 | 0 | segments = trunc(total_angle / increment); |
375 | | |
376 | | /* Figure out the angle remainder, i.e. the amount of the angle |
377 | | * that is left after we can take no more complete angle |
378 | | * increments. */ |
379 | 0 | double angle_remainder = total_angle - (increment * segments); |
380 | | |
381 | | /* Shift the starting angle by half of the remainder. This |
382 | | * will have the effect of evenly distributing the remainder |
383 | | * among the first and last segments in the arc. */ |
384 | 0 | angle_shift = angle_remainder / 2.0; |
385 | |
|
386 | 0 | LWDEBUGF(2, |
387 | 0 | "lwarc_linearize RETAIN_ANGLE operation requested - " |
388 | 0 | "total angle %g, steps %d, increment %g, remainder %g", |
389 | 0 | total_angle * 180 / M_PI, |
390 | 0 | segments, |
391 | 0 | increment * 180 / M_PI, |
392 | 0 | angle_remainder * 180 / M_PI); |
393 | 0 | } |
394 | 0 | else |
395 | 0 | { |
396 | | /* Number of segments in output */ |
397 | 0 | segments = ceil(total_angle / increment); |
398 | | /* Tweak increment to be regular for all the arc */ |
399 | 0 | increment = total_angle / segments; |
400 | |
|
401 | 0 | LWDEBUGF(2, |
402 | 0 | "lwarc_linearize SYMMETRIC operation requested - " |
403 | 0 | "total angle %g degrees - LINESTRING(%g %g,%g %g,%g %g) - S:%d - I:%g", |
404 | 0 | total_angle * 180 / M_PI, |
405 | 0 | p1->x, |
406 | 0 | p1->y, |
407 | 0 | center.x, |
408 | 0 | center.y, |
409 | 0 | p3->x, |
410 | 0 | p3->y, |
411 | 0 | segments, |
412 | 0 | increment * 180 / M_PI); |
413 | 0 | } |
414 | 0 | } |
415 | | |
416 | | /* p2 on left side => clockwise sweep */ |
417 | 0 | if ( clockwise ) |
418 | 0 | { |
419 | 0 | LWDEBUG(2, " Clockwise sweep"); |
420 | 0 | increment *= -1; |
421 | 0 | angle_shift *= -1; |
422 | | /* Adjust a3 down so we can decrement from a1 to a3 cleanly */ |
423 | 0 | if ( a3 > a1 ) |
424 | 0 | a3 -= 2.0 * M_PI; |
425 | 0 | if ( a2 > a1 ) |
426 | 0 | a2 -= 2.0 * M_PI; |
427 | 0 | } |
428 | | /* p2 on right side => counter-clockwise sweep */ |
429 | 0 | else |
430 | 0 | { |
431 | 0 | LWDEBUG(2, " Counterclockwise sweep"); |
432 | | /* Adjust a3 up so we can increment from a1 to a3 cleanly */ |
433 | 0 | if ( a3 < a1 ) |
434 | 0 | a3 += 2.0 * M_PI; |
435 | 0 | if ( a2 < a1 ) |
436 | 0 | a2 += 2.0 * M_PI; |
437 | 0 | } |
438 | | |
439 | | /* Override angles for circle case */ |
440 | 0 | if( is_circle ) |
441 | 0 | { |
442 | 0 | increment = fabs(increment); |
443 | 0 | segments = ceil(total_angle / increment); |
444 | 0 | if (segments < 3) |
445 | 0 | { |
446 | 0 | segments = 3; |
447 | 0 | increment = total_angle / 3; |
448 | 0 | } |
449 | 0 | a3 = a1 + 2.0 * M_PI; |
450 | 0 | a2 = a1 + M_PI; |
451 | 0 | clockwise = LW_FALSE; |
452 | 0 | angle_shift = 0.0; |
453 | 0 | } |
454 | |
|
455 | 0 | LWDEBUGF(2, "lwarc_linearize angle_shift:%g, increment:%g", |
456 | 0 | angle_shift * 180/M_PI, increment * 180/M_PI); |
457 | |
|
458 | 0 | if ( reverse ) |
459 | 0 | { |
460 | | /* Append points in order to a temporary POINTARRAY and |
461 | | * reverse them before writing to the output POINTARRAY. */ |
462 | 0 | const int capacity = 8; /* TODO: compute exactly ? */ |
463 | 0 | pa = ptarray_construct_empty(ptarray_has_z(to), ptarray_has_m(to), capacity); |
464 | 0 | } |
465 | 0 | else |
466 | 0 | { |
467 | | /* Append points directly to the output POINTARRAY, |
468 | | * starting with p1. */ |
469 | 0 | pa = to; |
470 | |
|
471 | 0 | ptarray_append_point(pa, p1, LW_FALSE); |
472 | 0 | ++points_added; |
473 | 0 | } |
474 | | |
475 | | /* Sweep from a1 to a3 */ |
476 | 0 | int seg_start = 1; /* First point is added manually */ |
477 | 0 | int seg_end = segments; |
478 | 0 | if (angle_shift != 0.0) |
479 | 0 | { |
480 | | /* When we have extra angles we need to add the extra segments at the |
481 | | * start and end that cover those parts of the arc */ |
482 | 0 | seg_start = 0; |
483 | 0 | seg_end = segments + 1; |
484 | 0 | } |
485 | 0 | LWDEBUGF(2, "a1:%g (%g deg), a3:%g (%g deg), inc:%g, shi:%g, cw:%d", |
486 | 0 | a1, a1 * 180 / M_PI, a3, a3 * 180 / M_PI, increment, angle_shift, clockwise); |
487 | 0 | for (int s = seg_start; s < seg_end; s++) |
488 | 0 | { |
489 | 0 | double angle = a1 + increment * s + angle_shift; |
490 | 0 | LWDEBUGF(2, " SA: %g ( %g deg )", angle, angle*180/M_PI); |
491 | 0 | pt.x = center.x + radius * cos(angle); |
492 | 0 | pt.y = center.y + radius * sin(angle); |
493 | 0 | pt.z = interpolate_arc(angle, a1, a2, a3, p1->z, p2->z, p3->z); |
494 | 0 | pt.m = interpolate_arc(angle, a1, a2, a3, p1->m, p2->m, p3->m); |
495 | 0 | ptarray_append_point(pa, &pt, LW_FALSE); |
496 | 0 | ++points_added; |
497 | 0 | } |
498 | | |
499 | | /* Ensure the final point is EXACTLY the same as the first for the circular case */ |
500 | 0 | if ( is_circle ) |
501 | 0 | { |
502 | 0 | ptarray_remove_point(pa, pa->npoints - 1); |
503 | 0 | ptarray_append_point(pa, p1, LW_FALSE); |
504 | 0 | } |
505 | |
|
506 | 0 | if ( reverse ) |
507 | 0 | { |
508 | 0 | int i; |
509 | 0 | ptarray_append_point(to, p3, LW_FALSE); |
510 | 0 | for ( i=pa->npoints; i>0; i-- ) { |
511 | 0 | getPoint4d_p(pa, i-1, &pt); |
512 | 0 | ptarray_append_point(to, &pt, LW_FALSE); |
513 | 0 | } |
514 | 0 | ptarray_free(pa); |
515 | 0 | } |
516 | |
|
517 | 0 | return points_added; |
518 | 0 | } |
519 | | |
520 | | /* |
521 | | * @param icurve input curve |
522 | | * @param tol tolerance, semantic driven by tolerance_type |
523 | | * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE |
524 | | * @param flags see flags in lwarc_linearize |
525 | | * |
526 | | * @return a newly allocated LWLINE |
527 | | */ |
528 | | static LWLINE * |
529 | | lwcircstring_linearize(const LWCIRCSTRING *icurve, double tol, |
530 | | LW_LINEARIZE_TOLERANCE_TYPE tolerance_type, |
531 | | int flags) |
532 | 0 | { |
533 | 0 | LWLINE *oline; |
534 | 0 | POINTARRAY *ptarray; |
535 | 0 | uint32_t i, j; |
536 | 0 | POINT4D p1, p2, p3, p4; |
537 | 0 | int ret; |
538 | |
|
539 | 0 | LWDEBUGF(2, "lwcircstring_linearize called., dim = %d", icurve->points->flags); |
540 | |
|
541 | 0 | ptarray = ptarray_construct_empty(FLAGS_GET_Z(icurve->points->flags), FLAGS_GET_M(icurve->points->flags), 64); |
542 | |
|
543 | 0 | for (i = 2; i < icurve->points->npoints; i+=2) |
544 | 0 | { |
545 | 0 | LWDEBUGF(3, "lwcircstring_linearize: arc ending at point %d", i); |
546 | |
|
547 | 0 | getPoint4d_p(icurve->points, i - 2, &p1); |
548 | 0 | getPoint4d_p(icurve->points, i - 1, &p2); |
549 | 0 | getPoint4d_p(icurve->points, i, &p3); |
550 | |
|
551 | 0 | ret = lwarc_linearize(ptarray, &p1, &p2, &p3, tol, tolerance_type, flags); |
552 | 0 | if ( ret > 0 ) |
553 | 0 | { |
554 | 0 | LWDEBUGF(3, "lwcircstring_linearize: generated %d points", ptarray->npoints); |
555 | 0 | } |
556 | 0 | else if ( ret == 0 ) |
557 | 0 | { |
558 | 0 | LWDEBUG(3, "lwcircstring_linearize: points are colinear, returning curve points as line"); |
559 | |
|
560 | 0 | for (j = i - 2 ; j < i ; j++) |
561 | 0 | { |
562 | 0 | getPoint4d_p(icurve->points, j, &p4); |
563 | 0 | ptarray_append_point(ptarray, &p4, LW_TRUE); |
564 | 0 | } |
565 | 0 | } |
566 | 0 | else |
567 | 0 | { |
568 | | /* An error occurred, lwerror should have been called by now */ |
569 | 0 | ptarray_free(ptarray); |
570 | 0 | return NULL; |
571 | 0 | } |
572 | 0 | } |
573 | 0 | if (icurve->points->npoints > 0) |
574 | 0 | { |
575 | 0 | getPoint4d_p(icurve->points, icurve->points->npoints-1, &p1); |
576 | 0 | ptarray_append_point(ptarray, &p1, LW_FALSE); |
577 | 0 | } |
578 | |
|
579 | 0 | oline = lwline_construct(icurve->srid, NULL, ptarray); |
580 | 0 | return oline; |
581 | 0 | } |
582 | | |
583 | | /* |
584 | | * @param icompound input compound curve |
585 | | * @param tol tolerance, semantic driven by tolerance_type |
586 | | * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE |
587 | | * @param flags see flags in lwarc_linearize |
588 | | * |
589 | | * @return a newly allocated LWLINE |
590 | | */ |
591 | | static LWLINE * |
592 | | lwcompound_linearize(const LWCOMPOUND *icompound, double tol, |
593 | | LW_LINEARIZE_TOLERANCE_TYPE tolerance_type, |
594 | | int flags) |
595 | 0 | { |
596 | 0 | LWGEOM *geom; |
597 | 0 | POINTARRAY *ptarray = NULL; |
598 | 0 | LWLINE *tmp = NULL; |
599 | 0 | uint32_t i, j; |
600 | 0 | POINT4D p; |
601 | |
|
602 | 0 | LWDEBUG(2, "lwcompound_stroke called."); |
603 | |
|
604 | 0 | ptarray = ptarray_construct_empty(FLAGS_GET_Z(icompound->flags), FLAGS_GET_M(icompound->flags), 64); |
605 | |
|
606 | 0 | for (i = 0; i < icompound->ngeoms; i++) |
607 | 0 | { |
608 | 0 | geom = icompound->geoms[i]; |
609 | 0 | if (geom->type == CIRCSTRINGTYPE) |
610 | 0 | { |
611 | 0 | tmp = lwcircstring_linearize((LWCIRCSTRING *)geom, tol, tolerance_type, flags); |
612 | 0 | for (j = 0; j < tmp->points->npoints; j++) |
613 | 0 | { |
614 | 0 | getPoint4d_p(tmp->points, j, &p); |
615 | 0 | ptarray_append_point(ptarray, &p, LW_TRUE); |
616 | 0 | } |
617 | 0 | lwline_free(tmp); |
618 | 0 | } |
619 | 0 | else if (geom->type == LINETYPE) |
620 | 0 | { |
621 | 0 | tmp = (LWLINE *)geom; |
622 | 0 | for (j = 0; j < tmp->points->npoints; j++) |
623 | 0 | { |
624 | 0 | getPoint4d_p(tmp->points, j, &p); |
625 | 0 | ptarray_append_point(ptarray, &p, LW_TRUE); |
626 | 0 | } |
627 | 0 | } |
628 | 0 | else if (geom->type == NURBSCURVETYPE) |
629 | 0 | { |
630 | 0 | tmp = lwnurbscurve_linearize((LWNURBSCURVE *)geom, tol, tolerance_type, flags); |
631 | 0 | if (!tmp) |
632 | 0 | { |
633 | 0 | lwerror("%s: failed to linearize NURBS curve component", __func__); |
634 | 0 | return NULL; |
635 | 0 | } |
636 | 0 | for (j = 0; j < tmp->points->npoints; j++) |
637 | 0 | { |
638 | 0 | getPoint4d_p(tmp->points, j, &p); |
639 | 0 | ptarray_append_point(ptarray, &p, LW_TRUE); |
640 | 0 | } |
641 | 0 | lwline_free(tmp); |
642 | 0 | } |
643 | 0 | else |
644 | 0 | { |
645 | 0 | lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(geom->type)); |
646 | 0 | return NULL; |
647 | 0 | } |
648 | 0 | } |
649 | | |
650 | 0 | ptarray_remove_repeated_points_in_place(ptarray, 0.0, 2); |
651 | 0 | return lwline_construct(icompound->srid, NULL, ptarray); |
652 | 0 | } |
653 | | |
654 | | |
655 | | /* |
656 | | * @param icompound input curve polygon |
657 | | * @param tol tolerance, semantic driven by tolerance_type |
658 | | * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE |
659 | | * @param flags see flags in lwarc_linearize |
660 | | * |
661 | | * @return a newly allocated LWPOLY |
662 | | */ |
663 | | static LWPOLY * |
664 | | lwcurvepoly_linearize(const LWCURVEPOLY *curvepoly, double tol, |
665 | | LW_LINEARIZE_TOLERANCE_TYPE tolerance_type, |
666 | | int flags) |
667 | 0 | { |
668 | 0 | LWPOLY *ogeom; |
669 | 0 | LWGEOM *tmp; |
670 | 0 | LWLINE *line; |
671 | 0 | POINTARRAY **ptarray; |
672 | 0 | uint32_t i; |
673 | |
|
674 | 0 | LWDEBUG(2, "lwcurvepoly_linearize called."); |
675 | |
|
676 | 0 | ptarray = lwalloc(sizeof(POINTARRAY *)*curvepoly->nrings); |
677 | |
|
678 | 0 | for (i = 0; i < curvepoly->nrings; i++) |
679 | 0 | { |
680 | 0 | tmp = curvepoly->rings[i]; |
681 | 0 | if (tmp->type == CIRCSTRINGTYPE) |
682 | 0 | { |
683 | 0 | line = lwcircstring_linearize((LWCIRCSTRING *)tmp, tol, tolerance_type, flags); |
684 | 0 | ptarray[i] = ptarray_clone_deep(line->points); |
685 | 0 | lwline_free(line); |
686 | 0 | } |
687 | 0 | else if (tmp->type == LINETYPE) |
688 | 0 | { |
689 | 0 | line = (LWLINE *)tmp; |
690 | 0 | ptarray[i] = ptarray_clone_deep(line->points); |
691 | 0 | } |
692 | 0 | else if (tmp->type == COMPOUNDTYPE) |
693 | 0 | { |
694 | 0 | line = lwcompound_linearize((LWCOMPOUND *)tmp, tol, tolerance_type, flags); |
695 | 0 | ptarray[i] = ptarray_clone_deep(line->points); |
696 | 0 | lwline_free(line); |
697 | 0 | } |
698 | 0 | else if (tmp->type == NURBSCURVETYPE) |
699 | 0 | { |
700 | 0 | line = lwnurbscurve_linearize((LWNURBSCURVE *)tmp, tol, tolerance_type, flags); |
701 | 0 | if (!line) |
702 | 0 | { |
703 | 0 | lwerror("%s: failed to linearize NURBS curve ring", __func__); |
704 | 0 | return NULL; |
705 | 0 | } |
706 | 0 | ptarray[i] = ptarray_clone_deep(line->points); |
707 | 0 | lwline_free(line); |
708 | 0 | } |
709 | 0 | else |
710 | 0 | { |
711 | 0 | lwerror("Invalid ring type found in CurvePoly."); |
712 | 0 | return NULL; |
713 | 0 | } |
714 | 0 | } |
715 | | |
716 | 0 | ogeom = lwpoly_construct(curvepoly->srid, NULL, curvepoly->nrings, ptarray); |
717 | 0 | return ogeom; |
718 | 0 | } |
719 | | |
720 | | /* Kept for backward compatibility - TODO: drop */ |
721 | | LWPOLY * |
722 | | lwcurvepoly_stroke(const LWCURVEPOLY *curvepoly, uint32_t perQuad) |
723 | 0 | { |
724 | 0 | return lwcurvepoly_linearize(curvepoly, perQuad, LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD, 0); |
725 | 0 | } |
726 | | |
727 | | |
728 | | /** |
729 | | * @param mcurve input compound curve |
730 | | * @param tol tolerance, semantic driven by tolerance_type |
731 | | * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE |
732 | | * @param flags see flags in lwarc_linearize |
733 | | * |
734 | | * @return a newly allocated LWMLINE |
735 | | */ |
736 | | static LWMLINE * |
737 | | lwmcurve_linearize(const LWMCURVE *mcurve, double tol, |
738 | | LW_LINEARIZE_TOLERANCE_TYPE type, |
739 | | int flags) |
740 | 0 | { |
741 | 0 | LWMLINE *ogeom; |
742 | 0 | LWGEOM **lines; |
743 | 0 | uint32_t i; |
744 | |
|
745 | 0 | LWDEBUGF(2, "lwmcurve_linearize called, geoms=%d, dim=%d.", mcurve->ngeoms, FLAGS_NDIMS(mcurve->flags)); |
746 | |
|
747 | 0 | lines = lwalloc(sizeof(LWGEOM *)*mcurve->ngeoms); |
748 | |
|
749 | 0 | for (i = 0; i < mcurve->ngeoms; i++) |
750 | 0 | { |
751 | 0 | const LWGEOM *tmp = mcurve->geoms[i]; |
752 | 0 | if (tmp->type == CIRCSTRINGTYPE) |
753 | 0 | { |
754 | 0 | lines[i] = (LWGEOM *)lwcircstring_linearize((LWCIRCSTRING *)tmp, tol, type, flags); |
755 | 0 | } |
756 | 0 | else if (tmp->type == LINETYPE) |
757 | 0 | { |
758 | 0 | lines[i] = (LWGEOM *)lwline_construct(mcurve->srid, NULL, ptarray_clone_deep(((LWLINE *)tmp)->points)); |
759 | 0 | } |
760 | 0 | else if (tmp->type == COMPOUNDTYPE) |
761 | 0 | { |
762 | 0 | lines[i] = (LWGEOM *)lwcompound_linearize((LWCOMPOUND *)tmp, tol, type, flags); |
763 | 0 | } |
764 | 0 | else if (tmp->type == NURBSCURVETYPE) |
765 | 0 | { |
766 | 0 | lines[i] = (LWGEOM *)lwnurbscurve_linearize((LWNURBSCURVE *)tmp, tol, type, flags); |
767 | 0 | if (!lines[i]) |
768 | 0 | { |
769 | 0 | lwerror("%s: failed to linearize NURBS curve component", __func__); |
770 | 0 | return NULL; |
771 | 0 | } |
772 | 0 | } |
773 | 0 | else |
774 | 0 | { |
775 | 0 | lwerror("Unsupported geometry found in MultiCurve."); |
776 | 0 | return NULL; |
777 | 0 | } |
778 | 0 | } |
779 | | |
780 | 0 | ogeom = (LWMLINE *)lwcollection_construct(MULTILINETYPE, mcurve->srid, NULL, mcurve->ngeoms, lines); |
781 | 0 | return ogeom; |
782 | 0 | } |
783 | | |
784 | | /** |
785 | | * @param msurface input multi surface |
786 | | * @param tol tolerance, semantic driven by tolerance_type |
787 | | * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE |
788 | | * @param flags see flags in lwarc_linearize |
789 | | * |
790 | | * @return a newly allocated LWMPOLY |
791 | | */ |
792 | | static LWMPOLY * |
793 | | lwmsurface_linearize(const LWMSURFACE *msurface, double tol, |
794 | | LW_LINEARIZE_TOLERANCE_TYPE type, |
795 | | int flags) |
796 | 0 | { |
797 | 0 | LWMPOLY *ogeom; |
798 | 0 | LWGEOM *tmp; |
799 | 0 | LWPOLY *poly; |
800 | 0 | LWGEOM **polys; |
801 | 0 | POINTARRAY **ptarray; |
802 | 0 | uint32_t i, j; |
803 | |
|
804 | 0 | LWDEBUG(2, "lwmsurface_linearize called."); |
805 | |
|
806 | 0 | polys = lwalloc(sizeof(LWGEOM *)*msurface->ngeoms); |
807 | |
|
808 | 0 | for (i = 0; i < msurface->ngeoms; i++) |
809 | 0 | { |
810 | 0 | tmp = msurface->geoms[i]; |
811 | 0 | if (tmp->type == CURVEPOLYTYPE) |
812 | 0 | { |
813 | 0 | polys[i] = (LWGEOM *)lwcurvepoly_linearize((LWCURVEPOLY *)tmp, tol, type, flags); |
814 | 0 | } |
815 | 0 | else if (tmp->type == POLYGONTYPE) |
816 | 0 | { |
817 | 0 | poly = (LWPOLY *)tmp; |
818 | 0 | ptarray = lwalloc(sizeof(POINTARRAY *)*poly->nrings); |
819 | 0 | for (j = 0; j < poly->nrings; j++) |
820 | 0 | { |
821 | 0 | ptarray[j] = ptarray_clone_deep(poly->rings[j]); |
822 | 0 | } |
823 | 0 | polys[i] = (LWGEOM *)lwpoly_construct(msurface->srid, NULL, poly->nrings, ptarray); |
824 | 0 | } |
825 | 0 | } |
826 | 0 | ogeom = (LWMPOLY *)lwcollection_construct(MULTIPOLYGONTYPE, msurface->srid, NULL, msurface->ngeoms, polys); |
827 | 0 | return ogeom; |
828 | 0 | } |
829 | | |
830 | | /** |
831 | | * @param collection input geometry collection |
832 | | * @param tol tolerance, semantic driven by tolerance_type |
833 | | * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE |
834 | | * @param flags see flags in lwarc_linearize |
835 | | * |
836 | | * @return a newly allocated LWCOLLECTION |
837 | | */ |
838 | | static LWCOLLECTION * |
839 | | lwcollection_linearize(const LWCOLLECTION *collection, double tol, |
840 | | LW_LINEARIZE_TOLERANCE_TYPE type, |
841 | | int flags) |
842 | 0 | { |
843 | 0 | LWCOLLECTION *ocol; |
844 | 0 | LWGEOM *tmp; |
845 | 0 | LWGEOM **geoms; |
846 | 0 | uint32_t i; |
847 | |
|
848 | 0 | LWDEBUG(2, "lwcollection_linearize called."); |
849 | |
|
850 | 0 | geoms = lwalloc(sizeof(LWGEOM *)*collection->ngeoms); |
851 | |
|
852 | 0 | for (i=0; i<collection->ngeoms; i++) |
853 | 0 | { |
854 | 0 | tmp = collection->geoms[i]; |
855 | 0 | switch (tmp->type) |
856 | 0 | { |
857 | 0 | case CIRCSTRINGTYPE: |
858 | 0 | geoms[i] = (LWGEOM *)lwcircstring_linearize((LWCIRCSTRING *)tmp, tol, type, flags); |
859 | 0 | break; |
860 | 0 | case NURBSCURVETYPE: |
861 | 0 | geoms[i] = (LWGEOM *)lwnurbscurve_linearize((LWNURBSCURVE *)tmp, tol, type, flags); |
862 | 0 | if (!geoms[i]) |
863 | 0 | { |
864 | 0 | lwerror("%s: failed to linearize NURBS curve element", __func__); |
865 | 0 | return NULL; |
866 | 0 | } |
867 | 0 | break; |
868 | 0 | case COMPOUNDTYPE: |
869 | 0 | geoms[i] = (LWGEOM *)lwcompound_linearize((LWCOMPOUND *)tmp, tol, type, flags); |
870 | 0 | break; |
871 | 0 | case CURVEPOLYTYPE: |
872 | 0 | geoms[i] = (LWGEOM *)lwcurvepoly_linearize((LWCURVEPOLY *)tmp, tol, type, flags); |
873 | 0 | break; |
874 | 0 | case MULTICURVETYPE: |
875 | 0 | case MULTISURFACETYPE: |
876 | 0 | case COLLECTIONTYPE: |
877 | 0 | geoms[i] = (LWGEOM *)lwcollection_linearize((LWCOLLECTION *)tmp, tol, type, flags); |
878 | 0 | break; |
879 | 0 | default: |
880 | 0 | geoms[i] = lwgeom_clone_deep(tmp); |
881 | 0 | break; |
882 | 0 | } |
883 | 0 | } |
884 | 0 | ocol = lwcollection_construct(COLLECTIONTYPE, collection->srid, NULL, collection->ngeoms, geoms); |
885 | 0 | return ocol; |
886 | 0 | } |
887 | | |
888 | | /* |
889 | | * @param curve input NURBS curve |
890 | | * @param tol tolerance, semantic driven by tolerance_type |
891 | | * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE |
892 | | * @param flags see flags in lwarc_linearize |
893 | | * |
894 | | * @return a newly allocated LWLINE |
895 | | */ |
896 | | static LWLINE * |
897 | | lwnurbscurve_linearize(const LWNURBSCURVE *curve, double tol, |
898 | | LW_LINEARIZE_TOLERANCE_TYPE tolerance_type, |
899 | | int flags) |
900 | 0 | { |
901 | 0 | uint32_t num_segments; |
902 | 0 | const double max_segments = (double)UINT32_MAX - 1.0; |
903 | |
|
904 | 0 | LWDEBUG(2, "lwnurbscurve_linearize called."); |
905 | |
|
906 | 0 | if (!curve) |
907 | 0 | return NULL; |
908 | | |
909 | 0 | if (!isfinite(tol) || tol <= 0.0) |
910 | 0 | { |
911 | 0 | lwerror("%s: tolerance must be finite and > 0, got %.15g", __func__, tol); |
912 | 0 | return NULL; |
913 | 0 | } |
914 | | |
915 | 0 | if (!curve->points || curve->points->npoints == 0) |
916 | 0 | return lwnurbscurve_to_linestring(curve, NURBS_MIN_LINEARIZE_SEGMENTS); |
917 | | |
918 | 0 | switch (tolerance_type) |
919 | 0 | { |
920 | 0 | case LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD: |
921 | 0 | if (tol != rint(tol)) |
922 | 0 | { |
923 | 0 | lwerror("%s: segs-per-quad must be an integer, got %.15g", __func__, tol); |
924 | 0 | return NULL; |
925 | 0 | } |
926 | 0 | if (tol > ((double)NURBS_MAX_LINEARIZE_SEGMENTS / 4.0)) |
927 | 0 | { |
928 | 0 | lwerror("%s: segs-per-quad is too large, got %.15g", __func__, tol); |
929 | 0 | return NULL; |
930 | 0 | } |
931 | 0 | num_segments = (uint32_t)(tol * 4); |
932 | 0 | break; |
933 | 0 | case LW_LINEARIZE_TOLERANCE_TYPE_MAX_DEVIATION: { |
934 | 0 | GBOX box; |
935 | 0 | double width = 0.0; |
936 | 0 | double height = 0.0; |
937 | 0 | double depth = 0.0; |
938 | 0 | double diagonal = 0.0; |
939 | 0 | double segments_required; |
940 | |
|
941 | 0 | if (ptarray_calculate_gbox_cartesian(curve->points, &box) == LW_SUCCESS) |
942 | 0 | { |
943 | 0 | width = box.xmax - box.xmin; |
944 | 0 | height = box.ymax - box.ymin; |
945 | 0 | if (FLAGS_GET_Z(curve->flags)) |
946 | 0 | depth = box.zmax - box.zmin; |
947 | 0 | diagonal = sqrt(width * width + height * height + depth * depth); |
948 | 0 | } |
949 | |
|
950 | 0 | segments_required = diagonal / tol; |
951 | 0 | if (!isfinite(segments_required) || segments_required > max_segments) |
952 | 0 | { |
953 | 0 | lwerror("%s: max deviation is too small, got %.15g", __func__, tol); |
954 | 0 | return NULL; |
955 | 0 | } |
956 | 0 | num_segments = diagonal > 0.0 ? (uint32_t)ceil(segments_required) : NURBS_MIN_LINEARIZE_SEGMENTS; |
957 | 0 | break; |
958 | 0 | } |
959 | 0 | case LW_LINEARIZE_TOLERANCE_TYPE_MAX_ANGLE: |
960 | 0 | if (tol < (2.0 * M_PI) / max_segments) |
961 | 0 | { |
962 | 0 | lwerror("%s: max angle is too small, got %.15g", __func__, tol); |
963 | 0 | return NULL; |
964 | 0 | } |
965 | 0 | num_segments = (uint32_t)ceil((2.0 * M_PI) / tol); |
966 | 0 | break; |
967 | 0 | default: |
968 | 0 | lwerror("%s: unsupported tolerance type %d", __func__, tolerance_type); |
969 | 0 | return NULL; |
970 | 0 | } |
971 | | |
972 | 0 | if (num_segments < NURBS_MIN_LINEARIZE_SEGMENTS) |
973 | 0 | num_segments = NURBS_MIN_LINEARIZE_SEGMENTS; |
974 | 0 | if (num_segments > NURBS_MAX_LINEARIZE_SEGMENTS) |
975 | 0 | { |
976 | 0 | lwerror("%s: requested too many segments, got %u", __func__, (unsigned int)num_segments); |
977 | 0 | return NULL; |
978 | 0 | } |
979 | | |
980 | 0 | (void)flags; /* Currently unused for NURBS linearization */ |
981 | |
|
982 | 0 | return lwnurbscurve_to_linestring(curve, num_segments); |
983 | 0 | } |
984 | | |
985 | | LWGEOM * |
986 | | lwcurve_linearize(const LWGEOM *geom, double tol, |
987 | | LW_LINEARIZE_TOLERANCE_TYPE type, |
988 | | int flags) |
989 | 0 | { |
990 | 0 | LWGEOM * ogeom = NULL; |
991 | 0 | switch (geom->type) |
992 | 0 | { |
993 | 0 | case CIRCSTRINGTYPE: |
994 | 0 | ogeom = (LWGEOM *)lwcircstring_linearize((LWCIRCSTRING *)geom, tol, type, flags); |
995 | 0 | break; |
996 | 0 | case NURBSCURVETYPE: |
997 | 0 | ogeom = (LWGEOM *)lwnurbscurve_linearize((LWNURBSCURVE *)geom, tol, type, flags); |
998 | 0 | break; |
999 | 0 | case COMPOUNDTYPE: |
1000 | 0 | ogeom = (LWGEOM *)lwcompound_linearize((LWCOMPOUND *)geom, tol, type, flags); |
1001 | 0 | break; |
1002 | 0 | case CURVEPOLYTYPE: |
1003 | 0 | ogeom = (LWGEOM *)lwcurvepoly_linearize((LWCURVEPOLY *)geom, tol, type, flags); |
1004 | 0 | break; |
1005 | 0 | case MULTICURVETYPE: |
1006 | 0 | ogeom = (LWGEOM *)lwmcurve_linearize((LWMCURVE *)geom, tol, type, flags); |
1007 | 0 | break; |
1008 | 0 | case MULTISURFACETYPE: |
1009 | 0 | ogeom = (LWGEOM *)lwmsurface_linearize((LWMSURFACE *)geom, tol, type, flags); |
1010 | 0 | break; |
1011 | 0 | case COLLECTIONTYPE: |
1012 | 0 | ogeom = (LWGEOM *)lwcollection_linearize((LWCOLLECTION *)geom, tol, type, flags); |
1013 | 0 | break; |
1014 | 0 | default: |
1015 | 0 | ogeom = lwgeom_clone_deep(geom); |
1016 | 0 | } |
1017 | 0 | return ogeom; |
1018 | 0 | } |
1019 | | |
1020 | | /* Kept for backward compatibility - TODO: drop */ |
1021 | | LWGEOM * |
1022 | | lwgeom_stroke(const LWGEOM *geom, uint32_t perQuad) |
1023 | 0 | { |
1024 | 0 | return lwcurve_linearize(geom, perQuad, LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD, 0); |
1025 | 0 | } |
1026 | | |
1027 | | /** |
1028 | | * Return ABC angle in radians |
1029 | | * TODO: move to lwalgorithm |
1030 | | */ |
1031 | | static double |
1032 | | lw_arc_angle(const POINT2D *a, const POINT2D *b, const POINT2D *c) |
1033 | 0 | { |
1034 | 0 | POINT2D ab, cb; |
1035 | |
|
1036 | 0 | ab.x = b->x - a->x; |
1037 | 0 | ab.y = b->y - a->y; |
1038 | |
|
1039 | 0 | cb.x = b->x - c->x; |
1040 | 0 | cb.y = b->y - c->y; |
1041 | |
|
1042 | 0 | double dot = (ab.x * cb.x + ab.y * cb.y); /* dot product */ |
1043 | 0 | double cross = (ab.x * cb.y - ab.y * cb.x); /* cross product */ |
1044 | |
|
1045 | 0 | double alpha = atan2(cross, dot); |
1046 | |
|
1047 | 0 | return alpha; |
1048 | 0 | } |
1049 | | |
1050 | | /** |
1051 | | * Returns LW_TRUE if b is on the arc formed by a1/a2/a3, but not within |
1052 | | * that portion already described by a1/a2/a3 |
1053 | | */ |
1054 | | static int pt_continues_arc(const POINT4D *a1, const POINT4D *a2, const POINT4D *a3, const POINT4D *b) |
1055 | 0 | { |
1056 | 0 | POINT2D center; |
1057 | 0 | POINT2D *t1 = (POINT2D*)a1; |
1058 | 0 | POINT2D *t2 = (POINT2D*)a2; |
1059 | 0 | POINT2D *t3 = (POINT2D*)a3; |
1060 | 0 | POINT2D *tb = (POINT2D*)b; |
1061 | 0 | double radius = lw_arc_center(t1, t2, t3, ¢er); |
1062 | 0 | double b_distance, diff; |
1063 | | |
1064 | | /* Co-linear a1/a2/a3 */ |
1065 | 0 | if ( radius < 0.0 ) |
1066 | 0 | return LW_FALSE; |
1067 | | |
1068 | 0 | b_distance = distance2d_pt_pt(tb, ¢er); |
1069 | 0 | diff = fabs(radius - b_distance); |
1070 | 0 | LWDEBUGF(4, "circle_radius=%g, b_distance=%g, diff=%g, percentage=%g", radius, b_distance, diff, diff/radius); |
1071 | | |
1072 | | /* Is the point b on the circle? */ |
1073 | 0 | if ( diff < EPSILON_SQLMM ) |
1074 | 0 | { |
1075 | 0 | int a2_side = lw_segment_side(t1, t3, t2); |
1076 | 0 | int b_side = lw_segment_side(t1, t3, tb); |
1077 | 0 | double angle1 = lw_arc_angle(t1, t2, t3); |
1078 | 0 | double angle2 = lw_arc_angle(t2, t3, tb); |
1079 | | |
1080 | | /* Is the angle similar to the previous one ? */ |
1081 | 0 | diff = fabs(angle1 - angle2); |
1082 | 0 | LWDEBUGF(4, " angle1: %g, angle2: %g, diff:%g", angle1, angle2, diff); |
1083 | 0 | if ( diff > EPSILON_SQLMM ) |
1084 | 0 | { |
1085 | 0 | return LW_FALSE; |
1086 | 0 | } |
1087 | | |
1088 | | /* Is the point b on the same side of a1/a3 as the mid-point a2 is? */ |
1089 | | /* If not, it's in the unbounded part of the circle, so it continues the arc, return true. */ |
1090 | 0 | if ( b_side != a2_side ) |
1091 | 0 | return LW_TRUE; |
1092 | 0 | } |
1093 | 0 | return LW_FALSE; |
1094 | 0 | } |
1095 | | |
1096 | | static LWGEOM * |
1097 | | linestring_from_pa(const POINTARRAY *pa, int32_t srid, int start, int end) |
1098 | 0 | { |
1099 | 0 | int i = 0, j = 0; |
1100 | 0 | POINT4D p; |
1101 | 0 | POINTARRAY *pao = ptarray_construct(ptarray_has_z(pa), ptarray_has_m(pa), end-start+2); |
1102 | 0 | LWDEBUGF(4, "srid=%d, start=%d, end=%d", srid, start, end); |
1103 | 0 | for( i = start; i < end + 2; i++ ) |
1104 | 0 | { |
1105 | 0 | getPoint4d_p(pa, i, &p); |
1106 | 0 | ptarray_set_point4d(pao, j++, &p); |
1107 | 0 | } |
1108 | 0 | return lwline_as_lwgeom(lwline_construct(srid, NULL, pao)); |
1109 | 0 | } |
1110 | | |
1111 | | static LWGEOM * |
1112 | | circstring_from_pa(const POINTARRAY *pa, int32_t srid, int start, int end) |
1113 | 0 | { |
1114 | |
|
1115 | 0 | POINT4D p0, p1, p2; |
1116 | 0 | POINTARRAY *pao = ptarray_construct(ptarray_has_z(pa), ptarray_has_m(pa), 3); |
1117 | 0 | LWDEBUGF(4, "srid=%d, start=%d, end=%d", srid, start, end); |
1118 | 0 | getPoint4d_p(pa, start, &p0); |
1119 | 0 | ptarray_set_point4d(pao, 0, &p0); |
1120 | 0 | getPoint4d_p(pa, (start+end+1)/2, &p1); |
1121 | 0 | ptarray_set_point4d(pao, 1, &p1); |
1122 | 0 | getPoint4d_p(pa, end+1, &p2); |
1123 | 0 | ptarray_set_point4d(pao, 2, &p2); |
1124 | 0 | return lwcircstring_as_lwgeom(lwcircstring_construct(srid, NULL, pao)); |
1125 | 0 | } |
1126 | | |
1127 | | static LWGEOM * |
1128 | | geom_from_pa(const POINTARRAY *pa, int32_t srid, int is_arc, int start, int end) |
1129 | 0 | { |
1130 | 0 | LWDEBUGF(4, "srid=%d, is_arc=%d, start=%d, end=%d", srid, is_arc, start, end); |
1131 | 0 | if ( is_arc ) |
1132 | 0 | return circstring_from_pa(pa, srid, start, end); |
1133 | 0 | else |
1134 | 0 | return linestring_from_pa(pa, srid, start, end); |
1135 | 0 | } |
1136 | | |
1137 | | LWGEOM * |
1138 | | pta_unstroke(const POINTARRAY *points, int32_t srid) |
1139 | 0 | { |
1140 | 0 | int i = 0, j, k; |
1141 | 0 | POINT4D a1, a2, a3, b; |
1142 | 0 | POINT4D first, center; |
1143 | 0 | char *edges_in_arcs; |
1144 | 0 | int found_arc = LW_FALSE; |
1145 | 0 | int current_arc = 1; |
1146 | 0 | int num_edges; |
1147 | 0 | int edge_type; /* non-zero if edge is part of an arc */ |
1148 | 0 | int start, end; |
1149 | 0 | LWCOLLECTION *outcol; |
1150 | | /* Minimum number of edges, per quadrant, required to define an arc */ |
1151 | 0 | const unsigned int min_quad_edges = 2; |
1152 | | |
1153 | | /* Die on null input */ |
1154 | 0 | if ( ! points ) |
1155 | 0 | lwerror("pta_unstroke called with null pointarray"); |
1156 | | |
1157 | | /* Null on empty input? */ |
1158 | 0 | if ( points->npoints == 0 ) |
1159 | 0 | return NULL; |
1160 | | |
1161 | | /* We can't desegmentize anything shorter than four points */ |
1162 | 0 | if ( points->npoints < 4 ) |
1163 | 0 | { |
1164 | | /* Return a linestring here*/ |
1165 | 0 | lwerror("pta_unstroke needs implementation for npoints < 4"); |
1166 | 0 | } |
1167 | | |
1168 | | /* Allocate our result array of vertices that are part of arcs */ |
1169 | 0 | num_edges = points->npoints - 1; |
1170 | 0 | edges_in_arcs = lwalloc(num_edges + 1); |
1171 | 0 | memset(edges_in_arcs, 0, num_edges + 1); |
1172 | | |
1173 | | /* We make a candidate arc of the first two edges, */ |
1174 | | /* And then see if the next edge follows it */ |
1175 | 0 | while( i < num_edges-2 ) |
1176 | 0 | { |
1177 | 0 | unsigned int arc_edges; |
1178 | 0 | double num_quadrants; |
1179 | 0 | double angle; |
1180 | |
|
1181 | 0 | found_arc = LW_FALSE; |
1182 | | /* Make candidate arc */ |
1183 | 0 | getPoint4d_p(points, i , &a1); |
1184 | 0 | getPoint4d_p(points, i+1, &a2); |
1185 | 0 | getPoint4d_p(points, i+2, &a3); |
1186 | 0 | memcpy(&first, &a1, sizeof(POINT4D)); |
1187 | |
|
1188 | 0 | for( j = i+3; j < num_edges+1; j++ ) |
1189 | 0 | { |
1190 | 0 | LWDEBUGF(4, "i=%d, j=%d", i, j); |
1191 | 0 | getPoint4d_p(points, j, &b); |
1192 | | /* Does this point fall on our candidate arc? */ |
1193 | 0 | if ( pt_continues_arc(&a1, &a2, &a3, &b) ) |
1194 | 0 | { |
1195 | | /* Yes. Mark this edge and the two preceding it as arc components */ |
1196 | 0 | LWDEBUGF(4, "pt_continues_arc #%d", current_arc); |
1197 | 0 | found_arc = LW_TRUE; |
1198 | 0 | for ( k = j-1; k > j-4; k-- ) |
1199 | 0 | edges_in_arcs[k] = current_arc; |
1200 | 0 | } |
1201 | 0 | else |
1202 | 0 | { |
1203 | | /* No. So we're done with this candidate arc */ |
1204 | 0 | LWDEBUG(4, "pt_continues_arc = false"); |
1205 | 0 | current_arc++; |
1206 | 0 | break; |
1207 | 0 | } |
1208 | | |
1209 | 0 | memcpy(&a1, &a2, sizeof(POINT4D)); |
1210 | 0 | memcpy(&a2, &a3, sizeof(POINT4D)); |
1211 | 0 | memcpy(&a3, &b, sizeof(POINT4D)); |
1212 | 0 | } |
1213 | | /* Jump past all the edges that were added to the arc */ |
1214 | 0 | if ( found_arc ) |
1215 | 0 | { |
1216 | | /* Check if an arc was composed by enough edges to be |
1217 | | * really considered an arc |
1218 | | * See http://trac.osgeo.org/postgis/ticket/2420 |
1219 | | */ |
1220 | 0 | arc_edges = j - 1 - i; |
1221 | 0 | LWDEBUGF(4, "arc defined by %d edges found", arc_edges); |
1222 | 0 | if ( first.x == b.x && first.y == b.y ) { |
1223 | 0 | LWDEBUG(4, "arc is a circle"); |
1224 | 0 | num_quadrants = 4; |
1225 | 0 | } |
1226 | 0 | else { |
1227 | 0 | lw_arc_center((POINT2D*)&first, (POINT2D*)&b, (POINT2D*)&a1, (POINT2D*)¢er); |
1228 | 0 | angle = lw_arc_angle((POINT2D*)&first, (POINT2D*)¢er, (POINT2D*)&b); |
1229 | 0 | int p2_side = lw_segment_side((POINT2D*)&first, (POINT2D*)&a1, (POINT2D*)&b); |
1230 | 0 | if ( p2_side >= 0 ) angle = -angle; |
1231 | |
|
1232 | 0 | if ( angle < 0 ) angle = 2 * M_PI + angle; |
1233 | 0 | num_quadrants = ( 4 * angle ) / ( 2 * M_PI ); |
1234 | 0 | LWDEBUGF(4, "arc angle (%g %g, %g %g, %g %g) is %g (side is %d), quadrants:%g", first.x, first.y, center.x, center.y, b.x, b.y, angle, p2_side, num_quadrants); |
1235 | 0 | } |
1236 | | /* a1 is first point, b is last point */ |
1237 | 0 | if ( arc_edges < min_quad_edges * num_quadrants ) { |
1238 | 0 | LWDEBUGF(4, "Not enough edges for a %g quadrants arc, %g needed", num_quadrants, min_quad_edges * num_quadrants); |
1239 | 0 | for ( k = j-1; k >= i; k-- ) |
1240 | 0 | edges_in_arcs[k] = 0; |
1241 | 0 | } |
1242 | |
|
1243 | 0 | i = j-1; |
1244 | 0 | } |
1245 | 0 | else |
1246 | 0 | { |
1247 | | /* Mark this edge as a linear edge */ |
1248 | 0 | edges_in_arcs[i] = 0; |
1249 | 0 | i = i+1; |
1250 | 0 | } |
1251 | 0 | } |
1252 | |
|
1253 | | #if POSTGIS_DEBUG_LEVEL > 3 |
1254 | | { |
1255 | | char *edgestr = lwalloc(num_edges+1); |
1256 | | for ( i = 0; i < num_edges; i++ ) |
1257 | | { |
1258 | | if ( edges_in_arcs[i] ) |
1259 | | edgestr[i] = 48 + edges_in_arcs[i]; |
1260 | | else |
1261 | | edgestr[i] = '.'; |
1262 | | } |
1263 | | edgestr[num_edges] = 0; |
1264 | | LWDEBUGF(3, "edge pattern %s", edgestr); |
1265 | | lwfree(edgestr); |
1266 | | } |
1267 | | #endif |
1268 | |
|
1269 | 0 | start = 0; |
1270 | 0 | edge_type = edges_in_arcs[0]; |
1271 | 0 | outcol = lwcollection_construct_empty(COMPOUNDTYPE, srid, ptarray_has_z(points), ptarray_has_m(points)); |
1272 | 0 | for( i = 1; i < num_edges; i++ ) |
1273 | 0 | { |
1274 | 0 | if( edge_type != edges_in_arcs[i] ) |
1275 | 0 | { |
1276 | 0 | end = i - 1; |
1277 | 0 | lwcollection_add_lwgeom(outcol, geom_from_pa(points, srid, edge_type, start, end)); |
1278 | 0 | start = i; |
1279 | 0 | edge_type = edges_in_arcs[i]; |
1280 | 0 | } |
1281 | 0 | } |
1282 | 0 | lwfree(edges_in_arcs); /* not needed anymore */ |
1283 | | |
1284 | | /* Roll out last item */ |
1285 | 0 | end = num_edges - 1; |
1286 | 0 | lwcollection_add_lwgeom(outcol, geom_from_pa(points, srid, edge_type, start, end)); |
1287 | | |
1288 | | /* Strip down to singleton if only one entry */ |
1289 | 0 | if ( outcol->ngeoms == 1 ) |
1290 | 0 | { |
1291 | 0 | LWGEOM *outgeom = outcol->geoms[0]; |
1292 | 0 | outcol->ngeoms = 0; lwcollection_free(outcol); |
1293 | 0 | return outgeom; |
1294 | 0 | } |
1295 | 0 | return lwcollection_as_lwgeom(outcol); |
1296 | 0 | } |
1297 | | |
1298 | | |
1299 | | LWGEOM * |
1300 | | lwline_unstroke(const LWLINE *line) |
1301 | 0 | { |
1302 | 0 | LWDEBUG(2, "lwline_unstroke called."); |
1303 | |
|
1304 | 0 | if ( line->points->npoints < 4 ) return lwline_as_lwgeom(lwline_clone_deep(line)); |
1305 | 0 | else return pta_unstroke(line->points, line->srid); |
1306 | 0 | } |
1307 | | |
1308 | | LWGEOM * |
1309 | | lwpolygon_unstroke(const LWPOLY *poly) |
1310 | 0 | { |
1311 | 0 | LWGEOM **geoms; |
1312 | 0 | uint32_t i, hascurve = 0; |
1313 | |
|
1314 | 0 | LWDEBUG(2, "lwpolygon_unstroke called."); |
1315 | |
|
1316 | 0 | geoms = lwalloc(sizeof(LWGEOM *)*poly->nrings); |
1317 | 0 | for (i=0; i<poly->nrings; i++) |
1318 | 0 | { |
1319 | 0 | geoms[i] = pta_unstroke(poly->rings[i], poly->srid); |
1320 | 0 | if (geoms[i]->type == CIRCSTRINGTYPE || geoms[i]->type == COMPOUNDTYPE) |
1321 | 0 | { |
1322 | 0 | hascurve = 1; |
1323 | 0 | } |
1324 | 0 | } |
1325 | 0 | if (hascurve == 0) |
1326 | 0 | { |
1327 | 0 | for (i=0; i<poly->nrings; i++) |
1328 | 0 | { |
1329 | 0 | lwfree(geoms[i]); /* TODO: should this be lwgeom_free instead ? */ |
1330 | 0 | } |
1331 | 0 | return lwgeom_clone_deep((LWGEOM *)poly); |
1332 | 0 | } |
1333 | | |
1334 | 0 | return (LWGEOM *)lwcollection_construct(CURVEPOLYTYPE, poly->srid, NULL, poly->nrings, geoms); |
1335 | 0 | } |
1336 | | |
1337 | | LWGEOM * |
1338 | | lwmline_unstroke(const LWMLINE *mline) |
1339 | 0 | { |
1340 | 0 | LWGEOM **geoms; |
1341 | 0 | uint32_t i, hascurve = 0; |
1342 | |
|
1343 | 0 | LWDEBUG(2, "lwmline_unstroke called."); |
1344 | |
|
1345 | 0 | geoms = lwalloc(sizeof(LWGEOM *)*mline->ngeoms); |
1346 | 0 | for (i=0; i<mline->ngeoms; i++) |
1347 | 0 | { |
1348 | 0 | geoms[i] = lwline_unstroke((LWLINE *)mline->geoms[i]); |
1349 | 0 | if (geoms[i]->type == CIRCSTRINGTYPE || geoms[i]->type == COMPOUNDTYPE) |
1350 | 0 | { |
1351 | 0 | hascurve = 1; |
1352 | 0 | } |
1353 | 0 | } |
1354 | 0 | if (hascurve == 0) |
1355 | 0 | { |
1356 | 0 | for (i=0; i<mline->ngeoms; i++) |
1357 | 0 | { |
1358 | 0 | lwfree(geoms[i]); /* TODO: should this be lwgeom_free instead ? */ |
1359 | 0 | } |
1360 | 0 | return lwgeom_clone_deep((LWGEOM *)mline); |
1361 | 0 | } |
1362 | 0 | return (LWGEOM *)lwcollection_construct(MULTICURVETYPE, mline->srid, NULL, mline->ngeoms, geoms); |
1363 | 0 | } |
1364 | | |
1365 | | LWGEOM * |
1366 | | lwmpolygon_unstroke(const LWMPOLY *mpoly) |
1367 | 0 | { |
1368 | 0 | LWGEOM **geoms; |
1369 | 0 | uint32_t i, hascurve = 0; |
1370 | |
|
1371 | 0 | LWDEBUG(2, "lwmpoly_unstroke called."); |
1372 | |
|
1373 | 0 | geoms = lwalloc(sizeof(LWGEOM *)*mpoly->ngeoms); |
1374 | 0 | for (i=0; i<mpoly->ngeoms; i++) |
1375 | 0 | { |
1376 | 0 | geoms[i] = lwpolygon_unstroke((LWPOLY *)mpoly->geoms[i]); |
1377 | 0 | if (geoms[i]->type == CURVEPOLYTYPE) |
1378 | 0 | { |
1379 | 0 | hascurve = 1; |
1380 | 0 | } |
1381 | 0 | } |
1382 | 0 | if (hascurve == 0) |
1383 | 0 | { |
1384 | 0 | for (i=0; i<mpoly->ngeoms; i++) |
1385 | 0 | { |
1386 | 0 | lwfree(geoms[i]); /* TODO: should this be lwgeom_free instead ? */ |
1387 | 0 | } |
1388 | 0 | return lwgeom_clone_deep((LWGEOM *)mpoly); |
1389 | 0 | } |
1390 | 0 | return (LWGEOM *)lwcollection_construct(MULTISURFACETYPE, mpoly->srid, NULL, mpoly->ngeoms, geoms); |
1391 | 0 | } |
1392 | | |
1393 | | LWGEOM * |
1394 | | lwcollection_unstroke(const LWCOLLECTION *c) |
1395 | 0 | { |
1396 | 0 | LWCOLLECTION *ret = lwalloc(sizeof(LWCOLLECTION)); |
1397 | 0 | memcpy(ret, c, sizeof(LWCOLLECTION)); |
1398 | |
|
1399 | 0 | if (c->ngeoms > 0) |
1400 | 0 | { |
1401 | 0 | uint32_t i; |
1402 | 0 | ret->geoms = lwalloc(sizeof(LWGEOM *)*c->ngeoms); |
1403 | 0 | for (i=0; i < c->ngeoms; i++) |
1404 | 0 | { |
1405 | 0 | ret->geoms[i] = lwgeom_unstroke(c->geoms[i]); |
1406 | 0 | } |
1407 | 0 | if (c->bbox) |
1408 | 0 | { |
1409 | 0 | ret->bbox = gbox_copy(c->bbox); |
1410 | 0 | } |
1411 | 0 | } |
1412 | 0 | else |
1413 | 0 | { |
1414 | 0 | ret->bbox = NULL; |
1415 | 0 | ret->geoms = NULL; |
1416 | 0 | } |
1417 | 0 | return (LWGEOM *)ret; |
1418 | 0 | } |
1419 | | |
1420 | | |
1421 | | LWGEOM * |
1422 | | lwgeom_unstroke(const LWGEOM *geom) |
1423 | 0 | { |
1424 | 0 | LWDEBUG(2, "lwgeom_unstroke called."); |
1425 | |
|
1426 | 0 | switch (geom->type) |
1427 | 0 | { |
1428 | 0 | case LINETYPE: |
1429 | 0 | return lwline_unstroke((LWLINE *)geom); |
1430 | 0 | case POLYGONTYPE: |
1431 | 0 | return lwpolygon_unstroke((LWPOLY *)geom); |
1432 | 0 | case MULTILINETYPE: |
1433 | 0 | return lwmline_unstroke((LWMLINE *)geom); |
1434 | 0 | case MULTIPOLYGONTYPE: |
1435 | 0 | return lwmpolygon_unstroke((LWMPOLY *)geom); |
1436 | 0 | case COLLECTIONTYPE: |
1437 | 0 | return lwcollection_unstroke((LWCOLLECTION *)geom); |
1438 | 0 | default: |
1439 | 0 | return lwgeom_clone_deep(geom); |
1440 | 0 | } |
1441 | 0 | } |