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