Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: OpenGIS Simple Features Reference Implementation
4 : * Purpose: Factory for converting geometry to and from well known binary
5 : * format.
6 : * Author: Frank Warmerdam, warmerdam@pobox.com
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 1999, Frank Warmerdam
10 : * Copyright (c) 2008-2014, Even Rouault <even dot rouault at spatialys dot com>
11 : *
12 : * SPDX-License-Identifier: MIT
13 : ****************************************************************************/
14 :
15 : #include "cpl_port.h"
16 : #include "cpl_quad_tree.h"
17 :
18 : #include "cpl_conv.h"
19 : #include "cpl_error.h"
20 : #include "cpl_string.h"
21 : #include "ogr_geometry.h"
22 : #include "ogr_api.h"
23 : #include "ogr_core.h"
24 : #include "ogr_geos.h"
25 : #include "ogr_sfcgal.h"
26 : #include "ogr_p.h"
27 : #include "ogr_spatialref.h"
28 : #include "ogr_srs_api.h"
29 : #ifdef HAVE_GEOS
30 : #include "ogr_geos.h"
31 : #endif
32 :
33 : #include "ogrgeojsongeometry.h"
34 :
35 : #include <cassert>
36 : #include <climits>
37 : #include <cmath>
38 : #include <cstdlib>
39 : #include <cstring>
40 : #include <cstddef>
41 :
42 : #include <algorithm>
43 : #include <limits>
44 : #include <new>
45 : #include <utility>
46 : #include <vector>
47 :
48 : #ifndef HAVE_GEOS
49 : #define UNUSED_IF_NO_GEOS CPL_UNUSED
50 : #else
51 : #define UNUSED_IF_NO_GEOS
52 : #endif
53 :
54 : /************************************************************************/
55 : /* createFromWkb() */
56 : /************************************************************************/
57 :
58 : /**
59 : * \brief Create a geometry object of the appropriate type from its
60 : * well known binary representation.
61 : *
62 : * Note that if nBytes is passed as zero, no checking can be done on whether
63 : * the pabyData is sufficient. This can result in a crash if the input
64 : * data is corrupt. This function returns no indication of the number of
65 : * bytes from the data source actually used to represent the returned
66 : * geometry object. Use OGRGeometry::WkbSize() on the returned geometry to
67 : * establish the number of bytes it required in WKB format.
68 : *
69 : * Also note that this is a static method, and that there
70 : * is no need to instantiate an OGRGeometryFactory object.
71 : *
72 : * The C function OGR_G_CreateFromWkb() is the same as this method.
73 : *
74 : * @param pabyData pointer to the input BLOB data.
75 : * @param poSR pointer to the spatial reference to be assigned to the
76 : * created geometry object. This may be NULL.
77 : * @param ppoReturn the newly created geometry object will be assigned to the
78 : * indicated pointer on return. This will be NULL in case
79 : * of failure. If not NULL, *ppoReturn should be freed with
80 : * OGRGeometryFactory::destroyGeometry() after use.
81 : * @param nBytes the number of bytes available in pabyData, or -1 if it isn't
82 : * known
83 : * @param eWkbVariant WKB variant.
84 : *
85 : * @return OGRERR_NONE if all goes well, otherwise any of
86 : * OGRERR_NOT_ENOUGH_DATA, OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or
87 : * OGRERR_CORRUPT_DATA may be returned.
88 : */
89 :
90 60799 : OGRErr OGRGeometryFactory::createFromWkb(const void *pabyData,
91 : const OGRSpatialReference *poSR,
92 : OGRGeometry **ppoReturn, size_t nBytes,
93 : OGRwkbVariant eWkbVariant)
94 :
95 : {
96 60799 : size_t nBytesConsumedOutIgnored = 0;
97 60799 : return createFromWkb(pabyData, poSR, ppoReturn, nBytes, eWkbVariant,
98 121598 : nBytesConsumedOutIgnored);
99 : }
100 :
101 : /**
102 : * \brief Create a geometry object of the appropriate type from its
103 : * well known binary representation.
104 : *
105 : * Note that if nBytes is passed as zero, no checking can be done on whether
106 : * the pabyData is sufficient. This can result in a crash if the input
107 : * data is corrupt. This function returns no indication of the number of
108 : * bytes from the data source actually used to represent the returned
109 : * geometry object. Use OGRGeometry::WkbSize() on the returned geometry to
110 : * establish the number of bytes it required in WKB format.
111 : *
112 : * Also note that this is a static method, and that there
113 : * is no need to instantiate an OGRGeometryFactory object.
114 : *
115 : * The C function OGR_G_CreateFromWkb() is the same as this method.
116 : *
117 : * @param pabyData pointer to the input BLOB data.
118 : * @param poSR pointer to the spatial reference to be assigned to the
119 : * created geometry object. This may be NULL.
120 : * @param ppoReturn the newly created geometry object will be assigned to the
121 : * indicated pointer on return. This will be NULL in case
122 : * of failure. If not NULL, *ppoReturn should be freed with
123 : * OGRGeometryFactory::destroyGeometry() after use.
124 : * @param nBytes the number of bytes available in pabyData, or -1 if it isn't
125 : * known
126 : * @param eWkbVariant WKB variant.
127 : * @param nBytesConsumedOut output parameter. Number of bytes consumed.
128 : *
129 : * @return OGRERR_NONE if all goes well, otherwise any of
130 : * OGRERR_NOT_ENOUGH_DATA, OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or
131 : * OGRERR_CORRUPT_DATA may be returned.
132 : */
133 :
134 100022 : OGRErr OGRGeometryFactory::createFromWkb(const void *pabyData,
135 : const OGRSpatialReference *poSR,
136 : OGRGeometry **ppoReturn, size_t nBytes,
137 : OGRwkbVariant eWkbVariant,
138 : size_t &nBytesConsumedOut)
139 :
140 : {
141 100022 : const GByte *l_pabyData = static_cast<const GByte *>(pabyData);
142 100022 : nBytesConsumedOut = 0;
143 100022 : *ppoReturn = nullptr;
144 :
145 100022 : if (nBytes < 9 && nBytes != static_cast<size_t>(-1))
146 1394 : return OGRERR_NOT_ENOUGH_DATA;
147 :
148 : /* -------------------------------------------------------------------- */
149 : /* Get the byte order byte. The extra tests are to work around */
150 : /* bug sin the WKB of DB2 v7.2 as identified by Safe Software. */
151 : /* -------------------------------------------------------------------- */
152 98628 : const int nByteOrder = DB2_V72_FIX_BYTE_ORDER(*l_pabyData);
153 98628 : if (nByteOrder != wkbXDR && nByteOrder != wkbNDR)
154 : {
155 295 : CPLDebug("OGR",
156 : "OGRGeometryFactory::createFromWkb() - got corrupt data.\n"
157 : "%02X%02X%02X%02X%02X%02X%02X%02X%02X",
158 295 : l_pabyData[0], l_pabyData[1], l_pabyData[2], l_pabyData[3],
159 295 : l_pabyData[4], l_pabyData[5], l_pabyData[6], l_pabyData[7],
160 295 : l_pabyData[8]);
161 295 : return OGRERR_CORRUPT_DATA;
162 : }
163 :
164 : /* -------------------------------------------------------------------- */
165 : /* Get the geometry feature type. For now we assume that */
166 : /* geometry type is between 0 and 255 so we only have to fetch */
167 : /* one byte. */
168 : /* -------------------------------------------------------------------- */
169 :
170 98333 : OGRwkbGeometryType eGeometryType = wkbUnknown;
171 : const OGRErr err =
172 98333 : OGRReadWKBGeometryType(l_pabyData, eWkbVariant, &eGeometryType);
173 :
174 98333 : if (err != OGRERR_NONE)
175 563 : return err;
176 :
177 : /* -------------------------------------------------------------------- */
178 : /* Instantiate a geometry of the appropriate type, and */
179 : /* initialize from the input stream. */
180 : /* -------------------------------------------------------------------- */
181 97770 : OGRGeometry *poGeom = createGeometry(eGeometryType);
182 :
183 97770 : if (poGeom == nullptr)
184 0 : return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
185 :
186 : /* -------------------------------------------------------------------- */
187 : /* Import from binary. */
188 : /* -------------------------------------------------------------------- */
189 195540 : const OGRErr eErr = poGeom->importFromWkb(l_pabyData, nBytes, eWkbVariant,
190 97770 : nBytesConsumedOut);
191 97770 : if (eErr != OGRERR_NONE)
192 : {
193 7315 : delete poGeom;
194 7315 : return eErr;
195 : }
196 :
197 : /* -------------------------------------------------------------------- */
198 : /* Assign spatial reference system. */
199 : /* -------------------------------------------------------------------- */
200 :
201 94016 : if (poGeom->hasCurveGeometry() &&
202 3561 : CPLTestBool(CPLGetConfigOption("OGR_STROKE_CURVE", "FALSE")))
203 : {
204 5 : OGRGeometry *poNewGeom = poGeom->getLinearGeometry();
205 5 : delete poGeom;
206 5 : poGeom = poNewGeom;
207 : }
208 90455 : poGeom->assignSpatialReference(poSR);
209 90455 : *ppoReturn = poGeom;
210 :
211 90455 : return OGRERR_NONE;
212 : }
213 :
214 : /************************************************************************/
215 : /* OGR_G_CreateFromWkb() */
216 : /************************************************************************/
217 : /**
218 : * \brief Create a geometry object of the appropriate type from its
219 : * well known binary representation.
220 : *
221 : * Note that if nBytes is passed as zero, no checking can be done on whether
222 : * the pabyData is sufficient. This can result in a crash if the input
223 : * data is corrupt. This function returns no indication of the number of
224 : * bytes from the data source actually used to represent the returned
225 : * geometry object. Use OGR_G_WkbSize() on the returned geometry to
226 : * establish the number of bytes it required in WKB format.
227 : *
228 : * The OGRGeometryFactory::createFromWkb() CPP method is the same as this
229 : * function.
230 : *
231 : * @param pabyData pointer to the input BLOB data.
232 : * @param hSRS handle to the spatial reference to be assigned to the
233 : * created geometry object. This may be NULL.
234 : * @param phGeometry the newly created geometry object will
235 : * be assigned to the indicated handle on return. This will be NULL in case
236 : * of failure. If not NULL, *phGeometry should be freed with
237 : * OGR_G_DestroyGeometry() after use.
238 : * @param nBytes the number of bytes of data available in pabyData, or -1
239 : * if it is not known, but assumed to be sufficient.
240 : *
241 : * @return OGRERR_NONE if all goes well, otherwise any of
242 : * OGRERR_NOT_ENOUGH_DATA, OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or
243 : * OGRERR_CORRUPT_DATA may be returned.
244 : */
245 :
246 2 : OGRErr CPL_DLL OGR_G_CreateFromWkb(const void *pabyData,
247 : OGRSpatialReferenceH hSRS,
248 : OGRGeometryH *phGeometry, int nBytes)
249 :
250 : {
251 2 : return OGRGeometryFactory::createFromWkb(
252 2 : pabyData, OGRSpatialReference::FromHandle(hSRS),
253 2 : reinterpret_cast<OGRGeometry **>(phGeometry), nBytes);
254 : }
255 :
256 : /************************************************************************/
257 : /* OGR_G_CreateFromWkbEx() */
258 : /************************************************************************/
259 : /**
260 : * \brief Create a geometry object of the appropriate type from its
261 : * well known binary representation.
262 : *
263 : * Note that if nBytes is passed as zero, no checking can be done on whether
264 : * the pabyData is sufficient. This can result in a crash if the input
265 : * data is corrupt. This function returns no indication of the number of
266 : * bytes from the data source actually used to represent the returned
267 : * geometry object. Use OGR_G_WkbSizeEx() on the returned geometry to
268 : * establish the number of bytes it required in WKB format.
269 : *
270 : * The OGRGeometryFactory::createFromWkb() CPP method is the same as this
271 : * function.
272 : *
273 : * @param pabyData pointer to the input BLOB data.
274 : * @param hSRS handle to the spatial reference to be assigned to the
275 : * created geometry object. This may be NULL.
276 : * @param phGeometry the newly created geometry object will
277 : * be assigned to the indicated handle on return. This will be NULL in case
278 : * of failure. If not NULL, *phGeometry should be freed with
279 : * OGR_G_DestroyGeometry() after use.
280 : * @param nBytes the number of bytes of data available in pabyData, or -1
281 : * if it is not known, but assumed to be sufficient.
282 : *
283 : * @return OGRERR_NONE if all goes well, otherwise any of
284 : * OGRERR_NOT_ENOUGH_DATA, OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or
285 : * OGRERR_CORRUPT_DATA may be returned.
286 : * @since GDAL 3.3
287 : */
288 :
289 31027 : OGRErr CPL_DLL OGR_G_CreateFromWkbEx(const void *pabyData,
290 : OGRSpatialReferenceH hSRS,
291 : OGRGeometryH *phGeometry, size_t nBytes)
292 :
293 : {
294 31027 : return OGRGeometryFactory::createFromWkb(
295 31027 : pabyData, OGRSpatialReference::FromHandle(hSRS),
296 31027 : reinterpret_cast<OGRGeometry **>(phGeometry), nBytes);
297 : }
298 :
299 : /************************************************************************/
300 : /* createFromWkt() */
301 : /************************************************************************/
302 :
303 : /**
304 : * \brief Create a geometry object of the appropriate type from its
305 : * well known text representation.
306 : *
307 : * The C function OGR_G_CreateFromWkt() is the same as this method.
308 : *
309 : * @param ppszData input zero terminated string containing well known text
310 : * representation of the geometry to be created. The pointer
311 : * is updated to point just beyond that last character consumed.
312 : * @param poSR pointer to the spatial reference to be assigned to the
313 : * created geometry object. This may be NULL.
314 : * @param ppoReturn the newly created geometry object will be assigned to the
315 : * indicated pointer on return. This will be NULL if the
316 : * method fails. If not NULL, *ppoReturn should be freed with
317 : * OGRGeometryFactory::destroyGeometry() after use.
318 : *
319 : * <b>Example:</b>
320 : *
321 : * \code{.cpp}
322 : * const char* wkt= "POINT(0 0)";
323 : *
324 : * // cast because OGR_G_CreateFromWkt will move the pointer
325 : * char* pszWkt = (char*) wkt;
326 : * OGRSpatialReferenceH ref = OSRNewSpatialReference(NULL);
327 : * OGRGeometryH new_geom;
328 : * OSRSetAxisMappingStrategy(poSR, OAMS_TRADITIONAL_GIS_ORDER);
329 : * OGRErr err = OGR_G_CreateFromWkt(&pszWkt, ref, &new_geom);
330 : * \endcode
331 : *
332 : *
333 : *
334 : * @return OGRERR_NONE if all goes well, otherwise any of
335 : * OGRERR_NOT_ENOUGH_DATA, OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or
336 : * OGRERR_CORRUPT_DATA may be returned.
337 : */
338 :
339 124317 : OGRErr OGRGeometryFactory::createFromWkt(const char **ppszData,
340 : const OGRSpatialReference *poSR,
341 : OGRGeometry **ppoReturn)
342 :
343 : {
344 124317 : const char *pszInput = *ppszData;
345 124317 : *ppoReturn = nullptr;
346 :
347 : /* -------------------------------------------------------------------- */
348 : /* Get the first token, which should be the geometry type. */
349 : /* -------------------------------------------------------------------- */
350 124317 : char szToken[OGR_WKT_TOKEN_MAX] = {};
351 124317 : if (OGRWktReadToken(pszInput, szToken) == nullptr)
352 0 : return OGRERR_CORRUPT_DATA;
353 :
354 : /* -------------------------------------------------------------------- */
355 : /* Instantiate a geometry of the appropriate type. */
356 : /* -------------------------------------------------------------------- */
357 124317 : OGRGeometry *poGeom = nullptr;
358 124317 : if (STARTS_WITH_CI(szToken, "POINT"))
359 : {
360 97503 : poGeom = new OGRPoint();
361 : }
362 26814 : else if (STARTS_WITH_CI(szToken, "LINESTRING"))
363 : {
364 1675 : poGeom = new OGRLineString();
365 : }
366 25139 : else if (STARTS_WITH_CI(szToken, "POLYGON"))
367 : {
368 16590 : poGeom = new OGRPolygon();
369 : }
370 8549 : else if (STARTS_WITH_CI(szToken, "TRIANGLE"))
371 : {
372 62 : poGeom = new OGRTriangle();
373 : }
374 8487 : else if (STARTS_WITH_CI(szToken, "GEOMETRYCOLLECTION"))
375 : {
376 527 : poGeom = new OGRGeometryCollection();
377 : }
378 7960 : else if (STARTS_WITH_CI(szToken, "MULTIPOLYGON"))
379 : {
380 950 : poGeom = new OGRMultiPolygon();
381 : }
382 7010 : else if (STARTS_WITH_CI(szToken, "MULTIPOINT"))
383 : {
384 597 : poGeom = new OGRMultiPoint();
385 : }
386 6413 : else if (STARTS_WITH_CI(szToken, "MULTILINESTRING"))
387 : {
388 640 : poGeom = new OGRMultiLineString();
389 : }
390 5773 : else if (STARTS_WITH_CI(szToken, "CIRCULARSTRING"))
391 : {
392 3546 : poGeom = new OGRCircularString();
393 : }
394 2227 : else if (STARTS_WITH_CI(szToken, "COMPOUNDCURVE"))
395 : {
396 314 : poGeom = new OGRCompoundCurve();
397 : }
398 1913 : else if (STARTS_WITH_CI(szToken, "CURVEPOLYGON"))
399 : {
400 331 : poGeom = new OGRCurvePolygon();
401 : }
402 1582 : else if (STARTS_WITH_CI(szToken, "MULTICURVE"))
403 : {
404 145 : poGeom = new OGRMultiCurve();
405 : }
406 1437 : else if (STARTS_WITH_CI(szToken, "MULTISURFACE"))
407 : {
408 161 : poGeom = new OGRMultiSurface();
409 : }
410 :
411 1276 : else if (STARTS_WITH_CI(szToken, "POLYHEDRALSURFACE"))
412 : {
413 70 : poGeom = new OGRPolyhedralSurface();
414 : }
415 :
416 1206 : else if (STARTS_WITH_CI(szToken, "TIN"))
417 : {
418 123 : poGeom = new OGRTriangulatedSurface();
419 : }
420 :
421 : else
422 : {
423 1083 : return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
424 : }
425 :
426 : /* -------------------------------------------------------------------- */
427 : /* Do the import. */
428 : /* -------------------------------------------------------------------- */
429 123234 : const OGRErr eErr = poGeom->importFromWkt(&pszInput);
430 :
431 : /* -------------------------------------------------------------------- */
432 : /* Assign spatial reference system. */
433 : /* -------------------------------------------------------------------- */
434 123234 : if (eErr == OGRERR_NONE)
435 : {
436 127465 : if (poGeom->hasCurveGeometry() &&
437 4470 : CPLTestBool(CPLGetConfigOption("OGR_STROKE_CURVE", "FALSE")))
438 : {
439 9 : OGRGeometry *poNewGeom = poGeom->getLinearGeometry();
440 9 : delete poGeom;
441 9 : poGeom = poNewGeom;
442 : }
443 122995 : poGeom->assignSpatialReference(poSR);
444 122995 : *ppoReturn = poGeom;
445 122995 : *ppszData = pszInput;
446 : }
447 : else
448 : {
449 239 : delete poGeom;
450 : }
451 :
452 123234 : return eErr;
453 : }
454 :
455 : /**
456 : * \brief Create a geometry object of the appropriate type from its
457 : * well known text representation.
458 : *
459 : * The C function OGR_G_CreateFromWkt() is the same as this method.
460 : *
461 : * @param pszData input zero terminated string containing well known text
462 : * representation of the geometry to be created.
463 : * @param poSR pointer to the spatial reference to be assigned to the
464 : * created geometry object. This may be NULL.
465 : * @param ppoReturn the newly created geometry object will be assigned to the
466 : * indicated pointer on return. This will be NULL if the
467 : * method fails. If not NULL, *ppoReturn should be freed with
468 : * OGRGeometryFactory::destroyGeometry() after use.
469 :
470 : * @return OGRERR_NONE if all goes well, otherwise any of
471 : * OGRERR_NOT_ENOUGH_DATA, OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or
472 : * OGRERR_CORRUPT_DATA may be returned.
473 : */
474 :
475 2087 : OGRErr OGRGeometryFactory::createFromWkt(const char *pszData,
476 : const OGRSpatialReference *poSR,
477 : OGRGeometry **ppoReturn)
478 :
479 : {
480 2087 : return createFromWkt(&pszData, poSR, ppoReturn);
481 : }
482 :
483 : /**
484 : * \brief Create a geometry object of the appropriate type from its
485 : * well known text representation.
486 : *
487 : * The C function OGR_G_CreateFromWkt() is the same as this method.
488 : *
489 : * @param pszData input zero terminated string containing well known text
490 : * representation of the geometry to be created.
491 : * @param poSR pointer to the spatial reference to be assigned to the
492 : * created geometry object. This may be NULL.
493 :
494 : * @return a pair of the newly created geometry an error code of OGRERR_NONE
495 : * if all goes well, otherwise any of OGRERR_NOT_ENOUGH_DATA,
496 : * OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or OGRERR_CORRUPT_DATA.
497 : *
498 : * @since GDAL 3.11
499 : */
500 :
501 : std::pair<std::unique_ptr<OGRGeometry>, OGRErr>
502 3840 : OGRGeometryFactory::createFromWkt(const char *pszData,
503 : const OGRSpatialReference *poSR)
504 :
505 : {
506 3840 : std::unique_ptr<OGRGeometry> poGeom;
507 : OGRGeometry *poTmpGeom;
508 3840 : auto err = createFromWkt(&pszData, poSR, &poTmpGeom);
509 3840 : poGeom.reset(poTmpGeom);
510 :
511 7680 : return {std::move(poGeom), err};
512 : }
513 :
514 : /************************************************************************/
515 : /* OGR_G_CreateFromWkt() */
516 : /************************************************************************/
517 : /**
518 : * \brief Create a geometry object of the appropriate type from its well known
519 : * text representation.
520 : *
521 : * The OGRGeometryFactory::createFromWkt CPP method is the same as this
522 : * function.
523 : *
524 : * @param ppszData input zero terminated string containing well known text
525 : * representation of the geometry to be created. The pointer
526 : * is updated to point just beyond that last character consumed.
527 : * @param hSRS handle to the spatial reference to be assigned to the
528 : * created geometry object. This may be NULL.
529 : * @param phGeometry the newly created geometry object will be assigned to the
530 : * indicated handle on return. This will be NULL if the
531 : * method fails. If not NULL, *phGeometry should be freed with
532 : * OGR_G_DestroyGeometry() after use.
533 : *
534 : * @return OGRERR_NONE if all goes well, otherwise any of
535 : * OGRERR_NOT_ENOUGH_DATA, OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or
536 : * OGRERR_CORRUPT_DATA may be returned.
537 : */
538 :
539 116675 : OGRErr CPL_DLL OGR_G_CreateFromWkt(char **ppszData, OGRSpatialReferenceH hSRS,
540 : OGRGeometryH *phGeometry)
541 :
542 : {
543 116675 : return OGRGeometryFactory::createFromWkt(
544 : const_cast<const char **>(ppszData),
545 116675 : OGRSpatialReference::FromHandle(hSRS),
546 116675 : reinterpret_cast<OGRGeometry **>(phGeometry));
547 : }
548 :
549 : /************************************************************************/
550 : /* OGR_G_CreateFromEnvelope() */
551 : /************************************************************************/
552 : /**
553 : * \brief Create a Polygon geometry from an envelope
554 : *
555 : *
556 : * @param dfMinX minimum X coordinate
557 : * @param dfMinY minimum Y coordinate
558 : * @param dfMaxX maximum X coordinate
559 : * @param dfMaxY maximum Y coordinate
560 : * @param hSRS handle to the spatial reference to be assigned to the
561 : * created geometry object. This may be NULL.
562 : *
563 : * @return the newly created geometry. Should be freed with
564 : * OGR_G_DestroyGeometry() after use.
565 : * @since 3.12
566 : */
567 :
568 1 : OGRGeometryH CPL_DLL OGR_G_CreateFromEnvelope(double dfMinX, double dfMinY,
569 : double dfMaxX, double dfMaxY,
570 : OGRSpatialReferenceH hSRS)
571 :
572 : {
573 : auto poPolygon =
574 2 : std::make_unique<OGRPolygon>(dfMinX, dfMinY, dfMaxX, dfMaxY);
575 :
576 1 : if (hSRS)
577 : {
578 2 : poPolygon->assignSpatialReference(
579 1 : OGRSpatialReference::FromHandle(hSRS));
580 : }
581 :
582 2 : return OGRGeometry::ToHandle(poPolygon.release());
583 : }
584 :
585 : /************************************************************************/
586 : /* createGeometry() */
587 : /************************************************************************/
588 :
589 : /**
590 : * \brief Create an empty geometry of desired type.
591 : *
592 : * This is equivalent to allocating the desired geometry with new, but
593 : * the allocation is guaranteed to take place in the context of the
594 : * GDAL/OGR heap.
595 : *
596 : * This method is the same as the C function OGR_G_CreateGeometry().
597 : *
598 : * @param eGeometryType the type code of the geometry class to be instantiated.
599 : *
600 : * @return the newly create geometry or NULL on failure. Should be freed with
601 : * OGRGeometryFactory::destroyGeometry() after use.
602 : */
603 :
604 : OGRGeometry *
605 267983 : OGRGeometryFactory::createGeometry(OGRwkbGeometryType eGeometryType)
606 :
607 : {
608 267983 : OGRGeometry *poGeom = nullptr;
609 267983 : switch (wkbFlatten(eGeometryType))
610 : {
611 184606 : case wkbPoint:
612 369212 : poGeom = new (std::nothrow) OGRPoint();
613 184606 : break;
614 :
615 12067 : case wkbLineString:
616 24134 : poGeom = new (std::nothrow) OGRLineString();
617 12067 : break;
618 :
619 29989 : case wkbPolygon:
620 59978 : poGeom = new (std::nothrow) OGRPolygon();
621 29989 : break;
622 :
623 2052 : case wkbGeometryCollection:
624 4104 : poGeom = new (std::nothrow) OGRGeometryCollection();
625 2052 : break;
626 :
627 3270 : case wkbMultiPolygon:
628 6540 : poGeom = new (std::nothrow) OGRMultiPolygon();
629 3270 : break;
630 :
631 1436 : case wkbMultiPoint:
632 2872 : poGeom = new (std::nothrow) OGRMultiPoint();
633 1436 : break;
634 :
635 1976 : case wkbMultiLineString:
636 3952 : poGeom = new (std::nothrow) OGRMultiLineString();
637 1976 : break;
638 :
639 61 : case wkbLinearRing:
640 122 : poGeom = new (std::nothrow) OGRLinearRing();
641 61 : break;
642 :
643 69 : case wkbCircularString:
644 138 : poGeom = new (std::nothrow) OGRCircularString();
645 69 : break;
646 :
647 1982 : case wkbCompoundCurve:
648 3964 : poGeom = new (std::nothrow) OGRCompoundCurve();
649 1982 : break;
650 :
651 46 : case wkbCurvePolygon:
652 92 : poGeom = new (std::nothrow) OGRCurvePolygon();
653 46 : break;
654 :
655 1121 : case wkbMultiCurve:
656 2242 : poGeom = new (std::nothrow) OGRMultiCurve();
657 1121 : break;
658 :
659 1183 : case wkbMultiSurface:
660 2366 : poGeom = new (std::nothrow) OGRMultiSurface();
661 1183 : break;
662 :
663 14503 : case wkbTriangle:
664 29006 : poGeom = new (std::nothrow) OGRTriangle();
665 14503 : break;
666 :
667 7378 : case wkbPolyhedralSurface:
668 14756 : poGeom = new (std::nothrow) OGRPolyhedralSurface();
669 7378 : break;
670 :
671 6243 : case wkbTIN:
672 12486 : poGeom = new (std::nothrow) OGRTriangulatedSurface();
673 6243 : break;
674 :
675 1 : case wkbUnknown:
676 1 : break;
677 :
678 0 : default:
679 0 : CPLAssert(false);
680 : break;
681 : }
682 267983 : if (poGeom)
683 : {
684 267982 : if (OGR_GT_HasZ(eGeometryType))
685 64826 : poGeom->set3D(true);
686 267982 : if (OGR_GT_HasM(eGeometryType))
687 59828 : poGeom->setMeasured(true);
688 : }
689 267983 : return poGeom;
690 : }
691 :
692 : /************************************************************************/
693 : /* OGR_G_CreateGeometry() */
694 : /************************************************************************/
695 : /**
696 : * \brief Create an empty geometry of desired type.
697 : *
698 : * This is equivalent to allocating the desired geometry with new, but
699 : * the allocation is guaranteed to take place in the context of the
700 : * GDAL/OGR heap.
701 : *
702 : * This function is the same as the CPP method
703 : * OGRGeometryFactory::createGeometry.
704 : *
705 : * @param eGeometryType the type code of the geometry to be created.
706 : *
707 : * @return handle to the newly create geometry or NULL on failure. Should be
708 : * freed with OGR_G_DestroyGeometry() after use.
709 : */
710 :
711 166829 : OGRGeometryH OGR_G_CreateGeometry(OGRwkbGeometryType eGeometryType)
712 :
713 : {
714 166829 : return OGRGeometry::ToHandle(
715 166829 : OGRGeometryFactory::createGeometry(eGeometryType));
716 : }
717 :
718 : /************************************************************************/
719 : /* destroyGeometry() */
720 : /************************************************************************/
721 :
722 : /**
723 : * \brief Destroy geometry object.
724 : *
725 : * Equivalent to invoking delete on a geometry, but it guaranteed to take
726 : * place within the context of the GDAL/OGR heap.
727 : *
728 : * This method is the same as the C function OGR_G_DestroyGeometry().
729 : *
730 : * @param poGeom the geometry to deallocate.
731 : */
732 :
733 2 : void OGRGeometryFactory::destroyGeometry(OGRGeometry *poGeom)
734 :
735 : {
736 2 : delete poGeom;
737 2 : }
738 :
739 : /************************************************************************/
740 : /* OGR_G_DestroyGeometry() */
741 : /************************************************************************/
742 : /**
743 : * \brief Destroy geometry object.
744 : *
745 : * Equivalent to invoking delete on a geometry, but it guaranteed to take
746 : * place within the context of the GDAL/OGR heap.
747 : *
748 : * This function is the same as the CPP method
749 : * OGRGeometryFactory::destroyGeometry.
750 : *
751 : * @param hGeom handle to the geometry to delete.
752 : */
753 :
754 291139 : void OGR_G_DestroyGeometry(OGRGeometryH hGeom)
755 :
756 : {
757 291139 : delete OGRGeometry::FromHandle(hGeom);
758 291139 : }
759 :
760 : /************************************************************************/
761 : /* forceToPolygon() */
762 : /************************************************************************/
763 :
764 : /**
765 : * \brief Convert to polygon.
766 : *
767 : * Tries to force the provided geometry to be a polygon. This effects a change
768 : * on multipolygons.
769 : * Curve polygons or closed curves will be changed to polygons.
770 : * The passed in geometry is consumed and a new one returned (or
771 : * potentially the same one).
772 : *
773 : * Note: the resulting polygon may break the Simple Features rules for polygons,
774 : * for example when converting from a multi-part multipolygon.
775 : *
776 : * @param poGeom the input geometry - ownership is passed to the method.
777 : * @return new geometry, or nullptr in case of error
778 : */
779 :
780 153 : OGRGeometry *OGRGeometryFactory::forceToPolygon(OGRGeometry *poGeom)
781 :
782 : {
783 153 : if (poGeom == nullptr)
784 0 : return nullptr;
785 :
786 153 : OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
787 :
788 153 : if (eGeomType == wkbCurvePolygon)
789 : {
790 39 : OGRCurvePolygon *poCurve = poGeom->toCurvePolygon();
791 :
792 39 : if (!poGeom->hasCurveGeometry(TRUE))
793 14 : return OGRSurface::CastToPolygon(poCurve);
794 :
795 25 : OGRPolygon *poPoly = poCurve->CurvePolyToPoly();
796 25 : delete poGeom;
797 25 : return poPoly;
798 : }
799 :
800 : // base polygon or triangle
801 114 : if (OGR_GT_IsSubClassOf(eGeomType, wkbPolygon))
802 : {
803 7 : return OGRSurface::CastToPolygon(poGeom->toSurface());
804 : }
805 :
806 107 : if (OGR_GT_IsCurve(eGeomType))
807 : {
808 60 : OGRCurve *poCurve = poGeom->toCurve();
809 60 : if (poCurve->getNumPoints() >= 3 && poCurve->get_IsClosed())
810 : {
811 40 : OGRPolygon *poPolygon = new OGRPolygon();
812 40 : poPolygon->assignSpatialReference(poGeom->getSpatialReference());
813 :
814 40 : if (!poGeom->hasCurveGeometry(TRUE))
815 : {
816 26 : poPolygon->addRingDirectly(OGRCurve::CastToLinearRing(poCurve));
817 : }
818 : else
819 : {
820 14 : OGRLineString *poLS = poCurve->CurveToLine();
821 14 : poPolygon->addRingDirectly(OGRCurve::CastToLinearRing(poLS));
822 14 : delete poGeom;
823 : }
824 40 : return poPolygon;
825 : }
826 : }
827 :
828 67 : if (OGR_GT_IsSubClassOf(eGeomType, wkbPolyhedralSurface))
829 : {
830 6 : OGRPolyhedralSurface *poPS = poGeom->toPolyhedralSurface();
831 6 : if (poPS->getNumGeometries() == 1)
832 : {
833 5 : poGeom = OGRSurface::CastToPolygon(
834 5 : poPS->getGeometryRef(0)->clone()->toSurface());
835 5 : delete poPS;
836 5 : return poGeom;
837 : }
838 : }
839 :
840 62 : if (eGeomType != wkbGeometryCollection && eGeomType != wkbMultiPolygon &&
841 : eGeomType != wkbMultiSurface)
842 38 : return poGeom;
843 :
844 : // Build an aggregated polygon from all the polygon rings in the container.
845 24 : OGRPolygon *poPolygon = new OGRPolygon();
846 24 : OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
847 24 : if (poGeom->hasCurveGeometry())
848 : {
849 : OGRGeometryCollection *poNewGC =
850 5 : poGC->getLinearGeometry()->toGeometryCollection();
851 5 : delete poGC;
852 5 : poGeom = poNewGC;
853 5 : poGC = poNewGC;
854 : }
855 :
856 24 : poPolygon->assignSpatialReference(poGeom->getSpatialReference());
857 :
858 53 : for (int iGeom = 0; iGeom < poGC->getNumGeometries(); iGeom++)
859 : {
860 29 : if (wkbFlatten(poGC->getGeometryRef(iGeom)->getGeometryType()) !=
861 : wkbPolygon)
862 12 : continue;
863 :
864 17 : OGRPolygon *poOldPoly = poGC->getGeometryRef(iGeom)->toPolygon();
865 :
866 17 : if (poOldPoly->getExteriorRing() == nullptr)
867 3 : continue;
868 :
869 14 : poPolygon->addRingDirectly(poOldPoly->stealExteriorRing());
870 :
871 22 : for (int iRing = 0; iRing < poOldPoly->getNumInteriorRings(); iRing++)
872 8 : poPolygon->addRingDirectly(poOldPoly->stealInteriorRing(iRing));
873 : }
874 :
875 24 : delete poGC;
876 :
877 24 : return poPolygon;
878 : }
879 :
880 : /************************************************************************/
881 : /* OGR_G_ForceToPolygon() */
882 : /************************************************************************/
883 :
884 : /**
885 : * \brief Convert to polygon.
886 : *
887 : * This function is the same as the C++ method
888 : * OGRGeometryFactory::forceToPolygon().
889 : *
890 : * @param hGeom handle to the geometry to convert (ownership surrendered).
891 : * @return the converted geometry (ownership to caller), or NULL in case of error
892 : *
893 : * @since GDAL/OGR 1.8.0
894 : */
895 :
896 46 : OGRGeometryH OGR_G_ForceToPolygon(OGRGeometryH hGeom)
897 :
898 : {
899 46 : return OGRGeometry::ToHandle(
900 46 : OGRGeometryFactory::forceToPolygon(OGRGeometry::FromHandle(hGeom)));
901 : }
902 :
903 : /************************************************************************/
904 : /* forceToMultiPolygon() */
905 : /************************************************************************/
906 :
907 : /**
908 : * \brief Convert to multipolygon.
909 : *
910 : * Tries to force the provided geometry to be a multipolygon. Currently
911 : * this just effects a change on polygons. The passed in geometry is
912 : * consumed and a new one returned (or potentially the same one).
913 : *
914 : * @return new geometry, or nullptr in case of error
915 : */
916 :
917 3761 : OGRGeometry *OGRGeometryFactory::forceToMultiPolygon(OGRGeometry *poGeom)
918 :
919 : {
920 3761 : if (poGeom == nullptr)
921 0 : return nullptr;
922 :
923 3761 : OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
924 :
925 : /* -------------------------------------------------------------------- */
926 : /* If this is already a MultiPolygon, nothing to do */
927 : /* -------------------------------------------------------------------- */
928 3761 : if (eGeomType == wkbMultiPolygon)
929 : {
930 38 : return poGeom;
931 : }
932 :
933 : /* -------------------------------------------------------------------- */
934 : /* If this is already a MultiSurface with compatible content, */
935 : /* just cast */
936 : /* -------------------------------------------------------------------- */
937 3723 : if (eGeomType == wkbMultiSurface)
938 : {
939 13 : OGRMultiSurface *poMS = poGeom->toMultiSurface();
940 13 : if (!poMS->hasCurveGeometry(TRUE))
941 : {
942 4 : return OGRMultiSurface::CastToMultiPolygon(poMS);
943 : }
944 : }
945 :
946 : /* -------------------------------------------------------------------- */
947 : /* Check for the case of a geometrycollection that can be */
948 : /* promoted to MultiPolygon. */
949 : /* -------------------------------------------------------------------- */
950 3719 : if (eGeomType == wkbGeometryCollection || eGeomType == wkbMultiSurface)
951 : {
952 82 : bool bAllPoly = true;
953 82 : OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
954 82 : if (poGeom->hasCurveGeometry())
955 : {
956 : OGRGeometryCollection *poNewGC =
957 10 : poGC->getLinearGeometry()->toGeometryCollection();
958 10 : delete poGC;
959 10 : poGeom = poNewGC;
960 10 : poGC = poNewGC;
961 : }
962 :
963 82 : bool bCanConvertToMultiPoly = true;
964 319 : for (int iGeom = 0; iGeom < poGC->getNumGeometries(); iGeom++)
965 : {
966 : OGRwkbGeometryType eSubGeomType =
967 237 : wkbFlatten(poGC->getGeometryRef(iGeom)->getGeometryType());
968 237 : if (eSubGeomType != wkbPolygon)
969 166 : bAllPoly = false;
970 237 : if (eSubGeomType != wkbMultiPolygon && eSubGeomType != wkbPolygon &&
971 130 : eSubGeomType != wkbPolyhedralSurface && eSubGeomType != wkbTIN)
972 : {
973 16 : bCanConvertToMultiPoly = false;
974 : }
975 : }
976 :
977 82 : if (!bCanConvertToMultiPoly)
978 12 : return poGeom;
979 :
980 70 : OGRMultiPolygon *poMP = new OGRMultiPolygon();
981 70 : poMP->assignSpatialReference(poGeom->getSpatialReference());
982 :
983 289 : while (poGC->getNumGeometries() > 0)
984 : {
985 219 : OGRGeometry *poSubGeom = poGC->getGeometryRef(0);
986 219 : poGC->removeGeometry(0, FALSE);
987 219 : if (bAllPoly)
988 : {
989 69 : poMP->addGeometryDirectly(poSubGeom);
990 : }
991 : else
992 : {
993 150 : poSubGeom = forceToMultiPolygon(poSubGeom);
994 150 : OGRMultiPolygon *poSubMP = poSubGeom->toMultiPolygon();
995 386 : while (poSubMP != nullptr && poSubMP->getNumGeometries() > 0)
996 : {
997 236 : poMP->addGeometryDirectly(poSubMP->getGeometryRef(0));
998 236 : poSubMP->removeGeometry(0, FALSE);
999 : }
1000 150 : delete poSubMP;
1001 : }
1002 : }
1003 :
1004 70 : delete poGC;
1005 :
1006 70 : return poMP;
1007 : }
1008 :
1009 3637 : if (eGeomType == wkbCurvePolygon)
1010 : {
1011 5 : OGRPolygon *poPoly = poGeom->toCurvePolygon()->CurvePolyToPoly();
1012 5 : OGRMultiPolygon *poMP = new OGRMultiPolygon();
1013 5 : poMP->assignSpatialReference(poGeom->getSpatialReference());
1014 5 : poMP->addGeometryDirectly(poPoly);
1015 5 : delete poGeom;
1016 5 : return poMP;
1017 : }
1018 :
1019 : /* -------------------------------------------------------------------- */
1020 : /* If it is PolyhedralSurface or TIN, then pretend it is a */
1021 : /* multipolygon. */
1022 : /* -------------------------------------------------------------------- */
1023 3632 : if (OGR_GT_IsSubClassOf(eGeomType, wkbPolyhedralSurface))
1024 : {
1025 982 : return OGRPolyhedralSurface::CastToMultiPolygon(
1026 982 : poGeom->toPolyhedralSurface());
1027 : }
1028 :
1029 2650 : if (eGeomType == wkbTriangle)
1030 : {
1031 2 : return forceToMultiPolygon(forceToPolygon(poGeom));
1032 : }
1033 :
1034 : /* -------------------------------------------------------------------- */
1035 : /* Eventually we should try to split the polygon into component */
1036 : /* island polygons. But that is a lot of work and can be put off. */
1037 : /* -------------------------------------------------------------------- */
1038 2648 : if (eGeomType != wkbPolygon)
1039 30 : return poGeom;
1040 :
1041 2618 : OGRMultiPolygon *poMP = new OGRMultiPolygon();
1042 2618 : poMP->assignSpatialReference(poGeom->getSpatialReference());
1043 2618 : poMP->addGeometryDirectly(poGeom);
1044 :
1045 2618 : return poMP;
1046 : }
1047 :
1048 : /************************************************************************/
1049 : /* OGR_G_ForceToMultiPolygon() */
1050 : /************************************************************************/
1051 :
1052 : /**
1053 : * \brief Convert to multipolygon.
1054 : *
1055 : * This function is the same as the C++ method
1056 : * OGRGeometryFactory::forceToMultiPolygon().
1057 : *
1058 : * @param hGeom handle to the geometry to convert (ownership surrendered).
1059 : * @return the converted geometry (ownership to caller), or NULL in case of error
1060 : *
1061 : * @since GDAL/OGR 1.8.0
1062 : */
1063 :
1064 47 : OGRGeometryH OGR_G_ForceToMultiPolygon(OGRGeometryH hGeom)
1065 :
1066 : {
1067 47 : return OGRGeometry::ToHandle(OGRGeometryFactory::forceToMultiPolygon(
1068 47 : OGRGeometry::FromHandle(hGeom)));
1069 : }
1070 :
1071 : /************************************************************************/
1072 : /* forceToMultiPoint() */
1073 : /************************************************************************/
1074 :
1075 : /**
1076 : * \brief Convert to multipoint.
1077 : *
1078 : * Tries to force the provided geometry to be a multipoint. Currently
1079 : * this just effects a change on points or collection of points.
1080 : * The passed in geometry is
1081 : * consumed and a new one returned (or potentially the same one).
1082 : *
1083 : * @return new geometry.
1084 : */
1085 :
1086 67 : OGRGeometry *OGRGeometryFactory::forceToMultiPoint(OGRGeometry *poGeom)
1087 :
1088 : {
1089 67 : if (poGeom == nullptr)
1090 0 : return nullptr;
1091 :
1092 67 : OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
1093 :
1094 : /* -------------------------------------------------------------------- */
1095 : /* If this is already a MultiPoint, nothing to do */
1096 : /* -------------------------------------------------------------------- */
1097 67 : if (eGeomType == wkbMultiPoint)
1098 : {
1099 2 : return poGeom;
1100 : }
1101 :
1102 : /* -------------------------------------------------------------------- */
1103 : /* Check for the case of a geometrycollection that can be */
1104 : /* promoted to MultiPoint. */
1105 : /* -------------------------------------------------------------------- */
1106 65 : if (eGeomType == wkbGeometryCollection)
1107 : {
1108 14 : OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
1109 18 : for (const auto &poMember : poGC)
1110 : {
1111 14 : if (wkbFlatten(poMember->getGeometryType()) != wkbPoint)
1112 10 : return poGeom;
1113 : }
1114 :
1115 4 : OGRMultiPoint *poMP = new OGRMultiPoint();
1116 4 : poMP->assignSpatialReference(poGeom->getSpatialReference());
1117 :
1118 8 : while (poGC->getNumGeometries() > 0)
1119 : {
1120 4 : poMP->addGeometryDirectly(poGC->getGeometryRef(0));
1121 4 : poGC->removeGeometry(0, FALSE);
1122 : }
1123 :
1124 4 : delete poGC;
1125 :
1126 4 : return poMP;
1127 : }
1128 :
1129 51 : if (eGeomType != wkbPoint)
1130 44 : return poGeom;
1131 :
1132 7 : OGRMultiPoint *poMP = new OGRMultiPoint();
1133 7 : poMP->assignSpatialReference(poGeom->getSpatialReference());
1134 7 : poMP->addGeometryDirectly(poGeom);
1135 :
1136 7 : return poMP;
1137 : }
1138 :
1139 : /************************************************************************/
1140 : /* OGR_G_ForceToMultiPoint() */
1141 : /************************************************************************/
1142 :
1143 : /**
1144 : * \brief Convert to multipoint.
1145 : *
1146 : * This function is the same as the C++ method
1147 : * OGRGeometryFactory::forceToMultiPoint().
1148 : *
1149 : * @param hGeom handle to the geometry to convert (ownership surrendered).
1150 : * @return the converted geometry (ownership to caller).
1151 : *
1152 : * @since GDAL/OGR 1.8.0
1153 : */
1154 :
1155 41 : OGRGeometryH OGR_G_ForceToMultiPoint(OGRGeometryH hGeom)
1156 :
1157 : {
1158 41 : return OGRGeometry::ToHandle(
1159 41 : OGRGeometryFactory::forceToMultiPoint(OGRGeometry::FromHandle(hGeom)));
1160 : }
1161 :
1162 : /************************************************************************/
1163 : /* forceToMultiLinestring() */
1164 : /************************************************************************/
1165 :
1166 : /**
1167 : * \brief Convert to multilinestring.
1168 : *
1169 : * Tries to force the provided geometry to be a multilinestring.
1170 : *
1171 : * - linestrings are placed in a multilinestring.
1172 : * - circularstrings and compoundcurves will be approximated and placed in a
1173 : * multilinestring.
1174 : * - geometry collections will be converted to multilinestring if they only
1175 : * contain linestrings.
1176 : * - polygons will be changed to a collection of linestrings (one per ring).
1177 : * - curvepolygons will be approximated and changed to a collection of
1178 : ( linestrings (one per ring).
1179 : *
1180 : * The passed in geometry is
1181 : * consumed and a new one returned (or potentially the same one).
1182 : *
1183 : * @return new geometry.
1184 : */
1185 :
1186 2172 : OGRGeometry *OGRGeometryFactory::forceToMultiLineString(OGRGeometry *poGeom)
1187 :
1188 : {
1189 2172 : if (poGeom == nullptr)
1190 0 : return nullptr;
1191 :
1192 2172 : OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
1193 :
1194 : /* -------------------------------------------------------------------- */
1195 : /* If this is already a MultiLineString, nothing to do */
1196 : /* -------------------------------------------------------------------- */
1197 2172 : if (eGeomType == wkbMultiLineString)
1198 : {
1199 2 : return poGeom;
1200 : }
1201 :
1202 : /* -------------------------------------------------------------------- */
1203 : /* Check for the case of a geometrycollection that can be */
1204 : /* promoted to MultiLineString. */
1205 : /* -------------------------------------------------------------------- */
1206 2170 : if (eGeomType == wkbGeometryCollection)
1207 : {
1208 16 : OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
1209 16 : if (poGeom->hasCurveGeometry())
1210 : {
1211 : OGRGeometryCollection *poNewGC =
1212 1 : poGC->getLinearGeometry()->toGeometryCollection();
1213 1 : delete poGC;
1214 1 : poGeom = poNewGC;
1215 1 : poGC = poNewGC;
1216 : }
1217 :
1218 24 : for (auto &&poMember : poGC)
1219 : {
1220 18 : if (wkbFlatten(poMember->getGeometryType()) != wkbLineString)
1221 : {
1222 10 : return poGeom;
1223 : }
1224 : }
1225 :
1226 6 : OGRMultiLineString *poMP = new OGRMultiLineString();
1227 6 : poMP->assignSpatialReference(poGeom->getSpatialReference());
1228 :
1229 14 : while (poGC->getNumGeometries() > 0)
1230 : {
1231 8 : poMP->addGeometryDirectly(poGC->getGeometryRef(0));
1232 8 : poGC->removeGeometry(0, FALSE);
1233 : }
1234 :
1235 6 : delete poGC;
1236 :
1237 6 : return poMP;
1238 : }
1239 :
1240 : /* -------------------------------------------------------------------- */
1241 : /* Turn a linestring into a multilinestring. */
1242 : /* -------------------------------------------------------------------- */
1243 2154 : if (eGeomType == wkbLineString)
1244 : {
1245 2064 : OGRMultiLineString *poMP = new OGRMultiLineString();
1246 2064 : poMP->assignSpatialReference(poGeom->getSpatialReference());
1247 2064 : poMP->addGeometryDirectly(poGeom);
1248 2064 : return poMP;
1249 : }
1250 :
1251 : /* -------------------------------------------------------------------- */
1252 : /* Convert polygons into a multilinestring. */
1253 : /* -------------------------------------------------------------------- */
1254 90 : if (OGR_GT_IsSubClassOf(eGeomType, wkbCurvePolygon))
1255 : {
1256 28 : OGRMultiLineString *poMLS = new OGRMultiLineString();
1257 28 : poMLS->assignSpatialReference(poGeom->getSpatialReference());
1258 :
1259 57 : const auto AddRingFromSrcPoly = [poMLS](const OGRPolygon *poPoly)
1260 : {
1261 61 : for (int iRing = 0; iRing < poPoly->getNumInteriorRings() + 1;
1262 : iRing++)
1263 : {
1264 : const OGRLineString *poLR;
1265 :
1266 35 : if (iRing == 0)
1267 : {
1268 28 : poLR = poPoly->getExteriorRing();
1269 28 : if (poLR == nullptr)
1270 2 : break;
1271 : }
1272 : else
1273 7 : poLR = poPoly->getInteriorRing(iRing - 1);
1274 :
1275 33 : if (poLR == nullptr || poLR->getNumPoints() == 0)
1276 4 : continue;
1277 :
1278 29 : auto poNewLS = new OGRLineString();
1279 29 : poNewLS->addSubLineString(poLR);
1280 29 : poMLS->addGeometryDirectly(poNewLS);
1281 : }
1282 28 : };
1283 :
1284 28 : if (OGR_GT_IsSubClassOf(eGeomType, wkbPolygon))
1285 : {
1286 24 : AddRingFromSrcPoly(poGeom->toPolygon());
1287 : }
1288 : else
1289 : {
1290 : auto poTmpPoly = std::unique_ptr<OGRPolygon>(
1291 8 : poGeom->toCurvePolygon()->CurvePolyToPoly());
1292 4 : AddRingFromSrcPoly(poTmpPoly.get());
1293 : }
1294 :
1295 28 : delete poGeom;
1296 :
1297 28 : return poMLS;
1298 : }
1299 :
1300 : /* -------------------------------------------------------------------- */
1301 : /* If it is PolyhedralSurface or TIN, then pretend it is a */
1302 : /* multipolygon. */
1303 : /* -------------------------------------------------------------------- */
1304 62 : if (OGR_GT_IsSubClassOf(eGeomType, wkbPolyhedralSurface))
1305 : {
1306 0 : poGeom = CPLAssertNotNull(forceToMultiPolygon(poGeom));
1307 0 : eGeomType = wkbMultiPolygon;
1308 : }
1309 :
1310 : /* -------------------------------------------------------------------- */
1311 : /* Convert multi-polygons into a multilinestring. */
1312 : /* -------------------------------------------------------------------- */
1313 62 : if (eGeomType == wkbMultiPolygon || eGeomType == wkbMultiSurface)
1314 : {
1315 9 : OGRMultiLineString *poMLS = new OGRMultiLineString();
1316 9 : poMLS->assignSpatialReference(poGeom->getSpatialReference());
1317 :
1318 22 : const auto AddRingFromSrcMP = [poMLS](const OGRMultiPolygon *poSrcMP)
1319 : {
1320 21 : for (auto &&poPoly : poSrcMP)
1321 : {
1322 27 : for (auto &&poLR : poPoly)
1323 : {
1324 15 : if (poLR->IsEmpty())
1325 2 : continue;
1326 :
1327 13 : OGRLineString *poNewLS = new OGRLineString();
1328 13 : poNewLS->addSubLineString(poLR);
1329 13 : poMLS->addGeometryDirectly(poNewLS);
1330 : }
1331 : }
1332 9 : };
1333 :
1334 9 : if (eGeomType == wkbMultiPolygon)
1335 : {
1336 6 : AddRingFromSrcMP(poGeom->toMultiPolygon());
1337 : }
1338 : else
1339 : {
1340 : auto poTmpMPoly = std::unique_ptr<OGRMultiPolygon>(
1341 6 : poGeom->getLinearGeometry()->toMultiPolygon());
1342 3 : AddRingFromSrcMP(poTmpMPoly.get());
1343 : }
1344 :
1345 9 : delete poGeom;
1346 9 : return poMLS;
1347 : }
1348 :
1349 : /* -------------------------------------------------------------------- */
1350 : /* If it is a curve line, approximate it and wrap in a multilinestring
1351 : */
1352 : /* -------------------------------------------------------------------- */
1353 53 : if (eGeomType == wkbCircularString || eGeomType == wkbCompoundCurve)
1354 : {
1355 20 : OGRMultiLineString *poMP = new OGRMultiLineString();
1356 20 : poMP->assignSpatialReference(poGeom->getSpatialReference());
1357 20 : poMP->addGeometryDirectly(poGeom->toCurve()->CurveToLine());
1358 20 : delete poGeom;
1359 20 : return poMP;
1360 : }
1361 :
1362 : /* -------------------------------------------------------------------- */
1363 : /* If this is already a MultiCurve with compatible content, */
1364 : /* just cast */
1365 : /* -------------------------------------------------------------------- */
1366 46 : if (eGeomType == wkbMultiCurve &&
1367 13 : !poGeom->toMultiCurve()->hasCurveGeometry(TRUE))
1368 : {
1369 3 : return OGRMultiCurve::CastToMultiLineString(poGeom->toMultiCurve());
1370 : }
1371 :
1372 : /* -------------------------------------------------------------------- */
1373 : /* If it is a multicurve, call getLinearGeometry() */
1374 : /* -------------------------------------------------------------------- */
1375 30 : if (eGeomType == wkbMultiCurve)
1376 : {
1377 10 : OGRGeometry *poNewGeom = poGeom->getLinearGeometry();
1378 10 : CPLAssert(wkbFlatten(poNewGeom->getGeometryType()) ==
1379 : wkbMultiLineString);
1380 10 : delete poGeom;
1381 10 : return poNewGeom->toMultiLineString();
1382 : }
1383 :
1384 20 : return poGeom;
1385 : }
1386 :
1387 : /************************************************************************/
1388 : /* OGR_G_ForceToMultiLineString() */
1389 : /************************************************************************/
1390 :
1391 : /**
1392 : * \brief Convert to multilinestring.
1393 : *
1394 : * This function is the same as the C++ method
1395 : * OGRGeometryFactory::forceToMultiLineString().
1396 : *
1397 : * @param hGeom handle to the geometry to convert (ownership surrendered).
1398 : * @return the converted geometry (ownership to caller).
1399 : *
1400 : * @since GDAL/OGR 1.8.0
1401 : */
1402 :
1403 50 : OGRGeometryH OGR_G_ForceToMultiLineString(OGRGeometryH hGeom)
1404 :
1405 : {
1406 50 : return OGRGeometry::ToHandle(OGRGeometryFactory::forceToMultiLineString(
1407 50 : OGRGeometry::FromHandle(hGeom)));
1408 : }
1409 :
1410 : /************************************************************************/
1411 : /* removeLowerDimensionSubGeoms() */
1412 : /************************************************************************/
1413 :
1414 : /** \brief Remove sub-geometries from a geometry collection that do not have
1415 : * the maximum topological dimensionality of the collection.
1416 : *
1417 : * This is typically to be used as a cleanup phase after running
1418 : * OGRGeometry::MakeValid()
1419 : *
1420 : * For example, MakeValid() on a polygon can return a geometry collection of
1421 : * polygons and linestrings. Calling this method will return either a polygon
1422 : * or multipolygon by dropping those linestrings.
1423 : *
1424 : * On a non-geometry collection, this will return a clone of the passed
1425 : * geometry.
1426 : *
1427 : * @param poGeom input geometry
1428 : * @return a new geometry.
1429 : *
1430 : * @since GDAL 3.1.0
1431 : */
1432 : OGRGeometry *
1433 33 : OGRGeometryFactory::removeLowerDimensionSubGeoms(const OGRGeometry *poGeom)
1434 : {
1435 33 : if (poGeom == nullptr)
1436 0 : return nullptr;
1437 48 : if (wkbFlatten(poGeom->getGeometryType()) != wkbGeometryCollection ||
1438 15 : poGeom->IsEmpty())
1439 : {
1440 19 : return poGeom->clone();
1441 : }
1442 14 : const OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
1443 14 : int nMaxDim = 0;
1444 14 : OGRBoolean bHasCurve = FALSE;
1445 39 : for (const auto poSubGeom : *poGC)
1446 : {
1447 25 : nMaxDim = std::max(nMaxDim, poSubGeom->getDimension());
1448 25 : bHasCurve |= poSubGeom->hasCurveGeometry();
1449 : }
1450 14 : int nCountAtMaxDim = 0;
1451 14 : const OGRGeometry *poGeomAtMaxDim = nullptr;
1452 39 : for (const auto poSubGeom : *poGC)
1453 : {
1454 25 : if (poSubGeom->getDimension() == nMaxDim)
1455 : {
1456 19 : poGeomAtMaxDim = poSubGeom;
1457 19 : nCountAtMaxDim++;
1458 : }
1459 : }
1460 14 : if (nCountAtMaxDim == 1 && poGeomAtMaxDim != nullptr)
1461 : {
1462 9 : return poGeomAtMaxDim->clone();
1463 : }
1464 : OGRGeometryCollection *poRet =
1465 5 : (nMaxDim == 0)
1466 10 : ? static_cast<OGRGeometryCollection *>(new OGRMultiPoint())
1467 5 : : (nMaxDim == 1)
1468 10 : ? (!bHasCurve
1469 4 : ? static_cast<OGRGeometryCollection *>(
1470 1 : new OGRMultiLineString())
1471 1 : : static_cast<OGRGeometryCollection *>(new OGRMultiCurve()))
1472 3 : : (nMaxDim == 2 && !bHasCurve)
1473 6 : ? static_cast<OGRGeometryCollection *>(new OGRMultiPolygon())
1474 1 : : static_cast<OGRGeometryCollection *>(new OGRMultiSurface());
1475 15 : for (const auto poSubGeom : *poGC)
1476 : {
1477 10 : if (poSubGeom->getDimension() == nMaxDim)
1478 : {
1479 10 : if (OGR_GT_IsSubClassOf(poSubGeom->getGeometryType(),
1480 10 : wkbGeometryCollection))
1481 : {
1482 : const OGRGeometryCollection *poSubGeomAsGC =
1483 1 : poSubGeom->toGeometryCollection();
1484 2 : for (const auto poSubSubGeom : *poSubGeomAsGC)
1485 : {
1486 1 : if (poSubSubGeom->getDimension() == nMaxDim)
1487 : {
1488 1 : poRet->addGeometryDirectly(poSubSubGeom->clone());
1489 : }
1490 : }
1491 : }
1492 : else
1493 : {
1494 9 : poRet->addGeometryDirectly(poSubGeom->clone());
1495 : }
1496 : }
1497 : }
1498 5 : return poRet;
1499 : }
1500 :
1501 : /************************************************************************/
1502 : /* OGR_G_RemoveLowerDimensionSubGeoms() */
1503 : /************************************************************************/
1504 :
1505 : /** \brief Remove sub-geometries from a geometry collection that do not have
1506 : * the maximum topological dimensionality of the collection.
1507 : *
1508 : * This function is the same as the C++ method
1509 : * OGRGeometryFactory::removeLowerDimensionSubGeoms().
1510 : *
1511 : * @param hGeom handle to the geometry to convert
1512 : * @return a new geometry.
1513 : *
1514 : * @since GDAL 3.1.0
1515 : */
1516 :
1517 18 : OGRGeometryH OGR_G_RemoveLowerDimensionSubGeoms(const OGRGeometryH hGeom)
1518 :
1519 : {
1520 18 : return OGRGeometry::ToHandle(
1521 : OGRGeometryFactory::removeLowerDimensionSubGeoms(
1522 36 : OGRGeometry::FromHandle(hGeom)));
1523 : }
1524 :
1525 : /************************************************************************/
1526 : /* organizePolygons() */
1527 : /************************************************************************/
1528 :
1529 85452 : struct sPolyExtended
1530 : {
1531 : CPL_DISALLOW_COPY_ASSIGN(sPolyExtended)
1532 60322 : sPolyExtended() = default;
1533 112182 : sPolyExtended(sPolyExtended &&) = default;
1534 : sPolyExtended &operator=(sPolyExtended &&) = default;
1535 :
1536 : std::unique_ptr<OGRCurvePolygon> poPolygon{};
1537 : OGREnvelope sEnvelope{};
1538 : OGRPoint sPoint{};
1539 : size_t nInitialIndex = 0;
1540 : OGRCurvePolygon *poEnclosingPolygon = nullptr;
1541 : double dfArea = 0.0;
1542 : bool bIsTopLevel = false;
1543 : bool bIsCW = false;
1544 : bool bIsPolygon = false;
1545 :
1546 1260 : inline const OGRLinearRing *getExteriorLinearRing() const
1547 : {
1548 1260 : return poPolygon->getExteriorRingCurve()->toLinearRing();
1549 : }
1550 :
1551 90788 : static void GetBoundsFromPolyEx(const void *hFeature, CPLRectObj *pBounds)
1552 : {
1553 90788 : const auto *poPolyEx = static_cast<const sPolyExtended *>(hFeature);
1554 90788 : pBounds->minx = poPolyEx->sEnvelope.MinX;
1555 90788 : pBounds->miny = poPolyEx->sEnvelope.MinY;
1556 90788 : pBounds->maxx = poPolyEx->sEnvelope.MaxX;
1557 90788 : pBounds->maxy = poPolyEx->sEnvelope.MaxY;
1558 90788 : }
1559 : };
1560 :
1561 5056 : static bool OGRGeometryFactoryCompareAreaDescending(const sPolyExtended &sPoly1,
1562 : const sPolyExtended &sPoly2)
1563 : {
1564 5056 : return sPoly1.dfArea > sPoly2.dfArea;
1565 : }
1566 :
1567 518867 : static bool OGRGeometryFactoryCompareByIndex(const sPolyExtended &sPoly1,
1568 : const sPolyExtended &sPoly2)
1569 : {
1570 518867 : return sPoly1.nInitialIndex < sPoly2.nInitialIndex;
1571 : }
1572 :
1573 : constexpr int N_CRITICAL_PART_NUMBER = 100;
1574 :
1575 : enum OrganizePolygonMethod
1576 : {
1577 : METHOD_NORMAL,
1578 : METHOD_SKIP,
1579 : METHOD_ONLY_CCW,
1580 : METHOD_CCW_INNER_JUST_AFTER_CW_OUTER
1581 : };
1582 :
1583 : /**
1584 : * \brief Organize polygons based on geometries.
1585 : *
1586 : * Analyse a set of rings (passed as simple polygons), and based on a
1587 : * geometric analysis convert them into a polygon with inner rings,
1588 : * (or a MultiPolygon if dealing with more than one polygon) that follow the
1589 : * OGC Simple Feature specification.
1590 : *
1591 : * All the input geometries must be OGRPolygon/OGRCurvePolygon with only a valid
1592 : * exterior ring (at least 4 points) and no interior rings.
1593 : *
1594 : * The passed in geometries become the responsibility of the method, but the
1595 : * papoPolygons "pointer array" remains owned by the caller.
1596 : *
1597 : * For faster computation, a polygon is considered to be inside
1598 : * another one if a single point of its external ring is included into the other
1599 : * one. (unless 'OGR_DEBUG_ORGANIZE_POLYGONS' configuration option is set to
1600 : * TRUE. In that case, a slower algorithm that tests exact topological
1601 : * relationships is used if GEOS is available.)
1602 : *
1603 : * In cases where a big number of polygons is passed to this function, the
1604 : * default processing may be really slow. You can skip the processing by adding
1605 : * METHOD=SKIP to the option list (the result of the function will be a
1606 : * multi-polygon with all polygons as toplevel polygons) or only make it analyze
1607 : * counterclockwise polygons by adding METHOD=ONLY_CCW to the option list if you
1608 : * can assume that the outline of holes is counterclockwise defined (this is the
1609 : * convention for example in shapefiles, Personal Geodatabases or File
1610 : * Geodatabases).
1611 : *
1612 : * For FileGDB, in most cases, but not always, a faster method than ONLY_CCW can
1613 : * be used. It is CCW_INNER_JUST_AFTER_CW_OUTER. When using it, inner rings are
1614 : * assumed to be counterclockwise oriented, and following immediately the outer
1615 : * ring (clockwise oriented) that they belong to. If that assumption is not met,
1616 : * an inner ring could be attached to the wrong outer ring, so this method must
1617 : * be used with care.
1618 : *
1619 : * If the OGR_ORGANIZE_POLYGONS configuration option is defined, its value will
1620 : * override the value of the METHOD option of papszOptions (useful to modify the
1621 : * behavior of the shapefile driver)
1622 : *
1623 : * @param papoPolygons array of geometry pointers - should all be OGRPolygons
1624 : * or OGRCurvePolygons. Ownership of the geometries is passed, but not of the
1625 : * array itself.
1626 : * @param nPolygonCount number of items in papoPolygons
1627 : * @param pbIsValidGeometry value may be set to FALSE if an invalid result is
1628 : * detected. Validity checks vary according to the method used and are are limited
1629 : * to what is needed to link inner rings to outer rings, so a result of TRUE
1630 : * does not mean that OGRGeometry::IsValid() returns TRUE.
1631 : * @param papszOptions a list of strings for passing options
1632 : *
1633 : * @return a single resulting geometry (either OGRPolygon, OGRCurvePolygon,
1634 : * OGRMultiPolygon, OGRMultiSurface or OGRGeometryCollection). Returns a
1635 : * POLYGON EMPTY in the case of nPolygonCount being 0.
1636 : *
1637 : * @deprecated since 3.13. Use variant
1638 : * that accepts a std::vector<std::unique_ptr<OGRGeometry>>& instead.
1639 : */
1640 :
1641 33 : OGRGeometry *OGRGeometryFactory::organizePolygons(OGRGeometry **papoPolygons,
1642 : int nPolygonCount,
1643 : int *pbIsValidGeometry,
1644 : CSLConstList papszOptions)
1645 : {
1646 : std::vector<std::unique_ptr<OGRGeometry>> apoPolygons(
1647 66 : papoPolygons, papoPolygons + nPolygonCount);
1648 33 : bool bIsValidGeometry = false;
1649 : auto poGeometry = OGRGeometryFactory::organizePolygons(
1650 66 : apoPolygons, &bIsValidGeometry, papszOptions);
1651 33 : if (pbIsValidGeometry)
1652 0 : *pbIsValidGeometry = bIsValidGeometry;
1653 66 : return poGeometry.release();
1654 : }
1655 :
1656 : /**
1657 : * \brief Organize polygons based on geometries.
1658 : *
1659 : * Analyse a set of rings (passed as simple polygons), and based on a
1660 : * geometric analysis convert them into a polygon with inner rings,
1661 : * (or a MultiPolygon if dealing with more than one polygon) that follow the
1662 : * OGC Simple Feature specification.
1663 : *
1664 : * All the input geometries must be OGRPolygon/OGRCurvePolygon with only a valid
1665 : * exterior ring (at least 4 points) and no interior rings.
1666 : *
1667 : * The passed in geometries become the responsibility of the method.
1668 : *
1669 : * For faster computation, a polygon is considered to be inside
1670 : * another one if a single point of its external ring is included into the other
1671 : * one. (unless 'OGR_DEBUG_ORGANIZE_POLYGONS' configuration option is set to
1672 : * TRUE. In that case, a slower algorithm that tests exact topological
1673 : * relationships is used if GEOS is available.)
1674 : *
1675 : * In cases where a big number of polygons is passed to this function, the
1676 : * default processing may be really slow. You can skip the processing by adding
1677 : * METHOD=SKIP to the option list (the result of the function will be a
1678 : * multi-polygon with all polygons as toplevel polygons) or only make it analyze
1679 : * counterclockwise polygons by adding METHOD=ONLY_CCW to the option list if you
1680 : * can assume that the outline of holes is counterclockwise defined (this is the
1681 : * convention for example in shapefiles, Personal Geodatabases or File
1682 : * Geodatabases).
1683 : *
1684 : * For FileGDB, in most cases, but not always, a faster method than ONLY_CCW can
1685 : * be used. It is CCW_INNER_JUST_AFTER_CW_OUTER. When using it, inner rings are
1686 : * assumed to be counterclockwise oriented, and following immediately the outer
1687 : * ring (clockwise oriented) that they belong to. If that assumption is not met,
1688 : * an inner ring could be attached to the wrong outer ring, so this method must
1689 : * be used with care.
1690 : *
1691 : * If the OGR_ORGANIZE_POLYGONS configuration option is defined, its value will
1692 : * override the value of the METHOD option of papszOptions (useful to modify the
1693 : * behavior of the shapefile driver)
1694 : *
1695 : * @param apoPolygons array of geometries - should all be OGRPolygons
1696 : * or OGRCurvePolygons. Ownership of the geometries is passed.
1697 : * @param pbIsValidGeometry value may be set to FALSE if an invalid result is
1698 : * detected. Validity checks vary according to the method used and are are limited
1699 : * to what is needed to link inner rings to outer rings, so a result of TRUE
1700 : * does not mean that OGRGeometry::IsValid() returns TRUE.
1701 : * @param papszOptions a list of strings for passing options
1702 : *
1703 : * @return a single resulting geometry (either OGRPolygon, OGRCurvePolygon,
1704 : * OGRMultiPolygon, OGRMultiSurface or OGRGeometryCollection). Returns a
1705 : * POLYGON EMPTY in the case of nPolygonCount being 0.
1706 : *
1707 : * @since 3.13
1708 : */
1709 :
1710 48637 : std::unique_ptr<OGRGeometry> OGRGeometryFactory::organizePolygons(
1711 : std::vector<std::unique_ptr<OGRGeometry>> &apoPolygons,
1712 : bool *pbIsValidGeometry, CSLConstList papszOptions)
1713 : {
1714 48637 : if (apoPolygons.empty())
1715 : {
1716 4 : if (pbIsValidGeometry)
1717 3 : *pbIsValidGeometry = true;
1718 :
1719 4 : return std::make_unique<OGRPolygon>();
1720 : }
1721 :
1722 48633 : std::unique_ptr<OGRGeometry> geom;
1723 48633 : OrganizePolygonMethod method = METHOD_NORMAL;
1724 48633 : bool bHasCurves = false;
1725 :
1726 : /* -------------------------------------------------------------------- */
1727 : /* Trivial case of a single polygon. */
1728 : /* -------------------------------------------------------------------- */
1729 48633 : if (apoPolygons.size() == 1)
1730 : {
1731 : OGRwkbGeometryType eType =
1732 33803 : wkbFlatten(apoPolygons[0]->getGeometryType());
1733 :
1734 33803 : bool bIsValid = true;
1735 :
1736 33803 : if (eType != wkbPolygon && eType != wkbCurvePolygon)
1737 : {
1738 3 : CPLError(CE_Warning, CPLE_AppDefined,
1739 : "organizePolygons() received a non-Polygon geometry.");
1740 3 : bIsValid = false;
1741 3 : apoPolygons[0].reset();
1742 3 : geom = std::make_unique<OGRPolygon>();
1743 : }
1744 : else
1745 : {
1746 33800 : geom = std::move(apoPolygons[0]);
1747 : }
1748 :
1749 33803 : if (pbIsValidGeometry)
1750 9 : *pbIsValidGeometry = bIsValid;
1751 :
1752 33803 : return geom;
1753 : }
1754 :
1755 14830 : bool bUseFastVersion = true;
1756 14830 : if (CPLTestBool(CPLGetConfigOption("OGR_DEBUG_ORGANIZE_POLYGONS", "NO")))
1757 : {
1758 : /* ------------------------------------------------------------------ */
1759 : /* A wee bit of a warning. */
1760 : /* ------------------------------------------------------------------ */
1761 0 : bUseFastVersion = !haveGEOS();
1762 : // cppcheck-suppress knownConditionTrueFalse
1763 0 : if (bUseFastVersion)
1764 : {
1765 0 : CPLDebugOnce(
1766 : "OGR",
1767 : "In OGR_DEBUG_ORGANIZE_POLYGONS mode, GDAL should be built "
1768 : "with GEOS support enabled in order "
1769 : "OGRGeometryFactory::organizePolygons to provide reliable "
1770 : "results on complex polygons.");
1771 : }
1772 : }
1773 :
1774 : /* -------------------------------------------------------------------- */
1775 : /* Setup per polygon envelope and area information. */
1776 : /* -------------------------------------------------------------------- */
1777 29660 : std::vector<sPolyExtended> asPolyEx;
1778 14830 : asPolyEx.reserve(apoPolygons.size());
1779 :
1780 14830 : bool bValidTopology = true;
1781 14830 : bool bMixedUpGeometries = false;
1782 14830 : bool bFoundCCW = false;
1783 :
1784 14830 : const char *pszMethodValue = CSLFetchNameValue(papszOptions, "METHOD");
1785 : const char *pszMethodValueOption =
1786 14830 : CPLGetConfigOption("OGR_ORGANIZE_POLYGONS", nullptr);
1787 14830 : if (pszMethodValueOption != nullptr && pszMethodValueOption[0] != '\0')
1788 13944 : pszMethodValue = pszMethodValueOption;
1789 :
1790 14830 : if (pszMethodValue != nullptr)
1791 : {
1792 14329 : if (EQUAL(pszMethodValue, "SKIP"))
1793 : {
1794 13948 : method = METHOD_SKIP;
1795 13948 : bMixedUpGeometries = true;
1796 : }
1797 381 : else if (EQUAL(pszMethodValue, "ONLY_CCW"))
1798 : {
1799 302 : method = METHOD_ONLY_CCW;
1800 : }
1801 79 : else if (EQUAL(pszMethodValue, "CCW_INNER_JUST_AFTER_CW_OUTER"))
1802 : {
1803 0 : method = METHOD_CCW_INNER_JUST_AFTER_CW_OUTER;
1804 : }
1805 79 : else if (!EQUAL(pszMethodValue, "DEFAULT"))
1806 : {
1807 0 : CPLError(CE_Warning, CPLE_AppDefined,
1808 : "Unrecognized value for METHOD option : %s",
1809 : pszMethodValue);
1810 : }
1811 : }
1812 :
1813 14830 : size_t nCountCWPolygon = 0;
1814 14830 : constexpr size_t INVALID_INDEX = static_cast<size_t>(-1);
1815 14830 : size_t indexOfCWPolygon = INVALID_INDEX;
1816 14830 : OGREnvelope sGlobalEnvelope;
1817 :
1818 75155 : for (size_t i = 0; i < apoPolygons.size(); ++i)
1819 : {
1820 : const OGRwkbGeometryType eType =
1821 60325 : wkbFlatten(apoPolygons[i]->getGeometryType());
1822 :
1823 60325 : if (eType != wkbPolygon && eType != wkbCurvePolygon)
1824 : {
1825 : // Ignore any points or lines that find their way in here.
1826 3 : CPLError(CE_Warning, CPLE_AppDefined,
1827 : "organizePolygons() received a non-Polygon geometry.");
1828 3 : apoPolygons[i].reset();
1829 3 : continue;
1830 : }
1831 :
1832 120644 : sPolyExtended sPolyEx;
1833 :
1834 60322 : sPolyEx.nInitialIndex = i;
1835 60322 : sPolyEx.poPolygon.reset(apoPolygons[i].release()->toCurvePolygon());
1836 :
1837 60322 : sPolyEx.poPolygon->getEnvelope(&sPolyEx.sEnvelope);
1838 60322 : sGlobalEnvelope.Merge(sPolyEx.sEnvelope);
1839 :
1840 60322 : if (eType == wkbCurvePolygon)
1841 33 : bHasCurves = true;
1842 60322 : if (!sPolyEx.poPolygon->IsEmpty() &&
1843 120644 : sPolyEx.poPolygon->getNumInteriorRings() == 0 &&
1844 60322 : sPolyEx.poPolygon->getExteriorRingCurve()->getNumPoints() >= 4)
1845 : {
1846 60320 : if (method != METHOD_CCW_INNER_JUST_AFTER_CW_OUTER)
1847 60320 : sPolyEx.dfArea = sPolyEx.poPolygon->get_Area();
1848 60320 : auto *poExteriorRing = sPolyEx.poPolygon->getExteriorRingCurve();
1849 60320 : poExteriorRing->StartPoint(&sPolyEx.sPoint);
1850 60320 : if (eType == wkbPolygon)
1851 : {
1852 60287 : sPolyEx.bIsCW =
1853 60287 : CPL_TO_BOOL(poExteriorRing->toLinearRing()->isClockwise());
1854 60287 : sPolyEx.bIsPolygon = true;
1855 : }
1856 : else
1857 : {
1858 33 : OGRLineString *poLS = poExteriorRing->CurveToLine();
1859 66 : OGRLinearRing oLR;
1860 33 : oLR.addSubLineString(poLS);
1861 33 : sPolyEx.bIsCW = CPL_TO_BOOL(oLR.isClockwise());
1862 33 : sPolyEx.bIsPolygon = false;
1863 33 : delete poLS;
1864 : }
1865 60320 : if (sPolyEx.bIsCW)
1866 : {
1867 17200 : indexOfCWPolygon = i;
1868 17200 : nCountCWPolygon++;
1869 : }
1870 60320 : if (!bFoundCCW)
1871 29679 : bFoundCCW = !(sPolyEx.bIsCW);
1872 : }
1873 : else
1874 : {
1875 2 : if (!bMixedUpGeometries)
1876 : {
1877 0 : CPLError(CE_Warning, CPLE_AppDefined,
1878 : "organizePolygons() received an unexpected geometry. "
1879 : "Either a polygon with interior rings, or a polygon "
1880 : "with less than 4 points, or a non-Polygon geometry. "
1881 : "Return arguments as a collection.");
1882 0 : bMixedUpGeometries = true;
1883 : }
1884 : }
1885 :
1886 60322 : asPolyEx.push_back(std::move(sPolyEx));
1887 : }
1888 :
1889 : // If we are in ONLY_CCW mode and that we have found that there is only one
1890 : // outer ring, then it is pretty easy : we can assume that all other rings
1891 : // are inside.
1892 14830 : if ((method == METHOD_ONLY_CCW ||
1893 302 : method == METHOD_CCW_INNER_JUST_AFTER_CW_OUTER) &&
1894 115 : nCountCWPolygon == 1 && bUseFastVersion)
1895 : {
1896 115 : assert(indexOfCWPolygon != INVALID_INDEX);
1897 230 : auto poCP = std::move(asPolyEx[indexOfCWPolygon].poPolygon);
1898 391 : for (size_t i = 0; i < asPolyEx.size(); i++)
1899 : {
1900 276 : if (i != indexOfCWPolygon)
1901 : {
1902 322 : poCP->addRingDirectly(
1903 161 : asPolyEx[i].poPolygon->stealExteriorRingCurve());
1904 : }
1905 : }
1906 :
1907 115 : if (pbIsValidGeometry)
1908 115 : *pbIsValidGeometry = TRUE;
1909 115 : return poCP;
1910 : }
1911 :
1912 14715 : if (method == METHOD_CCW_INNER_JUST_AFTER_CW_OUTER && asPolyEx[0].bIsCW)
1913 : {
1914 : // Inner rings are CCW oriented and follow immediately the outer
1915 : // ring (that is CW oriented) in which they are included.
1916 0 : std::unique_ptr<OGRMultiSurface> poMulti;
1917 0 : auto poCurvePoly = std::move(asPolyEx[0].poPolygon);
1918 :
1919 : // We have already checked that the first ring is CW.
1920 0 : const OGREnvelope *psEnvelope = &(asPolyEx[0].sEnvelope);
1921 0 : for (std::size_t i = 1; i < asPolyEx.size(); i++)
1922 : {
1923 0 : if (asPolyEx[i].bIsCW)
1924 : {
1925 0 : if (!poMulti)
1926 : {
1927 0 : if (bHasCurves)
1928 0 : poMulti = std::make_unique<OGRMultiSurface>();
1929 : else
1930 0 : poMulti = std::make_unique<OGRMultiPolygon>();
1931 0 : poMulti->addGeometry(std::move(poCurvePoly));
1932 : }
1933 0 : poMulti->addGeometry(std::move(asPolyEx[i].poPolygon));
1934 0 : psEnvelope = &(asPolyEx[i].sEnvelope);
1935 : }
1936 : else
1937 : {
1938 : auto poExteriorRing = std::unique_ptr<OGRCurve>(
1939 0 : asPolyEx[i].poPolygon->stealExteriorRingCurve());
1940 0 : if (poCurvePoly)
1941 : {
1942 0 : poCurvePoly->addRing(std::move(poExteriorRing));
1943 : }
1944 : else
1945 : {
1946 0 : poMulti->getGeometryRef(poMulti->getNumGeometries() - 1)
1947 : ->toCurvePolygon()
1948 0 : ->addRing(std::move(poExteriorRing));
1949 : }
1950 0 : if (!(asPolyEx[i].sPoint.getX() >= psEnvelope->MinX &&
1951 0 : asPolyEx[i].sPoint.getX() <= psEnvelope->MaxX &&
1952 0 : asPolyEx[i].sPoint.getY() >= psEnvelope->MinY &&
1953 0 : asPolyEx[i].sPoint.getY() <= psEnvelope->MaxY))
1954 : {
1955 0 : CPLError(CE_Warning, CPLE_AppDefined,
1956 : "Part %d does not respect "
1957 : "CCW_INNER_JUST_AFTER_CW_OUTER rule",
1958 : static_cast<int>(i));
1959 : }
1960 : }
1961 : }
1962 :
1963 0 : if (pbIsValidGeometry)
1964 0 : *pbIsValidGeometry = true;
1965 : // cppcheck-suppress accessMoved
1966 0 : if (poCurvePoly)
1967 : {
1968 : // cppcheck-suppress accessMoved
1969 0 : return poCurvePoly;
1970 : }
1971 : else
1972 0 : return poMulti;
1973 : }
1974 14715 : else if (method == METHOD_CCW_INNER_JUST_AFTER_CW_OUTER)
1975 : {
1976 0 : method = METHOD_ONLY_CCW;
1977 0 : for (std::size_t i = 0; i < asPolyEx.size(); i++)
1978 0 : asPolyEx[i].dfArea = asPolyEx[i].poPolygon->get_Area();
1979 : }
1980 :
1981 : // Emits a warning if the number of parts is sufficiently big to anticipate
1982 : // for very long computation time, and the user didn't specify an explicit
1983 : // method.
1984 14724 : if (apoPolygons.size() > N_CRITICAL_PART_NUMBER &&
1985 14724 : method == METHOD_NORMAL && pszMethodValue == nullptr)
1986 : {
1987 0 : if (bFoundCCW)
1988 : {
1989 0 : CPLErrorOnce(
1990 : CE_Warning, CPLE_AppDefined,
1991 : "organizePolygons() received a polygon with more than %d "
1992 : "parts. The processing may be really slow. "
1993 : "You can skip the processing by setting METHOD=SKIP, "
1994 : "or only make it analyze counter-clock wise parts by "
1995 : "setting METHOD=ONLY_CCW if you can assume that the "
1996 : "outline of holes is counter-clock wise defined",
1997 : N_CRITICAL_PART_NUMBER);
1998 : }
1999 : else
2000 : {
2001 0 : CPLErrorOnce(
2002 : CE_Warning, CPLE_AppDefined,
2003 : "organizePolygons() received a polygon with more than %d "
2004 : "parts. The processing may be really slow. "
2005 : "You can skip the processing by setting METHOD=SKIP.",
2006 : N_CRITICAL_PART_NUMBER);
2007 : }
2008 : }
2009 :
2010 : /* This a nulti-step algorithm :
2011 : 1) Sort polygons by descending areas
2012 : 2) For each polygon of rank i, find its smallest enclosing polygon
2013 : among the polygons of rank [i-1 ... 0]. If there are no such polygon,
2014 : this is a top-level polygon. Otherwise, depending on if the enclosing
2015 : polygon is top-level or not, we can decide if we are top-level or not
2016 : 3) Re-sort the polygons to retrieve their initial order (nicer for
2017 : some applications)
2018 : 4) For each non top-level polygon (= inner ring), add it to its
2019 : outer ring
2020 : 5) Add the top-level polygons to the multipolygon
2021 :
2022 : Complexity : O(nPolygonCount^2)
2023 : */
2024 :
2025 : /* Compute how each polygon relate to the other ones
2026 : To save a bit of computation we always begin the computation by a test
2027 : on the envelope. We also take into account the areas to avoid some
2028 : useless tests. (A contains B implies envelop(A) contains envelop(B)
2029 : and area(A) > area(B)) In practice, we can hope that few full geometry
2030 : intersection of inclusion test is done:
2031 : * if the polygons are well separated geographically (a set of islands
2032 : for example), no full geometry intersection or inclusion test is done.
2033 : (the envelopes don't intersect each other)
2034 :
2035 : * if the polygons are 'lake inside an island inside a lake inside an
2036 : area' and that each polygon is much smaller than its enclosing one,
2037 : their bounding boxes are strictly contained into each other, and thus,
2038 : no full geometry intersection or inclusion test is done
2039 : */
2040 :
2041 14715 : if (!bMixedUpGeometries)
2042 : {
2043 : // STEP 1: Sort polygons by descending area.
2044 767 : std::sort(asPolyEx.begin(), asPolyEx.end(),
2045 : OGRGeometryFactoryCompareAreaDescending);
2046 : }
2047 :
2048 : /* -------------------------------------------------------------------- */
2049 : /* Build a quadtree of polygons that can be exterior rings. */
2050 : /* -------------------------------------------------------------------- */
2051 :
2052 : CPLRectObj sRect;
2053 14715 : sRect.minx = sGlobalEnvelope.MinX;
2054 14715 : sRect.miny = sGlobalEnvelope.MinY;
2055 14715 : sRect.maxx = sGlobalEnvelope.MaxX;
2056 14715 : sRect.maxy = sGlobalEnvelope.MaxY;
2057 : std::unique_ptr<CPLQuadTree, decltype(&CPLQuadTreeDestroy)> poQuadTree(
2058 : CPLQuadTreeCreate(&sRect, sPolyExtended::GetBoundsFromPolyEx),
2059 29430 : CPLQuadTreeDestroy);
2060 74761 : for (auto &sPolyEx : asPolyEx)
2061 : {
2062 60046 : if (method == METHOD_ONLY_CCW && sPolyEx.bIsCW == false)
2063 : {
2064 : // In that mode, we are interested only in indexing clock-wise
2065 : // polygons, which are the exterior rings
2066 262 : continue;
2067 : }
2068 :
2069 59784 : CPLQuadTreeInsert(poQuadTree.get(), &sPolyEx);
2070 : }
2071 :
2072 : /* -------------------------------------------------------------------- */
2073 : /* Compute relationships, if things seem well structured. */
2074 : /* -------------------------------------------------------------------- */
2075 :
2076 : // The first (largest) polygon is necessarily top-level.
2077 14715 : asPolyEx[0].bIsTopLevel = true;
2078 14715 : asPolyEx[0].poEnclosingPolygon = nullptr;
2079 :
2080 14715 : size_t nCountTopLevel = 1;
2081 :
2082 : // STEP 2.
2083 16384 : for (size_t i = 1;
2084 16384 : !bMixedUpGeometries && bValidTopology && i < asPolyEx.size(); i++)
2085 : {
2086 1669 : auto &thisPoly = asPolyEx[i];
2087 :
2088 1669 : if (method == METHOD_ONLY_CCW && thisPoly.bIsCW)
2089 : {
2090 328 : nCountTopLevel++;
2091 328 : thisPoly.bIsTopLevel = true;
2092 328 : thisPoly.poEnclosingPolygon = nullptr;
2093 328 : continue;
2094 : }
2095 :
2096 : // Look for candidate rings that intersect the current ring
2097 : CPLRectObj aoi;
2098 1341 : aoi.minx = thisPoly.sEnvelope.MinX;
2099 1341 : aoi.miny = thisPoly.sEnvelope.MinY;
2100 1341 : aoi.maxx = thisPoly.sEnvelope.MaxX;
2101 1341 : aoi.maxy = thisPoly.sEnvelope.MaxY;
2102 1341 : int nCandidates = 0;
2103 : std::unique_ptr<const sPolyExtended *, decltype(&CPLFree)>
2104 : aphCandidateShells(
2105 : const_cast<const sPolyExtended **>(
2106 1341 : reinterpret_cast<sPolyExtended **>(CPLQuadTreeSearch(
2107 1341 : poQuadTree.get(), &aoi, &nCandidates))),
2108 4023 : CPLFree);
2109 :
2110 : // Sort candidate outer rings by increasing area
2111 1341 : if (nCandidates)
2112 : {
2113 1338 : std::sort(
2114 : aphCandidateShells.get(),
2115 1338 : aphCandidateShells.get() + nCandidates,
2116 779 : [](const sPolyExtended *psPoly1, const sPolyExtended *psPoly2)
2117 779 : { return psPoly1->dfArea < psPoly2->dfArea; });
2118 : }
2119 :
2120 1341 : int j = 0;
2121 2540 : for (; bValidTopology && j < nCandidates; j++)
2122 : {
2123 2052 : const auto &otherPoly = *(aphCandidateShells.get()[j]);
2124 :
2125 2052 : if (method == METHOD_ONLY_CCW && otherPoly.bIsCW == false)
2126 : {
2127 : // In that mode, this which is CCW if we reach here can only be
2128 : // included in a CW polygon.
2129 0 : continue;
2130 : }
2131 2052 : if (otherPoly.dfArea < thisPoly.dfArea || &otherPoly == &thisPoly)
2132 : {
2133 1139 : continue;
2134 : }
2135 :
2136 913 : bool thisInsideOther = false;
2137 913 : if (otherPoly.sEnvelope.Contains(thisPoly.sEnvelope))
2138 : {
2139 859 : if (bUseFastVersion)
2140 : {
2141 1114 : if (method == METHOD_ONLY_CCW &&
2142 255 : (&otherPoly) == (&asPolyEx[0]))
2143 : {
2144 : // We are testing if a CCW ring is in the biggest CW
2145 : // ring. It *must* be inside as this is the last
2146 : // candidate, otherwise the winding order rules is
2147 : // broken.
2148 237 : thisInsideOther = true;
2149 : }
2150 1244 : else if (thisPoly.bIsPolygon && otherPoly.bIsPolygon &&
2151 : otherPoly.getExteriorLinearRing()
2152 622 : ->isPointOnRingBoundary(&thisPoly.sPoint,
2153 : FALSE))
2154 : {
2155 : const OGRLinearRing *poLR_this =
2156 16 : thisPoly.getExteriorLinearRing();
2157 : const OGRLinearRing *poLR_other =
2158 16 : otherPoly.getExteriorLinearRing();
2159 :
2160 : // If the point of i is on the boundary of other, we will
2161 : // iterate over the other points of this.
2162 16 : const int nPoints = poLR_this->getNumPoints();
2163 16 : int k = 1; // Used after for.
2164 32 : OGRPoint previousPoint = thisPoly.sPoint;
2165 31 : for (; k < nPoints; k++)
2166 : {
2167 30 : OGRPoint point;
2168 30 : poLR_this->getPoint(k, &point);
2169 32 : if (point.getX() == previousPoint.getX() &&
2170 2 : point.getY() == previousPoint.getY())
2171 : {
2172 0 : continue;
2173 : }
2174 30 : if (poLR_other->isPointOnRingBoundary(&point,
2175 30 : FALSE))
2176 : {
2177 : // If it is on the boundary of other, iterate again.
2178 : }
2179 15 : else if (poLR_other->isPointInRing(&point, FALSE))
2180 : {
2181 : // If then point is strictly included in other, then
2182 : // this is considered inside other.
2183 13 : thisInsideOther = true;
2184 13 : break;
2185 : }
2186 : else
2187 : {
2188 : // If it is outside, then this cannot be inside other.
2189 2 : break;
2190 : }
2191 15 : previousPoint = std::move(point);
2192 : }
2193 16 : if (!thisInsideOther && k == nPoints && nPoints > 2)
2194 : {
2195 : // All points of this are on the boundary of other.
2196 : // Take a point in the middle of a segment of this and
2197 : // test it against other.
2198 1 : poLR_this->getPoint(0, &previousPoint);
2199 2 : for (k = 1; k < nPoints; k++)
2200 : {
2201 2 : OGRPoint point;
2202 2 : poLR_this->getPoint(k, &point);
2203 2 : if (point.getX() == previousPoint.getX() &&
2204 0 : point.getY() == previousPoint.getY())
2205 : {
2206 0 : continue;
2207 : }
2208 2 : OGRPoint pointMiddle;
2209 2 : pointMiddle.setX(
2210 2 : (point.getX() + previousPoint.getX()) / 2);
2211 2 : pointMiddle.setY(
2212 2 : (point.getY() + previousPoint.getY()) / 2);
2213 2 : if (poLR_other->isPointOnRingBoundary(
2214 2 : &pointMiddle, FALSE))
2215 : {
2216 : // If it is on the boundary of other, iterate
2217 : // again.
2218 : }
2219 1 : else if (poLR_other->isPointInRing(&pointMiddle,
2220 1 : FALSE))
2221 : {
2222 : // If then point is strictly included in other,
2223 : // then this is considered inside other.
2224 1 : thisInsideOther = true;
2225 1 : break;
2226 : }
2227 : else
2228 : {
2229 : // If it is outside, then this cannot be inside
2230 : // other.
2231 0 : break;
2232 : }
2233 1 : previousPoint = std::move(point);
2234 : }
2235 : }
2236 : }
2237 : // Note that isPointInRing only test strict inclusion in the
2238 : // ring.
2239 1212 : else if (thisPoly.bIsPolygon && otherPoly.bIsPolygon &&
2240 1212 : otherPoly.getExteriorLinearRing()->isPointInRing(
2241 606 : &thisPoly.sPoint, FALSE))
2242 : {
2243 602 : thisInsideOther = true;
2244 : }
2245 : }
2246 0 : else if (otherPoly.poPolygon->Contains(
2247 0 : thisPoly.poPolygon.get()))
2248 : {
2249 0 : thisInsideOther = true;
2250 : }
2251 : }
2252 :
2253 913 : if (thisInsideOther)
2254 : {
2255 853 : if (otherPoly.bIsTopLevel)
2256 : {
2257 : // We are a lake.
2258 852 : thisPoly.bIsTopLevel = false;
2259 852 : thisPoly.poEnclosingPolygon = otherPoly.poPolygon.get();
2260 : }
2261 : else
2262 : {
2263 : // We are included in a something not toplevel (a lake),
2264 : // so in OGCSF we are considered as toplevel too.
2265 1 : nCountTopLevel++;
2266 1 : thisPoly.bIsTopLevel = true;
2267 1 : thisPoly.poEnclosingPolygon = nullptr;
2268 : }
2269 853 : break;
2270 : }
2271 : // Use Overlaps instead of Intersects to be more
2272 : // tolerant about touching polygons.
2273 60 : else if (bUseFastVersion ||
2274 0 : !thisPoly.poPolygon->Overlaps(otherPoly.poPolygon.get()))
2275 : {
2276 : }
2277 : else
2278 : {
2279 : // Bad... The polygons are intersecting but no one is
2280 : // contained inside the other one. This is a really broken
2281 : // case. We just make a multipolygon with the whole set of
2282 : // polygons.
2283 0 : bValidTopology = false;
2284 : #ifdef DEBUG
2285 0 : char *wkt1 = nullptr;
2286 0 : char *wkt2 = nullptr;
2287 0 : thisPoly.poPolygon->exportToWkt(&wkt1);
2288 0 : otherPoly.poPolygon->exportToWkt(&wkt2);
2289 0 : const int realJ = static_cast<int>(&otherPoly - &asPolyEx[0]);
2290 0 : CPLDebug("OGR",
2291 : "Bad intersection for polygons %d and %d\n"
2292 : "geom %d: %s\n"
2293 : "geom %d: %s",
2294 : static_cast<int>(i), realJ, static_cast<int>(i), wkt1,
2295 : realJ, wkt2);
2296 0 : CPLFree(wkt1);
2297 0 : CPLFree(wkt2);
2298 : #endif
2299 : }
2300 : }
2301 :
2302 1341 : if (j == nCandidates)
2303 : {
2304 : // We come here because we are not included in anything.
2305 : // We are toplevel.
2306 488 : nCountTopLevel++;
2307 488 : thisPoly.bIsTopLevel = true;
2308 488 : thisPoly.poEnclosingPolygon = nullptr;
2309 : }
2310 : }
2311 :
2312 14715 : if (pbIsValidGeometry)
2313 207 : *pbIsValidGeometry = bValidTopology && !bMixedUpGeometries;
2314 :
2315 : /* --------------------------------------------------------------------- */
2316 : /* Things broke down - just mark everything as top-level so it gets */
2317 : /* turned into a multipolygon. */
2318 : /* --------------------------------------------------------------------- */
2319 14715 : if (!bValidTopology || bMixedUpGeometries)
2320 : {
2321 71558 : for (auto &sPolyEx : asPolyEx)
2322 : {
2323 57610 : sPolyEx.bIsTopLevel = true;
2324 : }
2325 13948 : nCountTopLevel = asPolyEx.size();
2326 : }
2327 :
2328 : /* -------------------------------------------------------------------- */
2329 : /* Try to turn into one or more polygons based on the ring */
2330 : /* relationships. */
2331 : /* -------------------------------------------------------------------- */
2332 : // STEP 3: Sort again in initial order.
2333 14715 : std::sort(asPolyEx.begin(), asPolyEx.end(),
2334 : OGRGeometryFactoryCompareByIndex);
2335 :
2336 : // STEP 4: Add holes as rings of their enclosing polygon.
2337 74761 : for (auto &sPolyEx : asPolyEx)
2338 : {
2339 60046 : if (!sPolyEx.bIsTopLevel)
2340 : {
2341 852 : sPolyEx.poEnclosingPolygon->addRing(std::unique_ptr<OGRCurve>(
2342 : sPolyEx.poPolygon->stealExteriorRingCurve()));
2343 852 : sPolyEx.poPolygon.reset();
2344 : }
2345 59194 : else if (nCountTopLevel == 1)
2346 : {
2347 100 : geom = std::move(sPolyEx.poPolygon);
2348 : }
2349 : }
2350 :
2351 : // STEP 5: Add toplevel polygons.
2352 14715 : if (nCountTopLevel > 1)
2353 : {
2354 14615 : std::unique_ptr<OGRMultiSurface> poMS;
2355 14615 : if (bHasCurves)
2356 9 : poMS = std::make_unique<OGRMultiSurface>();
2357 : else
2358 14606 : poMS = std::make_unique<OGRMultiPolygon>();
2359 74427 : for (auto &sPolyEx : asPolyEx)
2360 : {
2361 59812 : if (sPolyEx.bIsTopLevel)
2362 : {
2363 59094 : poMS->addGeometry(std::move(sPolyEx.poPolygon));
2364 : }
2365 : }
2366 14615 : geom = std::move(poMS);
2367 : }
2368 :
2369 14715 : return geom;
2370 : }
2371 :
2372 : /************************************************************************/
2373 : /* createFromGML() */
2374 : /************************************************************************/
2375 :
2376 : /**
2377 : * \brief Create geometry from GML.
2378 : *
2379 : * This method translates a fragment of GML containing only the geometry
2380 : * portion into a corresponding OGRGeometry. There are many limitations
2381 : * on the forms of GML geometries supported by this parser, but they are
2382 : * too numerous to list here.
2383 : *
2384 : * The following GML2 elements are parsed : Point, LineString, Polygon,
2385 : * MultiPoint, MultiLineString, MultiPolygon, MultiGeometry.
2386 : *
2387 : * The following GML3 elements are parsed : Surface,
2388 : * MultiSurface, PolygonPatch, Triangle, Rectangle, Curve, MultiCurve,
2389 : * LineStringSegment, Arc, Circle, CompositeSurface, OrientableSurface, Solid,
2390 : * Shell, Tin, TriangulatedSurface.
2391 : *
2392 : * Arc and Circle elements are returned as curves by default. Stroking to
2393 : * linestrings can be done with
2394 : * OGR_G_ForceTo(hGeom, OGR_GT_GetLinear(OGR_G_GetGeometryType(hGeom)), NULL).
2395 : * A 4 degrees step is used by default, unless the user
2396 : * has overridden the value with the OGR_ARC_STEPSIZE configuration variable.
2397 : *
2398 : * The C function OGR_G_CreateFromGML() is the same as this method.
2399 : *
2400 : * @param pszData The GML fragment for the geometry.
2401 : *
2402 : * @return a geometry on success, or NULL on error.
2403 : *
2404 : * @see OGR_G_ForceTo()
2405 : * @see OGR_GT_GetLinear()
2406 : * @see OGR_G_GetGeometryType()
2407 : */
2408 :
2409 0 : OGRGeometry *OGRGeometryFactory::createFromGML(const char *pszData)
2410 :
2411 : {
2412 : OGRGeometryH hGeom;
2413 :
2414 0 : hGeom = OGR_G_CreateFromGML(pszData);
2415 :
2416 0 : return OGRGeometry::FromHandle(hGeom);
2417 : }
2418 :
2419 : /************************************************************************/
2420 : /* createFromGEOS() */
2421 : /************************************************************************/
2422 :
2423 : /** Builds a OGRGeometry* from a GEOSGeom.
2424 : * @param hGEOSCtxt GEOS context
2425 : * @param geosGeom GEOS geometry
2426 : * @return a OGRGeometry*
2427 : */
2428 4140 : OGRGeometry *OGRGeometryFactory::createFromGEOS(
2429 : UNUSED_IF_NO_GEOS GEOSContextHandle_t hGEOSCtxt,
2430 : UNUSED_IF_NO_GEOS GEOSGeom geosGeom)
2431 :
2432 : {
2433 : #ifndef HAVE_GEOS
2434 :
2435 : CPLError(CE_Failure, CPLE_NotSupported, "GEOS support not enabled.");
2436 : return nullptr;
2437 :
2438 : #else
2439 :
2440 4140 : size_t nSize = 0;
2441 4140 : unsigned char *pabyBuf = nullptr;
2442 4140 : OGRGeometry *poGeometry = nullptr;
2443 :
2444 : // Special case as POINT EMPTY cannot be translated to WKB.
2445 4409 : if (GEOSGeomTypeId_r(hGEOSCtxt, geosGeom) == GEOS_POINT &&
2446 269 : GEOSisEmpty_r(hGEOSCtxt, geosGeom))
2447 14 : return new OGRPoint();
2448 :
2449 : const int nCoordDim =
2450 4126 : GEOSGeom_getCoordinateDimension_r(hGEOSCtxt, geosGeom);
2451 4126 : GEOSWKBWriter *wkbwriter = GEOSWKBWriter_create_r(hGEOSCtxt);
2452 4126 : GEOSWKBWriter_setOutputDimension_r(hGEOSCtxt, wkbwriter, nCoordDim);
2453 4126 : pabyBuf = GEOSWKBWriter_write_r(hGEOSCtxt, wkbwriter, geosGeom, &nSize);
2454 4126 : GEOSWKBWriter_destroy_r(hGEOSCtxt, wkbwriter);
2455 :
2456 4126 : if (pabyBuf == nullptr || nSize == 0)
2457 : {
2458 0 : return nullptr;
2459 : }
2460 :
2461 4126 : if (OGRGeometryFactory::createFromWkb(pabyBuf, nullptr, &poGeometry,
2462 4126 : static_cast<int>(nSize)) !=
2463 : OGRERR_NONE)
2464 : {
2465 0 : poGeometry = nullptr;
2466 : }
2467 :
2468 4126 : GEOSFree_r(hGEOSCtxt, pabyBuf);
2469 :
2470 4126 : return poGeometry;
2471 :
2472 : #endif // HAVE_GEOS
2473 : }
2474 :
2475 : /************************************************************************/
2476 : /* haveGEOS() */
2477 : /************************************************************************/
2478 :
2479 : /**
2480 : * \brief Test if GEOS enabled.
2481 : *
2482 : * This static method returns TRUE if GEOS support is built into OGR,
2483 : * otherwise it returns FALSE.
2484 : *
2485 : * @return TRUE if available, otherwise FALSE.
2486 : */
2487 :
2488 33917 : bool OGRGeometryFactory::haveGEOS()
2489 :
2490 : {
2491 : #ifndef HAVE_GEOS
2492 : return false;
2493 : #else
2494 33917 : return true;
2495 : #endif
2496 : }
2497 :
2498 : /************************************************************************/
2499 : /* createFromFgf() */
2500 : /************************************************************************/
2501 :
2502 : /**
2503 : * \brief Create a geometry object of the appropriate type from its FGF (FDO
2504 : * Geometry Format) binary representation.
2505 : *
2506 : * Also note that this is a static method, and that there
2507 : * is no need to instantiate an OGRGeometryFactory object.
2508 : *
2509 : * The C function OGR_G_CreateFromFgf() is the same as this method.
2510 : *
2511 : * @param pabyData pointer to the input BLOB data.
2512 : * @param poSR pointer to the spatial reference to be assigned to the
2513 : * created geometry object. This may be NULL.
2514 : * @param ppoReturn the newly created geometry object will be assigned to the
2515 : * indicated pointer on return. This will be NULL in case
2516 : * of failure, but NULL might be a valid return for a NULL
2517 : * shape.
2518 : * @param nBytes the number of bytes available in pabyData.
2519 : * @param pnBytesConsumed if not NULL, it will be set to the number of bytes
2520 : * consumed (at most nBytes).
2521 : *
2522 : * @return OGRERR_NONE if all goes well, otherwise any of
2523 : * OGRERR_NOT_ENOUGH_DATA, OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or
2524 : * OGRERR_CORRUPT_DATA may be returned.
2525 : */
2526 :
2527 293 : OGRErr OGRGeometryFactory::createFromFgf(const void *pabyData,
2528 : OGRSpatialReference *poSR,
2529 : OGRGeometry **ppoReturn, int nBytes,
2530 : int *pnBytesConsumed)
2531 :
2532 : {
2533 293 : return createFromFgfInternal(static_cast<const GByte *>(pabyData), poSR,
2534 293 : ppoReturn, nBytes, pnBytesConsumed, 0);
2535 : }
2536 :
2537 : /************************************************************************/
2538 : /* createFromFgfInternal() */
2539 : /************************************************************************/
2540 :
2541 296 : OGRErr OGRGeometryFactory::createFromFgfInternal(
2542 : const unsigned char *pabyData, OGRSpatialReference *poSR,
2543 : OGRGeometry **ppoReturn, int nBytes, int *pnBytesConsumed, int nRecLevel)
2544 : {
2545 : // Arbitrary value, but certainly large enough for reasonable usages.
2546 296 : if (nRecLevel == 32)
2547 : {
2548 0 : CPLError(CE_Failure, CPLE_AppDefined,
2549 : "Too many recursion levels (%d) while parsing FGF geometry.",
2550 : nRecLevel);
2551 0 : return OGRERR_CORRUPT_DATA;
2552 : }
2553 :
2554 296 : *ppoReturn = nullptr;
2555 :
2556 296 : if (nBytes < 4)
2557 109 : return OGRERR_NOT_ENOUGH_DATA;
2558 :
2559 : /* -------------------------------------------------------------------- */
2560 : /* Decode the geometry type. */
2561 : /* -------------------------------------------------------------------- */
2562 187 : GInt32 nGType = 0;
2563 187 : memcpy(&nGType, pabyData + 0, 4);
2564 187 : CPL_LSBPTR32(&nGType);
2565 :
2566 187 : if (nGType < 0 || nGType > 13)
2567 173 : return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
2568 :
2569 : /* -------------------------------------------------------------------- */
2570 : /* Decode the dimensionality if appropriate. */
2571 : /* -------------------------------------------------------------------- */
2572 14 : int nTupleSize = 0;
2573 14 : GInt32 nGDim = 0;
2574 :
2575 : // TODO: Why is this a switch?
2576 14 : switch (nGType)
2577 : {
2578 9 : case 1: // Point
2579 : case 2: // LineString
2580 : case 3: // Polygon
2581 9 : if (nBytes < 8)
2582 0 : return OGRERR_NOT_ENOUGH_DATA;
2583 :
2584 9 : memcpy(&nGDim, pabyData + 4, 4);
2585 9 : CPL_LSBPTR32(&nGDim);
2586 :
2587 9 : if (nGDim < 0 || nGDim > 3)
2588 0 : return OGRERR_CORRUPT_DATA;
2589 :
2590 9 : nTupleSize = 2;
2591 9 : if (nGDim & 0x01) // Z
2592 1 : nTupleSize++;
2593 9 : if (nGDim & 0x02) // M
2594 0 : nTupleSize++;
2595 :
2596 9 : break;
2597 :
2598 5 : default:
2599 5 : break;
2600 : }
2601 :
2602 14 : OGRGeometry *poGeom = nullptr;
2603 :
2604 : /* -------------------------------------------------------------------- */
2605 : /* None */
2606 : /* -------------------------------------------------------------------- */
2607 14 : if (nGType == 0)
2608 : {
2609 0 : if (pnBytesConsumed)
2610 0 : *pnBytesConsumed = 4;
2611 : }
2612 :
2613 : /* -------------------------------------------------------------------- */
2614 : /* Point */
2615 : /* -------------------------------------------------------------------- */
2616 14 : else if (nGType == 1)
2617 : {
2618 3 : if (nBytes < nTupleSize * 8 + 8)
2619 0 : return OGRERR_NOT_ENOUGH_DATA;
2620 :
2621 3 : double adfTuple[4] = {0.0, 0.0, 0.0, 0.0};
2622 3 : memcpy(adfTuple, pabyData + 8, nTupleSize * 8);
2623 : #ifdef CPL_MSB
2624 : for (int iOrdinal = 0; iOrdinal < nTupleSize; iOrdinal++)
2625 : CPL_SWAP64PTR(adfTuple + iOrdinal);
2626 : #endif
2627 3 : if (nTupleSize > 2)
2628 1 : poGeom = new OGRPoint(adfTuple[0], adfTuple[1], adfTuple[2]);
2629 : else
2630 2 : poGeom = new OGRPoint(adfTuple[0], adfTuple[1]);
2631 :
2632 3 : if (pnBytesConsumed)
2633 1 : *pnBytesConsumed = 8 + nTupleSize * 8;
2634 : }
2635 :
2636 : /* -------------------------------------------------------------------- */
2637 : /* LineString */
2638 : /* -------------------------------------------------------------------- */
2639 11 : else if (nGType == 2)
2640 : {
2641 2 : if (nBytes < 12)
2642 0 : return OGRERR_NOT_ENOUGH_DATA;
2643 :
2644 2 : GInt32 nPointCount = 0;
2645 2 : memcpy(&nPointCount, pabyData + 8, 4);
2646 2 : CPL_LSBPTR32(&nPointCount);
2647 :
2648 2 : if (nPointCount < 0 || nPointCount > INT_MAX / (nTupleSize * 8))
2649 0 : return OGRERR_CORRUPT_DATA;
2650 :
2651 2 : if (nBytes - 12 < nTupleSize * 8 * nPointCount)
2652 0 : return OGRERR_NOT_ENOUGH_DATA;
2653 :
2654 2 : OGRLineString *poLS = new OGRLineString();
2655 2 : poGeom = poLS;
2656 2 : poLS->setNumPoints(nPointCount);
2657 :
2658 4 : for (int iPoint = 0; iPoint < nPointCount; iPoint++)
2659 : {
2660 2 : double adfTuple[4] = {0.0, 0.0, 0.0, 0.0};
2661 2 : memcpy(adfTuple, pabyData + 12 + 8 * nTupleSize * iPoint,
2662 2 : nTupleSize * 8);
2663 : #ifdef CPL_MSB
2664 : for (int iOrdinal = 0; iOrdinal < nTupleSize; iOrdinal++)
2665 : CPL_SWAP64PTR(adfTuple + iOrdinal);
2666 : #endif
2667 2 : if (nTupleSize > 2)
2668 0 : poLS->setPoint(iPoint, adfTuple[0], adfTuple[1], adfTuple[2]);
2669 : else
2670 2 : poLS->setPoint(iPoint, adfTuple[0], adfTuple[1]);
2671 : }
2672 :
2673 2 : if (pnBytesConsumed)
2674 0 : *pnBytesConsumed = 12 + nTupleSize * 8 * nPointCount;
2675 : }
2676 :
2677 : /* -------------------------------------------------------------------- */
2678 : /* Polygon */
2679 : /* -------------------------------------------------------------------- */
2680 9 : else if (nGType == 3)
2681 : {
2682 4 : if (nBytes < 12)
2683 0 : return OGRERR_NOT_ENOUGH_DATA;
2684 :
2685 4 : GInt32 nRingCount = 0;
2686 4 : memcpy(&nRingCount, pabyData + 8, 4);
2687 4 : CPL_LSBPTR32(&nRingCount);
2688 :
2689 4 : if (nRingCount < 0 || nRingCount > INT_MAX / 4)
2690 0 : return OGRERR_CORRUPT_DATA;
2691 :
2692 : // Each ring takes at least 4 bytes.
2693 4 : if (nBytes - 12 < nRingCount * 4)
2694 0 : return OGRERR_NOT_ENOUGH_DATA;
2695 :
2696 4 : int nNextByte = 12;
2697 :
2698 4 : OGRPolygon *poPoly = new OGRPolygon();
2699 4 : poGeom = poPoly;
2700 :
2701 10 : for (int iRing = 0; iRing < nRingCount; iRing++)
2702 : {
2703 6 : if (nBytes - nNextByte < 4)
2704 : {
2705 0 : delete poGeom;
2706 0 : return OGRERR_NOT_ENOUGH_DATA;
2707 : }
2708 :
2709 6 : GInt32 nPointCount = 0;
2710 6 : memcpy(&nPointCount, pabyData + nNextByte, 4);
2711 6 : CPL_LSBPTR32(&nPointCount);
2712 :
2713 6 : if (nPointCount < 0 || nPointCount > INT_MAX / (nTupleSize * 8))
2714 : {
2715 0 : delete poGeom;
2716 0 : return OGRERR_CORRUPT_DATA;
2717 : }
2718 :
2719 6 : nNextByte += 4;
2720 :
2721 6 : if (nBytes - nNextByte < nTupleSize * 8 * nPointCount)
2722 : {
2723 0 : delete poGeom;
2724 0 : return OGRERR_NOT_ENOUGH_DATA;
2725 : }
2726 :
2727 6 : OGRLinearRing *poLR = new OGRLinearRing();
2728 6 : poLR->setNumPoints(nPointCount);
2729 :
2730 12 : for (int iPoint = 0; iPoint < nPointCount; iPoint++)
2731 : {
2732 6 : double adfTuple[4] = {0.0, 0.0, 0.0, 0.0};
2733 6 : memcpy(adfTuple, pabyData + nNextByte, nTupleSize * 8);
2734 6 : nNextByte += nTupleSize * 8;
2735 :
2736 : #ifdef CPL_MSB
2737 : for (int iOrdinal = 0; iOrdinal < nTupleSize; iOrdinal++)
2738 : CPL_SWAP64PTR(adfTuple + iOrdinal);
2739 : #endif
2740 6 : if (nTupleSize > 2)
2741 0 : poLR->setPoint(iPoint, adfTuple[0], adfTuple[1],
2742 : adfTuple[2]);
2743 : else
2744 6 : poLR->setPoint(iPoint, adfTuple[0], adfTuple[1]);
2745 : }
2746 :
2747 6 : poPoly->addRingDirectly(poLR);
2748 : }
2749 :
2750 4 : if (pnBytesConsumed)
2751 2 : *pnBytesConsumed = nNextByte;
2752 : }
2753 :
2754 : /* -------------------------------------------------------------------- */
2755 : /* GeometryCollections of various kinds. */
2756 : /* -------------------------------------------------------------------- */
2757 5 : else if (nGType == 4 // MultiPoint
2758 5 : || nGType == 5 // MultiLineString
2759 5 : || nGType == 6 // MultiPolygon
2760 3 : || nGType == 7) // MultiGeometry
2761 : {
2762 5 : if (nBytes < 8)
2763 3 : return OGRERR_NOT_ENOUGH_DATA;
2764 :
2765 5 : GInt32 nGeomCount = 0;
2766 5 : memcpy(&nGeomCount, pabyData + 4, 4);
2767 5 : CPL_LSBPTR32(&nGeomCount);
2768 :
2769 5 : if (nGeomCount < 0 || nGeomCount > INT_MAX / 4)
2770 0 : return OGRERR_CORRUPT_DATA;
2771 :
2772 : // Each geometry takes at least 4 bytes.
2773 5 : if (nBytes - 8 < 4 * nGeomCount)
2774 2 : return OGRERR_NOT_ENOUGH_DATA;
2775 :
2776 3 : OGRGeometryCollection *poGC = nullptr;
2777 3 : if (nGType == 4)
2778 0 : poGC = new OGRMultiPoint();
2779 3 : else if (nGType == 5)
2780 0 : poGC = new OGRMultiLineString();
2781 3 : else if (nGType == 6)
2782 1 : poGC = new OGRMultiPolygon();
2783 2 : else if (nGType == 7)
2784 2 : poGC = new OGRGeometryCollection();
2785 :
2786 3 : int nBytesUsed = 8;
2787 :
2788 5 : for (int iGeom = 0; iGeom < nGeomCount; iGeom++)
2789 : {
2790 3 : int nThisGeomSize = 0;
2791 3 : OGRGeometry *poThisGeom = nullptr;
2792 :
2793 6 : const OGRErr eErr = createFromFgfInternal(
2794 3 : pabyData + nBytesUsed, poSR, &poThisGeom, nBytes - nBytesUsed,
2795 : &nThisGeomSize, nRecLevel + 1);
2796 3 : if (eErr != OGRERR_NONE)
2797 : {
2798 0 : delete poGC;
2799 1 : return eErr;
2800 : }
2801 :
2802 3 : nBytesUsed += nThisGeomSize;
2803 3 : if (poThisGeom != nullptr)
2804 : {
2805 3 : const OGRErr eErr2 = poGC->addGeometryDirectly(poThisGeom);
2806 3 : if (eErr2 != OGRERR_NONE)
2807 : {
2808 1 : delete poGC;
2809 1 : delete poThisGeom;
2810 1 : return eErr2;
2811 : }
2812 : }
2813 : }
2814 :
2815 2 : poGeom = poGC;
2816 2 : if (pnBytesConsumed)
2817 2 : *pnBytesConsumed = nBytesUsed;
2818 : }
2819 :
2820 : /* -------------------------------------------------------------------- */
2821 : /* Currently unsupported geometry. */
2822 : /* */
2823 : /* We need to add 10/11/12/13 curve types in some fashion. */
2824 : /* -------------------------------------------------------------------- */
2825 : else
2826 : {
2827 0 : return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
2828 : }
2829 :
2830 : /* -------------------------------------------------------------------- */
2831 : /* Assign spatial reference system. */
2832 : /* -------------------------------------------------------------------- */
2833 11 : if (poGeom != nullptr && poSR)
2834 0 : poGeom->assignSpatialReference(poSR);
2835 11 : *ppoReturn = poGeom;
2836 :
2837 11 : return OGRERR_NONE;
2838 : }
2839 :
2840 : /************************************************************************/
2841 : /* OGR_G_CreateFromFgf() */
2842 : /************************************************************************/
2843 :
2844 : /**
2845 : * \brief Create a geometry object of the appropriate type from its FGF
2846 : * (FDO Geometry Format) binary representation.
2847 : *
2848 : * See OGRGeometryFactory::createFromFgf() */
2849 0 : OGRErr CPL_DLL OGR_G_CreateFromFgf(const void *pabyData,
2850 : OGRSpatialReferenceH hSRS,
2851 : OGRGeometryH *phGeometry, int nBytes,
2852 : int *pnBytesConsumed)
2853 :
2854 : {
2855 0 : return OGRGeometryFactory::createFromFgf(
2856 : pabyData, OGRSpatialReference::FromHandle(hSRS),
2857 0 : reinterpret_cast<OGRGeometry **>(phGeometry), nBytes, pnBytesConsumed);
2858 : }
2859 :
2860 : /************************************************************************/
2861 : /* SplitLineStringAtDateline() */
2862 : /************************************************************************/
2863 :
2864 8 : static void SplitLineStringAtDateline(OGRGeometryCollection *poMulti,
2865 : const OGRLineString *poLS,
2866 : double dfDateLineOffset, double dfXOffset)
2867 : {
2868 8 : const double dfLeftBorderX = 180 - dfDateLineOffset;
2869 8 : const double dfRightBorderX = -180 + dfDateLineOffset;
2870 8 : const double dfDiffSpace = 360 - dfDateLineOffset;
2871 :
2872 8 : const bool bIs3D = poLS->getCoordinateDimension() == 3;
2873 8 : OGRLineString *poNewLS = new OGRLineString();
2874 8 : poMulti->addGeometryDirectly(poNewLS);
2875 35 : for (int i = 0; i < poLS->getNumPoints(); i++)
2876 : {
2877 27 : const double dfX = poLS->getX(i) + dfXOffset;
2878 27 : if (i > 0 && fabs(dfX - (poLS->getX(i - 1) + dfXOffset)) > dfDiffSpace)
2879 : {
2880 9 : double dfX1 = poLS->getX(i - 1) + dfXOffset;
2881 9 : double dfY1 = poLS->getY(i - 1);
2882 9 : double dfZ1 = poLS->getY(i - 1);
2883 9 : double dfX2 = poLS->getX(i) + dfXOffset;
2884 9 : double dfY2 = poLS->getY(i);
2885 9 : double dfZ2 = poLS->getY(i);
2886 :
2887 8 : if (dfX1 > -180 && dfX1 < dfRightBorderX && dfX2 == 180 &&
2888 0 : i + 1 < poLS->getNumPoints() &&
2889 17 : poLS->getX(i + 1) + dfXOffset > -180 &&
2890 0 : poLS->getX(i + 1) + dfXOffset < dfRightBorderX)
2891 : {
2892 0 : if (bIs3D)
2893 0 : poNewLS->addPoint(-180, poLS->getY(i), poLS->getZ(i));
2894 : else
2895 0 : poNewLS->addPoint(-180, poLS->getY(i));
2896 :
2897 0 : i++;
2898 :
2899 0 : if (bIs3D)
2900 0 : poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i),
2901 : poLS->getZ(i));
2902 : else
2903 0 : poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i));
2904 0 : continue;
2905 : }
2906 4 : else if (dfX1 > dfLeftBorderX && dfX1 < 180 && dfX2 == -180 &&
2907 0 : i + 1 < poLS->getNumPoints() &&
2908 13 : poLS->getX(i + 1) + dfXOffset > dfLeftBorderX &&
2909 0 : poLS->getX(i + 1) + dfXOffset < 180)
2910 : {
2911 0 : if (bIs3D)
2912 0 : poNewLS->addPoint(180, poLS->getY(i), poLS->getZ(i));
2913 : else
2914 0 : poNewLS->addPoint(180, poLS->getY(i));
2915 :
2916 0 : i++;
2917 :
2918 0 : if (bIs3D)
2919 0 : poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i),
2920 : poLS->getZ(i));
2921 : else
2922 0 : poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i));
2923 0 : continue;
2924 : }
2925 :
2926 9 : if (dfX1 < dfRightBorderX && dfX2 > dfLeftBorderX)
2927 : {
2928 5 : std::swap(dfX1, dfX2);
2929 5 : std::swap(dfY1, dfY2);
2930 5 : std::swap(dfZ1, dfZ2);
2931 : }
2932 9 : if (dfX1 > dfLeftBorderX && dfX2 < dfRightBorderX)
2933 9 : dfX2 += 360;
2934 :
2935 9 : if (dfX1 <= 180 && dfX2 >= 180 && dfX1 < dfX2)
2936 : {
2937 9 : const double dfRatio = (180 - dfX1) / (dfX2 - dfX1);
2938 9 : const double dfY = dfRatio * dfY2 + (1 - dfRatio) * dfY1;
2939 9 : const double dfZ = dfRatio * dfZ2 + (1 - dfRatio) * dfZ1;
2940 : double dfNewX =
2941 9 : poLS->getX(i - 1) + dfXOffset > dfLeftBorderX ? 180 : -180;
2942 18 : if (poNewLS->getNumPoints() == 0 ||
2943 18 : poNewLS->getX(poNewLS->getNumPoints() - 1) != dfNewX ||
2944 2 : poNewLS->getY(poNewLS->getNumPoints() - 1) != dfY)
2945 : {
2946 7 : if (bIs3D)
2947 0 : poNewLS->addPoint(dfNewX, dfY, dfZ);
2948 : else
2949 7 : poNewLS->addPoint(dfNewX, dfY);
2950 : }
2951 9 : poNewLS = new OGRLineString();
2952 9 : if (bIs3D)
2953 0 : poNewLS->addPoint(
2954 0 : poLS->getX(i - 1) + dfXOffset > dfLeftBorderX ? -180
2955 : : 180,
2956 : dfY, dfZ);
2957 : else
2958 9 : poNewLS->addPoint(
2959 9 : poLS->getX(i - 1) + dfXOffset > dfLeftBorderX ? -180
2960 : : 180,
2961 : dfY);
2962 9 : poMulti->addGeometryDirectly(poNewLS);
2963 : }
2964 : else
2965 : {
2966 0 : poNewLS = new OGRLineString();
2967 0 : poMulti->addGeometryDirectly(poNewLS);
2968 : }
2969 : }
2970 27 : if (bIs3D)
2971 0 : poNewLS->addPoint(dfX, poLS->getY(i), poLS->getZ(i));
2972 : else
2973 27 : poNewLS->addPoint(dfX, poLS->getY(i));
2974 : }
2975 8 : }
2976 :
2977 : /************************************************************************/
2978 : /* FixPolygonCoordinatesAtDateLine() */
2979 : /************************************************************************/
2980 :
2981 : #ifdef HAVE_GEOS
2982 4 : static void FixPolygonCoordinatesAtDateLine(OGRPolygon *poPoly,
2983 : double dfDateLineOffset)
2984 : {
2985 4 : const double dfLeftBorderX = 180 - dfDateLineOffset;
2986 4 : const double dfRightBorderX = -180 + dfDateLineOffset;
2987 4 : const double dfDiffSpace = 360 - dfDateLineOffset;
2988 :
2989 8 : for (int iPart = 0; iPart < 1 + poPoly->getNumInteriorRings(); iPart++)
2990 : {
2991 4 : OGRLineString *poLS = (iPart == 0) ? poPoly->getExteriorRing()
2992 4 : : poPoly->getInteriorRing(iPart - 1);
2993 4 : bool bGoEast = false;
2994 4 : const bool bIs3D = poLS->getCoordinateDimension() == 3;
2995 36 : for (int i = 1; i < poLS->getNumPoints(); i++)
2996 : {
2997 32 : double dfX = poLS->getX(i);
2998 32 : const double dfPrevX = poLS->getX(i - 1);
2999 32 : const double dfDiffLong = fabs(dfX - dfPrevX);
3000 32 : if (dfDiffLong > dfDiffSpace)
3001 : {
3002 18 : if ((dfPrevX > dfLeftBorderX && dfX < dfRightBorderX) ||
3003 6 : (dfX < 0 && bGoEast))
3004 : {
3005 16 : dfX += 360;
3006 16 : bGoEast = true;
3007 16 : if (bIs3D)
3008 0 : poLS->setPoint(i, dfX, poLS->getY(i), poLS->getZ(i));
3009 : else
3010 16 : poLS->setPoint(i, dfX, poLS->getY(i));
3011 : }
3012 2 : else if (dfPrevX < dfRightBorderX && dfX > dfLeftBorderX)
3013 : {
3014 8 : for (int j = i - 1; j >= 0; j--)
3015 : {
3016 6 : dfX = poLS->getX(j);
3017 6 : if (dfX < 0)
3018 : {
3019 6 : if (bIs3D)
3020 0 : poLS->setPoint(j, dfX + 360, poLS->getY(j),
3021 : poLS->getZ(j));
3022 : else
3023 6 : poLS->setPoint(j, dfX + 360, poLS->getY(j));
3024 : }
3025 : }
3026 2 : bGoEast = false;
3027 : }
3028 : else
3029 : {
3030 0 : bGoEast = false;
3031 : }
3032 : }
3033 : }
3034 : }
3035 4 : }
3036 : #endif
3037 :
3038 : /************************************************************************/
3039 : /* AddOffsetToLon() */
3040 : /************************************************************************/
3041 :
3042 17 : static void AddOffsetToLon(OGRGeometry *poGeom, double dfOffset)
3043 : {
3044 17 : switch (wkbFlatten(poGeom->getGeometryType()))
3045 : {
3046 7 : case wkbPolygon:
3047 : {
3048 14 : for (auto poSubGeom : *(poGeom->toPolygon()))
3049 : {
3050 7 : AddOffsetToLon(poSubGeom, dfOffset);
3051 : }
3052 :
3053 7 : break;
3054 : }
3055 :
3056 0 : case wkbMultiLineString:
3057 : case wkbMultiPolygon:
3058 : case wkbGeometryCollection:
3059 : {
3060 0 : for (auto poSubGeom : *(poGeom->toGeometryCollection()))
3061 : {
3062 0 : AddOffsetToLon(poSubGeom, dfOffset);
3063 : }
3064 :
3065 0 : break;
3066 : }
3067 :
3068 10 : case wkbLineString:
3069 : {
3070 10 : OGRLineString *poLineString = poGeom->toLineString();
3071 10 : const int nPointCount = poLineString->getNumPoints();
3072 10 : const int nCoordDim = poLineString->getCoordinateDimension();
3073 63 : for (int iPoint = 0; iPoint < nPointCount; iPoint++)
3074 : {
3075 53 : if (nCoordDim == 2)
3076 106 : poLineString->setPoint(
3077 53 : iPoint, poLineString->getX(iPoint) + dfOffset,
3078 : poLineString->getY(iPoint));
3079 : else
3080 0 : poLineString->setPoint(
3081 0 : iPoint, poLineString->getX(iPoint) + dfOffset,
3082 : poLineString->getY(iPoint), poLineString->getZ(iPoint));
3083 : }
3084 10 : break;
3085 : }
3086 :
3087 0 : default:
3088 0 : break;
3089 : }
3090 17 : }
3091 :
3092 : /************************************************************************/
3093 : /* AddSimpleGeomToMulti() */
3094 : /************************************************************************/
3095 :
3096 : #ifdef HAVE_GEOS
3097 12 : static void AddSimpleGeomToMulti(OGRGeometryCollection *poMulti,
3098 : const OGRGeometry *poGeom)
3099 : {
3100 12 : switch (wkbFlatten(poGeom->getGeometryType()))
3101 : {
3102 12 : case wkbPolygon:
3103 : case wkbLineString:
3104 12 : poMulti->addGeometry(poGeom);
3105 12 : break;
3106 :
3107 0 : case wkbMultiLineString:
3108 : case wkbMultiPolygon:
3109 : case wkbGeometryCollection:
3110 : {
3111 0 : for (const auto poSubGeom : *(poGeom->toGeometryCollection()))
3112 : {
3113 0 : AddSimpleGeomToMulti(poMulti, poSubGeom);
3114 : }
3115 0 : break;
3116 : }
3117 :
3118 0 : default:
3119 0 : break;
3120 : }
3121 12 : }
3122 : #endif // #ifdef HAVE_GEOS
3123 :
3124 : /************************************************************************/
3125 : /* WrapPointDateLine() */
3126 : /************************************************************************/
3127 :
3128 14 : static void WrapPointDateLine(OGRPoint *poPoint)
3129 : {
3130 14 : if (poPoint->getX() > 180)
3131 : {
3132 2 : poPoint->setX(fmod(poPoint->getX() + 180, 360) - 180);
3133 : }
3134 12 : else if (poPoint->getX() < -180)
3135 : {
3136 3 : poPoint->setX(-(fmod(-poPoint->getX() + 180, 360) - 180));
3137 : }
3138 14 : }
3139 :
3140 : /************************************************************************/
3141 : /* CutGeometryOnDateLineAndAddToMulti() */
3142 : /************************************************************************/
3143 :
3144 73 : static void CutGeometryOnDateLineAndAddToMulti(OGRGeometryCollection *poMulti,
3145 : const OGRGeometry *poGeom,
3146 : double dfDateLineOffset)
3147 : {
3148 73 : const OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
3149 73 : switch (eGeomType)
3150 : {
3151 1 : case wkbPoint:
3152 : {
3153 1 : auto poPoint = poGeom->toPoint()->clone();
3154 1 : WrapPointDateLine(poPoint);
3155 1 : poMulti->addGeometryDirectly(poPoint);
3156 1 : break;
3157 : }
3158 :
3159 57 : case wkbPolygon:
3160 : case wkbLineString:
3161 : {
3162 57 : bool bSplitLineStringAtDateline = false;
3163 57 : OGREnvelope oEnvelope;
3164 :
3165 57 : poGeom->getEnvelope(&oEnvelope);
3166 57 : const bool bAroundMinus180 = (oEnvelope.MinX < -180.0);
3167 :
3168 : // Naive heuristics... Place to improve.
3169 : #ifdef HAVE_GEOS
3170 57 : std::unique_ptr<OGRGeometry> poDupGeom;
3171 57 : bool bWrapDateline = false;
3172 : #endif
3173 :
3174 57 : const double dfLeftBorderX = 180 - dfDateLineOffset;
3175 57 : const double dfRightBorderX = -180 + dfDateLineOffset;
3176 57 : const double dfDiffSpace = 360 - dfDateLineOffset;
3177 :
3178 57 : const double dfXOffset = (bAroundMinus180) ? 360.0 : 0.0;
3179 57 : if (oEnvelope.MinX < -180 || oEnvelope.MaxX > 180 ||
3180 55 : (oEnvelope.MinX + dfXOffset > dfLeftBorderX &&
3181 12 : oEnvelope.MaxX + dfXOffset > 180))
3182 : {
3183 : #ifndef HAVE_GEOS
3184 : CPLError(CE_Failure, CPLE_NotSupported,
3185 : "GEOS support not enabled.");
3186 : #else
3187 2 : bWrapDateline = true;
3188 : #endif
3189 : }
3190 : else
3191 : {
3192 : auto poLS = eGeomType == wkbPolygon
3193 55 : ? poGeom->toPolygon()->getExteriorRing()
3194 14 : : poGeom->toLineString();
3195 55 : if (poLS)
3196 : {
3197 55 : double dfMaxSmallDiffLong = 0;
3198 55 : bool bHasBigDiff = false;
3199 : // Detect big gaps in longitude.
3200 317 : for (int i = 1; i < poLS->getNumPoints(); i++)
3201 : {
3202 262 : const double dfPrevX = poLS->getX(i - 1) + dfXOffset;
3203 262 : const double dfX = poLS->getX(i) + dfXOffset;
3204 262 : const double dfDiffLong = fabs(dfX - dfPrevX);
3205 :
3206 262 : if (dfDiffLong > dfDiffSpace &&
3207 11 : ((dfX > dfLeftBorderX &&
3208 10 : dfPrevX < dfRightBorderX) ||
3209 10 : (dfPrevX > dfLeftBorderX && dfX < dfRightBorderX)))
3210 : {
3211 21 : constexpr double EPSILON = 1e-5;
3212 25 : if (!(std::fabs(dfDiffLong - 360) < EPSILON &&
3213 4 : std::fabs(std::fabs(poLS->getY(i)) - 90) <
3214 : EPSILON))
3215 : {
3216 17 : bHasBigDiff = true;
3217 21 : }
3218 : }
3219 241 : else if (dfDiffLong > dfMaxSmallDiffLong)
3220 61 : dfMaxSmallDiffLong = dfDiffLong;
3221 : }
3222 55 : if (bHasBigDiff && dfMaxSmallDiffLong < dfDateLineOffset)
3223 : {
3224 12 : if (eGeomType == wkbLineString)
3225 8 : bSplitLineStringAtDateline = true;
3226 : else
3227 : {
3228 : #ifndef HAVE_GEOS
3229 : CPLError(CE_Failure, CPLE_NotSupported,
3230 : "GEOS support not enabled.");
3231 : #else
3232 4 : poDupGeom.reset(poGeom->clone());
3233 4 : FixPolygonCoordinatesAtDateLine(
3234 : poDupGeom->toPolygon(), dfDateLineOffset);
3235 :
3236 4 : OGREnvelope sEnvelope;
3237 4 : poDupGeom->getEnvelope(&sEnvelope);
3238 4 : bWrapDateline = sEnvelope.MinX != sEnvelope.MaxX;
3239 : #endif
3240 : }
3241 : }
3242 : }
3243 : }
3244 :
3245 57 : if (bSplitLineStringAtDateline)
3246 : {
3247 8 : SplitLineStringAtDateline(poMulti, poGeom->toLineString(),
3248 : dfDateLineOffset,
3249 : (bAroundMinus180) ? 360.0 : 0.0);
3250 : }
3251 : #ifdef HAVE_GEOS
3252 49 : else if (bWrapDateline)
3253 : {
3254 : const OGRGeometry *poWorkGeom =
3255 6 : poDupGeom ? poDupGeom.get() : poGeom;
3256 6 : assert(poWorkGeom);
3257 6 : OGRGeometry *poRectangle1 = nullptr;
3258 6 : OGRGeometry *poRectangle2 = nullptr;
3259 6 : const char *pszWKT1 =
3260 : !bAroundMinus180
3261 6 : ? "POLYGON((-180 90,180 90,180 -90,-180 -90,-180 90))"
3262 : : "POLYGON((180 90,-180 90,-180 -90,180 -90,180 90))";
3263 6 : const char *pszWKT2 =
3264 : !bAroundMinus180
3265 6 : ? "POLYGON((180 90,360 90,360 -90,180 -90,180 90))"
3266 : : "POLYGON((-180 90,-360 90,-360 -90,-180 -90,-180 "
3267 : "90))";
3268 6 : OGRGeometryFactory::createFromWkt(pszWKT1, nullptr,
3269 : &poRectangle1);
3270 6 : OGRGeometryFactory::createFromWkt(pszWKT2, nullptr,
3271 : &poRectangle2);
3272 : auto poGeom1 = std::unique_ptr<OGRGeometry>(
3273 12 : poWorkGeom->Intersection(poRectangle1));
3274 : auto poGeom2 = std::unique_ptr<OGRGeometry>(
3275 12 : poWorkGeom->Intersection(poRectangle2));
3276 6 : delete poRectangle1;
3277 6 : delete poRectangle2;
3278 :
3279 6 : if (poGeom1 != nullptr && poGeom2 != nullptr)
3280 : {
3281 6 : AddSimpleGeomToMulti(poMulti, poGeom1.get());
3282 6 : AddOffsetToLon(poGeom2.get(),
3283 : !bAroundMinus180 ? -360.0 : 360.0);
3284 6 : AddSimpleGeomToMulti(poMulti, poGeom2.get());
3285 : }
3286 : else
3287 : {
3288 0 : AddSimpleGeomToMulti(poMulti, poGeom);
3289 : }
3290 : }
3291 : #endif
3292 : else
3293 : {
3294 43 : poMulti->addGeometry(poGeom);
3295 : }
3296 57 : break;
3297 : }
3298 :
3299 15 : case wkbMultiLineString:
3300 : case wkbMultiPolygon:
3301 : case wkbGeometryCollection:
3302 : {
3303 48 : for (const auto poSubGeom : *(poGeom->toGeometryCollection()))
3304 : {
3305 33 : CutGeometryOnDateLineAndAddToMulti(poMulti, poSubGeom,
3306 : dfDateLineOffset);
3307 : }
3308 15 : break;
3309 : }
3310 :
3311 0 : default:
3312 0 : break;
3313 : }
3314 73 : }
3315 :
3316 : #ifdef HAVE_GEOS
3317 :
3318 : /************************************************************************/
3319 : /* RemovePoint() */
3320 : /************************************************************************/
3321 :
3322 9 : static void RemovePoint(OGRGeometry *poGeom, OGRPoint *poPoint)
3323 : {
3324 9 : const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
3325 9 : switch (eType)
3326 : {
3327 4 : case wkbLineString:
3328 : {
3329 4 : OGRLineString *poLS = poGeom->toLineString();
3330 4 : const bool bIs3D = (poLS->getCoordinateDimension() == 3);
3331 4 : int j = 0;
3332 32 : for (int i = 0; i < poLS->getNumPoints(); i++)
3333 : {
3334 30 : if (poLS->getX(i) != poPoint->getX() ||
3335 2 : poLS->getY(i) != poPoint->getY())
3336 : {
3337 26 : if (i > j)
3338 : {
3339 4 : if (bIs3D)
3340 : {
3341 0 : poLS->setPoint(j, poLS->getX(i), poLS->getY(i),
3342 : poLS->getZ(i));
3343 : }
3344 : else
3345 : {
3346 4 : poLS->setPoint(j, poLS->getX(i), poLS->getY(i));
3347 : }
3348 : }
3349 26 : j++;
3350 : }
3351 : }
3352 4 : poLS->setNumPoints(j);
3353 4 : break;
3354 : }
3355 :
3356 4 : case wkbPolygon:
3357 : {
3358 4 : OGRPolygon *poPoly = poGeom->toPolygon();
3359 4 : if (poPoly->getExteriorRing() != nullptr)
3360 : {
3361 4 : RemovePoint(poPoly->getExteriorRing(), poPoint);
3362 4 : for (int i = 0; i < poPoly->getNumInteriorRings(); ++i)
3363 : {
3364 0 : RemovePoint(poPoly->getInteriorRing(i), poPoint);
3365 : }
3366 : }
3367 4 : break;
3368 : }
3369 :
3370 1 : case wkbMultiLineString:
3371 : case wkbMultiPolygon:
3372 : case wkbGeometryCollection:
3373 : {
3374 1 : OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
3375 3 : for (int i = 0; i < poGC->getNumGeometries(); ++i)
3376 : {
3377 2 : RemovePoint(poGC->getGeometryRef(i), poPoint);
3378 : }
3379 1 : break;
3380 : }
3381 :
3382 0 : default:
3383 0 : break;
3384 : }
3385 9 : }
3386 :
3387 : /************************************************************************/
3388 : /* GetDist() */
3389 : /************************************************************************/
3390 :
3391 78 : static double GetDist(double dfDeltaX, double dfDeltaY)
3392 : {
3393 78 : return sqrt(dfDeltaX * dfDeltaX + dfDeltaY * dfDeltaY);
3394 : }
3395 :
3396 : /************************************************************************/
3397 : /* AlterPole() */
3398 : /* */
3399 : /* Replace and point at the pole by points really close to the pole, */
3400 : /* but on the previous and later segments. */
3401 : /************************************************************************/
3402 :
3403 5 : static void AlterPole(OGRGeometry *poGeom, OGRPoint *poPole,
3404 : bool bIsRing = false)
3405 : {
3406 5 : const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
3407 5 : switch (eType)
3408 : {
3409 2 : case wkbLineString:
3410 : {
3411 2 : if (!bIsRing)
3412 0 : return;
3413 2 : OGRLineString *poLS = poGeom->toLineString();
3414 2 : const int nNumPoints = poLS->getNumPoints();
3415 2 : if (nNumPoints >= 4)
3416 : {
3417 2 : const bool bIs3D = (poLS->getCoordinateDimension() == 3);
3418 4 : std::vector<OGRRawPoint> aoPoints;
3419 4 : std::vector<double> adfZ;
3420 2 : bool bMustClose = false;
3421 10 : for (int i = 0; i < nNumPoints; i++)
3422 : {
3423 8 : const double dfX = poLS->getX(i);
3424 8 : const double dfY = poLS->getY(i);
3425 8 : if (dfX == poPole->getX() && dfY == poPole->getY())
3426 : {
3427 : // Replace the pole by points really close to it
3428 2 : if (i == 0)
3429 0 : bMustClose = true;
3430 2 : if (i == nNumPoints - 1)
3431 0 : continue;
3432 2 : const int iBefore = i > 0 ? i - 1 : nNumPoints - 2;
3433 2 : double dfXBefore = poLS->getX(iBefore);
3434 2 : double dfYBefore = poLS->getY(iBefore);
3435 : double dfNorm =
3436 2 : GetDist(dfXBefore - dfX, dfYBefore - dfY);
3437 2 : double dfXInterp =
3438 2 : dfX + (dfXBefore - dfX) / dfNorm * 1.0e-7;
3439 2 : double dfYInterp =
3440 2 : dfY + (dfYBefore - dfY) / dfNorm * 1.0e-7;
3441 2 : OGRRawPoint oPoint;
3442 2 : oPoint.x = dfXInterp;
3443 2 : oPoint.y = dfYInterp;
3444 2 : aoPoints.push_back(oPoint);
3445 2 : adfZ.push_back(poLS->getZ(i));
3446 :
3447 2 : const int iAfter = i + 1;
3448 2 : double dfXAfter = poLS->getX(iAfter);
3449 2 : double dfYAfter = poLS->getY(iAfter);
3450 2 : dfNorm = GetDist(dfXAfter - dfX, dfYAfter - dfY);
3451 2 : dfXInterp = dfX + (dfXAfter - dfX) / dfNorm * 1e-7;
3452 2 : dfYInterp = dfY + (dfYAfter - dfY) / dfNorm * 1e-7;
3453 2 : oPoint.x = dfXInterp;
3454 2 : oPoint.y = dfYInterp;
3455 2 : aoPoints.push_back(oPoint);
3456 2 : adfZ.push_back(poLS->getZ(i));
3457 : }
3458 : else
3459 : {
3460 6 : OGRRawPoint oPoint;
3461 6 : oPoint.x = dfX;
3462 6 : oPoint.y = dfY;
3463 6 : aoPoints.push_back(oPoint);
3464 6 : adfZ.push_back(poLS->getZ(i));
3465 : }
3466 : }
3467 2 : if (bMustClose)
3468 : {
3469 0 : aoPoints.push_back(aoPoints[0]);
3470 0 : adfZ.push_back(adfZ[0]);
3471 : }
3472 :
3473 4 : poLS->setPoints(static_cast<int>(aoPoints.size()),
3474 2 : &(aoPoints[0]), bIs3D ? &adfZ[0] : nullptr);
3475 : }
3476 2 : break;
3477 : }
3478 :
3479 2 : case wkbPolygon:
3480 : {
3481 2 : OGRPolygon *poPoly = poGeom->toPolygon();
3482 2 : if (poPoly->getExteriorRing() != nullptr)
3483 : {
3484 2 : AlterPole(poPoly->getExteriorRing(), poPole, true);
3485 2 : for (int i = 0; i < poPoly->getNumInteriorRings(); ++i)
3486 : {
3487 0 : AlterPole(poPoly->getInteriorRing(i), poPole, true);
3488 : }
3489 : }
3490 2 : break;
3491 : }
3492 :
3493 1 : case wkbMultiLineString:
3494 : case wkbMultiPolygon:
3495 : case wkbGeometryCollection:
3496 : {
3497 1 : OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
3498 2 : for (int i = 0; i < poGC->getNumGeometries(); ++i)
3499 : {
3500 1 : AlterPole(poGC->getGeometryRef(i), poPole);
3501 : }
3502 1 : break;
3503 : }
3504 :
3505 0 : default:
3506 0 : break;
3507 : }
3508 : }
3509 :
3510 : /************************************************************************/
3511 : /* IsPolarToGeographic() */
3512 : /* */
3513 : /* Returns true if poCT transforms from a projection that includes one */
3514 : /* of the pole in a continuous way. */
3515 : /************************************************************************/
3516 :
3517 26 : static bool IsPolarToGeographic(OGRCoordinateTransformation *poCT,
3518 : OGRCoordinateTransformation *poRevCT,
3519 : bool &bIsNorthPolarOut)
3520 : {
3521 26 : bool bIsNorthPolar = false;
3522 26 : bool bIsSouthPolar = false;
3523 26 : double x = 0.0;
3524 26 : double y = 90.0;
3525 :
3526 26 : CPLErrorStateBackuper oErrorBackuper(CPLQuietErrorHandler);
3527 :
3528 26 : const bool bBackupEmitErrors = poCT->GetEmitErrors();
3529 26 : poRevCT->SetEmitErrors(false);
3530 26 : poCT->SetEmitErrors(false);
3531 :
3532 26 : if (poRevCT->Transform(1, &x, &y) &&
3533 : // Surprisingly, pole south projects correctly back &
3534 : // forth for antarctic polar stereographic. Therefore, check that
3535 : // the projected value is not too big.
3536 26 : fabs(x) < 1e10 && fabs(y) < 1e10)
3537 : {
3538 24 : double x_tab[] = {x, x - 1e5, x + 1e5};
3539 24 : double y_tab[] = {y, y - 1e5, y + 1e5};
3540 24 : if (poCT->Transform(3, x_tab, y_tab) &&
3541 24 : fabs(y_tab[0] - (90.0)) < 1e-10 &&
3542 71 : fabs(x_tab[2] - x_tab[1]) > 170 &&
3543 23 : fabs(y_tab[2] - y_tab[1]) < 1e-10)
3544 : {
3545 23 : bIsNorthPolar = true;
3546 : }
3547 : }
3548 :
3549 26 : x = 0.0;
3550 26 : y = -90.0;
3551 26 : if (poRevCT->Transform(1, &x, &y) && fabs(x) < 1e10 && fabs(y) < 1e10)
3552 : {
3553 15 : double x_tab[] = {x, x - 1e5, x + 1e5};
3554 15 : double y_tab[] = {y, y - 1e5, y + 1e5};
3555 15 : if (poCT->Transform(3, x_tab, y_tab) &&
3556 15 : fabs(y_tab[0] - (-90.0)) < 1e-10 &&
3557 44 : fabs(x_tab[2] - x_tab[1]) > 170 &&
3558 14 : fabs(y_tab[2] - y_tab[1]) < 1e-10)
3559 : {
3560 14 : bIsSouthPolar = true;
3561 : }
3562 : }
3563 :
3564 26 : poCT->SetEmitErrors(bBackupEmitErrors);
3565 :
3566 26 : if (bIsNorthPolar && bIsSouthPolar)
3567 : {
3568 13 : bIsNorthPolar = false;
3569 13 : bIsSouthPolar = false;
3570 : }
3571 :
3572 26 : bIsNorthPolarOut = bIsNorthPolar;
3573 52 : return bIsNorthPolar || bIsSouthPolar;
3574 : }
3575 :
3576 : /************************************************************************/
3577 : /* ContainsPole() */
3578 : /************************************************************************/
3579 :
3580 14 : static bool ContainsPole(const OGRGeometry *poGeom, const OGRPoint *poPole)
3581 : {
3582 14 : switch (wkbFlatten(poGeom->getGeometryType()))
3583 : {
3584 12 : case wkbPolygon:
3585 : case wkbCurvePolygon:
3586 : {
3587 12 : const auto poPoly = poGeom->toCurvePolygon();
3588 12 : if (poPoly->getNumInteriorRings() > 0)
3589 : {
3590 3 : const auto poRing = poPoly->getExteriorRingCurve();
3591 3 : OGRPolygon oPolygon;
3592 3 : oPolygon.addRing(poRing);
3593 3 : return oPolygon.Contains(poPole);
3594 : }
3595 :
3596 9 : return poGeom->Contains(poPole);
3597 : }
3598 :
3599 2 : case wkbMultiPolygon:
3600 : case wkbMultiSurface:
3601 : case wkbGeometryCollection:
3602 : {
3603 3 : for (const auto *poSubGeom : poGeom->toGeometryCollection())
3604 : {
3605 2 : if (ContainsPole(poSubGeom, poPole))
3606 1 : return true;
3607 : }
3608 1 : return false;
3609 : }
3610 :
3611 0 : default:
3612 0 : break;
3613 : }
3614 0 : return poGeom->Contains(poPole);
3615 : }
3616 :
3617 : /************************************************************************/
3618 : /* TransformBeforePolarToGeographic() */
3619 : /* */
3620 : /* Transform the geometry (by intersection), so as to cut each geometry */
3621 : /* that crosses the pole, in 2 parts. Do also tricks for geometries */
3622 : /* that just touch the pole. */
3623 : /************************************************************************/
3624 :
3625 12 : static std::unique_ptr<OGRGeometry> TransformBeforePolarToGeographic(
3626 : OGRCoordinateTransformation *poRevCT, bool bIsNorthPolar,
3627 : std::unique_ptr<OGRGeometry> poDstGeom, bool &bNeedPostCorrectionOut)
3628 : {
3629 12 : const int nSign = (bIsNorthPolar) ? 1 : -1;
3630 :
3631 : // Does the geometry fully contains the pole ? */
3632 12 : double dfXPole = 0.0;
3633 12 : double dfYPole = nSign * 90.0;
3634 12 : poRevCT->Transform(1, &dfXPole, &dfYPole);
3635 24 : OGRPoint oPole(dfXPole, dfYPole);
3636 12 : const bool bContainsPole = ContainsPole(poDstGeom.get(), &oPole);
3637 :
3638 12 : const double EPS = 1e-9;
3639 :
3640 : // Does the geometry touches the pole and intersects the antimeridian ?
3641 12 : double dfNearPoleAntiMeridianX = 180.0;
3642 12 : double dfNearPoleAntiMeridianY = nSign * (90.0 - EPS);
3643 12 : poRevCT->Transform(1, &dfNearPoleAntiMeridianX, &dfNearPoleAntiMeridianY);
3644 : OGRPoint oNearPoleAntimeridian(dfNearPoleAntiMeridianX,
3645 24 : dfNearPoleAntiMeridianY);
3646 : const bool bContainsNearPoleAntimeridian =
3647 12 : CPL_TO_BOOL(poDstGeom->Contains(&oNearPoleAntimeridian));
3648 :
3649 : // Does the geometry intersects the antimeridian ?
3650 24 : OGRLineString oAntiMeridianLine;
3651 12 : oAntiMeridianLine.addPoint(180.0, nSign * (90.0 - EPS));
3652 12 : oAntiMeridianLine.addPoint(180.0, 0);
3653 12 : oAntiMeridianLine.transform(poRevCT);
3654 : const bool bIntersectsAntimeridian =
3655 21 : bContainsNearPoleAntimeridian ||
3656 9 : CPL_TO_BOOL(poDstGeom->Intersects(&oAntiMeridianLine));
3657 :
3658 : // Does the geometry touches the pole (but not intersect the antimeridian) ?
3659 : const bool bRegularTouchesPole =
3660 7 : !bContainsPole && !bContainsNearPoleAntimeridian &&
3661 19 : !bIntersectsAntimeridian && CPL_TO_BOOL(poDstGeom->Touches(&oPole));
3662 :
3663 : // Create a polygon of nearly a full hemisphere, but excluding the anti
3664 : // meridian and the pole.
3665 24 : OGRPolygon oCutter;
3666 12 : OGRLinearRing *poRing = new OGRLinearRing();
3667 12 : poRing->addPoint(180.0 - EPS, 0);
3668 12 : poRing->addPoint(180.0 - EPS, nSign * (90.0 - EPS));
3669 : // If the geometry doesn't contain the pole, then we add it to the cutter
3670 : // geometry, but will later remove it completely (geometry touching the
3671 : // pole but intersecting the antimeridian), or will replace it by 2
3672 : // close points (geometry touching the pole without intersecting the
3673 : // antimeridian)
3674 12 : if (!bContainsPole)
3675 7 : poRing->addPoint(180.0, nSign * 90);
3676 12 : poRing->addPoint(-180.0 + EPS, nSign * (90.0 - EPS));
3677 12 : poRing->addPoint(-180.0 + EPS, 0);
3678 12 : poRing->addPoint(180.0 - EPS, 0);
3679 12 : oCutter.addRingDirectly(poRing);
3680 :
3681 12 : if (oCutter.transform(poRevCT) == OGRERR_NONE &&
3682 : // Check that longitudes +/- 180 are continuous
3683 : // in the polar projection
3684 19 : fabs(poRing->getX(0) - poRing->getX(poRing->getNumPoints() - 2)) < 1 &&
3685 7 : (bContainsPole || bIntersectsAntimeridian ||
3686 3 : bContainsNearPoleAntimeridian || bRegularTouchesPole))
3687 : {
3688 11 : if (bContainsPole || bIntersectsAntimeridian ||
3689 : bContainsNearPoleAntimeridian)
3690 : {
3691 : auto poNewGeom =
3692 18 : std::unique_ptr<OGRGeometry>(poDstGeom->Difference(&oCutter));
3693 9 : if (poNewGeom)
3694 : {
3695 9 : if (bContainsNearPoleAntimeridian)
3696 3 : RemovePoint(poNewGeom.get(), &oPole);
3697 9 : poDstGeom = std::move(poNewGeom);
3698 : }
3699 : }
3700 :
3701 11 : if (bRegularTouchesPole)
3702 : {
3703 2 : AlterPole(poDstGeom.get(), &oPole);
3704 : }
3705 :
3706 11 : bNeedPostCorrectionOut = true;
3707 : }
3708 24 : return poDstGeom;
3709 : }
3710 :
3711 : /************************************************************************/
3712 : /* IsAntimeridianProjToGeographic() */
3713 : /* */
3714 : /* Returns true if poCT transforms from a projection that includes the */
3715 : /* antimeridian in a continuous way. */
3716 : /************************************************************************/
3717 :
3718 26 : static bool IsAntimeridianProjToGeographic(OGRCoordinateTransformation *poCT,
3719 : OGRCoordinateTransformation *poRevCT,
3720 : OGRGeometry *poDstGeometry)
3721 : {
3722 26 : const bool bBackupEmitErrors = poCT->GetEmitErrors();
3723 26 : poRevCT->SetEmitErrors(false);
3724 26 : poCT->SetEmitErrors(false);
3725 :
3726 : // Find a reasonable latitude for the geometry
3727 26 : OGREnvelope sEnvelope;
3728 26 : poDstGeometry->getEnvelope(&sEnvelope);
3729 52 : OGRPoint pMean(sEnvelope.MinX, (sEnvelope.MinY + sEnvelope.MaxY) / 2);
3730 26 : if (pMean.transform(poCT) != OGRERR_NONE)
3731 : {
3732 0 : poCT->SetEmitErrors(bBackupEmitErrors);
3733 0 : return false;
3734 : }
3735 26 : const double dfMeanLat = pMean.getY();
3736 :
3737 : // Check that close points on each side of the antimeridian in (long, lat)
3738 : // project to close points in the source projection, and check that they
3739 : // roundtrip correctly.
3740 26 : const double EPS = 1.0e-8;
3741 26 : double x1 = 180 - EPS;
3742 26 : double y1 = dfMeanLat;
3743 26 : double x2 = -180 + EPS;
3744 26 : double y2 = dfMeanLat;
3745 78 : if (!poRevCT->Transform(1, &x1, &y1) || !poRevCT->Transform(1, &x2, &y2) ||
3746 50 : GetDist(x2 - x1, y2 - y1) > 1 || !poCT->Transform(1, &x1, &y1) ||
3747 48 : !poCT->Transform(1, &x2, &y2) ||
3748 76 : GetDist(x1 - (180 - EPS), y1 - dfMeanLat) > 2 * EPS ||
3749 24 : GetDist(x2 - (-180 + EPS), y2 - dfMeanLat) > 2 * EPS)
3750 : {
3751 2 : poCT->SetEmitErrors(bBackupEmitErrors);
3752 2 : return false;
3753 : }
3754 :
3755 24 : poCT->SetEmitErrors(bBackupEmitErrors);
3756 :
3757 24 : return true;
3758 : }
3759 :
3760 : /************************************************************************/
3761 : /* CollectPointsOnAntimeridian() */
3762 : /* */
3763 : /* Collect points that are the intersection of the lines of the geometry*/
3764 : /* with the antimeridian. */
3765 : /************************************************************************/
3766 :
3767 21 : static void CollectPointsOnAntimeridian(OGRGeometry *poGeom,
3768 : OGRCoordinateTransformation *poCT,
3769 : OGRCoordinateTransformation *poRevCT,
3770 : std::vector<OGRRawPoint> &aoPoints)
3771 : {
3772 21 : const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
3773 21 : switch (eType)
3774 : {
3775 11 : case wkbLineString:
3776 : {
3777 11 : OGRLineString *poLS = poGeom->toLineString();
3778 11 : const int nNumPoints = poLS->getNumPoints();
3779 44 : for (int i = 0; i < nNumPoints - 1; i++)
3780 : {
3781 33 : const double dfX = poLS->getX(i);
3782 33 : const double dfY = poLS->getY(i);
3783 33 : const double dfX2 = poLS->getX(i + 1);
3784 33 : const double dfY2 = poLS->getY(i + 1);
3785 33 : double dfXTrans = dfX;
3786 33 : double dfYTrans = dfY;
3787 33 : double dfX2Trans = dfX2;
3788 33 : double dfY2Trans = dfY2;
3789 33 : poCT->Transform(1, &dfXTrans, &dfYTrans);
3790 33 : poCT->Transform(1, &dfX2Trans, &dfY2Trans);
3791 : // Are we crossing the antimeridian ? (detecting by inversion of
3792 : // sign of X)
3793 33 : if ((dfX2 - dfX) * (dfX2Trans - dfXTrans) < 0 ||
3794 14 : (dfX == dfX2 && dfX2Trans * dfXTrans < 0 &&
3795 1 : fabs(fabs(dfXTrans) - 180) < 10 &&
3796 1 : fabs(fabs(dfX2Trans) - 180) < 10))
3797 : {
3798 17 : double dfXStart = dfX;
3799 17 : double dfYStart = dfY;
3800 17 : double dfXEnd = dfX2;
3801 17 : double dfYEnd = dfY2;
3802 17 : double dfXStartTrans = dfXTrans;
3803 17 : double dfXEndTrans = dfX2Trans;
3804 17 : int iIter = 0;
3805 17 : const double EPS = 1e-8;
3806 : // Find point of the segment intersecting the antimeridian
3807 : // by dichotomy
3808 453 : for (;
3809 470 : iIter < 50 && (fabs(fabs(dfXStartTrans) - 180) > EPS ||
3810 25 : fabs(fabs(dfXEndTrans) - 180) > EPS);
3811 : ++iIter)
3812 : {
3813 453 : double dfXMid = (dfXStart + dfXEnd) / 2;
3814 453 : double dfYMid = (dfYStart + dfYEnd) / 2;
3815 453 : double dfXMidTrans = dfXMid;
3816 453 : double dfYMidTrans = dfYMid;
3817 453 : poCT->Transform(1, &dfXMidTrans, &dfYMidTrans);
3818 453 : if ((dfXMid - dfXStart) *
3819 453 : (dfXMidTrans - dfXStartTrans) <
3820 247 : 0 ||
3821 22 : (dfXMid == dfXStart &&
3822 22 : dfXMidTrans * dfXStartTrans < 0))
3823 : {
3824 214 : dfXEnd = dfXMid;
3825 214 : dfYEnd = dfYMid;
3826 214 : dfXEndTrans = dfXMidTrans;
3827 : }
3828 : else
3829 : {
3830 239 : dfXStart = dfXMid;
3831 239 : dfYStart = dfYMid;
3832 239 : dfXStartTrans = dfXMidTrans;
3833 : }
3834 : }
3835 17 : if (iIter < 50)
3836 : {
3837 17 : OGRRawPoint oPoint;
3838 17 : oPoint.x = (dfXStart + dfXEnd) / 2;
3839 17 : oPoint.y = (dfYStart + dfYEnd) / 2;
3840 17 : poCT->Transform(1, &(oPoint.x), &(oPoint.y));
3841 17 : oPoint.x = 180.0;
3842 17 : aoPoints.push_back(oPoint);
3843 : }
3844 : }
3845 : }
3846 11 : break;
3847 : }
3848 :
3849 6 : case wkbPolygon:
3850 : {
3851 6 : OGRPolygon *poPoly = poGeom->toPolygon();
3852 6 : if (poPoly->getExteriorRing() != nullptr)
3853 : {
3854 6 : CollectPointsOnAntimeridian(poPoly->getExteriorRing(), poCT,
3855 : poRevCT, aoPoints);
3856 6 : for (int i = 0; i < poPoly->getNumInteriorRings(); ++i)
3857 : {
3858 0 : CollectPointsOnAntimeridian(poPoly->getInteriorRing(i),
3859 : poCT, poRevCT, aoPoints);
3860 : }
3861 : }
3862 6 : break;
3863 : }
3864 :
3865 4 : case wkbMultiLineString:
3866 : case wkbMultiPolygon:
3867 : case wkbGeometryCollection:
3868 : {
3869 4 : OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
3870 8 : for (int i = 0; i < poGC->getNumGeometries(); ++i)
3871 : {
3872 4 : CollectPointsOnAntimeridian(poGC->getGeometryRef(i), poCT,
3873 : poRevCT, aoPoints);
3874 : }
3875 4 : break;
3876 : }
3877 :
3878 0 : default:
3879 0 : break;
3880 : }
3881 21 : }
3882 :
3883 : /************************************************************************/
3884 : /* SortPointsByAscendingY() */
3885 : /************************************************************************/
3886 :
3887 : struct SortPointsByAscendingY
3888 : {
3889 8 : bool operator()(const OGRRawPoint &a, const OGRRawPoint &b)
3890 : {
3891 8 : return a.y < b.y;
3892 : }
3893 : };
3894 :
3895 : /************************************************************************/
3896 : /* TransformBeforeAntimeridianToGeographic() */
3897 : /* */
3898 : /* Transform the geometry (by intersection), so as to cut each geometry */
3899 : /* that crosses the antimeridian, in 2 parts. */
3900 : /************************************************************************/
3901 :
3902 24 : static std::unique_ptr<OGRGeometry> TransformBeforeAntimeridianToGeographic(
3903 : OGRCoordinateTransformation *poCT, OGRCoordinateTransformation *poRevCT,
3904 : std::unique_ptr<OGRGeometry> poDstGeom, bool &bNeedPostCorrectionOut)
3905 : {
3906 24 : OGREnvelope sEnvelope;
3907 24 : poDstGeom->getEnvelope(&sEnvelope);
3908 48 : OGRPoint pMean(sEnvelope.MinX, (sEnvelope.MinY + sEnvelope.MaxY) / 2);
3909 24 : pMean.transform(poCT);
3910 24 : const double dfMeanLat = pMean.getY();
3911 24 : pMean.setX(180.0);
3912 24 : pMean.setY(dfMeanLat);
3913 24 : pMean.transform(poRevCT);
3914 : // Check if the antimeridian crosses the bbox of our geometry
3915 36 : if (!(pMean.getX() >= sEnvelope.MinX && pMean.getY() >= sEnvelope.MinY &&
3916 12 : pMean.getX() <= sEnvelope.MaxX && pMean.getY() <= sEnvelope.MaxY))
3917 : {
3918 13 : return poDstGeom;
3919 : }
3920 :
3921 : // Collect points that are the intersection of the lines of the geometry
3922 : // with the antimeridian
3923 22 : std::vector<OGRRawPoint> aoPoints;
3924 11 : CollectPointsOnAntimeridian(poDstGeom.get(), poCT, poRevCT, aoPoints);
3925 11 : if (aoPoints.empty())
3926 0 : return poDstGeom;
3927 :
3928 : SortPointsByAscendingY sortFunc;
3929 11 : std::sort(aoPoints.begin(), aoPoints.end(), sortFunc);
3930 :
3931 11 : const double EPS = 1e-9;
3932 :
3933 : // Build a very thin polygon cutting the antimeridian at our points
3934 11 : OGRLinearRing *poLR = new OGRLinearRing;
3935 : {
3936 11 : double x = 180.0 - EPS;
3937 11 : double y = aoPoints[0].y - EPS;
3938 11 : poRevCT->Transform(1, &x, &y);
3939 11 : poLR->addPoint(x, y);
3940 : }
3941 28 : for (const auto &oPoint : aoPoints)
3942 : {
3943 17 : double x = 180.0 - EPS;
3944 17 : double y = oPoint.y;
3945 17 : poRevCT->Transform(1, &x, &y);
3946 17 : poLR->addPoint(x, y);
3947 : }
3948 : {
3949 11 : double x = 180.0 - EPS;
3950 11 : double y = aoPoints.back().y + EPS;
3951 11 : poRevCT->Transform(1, &x, &y);
3952 11 : poLR->addPoint(x, y);
3953 : }
3954 : {
3955 11 : double x = 180.0 + EPS;
3956 11 : double y = aoPoints.back().y + EPS;
3957 11 : poRevCT->Transform(1, &x, &y);
3958 11 : poLR->addPoint(x, y);
3959 : }
3960 28 : for (size_t i = aoPoints.size(); i > 0;)
3961 : {
3962 17 : --i;
3963 17 : const OGRRawPoint &oPoint = aoPoints[i];
3964 17 : double x = 180.0 + EPS;
3965 17 : double y = oPoint.y;
3966 17 : poRevCT->Transform(1, &x, &y);
3967 17 : poLR->addPoint(x, y);
3968 : }
3969 : {
3970 11 : double x = 180.0 + EPS;
3971 11 : double y = aoPoints[0].y - EPS;
3972 11 : poRevCT->Transform(1, &x, &y);
3973 11 : poLR->addPoint(x, y);
3974 : }
3975 11 : poLR->closeRings();
3976 :
3977 22 : OGRPolygon oPolyToCut;
3978 11 : oPolyToCut.addRingDirectly(poLR);
3979 :
3980 : #if DEBUG_VERBOSE
3981 : char *pszWKT = NULL;
3982 : oPolyToCut.exportToWkt(&pszWKT);
3983 : CPLDebug("OGR", "Geometry to cut: %s", pszWKT);
3984 : CPLFree(pszWKT);
3985 : #endif
3986 :
3987 : // Get the geometry without the antimeridian
3988 : auto poInter =
3989 22 : std::unique_ptr<OGRGeometry>(poDstGeom->Difference(&oPolyToCut));
3990 11 : if (poInter != nullptr)
3991 : {
3992 11 : poDstGeom = std::move(poInter);
3993 11 : bNeedPostCorrectionOut = true;
3994 : }
3995 :
3996 11 : return poDstGeom;
3997 : }
3998 :
3999 : /************************************************************************/
4000 : /* SnapCoordsCloseToLatLongBounds() */
4001 : /* */
4002 : /* This function snaps points really close to the antimerdian or poles */
4003 : /* to their exact longitudes/latitudes. */
4004 : /************************************************************************/
4005 :
4006 80 : static void SnapCoordsCloseToLatLongBounds(OGRGeometry *poGeom)
4007 : {
4008 80 : const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
4009 80 : switch (eType)
4010 : {
4011 37 : case wkbLineString:
4012 : {
4013 37 : OGRLineString *poLS = poGeom->toLineString();
4014 37 : const double EPS = 1e-8;
4015 243 : for (int i = 0; i < poLS->getNumPoints(); i++)
4016 : {
4017 412 : OGRPoint p;
4018 206 : poLS->getPoint(i, &p);
4019 206 : if (fabs(p.getX() - 180.0) < EPS)
4020 : {
4021 48 : p.setX(180.0);
4022 48 : poLS->setPoint(i, &p);
4023 : }
4024 158 : else if (fabs(p.getX() - -180.0) < EPS)
4025 : {
4026 43 : p.setX(-180.0);
4027 43 : poLS->setPoint(i, &p);
4028 : }
4029 :
4030 206 : if (fabs(p.getY() - 90.0) < EPS)
4031 : {
4032 8 : p.setY(90.0);
4033 8 : poLS->setPoint(i, &p);
4034 : }
4035 198 : else if (fabs(p.getY() - -90.0) < EPS)
4036 : {
4037 2 : p.setY(-90.0);
4038 2 : poLS->setPoint(i, &p);
4039 : }
4040 : }
4041 37 : break;
4042 : }
4043 :
4044 27 : case wkbPolygon:
4045 : {
4046 27 : OGRPolygon *poPoly = poGeom->toPolygon();
4047 27 : if (poPoly->getExteriorRing() != nullptr)
4048 : {
4049 27 : SnapCoordsCloseToLatLongBounds(poPoly->getExteriorRing());
4050 27 : for (int i = 0; i < poPoly->getNumInteriorRings(); ++i)
4051 : {
4052 0 : SnapCoordsCloseToLatLongBounds(poPoly->getInteriorRing(i));
4053 : }
4054 : }
4055 27 : break;
4056 : }
4057 :
4058 16 : case wkbMultiLineString:
4059 : case wkbMultiPolygon:
4060 : case wkbGeometryCollection:
4061 : {
4062 16 : OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
4063 47 : for (int i = 0; i < poGC->getNumGeometries(); ++i)
4064 : {
4065 31 : SnapCoordsCloseToLatLongBounds(poGC->getGeometryRef(i));
4066 : }
4067 16 : break;
4068 : }
4069 :
4070 0 : default:
4071 0 : break;
4072 : }
4073 80 : }
4074 :
4075 : #endif
4076 :
4077 : /************************************************************************/
4078 : /* TransformWithOptionsCache::Private */
4079 : /************************************************************************/
4080 :
4081 : struct OGRGeometryFactory::TransformWithOptionsCache::Private
4082 : {
4083 : const OGRSpatialReference *poSourceCRS = nullptr;
4084 : const OGRSpatialReference *poTargetCRS = nullptr;
4085 : const OGRCoordinateTransformation *poCT = nullptr;
4086 : std::unique_ptr<OGRCoordinateTransformation> poRevCT{};
4087 : bool bIsPolar = false;
4088 : bool bIsNorthPolar = false;
4089 :
4090 71 : void clear()
4091 : {
4092 71 : poSourceCRS = nullptr;
4093 71 : poTargetCRS = nullptr;
4094 71 : poCT = nullptr;
4095 71 : poRevCT.reset();
4096 71 : bIsPolar = false;
4097 71 : bIsNorthPolar = false;
4098 71 : }
4099 : };
4100 :
4101 : /************************************************************************/
4102 : /* TransformWithOptionsCache() */
4103 : /************************************************************************/
4104 :
4105 1320 : OGRGeometryFactory::TransformWithOptionsCache::TransformWithOptionsCache()
4106 1320 : : d(new Private())
4107 : {
4108 1320 : }
4109 :
4110 : /************************************************************************/
4111 : /* ~TransformWithOptionsCache() */
4112 : /************************************************************************/
4113 :
4114 1320 : OGRGeometryFactory::TransformWithOptionsCache::~TransformWithOptionsCache()
4115 : {
4116 1320 : }
4117 :
4118 : /************************************************************************/
4119 : /* isTransformWithOptionsRegularTransform() */
4120 : /************************************************************************/
4121 :
4122 : #ifdef HAVE_GEOS
4123 84 : static bool MayBePolarToGeographic(const OGRSpatialReference *poSourceCRS,
4124 : const OGRSpatialReference *poTargetCRS)
4125 : {
4126 84 : if (poSourceCRS && poTargetCRS && poSourceCRS->IsProjected() &&
4127 59 : poTargetCRS->IsGeographic() &&
4128 220 : poTargetCRS->GetAxisMappingStrategy() == OAMS_TRADITIONAL_GIS_ORDER &&
4129 : // check that angular units is degree
4130 52 : std::fabs(poTargetCRS->GetAngularUnits(nullptr) -
4131 52 : CPLAtof(SRS_UA_DEGREE_CONV)) <=
4132 52 : 1e-8 * CPLAtof(SRS_UA_DEGREE_CONV))
4133 : {
4134 52 : double dfWestLong = 0.0;
4135 52 : double dfSouthLat = 0.0;
4136 52 : double dfEastLong = 0.0;
4137 52 : double dfNorthLat = 0.0;
4138 52 : if (poSourceCRS->GetAreaOfUse(&dfWestLong, &dfSouthLat, &dfEastLong,
4139 91 : &dfNorthLat, nullptr) &&
4140 39 : !(dfSouthLat == -90.0 || dfNorthLat == 90.0 ||
4141 33 : dfWestLong == -180.0 || dfEastLong == 180.0 ||
4142 25 : dfWestLong > dfEastLong))
4143 : {
4144 : // Not a global geographic CRS
4145 25 : return false;
4146 : }
4147 27 : return true;
4148 : }
4149 32 : return false;
4150 : }
4151 : #endif
4152 :
4153 : //! @cond Doxygen_Suppress
4154 : /*static */
4155 13 : bool OGRGeometryFactory::isTransformWithOptionsRegularTransform(
4156 : [[maybe_unused]] const OGRSpatialReference *poSourceCRS,
4157 : [[maybe_unused]] const OGRSpatialReference *poTargetCRS,
4158 : CSLConstList papszOptions)
4159 : {
4160 13 : if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "WRAPDATELINE", "NO")) &&
4161 13 : poTargetCRS && poTargetCRS->IsGeographic())
4162 : {
4163 0 : return false;
4164 : }
4165 :
4166 : #ifdef HAVE_GEOS
4167 13 : if (MayBePolarToGeographic(poSourceCRS, poTargetCRS))
4168 : {
4169 1 : return false;
4170 : }
4171 : #endif
4172 :
4173 12 : return true;
4174 : }
4175 :
4176 : //! @endcond
4177 :
4178 : /************************************************************************/
4179 : /* transformWithOptions() */
4180 : /************************************************************************/
4181 :
4182 : /** Transform a geometry.
4183 : *
4184 : * This is an enhanced version of OGRGeometry::Transform().
4185 : *
4186 : * When reprojecting geometries from a Polar Stereographic projection or a
4187 : * projection naturally crossing the antimeridian (like UTM Zone 60) to a
4188 : * geographic CRS, it will cut geometries along the antimeridian. So a
4189 : * LineString might be returned as a MultiLineString.
4190 : *
4191 : * The WRAPDATELINE=YES option might be specified for circumstances to correct
4192 : * geometries that incorrectly go from a longitude on a side of the antimeridian
4193 : * to the other side, like a LINESTRING(-179 0,179 0) will be transformed to
4194 : * a MULTILINESTRING ((-179 0,-180 0),(180 0,179 0)). For that use case, hCT
4195 : * might be NULL.
4196 : *
4197 : * Supported options in papszOptions are:
4198 : * <ul>
4199 : * <li>WRAPDATELINE=YES</li>
4200 : * <li>DATELINEOFFSET=longitude_gap_in_degree. Defaults to 10.</li>
4201 : * </ul>
4202 : *
4203 : * This is the same as the C function OGR_GeomTransformer_Transform().
4204 : *
4205 : * @param poSrcGeom source geometry
4206 : * @param poCT coordinate transformation object, or NULL.
4207 : * @param papszOptions NULL terminated list of options, or NULL.
4208 : * @param cache Cache. May increase performance if persisted between invocations
4209 : * @return (new) transformed geometry.
4210 : */
4211 646 : OGRGeometry *OGRGeometryFactory::transformWithOptions(
4212 : const OGRGeometry *poSrcGeom, OGRCoordinateTransformation *poCT,
4213 : CSLConstList papszOptions,
4214 : CPL_UNUSED const TransformWithOptionsCache &cache)
4215 : {
4216 1292 : auto poDstGeom = std::unique_ptr<OGRGeometry>(poSrcGeom->clone());
4217 646 : if (poCT)
4218 : {
4219 : #ifdef HAVE_GEOS
4220 611 : bool bNeedPostCorrection = false;
4221 611 : const auto poSourceCRS = poCT->GetSourceCS();
4222 611 : const auto poTargetCRS = poCT->GetTargetCS();
4223 611 : const auto eSrcGeomType = wkbFlatten(poSrcGeom->getGeometryType());
4224 : // Check if we are transforming from projected coordinates to
4225 : // geographic coordinates, with a chance that there might be polar or
4226 : // anti-meridian discontinuities. If so, create the inverse transform.
4227 802 : if (eSrcGeomType != wkbPoint && eSrcGeomType != wkbMultiPoint &&
4228 191 : (poSourceCRS != cache.d->poSourceCRS ||
4229 120 : poTargetCRS != cache.d->poTargetCRS || poCT != cache.d->poCT))
4230 : {
4231 71 : cache.d->clear();
4232 71 : cache.d->poSourceCRS = poSourceCRS;
4233 71 : cache.d->poTargetCRS = poTargetCRS;
4234 71 : cache.d->poCT = poCT;
4235 71 : if (MayBePolarToGeographic(poSourceCRS, poTargetCRS))
4236 : {
4237 26 : cache.d->poRevCT.reset(OGRCreateCoordinateTransformation(
4238 : poTargetCRS, poSourceCRS));
4239 26 : cache.d->bIsNorthPolar = false;
4240 26 : cache.d->bIsPolar = false;
4241 26 : cache.d->poRevCT.reset(poCT->GetInverse());
4242 78 : if (cache.d->poRevCT &&
4243 26 : IsPolarToGeographic(poCT, cache.d->poRevCT.get(),
4244 52 : cache.d->bIsNorthPolar))
4245 : {
4246 11 : cache.d->bIsPolar = true;
4247 : }
4248 : }
4249 : }
4250 :
4251 611 : if (auto poRevCT = cache.d->poRevCT.get())
4252 : {
4253 38 : if (cache.d->bIsPolar)
4254 : {
4255 24 : poDstGeom = TransformBeforePolarToGeographic(
4256 24 : poRevCT, cache.d->bIsNorthPolar, std::move(poDstGeom),
4257 12 : bNeedPostCorrection);
4258 : }
4259 26 : else if (IsAntimeridianProjToGeographic(poCT, poRevCT,
4260 : poDstGeom.get()))
4261 : {
4262 48 : poDstGeom = TransformBeforeAntimeridianToGeographic(
4263 48 : poCT, poRevCT, std::move(poDstGeom), bNeedPostCorrection);
4264 : }
4265 : }
4266 : #endif
4267 611 : OGRErr eErr = poDstGeom->transform(poCT);
4268 611 : if (eErr != OGRERR_NONE)
4269 : {
4270 4 : return nullptr;
4271 : }
4272 : #ifdef HAVE_GEOS
4273 607 : if (bNeedPostCorrection)
4274 : {
4275 22 : SnapCoordsCloseToLatLongBounds(poDstGeom.get());
4276 : }
4277 : #endif
4278 : }
4279 :
4280 642 : if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "WRAPDATELINE", "NO")))
4281 : {
4282 55 : const auto poDstGeomSRS = poDstGeom->getSpatialReference();
4283 55 : if (poDstGeomSRS && !poDstGeomSRS->IsGeographic())
4284 : {
4285 1 : CPLDebugOnce(
4286 : "OGR", "WRAPDATELINE is without effect when reprojecting to a "
4287 : "non-geographic CRS");
4288 1 : return poDstGeom.release();
4289 : }
4290 : // TODO and we should probably also test that the axis order + data axis
4291 : // mapping is long-lat...
4292 : const OGRwkbGeometryType eType =
4293 54 : wkbFlatten(poDstGeom->getGeometryType());
4294 54 : if (eType == wkbPoint)
4295 : {
4296 9 : OGRPoint *poDstPoint = poDstGeom->toPoint();
4297 9 : WrapPointDateLine(poDstPoint);
4298 : }
4299 45 : else if (eType == wkbMultiPoint)
4300 : {
4301 5 : for (auto *poDstPoint : *(poDstGeom->toMultiPoint()))
4302 : {
4303 4 : WrapPointDateLine(poDstPoint);
4304 : }
4305 : }
4306 : else
4307 : {
4308 44 : OGREnvelope sEnvelope;
4309 44 : poDstGeom->getEnvelope(&sEnvelope);
4310 44 : if (sEnvelope.MinX >= -360.0 && sEnvelope.MaxX <= -180.0)
4311 2 : AddOffsetToLon(poDstGeom.get(), 360.0);
4312 42 : else if (sEnvelope.MinX >= 180.0 && sEnvelope.MaxX <= 360.0)
4313 2 : AddOffsetToLon(poDstGeom.get(), -360.0);
4314 : else
4315 : {
4316 : OGRwkbGeometryType eNewType;
4317 40 : if (eType == wkbPolygon || eType == wkbMultiPolygon)
4318 29 : eNewType = wkbMultiPolygon;
4319 11 : else if (eType == wkbLineString || eType == wkbMultiLineString)
4320 10 : eNewType = wkbMultiLineString;
4321 : else
4322 1 : eNewType = wkbGeometryCollection;
4323 :
4324 : auto poMulti = std::unique_ptr<OGRGeometryCollection>(
4325 80 : createGeometry(eNewType)->toGeometryCollection());
4326 :
4327 40 : double dfDateLineOffset = CPLAtofM(
4328 : CSLFetchNameValueDef(papszOptions, "DATELINEOFFSET", "10"));
4329 40 : if (dfDateLineOffset <= 0.0 || dfDateLineOffset >= 360.0)
4330 0 : dfDateLineOffset = 10.0;
4331 :
4332 40 : CutGeometryOnDateLineAndAddToMulti(
4333 40 : poMulti.get(), poDstGeom.get(), dfDateLineOffset);
4334 :
4335 40 : if (poMulti->getNumGeometries() == 0)
4336 : {
4337 : // do nothing
4338 : }
4339 41 : else if (poMulti->getNumGeometries() == 1 &&
4340 1 : (eType == wkbPolygon || eType == wkbLineString))
4341 : {
4342 13 : poDstGeom = poMulti->stealGeometry(0);
4343 : }
4344 : else
4345 : {
4346 27 : poDstGeom = std::move(poMulti);
4347 : }
4348 : }
4349 : }
4350 : }
4351 :
4352 641 : return poDstGeom.release();
4353 : }
4354 :
4355 : /************************************************************************/
4356 : /* OGRGeomTransformer() */
4357 : /************************************************************************/
4358 :
4359 : struct OGRGeomTransformer
4360 : {
4361 : std::unique_ptr<OGRCoordinateTransformation> poCT{};
4362 : OGRGeometryFactory::TransformWithOptionsCache cache{};
4363 : CPLStringList aosOptions{};
4364 :
4365 10 : OGRGeomTransformer() = default;
4366 : OGRGeomTransformer(const OGRGeomTransformer &) = delete;
4367 : OGRGeomTransformer &operator=(const OGRGeomTransformer &) = delete;
4368 : };
4369 :
4370 : /************************************************************************/
4371 : /* OGR_GeomTransformer_Create() */
4372 : /************************************************************************/
4373 :
4374 : /** Create a geometry transformer.
4375 : *
4376 : * This is an enhanced version of OGR_G_Transform().
4377 : *
4378 : * When reprojecting geometries from a Polar Stereographic projection or a
4379 : * projection naturally crossing the antimeridian (like UTM Zone 60) to a
4380 : * geographic CRS, it will cut geometries along the antimeridian. So a
4381 : * LineString might be returned as a MultiLineString.
4382 : *
4383 : * The WRAPDATELINE=YES option might be specified for circumstances to correct
4384 : * geometries that incorrectly go from a longitude on a side of the antimeridian
4385 : * to the other side, like a LINESTRING(-179 0,179 0) will be transformed to
4386 : * a MULTILINESTRING ((-179 0,-180 0),(180 0,179 0)). For that use case, hCT
4387 : * might be NULL.
4388 : *
4389 : * Supported options in papszOptions are:
4390 : * <ul>
4391 : * <li>WRAPDATELINE=YES</li>
4392 : * <li>DATELINEOFFSET=longitude_gap_in_degree. Defaults to 10.</li>
4393 : * </ul>
4394 : *
4395 : * This is the same as the C++ method OGRGeometryFactory::transformWithOptions().
4396 :
4397 : * @param hCT Coordinate transformation object (will be cloned) or NULL.
4398 : * @param papszOptions NULL terminated list of options, or NULL.
4399 : * @return transformer object to free with OGR_GeomTransformer_Destroy()
4400 : * @since GDAL 3.1
4401 : */
4402 10 : OGRGeomTransformerH OGR_GeomTransformer_Create(OGRCoordinateTransformationH hCT,
4403 : CSLConstList papszOptions)
4404 : {
4405 10 : OGRGeomTransformer *transformer = new OGRGeomTransformer;
4406 10 : if (hCT)
4407 : {
4408 7 : transformer->poCT.reset(
4409 7 : OGRCoordinateTransformation::FromHandle(hCT)->Clone());
4410 : }
4411 10 : transformer->aosOptions.Assign(CSLDuplicate(papszOptions));
4412 10 : return transformer;
4413 : }
4414 :
4415 : /************************************************************************/
4416 : /* OGR_GeomTransformer_Transform() */
4417 : /************************************************************************/
4418 :
4419 : /** Transforms a geometry.
4420 : *
4421 : * @param hTransformer transformer object.
4422 : * @param hGeom Source geometry.
4423 : * @return a new geometry (or NULL) to destroy with OGR_G_DestroyGeometry()
4424 : * @since GDAL 3.1
4425 : */
4426 10 : OGRGeometryH OGR_GeomTransformer_Transform(OGRGeomTransformerH hTransformer,
4427 : OGRGeometryH hGeom)
4428 : {
4429 10 : VALIDATE_POINTER1(hTransformer, "OGR_GeomTransformer_Transform", nullptr);
4430 10 : VALIDATE_POINTER1(hGeom, "OGR_GeomTransformer_Transform", nullptr);
4431 :
4432 20 : return OGRGeometry::ToHandle(OGRGeometryFactory::transformWithOptions(
4433 10 : OGRGeometry::FromHandle(hGeom), hTransformer->poCT.get(),
4434 20 : hTransformer->aosOptions.List(), hTransformer->cache));
4435 : }
4436 :
4437 : /************************************************************************/
4438 : /* OGR_GeomTransformer_Destroy() */
4439 : /************************************************************************/
4440 :
4441 : /** Destroy a geometry transformer allocated with OGR_GeomTransformer_Create()
4442 : *
4443 : * @param hTransformer transformer object.
4444 : * @since GDAL 3.1
4445 : */
4446 10 : void OGR_GeomTransformer_Destroy(OGRGeomTransformerH hTransformer)
4447 : {
4448 10 : delete hTransformer;
4449 10 : }
4450 :
4451 : /************************************************************************/
4452 : /* OGRGeometryFactory::GetDefaultArcStepSize() */
4453 : /************************************************************************/
4454 :
4455 : /** Return the default value of the angular step used when stroking curves
4456 : * as lines. Defaults to 4 degrees.
4457 : * Can be modified by setting the OGR_ARC_STEPSIZE configuration option.
4458 : * Valid values are in [1e-2, 180] degree range.
4459 : * @since 3.11
4460 : */
4461 :
4462 : /* static */
4463 4419 : double OGRGeometryFactory::GetDefaultArcStepSize()
4464 : {
4465 4419 : const double dfVal = CPLAtofM(CPLGetConfigOption("OGR_ARC_STEPSIZE", "4"));
4466 4419 : constexpr double MIN_VAL = 1e-2;
4467 4419 : if (dfVal < MIN_VAL)
4468 : {
4469 1 : CPLErrorOnce(CE_Warning, CPLE_AppDefined,
4470 : "Too small value for OGR_ARC_STEPSIZE. Clamping it to %f",
4471 : MIN_VAL);
4472 1 : return MIN_VAL;
4473 : }
4474 4418 : constexpr double MAX_VAL = 180;
4475 4418 : if (dfVal > MAX_VAL)
4476 : {
4477 1 : CPLErrorOnce(CE_Warning, CPLE_AppDefined,
4478 : "Too large value for OGR_ARC_STEPSIZE. Clamping it to %f",
4479 : MAX_VAL);
4480 1 : return MAX_VAL;
4481 : }
4482 4417 : return dfVal;
4483 : }
4484 :
4485 : /************************************************************************/
4486 : /* DISTANCE() */
4487 : /************************************************************************/
4488 :
4489 351224 : static inline double DISTANCE(double x1, double y1, double x2, double y2)
4490 : {
4491 351224 : return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
4492 : }
4493 :
4494 : /************************************************************************/
4495 : /* approximateArcAngles() */
4496 : /************************************************************************/
4497 :
4498 : /**
4499 : * Stroke arc to linestring.
4500 : *
4501 : * Stroke an arc of a circle to a linestring based on a center
4502 : * point, radius, start angle and end angle, all angles in degrees.
4503 : *
4504 : * If the dfMaxAngleStepSizeDegrees is zero, then a default value will be
4505 : * used. This is currently 4 degrees unless the user has overridden the
4506 : * value with the OGR_ARC_STEPSIZE configuration variable.
4507 : *
4508 : * If the OGR_ARC_MAX_GAP configuration variable is set, the straight-line
4509 : * distance between adjacent pairs of interpolated points will be limited to
4510 : * the specified distance. If the distance between a pair of points exceeds
4511 : * this maximum, additional points are interpolated between the two points.
4512 : *
4513 : * @see CPLSetConfigOption()
4514 : *
4515 : * @param dfCenterX center X
4516 : * @param dfCenterY center Y
4517 : * @param dfZ center Z
4518 : * @param dfPrimaryRadius X radius of ellipse.
4519 : * @param dfSecondaryRadius Y radius of ellipse.
4520 : * @param dfRotation rotation of the ellipse clockwise.
4521 : * @param dfStartAngle angle to first point on arc (clockwise of X-positive)
4522 : * @param dfEndAngle angle to last point on arc (clockwise of X-positive)
4523 : * @param dfMaxAngleStepSizeDegrees the largest step in degrees along the
4524 : * arc, zero to use the default setting.
4525 : * @param bUseMaxGap Optional: whether to honor OGR_ARC_MAX_GAP.
4526 : *
4527 : * @return OGRLineString geometry representing an approximation of the arc.
4528 : *
4529 : */
4530 :
4531 118 : OGRGeometry *OGRGeometryFactory::approximateArcAngles(
4532 : double dfCenterX, double dfCenterY, double dfZ, double dfPrimaryRadius,
4533 : double dfSecondaryRadius, double dfRotation, double dfStartAngle,
4534 : double dfEndAngle, double dfMaxAngleStepSizeDegrees,
4535 : const bool bUseMaxGap /* = false */)
4536 :
4537 : {
4538 118 : OGRLineString *poLine = new OGRLineString();
4539 118 : const double dfRotationRadians = dfRotation * M_PI / 180.0;
4540 :
4541 : // Support default arc step setting.
4542 118 : if (dfMaxAngleStepSizeDegrees < 1e-6)
4543 : {
4544 117 : dfMaxAngleStepSizeDegrees = OGRGeometryFactory::GetDefaultArcStepSize();
4545 : }
4546 :
4547 : // Determine maximum interpolation gap. This is the largest straight-line
4548 : // distance allowed between pairs of interpolated points. Default zero,
4549 : // meaning no gap.
4550 : // coverity[tainted_data]
4551 : const double dfMaxInterpolationGap =
4552 118 : bUseMaxGap ? CPLAtofM(CPLGetConfigOption("OGR_ARC_MAX_GAP", "0")) : 0.0;
4553 :
4554 : // Is this a full circle?
4555 118 : const bool bIsFullCircle = fabs(dfEndAngle - dfStartAngle) == 360.0;
4556 :
4557 : // Switch direction.
4558 118 : dfStartAngle *= -1;
4559 118 : dfEndAngle *= -1;
4560 :
4561 : // Figure out the number of slices to make this into.
4562 : int nVertexCount =
4563 236 : std::max(2, static_cast<int>(ceil(fabs(dfEndAngle - dfStartAngle) /
4564 118 : dfMaxAngleStepSizeDegrees) +
4565 118 : 1));
4566 118 : const double dfSlice = (dfEndAngle - dfStartAngle) / (nVertexCount - 1);
4567 :
4568 : // If it is a full circle we will work out the last point separately.
4569 118 : if (bIsFullCircle)
4570 : {
4571 52 : nVertexCount--;
4572 : }
4573 :
4574 : /* -------------------------------------------------------------------- */
4575 : /* Compute the interpolated points. */
4576 : /* -------------------------------------------------------------------- */
4577 118 : double dfLastX = 0.0;
4578 118 : double dfLastY = 0.0;
4579 118 : int nTotalAddPoints = 0;
4580 7071 : for (int iPoint = 0; iPoint < nVertexCount; iPoint++)
4581 : {
4582 6953 : const double dfAngleOnEllipse =
4583 6953 : (dfStartAngle + iPoint * dfSlice) * M_PI / 180.0;
4584 :
4585 : // Compute position on the unrotated ellipse.
4586 6953 : const double dfEllipseX = cos(dfAngleOnEllipse) * dfPrimaryRadius;
4587 6953 : const double dfEllipseY = sin(dfAngleOnEllipse) * dfSecondaryRadius;
4588 :
4589 : // Is this point too far from the previous point?
4590 6953 : if (iPoint && dfMaxInterpolationGap != 0.0)
4591 : {
4592 : const double dfDistFromLast =
4593 1 : DISTANCE(dfLastX, dfLastY, dfEllipseX, dfEllipseY);
4594 :
4595 1 : if (dfDistFromLast > dfMaxInterpolationGap)
4596 : {
4597 1 : const int nAddPoints =
4598 1 : static_cast<int>(dfDistFromLast / dfMaxInterpolationGap);
4599 1 : const double dfAddSlice = dfSlice / (nAddPoints + 1);
4600 :
4601 : // Interpolate additional points
4602 3 : for (int iAddPoint = 0; iAddPoint < nAddPoints; iAddPoint++)
4603 : {
4604 2 : const double dfAddAngleOnEllipse =
4605 2 : (dfStartAngle + (iPoint - 1) * dfSlice +
4606 2 : (iAddPoint + 1) * dfAddSlice) *
4607 : (M_PI / 180.0);
4608 :
4609 2 : poLine->setPoint(
4610 2 : iPoint + nTotalAddPoints + iAddPoint,
4611 2 : cos(dfAddAngleOnEllipse) * dfPrimaryRadius,
4612 2 : sin(dfAddAngleOnEllipse) * dfSecondaryRadius, dfZ);
4613 : }
4614 :
4615 1 : nTotalAddPoints += nAddPoints;
4616 : }
4617 : }
4618 :
4619 6953 : poLine->setPoint(iPoint + nTotalAddPoints, dfEllipseX, dfEllipseY, dfZ);
4620 6953 : dfLastX = dfEllipseX;
4621 6953 : dfLastY = dfEllipseY;
4622 : }
4623 :
4624 : /* -------------------------------------------------------------------- */
4625 : /* Rotate and translate the ellipse. */
4626 : /* -------------------------------------------------------------------- */
4627 118 : nVertexCount = poLine->getNumPoints();
4628 7073 : for (int iPoint = 0; iPoint < nVertexCount; iPoint++)
4629 : {
4630 6955 : const double dfEllipseX = poLine->getX(iPoint);
4631 6955 : const double dfEllipseY = poLine->getY(iPoint);
4632 :
4633 : // Rotate this position around the center of the ellipse.
4634 6955 : const double dfArcX = dfCenterX + dfEllipseX * cos(dfRotationRadians) +
4635 6955 : dfEllipseY * sin(dfRotationRadians);
4636 6955 : const double dfArcY = dfCenterY - dfEllipseX * sin(dfRotationRadians) +
4637 6955 : dfEllipseY * cos(dfRotationRadians);
4638 :
4639 6955 : poLine->setPoint(iPoint, dfArcX, dfArcY, dfZ);
4640 : }
4641 :
4642 : /* -------------------------------------------------------------------- */
4643 : /* If we're asked to make a full circle, ensure the start and */
4644 : /* end points coincide exactly, in spite of any rounding error. */
4645 : /* -------------------------------------------------------------------- */
4646 118 : if (bIsFullCircle)
4647 : {
4648 104 : OGRPoint oPoint;
4649 52 : poLine->getPoint(0, &oPoint);
4650 52 : poLine->setPoint(nVertexCount, &oPoint);
4651 : }
4652 :
4653 118 : return poLine;
4654 : }
4655 :
4656 : /************************************************************************/
4657 : /* OGR_G_ApproximateArcAngles() */
4658 : /************************************************************************/
4659 :
4660 : /**
4661 : * Stroke arc to linestring.
4662 : *
4663 : * Stroke an arc of a circle to a linestring based on a center
4664 : * point, radius, start angle and end angle, all angles in degrees.
4665 : *
4666 : * If the dfMaxAngleStepSizeDegrees is zero, then a default value will be
4667 : * used. This is currently 4 degrees unless the user has overridden the
4668 : * value with the OGR_ARC_STEPSIZE configuration variable.
4669 : *
4670 : * @see CPLSetConfigOption()
4671 : *
4672 : * @param dfCenterX center X
4673 : * @param dfCenterY center Y
4674 : * @param dfZ center Z
4675 : * @param dfPrimaryRadius X radius of ellipse.
4676 : * @param dfSecondaryRadius Y radius of ellipse.
4677 : * @param dfRotation rotation of the ellipse clockwise.
4678 : * @param dfStartAngle angle to first point on arc (clockwise of X-positive)
4679 : * @param dfEndAngle angle to last point on arc (clockwise of X-positive)
4680 : * @param dfMaxAngleStepSizeDegrees the largest step in degrees along the
4681 : * arc, zero to use the default setting.
4682 : *
4683 : * @return OGRLineString geometry representing an approximation of the arc.
4684 : *
4685 : */
4686 :
4687 1 : OGRGeometryH CPL_DLL OGR_G_ApproximateArcAngles(
4688 : double dfCenterX, double dfCenterY, double dfZ, double dfPrimaryRadius,
4689 : double dfSecondaryRadius, double dfRotation, double dfStartAngle,
4690 : double dfEndAngle, double dfMaxAngleStepSizeDegrees)
4691 :
4692 : {
4693 1 : return OGRGeometry::ToHandle(OGRGeometryFactory::approximateArcAngles(
4694 : dfCenterX, dfCenterY, dfZ, dfPrimaryRadius, dfSecondaryRadius,
4695 1 : dfRotation, dfStartAngle, dfEndAngle, dfMaxAngleStepSizeDegrees));
4696 : }
4697 :
4698 : /************************************************************************/
4699 : /* forceToLineString() */
4700 : /************************************************************************/
4701 :
4702 : /**
4703 : * \brief Convert to line string.
4704 : *
4705 : * Tries to force the provided geometry to be a line string. This nominally
4706 : * effects a change on multilinestrings.
4707 : * For polygons or curvepolygons that have a single exterior ring,
4708 : * it will return the ring. For circular strings or compound curves, it will
4709 : * return an approximated line string.
4710 : *
4711 : * The passed in geometry is
4712 : * consumed and a new one returned (or potentially the same one).
4713 : *
4714 : * @param poGeom the input geometry - ownership is passed to the method.
4715 : * @param bOnlyInOrder flag that, if set to FALSE, indicate that the order of
4716 : * points in a linestring might be reversed if it enables
4717 : * to match the extremity of another linestring. If set
4718 : * to TRUE, the start of a linestring must match the end
4719 : * of another linestring.
4720 : * @return new geometry.
4721 : */
4722 :
4723 187 : OGRGeometry *OGRGeometryFactory::forceToLineString(OGRGeometry *poGeom,
4724 : bool bOnlyInOrder)
4725 :
4726 : {
4727 187 : if (poGeom == nullptr)
4728 2 : return nullptr;
4729 :
4730 185 : const OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
4731 :
4732 : /* -------------------------------------------------------------------- */
4733 : /* If this is already a LineString, nothing to do */
4734 : /* -------------------------------------------------------------------- */
4735 185 : if (eGeomType == wkbLineString)
4736 : {
4737 : // Except if it is a linearring.
4738 25 : poGeom = OGRCurve::CastToLineString(poGeom->toCurve());
4739 :
4740 25 : return poGeom;
4741 : }
4742 :
4743 : /* -------------------------------------------------------------------- */
4744 : /* If it is a polygon with a single ring, return it */
4745 : /* -------------------------------------------------------------------- */
4746 160 : if (eGeomType == wkbPolygon || eGeomType == wkbCurvePolygon)
4747 : {
4748 30 : OGRCurvePolygon *poCP = poGeom->toCurvePolygon();
4749 30 : if (poCP->getNumInteriorRings() == 0)
4750 : {
4751 28 : OGRCurve *poRing = poCP->stealExteriorRingCurve();
4752 28 : delete poCP;
4753 28 : return forceToLineString(poRing);
4754 : }
4755 2 : return poGeom;
4756 : }
4757 :
4758 : /* -------------------------------------------------------------------- */
4759 : /* If it is a curve line, call CurveToLine() */
4760 : /* -------------------------------------------------------------------- */
4761 130 : if (eGeomType == wkbCircularString || eGeomType == wkbCompoundCurve)
4762 : {
4763 79 : OGRGeometry *poNewGeom = poGeom->toCurve()->CurveToLine();
4764 79 : delete poGeom;
4765 79 : return poNewGeom;
4766 : }
4767 :
4768 51 : if (eGeomType != wkbGeometryCollection && eGeomType != wkbMultiLineString &&
4769 : eGeomType != wkbMultiCurve)
4770 20 : return poGeom;
4771 :
4772 : // Build an aggregated linestring from all the linestrings in the container.
4773 31 : OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
4774 31 : if (poGeom->hasCurveGeometry())
4775 : {
4776 : OGRGeometryCollection *poNewGC =
4777 7 : poGC->getLinearGeometry()->toGeometryCollection();
4778 7 : delete poGC;
4779 7 : poGC = poNewGC;
4780 : }
4781 :
4782 31 : if (poGC->getNumGeometries() == 0)
4783 : {
4784 3 : poGeom = new OGRLineString();
4785 3 : poGeom->assignSpatialReference(poGC->getSpatialReference());
4786 3 : delete poGC;
4787 3 : return poGeom;
4788 : }
4789 :
4790 28 : int iGeom0 = 0;
4791 69 : while (iGeom0 < poGC->getNumGeometries())
4792 : {
4793 41 : if (wkbFlatten(poGC->getGeometryRef(iGeom0)->getGeometryType()) !=
4794 : wkbLineString)
4795 : {
4796 12 : iGeom0++;
4797 26 : continue;
4798 : }
4799 :
4800 : OGRLineString *poLineString0 =
4801 29 : poGC->getGeometryRef(iGeom0)->toLineString();
4802 29 : if (poLineString0->getNumPoints() < 2)
4803 : {
4804 14 : iGeom0++;
4805 14 : continue;
4806 : }
4807 :
4808 30 : OGRPoint pointStart0;
4809 15 : poLineString0->StartPoint(&pointStart0);
4810 30 : OGRPoint pointEnd0;
4811 15 : poLineString0->EndPoint(&pointEnd0);
4812 :
4813 15 : int iGeom1 = iGeom0 + 1; // Used after for.
4814 17 : for (; iGeom1 < poGC->getNumGeometries(); iGeom1++)
4815 : {
4816 6 : if (wkbFlatten(poGC->getGeometryRef(iGeom1)->getGeometryType()) !=
4817 : wkbLineString)
4818 1 : continue;
4819 :
4820 : OGRLineString *poLineString1 =
4821 6 : poGC->getGeometryRef(iGeom1)->toLineString();
4822 6 : if (poLineString1->getNumPoints() < 2)
4823 1 : continue;
4824 :
4825 5 : OGRPoint pointStart1;
4826 5 : poLineString1->StartPoint(&pointStart1);
4827 5 : OGRPoint pointEnd1;
4828 5 : poLineString1->EndPoint(&pointEnd1);
4829 :
4830 5 : if (!bOnlyInOrder && (pointEnd0.Equals(&pointEnd1) ||
4831 0 : pointStart0.Equals(&pointStart1)))
4832 : {
4833 0 : poLineString1->reversePoints();
4834 0 : poLineString1->StartPoint(&pointStart1);
4835 0 : poLineString1->EndPoint(&pointEnd1);
4836 : }
4837 :
4838 5 : if (pointEnd0.Equals(&pointStart1))
4839 : {
4840 4 : poLineString0->addSubLineString(poLineString1, 1);
4841 4 : poGC->removeGeometry(iGeom1);
4842 4 : break;
4843 : }
4844 :
4845 1 : if (pointEnd1.Equals(&pointStart0))
4846 : {
4847 0 : poLineString1->addSubLineString(poLineString0, 1);
4848 0 : poGC->removeGeometry(iGeom0);
4849 0 : break;
4850 : }
4851 : }
4852 :
4853 15 : if (iGeom1 == poGC->getNumGeometries())
4854 : {
4855 14 : iGeom0++;
4856 : }
4857 : }
4858 :
4859 28 : if (poGC->getNumGeometries() == 1)
4860 : {
4861 20 : OGRGeometry *poSingleGeom = poGC->getGeometryRef(0);
4862 20 : poGC->removeGeometry(0, FALSE);
4863 20 : delete poGC;
4864 :
4865 20 : return poSingleGeom;
4866 : }
4867 :
4868 8 : return poGC;
4869 : }
4870 :
4871 : /************************************************************************/
4872 : /* OGR_G_ForceToLineString() */
4873 : /************************************************************************/
4874 :
4875 : /**
4876 : * \brief Convert to line string.
4877 : *
4878 : * This function is the same as the C++ method
4879 : * OGRGeometryFactory::forceToLineString().
4880 : *
4881 : * @param hGeom handle to the geometry to convert (ownership surrendered).
4882 : * @return the converted geometry (ownership to caller).
4883 : *
4884 : * @since GDAL/OGR 1.10.0
4885 : */
4886 :
4887 60 : OGRGeometryH OGR_G_ForceToLineString(OGRGeometryH hGeom)
4888 :
4889 : {
4890 60 : return OGRGeometry::ToHandle(
4891 60 : OGRGeometryFactory::forceToLineString(OGRGeometry::FromHandle(hGeom)));
4892 : }
4893 :
4894 : /************************************************************************/
4895 : /* forceTo() */
4896 : /************************************************************************/
4897 :
4898 : /**
4899 : * \brief Convert to another geometry type
4900 : *
4901 : * Tries to force the provided geometry to the specified geometry type.
4902 : *
4903 : * It can promote 'single' geometry type to their corresponding collection type
4904 : * (see OGR_GT_GetCollection()) or the reverse. non-linear geometry type to
4905 : * their corresponding linear geometry type (see OGR_GT_GetLinear()), by
4906 : * possibly approximating circular arcs they may contain. Regarding conversion
4907 : * from linear geometry types to curve geometry types, only "wrapping" will be
4908 : * done. No attempt to retrieve potential circular arcs by de-approximating
4909 : * stroking will be done. For that, OGRGeometry::getCurveGeometry() can be used.
4910 : *
4911 : * The passed in geometry is consumed and a new one returned (or potentially the
4912 : * same one).
4913 : *
4914 : * Starting with GDAL 3.9, this method honours the dimensionality of eTargetType.
4915 : *
4916 : * @param poGeom the input geometry - ownership is passed to the method.
4917 : * @param eTargetType target output geometry type.
4918 : * @param papszOptions options as a null-terminated list of strings or NULL.
4919 : * @return new geometry, or nullptr in case of error.
4920 : *
4921 : */
4922 :
4923 0 : OGRGeometry *OGRGeometryFactory::forceTo(OGRGeometry *poGeom,
4924 : OGRwkbGeometryType eTargetType,
4925 : const char *const *papszOptions)
4926 : {
4927 0 : return forceTo(std::unique_ptr<OGRGeometry>(poGeom), eTargetType,
4928 : papszOptions)
4929 0 : .release();
4930 : }
4931 :
4932 : /**
4933 : * \brief Convert to another geometry type
4934 : *
4935 : * Tries to force the provided geometry to the specified geometry type.
4936 : *
4937 : * It can promote 'single' geometry type to their corresponding collection type
4938 : * (see OGR_GT_GetCollection()) or the reverse. non-linear geometry type to
4939 : * their corresponding linear geometry type (see OGR_GT_GetLinear()), by
4940 : * possibly approximating circular arcs they may contain. Regarding conversion
4941 : * from linear geometry types to curve geometry types, only "wrapping" will be
4942 : * done. No attempt to retrieve potential circular arcs by de-approximating
4943 : * stroking will be done. For that, OGRGeometry::getCurveGeometry() can be used.
4944 : *
4945 : * The passed in geometry is consumed and a new one returned (or potentially the
4946 : * same one).
4947 : *
4948 : * This method honours the dimensionality of eTargetType.
4949 : *
4950 : * @param poGeom the input geometry - ownership is passed to the method.
4951 : * @param eTargetType target output geometry type.
4952 : * @param papszOptions options as a null-terminated list of strings or NULL.
4953 : * @return new geometry, or nullptr in case of error.
4954 : *
4955 : * @since 3.13
4956 : */
4957 :
4958 : std::unique_ptr<OGRGeometry>
4959 5134 : OGRGeometryFactory::forceTo(std::unique_ptr<OGRGeometry> poGeom,
4960 : OGRwkbGeometryType eTargetType,
4961 : const char *const *papszOptions)
4962 : {
4963 5134 : if (poGeom == nullptr)
4964 0 : return poGeom;
4965 :
4966 5134 : const OGRwkbGeometryType eTargetTypeFlat = wkbFlatten(eTargetType);
4967 5134 : if (eTargetTypeFlat == wkbUnknown)
4968 274 : return poGeom;
4969 :
4970 4860 : if (poGeom->IsEmpty())
4971 : {
4972 558 : auto poRet = std::unique_ptr<OGRGeometry>(createGeometry(eTargetType));
4973 279 : if (poRet)
4974 : {
4975 279 : poRet->assignSpatialReference(poGeom->getSpatialReference());
4976 279 : poRet->set3D(OGR_GT_HasZ(eTargetType));
4977 279 : poRet->setMeasured(OGR_GT_HasM(eTargetType));
4978 : }
4979 279 : return poRet;
4980 : }
4981 :
4982 4581 : OGRwkbGeometryType eType = poGeom->getGeometryType();
4983 4581 : OGRwkbGeometryType eTypeFlat = wkbFlatten(eType);
4984 :
4985 4581 : if (eTargetTypeFlat != eTargetType && (eType == eTypeFlat))
4986 : {
4987 : auto poGeomNew =
4988 132 : forceTo(std::move(poGeom), eTargetTypeFlat, papszOptions);
4989 66 : if (poGeomNew)
4990 : {
4991 66 : poGeomNew->set3D(OGR_GT_HasZ(eTargetType));
4992 66 : poGeomNew->setMeasured(OGR_GT_HasM(eTargetType));
4993 : }
4994 66 : return poGeomNew;
4995 : }
4996 :
4997 4515 : if (eTypeFlat == eTargetTypeFlat)
4998 : {
4999 555 : poGeom->set3D(OGR_GT_HasZ(eTargetType));
5000 555 : poGeom->setMeasured(OGR_GT_HasM(eTargetType));
5001 555 : return poGeom;
5002 : }
5003 :
5004 3960 : eType = eTypeFlat;
5005 :
5006 5669 : if (OGR_GT_IsSubClassOf(eType, wkbPolyhedralSurface) &&
5007 1709 : (eTargetTypeFlat == wkbMultiSurface ||
5008 : eTargetTypeFlat == wkbGeometryCollection))
5009 : {
5010 847 : OGRwkbGeometryType eTempGeomType = wkbMultiPolygon;
5011 847 : if (OGR_GT_HasZ(eTargetType))
5012 843 : eTempGeomType = OGR_GT_SetZ(eTempGeomType);
5013 847 : if (OGR_GT_HasM(eTargetType))
5014 0 : eTempGeomType = OGR_GT_SetM(eTempGeomType);
5015 1694 : return forceTo(forceTo(std::move(poGeom), eTempGeomType, papszOptions),
5016 847 : eTargetType, papszOptions);
5017 : }
5018 :
5019 3113 : if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection) &&
5020 : eTargetTypeFlat == wkbGeometryCollection)
5021 : {
5022 919 : OGRGeometryCollection *poGC = poGeom.release()->toGeometryCollection();
5023 : auto poRet = std::unique_ptr<OGRGeometry>(
5024 1838 : OGRGeometryCollection::CastToGeometryCollection(poGC));
5025 919 : poRet->set3D(OGR_GT_HasZ(eTargetType));
5026 919 : poRet->setMeasured(OGR_GT_HasM(eTargetType));
5027 919 : return poRet;
5028 : }
5029 :
5030 2194 : if (eType == wkbTriangle && eTargetTypeFlat == wkbPolyhedralSurface)
5031 : {
5032 2 : auto poPS = std::make_unique<OGRPolyhedralSurface>();
5033 1 : poPS->assignSpatialReference(poGeom->getSpatialReference());
5034 1 : poPS->addGeometryDirectly(OGRTriangle::CastToPolygon(poGeom.release()));
5035 1 : poPS->set3D(OGR_GT_HasZ(eTargetType));
5036 1 : poPS->setMeasured(OGR_GT_HasM(eTargetType));
5037 1 : return poPS;
5038 : }
5039 2193 : else if (eType == wkbPolygon && eTargetTypeFlat == wkbPolyhedralSurface)
5040 : {
5041 6 : auto poPS = std::make_unique<OGRPolyhedralSurface>();
5042 3 : poPS->assignSpatialReference(poGeom->getSpatialReference());
5043 3 : poPS->addGeometry(std::move(poGeom));
5044 3 : poPS->set3D(OGR_GT_HasZ(eTargetType));
5045 3 : poPS->setMeasured(OGR_GT_HasM(eTargetType));
5046 3 : return poPS;
5047 : }
5048 2190 : else if (eType == wkbMultiPolygon &&
5049 : eTargetTypeFlat == wkbPolyhedralSurface)
5050 : {
5051 2 : const OGRMultiPolygon *poMP = poGeom->toMultiPolygon();
5052 4 : auto poPS = std::make_unique<OGRPolyhedralSurface>();
5053 4 : for (const auto *poPoly : *poMP)
5054 : {
5055 2 : poPS->addGeometry(poPoly);
5056 : }
5057 2 : poPS->set3D(OGR_GT_HasZ(eTargetType));
5058 2 : poPS->setMeasured(OGR_GT_HasM(eTargetType));
5059 2 : return poPS;
5060 : }
5061 2188 : else if (eType == wkbTIN && eTargetTypeFlat == wkbPolyhedralSurface)
5062 : {
5063 1 : poGeom.reset(OGRTriangulatedSurface::CastToPolyhedralSurface(
5064 : poGeom.release()->toTriangulatedSurface()));
5065 : }
5066 2187 : else if (eType == wkbCurvePolygon &&
5067 : eTargetTypeFlat == wkbPolyhedralSurface)
5068 : {
5069 1 : OGRwkbGeometryType eTempGeomType = wkbPolygon;
5070 1 : if (OGR_GT_HasZ(eTargetType))
5071 0 : eTempGeomType = OGR_GT_SetZ(eTempGeomType);
5072 1 : if (OGR_GT_HasM(eTargetType))
5073 0 : eTempGeomType = OGR_GT_SetM(eTempGeomType);
5074 2 : return forceTo(forceTo(std::move(poGeom), eTempGeomType, papszOptions),
5075 1 : eTargetType, papszOptions);
5076 : }
5077 2186 : else if (eType == wkbMultiSurface &&
5078 : eTargetTypeFlat == wkbPolyhedralSurface)
5079 : {
5080 1 : OGRwkbGeometryType eTempGeomType = wkbMultiPolygon;
5081 1 : if (OGR_GT_HasZ(eTargetType))
5082 0 : eTempGeomType = OGR_GT_SetZ(eTempGeomType);
5083 1 : if (OGR_GT_HasM(eTargetType))
5084 0 : eTempGeomType = OGR_GT_SetM(eTempGeomType);
5085 2 : return forceTo(forceTo(std::move(poGeom), eTempGeomType, papszOptions),
5086 1 : eTargetType, papszOptions);
5087 : }
5088 :
5089 2185 : else if (eType == wkbTriangle && eTargetTypeFlat == wkbTIN)
5090 : {
5091 2 : auto poTS = std::make_unique<OGRTriangulatedSurface>();
5092 1 : poTS->assignSpatialReference(poGeom->getSpatialReference());
5093 1 : poTS->addGeometry(std::move(poGeom));
5094 1 : poTS->set3D(OGR_GT_HasZ(eTargetType));
5095 1 : poTS->setMeasured(OGR_GT_HasM(eTargetType));
5096 1 : return poTS;
5097 : }
5098 2184 : else if (eType == wkbPolygon && eTargetTypeFlat == wkbTIN)
5099 : {
5100 4 : const OGRPolygon *poPoly = poGeom->toPolygon();
5101 4 : const OGRLinearRing *poLR = poPoly->getExteriorRing();
5102 7 : if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
5103 3 : poPoly->getNumInteriorRings() == 0))
5104 : {
5105 1 : return poGeom;
5106 : }
5107 3 : OGRErr eErr = OGRERR_NONE;
5108 6 : auto poTriangle = std::make_unique<OGRTriangle>(*poPoly, eErr);
5109 6 : auto poTS = std::make_unique<OGRTriangulatedSurface>();
5110 3 : poTS->assignSpatialReference(poGeom->getSpatialReference());
5111 3 : poTS->addGeometry(std::move(poTriangle));
5112 3 : poTS->set3D(OGR_GT_HasZ(eTargetType));
5113 3 : poTS->setMeasured(OGR_GT_HasM(eTargetType));
5114 3 : return poTS;
5115 : }
5116 2180 : else if (eType == wkbMultiPolygon && eTargetTypeFlat == wkbTIN)
5117 : {
5118 1 : const OGRMultiPolygon *poMP = poGeom->toMultiPolygon();
5119 2 : for (const auto poPoly : *poMP)
5120 : {
5121 1 : const OGRLinearRing *poLR = poPoly->getExteriorRing();
5122 2 : if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
5123 1 : poPoly->getNumInteriorRings() == 0))
5124 : {
5125 0 : return poGeom;
5126 : }
5127 : }
5128 2 : auto poTS = std::make_unique<OGRTriangulatedSurface>();
5129 1 : poTS->assignSpatialReference(poGeom->getSpatialReference());
5130 2 : for (const auto poPoly : *poMP)
5131 : {
5132 1 : OGRErr eErr = OGRERR_NONE;
5133 1 : poTS->addGeometry(std::make_unique<OGRTriangle>(*poPoly, eErr));
5134 : }
5135 1 : poTS->set3D(OGR_GT_HasZ(eTargetType));
5136 1 : poTS->setMeasured(OGR_GT_HasM(eTargetType));
5137 1 : return poTS;
5138 : }
5139 2179 : else if (eType == wkbPolyhedralSurface && eTargetTypeFlat == wkbTIN)
5140 : {
5141 2 : const OGRPolyhedralSurface *poPS = poGeom->toPolyhedralSurface();
5142 3 : for (const auto poPoly : *poPS)
5143 : {
5144 2 : const OGRLinearRing *poLR = poPoly->getExteriorRing();
5145 3 : if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
5146 1 : poPoly->getNumInteriorRings() == 0))
5147 : {
5148 1 : poGeom->set3D(OGR_GT_HasZ(eTargetType));
5149 1 : poGeom->setMeasured(OGR_GT_HasM(eTargetType));
5150 1 : return poGeom;
5151 : }
5152 : }
5153 2 : auto poTS = std::make_unique<OGRTriangulatedSurface>();
5154 1 : poTS->assignSpatialReference(poGeom->getSpatialReference());
5155 2 : for (const auto poPoly : *poPS)
5156 : {
5157 1 : OGRErr eErr = OGRERR_NONE;
5158 1 : poTS->addGeometry(std::make_unique<OGRTriangle>(*poPoly, eErr));
5159 : }
5160 1 : poTS->set3D(OGR_GT_HasZ(eTargetType));
5161 1 : poTS->setMeasured(OGR_GT_HasM(eTargetType));
5162 1 : return poTS;
5163 : }
5164 :
5165 2177 : else if (eType == wkbPolygon && eTargetTypeFlat == wkbTriangle)
5166 : {
5167 7 : const OGRPolygon *poPoly = poGeom->toPolygon();
5168 7 : const OGRLinearRing *poLR = poPoly->getExteriorRing();
5169 13 : if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
5170 6 : poPoly->getNumInteriorRings() == 0))
5171 : {
5172 1 : poGeom->set3D(OGR_GT_HasZ(eTargetType));
5173 1 : poGeom->setMeasured(OGR_GT_HasM(eTargetType));
5174 1 : return poGeom;
5175 : }
5176 6 : OGRErr eErr = OGRERR_NONE;
5177 12 : auto poTriangle = std::make_unique<OGRTriangle>(*poPoly, eErr);
5178 6 : poTriangle->set3D(OGR_GT_HasZ(eTargetType));
5179 6 : poTriangle->setMeasured(OGR_GT_HasM(eTargetType));
5180 6 : return poTriangle;
5181 : }
5182 :
5183 2171 : if (eTargetTypeFlat == wkbTriangle || eTargetTypeFlat == wkbTIN ||
5184 : eTargetTypeFlat == wkbPolyhedralSurface)
5185 : {
5186 9 : OGRwkbGeometryType eTempGeomType = wkbPolygon;
5187 9 : if (OGR_GT_HasZ(eTargetType))
5188 0 : eTempGeomType = OGR_GT_SetZ(eTempGeomType);
5189 9 : if (OGR_GT_HasM(eTargetType))
5190 1 : eTempGeomType = OGR_GT_SetM(eTempGeomType);
5191 9 : auto poGeomPtr = poGeom.get();
5192 18 : auto poPoly = forceTo(std::move(poGeom), eTempGeomType, papszOptions);
5193 9 : if (poPoly.get() == poGeomPtr)
5194 0 : return poPoly;
5195 9 : return forceTo(std::move(poPoly), eTargetType, papszOptions);
5196 : }
5197 :
5198 2162 : if (eType == wkbTriangle && eTargetTypeFlat == wkbGeometryCollection)
5199 : {
5200 2 : auto poGC = std::make_unique<OGRGeometryCollection>();
5201 1 : poGC->assignSpatialReference(poGeom->getSpatialReference());
5202 1 : poGC->addGeometry(std::move(poGeom));
5203 1 : poGC->set3D(OGR_GT_HasZ(eTargetType));
5204 1 : poGC->setMeasured(OGR_GT_HasM(eTargetType));
5205 1 : return poGC;
5206 : }
5207 :
5208 : // Promote single to multi.
5209 4023 : if (!OGR_GT_IsSubClassOf(eType, wkbGeometryCollection) &&
5210 1862 : OGR_GT_IsSubClassOf(OGR_GT_GetCollection(eType), eTargetType))
5211 : {
5212 1108 : auto poRet = std::unique_ptr<OGRGeometry>(createGeometry(eTargetType));
5213 554 : if (poRet == nullptr)
5214 : {
5215 0 : return nullptr;
5216 : }
5217 554 : poRet->assignSpatialReference(poGeom->getSpatialReference());
5218 554 : if (eType == wkbLineString)
5219 65 : poGeom.reset(
5220 65 : OGRCurve::CastToLineString(poGeom.release()->toCurve()));
5221 554 : poRet->toGeometryCollection()->addGeometry(std::move(poGeom));
5222 554 : poRet->set3D(OGR_GT_HasZ(eTargetType));
5223 554 : poRet->setMeasured(OGR_GT_HasM(eTargetType));
5224 554 : return poRet;
5225 : }
5226 :
5227 1607 : const bool bIsCurve = CPL_TO_BOOL(OGR_GT_IsCurve(eType));
5228 1607 : if (bIsCurve && eTargetTypeFlat == wkbCompoundCurve)
5229 : {
5230 : auto poRet = std::unique_ptr<OGRGeometry>(
5231 64 : OGRCurve::CastToCompoundCurve(poGeom.release()->toCurve()));
5232 32 : if (poRet)
5233 : {
5234 30 : poRet->set3D(OGR_GT_HasZ(eTargetType));
5235 30 : poRet->setMeasured(OGR_GT_HasM(eTargetType));
5236 : }
5237 32 : return poRet;
5238 : }
5239 1575 : else if (bIsCurve && eTargetTypeFlat == wkbCurvePolygon)
5240 : {
5241 26 : const OGRCurve *poCurve = poGeom->toCurve();
5242 26 : if (poCurve->getNumPoints() >= 3 && poCurve->get_IsClosed())
5243 : {
5244 18 : auto poCP = std::make_unique<OGRCurvePolygon>();
5245 18 : if (poCP->addRing(std::move(poCurve)) == OGRERR_NONE)
5246 : {
5247 18 : poCP->assignSpatialReference(poGeom->getSpatialReference());
5248 18 : poCP->set3D(OGR_GT_HasZ(eTargetType));
5249 18 : poCP->setMeasured(OGR_GT_HasM(eTargetType));
5250 18 : return poCP;
5251 : }
5252 8 : }
5253 : }
5254 1626 : else if (eType == wkbLineString &&
5255 77 : OGR_GT_IsSubClassOf(eTargetType, wkbMultiSurface))
5256 : {
5257 23 : auto poTmp = forceTo(std::move(poGeom), wkbPolygon, papszOptions);
5258 23 : if (wkbFlatten(poTmp->getGeometryType()) != eType)
5259 15 : return forceTo(std::move(poTmp), eTargetType, papszOptions);
5260 8 : poGeom = std::move(poTmp);
5261 : }
5262 1526 : else if (bIsCurve && eTargetTypeFlat == wkbMultiSurface)
5263 : {
5264 10 : auto poTmp = forceTo(std::move(poGeom), wkbCurvePolygon, papszOptions);
5265 10 : if (wkbFlatten(poTmp->getGeometryType()) != eType)
5266 8 : return forceTo(std::move(poTmp), eTargetType, papszOptions);
5267 4 : poGeom = std::move(poTmp);
5268 : }
5269 1516 : else if (bIsCurve && eTargetTypeFlat == wkbMultiPolygon)
5270 : {
5271 13 : auto poTmp = forceTo(std::move(poGeom), wkbPolygon, papszOptions);
5272 13 : if (wkbFlatten(poTmp->getGeometryType()) != eType)
5273 11 : return forceTo(std::move(poTmp), eTargetType, papszOptions);
5274 4 : poGeom = std::move(poTmp);
5275 : }
5276 1503 : else if (eType == wkbTriangle && eTargetTypeFlat == wkbCurvePolygon)
5277 : {
5278 : auto poRet =
5279 1 : std::unique_ptr<OGRGeometry>(OGRSurface::CastToCurvePolygon(
5280 2 : OGRTriangle::CastToPolygon(poGeom.release())->toSurface()));
5281 1 : poRet->set3D(OGR_GT_HasZ(eTargetType));
5282 1 : poRet->setMeasured(OGR_GT_HasM(eTargetType));
5283 1 : return poRet;
5284 : }
5285 1502 : else if (eType == wkbPolygon && eTargetTypeFlat == wkbCurvePolygon)
5286 : {
5287 : auto poRet = std::unique_ptr<OGRGeometry>(
5288 38 : OGRSurface::CastToCurvePolygon(poGeom.release()->toPolygon()));
5289 19 : poRet->set3D(OGR_GT_HasZ(eTargetType));
5290 19 : poRet->setMeasured(OGR_GT_HasM(eTargetType));
5291 19 : return poRet;
5292 : }
5293 1483 : else if (OGR_GT_IsSubClassOf(eType, wkbCurvePolygon) &&
5294 : eTargetTypeFlat == wkbCompoundCurve)
5295 : {
5296 15 : OGRCurvePolygon *poPoly = poGeom->toCurvePolygon();
5297 15 : if (poPoly->getNumInteriorRings() == 0)
5298 : {
5299 : auto poRet =
5300 14 : std::unique_ptr<OGRGeometry>(poPoly->stealExteriorRingCurve());
5301 14 : if (poRet)
5302 14 : poRet->assignSpatialReference(poGeom->getSpatialReference());
5303 14 : return forceTo(std::move(poRet), eTargetType, papszOptions);
5304 : }
5305 : }
5306 1468 : else if (eType == wkbMultiPolygon && eTargetTypeFlat == wkbMultiSurface)
5307 : {
5308 : auto poRet =
5309 14 : std::unique_ptr<OGRGeometry>(OGRMultiPolygon::CastToMultiSurface(
5310 28 : poGeom.release()->toMultiPolygon()));
5311 14 : poRet->set3D(OGR_GT_HasZ(eTargetType));
5312 14 : poRet->setMeasured(OGR_GT_HasM(eTargetType));
5313 14 : return poRet;
5314 : }
5315 1454 : else if (eType == wkbMultiLineString && eTargetTypeFlat == wkbMultiCurve)
5316 : {
5317 : auto poRet =
5318 9 : std::unique_ptr<OGRGeometry>(OGRMultiLineString::CastToMultiCurve(
5319 18 : poGeom.release()->toMultiLineString()));
5320 9 : poRet->set3D(OGR_GT_HasZ(eTargetType));
5321 9 : poRet->setMeasured(OGR_GT_HasM(eTargetType));
5322 9 : return poRet;
5323 : }
5324 1445 : else if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection))
5325 : {
5326 276 : const OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
5327 276 : if (poGC->getNumGeometries() == 1)
5328 : {
5329 170 : const OGRGeometry *poSubGeom = poGC->getGeometryRef(0);
5330 170 : if (poSubGeom)
5331 : {
5332 : auto poSubGeomClone =
5333 170 : std::unique_ptr<OGRGeometry>(poSubGeom->clone());
5334 340 : poSubGeomClone->assignSpatialReference(
5335 170 : poGeom->getSpatialReference());
5336 170 : auto poRet = forceTo(std::move(poSubGeomClone), eTargetType,
5337 170 : papszOptions);
5338 340 : if (poRet &&
5339 170 : OGR_GT_IsSubClassOf(wkbFlatten(poRet->getGeometryType()),
5340 170 : eTargetType))
5341 : {
5342 135 : return poRet;
5343 : }
5344 : }
5345 : }
5346 : }
5347 1288 : else if (OGR_GT_IsSubClassOf(eType, wkbCurvePolygon) &&
5348 119 : (OGR_GT_IsSubClassOf(eTargetType, wkbMultiSurface) ||
5349 107 : OGR_GT_IsSubClassOf(eTargetType, wkbMultiCurve)))
5350 : {
5351 43 : const OGRCurvePolygon *poCP = poGeom->toCurvePolygon();
5352 43 : if (poCP->getNumInteriorRings() == 0)
5353 : {
5354 41 : const OGRCurve *poRing = poCP->getExteriorRingCurve();
5355 41 : auto poRingClone = std::unique_ptr<OGRGeometry>(poRing->clone());
5356 41 : poRingClone->assignSpatialReference(poGeom->getSpatialReference());
5357 41 : const OGRwkbGeometryType eRingType = poRing->getGeometryType();
5358 : auto poRet =
5359 41 : forceTo(std::move(poRingClone), eTargetType, papszOptions);
5360 57 : if (poRet->getGeometryType() != eRingType &&
5361 16 : !(eTypeFlat == wkbPolygon &&
5362 : eTargetTypeFlat == wkbMultiLineString))
5363 : {
5364 29 : return poRet;
5365 : }
5366 : }
5367 : }
5368 :
5369 1302 : if (eTargetTypeFlat == wkbLineString)
5370 : {
5371 : auto poNewGeom =
5372 198 : std::unique_ptr<OGRGeometry>(forceToLineString(poGeom.release()));
5373 99 : poNewGeom->set3D(OGR_GT_HasZ(eTargetType));
5374 99 : poNewGeom->setMeasured(OGR_GT_HasM(eTargetType));
5375 99 : poGeom = std::move(poNewGeom);
5376 : }
5377 1203 : else if (eTargetTypeFlat == wkbPolygon)
5378 : {
5379 : auto poNewGeom =
5380 208 : std::unique_ptr<OGRGeometry>(forceToPolygon(poGeom.release()));
5381 104 : if (poNewGeom)
5382 : {
5383 104 : poNewGeom->set3D(OGR_GT_HasZ(eTargetType));
5384 104 : poNewGeom->setMeasured(OGR_GT_HasM(eTargetType));
5385 : }
5386 104 : poGeom = std::move(poNewGeom);
5387 : }
5388 1099 : else if (eTargetTypeFlat == wkbMultiPolygon)
5389 : {
5390 : auto poNewGeom =
5391 1830 : std::unique_ptr<OGRGeometry>(forceToMultiPolygon(poGeom.release()));
5392 915 : if (poNewGeom)
5393 : {
5394 915 : poNewGeom->set3D(OGR_GT_HasZ(eTargetType));
5395 915 : poNewGeom->setMeasured(OGR_GT_HasM(eTargetType));
5396 : }
5397 915 : poGeom = std::move(poNewGeom);
5398 : }
5399 184 : else if (eTargetTypeFlat == wkbMultiLineString)
5400 : {
5401 : auto poNewGeom = std::unique_ptr<OGRGeometry>(
5402 82 : forceToMultiLineString(poGeom.release()));
5403 41 : poNewGeom->set3D(OGR_GT_HasZ(eTargetType));
5404 41 : poNewGeom->setMeasured(OGR_GT_HasM(eTargetType));
5405 41 : poGeom = std::move(poNewGeom);
5406 : }
5407 143 : else if (eTargetTypeFlat == wkbMultiPoint)
5408 : {
5409 : auto poNewGeom =
5410 44 : std::unique_ptr<OGRGeometry>(forceToMultiPoint(poGeom.release()));
5411 22 : poNewGeom->set3D(OGR_GT_HasZ(eTargetType));
5412 22 : poNewGeom->setMeasured(OGR_GT_HasM(eTargetType));
5413 22 : poGeom = std::move(poNewGeom);
5414 : }
5415 :
5416 1302 : return poGeom;
5417 : }
5418 :
5419 : /************************************************************************/
5420 : /* OGR_G_ForceTo() */
5421 : /************************************************************************/
5422 :
5423 : /**
5424 : * \brief Convert to another geometry type
5425 : *
5426 : * This function is the same as the C++ method OGRGeometryFactory::forceTo().
5427 : *
5428 : * @param hGeom the input geometry - ownership is passed to the method.
5429 : * @param eTargetType target output geometry type.
5430 : * @param papszOptions options as a null-terminated list of strings or NULL.
5431 : * @return new geometry.
5432 : *
5433 : */
5434 :
5435 848 : OGRGeometryH OGR_G_ForceTo(OGRGeometryH hGeom, OGRwkbGeometryType eTargetType,
5436 : CSLConstList papszOptions)
5437 :
5438 : {
5439 848 : return OGRGeometry::ToHandle(
5440 1696 : OGRGeometryFactory::forceTo(
5441 1696 : std::unique_ptr<OGRGeometry>(OGRGeometry::FromHandle(hGeom)),
5442 : eTargetType, papszOptions)
5443 1696 : .release());
5444 : }
5445 :
5446 : /************************************************************************/
5447 : /* makeCompatibleWith() */
5448 : /************************************************************************/
5449 :
5450 : /**
5451 : * \brief Adjust a geometry to be compatible with a specified geometry type.
5452 : *
5453 : * This is a soft version of forceTo() that:
5454 : * - converts single geometry type to a multi-geometry type if eTargetType is
5455 : * a multi-geometry type (e.g. wkbMultiPolygon) and the single geometry type
5456 : * is compatible with it (e.g. wkbPolygon)
5457 : * - insert components of multi-geometries that are not wkbGeometryCollection
5458 : * into a GeometryCollection, when eTargetType == wkbGeometryCollection
5459 : * - insert single geometries into a GeometryCollection, when
5460 : * eTargetType == wkbGeometryCollection.
5461 : * - convert a single-part multi-geometry to the specified target single
5462 : * geometry type. e.g a MultiPolygon to a Polygon
5463 : * - in other cases, the geometry is returned unmodified.
5464 : *
5465 : * @param poGeom the input geometry - ownership is passed to the method.
5466 : * @param eTargetType target output geometry type.
5467 : * Typically a layer geometry type.
5468 : * @return a geometry (potentially poGeom itself)
5469 : *
5470 : * @since GDAL 3.12
5471 : */
5472 :
5473 : std::unique_ptr<OGRGeometry>
5474 39 : OGRGeometryFactory::makeCompatibleWith(std::unique_ptr<OGRGeometry> poGeom,
5475 : OGRwkbGeometryType eTargetType)
5476 : {
5477 39 : const auto eGeomType = poGeom->getGeometryType();
5478 39 : const auto eFlattenTargetType = wkbFlatten(eTargetType);
5479 78 : if (eFlattenTargetType != wkbUnknown &&
5480 39 : eFlattenTargetType != wkbFlatten(eGeomType))
5481 : {
5482 12 : if (OGR_GT_GetCollection(eGeomType) == eFlattenTargetType)
5483 : {
5484 : poGeom =
5485 4 : OGRGeometryFactory::forceTo(std::move(poGeom), eTargetType);
5486 : }
5487 8 : else if (eGeomType == OGR_GT_GetCollection(eTargetType) &&
5488 0 : poGeom->toGeometryCollection()->getNumGeometries() == 1)
5489 : {
5490 0 : poGeom = poGeom->toGeometryCollection()->stealGeometry(0);
5491 : }
5492 8 : else if (eFlattenTargetType == wkbGeometryCollection)
5493 : {
5494 4 : auto poGeomColl = std::make_unique<OGRGeometryCollection>();
5495 2 : if (OGR_GT_IsSubClassOf(eGeomType, wkbGeometryCollection))
5496 : {
5497 3 : for (const auto *poSubGeom : *(poGeom->toGeometryCollection()))
5498 : {
5499 2 : poGeomColl->addGeometry(poSubGeom);
5500 : }
5501 : }
5502 : else
5503 : {
5504 1 : poGeomColl->addGeometry(std::move(poGeom));
5505 : }
5506 2 : poGeom = std::move(poGeomColl);
5507 : }
5508 : }
5509 39 : return poGeom;
5510 : }
5511 :
5512 : /************************************************************************/
5513 : /* GetCurveParameters() */
5514 : /************************************************************************/
5515 :
5516 : /**
5517 : * \brief Returns the parameter of an arc circle.
5518 : *
5519 : * Angles are return in radians, with trigonometic convention (counter clock
5520 : * wise)
5521 : *
5522 : * @param x0 x of first point
5523 : * @param y0 y of first point
5524 : * @param x1 x of intermediate point
5525 : * @param y1 y of intermediate point
5526 : * @param x2 x of final point
5527 : * @param y2 y of final point
5528 : * @param R radius (output)
5529 : * @param cx x of arc center (output)
5530 : * @param cy y of arc center (output)
5531 : * @param alpha0 angle between center and first point, in radians (output)
5532 : * @param alpha1 angle between center and intermediate point, in radians
5533 : * (output)
5534 : * @param alpha2 angle between center and final point, in radians (output)
5535 : * @return TRUE if the points are not aligned and define an arc circle.
5536 : *
5537 : */
5538 :
5539 186712 : int OGRGeometryFactory::GetCurveParameters(double x0, double y0, double x1,
5540 : double y1, double x2, double y2,
5541 : double &R, double &cx, double &cy,
5542 : double &alpha0, double &alpha1,
5543 : double &alpha2)
5544 : {
5545 560136 : if (std::isnan(x0) || std::isnan(y0) || std::isnan(x1) || std::isnan(y1) ||
5546 560136 : std::isnan(x2) || std::isnan(y2))
5547 : {
5548 0 : return FALSE;
5549 : }
5550 :
5551 : // Circle.
5552 186712 : if (x0 == x2 && y0 == y2)
5553 : {
5554 149 : if (x0 != x1 || y0 != y1)
5555 : {
5556 148 : cx = (x0 + x1) / 2;
5557 148 : cy = (y0 + y1) / 2;
5558 148 : R = DISTANCE(cx, cy, x0, y0);
5559 : // Arbitrarily pick counter-clock-wise order (like PostGIS does).
5560 148 : alpha0 = atan2(y0 - cy, x0 - cx);
5561 148 : alpha1 = alpha0 + M_PI;
5562 148 : alpha2 = alpha0 + 2 * M_PI;
5563 148 : return TRUE;
5564 : }
5565 : else
5566 : {
5567 1 : return FALSE;
5568 : }
5569 : }
5570 :
5571 186563 : double dx01 = x1 - x0;
5572 186563 : double dy01 = y1 - y0;
5573 186563 : double dx12 = x2 - x1;
5574 186563 : double dy12 = y2 - y1;
5575 :
5576 : // Normalize above values so as to make sure we don't end up with
5577 : // computing a difference of too big values.
5578 186563 : double dfScale = fabs(dx01);
5579 186563 : if (fabs(dy01) > dfScale)
5580 92319 : dfScale = fabs(dy01);
5581 186563 : if (fabs(dx12) > dfScale)
5582 46950 : dfScale = fabs(dx12);
5583 186563 : if (fabs(dy12) > dfScale)
5584 46226 : dfScale = fabs(dy12);
5585 186563 : const double dfInvScale = 1.0 / dfScale;
5586 186563 : dx01 *= dfInvScale;
5587 186563 : dy01 *= dfInvScale;
5588 186563 : dx12 *= dfInvScale;
5589 186563 : dy12 *= dfInvScale;
5590 :
5591 186563 : const double det = dx01 * dy12 - dx12 * dy01;
5592 186563 : if (fabs(det) < 1.0e-8 || std::isnan(det))
5593 : {
5594 130 : return FALSE;
5595 : }
5596 186433 : const double x01_mid = (x0 + x1) * dfInvScale;
5597 186433 : const double x12_mid = (x1 + x2) * dfInvScale;
5598 186433 : const double y01_mid = (y0 + y1) * dfInvScale;
5599 186433 : const double y12_mid = (y1 + y2) * dfInvScale;
5600 186433 : const double c01 = dx01 * x01_mid + dy01 * y01_mid;
5601 186433 : const double c12 = dx12 * x12_mid + dy12 * y12_mid;
5602 186433 : cx = 0.5 * dfScale * (c01 * dy12 - c12 * dy01) / det;
5603 186433 : cy = 0.5 * dfScale * (-c01 * dx12 + c12 * dx01) / det;
5604 :
5605 186433 : alpha0 = atan2((y0 - cy) * dfInvScale, (x0 - cx) * dfInvScale);
5606 186433 : alpha1 = atan2((y1 - cy) * dfInvScale, (x1 - cx) * dfInvScale);
5607 186433 : alpha2 = atan2((y2 - cy) * dfInvScale, (x2 - cx) * dfInvScale);
5608 186433 : R = DISTANCE(cx, cy, x0, y0);
5609 :
5610 : // If det is negative, the orientation if clockwise.
5611 186433 : if (det < 0)
5612 : {
5613 94075 : if (alpha1 > alpha0)
5614 1284 : alpha1 -= 2 * M_PI;
5615 94075 : if (alpha2 > alpha1)
5616 3324 : alpha2 -= 2 * M_PI;
5617 : }
5618 : else
5619 : {
5620 92358 : if (alpha1 < alpha0)
5621 1285 : alpha1 += 2 * M_PI;
5622 92358 : if (alpha2 < alpha1)
5623 3156 : alpha2 += 2 * M_PI;
5624 : }
5625 :
5626 186433 : CPLAssert((alpha0 <= alpha1 && alpha1 <= alpha2) ||
5627 : (alpha0 >= alpha1 && alpha1 >= alpha2));
5628 :
5629 186433 : return TRUE;
5630 : }
5631 :
5632 : /************************************************************************/
5633 : /* OGRGeometryFactoryStrokeArc() */
5634 : /************************************************************************/
5635 :
5636 4371 : static void OGRGeometryFactoryStrokeArc(OGRLineString *poLine, double cx,
5637 : double cy, double R, double z0,
5638 : double z1, int bHasZ, double alpha0,
5639 : double alpha1, double dfStep,
5640 : int bStealthConstraints)
5641 : {
5642 4371 : const int nSign = dfStep > 0 ? 1 : -1;
5643 :
5644 : // Constant angle between all points, so as to not depend on winding order.
5645 4371 : const double dfNumSteps = fabs((alpha1 - alpha0) / dfStep) + 0.5;
5646 4371 : if (dfNumSteps >= std::numeric_limits<int>::max() ||
5647 4371 : dfNumSteps <= std::numeric_limits<int>::min() || std::isnan(dfNumSteps))
5648 : {
5649 0 : CPLError(CE_Warning, CPLE_AppDefined,
5650 : "OGRGeometryFactoryStrokeArc: bogus steps: "
5651 : "%lf %lf %lf %lf",
5652 : alpha0, alpha1, dfStep, dfNumSteps);
5653 0 : return;
5654 : }
5655 :
5656 4371 : int nSteps = static_cast<int>(dfNumSteps);
5657 4371 : if (bStealthConstraints)
5658 : {
5659 : // We need at least 6 intermediate vertex, and if more additional
5660 : // multiples of 2.
5661 4175 : if (nSteps < 1 + 6)
5662 99 : nSteps = 1 + 6;
5663 : else
5664 4076 : nSteps = 1 + 6 + 2 * ((nSteps - (1 + 6) + (2 - 1)) / 2);
5665 : }
5666 196 : else if (nSteps < 4)
5667 : {
5668 192 : nSteps = 4;
5669 : }
5670 4371 : dfStep = nSign * fabs((alpha1 - alpha0) / nSteps);
5671 4371 : double alpha = alpha0 + dfStep;
5672 :
5673 232143 : for (; (alpha - alpha1) * nSign < -1e-8; alpha += dfStep)
5674 : {
5675 227772 : const double dfX = cx + R * cos(alpha);
5676 227772 : const double dfY = cy + R * sin(alpha);
5677 227772 : if (bHasZ)
5678 : {
5679 9896 : const double z =
5680 9896 : z0 + (z1 - z0) * (alpha - alpha0) / (alpha1 - alpha0);
5681 9896 : poLine->addPoint(dfX, dfY, z);
5682 : }
5683 : else
5684 : {
5685 217876 : poLine->addPoint(dfX, dfY);
5686 : }
5687 : }
5688 : }
5689 :
5690 : /************************************************************************/
5691 : /* OGRGF_SetHiddenValue() */
5692 : /************************************************************************/
5693 :
5694 : // TODO(schwehr): Cleanup these static constants.
5695 : constexpr int HIDDEN_ALPHA_WIDTH = 32;
5696 : constexpr GUInt32 HIDDEN_ALPHA_SCALE =
5697 : static_cast<GUInt32>((static_cast<GUIntBig>(1) << HIDDEN_ALPHA_WIDTH) - 2);
5698 : constexpr int HIDDEN_ALPHA_HALF_WIDTH = (HIDDEN_ALPHA_WIDTH / 2);
5699 : constexpr int HIDDEN_ALPHA_HALF_MASK = (1 << HIDDEN_ALPHA_HALF_WIDTH) - 1;
5700 :
5701 : // Encode 16-bit nValue in the 8-lsb of dfX and dfY.
5702 :
5703 : #ifdef CPL_LSB
5704 : constexpr int DOUBLE_LSB_OFFSET = 0;
5705 : #else
5706 : constexpr int DOUBLE_LSB_OFFSET = 7;
5707 : #endif
5708 :
5709 227638 : static void OGRGF_SetHiddenValue(GUInt16 nValue, double &dfX, double &dfY)
5710 : {
5711 227638 : GByte abyData[8] = {};
5712 :
5713 227638 : memcpy(abyData, &dfX, sizeof(double));
5714 227638 : abyData[DOUBLE_LSB_OFFSET] = static_cast<GByte>(nValue & 0xFF);
5715 227638 : memcpy(&dfX, abyData, sizeof(double));
5716 :
5717 227638 : memcpy(abyData, &dfY, sizeof(double));
5718 227638 : abyData[DOUBLE_LSB_OFFSET] = static_cast<GByte>(nValue >> 8);
5719 227638 : memcpy(&dfY, abyData, sizeof(double));
5720 227638 : }
5721 :
5722 : /************************************************************************/
5723 : /* OGRGF_GetHiddenValue() */
5724 : /************************************************************************/
5725 :
5726 : // Decode 16-bit nValue from the 8-lsb of dfX and dfY.
5727 181520 : static GUInt16 OGRGF_GetHiddenValue(double dfX, double dfY)
5728 : {
5729 181520 : GByte abyData[8] = {};
5730 181520 : memcpy(abyData, &dfX, sizeof(double));
5731 181520 : GUInt16 nValue = abyData[DOUBLE_LSB_OFFSET];
5732 181520 : memcpy(abyData, &dfY, sizeof(double));
5733 181520 : nValue |= (abyData[DOUBLE_LSB_OFFSET] << 8);
5734 :
5735 181520 : return nValue;
5736 : }
5737 :
5738 : /************************************************************************/
5739 : /* OGRGF_NeedSwithArcOrder() */
5740 : /************************************************************************/
5741 :
5742 : // We need to define a full ordering between starting point and ending point
5743 : // whatever it is.
5744 9559 : static bool OGRGF_NeedSwithArcOrder(double x0, double y0, double x2, double y2)
5745 : {
5746 9559 : return x0 < x2 || (x0 == x2 && y0 < y2);
5747 : }
5748 :
5749 : /************************************************************************/
5750 : /* curveToLineString() */
5751 : /************************************************************************/
5752 :
5753 : /* clang-format off */
5754 : /**
5755 : * \brief Converts an arc circle into an approximate line string
5756 : *
5757 : * The arc circle is defined by a first point, an intermediate point and a
5758 : * final point.
5759 : *
5760 : * The provided dfMaxAngleStepSizeDegrees is a hint. The discretization
5761 : * algorithm may pick a slightly different value.
5762 : *
5763 : * So as to avoid gaps when rendering curve polygons that share common arcs,
5764 : * this method is guaranteed to return a line with reversed vertex if called
5765 : * with inverted first and final point, and identical intermediate point.
5766 : *
5767 : * @param x0 x of first point
5768 : * @param y0 y of first point
5769 : * @param z0 z of first point
5770 : * @param x1 x of intermediate point
5771 : * @param y1 y of intermediate point
5772 : * @param z1 z of intermediate point
5773 : * @param x2 x of final point
5774 : * @param y2 y of final point
5775 : * @param z2 z of final point
5776 : * @param bHasZ TRUE if z must be taken into account
5777 : * @param dfMaxAngleStepSizeDegrees the largest step in degrees along the
5778 : * arc, zero to use the default setting.
5779 : * @param papszOptions options as a null-terminated list of strings or NULL.
5780 : * Recognized options:
5781 : * <ul>
5782 : * <li>ADD_INTERMEDIATE_POINT=STEALTH/YES/NO (Default to STEALTH).
5783 : * Determine if and how the intermediate point must be output in the
5784 : * linestring. If set to STEALTH, no explicit intermediate point is
5785 : * added but its properties are encoded in low significant bits of
5786 : * intermediate points and OGRGeometryFactory::curveFromLineString() can
5787 : * decode them. This is the best compromise for round-tripping in OGR
5788 : * and better results with PostGIS
5789 : * <a href="http://postgis.org/docs/ST_LineToCurve.html">ST_LineToCurve()</a>.
5790 : * If set to YES, the intermediate point is explicitly added to the
5791 : * linestring. If set to NO, the intermediate point is not explicitly
5792 : * added.
5793 : * </li>
5794 : * </ul>
5795 : *
5796 : * @return the converted geometry (ownership to caller).
5797 : *
5798 : */
5799 : /* clang-format on */
5800 :
5801 6483 : OGRLineString *OGRGeometryFactory::curveToLineString(
5802 : double x0, double y0, double z0, double x1, double y1, double z1, double x2,
5803 : double y2, double z2, int bHasZ, double dfMaxAngleStepSizeDegrees,
5804 : const char *const *papszOptions)
5805 : {
5806 : // So as to make sure the same curve followed in both direction results
5807 : // in perfectly(=binary identical) symmetrical points.
5808 6483 : if (OGRGF_NeedSwithArcOrder(x0, y0, x2, y2))
5809 : {
5810 : OGRLineString *poLS =
5811 2209 : curveToLineString(x2, y2, z2, x1, y1, z1, x0, y0, z0, bHasZ,
5812 : dfMaxAngleStepSizeDegrees, papszOptions);
5813 2209 : poLS->reversePoints();
5814 2209 : return poLS;
5815 : }
5816 :
5817 4274 : double R = 0.0;
5818 4274 : double cx = 0.0;
5819 4274 : double cy = 0.0;
5820 4274 : double alpha0 = 0.0;
5821 4274 : double alpha1 = 0.0;
5822 4274 : double alpha2 = 0.0;
5823 :
5824 4274 : OGRLineString *poLine = new OGRLineString();
5825 4274 : bool bIsArc = true;
5826 4274 : if (!GetCurveParameters(x0, y0, x1, y1, x2, y2, R, cx, cy, alpha0, alpha1,
5827 : alpha2))
5828 : {
5829 96 : bIsArc = false;
5830 96 : cx = 0.0;
5831 96 : cy = 0.0;
5832 96 : R = 0.0;
5833 96 : alpha0 = 0.0;
5834 96 : alpha1 = 0.0;
5835 96 : alpha2 = 0.0;
5836 : }
5837 :
5838 4274 : const int nSign = alpha1 >= alpha0 ? 1 : -1;
5839 :
5840 : // support default arc step setting.
5841 4274 : if (dfMaxAngleStepSizeDegrees < 1e-6)
5842 : {
5843 4255 : dfMaxAngleStepSizeDegrees = OGRGeometryFactory::GetDefaultArcStepSize();
5844 : }
5845 :
5846 4274 : double dfStep = dfMaxAngleStepSizeDegrees / 180 * M_PI;
5847 4274 : if (dfStep <= 0.01 / 180 * M_PI)
5848 : {
5849 0 : CPLDebug("OGR", "Too small arc step size: limiting to 0.01 degree.");
5850 0 : dfStep = 0.01 / 180 * M_PI;
5851 : }
5852 :
5853 4274 : dfStep *= nSign;
5854 :
5855 4274 : if (bHasZ)
5856 272 : poLine->addPoint(x0, y0, z0);
5857 : else
5858 4002 : poLine->addPoint(x0, y0);
5859 :
5860 4274 : bool bAddIntermediatePoint = false;
5861 4274 : bool bStealth = true;
5862 4280 : for (const char *const *papszIter = papszOptions; papszIter && *papszIter;
5863 : papszIter++)
5864 : {
5865 6 : char *pszKey = nullptr;
5866 6 : const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
5867 6 : if (pszKey != nullptr && EQUAL(pszKey, "ADD_INTERMEDIATE_POINT"))
5868 : {
5869 4 : if (EQUAL(pszValue, "YES") || EQUAL(pszValue, "TRUE") ||
5870 3 : EQUAL(pszValue, "ON"))
5871 : {
5872 1 : bAddIntermediatePoint = true;
5873 1 : bStealth = false;
5874 : }
5875 3 : else if (EQUAL(pszValue, "NO") || EQUAL(pszValue, "FALSE") ||
5876 1 : EQUAL(pszValue, "OFF"))
5877 : {
5878 2 : bAddIntermediatePoint = false;
5879 2 : bStealth = false;
5880 : }
5881 : else if (EQUAL(pszValue, "STEALTH"))
5882 : {
5883 : // default.
5884 : }
5885 : }
5886 : else
5887 : {
5888 2 : CPLError(CE_Warning, CPLE_NotSupported, "Unsupported option: %s",
5889 : *papszIter);
5890 : }
5891 6 : CPLFree(pszKey);
5892 : }
5893 :
5894 4274 : if (!bIsArc || bAddIntermediatePoint)
5895 : {
5896 97 : OGRGeometryFactoryStrokeArc(poLine, cx, cy, R, z0, z1, bHasZ, alpha0,
5897 : alpha1, dfStep, FALSE);
5898 :
5899 97 : if (bHasZ)
5900 25 : poLine->addPoint(x1, y1, z1);
5901 : else
5902 72 : poLine->addPoint(x1, y1);
5903 :
5904 97 : OGRGeometryFactoryStrokeArc(poLine, cx, cy, R, z1, z2, bHasZ, alpha1,
5905 : alpha2, dfStep, FALSE);
5906 : }
5907 : else
5908 : {
5909 4177 : OGRGeometryFactoryStrokeArc(poLine, cx, cy, R, z0, z2, bHasZ, alpha0,
5910 : alpha2, dfStep, bStealth);
5911 :
5912 4177 : if (bStealth && poLine->getNumPoints() > 6)
5913 : {
5914 : // 'Hide' the angle of the intermediate point in the 8
5915 : // low-significant bits of the x, y of the first 2 computed points
5916 : // (so 32 bits), then put 0xFF, and on the last couple points put
5917 : // again the angle but in reverse order, so that overall the
5918 : // low-significant bits of all the points are symmetrical w.r.t the
5919 : // mid-point.
5920 4175 : const double dfRatio = (alpha1 - alpha0) / (alpha2 - alpha0);
5921 4175 : double dfAlphaRatio = 0.5 + HIDDEN_ALPHA_SCALE * dfRatio;
5922 4175 : if (dfAlphaRatio < 0.0)
5923 : {
5924 0 : CPLError(CE_Warning, CPLE_AppDefined, "AlphaRation < 0: %lf",
5925 : dfAlphaRatio);
5926 0 : dfAlphaRatio *= -1;
5927 : }
5928 8350 : else if (dfAlphaRatio >= std::numeric_limits<GUInt32>::max() ||
5929 4175 : std::isnan(dfAlphaRatio))
5930 : {
5931 0 : CPLError(CE_Warning, CPLE_AppDefined,
5932 : "AlphaRatio too large: %lf", dfAlphaRatio);
5933 0 : dfAlphaRatio = std::numeric_limits<GUInt32>::max();
5934 : }
5935 4175 : const GUInt32 nAlphaRatio = static_cast<GUInt32>(dfAlphaRatio);
5936 4175 : const GUInt16 nAlphaRatioLow = nAlphaRatio & HIDDEN_ALPHA_HALF_MASK;
5937 4175 : const GUInt16 nAlphaRatioHigh =
5938 4175 : nAlphaRatio >> HIDDEN_ALPHA_HALF_WIDTH;
5939 : // printf("alpha0=%f, alpha1=%f, alpha2=%f, dfRatio=%f, "/*ok*/
5940 : // "nAlphaRatio = %u\n",
5941 : // alpha0, alpha1, alpha2, dfRatio, nAlphaRatio);
5942 :
5943 4175 : CPLAssert(((poLine->getNumPoints() - 1 - 6) % 2) == 0);
5944 :
5945 117994 : for (int i = 1; i + 1 < poLine->getNumPoints(); i += 2)
5946 : {
5947 113819 : GUInt16 nVal = 0xFFFF;
5948 :
5949 113819 : double dfX = poLine->getX(i);
5950 113819 : double dfY = poLine->getY(i);
5951 113819 : if (i == 1)
5952 4175 : nVal = nAlphaRatioLow;
5953 109644 : else if (i == poLine->getNumPoints() - 2)
5954 4175 : nVal = nAlphaRatioHigh;
5955 113819 : OGRGF_SetHiddenValue(nVal, dfX, dfY);
5956 113819 : poLine->setPoint(i, dfX, dfY);
5957 :
5958 113819 : dfX = poLine->getX(i + 1);
5959 113819 : dfY = poLine->getY(i + 1);
5960 113819 : if (i == 1)
5961 4175 : nVal = nAlphaRatioHigh;
5962 109644 : else if (i == poLine->getNumPoints() - 2)
5963 4175 : nVal = nAlphaRatioLow;
5964 113819 : OGRGF_SetHiddenValue(nVal, dfX, dfY);
5965 113819 : poLine->setPoint(i + 1, dfX, dfY);
5966 : }
5967 : }
5968 : }
5969 :
5970 4274 : if (bHasZ)
5971 272 : poLine->addPoint(x2, y2, z2);
5972 : else
5973 4002 : poLine->addPoint(x2, y2);
5974 :
5975 4274 : return poLine;
5976 : }
5977 :
5978 : /************************************************************************/
5979 : /* OGRGF_FixAngle() */
5980 : /************************************************************************/
5981 :
5982 : // Fix dfAngle by offsets of 2 PI so that it lies between dfAngleStart and
5983 : // dfAngleStop, whatever their respective order.
5984 180343 : static double OGRGF_FixAngle(double dfAngleStart, double dfAngleStop,
5985 : double dfAngle)
5986 : {
5987 180343 : if (dfAngleStart < dfAngleStop)
5988 : {
5989 121778 : while (dfAngle <= dfAngleStart + 1e-8)
5990 32431 : dfAngle += 2 * M_PI;
5991 : }
5992 : else
5993 : {
5994 124806 : while (dfAngle >= dfAngleStart - 1e-8)
5995 33810 : dfAngle -= 2 * M_PI;
5996 : }
5997 180343 : return dfAngle;
5998 : }
5999 :
6000 : /************************************************************************/
6001 : /* OGRGF_DetectArc() */
6002 : /************************************************************************/
6003 :
6004 : // #define VERBOSE_DEBUG_CURVEFROMLINESTRING
6005 :
6006 12247 : static inline bool IS_ALMOST_INTEGER(double x)
6007 : {
6008 12247 : const double val = fabs(x - floor(x + 0.5));
6009 12247 : return val < 1.0e-8;
6010 : }
6011 :
6012 3477 : static int OGRGF_DetectArc(const OGRLineString *poLS, int i,
6013 : OGRCompoundCurve *&poCC, OGRCircularString *&poCS,
6014 : OGRLineString *&poLSNew)
6015 : {
6016 3477 : if (i + 3 >= poLS->getNumPoints())
6017 305 : return -1;
6018 :
6019 6344 : OGRPoint p0;
6020 6344 : OGRPoint p1;
6021 6344 : OGRPoint p2;
6022 3172 : poLS->getPoint(i, &p0);
6023 3172 : poLS->getPoint(i + 1, &p1);
6024 3172 : poLS->getPoint(i + 2, &p2);
6025 3172 : double R_1 = 0.0;
6026 3172 : double cx_1 = 0.0;
6027 3172 : double cy_1 = 0.0;
6028 3172 : double alpha0_1 = 0.0;
6029 3172 : double alpha1_1 = 0.0;
6030 3172 : double alpha2_1 = 0.0;
6031 6337 : if (!(OGRGeometryFactory::GetCurveParameters(
6032 : p0.getX(), p0.getY(), p1.getX(), p1.getY(), p2.getX(), p2.getY(),
6033 : R_1, cx_1, cy_1, alpha0_1, alpha1_1, alpha2_1) &&
6034 3165 : fabs(alpha2_1 - alpha0_1) < 2.0 * 20.0 / 180.0 * M_PI))
6035 : {
6036 24 : return -1;
6037 : }
6038 :
6039 3148 : const double dfDeltaAlpha10 = alpha1_1 - alpha0_1;
6040 3148 : const double dfDeltaAlpha21 = alpha2_1 - alpha1_1;
6041 : const double dfMaxDeltaAlpha =
6042 3148 : std::max(fabs(dfDeltaAlpha10), fabs(dfDeltaAlpha21));
6043 : GUInt32 nAlphaRatioRef =
6044 3148 : OGRGF_GetHiddenValue(p1.getX(), p1.getY()) |
6045 3148 : (OGRGF_GetHiddenValue(p2.getX(), p2.getY()) << HIDDEN_ALPHA_HALF_WIDTH);
6046 3148 : bool bFoundFFFFFFFFPattern = false;
6047 3148 : bool bFoundReversedAlphaRatioRef = false;
6048 3148 : bool bValidAlphaRatio = nAlphaRatioRef > 0 && nAlphaRatioRef < 0xFFFFFFFF;
6049 3148 : int nCountValidAlphaRatio = 1;
6050 :
6051 3148 : double dfScale = std::max(1.0, R_1);
6052 3148 : dfScale = std::max(dfScale, fabs(cx_1));
6053 3148 : dfScale = std::max(dfScale, fabs(cy_1));
6054 3148 : dfScale = pow(10.0, ceil(log10(dfScale)));
6055 3148 : const double dfInvScale = 1.0 / dfScale;
6056 :
6057 3148 : const int bInitialConstantStep =
6058 3148 : (fabs(dfDeltaAlpha10 - dfDeltaAlpha21) / dfMaxDeltaAlpha) < 1.0e-4;
6059 3148 : const double dfDeltaEpsilon =
6060 3148 : bInitialConstantStep ? dfMaxDeltaAlpha * 1e-4 : dfMaxDeltaAlpha / 10;
6061 :
6062 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6063 : printf("----------------------------\n"); /*ok*/
6064 : printf("Curve beginning at offset i = %d\n", i); /*ok*/
6065 : printf("Initial alpha ratio = %u\n", nAlphaRatioRef); /*ok*/
6066 : /*ok*/ printf("Initial R = %.16g, cx = %.16g, cy = %.16g\n", R_1, cx_1,
6067 : cy_1);
6068 : printf("dfScale = %f\n", dfScale); /*ok*/
6069 : printf("bInitialConstantStep = %d, " /*ok*/
6070 : "fabs(dfDeltaAlpha10 - dfDeltaAlpha21)=%.8g, "
6071 : "dfMaxDeltaAlpha = %.8f, "
6072 : "dfDeltaEpsilon = %.8f (%.8f)\n",
6073 : bInitialConstantStep, fabs(dfDeltaAlpha10 - dfDeltaAlpha21),
6074 : dfMaxDeltaAlpha, dfDeltaEpsilon, 1.0 / 180.0 * M_PI);
6075 : #endif
6076 3148 : int iMidPoint = -1;
6077 3148 : double dfLastValidAlpha = alpha2_1;
6078 :
6079 3148 : double dfLastLogRelDiff = 0;
6080 :
6081 6296 : OGRPoint p3;
6082 3148 : int j = i + 1; // Used after for.
6083 181893 : for (; j + 2 < poLS->getNumPoints(); j++)
6084 : {
6085 178844 : poLS->getPoint(j, &p1);
6086 178844 : poLS->getPoint(j + 1, &p2);
6087 178844 : poLS->getPoint(j + 2, &p3);
6088 178844 : double R_2 = 0.0;
6089 178844 : double cx_2 = 0.0;
6090 178844 : double cy_2 = 0.0;
6091 178844 : double alpha0_2 = 0.0;
6092 178844 : double alpha1_2 = 0.0;
6093 178844 : double alpha2_2 = 0.0;
6094 : // Check that the new candidate arc shares the same
6095 : // radius, center and winding order.
6096 178844 : if (!(OGRGeometryFactory::GetCurveParameters(
6097 : p1.getX(), p1.getY(), p2.getX(), p2.getY(), p3.getX(),
6098 : p3.getY(), R_2, cx_2, cy_2, alpha0_2, alpha1_2, alpha2_2)))
6099 : {
6100 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6101 : printf("End of curve at j=%d\n : straight line", j); /*ok*/
6102 : #endif
6103 99 : break;
6104 : }
6105 :
6106 178836 : const double dfRelDiffR = fabs(R_1 - R_2) * dfInvScale;
6107 178836 : const double dfRelDiffCx = fabs(cx_1 - cx_2) * dfInvScale;
6108 178836 : const double dfRelDiffCy = fabs(cy_1 - cy_2) * dfInvScale;
6109 :
6110 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6111 : printf("j=%d: R = %.16g, cx = %.16g, cy = %.16g, " /*ok*/
6112 : "rel_diff_R=%.8g rel_diff_cx=%.8g rel_diff_cy=%.8g\n",
6113 : j, R_2, cx_2, cy_2, dfRelDiffR, dfRelDiffCx, dfRelDiffCy);
6114 : #endif
6115 :
6116 178836 : if (dfRelDiffR > 1.0e-7 || dfRelDiffCx > 1.0e-7 ||
6117 178767 : dfRelDiffCy > 1.0e-7 ||
6118 178767 : dfDeltaAlpha10 * (alpha1_2 - alpha0_2) < 0.0)
6119 : {
6120 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6121 : printf("End of curve at j=%d\n", j); /*ok*/
6122 : #endif
6123 : break;
6124 : }
6125 :
6126 178767 : if (dfRelDiffR > 0.0 && dfRelDiffCx > 0.0 && dfRelDiffCy > 0.0)
6127 : {
6128 : const double dfLogRelDiff = std::min(
6129 357498 : std::min(fabs(log10(dfRelDiffR)), fabs(log10(dfRelDiffCx))),
6130 178749 : fabs(log10(dfRelDiffCy)));
6131 : // printf("dfLogRelDiff = %f, dfLastLogRelDiff=%f, "/*ok*/
6132 : // "dfLogRelDiff - dfLastLogRelDiff=%f\n",
6133 : // dfLogRelDiff, dfLastLogRelDiff,
6134 : // dfLogRelDiff - dfLastLogRelDiff);
6135 178749 : if (dfLogRelDiff > 0.0 && dfLastLogRelDiff >= 8.0 &&
6136 2 : dfLogRelDiff <= 8.0 && dfLogRelDiff < dfLastLogRelDiff - 2.0)
6137 : {
6138 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6139 : printf("End of curve at j=%d. Significant different in " /*ok*/
6140 : "relative error w.r.t previous points\n",
6141 : j);
6142 : #endif
6143 2 : break;
6144 : }
6145 178747 : dfLastLogRelDiff = dfLogRelDiff;
6146 : }
6147 :
6148 178765 : const double dfStep10 = fabs(alpha1_2 - alpha0_2);
6149 178765 : const double dfStep21 = fabs(alpha2_2 - alpha1_2);
6150 : // Check that the angle step is consistent with the original step.
6151 178765 : if (!(dfStep10 < 2.0 * dfMaxDeltaAlpha &&
6152 178765 : dfStep21 < 2.0 * dfMaxDeltaAlpha))
6153 : {
6154 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6155 : printf("End of curve at j=%d: dfStep10=%f, dfStep21=%f, " /*ok*/
6156 : "2*dfMaxDeltaAlpha=%f\n",
6157 : j, dfStep10, dfStep21, 2 * dfMaxDeltaAlpha);
6158 : #endif
6159 : break;
6160 : }
6161 :
6162 178764 : if (bValidAlphaRatio && j > i + 1 && (i % 2) != (j % 2))
6163 : {
6164 : const GUInt32 nAlphaRatioReversed =
6165 87612 : (OGRGF_GetHiddenValue(p1.getX(), p1.getY())
6166 175224 : << HIDDEN_ALPHA_HALF_WIDTH) |
6167 87612 : (OGRGF_GetHiddenValue(p2.getX(), p2.getY()));
6168 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6169 : printf("j=%d, nAlphaRatioReversed = %u\n", /*ok*/
6170 : j, nAlphaRatioReversed);
6171 : #endif
6172 87612 : if (!bFoundFFFFFFFFPattern && nAlphaRatioReversed == 0xFFFFFFFF)
6173 : {
6174 3076 : bFoundFFFFFFFFPattern = true;
6175 3076 : nCountValidAlphaRatio++;
6176 : }
6177 84536 : else if (bFoundFFFFFFFFPattern && !bFoundReversedAlphaRatioRef &&
6178 : nAlphaRatioReversed == 0xFFFFFFFF)
6179 : {
6180 81433 : nCountValidAlphaRatio++;
6181 : }
6182 3103 : else if (bFoundFFFFFFFFPattern && !bFoundReversedAlphaRatioRef &&
6183 : nAlphaRatioReversed == nAlphaRatioRef)
6184 : {
6185 3076 : bFoundReversedAlphaRatioRef = true;
6186 3076 : nCountValidAlphaRatio++;
6187 : }
6188 : else
6189 : {
6190 27 : if (bInitialConstantStep &&
6191 26 : fabs(dfLastValidAlpha - alpha0_1) >= M_PI &&
6192 : nCountValidAlphaRatio > 10)
6193 : {
6194 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6195 : printf("End of curve at j=%d: " /*ok*/
6196 : "fabs(dfLastValidAlpha - alpha0_1)=%f, "
6197 : "nCountValidAlphaRatio=%d\n",
6198 : j, fabs(dfLastValidAlpha - alpha0_1),
6199 : nCountValidAlphaRatio);
6200 : #endif
6201 19 : if (dfLastValidAlpha - alpha0_1 > 0)
6202 : {
6203 21 : while (dfLastValidAlpha - alpha0_1 - dfMaxDeltaAlpha -
6204 14 : M_PI >
6205 14 : -dfMaxDeltaAlpha / 10)
6206 : {
6207 7 : dfLastValidAlpha -= dfMaxDeltaAlpha;
6208 7 : j--;
6209 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6210 : printf(/*ok*/
6211 : "--> corrected as fabs(dfLastValidAlpha - "
6212 : "alpha0_1)=%f, j=%d\n",
6213 : fabs(dfLastValidAlpha - alpha0_1), j);
6214 : #endif
6215 : }
6216 : }
6217 : else
6218 : {
6219 36 : while (dfLastValidAlpha - alpha0_1 + dfMaxDeltaAlpha +
6220 24 : M_PI <
6221 24 : dfMaxDeltaAlpha / 10)
6222 : {
6223 12 : dfLastValidAlpha += dfMaxDeltaAlpha;
6224 12 : j--;
6225 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6226 : printf(/*ok*/
6227 : "--> corrected as fabs(dfLastValidAlpha - "
6228 : "alpha0_1)=%f, j=%d\n",
6229 : fabs(dfLastValidAlpha - alpha0_1), j);
6230 : #endif
6231 : }
6232 : }
6233 19 : poLS->getPoint(j + 1, &p2);
6234 19 : break;
6235 : }
6236 :
6237 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6238 : printf("j=%d, nAlphaRatioReversed = %u --> inconsistent " /*ok*/
6239 : "values across arc. Don't use it\n",
6240 : j, nAlphaRatioReversed);
6241 : #endif
6242 8 : bValidAlphaRatio = false;
6243 : }
6244 : }
6245 :
6246 : // Correct current end angle, consistently with start angle.
6247 178745 : dfLastValidAlpha = OGRGF_FixAngle(alpha0_1, alpha1_1, alpha2_2);
6248 :
6249 : // Try to detect the precise intermediate point of the
6250 : // arc circle by detecting irregular angle step
6251 : // This is OK if we don't detect the right point or fail
6252 : // to detect it.
6253 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6254 : printf("j=%d A(0,1)-maxDelta=%.8f A(1,2)-maxDelta=%.8f " /*ok*/
6255 : "x1=%.8f y1=%.8f x2=%.8f y2=%.8f x3=%.8f y3=%.8f\n",
6256 : j, fabs(dfStep10 - dfMaxDeltaAlpha),
6257 : fabs(dfStep21 - dfMaxDeltaAlpha), p1.getX(), p1.getY(),
6258 : p2.getX(), p2.getY(), p3.getX(), p3.getY());
6259 : #endif
6260 178745 : if (j > i + 1 && iMidPoint < 0 && dfDeltaEpsilon < 1.0 / 180.0 * M_PI)
6261 : {
6262 175265 : if (fabs(dfStep10 - dfMaxDeltaAlpha) > dfDeltaEpsilon)
6263 8 : iMidPoint = j + ((bInitialConstantStep) ? 0 : 1);
6264 175257 : else if (fabs(dfStep21 - dfMaxDeltaAlpha) > dfDeltaEpsilon)
6265 4 : iMidPoint = j + ((bInitialConstantStep) ? 1 : 2);
6266 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6267 : if (iMidPoint >= 0)
6268 : {
6269 : OGRPoint pMid;
6270 : poLS->getPoint(iMidPoint, &pMid);
6271 : printf("Midpoint detected at j = %d, iMidPoint = %d, " /*ok*/
6272 : "x=%.8f y=%.8f\n",
6273 : j, iMidPoint, pMid.getX(), pMid.getY());
6274 : }
6275 : #endif
6276 : }
6277 : }
6278 :
6279 : // Take a minimum threshold of consecutive points
6280 : // on the arc to avoid false positives.
6281 3148 : if (j < i + 3)
6282 61 : return -1;
6283 :
6284 3087 : bValidAlphaRatio &= bFoundFFFFFFFFPattern && bFoundReversedAlphaRatioRef;
6285 :
6286 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6287 : printf("bValidAlphaRatio=%d bFoundFFFFFFFFPattern=%d, " /*ok*/
6288 : "bFoundReversedAlphaRatioRef=%d\n",
6289 : static_cast<int>(bValidAlphaRatio),
6290 : static_cast<int>(bFoundFFFFFFFFPattern),
6291 : static_cast<int>(bFoundReversedAlphaRatioRef));
6292 : printf("alpha0_1=%f dfLastValidAlpha=%f\n", /*ok*/
6293 : alpha0_1, dfLastValidAlpha);
6294 : #endif
6295 :
6296 3087 : if (poLSNew != nullptr)
6297 : {
6298 11 : double dfScale2 = std::max(1.0, fabs(p0.getX()));
6299 11 : dfScale2 = std::max(dfScale2, fabs(p0.getY()));
6300 : // Not strictly necessary, but helps having 'clean' lines without
6301 : // duplicated points.
6302 11 : constexpr double dfToleranceEps =
6303 : OGRCompoundCurve::DEFAULT_TOLERANCE_EPSILON;
6304 11 : if (fabs(poLSNew->getX(poLSNew->getNumPoints() - 1) - p0.getX()) >
6305 12 : dfToleranceEps * dfScale2 ||
6306 1 : fabs(poLSNew->getY(poLSNew->getNumPoints() - 1) - p0.getY()) >
6307 1 : dfToleranceEps * dfScale2)
6308 10 : poLSNew->addPoint(&p0);
6309 11 : if (poLSNew->getNumPoints() >= 2)
6310 : {
6311 10 : if (poCC == nullptr)
6312 3 : poCC = new OGRCompoundCurve();
6313 10 : poCC->addCurveDirectly(poLSNew);
6314 : }
6315 : else
6316 1 : delete poLSNew;
6317 11 : poLSNew = nullptr;
6318 : }
6319 :
6320 3087 : if (poCS == nullptr)
6321 : {
6322 3063 : poCS = new OGRCircularString();
6323 3063 : poCS->addPoint(&p0);
6324 : }
6325 :
6326 3087 : OGRPoint *poFinalPoint = (j + 2 >= poLS->getNumPoints()) ? &p3 : &p2;
6327 :
6328 3087 : double dfXMid = 0.0;
6329 3087 : double dfYMid = 0.0;
6330 3087 : double dfZMid = 0.0;
6331 3087 : if (bValidAlphaRatio)
6332 : {
6333 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6334 : printf("Using alpha ratio...\n"); /*ok*/
6335 : #endif
6336 3076 : double dfAlphaMid = 0.0;
6337 3076 : if (OGRGF_NeedSwithArcOrder(p0.getX(), p0.getY(), poFinalPoint->getX(),
6338 : poFinalPoint->getY()))
6339 : {
6340 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6341 : printf("Switching angles\n"); /*ok*/
6342 : #endif
6343 1550 : dfAlphaMid = dfLastValidAlpha + nAlphaRatioRef *
6344 1550 : (alpha0_1 - dfLastValidAlpha) /
6345 : HIDDEN_ALPHA_SCALE;
6346 1550 : dfAlphaMid = OGRGF_FixAngle(alpha0_1, dfLastValidAlpha, dfAlphaMid);
6347 : }
6348 : else
6349 : {
6350 1526 : dfAlphaMid = alpha0_1 + nAlphaRatioRef *
6351 1526 : (dfLastValidAlpha - alpha0_1) /
6352 : HIDDEN_ALPHA_SCALE;
6353 : }
6354 :
6355 3076 : dfXMid = cx_1 + R_1 * cos(dfAlphaMid);
6356 3076 : dfYMid = cy_1 + R_1 * sin(dfAlphaMid);
6357 :
6358 3076 : if (poLS->getCoordinateDimension() == 3)
6359 : {
6360 2 : double dfLastAlpha = 0.0;
6361 2 : double dfLastZ = 0.0;
6362 2 : int k = i; // Used after for.
6363 48 : for (; k < j + 2; k++)
6364 : {
6365 48 : OGRPoint p;
6366 48 : poLS->getPoint(k, &p);
6367 48 : double dfAlpha = atan2(p.getY() - cy_1, p.getX() - cx_1);
6368 48 : dfAlpha = OGRGF_FixAngle(alpha0_1, dfLastValidAlpha, dfAlpha);
6369 48 : if (k > i &&
6370 46 : ((dfAlpha < dfLastValidAlpha && dfAlphaMid < dfAlpha) ||
6371 23 : (dfAlpha > dfLastValidAlpha && dfAlphaMid > dfAlpha)))
6372 : {
6373 2 : const double dfRatio =
6374 2 : (dfAlphaMid - dfLastAlpha) / (dfAlpha - dfLastAlpha);
6375 2 : dfZMid = (1 - dfRatio) * dfLastZ + dfRatio * p.getZ();
6376 2 : break;
6377 : }
6378 46 : dfLastAlpha = dfAlpha;
6379 46 : dfLastZ = p.getZ();
6380 : }
6381 2 : if (k == j + 2)
6382 0 : dfZMid = dfLastZ;
6383 2 : if (IS_ALMOST_INTEGER(dfZMid))
6384 2 : dfZMid = static_cast<int>(floor(dfZMid + 0.5));
6385 : }
6386 :
6387 : // A few rounding strategies in case the mid point was at "exact"
6388 : // coordinates.
6389 3076 : if (R_1 > 1e-5)
6390 : {
6391 : const bool bStartEndInteger =
6392 9188 : IS_ALMOST_INTEGER(p0.getX()) && IS_ALMOST_INTEGER(p0.getY()) &&
6393 9188 : IS_ALMOST_INTEGER(poFinalPoint->getX()) &&
6394 3057 : IS_ALMOST_INTEGER(poFinalPoint->getY());
6395 3070 : if (bStartEndInteger &&
6396 3057 : fabs(dfXMid - floor(dfXMid + 0.5)) / dfScale < 1e-4 &&
6397 3038 : fabs(dfYMid - floor(dfYMid + 0.5)) / dfScale < 1e-4)
6398 : {
6399 3038 : dfXMid = static_cast<int>(floor(dfXMid + 0.5));
6400 3038 : dfYMid = static_cast<int>(floor(dfYMid + 0.5));
6401 : // Sometimes rounding to closest is not best approach
6402 : // Try neighbouring integers to look for the one that
6403 : // minimize the error w.r.t to the arc center
6404 : // But only do that if the radius is greater than
6405 : // the magnitude of the delta that we will try!
6406 : double dfBestRError =
6407 3038 : fabs(R_1 - DISTANCE(dfXMid, dfYMid, cx_1, cy_1));
6408 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6409 : printf("initial_error=%f\n", dfBestRError); /*ok*/
6410 : #endif
6411 3038 : int iBestX = 0;
6412 3038 : int iBestY = 0;
6413 3038 : if (dfBestRError > 0.001 && R_1 > 2)
6414 : {
6415 4 : int nSearchRadius = 1;
6416 : // Extend the search radius if the arc circle radius
6417 : // is much higher than the coordinate values.
6418 : double dfMaxCoords =
6419 4 : std::max(fabs(p0.getX()), fabs(p0.getY()));
6420 4 : dfMaxCoords = std::max(dfMaxCoords, poFinalPoint->getX());
6421 4 : dfMaxCoords = std::max(dfMaxCoords, poFinalPoint->getY());
6422 4 : dfMaxCoords = std::max(dfMaxCoords, dfXMid);
6423 4 : dfMaxCoords = std::max(dfMaxCoords, dfYMid);
6424 4 : if (R_1 > dfMaxCoords * 1000)
6425 4 : nSearchRadius = 100;
6426 0 : else if (R_1 > dfMaxCoords * 10)
6427 0 : nSearchRadius = 10;
6428 808 : for (int iY = -nSearchRadius; iY <= nSearchRadius; iY++)
6429 : {
6430 162408 : for (int iX = -nSearchRadius; iX <= nSearchRadius; iX++)
6431 : {
6432 161604 : const double dfCandidateX = dfXMid + iX;
6433 161604 : const double dfCandidateY = dfYMid + iY;
6434 161604 : if (fabs(dfCandidateX - p0.getX()) < 1e-8 &&
6435 0 : fabs(dfCandidateY - p0.getY()) < 1e-8)
6436 0 : continue;
6437 161604 : if (fabs(dfCandidateX - poFinalPoint->getX()) <
6438 161604 : 1e-8 &&
6439 0 : fabs(dfCandidateY - poFinalPoint->getY()) <
6440 : 1e-8)
6441 0 : continue;
6442 : const double dfRError =
6443 161604 : fabs(R_1 - DISTANCE(dfCandidateX, dfCandidateY,
6444 161604 : cx_1, cy_1));
6445 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6446 : printf("x=%d y=%d error=%f besterror=%f\n", /*ok*/
6447 : static_cast<int>(dfXMid + iX),
6448 : static_cast<int>(dfYMid + iY), dfRError,
6449 : dfBestRError);
6450 : #endif
6451 161604 : if (dfRError < dfBestRError)
6452 : {
6453 24 : iBestX = iX;
6454 24 : iBestY = iY;
6455 24 : dfBestRError = dfRError;
6456 : }
6457 : }
6458 : }
6459 : }
6460 3038 : dfXMid += iBestX;
6461 3038 : dfYMid += iBestY;
6462 : }
6463 : else
6464 : {
6465 : // Limit the number of significant figures in decimal
6466 : // representation.
6467 32 : if (fabs(dfXMid) < 100000000.0)
6468 : {
6469 32 : dfXMid =
6470 32 : static_cast<GIntBig>(floor(dfXMid * 100000000 + 0.5)) /
6471 : 100000000.0;
6472 : }
6473 32 : if (fabs(dfYMid) < 100000000.0)
6474 : {
6475 32 : dfYMid =
6476 32 : static_cast<GIntBig>(floor(dfYMid * 100000000 + 0.5)) /
6477 : 100000000.0;
6478 : }
6479 : }
6480 : }
6481 :
6482 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6483 : printf("dfAlphaMid=%f, x_mid = %f, y_mid = %f\n", /*ok*/
6484 : dfLastValidAlpha, dfXMid, dfYMid);
6485 : #endif
6486 : }
6487 :
6488 : // If this is a full circle of a non-polygonal zone, we must
6489 : // use a 5-point representation to keep the winding order.
6490 3098 : if (p0.Equals(poFinalPoint) &&
6491 11 : !EQUAL(poLS->getGeometryName(), "LINEARRING"))
6492 : {
6493 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6494 : printf("Full circle of a non-polygonal zone\n"); /*ok*/
6495 : #endif
6496 1 : poLS->getPoint((i + j + 2) / 4, &p1);
6497 1 : poCS->addPoint(&p1);
6498 1 : if (bValidAlphaRatio)
6499 : {
6500 1 : p1.setX(dfXMid);
6501 1 : p1.setY(dfYMid);
6502 1 : if (poLS->getCoordinateDimension() == 3)
6503 0 : p1.setZ(dfZMid);
6504 : }
6505 : else
6506 : {
6507 0 : poLS->getPoint((i + j + 1) / 2, &p1);
6508 : }
6509 1 : poCS->addPoint(&p1);
6510 1 : poLS->getPoint(3 * (i + j + 2) / 4, &p1);
6511 1 : poCS->addPoint(&p1);
6512 : }
6513 :
6514 3086 : else if (bValidAlphaRatio)
6515 : {
6516 3075 : p1.setX(dfXMid);
6517 3075 : p1.setY(dfYMid);
6518 3075 : if (poLS->getCoordinateDimension() == 3)
6519 2 : p1.setZ(dfZMid);
6520 3075 : poCS->addPoint(&p1);
6521 : }
6522 :
6523 : // If we have found a candidate for a precise intermediate
6524 : // point, use it.
6525 11 : else if (iMidPoint >= 1 && iMidPoint < j)
6526 : {
6527 3 : poLS->getPoint(iMidPoint, &p1);
6528 3 : poCS->addPoint(&p1);
6529 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6530 : printf("Using detected midpoint...\n"); /*ok*/
6531 : printf("x_mid = %f, y_mid = %f\n", p1.getX(), p1.getY()); /*ok*/
6532 : #endif
6533 : }
6534 : // Otherwise pick up the mid point between both extremities.
6535 : else
6536 : {
6537 8 : poLS->getPoint((i + j + 1) / 2, &p1);
6538 8 : poCS->addPoint(&p1);
6539 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6540 : printf("Pickup 'random' midpoint at index=%d...\n", /*ok*/
6541 : (i + j + 1) / 2);
6542 : printf("x_mid = %f, y_mid = %f\n", p1.getX(), p1.getY()); /*ok*/
6543 : #endif
6544 : }
6545 3087 : poCS->addPoint(poFinalPoint);
6546 :
6547 : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6548 : printf("----------------------------\n"); /*ok*/
6549 : #endif
6550 :
6551 3087 : if (j + 2 >= poLS->getNumPoints())
6552 3049 : return -2;
6553 38 : return j + 1;
6554 : }
6555 :
6556 : /************************************************************************/
6557 : /* curveFromLineString() */
6558 : /************************************************************************/
6559 :
6560 : /**
6561 : * \brief Try to convert a linestring approximating curves into a curve.
6562 : *
6563 : * This method can return a COMPOUNDCURVE, a CIRCULARSTRING or a LINESTRING.
6564 : *
6565 : * This method is the reverse of curveFromLineString().
6566 : *
6567 : * @param poLS handle to the geometry to convert.
6568 : * @param papszOptions options as a null-terminated list of strings.
6569 : * Unused for now. Must be set to NULL.
6570 : *
6571 : * @return the converted geometry (ownership to caller).
6572 : *
6573 : */
6574 :
6575 3199 : OGRCurve *OGRGeometryFactory::curveFromLineString(
6576 : const OGRLineString *poLS, CPL_UNUSED const char *const *papszOptions)
6577 : {
6578 3199 : OGRCompoundCurve *poCC = nullptr;
6579 3199 : OGRCircularString *poCS = nullptr;
6580 3199 : OGRLineString *poLSNew = nullptr;
6581 3199 : const int nLSNumPoints = poLS->getNumPoints();
6582 3199 : const bool bIsClosed = nLSNumPoints >= 4 && poLS->get_IsClosed();
6583 3627 : for (int i = 0; i < nLSNumPoints; /* nothing */)
6584 : {
6585 3477 : const int iNewI = OGRGF_DetectArc(poLS, i, poCC, poCS, poLSNew);
6586 3477 : if (iNewI == -2)
6587 3049 : break;
6588 428 : if (iNewI >= 0)
6589 : {
6590 38 : i = iNewI;
6591 38 : continue;
6592 : }
6593 :
6594 390 : if (poCS != nullptr)
6595 : {
6596 14 : if (poCC == nullptr)
6597 5 : poCC = new OGRCompoundCurve();
6598 14 : poCC->addCurveDirectly(poCS);
6599 14 : poCS = nullptr;
6600 : }
6601 :
6602 390 : OGRPoint p;
6603 390 : poLS->getPoint(i, &p);
6604 390 : if (poLSNew == nullptr)
6605 : {
6606 160 : poLSNew = new OGRLineString();
6607 160 : poLSNew->addPoint(&p);
6608 : }
6609 : // Not strictly necessary, but helps having 'clean' lines without
6610 : // duplicated points.
6611 : else
6612 : {
6613 230 : double dfScale = std::max(1.0, fabs(p.getX()));
6614 230 : dfScale = std::max(dfScale, fabs(p.getY()));
6615 230 : if (bIsClosed && i == nLSNumPoints - 1)
6616 7 : dfScale = 0;
6617 230 : constexpr double dfToleranceEps =
6618 : OGRCompoundCurve::DEFAULT_TOLERANCE_EPSILON;
6619 230 : if (fabs(poLSNew->getX(poLSNew->getNumPoints() - 1) - p.getX()) >
6620 239 : dfToleranceEps * dfScale ||
6621 9 : fabs(poLSNew->getY(poLSNew->getNumPoints() - 1) - p.getY()) >
6622 9 : dfToleranceEps * dfScale)
6623 : {
6624 229 : poLSNew->addPoint(&p);
6625 : }
6626 : }
6627 :
6628 390 : i++;
6629 : }
6630 :
6631 3199 : OGRCurve *poRet = nullptr;
6632 :
6633 3199 : if (poLSNew != nullptr && poLSNew->getNumPoints() < 2)
6634 : {
6635 1 : delete poLSNew;
6636 1 : poLSNew = nullptr;
6637 1 : if (poCC != nullptr)
6638 : {
6639 1 : if (poCC->getNumCurves() == 1)
6640 : {
6641 1 : poRet = poCC->stealCurve(0);
6642 1 : delete poCC;
6643 1 : poCC = nullptr;
6644 : }
6645 : else
6646 0 : poRet = poCC;
6647 : }
6648 : else
6649 0 : poRet = poLS->clone();
6650 : }
6651 3198 : else if (poCC != nullptr)
6652 : {
6653 7 : if (poLSNew)
6654 6 : poCC->addCurveDirectly(poLSNew);
6655 : else
6656 1 : poCC->addCurveDirectly(poCS);
6657 7 : poRet = poCC;
6658 : }
6659 3191 : else if (poLSNew != nullptr)
6660 142 : poRet = poLSNew;
6661 3049 : else if (poCS != nullptr)
6662 3048 : poRet = poCS;
6663 : else
6664 1 : poRet = poLS->clone();
6665 :
6666 3199 : poRet->assignSpatialReference(poLS->getSpatialReference());
6667 :
6668 3199 : return poRet;
6669 : }
6670 :
6671 : /************************************************************************/
6672 : /* createFromGeoJson( const char* ) */
6673 : /************************************************************************/
6674 :
6675 : /**
6676 : * @brief Create geometry from GeoJson fragment.
6677 : * @param pszJsonString The GeoJSON fragment for the geometry.
6678 : * @param nSize (new in GDAL 3.4) Optional length of the string
6679 : * if it is not null-terminated
6680 : * @return a geometry on success, or NULL on error.
6681 : */
6682 5 : OGRGeometry *OGRGeometryFactory::createFromGeoJson(const char *pszJsonString,
6683 : int nSize)
6684 : {
6685 10 : CPLJSONDocument oDocument;
6686 5 : if (!oDocument.LoadMemory(reinterpret_cast<const GByte *>(pszJsonString),
6687 : nSize))
6688 : {
6689 3 : return nullptr;
6690 : }
6691 :
6692 2 : return createFromGeoJson(oDocument.GetRoot());
6693 : }
6694 :
6695 : /************************************************************************/
6696 : /* createFromGeoJson( const CPLJSONObject& ) */
6697 : /************************************************************************/
6698 :
6699 : /**
6700 : * @brief Create geometry from GeoJson fragment.
6701 : * @param oJsonObject The JSONObject class describes the GeoJSON geometry.
6702 : * @return a geometry on success, or NULL on error.
6703 : */
6704 : OGRGeometry *
6705 2 : OGRGeometryFactory::createFromGeoJson(const CPLJSONObject &oJsonObject)
6706 : {
6707 2 : if (!oJsonObject.IsValid())
6708 : {
6709 0 : return nullptr;
6710 : }
6711 :
6712 : // TODO: Move from GeoJSON driver functions create geometry here, and
6713 : // replace json-c specific json_object to CPLJSONObject
6714 4 : return OGRGeoJSONReadGeometry(
6715 2 : static_cast<json_object *>(oJsonObject.GetInternalHandle()),
6716 : /* bHasM = */ false, /* OGRSpatialReference* = */ nullptr)
6717 2 : .release();
6718 : }
|