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

Generated by: LCOV version 1.14