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