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