LCOV - code coverage report
Current view: top level - ogr - ogrgeojsonwriter.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 722 759 95.1 %
Date: 2025-10-01 17:07:58 Functions: 36 36 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OpenGIS Simple Features Reference Implementation
       4             :  * Purpose:  Implementation of GeoJSON writer utilities (OGR GeoJSON Driver).
       5             :  * Author:   Mateusz Loskot, mateusz@loskot.net
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2007, Mateusz Loskot
       9             :  * Copyright (c) 2008-2014, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : /*! @cond Doxygen_Suppress */
      15             : 
      16             : #define JSON_C_VER_013 (13 << 8)
      17             : 
      18             : #include "ogrgeojsonwriter.h"
      19             : #include "ogr_geometry.h"
      20             : #include "ogrgeojsongeometry.h"
      21             : #include "ogrlibjsonutils.h"
      22             : #include "ogr_feature.h"
      23             : #include "ogr_p.h"
      24             : #include <json.h>  // JSON-C
      25             : 
      26             : #if (!defined(JSON_C_VERSION_NUM)) || (JSON_C_VERSION_NUM < JSON_C_VER_013)
      27             : #include <json_object_private.h>
      28             : #endif
      29             : 
      30             : #include <printbuf.h>
      31             : #include "ogr_api.h"
      32             : 
      33             : #include <algorithm>
      34             : #include <cmath>
      35             : #include <cstdint>
      36             : #include <limits>
      37             : #include <optional>
      38             : 
      39             : static json_object *
      40             : json_object_new_float_with_significant_figures(float fVal,
      41             :                                                int nSignificantFigures);
      42             : 
      43             : static json_object *
      44             : OGRGeoJSONWritePoint(const OGRPoint *poPoint,
      45             :                      const OGRGeoJSONWriteOptions &oOptions);
      46             : 
      47             : static json_object *
      48             : OGRGeoJSONWriteSimpleCurve(const OGRSimpleCurve *poLine,
      49             :                            const OGRGeoJSONWriteOptions &oOptions);
      50             : 
      51             : static json_object *
      52             : OGRGeoJSONWriteMultiPoint(const OGRMultiPoint *poGeometry,
      53             :                           const OGRGeoJSONWriteOptions &oOptions);
      54             : 
      55             : static json_object *
      56             : OGRGeoJSONWriteMultiLineString(const OGRMultiLineString *poGeometry,
      57             :                                const OGRGeoJSONWriteOptions &oOptions);
      58             : 
      59             : static json_object *
      60             : OGRGeoJSONWriteMultiPolygon(const OGRMultiPolygon *poGeometry,
      61             :                             const OGRGeoJSONWriteOptions &oOptions);
      62             : 
      63             : static json_object *
      64             : OGRGeoJSONWriteGeometryCollection(const OGRGeometryCollection *poGeometry,
      65             :                                   const OGRGeoJSONWriteOptions &oOptions);
      66             : 
      67             : static json_object *
      68             : OGRGeoJSONWriteCoords(double dfX, double dfY, std::optional<double> dfZ,
      69             :                       std::optional<double> dfM,
      70             :                       const OGRGeoJSONWriteOptions &oOptions);
      71             : 
      72             : static json_object *
      73             : OGRGeoJSONWriteLineCoords(const OGRSimpleCurve *poLine,
      74             :                           const OGRGeoJSONWriteOptions &oOptions);
      75             : 
      76             : static json_object *
      77             : OGRGeoJSONWriteRingCoords(const OGRLinearRing *poLine, bool bIsExteriorRing,
      78             :                           const OGRGeoJSONWriteOptions &oOptions);
      79             : 
      80             : static json_object *
      81             : OGRGeoJSONWriteCompoundCurve(const OGRCompoundCurve *poCC,
      82             :                              const OGRGeoJSONWriteOptions &oOptions);
      83             : 
      84             : static json_object *
      85             : OGRGeoJSONWriteCurvePolygon(const OGRCurvePolygon *poCP,
      86             :                             const OGRGeoJSONWriteOptions &oOptions);
      87             : 
      88             : /************************************************************************/
      89             : /*                         SetRFC7946Settings()                         */
      90             : /************************************************************************/
      91             : 
      92             : /*! @cond Doxygen_Suppress */
      93         208 : void OGRGeoJSONWriteOptions::SetRFC7946Settings()
      94             : {
      95         208 :     bBBOXRFC7946 = true;
      96         208 :     if (nXYCoordPrecision < 0)
      97          74 :         nXYCoordPrecision = 7;
      98         208 :     if (nZCoordPrecision < 0)
      99          74 :         nZCoordPrecision = 3;
     100         208 :     bPolygonRightHandRule = true;
     101         208 :     bCanPatchCoordinatesWithNativeData = false;
     102         208 :     bHonourReservedRFC7946Members = true;
     103         208 : }
     104             : 
     105         343 : void OGRGeoJSONWriteOptions::SetIDOptions(CSLConstList papszOptions)
     106             : {
     107             : 
     108         343 :     osIDField = CSLFetchNameValueDef(papszOptions, "ID_FIELD", "");
     109         343 :     const char *pszIDFieldType = CSLFetchNameValue(papszOptions, "ID_TYPE");
     110         343 :     if (pszIDFieldType)
     111             :     {
     112          10 :         if (EQUAL(pszIDFieldType, "String"))
     113             :         {
     114           5 :             bForceIDFieldType = true;
     115           5 :             eForcedIDFieldType = OFTString;
     116             :         }
     117           5 :         else if (EQUAL(pszIDFieldType, "Integer"))
     118             :         {
     119           5 :             bForceIDFieldType = true;
     120           5 :             eForcedIDFieldType = OFTInteger64;
     121             :         }
     122             :     }
     123         343 :     bGenerateID =
     124         343 :         CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "ID_GENERATE", false));
     125         343 : }
     126             : 
     127             : /*! @endcond */
     128             : 
     129             : /************************************************************************/
     130             : /*                        json_object_new_coord()                       */
     131             : /************************************************************************/
     132             : 
     133             : static json_object *
     134       25983 : json_object_new_coord(double dfVal, int nDimIdx,
     135             :                       const OGRGeoJSONWriteOptions &oOptions)
     136             : {
     137             :     // If coordinate precision is specified, or significant figures is not
     138             :     // then use the '%f' formatting.
     139       25983 :     if (nDimIdx <= 2)
     140             :     {
     141       24528 :         if (oOptions.nXYCoordPrecision >= 0 || oOptions.nSignificantFigures < 0)
     142       49052 :             return json_object_new_double_with_precision(
     143       24526 :                 dfVal, oOptions.nXYCoordPrecision);
     144             :     }
     145             :     else
     146             :     {
     147        1455 :         if (oOptions.nZCoordPrecision >= 0 || oOptions.nSignificantFigures < 0)
     148        2910 :             return json_object_new_double_with_precision(
     149        1455 :                 dfVal, oOptions.nZCoordPrecision);
     150             :     }
     151             : 
     152           4 :     return json_object_new_double_with_significant_figures(
     153           2 :         dfVal, oOptions.nSignificantFigures);
     154             : }
     155             : 
     156             : /************************************************************************/
     157             : /*                     OGRGeoJSONIsPatchablePosition()                  */
     158             : /************************************************************************/
     159             : 
     160         525 : static bool OGRGeoJSONIsPatchablePosition(json_object *poJSonCoordinates,
     161             :                                           json_object *poNativeCoordinates)
     162             : {
     163         525 :     return json_object_get_type(poJSonCoordinates) == json_type_array &&
     164         498 :            json_object_get_type(poNativeCoordinates) == json_type_array &&
     165         498 :            json_object_array_length(poJSonCoordinates) == 3 &&
     166          16 :            json_object_array_length(poNativeCoordinates) >= 4 &&
     167          12 :            json_object_get_type(json_object_array_get_idx(
     168        1023 :                poJSonCoordinates, 0)) != json_type_array &&
     169          12 :            json_object_get_type(json_object_array_get_idx(
     170         525 :                poNativeCoordinates, 0)) != json_type_array;
     171             : }
     172             : 
     173             : /************************************************************************/
     174             : /*                    OGRGeoJSONIsCompatiblePosition()                  */
     175             : /************************************************************************/
     176             : 
     177         424 : static bool OGRGeoJSONIsCompatiblePosition(json_object *poJSonCoordinates,
     178             :                                            json_object *poNativeCoordinates)
     179             : {
     180         424 :     return json_object_get_type(poJSonCoordinates) == json_type_array &&
     181         424 :            json_object_get_type(poNativeCoordinates) == json_type_array &&
     182         424 :            json_object_array_length(poJSonCoordinates) ==
     183         424 :                json_object_array_length(poNativeCoordinates) &&
     184         418 :            json_object_get_type(json_object_array_get_idx(
     185         848 :                poJSonCoordinates, 0)) != json_type_array &&
     186         377 :            json_object_get_type(json_object_array_get_idx(
     187         424 :                poNativeCoordinates, 0)) != json_type_array;
     188             : }
     189             : 
     190             : /************************************************************************/
     191             : /*                       OGRGeoJSONPatchPosition()                      */
     192             : /************************************************************************/
     193             : 
     194           6 : static void OGRGeoJSONPatchPosition(json_object *poJSonCoordinates,
     195             :                                     json_object *poNativeCoordinates)
     196             : {
     197           6 :     const auto nLength = json_object_array_length(poNativeCoordinates);
     198          12 :     for (auto i = decltype(nLength){3}; i < nLength; i++)
     199             :     {
     200           6 :         json_object_array_add(
     201             :             poJSonCoordinates,
     202             :             json_object_get(json_object_array_get_idx(poNativeCoordinates, i)));
     203             :     }
     204           6 : }
     205             : 
     206             : /************************************************************************/
     207             : /*                      OGRGeoJSONIsPatchableArray()                    */
     208             : /************************************************************************/
     209             : 
     210         266 : static bool OGRGeoJSONIsPatchableArray(json_object *poJSonArray,
     211             :                                        json_object *poNativeArray, int nDepth)
     212             : {
     213         266 :     if (nDepth == 0)
     214         101 :         return OGRGeoJSONIsPatchablePosition(poJSonArray, poNativeArray);
     215             : 
     216         308 :     if (json_object_get_type(poJSonArray) == json_type_array &&
     217         143 :         json_object_get_type(poNativeArray) == json_type_array)
     218             :     {
     219         143 :         const auto nLength = json_object_array_length(poJSonArray);
     220         143 :         if (nLength == json_object_array_length(poNativeArray))
     221             :         {
     222         143 :             if (nLength > 0)
     223             :             {
     224             :                 json_object *poJSonChild =
     225         143 :                     json_object_array_get_idx(poJSonArray, 0);
     226             :                 json_object *poNativeChild =
     227         143 :                     json_object_array_get_idx(poNativeArray, 0);
     228         143 :                 if (!OGRGeoJSONIsPatchableArray(poJSonChild, poNativeChild,
     229             :                                                 nDepth - 1))
     230             :                 {
     231         134 :                     return false;
     232             :                 }
     233             :                 // Light check as a former extensive check was done in
     234             :                 // OGRGeoJSONComputePatchableOrCompatibleArray
     235             :             }
     236           9 :             return true;
     237             :         }
     238             :     }
     239          22 :     return false;
     240             : }
     241             : 
     242             : /************************************************************************/
     243             : /*                OGRGeoJSONComputePatchableOrCompatibleArray()         */
     244             : /************************************************************************/
     245             : 
     246             : /* Returns true if the objects are comparable, ie Point vs Point, LineString
     247             :    vs LineString, but they might not be patchable or compatible */
     248         486 : static bool OGRGeoJSONComputePatchableOrCompatibleArrayInternal(
     249             :     json_object *poJSonArray, json_object *poNativeArray, int nDepth,
     250             :     bool &bOutPatchable, bool &bOutCompatible)
     251             : {
     252         486 :     if (nDepth == 0)
     253             :     {
     254         424 :         bOutPatchable &=
     255         424 :             OGRGeoJSONIsPatchablePosition(poJSonArray, poNativeArray);
     256         424 :         bOutCompatible &=
     257         424 :             OGRGeoJSONIsCompatiblePosition(poJSonArray, poNativeArray);
     258         424 :         return json_object_get_type(poJSonArray) == json_type_array &&
     259         424 :                json_object_get_type(poNativeArray) == json_type_array &&
     260         424 :                json_object_get_type(json_object_array_get_idx(
     261         848 :                    poJSonArray, 0)) != json_type_array &&
     262         383 :                json_object_get_type(json_object_array_get_idx(
     263         424 :                    poNativeArray, 0)) != json_type_array;
     264             :     }
     265             : 
     266         124 :     if (json_object_get_type(poJSonArray) == json_type_array &&
     267          62 :         json_object_get_type(poNativeArray) == json_type_array)
     268             :     {
     269          62 :         const auto nLength = json_object_array_length(poJSonArray);
     270          62 :         if (nLength == json_object_array_length(poNativeArray))
     271             :         {
     272         452 :             for (auto i = decltype(nLength){0}; i < nLength; i++)
     273             :             {
     274             :                 json_object *poJSonChild =
     275         411 :                     json_object_array_get_idx(poJSonArray, i);
     276             :                 json_object *poNativeChild =
     277         411 :                     json_object_array_get_idx(poNativeArray, i);
     278         411 :                 if (!OGRGeoJSONComputePatchableOrCompatibleArrayInternal(
     279             :                         poJSonChild, poNativeChild, nDepth - 1, bOutPatchable,
     280             :                         bOutCompatible))
     281             :                 {
     282          21 :                     return false;
     283             :                 }
     284         390 :                 if (!bOutPatchable && !bOutCompatible)
     285           0 :                     break;
     286             :             }
     287          41 :             return true;
     288             :         }
     289             :     }
     290             : 
     291           0 :     bOutPatchable = false;
     292           0 :     bOutCompatible = false;
     293           0 :     return false;
     294             : }
     295             : 
     296             : /* Returns true if the objects are comparable, ie Point vs Point, LineString
     297             :    vs LineString, but they might not be patchable or compatible */
     298          75 : static bool OGRGeoJSONComputePatchableOrCompatibleArray(
     299             :     json_object *poJSonArray, json_object *poNativeArray, int nDepth,
     300             :     bool &bOutPatchable, bool &bOutCompatible)
     301             : {
     302          75 :     bOutPatchable = true;
     303          75 :     bOutCompatible = true;
     304          75 :     return OGRGeoJSONComputePatchableOrCompatibleArrayInternal(
     305          75 :         poJSonArray, poNativeArray, nDepth, bOutPatchable, bOutCompatible);
     306             : }
     307             : 
     308             : /************************************************************************/
     309             : /*                        OGRGeoJSONPatchArray()                        */
     310             : /************************************************************************/
     311             : 
     312          15 : static void OGRGeoJSONPatchArray(json_object *poJSonArray,
     313             :                                  json_object *poNativeArray, int nDepth)
     314             : {
     315          15 :     if (nDepth == 0)
     316             :     {
     317           6 :         OGRGeoJSONPatchPosition(poJSonArray, poNativeArray);
     318           6 :         return;
     319             :     }
     320           9 :     const auto nLength = json_object_array_length(poJSonArray);
     321          18 :     for (auto i = decltype(nLength){0}; i < nLength; i++)
     322             :     {
     323           9 :         json_object *poJSonChild = json_object_array_get_idx(poJSonArray, i);
     324             :         json_object *poNativeChild =
     325           9 :             json_object_array_get_idx(poNativeArray, i);
     326           9 :         OGRGeoJSONPatchArray(poJSonChild, poNativeChild, nDepth - 1);
     327             :     }
     328             : }
     329             : 
     330             : /************************************************************************/
     331             : /*                        OGRGeoJSONIsPatchableGeometry()                */
     332             : /************************************************************************/
     333             : 
     334        1367 : static bool OGRGeoJSONIsPatchableGeometry(json_object *poJSonGeometry,
     335             :                                           json_object *poNativeGeometry,
     336             :                                           bool &bOutPatchableCoords,
     337             :                                           bool &bOutCompatibleCoords)
     338             : {
     339        2727 :     if (json_object_get_type(poJSonGeometry) != json_type_object ||
     340        1360 :         json_object_get_type(poNativeGeometry) != json_type_object)
     341             :     {
     342        1332 :         return false;
     343             :     }
     344             : 
     345          35 :     json_object *poType = CPL_json_object_object_get(poJSonGeometry, "type");
     346             :     json_object *poNativeType =
     347          35 :         CPL_json_object_object_get(poNativeGeometry, "type");
     348          35 :     if (poType == nullptr || poNativeType == nullptr ||
     349          35 :         json_object_get_type(poType) != json_type_string ||
     350         105 :         json_object_get_type(poNativeType) != json_type_string ||
     351          35 :         strcmp(json_object_get_string(poType),
     352             :                json_object_get_string(poNativeType)) != 0)
     353             :     {
     354           0 :         return false;
     355             :     }
     356             : 
     357             :     json_object_iter it;
     358          35 :     it.key = nullptr;
     359          35 :     it.val = nullptr;
     360          35 :     it.entry = nullptr;
     361          82 :     json_object_object_foreachC(poNativeGeometry, it)
     362             :     {
     363          82 :         if (strcmp(it.key, "coordinates") == 0)
     364             :         {
     365             :             json_object *poJSonCoordinates =
     366          34 :                 CPL_json_object_object_get(poJSonGeometry, "coordinates");
     367          34 :             json_object *poNativeCoordinates = it.val;
     368             :             // 0 = Point
     369             :             // 1 = LineString or MultiPoint
     370             :             // 2 = MultiLineString or Polygon
     371             :             // 3 = MultiPolygon
     372          75 :             for (int i = 0; i <= 3; i++)
     373             :             {
     374          75 :                 if (OGRGeoJSONComputePatchableOrCompatibleArray(
     375             :                         poJSonCoordinates, poNativeCoordinates, i,
     376             :                         bOutPatchableCoords, bOutCompatibleCoords))
     377             :                 {
     378          34 :                     return bOutPatchableCoords || bOutCompatibleCoords;
     379             :                 }
     380             :             }
     381           0 :             return false;
     382             :         }
     383          48 :         if (strcmp(it.key, "geometries") == 0)
     384             :         {
     385             :             json_object *poJSonGeometries =
     386           1 :                 CPL_json_object_object_get(poJSonGeometry, "geometries");
     387           1 :             json_object *poNativeGeometries = it.val;
     388           2 :             if (json_object_get_type(poJSonGeometries) == json_type_array &&
     389           1 :                 json_object_get_type(poNativeGeometries) == json_type_array)
     390             :             {
     391           1 :                 const auto nLength = json_object_array_length(poJSonGeometries);
     392           1 :                 if (nLength == json_object_array_length(poNativeGeometries))
     393             :                 {
     394           7 :                     for (auto i = decltype(nLength){0}; i < nLength; i++)
     395             :                     {
     396             :                         json_object *poJSonChild =
     397           6 :                             json_object_array_get_idx(poJSonGeometries, i);
     398             :                         json_object *poNativeChild =
     399           6 :                             json_object_array_get_idx(poNativeGeometries, i);
     400           6 :                         if (!OGRGeoJSONIsPatchableGeometry(
     401             :                                 poJSonChild, poNativeChild, bOutPatchableCoords,
     402             :                                 bOutCompatibleCoords))
     403             :                         {
     404           0 :                             return false;
     405             :                         }
     406             :                     }
     407           1 :                     return true;
     408             :                 }
     409             :             }
     410           0 :             return false;
     411             :         }
     412             :     }
     413           0 :     return false;
     414             : }
     415             : 
     416             : /************************************************************************/
     417             : /*                        OGRGeoJSONPatchGeometry()                     */
     418             : /************************************************************************/
     419             : 
     420          35 : static void OGRGeoJSONPatchGeometry(json_object *poJSonGeometry,
     421             :                                     json_object *poNativeGeometry,
     422             :                                     bool bPatchableCoordinates,
     423             :                                     const OGRGeoJSONWriteOptions &oOptions)
     424             : {
     425             :     json_object_iter it;
     426          35 :     it.key = nullptr;
     427          35 :     it.val = nullptr;
     428          35 :     it.entry = nullptr;
     429         117 :     json_object_object_foreachC(poNativeGeometry, it)
     430             :     {
     431          82 :         if (strcmp(it.key, "type") == 0 || strcmp(it.key, "bbox") == 0)
     432             :         {
     433          36 :             continue;
     434             :         }
     435          46 :         if (strcmp(it.key, "coordinates") == 0)
     436             :         {
     437          34 :             if (!bPatchableCoordinates &&
     438          28 :                 !oOptions.bCanPatchCoordinatesWithNativeData)
     439             :             {
     440           1 :                 continue;
     441             :             }
     442             : 
     443             :             json_object *poJSonCoordinates =
     444          33 :                 CPL_json_object_object_get(poJSonGeometry, "coordinates");
     445          33 :             json_object *poNativeCoordinates = it.val;
     446         150 :             for (int i = 0; i <= 3; i++)
     447             :             {
     448         123 :                 if (OGRGeoJSONIsPatchableArray(poJSonCoordinates,
     449             :                                                poNativeCoordinates, i))
     450             :                 {
     451           6 :                     OGRGeoJSONPatchArray(poJSonCoordinates, poNativeCoordinates,
     452             :                                          i);
     453           6 :                     break;
     454             :                 }
     455             :             }
     456             : 
     457          33 :             continue;
     458             :         }
     459          12 :         if (strcmp(it.key, "geometries") == 0)
     460             :         {
     461             :             json_object *poJSonGeometries =
     462           1 :                 CPL_json_object_object_get(poJSonGeometry, "geometries");
     463           1 :             json_object *poNativeGeometries = it.val;
     464           1 :             const auto nLength = json_object_array_length(poJSonGeometries);
     465           7 :             for (auto i = decltype(nLength){0}; i < nLength; i++)
     466             :             {
     467             :                 json_object *poJSonChild =
     468           6 :                     json_object_array_get_idx(poJSonGeometries, i);
     469             :                 json_object *poNativeChild =
     470           6 :                     json_object_array_get_idx(poNativeGeometries, i);
     471           6 :                 OGRGeoJSONPatchGeometry(poJSonChild, poNativeChild,
     472             :                                         bPatchableCoordinates, oOptions);
     473             :             }
     474             : 
     475           1 :             continue;
     476             :         }
     477             : 
     478             :         // See https://tools.ietf.org/html/rfc7946#section-7.1
     479          11 :         if (oOptions.bHonourReservedRFC7946Members &&
     480           4 :             (strcmp(it.key, "geometry") == 0 ||
     481           3 :              strcmp(it.key, "properties") == 0 ||
     482           2 :              strcmp(it.key, "features") == 0))
     483             :         {
     484           3 :             continue;
     485             :         }
     486             : 
     487           8 :         json_object_object_add(poJSonGeometry, it.key, json_object_get(it.val));
     488             :     }
     489          35 : }
     490             : 
     491             : /************************************************************************/
     492             : /*                           OGRGeoJSONGetBBox                          */
     493             : /************************************************************************/
     494             : 
     495        1035 : OGREnvelope3D OGRGeoJSONGetBBox(const OGRGeometry *poGeometry,
     496             :                                 const OGRGeoJSONWriteOptions &oOptions)
     497             : {
     498        1035 :     OGREnvelope3D sEnvelope;
     499        1035 :     poGeometry->getEnvelope(&sEnvelope);
     500             : 
     501        1035 :     if (oOptions.bBBOXRFC7946)
     502             :     {
     503             :         // Heuristics to determine if the geometry was split along the
     504             :         // date line.
     505          79 :         const double EPS = 1e-7;
     506             :         const OGRwkbGeometryType eType =
     507          79 :             wkbFlatten(poGeometry->getGeometryType());
     508             :         const bool bMultiPart =
     509         116 :             OGR_GT_IsSubClassOf(eType, wkbGeometryCollection) &&
     510          37 :             poGeometry->toGeometryCollection()->getNumGeometries() >= 2;
     511          79 :         if (bMultiPart && fabs(sEnvelope.MinX - (-180.0)) < EPS &&
     512          24 :             fabs(sEnvelope.MaxX - 180.0) < EPS)
     513             :         {
     514             :             // First heuristics (quite safe) when the geometry looks to
     515             :             // have been really split at the dateline.
     516          24 :             const auto *poGC = poGeometry->toGeometryCollection();
     517          24 :             double dfWestLimit = -180.0;
     518          24 :             double dfEastLimit = 180.0;
     519          24 :             bool bWestLimitIsInit = false;
     520          24 :             bool bEastLimitIsInit = false;
     521          72 :             for (const auto *poMember : poGC)
     522             :             {
     523          48 :                 OGREnvelope sEnvelopePart;
     524          48 :                 if (poMember->IsEmpty())
     525           0 :                     continue;
     526          48 :                 poMember->getEnvelope(&sEnvelopePart);
     527          48 :                 const bool bTouchesMinus180 =
     528          48 :                     fabs(sEnvelopePart.MinX - (-180.0)) < EPS;
     529          48 :                 const bool bTouchesPlus180 =
     530          48 :                     fabs(sEnvelopePart.MaxX - 180.0) < EPS;
     531          48 :                 if (bTouchesMinus180 && !bTouchesPlus180)
     532             :                 {
     533          24 :                     if (sEnvelopePart.MaxX > dfEastLimit || !bEastLimitIsInit)
     534             :                     {
     535          24 :                         bEastLimitIsInit = true;
     536          24 :                         dfEastLimit = sEnvelopePart.MaxX;
     537             :                     }
     538             :                 }
     539          24 :                 else if (bTouchesPlus180 && !bTouchesMinus180)
     540             :                 {
     541          24 :                     if (sEnvelopePart.MinX < dfWestLimit || !bWestLimitIsInit)
     542             :                     {
     543          24 :                         bWestLimitIsInit = true;
     544          24 :                         dfWestLimit = sEnvelopePart.MinX;
     545             :                     }
     546             :                 }
     547           0 :                 else if (!bTouchesMinus180 && !bTouchesPlus180)
     548             :                 {
     549           0 :                     if (sEnvelopePart.MinX > 0 &&
     550           0 :                         (sEnvelopePart.MinX < dfWestLimit || !bWestLimitIsInit))
     551             :                     {
     552           0 :                         bWestLimitIsInit = true;
     553           0 :                         dfWestLimit = sEnvelopePart.MinX;
     554             :                     }
     555           0 :                     else if (sEnvelopePart.MaxX < 0 &&
     556           0 :                              (sEnvelopePart.MaxX > dfEastLimit ||
     557           0 :                               !bEastLimitIsInit))
     558             :                     {
     559           0 :                         bEastLimitIsInit = true;
     560           0 :                         dfEastLimit = sEnvelopePart.MaxX;
     561             :                     }
     562             :                 }
     563             :             }
     564          24 :             sEnvelope.MinX = dfWestLimit;
     565          24 :             sEnvelope.MaxX = dfEastLimit;
     566             :         }
     567          55 :         else if (bMultiPart && sEnvelope.MaxX - sEnvelope.MinX > 180 &&
     568          10 :                  sEnvelope.MinX >= -180 && sEnvelope.MaxX <= 180)
     569             :         {
     570             :             // More fragile heuristics for a geometry like Alaska
     571             :             // (https://github.com/qgis/QGIS/issues/42827) which spans over
     572             :             // the antimeridian but does not touch it.
     573          10 :             const auto *poGC = poGeometry->toGeometryCollection();
     574          10 :             double dfWestLimit = std::numeric_limits<double>::infinity();
     575          10 :             double dfEastLimit = -std::numeric_limits<double>::infinity();
     576          32 :             for (const auto *poMember : poGC)
     577             :             {
     578          28 :                 OGREnvelope sEnvelopePart;
     579          28 :                 if (poMember->IsEmpty())
     580           0 :                     continue;
     581          28 :                 poMember->getEnvelope(&sEnvelopePart);
     582          28 :                 if (sEnvelopePart.MinX > -120 && sEnvelopePart.MaxX < 120)
     583             :                 {
     584           6 :                     dfWestLimit = std::numeric_limits<double>::infinity();
     585           6 :                     dfEastLimit = -std::numeric_limits<double>::infinity();
     586           6 :                     break;
     587             :                 }
     588          22 :                 if (sEnvelopePart.MinX > 0)
     589             :                 {
     590          12 :                     dfWestLimit = std::min(dfWestLimit, sEnvelopePart.MinX);
     591             :                 }
     592             :                 else
     593             :                 {
     594          10 :                     CPLAssert(sEnvelopePart.MaxX < 0);
     595          10 :                     dfEastLimit = std::max(dfEastLimit, sEnvelopePart.MaxX);
     596             :                 }
     597             :             }
     598          14 :             if (dfWestLimit != std::numeric_limits<double>::infinity() &&
     599           4 :                 dfEastLimit + 360 - dfWestLimit < 180)
     600             :             {
     601           2 :                 sEnvelope.MinX = dfWestLimit;
     602           2 :                 sEnvelope.MaxX = dfEastLimit;
     603             :             }
     604             :         }
     605             :     }
     606             : 
     607        1035 :     return sEnvelope;
     608             : }
     609             : 
     610             : /************************************************************************/
     611             : /*                           OGRGeoJSONWriteFeature                     */
     612             : /************************************************************************/
     613             : 
     614        1483 : json_object *OGRGeoJSONWriteFeature(OGRFeature *poFeature,
     615             :                                     const OGRGeoJSONWriteOptions &oOptions)
     616             : {
     617        1483 :     CPLAssert(nullptr != poFeature);
     618             : 
     619        1483 :     bool bWriteBBOX = oOptions.bWriteBBOX;
     620             : 
     621        1483 :     json_object *poObj = json_object_new_object();
     622        1483 :     CPLAssert(nullptr != poObj);
     623             : 
     624        1483 :     json_object_object_add(poObj, "type", json_object_new_string("Feature"));
     625             : 
     626             :     /* -------------------------------------------------------------------- */
     627             :     /*      Write native JSon data.                                         */
     628             :     /* -------------------------------------------------------------------- */
     629        1483 :     bool bIdAlreadyWritten = false;
     630        1483 :     const char *pszNativeMediaType = poFeature->GetNativeMediaType();
     631        1483 :     json_object *poNativeGeom = nullptr;
     632        1483 :     bool bHasProperties = true;
     633        1483 :     bool bWriteIdIfFoundInAttributes = true;
     634        1483 :     if (pszNativeMediaType &&
     635          44 :         EQUAL(pszNativeMediaType, "application/vnd.geo+json"))
     636             :     {
     637          44 :         const char *pszNativeData = poFeature->GetNativeData();
     638          44 :         json_object *poNativeJSon = nullptr;
     639          88 :         if (pszNativeData && OGRJSonParse(pszNativeData, &poNativeJSon) &&
     640          44 :             json_object_get_type(poNativeJSon) == json_type_object)
     641             :         {
     642             :             json_object_iter it;
     643          44 :             it.key = nullptr;
     644          44 :             it.val = nullptr;
     645          44 :             it.entry = nullptr;
     646          88 :             CPLString osNativeData;
     647          44 :             bHasProperties = false;
     648         199 :             json_object_object_foreachC(poNativeJSon, it)
     649             :             {
     650         155 :                 if (strcmp(it.key, "type") == 0)
     651             :                 {
     652          44 :                     continue;
     653             :                 }
     654         111 :                 if (strcmp(it.key, "properties") == 0)
     655             :                 {
     656          43 :                     bHasProperties = true;
     657          43 :                     continue;
     658             :                 }
     659          68 :                 if (strcmp(it.key, "bbox") == 0)
     660             :                 {
     661           2 :                     bWriteBBOX = true;
     662           2 :                     continue;
     663             :                 }
     664          66 :                 if (strcmp(it.key, "geometry") == 0)
     665             :                 {
     666          44 :                     poNativeGeom = json_object_get(it.val);
     667          44 :                     continue;
     668             :                 }
     669          22 :                 if (strcmp(it.key, "id") == 0)
     670             :                 {
     671          13 :                     const auto eType = json_object_get_type(it.val);
     672             :                     // See https://tools.ietf.org/html/rfc7946#section-3.2
     673          13 :                     if (oOptions.bHonourReservedRFC7946Members &&
     674           1 :                         !oOptions.bForceIDFieldType &&
     675           1 :                         eType != json_type_string && eType != json_type_int &&
     676             :                         eType != json_type_double)
     677             :                     {
     678           1 :                         continue;
     679             :                     }
     680             : 
     681          12 :                     bIdAlreadyWritten = true;
     682             : 
     683          12 :                     if (it.val && oOptions.bForceIDFieldType &&
     684           4 :                         oOptions.eForcedIDFieldType == OFTInteger64)
     685             :                     {
     686           2 :                         if (eType != json_type_int)
     687             :                         {
     688           2 :                             json_object_object_add(
     689           1 :                                 poObj, it.key,
     690           1 :                                 json_object_new_int64(CPLAtoGIntBig(
     691             :                                     json_object_get_string(it.val))));
     692           1 :                             bWriteIdIfFoundInAttributes = false;
     693           1 :                             continue;
     694             :                         }
     695             :                     }
     696          10 :                     else if (it.val && oOptions.bForceIDFieldType &&
     697           2 :                              oOptions.eForcedIDFieldType == OFTString)
     698             :                     {
     699           2 :                         if (eType != json_type_string)
     700             :                         {
     701           1 :                             json_object_object_add(
     702           1 :                                 poObj, it.key,
     703             :                                 json_object_new_string(
     704             :                                     json_object_get_string(it.val)));
     705           1 :                             bWriteIdIfFoundInAttributes = false;
     706           1 :                             continue;
     707             :                         }
     708             :                     }
     709             : 
     710          10 :                     if (it.val != nullptr)
     711             :                     {
     712             :                         int nIdx =
     713          10 :                             poFeature->GetDefnRef()->GetFieldIndexCaseSensitive(
     714             :                                 "id");
     715           6 :                         if (eType == json_type_string && nIdx >= 0 &&
     716           3 :                             poFeature->GetFieldDefnRef(nIdx)->GetType() ==
     717          13 :                                 OFTString &&
     718           3 :                             strcmp(json_object_get_string(it.val),
     719             :                                    poFeature->GetFieldAsString(nIdx)) == 0)
     720             :                         {
     721           3 :                             bWriteIdIfFoundInAttributes = false;
     722             :                         }
     723           7 :                         else if (eType == json_type_int && nIdx >= 0 &&
     724           0 :                                  (poFeature->GetFieldDefnRef(nIdx)->GetType() ==
     725           0 :                                       OFTInteger ||
     726           0 :                                   poFeature->GetFieldDefnRef(nIdx)->GetType() ==
     727          14 :                                       OFTInteger64) &&
     728           0 :                                  json_object_get_int64(it.val) ==
     729           0 :                                      poFeature->GetFieldAsInteger64(nIdx))
     730             :                         {
     731           0 :                             bWriteIdIfFoundInAttributes = false;
     732             :                         }
     733             :                     }
     734             :                 }
     735             : 
     736             :                 // See https://tools.ietf.org/html/rfc7946#section-7.1
     737          19 :                 if (oOptions.bHonourReservedRFC7946Members &&
     738           4 :                     (strcmp(it.key, "coordinates") == 0 ||
     739           3 :                      strcmp(it.key, "geometries") == 0 ||
     740           2 :                      strcmp(it.key, "features") == 0))
     741             :                 {
     742           3 :                     continue;
     743             :                 }
     744             : 
     745          16 :                 json_object_object_add(poObj, it.key, json_object_get(it.val));
     746             :             }
     747          44 :             json_object_put(poNativeJSon);
     748             :         }
     749             :     }
     750             : 
     751             :     /* -------------------------------------------------------------------- */
     752             :     /*      Write FID if available                                          */
     753             :     /* -------------------------------------------------------------------- */
     754        1483 :     OGRGeoJSONWriteId(poFeature, poObj, bIdAlreadyWritten, oOptions);
     755             : 
     756             :     /* -------------------------------------------------------------------- */
     757             :     /*      Write feature attributes to GeoJSON "properties" object.        */
     758             :     /* -------------------------------------------------------------------- */
     759        1483 :     if (bHasProperties)
     760             :     {
     761        1482 :         json_object *poObjProps = OGRGeoJSONWriteAttributes(
     762             :             poFeature, bWriteIdIfFoundInAttributes, oOptions);
     763        1482 :         json_object_object_add(poObj, "properties", poObjProps);
     764             :     }
     765             : 
     766             :     /* -------------------------------------------------------------------- */
     767             :     /*      Write feature geometry to GeoJSON "geometry" object.            */
     768             :     /*      Null geometries are allowed, according to the GeoJSON Spec.     */
     769             :     /* -------------------------------------------------------------------- */
     770        1483 :     json_object *poObjGeom = nullptr;
     771             : 
     772        1483 :     OGRGeometry *poGeometry = poFeature->GetGeometryRef();
     773        1483 :     if (nullptr != poGeometry)
     774             :     {
     775        1361 :         poObjGeom = OGRGeoJSONWriteGeometry(poGeometry, oOptions);
     776             : 
     777        1361 :         if (bWriteBBOX && !poGeometry->IsEmpty())
     778             :         {
     779          42 :             OGREnvelope3D sEnvelope = OGRGeoJSONGetBBox(poGeometry, oOptions);
     780             : 
     781          42 :             json_object *poObjBBOX = json_object_new_array();
     782          42 :             json_object_array_add(
     783             :                 poObjBBOX, json_object_new_coord(sEnvelope.MinX, 1, oOptions));
     784          42 :             json_object_array_add(
     785             :                 poObjBBOX, json_object_new_coord(sEnvelope.MinY, 2, oOptions));
     786          42 :             if (wkbHasZ(poGeometry->getGeometryType()))
     787           1 :                 json_object_array_add(
     788             :                     poObjBBOX,
     789             :                     json_object_new_coord(sEnvelope.MinZ, 3, oOptions));
     790          42 :             json_object_array_add(
     791             :                 poObjBBOX, json_object_new_coord(sEnvelope.MaxX, 1, oOptions));
     792          42 :             json_object_array_add(
     793             :                 poObjBBOX, json_object_new_coord(sEnvelope.MaxY, 2, oOptions));
     794          42 :             if (wkbHasZ(poGeometry->getGeometryType()))
     795           1 :                 json_object_array_add(
     796             :                     poObjBBOX,
     797             :                     json_object_new_coord(sEnvelope.MaxZ, 3, oOptions));
     798             : 
     799          42 :             json_object_object_add(poObj, "bbox", poObjBBOX);
     800             :         }
     801             : 
     802        1361 :         bool bOutPatchableCoords = false;
     803        1361 :         bool bOutCompatibleCoords = false;
     804        1361 :         if (OGRGeoJSONIsPatchableGeometry(poObjGeom, poNativeGeom,
     805             :                                           bOutPatchableCoords,
     806             :                                           bOutCompatibleCoords))
     807             :         {
     808          29 :             OGRGeoJSONPatchGeometry(poObjGeom, poNativeGeom,
     809             :                                     bOutPatchableCoords, oOptions);
     810             :         }
     811             :     }
     812             : 
     813        1483 :     json_object_object_add(poObj, "geometry", poObjGeom);
     814             : 
     815        1483 :     if (poNativeGeom != nullptr)
     816          29 :         json_object_put(poNativeGeom);
     817             : 
     818        1483 :     return poObj;
     819             : }
     820             : 
     821             : /************************************************************************/
     822             : /*                        OGRGeoJSONWriteId                            */
     823             : /************************************************************************/
     824             : 
     825        1656 : void OGRGeoJSONWriteId(const OGRFeature *poFeature, json_object *poObj,
     826             :                        bool bIdAlreadyWritten,
     827             :                        const OGRGeoJSONWriteOptions &oOptions)
     828             : {
     829        1656 :     if (!oOptions.osIDField.empty())
     830             :     {
     831           4 :         int nIdx = poFeature->GetDefnRef()->GetFieldIndexCaseSensitive(
     832             :             oOptions.osIDField);
     833           4 :         if (nIdx >= 0)
     834             :         {
     835          10 :             if ((oOptions.bForceIDFieldType &&
     836           7 :                  oOptions.eForcedIDFieldType == OFTInteger64) ||
     837           5 :                 (!oOptions.bForceIDFieldType &&
     838           4 :                  (poFeature->GetFieldDefnRef(nIdx)->GetType() == OFTInteger ||
     839           2 :                   poFeature->GetFieldDefnRef(nIdx)->GetType() == OFTInteger64)))
     840             :             {
     841           2 :                 json_object_object_add(
     842             :                     poObj, "id",
     843             :                     json_object_new_int64(
     844           2 :                         poFeature->GetFieldAsInteger64(nIdx)));
     845             :             }
     846             :             else
     847             :             {
     848           2 :                 json_object_object_add(
     849             :                     poObj, "id",
     850             :                     json_object_new_string(poFeature->GetFieldAsString(nIdx)));
     851             :             }
     852             :         }
     853             :     }
     854        1652 :     else if (poFeature->GetFID() != OGRNullFID && !bIdAlreadyWritten)
     855             :     {
     856          17 :         if (oOptions.bForceIDFieldType &&
     857           4 :             oOptions.eForcedIDFieldType == OFTString)
     858             :         {
     859           2 :             json_object_object_add(poObj, "id",
     860             :                                    json_object_new_string(CPLSPrintf(
     861             :                                        CPL_FRMT_GIB, poFeature->GetFID())));
     862             :         }
     863             :         else
     864             :         {
     865          15 :             json_object_object_add(poObj, "id",
     866          15 :                                    json_object_new_int64(poFeature->GetFID()));
     867             :         }
     868             :     }
     869        1656 : }
     870             : 
     871             : /************************************************************************/
     872             : /*                        OGRGeoJSONWriteAttributes                     */
     873             : /************************************************************************/
     874             : 
     875        1655 : json_object *OGRGeoJSONWriteAttributes(OGRFeature *poFeature,
     876             :                                        bool bWriteIdIfFoundInAttributes,
     877             :                                        const OGRGeoJSONWriteOptions &oOptions)
     878             : {
     879        1655 :     CPLAssert(nullptr != poFeature);
     880             : 
     881        1655 :     json_object *poObjProps = json_object_new_object();
     882        1655 :     CPLAssert(nullptr != poObjProps);
     883             : 
     884        1655 :     const OGRFeatureDefn *poDefn = poFeature->GetDefnRef();
     885             : 
     886             :     const int nIDField =
     887        1655 :         !oOptions.osIDField.empty()
     888        1655 :             ? poDefn->GetFieldIndexCaseSensitive(oOptions.osIDField)
     889        1655 :             : -1;
     890             : 
     891        1655 :     constexpr int MAX_SIGNIFICANT_DIGITS_FLOAT32 = 8;
     892             :     const int nFloat32SignificantDigits =
     893        1655 :         oOptions.nSignificantFigures >= 0
     894        1657 :             ? std::min(oOptions.nSignificantFigures,
     895           2 :                        MAX_SIGNIFICANT_DIGITS_FLOAT32)
     896        1655 :             : MAX_SIGNIFICANT_DIGITS_FLOAT32;
     897             : 
     898        1655 :     const int nFieldCount = poDefn->GetFieldCount();
     899             : 
     900        1655 :     json_object *poNativeObjProp = nullptr;
     901        1655 :     json_object *poProperties = nullptr;
     902             : 
     903             :     // Scan the fields to determine if there is a chance of
     904             :     // mixed types and we can use native media
     905        1655 :     bool bUseNativeMedia{false};
     906             : 
     907        1655 :     if (poFeature->GetNativeMediaType() &&
     908          43 :         strcmp(poFeature->GetNativeMediaType(), "application/vnd.geo+json") ==
     909        1698 :             0 &&
     910          43 :         poFeature->GetNativeData())
     911             :     {
     912         107 :         for (int nField = 0; nField < nFieldCount; ++nField)
     913             :         {
     914          69 :             if (poDefn->GetFieldDefn(nField)->GetSubType() == OFSTJSON)
     915             :             {
     916           5 :                 if (OGRJSonParse(poFeature->GetNativeData(), &poNativeObjProp,
     917             :                                  false))
     918             :                 {
     919           5 :                     poProperties = OGRGeoJSONFindMemberByName(poNativeObjProp,
     920             :                                                               "properties");
     921           5 :                     bUseNativeMedia = poProperties != nullptr;
     922             :                 }
     923           5 :                 break;
     924             :             }
     925             :         }
     926             :     }
     927             : 
     928        4120 :     for (int nField = 0; nField < nFieldCount; ++nField)
     929             :     {
     930        2465 :         if (!poFeature->IsFieldSet(nField) || nField == nIDField)
     931             :         {
     932         595 :             continue;
     933             :         }
     934             : 
     935        1877 :         const OGRFieldDefn *poFieldDefn = poDefn->GetFieldDefn(nField);
     936        1877 :         CPLAssert(nullptr != poFieldDefn);
     937        1877 :         const OGRFieldType eType = poFieldDefn->GetType();
     938        1877 :         const OGRFieldSubType eSubType = poFieldDefn->GetSubType();
     939             : 
     940        1889 :         if (!bWriteIdIfFoundInAttributes &&
     941          12 :             strcmp(poFieldDefn->GetNameRef(), "id") == 0)
     942             :         {
     943           4 :             continue;
     944             :         }
     945             : 
     946        1873 :         json_object *poObjProp = nullptr;
     947             : 
     948        1873 :         if (poFeature->IsFieldNull(nField))
     949             :         {
     950             :             // poObjProp = NULL;
     951             :         }
     952        1871 :         else if (OFTInteger == eType)
     953             :         {
     954         981 :             if (eSubType == OFSTBoolean)
     955           2 :                 poObjProp = json_object_new_boolean(
     956             :                     poFeature->GetFieldAsInteger(nField));
     957             :             else
     958         979 :                 poObjProp =
     959         979 :                     json_object_new_int(poFeature->GetFieldAsInteger(nField));
     960             :         }
     961         890 :         else if (OFTInteger64 == eType)
     962             :         {
     963          49 :             if (eSubType == OFSTBoolean)
     964           0 :                 poObjProp = json_object_new_boolean(static_cast<json_bool>(
     965           0 :                     poFeature->GetFieldAsInteger64(nField)));
     966             :             else
     967          49 :                 poObjProp = json_object_new_int64(
     968          49 :                     poFeature->GetFieldAsInteger64(nField));
     969             :         }
     970         841 :         else if (OFTReal == eType)
     971             :         {
     972         235 :             const double val = poFeature->GetFieldAsDouble(nField);
     973         235 :             if (!std::isfinite(val))
     974             :             {
     975           6 :                 if (!oOptions.bAllowNonFiniteValues)
     976             :                 {
     977           3 :                     CPLErrorOnce(CE_Warning, CPLE_AppDefined,
     978             :                                  "NaN of Infinity value found. Skipped");
     979           3 :                     continue;
     980             :                 }
     981             :             }
     982         232 :             if (eSubType == OFSTFloat32)
     983             :             {
     984           2 :                 poObjProp = json_object_new_float_with_significant_figures(
     985             :                     static_cast<float>(val), nFloat32SignificantDigits);
     986             :             }
     987             :             else
     988             :             {
     989         230 :                 poObjProp = json_object_new_double_with_significant_figures(
     990         230 :                     val, oOptions.nSignificantFigures);
     991             :             }
     992             :         }
     993         606 :         else if (OFTString == eType)
     994             :         {
     995         270 :             const char *pszStr = poFeature->GetFieldAsString(nField);
     996         270 :             const size_t nLen = strlen(pszStr);
     997             : 
     998         270 :             if (eSubType == OFSTJSON ||
     999         259 :                 (oOptions.bAutodetectJsonStrings &&
    1000         257 :                  ((pszStr[0] == '{' && pszStr[nLen - 1] == '}') ||
    1001         256 :                   (pszStr[0] == '[' && pszStr[nLen - 1] == ']'))))
    1002             :             {
    1003          14 :                 if (bUseNativeMedia)
    1004             :                 {
    1005           5 :                     if (json_object *poProperty = OGRGeoJSONFindMemberByName(
    1006             :                             poProperties, poFieldDefn->GetNameRef()))
    1007             :                     {
    1008           5 :                         const char *pszProp{json_object_get_string(poProperty)};
    1009           5 :                         if (pszProp && strcmp(pszProp, pszStr) == 0)
    1010             :                         {
    1011           5 :                             poObjProp = json_object_get(poProperty);
    1012             :                         }
    1013             :                     }
    1014             :                 }
    1015             : 
    1016          14 :                 if (poObjProp == nullptr)
    1017             :                 {
    1018           9 :                     if ((pszStr[0] == '{' && pszStr[nLen - 1] == '}') ||
    1019           6 :                         (pszStr[0] == '[' && pszStr[nLen - 1] == ']'))
    1020             :                     {
    1021           7 :                         OGRJSonParse(pszStr, &poObjProp, false);
    1022             :                     }
    1023             :                 }
    1024             :             }
    1025             : 
    1026         270 :             if (poObjProp == nullptr)
    1027         258 :                 poObjProp = json_object_new_string(pszStr);
    1028             :         }
    1029         336 :         else if (OFTIntegerList == eType)
    1030             :         {
    1031           2 :             int nSize = 0;
    1032             :             const int *panList =
    1033           2 :                 poFeature->GetFieldAsIntegerList(nField, &nSize);
    1034           2 :             poObjProp = json_object_new_array();
    1035           5 :             for (int i = 0; i < nSize; i++)
    1036             :             {
    1037           3 :                 if (eSubType == OFSTBoolean)
    1038           2 :                     json_object_array_add(poObjProp,
    1039           2 :                                           json_object_new_boolean(panList[i]));
    1040             :                 else
    1041           1 :                     json_object_array_add(poObjProp,
    1042           1 :                                           json_object_new_int(panList[i]));
    1043             :             }
    1044             :         }
    1045         334 :         else if (OFTInteger64List == eType)
    1046             :         {
    1047          10 :             int nSize = 0;
    1048             :             const GIntBig *panList =
    1049          10 :                 poFeature->GetFieldAsInteger64List(nField, &nSize);
    1050          10 :             poObjProp = json_object_new_array();
    1051          28 :             for (int i = 0; i < nSize; i++)
    1052             :             {
    1053          18 :                 if (eSubType == OFSTBoolean)
    1054           0 :                     json_object_array_add(
    1055             :                         poObjProp, json_object_new_boolean(
    1056           0 :                                        static_cast<json_bool>(panList[i])));
    1057             :                 else
    1058          18 :                     json_object_array_add(poObjProp,
    1059          18 :                                           json_object_new_int64(panList[i]));
    1060             :             }
    1061             :         }
    1062         324 :         else if (OFTRealList == eType)
    1063             :         {
    1064           2 :             int nSize = 0;
    1065             :             const double *padfList =
    1066           2 :                 poFeature->GetFieldAsDoubleList(nField, &nSize);
    1067           2 :             poObjProp = json_object_new_array();
    1068          10 :             for (int i = 0; i < nSize; i++)
    1069             :             {
    1070           8 :                 if (eSubType == OFSTFloat32)
    1071             :                 {
    1072           7 :                     json_object_array_add(
    1073             :                         poObjProp,
    1074             :                         json_object_new_float_with_significant_figures(
    1075           7 :                             static_cast<float>(padfList[i]),
    1076             :                             nFloat32SignificantDigits));
    1077             :                 }
    1078             :                 else
    1079             :                 {
    1080           1 :                     json_object_array_add(
    1081             :                         poObjProp,
    1082             :                         json_object_new_double_with_significant_figures(
    1083           1 :                             padfList[i], oOptions.nSignificantFigures));
    1084             :                 }
    1085             :             }
    1086             :         }
    1087         322 :         else if (OFTStringList == eType)
    1088             :         {
    1089           1 :             char **papszStringList = poFeature->GetFieldAsStringList(nField);
    1090           1 :             poObjProp = json_object_new_array();
    1091           3 :             for (int i = 0; papszStringList && papszStringList[i]; i++)
    1092             :             {
    1093           2 :                 json_object_array_add(
    1094           2 :                     poObjProp, json_object_new_string(papszStringList[i]));
    1095             :             }
    1096             :         }
    1097         321 :         else if (OFTDateTime == eType || OFTDate == eType)
    1098             :         {
    1099         319 :             char *pszDT = OGRGetXMLDateTime(poFeature->GetRawFieldRef(nField));
    1100         319 :             if (eType == OFTDate)
    1101             :             {
    1102         157 :                 char *pszT = strchr(pszDT, 'T');
    1103         157 :                 if (pszT)
    1104         157 :                     *pszT = 0;
    1105             :             }
    1106         319 :             poObjProp = json_object_new_string(pszDT);
    1107         319 :             CPLFree(pszDT);
    1108             :         }
    1109             :         else
    1110             :         {
    1111           2 :             poObjProp =
    1112           2 :                 json_object_new_string(poFeature->GetFieldAsString(nField));
    1113             :         }
    1114             : 
    1115        1870 :         json_object_object_add(poObjProps, poFieldDefn->GetNameRef(),
    1116             :                                poObjProp);
    1117             :     }
    1118             : 
    1119        1655 :     if (bUseNativeMedia)
    1120             :     {
    1121           5 :         json_object_put(poNativeObjProp);
    1122             :     }
    1123             : 
    1124        1655 :     return poObjProps;
    1125             : }
    1126             : 
    1127             : /************************************************************************/
    1128             : /*                           OGRGeoJSONWriteGeometry                    */
    1129             : /************************************************************************/
    1130             : 
    1131        1831 : json_object *OGRGeoJSONWriteGeometry(const OGRGeometry *poGeometry,
    1132             :                                      const OGRGeoJSONWriteOptions &oOptions)
    1133             : {
    1134        1831 :     if (poGeometry == nullptr)
    1135             :     {
    1136           0 :         CPLAssert(false);
    1137             :         return nullptr;
    1138             :     }
    1139             : 
    1140        1831 :     if (!oOptions.bAllowCurve && poGeometry->hasCurveGeometry(true))
    1141             :     {
    1142          44 :         auto poGeomClone = std::unique_ptr<OGRGeometry>(poGeometry->clone());
    1143             :         const OGRwkbGeometryType eTargetType =
    1144          22 :             OGR_GT_GetLinear(poGeometry->getGeometryType());
    1145          22 :         poGeomClone.reset(
    1146             :             OGRGeometryFactory::forceTo(poGeomClone.release(), eTargetType));
    1147          22 :         return OGRGeoJSONWriteGeometry(poGeomClone.get(), oOptions);
    1148             :     }
    1149             : 
    1150        1809 :     OGRwkbGeometryType eFType = wkbFlatten(poGeometry->getGeometryType());
    1151             :     // For point empty, return a null geometry. For other empty geometry types,
    1152             :     // we will generate an empty coordinate array, which is probably also
    1153             :     // borderline.
    1154        1809 :     if (eFType == wkbPoint && poGeometry->IsEmpty())
    1155             :     {
    1156           2 :         return nullptr;
    1157             :     }
    1158             : 
    1159           0 :     std::unique_ptr<OGRGeometry> poTmpGeom;  // keep in that scope
    1160        1807 :     if (eFType == wkbCircularString)
    1161             :     {
    1162          24 :         auto poCS = poGeometry->toCircularString();
    1163          24 :         const int nNumPoints = poCS->getNumPoints();
    1164          24 :         constexpr int MAX_POINTS_PER_CC = 11;
    1165          24 :         if (nNumPoints > MAX_POINTS_PER_CC)
    1166             :         {
    1167           2 :             auto poCC = std::make_unique<OGRCompoundCurve>();
    1168           1 :             auto poSubCS = std::make_unique<OGRCircularString>();
    1169          14 :             for (int i = 0; i < nNumPoints; ++i)
    1170             :             {
    1171          26 :                 OGRPoint oPoint;
    1172          13 :                 poCS->getPoint(i, &oPoint);
    1173          13 :                 poSubCS->addPoint(&oPoint);
    1174          13 :                 if (poSubCS->getNumPoints() == MAX_POINTS_PER_CC)
    1175             :                 {
    1176           1 :                     poCC->addCurve(std::move(poSubCS));
    1177           1 :                     poSubCS = std::make_unique<OGRCircularString>();
    1178           1 :                     poSubCS->addPoint(&oPoint);
    1179             :                 }
    1180             :             }
    1181           1 :             if (poSubCS->getNumPoints() > 1)
    1182           1 :                 poCC->addCurve(std::move(poSubCS));
    1183           1 :             poTmpGeom = std::move(poCC);
    1184           1 :             poGeometry = poTmpGeom.get();
    1185           1 :             eFType = wkbCompoundCurve;
    1186             :         }
    1187             :     }
    1188             : 
    1189        1807 :     json_object *poObj = json_object_new_object();
    1190        1807 :     CPLAssert(nullptr != poObj);
    1191             : 
    1192             :     /* -------------------------------------------------------------------- */
    1193             :     /*      Build "type" member of GeoJSON "geometry" object.               */
    1194             :     /* -------------------------------------------------------------------- */
    1195             : 
    1196        1807 :     const char *pszName = OGRGeoJSONGetGeometryName(poGeometry);
    1197        1807 :     json_object_object_add(poObj, "type", json_object_new_string(pszName));
    1198             : 
    1199             :     /* -------------------------------------------------------------------- */
    1200             :     /*      Build "coordinates" member of GeoJSON "geometry" object.        */
    1201             :     /* -------------------------------------------------------------------- */
    1202        1807 :     json_object *poObjGeom = nullptr;
    1203             : 
    1204        1807 :     if (eFType == wkbGeometryCollection || eFType == wkbMultiCurve ||
    1205             :         eFType == wkbMultiSurface)
    1206             :     {
    1207          33 :         poObjGeom = OGRGeoJSONWriteGeometryCollection(
    1208             :             poGeometry->toGeometryCollection(), oOptions);
    1209          33 :         json_object_object_add(poObj, "geometries", poObjGeom);
    1210             :     }
    1211        1774 :     else if (eFType == wkbCompoundCurve)
    1212             :     {
    1213          14 :         poObjGeom = OGRGeoJSONWriteCompoundCurve(poGeometry->toCompoundCurve(),
    1214             :                                                  oOptions);
    1215          14 :         json_object_object_add(poObj, "geometries", poObjGeom);
    1216             :     }
    1217        1760 :     else if (eFType == wkbCurvePolygon)
    1218             :     {
    1219             :         poObjGeom =
    1220           9 :             OGRGeoJSONWriteCurvePolygon(poGeometry->toCurvePolygon(), oOptions);
    1221           9 :         json_object_object_add(poObj, "geometries", poObjGeom);
    1222             :     }
    1223             :     else
    1224             :     {
    1225        1751 :         if (wkbPoint == eFType)
    1226         178 :             poObjGeom = OGRGeoJSONWritePoint(poGeometry->toPoint(), oOptions);
    1227        1573 :         else if (wkbLineString == eFType || wkbCircularString == eFType)
    1228         120 :             poObjGeom = OGRGeoJSONWriteSimpleCurve(poGeometry->toSimpleCurve(),
    1229             :                                                    oOptions);
    1230        1453 :         else if (wkbPolygon == eFType)
    1231             :             poObjGeom =
    1232        1302 :                 OGRGeoJSONWritePolygon(poGeometry->toPolygon(), oOptions);
    1233         151 :         else if (wkbMultiPoint == eFType)
    1234             :             poObjGeom =
    1235          64 :                 OGRGeoJSONWriteMultiPoint(poGeometry->toMultiPoint(), oOptions);
    1236          87 :         else if (wkbMultiLineString == eFType)
    1237          38 :             poObjGeom = OGRGeoJSONWriteMultiLineString(
    1238             :                 poGeometry->toMultiLineString(), oOptions);
    1239          49 :         else if (wkbMultiPolygon == eFType)
    1240          48 :             poObjGeom = OGRGeoJSONWriteMultiPolygon(
    1241             :                 poGeometry->toMultiPolygon(), oOptions);
    1242             :         else
    1243             :         {
    1244           1 :             CPLError(
    1245             :                 CE_Failure, CPLE_NotSupported,
    1246             :                 "OGR geometry type unsupported as a GeoJSON geometry detected. "
    1247             :                 "Feature gets NULL geometry assigned.");
    1248             :         }
    1249             : 
    1250        1751 :         if (poObjGeom != nullptr)
    1251             :         {
    1252        1742 :             json_object_object_add(poObj, "coordinates", poObjGeom);
    1253             :         }
    1254             :         else
    1255             :         {
    1256           9 :             json_object_put(poObj);
    1257           9 :             poObj = nullptr;
    1258             :         }
    1259             :     }
    1260             : 
    1261        1807 :     return poObj;
    1262             : }
    1263             : 
    1264             : /************************************************************************/
    1265             : /*                           OGRGeoJSONWritePoint                       */
    1266             : /************************************************************************/
    1267             : 
    1268         370 : json_object *OGRGeoJSONWritePoint(const OGRPoint *poPoint,
    1269             :                                   const OGRGeoJSONWriteOptions &oOptions)
    1270             : {
    1271         370 :     CPLAssert(nullptr != poPoint);
    1272             : 
    1273         370 :     json_object *poObj = nullptr;
    1274             : 
    1275             :     // Generate "coordinates" object
    1276         370 :     if (!poPoint->IsEmpty())
    1277             :     {
    1278         370 :         if (oOptions.bAllowMeasure && poPoint->IsMeasured())
    1279             :         {
    1280           6 :             if (poPoint->Is3D())
    1281             :             {
    1282           1 :                 poObj = OGRGeoJSONWriteCoords(poPoint->getX(), poPoint->getY(),
    1283           2 :                                               poPoint->getZ(), poPoint->getM(),
    1284             :                                               oOptions);
    1285             :             }
    1286             :             else
    1287             :             {
    1288           5 :                 poObj = OGRGeoJSONWriteCoords(poPoint->getX(), poPoint->getY(),
    1289          10 :                                               std::nullopt, poPoint->getM(),
    1290             :                                               oOptions);
    1291             :             }
    1292             :         }
    1293         364 :         else if (poPoint->Is3D())
    1294             :         {
    1295             :             poObj =
    1296         448 :                 OGRGeoJSONWriteCoords(poPoint->getX(), poPoint->getY(),
    1297         448 :                                       poPoint->getZ(), std::nullopt, oOptions);
    1298             :         }
    1299             :         else
    1300             :         {
    1301         140 :             poObj = OGRGeoJSONWriteCoords(poPoint->getX(), poPoint->getY(),
    1302             :                                           std::nullopt, std::nullopt, oOptions);
    1303             :         }
    1304             :     }
    1305             : 
    1306         370 :     return poObj;
    1307             : }
    1308             : 
    1309             : /************************************************************************/
    1310             : /*                           OGRGeoJSONWriteSimpleCurve                  */
    1311             : /************************************************************************/
    1312             : 
    1313         173 : json_object *OGRGeoJSONWriteSimpleCurve(const OGRSimpleCurve *poLine,
    1314             :                                         const OGRGeoJSONWriteOptions &oOptions)
    1315             : {
    1316         173 :     CPLAssert(nullptr != poLine);
    1317             : 
    1318             :     // Generate "coordinates" object for 2D or 3D dimension.
    1319         173 :     json_object *poObj = OGRGeoJSONWriteLineCoords(poLine, oOptions);
    1320             : 
    1321         173 :     return poObj;
    1322             : }
    1323             : 
    1324             : /************************************************************************/
    1325             : /*                           OGRGeoJSONWritePolygon                     */
    1326             : /************************************************************************/
    1327             : 
    1328        1380 : json_object *OGRGeoJSONWritePolygon(const OGRPolygon *poPolygon,
    1329             :                                     const OGRGeoJSONWriteOptions &oOptions)
    1330             : {
    1331        1380 :     CPLAssert(nullptr != poPolygon);
    1332             : 
    1333             :     // Generate "coordinates" array object.
    1334        1380 :     json_object *poObj = json_object_new_array();
    1335             : 
    1336        1380 :     bool bExteriorRing = true;
    1337        2765 :     for (const auto *poRing : *poPolygon)
    1338             :     {
    1339             :         json_object *poObjRing =
    1340        1388 :             OGRGeoJSONWriteRingCoords(poRing, bExteriorRing, oOptions);
    1341        1388 :         bExteriorRing = false;
    1342        1388 :         if (poObjRing == nullptr)
    1343             :         {
    1344           3 :             json_object_put(poObj);
    1345           3 :             return nullptr;
    1346             :         }
    1347        1385 :         json_object_array_add(poObj, poObjRing);
    1348             :     }
    1349             : 
    1350        1377 :     return poObj;
    1351             : }
    1352             : 
    1353             : /************************************************************************/
    1354             : /*                           OGRGeoJSONWriteMultiPoint                  */
    1355             : /************************************************************************/
    1356             : 
    1357          64 : json_object *OGRGeoJSONWriteMultiPoint(const OGRMultiPoint *poGeometry,
    1358             :                                        const OGRGeoJSONWriteOptions &oOptions)
    1359             : {
    1360          64 :     CPLAssert(nullptr != poGeometry);
    1361             : 
    1362             :     // Generate "coordinates" object
    1363          64 :     json_object *poObj = json_object_new_array();
    1364             : 
    1365         255 :     for (const auto *poPoint : poGeometry)
    1366             :     {
    1367         192 :         json_object *poObjPoint = OGRGeoJSONWritePoint(poPoint, oOptions);
    1368         192 :         if (poObjPoint == nullptr)
    1369             :         {
    1370           1 :             json_object_put(poObj);
    1371           1 :             return nullptr;
    1372             :         }
    1373             : 
    1374         191 :         json_object_array_add(poObj, poObjPoint);
    1375             :     }
    1376             : 
    1377          63 :     return poObj;
    1378             : }
    1379             : 
    1380             : /************************************************************************/
    1381             : /*                           OGRGeoJSONWriteMultiLineString             */
    1382             : /************************************************************************/
    1383             : 
    1384             : json_object *
    1385          38 : OGRGeoJSONWriteMultiLineString(const OGRMultiLineString *poGeometry,
    1386             :                                const OGRGeoJSONWriteOptions &oOptions)
    1387             : {
    1388          38 :     CPLAssert(nullptr != poGeometry);
    1389             : 
    1390             :     // Generate "coordinates" object
    1391          38 :     json_object *poObj = json_object_new_array();
    1392             : 
    1393          90 :     for (const auto *poLine : poGeometry)
    1394             :     {
    1395          53 :         json_object *poObjLine = OGRGeoJSONWriteSimpleCurve(poLine, oOptions);
    1396          53 :         if (poObjLine == nullptr)
    1397             :         {
    1398           1 :             json_object_put(poObj);
    1399           1 :             return nullptr;
    1400             :         }
    1401             : 
    1402          52 :         json_object_array_add(poObj, poObjLine);
    1403             :     }
    1404             : 
    1405          37 :     return poObj;
    1406             : }
    1407             : 
    1408             : /************************************************************************/
    1409             : /*                           OGRGeoJSONWriteMultiPolygon                */
    1410             : /************************************************************************/
    1411             : 
    1412          48 : json_object *OGRGeoJSONWriteMultiPolygon(const OGRMultiPolygon *poGeometry,
    1413             :                                          const OGRGeoJSONWriteOptions &oOptions)
    1414             : {
    1415          48 :     CPLAssert(nullptr != poGeometry);
    1416             : 
    1417             :     // Generate "coordinates" object
    1418          48 :     json_object *poObj = json_object_new_array();
    1419             : 
    1420         120 :     for (const auto *poPoly : poGeometry)
    1421             :     {
    1422          73 :         json_object *poObjPoly = OGRGeoJSONWritePolygon(poPoly, oOptions);
    1423          73 :         if (poObjPoly == nullptr)
    1424             :         {
    1425           1 :             json_object_put(poObj);
    1426           1 :             return nullptr;
    1427             :         }
    1428             : 
    1429          72 :         json_object_array_add(poObj, poObjPoly);
    1430             :     }
    1431             : 
    1432          47 :     return poObj;
    1433             : }
    1434             : 
    1435             : /************************************************************************/
    1436             : /*                   OGRGeoJSONWriteCollectionGeneric()                 */
    1437             : /************************************************************************/
    1438             : 
    1439             : template <class T>
    1440             : static json_object *
    1441          56 : OGRGeoJSONWriteCollectionGeneric(const T *poGeometry,
    1442             :                                  const OGRGeoJSONWriteOptions &oOptions)
    1443             : {
    1444          56 :     CPLAssert(nullptr != poGeometry);
    1445             : 
    1446             :     /* Generate "geometries" object. */
    1447          56 :     json_object *poObj = json_object_new_array();
    1448             : 
    1449         182 :     for (const OGRGeometry *poGeom : *poGeometry)
    1450             :     {
    1451         127 :         json_object *poObjGeom = OGRGeoJSONWriteGeometry(poGeom, oOptions);
    1452         127 :         if (poObjGeom == nullptr)
    1453             :         {
    1454           1 :             json_object_put(poObj);
    1455           1 :             return nullptr;
    1456             :         }
    1457             : 
    1458         126 :         json_object_array_add(poObj, poObjGeom);
    1459             :     }
    1460             : 
    1461          55 :     return poObj;
    1462             : }
    1463             : 
    1464             : /************************************************************************/
    1465             : /*                           OGRGeoJSONWriteGeometryCollection          */
    1466             : /************************************************************************/
    1467             : 
    1468             : json_object *
    1469          33 : OGRGeoJSONWriteGeometryCollection(const OGRGeometryCollection *poGeometry,
    1470             :                                   const OGRGeoJSONWriteOptions &oOptions)
    1471             : {
    1472          33 :     return OGRGeoJSONWriteCollectionGeneric(poGeometry, oOptions);
    1473             : }
    1474             : 
    1475             : /************************************************************************/
    1476             : /*                       OGRGeoJSONWriteCompoundCurve                   */
    1477             : /************************************************************************/
    1478             : 
    1479             : json_object *
    1480          14 : OGRGeoJSONWriteCompoundCurve(const OGRCompoundCurve *poGeometry,
    1481             :                              const OGRGeoJSONWriteOptions &oOptions)
    1482             : {
    1483          14 :     return OGRGeoJSONWriteCollectionGeneric(poGeometry, oOptions);
    1484             : }
    1485             : 
    1486             : /************************************************************************/
    1487             : /*                       OGRGeoJSONWriteCurvePolygon                    */
    1488             : /************************************************************************/
    1489             : 
    1490           9 : json_object *OGRGeoJSONWriteCurvePolygon(const OGRCurvePolygon *poGeometry,
    1491             :                                          const OGRGeoJSONWriteOptions &oOptions)
    1492             : {
    1493           9 :     return OGRGeoJSONWriteCollectionGeneric(poGeometry, oOptions);
    1494             : }
    1495             : 
    1496             : /************************************************************************/
    1497             : /*                           OGRGeoJSONWriteCoords                      */
    1498             : /************************************************************************/
    1499             : 
    1500       12188 : json_object *OGRGeoJSONWriteCoords(double dfX, double dfY,
    1501             :                                    std::optional<double> dfZ,
    1502             :                                    std::optional<double> dfM,
    1503             :                                    const OGRGeoJSONWriteOptions &oOptions)
    1504             : {
    1505       12188 :     json_object *poObjCoords = nullptr;
    1506       36548 :     if (!std::isfinite(dfX) || !std::isfinite(dfY) ||
    1507       36548 :         (dfZ && !std::isfinite(*dfZ)) || (dfM && !std::isfinite(*dfM)))
    1508             :     {
    1509           8 :         CPLError(CE_Warning, CPLE_AppDefined,
    1510             :                  "Infinite or NaN coordinate encountered");
    1511           8 :         return nullptr;
    1512             :     }
    1513       12180 :     poObjCoords = json_object_new_array();
    1514       12180 :     json_object_array_add(poObjCoords, json_object_new_coord(dfX, 1, oOptions));
    1515       12180 :     json_object_array_add(poObjCoords, json_object_new_coord(dfY, 2, oOptions));
    1516       12180 :     int nIdx = 3;
    1517       12180 :     if (dfZ)
    1518             :     {
    1519        1347 :         json_object_array_add(poObjCoords,
    1520        1347 :                               json_object_new_coord(*dfZ, nIdx, oOptions));
    1521        1347 :         nIdx++;
    1522             :     }
    1523       12180 :     if (dfM)
    1524             :     {
    1525         106 :         json_object_array_add(poObjCoords,
    1526         106 :                               json_object_new_coord(*dfM, nIdx, oOptions));
    1527             :     }
    1528             : 
    1529       12180 :     return poObjCoords;
    1530             : }
    1531             : 
    1532             : /************************************************************************/
    1533             : /*                           OGRGeoJSONWriteLineCoords                  */
    1534             : /************************************************************************/
    1535             : 
    1536         173 : json_object *OGRGeoJSONWriteLineCoords(const OGRSimpleCurve *poLine,
    1537             :                                        const OGRGeoJSONWriteOptions &oOptions)
    1538             : {
    1539         173 :     json_object *poObjCoords = json_object_new_array();
    1540             : 
    1541         173 :     const int nCount = poLine->getNumPoints();
    1542         173 :     const auto bHasZ = poLine->Is3D();
    1543         173 :     const auto bHasM = oOptions.bAllowMeasure && poLine->IsMeasured();
    1544        1156 :     for (int i = 0; i < nCount; ++i)
    1545             :     {
    1546             :         json_object *poObjPoint;
    1547         986 :         if (bHasZ)
    1548             :         {
    1549         440 :             if (bHasM)
    1550             :             {
    1551          54 :                 poObjPoint = OGRGeoJSONWriteCoords(
    1552          54 :                     poLine->getX(i), poLine->getY(i), poLine->getZ(i),
    1553         108 :                     poLine->getM(i), oOptions);
    1554             :             }
    1555             :             else
    1556             :             {
    1557         772 :                 poObjPoint = OGRGeoJSONWriteCoords(
    1558         772 :                     poLine->getX(i), poLine->getY(i), poLine->getZ(i),
    1559             :                     std::nullopt, oOptions);
    1560             :             }
    1561             :         }
    1562         546 :         else if (bHasM)
    1563             :         {
    1564             :             poObjPoint =
    1565          38 :                 OGRGeoJSONWriteCoords(poLine->getX(i), poLine->getY(i),
    1566          76 :                                       std::nullopt, poLine->getM(i), oOptions);
    1567             :         }
    1568             :         else
    1569             :         {
    1570             :             poObjPoint =
    1571         508 :                 OGRGeoJSONWriteCoords(poLine->getX(i), poLine->getY(i),
    1572             :                                       std::nullopt, std::nullopt, oOptions);
    1573             :         }
    1574         986 :         if (poObjPoint == nullptr)
    1575             :         {
    1576           3 :             json_object_put(poObjCoords);
    1577           3 :             return nullptr;
    1578             :         }
    1579         983 :         json_object_array_add(poObjCoords, poObjPoint);
    1580             :     }
    1581             : 
    1582         170 :     return poObjCoords;
    1583             : }
    1584             : 
    1585             : /************************************************************************/
    1586             : /*                        OGRGeoJSONWriteRingCoords                     */
    1587             : /************************************************************************/
    1588             : 
    1589        1388 : json_object *OGRGeoJSONWriteRingCoords(const OGRLinearRing *poLine,
    1590             :                                        bool bIsExteriorRing,
    1591             :                                        const OGRGeoJSONWriteOptions &oOptions)
    1592             : {
    1593        1388 :     json_object *poObjCoords = json_object_new_array();
    1594             : 
    1595        1486 :     const bool bInvertOrder = oOptions.bPolygonRightHandRule &&
    1596          86 :                               ((bIsExteriorRing && poLine->isClockwise()) ||
    1597          29 :                                (!bIsExteriorRing && !poLine->isClockwise()));
    1598             : 
    1599        1388 :     const int nCount = poLine->getNumPoints();
    1600        1388 :     const auto bHasZ = poLine->Is3D();
    1601        1388 :     const auto bHasM = oOptions.bAllowMeasure && poLine->IsMeasured();
    1602       12217 :     for (int i = 0; i < nCount; ++i)
    1603             :     {
    1604       10832 :         const int nIdx = (bInvertOrder) ? nCount - 1 - i : i;
    1605             :         json_object *poObjPoint;
    1606       10832 :         if (bHasZ)
    1607             :         {
    1608         683 :             if (bHasM)
    1609             :             {
    1610           4 :                 poObjPoint = OGRGeoJSONWriteCoords(
    1611           4 :                     poLine->getX(nIdx), poLine->getY(nIdx), poLine->getZ(nIdx),
    1612           8 :                     poLine->getM(nIdx), oOptions);
    1613             :             }
    1614             :             else
    1615             :             {
    1616        1358 :                 poObjPoint = OGRGeoJSONWriteCoords(
    1617        1358 :                     poLine->getX(nIdx), poLine->getY(nIdx), poLine->getZ(nIdx),
    1618             :                     std::nullopt, oOptions);
    1619             :             }
    1620             :         }
    1621       10149 :         else if (bHasM)
    1622             :         {
    1623           4 :             poObjPoint = OGRGeoJSONWriteCoords(poLine->getX(nIdx),
    1624             :                                                poLine->getY(nIdx), std::nullopt,
    1625           8 :                                                poLine->getM(nIdx), oOptions);
    1626             :         }
    1627             :         else
    1628             :         {
    1629             :             poObjPoint =
    1630       10145 :                 OGRGeoJSONWriteCoords(poLine->getX(nIdx), poLine->getY(nIdx),
    1631             :                                       std::nullopt, std::nullopt, oOptions);
    1632             :         }
    1633       10832 :         if (poObjPoint == nullptr)
    1634             :         {
    1635           3 :             json_object_put(poObjCoords);
    1636           3 :             return nullptr;
    1637             :         }
    1638       10829 :         json_object_array_add(poObjCoords, poObjPoint);
    1639             :     }
    1640             : 
    1641        1385 :     return poObjCoords;
    1642             : }
    1643             : 
    1644             : /************************************************************************/
    1645             : /*             OGR_json_float_with_significant_figures_to_string()      */
    1646             : /************************************************************************/
    1647             : 
    1648           9 : static int OGR_json_float_with_significant_figures_to_string(
    1649             :     struct json_object *jso, struct printbuf *pb, int /* level */,
    1650             :     int /* flags */)
    1651             : {
    1652           9 :     char szBuffer[75] = {};
    1653           9 :     int nSize = 0;
    1654           9 :     const float fVal = static_cast<float>(json_object_get_double(jso));
    1655           9 :     if (std::isnan(fVal))
    1656           0 :         nSize = CPLsnprintf(szBuffer, sizeof(szBuffer), "NaN");
    1657           9 :     else if (std::isinf(fVal))
    1658             :     {
    1659           0 :         if (fVal > 0)
    1660           0 :             nSize = CPLsnprintf(szBuffer, sizeof(szBuffer), "Infinity");
    1661             :         else
    1662           0 :             nSize = CPLsnprintf(szBuffer, sizeof(szBuffer), "-Infinity");
    1663             :     }
    1664             :     else
    1665             :     {
    1666             :         const void *userData =
    1667             : #if (!defined(JSON_C_VERSION_NUM)) || (JSON_C_VERSION_NUM < JSON_C_VER_013)
    1668             :             jso->_userdata;
    1669             : #else
    1670           9 :             json_object_get_userdata(jso);
    1671             : #endif
    1672           9 :         const uintptr_t nSignificantFigures =
    1673             :             reinterpret_cast<uintptr_t>(userData);
    1674           9 :         const bool bSignificantFiguresIsNegative =
    1675           9 :             (nSignificantFigures >> (8 * sizeof(nSignificantFigures) - 1)) != 0;
    1676           9 :         const int nInitialSignificantFigures =
    1677             :             bSignificantFiguresIsNegative
    1678           9 :                 ? 8
    1679             :                 : static_cast<int>(nSignificantFigures);
    1680           9 :         nSize = OGRFormatFloat(szBuffer, sizeof(szBuffer), fVal,
    1681             :                                nInitialSignificantFigures, 'g');
    1682             :     }
    1683             : 
    1684          18 :     return printbuf_memappend(pb, szBuffer, nSize);
    1685             : }
    1686             : 
    1687             : /************************************************************************/
    1688             : /*              json_object_new_float_with_significant_figures()        */
    1689             : /************************************************************************/
    1690             : 
    1691             : json_object *
    1692           9 : json_object_new_float_with_significant_figures(float fVal,
    1693             :                                                int nSignificantFigures)
    1694             : {
    1695           9 :     json_object *jso = json_object_new_double(double(fVal));
    1696           9 :     json_object_set_serializer(
    1697             :         jso, OGR_json_float_with_significant_figures_to_string,
    1698           9 :         reinterpret_cast<void *>(static_cast<uintptr_t>(nSignificantFigures)),
    1699             :         nullptr);
    1700           9 :     return jso;
    1701             : }
    1702             : 
    1703             : /*! @endcond */
    1704             : 
    1705             : /************************************************************************/
    1706             : /*                           OGR_G_ExportToJson                         */
    1707             : /************************************************************************/
    1708             : 
    1709             : /**
    1710             :  * \brief Convert a geometry into GeoJSON format.
    1711             :  *
    1712             :  * The returned string should be freed with CPLFree() when no longer required.
    1713             :  *
    1714             :  * This method is the same as the C++ method OGRGeometry::exportToJson().
    1715             :  *
    1716             :  * @param hGeometry handle to the geometry.
    1717             :  * @return A GeoJSON fragment or NULL in case of error.
    1718             :  */
    1719             : 
    1720           2 : char *OGR_G_ExportToJson(OGRGeometryH hGeometry)
    1721             : {
    1722           2 :     return OGR_G_ExportToJsonEx(hGeometry, nullptr);
    1723             : }
    1724             : 
    1725             : /************************************************************************/
    1726             : /*                           OGR_G_ExportToJsonEx                       */
    1727             : /************************************************************************/
    1728             : 
    1729             : /**
    1730             :  * \brief Convert a geometry into GeoJSON format.
    1731             :  *
    1732             :  * The returned string should be freed with CPLFree() when no longer required.
    1733             :  *
    1734             :  * The following options are supported :
    1735             :  * <ul>
    1736             :  * <li>COORDINATE_PRECISION=number: maximum number of figures after decimal
    1737             :  * separator to write in coordinates.</li>
    1738             :  * <li>XY_COORD_PRECISION=integer: number of decimal figures for X,Y coordinates
    1739             :  * (added in GDAL 3.9)</li>
    1740             :  * <li>Z_COORD_PRECISION=integer: number of decimal figures for Z coordinates
    1741             :  * (added in GDAL 3.9)</li>
    1742             :  * <li>SIGNIFICANT_FIGURES=number:
    1743             :  * maximum number of significant figures (GDAL &gt;= 2.1).</li>
    1744             :  * </ul>
    1745             :  *
    1746             :  * If XY_COORD_PRECISION or Z_COORD_PRECISION is specified, COORDINATE_PRECISION
    1747             :  * or SIGNIFICANT_FIGURES will be ignored if specified.
    1748             :  * If COORDINATE_PRECISION is defined, SIGNIFICANT_FIGURES will be ignored if
    1749             :  * specified.
    1750             :  * When none are defined, the default is COORDINATE_PRECISION=15.
    1751             :  *
    1752             :  * This method is the same as the C++ method OGRGeometry::exportToJson().
    1753             :  *
    1754             :  * @param hGeometry handle to the geometry.
    1755             :  * @param papszOptions a null terminated list of options.
    1756             :  * @return A GeoJSON fragment or NULL in case of error.
    1757             :  *
    1758             :  * @since OGR 1.9.0
    1759             :  */
    1760             : 
    1761         132 : char *OGR_G_ExportToJsonEx(OGRGeometryH hGeometry, char **papszOptions)
    1762             : {
    1763         132 :     VALIDATE_POINTER1(hGeometry, "OGR_G_ExportToJson", nullptr);
    1764             : 
    1765         132 :     OGRGeometry *poGeometry = OGRGeometry::FromHandle(hGeometry);
    1766             : 
    1767             :     const char *pszCoordPrecision =
    1768         132 :         CSLFetchNameValueDef(papszOptions, "COORDINATE_PRECISION", "-1");
    1769             : 
    1770             :     const int nSignificantFigures =
    1771         132 :         atoi(CSLFetchNameValueDef(papszOptions, "SIGNIFICANT_FIGURES", "-1"));
    1772             : 
    1773         264 :     OGRGeoJSONWriteOptions oOptions;
    1774         132 :     oOptions.nXYCoordPrecision = atoi(CSLFetchNameValueDef(
    1775             :         papszOptions, "XY_COORD_PRECISION", pszCoordPrecision));
    1776         132 :     oOptions.nZCoordPrecision = atoi(CSLFetchNameValueDef(
    1777             :         papszOptions, "Z_COORD_PRECISION", pszCoordPrecision));
    1778         132 :     oOptions.nSignificantFigures = nSignificantFigures;
    1779             : 
    1780             :     // If the CRS has latitude, longitude (or northing, easting) axis order,
    1781             :     // and the data axis to SRS axis mapping doesn't change that order,
    1782             :     // then swap X and Y values.
    1783         132 :     bool bHasSwappedXY = false;
    1784         132 :     const auto poSRS = poGeometry->getSpatialReference();
    1785         211 :     if (poSRS &&
    1786          79 :         (poSRS->EPSGTreatsAsLatLong() ||
    1787         212 :          poSRS->EPSGTreatsAsNorthingEasting()) &&
    1788         144 :         poSRS->GetDataAxisToSRSAxisMapping() == std::vector<int>{1, 2})
    1789             :     {
    1790           2 :         poGeometry->swapXY();
    1791           2 :         bHasSwappedXY = true;
    1792             :     }
    1793             : 
    1794         132 :     json_object *poObj = OGRGeoJSONWriteGeometry(poGeometry, oOptions);
    1795             : 
    1796             :     // Unswap back
    1797         132 :     if (bHasSwappedXY)
    1798           2 :         poGeometry->swapXY();
    1799             : 
    1800         132 :     if (nullptr != poObj)
    1801             :     {
    1802         129 :         char *pszJson = CPLStrdup(json_object_to_json_string(poObj));
    1803             : 
    1804             :         // Release JSON tree.
    1805         129 :         json_object_put(poObj);
    1806             : 
    1807         129 :         return pszJson;
    1808             :     }
    1809             : 
    1810             :     // Translation failed.
    1811           3 :     return nullptr;
    1812             : }

Generated by: LCOV version 1.14