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