LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/arrow_common - ograrrowwriterlayer.hpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1364 1566 87.1 %
Date: 2025-07-09 17:50:03 Functions: 39 42 92.9 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  Arrow generic code
       4             :  * Purpose:  Arrow generic code
       5             :  * Author:   Even Rouault, <even.rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2022, Planet Labs
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #ifndef OGARROWWRITERLAYER_HPP_INCLUDED
      14             : #define OGARROWWRITERLAYER_HPP_INCLUDED
      15             : 
      16             : #include "ogr_arrow.h"
      17             : 
      18             : #include "cpl_json.h"
      19             : #include "cpl_time.h"
      20             : 
      21             : #include "ogrlayerarrow.h"
      22             : #include "ogr_wkb.h"
      23             : 
      24             : #include <array>
      25             : #include <cinttypes>
      26             : #include <limits>
      27             : 
      28             : static constexpr int TZFLAG_UNINITIALIZED = -1;
      29             : 
      30             : #define OGR_ARROW_RETURN_NOT_OK(status, ret_value)                             \
      31             :     do                                                                         \
      32             :     {                                                                          \
      33             :         if (!(status).ok())                                                    \
      34             :         {                                                                      \
      35             :             CPLError(CE_Failure, CPLE_AppDefined, "%s failed",                 \
      36             :                      ARROW_STRINGIFY(status));                                 \
      37             :             return (ret_value);                                                \
      38             :         }                                                                      \
      39             :     } while (false)
      40             : 
      41             : #define OGR_ARROW_RETURN_FALSE_NOT_OK(status)                                  \
      42             :     OGR_ARROW_RETURN_NOT_OK(status, false)
      43             : 
      44             : #define OGR_ARROW_RETURN_OGRERR_NOT_OK(status)                                 \
      45             :     OGR_ARROW_RETURN_NOT_OK(status, OGRERR_FAILURE)
      46             : 
      47             : #define OGR_ARROW_PROPAGATE_OGRERR(ret_value)                                  \
      48             :     do                                                                         \
      49             :     {                                                                          \
      50             :         if ((ret_value) != OGRERR_NONE)                                        \
      51             :             return OGRERR_FAILURE;                                             \
      52             :     } while (0)
      53             : 
      54             : /************************************************************************/
      55             : /*                      OGRArrowWriterLayer()                           */
      56             : /************************************************************************/
      57             : 
      58         422 : inline OGRArrowWriterLayer::OGRArrowWriterLayer(
      59             :     arrow::MemoryPool *poMemoryPool,
      60             :     const std::shared_ptr<arrow::io::OutputStream> &poOutputStream,
      61         422 :     const char *pszLayerName)
      62         422 :     : m_poMemoryPool(poMemoryPool), m_poOutputStream(poOutputStream)
      63             : {
      64         422 :     m_poFeatureDefn = new OGRFeatureDefn(pszLayerName);
      65         422 :     m_poFeatureDefn->SetGeomType(wkbNone);
      66         422 :     m_poFeatureDefn->Reference();
      67         422 :     SetDescription(pszLayerName);
      68         422 : }
      69             : 
      70             : /************************************************************************/
      71             : /*                     ~OGRArrowWriterLayer()                           */
      72             : /************************************************************************/
      73             : 
      74         422 : inline OGRArrowWriterLayer::~OGRArrowWriterLayer()
      75             : {
      76         422 :     CPLDebug("ARROW", "Memory pool (writer layer): bytes_allocated = %" PRId64,
      77         422 :              m_poMemoryPool->bytes_allocated());
      78         422 :     CPLDebug("ARROW", "Memory pool (writer layer): max_memory = %" PRId64,
      79         422 :              m_poMemoryPool->max_memory());
      80             : 
      81         422 :     m_poFeatureDefn->Release();
      82         422 : }
      83             : 
      84             : /************************************************************************/
      85             : /*                         FinalizeWriting()                            */
      86             : /************************************************************************/
      87             : 
      88         410 : inline bool OGRArrowWriterLayer::FinalizeWriting()
      89             : {
      90         410 :     bool ret = true;
      91             : 
      92         410 :     if (!IsFileWriterCreated())
      93             :     {
      94         281 :         CreateWriter();
      95             :     }
      96         410 :     if (IsFileWriterCreated())
      97             :     {
      98         410 :         PerformStepsBeforeFinalFlushGroup();
      99             : 
     100         410 :         if (!m_apoBuilders.empty() && m_apoFieldsFromArrowSchema.empty())
     101         235 :             ret = FlushGroup();
     102             : 
     103         410 :         if (!CloseFileWriter())
     104           0 :             ret = false;
     105             :     }
     106             : 
     107         410 :     return ret;
     108             : }
     109             : 
     110             : /************************************************************************/
     111             : /*                      RemoveIDFromMemberOfEnsembles()                 */
     112             : /************************************************************************/
     113             : 
     114             : /* static */
     115             : inline void
     116         392 : OGRArrowWriterLayer::RemoveIDFromMemberOfEnsembles(CPLJSONObject &obj)
     117             : {
     118             :     // Remove "id" from members of datum ensembles for compatibility with
     119             :     // older PROJ versions
     120             :     // Cf https://github.com/opengeospatial/geoparquet/discussions/110
     121             :     // and https://github.com/OSGeo/PROJ/pull/3221
     122         392 :     if (obj.GetType() == CPLJSONObject::Type::Object)
     123             :     {
     124         498 :         for (auto &subObj : obj.GetChildren())
     125             :         {
     126         380 :             RemoveIDFromMemberOfEnsembles(subObj);
     127             :         }
     128             :     }
     129         302 :     else if (obj.GetType() == CPLJSONObject::Type::Array &&
     130         302 :              obj.GetName() == "members")
     131             :     {
     132           0 :         for (auto &subObj : obj.ToArray())
     133             :         {
     134           0 :             subObj.Delete("id");
     135             :         }
     136             :     }
     137         392 : }
     138             : 
     139             : /************************************************************************/
     140             : /*                            IdentifyCRS()                             */
     141             : /************************************************************************/
     142             : 
     143             : /* static */
     144             : inline OGRSpatialReference
     145          26 : OGRArrowWriterLayer::IdentifyCRS(const OGRSpatialReference *poSRS)
     146             : {
     147          26 :     OGRSpatialReference oSRSIdentified(*poSRS);
     148             : 
     149          26 :     if (poSRS->GetAuthorityName(nullptr) == nullptr)
     150             :     {
     151             :         // Try to find a registered CRS that matches the input one
     152           4 :         int nEntries = 0;
     153           4 :         int *panConfidence = nullptr;
     154             :         OGRSpatialReferenceH *pahSRS =
     155           4 :             poSRS->FindMatches(nullptr, &nEntries, &panConfidence);
     156             : 
     157             :         // If there are several matches >= 90%, take the only one
     158             :         // that is EPSG
     159           4 :         int iOtherAuthority = -1;
     160           4 :         int iEPSG = -1;
     161           4 :         const char *const apszOptions[] = {
     162             :             "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES", nullptr};
     163           4 :         int iConfidenceBestMatch = -1;
     164           6 :         for (int iSRS = 0; iSRS < nEntries; iSRS++)
     165             :         {
     166           4 :             auto poCandidateCRS = OGRSpatialReference::FromHandle(pahSRS[iSRS]);
     167           4 :             if (panConfidence[iSRS] < iConfidenceBestMatch ||
     168           4 :                 panConfidence[iSRS] < 70)
     169             :             {
     170             :                 break;
     171             :             }
     172           3 :             if (poSRS->IsSame(poCandidateCRS, apszOptions))
     173             :             {
     174             :                 const char *pszAuthName =
     175           3 :                     poCandidateCRS->GetAuthorityName(nullptr);
     176           3 :                 if (pszAuthName != nullptr && EQUAL(pszAuthName, "EPSG"))
     177             :                 {
     178           2 :                     iOtherAuthority = -2;
     179           2 :                     if (iEPSG < 0)
     180             :                     {
     181           2 :                         iConfidenceBestMatch = panConfidence[iSRS];
     182           2 :                         iEPSG = iSRS;
     183             :                     }
     184             :                     else
     185             :                     {
     186           0 :                         iEPSG = -1;
     187           0 :                         break;
     188             :                     }
     189             :                 }
     190           1 :                 else if (iEPSG < 0 && pszAuthName != nullptr)
     191             :                 {
     192           1 :                     if (EQUAL(pszAuthName, "OGC"))
     193             :                     {
     194             :                         const char *pszAuthCode =
     195           1 :                             poCandidateCRS->GetAuthorityCode(nullptr);
     196           1 :                         if (pszAuthCode && EQUAL(pszAuthCode, "CRS84"))
     197             :                         {
     198           1 :                             iOtherAuthority = iSRS;
     199           1 :                             break;
     200             :                         }
     201             :                     }
     202           0 :                     else if (iOtherAuthority == -1)
     203             :                     {
     204           0 :                         iConfidenceBestMatch = panConfidence[iSRS];
     205           0 :                         iOtherAuthority = iSRS;
     206             :                     }
     207             :                     else
     208           0 :                         iOtherAuthority = -2;
     209             :                 }
     210             :             }
     211             :         }
     212           4 :         if (iEPSG >= 0)
     213             :         {
     214           2 :             oSRSIdentified = *OGRSpatialReference::FromHandle(pahSRS[iEPSG]);
     215             :         }
     216           2 :         else if (iOtherAuthority >= 0)
     217             :         {
     218             :             oSRSIdentified =
     219           1 :                 *OGRSpatialReference::FromHandle(pahSRS[iOtherAuthority]);
     220             :         }
     221           4 :         OSRFreeSRSArray(pahSRS);
     222           4 :         CPLFree(panConfidence);
     223             :     }
     224             : 
     225          26 :     return oSRSIdentified;
     226             : }
     227             : 
     228             : /************************************************************************/
     229             : /*                       CreateSchemaCommon()                           */
     230             : /************************************************************************/
     231             : 
     232         410 : inline void OGRArrowWriterLayer::CreateSchemaCommon()
     233             : {
     234         410 :     CPLAssert(static_cast<int>(m_aeGeomEncoding.size()) ==
     235             :               m_poFeatureDefn->GetGeomFieldCount());
     236             : 
     237         820 :     std::vector<std::shared_ptr<arrow::Field>> fields;
     238         410 :     bool bNeedGDALSchema = false;
     239             : 
     240         410 :     m_anTZFlag.resize(m_poFeatureDefn->GetFieldCount(), TZFLAG_UNINITIALIZED);
     241             : 
     242         410 :     if (!m_osFIDColumn.empty())
     243             :     {
     244          18 :         bNeedGDALSchema = true;
     245          18 :         fields.emplace_back(arrow::field(m_osFIDColumn, arrow::int64(), false));
     246             :     }
     247             : 
     248         410 :     if (!m_apoFieldsFromArrowSchema.empty())
     249             :     {
     250         119 :         fields.insert(fields.end(), m_apoFieldsFromArrowSchema.begin(),
     251         238 :                       m_apoFieldsFromArrowSchema.end());
     252             :     }
     253             : 
     254        1064 :     for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); ++i)
     255             :     {
     256         654 :         const auto poFieldDefn = m_poFeatureDefn->GetFieldDefn(i);
     257         654 :         std::shared_ptr<arrow::DataType> dt;
     258         654 :         const auto eDT = poFieldDefn->GetType();
     259         654 :         const auto eSubDT = poFieldDefn->GetSubType();
     260         654 :         const auto &osDomainName = poFieldDefn->GetDomainName();
     261         654 :         const OGRFieldDomain *poFieldDomain = nullptr;
     262         654 :         const int nWidth = poFieldDefn->GetWidth();
     263         654 :         if (!osDomainName.empty())
     264             :         {
     265           4 :             const auto oIter = m_oMapFieldDomains.find(osDomainName);
     266           4 :             if (oIter == m_oMapFieldDomains.end())
     267             :             {
     268           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     269             :                          "Field %s references domain %s, but the later one "
     270             :                          "has not been created",
     271             :                          poFieldDefn->GetNameRef(), osDomainName.c_str());
     272             :             }
     273             :             else
     274             :             {
     275           4 :                 poFieldDomain = oIter->second.get();
     276             :             }
     277             :         }
     278         654 :         switch (eDT)
     279             :         {
     280          65 :             case OFTInteger:
     281          65 :                 if (eSubDT == OFSTBoolean)
     282           4 :                     dt = arrow::boolean();
     283          61 :                 else if (eSubDT == OFSTInt16)
     284           4 :                     dt = arrow::int16();
     285             :                 else
     286          57 :                     dt = arrow::int32();
     287          65 :                 if (poFieldDomain != nullptr)
     288             :                 {
     289           4 :                     dt = arrow::dictionary(dt, arrow::utf8());
     290             :                 }
     291          65 :                 break;
     292             : 
     293          38 :             case OFTInteger64:
     294          38 :                 dt = arrow::int64();
     295          38 :                 if (poFieldDomain != nullptr)
     296             :                 {
     297           0 :                     dt = arrow::dictionary(dt, arrow::utf8());
     298             :                 }
     299          38 :                 break;
     300             : 
     301          54 :             case OFTReal:
     302             :             {
     303          54 :                 const int nPrecision = poFieldDefn->GetPrecision();
     304          54 :                 if (nWidth != 0 && nPrecision != 0)
     305             :                 {
     306             :                     // Since arrow 18.0, we could use arrow::smallest_decimal()
     307             :                     // to return the smallest representation (i.e. possibly
     308             :                     // decimal32 and decimal64). But for now keep decimal128
     309             :                     // as the minimum for backwards compatibility.
     310             :                     // GetValueDecimal() and other functions in
     311             :                     // ogrlayerarrow.cpp would have to be adapted for decimal32
     312             :                     // and decimal64 compatibility.
     313          10 :                     if (nWidth > 38)
     314           0 :                         dt = arrow::decimal256(nWidth, nPrecision);
     315             :                     else
     316          10 :                         dt = arrow::decimal128(nWidth, nPrecision);
     317             :                 }
     318          44 :                 else if (eSubDT == OFSTFloat32)
     319           7 :                     dt = arrow::float32();
     320             :                 else
     321          37 :                     dt = arrow::float64();
     322          54 :                 break;
     323             :             }
     324             : 
     325         277 :             case OFTString:
     326             :             case OFTWideString:
     327         277 :                 if ((eSubDT != OFSTNone && eSubDT != OFSTJSON) || nWidth > 0)
     328           2 :                     bNeedGDALSchema = true;
     329         277 :                 dt = arrow::utf8();
     330         277 :                 break;
     331             : 
     332          19 :             case OFTBinary:
     333          19 :                 if (nWidth != 0)
     334           4 :                     dt = arrow::fixed_size_binary(nWidth);
     335             :                 else
     336          15 :                     dt = arrow::binary();
     337          19 :                 break;
     338             : 
     339          48 :             case OFTIntegerList:
     340          48 :                 if (eSubDT == OFSTBoolean)
     341           8 :                     dt = arrow::list(arrow::boolean());
     342          40 :                 else if (eSubDT == OFSTInt16)
     343           0 :                     dt = arrow::list(arrow::int16());
     344             :                 else
     345          40 :                     dt = arrow::list(arrow::int32());
     346          48 :                 break;
     347             : 
     348          20 :             case OFTInteger64List:
     349          20 :                 dt = arrow::list(arrow::int64());
     350          20 :                 break;
     351             : 
     352          35 :             case OFTRealList:
     353          35 :                 if (eSubDT == OFSTFloat32)
     354          11 :                     dt = arrow::list(arrow::float32());
     355             :                 else
     356          24 :                     dt = arrow::list(arrow::float64());
     357          35 :                 break;
     358             : 
     359          12 :             case OFTStringList:
     360             :             case OFTWideStringList:
     361          12 :                 dt = arrow::list(arrow::utf8());
     362          12 :                 break;
     363             : 
     364          31 :             case OFTDate:
     365          31 :                 dt = arrow::date32();
     366          31 :                 break;
     367             : 
     368           8 :             case OFTTime:
     369           8 :                 dt = arrow::time32(arrow::TimeUnit::MILLI);
     370           8 :                 break;
     371             : 
     372          47 :             case OFTDateTime:
     373             :             {
     374          47 :                 const int nTZFlag = poFieldDefn->GetTZFlag();
     375          47 :                 if (nTZFlag >= OGR_TZFLAG_MIXED_TZ)
     376             :                 {
     377          12 :                     m_anTZFlag[i] = nTZFlag;
     378             :                 }
     379          47 :                 dt = arrow::timestamp(arrow::TimeUnit::MILLI);
     380          47 :                 break;
     381             :             }
     382             :         }
     383             : 
     384         654 :         auto field = arrow::field(poFieldDefn->GetNameRef(), std::move(dt),
     385        1962 :                                   poFieldDefn->IsNullable());
     386         654 :         if (eDT == OFTString && eSubDT == OFSTJSON)
     387             :         {
     388          83 :             auto kvMetadata = std::make_shared<arrow::KeyValueMetadata>();
     389          83 :             kvMetadata->Append(ARROW_EXTENSION_NAME_KEY,
     390             :                                EXTENSION_NAME_ARROW_JSON);
     391          83 :             field = field->WithMetadata(kvMetadata);
     392             :         }
     393             : 
     394         654 :         fields.emplace_back(std::move(field));
     395         654 :         if (poFieldDefn->GetAlternativeNameRef()[0])
     396           2 :             bNeedGDALSchema = true;
     397         654 :         if (!poFieldDefn->GetComment().empty())
     398           3 :             bNeedGDALSchema = true;
     399             :     }
     400             : 
     401         814 :     for (int i = 0; i < m_poFeatureDefn->GetGeomFieldCount(); ++i)
     402             :     {
     403         404 :         const auto poGeomFieldDefn = m_poFeatureDefn->GetGeomFieldDefn(i);
     404         404 :         const auto eGType = poGeomFieldDefn->GetType();
     405             :         const int nDim =
     406         404 :             2 + (OGR_GT_HasZ(eGType) ? 1 : 0) + (OGR_GT_HasM(eGType) ? 1 : 0);
     407             : 
     408         404 :         const bool pointFieldNullable = GetDriverUCName() == "PARQUET";
     409             : 
     410             :         // Fixed Size List GeoArrow encoding
     411             :         const auto getFixedSizeListOfPoint =
     412         228 :             [nDim, eGType, pointFieldNullable]()
     413             :         {
     414             :             return arrow::fixed_size_list(
     415         146 :                 arrow::field(nDim == 2   ? "xy"
     416          30 :                              : nDim == 3 ? (OGR_GT_HasZ(eGType) ? "xyz" : "xym")
     417             :                                          : "xyzm",
     418             :                              arrow::float64(), pointFieldNullable),
     419         116 :                 nDim);
     420         404 :         };
     421             : 
     422             :         // Struct GeoArrow encoding
     423        1212 :         auto xField(arrow::field("x", arrow::float64(), false));
     424        1212 :         auto yField(arrow::field("y", arrow::float64(), false));
     425             :         std::vector<std::shared_ptr<arrow::Field>> pointFields{
     426             :             arrow::field("x", arrow::float64(), false),
     427        2424 :             arrow::field("y", arrow::float64(), false)};
     428         404 :         if (OGR_GT_HasZ(eGType))
     429             :             pointFields.emplace_back(
     430         121 :                 arrow::field("z", arrow::float64(), false));
     431         404 :         if (OGR_GT_HasM(eGType))
     432             :             pointFields.emplace_back(
     433          52 :                 arrow::field("m", arrow::float64(), false));
     434         808 :         auto pointStructType(arrow::struct_(std::move(pointFields)));
     435             : 
     436          40 :         const auto getListOfVertices = [&getFixedSizeListOfPoint]()
     437             :         {
     438          80 :             return arrow::list(std::make_shared<arrow::Field>(
     439         120 :                 "vertices", getFixedSizeListOfPoint()));
     440         404 :         };
     441             : 
     442          22 :         const auto getListOfRings = [&getListOfVertices]()
     443             :         {
     444             :             return arrow::list(
     445          44 :                 std::make_shared<arrow::Field>("rings", getListOfVertices()));
     446         404 :         };
     447             : 
     448         104 :         const auto getListOfVerticesStruct = [&pointStructType]()
     449             :         {
     450             :             return arrow::list(
     451         208 :                 std::make_shared<arrow::Field>("vertices", pointStructType));
     452         404 :         };
     453             : 
     454          60 :         const auto getListOfRingsStruct = [&getListOfVerticesStruct]()
     455             :         {
     456         120 :             return arrow::list(std::make_shared<arrow::Field>(
     457         180 :                 "rings", getListOfVerticesStruct()));
     458         404 :         };
     459             : 
     460         404 :         std::shared_ptr<arrow::DataType> dt;
     461         404 :         switch (m_aeGeomEncoding[i])
     462             :         {
     463         136 :             case OGRArrowGeomEncoding::WKB:
     464             : #if ARROW_VERSION_MAJOR >= 21
     465             :                 if (m_bUseArrowWKBExtension)
     466             :                 {
     467             :                     CPLJSONDocument oMetadataDoc;
     468             : 
     469             :                     const auto poSRS = poGeomFieldDefn->GetSpatialRef();
     470             :                     if (poSRS)
     471             :                     {
     472             :                         OGRSpatialReference oSRSIdentified(IdentifyCRS(poSRS));
     473             : 
     474             :                         // CRS encoded as PROJJSON
     475             :                         char *pszPROJJSON = nullptr;
     476             :                         oSRSIdentified.exportToPROJJSON(&pszPROJJSON, nullptr);
     477             :                         CPLJSONDocument oCRSDoc;
     478             :                         CPL_IGNORE_RET_VAL(oCRSDoc.LoadMemory(pszPROJJSON));
     479             :                         CPLFree(pszPROJJSON);
     480             :                         CPLJSONObject oCRSRoot = oCRSDoc.GetRoot();
     481             :                         RemoveIDFromMemberOfEnsembles(oCRSRoot);
     482             : 
     483             :                         oMetadataDoc.GetRoot().Add("crs", oCRSRoot);
     484             :                     }
     485             : 
     486             :                     if (m_bEdgesSpherical)
     487             :                     {
     488             :                         oMetadataDoc.GetRoot().Add("edges", "spherical");
     489             :                     }
     490             : 
     491             :                     const std::string metadata = oMetadataDoc.GetRoot().Format(
     492             :                         CPLJSONObject::PrettyFormat::Plain);
     493             :                     dt = std::make_shared<OGRGeoArrowWkbExtensionType>(
     494             :                         arrow::binary(), metadata);
     495             :                 }
     496             :                 else
     497             : #endif
     498             :                 {
     499         136 :                     dt = arrow::binary();
     500             :                 }
     501         136 :                 break;
     502             : 
     503          53 :             case OGRArrowGeomEncoding::WKT:
     504          53 :                 dt = arrow::utf8();
     505          53 :                 break;
     506             : 
     507           0 :             case OGRArrowGeomEncoding::GEOARROW_FSL_GENERIC:
     508             :             case OGRArrowGeomEncoding::GEOARROW_STRUCT_GENERIC:
     509           0 :                 CPLAssert(false);
     510             :                 break;
     511             : 
     512           9 :             case OGRArrowGeomEncoding::GEOARROW_FSL_POINT:
     513           9 :                 dt = getFixedSizeListOfPoint();
     514           9 :                 break;
     515             : 
     516           9 :             case OGRArrowGeomEncoding::GEOARROW_FSL_LINESTRING:
     517           9 :                 dt = getListOfVertices();
     518           9 :                 break;
     519             : 
     520          11 :             case OGRArrowGeomEncoding::GEOARROW_FSL_POLYGON:
     521          11 :                 dt = getListOfRings();
     522          11 :                 break;
     523             : 
     524           9 :             case OGRArrowGeomEncoding::GEOARROW_FSL_MULTIPOINT:
     525          18 :                 dt = arrow::list(std::make_shared<arrow::Field>(
     526          27 :                     "points", getFixedSizeListOfPoint()));
     527           9 :                 break;
     528             : 
     529           9 :             case OGRArrowGeomEncoding::GEOARROW_FSL_MULTILINESTRING:
     530          18 :                 dt = arrow::list(std::make_shared<arrow::Field>(
     531          27 :                     "linestrings", getListOfVertices()));
     532           9 :                 break;
     533             : 
     534          11 :             case OGRArrowGeomEncoding::GEOARROW_FSL_MULTIPOLYGON:
     535          22 :                 dt = arrow::list(std::make_shared<arrow::Field>(
     536          33 :                     "polygons", getListOfRings()));
     537          11 :                 break;
     538             : 
     539          31 :             case OGRArrowGeomEncoding::GEOARROW_STRUCT_POINT:
     540          31 :                 dt = pointStructType;
     541          31 :                 break;
     542             : 
     543          22 :             case OGRArrowGeomEncoding::GEOARROW_STRUCT_LINESTRING:
     544          22 :                 dt = getListOfVerticesStruct();
     545          22 :                 break;
     546             : 
     547          30 :             case OGRArrowGeomEncoding::GEOARROW_STRUCT_POLYGON:
     548          30 :                 dt = getListOfRingsStruct();
     549          30 :                 break;
     550             : 
     551          22 :             case OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTIPOINT:
     552          44 :                 dt = arrow::list(
     553          66 :                     std::make_shared<arrow::Field>("points", pointStructType));
     554          22 :                 break;
     555             : 
     556          22 :             case OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTILINESTRING:
     557          44 :                 dt = arrow::list(std::make_shared<arrow::Field>(
     558          66 :                     "linestrings", getListOfVerticesStruct()));
     559          22 :                 break;
     560             : 
     561          30 :             case OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTIPOLYGON:
     562          60 :                 dt = arrow::list(std::make_shared<arrow::Field>(
     563          90 :                     "polygons", getListOfRingsStruct()));
     564          30 :                 break;
     565             :         }
     566             : 
     567             :         std::shared_ptr<arrow::Field> field(
     568         404 :             arrow::field(poGeomFieldDefn->GetNameRef(), std::move(dt),
     569        1212 :                          poGeomFieldDefn->IsNullable()));
     570         404 :         if (m_bWriteFieldArrowExtensionName)
     571             :         {
     572         136 :             auto kvMetadata = field->metadata()
     573         136 :                                   ? field->metadata()->Copy()
     574         136 :                                   : std::make_shared<arrow::KeyValueMetadata>();
     575         272 :             kvMetadata->Append(
     576             :                 ARROW_EXTENSION_NAME_KEY,
     577         136 :                 GetGeomEncodingAsString(m_aeGeomEncoding[i], false));
     578         136 :             field = field->WithMetadata(kvMetadata);
     579             :         }
     580             : 
     581         404 :         m_apoBaseStructGeomType.emplace_back(std::move(pointStructType));
     582             : 
     583         404 :         fields.emplace_back(std::move(field));
     584             :     }
     585             : 
     586         410 :     if (m_bWriteBBoxStruct)
     587             :     {
     588         398 :         for (int i = 0; i < m_poFeatureDefn->GetGeomFieldCount(); ++i)
     589             :         {
     590         199 :             const auto poGeomFieldDefn = m_poFeatureDefn->GetGeomFieldDefn(i);
     591         597 :             auto bbox_field_xmin(arrow::field("xmin", arrow::float32(), false));
     592         597 :             auto bbox_field_ymin(arrow::field("ymin", arrow::float32(), false));
     593         597 :             auto bbox_field_xmax(arrow::field("xmax", arrow::float32(), false));
     594         597 :             auto bbox_field_ymax(arrow::field("ymax", arrow::float32(), false));
     595             :             auto bbox_field(arrow::field(
     596             :                 CPLGetConfigOption("OGR_PARQUET_COVERING_BBOX_NAME",
     597         398 :                                    std::string(poGeomFieldDefn->GetNameRef())
     598         199 :                                        .append("_bbox")
     599             :                                        .c_str()),
     600        1194 :                 arrow::struct_(
     601         199 :                     {std::move(bbox_field_xmin), std::move(bbox_field_ymin),
     602        1194 :                      std::move(bbox_field_xmax), std::move(bbox_field_ymax)}),
     603        1194 :                 poGeomFieldDefn->IsNullable()));
     604         199 :             fields.emplace_back(bbox_field);
     605         199 :             m_apoFieldsBBOX.emplace_back(bbox_field);
     606             :         }
     607             :     }
     608             : 
     609         410 :     m_aoEnvelopes.resize(m_poFeatureDefn->GetGeomFieldCount());
     610         410 :     m_oSetWrittenGeometryTypes.resize(m_poFeatureDefn->GetGeomFieldCount());
     611             : 
     612         410 :     m_poSchema = arrow::schema(std::move(fields));
     613         410 :     CPLAssert(m_poSchema);
     614         433 :     if (bNeedGDALSchema &&
     615          23 :         CPLTestBool(CPLGetConfigOption(
     616         433 :             ("OGR_" + GetDriverUCName() + "_WRITE_GDAL_SCHEMA").c_str(),
     617             :             "YES")))
     618             :     {
     619          46 :         CPLJSONObject oRoot;
     620          46 :         CPLJSONObject oColumns;
     621             : 
     622          23 :         if (!m_osFIDColumn.empty())
     623          18 :             oRoot.Add("fid", m_osFIDColumn);
     624             : 
     625          23 :         oRoot.Add("columns", oColumns);
     626         210 :         for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); ++i)
     627             :         {
     628         187 :             const auto poFieldDefn = m_poFeatureDefn->GetFieldDefn(i);
     629         374 :             CPLJSONObject oColumn;
     630         187 :             oColumns.Add(poFieldDefn->GetNameRef(), oColumn);
     631         187 :             oColumn.Add("type", OGR_GetFieldTypeName(poFieldDefn->GetType()));
     632         187 :             const auto eSubDT = poFieldDefn->GetSubType();
     633         187 :             if (eSubDT != OFSTNone)
     634          56 :                 oColumn.Add("subtype", OGR_GetFieldSubTypeName(eSubDT));
     635         187 :             const int nWidth = poFieldDefn->GetWidth();
     636         187 :             if (nWidth > 0)
     637          12 :                 oColumn.Add("width", nWidth);
     638         187 :             const int nPrecision = poFieldDefn->GetPrecision();
     639         187 :             if (nPrecision > 0)
     640           6 :                 oColumn.Add("precision", nPrecision);
     641         187 :             if (poFieldDefn->GetAlternativeNameRef()[0])
     642           2 :                 oColumn.Add("alternative_name",
     643             :                             poFieldDefn->GetAlternativeNameRef());
     644         187 :             if (!poFieldDefn->GetComment().empty())
     645           3 :                 oColumn.Add("comment", poFieldDefn->GetComment());
     646             :         }
     647             : 
     648          23 :         auto kvMetadata = m_poSchema->metadata()
     649           0 :                               ? m_poSchema->metadata()->Copy()
     650          46 :                               : std::make_shared<arrow::KeyValueMetadata>();
     651          46 :         kvMetadata->Append("gdal:schema",
     652          46 :                            oRoot.Format(CPLJSONObject::PrettyFormat::Plain));
     653          23 :         m_poSchema = m_poSchema->WithMetadata(kvMetadata);
     654          23 :         CPLAssert(m_poSchema);
     655             :     }
     656         410 : }
     657             : 
     658             : /************************************************************************/
     659             : /*                         FinalizeSchema()                             */
     660             : /************************************************************************/
     661             : 
     662         355 : inline void OGRArrowWriterLayer::FinalizeSchema()
     663             : {
     664             :     // Final tuning of schema taking into actual timezone values
     665             :     // from features
     666         355 :     int nArrowIdxFirstField = !m_osFIDColumn.empty() ? 1 : 0;
     667        1007 :     for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); ++i)
     668             :     {
     669         652 :         if (m_anTZFlag[i] >= OGR_TZFLAG_MIXED_TZ)
     670             :         {
     671          12 :             const int nOffset = m_anTZFlag[i] == OGR_TZFLAG_UTC
     672          12 :                                     ? 0
     673           8 :                                     : (m_anTZFlag[i] - OGR_TZFLAG_UTC) * 15;
     674          12 :             int nHours = static_cast<int>(nOffset / 60);  // Round towards zero.
     675          12 :             const int nMinutes = std::abs(nOffset - nHours * 60);
     676             : 
     677             :             const std::string osTZ =
     678             :                 CPLSPrintf("%c%02d:%02d", nOffset >= 0 ? '+' : '-',
     679          24 :                            std::abs(nHours), nMinutes);
     680          24 :             auto dt = arrow::timestamp(arrow::TimeUnit::MILLI, osTZ);
     681          12 :             const auto poFieldDefn = m_poFeatureDefn->GetFieldDefn(i);
     682          12 :             auto field = arrow::field(poFieldDefn->GetNameRef(), std::move(dt),
     683          36 :                                       poFieldDefn->IsNullable());
     684          24 :             auto result = m_poSchema->SetField(nArrowIdxFirstField + i, field);
     685          12 :             if (!result.ok())
     686             :             {
     687           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     688             :                          "Schema::SetField() failed with %s",
     689           0 :                          result.status().message().c_str());
     690             :             }
     691             :             else
     692             :             {
     693          12 :                 m_poSchema = *result;
     694             :             }
     695             :         }
     696             :     }
     697         355 : }
     698             : 
     699             : /************************************************************************/
     700             : /*                         AddFieldDomain()                             */
     701             : /************************************************************************/
     702             : 
     703             : inline bool
     704          11 : OGRArrowWriterLayer::AddFieldDomain(std::unique_ptr<OGRFieldDomain> &&domain,
     705             :                                     std::string &failureReason)
     706             : {
     707          11 :     if (domain->GetDomainType() != OFDT_CODED)
     708             :     {
     709           0 :         failureReason = "Only coded field domains are supported by Arrow";
     710           0 :         return false;
     711             :     }
     712             : 
     713             :     const OGRCodedFieldDomain *poDomain =
     714          11 :         static_cast<const OGRCodedFieldDomain *>(domain.get());
     715          11 :     const OGRCodedValue *psIter = poDomain->GetEnumeration();
     716             : 
     717             :     auto poStringBuilder =
     718          22 :         std::make_shared<arrow::StringBuilder>(m_poMemoryPool);
     719             : 
     720          11 :     int nLastCode = -1;
     721          44 :     for (; psIter->pszCode; ++psIter)
     722             :     {
     723          33 :         if (CPLGetValueType(psIter->pszCode) != CPL_VALUE_INTEGER)
     724             :         {
     725           0 :             failureReason = "Non integer code in domain ";
     726           0 :             failureReason += domain->GetName();
     727           0 :             return false;
     728             :         }
     729          33 :         int nCode = atoi(psIter->pszCode);
     730          33 :         if (nCode <= nLastCode || nCode - nLastCode > 100)
     731             :         {
     732           0 :             failureReason = "Too sparse codes in domain ";
     733           0 :             failureReason += domain->GetName();
     734           0 :             return false;
     735             :         }
     736          33 :         for (int i = nLastCode + 1; i < nCode; ++i)
     737             :         {
     738           0 :             OGR_ARROW_RETURN_FALSE_NOT_OK(poStringBuilder->AppendNull());
     739             :         }
     740          33 :         if (psIter->pszValue)
     741          33 :             OGR_ARROW_RETURN_FALSE_NOT_OK(
     742             :                 poStringBuilder->Append(psIter->pszValue));
     743             :         else
     744           0 :             OGR_ARROW_RETURN_FALSE_NOT_OK(poStringBuilder->AppendNull());
     745          33 :         nLastCode = nCode;
     746             :     }
     747             : 
     748          11 :     std::shared_ptr<arrow::Array> stringArray;
     749          22 :     auto status = poStringBuilder->Finish(&stringArray);
     750          11 :     if (!status.ok())
     751             :     {
     752           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     753             :                  "StringArray::Finish() failed with %s",
     754           0 :                  status.message().c_str());
     755           0 :         return false;
     756             :     }
     757             : 
     758          11 :     m_oMapFieldDomainToStringArray[domain->GetName()] = std::move(stringArray);
     759          11 :     m_oMapFieldDomains[domain->GetName()] = std::move(domain);
     760          11 :     return true;
     761             : }
     762             : 
     763             : /************************************************************************/
     764             : /*                          GetFieldDomainNames()                       */
     765             : /************************************************************************/
     766             : 
     767           0 : inline std::vector<std::string> OGRArrowWriterLayer::GetFieldDomainNames() const
     768             : {
     769           0 :     std::vector<std::string> names;
     770           0 :     names.reserve(m_oMapFieldDomains.size());
     771           0 :     for (const auto &it : m_oMapFieldDomains)
     772             :     {
     773           0 :         names.emplace_back(it.first);
     774             :     }
     775           0 :     return names;
     776             : }
     777             : 
     778             : /************************************************************************/
     779             : /*                          GetFieldDomain()                            */
     780             : /************************************************************************/
     781             : 
     782             : inline const OGRFieldDomain *
     783          15 : OGRArrowWriterLayer::GetFieldDomain(const std::string &name) const
     784             : {
     785          15 :     const auto iter = m_oMapFieldDomains.find(name);
     786          15 :     if (iter == m_oMapFieldDomains.end())
     787          11 :         return nullptr;
     788           4 :     return iter->second.get();
     789             : }
     790             : 
     791             : /************************************************************************/
     792             : /*                          CreateField()                               */
     793             : /************************************************************************/
     794             : 
     795         655 : inline OGRErr OGRArrowWriterLayer::CreateField(const OGRFieldDefn *poField,
     796             :                                                int /* bApproxOK */)
     797             : {
     798         655 :     if (m_poSchema)
     799             :     {
     800           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     801             :                  "Cannot add field after a first feature has been written");
     802           1 :         return OGRERR_FAILURE;
     803             :     }
     804         654 :     if (!m_apoFieldsFromArrowSchema.empty())
     805             :     {
     806           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     807             :                  "Cannot mix calls to CreateField() and "
     808             :                  "CreateFieldFromArrowSchema()");
     809           0 :         return OGRERR_FAILURE;
     810             :     }
     811         654 :     m_poFeatureDefn->AddFieldDefn(poField);
     812         654 :     return OGRERR_NONE;
     813             : }
     814             : 
     815             : /************************************************************************/
     816             : /*                OGRLayer::CreateFieldFromArrowSchema()                */
     817             : /************************************************************************/
     818             : 
     819         998 : inline bool OGRArrowWriterLayer::CreateFieldFromArrowSchema(
     820             :     const struct ArrowSchema *schema, CSLConstList /*papszOptions*/)
     821             : {
     822         998 :     if (m_poSchema)
     823             :     {
     824           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     825             :                  "Cannot add field after a first feature has been written");
     826           0 :         return false;
     827             :     }
     828             : 
     829         998 :     if (m_poFeatureDefn->GetFieldCount())
     830             :     {
     831           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     832             :                  "Cannot mix calls to CreateField() and "
     833             :                  "CreateFieldFromArrowSchema()");
     834           0 :         return false;
     835             :     }
     836             : 
     837         998 :     if (m_osFIDColumn == schema->name)
     838             :     {
     839           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     840             :                  "FID column has the same name as this field: %s",
     841           0 :                  schema->name);
     842           0 :         return false;
     843             :     }
     844             : 
     845       35951 :     for (auto &apoField : m_apoFieldsFromArrowSchema)
     846             :     {
     847       34953 :         if (apoField->name() == schema->name)
     848             :         {
     849           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     850           0 :                      "Field of name %s already exists", schema->name);
     851           0 :             return false;
     852             :         }
     853             :     }
     854             : 
     855         998 :     if (m_poFeatureDefn->GetGeomFieldIndex(schema->name) >= 0)
     856             :     {
     857           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     858           0 :                  "Geometry field of name %s already exists", schema->name);
     859           0 :         return false;
     860             :     }
     861             : 
     862             :     // ImportField() would release the schema, but we don't want that
     863             :     // So copy the structure content into a local variable, and override its
     864             :     // release callback to a no-op. This may be a bit fragile, but it doesn't
     865             :     // look like ImportField implementation tries to access the C ArrowSchema
     866             :     // after it has been called.
     867         998 :     struct ArrowSchema lSchema = *schema;
     868         998 :     const auto DummyFreeSchema = [](struct ArrowSchema *ptrSchema)
     869         998 :     { ptrSchema->release = nullptr; };
     870         998 :     lSchema.release = DummyFreeSchema;
     871        1996 :     auto result = arrow::ImportField(&lSchema);
     872         998 :     CPLAssert(lSchema.release == nullptr);
     873         998 :     if (!result.ok())
     874             :     {
     875           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     876             :                  "CreateFieldFromArrowSchema() failed");
     877           0 :         return false;
     878             :     }
     879         998 :     m_apoFieldsFromArrowSchema.emplace_back(std::move(*result));
     880         998 :     return true;
     881             : }
     882             : 
     883             : /************************************************************************/
     884             : /*                   GetPreciseArrowGeomEncoding()                      */
     885             : /************************************************************************/
     886             : 
     887         217 : inline OGRArrowGeomEncoding OGRArrowWriterLayer::GetPreciseArrowGeomEncoding(
     888             :     OGRArrowGeomEncoding eEncodingType, OGRwkbGeometryType eGType)
     889             : {
     890         217 :     CPLAssert(eEncodingType == OGRArrowGeomEncoding::GEOARROW_FSL_GENERIC ||
     891             :               eEncodingType == OGRArrowGeomEncoding::GEOARROW_STRUCT_GENERIC);
     892         217 :     const auto eFlatType = wkbFlatten(eGType);
     893         217 :     if (eFlatType == wkbPoint)
     894             :     {
     895             :         return eEncodingType == OGRArrowGeomEncoding::GEOARROW_FSL_GENERIC
     896          40 :                    ? OGRArrowGeomEncoding::GEOARROW_FSL_POINT
     897          40 :                    : OGRArrowGeomEncoding::GEOARROW_STRUCT_POINT;
     898             :     }
     899         177 :     else if (eFlatType == wkbLineString)
     900             :     {
     901             :         return eEncodingType == OGRArrowGeomEncoding::GEOARROW_FSL_GENERIC
     902          31 :                    ? OGRArrowGeomEncoding::GEOARROW_FSL_LINESTRING
     903          31 :                    : OGRArrowGeomEncoding::GEOARROW_STRUCT_LINESTRING;
     904             :     }
     905         146 :     else if (eFlatType == wkbPolygon)
     906             :     {
     907             :         return eEncodingType == OGRArrowGeomEncoding::GEOARROW_FSL_GENERIC
     908          41 :                    ? OGRArrowGeomEncoding::GEOARROW_FSL_POLYGON
     909          41 :                    : OGRArrowGeomEncoding::GEOARROW_STRUCT_POLYGON;
     910             :     }
     911         105 :     else if (eFlatType == wkbMultiPoint)
     912             :     {
     913             :         return eEncodingType == OGRArrowGeomEncoding::GEOARROW_FSL_GENERIC
     914          31 :                    ? OGRArrowGeomEncoding::GEOARROW_FSL_MULTIPOINT
     915          31 :                    : OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTIPOINT;
     916             :     }
     917          74 :     else if (eFlatType == wkbMultiLineString)
     918             :     {
     919             :         return eEncodingType == OGRArrowGeomEncoding::GEOARROW_FSL_GENERIC
     920          31 :                    ? OGRArrowGeomEncoding::GEOARROW_FSL_MULTILINESTRING
     921          31 :                    : OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTILINESTRING;
     922             :     }
     923          43 :     else if (eFlatType == wkbMultiPolygon)
     924             :     {
     925             :         return eEncodingType == OGRArrowGeomEncoding::GEOARROW_FSL_GENERIC
     926          41 :                    ? OGRArrowGeomEncoding::GEOARROW_FSL_MULTIPOLYGON
     927          41 :                    : OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTIPOLYGON;
     928             :     }
     929             :     else
     930             :     {
     931           2 :         CPLError(CE_Failure, CPLE_NotSupported,
     932             :                  "GeoArrow encoding is currently not supported for %s",
     933             :                  OGRGeometryTypeToName(eGType));
     934           2 :         return eEncodingType;
     935             :     }
     936             : }
     937             : 
     938             : /************************************************************************/
     939             : /*                        GetGeomEncodingAsString()                     */
     940             : /************************************************************************/
     941             : 
     942             : inline const char *
     943         652 : OGRArrowWriterLayer::GetGeomEncodingAsString(OGRArrowGeomEncoding eGeomEncoding,
     944             :                                              bool bForParquetGeo)
     945             : {
     946         652 :     switch (eGeomEncoding)
     947             :     {
     948         183 :         case OGRArrowGeomEncoding::WKB:
     949         183 :             return bForParquetGeo ? "WKB" : "geoarrow.wkb";
     950         111 :         case OGRArrowGeomEncoding::WKT:
     951         111 :             return bForParquetGeo ? "WKT" : "geoarrow.wkt";
     952           0 :         case OGRArrowGeomEncoding::GEOARROW_FSL_GENERIC:
     953             :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_GENERIC:
     954           0 :             CPLAssert(false);
     955             :             break;
     956          19 :         case OGRArrowGeomEncoding::GEOARROW_FSL_POINT:
     957          19 :             return "geoarrow.point";
     958          19 :         case OGRArrowGeomEncoding::GEOARROW_FSL_LINESTRING:
     959          19 :             return "geoarrow.linestring";
     960          21 :         case OGRArrowGeomEncoding::GEOARROW_FSL_POLYGON:
     961          21 :             return "geoarrow.polygon";
     962          19 :         case OGRArrowGeomEncoding::GEOARROW_FSL_MULTIPOINT:
     963          19 :             return "geoarrow.multipoint";
     964          19 :         case OGRArrowGeomEncoding::GEOARROW_FSL_MULTILINESTRING:
     965          19 :             return "geoarrow.multilinestring";
     966          21 :         case OGRArrowGeomEncoding::GEOARROW_FSL_MULTIPOLYGON:
     967          21 :             return "geoarrow.multipolygon";
     968          54 :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_POINT:
     969          54 :             return bForParquetGeo ? "point" : "geoarrow.point";
     970          34 :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_LINESTRING:
     971          34 :             return bForParquetGeo ? "linestring" : "geoarrow.linestring";
     972          42 :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_POLYGON:
     973          42 :             return bForParquetGeo ? "polygon" : "geoarrow.polygon";
     974          34 :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTIPOINT:
     975          34 :             return bForParquetGeo ? "multipoint" : "geoarrow.multipoint";
     976          34 :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTILINESTRING:
     977          34 :             return bForParquetGeo ? "multilinestring"
     978          34 :                                   : "geoarrow.multilinestring";
     979          42 :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTIPOLYGON:
     980          42 :             return bForParquetGeo ? "multipolygon" : "geoarrow.multipolygon";
     981             :     }
     982           0 :     return nullptr;
     983             : }
     984             : 
     985             : /************************************************************************/
     986             : /*                          CreateGeomField()                           */
     987             : /************************************************************************/
     988             : 
     989             : inline OGRErr
     990          27 : OGRArrowWriterLayer::CreateGeomField(const OGRGeomFieldDefn *poField,
     991             :                                      int /* bApproxOK */)
     992             : {
     993          27 :     if (m_poSchema)
     994             :     {
     995           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     996             :                  "Cannot add field after a first feature has been written");
     997           1 :         return OGRERR_FAILURE;
     998             :     }
     999          26 :     const auto eGType = poField->GetType();
    1000          26 :     if (!IsSupportedGeometryType(eGType))
    1001             :     {
    1002           0 :         return OGRERR_FAILURE;
    1003             :     }
    1004             : 
    1005          26 :     if (IsSRSRequired() && poField->GetSpatialRef() == nullptr)
    1006             :     {
    1007           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    1008             :                  "Geometry column should have an associated CRS");
    1009             :     }
    1010          26 :     auto eGeomEncoding = m_eGeomEncoding;
    1011          26 :     if (eGeomEncoding == OGRArrowGeomEncoding::GEOARROW_FSL_GENERIC ||
    1012          26 :         eGeomEncoding == OGRArrowGeomEncoding::GEOARROW_STRUCT_GENERIC)
    1013             :     {
    1014           0 :         const auto eEncodingType = eGeomEncoding;
    1015           0 :         eGeomEncoding = GetPreciseArrowGeomEncoding(eEncodingType, eGType);
    1016           0 :         if (eGeomEncoding == eEncodingType)
    1017           0 :             return OGRERR_FAILURE;
    1018             :     }
    1019          26 :     m_aeGeomEncoding.push_back(eGeomEncoding);
    1020          26 :     m_poFeatureDefn->AddGeomFieldDefn(poField);
    1021          26 :     return OGRERR_NONE;
    1022             : }
    1023             : 
    1024             : /************************************************************************/
    1025             : /*                        MakeGeoArrowBuilder()                         */
    1026             : /************************************************************************/
    1027             : 
    1028             : static std::shared_ptr<arrow::ArrayBuilder>
    1029         134 : MakeGeoArrowBuilder(arrow::MemoryPool *poMemoryPool, int nDim, int nDepth)
    1030             : {
    1031         134 :     if (nDepth == 0)
    1032         104 :         return std::make_shared<arrow::FixedSizeListBuilder>(
    1033         104 :             poMemoryPool, std::make_shared<arrow::DoubleBuilder>(poMemoryPool),
    1034          52 :             nDim);
    1035             :     else
    1036         164 :         return std::make_shared<arrow::ListBuilder>(
    1037         246 :             poMemoryPool, MakeGeoArrowBuilder(poMemoryPool, nDim, nDepth - 1));
    1038             : }
    1039             : 
    1040             : /************************************************************************/
    1041             : /*                      MakeGeoArrowStructBuilder()                     */
    1042             : /************************************************************************/
    1043             : 
    1044             : static std::shared_ptr<arrow::ArrayBuilder>
    1045         384 : MakeGeoArrowStructBuilder(arrow::MemoryPool *poMemoryPool, int nDim, int nDepth,
    1046             :                           const std::shared_ptr<arrow::DataType> &eBaseType)
    1047             : {
    1048         384 :     if (nDepth == 0)
    1049             :     {
    1050         155 :         std::vector<std::shared_ptr<arrow::ArrayBuilder>> builders;
    1051         537 :         for (int i = 0; i < nDim; ++i)
    1052             :             builders.emplace_back(
    1053         382 :                 std::make_shared<arrow::DoubleBuilder>(poMemoryPool));
    1054         310 :         return std::make_shared<arrow::StructBuilder>(eBaseType, poMemoryPool,
    1055         310 :                                                       std::move(builders));
    1056             :     }
    1057             :     else
    1058         458 :         return std::make_shared<arrow::ListBuilder>(
    1059         458 :             poMemoryPool, MakeGeoArrowStructBuilder(poMemoryPool, nDim,
    1060         229 :                                                     nDepth - 1, eBaseType));
    1061             : }
    1062             : 
    1063             : /************************************************************************/
    1064             : /*                         ClearArrayBuilers()                          */
    1065             : /************************************************************************/
    1066             : 
    1067         273 : inline void OGRArrowWriterLayer::ClearArrayBuilers()
    1068             : {
    1069         273 :     m_apoBuilders.clear();
    1070         273 :     m_apoBuildersBBOXStruct.clear();
    1071         273 :     m_apoBuildersBBOXXMin.clear();
    1072         273 :     m_apoBuildersBBOXYMin.clear();
    1073         273 :     m_apoBuildersBBOXXMax.clear();
    1074         273 :     m_apoBuildersBBOXYMax.clear();
    1075         273 : }
    1076             : 
    1077             : /************************************************************************/
    1078             : /*                        CreateArrayBuilders()                         */
    1079             : /************************************************************************/
    1080             : 
    1081         392 : inline void OGRArrowWriterLayer::CreateArrayBuilders()
    1082             : {
    1083         392 :     m_apoBuilders.reserve(1 + m_poFeatureDefn->GetFieldCount() +
    1084         392 :                           m_poFeatureDefn->GetGeomFieldCount());
    1085             : 
    1086         392 :     int nArrowIdx = 0;
    1087         392 :     if (!m_osFIDColumn.empty())
    1088             :     {
    1089          49 :         m_apoBuilders.emplace_back(std::make_shared<arrow::Int64Builder>());
    1090          49 :         nArrowIdx++;
    1091             :     }
    1092             : 
    1093        1760 :     for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); ++i, ++nArrowIdx)
    1094             :     {
    1095        1368 :         const auto poFieldDefn = m_poFeatureDefn->GetFieldDefn(i);
    1096        1368 :         std::shared_ptr<arrow::ArrayBuilder> builder;
    1097        1368 :         const auto eSubDT = poFieldDefn->GetSubType();
    1098        1368 :         switch (poFieldDefn->GetType())
    1099             :         {
    1100         145 :             case OFTInteger:
    1101         145 :                 if (eSubDT == OFSTBoolean)
    1102             :                     builder =
    1103          12 :                         std::make_shared<arrow::BooleanBuilder>(m_poMemoryPool);
    1104         133 :                 else if (eSubDT == OFSTInt16)
    1105             :                     builder =
    1106          12 :                         std::make_shared<arrow::Int16Builder>(m_poMemoryPool);
    1107             :                 else
    1108             :                     builder =
    1109         121 :                         std::make_shared<arrow::Int32Builder>(m_poMemoryPool);
    1110         145 :                 break;
    1111             : 
    1112          86 :             case OFTInteger64:
    1113          86 :                 builder = std::make_shared<arrow::Int64Builder>(m_poMemoryPool);
    1114          86 :                 break;
    1115             : 
    1116         108 :             case OFTReal:
    1117             :             {
    1118         216 :                 const auto arrowType = m_poSchema->fields()[nArrowIdx]->type();
    1119         108 :                 if (arrowType->id() == arrow::Type::DECIMAL128)
    1120          26 :                     builder = std::make_shared<arrow::Decimal128Builder>(
    1121          26 :                         arrowType, m_poMemoryPool);
    1122          82 :                 else if (arrowType->id() == arrow::Type::DECIMAL256)
    1123           0 :                     builder = std::make_shared<arrow::Decimal256Builder>(
    1124           0 :                         arrowType, m_poMemoryPool);
    1125          82 :                 else if (eSubDT == OFSTFloat32)
    1126             :                     builder =
    1127          21 :                         std::make_shared<arrow::FloatBuilder>(m_poMemoryPool);
    1128             :                 else
    1129             :                     builder =
    1130          61 :                         std::make_shared<arrow::DoubleBuilder>(m_poMemoryPool);
    1131         108 :                 break;
    1132             :             }
    1133             : 
    1134         475 :             case OFTString:
    1135             :             case OFTWideString:
    1136             :                 builder =
    1137         475 :                     std::make_shared<arrow::StringBuilder>(m_poMemoryPool);
    1138         475 :                 break;
    1139             : 
    1140          43 :             case OFTBinary:
    1141          43 :                 if (poFieldDefn->GetWidth() != 0)
    1142          24 :                     builder = std::make_shared<arrow::FixedSizeBinaryBuilder>(
    1143          24 :                         arrow::fixed_size_binary(poFieldDefn->GetWidth()),
    1144          24 :                         m_poMemoryPool);
    1145             :                 else
    1146             :                     builder =
    1147          31 :                         std::make_shared<arrow::BinaryBuilder>(m_poMemoryPool);
    1148          43 :                 break;
    1149             : 
    1150         144 :             case OFTIntegerList:
    1151             :             {
    1152         144 :                 std::shared_ptr<arrow::ArrayBuilder> poBaseBuilder;
    1153         144 :                 if (eSubDT == OFSTBoolean)
    1154             :                     poBaseBuilder =
    1155          24 :                         std::make_shared<arrow::BooleanBuilder>(m_poMemoryPool);
    1156         120 :                 else if (eSubDT == OFSTInt16)
    1157             :                     poBaseBuilder =
    1158           0 :                         std::make_shared<arrow::Int16Builder>(m_poMemoryPool);
    1159             :                 else
    1160             :                     poBaseBuilder =
    1161         120 :                         std::make_shared<arrow::Int32Builder>(m_poMemoryPool);
    1162         288 :                 builder = std::make_shared<arrow::ListBuilder>(m_poMemoryPool,
    1163         144 :                                                                poBaseBuilder);
    1164         144 :                 break;
    1165             :             }
    1166             : 
    1167          60 :             case OFTInteger64List:
    1168          60 :                 builder = std::make_shared<arrow::ListBuilder>(
    1169          60 :                     m_poMemoryPool,
    1170         180 :                     std::make_shared<arrow::Int64Builder>(m_poMemoryPool));
    1171             : 
    1172          60 :                 break;
    1173             : 
    1174         105 :             case OFTRealList:
    1175         105 :                 if (eSubDT == OFSTFloat32)
    1176          33 :                     builder = std::make_shared<arrow::ListBuilder>(
    1177          33 :                         m_poMemoryPool,
    1178          99 :                         std::make_shared<arrow::FloatBuilder>(m_poMemoryPool));
    1179             :                 else
    1180          72 :                     builder = std::make_shared<arrow::ListBuilder>(
    1181          72 :                         m_poMemoryPool,
    1182         216 :                         std::make_shared<arrow::DoubleBuilder>(m_poMemoryPool));
    1183         105 :                 break;
    1184             : 
    1185          36 :             case OFTStringList:
    1186             :             case OFTWideStringList:
    1187          36 :                 builder = std::make_shared<arrow::ListBuilder>(
    1188          36 :                     m_poMemoryPool,
    1189         108 :                     std::make_shared<arrow::StringBuilder>(m_poMemoryPool));
    1190             : 
    1191          36 :                 break;
    1192             : 
    1193          47 :             case OFTDate:
    1194             :                 builder =
    1195          47 :                     std::make_shared<arrow::Date32Builder>(m_poMemoryPool);
    1196          47 :                 break;
    1197             : 
    1198          24 :             case OFTTime:
    1199          48 :                 builder = std::make_shared<arrow::Time32Builder>(
    1200          72 :                     arrow::time32(arrow::TimeUnit::MILLI), m_poMemoryPool);
    1201          24 :                 break;
    1202             : 
    1203          95 :             case OFTDateTime:
    1204         190 :                 builder = std::make_shared<arrow::TimestampBuilder>(
    1205         285 :                     arrow::timestamp(arrow::TimeUnit::MILLI), m_poMemoryPool);
    1206          95 :                 break;
    1207             :         }
    1208        1368 :         m_apoBuilders.emplace_back(builder);
    1209             :     }
    1210             : 
    1211         778 :     for (int i = 0; i < m_poFeatureDefn->GetGeomFieldCount(); ++i, ++nArrowIdx)
    1212             :     {
    1213         386 :         std::shared_ptr<arrow::ArrayBuilder> builder;
    1214         386 :         const auto poGeomFieldDefn = m_poFeatureDefn->GetGeomFieldDefn(i);
    1215         386 :         const auto eGType = poGeomFieldDefn->GetType();
    1216             :         const int nDim =
    1217         386 :             2 + (OGR_GT_HasZ(eGType) ? 1 : 0) + (OGR_GT_HasM(eGType) ? 1 : 0);
    1218             : 
    1219         386 :         switch (m_aeGeomEncoding[i])
    1220             :         {
    1221         126 :             case OGRArrowGeomEncoding::WKB:
    1222             :                 builder =
    1223         126 :                     std::make_shared<arrow::BinaryBuilder>(m_poMemoryPool);
    1224         126 :                 break;
    1225             : 
    1226          53 :             case OGRArrowGeomEncoding::WKT:
    1227             :                 builder =
    1228          53 :                     std::make_shared<arrow::StringBuilder>(m_poMemoryPool);
    1229          53 :                 break;
    1230             : 
    1231           8 :             case OGRArrowGeomEncoding::GEOARROW_FSL_POINT:
    1232           8 :                 builder = MakeGeoArrowBuilder(m_poMemoryPool, nDim, 0);
    1233           8 :                 break;
    1234             : 
    1235           8 :             case OGRArrowGeomEncoding::GEOARROW_FSL_LINESTRING:
    1236           8 :                 builder = MakeGeoArrowBuilder(m_poMemoryPool, nDim, 1);
    1237           8 :                 break;
    1238             : 
    1239          10 :             case OGRArrowGeomEncoding::GEOARROW_FSL_POLYGON:
    1240          10 :                 builder = MakeGeoArrowBuilder(m_poMemoryPool, nDim, 2);
    1241          10 :                 break;
    1242             : 
    1243           8 :             case OGRArrowGeomEncoding::GEOARROW_FSL_MULTIPOINT:
    1244           8 :                 builder = MakeGeoArrowBuilder(m_poMemoryPool, nDim, 1);
    1245           8 :                 break;
    1246             : 
    1247           8 :             case OGRArrowGeomEncoding::GEOARROW_FSL_MULTILINESTRING:
    1248           8 :                 builder = MakeGeoArrowBuilder(m_poMemoryPool, nDim, 2);
    1249           8 :                 break;
    1250             : 
    1251          10 :             case OGRArrowGeomEncoding::GEOARROW_FSL_MULTIPOLYGON:
    1252          10 :                 builder = MakeGeoArrowBuilder(m_poMemoryPool, nDim, 3);
    1253          10 :                 break;
    1254             : 
    1255          34 :             case OGRArrowGeomEncoding::GEOARROW_STRUCT_POINT:
    1256          68 :                 builder = MakeGeoArrowStructBuilder(m_poMemoryPool, nDim, 0,
    1257          68 :                                                     m_apoBaseStructGeomType[i]);
    1258          34 :                 break;
    1259             : 
    1260          21 :             case OGRArrowGeomEncoding::GEOARROW_STRUCT_LINESTRING:
    1261          42 :                 builder = MakeGeoArrowStructBuilder(m_poMemoryPool, nDim, 1,
    1262          42 :                                                     m_apoBaseStructGeomType[i]);
    1263          21 :                 break;
    1264             : 
    1265          29 :             case OGRArrowGeomEncoding::GEOARROW_STRUCT_POLYGON:
    1266          58 :                 builder = MakeGeoArrowStructBuilder(m_poMemoryPool, nDim, 2,
    1267          58 :                                                     m_apoBaseStructGeomType[i]);
    1268          29 :                 break;
    1269             : 
    1270          21 :             case OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTIPOINT:
    1271          42 :                 builder = MakeGeoArrowStructBuilder(m_poMemoryPool, nDim, 1,
    1272          42 :                                                     m_apoBaseStructGeomType[i]);
    1273          21 :                 break;
    1274             : 
    1275          21 :             case OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTILINESTRING:
    1276          42 :                 builder = MakeGeoArrowStructBuilder(m_poMemoryPool, nDim, 2,
    1277          42 :                                                     m_apoBaseStructGeomType[i]);
    1278          21 :                 break;
    1279             : 
    1280          29 :             case OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTIPOLYGON:
    1281          58 :                 builder = MakeGeoArrowStructBuilder(m_poMemoryPool, nDim, 3,
    1282          58 :                                                     m_apoBaseStructGeomType[i]);
    1283          29 :                 break;
    1284             : 
    1285           0 :             case OGRArrowGeomEncoding::GEOARROW_FSL_GENERIC:
    1286             :             case OGRArrowGeomEncoding::GEOARROW_STRUCT_GENERIC:
    1287           0 :                 CPLAssert(false);
    1288             :                 break;
    1289             :         }
    1290             : 
    1291         386 :         m_apoBuilders.emplace_back(builder);
    1292             : 
    1293         386 :         if (m_bWriteBBoxStruct)
    1294             :         {
    1295             :             m_apoBuildersBBOXXMin.emplace_back(
    1296         189 :                 std::make_shared<arrow::FloatBuilder>(m_poMemoryPool));
    1297             :             m_apoBuildersBBOXYMin.emplace_back(
    1298         189 :                 std::make_shared<arrow::FloatBuilder>(m_poMemoryPool));
    1299             :             m_apoBuildersBBOXXMax.emplace_back(
    1300         189 :                 std::make_shared<arrow::FloatBuilder>(m_poMemoryPool));
    1301             :             m_apoBuildersBBOXYMax.emplace_back(
    1302         189 :                 std::make_shared<arrow::FloatBuilder>(m_poMemoryPool));
    1303             :             m_apoBuildersBBOXStruct.emplace_back(
    1304         378 :                 std::make_shared<arrow::StructBuilder>(
    1305         189 :                     m_apoFieldsBBOX[i]->type(), m_poMemoryPool,
    1306        1701 :                     std::vector<std::shared_ptr<arrow::ArrayBuilder>>{
    1307         189 :                         m_apoBuildersBBOXXMin.back(),
    1308         189 :                         m_apoBuildersBBOXYMin.back(),
    1309         189 :                         m_apoBuildersBBOXXMax.back(),
    1310        1323 :                         m_apoBuildersBBOXYMax.back()}));
    1311             :         }
    1312             :     }
    1313         392 : }
    1314             : 
    1315             : /************************************************************************/
    1316             : /*                          castToFloatDown()                            */
    1317             : /************************************************************************/
    1318             : 
    1319             : // Cf https://github.com/sqlite/sqlite/blob/90e4a3b7fcdf63035d6f35eb44d11ff58ff4b068/ext/rtree/rtree.c#L2993C1-L2995C3
    1320             : /*
    1321             : ** Rounding constants for float->double conversion.
    1322             : */
    1323             : #define RNDTOWARDS (1.0 - 1.0 / 8388608.0) /* Round towards zero */
    1324             : #define RNDAWAY (1.0 + 1.0 / 8388608.0)    /* Round away from zero */
    1325             : 
    1326             : /*
    1327             : ** Convert an sqlite3_value into an RtreeValue (presumably a float)
    1328             : ** while taking care to round toward negative or positive, respectively.
    1329             : */
    1330        3490 : static float castToFloatDown(double d)
    1331             : {
    1332        3490 :     float f = static_cast<float>(d);
    1333        3490 :     if (f > d)
    1334             :     {
    1335          12 :         f = static_cast<float>(d * (d < 0 ? RNDAWAY : RNDTOWARDS));
    1336             :     }
    1337        3490 :     return f;
    1338             : }
    1339             : 
    1340        3490 : static float castToFloatUp(double d)
    1341             : {
    1342        3490 :     float f = static_cast<float>(d);
    1343        3490 :     if (f < d)
    1344             :     {
    1345           7 :         f = static_cast<float>(d * (d < 0 ? RNDTOWARDS : RNDAWAY));
    1346             :     }
    1347        3490 :     return f;
    1348             : }
    1349             : 
    1350             : /************************************************************************/
    1351             : /*                         GeoArrowLineBuilder()                        */
    1352             : /************************************************************************/
    1353             : 
    1354             : template <class PointBuilderType>
    1355         516 : static OGRErr GeoArrowLineBuilder(const OGRLineString *poLS,
    1356             :                                   PointBuilderType *poPointBuilder,
    1357             :                                   arrow::DoubleBuilder *poXBuilder,
    1358             :                                   arrow::DoubleBuilder *poYBuilder,
    1359             :                                   arrow::DoubleBuilder *poZBuilder,
    1360             :                                   arrow::DoubleBuilder *poMBuilder)
    1361             : {
    1362        2360 :     for (int j = 0; j < poLS->getNumPoints(); ++j)
    1363             :     {
    1364        1844 :         OGR_ARROW_RETURN_OGRERR_NOT_OK(poPointBuilder->Append());
    1365        1844 :         OGR_ARROW_RETURN_OGRERR_NOT_OK(poXBuilder->Append(poLS->getX(j)));
    1366        1844 :         OGR_ARROW_RETURN_OGRERR_NOT_OK(poYBuilder->Append(poLS->getY(j)));
    1367        1844 :         if (poZBuilder)
    1368         540 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poZBuilder->Append(poLS->getZ(j)));
    1369        1844 :         if (poMBuilder)
    1370         220 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poMBuilder->Append(poLS->getM(j)));
    1371             :     }
    1372         516 :     return OGRERR_NONE;
    1373             : }
    1374             : 
    1375             : /************************************************************************/
    1376             : /*                          BuildGeometry()                             */
    1377             : /************************************************************************/
    1378             : 
    1379        3592 : inline OGRErr OGRArrowWriterLayer::BuildGeometry(OGRGeometry *poGeom,
    1380             :                                                  int iGeomField,
    1381             :                                                  arrow::ArrayBuilder *poBuilder)
    1382             : {
    1383        3592 :     const auto eGType = poGeom ? poGeom->getGeometryType() : wkbNone;
    1384             :     const auto eColumnGType =
    1385        3592 :         m_poFeatureDefn->GetGeomFieldDefn(iGeomField)->GetType();
    1386        3592 :     const bool bHasZ = CPL_TO_BOOL(OGR_GT_HasZ(eColumnGType));
    1387        3592 :     const bool bHasM = CPL_TO_BOOL(OGR_GT_HasM(eColumnGType));
    1388        3592 :     const bool bIsEmpty = poGeom != nullptr && poGeom->IsEmpty();
    1389        3592 :     OGREnvelope3D oEnvelope;
    1390        3592 :     if (poGeom != nullptr && !bIsEmpty)
    1391             :     {
    1392        2093 :         if (poGeom->Is3D())
    1393             :         {
    1394         266 :             poGeom->getEnvelope(&oEnvelope);
    1395         266 :             m_aoEnvelopes[iGeomField].Merge(oEnvelope);
    1396             :         }
    1397             :         else
    1398             :         {
    1399        1827 :             poGeom->getEnvelope(static_cast<OGREnvelope *>(&oEnvelope));
    1400        1827 :             m_aoEnvelopes[iGeomField].Merge(oEnvelope);
    1401             :         }
    1402        2093 :         m_oSetWrittenGeometryTypes[iGeomField].insert(eGType);
    1403             :     }
    1404             : 
    1405        3592 :     if (m_bWriteBBoxStruct)
    1406             :     {
    1407        2889 :         if (poGeom && !bIsEmpty)
    1408             :         {
    1409        1702 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1410             :                 m_apoBuildersBBOXXMin[iGeomField]->Append(
    1411             :                     castToFloatDown(oEnvelope.MinX)));
    1412        1702 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1413             :                 m_apoBuildersBBOXYMin[iGeomField]->Append(
    1414             :                     castToFloatDown(oEnvelope.MinY)));
    1415        1702 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1416             :                 m_apoBuildersBBOXXMax[iGeomField]->Append(
    1417             :                     castToFloatUp(oEnvelope.MaxX)));
    1418        1702 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1419             :                 m_apoBuildersBBOXYMax[iGeomField]->Append(
    1420             :                     castToFloatUp(oEnvelope.MaxY)));
    1421        1702 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1422             :                 m_apoBuildersBBOXStruct[iGeomField]->Append());
    1423             :         }
    1424             :         else
    1425             :         {
    1426        1187 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1427             :                 m_apoBuildersBBOXStruct[iGeomField]->AppendNull());
    1428             :         }
    1429             :     }
    1430             : 
    1431        3592 :     if (poGeom == nullptr)
    1432             :     {
    1433        3831 :         if (m_aeGeomEncoding[iGeomField] ==
    1434        1285 :                 OGRArrowGeomEncoding::GEOARROW_FSL_POINT &&
    1435        1285 :             GetDriverUCName() == "PARQUET")
    1436             :         {
    1437             :             // For some reason, Parquet doesn't support a NULL FixedSizeList
    1438             :             // on reading
    1439           4 :             auto poPointBuilder =
    1440             :                 static_cast<arrow::FixedSizeListBuilder *>(poBuilder);
    1441           4 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poPointBuilder->Append());
    1442             :             auto poValueBuilder = static_cast<arrow::DoubleBuilder *>(
    1443           4 :                 poPointBuilder->value_builder());
    1444           4 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poValueBuilder->Append(
    1445             :                 std::numeric_limits<double>::quiet_NaN()));
    1446           4 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poValueBuilder->Append(
    1447             :                 std::numeric_limits<double>::quiet_NaN()));
    1448           4 :             if (bHasZ)
    1449           2 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(poValueBuilder->Append(
    1450             :                     std::numeric_limits<double>::quiet_NaN()));
    1451           4 :             if (bHasM)
    1452           0 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(poValueBuilder->Append(
    1453             :                     std::numeric_limits<double>::quiet_NaN()));
    1454             :         }
    1455             :         else
    1456             :         {
    1457        1273 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poBuilder->AppendNull());
    1458             :         }
    1459             : 
    1460        1277 :         return OGRERR_NONE;
    1461             :     }
    1462             : 
    1463             :     // The following checks are only valid for GeoArrow encoding
    1464        3297 :     if (m_aeGeomEncoding[iGeomField] != OGRArrowGeomEncoding::WKB &&
    1465         982 :         m_aeGeomEncoding[iGeomField] != OGRArrowGeomEncoding::WKT)
    1466             :     {
    1467         862 :         if ((!bIsEmpty && eGType != eColumnGType) ||
    1468         188 :             (bIsEmpty && wkbFlatten(eGType) != wkbFlatten(eColumnGType)))
    1469             :         {
    1470           6 :             CPLError(CE_Warning, CPLE_AppDefined,
    1471             :                      "Geometry of type %s found, whereas %s is expected. "
    1472             :                      "Writing null geometry",
    1473             :                      OGRGeometryTypeToName(eGType),
    1474             :                      OGRGeometryTypeToName(eColumnGType));
    1475           6 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poBuilder->AppendNull());
    1476             : 
    1477           6 :             return OGRERR_NONE;
    1478             :         }
    1479             :     }
    1480             : 
    1481        2309 :     switch (m_aeGeomEncoding[iGeomField])
    1482             :     {
    1483        1333 :         case OGRArrowGeomEncoding::WKB:
    1484             :         {
    1485           0 :             std::unique_ptr<OGRGeometry> poGeomModified;
    1486        1333 :             if (OGR_GT_HasM(eGType) && !OGR_GT_HasM(eColumnGType))
    1487             :             {
    1488             :                 static bool bHasWarned = false;
    1489           0 :                 if (!bHasWarned)
    1490             :                 {
    1491           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1492             :                              "Removing M component from geometry");
    1493           0 :                     bHasWarned = true;
    1494             :                 }
    1495           0 :                 poGeomModified.reset(poGeom->clone());
    1496           0 :                 poGeomModified->setMeasured(false);
    1497           0 :                 poGeom = poGeomModified.get();
    1498             :             }
    1499        1333 :             FixupGeometryBeforeWriting(poGeom);
    1500        1333 :             const auto nSize = poGeom->WkbSize();
    1501        1333 :             if (nSize < INT_MAX)
    1502             :             {
    1503        1333 :                 m_abyBuffer.resize(nSize);
    1504        1333 :                 poGeom->exportToWkb(wkbNDR, &m_abyBuffer[0], wkbVariantIso);
    1505        1333 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1506             :                     static_cast<arrow::BinaryBuilder *>(poBuilder)->Append(
    1507             :                         m_abyBuffer.data(),
    1508             :                         static_cast<int>(m_abyBuffer.size())));
    1509             :             }
    1510             :             else
    1511             :             {
    1512           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1513             :                          "Too big geometry. "
    1514             :                          "Writing null geometry");
    1515           0 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(poBuilder->AppendNull());
    1516             :             }
    1517        1333 :             break;
    1518             :         }
    1519             : 
    1520         308 :         case OGRArrowGeomEncoding::WKT:
    1521             :         {
    1522         308 :             OGRWktOptions options;
    1523         308 :             options.variant = wkbVariantIso;
    1524         308 :             if (m_nWKTCoordinatePrecision >= 0)
    1525             :             {
    1526           0 :                 options.format = OGRWktFormat::F;
    1527           0 :                 options.xyPrecision = m_nWKTCoordinatePrecision;
    1528           0 :                 options.zPrecision = m_nWKTCoordinatePrecision;
    1529           0 :                 options.mPrecision = m_nWKTCoordinatePrecision;
    1530             :             }
    1531         308 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1532             :                 static_cast<arrow::StringBuilder *>(poBuilder)->Append(
    1533             :                     poGeom->exportToWkt(options)));
    1534         308 :             break;
    1535             :         }
    1536             : 
    1537          20 :         case OGRArrowGeomEncoding::GEOARROW_FSL_POINT:
    1538             :         {
    1539          20 :             const auto poPoint = poGeom->toPoint();
    1540          20 :             auto poPointBuilder =
    1541             :                 static_cast<arrow::FixedSizeListBuilder *>(poBuilder);
    1542          20 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poPointBuilder->Append());
    1543             :             auto poValueBuilder = static_cast<arrow::DoubleBuilder *>(
    1544          20 :                 poPointBuilder->value_builder());
    1545          20 :             if (bIsEmpty)
    1546             :             {
    1547           8 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(poValueBuilder->Append(
    1548             :                     std::numeric_limits<double>::quiet_NaN()));
    1549           8 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(poValueBuilder->Append(
    1550             :                     std::numeric_limits<double>::quiet_NaN()));
    1551           8 :                 if (bHasZ)
    1552           4 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(poValueBuilder->Append(
    1553             :                         std::numeric_limits<double>::quiet_NaN()));
    1554           8 :                 if (bHasM)
    1555           2 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(poValueBuilder->Append(
    1556             :                         std::numeric_limits<double>::quiet_NaN()));
    1557             :             }
    1558             :             else
    1559             :             {
    1560          12 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1561             :                     poValueBuilder->Append(poPoint->getX()));
    1562          12 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1563             :                     poValueBuilder->Append(poPoint->getY()));
    1564          12 :                 if (bHasZ)
    1565           6 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1566             :                         poValueBuilder->Append(poPoint->getZ()));
    1567          12 :                 if (bHasM)
    1568           2 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1569             :                         poValueBuilder->Append(poPoint->getM()));
    1570             :             }
    1571          20 :             break;
    1572             :         }
    1573             : 
    1574             : #define GET_XYZM_STRUCT_FIELD_BUILDERS_FROM(poPointBuilder)                    \
    1575             :     auto poXBuilder =                                                          \
    1576             :         static_cast<arrow::DoubleBuilder *>(poPointBuilder->field_builder(0)); \
    1577             :     auto poYBuilder =                                                          \
    1578             :         static_cast<arrow::DoubleBuilder *>(poPointBuilder->field_builder(1)); \
    1579             :     int iSubField = 2;                                                         \
    1580             :     arrow::DoubleBuilder *poZBuilder = nullptr;                                \
    1581             :     if (bHasZ)                                                                 \
    1582             :     {                                                                          \
    1583             :         poZBuilder = static_cast<arrow::DoubleBuilder *>(                      \
    1584             :             poPointBuilder->field_builder(iSubField));                         \
    1585             :         ++iSubField;                                                           \
    1586             :     }                                                                          \
    1587             :     arrow::DoubleBuilder *poMBuilder = nullptr;                                \
    1588             :     if (bHasM)                                                                 \
    1589             :     {                                                                          \
    1590             :         poMBuilder = static_cast<arrow::DoubleBuilder *>(                      \
    1591             :             poPointBuilder->field_builder(iSubField));                         \
    1592             :     }                                                                          \
    1593             :     do                                                                         \
    1594             :     {                                                                          \
    1595             :     } while (0)
    1596             : 
    1597          85 :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_POINT:
    1598             :         {
    1599          85 :             const auto poPoint = poGeom->toPoint();
    1600          85 :             auto poPointBuilder =
    1601             :                 static_cast<arrow::StructBuilder *>(poBuilder);
    1602          85 :             GET_XYZM_STRUCT_FIELD_BUILDERS_FROM(poPointBuilder);
    1603          85 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poPointBuilder->Append());
    1604             : 
    1605          85 :             if (bIsEmpty)
    1606             :             {
    1607          20 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(poXBuilder->Append(
    1608             :                     std::numeric_limits<double>::quiet_NaN()));
    1609          20 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(poYBuilder->Append(
    1610             :                     std::numeric_limits<double>::quiet_NaN()));
    1611             :             }
    1612             :             else
    1613             :             {
    1614          65 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1615             :                     poXBuilder->Append(poPoint->getX()));
    1616          65 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1617             :                     poYBuilder->Append(poPoint->getY()));
    1618             :             }
    1619          85 :             if (poZBuilder)
    1620             :             {
    1621          28 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(poZBuilder->Append(
    1622             :                     bIsEmpty ? std::numeric_limits<double>::quiet_NaN()
    1623             :                              : poPoint->getZ()));
    1624             :             }
    1625          85 :             if (poMBuilder)
    1626             :             {
    1627           4 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(poMBuilder->Append(
    1628             :                     bIsEmpty ? std::numeric_limits<double>::quiet_NaN()
    1629             :                              : poPoint->getM()));
    1630             :             }
    1631          85 :             break;
    1632             :         }
    1633             : 
    1634          20 :         case OGRArrowGeomEncoding::GEOARROW_FSL_LINESTRING:
    1635             :         {
    1636          20 :             const auto poLS = poGeom->toLineString();
    1637          20 :             auto poListBuilder = static_cast<arrow::ListBuilder *>(poBuilder);
    1638             :             auto poPointBuilder = static_cast<arrow::FixedSizeListBuilder *>(
    1639          20 :                 poListBuilder->value_builder());
    1640             :             auto poValueBuilder = static_cast<arrow::DoubleBuilder *>(
    1641          20 :                 poPointBuilder->value_builder());
    1642             : 
    1643          20 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poListBuilder->Append());
    1644          20 :             OGR_ARROW_PROPAGATE_OGRERR(GeoArrowLineBuilder(
    1645             :                 poLS, poPointBuilder, poValueBuilder, poValueBuilder,
    1646             :                 bHasZ ? poValueBuilder : nullptr,
    1647             :                 bHasM ? poValueBuilder : nullptr));
    1648          20 :             break;
    1649             :         }
    1650             : 
    1651          57 :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_LINESTRING:
    1652             :         {
    1653          57 :             const auto poLS = poGeom->toLineString();
    1654          57 :             auto poListBuilder = static_cast<arrow::ListBuilder *>(poBuilder);
    1655             :             auto poPointBuilder = static_cast<arrow::StructBuilder *>(
    1656          57 :                 poListBuilder->value_builder());
    1657          57 :             GET_XYZM_STRUCT_FIELD_BUILDERS_FROM(poPointBuilder);
    1658             : 
    1659          57 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poListBuilder->Append());
    1660          57 :             OGR_ARROW_PROPAGATE_OGRERR(
    1661             :                 GeoArrowLineBuilder(poLS, poPointBuilder, poXBuilder,
    1662             :                                     poYBuilder, poZBuilder, poMBuilder));
    1663          57 :             break;
    1664             :         }
    1665             : 
    1666          32 :         case OGRArrowGeomEncoding::GEOARROW_FSL_POLYGON:
    1667             :         {
    1668          32 :             const auto poPolygon = poGeom->toPolygon();
    1669          32 :             auto poPolygonBuilder =
    1670             :                 static_cast<arrow::ListBuilder *>(poBuilder);
    1671             :             auto poRingBuilder = static_cast<arrow::ListBuilder *>(
    1672          32 :                 poPolygonBuilder->value_builder());
    1673             :             auto poPointBuilder = static_cast<arrow::FixedSizeListBuilder *>(
    1674          32 :                 poRingBuilder->value_builder());
    1675             :             auto poValueBuilder = static_cast<arrow::DoubleBuilder *>(
    1676          32 :                 poPointBuilder->value_builder());
    1677          32 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poPolygonBuilder->Append());
    1678          62 :             for (const auto *poRing : *poPolygon)
    1679             :             {
    1680          30 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(poRingBuilder->Append());
    1681          30 :                 OGR_ARROW_PROPAGATE_OGRERR(GeoArrowLineBuilder(
    1682             :                     poRing, poPointBuilder, poValueBuilder, poValueBuilder,
    1683             :                     bHasZ ? poValueBuilder : nullptr,
    1684             :                     bHasM ? poValueBuilder : nullptr));
    1685             :             }
    1686          32 :             break;
    1687             :         }
    1688             : 
    1689          93 :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_POLYGON:
    1690             :         {
    1691          93 :             const auto poPolygon = poGeom->toPolygon();
    1692          93 :             auto poPolygonBuilder =
    1693             :                 static_cast<arrow::ListBuilder *>(poBuilder);
    1694             :             auto poRingBuilder = static_cast<arrow::ListBuilder *>(
    1695          93 :                 poPolygonBuilder->value_builder());
    1696             :             auto poPointBuilder = static_cast<arrow::StructBuilder *>(
    1697          93 :                 poRingBuilder->value_builder());
    1698          93 :             GET_XYZM_STRUCT_FIELD_BUILDERS_FROM(poPointBuilder);
    1699             : 
    1700          93 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poPolygonBuilder->Append());
    1701         178 :             for (const auto *poRing : *poPolygon)
    1702             :             {
    1703          85 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(poRingBuilder->Append());
    1704          85 :                 OGR_ARROW_PROPAGATE_OGRERR(
    1705             :                     GeoArrowLineBuilder(poRing, poPointBuilder, poXBuilder,
    1706             :                                         poYBuilder, poZBuilder, poMBuilder));
    1707             :             }
    1708          93 :             break;
    1709             :         }
    1710             : 
    1711          32 :         case OGRArrowGeomEncoding::GEOARROW_FSL_MULTIPOINT:
    1712             :         {
    1713          32 :             const auto poMultiPoint = poGeom->toMultiPoint();
    1714          32 :             auto poListBuilder = static_cast<arrow::ListBuilder *>(poBuilder);
    1715             :             auto poPointBuilder = static_cast<arrow::FixedSizeListBuilder *>(
    1716          32 :                 poListBuilder->value_builder());
    1717             :             auto poValueBuilder = static_cast<arrow::DoubleBuilder *>(
    1718          32 :                 poPointBuilder->value_builder());
    1719          32 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poListBuilder->Append());
    1720          88 :             for (const auto *poPoint : *poMultiPoint)
    1721             :             {
    1722          56 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(poPointBuilder->Append());
    1723          56 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1724             :                     poValueBuilder->Append(poPoint->getX()));
    1725          56 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1726             :                     poValueBuilder->Append(poPoint->getY()));
    1727          56 :                 if (bHasZ)
    1728          28 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1729             :                         poValueBuilder->Append(poPoint->getZ()));
    1730          56 :                 if (bHasM)
    1731          18 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1732             :                         poValueBuilder->Append(poPoint->getM()));
    1733             :             }
    1734          32 :             break;
    1735             :         }
    1736             : 
    1737          81 :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTIPOINT:
    1738             :         {
    1739          81 :             const auto poMultiPoint = poGeom->toMultiPoint();
    1740          81 :             auto poListBuilder = static_cast<arrow::ListBuilder *>(poBuilder);
    1741             :             auto poPointBuilder = static_cast<arrow::StructBuilder *>(
    1742          81 :                 poListBuilder->value_builder());
    1743          81 :             GET_XYZM_STRUCT_FIELD_BUILDERS_FROM(poPointBuilder);
    1744             : 
    1745          81 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poListBuilder->Append());
    1746         198 :             for (const auto *poPoint : *poMultiPoint)
    1747             :             {
    1748         117 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(poPointBuilder->Append());
    1749         117 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1750             :                     poXBuilder->Append(poPoint->getX()));
    1751         117 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1752             :                     poYBuilder->Append(poPoint->getY()));
    1753         117 :                 if (poZBuilder)
    1754          58 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1755             :                         poZBuilder->Append(poPoint->getZ()));
    1756         117 :                 if (poMBuilder)
    1757          18 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1758             :                         poMBuilder->Append(poPoint->getM()));
    1759             :             }
    1760          81 :             break;
    1761             :         }
    1762             : 
    1763          28 :         case OGRArrowGeomEncoding::GEOARROW_FSL_MULTILINESTRING:
    1764             :         {
    1765          28 :             const auto poMLS = poGeom->toMultiLineString();
    1766          28 :             auto poMLSBuilder = static_cast<arrow::ListBuilder *>(poBuilder);
    1767             :             auto poLSBuilder = static_cast<arrow::ListBuilder *>(
    1768          28 :                 poMLSBuilder->value_builder());
    1769             :             auto poPointBuilder = static_cast<arrow::FixedSizeListBuilder *>(
    1770          28 :                 poLSBuilder->value_builder());
    1771             :             auto poValueBuilder = static_cast<arrow::DoubleBuilder *>(
    1772          28 :                 poPointBuilder->value_builder());
    1773          28 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poMLSBuilder->Append());
    1774          60 :             for (const auto *poLS : *poMLS)
    1775             :             {
    1776          32 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(poLSBuilder->Append());
    1777          32 :                 OGR_ARROW_PROPAGATE_OGRERR(GeoArrowLineBuilder(
    1778             :                     poLS, poPointBuilder, poValueBuilder, poValueBuilder,
    1779             :                     bHasZ ? poValueBuilder : nullptr,
    1780             :                     bHasM ? poValueBuilder : nullptr));
    1781             :             }
    1782          28 :             break;
    1783             :         }
    1784             : 
    1785          77 :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTILINESTRING:
    1786             :         {
    1787          77 :             const auto poMLS = poGeom->toMultiLineString();
    1788          77 :             auto poMLSBuilder = static_cast<arrow::ListBuilder *>(poBuilder);
    1789             :             auto poLSBuilder = static_cast<arrow::ListBuilder *>(
    1790          77 :                 poMLSBuilder->value_builder());
    1791             :             auto poPointBuilder = static_cast<arrow::StructBuilder *>(
    1792          77 :                 poLSBuilder->value_builder());
    1793          77 :             GET_XYZM_STRUCT_FIELD_BUILDERS_FROM(poPointBuilder);
    1794             : 
    1795          77 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poMLSBuilder->Append());
    1796         170 :             for (const auto *poLS : *poMLS)
    1797             :             {
    1798          93 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(poLSBuilder->Append());
    1799          93 :                 OGR_ARROW_PROPAGATE_OGRERR(
    1800             :                     GeoArrowLineBuilder(poLS, poPointBuilder, poXBuilder,
    1801             :                                         poYBuilder, poZBuilder, poMBuilder));
    1802             :             }
    1803          77 :             break;
    1804             :         }
    1805             : 
    1806          38 :         case OGRArrowGeomEncoding::GEOARROW_FSL_MULTIPOLYGON:
    1807             :         {
    1808          38 :             const auto poMPoly = poGeom->toMultiPolygon();
    1809          38 :             auto poMPolyBuilder = static_cast<arrow::ListBuilder *>(poBuilder);
    1810             :             auto poPolyBuilder = static_cast<arrow::ListBuilder *>(
    1811          38 :                 poMPolyBuilder->value_builder());
    1812             :             auto poRingBuilder = static_cast<arrow::ListBuilder *>(
    1813          38 :                 poPolyBuilder->value_builder());
    1814             :             auto poPointBuilder = static_cast<arrow::FixedSizeListBuilder *>(
    1815          38 :                 poRingBuilder->value_builder());
    1816             :             auto poValueBuilder = static_cast<arrow::DoubleBuilder *>(
    1817          38 :                 poPointBuilder->value_builder());
    1818          38 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poMPolyBuilder->Append());
    1819          82 :             for (const auto *poPolygon : *poMPoly)
    1820             :             {
    1821          44 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(poPolyBuilder->Append());
    1822          98 :                 for (const auto *poRing : *poPolygon)
    1823             :                 {
    1824          54 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(poRingBuilder->Append());
    1825          54 :                     OGR_ARROW_PROPAGATE_OGRERR(GeoArrowLineBuilder(
    1826             :                         poRing, poPointBuilder, poValueBuilder, poValueBuilder,
    1827             :                         bHasZ ? poValueBuilder : nullptr,
    1828             :                         bHasM ? poValueBuilder : nullptr));
    1829             :                 }
    1830             :             }
    1831          38 :             break;
    1832             :         }
    1833             : 
    1834         105 :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTIPOLYGON:
    1835             :         {
    1836         105 :             const auto poMPoly = poGeom->toMultiPolygon();
    1837         105 :             auto poMPolyBuilder = static_cast<arrow::ListBuilder *>(poBuilder);
    1838             :             auto poPolyBuilder = static_cast<arrow::ListBuilder *>(
    1839         105 :                 poMPolyBuilder->value_builder());
    1840             :             auto poRingBuilder = static_cast<arrow::ListBuilder *>(
    1841         105 :                 poPolyBuilder->value_builder());
    1842             :             auto poPointBuilder = static_cast<arrow::StructBuilder *>(
    1843         105 :                 poRingBuilder->value_builder());
    1844         105 :             GET_XYZM_STRUCT_FIELD_BUILDERS_FROM(poPointBuilder);
    1845             : 
    1846         105 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poMPolyBuilder->Append());
    1847         222 :             for (const auto *poPolygon : *poMPoly)
    1848             :             {
    1849         117 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(poPolyBuilder->Append());
    1850         262 :                 for (const auto *poRing : *poPolygon)
    1851             :                 {
    1852         145 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(poRingBuilder->Append());
    1853         145 :                     OGR_ARROW_PROPAGATE_OGRERR(GeoArrowLineBuilder(
    1854             :                         poRing, poPointBuilder, poXBuilder, poYBuilder,
    1855             :                         poZBuilder, poMBuilder));
    1856             :                 }
    1857             :             }
    1858         105 :             break;
    1859             :         }
    1860             : 
    1861           0 :         case OGRArrowGeomEncoding::GEOARROW_FSL_GENERIC:
    1862             :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_GENERIC:
    1863             :         {
    1864           0 :             CPLAssert(false);
    1865             :             break;
    1866             :         }
    1867             :     }
    1868             : 
    1869        2309 :     return OGRERR_NONE;
    1870             : }
    1871             : 
    1872             : /************************************************************************/
    1873             : /*                          ICreateFeature()                            */
    1874             : /************************************************************************/
    1875             : 
    1876        3138 : inline OGRErr OGRArrowWriterLayer::ICreateFeature(OGRFeature *poFeature)
    1877             : {
    1878        3138 :     if (m_poSchema == nullptr)
    1879             :     {
    1880         234 :         CreateSchema();
    1881             :     }
    1882             : 
    1883        3138 :     if (m_apoBuilders.empty())
    1884             :     {
    1885         271 :         if (!m_apoFieldsFromArrowSchema.empty())
    1886             :         {
    1887           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    1888             :                      "ICreateFeature() cannot be used after "
    1889             :                      "CreateFieldFromArrowSchema()");
    1890           0 :             return OGRERR_FAILURE;
    1891             :         }
    1892         271 :         CreateArrayBuilders();
    1893             :     }
    1894             : 
    1895             :     // First pass to check not-null constraints as Arrow doesn't seem
    1896             :     // to do that on the writing side. But such files can't be read.
    1897        3138 :     const int nFieldCount = m_poFeatureDefn->GetFieldCount();
    1898        8373 :     for (int i = 0; i < nFieldCount; ++i)
    1899             :     {
    1900        5236 :         const auto poFieldDefn = m_poFeatureDefn->GetFieldDefn(i);
    1901        5238 :         if (!poFieldDefn->IsNullable() &&
    1902           2 :             !poFeature->IsFieldSetAndNotNullUnsafe(i))
    1903             :         {
    1904           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1905             :                      "Null value found in non-nullable field %s",
    1906             :                      poFieldDefn->GetNameRef());
    1907           1 :             return OGRERR_FAILURE;
    1908             :         }
    1909             :     }
    1910             : 
    1911        3137 :     const int nGeomFieldCount = m_poFeatureDefn->GetGeomFieldCount();
    1912        6380 :     for (int i = 0; i < nGeomFieldCount; ++i)
    1913             :     {
    1914        3243 :         const auto poGeomFieldDefn = m_poFeatureDefn->GetGeomFieldDefn(i);
    1915        3275 :         if (!poGeomFieldDefn->IsNullable() &&
    1916          32 :             poFeature->GetGeomFieldRef(i) == nullptr)
    1917             :         {
    1918           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1919             :                      "Null value found in non-nullable geometry field %s",
    1920             :                      poGeomFieldDefn->GetNameRef());
    1921           0 :             return OGRERR_FAILURE;
    1922             :         }
    1923             :     }
    1924             : 
    1925             :     // Write FID, if FID column present
    1926        3137 :     int nArrowIdx = 0;
    1927        3137 :     if (!m_osFIDColumn.empty())
    1928             :     {
    1929        2258 :         int64_t nFID = poFeature->GetFID();
    1930        2258 :         if (nFID == OGRNullFID)
    1931             :         {
    1932          36 :             nFID = m_nFeatureCount;
    1933          36 :             poFeature->SetFID(nFID);
    1934             :         }
    1935             :         auto poBuilder =
    1936        2258 :             static_cast<arrow::Int64Builder *>(m_apoBuilders[0].get());
    1937        2258 :         OGR_ARROW_RETURN_OGRERR_NOT_OK(poBuilder->Append(nFID));
    1938        2258 :         nArrowIdx++;
    1939             :     }
    1940             : 
    1941             :     // Write attributes
    1942        8372 :     for (int i = 0; i < nFieldCount; ++i, ++nArrowIdx)
    1943             :     {
    1944        5235 :         auto poBuilder = m_apoBuilders[nArrowIdx].get();
    1945        5235 :         if (!poFeature->IsFieldSetAndNotNullUnsafe(i))
    1946             :         {
    1947         971 :             OGR_ARROW_RETURN_OGRERR_NOT_OK(poBuilder->AppendNull());
    1948         971 :             continue;
    1949             :         }
    1950             : 
    1951        4264 :         const auto poFieldDefn = m_poFeatureDefn->GetFieldDefn(i);
    1952        4264 :         const auto eSubDT = poFieldDefn->GetSubType();
    1953        4264 :         switch (poFieldDefn->GetType())
    1954             :         {
    1955        2453 :             case OFTInteger:
    1956        2453 :                 if (eSubDT == OFSTBoolean)
    1957          16 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1958             :                         static_cast<arrow::BooleanBuilder *>(poBuilder)->Append(
    1959             :                             poFeature->GetFieldAsIntegerUnsafe(i) != 0));
    1960        2437 :                 else if (eSubDT == OFSTInt16)
    1961          16 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1962             :                         static_cast<arrow::Int16Builder *>(poBuilder)->Append(
    1963             :                             static_cast<int16_t>(
    1964             :                                 poFeature->GetFieldAsIntegerUnsafe(i))));
    1965             :                 else
    1966        2421 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1967             :                         static_cast<arrow::Int32Builder *>(poBuilder)->Append(
    1968             :                             poFeature->GetFieldAsIntegerUnsafe(i)));
    1969        2453 :                 break;
    1970             : 
    1971         158 :             case OFTInteger64:
    1972         158 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1973             :                     static_cast<arrow::Int64Builder *>(poBuilder)->Append(
    1974             :                         static_cast<int64_t>(
    1975             :                             poFeature->GetFieldAsInteger64Unsafe(i))));
    1976         158 :                 break;
    1977             : 
    1978         221 :             case OFTReal:
    1979             :             {
    1980         221 :                 const auto arrowType = m_poSchema->fields()[nArrowIdx]->type();
    1981         221 :                 const double dfVal = poFeature->GetFieldAsDoubleUnsafe(i);
    1982         221 :                 if (arrowType->id() == arrow::Type::DECIMAL128)
    1983             :                 {
    1984             :                     auto res = arrow::Decimal128::FromReal(
    1985             :                         dfVal, poFieldDefn->GetWidth(),
    1986          52 :                         poFieldDefn->GetPrecision());
    1987          52 :                     if (res.ok())
    1988             :                     {
    1989          52 :                         OGR_ARROW_RETURN_OGRERR_NOT_OK(
    1990             :                             static_cast<arrow::Decimal128Builder *>(poBuilder)
    1991             :                                 ->Append(*res));
    1992             :                     }
    1993             :                     else
    1994             :                     {
    1995           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
    1996             :                                  "Cannot parse %.18g as a %d.%d decimal", dfVal,
    1997             :                                  poFieldDefn->GetWidth(),
    1998             :                                  poFieldDefn->GetPrecision());
    1999           0 :                         OGR_ARROW_RETURN_OGRERR_NOT_OK(poBuilder->AppendNull());
    2000             :                     }
    2001             :                 }
    2002         169 :                 else if (arrowType->id() == arrow::Type::DECIMAL256)
    2003             :                 {
    2004             :                     auto res = arrow::Decimal256::FromReal(
    2005             :                         dfVal, poFieldDefn->GetWidth(),
    2006           0 :                         poFieldDefn->GetPrecision());
    2007           0 :                     if (res.ok())
    2008             :                     {
    2009           0 :                         OGR_ARROW_RETURN_OGRERR_NOT_OK(
    2010             :                             static_cast<arrow::Decimal256Builder *>(poBuilder)
    2011             :                                 ->Append(*res));
    2012             :                     }
    2013             :                     else
    2014             :                     {
    2015           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
    2016             :                                  "Cannot parse %.18g as a %d.%d decimal", dfVal,
    2017             :                                  poFieldDefn->GetWidth(),
    2018             :                                  poFieldDefn->GetPrecision());
    2019           0 :                         OGR_ARROW_RETURN_OGRERR_NOT_OK(poBuilder->AppendNull());
    2020             :                     }
    2021             :                 }
    2022         169 :                 else if (eSubDT == OFSTFloat32)
    2023             :                 {
    2024          28 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(
    2025             :                         static_cast<arrow::FloatBuilder *>(poBuilder)->Append(
    2026             :                             static_cast<float>(dfVal)));
    2027             :                 }
    2028             :                 else
    2029             :                 {
    2030         141 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(
    2031             :                         static_cast<arrow::DoubleBuilder *>(poBuilder)->Append(
    2032             :                             dfVal));
    2033             :                 }
    2034         221 :                 break;
    2035             :             }
    2036             : 
    2037         524 :             case OFTString:
    2038             :             case OFTWideString:
    2039         524 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(
    2040             :                     static_cast<arrow::StringBuilder *>(poBuilder)->Append(
    2041             :                         poFeature->GetFieldAsStringUnsafe(i)));
    2042         524 :                 break;
    2043             : 
    2044          62 :             case OFTBinary:
    2045             :             {
    2046          62 :                 int nSize = 0;
    2047          62 :                 const auto pData = poFeature->GetFieldAsBinary(i, &nSize);
    2048          62 :                 if (poFieldDefn->GetWidth() != 0)
    2049             :                 {
    2050          20 :                     if (poFieldDefn->GetWidth() != nSize)
    2051             :                     {
    2052           0 :                         CPLError(
    2053             :                             CE_Warning, CPLE_AppDefined,
    2054             :                             "Cannot write field %s. Got %d bytes, expected %d",
    2055             :                             poFieldDefn->GetNameRef(), nSize,
    2056             :                             poFieldDefn->GetWidth());
    2057           0 :                         OGR_ARROW_RETURN_OGRERR_NOT_OK(poBuilder->AppendNull());
    2058             :                     }
    2059             :                     else
    2060             :                     {
    2061          20 :                         OGR_ARROW_RETURN_OGRERR_NOT_OK(
    2062             :                             static_cast<arrow::FixedSizeBinaryBuilder *>(
    2063             :                                 poBuilder)
    2064             :                                 ->Append(pData));
    2065             :                     }
    2066             :                 }
    2067             :                 else
    2068          42 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(
    2069             :                         static_cast<arrow::BinaryBuilder *>(poBuilder)->Append(
    2070             :                             pData, nSize));
    2071          62 :                 break;
    2072             :             }
    2073             : 
    2074         216 :             case OFTIntegerList:
    2075             :             {
    2076         216 :                 auto poListBuilder =
    2077             :                     static_cast<arrow::ListBuilder *>(poBuilder);
    2078         216 :                 if (eSubDT == OFSTBoolean)
    2079             :                 {
    2080          36 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(poListBuilder->Append());
    2081             :                     auto poValueBuilder = static_cast<arrow::BooleanBuilder *>(
    2082          36 :                         poListBuilder->value_builder());
    2083          36 :                     int nValues = 0;
    2084             :                     const auto panValues =
    2085          36 :                         poFeature->GetFieldAsIntegerList(i, &nValues);
    2086         108 :                     for (int j = 0; j < nValues; ++j)
    2087          72 :                         OGR_ARROW_RETURN_OGRERR_NOT_OK(
    2088             :                             poValueBuilder->Append(panValues[j] != 0));
    2089             :                 }
    2090         180 :                 else if (eSubDT == OFSTInt16)
    2091             :                 {
    2092           0 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(poListBuilder->Append());
    2093             :                     auto poValueBuilder = static_cast<arrow::Int16Builder *>(
    2094           0 :                         poListBuilder->value_builder());
    2095           0 :                     int nValues = 0;
    2096             :                     const auto panValues =
    2097           0 :                         poFeature->GetFieldAsIntegerList(i, &nValues);
    2098           0 :                     for (int j = 0; j < nValues; ++j)
    2099           0 :                         OGR_ARROW_RETURN_OGRERR_NOT_OK(poValueBuilder->Append(
    2100             :                             static_cast<int16_t>(panValues[j])));
    2101             :                 }
    2102             :                 else
    2103             :                 {
    2104         180 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(poListBuilder->Append());
    2105             :                     auto poValueBuilder = static_cast<arrow::Int32Builder *>(
    2106         180 :                         poListBuilder->value_builder());
    2107         180 :                     int nValues = 0;
    2108             :                     const auto panValues =
    2109         180 :                         poFeature->GetFieldAsIntegerList(i, &nValues);
    2110         540 :                     for (int j = 0; j < nValues; ++j)
    2111         360 :                         OGR_ARROW_RETURN_OGRERR_NOT_OK(
    2112             :                             poValueBuilder->Append(panValues[j]));
    2113             :                 }
    2114         216 :                 break;
    2115             :             }
    2116             : 
    2117          92 :             case OFTInteger64List:
    2118             :             {
    2119          92 :                 auto poListBuilder =
    2120             :                     static_cast<arrow::ListBuilder *>(poBuilder);
    2121          92 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(poListBuilder->Append());
    2122             :                 auto poValueBuilder = static_cast<arrow::Int64Builder *>(
    2123          92 :                     poListBuilder->value_builder());
    2124          92 :                 int nValues = 0;
    2125             :                 const auto panValues =
    2126          92 :                     poFeature->GetFieldAsInteger64List(i, &nValues);
    2127         292 :                 for (int j = 0; j < nValues; ++j)
    2128         200 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(poValueBuilder->Append(
    2129             :                         static_cast<int64_t>(panValues[j])));
    2130          92 :                 break;
    2131             :             }
    2132             : 
    2133         152 :             case OFTRealList:
    2134             :             {
    2135         152 :                 auto poListBuilder =
    2136             :                     static_cast<arrow::ListBuilder *>(poBuilder);
    2137         152 :                 if (eSubDT == OFSTFloat32)
    2138             :                 {
    2139          48 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(poListBuilder->Append());
    2140             :                     auto poValueBuilder = static_cast<arrow::FloatBuilder *>(
    2141          48 :                         poListBuilder->value_builder());
    2142          48 :                     int nValues = 0;
    2143             :                     const auto padfValues =
    2144          48 :                         poFeature->GetFieldAsDoubleList(i, &nValues);
    2145         144 :                     for (int j = 0; j < nValues; ++j)
    2146          96 :                         OGR_ARROW_RETURN_OGRERR_NOT_OK(poValueBuilder->Append(
    2147             :                             static_cast<float>(padfValues[j])));
    2148             :                 }
    2149             :                 else
    2150             :                 {
    2151         104 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(poListBuilder->Append());
    2152             :                     auto poValueBuilder = static_cast<arrow::DoubleBuilder *>(
    2153         104 :                         poListBuilder->value_builder());
    2154         104 :                     int nValues = 0;
    2155             :                     const auto padfValues =
    2156         104 :                         poFeature->GetFieldAsDoubleList(i, &nValues);
    2157         280 :                     for (int j = 0; j < nValues; ++j)
    2158         176 :                         OGR_ARROW_RETURN_OGRERR_NOT_OK(
    2159             :                             poValueBuilder->Append(padfValues[j]));
    2160             :                 }
    2161         152 :                 break;
    2162             :             }
    2163             : 
    2164          52 :             case OFTStringList:
    2165             :             case OFTWideStringList:
    2166             :             {
    2167          52 :                 auto poListBuilder =
    2168             :                     static_cast<arrow::ListBuilder *>(poBuilder);
    2169          52 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(poListBuilder->Append());
    2170             :                 auto poValueBuilder = static_cast<arrow::StringBuilder *>(
    2171          52 :                     poListBuilder->value_builder());
    2172          52 :                 const auto papszValues = poFeature->GetFieldAsStringList(i);
    2173         132 :                 for (int j = 0; papszValues && papszValues[j]; ++j)
    2174          80 :                     OGR_ARROW_RETURN_OGRERR_NOT_OK(
    2175             :                         poValueBuilder->Append(papszValues[j]));
    2176          52 :                 break;
    2177             :             }
    2178             : 
    2179         109 :             case OFTDate:
    2180             :             {
    2181             :                 int nYear, nMonth, nDay, nHour, nMinute;
    2182             :                 float fSec;
    2183             :                 int nTZFlag;
    2184         109 :                 poFeature->GetFieldAsDateTime(i, &nYear, &nMonth, &nDay, &nHour,
    2185             :                                               &nMinute, &fSec, &nTZFlag);
    2186             :                 struct tm brokenDown;
    2187         109 :                 memset(&brokenDown, 0, sizeof(brokenDown));
    2188         109 :                 brokenDown.tm_year = nYear - 1900;
    2189         109 :                 brokenDown.tm_mon = nMonth - 1;
    2190         109 :                 brokenDown.tm_mday = nDay;
    2191         109 :                 GIntBig nVal = CPLYMDHMSToUnixTime(&brokenDown);
    2192         109 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(
    2193             :                     static_cast<arrow::Date32Builder *>(poBuilder)->Append(
    2194             :                         static_cast<int>(nVal / 86400)));
    2195         109 :                 break;
    2196             :             }
    2197             : 
    2198          36 :             case OFTTime:
    2199             :             {
    2200             :                 int nYear, nMonth, nDay, nHour, nMinute;
    2201             :                 float fSec;
    2202             :                 int nTZFlag;
    2203          36 :                 poFeature->GetFieldAsDateTime(i, &nYear, &nMonth, &nDay, &nHour,
    2204             :                                               &nMinute, &fSec, &nTZFlag);
    2205          36 :                 int nVal = nHour * 3600 + nMinute * 60;
    2206          36 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(
    2207             :                     static_cast<arrow::Time32Builder *>(poBuilder)->Append(
    2208             :                         static_cast<int>(
    2209             :                             (static_cast<double>(nVal) + fSec) * 1000 + 0.5)));
    2210          36 :                 break;
    2211             :             }
    2212             : 
    2213         189 :             case OFTDateTime:
    2214             :             {
    2215             :                 int nYear, nMonth, nDay, nHour, nMinute;
    2216             :                 float fSec;
    2217             :                 int nTZFlag;
    2218         189 :                 poFeature->GetFieldAsDateTime(i, &nYear, &nMonth, &nDay, &nHour,
    2219             :                                               &nMinute, &fSec, &nTZFlag);
    2220             :                 struct tm brokenDown;
    2221         189 :                 memset(&brokenDown, 0, sizeof(brokenDown));
    2222         189 :                 brokenDown.tm_year = nYear - 1900;
    2223         189 :                 brokenDown.tm_mon = nMonth - 1;
    2224         189 :                 brokenDown.tm_mday = nDay;
    2225         189 :                 brokenDown.tm_hour = nHour;
    2226         189 :                 brokenDown.tm_min = nMinute;
    2227         189 :                 brokenDown.tm_sec = 0;
    2228         189 :                 GIntBig nVal = CPLYMDHMSToUnixTime(&brokenDown);
    2229         306 :                 if (!IsFileWriterCreated() &&
    2230         117 :                     m_anTZFlag[i] != OGR_TZFLAG_UNKNOWN)
    2231             :                 {
    2232          59 :                     if (m_anTZFlag[i] == TZFLAG_UNINITIALIZED)
    2233          35 :                         m_anTZFlag[i] = nTZFlag;
    2234          24 :                     else if (m_anTZFlag[i] != nTZFlag)
    2235             :                     {
    2236           0 :                         if (m_anTZFlag[i] >= OGR_TZFLAG_MIXED_TZ &&
    2237           0 :                             nTZFlag >= OGR_TZFLAG_MIXED_TZ)
    2238             :                         {
    2239           0 :                             m_anTZFlag[i] =
    2240             :                                 OGR_TZFLAG_MIXED_TZ;  // harmonize on UTC ultimately
    2241             :                         }
    2242             :                         else
    2243             :                         {
    2244           0 :                             CPLError(CE_Warning, CPLE_AppDefined,
    2245             :                                      "Field %s contains a mix of "
    2246             :                                      "timezone-aware and local/without "
    2247             :                                      "timezone values.",
    2248             :                                      poFieldDefn->GetNameRef());
    2249           0 :                             m_anTZFlag[i] = OGR_TZFLAG_UNKNOWN;
    2250             :                         }
    2251             :                     }
    2252             :                 }
    2253         189 :                 if (nTZFlag > OGR_TZFLAG_MIXED_TZ)
    2254             :                 {
    2255          60 :                     const int nOffsetSec = (nTZFlag - OGR_TZFLAG_UTC) * 15 * 60;
    2256          60 :                     nVal -= nOffsetSec;
    2257             :                 }
    2258         189 :                 OGR_ARROW_RETURN_OGRERR_NOT_OK(
    2259             :                     static_cast<arrow::TimestampBuilder *>(poBuilder)->Append(
    2260             :                         static_cast<int64_t>(
    2261             :                             (static_cast<double>(nVal) + fSec) * 1000 + 0.5)));
    2262         189 :                 break;
    2263             :             }
    2264             :         }
    2265             :     }
    2266             : 
    2267             :     // Write geometries
    2268        6380 :     for (int i = 0; i < nGeomFieldCount; ++i, ++nArrowIdx)
    2269             :     {
    2270        3243 :         auto poBuilder = m_apoBuilders[nArrowIdx].get();
    2271        3243 :         OGRGeometry *poGeom = poFeature->GetGeomFieldRef(i);
    2272        3243 :         if (BuildGeometry(poGeom, i, poBuilder) != OGRERR_NONE)
    2273           0 :             return OGRERR_FAILURE;
    2274             :     }
    2275             : 
    2276        3137 :     m_nFeatureCount++;
    2277             : 
    2278             :     // Flush the current row group if reaching the limit of rows per group.
    2279        3137 :     if (!m_apoBuilders.empty() && m_apoBuilders[0]->length() == m_nRowGroupSize)
    2280             :     {
    2281          22 :         if (!FlushFeatures())
    2282           0 :             return OGRERR_FAILURE;
    2283             :     }
    2284             : 
    2285        3137 :     return OGRERR_NONE;
    2286             : }
    2287             : 
    2288             : /************************************************************************/
    2289             : /*                         FlushFeatures()                              */
    2290             : /************************************************************************/
    2291             : 
    2292          38 : inline bool OGRArrowWriterLayer::FlushFeatures()
    2293             : {
    2294          38 :     if (m_apoBuilders.empty() || m_apoBuilders[0]->length() == 0)
    2295           0 :         return true;
    2296             : 
    2297          38 :     if (!IsFileWriterCreated())
    2298             :     {
    2299           8 :         CreateWriter();
    2300           8 :         if (!IsFileWriterCreated())
    2301           0 :             return false;
    2302             :     }
    2303             : 
    2304          38 :     return FlushGroup();
    2305             : }
    2306             : 
    2307             : /************************************************************************/
    2308             : /*                        GetFeatureCount()                             */
    2309             : /************************************************************************/
    2310             : 
    2311           1 : inline GIntBig OGRArrowWriterLayer::GetFeatureCount(int bForce)
    2312             : {
    2313           1 :     if (m_poAttrQuery == nullptr && m_poFilterGeom == nullptr)
    2314             :     {
    2315           1 :         return m_nFeatureCount;
    2316             :     }
    2317           0 :     return OGRLayer::GetFeatureCount(bForce);
    2318             : }
    2319             : 
    2320             : /************************************************************************/
    2321             : /*                         TestCapability()                             */
    2322             : /************************************************************************/
    2323             : 
    2324         642 : inline int OGRArrowWriterLayer::TestCapability(const char *pszCap)
    2325             : {
    2326         642 :     if (EQUAL(pszCap, OLCCreateField) || EQUAL(pszCap, OLCCreateGeomField))
    2327          27 :         return m_poSchema == nullptr;
    2328             : 
    2329         615 :     if (EQUAL(pszCap, OLCSequentialWrite))
    2330          24 :         return true;
    2331             : 
    2332         591 :     if (EQUAL(pszCap, OLCFastWriteArrowBatch))
    2333           0 :         return true;
    2334             : 
    2335         591 :     if (EQUAL(pszCap, OLCStringsAsUTF8))
    2336           1 :         return true;
    2337             : 
    2338         590 :     if (EQUAL(pszCap, OLCMeasuredGeometries))
    2339         234 :         return true;
    2340             : 
    2341         356 :     return false;
    2342             : }
    2343             : 
    2344             : /************************************************************************/
    2345             : /*                         WriteArrays()                                */
    2346             : /************************************************************************/
    2347             : 
    2348         273 : inline bool OGRArrowWriterLayer::WriteArrays(
    2349             :     std::function<bool(const std::shared_ptr<arrow::Field> &,
    2350             :                        const std::shared_ptr<arrow::Array> &)>
    2351             :         postProcessArray)
    2352             : {
    2353         273 :     int nArrowIdx = 0;
    2354         273 :     int nArrowIdxFirstField = !m_osFIDColumn.empty() ? 1 : 0;
    2355        1956 :     for (const auto &poBuilder : m_apoBuilders)
    2356             :     {
    2357        1683 :         const auto &field = m_poSchema->fields()[nArrowIdx];
    2358             : 
    2359           0 :         std::shared_ptr<arrow::Array> array;
    2360        1683 :         auto status = poBuilder->Finish(&array);
    2361        1683 :         if (!status.ok())
    2362             :         {
    2363           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2364             :                      "builder::Finish() for field %s failed with %s",
    2365           0 :                      field->name().c_str(), status.message().c_str());
    2366           0 :             return false;
    2367             :         }
    2368             : 
    2369             :         // CPLDebug("ARROW", "%s", array->ToString().c_str());
    2370             : 
    2371        1683 :         const int iCol = nArrowIdx - nArrowIdxFirstField;
    2372        1683 :         if (iCol >= 0 && iCol < m_poFeatureDefn->GetFieldCount())
    2373             :         {
    2374        1368 :             const auto poFieldDefn = m_poFeatureDefn->GetFieldDefn(iCol);
    2375        1368 :             const auto eFieldType = poFieldDefn->GetType();
    2376        1368 :             if (eFieldType == OFTInteger || eFieldType == OFTInteger64)
    2377             :             {
    2378         231 :                 const auto &osDomainName = poFieldDefn->GetDomainName();
    2379             :                 const auto oIter =
    2380         231 :                     m_oMapFieldDomainToStringArray.find(osDomainName);
    2381         231 :                 if (oIter != m_oMapFieldDomainToStringArray.end())
    2382             :                 {
    2383             :                     auto result = arrow::DictionaryArray::FromArrays(
    2384          12 :                         field->type(), array, oIter->second);
    2385          12 :                     if (!result.ok())
    2386             :                     {
    2387           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
    2388             :                                  "DictionaryArray::FromArrays() for field %s "
    2389             :                                  "failed with %s",
    2390           0 :                                  field->name().c_str(),
    2391           0 :                                  result.status().message().c_str());
    2392           0 :                         return false;
    2393             :                     }
    2394          12 :                     array = *result;
    2395             :                 }
    2396             :             }
    2397             :         }
    2398             : 
    2399        1683 :         if (!postProcessArray(field, array))
    2400             :         {
    2401           0 :             return false;
    2402             :         }
    2403             : 
    2404        1683 :         nArrowIdx++;
    2405             :     }
    2406             : 
    2407         273 :     if (m_bWriteBBoxStruct)
    2408             :     {
    2409         183 :         const int nGeomFieldCount = m_poFeatureDefn->GetGeomFieldCount();
    2410         365 :         for (int i = 0; i < nGeomFieldCount; ++i)
    2411             :         {
    2412         182 :             const auto &field = m_apoFieldsBBOX[i];
    2413           0 :             std::shared_ptr<arrow::Array> array;
    2414         182 :             auto status = m_apoBuildersBBOXStruct[i]->Finish(&array);
    2415         182 :             if (!status.ok())
    2416             :             {
    2417           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2418             :                          "builder::Finish() for field %s failed with %s",
    2419           0 :                          field->name().c_str(), status.message().c_str());
    2420           0 :                 return false;
    2421             :             }
    2422             : 
    2423         182 :             if (!postProcessArray(field, array))
    2424             :             {
    2425           0 :                 return false;
    2426             :             }
    2427             :         }
    2428             :     }
    2429             : 
    2430         273 :     return true;
    2431             : }
    2432             : 
    2433             : /************************************************************************/
    2434             : /*                            TestBit()                                 */
    2435             : /************************************************************************/
    2436             : 
    2437         504 : static inline bool TestBit(const uint8_t *pabyData, size_t nIdx)
    2438             : {
    2439         504 :     return (pabyData[nIdx / 8] & (1 << (nIdx % 8))) != 0;
    2440             : }
    2441             : 
    2442             : /************************************************************************/
    2443             : /*                       WriteArrowBatchInternal()                      */
    2444             : /************************************************************************/
    2445             : 
    2446         126 : inline bool OGRArrowWriterLayer::WriteArrowBatchInternal(
    2447             :     const struct ArrowSchema *schema, struct ArrowArray *array,
    2448             :     CSLConstList papszOptions,
    2449             :     std::function<bool(const std::shared_ptr<arrow::RecordBatch> &)> writeBatch)
    2450             : {
    2451             : #ifdef __COVERITY__
    2452             :     (void)schema;
    2453             :     (void)array;
    2454             :     (void)papszOptions;
    2455             :     (void)writeBatch;
    2456             :     CPLError(CE_Failure, CPLE_AppDefined, "Not implemented");
    2457             :     return false;
    2458             : #else
    2459         126 :     if (m_poSchema == nullptr)
    2460             :     {
    2461         121 :         CreateSchema();
    2462             :     }
    2463             : 
    2464         126 :     if (!IsFileWriterCreated())
    2465             :     {
    2466         121 :         CreateWriter();
    2467         121 :         if (!IsFileWriterCreated())
    2468           0 :             return false;
    2469             :     }
    2470             : 
    2471         126 :     if (m_apoBuilders.empty())
    2472             :     {
    2473         121 :         CreateArrayBuilders();
    2474             :     }
    2475             : 
    2476         126 :     const int nGeomFieldCount = m_poFeatureDefn->GetGeomFieldCount();
    2477         126 :     const int nGeomFieldCountBBoxFields =
    2478         126 :         m_bWriteBBoxStruct ? nGeomFieldCount : 0;
    2479             : 
    2480         126 :     const char *pszFIDName = CSLFetchNameValueDef(
    2481             :         papszOptions, "FID", OGRLayer::DEFAULT_ARROW_FID_NAME);
    2482             :     const char *pszSingleGeomFieldName =
    2483         126 :         CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
    2484             : 
    2485             :     // Sort schema and array children in the same order as m_poSchema.
    2486             :     // This is needed for non-WKB geometry encoding
    2487         252 :     std::map<std::string, int> oMapSchemaChildrenNameToIdx;
    2488        1644 :     for (int i = 0; i < static_cast<int>(schema->n_children); ++i)
    2489             :     {
    2490        1518 :         if (cpl::contains(oMapSchemaChildrenNameToIdx,
    2491        1518 :                           schema->children[i]->name))
    2492             :         {
    2493           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2494             :                      "Several fields with same name '%s' found",
    2495           0 :                      schema->children[i]->name);
    2496           0 :             return false;
    2497             :         }
    2498        1518 :         oMapSchemaChildrenNameToIdx[schema->children[i]->name] = i;
    2499             : 
    2500        1518 :         if (!pszSingleGeomFieldName && schema->children[i]->metadata)
    2501             :         {
    2502             :             const auto oMetadata =
    2503         256 :                 OGRParseArrowMetadata(schema->children[i]->metadata);
    2504         128 :             const auto oIter = oMetadata.find(ARROW_EXTENSION_NAME_KEY);
    2505         262 :             if (oIter != oMetadata.end() &&
    2506         134 :                 (oIter->second == EXTENSION_NAME_OGC_WKB ||
    2507           8 :                  oIter->second == EXTENSION_NAME_GEOARROW_WKB))
    2508             :             {
    2509         126 :                 pszSingleGeomFieldName = schema->children[i]->name;
    2510             :             }
    2511             :         }
    2512             :     }
    2513         126 :     if (!pszSingleGeomFieldName)
    2514           0 :         pszSingleGeomFieldName = OGRLayer::DEFAULT_ARROW_GEOMETRY_NAME;
    2515             : 
    2516         126 :     std::vector<int> anMapLayerSchemaToArraySchema(m_poSchema->num_fields(),
    2517         378 :                                                    -1);
    2518             :     struct ArrowArray fidArray;
    2519             :     struct ArrowSchema fidSchema;
    2520         126 :     memset(&fidArray, 0, sizeof(fidArray));
    2521         126 :     memset(&fidSchema, 0, sizeof(fidSchema));
    2522         252 :     std::vector<void *> apBuffersFid;
    2523         252 :     std::vector<int64_t> fids;
    2524             : 
    2525         252 :     std::set<int> oSetReferencedFieldsInArraySchema;
    2526           0 :     const auto DummyFreeArray = [](struct ArrowArray *ptrArray)
    2527           0 :     { ptrArray->release = nullptr; };
    2528         126 :     const auto DummyFreeSchema = [](struct ArrowSchema *ptrSchema)
    2529         126 :     { ptrSchema->release = nullptr; };
    2530         126 :     bool bRebuildBatch = false;
    2531        1642 :     for (int i = 0; i < m_poSchema->num_fields() - nGeomFieldCountBBoxFields;
    2532             :          ++i)
    2533             :     {
    2534             :         auto oIter =
    2535        1516 :             oMapSchemaChildrenNameToIdx.find(m_poSchema->field(i)->name());
    2536        1516 :         if (oIter == oMapSchemaChildrenNameToIdx.end())
    2537             :         {
    2538           4 :             if (m_poSchema->field(i)->name() == m_osFIDColumn)
    2539             :             {
    2540           1 :                 oIter = oMapSchemaChildrenNameToIdx.find(pszFIDName);
    2541           1 :                 if (oIter == oMapSchemaChildrenNameToIdx.end())
    2542             :                 {
    2543             :                     // If the input data does not contain a FID column, but
    2544             :                     // the output file requires it, creates a default FID column
    2545           0 :                     fidArray.release = DummyFreeArray;
    2546           0 :                     fidArray.n_buffers = 2;
    2547           0 :                     apBuffersFid.resize(2);
    2548           0 :                     fidArray.buffers =
    2549           0 :                         const_cast<const void **>(apBuffersFid.data());
    2550           0 :                     fids.reserve(static_cast<size_t>(array->length));
    2551           0 :                     for (size_t iRow = 0;
    2552           0 :                          iRow < static_cast<size_t>(array->length); ++iRow)
    2553           0 :                         fids.push_back(m_nFeatureCount + iRow);
    2554           0 :                     fidArray.buffers[1] = fids.data();
    2555           0 :                     fidArray.length = array->length;
    2556           0 :                     fidSchema.release = DummyFreeSchema;
    2557           0 :                     fidSchema.name = m_osFIDColumn.c_str();
    2558           0 :                     fidSchema.format = "l";  // int64
    2559           0 :                     continue;
    2560             :                 }
    2561             :             }
    2562           6 :             else if (nGeomFieldCount == 1 &&
    2563           3 :                      m_poFeatureDefn->GetGeomFieldIndex(
    2564           3 :                          m_poSchema->field(i)->name().c_str()) == 0)
    2565             :             {
    2566             :                 oIter =
    2567           3 :                     oMapSchemaChildrenNameToIdx.find(pszSingleGeomFieldName);
    2568           3 :                 if (oIter != oMapSchemaChildrenNameToIdx.end())
    2569           3 :                     bRebuildBatch = true;
    2570             :             }
    2571             : 
    2572           4 :             if (oIter == oMapSchemaChildrenNameToIdx.end())
    2573             :             {
    2574           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2575             :                          "Cannot find field '%s' in schema",
    2576           0 :                          m_poSchema->field(i)->name().c_str());
    2577           0 :                 return false;
    2578             :             }
    2579             :         }
    2580        1516 :         anMapLayerSchemaToArraySchema[i] = oIter->second;
    2581        1516 :         oSetReferencedFieldsInArraySchema.insert(oIter->second);
    2582             :     }
    2583             : 
    2584             :     // Note: we cheat a bit by declaring a single instance of the minx/miny/
    2585             :     // maxx/maxy sub-field ArrowSchema*, and make all struct ArrowSchema point
    2586             :     // to them. That's OK because we use DummyFreeSchema to release, which does
    2587             :     // nothing.
    2588             :     struct ArrowSchema bboxStructSchemaXMin;
    2589             :     struct ArrowSchema bboxStructSchemaYMin;
    2590             :     struct ArrowSchema bboxStructSchemaXMax;
    2591             :     struct ArrowSchema bboxStructSchemaYMax;
    2592         126 :     constexpr int BBOX_SUBFIELD_COUNT = 4;
    2593             :     std::array<struct ArrowSchema *, BBOX_SUBFIELD_COUNT>
    2594             :         bboxStructSchemaChildren;
    2595         126 :     constexpr int BBOX_STRUCT_BUFFER_COUNT = 1;  // validity bitmap array
    2596             :     // cppcheck-suppress constStatement
    2597             :     std::vector<std::array<const void *, BBOX_STRUCT_BUFFER_COUNT>>
    2598         252 :         bboxStructBuffersPtr;
    2599         252 :     std::vector<std::vector<GByte>> aabyBboxStructValidity;
    2600         252 :     std::vector<std::vector<float>> aadfMinX, aadfMinY, aadfMaxX, aadfMaxY;
    2601             :     // cppcheck-suppress constStatement
    2602         252 :     std::vector<std::array<struct ArrowArray, BBOX_SUBFIELD_COUNT>> bboxArrays;
    2603             :     // cppcheck-suppress constStatement
    2604             :     std::vector<std::array<struct ArrowArray *, BBOX_SUBFIELD_COUNT>>
    2605         252 :         bboxArraysPtr;
    2606         126 :     constexpr int BBOX_SUBFIELD_BUFFER_COUNT =
    2607             :         2;  // validity bitmap array and float array
    2608             :     std::vector<std::array<std::array<const void *, BBOX_SUBFIELD_BUFFER_COUNT>,
    2609             :                            BBOX_SUBFIELD_COUNT>>
    2610         252 :         bboxBuffersPtr;
    2611             : 
    2612             :     // Temporary arrays to hold the geometry bounding boxes.
    2613         252 :     std::vector<struct ArrowArray> bboxStructArray;
    2614         252 :     std::vector<struct ArrowSchema> bboxStructSchema;
    2615             : 
    2616         252 :     std::vector<struct ArrowSchema *> newSchemaChildren;
    2617         252 :     std::vector<struct ArrowArray *> newArrayChildren;
    2618         126 :     newSchemaChildren.reserve(m_poSchema->num_fields());
    2619         126 :     newArrayChildren.reserve(m_poSchema->num_fields());
    2620        1642 :     for (int i = 0; i < m_poSchema->num_fields() - nGeomFieldCountBBoxFields;
    2621             :          ++i)
    2622             :     {
    2623        1516 :         if (anMapLayerSchemaToArraySchema[i] < 0)
    2624             :         {
    2625           0 :             CPLAssert(m_poSchema->field(i)->name() == m_osFIDColumn);
    2626           0 :             newSchemaChildren.emplace_back(&fidSchema);
    2627           0 :             newArrayChildren.emplace_back(&fidArray);
    2628             :         }
    2629             :         else
    2630             :         {
    2631             :             newSchemaChildren.emplace_back(
    2632        1516 :                 schema->children[anMapLayerSchemaToArraySchema[i]]);
    2633             :             newArrayChildren.emplace_back(
    2634        1516 :                 array->children[anMapLayerSchemaToArraySchema[i]]);
    2635             :         }
    2636             :     }
    2637             : 
    2638         126 :     if (m_bWriteBBoxStruct)
    2639             :     {
    2640          14 :         memset(&bboxStructSchemaXMin, 0, sizeof(bboxStructSchemaXMin));
    2641          14 :         memset(&bboxStructSchemaYMin, 0, sizeof(bboxStructSchemaYMin));
    2642          14 :         memset(&bboxStructSchemaXMax, 0, sizeof(bboxStructSchemaXMax));
    2643          14 :         memset(&bboxStructSchemaYMax, 0, sizeof(bboxStructSchemaYMax));
    2644             : 
    2645          14 :         bboxStructSchemaXMin.release = DummyFreeSchema;
    2646          14 :         bboxStructSchemaXMin.name = "xmin";
    2647          14 :         bboxStructSchemaXMin.format = "f";  // float32
    2648             : 
    2649          14 :         bboxStructSchemaYMin.release = DummyFreeSchema;
    2650          14 :         bboxStructSchemaYMin.name = "ymin";
    2651          14 :         bboxStructSchemaYMin.format = "f";  // float32
    2652             : 
    2653          14 :         bboxStructSchemaXMax.release = DummyFreeSchema;
    2654          14 :         bboxStructSchemaXMax.name = "xmax";
    2655          14 :         bboxStructSchemaXMax.format = "f";  // float32
    2656             : 
    2657          14 :         bboxStructSchemaYMax.release = DummyFreeSchema;
    2658          14 :         bboxStructSchemaYMax.name = "ymax";
    2659          14 :         bboxStructSchemaYMax.format = "f";  // float32
    2660             : 
    2661             :         try
    2662             :         {
    2663          14 :             constexpr int XMIN_IDX = 0;
    2664          14 :             constexpr int YMIN_IDX = 1;
    2665          14 :             constexpr int XMAX_IDX = 2;
    2666          14 :             constexpr int YMAX_IDX = 3;
    2667          14 :             bboxStructSchemaChildren[XMIN_IDX] = &bboxStructSchemaXMin;
    2668             :             // cppcheck-suppress objectIndex
    2669          14 :             bboxStructSchemaChildren[YMIN_IDX] = &bboxStructSchemaYMin;
    2670             :             // cppcheck-suppress objectIndex
    2671          14 :             bboxStructSchemaChildren[XMAX_IDX] = &bboxStructSchemaXMax;
    2672             :             // cppcheck-suppress objectIndex
    2673          14 :             bboxStructSchemaChildren[YMAX_IDX] = &bboxStructSchemaYMax;
    2674             : 
    2675          14 :             bboxStructArray.resize(nGeomFieldCount);
    2676          14 :             bboxStructSchema.resize(nGeomFieldCount);
    2677          14 :             bboxArrays.resize(nGeomFieldCount);
    2678          14 :             bboxArraysPtr.resize(nGeomFieldCount);
    2679          14 :             bboxBuffersPtr.resize(nGeomFieldCount);
    2680          14 :             bboxStructBuffersPtr.resize(nGeomFieldCount);
    2681          14 :             aabyBboxStructValidity.resize(nGeomFieldCount);
    2682          28 :             memset(bboxStructArray.data(), 0,
    2683          14 :                    nGeomFieldCount * sizeof(bboxStructArray[0]));
    2684          28 :             memset(bboxStructSchema.data(), 0,
    2685          14 :                    nGeomFieldCount * sizeof(bboxStructSchema[0]));
    2686          28 :             memset(bboxArrays.data(), 0,
    2687          14 :                    nGeomFieldCount * sizeof(bboxArrays[0]));
    2688          14 :             aadfMinX.resize(nGeomFieldCount);
    2689          14 :             aadfMinY.resize(nGeomFieldCount);
    2690          14 :             aadfMaxX.resize(nGeomFieldCount);
    2691          14 :             aadfMaxY.resize(nGeomFieldCount);
    2692          28 :             for (int i = 0; i < nGeomFieldCount; ++i)
    2693             :             {
    2694          14 :                 const bool bIsNullable = CPL_TO_BOOL(
    2695          14 :                     m_poFeatureDefn->GetGeomFieldDefn(i)->IsNullable());
    2696          14 :                 aadfMinX[i].reserve(static_cast<size_t>(array->length));
    2697          14 :                 aadfMinY[i].reserve(static_cast<size_t>(array->length));
    2698          14 :                 aadfMaxX[i].reserve(static_cast<size_t>(array->length));
    2699          14 :                 aadfMaxY[i].reserve(static_cast<size_t>(array->length));
    2700          14 :                 aabyBboxStructValidity[i].resize(
    2701          14 :                     static_cast<size_t>(array->length + 7) / 8, 0xFF);
    2702             : 
    2703          14 :                 bboxStructSchema[i].release = DummyFreeSchema;
    2704          14 :                 bboxStructSchema[i].name = m_apoFieldsBBOX[i]->name().c_str();
    2705          14 :                 bboxStructSchema[i].format = "+s";  // structure
    2706          14 :                 bboxStructSchema[i].flags =
    2707          14 :                     bIsNullable ? ARROW_FLAG_NULLABLE : 0;
    2708          14 :                 bboxStructSchema[i].n_children = BBOX_SUBFIELD_COUNT;
    2709          14 :                 bboxStructSchema[i].children = bboxStructSchemaChildren.data();
    2710             : 
    2711          14 :                 constexpr int VALIDITY_ARRAY_IDX = 0;
    2712          14 :                 constexpr int BBOX_SUBFIELD_FLOAT_VALUE_IDX = 1;
    2713          14 :                 bboxBuffersPtr[i][XMIN_IDX][BBOX_SUBFIELD_FLOAT_VALUE_IDX] =
    2714          14 :                     aadfMinX[i].data();
    2715          14 :                 bboxBuffersPtr[i][YMIN_IDX][BBOX_SUBFIELD_FLOAT_VALUE_IDX] =
    2716          14 :                     aadfMinY[i].data();
    2717          14 :                 bboxBuffersPtr[i][XMAX_IDX][BBOX_SUBFIELD_FLOAT_VALUE_IDX] =
    2718          14 :                     aadfMaxX[i].data();
    2719          14 :                 bboxBuffersPtr[i][YMAX_IDX][BBOX_SUBFIELD_FLOAT_VALUE_IDX] =
    2720          14 :                     aadfMaxY[i].data();
    2721             : 
    2722          70 :                 for (int j = 0; j < BBOX_SUBFIELD_COUNT; ++j)
    2723             :                 {
    2724          56 :                     bboxBuffersPtr[i][j][VALIDITY_ARRAY_IDX] = nullptr;
    2725             : 
    2726          56 :                     bboxArrays[i][j].release = DummyFreeArray;
    2727          56 :                     bboxArrays[i][j].length = array->length;
    2728          56 :                     bboxArrays[i][j].n_buffers = BBOX_SUBFIELD_BUFFER_COUNT;
    2729          56 :                     bboxArrays[i][j].buffers = bboxBuffersPtr[i][j].data();
    2730             : 
    2731          56 :                     bboxArraysPtr[i][j] = &bboxArrays[i][j];
    2732             :                 }
    2733             : 
    2734          14 :                 bboxStructArray[i].release = DummyFreeArray;
    2735          14 :                 bboxStructArray[i].n_children = BBOX_SUBFIELD_COUNT;
    2736             :                 // coverity[escape]
    2737          14 :                 bboxStructArray[i].children = bboxArraysPtr[i].data();
    2738          14 :                 bboxStructArray[i].length = array->length;
    2739          14 :                 bboxStructArray[i].n_buffers = BBOX_STRUCT_BUFFER_COUNT;
    2740          14 :                 bboxStructBuffersPtr[i][VALIDITY_ARRAY_IDX] =
    2741          14 :                     bIsNullable ? aabyBboxStructValidity[i].data() : nullptr;
    2742             :                 // coverity[escape]
    2743          14 :                 bboxStructArray[i].buffers = bboxStructBuffersPtr[i].data();
    2744             : 
    2745          14 :                 newSchemaChildren.emplace_back(&bboxStructSchema[i]);
    2746          14 :                 newArrayChildren.emplace_back(&bboxStructArray[i]);
    2747             :             }
    2748             :         }
    2749           0 :         catch (const std::bad_alloc &)
    2750             :         {
    2751           0 :             CPLError(CE_Failure, CPLE_OutOfMemory,
    2752             :                      "Out of memory in "
    2753             :                      "OGRArrowWriterLayer::WriteArrowBatchInternal()");
    2754           0 :             return false;
    2755             :         }
    2756             :     }
    2757             : 
    2758        1644 :     for (int i = 0; i < static_cast<int>(schema->n_children); ++i)
    2759             :     {
    2760        1518 :         if (!cpl::contains(oSetReferencedFieldsInArraySchema, i))
    2761             :         {
    2762           4 :             if (m_osFIDColumn.empty() &&
    2763           2 :                 strcmp(schema->children[i]->name, pszFIDName) == 0)
    2764             :             {
    2765             :                 // If the input data contains a FID column, but the output data
    2766             :                 // does not, then ignore it.
    2767             :             }
    2768             :             else
    2769             :             {
    2770           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2771             :                          "Found field '%s' in array schema that does not exist "
    2772             :                          "in layer schema",
    2773           0 :                          schema->children[i]->name);
    2774           0 :                 return false;
    2775             :             }
    2776             :         }
    2777             :     }
    2778             : 
    2779             :     // ImportSchema() would release the schema, but we don't want that
    2780             :     // So copy the structure content into a local variable, and override its
    2781             :     // release callback to a no-op. This may be a bit fragile, but it doesn't
    2782             :     // look like ImportSchema implementation tries to access the C ArrowSchema
    2783             :     // after it has been called.
    2784         126 :     struct ArrowSchema lSchema = *schema;
    2785         126 :     schema = &lSchema;
    2786         126 :     CPL_IGNORE_RET_VAL(schema);
    2787             : 
    2788         126 :     lSchema.n_children = newSchemaChildren.size();
    2789         126 :     lSchema.children = newSchemaChildren.data();
    2790             : 
    2791         126 :     lSchema.release = DummyFreeSchema;
    2792         252 :     auto poSchemaResult = arrow::ImportSchema(&lSchema);
    2793         126 :     CPLAssert(lSchema.release == nullptr);
    2794         126 :     if (!poSchemaResult.ok())
    2795             :     {
    2796           0 :         CPLError(CE_Failure, CPLE_AppDefined, "ImportSchema() failed with %s",
    2797           0 :                  poSchemaResult.status().message().c_str());
    2798           0 :         return false;
    2799             :     }
    2800         252 :     auto poSchema = *poSchemaResult;
    2801             : 
    2802             :     // Hack the array to use the new children we've computed above
    2803             :     // but make sure the original release() callback sees the original children
    2804             :     struct ArrayReleaser
    2805             :     {
    2806             :         struct ArrowArray ori_array
    2807             :         {
    2808             :         };
    2809             : 
    2810         126 :         explicit ArrayReleaser(struct ArrowArray *array)
    2811         126 :         {
    2812         126 :             memcpy(&ori_array, array, sizeof(*array));
    2813         126 :             array->release = ArrayReleaser::release;
    2814         126 :             array->private_data = this;
    2815         126 :         }
    2816             : 
    2817         126 :         static void release(struct ArrowArray *array)
    2818             :         {
    2819         126 :             struct ArrayReleaser *releaser =
    2820             :                 static_cast<struct ArrayReleaser *>(array->private_data);
    2821         126 :             memcpy(array, &(releaser->ori_array), sizeof(*array));
    2822         126 :             CPLAssert(array->release != nullptr);
    2823         126 :             array->release(array);
    2824         126 :             CPLAssert(array->release == nullptr);
    2825         126 :             delete releaser;
    2826         126 :         }
    2827             :     };
    2828             : 
    2829             :     // Must be allocated on the heap, since ArrayReleaser::release() will be
    2830             :     // called after this method has ended.
    2831         126 :     ArrayReleaser *releaser = new ArrayReleaser(array);
    2832         126 :     array->private_data = releaser;
    2833         126 :     array->n_children = newArrayChildren.size();
    2834             :     // cppcheck-suppress autoVariables
    2835         126 :     array->children = newArrayChildren.data();
    2836             : 
    2837             :     // Process geometry columns:
    2838             :     // - if the output encoding is WKB, then just note the geometry type and
    2839             :     //   envelope.
    2840             :     // - otherwise convert to the output encoding.
    2841         126 :     int nBuilderIdx = 0;
    2842         126 :     if (!m_osFIDColumn.empty())
    2843             :     {
    2844           2 :         nBuilderIdx++;
    2845             :     }
    2846             :     std::map<std::string, std::shared_ptr<arrow::Array>>
    2847         252 :         oMapGeomFieldNameToArray;
    2848         252 :     for (int i = 0; i < nGeomFieldCount; ++i, ++nBuilderIdx)
    2849             :     {
    2850             :         const char *pszThisGeomFieldName =
    2851         126 :             m_poFeatureDefn->GetGeomFieldDefn(i)->GetNameRef();
    2852         126 :         int nIdx = poSchema->GetFieldIndex(pszThisGeomFieldName);
    2853         126 :         if (nIdx < 0)
    2854             :         {
    2855           3 :             if (nGeomFieldCount == 1)
    2856           3 :                 nIdx = poSchema->GetFieldIndex(pszSingleGeomFieldName);
    2857           3 :             if (nIdx < 0)
    2858             :             {
    2859           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2860             :                          "Cannot find geometry field '%s' in schema",
    2861             :                          pszThisGeomFieldName);
    2862           0 :                 return false;
    2863             :             }
    2864             :         }
    2865             : 
    2866         126 :         if (strcmp(lSchema.children[nIdx]->format, "z") != 0 &&
    2867           1 :             strcmp(lSchema.children[nIdx]->format, "Z") != 0)
    2868             :         {
    2869           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2870             :                      "Type of geometry field '%s' is not binary, but '%s'",
    2871           0 :                      pszThisGeomFieldName, lSchema.children[nIdx]->format);
    2872           0 :             return false;
    2873             :         }
    2874             : 
    2875         126 :         const auto psGeomArray = array->children[nIdx];
    2876         126 :         const uint8_t *pabyValidity =
    2877         126 :             psGeomArray->null_count != 0
    2878         126 :                 ? static_cast<const uint8_t *>(psGeomArray->buffers[0])
    2879             :                 : nullptr;
    2880         126 :         const bool bUseOffsets32 =
    2881         126 :             (strcmp(lSchema.children[nIdx]->format, "z") == 0);
    2882         126 :         const uint32_t *panOffsets32 =
    2883         126 :             static_cast<const uint32_t *>(psGeomArray->buffers[1]) +
    2884         126 :             psGeomArray->offset;
    2885         126 :         const uint64_t *panOffsets64 =
    2886         126 :             static_cast<const uint64_t *>(psGeomArray->buffers[1]) +
    2887         126 :             psGeomArray->offset;
    2888         126 :         GByte *pabyData =
    2889         126 :             static_cast<GByte *>(const_cast<void *>(psGeomArray->buffers[2]));
    2890         126 :         OGREnvelope sEnvelope;
    2891         126 :         auto poBuilder = m_apoBuilders[nBuilderIdx].get();
    2892             : 
    2893         681 :         for (size_t iRow = 0; iRow < static_cast<size_t>(psGeomArray->length);
    2894             :              ++iRow)
    2895             :         {
    2896         555 :             bool bValidGeom = false;
    2897             : 
    2898        1059 :             if (!pabyValidity ||
    2899         504 :                 TestBit(pabyValidity,
    2900         504 :                         static_cast<size_t>(iRow + psGeomArray->offset)))
    2901             :             {
    2902         439 :                 const auto nLen =
    2903         439 :                     bUseOffsets32 ? static_cast<size_t>(panOffsets32[iRow + 1] -
    2904         429 :                                                         panOffsets32[iRow])
    2905          10 :                                   : static_cast<size_t>(panOffsets64[iRow + 1] -
    2906          10 :                                                         panOffsets64[iRow]);
    2907         439 :                 GByte *pabyWkb =
    2908         439 :                     pabyData + (bUseOffsets32
    2909         429 :                                     ? panOffsets32[iRow]
    2910          10 :                                     : static_cast<size_t>(panOffsets64[iRow]));
    2911         439 :                 if (m_aeGeomEncoding[i] == OGRArrowGeomEncoding::WKB)
    2912             :                 {
    2913         171 :                     FixupWKBGeometryBeforeWriting(pabyWkb, nLen);
    2914             : 
    2915         171 :                     uint32_t nType = 0;
    2916         171 :                     bool bNeedSwap = false;
    2917         171 :                     if (OGRWKBGetGeomType(pabyWkb, nLen, bNeedSwap, nType))
    2918             :                     {
    2919         171 :                         m_oSetWrittenGeometryTypes[i].insert(
    2920         171 :                             static_cast<OGRwkbGeometryType>(nType));
    2921         171 :                         if (OGRWKBGetBoundingBox(pabyWkb, nLen, sEnvelope))
    2922             :                         {
    2923         171 :                             bValidGeom = true;
    2924         171 :                             m_aoEnvelopes[i].Merge(sEnvelope);
    2925             : 
    2926         171 :                             if (m_bWriteBBoxStruct)
    2927             :                             {
    2928          43 :                                 aadfMinX[i].push_back(
    2929          43 :                                     castToFloatDown(sEnvelope.MinX));
    2930          43 :                                 aadfMinY[i].push_back(
    2931          43 :                                     castToFloatDown(sEnvelope.MinY));
    2932          43 :                                 aadfMaxX[i].push_back(
    2933          43 :                                     castToFloatUp(sEnvelope.MaxX));
    2934          43 :                                 aadfMaxY[i].push_back(
    2935          43 :                                     castToFloatUp(sEnvelope.MaxY));
    2936             :                             }
    2937             :                         }
    2938             :                     }
    2939             :                 }
    2940             :                 else
    2941             :                 {
    2942         268 :                     size_t nBytesConsumedOut = 0;
    2943         268 :                     OGRGeometry *poGeometry = nullptr;
    2944         268 :                     OGRGeometryFactory::createFromWkb(
    2945             :                         pabyWkb, nullptr, &poGeometry, nLen, wkbVariantIso,
    2946             :                         nBytesConsumedOut);
    2947         268 :                     if (BuildGeometry(poGeometry, i, poBuilder) != OGRERR_NONE)
    2948             :                     {
    2949           0 :                         delete poGeometry;
    2950           0 :                         return false;
    2951             :                     }
    2952         268 :                     bValidGeom = true;
    2953         268 :                     if (m_bWriteBBoxStruct)
    2954             :                     {
    2955           0 :                         poGeometry->getEnvelope(&sEnvelope);
    2956           0 :                         aadfMinX[i].push_back(castToFloatDown(sEnvelope.MinX));
    2957           0 :                         aadfMinY[i].push_back(castToFloatDown(sEnvelope.MinY));
    2958           0 :                         aadfMaxX[i].push_back(castToFloatUp(sEnvelope.MaxX));
    2959           0 :                         aadfMaxY[i].push_back(castToFloatUp(sEnvelope.MaxY));
    2960             :                     }
    2961         268 :                     delete poGeometry;
    2962             :                 }
    2963             :             }
    2964             :             else
    2965             :             {
    2966         116 :                 if (m_aeGeomEncoding[i] != OGRArrowGeomEncoding::WKB)
    2967             :                 {
    2968          81 :                     if (BuildGeometry(nullptr, i, poBuilder) != OGRERR_NONE)
    2969           0 :                         return false;
    2970             :                 }
    2971             :             }
    2972             : 
    2973         555 :             if (!bValidGeom && m_bWriteBBoxStruct)
    2974             :             {
    2975           6 :                 if ((bboxStructSchema[i].flags & ARROW_FLAG_NULLABLE))
    2976             :                 {
    2977           6 :                     bboxStructArray[i].null_count++;
    2978           6 :                     aabyBboxStructValidity[i][iRow / 8] &=
    2979           6 :                         ~(1 << static_cast<int>(iRow % 8));
    2980             :                 }
    2981           6 :                 aadfMinX[i].push_back(0.0f);
    2982           6 :                 aadfMinY[i].push_back(0.0f);
    2983           6 :                 aadfMaxX[i].push_back(0.0f);
    2984           6 :                 aadfMaxY[i].push_back(0.0f);
    2985             :             }
    2986             :         }
    2987             : 
    2988         126 :         if (m_aeGeomEncoding[i] != OGRArrowGeomEncoding::WKB)
    2989             :         {
    2990           0 :             std::shared_ptr<arrow::Array> geomArray;
    2991          81 :             auto status = poBuilder->Finish(&geomArray);
    2992          81 :             if (!status.ok())
    2993             :             {
    2994           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2995             :                          "builder::Finish() for field %s failed with %s",
    2996           0 :                          pszThisGeomFieldName, status.message().c_str());
    2997           0 :                 return false;
    2998             :             }
    2999         162 :             oMapGeomFieldNameToArray[pszThisGeomFieldName] =
    3000         162 :                 std::move(geomArray);
    3001             :         }
    3002             :     }
    3003             : 
    3004             :     auto poRecordBatchResult =
    3005         252 :         arrow::ImportRecordBatch(array, std::move(poSchema));
    3006         126 :     if (!poRecordBatchResult.ok())
    3007             :     {
    3008           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3009             :                  "ImportRecordBatch() failed with %s",
    3010           0 :                  poRecordBatchResult.status().message().c_str());
    3011           0 :         return false;
    3012             :     }
    3013         252 :     auto poRecordBatch = *poRecordBatchResult;
    3014             : 
    3015         126 :     if (!(bRebuildBatch || !oMapGeomFieldNameToArray.empty()))
    3016             :     {
    3017         994 :         for (int i = 0; i < m_poSchema->num_fields(); ++i)
    3018             :         {
    3019             :             const auto oIter =
    3020         952 :                 oMapGeomFieldNameToArray.find(m_poSchema->field(i)->name());
    3021         952 :             auto l_array = (oIter != oMapGeomFieldNameToArray.end())
    3022           0 :                                ? oIter->second
    3023        1904 :                                : poRecordBatch->column(i);
    3024        1904 :             const auto schemaType = m_poSchema->field(i)->type();
    3025        1904 :             const auto arrayType = l_array->type();
    3026        1904 :             if (schemaType->id() != arrow::Type::EXTENSION &&
    3027         952 :                 arrayType->id() == arrow::Type::EXTENSION)
    3028             :             {
    3029           0 :                 bRebuildBatch = true;
    3030             :             }
    3031         952 :             else if (schemaType->id() != arrayType->id())
    3032             :             {
    3033           3 :                 CPLDebug(
    3034             :                     "Arrow",
    3035             :                     "Field idx=%d name='%s', schema type=%s, array type=%s", i,
    3036           1 :                     m_poSchema->field(i)->name().c_str(),
    3037           2 :                     schemaType->ToString().c_str(),
    3038           2 :                     arrayType->ToString().c_str());
    3039             :             }
    3040             :         }
    3041             :     }
    3042             : 
    3043             :     // below assertion commented out since it is not strictly necessary, but
    3044             :     // reflects what ImportRecordBatch() does.
    3045             :     // CPLAssert(array->release == nullptr);
    3046             : 
    3047             :     // We may need to reconstruct a final record batch that perfectly matches
    3048             :     // the expected schema.
    3049         126 :     if (bRebuildBatch || !oMapGeomFieldNameToArray.empty())
    3050             :     {
    3051          84 :         std::vector<std::shared_ptr<arrow::Array>> apoArrays;
    3052         662 :         for (int i = 0; i < m_poSchema->num_fields(); ++i)
    3053             :         {
    3054             :             const auto oIter =
    3055         578 :                 oMapGeomFieldNameToArray.find(m_poSchema->field(i)->name());
    3056         578 :             if (oIter != oMapGeomFieldNameToArray.end())
    3057          81 :                 apoArrays.emplace_back(oIter->second);
    3058             :             else
    3059         497 :                 apoArrays.emplace_back(poRecordBatch->column(i));
    3060             : 
    3061         578 :             auto expectedFieldType = m_poSchema->field(i)->type();
    3062         578 :             if (expectedFieldType->id() == arrow::Type::EXTENSION)
    3063             :             {
    3064           0 :                 auto extensionType = cpl::down_cast<arrow::ExtensionType *>(
    3065             :                     expectedFieldType.get());
    3066           0 :                 expectedFieldType = extensionType->storage_type();
    3067             :             }
    3068             : 
    3069         578 :             if (apoArrays.back()->type()->id() == arrow::Type::EXTENSION)
    3070             :             {
    3071           0 :                 apoArrays.back() =
    3072           0 :                     std::static_pointer_cast<arrow::ExtensionArray>(
    3073           0 :                         apoArrays.back())
    3074           0 :                         ->storage();
    3075             :             }
    3076             : 
    3077         578 :             if (apoArrays.back()->type()->id() != expectedFieldType->id())
    3078             :             {
    3079           0 :                 CPLError(
    3080             :                     CE_Failure, CPLE_AppDefined,
    3081             :                     "Field '%s' of unexpected type. Got '%s', expected '%s'",
    3082           0 :                     m_poSchema->field(i)->name().c_str(),
    3083           0 :                     apoArrays.back()->type()->name().c_str(),
    3084           0 :                     expectedFieldType->name().c_str());
    3085           0 :                 return false;
    3086             :             }
    3087             :         }
    3088         336 :         poRecordBatchResult = arrow::RecordBatch::Make(
    3089         252 :             m_poSchema, poRecordBatch->num_rows(), std::move(apoArrays));
    3090          84 :         if (!poRecordBatchResult.ok())
    3091             :         {
    3092           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3093             :                      "RecordBatch::Make() failed with %s",
    3094           0 :                      poRecordBatchResult.status().message().c_str());
    3095           0 :             return false;
    3096             :         }
    3097          84 :         poRecordBatch = *poRecordBatchResult;
    3098             :     }
    3099             : 
    3100         126 :     if (writeBatch(poRecordBatch))
    3101             :     {
    3102         126 :         m_nFeatureCount += poRecordBatch->num_rows();
    3103         126 :         return true;
    3104             :     }
    3105           0 :     return false;
    3106             : #endif
    3107             : }
    3108             : 
    3109             : #endif /* OGARROWWRITERLAYER_HPP_INCLUDED */

Generated by: LCOV version 1.14