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