LCOV - code coverage report
Current view: top level - ogr - ogrgeojsonwriter.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 751 788 95.3 %
Date: 2026-09-17 18:16:05 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         361 : void OGRGeoJSONWriteOptions::SetIDOptions(CSLConstList papszOptions)
     106             : {
     107             : 
     108         361 :     osIDField = CSLFetchNameValueDef(papszOptions, "ID_FIELD", "");
     109         361 :     const char *pszIDFieldType = CSLFetchNameValue(papszOptions, "ID_TYPE");
     110         361 :     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         361 :     bGenerateID =
     124         361 :         CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "ID_GENERATE", false));
     125         361 : }
     126             : 
     127             : /*! @endcond */
     128             : 
     129             : /************************************************************************/
     130             : /*                       json_object_new_coord()                        */
     131             : /************************************************************************/
     132             : 
     133             : static json_object *
     134       30816 : 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       30816 :     if (nDimIdx <= 2)
     140             :     {
     141       29252 :         if (oOptions.nXYCoordPrecision >= 0 || oOptions.nSignificantFigures < 0)
     142       58500 :             return json_object_new_double_with_precision(
     143       29250 :                 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         571 : static bool OGRGeoJSONIsPatchablePosition(json_object *poJSonCoordinates,
     161             :                                           json_object *poNativeCoordinates)
     162             : {
     163         571 :     return json_object_get_type(poJSonCoordinates) == json_type_array &&
     164         560 :            json_object_get_type(poNativeCoordinates) == json_type_array &&
     165         560 :            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        1131 :                poJSonCoordinates, 0)) != json_type_array &&
     169          24 :            json_object_get_type(json_object_array_get_idx(
     170         571 :                poNativeCoordinates, 0)) != json_type_array;
     171             : }
     172             : 
     173             : /************************************************************************/
     174             : /*                   OGRGeoJSONIsCompatiblePosition()                   */
     175             : /************************************************************************/
     176             : 
     177         455 : static bool OGRGeoJSONIsCompatiblePosition(json_object *poJSonCoordinates,
     178             :                                            json_object *poNativeCoordinates)
     179             : {
     180         455 :     return json_object_get_type(poJSonCoordinates) == json_type_array &&
     181         455 :            json_object_get_type(poNativeCoordinates) == json_type_array &&
     182         455 :            json_object_array_length(poJSonCoordinates) ==
     183         455 :                json_object_array_length(poNativeCoordinates) &&
     184         443 :            json_object_get_type(json_object_array_get_idx(
     185         910 :                poJSonCoordinates, 0)) != json_type_array &&
     186         377 :            json_object_get_type(json_object_array_get_idx(
     187         455 :                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         579 : static bool OGRGeoJSONComputePatchableOrCompatibleArrayInternal(
     249             :     json_object *poJSonArray, json_object *poNativeArray, int nDepth,
     250             :     bool &bOutPatchable, bool &bOutCompatible)
     251             : {
     252         579 :     if (nDepth == 0)
     253             :     {
     254         455 :         bOutPatchable &=
     255         455 :             OGRGeoJSONIsPatchablePosition(poJSonArray, poNativeArray);
     256         455 :         bOutCompatible &=
     257         455 :             OGRGeoJSONIsCompatiblePosition(poJSonArray, poNativeArray);
     258         455 :         return json_object_get_type(poJSonArray) == json_type_array &&
     259         455 :                json_object_get_type(poNativeArray) == json_type_array &&
     260         455 :                json_object_get_type(json_object_array_get_idx(
     261         910 :                    poJSonArray, 0)) != json_type_array &&
     262         389 :                json_object_get_type(json_object_array_get_idx(
     263         455 :                    poNativeArray, 0)) != json_type_array;
     264             :     }
     265             : 
     266         248 :     if (json_object_get_type(poJSonArray) == json_type_array &&
     267         124 :         json_object_get_type(poNativeArray) == json_type_array)
     268             :     {
     269         124 :         const auto nLength = json_object_array_length(poJSonArray);
     270         124 :         if (nLength == json_object_array_length(poNativeArray))
     271             :         {
     272         539 :             for (auto i = decltype(nLength){0}; i < nLength; i++)
     273             :             {
     274             :                 json_object *poJSonChild =
     275         473 :                     json_object_array_get_idx(poJSonArray, i);
     276             :                 json_object *poNativeChild =
     277         473 :                     json_object_array_get_idx(poNativeArray, i);
     278         473 :                 if (!OGRGeoJSONComputePatchableOrCompatibleArrayInternal(
     279             :                         poJSonChild, poNativeChild, nDepth - 1, bOutPatchable,
     280             :                         bOutCompatible))
     281             :                 {
     282          58 :                     return false;
     283             :                 }
     284         415 :                 if (!bOutPatchable && !bOutCompatible)
     285           0 :                     break;
     286             :             }
     287          66 :             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         106 : static bool OGRGeoJSONComputePatchableOrCompatibleArray(
     299             :     json_object *poJSonArray, json_object *poNativeArray, int nDepth,
     300             :     bool &bOutPatchable, bool &bOutCompatible)
     301             : {
     302         106 :     bOutPatchable = true;
     303         106 :     bOutCompatible = true;
     304         106 :     return OGRGeoJSONComputePatchableOrCompatibleArrayInternal(
     305         106 :         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        1407 : static bool OGRGeoJSONIsPatchableGeometry(json_object *poJSonGeometry,
     335             :                                           json_object *poNativeGeometry,
     336             :                                           bool &bOutPatchableCoords,
     337             :                                           bool &bOutCompatibleCoords)
     338             : {
     339        2807 :     if (json_object_get_type(poJSonGeometry) != json_type_object ||
     340        1400 :         json_object_get_type(poNativeGeometry) != json_type_object)
     341             :     {
     342        1365 :         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         106 :             for (int i = 0; i <= 3; i++)
     373             :             {
     374         106 :                 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        1070 : OGREnvelope3D OGRGeoJSONGetBBox(const OGRGeometry *poGeometry,
     496             :                                 const OGRGeoJSONWriteOptions &oOptions)
     497             : {
     498        1070 :     OGREnvelope3D sEnvelope;
     499        1070 :     poGeometry->getEnvelope(&sEnvelope);
     500             : 
     501        1070 :     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        1070 :     return sEnvelope;
     608             : }
     609             : 
     610             : /************************************************************************/
     611             : /*                        OGRGeoJSONWriteFeature                        */
     612             : /************************************************************************/
     613             : 
     614        1520 : json_object *OGRGeoJSONWriteFeature(OGRFeature *poFeature,
     615             :                                     const OGRGeoJSONWriteOptions &oOptions)
     616             : {
     617        1520 :     CPLAssert(nullptr != poFeature);
     618             : 
     619        1520 :     bool bWriteBBOX = oOptions.bWriteBBOX;
     620             : 
     621        1520 :     json_object *poObj = json_object_new_object();
     622        1520 :     CPLAssert(nullptr != poObj);
     623             : 
     624        1520 :     json_object_object_add(poObj, "type", json_object_new_string("Feature"));
     625             : 
     626             :     /* -------------------------------------------------------------------- */
     627             :     /*      Write native JSon data.                                         */
     628             :     /* -------------------------------------------------------------------- */
     629        1520 :     bool bIdAlreadyWritten = false;
     630        1520 :     const char *pszNativeMediaType = poFeature->GetNativeMediaType();
     631        1520 :     json_object *poNativeGeom = nullptr;
     632        1520 :     bool bHasProperties = true;
     633        1520 :     bool bWriteIdIfFoundInAttributes = true;
     634        1520 :     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          47 :             bHasProperties = false;
     646         215 :             json_object_object_foreachC(poNativeJSon, it)
     647             :             {
     648         168 :                 if (strcmp(it.key, "type") == 0)
     649             :                 {
     650          47 :                     continue;
     651             :                 }
     652         121 :                 if (strcmp(it.key, "properties") == 0)
     653             :                 {
     654          46 :                     bHasProperties = true;
     655          46 :                     continue;
     656             :                 }
     657          75 :                 if (strcmp(it.key, "bbox") == 0)
     658             :                 {
     659           3 :                     bWriteBBOX = true;
     660           3 :                     continue;
     661             :                 }
     662          72 :                 if (strcmp(it.key, "geometry") == 0)
     663             :                 {
     664          47 :                     poNativeGeom = json_object_get(it.val);
     665          47 :                     continue;
     666             :                 }
     667          25 :                 if (strcmp(it.key, "id") == 0)
     668             :                 {
     669          15 :                     const auto eType = json_object_get_type(it.val);
     670             :                     // See https://tools.ietf.org/html/rfc7946#section-3.2
     671          15 :                     if (oOptions.bHonourReservedRFC7946Members &&
     672           1 :                         !oOptions.bForceIDFieldType &&
     673           1 :                         eType != json_type_string && eType != json_type_int &&
     674             :                         eType != json_type_double)
     675             :                     {
     676           1 :                         continue;
     677             :                     }
     678             : 
     679          14 :                     bIdAlreadyWritten = true;
     680             : 
     681          14 :                     if (it.val && oOptions.bForceIDFieldType &&
     682           4 :                         oOptions.eForcedIDFieldType == OFTInteger64)
     683             :                     {
     684           2 :                         if (eType != json_type_int)
     685             :                         {
     686           2 :                             json_object_object_add(
     687           1 :                                 poObj, it.key,
     688           1 :                                 json_object_new_int64(CPLAtoGIntBig(
     689             :                                     json_object_get_string(it.val))));
     690           1 :                             bWriteIdIfFoundInAttributes = false;
     691           1 :                             continue;
     692             :                         }
     693             :                     }
     694          12 :                     else if (it.val && oOptions.bForceIDFieldType &&
     695           2 :                              oOptions.eForcedIDFieldType == OFTString)
     696             :                     {
     697           2 :                         if (eType != json_type_string)
     698             :                         {
     699           1 :                             json_object_object_add(
     700           1 :                                 poObj, it.key,
     701             :                                 json_object_new_string(
     702             :                                     json_object_get_string(it.val)));
     703           1 :                             bWriteIdIfFoundInAttributes = false;
     704           1 :                             continue;
     705             :                         }
     706             :                     }
     707             : 
     708          12 :                     if (it.val != nullptr)
     709             :                     {
     710             :                         int nIdx =
     711          12 :                             poFeature->GetDefnRef()->GetFieldIndexCaseSensitive(
     712             :                                 "id");
     713           8 :                         if (eType == json_type_string && nIdx >= 0 &&
     714           4 :                             poFeature->GetFieldDefnRef(nIdx)->GetType() ==
     715          16 :                                 OFTString &&
     716           4 :                             strcmp(json_object_get_string(it.val),
     717             :                                    poFeature->GetFieldAsString(nIdx)) == 0)
     718             :                         {
     719           4 :                             bWriteIdIfFoundInAttributes = false;
     720             :                         }
     721           8 :                         else if (eType == json_type_int && nIdx >= 0 &&
     722           0 :                                  (poFeature->GetFieldDefnRef(nIdx)->GetType() ==
     723           0 :                                       OFTInteger ||
     724           0 :                                   poFeature->GetFieldDefnRef(nIdx)->GetType() ==
     725          16 :                                       OFTInteger64) &&
     726           0 :                                  json_object_get_int64(it.val) ==
     727           0 :                                      poFeature->GetFieldAsInteger64(nIdx))
     728             :                         {
     729           0 :                             bWriteIdIfFoundInAttributes = false;
     730             :                         }
     731             :                     }
     732             :                 }
     733             : 
     734             :                 // See https://tools.ietf.org/html/rfc7946#section-7.1
     735          22 :                 if (oOptions.bHonourReservedRFC7946Members &&
     736           4 :                     (strcmp(it.key, "coordinates") == 0 ||
     737           3 :                      strcmp(it.key, "geometries") == 0 ||
     738           2 :                      strcmp(it.key, "features") == 0))
     739             :                 {
     740           3 :                     continue;
     741             :                 }
     742             : 
     743          19 :                 json_object_object_add(poObj, it.key, json_object_get(it.val));
     744             :             }
     745          47 :             json_object_put(poNativeJSon);
     746             :         }
     747             :     }
     748             : 
     749             :     /* -------------------------------------------------------------------- */
     750             :     /*      Write FID if available                                          */
     751             :     /* -------------------------------------------------------------------- */
     752        1520 :     OGRGeoJSONWriteId(poFeature, poObj, bIdAlreadyWritten, oOptions);
     753             : 
     754             :     /* -------------------------------------------------------------------- */
     755             :     /*      Write feature attributes to GeoJSON "properties" object.        */
     756             :     /* -------------------------------------------------------------------- */
     757        1520 :     if (bHasProperties)
     758             :     {
     759        1519 :         json_object *poObjProps = OGRGeoJSONWriteAttributes(
     760             :             poFeature, bWriteIdIfFoundInAttributes, oOptions);
     761        1519 :         json_object_object_add(poObj, "properties", poObjProps);
     762             :     }
     763             : 
     764             :     /* -------------------------------------------------------------------- */
     765             :     /*      Write feature geometry to GeoJSON "geometry" object.            */
     766             :     /*      Null geometries are allowed, according to the GeoJSON Spec.     */
     767             :     /* -------------------------------------------------------------------- */
     768        1520 :     json_object *poObjGeom = nullptr;
     769             : 
     770        1520 :     OGRGeometry *poGeometry = poFeature->GetGeometryRef();
     771        1520 :     if (nullptr != poGeometry)
     772             :     {
     773        1395 :         poObjGeom = OGRGeoJSONWriteGeometry(poGeometry, oOptions);
     774             : 
     775        1395 :         if (bWriteBBOX && !poGeometry->IsEmpty())
     776             :         {
     777          43 :             OGREnvelope3D sEnvelope = OGRGeoJSONGetBBox(poGeometry, oOptions);
     778             : 
     779          43 :             json_object *poObjBBOX = json_object_new_array();
     780          43 :             json_object_array_add(
     781             :                 poObjBBOX, json_object_new_coord(sEnvelope.MinX, 1, oOptions));
     782          43 :             json_object_array_add(
     783             :                 poObjBBOX, json_object_new_coord(sEnvelope.MinY, 2, oOptions));
     784          43 :             if (wkbHasZ(poGeometry->getGeometryType()))
     785           2 :                 json_object_array_add(
     786             :                     poObjBBOX,
     787             :                     json_object_new_coord(sEnvelope.MinZ, 3, oOptions));
     788          43 :             json_object_array_add(
     789             :                 poObjBBOX, json_object_new_coord(sEnvelope.MaxX, 1, oOptions));
     790          43 :             json_object_array_add(
     791             :                 poObjBBOX, json_object_new_coord(sEnvelope.MaxY, 2, oOptions));
     792          43 :             if (wkbHasZ(poGeometry->getGeometryType()))
     793           2 :                 json_object_array_add(
     794             :                     poObjBBOX,
     795             :                     json_object_new_coord(sEnvelope.MaxZ, 3, oOptions));
     796             : 
     797          43 :             json_object_object_add(poObj, "bbox", poObjBBOX);
     798             :         }
     799             : 
     800        1395 :         bool bOutPatchableCoords = false;
     801        1395 :         bool bOutCompatibleCoords = false;
     802        1395 :         if (OGRGeoJSONIsPatchableGeometry(poObjGeom, poNativeGeom,
     803             :                                           bOutPatchableCoords,
     804             :                                           bOutCompatibleCoords))
     805             :         {
     806          30 :             OGRGeoJSONPatchGeometry(poObjGeom, poNativeGeom,
     807             :                                     bOutPatchableCoords, oOptions);
     808             :         }
     809             :     }
     810             : 
     811        1520 :     json_object_object_add(poObj, "geometry", poObjGeom);
     812             : 
     813        1520 :     if (poNativeGeom != nullptr)
     814          30 :         json_object_put(poNativeGeom);
     815             : 
     816        1520 :     return poObj;
     817             : }
     818             : 
     819             : /************************************************************************/
     820             : /*                          OGRGeoJSONWriteId                           */
     821             : /************************************************************************/
     822             : 
     823        1693 : void OGRGeoJSONWriteId(const OGRFeature *poFeature, json_object *poObj,
     824             :                        bool bIdAlreadyWritten,
     825             :                        const OGRGeoJSONWriteOptions &oOptions)
     826             : {
     827        1693 :     if (!oOptions.osIDField.empty())
     828             :     {
     829           4 :         int nIdx = poFeature->GetDefnRef()->GetFieldIndexCaseSensitive(
     830             :             oOptions.osIDField);
     831           4 :         if (nIdx >= 0)
     832             :         {
     833          10 :             if ((oOptions.bForceIDFieldType &&
     834           7 :                  oOptions.eForcedIDFieldType == OFTInteger64) ||
     835           5 :                 (!oOptions.bForceIDFieldType &&
     836           4 :                  (poFeature->GetFieldDefnRef(nIdx)->GetType() == OFTInteger ||
     837           2 :                   poFeature->GetFieldDefnRef(nIdx)->GetType() == OFTInteger64)))
     838             :             {
     839           2 :                 json_object_object_add(
     840             :                     poObj, "id",
     841             :                     json_object_new_int64(
     842           2 :                         poFeature->GetFieldAsInteger64(nIdx)));
     843             :             }
     844             :             else
     845             :             {
     846           2 :                 json_object_object_add(
     847             :                     poObj, "id",
     848             :                     json_object_new_string(poFeature->GetFieldAsString(nIdx)));
     849             :             }
     850             :         }
     851             :     }
     852        1689 :     else if (poFeature->GetFID() != OGRNullFID && !bIdAlreadyWritten)
     853             :     {
     854          17 :         if (oOptions.bForceIDFieldType &&
     855           4 :             oOptions.eForcedIDFieldType == OFTString)
     856             :         {
     857           2 :             json_object_object_add(poObj, "id",
     858             :                                    json_object_new_string(CPLSPrintf(
     859             :                                        CPL_FRMT_GIB, poFeature->GetFID())));
     860             :         }
     861             :         else
     862             :         {
     863          15 :             json_object_object_add(poObj, "id",
     864          15 :                                    json_object_new_int64(poFeature->GetFID()));
     865             :         }
     866             :     }
     867        1693 : }
     868             : 
     869             : /************************************************************************/
     870             : /*                      OGRGeoJSONWriteAttributes                       */
     871             : /************************************************************************/
     872             : 
     873        1692 : json_object *OGRGeoJSONWriteAttributes(OGRFeature *poFeature,
     874             :                                        bool bWriteIdIfFoundInAttributes,
     875             :                                        const OGRGeoJSONWriteOptions &oOptions)
     876             : {
     877        1692 :     CPLAssert(nullptr != poFeature);
     878             : 
     879        1692 :     json_object *poObjProps = json_object_new_object();
     880        1692 :     CPLAssert(nullptr != poObjProps);
     881             : 
     882        1692 :     const OGRFeatureDefn *poDefn = poFeature->GetDefnRef();
     883             : 
     884             :     const int nIDField =
     885        1692 :         !oOptions.osIDField.empty()
     886        1692 :             ? poDefn->GetFieldIndexCaseSensitive(oOptions.osIDField)
     887        1692 :             : -1;
     888             : 
     889        1692 :     constexpr int MAX_SIGNIFICANT_DIGITS_FLOAT32 = 8;
     890             :     const int nFloat32SignificantDigits =
     891        1692 :         oOptions.nSignificantFigures >= 0
     892        1694 :             ? std::min(oOptions.nSignificantFigures,
     893           2 :                        MAX_SIGNIFICANT_DIGITS_FLOAT32)
     894        1692 :             : MAX_SIGNIFICANT_DIGITS_FLOAT32;
     895             : 
     896        1692 :     const int nFieldCount = poDefn->GetFieldCount();
     897             : 
     898        1692 :     json_object *poNativeObjProp = nullptr;
     899        1692 :     json_object *poProperties = nullptr;
     900             : 
     901             :     // Scan the fields to determine if there is a chance of
     902             :     // mixed types and we can use native media
     903        1692 :     bool bUseNativeMedia{false};
     904             : 
     905        1692 :     if (poFeature->GetNativeMediaType() &&
     906        1738 :         OGRIsGeoJSONMediaType(poFeature->GetNativeMediaType()) &&
     907          46 :         poFeature->GetNativeData())
     908             :     {
     909         111 :         for (int nField = 0; nField < nFieldCount; ++nField)
     910             :         {
     911          70 :             if (poDefn->GetFieldDefn(nField)->GetSubType() == OFSTJSON)
     912             :             {
     913           5 :                 if (OGRJSonParse(poFeature->GetNativeData(), &poNativeObjProp,
     914             :                                  false))
     915             :                 {
     916           5 :                     poProperties = OGRGeoJSONFindMemberByName(poNativeObjProp,
     917             :                                                               "properties");
     918           5 :                     bUseNativeMedia = poProperties != nullptr;
     919             :                 }
     920           5 :                 break;
     921             :             }
     922             :         }
     923             :     }
     924             : 
     925        4215 :     for (int nField = 0; nField < nFieldCount; ++nField)
     926             :     {
     927        2523 :         if (!poFeature->IsFieldSet(nField) || nField == nIDField)
     928             :         {
     929         603 :             continue;
     930             :         }
     931             : 
     932        1928 :         const OGRFieldDefn *poFieldDefn = poDefn->GetFieldDefn(nField);
     933        1928 :         CPLAssert(nullptr != poFieldDefn);
     934        1928 :         const OGRFieldType eType = poFieldDefn->GetType();
     935        1928 :         const OGRFieldSubType eSubType = poFieldDefn->GetSubType();
     936             : 
     937        1941 :         if (!bWriteIdIfFoundInAttributes &&
     938          13 :             strcmp(poFieldDefn->GetNameRef(), "id") == 0)
     939             :         {
     940           5 :             continue;
     941             :         }
     942             : 
     943        1923 :         json_object *poObjProp = nullptr;
     944             : 
     945        1923 :         if (poFeature->IsFieldNull(nField))
     946             :         {
     947             :             // poObjProp = NULL;
     948             :         }
     949        1921 :         else if (OFTInteger == eType)
     950             :         {
     951         981 :             if (eSubType == OFSTBoolean)
     952           2 :                 poObjProp = json_object_new_boolean(
     953             :                     poFeature->GetFieldAsInteger(nField));
     954             :             else
     955         979 :                 poObjProp =
     956         979 :                     json_object_new_int(poFeature->GetFieldAsInteger(nField));
     957             :         }
     958         940 :         else if (OFTInteger64 == eType)
     959             :         {
     960          59 :             if (eSubType == OFSTBoolean)
     961           0 :                 poObjProp = json_object_new_boolean(static_cast<json_bool>(
     962           0 :                     poFeature->GetFieldAsInteger64(nField)));
     963             :             else
     964          59 :                 poObjProp = json_object_new_int64(
     965          59 :                     poFeature->GetFieldAsInteger64(nField));
     966             :         }
     967         881 :         else if (OFTReal == eType)
     968             :         {
     969         246 :             const double val = poFeature->GetFieldAsDouble(nField);
     970         246 :             if (!std::isfinite(val))
     971             :             {
     972           6 :                 if (!oOptions.bAllowNonFiniteValues)
     973             :                 {
     974           3 :                     CPLErrorOnce(CE_Warning, CPLE_AppDefined,
     975             :                                  "NaN of Infinity value found. Skipped");
     976           3 :                     continue;
     977             :                 }
     978             :             }
     979         243 :             if (eSubType == OFSTFloat32)
     980             :             {
     981           2 :                 poObjProp = json_object_new_float_with_significant_figures(
     982             :                     static_cast<float>(val), nFloat32SignificantDigits);
     983             :             }
     984             :             else
     985             :             {
     986         241 :                 poObjProp = json_object_new_double_with_significant_figures(
     987         241 :                     val, oOptions.nSignificantFigures);
     988             :             }
     989             :         }
     990         635 :         else if (OFTString == eType)
     991             :         {
     992         286 :             const char *pszStr = poFeature->GetFieldAsString(nField);
     993         286 :             const size_t nLen = strlen(pszStr);
     994             : 
     995         286 :             if (eSubType == OFSTJSON ||
     996         275 :                 (oOptions.bAutodetectJsonStrings &&
     997         273 :                  ((pszStr[0] == '{' && pszStr[nLen - 1] == '}') ||
     998         272 :                   (pszStr[0] == '[' && pszStr[nLen - 1] == ']'))))
     999             :             {
    1000          14 :                 if (bUseNativeMedia)
    1001             :                 {
    1002           5 :                     if (json_object *poProperty = OGRGeoJSONFindMemberByName(
    1003             :                             poProperties, poFieldDefn->GetNameRef()))
    1004             :                     {
    1005           5 :                         const char *pszProp{json_object_get_string(poProperty)};
    1006           5 :                         if (pszProp && strcmp(pszProp, pszStr) == 0)
    1007             :                         {
    1008           5 :                             poObjProp = json_object_get(poProperty);
    1009             :                         }
    1010             :                     }
    1011             :                 }
    1012             : 
    1013          14 :                 if (poObjProp == nullptr)
    1014             :                 {
    1015           9 :                     if ((pszStr[0] == '{' && pszStr[nLen - 1] == '}') ||
    1016           6 :                         (pszStr[0] == '[' && pszStr[nLen - 1] == ']'))
    1017             :                     {
    1018           7 :                         OGRJSonParse(pszStr, &poObjProp, false);
    1019             :                     }
    1020             :                 }
    1021             :             }
    1022             : 
    1023         286 :             if (poObjProp == nullptr)
    1024         274 :                 poObjProp = json_object_new_string(pszStr);
    1025             :         }
    1026         349 :         else if (OFTIntegerList == eType)
    1027             :         {
    1028           6 :             int nSize = 0;
    1029             :             const int *panList =
    1030           6 :                 poFeature->GetFieldAsIntegerList(nField, &nSize);
    1031           6 :             poObjProp = json_object_new_array();
    1032          21 :             for (int i = 0; i < nSize; i++)
    1033             :             {
    1034          15 :                 if (eSubType == OFSTBoolean)
    1035           2 :                     json_object_array_add(poObjProp,
    1036           2 :                                           json_object_new_boolean(panList[i]));
    1037             :                 else
    1038          13 :                     json_object_array_add(poObjProp,
    1039          13 :                                           json_object_new_int(panList[i]));
    1040             :             }
    1041             :         }
    1042         343 :         else if (OFTInteger64List == eType)
    1043             :         {
    1044          13 :             int nSize = 0;
    1045             :             const GIntBig *panList =
    1046          13 :                 poFeature->GetFieldAsInteger64List(nField, &nSize);
    1047          13 :             poObjProp = json_object_new_array();
    1048          40 :             for (int i = 0; i < nSize; i++)
    1049             :             {
    1050          27 :                 if (eSubType == OFSTBoolean)
    1051           0 :                     json_object_array_add(
    1052             :                         poObjProp, json_object_new_boolean(
    1053           0 :                                        static_cast<json_bool>(panList[i])));
    1054             :                 else
    1055          27 :                     json_object_array_add(poObjProp,
    1056          27 :                                           json_object_new_int64(panList[i]));
    1057             :             }
    1058             :         }
    1059         330 :         else if (OFTRealList == eType)
    1060             :         {
    1061           5 :             int nSize = 0;
    1062             :             const double *padfList =
    1063           5 :                 poFeature->GetFieldAsDoubleList(nField, &nSize);
    1064           5 :             poObjProp = json_object_new_array();
    1065          22 :             for (int i = 0; i < nSize; i++)
    1066             :             {
    1067          17 :                 if (eSubType == OFSTFloat32)
    1068             :                 {
    1069           7 :                     json_object_array_add(
    1070             :                         poObjProp,
    1071             :                         json_object_new_float_with_significant_figures(
    1072           7 :                             static_cast<float>(padfList[i]),
    1073             :                             nFloat32SignificantDigits));
    1074             :                 }
    1075             :                 else
    1076             :                 {
    1077          10 :                     json_object_array_add(
    1078             :                         poObjProp,
    1079             :                         json_object_new_double_with_significant_figures(
    1080          10 :                             padfList[i], oOptions.nSignificantFigures));
    1081             :                 }
    1082             :             }
    1083             :         }
    1084         325 :         else if (OFTStringList == eType)
    1085             :         {
    1086           4 :             char **papszStringList = poFeature->GetFieldAsStringList(nField);
    1087           4 :             poObjProp = json_object_new_array();
    1088          15 :             for (int i = 0; papszStringList && papszStringList[i]; i++)
    1089             :             {
    1090          11 :                 json_object_array_add(
    1091          11 :                     poObjProp, json_object_new_string(papszStringList[i]));
    1092             :             }
    1093             :         }
    1094         321 :         else if (OFTDateTime == eType || OFTDate == eType)
    1095             :         {
    1096         319 :             char *pszDT = OGRGetXMLDateTime(poFeature->GetRawFieldRef(nField));
    1097         319 :             if (eType == OFTDate)
    1098             :             {
    1099         157 :                 char *pszT = strchr(pszDT, 'T');
    1100         157 :                 if (pszT)
    1101         157 :                     *pszT = 0;
    1102             :             }
    1103         319 :             poObjProp = json_object_new_string(pszDT);
    1104         319 :             CPLFree(pszDT);
    1105             :         }
    1106             :         else
    1107             :         {
    1108           2 :             poObjProp =
    1109           2 :                 json_object_new_string(poFeature->GetFieldAsString(nField));
    1110             :         }
    1111             : 
    1112        1920 :         json_object_object_add(poObjProps, poFieldDefn->GetNameRef(),
    1113             :                                poObjProp);
    1114             :     }
    1115             : 
    1116        1692 :     if (bUseNativeMedia)
    1117             :     {
    1118           5 :         json_object_put(poNativeObjProp);
    1119             :     }
    1120             : 
    1121        1692 :     return poObjProps;
    1122             : }
    1123             : 
    1124             : /************************************************************************/
    1125             : /*                        GetLinearCollection()                         */
    1126             : /************************************************************************/
    1127             : 
    1128             : static std::unique_ptr<OGRGeometry>
    1129           2 : GetLinearCollection(const OGRGeometryCollection *poGeomColl)
    1130             : {
    1131           4 :     auto poFlatGeom = std::make_unique<OGRGeometryCollection>();
    1132           5 :     for (const auto *poSubGeom : *poGeomColl)
    1133             :     {
    1134           3 :         if (wkbFlatten(poSubGeom->getGeometryType()) == wkbGeometryCollection)
    1135             :         {
    1136           2 :             poFlatGeom->addGeometry(
    1137           2 :                 GetLinearCollection(poSubGeom->toGeometryCollection()));
    1138             :         }
    1139             :         else
    1140             :         {
    1141             :             auto poNewGeom = OGRGeometryFactory::forceTo(
    1142           2 :                 std::unique_ptr<OGRGeometry>(poSubGeom->clone()),
    1143           6 :                 OGR_GT_GetLinear(poSubGeom->getGeometryType()));
    1144           2 :             if (poNewGeom)
    1145           2 :                 poFlatGeom->addGeometry(std::move(poNewGeom));
    1146             :         }
    1147             :     }
    1148           4 :     return poFlatGeom;
    1149             : }
    1150             : 
    1151             : /************************************************************************/
    1152             : /*                       OGRGeoJSONWriteGeometry                        */
    1153             : /************************************************************************/
    1154             : 
    1155        2028 : json_object *OGRGeoJSONWriteGeometry(const OGRGeometry *poGeometry,
    1156             :                                      const OGRGeoJSONWriteOptions &oOptions)
    1157             : {
    1158        2028 :     if (poGeometry == nullptr)
    1159             :     {
    1160           0 :         CPLAssert(false);
    1161             :         return nullptr;
    1162             :     }
    1163             : 
    1164        2028 :     if (!oOptions.bAllowCurve && poGeometry->hasCurveGeometry(true))
    1165             :     {
    1166             :         const OGRwkbGeometryType eTargetType =
    1167          24 :             OGR_GT_GetLinear(poGeometry->getGeometryType());
    1168          24 :         std::unique_ptr<OGRGeometry> poFlatGeom;
    1169          24 :         if (wkbFlatten(eTargetType) == wkbGeometryCollection)
    1170             :         {
    1171             :             poFlatGeom =
    1172           1 :                 GetLinearCollection(poGeometry->toGeometryCollection());
    1173             :         }
    1174             :         else
    1175             :         {
    1176          46 :             poFlatGeom = OGRGeometryFactory::forceTo(
    1177          69 :                 std::unique_ptr<OGRGeometry>(poGeometry->clone()), eTargetType);
    1178             :         }
    1179          24 :         return OGRGeoJSONWriteGeometry(poFlatGeom.get(), oOptions);
    1180             :     }
    1181             : 
    1182        2004 :     OGRwkbGeometryType eFType = wkbFlatten(poGeometry->getGeometryType());
    1183             :     // For point empty, return a null geometry. For other empty geometry types,
    1184             :     // we will generate an empty coordinate array, which is probably also
    1185             :     // borderline.
    1186        2004 :     if (eFType == wkbPoint && poGeometry->IsEmpty())
    1187             :     {
    1188           2 :         return nullptr;
    1189             :     }
    1190             : 
    1191           0 :     std::unique_ptr<OGRGeometry> poTmpGeom;  // keep in that scope
    1192        2002 :     if (eFType == wkbCircularString)
    1193             :     {
    1194          25 :         auto poCS = poGeometry->toCircularString();
    1195          25 :         const int nNumPoints = poCS->getNumPoints();
    1196          25 :         constexpr int MAX_POINTS_PER_CC = 11;
    1197          25 :         if (nNumPoints > MAX_POINTS_PER_CC)
    1198             :         {
    1199           2 :             auto poCC = std::make_unique<OGRCompoundCurve>();
    1200           1 :             auto poSubCS = std::make_unique<OGRCircularString>();
    1201          14 :             for (int i = 0; i < nNumPoints; ++i)
    1202             :             {
    1203          26 :                 OGRPoint oPoint;
    1204          13 :                 poCS->getPoint(i, &oPoint);
    1205          13 :                 poSubCS->addPoint(&oPoint);
    1206          13 :                 if (poSubCS->getNumPoints() == MAX_POINTS_PER_CC)
    1207             :                 {
    1208           1 :                     poCC->addCurve(std::move(poSubCS));
    1209           1 :                     poSubCS = std::make_unique<OGRCircularString>();
    1210           1 :                     poSubCS->addPoint(&oPoint);
    1211             :                 }
    1212             :             }
    1213           1 :             if (poSubCS->getNumPoints() > 1)
    1214           1 :                 poCC->addCurve(std::move(poSubCS));
    1215           1 :             poTmpGeom = std::move(poCC);
    1216           1 :             poGeometry = poTmpGeom.get();
    1217           1 :             eFType = wkbCompoundCurve;
    1218             :         }
    1219             :     }
    1220             : 
    1221        2002 :     json_object *poObj = json_object_new_object();
    1222        2002 :     CPLAssert(nullptr != poObj);
    1223             : 
    1224             :     /* -------------------------------------------------------------------- */
    1225             :     /*      Build "type" member of GeoJSON "geometry" object.               */
    1226             :     /* -------------------------------------------------------------------- */
    1227             : 
    1228        2002 :     const char *pszName = OGRGeoJSONGetGeometryName(poGeometry);
    1229        2002 :     json_object_object_add(poObj, "type", json_object_new_string(pszName));
    1230             : 
    1231             :     /* -------------------------------------------------------------------- */
    1232             :     /*      Build "coordinates" member of GeoJSON "geometry" object.        */
    1233             :     /* -------------------------------------------------------------------- */
    1234        2002 :     json_object *poObjGeom = nullptr;
    1235             : 
    1236        2002 :     if (eFType == wkbGeometryCollection || eFType == wkbMultiCurve ||
    1237             :         eFType == wkbMultiSurface)
    1238             :     {
    1239          39 :         poObjGeom = OGRGeoJSONWriteGeometryCollection(
    1240             :             poGeometry->toGeometryCollection(), oOptions);
    1241          39 :         json_object_object_add(poObj, "geometries", poObjGeom);
    1242             :     }
    1243        1963 :     else if (eFType == wkbCompoundCurve)
    1244             :     {
    1245          14 :         poObjGeom = OGRGeoJSONWriteCompoundCurve(poGeometry->toCompoundCurve(),
    1246             :                                                  oOptions);
    1247          14 :         json_object_object_add(poObj, "geometries", poObjGeom);
    1248             :     }
    1249        1949 :     else if (eFType == wkbCurvePolygon)
    1250             :     {
    1251             :         poObjGeom =
    1252           9 :             OGRGeoJSONWriteCurvePolygon(poGeometry->toCurvePolygon(), oOptions);
    1253           9 :         json_object_object_add(poObj, "geometries", poObjGeom);
    1254             :     }
    1255             :     else
    1256             :     {
    1257        1940 :         if (wkbPoint == eFType)
    1258         231 :             poObjGeom = OGRGeoJSONWritePoint(poGeometry->toPoint(), oOptions);
    1259        1709 :         else if (wkbLineString == eFType || wkbCircularString == eFType)
    1260         159 :             poObjGeom = OGRGeoJSONWriteSimpleCurve(poGeometry->toSimpleCurve(),
    1261             :                                                    oOptions);
    1262        1550 :         else if (wkbPolygon == eFType)
    1263             :             poObjGeom =
    1264        1202 :                 OGRGeoJSONWritePolygon(poGeometry->toPolygon(), oOptions);
    1265         348 :         else if (wkbMultiPoint == eFType)
    1266             :             poObjGeom =
    1267         103 :                 OGRGeoJSONWriteMultiPoint(poGeometry->toMultiPoint(), oOptions);
    1268         245 :         else if (wkbMultiLineString == eFType)
    1269          44 :             poObjGeom = OGRGeoJSONWriteMultiLineString(
    1270             :                 poGeometry->toMultiLineString(), oOptions);
    1271         201 :         else if (wkbMultiPolygon == eFType)
    1272         200 :             poObjGeom = OGRGeoJSONWriteMultiPolygon(
    1273             :                 poGeometry->toMultiPolygon(), oOptions);
    1274             :         else
    1275             :         {
    1276           1 :             CPLError(
    1277             :                 CE_Failure, CPLE_NotSupported,
    1278             :                 "OGR geometry type unsupported as a GeoJSON geometry detected. "
    1279             :                 "Feature gets NULL geometry assigned.");
    1280             :         }
    1281             : 
    1282        1940 :         if (poObjGeom != nullptr)
    1283             :         {
    1284        1931 :             json_object_object_add(poObj, "coordinates", poObjGeom);
    1285             :         }
    1286             :         else
    1287             :         {
    1288           9 :             json_object_put(poObj);
    1289           9 :             poObj = nullptr;
    1290             :         }
    1291             :     }
    1292             : 
    1293        2002 :     return poObj;
    1294             : }
    1295             : 
    1296             : /************************************************************************/
    1297             : /*                         OGRGeoJSONWritePoint                         */
    1298             : /************************************************************************/
    1299             : 
    1300         536 : json_object *OGRGeoJSONWritePoint(const OGRPoint *poPoint,
    1301             :                                   const OGRGeoJSONWriteOptions &oOptions)
    1302             : {
    1303         536 :     CPLAssert(nullptr != poPoint);
    1304             : 
    1305         536 :     json_object *poObj = nullptr;
    1306             : 
    1307             :     // Generate "coordinates" object
    1308         536 :     if (!poPoint->IsEmpty())
    1309             :     {
    1310         536 :         if (oOptions.bAllowMeasure && poPoint->IsMeasured())
    1311             :         {
    1312           7 :             if (poPoint->Is3D())
    1313             :             {
    1314           1 :                 poObj = OGRGeoJSONWriteCoords(poPoint->getX(), poPoint->getY(),
    1315           2 :                                               poPoint->getZ(), poPoint->getM(),
    1316             :                                               oOptions);
    1317             :             }
    1318             :             else
    1319             :             {
    1320           6 :                 poObj = OGRGeoJSONWriteCoords(poPoint->getX(), poPoint->getY(),
    1321          12 :                                               std::nullopt, poPoint->getM(),
    1322             :                                               oOptions);
    1323             :             }
    1324             :         }
    1325         529 :         else if (poPoint->Is3D())
    1326             :         {
    1327             :             poObj =
    1328         652 :                 OGRGeoJSONWriteCoords(poPoint->getX(), poPoint->getY(),
    1329         652 :                                       poPoint->getZ(), std::nullopt, oOptions);
    1330             :         }
    1331             :         else
    1332             :         {
    1333         203 :             poObj = OGRGeoJSONWriteCoords(poPoint->getX(), poPoint->getY(),
    1334             :                                           std::nullopt, std::nullopt, oOptions);
    1335             :         }
    1336             :     }
    1337             : 
    1338         536 :     return poObj;
    1339             : }
    1340             : 
    1341             : /************************************************************************/
    1342             : /*                      OGRGeoJSONWriteSimpleCurve                      */
    1343             : /************************************************************************/
    1344             : 
    1345         219 : json_object *OGRGeoJSONWriteSimpleCurve(const OGRSimpleCurve *poLine,
    1346             :                                         const OGRGeoJSONWriteOptions &oOptions)
    1347             : {
    1348         219 :     CPLAssert(nullptr != poLine);
    1349             : 
    1350             :     // Generate "coordinates" object for 2D or 3D dimension.
    1351         219 :     json_object *poObj = OGRGeoJSONWriteLineCoords(poLine, oOptions);
    1352             : 
    1353         219 :     return poObj;
    1354             : }
    1355             : 
    1356             : /************************************************************************/
    1357             : /*                        OGRGeoJSONWritePolygon                        */
    1358             : /************************************************************************/
    1359             : 
    1360        1437 : json_object *OGRGeoJSONWritePolygon(const OGRPolygon *poPolygon,
    1361             :                                     const OGRGeoJSONWriteOptions &oOptions)
    1362             : {
    1363        1437 :     CPLAssert(nullptr != poPolygon);
    1364             : 
    1365             :     // Generate "coordinates" array object.
    1366        1437 :     json_object *poObj = json_object_new_array();
    1367             : 
    1368        1437 :     bool bExteriorRing = true;
    1369        2896 :     for (const auto *poRing : *poPolygon)
    1370             :     {
    1371             :         json_object *poObjRing =
    1372        1462 :             OGRGeoJSONWriteRingCoords(poRing, bExteriorRing, oOptions);
    1373        1462 :         bExteriorRing = false;
    1374        1462 :         if (poObjRing == nullptr)
    1375             :         {
    1376           3 :             json_object_put(poObj);
    1377           3 :             return nullptr;
    1378             :         }
    1379        1459 :         json_object_array_add(poObj, poObjRing);
    1380             :     }
    1381             : 
    1382        1434 :     return poObj;
    1383             : }
    1384             : 
    1385             : /************************************************************************/
    1386             : /*                      OGRGeoJSONWriteMultiPoint                       */
    1387             : /************************************************************************/
    1388             : 
    1389         103 : json_object *OGRGeoJSONWriteMultiPoint(const OGRMultiPoint *poGeometry,
    1390             :                                        const OGRGeoJSONWriteOptions &oOptions)
    1391             : {
    1392         103 :     CPLAssert(nullptr != poGeometry);
    1393             : 
    1394             :     // Generate "coordinates" object
    1395         103 :     json_object *poObj = json_object_new_array();
    1396             : 
    1397         407 :     for (const auto *poPoint : poGeometry)
    1398             :     {
    1399         305 :         json_object *poObjPoint = OGRGeoJSONWritePoint(poPoint, oOptions);
    1400         305 :         if (poObjPoint == nullptr)
    1401             :         {
    1402           1 :             json_object_put(poObj);
    1403           1 :             return nullptr;
    1404             :         }
    1405             : 
    1406         304 :         json_object_array_add(poObj, poObjPoint);
    1407             :     }
    1408             : 
    1409         102 :     return poObj;
    1410             : }
    1411             : 
    1412             : /************************************************************************/
    1413             : /*                    OGRGeoJSONWriteMultiLineString                    */
    1414             : /************************************************************************/
    1415             : 
    1416             : json_object *
    1417          44 : OGRGeoJSONWriteMultiLineString(const OGRMultiLineString *poGeometry,
    1418             :                                const OGRGeoJSONWriteOptions &oOptions)
    1419             : {
    1420          44 :     CPLAssert(nullptr != poGeometry);
    1421             : 
    1422             :     // Generate "coordinates" object
    1423          44 :     json_object *poObj = json_object_new_array();
    1424             : 
    1425         103 :     for (const auto *poLine : poGeometry)
    1426             :     {
    1427          60 :         json_object *poObjLine = OGRGeoJSONWriteSimpleCurve(poLine, oOptions);
    1428          60 :         if (poObjLine == nullptr)
    1429             :         {
    1430           1 :             json_object_put(poObj);
    1431           1 :             return nullptr;
    1432             :         }
    1433             : 
    1434          59 :         json_object_array_add(poObj, poObjLine);
    1435             :     }
    1436             : 
    1437          43 :     return poObj;
    1438             : }
    1439             : 
    1440             : /************************************************************************/
    1441             : /*                     OGRGeoJSONWriteMultiPolygon                      */
    1442             : /************************************************************************/
    1443             : 
    1444         200 : json_object *OGRGeoJSONWriteMultiPolygon(const OGRMultiPolygon *poGeometry,
    1445             :                                          const OGRGeoJSONWriteOptions &oOptions)
    1446             : {
    1447         200 :     CPLAssert(nullptr != poGeometry);
    1448             : 
    1449             :     // Generate "coordinates" object
    1450         200 :     json_object *poObj = json_object_new_array();
    1451             : 
    1452         429 :     for (const auto *poPoly : poGeometry)
    1453             :     {
    1454         230 :         json_object *poObjPoly = OGRGeoJSONWritePolygon(poPoly, oOptions);
    1455         230 :         if (poObjPoly == nullptr)
    1456             :         {
    1457           1 :             json_object_put(poObj);
    1458           1 :             return nullptr;
    1459             :         }
    1460             : 
    1461         229 :         json_object_array_add(poObj, poObjPoly);
    1462             :     }
    1463             : 
    1464         199 :     return poObj;
    1465             : }
    1466             : 
    1467             : /************************************************************************/
    1468             : /*                  OGRGeoJSONWriteCollectionGeneric()                  */
    1469             : /************************************************************************/
    1470             : 
    1471             : template <class T>
    1472             : static json_object *
    1473          62 : OGRGeoJSONWriteCollectionGeneric(const T *poGeometry,
    1474             :                                  const OGRGeoJSONWriteOptions &oOptions)
    1475             : {
    1476          62 :     CPLAssert(nullptr != poGeometry);
    1477             : 
    1478             :     /* Generate "geometries" object. */
    1479          62 :     json_object *poObj = json_object_new_array();
    1480             : 
    1481         205 :     for (const OGRGeometry *poGeom : *poGeometry)
    1482             :     {
    1483         144 :         json_object *poObjGeom = OGRGeoJSONWriteGeometry(poGeom, oOptions);
    1484         144 :         if (poObjGeom == nullptr)
    1485             :         {
    1486           1 :             json_object_put(poObj);
    1487           1 :             return nullptr;
    1488             :         }
    1489             : 
    1490         143 :         json_object_array_add(poObj, poObjGeom);
    1491             :     }
    1492             : 
    1493          61 :     return poObj;
    1494             : }
    1495             : 
    1496             : /************************************************************************/
    1497             : /*                  OGRGeoJSONWriteGeometryCollection                   */
    1498             : /************************************************************************/
    1499             : 
    1500             : json_object *
    1501          39 : OGRGeoJSONWriteGeometryCollection(const OGRGeometryCollection *poGeometry,
    1502             :                                   const OGRGeoJSONWriteOptions &oOptions)
    1503             : {
    1504          39 :     return OGRGeoJSONWriteCollectionGeneric(poGeometry, oOptions);
    1505             : }
    1506             : 
    1507             : /************************************************************************/
    1508             : /*                     OGRGeoJSONWriteCompoundCurve                     */
    1509             : /************************************************************************/
    1510             : 
    1511             : json_object *
    1512          14 : OGRGeoJSONWriteCompoundCurve(const OGRCompoundCurve *poGeometry,
    1513             :                              const OGRGeoJSONWriteOptions &oOptions)
    1514             : {
    1515          14 :     return OGRGeoJSONWriteCollectionGeneric(poGeometry, oOptions);
    1516             : }
    1517             : 
    1518             : /************************************************************************/
    1519             : /*                     OGRGeoJSONWriteCurvePolygon                      */
    1520             : /************************************************************************/
    1521             : 
    1522           9 : json_object *OGRGeoJSONWriteCurvePolygon(const OGRCurvePolygon *poGeometry,
    1523             :                                          const OGRGeoJSONWriteOptions &oOptions)
    1524             : {
    1525           9 :     return OGRGeoJSONWriteCollectionGeneric(poGeometry, oOptions);
    1526             : }
    1527             : 
    1528             : /************************************************************************/
    1529             : /*                        OGRGeoJSONWriteCoords                         */
    1530             : /************************************************************************/
    1531             : 
    1532       14548 : json_object *OGRGeoJSONWriteCoords(double dfX, double dfY,
    1533             :                                    std::optional<double> dfZ,
    1534             :                                    std::optional<double> dfM,
    1535             :                                    const OGRGeoJSONWriteOptions &oOptions)
    1536             : {
    1537       14548 :     json_object *poObjCoords = nullptr;
    1538       43628 :     if (!std::isfinite(dfX) || !std::isfinite(dfY) ||
    1539       43628 :         (dfZ && !std::isfinite(*dfZ)) || (dfM && !std::isfinite(*dfM)))
    1540             :     {
    1541           8 :         CPLError(CE_Warning, CPLE_AppDefined,
    1542             :                  "Infinite or NaN coordinate encountered");
    1543           8 :         return nullptr;
    1544             :     }
    1545       14540 :     poObjCoords = json_object_new_array();
    1546       14540 :     json_object_array_add(poObjCoords, json_object_new_coord(dfX, 1, oOptions));
    1547       14540 :     json_object_array_add(poObjCoords, json_object_new_coord(dfY, 2, oOptions));
    1548       14540 :     int nIdx = 3;
    1549       14540 :     if (dfZ)
    1550             :     {
    1551        1453 :         json_object_array_add(poObjCoords,
    1552        1453 :                               json_object_new_coord(*dfZ, nIdx, oOptions));
    1553        1453 :         nIdx++;
    1554             :     }
    1555       14540 :     if (dfM)
    1556             :     {
    1557         107 :         json_object_array_add(poObjCoords,
    1558         107 :                               json_object_new_coord(*dfM, nIdx, oOptions));
    1559             :     }
    1560             : 
    1561       14540 :     return poObjCoords;
    1562             : }
    1563             : 
    1564             : /************************************************************************/
    1565             : /*                      OGRGeoJSONWriteLineCoords                       */
    1566             : /************************************************************************/
    1567             : 
    1568         219 : json_object *OGRGeoJSONWriteLineCoords(const OGRSimpleCurve *poLine,
    1569             :                                        const OGRGeoJSONWriteOptions &oOptions)
    1570             : {
    1571         219 :     json_object *poObjCoords = json_object_new_array();
    1572             : 
    1573         219 :     const int nCount = poLine->getNumPoints();
    1574         219 :     const auto bHasZ = poLine->Is3D();
    1575         219 :     const auto bHasM = oOptions.bAllowMeasure && poLine->IsMeasured();
    1576        1733 :     for (int i = 0; i < nCount; ++i)
    1577             :     {
    1578             :         json_object *poObjPoint;
    1579        1517 :         if (bHasZ)
    1580             :         {
    1581         442 :             if (bHasM)
    1582             :             {
    1583          54 :                 poObjPoint = OGRGeoJSONWriteCoords(
    1584          54 :                     poLine->getX(i), poLine->getY(i), poLine->getZ(i),
    1585         108 :                     poLine->getM(i), oOptions);
    1586             :             }
    1587             :             else
    1588             :             {
    1589         776 :                 poObjPoint = OGRGeoJSONWriteCoords(
    1590         776 :                     poLine->getX(i), poLine->getY(i), poLine->getZ(i),
    1591             :                     std::nullopt, oOptions);
    1592             :             }
    1593             :         }
    1594        1075 :         else if (bHasM)
    1595             :         {
    1596             :             poObjPoint =
    1597          38 :                 OGRGeoJSONWriteCoords(poLine->getX(i), poLine->getY(i),
    1598          76 :                                       std::nullopt, poLine->getM(i), oOptions);
    1599             :         }
    1600             :         else
    1601             :         {
    1602             :             poObjPoint =
    1603        1037 :                 OGRGeoJSONWriteCoords(poLine->getX(i), poLine->getY(i),
    1604             :                                       std::nullopt, std::nullopt, oOptions);
    1605             :         }
    1606        1517 :         if (poObjPoint == nullptr)
    1607             :         {
    1608           3 :             json_object_put(poObjCoords);
    1609           3 :             return nullptr;
    1610             :         }
    1611        1514 :         json_object_array_add(poObjCoords, poObjPoint);
    1612             :     }
    1613             : 
    1614         216 :     return poObjCoords;
    1615             : }
    1616             : 
    1617             : /************************************************************************/
    1618             : /*                      OGRGeoJSONWriteRingCoords                       */
    1619             : /************************************************************************/
    1620             : 
    1621        1462 : json_object *OGRGeoJSONWriteRingCoords(const OGRLinearRing *poLine,
    1622             :                                        bool bIsExteriorRing,
    1623             :                                        const OGRGeoJSONWriteOptions &oOptions)
    1624             : {
    1625        1462 :     json_object *poObjCoords = json_object_new_array();
    1626             : 
    1627        1560 :     const bool bInvertOrder = oOptions.bPolygonRightHandRule &&
    1628          86 :                               ((bIsExteriorRing && poLine->isClockwise()) ||
    1629          29 :                                (!bIsExteriorRing && !poLine->isClockwise()));
    1630             : 
    1631        1462 :     const int nCount = poLine->getNumPoints();
    1632        1462 :     const auto bHasZ = poLine->Is3D();
    1633        1462 :     const auto bHasM = oOptions.bAllowMeasure && poLine->IsMeasured();
    1634       13954 :     for (int i = 0; i < nCount; ++i)
    1635             :     {
    1636       12495 :         const int nIdx = (bInvertOrder) ? nCount - 1 - i : i;
    1637             :         json_object *poObjPoint;
    1638       12495 :         if (bHasZ)
    1639             :         {
    1640         685 :             if (bHasM)
    1641             :             {
    1642           4 :                 poObjPoint = OGRGeoJSONWriteCoords(
    1643           4 :                     poLine->getX(nIdx), poLine->getY(nIdx), poLine->getZ(nIdx),
    1644           8 :                     poLine->getM(nIdx), oOptions);
    1645             :             }
    1646             :             else
    1647             :             {
    1648        1362 :                 poObjPoint = OGRGeoJSONWriteCoords(
    1649        1362 :                     poLine->getX(nIdx), poLine->getY(nIdx), poLine->getZ(nIdx),
    1650             :                     std::nullopt, oOptions);
    1651             :             }
    1652             :         }
    1653       11810 :         else if (bHasM)
    1654             :         {
    1655           4 :             poObjPoint = OGRGeoJSONWriteCoords(poLine->getX(nIdx),
    1656             :                                                poLine->getY(nIdx), std::nullopt,
    1657           8 :                                                poLine->getM(nIdx), oOptions);
    1658             :         }
    1659             :         else
    1660             :         {
    1661             :             poObjPoint =
    1662       11806 :                 OGRGeoJSONWriteCoords(poLine->getX(nIdx), poLine->getY(nIdx),
    1663             :                                       std::nullopt, std::nullopt, oOptions);
    1664             :         }
    1665       12495 :         if (poObjPoint == nullptr)
    1666             :         {
    1667           3 :             json_object_put(poObjCoords);
    1668           3 :             return nullptr;
    1669             :         }
    1670       12492 :         json_object_array_add(poObjCoords, poObjPoint);
    1671             :     }
    1672             : 
    1673        1459 :     return poObjCoords;
    1674             : }
    1675             : 
    1676             : /************************************************************************/
    1677             : /*         OGR_json_float_with_significant_figures_to_string()          */
    1678             : /************************************************************************/
    1679             : 
    1680           9 : static int OGR_json_float_with_significant_figures_to_string(
    1681             :     struct json_object *jso, struct printbuf *pb, int /* level */,
    1682             :     int /* flags */)
    1683             : {
    1684           9 :     char szBuffer[75] = {};
    1685           9 :     int nSize = 0;
    1686           9 :     const float fVal = static_cast<float>(json_object_get_double(jso));
    1687           9 :     if (std::isnan(fVal))
    1688           0 :         nSize = CPLsnprintf(szBuffer, sizeof(szBuffer), "NaN");
    1689           9 :     else if (std::isinf(fVal))
    1690             :     {
    1691           0 :         if (fVal > 0)
    1692           0 :             nSize = CPLsnprintf(szBuffer, sizeof(szBuffer), "Infinity");
    1693             :         else
    1694           0 :             nSize = CPLsnprintf(szBuffer, sizeof(szBuffer), "-Infinity");
    1695             :     }
    1696             :     else
    1697             :     {
    1698             :         const void *userData =
    1699             : #if (!defined(JSON_C_VERSION_NUM)) || (JSON_C_VERSION_NUM < JSON_C_VER_013)
    1700             :             jso->_userdata;
    1701             : #else
    1702           9 :             json_object_get_userdata(jso);
    1703             : #endif
    1704           9 :         const uintptr_t nSignificantFigures =
    1705             :             reinterpret_cast<uintptr_t>(userData);
    1706           9 :         const bool bSignificantFiguresIsNegative =
    1707           9 :             (nSignificantFigures >> (8 * sizeof(nSignificantFigures) - 1)) != 0;
    1708           9 :         const int nInitialSignificantFigures =
    1709             :             bSignificantFiguresIsNegative
    1710           9 :                 ? 8
    1711             :                 : static_cast<int>(nSignificantFigures);
    1712           9 :         nSize = OGRFormatFloat(szBuffer, sizeof(szBuffer), fVal,
    1713             :                                nInitialSignificantFigures, 'g');
    1714             :     }
    1715             : 
    1716          18 :     return printbuf_memappend(pb, szBuffer, nSize);
    1717             : }
    1718             : 
    1719             : /************************************************************************/
    1720             : /*           json_object_new_float_with_significant_figures()           */
    1721             : /************************************************************************/
    1722             : 
    1723             : json_object *
    1724           9 : json_object_new_float_with_significant_figures(float fVal,
    1725             :                                                int nSignificantFigures)
    1726             : {
    1727           9 :     json_object *jso = json_object_new_double(double(fVal));
    1728           9 :     json_object_set_serializer(
    1729             :         jso, OGR_json_float_with_significant_figures_to_string,
    1730           9 :         reinterpret_cast<void *>(static_cast<uintptr_t>(nSignificantFigures)),
    1731             :         nullptr);
    1732           9 :     return jso;
    1733             : }
    1734             : 
    1735             : /*! @endcond */
    1736             : 
    1737             : /************************************************************************/
    1738             : /*                          OGR_G_ExportToJson                          */
    1739             : /************************************************************************/
    1740             : 
    1741             : /**
    1742             :  * \brief Convert a geometry into GeoJSON format.
    1743             :  *
    1744             :  * The returned string should be freed with CPLFree() when no longer required.
    1745             :  *
    1746             :  * This method is the same as the C++ method OGRGeometry::exportToJson().
    1747             :  *
    1748             :  * @param hGeometry handle to the geometry.
    1749             :  * @return A GeoJSON fragment or NULL in case of error.
    1750             :  */
    1751             : 
    1752           2 : char *OGR_G_ExportToJson(OGRGeometryH hGeometry)
    1753             : {
    1754           2 :     return OGR_G_ExportToJsonEx(hGeometry, nullptr);
    1755             : }
    1756             : 
    1757             : /************************************************************************/
    1758             : /*                         OGR_G_ExportToJsonEx                         */
    1759             : /************************************************************************/
    1760             : 
    1761             : /**
    1762             :  * \brief Convert a geometry into GeoJSON-style format.
    1763             :  *
    1764             :  * The returned string should be freed with CPLFree() when no longer required.
    1765             :  *
    1766             :  * If setting ALLOW_CURVE=YES and ALLOW_MEASURE=YES, the result is compatible
    1767             :  * of JSON-FG geometries. If there is a SRS attached to the geometry, and the
    1768             :  * geometry is aimed at being stored in the "place" member of JSON-FG features,
    1769             :  * then the COORDINATE_ORDER option must be set to AUTHORITY_COMPLIANT.
    1770             :  *
    1771             :  * The following options are supported :
    1772             :  * <ul>
    1773             :  * <li>COORDINATE_PRECISION=number: maximum number of figures after decimal
    1774             :  * separator to write in coordinates.</li>
    1775             :  * <li>XY_COORD_PRECISION=integer: number of decimal figures for X,Y coordinates
    1776             :  * (added in GDAL 3.9)</li>
    1777             :  * <li>Z_COORD_PRECISION=integer: number of decimal figures for Z coordinates
    1778             :  * (added in GDAL 3.9)</li>
    1779             :  * <li>SIGNIFICANT_FIGURES=number: maximum number of significant figures.</li>
    1780             :  * <li>ALLOW_CURVE=YES/NO: whether curve geometries are allowed. When set to NO
    1781             :  * (its default value), they are converted to linear geometries first.
    1782             :  * Curves are not allowed in GeoJSON, but they are in JSON-FG geometries.
    1783             :  * (added in GDAL 3.12.1)</li>
    1784             :  * <li>ALLOW_MEASURE=YES/NO: whether the measure (M) component of geometries is
    1785             :  * allowed. When set to NO (its default value), it is dropped when present.
    1786             :  * Measures are not allowed in GeoJSON, but they are in JSON-FG geometries.
    1787             :  * (added in GDAL 3.12.1)</li>
    1788             :  * <li>COORDINATE_ORDER=TRADITIONAL_GIS_ORDER/AUTHORITY_COMPLIANT (added in GDAL 3.12.1):
    1789             :  * When a SRS is attached to the geometry, and AUTHORITY_COMPLIANT is used,
    1790             :  * the coordinates will be emitted in the order of the official SRS definition.
    1791             :  * When using TRADITIONAL_GIS_ORDER (the default), coordinates are emitted in
    1792             :  * longitude/easting first, latitude/northing second.
    1793             :  * When no SRS is attached, coordinates are emitted in the order they are set
    1794             :  * in the geometry.
    1795             :  * When this function is used to emit JSON-FG geometries stored in the "place"
    1796             :  * member, this option must be set to AUTHORITY_COMPLIANT if there is a SRS
    1797             :  * attached to the geometry.
    1798             :  * </li>
    1799             :  * </ul>
    1800             :  *
    1801             :  * If XY_COORD_PRECISION or Z_COORD_PRECISION is specified, COORDINATE_PRECISION
    1802             :  * or SIGNIFICANT_FIGURES will be ignored if specified.
    1803             :  * If COORDINATE_PRECISION is defined, SIGNIFICANT_FIGURES will be ignored if
    1804             :  * specified.
    1805             :  * When none are defined, the default is COORDINATE_PRECISION=15.
    1806             :  *
    1807             :  * This method is the same as the C++ method OGRGeometry::exportToJson().
    1808             :  *
    1809             :  * @param hGeometry handle to the geometry.
    1810             :  * @param papszOptions a null terminated list of options.
    1811             :  * @return A GeoJSON fragment or NULL in case of error.
    1812             :  *
    1813             :  */
    1814             : 
    1815         277 : char *OGR_G_ExportToJsonEx(OGRGeometryH hGeometry, CSLConstList papszOptions)
    1816             : {
    1817         277 :     VALIDATE_POINTER1(hGeometry, "OGR_G_ExportToJson", nullptr);
    1818             : 
    1819         277 :     OGRGeometry *poGeometry = OGRGeometry::FromHandle(hGeometry);
    1820             : 
    1821             :     const char *pszCoordPrecision =
    1822         277 :         CSLFetchNameValueDef(papszOptions, "COORDINATE_PRECISION", "-1");
    1823             : 
    1824             :     const int nSignificantFigures =
    1825         277 :         atoi(CSLFetchNameValueDef(papszOptions, "SIGNIFICANT_FIGURES", "-1"));
    1826             : 
    1827         554 :     OGRGeoJSONWriteOptions oOptions;
    1828         277 :     oOptions.nXYCoordPrecision = atoi(CSLFetchNameValueDef(
    1829             :         papszOptions, "XY_COORD_PRECISION", pszCoordPrecision));
    1830         277 :     oOptions.nZCoordPrecision = atoi(CSLFetchNameValueDef(
    1831             :         papszOptions, "Z_COORD_PRECISION", pszCoordPrecision));
    1832         277 :     oOptions.nSignificantFigures = nSignificantFigures;
    1833         277 :     oOptions.bAllowCurve =
    1834         277 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "ALLOW_CURVE", "NO"));
    1835         277 :     oOptions.bAllowMeasure =
    1836         277 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "ALLOW_MEASURE", "NO"));
    1837             : 
    1838         277 :     bool bHasSwappedXY = false;
    1839         277 :     const char *pszCoordinateOrder = CSLFetchNameValueDef(
    1840             :         papszOptions, "COORDINATE_ORDER", "TRADITIONAL_GIS_ORDER");
    1841         277 :     if (EQUAL(pszCoordinateOrder, "TRADITIONAL_GIS_ORDER"))
    1842             :     {
    1843             :         // If the CRS has latitude, longitude (or northing, easting) axis order,
    1844             :         // and the data axis to SRS axis mapping doesn't change that order,
    1845             :         // then swap X and Y values.
    1846         272 :         const auto poSRS = poGeometry->getSpatialReference();
    1847         370 :         if (poSRS && (poSRS->EPSGTreatsAsLatLong() ||
    1848          98 :                       poSRS->EPSGTreatsAsNorthingEasting()))
    1849             :         {
    1850             :             auto anMapping =
    1851         184 :                 std::vector<int>(poSRS->GetDataAxisToSRSAxisMapping());
    1852          92 :             anMapping.resize(2);
    1853          92 :             if (anMapping == std::vector<int>{1, 2})
    1854             :             {
    1855           5 :                 poGeometry->swapXY();
    1856           5 :                 bHasSwappedXY = true;
    1857             :             }
    1858             :         }
    1859             :     }
    1860           5 :     else if (EQUAL(pszCoordinateOrder, "AUTHORITY_COMPLIANT"))
    1861             :     {
    1862           4 :         const auto poSRS = poGeometry->getSpatialReference();
    1863           6 :         if (poSRS && (poSRS->EPSGTreatsAsLatLong() ||
    1864           2 :                       poSRS->EPSGTreatsAsNorthingEasting()))
    1865             :         {
    1866             :             auto anMapping =
    1867           4 :                 std::vector<int>(poSRS->GetDataAxisToSRSAxisMapping());
    1868           2 :             anMapping.resize(2);
    1869           2 :             if (anMapping == std::vector<int>{2, 1})
    1870             :             {
    1871           1 :                 poGeometry->swapXY();
    1872           1 :                 bHasSwappedXY = true;
    1873             :             }
    1874             :         }
    1875             :     }
    1876             :     else
    1877             :     {
    1878           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    1879             :                  "Unsupported COORDINATE_ORDER='%s'", pszCoordinateOrder);
    1880           1 :         return nullptr;
    1881             :     }
    1882             : 
    1883         276 :     json_object *poObj = OGRGeoJSONWriteGeometry(poGeometry, oOptions);
    1884             : 
    1885             :     // Unswap back
    1886         276 :     if (bHasSwappedXY)
    1887           6 :         poGeometry->swapXY();
    1888             : 
    1889         276 :     if (nullptr != poObj)
    1890             :     {
    1891         273 :         char *pszJson = CPLStrdup(json_object_to_json_string(poObj));
    1892             : 
    1893             :         // Release JSON tree.
    1894         273 :         json_object_put(poObj);
    1895             : 
    1896         273 :         return pszJson;
    1897             :     }
    1898             : 
    1899             :     // Translation failed.
    1900           3 :     return nullptr;
    1901             : }

Generated by: LCOV version 1.14