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