LCOV - code coverage report
Current view: top level - ogr - ogrgeojsonwriter.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 752 789 95.3 %
Date: 2026-06-07 13:20:54 Functions: 37 37 100.0 %

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

Generated by: LCOV version 1.14