LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/geojson - ogrgeojsonwriter.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 751 787 95.4 %
Date: 2024-05-03 15:49:35 Functions: 36 36 100.0 %

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

Generated by: LCOV version 1.14