LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/parquet - ogrparquetlayer.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1324 1427 92.8 %
Date: 2025-12-11 01:36:05 Functions: 57 57 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  Parquet Translator
       4             :  * Purpose:  Implements OGRParquetDriver.
       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             : #include "cpl_json.h"
      14             : #include "cpl_time.h"
      15             : #include "cpl_multiproc.h"
      16             : #include "gdal_pam.h"
      17             : #include "ogrsf_frmts.h"
      18             : #include "ogr_p.h"
      19             : 
      20             : #include <algorithm>
      21             : #include <cinttypes>
      22             : #include <cmath>
      23             : #include <limits>
      24             : #include <map>
      25             : #include <set>
      26             : #include <utility>
      27             : 
      28             : #include "ogr_parquet.h"
      29             : 
      30             : #include "../arrow_common/ograrrowlayer.hpp"
      31             : #include "../arrow_common/ograrrowdataset.hpp"
      32             : 
      33             : /************************************************************************/
      34             : /*                    OGRParquetLayerBase()                             */
      35             : /************************************************************************/
      36             : 
      37        1402 : OGRParquetLayerBase::OGRParquetLayerBase(OGRParquetDataset *poDS,
      38             :                                          const char *pszLayerName,
      39        1402 :                                          CSLConstList papszOpenOptions)
      40             :     : OGRArrowLayer(poDS, pszLayerName,
      41        1402 :                     CPLTestBool(CSLFetchNameValueDef(
      42             :                         papszOpenOptions, "LISTS_AS_STRING_JSON", "NO"))),
      43             :       m_poDS(poDS),
      44             :       m_aosGeomPossibleNames(CSLTokenizeString2(
      45             :           CSLFetchNameValueDef(papszOpenOptions, "GEOM_POSSIBLE_NAMES",
      46             :                                "geometry,wkb_geometry,wkt_geometry"),
      47             :           ",", 0)),
      48        2804 :       m_osCRS(CSLFetchNameValueDef(papszOpenOptions, "CRS", ""))
      49             : {
      50        1402 : }
      51             : 
      52             : /************************************************************************/
      53             : /*                           GetDataset()                               */
      54             : /************************************************************************/
      55             : 
      56          27 : GDALDataset *OGRParquetLayerBase::GetDataset()
      57             : {
      58          27 :     return m_poDS;
      59             : }
      60             : 
      61             : /************************************************************************/
      62             : /*                           ResetReading()                             */
      63             : /************************************************************************/
      64             : 
      65        8053 : void OGRParquetLayerBase::ResetReading()
      66             : {
      67        8053 :     if (m_iRecordBatch != 0)
      68             :     {
      69        7585 :         m_poRecordBatchReader.reset();
      70             :     }
      71        8053 :     OGRArrowLayer::ResetReading();
      72        8053 : }
      73             : 
      74             : /************************************************************************/
      75             : /*                     InvalidateCachedBatches()                        */
      76             : /************************************************************************/
      77             : 
      78        2207 : void OGRParquetLayerBase::InvalidateCachedBatches()
      79             : {
      80        2207 :     m_iRecordBatch = -1;
      81        2207 :     ResetReading();
      82        2207 : }
      83             : 
      84             : /************************************************************************/
      85             : /*                          LoadGeoMetadata()                           */
      86             : /************************************************************************/
      87             : 
      88        1402 : void OGRParquetLayerBase::LoadGeoMetadata(
      89             :     const std::shared_ptr<const arrow::KeyValueMetadata> &kv_metadata)
      90             : {
      91        1402 :     if (kv_metadata && kv_metadata->Contains("geo"))
      92             :     {
      93        2676 :         auto geo = kv_metadata->Get("geo");
      94        1338 :         if (geo.ok())
      95             :         {
      96        1338 :             CPLDebug("PARQUET", "geo = %s", geo->c_str());
      97        2676 :             CPLJSONDocument oDoc;
      98        1338 :             if (oDoc.LoadMemory(*geo))
      99             :             {
     100        2674 :                 auto oRoot = oDoc.GetRoot();
     101        4011 :                 const auto osVersion = oRoot.GetString("version");
     102        3263 :                 if (osVersion != "0.1.0" && osVersion != "0.2.0" &&
     103        2888 :                     osVersion != "0.3.0" && osVersion != "0.4.0" &&
     104        2884 :                     osVersion != "1.0.0-beta.1" && osVersion != "1.0.0-rc.1" &&
     105        3261 :                     osVersion != "1.0.0" && osVersion != "1.1.0")
     106             :                 {
     107           1 :                     CPLDebug(
     108             :                         "PARQUET",
     109             :                         "version = %s not explicitly handled by the driver",
     110             :                         osVersion.c_str());
     111             :                 }
     112             : 
     113        4011 :                 auto oColumns = oRoot.GetObj("columns");
     114        1337 :                 if (oColumns.IsValid())
     115             :                 {
     116        2689 :                     for (const auto &oColumn : oColumns.GetChildren())
     117             :                     {
     118        1353 :                         m_oMapGeometryColumns[oColumn.GetName()] = oColumn;
     119             :                     }
     120             :                 }
     121             :             }
     122             :             else
     123             :             {
     124           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
     125             :                          "Cannot parse 'geo' metadata");
     126             :             }
     127             :         }
     128             :     }
     129        1402 : }
     130             : 
     131             : /************************************************************************/
     132             : /*                   ParseGeometryColumnCovering()                      */
     133             : /************************************************************************/
     134             : 
     135             : //! Parse bounding box column definition
     136             : /*static */
     137        2696 : bool OGRParquetLayerBase::ParseGeometryColumnCovering(
     138             :     const CPLJSONObject &oJSONDef, std::string &osBBOXColumn,
     139             :     std::string &osXMin, std::string &osYMin, std::string &osXMax,
     140             :     std::string &osYMax)
     141             : {
     142        8088 :     const auto oCovering = oJSONDef["covering"];
     143        4073 :     if (oCovering.IsValid() &&
     144        1377 :         oCovering.GetType() == CPLJSONObject::Type::Object)
     145             :     {
     146        2754 :         const auto oBBOX = oCovering["bbox"];
     147        1377 :         if (oBBOX.IsValid() && oBBOX.GetType() == CPLJSONObject::Type::Object)
     148             :         {
     149        2754 :             const auto oXMin = oBBOX["xmin"];
     150        2754 :             const auto oYMin = oBBOX["ymin"];
     151        2754 :             const auto oXMax = oBBOX["xmax"];
     152        2754 :             const auto oYMax = oBBOX["ymax"];
     153        2754 :             if (oXMin.IsValid() && oYMin.IsValid() && oXMax.IsValid() &&
     154        1377 :                 oYMax.IsValid() &&
     155        1377 :                 oXMin.GetType() == CPLJSONObject::Type::Array &&
     156        1377 :                 oYMin.GetType() == CPLJSONObject::Type::Array &&
     157        4131 :                 oXMax.GetType() == CPLJSONObject::Type::Array &&
     158        1377 :                 oYMax.GetType() == CPLJSONObject::Type::Array)
     159             :             {
     160        1377 :                 const auto osXMinArray = oXMin.ToArray();
     161        1377 :                 const auto osYMinArray = oYMin.ToArray();
     162        1377 :                 const auto osXMaxArray = oXMax.ToArray();
     163        1377 :                 const auto osYMaxArray = oYMax.ToArray();
     164        1377 :                 if (osXMinArray.Size() == 2 && osYMinArray.Size() == 2 &&
     165        1377 :                     osXMaxArray.Size() == 2 && osYMaxArray.Size() == 2 &&
     166        2754 :                     osXMinArray[0].GetType() == CPLJSONObject::Type::String &&
     167        2754 :                     osXMinArray[1].GetType() == CPLJSONObject::Type::String &&
     168        2754 :                     osYMinArray[0].GetType() == CPLJSONObject::Type::String &&
     169        2754 :                     osYMinArray[1].GetType() == CPLJSONObject::Type::String &&
     170        2754 :                     osXMaxArray[0].GetType() == CPLJSONObject::Type::String &&
     171        2754 :                     osXMaxArray[1].GetType() == CPLJSONObject::Type::String &&
     172        2754 :                     osYMaxArray[0].GetType() == CPLJSONObject::Type::String &&
     173        4131 :                     osYMaxArray[1].GetType() == CPLJSONObject::Type::String &&
     174        4131 :                     osXMinArray[0].ToString() == osYMinArray[0].ToString() &&
     175        5508 :                     osXMinArray[0].ToString() == osXMaxArray[0].ToString() &&
     176        2754 :                     osXMinArray[0].ToString() == osYMaxArray[0].ToString())
     177             :                 {
     178        1377 :                     osBBOXColumn = osXMinArray[0].ToString();
     179        1377 :                     osXMin = osXMinArray[1].ToString();
     180        1377 :                     osYMin = osYMinArray[1].ToString();
     181        1377 :                     osXMax = osXMaxArray[1].ToString();
     182        1377 :                     osYMax = osYMaxArray[1].ToString();
     183        1377 :                     return true;
     184             :                 }
     185             :             }
     186             :         }
     187             :     }
     188        1319 :     return false;
     189             : }
     190             : 
     191             : /************************************************************************/
     192             : /*                      DealWithGeometryColumn()                        */
     193             : /************************************************************************/
     194             : 
     195       32654 : bool OGRParquetLayerBase::DealWithGeometryColumn(
     196             :     int iFieldIdx, const std::shared_ptr<arrow::Field> &field,
     197             :     std::function<OGRwkbGeometryType(void)> computeGeometryTypeFun,
     198             :     [[maybe_unused]] const parquet::ColumnDescriptor *parquetColumn,
     199             :     [[maybe_unused]] const parquet::FileMetaData *metadata,
     200             :     [[maybe_unused]] int iColumn)
     201             : {
     202       65308 :     const auto &field_kv_metadata = field->metadata();
     203       65308 :     std::string osExtensionName;
     204       32654 :     if (field_kv_metadata)
     205             :     {
     206         102 :         auto extension_name = field_kv_metadata->Get(ARROW_EXTENSION_NAME_KEY);
     207          51 :         if (extension_name.ok())
     208             :         {
     209           2 :             osExtensionName = *extension_name;
     210             :         }
     211             : #ifdef DEBUG
     212          51 :         CPLDebug("PARQUET", "Metadata field %s:", field->name().c_str());
     213          54 :         for (const auto &keyValue : field_kv_metadata->sorted_pairs())
     214             :         {
     215           3 :             CPLDebug("PARQUET", "  %s = %s", keyValue.first.c_str(),
     216             :                      keyValue.second.c_str());
     217             :         }
     218             : #endif
     219             :     }
     220             : 
     221       32654 :     bool bRegularField = true;
     222       32654 :     auto oIter = m_oMapGeometryColumns.find(field->name());
     223       32654 :     if (oIter != m_oMapGeometryColumns.end() ||
     224       63955 :         STARTS_WITH(osExtensionName.c_str(), "ogc.") ||
     225       31301 :         STARTS_WITH(osExtensionName.c_str(), "geoarrow."))
     226             :     {
     227        2708 :         CPLJSONObject oJSONDef;
     228        1354 :         if (oIter != m_oMapGeometryColumns.end())
     229        1353 :             oJSONDef = oIter->second;
     230        4062 :         auto osEncoding = oJSONDef.GetString("encoding");
     231        1354 :         if (osEncoding.empty() && !osExtensionName.empty())
     232           1 :             osEncoding = osExtensionName;
     233             : 
     234        1354 :         OGRwkbGeometryType eGeomType = wkbUnknown;
     235        1354 :         auto eGeomEncoding = OGRArrowGeomEncoding::WKB;
     236        1354 :         if (IsValidGeometryEncoding(field, osEncoding,
     237        2708 :                                     oIter != m_oMapGeometryColumns.end(),
     238             :                                     eGeomType, eGeomEncoding))
     239             :         {
     240        1354 :             bRegularField = false;
     241        2708 :             OGRGeomFieldDefn oField(field->name().c_str(), wkbUnknown);
     242             : 
     243        4062 :             auto oCRS = oJSONDef["crs"];
     244        1354 :             OGRSpatialReference *poSRS = nullptr;
     245        1354 :             if (!oCRS.IsValid())
     246             :             {
     247          48 :                 if (!m_oMapGeometryColumns.empty())
     248             :                 {
     249             :                     // WGS 84 is implied if no crs member is found.
     250          47 :                     poSRS = new OGRSpatialReference();
     251          47 :                     poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     252          47 :                     poSRS->importFromEPSG(4326);
     253             :                 }
     254             :             }
     255        1306 :             else if (oCRS.GetType() == CPLJSONObject::Type::String)
     256             :             {
     257        1119 :                 const auto osWKT = oCRS.ToString();
     258         373 :                 poSRS = new OGRSpatialReference();
     259         373 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     260             : 
     261         373 :                 if (poSRS->importFromWkt(osWKT.c_str()) != OGRERR_NONE)
     262             :                 {
     263           0 :                     poSRS->Release();
     264           0 :                     poSRS = nullptr;
     265             :                 }
     266             :             }
     267         933 :             else if (oCRS.GetType() == CPLJSONObject::Type::Object)
     268             :             {
     269             :                 // CRS encoded as PROJJSON (extension)
     270         120 :                 const auto oType = oCRS["type"];
     271          80 :                 if (oType.IsValid() &&
     272          40 :                     oType.GetType() == CPLJSONObject::Type::String)
     273             :                 {
     274         120 :                     const auto osType = oType.ToString();
     275          40 :                     if (osType.find("CRS") != std::string::npos)
     276             :                     {
     277          40 :                         poSRS = new OGRSpatialReference();
     278          40 :                         poSRS->SetAxisMappingStrategy(
     279             :                             OAMS_TRADITIONAL_GIS_ORDER);
     280             : 
     281          80 :                         if (poSRS->SetFromUserInput(
     282          80 :                                 oCRS.ToString().c_str(),
     283             :                                 OGRSpatialReference::
     284          40 :                                     SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
     285             :                             OGRERR_NONE)
     286             :                         {
     287           0 :                             poSRS->Release();
     288           0 :                             poSRS = nullptr;
     289             :                         }
     290             :                     }
     291             :                 }
     292             :             }
     293             : 
     294        1354 :             if (poSRS)
     295             :             {
     296         460 :                 const double dfCoordEpoch = oJSONDef.GetDouble("epoch");
     297         460 :                 if (dfCoordEpoch > 0)
     298           4 :                     poSRS->SetCoordinateEpoch(dfCoordEpoch);
     299             : 
     300         460 :                 oField.SetSpatialRef(poSRS);
     301             : 
     302         460 :                 poSRS->Release();
     303             :             }
     304             : 
     305        1354 :             if (!m_osCRS.empty())
     306             :             {
     307           0 :                 poSRS = new OGRSpatialReference();
     308           0 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     309           0 :                 if (poSRS->SetFromUserInput(
     310             :                         m_osCRS.c_str(),
     311             :                         OGRSpatialReference::
     312           0 :                             SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
     313             :                     OGRERR_NONE)
     314             :                 {
     315           0 :                     oField.SetSpatialRef(poSRS);
     316             :                 }
     317           0 :                 poSRS->Release();
     318             :             }
     319             : 
     320        1354 :             if (oJSONDef.GetString("edges") == "spherical")
     321             :             {
     322           5 :                 SetMetadataItem("EDGES", "SPHERICAL");
     323             :             }
     324             : 
     325             :             // m_aeGeomEncoding be filled before calling
     326             :             // ComputeGeometryColumnType()
     327        1354 :             m_aeGeomEncoding.push_back(eGeomEncoding);
     328        1354 :             if (eGeomType == wkbUnknown)
     329             :             {
     330             :                 // geometry_types since 1.0.0-beta1. Was geometry_type
     331             :                 // before
     332        1794 :                 auto oType = oJSONDef.GetObj("geometry_types");
     333         598 :                 if (!oType.IsValid())
     334         377 :                     oType = oJSONDef.GetObj("geometry_type");
     335         598 :                 if (oType.GetType() == CPLJSONObject::Type::String)
     336             :                 {
     337             :                     // string is no longer valid since 1.0.0-beta1
     338           3 :                     const auto osType = oType.ToString();
     339           1 :                     if (osType != "Unknown")
     340           1 :                         eGeomType = GetGeometryTypeFromString(osType);
     341             :                 }
     342         597 :                 else if (oType.GetType() == CPLJSONObject::Type::Array)
     343             :                 {
     344         442 :                     const auto oTypeArray = oType.ToArray();
     345         221 :                     if (oTypeArray.Size() == 1)
     346             :                     {
     347         114 :                         eGeomType =
     348         114 :                             GetGeometryTypeFromString(oTypeArray[0].ToString());
     349             :                     }
     350         107 :                     else if (oTypeArray.Size() > 1)
     351             :                     {
     352             :                         const auto PromoteToCollection =
     353         266 :                             [](OGRwkbGeometryType eType)
     354             :                         {
     355         266 :                             if (eType == wkbPoint)
     356          41 :                                 return wkbMultiPoint;
     357         225 :                             if (eType == wkbLineString)
     358          36 :                                 return wkbMultiLineString;
     359         189 :                             if (eType == wkbPolygon)
     360          49 :                                 return wkbMultiPolygon;
     361         140 :                             return eType;
     362             :                         };
     363          50 :                         bool bMixed = false;
     364          50 :                         bool bHasMulti = false;
     365          50 :                         bool bHasZ = false;
     366          50 :                         bool bHasM = false;
     367             :                         const auto eFirstType =
     368          50 :                             OGR_GT_Flatten(GetGeometryTypeFromString(
     369         100 :                                 oTypeArray[0].ToString()));
     370             :                         const auto eFirstTypeCollection =
     371          50 :                             PromoteToCollection(eFirstType);
     372         142 :                         for (int i = 0; i < oTypeArray.Size(); ++i)
     373             :                         {
     374         124 :                             const auto eThisGeom = GetGeometryTypeFromString(
     375         248 :                                 oTypeArray[i].ToString());
     376         124 :                             if (PromoteToCollection(OGR_GT_Flatten(
     377         124 :                                     eThisGeom)) != eFirstTypeCollection)
     378             :                             {
     379          32 :                                 bMixed = true;
     380          32 :                                 break;
     381             :                             }
     382          92 :                             bHasZ |= OGR_GT_HasZ(eThisGeom) != FALSE;
     383          92 :                             bHasM |= OGR_GT_HasM(eThisGeom) != FALSE;
     384          92 :                             bHasMulti |=
     385          92 :                                 (PromoteToCollection(OGR_GT_Flatten(
     386          92 :                                      eThisGeom)) == OGR_GT_Flatten(eThisGeom));
     387             :                         }
     388          50 :                         if (!bMixed)
     389             :                         {
     390          18 :                             if (eFirstTypeCollection == wkbMultiPolygon ||
     391             :                                 eFirstTypeCollection == wkbMultiLineString)
     392             :                             {
     393          17 :                                 if (bHasMulti)
     394          17 :                                     eGeomType = OGR_GT_SetModifier(
     395             :                                         eFirstTypeCollection, bHasZ, bHasM);
     396             :                                 else
     397           0 :                                     eGeomType = OGR_GT_SetModifier(
     398             :                                         eFirstType, bHasZ, bHasM);
     399             :                             }
     400             :                         }
     401             :                     }
     402             :                 }
     403         376 :                 else if (CPLTestBool(CPLGetConfigOption(
     404             :                              "OGR_PARQUET_COMPUTE_GEOMETRY_TYPE", "YES")))
     405             :                 {
     406         376 :                     eGeomType = computeGeometryTypeFun();
     407             :                 }
     408             :             }
     409             : 
     410        1354 :             oField.SetType(eGeomType);
     411        1354 :             oField.SetNullable(field->nullable());
     412        1354 :             m_poFeatureDefn->AddGeomFieldDefn(&oField);
     413        1354 :             m_anMapGeomFieldIndexToArrowColumn.push_back(iFieldIdx);
     414             :         }
     415             :     }
     416             : 
     417       32654 :     std::shared_ptr<arrow::DataType> fieldType = field->type();
     418       32654 :     auto fieldTypeId = fieldType->id();
     419             : #if PARQUET_VERSION_MAJOR >= 21
     420             :     // Try to detect Arrow >= 21 GEOMETRY/GEOGRAPHY logical type
     421             :     if (bRegularField && fieldTypeId == arrow::Type::EXTENSION)
     422             :     {
     423             :         auto extensionType =
     424             :             cpl::down_cast<arrow::ExtensionType *>(fieldType.get());
     425             :         osExtensionName = extensionType->extension_name();
     426             :         if (osExtensionName == EXTENSION_NAME_GEOARROW_WKB)
     427             :         {
     428             :             const auto arrowWkb =
     429             :                 dynamic_cast<const OGRGeoArrowWkbExtensionType *>(
     430             :                     extensionType);
     431             : #ifdef DEBUG
     432             :             if (arrowWkb)
     433             :             {
     434             :                 CPLDebug("PARQUET", "arrowWkb = '%s'",
     435             :                          arrowWkb->Serialize().c_str());
     436             :             }
     437             : #endif
     438             : 
     439             :             fieldTypeId = extensionType->storage_type()->id();
     440             :             if (fieldTypeId == arrow::Type::BINARY ||
     441             :                 fieldTypeId == arrow::Type::LARGE_BINARY)
     442             :             {
     443             :                 OGRwkbGeometryType eGeomType = wkbUnknown;
     444             :                 bool bSkipRowGroups = false;
     445             : 
     446             :                 // m_aeGeomEncoding be filled before calling
     447             :                 // ComputeGeometryColumnType()
     448             :                 bRegularField = false;
     449             :                 m_aeGeomEncoding.push_back(OGRArrowGeomEncoding::WKB);
     450             : 
     451             :                 std::string crs(m_osCRS);
     452             :                 if (parquetColumn && crs.empty())
     453             :                 {
     454             :                     const auto &logicalType = parquetColumn->logical_type();
     455             :                     if (logicalType->is_geometry())
     456             :                     {
     457             :                         crs = static_cast<const parquet::GeometryLogicalType *>(
     458             :                                   logicalType.get())
     459             :                                   ->crs();
     460             :                         if (crs.empty())
     461             :                             crs = "EPSG:4326";
     462             :                         CPLDebugOnly("PARQUET", "GeometryLogicalType crs=%s",
     463             :                                      crs.c_str());
     464             :                     }
     465             :                     else if (logicalType->is_geography())
     466             :                     {
     467             :                         const auto *geographyType =
     468             :                             static_cast<const parquet::GeographyLogicalType *>(
     469             :                                 logicalType.get());
     470             :                         crs = geographyType->crs();
     471             :                         if (crs.empty())
     472             :                             crs = "EPSG:4326";
     473             : 
     474             :                         SetMetadataItem(
     475             :                             "EDGES",
     476             :                             CPLString(
     477             :                                 std::string(geographyType->algorithm_name()))
     478             :                                 .toupper());
     479             :                         CPLDebugOnly("PARQUET", "GeographyLogicalType crs=%s",
     480             :                                      crs.c_str());
     481             :                     }
     482             :                     else
     483             :                     {
     484             :                         CPLDebug("PARQUET", "geoarrow.wkb column is neither a "
     485             :                                             "geometry or geography one");
     486             : 
     487             :                         // This might be an old geoarrow.wkb extension...
     488             :                         if (CPLTestBool(CPLGetConfigOption(
     489             :                                 "OGR_PARQUET_COMPUTE_GEOMETRY_TYPE", "YES")))
     490             :                         {
     491             :                             eGeomType = computeGeometryTypeFun();
     492             :                             bSkipRowGroups = true;
     493             :                         }
     494             :                     }
     495             : 
     496             :                     // Cf https://github.com/apache/parquet-format/blob/master/Geospatial.md#crs-customization
     497             :                     // "projjson: PROJJSON, identifier is the name of a table property or a file property where the projjson string is stored."
     498             :                     // Here the property is interpreted as the key of a file metadata (as done in libarrow)
     499             :                     constexpr const char *PROJJSON_PREFIX = "projjson:";
     500             :                     if (cpl::starts_with(crs, PROJJSON_PREFIX) && metadata)
     501             :                     {
     502             :                         auto projjson_value =
     503             :                             metadata->key_value_metadata()->Get(
     504             :                                 crs.substr(strlen(PROJJSON_PREFIX)));
     505             :                         if (projjson_value.ok())
     506             :                         {
     507             :                             crs = *projjson_value;
     508             :                         }
     509             :                         else
     510             :                         {
     511             :                             CPLDebug("PARQUET",
     512             :                                      "Cannot find file metadata for %s",
     513             :                                      crs.c_str());
     514             :                         }
     515             :                     }
     516             :                 }
     517             :                 else if (!parquetColumn && arrowWkb)
     518             :                 {
     519             :                     // For a OGRParquetDatasetLayer for example
     520             :                     const std::string arrowWkbMetadata = arrowWkb->Serialize();
     521             :                     if (arrowWkbMetadata.empty() || arrowWkbMetadata == "{}")
     522             :                     {
     523             :                         crs = "EPSG:4326";
     524             :                     }
     525             :                     else if (arrowWkbMetadata[0] == '{')
     526             :                     {
     527             :                         CPLJSONDocument oDoc;
     528             :                         if (oDoc.LoadMemory(arrowWkbMetadata))
     529             :                         {
     530             :                             auto jCrs = oDoc.GetRoot()["crs"];
     531             :                             if (jCrs.GetType() == CPLJSONObject::Type::Object)
     532             :                             {
     533             :                                 crs = jCrs.Format(
     534             :                                     CPLJSONObject::PrettyFormat::Plain);
     535             :                             }
     536             :                             else if (jCrs.GetType() ==
     537             :                                      CPLJSONObject::Type::String)
     538             :                             {
     539             :                                 crs = jCrs.ToString();
     540             :                             }
     541             :                             if (oDoc.GetRoot()["edges"].ToString() ==
     542             :                                 "spherical")
     543             :                             {
     544             :                                 SetMetadataItem("EDGES", "SPHERICAL");
     545             :                             }
     546             :                         }
     547             :                     }
     548             :                 }
     549             : 
     550             :                 bool bGeomTypeInvalid = false;
     551             :                 bool bHasMulti = false;
     552             :                 bool bHasZ = false;
     553             :                 bool bHasM = false;
     554             :                 bool bFirst = true;
     555             :                 OGRwkbGeometryType eFirstType = wkbUnknown;
     556             :                 OGRwkbGeometryType eFirstTypeCollection = wkbUnknown;
     557             :                 const auto numRowGroups =
     558             :                     metadata ? metadata->num_row_groups() : 0;
     559             :                 bool bEnvelopeValid = true;
     560             :                 OGREnvelope sEnvelope;
     561             :                 bool bEnvelope3DValid = true;
     562             :                 OGREnvelope3D sEnvelope3D;
     563             :                 for (int iRowGroup = 0;
     564             :                      !bSkipRowGroups && iRowGroup < numRowGroups; ++iRowGroup)
     565             :                 {
     566             :                     const auto columnChunk =
     567             :                         metadata->RowGroup(iRowGroup)->ColumnChunk(iColumn);
     568             :                     if (auto geostats = columnChunk->geo_statistics())
     569             :                     {
     570             :                         double dfMinX =
     571             :                             std::numeric_limits<double>::quiet_NaN();
     572             :                         double dfMinY =
     573             :                             std::numeric_limits<double>::quiet_NaN();
     574             :                         double dfMinZ =
     575             :                             std::numeric_limits<double>::quiet_NaN();
     576             :                         double dfMaxX =
     577             :                             std::numeric_limits<double>::quiet_NaN();
     578             :                         double dfMaxY =
     579             :                             std::numeric_limits<double>::quiet_NaN();
     580             :                         double dfMaxZ =
     581             :                             std::numeric_limits<double>::quiet_NaN();
     582             :                         if (bEnvelopeValid && geostats->dimension_valid()[0] &&
     583             :                             geostats->dimension_valid()[1])
     584             :                         {
     585             :                             dfMinX = geostats->lower_bound()[0];
     586             :                             dfMaxX = geostats->upper_bound()[0];
     587             :                             dfMinY = geostats->lower_bound()[1];
     588             :                             dfMaxY = geostats->upper_bound()[1];
     589             : 
     590             :                             // Deal as best as we can with wrap around bounding box
     591             :                             if (dfMinX > dfMaxX && std::fabs(dfMinX) <= 180 &&
     592             :                                 std::fabs(dfMaxX) <= 180)
     593             :                             {
     594             :                                 dfMinX = -180;
     595             :                                 dfMaxX = 180;
     596             :                             }
     597             : 
     598             :                             if (std::isfinite(dfMinX) &&
     599             :                                 std::isfinite(dfMaxX) &&
     600             :                                 std::isfinite(dfMinY) && std::isfinite(dfMaxY))
     601             :                             {
     602             :                                 sEnvelope.Merge(dfMinX, dfMinY);
     603             :                                 sEnvelope.Merge(dfMaxX, dfMaxY);
     604             :                                 if (bEnvelope3DValid &&
     605             :                                     geostats->dimension_valid()[2])
     606             :                                 {
     607             :                                     dfMinZ = geostats->lower_bound()[2];
     608             :                                     dfMaxZ = geostats->upper_bound()[2];
     609             :                                     if (std::isfinite(dfMinZ) &&
     610             :                                         std::isfinite(dfMaxZ))
     611             :                                     {
     612             :                                         sEnvelope3D.Merge(dfMinX, dfMinY,
     613             :                                                           dfMinZ);
     614             :                                         sEnvelope3D.Merge(dfMaxX, dfMaxY,
     615             :                                                           dfMaxZ);
     616             :                                     }
     617             :                                 }
     618             :                             }
     619             :                         }
     620             : 
     621             :                         bEnvelopeValid =
     622             :                             bEnvelopeValid && std::isfinite(dfMinX) &&
     623             :                             std::isfinite(dfMaxX) && std::isfinite(dfMinY) &&
     624             :                             std::isfinite(dfMaxY);
     625             : 
     626             :                         bEnvelope3DValid = bEnvelope3DValid &&
     627             :                                            std::isfinite(dfMinZ) &&
     628             :                                            std::isfinite(dfMaxZ);
     629             : 
     630             :                         if (auto geometry_types = geostats->geometry_types())
     631             :                         {
     632             :                             const auto PromoteToCollection =
     633             :                                 [](OGRwkbGeometryType eType)
     634             :                             {
     635             :                                 if (eType == wkbPoint)
     636             :                                     return wkbMultiPoint;
     637             :                                 if (eType == wkbLineString)
     638             :                                     return wkbMultiLineString;
     639             :                                 if (eType == wkbPolygon)
     640             :                                     return wkbMultiPolygon;
     641             :                                 return eType;
     642             :                             };
     643             : 
     644             :                             for (int nGeomType : *geometry_types)
     645             :                             {
     646             :                                 OGRwkbGeometryType eThisGeom = wkbUnknown;
     647             :                                 if ((nGeomType > 0 && nGeomType <= 17) ||
     648             :                                     (nGeomType > 2000 && nGeomType <= 2017) ||
     649             :                                     (nGeomType > 3000 && nGeomType <= 3017))
     650             :                                 {
     651             :                                     eThisGeom = static_cast<OGRwkbGeometryType>(
     652             :                                         nGeomType);
     653             :                                 }
     654             :                                 else if (nGeomType > 1000 && nGeomType <= 1017)
     655             :                                 {
     656             :                                     eThisGeom = OGR_GT_SetZ(
     657             :                                         static_cast<OGRwkbGeometryType>(
     658             :                                             nGeomType - 1000));
     659             :                                     ;
     660             :                                 }
     661             :                                 else
     662             :                                 {
     663             :                                     CPLDebug("PARQUET",
     664             :                                              "Unknown geometry type: %d",
     665             :                                              nGeomType);
     666             :                                     bGeomTypeInvalid = true;
     667             :                                     break;
     668             :                                 }
     669             :                                 if (bFirst)
     670             :                                 {
     671             :                                     bFirst = false;
     672             :                                     eFirstType = eThisGeom;
     673             :                                     eFirstTypeCollection =
     674             :                                         PromoteToCollection(eFirstType);
     675             :                                 }
     676             :                                 else if (PromoteToCollection(
     677             :                                              OGR_GT_Flatten(eThisGeom)) !=
     678             :                                          eFirstTypeCollection)
     679             :                                 {
     680             :                                     bGeomTypeInvalid = true;
     681             :                                     break;
     682             :                                 }
     683             :                                 bHasZ |= OGR_GT_HasZ(eThisGeom) != FALSE;
     684             :                                 bHasM |= OGR_GT_HasM(eThisGeom) != FALSE;
     685             :                                 bHasMulti |= (PromoteToCollection(
     686             :                                                   OGR_GT_Flatten(eThisGeom)) ==
     687             :                                               OGR_GT_Flatten(eThisGeom));
     688             :                             }
     689             :                         }
     690             :                     }
     691             :                     else
     692             :                     {
     693             :                         bEnvelopeValid = false;
     694             :                         bEnvelope3DValid = false;
     695             :                         bGeomTypeInvalid = true;
     696             :                     }
     697             :                 }
     698             : 
     699             :                 if (bEnvelopeValid && sEnvelope.IsInit())
     700             :                 {
     701             :                     CPLDebug("PARQUET", "Got bounding box from geo_statistics");
     702             :                     m_geoStatsWithBBOXAvailable.insert(
     703             :                         m_poFeatureDefn->GetGeomFieldCount());
     704             :                     m_oMapExtents[m_poFeatureDefn->GetGeomFieldCount()] =
     705             :                         std::move(sEnvelope);
     706             : 
     707             :                     if (bEnvelope3DValid && sEnvelope3D.IsInit())
     708             :                     {
     709             :                         CPLDebug("PARQUET",
     710             :                                  "Got bounding box 3D from geo_statistics");
     711             :                         m_oMapExtents3D[m_poFeatureDefn->GetGeomFieldCount()] =
     712             :                             std::move(sEnvelope3D);
     713             :                     }
     714             :                 }
     715             : 
     716             :                 if (!bSkipRowGroups && !bGeomTypeInvalid)
     717             :                 {
     718             :                     if (eFirstTypeCollection == wkbMultiPoint ||
     719             :                         eFirstTypeCollection == wkbMultiPolygon ||
     720             :                         eFirstTypeCollection == wkbMultiLineString)
     721             :                     {
     722             :                         if (bHasMulti)
     723             :                             eGeomType = OGR_GT_SetModifier(eFirstTypeCollection,
     724             :                                                            bHasZ, bHasM);
     725             :                         else
     726             :                             eGeomType =
     727             :                                 OGR_GT_SetModifier(eFirstType, bHasZ, bHasM);
     728             :                     }
     729             :                 }
     730             : 
     731             :                 OGRGeomFieldDefn oField(field->name().c_str(), eGeomType);
     732             :                 oField.SetNullable(field->nullable());
     733             : 
     734             :                 if (!crs.empty())
     735             :                 {
     736             :                     // Cf https://github.com/apache/parquet-format/blob/master/Geospatial.md#crs-customization
     737             :                     // "srid: Spatial reference identifier, identifier is the SRID itself.."
     738             :                     constexpr const char *SRID_PREFIX = "srid:";
     739             :                     if (cpl::starts_with(crs, SRID_PREFIX))
     740             :                     {
     741             :                         // When getting the value from the GeometryLogicalType::crs() method
     742             :                         crs = crs.substr(strlen(SRID_PREFIX));
     743             :                     }
     744             :                     if (CPLGetValueType(crs.c_str()) == CPL_VALUE_INTEGER)
     745             :                     {
     746             :                         // Getting here from above if, or if reading the ArrowWkb
     747             :                         // metadata directly (typically from a OGRParquetDatasetLayer)
     748             : 
     749             :                         // Assumes a SRID code is an EPSG code...
     750             :                         crs = std::string("EPSG:") + crs;
     751             :                     }
     752             : 
     753             :                     auto poSRS = new OGRSpatialReference();
     754             :                     poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     755             :                     if (poSRS->SetFromUserInput(
     756             :                             crs.c_str(),
     757             :                             OGRSpatialReference::
     758             :                                 SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
     759             :                         OGRERR_NONE)
     760             :                     {
     761             :                         const char *pszAuthName =
     762             :                             poSRS->GetAuthorityName(nullptr);
     763             :                         const char *pszAuthCode =
     764             :                             poSRS->GetAuthorityCode(nullptr);
     765             :                         if (pszAuthName && pszAuthCode &&
     766             :                             EQUAL(pszAuthName, "OGC") &&
     767             :                             EQUAL(pszAuthCode, "CRS84"))
     768             :                             poSRS->importFromEPSG(4326);
     769             :                         oField.SetSpatialRef(poSRS);
     770             :                     }
     771             :                     poSRS->Release();
     772             :                 }
     773             : 
     774             :                 m_poFeatureDefn->AddGeomFieldDefn(&oField);
     775             :                 m_anMapGeomFieldIndexToArrowColumn.push_back(iFieldIdx);
     776             :             }
     777             :         }
     778             :     }
     779             : #endif
     780             : 
     781             :     // Try to autodetect a (WKB) geometry column from the GEOM_POSSIBLE_NAMES
     782             :     // open option
     783       62599 :     if (bRegularField && osExtensionName.empty() &&
     784       95253 :         m_oMapGeometryColumns.empty() &&
     785         248 :         m_aosGeomPossibleNames.FindString(field->name().c_str()) >= 0)
     786             :     {
     787          12 :         if (fieldTypeId == arrow::Type::BINARY ||
     788             :             fieldTypeId == arrow::Type::LARGE_BINARY)
     789             :         {
     790           6 :             CPLDebug("PARQUET",
     791             :                      "Field %s detected as likely WKB geometry field",
     792           6 :                      field->name().c_str());
     793           6 :             bRegularField = false;
     794           6 :             m_aeGeomEncoding.push_back(OGRArrowGeomEncoding::WKB);
     795             :         }
     796           0 :         else if ((fieldTypeId == arrow::Type::STRING ||
     797          16 :                   fieldTypeId == arrow::Type::LARGE_STRING) &&
     798          10 :                  (field->name().find("wkt") != std::string::npos ||
     799           4 :                   field->name().find("WKT") != std::string::npos))
     800             :         {
     801           2 :             CPLDebug("PARQUET",
     802             :                      "Field %s detected as likely WKT geometry field",
     803           2 :                      field->name().c_str());
     804           2 :             bRegularField = false;
     805           2 :             m_aeGeomEncoding.push_back(OGRArrowGeomEncoding::WKT);
     806             :         }
     807          12 :         if (!bRegularField)
     808             :         {
     809          16 :             OGRGeomFieldDefn oField(field->name().c_str(), wkbUnknown);
     810           8 :             oField.SetNullable(field->nullable());
     811             : 
     812           8 :             if (!m_osCRS.empty())
     813             :             {
     814           2 :                 auto poSRS = new OGRSpatialReference();
     815           2 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     816           2 :                 if (poSRS->SetFromUserInput(
     817             :                         m_osCRS.c_str(),
     818             :                         OGRSpatialReference::
     819           2 :                             SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
     820             :                     OGRERR_NONE)
     821             :                 {
     822           2 :                     oField.SetSpatialRef(poSRS);
     823             :                 }
     824           2 :                 poSRS->Release();
     825             :             }
     826             : 
     827           8 :             m_poFeatureDefn->AddGeomFieldDefn(&oField);
     828           8 :             m_anMapGeomFieldIndexToArrowColumn.push_back(iFieldIdx);
     829             :         }
     830             :     }
     831             : 
     832       65308 :     return !bRegularField;
     833             : }
     834             : 
     835             : /************************************************************************/
     836             : /*                         TestCapability()                             */
     837             : /************************************************************************/
     838             : 
     839         763 : int OGRParquetLayerBase::TestCapability(const char *pszCap) const
     840             : {
     841         763 :     if (EQUAL(pszCap, OLCMeasuredGeometries))
     842          32 :         return true;
     843             : 
     844         731 :     if (EQUAL(pszCap, OLCFastSetNextByIndex))
     845           0 :         return true;
     846             : 
     847         731 :     if (EQUAL(pszCap, OLCFastSpatialFilter))
     848             :     {
     849          73 :         if (m_oMapGeomFieldIndexToGeomColBBOX.find(m_iGeomFieldFilter) !=
     850         146 :             m_oMapGeomFieldIndexToGeomColBBOX.end())
     851             :         {
     852          49 :             return true;
     853             :         }
     854          24 :         return false;
     855             :     }
     856             : 
     857         658 :     return OGRArrowLayer::TestCapability(pszCap);
     858             : }
     859             : 
     860             : /************************************************************************/
     861             : /*                           GetNumCPUs()                               */
     862             : /************************************************************************/
     863             : 
     864             : /* static */
     865        1982 : int OGRParquetLayerBase::GetNumCPUs()
     866             : {
     867        1982 :     const char *pszNumThreads = CPLGetConfigOption("GDAL_NUM_THREADS", nullptr);
     868        1982 :     int nNumThreads = 0;
     869        1982 :     if (pszNumThreads == nullptr)
     870        1982 :         nNumThreads = std::min(4, CPLGetNumCPUs());
     871             :     else
     872           0 :         nNumThreads = EQUAL(pszNumThreads, "ALL_CPUS") ? CPLGetNumCPUs()
     873           0 :                                                        : atoi(pszNumThreads);
     874        1982 :     if (nNumThreads > 1)
     875             :     {
     876        1982 :         CPL_IGNORE_RET_VAL(arrow::SetCpuThreadPoolCapacity(nNumThreads));
     877             :     }
     878        1982 :     return nNumThreads;
     879             : }
     880             : 
     881             : /************************************************************************/
     882             : /*                        OGRParquetLayer()                             */
     883             : /************************************************************************/
     884             : 
     885        1045 : OGRParquetLayer::OGRParquetLayer(
     886             :     OGRParquetDataset *poDS, const char *pszLayerName,
     887             :     std::unique_ptr<parquet::arrow::FileReader> &&arrow_reader,
     888        1045 :     CSLConstList papszOpenOptions)
     889             :     : OGRParquetLayerBase(poDS, pszLayerName, papszOpenOptions),
     890        1045 :       m_poArrowReader(std::move(arrow_reader))
     891             : {
     892        1045 :     EstablishFeatureDefn();
     893        1045 :     CPLAssert(static_cast<int>(m_aeGeomEncoding.size()) ==
     894             :               m_poFeatureDefn->GetGeomFieldCount());
     895             : 
     896        1045 :     m_oFeatureIdxRemappingIter = m_asFeatureIdxRemapping.begin();
     897        1045 : }
     898             : 
     899             : /************************************************************************/
     900             : /*                        EstablishFeatureDefn()                        */
     901             : /************************************************************************/
     902             : 
     903        1045 : void OGRParquetLayer::EstablishFeatureDefn()
     904             : {
     905        1045 :     const auto metadata = m_poArrowReader->parquet_reader()->metadata();
     906        1045 :     const auto &kv_metadata = metadata->key_value_metadata();
     907             : 
     908        1045 :     LoadGeoMetadata(kv_metadata);
     909             :     const auto oMapFieldNameToGDALSchemaFieldDefn =
     910        1045 :         LoadGDALSchema(kv_metadata.get());
     911             : 
     912        1045 :     LoadGDALMetadata(kv_metadata.get());
     913             : 
     914        1045 :     if (kv_metadata && kv_metadata->Contains("gdal:creation-options"))
     915             :     {
     916        1096 :         auto co = kv_metadata->Get("gdal:creation-options");
     917         548 :         if (co.ok())
     918             :         {
     919         548 :             CPLDebugOnly("PARQUET", "gdal:creation-options = %s", co->c_str());
     920        1096 :             CPLJSONDocument oDoc;
     921         548 :             if (oDoc.LoadMemory(*co))
     922             :             {
     923        1096 :                 auto oRoot = oDoc.GetRoot();
     924         548 :                 if (oRoot.GetType() == CPLJSONObject::Type::Object)
     925             :                 {
     926        1771 :                     for (const auto &oChild : oRoot.GetChildren())
     927             :                     {
     928        1223 :                         if (oChild.GetType() == CPLJSONObject::Type::String)
     929             :                         {
     930             :                             m_aosCreationOptions.SetNameValue(
     931        2446 :                                 oChild.GetName().c_str(),
     932        3669 :                                 oChild.ToString().c_str());
     933             :                         }
     934             :                     }
     935             :                 }
     936             :             }
     937             :         }
     938             :     }
     939             : 
     940        1045 :     if (!m_poArrowReader->GetSchema(&m_poSchema).ok())
     941             :     {
     942           0 :         return;
     943             :     }
     944             : 
     945             :     const bool bUseBBOX =
     946        1045 :         CPLTestBool(CPLGetConfigOption("OGR_PARQUET_USE_BBOX", "YES"));
     947             : 
     948             :     // Keep track of declared bounding box columns in GeoParquet JSON metadata,
     949             :     // in order not to expose them as regular fields.
     950        2090 :     std::set<std::string> oSetBBOXColumns;
     951        1045 :     if (bUseBBOX)
     952             :     {
     953        2050 :         for (const auto &iter : m_oMapGeometryColumns)
     954             :         {
     955        2020 :             std::string osBBOXColumn;
     956        2020 :             std::string osXMin, osYMin, osXMax, osYMax;
     957        1010 :             if (ParseGeometryColumnCovering(iter.second, osBBOXColumn, osXMin,
     958             :                                             osYMin, osXMax, osYMax))
     959             :             {
     960         514 :                 oSetBBOXColumns.insert(std::move(osBBOXColumn));
     961             :             }
     962             :         }
     963             :     }
     964             : 
     965        1045 :     const auto &fields = m_poSchema->fields();
     966        1045 :     const auto poParquetSchema = metadata->schema();
     967             : 
     968             :     // Map from Parquet column name (with dot separator) to Parquet index
     969        2090 :     std::map<std::string, int> oMapParquetColumnNameToIdx;
     970        1045 :     const int nParquetColumns = poParquetSchema->num_columns();
     971       37098 :     for (int iParquetCol = 0; iParquetCol < nParquetColumns; ++iParquetCol)
     972             :     {
     973       36053 :         const auto parquetColumn = poParquetSchema->Column(iParquetCol);
     974       36053 :         const auto parquetColumnName = parquetColumn->path()->ToDotString();
     975       36053 :         oMapParquetColumnNameToIdx[parquetColumnName] = iParquetCol;
     976             :     }
     977             : 
     978             :     // Synthetize a GeoParquet bounding box column definition when detecting
     979             :     // a Overture Map dataset < 2024-04-16-beta.0
     980         997 :     if ((m_oMapGeometryColumns.empty() ||
     981             :          // Below is for release 2024-01-17-alpha.0
     982        2042 :          (m_oMapGeometryColumns.find("geometry") !=
     983        2042 :               m_oMapGeometryColumns.end() &&
     984        2525 :           !m_oMapGeometryColumns["geometry"].GetObj("covering").IsValid() &&
     985        1901 :           m_oMapGeometryColumns["geometry"].GetString("encoding") == "WKB")) &&
     986         358 :         bUseBBOX &&
     987        1403 :         oMapParquetColumnNameToIdx.find("geometry") !=
     988        1722 :             oMapParquetColumnNameToIdx.end() &&
     989        1364 :         oMapParquetColumnNameToIdx.find("bbox.minx") !=
     990        1365 :             oMapParquetColumnNameToIdx.end() &&
     991        1046 :         oMapParquetColumnNameToIdx.find("bbox.miny") !=
     992        1047 :             oMapParquetColumnNameToIdx.end() &&
     993        1046 :         oMapParquetColumnNameToIdx.find("bbox.maxx") !=
     994        3136 :             oMapParquetColumnNameToIdx.end() &&
     995        1046 :         oMapParquetColumnNameToIdx.find("bbox.maxy") !=
     996        1046 :             oMapParquetColumnNameToIdx.end())
     997             :     {
     998           2 :         CPLJSONObject oDef;
     999           1 :         if (m_oMapGeometryColumns.find("geometry") !=
    1000           2 :             m_oMapGeometryColumns.end())
    1001             :         {
    1002           0 :             oDef = m_oMapGeometryColumns["geometry"];
    1003             :         }
    1004           2 :         CPLJSONObject oCovering;
    1005           1 :         oDef.Add("covering", oCovering);
    1006           1 :         CPLJSONObject oBBOX;
    1007           1 :         oCovering.Add("bbox", oBBOX);
    1008             :         {
    1009           1 :             CPLJSONArray oArray;
    1010           1 :             oArray.Add("bbox");
    1011           1 :             oArray.Add("minx");
    1012           1 :             oBBOX.Add("xmin", oArray);
    1013             :         }
    1014             :         {
    1015           1 :             CPLJSONArray oArray;
    1016           1 :             oArray.Add("bbox");
    1017           1 :             oArray.Add("miny");
    1018           1 :             oBBOX.Add("ymin", oArray);
    1019             :         }
    1020             :         {
    1021           1 :             CPLJSONArray oArray;
    1022           1 :             oArray.Add("bbox");
    1023           1 :             oArray.Add("maxx");
    1024           1 :             oBBOX.Add("xmax", oArray);
    1025             :         }
    1026             :         {
    1027           1 :             CPLJSONArray oArray;
    1028           1 :             oArray.Add("bbox");
    1029           1 :             oArray.Add("maxy");
    1030           1 :             oBBOX.Add("ymax", oArray);
    1031             :         }
    1032           1 :         oSetBBOXColumns.insert("bbox");
    1033           1 :         oDef.Add("encoding", "WKB");
    1034           1 :         m_oMapGeometryColumns["geometry"] = std::move(oDef);
    1035             :     }
    1036             :     // Overture Maps 2024-04-16-beta.0 almost follows GeoParquet 1.1, except
    1037             :     // they don't declare the "covering" element in the GeoParquet JSON metadata
    1038        2088 :     else if (m_oMapGeometryColumns.find("geometry") !=
    1039        2028 :                  m_oMapGeometryColumns.end() &&
    1040        1960 :              bUseBBOX &&
    1041        2519 :              !m_oMapGeometryColumns["geometry"].GetObj("covering").IsValid() &&
    1042        1850 :              m_oMapGeometryColumns["geometry"].GetString("encoding") == "WKB" &&
    1043        1355 :              oMapParquetColumnNameToIdx.find("geometry") !=
    1044        1666 :                  oMapParquetColumnNameToIdx.end() &&
    1045        1355 :              oMapParquetColumnNameToIdx.find("bbox.xmin") !=
    1046        1356 :                  oMapParquetColumnNameToIdx.end() &&
    1047        1045 :              oMapParquetColumnNameToIdx.find("bbox.ymin") !=
    1048        1046 :                  oMapParquetColumnNameToIdx.end() &&
    1049        1045 :              oMapParquetColumnNameToIdx.find("bbox.xmax") !=
    1050        4117 :                  oMapParquetColumnNameToIdx.end() &&
    1051        1045 :              oMapParquetColumnNameToIdx.find("bbox.ymax") !=
    1052        1045 :                  oMapParquetColumnNameToIdx.end())
    1053             :     {
    1054           3 :         CPLJSONObject oDef = m_oMapGeometryColumns["geometry"];
    1055           2 :         CPLJSONObject oCovering;
    1056           1 :         oDef.Add("covering", oCovering);
    1057           1 :         CPLJSONObject oBBOX;
    1058           1 :         oCovering.Add("bbox", oBBOX);
    1059             :         {
    1060           1 :             CPLJSONArray oArray;
    1061           1 :             oArray.Add("bbox");
    1062           1 :             oArray.Add("xmin");
    1063           1 :             oBBOX.Add("xmin", oArray);
    1064             :         }
    1065             :         {
    1066           1 :             CPLJSONArray oArray;
    1067           1 :             oArray.Add("bbox");
    1068           1 :             oArray.Add("ymin");
    1069           1 :             oBBOX.Add("ymin", oArray);
    1070             :         }
    1071             :         {
    1072           1 :             CPLJSONArray oArray;
    1073           1 :             oArray.Add("bbox");
    1074           1 :             oArray.Add("xmax");
    1075           1 :             oBBOX.Add("xmax", oArray);
    1076             :         }
    1077             :         {
    1078           1 :             CPLJSONArray oArray;
    1079           1 :             oArray.Add("bbox");
    1080           1 :             oArray.Add("ymax");
    1081           1 :             oBBOX.Add("ymax", oArray);
    1082             :         }
    1083           1 :         oSetBBOXColumns.insert("bbox");
    1084           1 :         m_oMapGeometryColumns["geometry"] = std::move(oDef);
    1085             :     }
    1086             : 
    1087        1045 :     int iParquetCol = 0;
    1088       27272 :     for (int i = 0; i < m_poSchema->num_fields(); ++i)
    1089             :     {
    1090       26227 :         const auto &field = fields[i];
    1091             : 
    1092             :         bool bParquetColValid =
    1093       26227 :             CheckMatchArrowParquetColumnNames(iParquetCol, field);
    1094       26227 :         if (!bParquetColValid)
    1095           0 :             m_bHasMissingMappingToParquet = true;
    1096             : 
    1097       26271 :         if (!m_osFIDColumn.empty() && field->name() == m_osFIDColumn &&
    1098          44 :             (field->type()->id() == arrow::Type::INT32 ||
    1099          22 :              field->type()->id() == arrow::Type::INT64))
    1100             :         {
    1101          22 :             m_poFIDType = field->type();
    1102          22 :             m_iFIDArrowColumn = i;
    1103          22 :             if (bParquetColValid)
    1104             :             {
    1105          22 :                 m_iFIDParquetColumn = iParquetCol;
    1106          22 :                 iParquetCol++;
    1107             :             }
    1108         538 :             continue;
    1109             :         }
    1110             : 
    1111       26205 :         if (oSetBBOXColumns.find(field->name()) != oSetBBOXColumns.end())
    1112             :         {
    1113         516 :             m_oSetBBoxArrowColumns.insert(i);
    1114         516 :             if (bParquetColValid)
    1115         516 :                 iParquetCol++;
    1116         516 :             continue;
    1117             :         }
    1118             : 
    1119             :         const auto ComputeGeometryColumnTypeLambda =
    1120         891 :             [this, bParquetColValid, iParquetCol, &poParquetSchema]()
    1121             :         {
    1122             :             // only with GeoParquet < 0.2.0
    1123         594 :             if (bParquetColValid &&
    1124         297 :                 poParquetSchema->Column(iParquetCol)->physical_type() ==
    1125             :                     parquet::Type::BYTE_ARRAY)
    1126             :             {
    1127         297 :                 return ComputeGeometryColumnType(
    1128         594 :                     m_poFeatureDefn->GetGeomFieldCount(), iParquetCol);
    1129             :             }
    1130           0 :             return wkbUnknown;
    1131       25689 :         };
    1132             : 
    1133       77067 :         const bool bGeometryField = DealWithGeometryColumn(
    1134             :             i, field, ComputeGeometryColumnTypeLambda,
    1135       25689 :             bParquetColValid ? poParquetSchema->Column(iParquetCol) : nullptr,
    1136       25689 :             metadata.get(), bParquetColValid ? iParquetCol : -1);
    1137       25689 :         if (bGeometryField)
    1138             :         {
    1139        1023 :             const auto oIter = m_oMapGeometryColumns.find(field->name());
    1140        1023 :             if (bUseBBOX && oIter != m_oMapGeometryColumns.end())
    1141             :             {
    1142        1010 :                 ProcessGeometryColumnCovering(field, oIter->second,
    1143             :                                               oMapParquetColumnNameToIdx);
    1144             :             }
    1145             : 
    1146        3009 :             if (bParquetColValid &&
    1147        1986 :                 (field->type()->id() == arrow::Type::STRUCT ||
    1148         963 :                  field->type()->id() == arrow::Type::LIST))
    1149             :             {
    1150             :                 // GeoArrow types
    1151         492 :                 std::vector<int> anParquetCols;
    1152        3340 :                 for (const auto &iterParquetCols : oMapParquetColumnNameToIdx)
    1153             :                 {
    1154        2848 :                     if (STARTS_WITH(
    1155             :                             iterParquetCols.first.c_str(),
    1156             :                             std::string(field->name()).append(".").c_str()))
    1157             :                     {
    1158        1092 :                         iParquetCol =
    1159        1092 :                             std::max(iParquetCol, iterParquetCols.second);
    1160        1092 :                         anParquetCols.push_back(iterParquetCols.second);
    1161             :                     }
    1162             :                 }
    1163         492 :                 m_anMapGeomFieldIndexToParquetColumns.push_back(
    1164         492 :                     std::move(anParquetCols));
    1165         492 :                 ++iParquetCol;
    1166             :             }
    1167             :             else
    1168             :             {
    1169         531 :                 m_anMapGeomFieldIndexToParquetColumns.push_back(
    1170         531 :                     {bParquetColValid ? iParquetCol : -1});
    1171         531 :                 if (bParquetColValid)
    1172         531 :                     iParquetCol++;
    1173             :             }
    1174             :         }
    1175             :         else
    1176             :         {
    1177       24666 :             CreateFieldFromSchema(field, bParquetColValid, iParquetCol, {i},
    1178             :                                   oMapFieldNameToGDALSchemaFieldDefn);
    1179             :         }
    1180             :     }
    1181             : 
    1182        1045 :     CPLAssert(static_cast<int>(m_anMapFieldIndexToArrowColumn.size()) ==
    1183             :               m_poFeatureDefn->GetFieldCount());
    1184        1045 :     CPLAssert(static_cast<int>(m_anMapGeomFieldIndexToArrowColumn.size()) ==
    1185             :               m_poFeatureDefn->GetGeomFieldCount());
    1186        1045 :     CPLAssert(static_cast<int>(m_anMapGeomFieldIndexToParquetColumns.size()) ==
    1187             :               m_poFeatureDefn->GetGeomFieldCount());
    1188             : 
    1189        1045 :     if (!fields.empty())
    1190             :     {
    1191             :         try
    1192             :         {
    1193        2035 :             auto poRowGroup = m_poArrowReader->parquet_reader()->RowGroup(0);
    1194         991 :             if (poRowGroup)
    1195             :             {
    1196        1982 :                 auto poColumn = poRowGroup->metadata()->ColumnChunk(0);
    1197         991 :                 CPLDebug("PARQUET", "Compression (of first column): %s",
    1198             :                          arrow::util::Codec::GetCodecAsString(
    1199         991 :                              poColumn->compression())
    1200             :                              .c_str());
    1201             :             }
    1202             :         }
    1203          53 :         catch (const std::exception &)
    1204             :         {
    1205             :         }
    1206             :     }
    1207             : }
    1208             : 
    1209             : /************************************************************************/
    1210             : /*                  ProcessGeometryColumnCovering()                     */
    1211             : /************************************************************************/
    1212             : 
    1213             : /** Process GeoParquet JSON geometry field object to extract information about
    1214             :  * its bounding box column, and appropriately fill m_oMapGeomFieldIndexToGeomColBBOX
    1215             :  * and m_oMapGeomFieldIndexToGeomColBBOXParquet members with information on that
    1216             :  * bounding box column.
    1217             :  */
    1218        1010 : void OGRParquetLayer::ProcessGeometryColumnCovering(
    1219             :     const std::shared_ptr<arrow::Field> &field,
    1220             :     const CPLJSONObject &oJSONGeometryColumn,
    1221             :     const std::map<std::string, int> &oMapParquetColumnNameToIdx)
    1222             : {
    1223        2020 :     std::string osBBOXColumn;
    1224        2020 :     std::string osXMin, osYMin, osXMax, osYMax;
    1225        1010 :     if (ParseGeometryColumnCovering(oJSONGeometryColumn, osBBOXColumn, osXMin,
    1226             :                                     osYMin, osXMax, osYMax))
    1227             :     {
    1228         516 :         OGRArrowLayer::GeomColBBOX sDesc;
    1229         516 :         sDesc.iArrowCol = m_poSchema->GetFieldIndex(osBBOXColumn);
    1230        1032 :         const auto fieldBBOX = m_poSchema->GetFieldByName(osBBOXColumn);
    1231        1032 :         if (sDesc.iArrowCol >= 0 && fieldBBOX &&
    1232         516 :             fieldBBOX->type()->id() == arrow::Type::STRUCT)
    1233             :         {
    1234             :             const auto fieldBBOXStruct =
    1235        1032 :                 std::static_pointer_cast<arrow::StructType>(fieldBBOX->type());
    1236        1032 :             const auto fieldXMin = fieldBBOXStruct->GetFieldByName(osXMin);
    1237        1032 :             const auto fieldYMin = fieldBBOXStruct->GetFieldByName(osYMin);
    1238        1032 :             const auto fieldXMax = fieldBBOXStruct->GetFieldByName(osXMax);
    1239        1032 :             const auto fieldYMax = fieldBBOXStruct->GetFieldByName(osYMax);
    1240         516 :             const int nXMinIdx = fieldBBOXStruct->GetFieldIndex(osXMin);
    1241         516 :             const int nYMinIdx = fieldBBOXStruct->GetFieldIndex(osYMin);
    1242         516 :             const int nXMaxIdx = fieldBBOXStruct->GetFieldIndex(osXMax);
    1243         516 :             const int nYMaxIdx = fieldBBOXStruct->GetFieldIndex(osYMax);
    1244             :             const auto oIterParquetIdxXMin = oMapParquetColumnNameToIdx.find(
    1245         516 :                 std::string(osBBOXColumn).append(".").append(osXMin));
    1246             :             const auto oIterParquetIdxYMin = oMapParquetColumnNameToIdx.find(
    1247         516 :                 std::string(osBBOXColumn).append(".").append(osYMin));
    1248             :             const auto oIterParquetIdxXMax = oMapParquetColumnNameToIdx.find(
    1249         516 :                 std::string(osBBOXColumn).append(".").append(osXMax));
    1250             :             const auto oIterParquetIdxYMax = oMapParquetColumnNameToIdx.find(
    1251         516 :                 std::string(osBBOXColumn).append(".").append(osYMax));
    1252         516 :             if (nXMinIdx >= 0 && nYMinIdx >= 0 && nXMaxIdx >= 0 &&
    1253        1032 :                 nYMaxIdx >= 0 && fieldXMin && fieldYMin && fieldXMax &&
    1254        1032 :                 fieldYMax &&
    1255        1032 :                 oIterParquetIdxXMin != oMapParquetColumnNameToIdx.end() &&
    1256        1032 :                 oIterParquetIdxYMin != oMapParquetColumnNameToIdx.end() &&
    1257        1032 :                 oIterParquetIdxXMax != oMapParquetColumnNameToIdx.end() &&
    1258        1032 :                 oIterParquetIdxYMax != oMapParquetColumnNameToIdx.end() &&
    1259         517 :                 (fieldXMin->type()->id() == arrow::Type::FLOAT ||
    1260           1 :                  fieldXMin->type()->id() == arrow::Type::DOUBLE) &&
    1261         516 :                 fieldXMin->type()->id() == fieldYMin->type()->id() &&
    1262        1548 :                 fieldXMin->type()->id() == fieldXMax->type()->id() &&
    1263         516 :                 fieldXMin->type()->id() == fieldYMax->type()->id())
    1264             :             {
    1265         516 :                 CPLDebug("PARQUET",
    1266             :                          "Bounding box column '%s' detected for "
    1267             :                          "geometry column '%s'",
    1268         516 :                          osBBOXColumn.c_str(), field->name().c_str());
    1269         516 :                 sDesc.iArrowSubfieldXMin = nXMinIdx;
    1270         516 :                 sDesc.iArrowSubfieldYMin = nYMinIdx;
    1271         516 :                 sDesc.iArrowSubfieldXMax = nXMaxIdx;
    1272         516 :                 sDesc.iArrowSubfieldYMax = nYMaxIdx;
    1273         516 :                 sDesc.bIsFloat =
    1274         516 :                     (fieldXMin->type()->id() == arrow::Type::FLOAT);
    1275             : 
    1276             :                 m_oMapGeomFieldIndexToGeomColBBOX
    1277         516 :                     [m_poFeatureDefn->GetGeomFieldCount() - 1] =
    1278         516 :                         std::move(sDesc);
    1279             : 
    1280         516 :                 GeomColBBOXParquet sDescParquet;
    1281         516 :                 sDescParquet.iParquetXMin = oIterParquetIdxXMin->second;
    1282         516 :                 sDescParquet.iParquetYMin = oIterParquetIdxYMin->second;
    1283         516 :                 sDescParquet.iParquetXMax = oIterParquetIdxXMax->second;
    1284         516 :                 sDescParquet.iParquetYMax = oIterParquetIdxYMax->second;
    1285        5381 :                 for (const auto &iterParquetCols : oMapParquetColumnNameToIdx)
    1286             :                 {
    1287        4865 :                     if (STARTS_WITH(
    1288             :                             iterParquetCols.first.c_str(),
    1289             :                             std::string(osBBOXColumn).append(".").c_str()))
    1290             :                     {
    1291        2064 :                         sDescParquet.anParquetCols.push_back(
    1292        2064 :                             iterParquetCols.second);
    1293             :                     }
    1294             :                 }
    1295             :                 m_oMapGeomFieldIndexToGeomColBBOXParquet
    1296        1032 :                     [m_poFeatureDefn->GetGeomFieldCount() - 1] =
    1297        1032 :                         std::move(sDescParquet);
    1298             :             }
    1299             :         }
    1300             :     }
    1301        1010 : }
    1302             : 
    1303             : /************************************************************************/
    1304             : /*                               FindNode()                             */
    1305             : /************************************************************************/
    1306             : 
    1307      291898 : static const parquet::schema::Node *FindNode(const parquet::schema::Node *node,
    1308             :                                              const std::string &arrowFieldName)
    1309             : {
    1310      291898 :     CPLAssert(node);
    1311      291898 :     if (node->name() == arrowFieldName)
    1312             :     {
    1313        4837 :         return node;
    1314             :     }
    1315      287061 :     else if (node->is_group())
    1316             :     {
    1317             :         const auto groupNode =
    1318      113420 :             cpl::down_cast<const parquet::schema::GroupNode *>(node);
    1319      395642 :         for (int i = 0; i < groupNode->field_count(); ++i)
    1320             :         {
    1321             :             const auto found =
    1322      287059 :                 FindNode(groupNode->field(i).get(), arrowFieldName);
    1323      287059 :             if (found)
    1324        4837 :                 return found;
    1325             :         }
    1326             :     }
    1327      282224 :     return nullptr;
    1328             : }
    1329             : 
    1330             : /************************************************************************/
    1331             : /*                         CollectLeaveNodes()                          */
    1332             : /************************************************************************/
    1333             : 
    1334       12811 : static void CollectLeaveNodes(
    1335             :     const parquet::schema::Node *node,
    1336             :     const std::map<const parquet::schema::Node *, int> &oMapNodeToColIdx,
    1337             :     std::vector<int> &anParquetCols)
    1338             : {
    1339       12811 :     CPLAssert(node);
    1340       12811 :     if (node->is_primitive())
    1341             :     {
    1342        6976 :         const auto it = oMapNodeToColIdx.find(node);
    1343        6976 :         if (it != oMapNodeToColIdx.end())
    1344        6976 :             anParquetCols.push_back(it->second);
    1345             :     }
    1346        5835 :     else if (node->is_group())
    1347             :     {
    1348             :         const auto groupNode =
    1349        5835 :             cpl::down_cast<const parquet::schema::GroupNode *>(node);
    1350       13809 :         for (int i = 0; i < groupNode->field_count(); ++i)
    1351             :         {
    1352        7974 :             CollectLeaveNodes(groupNode->field(i).get(), oMapNodeToColIdx,
    1353             :                               anParquetCols);
    1354             :         }
    1355             :     }
    1356       12811 : }
    1357             : 
    1358             : /************************************************************************/
    1359             : /*                 GetParquetColumnIndicesForArrowField()               */
    1360             : /************************************************************************/
    1361             : 
    1362        4839 : std::vector<int> OGRParquetLayer::GetParquetColumnIndicesForArrowField(
    1363             :     const std::string &arrowFieldName) const
    1364             : {
    1365        9678 :     const auto metadata = m_poArrowReader->parquet_reader()->metadata();
    1366        4839 :     const auto schema = metadata->schema();
    1367             : 
    1368        4839 :     std::vector<int> anParquetCols;
    1369        4839 :     const auto *rootNode = schema->schema_root().get();
    1370        4839 :     const auto *fieldNode = FindNode(rootNode, arrowFieldName);
    1371        4839 :     if (!fieldNode)
    1372             :     {
    1373           2 :         CPLDebug("Parquet",
    1374             :                  "Cannot find Parquet node corresponding to Arrow field %s",
    1375             :                  arrowFieldName.c_str());
    1376           2 :         return anParquetCols;
    1377             :     }
    1378             : 
    1379             :     /// Build mapping from schema node to column index
    1380        9674 :     std::map<const parquet::schema::Node *, int> oMapNodeToColIdx;
    1381        4837 :     const int num_cols = schema->num_columns();
    1382      505128 :     for (int i = 0; i < num_cols; ++i)
    1383             :     {
    1384      500291 :         const auto *node = schema->Column(i)->schema_node().get();
    1385      500291 :         oMapNodeToColIdx[node] = i;
    1386             :     }
    1387             : 
    1388        4837 :     CollectLeaveNodes(fieldNode, oMapNodeToColIdx, anParquetCols);
    1389             : 
    1390        4837 :     return anParquetCols;
    1391             : }
    1392             : 
    1393             : /************************************************************************/
    1394             : /*                CheckMatchArrowParquetColumnNames()                   */
    1395             : /************************************************************************/
    1396             : 
    1397       28474 : bool OGRParquetLayer::CheckMatchArrowParquetColumnNames(
    1398             :     int &iParquetCol, const std::shared_ptr<arrow::Field> &field) const
    1399             : {
    1400       56948 :     const auto metadata = m_poArrowReader->parquet_reader()->metadata();
    1401       28474 :     const auto poParquetSchema = metadata->schema();
    1402       28474 :     const int nParquetColumns = poParquetSchema->num_columns();
    1403       28474 :     const auto &fieldName = field->name();
    1404       28474 :     const int iParquetColBefore = iParquetCol;
    1405             : 
    1406       29248 :     while (iParquetCol < nParquetColumns)
    1407             :     {
    1408       29248 :         const auto parquetColumn = poParquetSchema->Column(iParquetCol);
    1409       29248 :         const auto parquetColumnName = parquetColumn->path()->ToDotString();
    1410       62364 :         if (fieldName == parquetColumnName ||
    1411       16558 :             (parquetColumnName.size() > fieldName.size() &&
    1412       16558 :              STARTS_WITH(parquetColumnName.c_str(), fieldName.c_str()) &&
    1413       15784 :              parquetColumnName[fieldName.size()] == '.'))
    1414             :         {
    1415       28474 :             return true;
    1416             :         }
    1417             :         else
    1418             :         {
    1419         774 :             iParquetCol++;
    1420             :         }
    1421             :     }
    1422             : 
    1423           0 :     CPLError(CE_Warning, CPLE_AppDefined,
    1424             :              "Cannot match Arrow column name %s with a Parquet one",
    1425             :              fieldName.c_str());
    1426           0 :     iParquetCol = iParquetColBefore;
    1427           0 :     return false;
    1428             : }
    1429             : 
    1430             : /************************************************************************/
    1431             : /*                         CreateFieldFromSchema()                      */
    1432             : /************************************************************************/
    1433             : 
    1434       26913 : void OGRParquetLayer::CreateFieldFromSchema(
    1435             :     const std::shared_ptr<arrow::Field> &field, bool bParquetColValid,
    1436             :     int &iParquetCol, const std::vector<int> &path,
    1437             :     const std::map<std::string, std::unique_ptr<OGRFieldDefn>>
    1438             :         &oMapFieldNameToGDALSchemaFieldDefn)
    1439             : {
    1440       26913 :     OGRFieldDefn oField(field->name().c_str(), OFTString);
    1441       26913 :     OGRFieldType eType = OFTString;
    1442       26913 :     OGRFieldSubType eSubType = OFSTNone;
    1443       26913 :     bool bTypeOK = true;
    1444             : 
    1445       26913 :     auto type = field->type();
    1446       26913 :     if (type->id() == arrow::Type::DICTIONARY && path.size() == 1)
    1447             :     {
    1448             :         const auto dictionaryType =
    1449         598 :             std::static_pointer_cast<arrow::DictionaryType>(field->type());
    1450         598 :         auto indexType = dictionaryType->index_type();
    1451         598 :         if (dictionaryType->value_type()->id() == arrow::Type::STRING &&
    1452         299 :             IsIntegerArrowType(indexType->id()))
    1453             :         {
    1454         299 :             if (bParquetColValid)
    1455             :             {
    1456         598 :                 std::string osDomainName(field->name() + "Domain");
    1457         299 :                 m_poDS->RegisterDomainName(osDomainName,
    1458         299 :                                            m_poFeatureDefn->GetFieldCount());
    1459         299 :                 oField.SetDomainName(osDomainName);
    1460             :             }
    1461         299 :             type = std::move(indexType);
    1462             :         }
    1463             :         else
    1464             :         {
    1465           0 :             bTypeOK = false;
    1466             :         }
    1467             :     }
    1468             : 
    1469       26913 :     int nParquetColIncrement = 1;
    1470       26913 :     switch (type->id())
    1471             :     {
    1472         644 :         case arrow::Type::STRUCT:
    1473             :         {
    1474        1288 :             const auto subfields = field->Flatten();
    1475        1288 :             auto newpath = path;
    1476         644 :             newpath.push_back(0);
    1477        2891 :             for (int j = 0; j < static_cast<int>(subfields.size()); j++)
    1478             :             {
    1479        2247 :                 const auto &subfield = subfields[j];
    1480             :                 bParquetColValid =
    1481        2247 :                     CheckMatchArrowParquetColumnNames(iParquetCol, subfield);
    1482        2247 :                 if (!bParquetColValid)
    1483           0 :                     m_bHasMissingMappingToParquet = true;
    1484        2247 :                 newpath.back() = j;
    1485        2247 :                 CreateFieldFromSchema(subfield, bParquetColValid, iParquetCol,
    1486             :                                       newpath,
    1487             :                                       oMapFieldNameToGDALSchemaFieldDefn);
    1488             :             }
    1489         644 :             return;  // return intended, not break
    1490             :         }
    1491             : 
    1492        5353 :         case arrow::Type::MAP:
    1493             :         {
    1494             :             // A arrow map maps to 2 Parquet columns
    1495        5353 :             nParquetColIncrement = 2;
    1496        5353 :             break;
    1497             :         }
    1498             : 
    1499       20916 :         default:
    1500       20916 :             break;
    1501             :     }
    1502             : 
    1503       26269 :     if (bTypeOK)
    1504             :     {
    1505       26269 :         bTypeOK = MapArrowTypeToOGR(type, field, oField, eType, eSubType, path,
    1506             :                                     oMapFieldNameToGDALSchemaFieldDefn);
    1507       26269 :         if (bTypeOK)
    1508             :         {
    1509       25979 :             m_apoArrowDataTypes.push_back(std::move(type));
    1510             :         }
    1511             :     }
    1512             : 
    1513       26269 :     if (bParquetColValid)
    1514       26269 :         iParquetCol += nParquetColIncrement;
    1515             : }
    1516             : 
    1517             : /************************************************************************/
    1518             : /*                          BuildDomain()                               */
    1519             : /************************************************************************/
    1520             : 
    1521             : std::unique_ptr<OGRFieldDomain>
    1522          16 : OGRParquetLayer::BuildDomain(const std::string &osDomainName,
    1523             :                              int iFieldIndex) const
    1524             : {
    1525          16 :     const int iArrowCol = m_anMapFieldIndexToArrowColumn[iFieldIndex][0];
    1526          32 :     const std::string osArrowColName = m_poSchema->fields()[iArrowCol]->name();
    1527          16 :     CPLAssert(m_poSchema->fields()[iArrowCol]->type()->id() ==
    1528             :               arrow::Type::DICTIONARY);
    1529             :     const auto anParquetColsForField =
    1530          48 :         GetParquetColumnIndicesForArrowField(osArrowColName.c_str());
    1531          16 :     CPLAssert(!anParquetColsForField.empty());
    1532          16 :     const auto oldBatchSize = m_poArrowReader->properties().batch_size();
    1533          16 :     m_poArrowReader->set_batch_size(1);
    1534             : #if PARQUET_VERSION_MAJOR >= 21
    1535             :     std::unique_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1536             :     auto result =
    1537             :         m_poArrowReader->GetRecordBatchReader({0}, anParquetColsForField);
    1538             :     if (result.ok())
    1539             :         poRecordBatchReader = std::move(*result);
    1540             : #else
    1541          16 :     std::shared_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1542          16 :     CPL_IGNORE_RET_VAL(m_poArrowReader->GetRecordBatchReader(
    1543             :         {0}, anParquetColsForField, &poRecordBatchReader));
    1544             : #endif
    1545          16 :     if (poRecordBatchReader != nullptr)
    1546             :     {
    1547           0 :         std::shared_ptr<arrow::RecordBatch> poBatch;
    1548          16 :         auto status = poRecordBatchReader->ReadNext(&poBatch);
    1549          16 :         if (!status.ok())
    1550             :         {
    1551           0 :             CPLError(CE_Failure, CPLE_AppDefined, "ReadNext() failed: %s",
    1552           0 :                      status.message().c_str());
    1553             :         }
    1554          16 :         else if (poBatch)
    1555             :         {
    1556          16 :             m_poArrowReader->set_batch_size(oldBatchSize);
    1557          16 :             return BuildDomainFromBatch(osDomainName, poBatch, 0);
    1558             :         }
    1559             :     }
    1560           0 :     m_poArrowReader->set_batch_size(oldBatchSize);
    1561           0 :     return nullptr;
    1562             : }
    1563             : 
    1564             : /************************************************************************/
    1565             : /*                     ComputeGeometryColumnType()                      */
    1566             : /************************************************************************/
    1567             : 
    1568             : OGRwkbGeometryType
    1569         297 : OGRParquetLayer::ComputeGeometryColumnType(int iGeomCol, int iParquetCol) const
    1570             : {
    1571             :     // Compute type of geometry column by iterating over each geometry, and
    1572             :     // looking at the WKB geometry type in the first 5 bytes of each geometry.
    1573             : 
    1574         297 :     OGRwkbGeometryType eGeomType = wkbNone;
    1575             : 
    1576         594 :     std::vector<int> anRowGroups;
    1577         297 :     const int nNumGroups = m_poArrowReader->num_row_groups();
    1578         297 :     anRowGroups.reserve(nNumGroups);
    1579         884 :     for (int i = 0; i < nNumGroups; ++i)
    1580         587 :         anRowGroups.push_back(i);
    1581             : #if PARQUET_VERSION_MAJOR >= 21
    1582             :     std::unique_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1583             :     auto result =
    1584             :         m_poArrowReader->GetRecordBatchReader(anRowGroups, {iParquetCol});
    1585             :     if (result.ok())
    1586             :         poRecordBatchReader = std::move(*result);
    1587             : #else
    1588           0 :     std::shared_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1589         297 :     CPL_IGNORE_RET_VAL(m_poArrowReader->GetRecordBatchReader(
    1590             :         anRowGroups, {iParquetCol}, &poRecordBatchReader));
    1591             : #endif
    1592         297 :     if (poRecordBatchReader != nullptr)
    1593             :     {
    1594         594 :         std::shared_ptr<arrow::RecordBatch> poBatch;
    1595             :         while (true)
    1596             :         {
    1597         596 :             auto status = poRecordBatchReader->ReadNext(&poBatch);
    1598         596 :             if (!status.ok())
    1599             :             {
    1600           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "ReadNext() failed: %s",
    1601           0 :                          status.message().c_str());
    1602           0 :                 break;
    1603             :             }
    1604         596 :             else if (!poBatch)
    1605         295 :                 break;
    1606             : 
    1607         301 :             eGeomType = ComputeGeometryColumnTypeProcessBatch(poBatch, iGeomCol,
    1608             :                                                               0, eGeomType);
    1609         301 :             if (eGeomType == wkbUnknown)
    1610           2 :                 break;
    1611         299 :         }
    1612             :     }
    1613             : 
    1614         594 :     return eGeomType == wkbNone ? wkbUnknown : eGeomType;
    1615             : }
    1616             : 
    1617             : /************************************************************************/
    1618             : /*                       GetFeatureExplicitFID()                        */
    1619             : /************************************************************************/
    1620             : 
    1621           4 : OGRFeature *OGRParquetLayer::GetFeatureExplicitFID(GIntBig nFID)
    1622             : {
    1623           8 :     std::vector<int> anRowGroups;
    1624           4 :     const int nNumGroups = m_poArrowReader->num_row_groups();
    1625           4 :     anRowGroups.reserve(nNumGroups);
    1626          16 :     for (int i = 0; i < nNumGroups; ++i)
    1627          12 :         anRowGroups.push_back(i);
    1628             : #if PARQUET_VERSION_MAJOR >= 21
    1629             :     std::unique_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1630             :     auto result = m_bIgnoredFields
    1631             :                       ? m_poArrowReader->GetRecordBatchReader(
    1632             :                             anRowGroups, m_anRequestedParquetColumns)
    1633             :                       : m_poArrowReader->GetRecordBatchReader(anRowGroups);
    1634             :     if (result.ok())
    1635             :     {
    1636             :         poRecordBatchReader = std::move(*result);
    1637             :     }
    1638             : #else
    1639           4 :     std::shared_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1640           4 :     if (m_bIgnoredFields)
    1641             :     {
    1642           4 :         CPL_IGNORE_RET_VAL(m_poArrowReader->GetRecordBatchReader(
    1643           2 :             anRowGroups, m_anRequestedParquetColumns, &poRecordBatchReader));
    1644             :     }
    1645             :     else
    1646             :     {
    1647           2 :         CPL_IGNORE_RET_VAL(m_poArrowReader->GetRecordBatchReader(
    1648             :             anRowGroups, &poRecordBatchReader));
    1649             :     }
    1650             : #endif
    1651           4 :     if (poRecordBatchReader != nullptr)
    1652             :     {
    1653           4 :         std::shared_ptr<arrow::RecordBatch> poBatch;
    1654             :         while (true)
    1655             :         {
    1656          14 :             auto status = poRecordBatchReader->ReadNext(&poBatch);
    1657          14 :             if (!status.ok())
    1658             :             {
    1659           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "ReadNext() failed: %s",
    1660           0 :                          status.message().c_str());
    1661           0 :                 break;
    1662             :             }
    1663          14 :             else if (!poBatch)
    1664           2 :                 break;
    1665             : 
    1666          12 :             const auto array = poBatch->column(
    1667          12 :                 m_bIgnoredFields ? m_nRequestedFIDColumn : m_iFIDArrowColumn);
    1668          12 :             const auto arrayPtr = array.get();
    1669          12 :             const auto arrayTypeId = array->type_id();
    1670          30 :             for (int64_t nIdxInBatch = 0; nIdxInBatch < poBatch->num_rows();
    1671             :                  nIdxInBatch++)
    1672             :             {
    1673          20 :                 if (!array->IsNull(nIdxInBatch))
    1674             :                 {
    1675          20 :                     if (arrayTypeId == arrow::Type::INT64)
    1676             :                     {
    1677          20 :                         const auto castArray =
    1678             :                             static_cast<const arrow::Int64Array *>(arrayPtr);
    1679          20 :                         if (castArray->Value(nIdxInBatch) == nFID)
    1680             :                         {
    1681           2 :                             return ReadFeature(nIdxInBatch, poBatch->columns());
    1682             :                         }
    1683             :                     }
    1684           0 :                     else if (arrayTypeId == arrow::Type::INT32)
    1685             :                     {
    1686           0 :                         const auto castArray =
    1687             :                             static_cast<const arrow::Int32Array *>(arrayPtr);
    1688           0 :                         if (castArray->Value(nIdxInBatch) == nFID)
    1689             :                         {
    1690           0 :                             return ReadFeature(nIdxInBatch, poBatch->columns());
    1691             :                         }
    1692             :                     }
    1693             :                 }
    1694             :             }
    1695          10 :         }
    1696             :     }
    1697           2 :     return nullptr;
    1698             : }
    1699             : 
    1700             : /************************************************************************/
    1701             : /*                         GetFeatureByIndex()                          */
    1702             : /************************************************************************/
    1703             : 
    1704          64 : OGRFeature *OGRParquetLayer::GetFeatureByIndex(GIntBig nFID)
    1705             : {
    1706             : 
    1707          64 :     if (nFID < 0)
    1708           5 :         return nullptr;
    1709             : 
    1710         118 :     const auto metadata = m_poArrowReader->parquet_reader()->metadata();
    1711          59 :     const int nNumGroups = m_poArrowReader->num_row_groups();
    1712          59 :     int64_t nAccRows = 0;
    1713          72 :     for (int iGroup = 0; iGroup < nNumGroups; ++iGroup)
    1714             :     {
    1715             :         const int64_t nNextAccRows =
    1716          63 :             nAccRows + metadata->RowGroup(iGroup)->num_rows();
    1717          63 :         if (nFID < nNextAccRows)
    1718             :         {
    1719             : #if PARQUET_VERSION_MAJOR >= 21
    1720             :             std::unique_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1721             :             auto result = m_bIgnoredFields
    1722             :                               ? m_poArrowReader->GetRecordBatchReader(
    1723             :                                     {iGroup}, m_anRequestedParquetColumns)
    1724             :                               : m_poArrowReader->GetRecordBatchReader({iGroup});
    1725             :             if (result.ok())
    1726             :             {
    1727             :                 poRecordBatchReader = std::move(*result);
    1728             :             }
    1729             :             else
    1730             :             {
    1731             :                 CPLError(CE_Failure, CPLE_AppDefined,
    1732             :                          "GetRecordBatchReader() failed: %s",
    1733             :                          result.status().message().c_str());
    1734             :                 return nullptr;
    1735             :             }
    1736             : #else
    1737          50 :             std::shared_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1738             :             {
    1739           0 :                 arrow::Status status;
    1740          50 :                 if (m_bIgnoredFields)
    1741             :                 {
    1742           0 :                     status = m_poArrowReader->GetRecordBatchReader(
    1743           0 :                         {iGroup}, m_anRequestedParquetColumns,
    1744           0 :                         &poRecordBatchReader);
    1745             :                 }
    1746             :                 else
    1747             :                 {
    1748         100 :                     status = m_poArrowReader->GetRecordBatchReader(
    1749          50 :                         {iGroup}, &poRecordBatchReader);
    1750             :                 }
    1751          50 :                 if (poRecordBatchReader == nullptr)
    1752             :                 {
    1753           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1754             :                              "GetRecordBatchReader() failed: %s",
    1755           0 :                              status.message().c_str());
    1756           0 :                     return nullptr;
    1757             :                 }
    1758             :             }
    1759             : #endif
    1760             : 
    1761          50 :             const int64_t nExpectedIdxInGroup = nFID - nAccRows;
    1762          50 :             int64_t nIdxInGroup = 0;
    1763             :             while (true)
    1764             :             {
    1765           0 :                 std::shared_ptr<arrow::RecordBatch> poBatch;
    1766          50 :                 arrow::Status status = poRecordBatchReader->ReadNext(&poBatch);
    1767          50 :                 if (!status.ok())
    1768             :                 {
    1769           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1770           0 :                              "ReadNext() failed: %s", status.message().c_str());
    1771           0 :                     return nullptr;
    1772             :                 }
    1773          50 :                 if (poBatch == nullptr)
    1774             :                 {
    1775           0 :                     return nullptr;
    1776             :                 }
    1777          50 :                 if (nExpectedIdxInGroup < nIdxInGroup + poBatch->num_rows())
    1778             :                 {
    1779          50 :                     const auto nIdxInBatch = nExpectedIdxInGroup - nIdxInGroup;
    1780             :                     auto poFeature =
    1781          50 :                         ReadFeature(nIdxInBatch, poBatch->columns());
    1782          50 :                     poFeature->SetFID(nFID);
    1783          50 :                     return poFeature;
    1784             :                 }
    1785           0 :                 nIdxInGroup += poBatch->num_rows();
    1786           0 :             }
    1787             :         }
    1788          13 :         nAccRows = nNextAccRows;
    1789             :     }
    1790           9 :     return nullptr;
    1791             : }
    1792             : 
    1793             : /************************************************************************/
    1794             : /*                           GetFeature()                               */
    1795             : /************************************************************************/
    1796             : 
    1797          68 : OGRFeature *OGRParquetLayer::GetFeature(GIntBig nFID)
    1798             : {
    1799          68 :     if (!m_osFIDColumn.empty())
    1800             :     {
    1801           4 :         return GetFeatureExplicitFID(nFID);
    1802             :     }
    1803             :     else
    1804             :     {
    1805          64 :         return GetFeatureByIndex(nFID);
    1806             :     }
    1807             : }
    1808             : 
    1809             : /************************************************************************/
    1810             : /*                           ResetReading()                             */
    1811             : /************************************************************************/
    1812             : 
    1813        4561 : void OGRParquetLayer::ResetReading()
    1814             : {
    1815        4561 :     OGRParquetLayerBase::ResetReading();
    1816        4561 :     m_oFeatureIdxRemappingIter = m_asFeatureIdxRemapping.begin();
    1817        4561 :     m_nFeatureIdxSelected = 0;
    1818        4561 :     if (!m_asFeatureIdxRemapping.empty())
    1819             :     {
    1820        2202 :         m_nFeatureIdx = m_oFeatureIdxRemappingIter->second;
    1821        2202 :         ++m_oFeatureIdxRemappingIter;
    1822             :     }
    1823        4561 : }
    1824             : 
    1825             : /************************************************************************/
    1826             : /*                      CreateRecordBatchReader()                       */
    1827             : /************************************************************************/
    1828             : 
    1829         681 : bool OGRParquetLayer::CreateRecordBatchReader(int iStartingRowGroup)
    1830             : {
    1831        1362 :     std::vector<int> anRowGroups;
    1832         681 :     const int nNumGroups = m_poArrowReader->num_row_groups();
    1833         681 :     anRowGroups.reserve(nNumGroups - iStartingRowGroup);
    1834        1715 :     for (int i = iStartingRowGroup; i < nNumGroups; ++i)
    1835        1034 :         anRowGroups.push_back(i);
    1836        1362 :     return CreateRecordBatchReader(anRowGroups);
    1837             : }
    1838             : 
    1839         981 : bool OGRParquetLayer::CreateRecordBatchReader(
    1840             :     const std::vector<int> &anRowGroups)
    1841             : {
    1842             : #if PARQUET_VERSION_MAJOR >= 21
    1843             :     auto result = m_bIgnoredFields
    1844             :                       ? m_poArrowReader->GetRecordBatchReader(
    1845             :                             anRowGroups, m_anRequestedParquetColumns)
    1846             :                       : m_poArrowReader->GetRecordBatchReader(anRowGroups);
    1847             :     if (result.ok())
    1848             :     {
    1849             :         m_poRecordBatchReader = std::move(*result);
    1850             :         return true;
    1851             :     }
    1852             :     else
    1853             :     {
    1854             :         CPLError(CE_Failure, CPLE_AppDefined,
    1855             :                  "GetRecordBatchReader() failed: %s",
    1856             :                  result.status().message().c_str());
    1857             :         return false;
    1858             :     }
    1859             : #else
    1860         981 :     arrow::Status status;
    1861         981 :     if (m_bIgnoredFields)
    1862             :     {
    1863         470 :         status = m_poArrowReader->GetRecordBatchReader(
    1864         235 :             anRowGroups, m_anRequestedParquetColumns, &m_poRecordBatchReader);
    1865             :     }
    1866             :     else
    1867             :     {
    1868        1492 :         status = m_poArrowReader->GetRecordBatchReader(anRowGroups,
    1869         746 :                                                        &m_poRecordBatchReader);
    1870             :     }
    1871         981 :     if (m_poRecordBatchReader == nullptr)
    1872             :     {
    1873           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1874           0 :                  "GetRecordBatchReader() failed: %s", status.message().c_str());
    1875           0 :         return false;
    1876             :     }
    1877         981 :     return true;
    1878             : #endif
    1879             : }
    1880             : 
    1881             : /************************************************************************/
    1882             : /*                       IsConstraintPossible()                         */
    1883             : /************************************************************************/
    1884             : 
    1885             : enum class IsConstraintPossibleRes
    1886             : {
    1887             :     YES,
    1888             :     NO,
    1889             :     UNKNOWN
    1890             : };
    1891             : 
    1892             : template <class T>
    1893         224 : static IsConstraintPossibleRes IsConstraintPossible(int nOperation, T v, T min,
    1894             :                                                     T max)
    1895             : {
    1896         224 :     if (nOperation == SWQ_EQ)
    1897             :     {
    1898         146 :         if (v < min || v > max)
    1899             :         {
    1900          59 :             return IsConstraintPossibleRes::NO;
    1901             :         }
    1902             :     }
    1903          78 :     else if (nOperation == SWQ_NE)
    1904             :     {
    1905          38 :         if (v == min && v == max)
    1906             :         {
    1907           0 :             return IsConstraintPossibleRes::NO;
    1908             :         }
    1909             :     }
    1910          40 :     else if (nOperation == SWQ_LE)
    1911             :     {
    1912          10 :         if (v < min)
    1913             :         {
    1914           4 :             return IsConstraintPossibleRes::NO;
    1915             :         }
    1916             :     }
    1917          30 :     else if (nOperation == SWQ_LT)
    1918             :     {
    1919          10 :         if (v <= min)
    1920             :         {
    1921           4 :             return IsConstraintPossibleRes::NO;
    1922             :         }
    1923             :     }
    1924          20 :     else if (nOperation == SWQ_GE)
    1925             :     {
    1926          10 :         if (v > max)
    1927             :         {
    1928           4 :             return IsConstraintPossibleRes::NO;
    1929             :         }
    1930             :     }
    1931          10 :     else if (nOperation == SWQ_GT)
    1932             :     {
    1933          10 :         if (v >= max)
    1934             :         {
    1935           6 :             return IsConstraintPossibleRes::NO;
    1936             :         }
    1937             :     }
    1938             :     else
    1939             :     {
    1940           0 :         CPLDebug("PARQUET",
    1941             :                  "IsConstraintPossible: Unhandled operation type: %d",
    1942             :                  nOperation);
    1943           0 :         return IsConstraintPossibleRes::UNKNOWN;
    1944             :     }
    1945         147 :     return IsConstraintPossibleRes::YES;
    1946             : }
    1947             : 
    1948             : /************************************************************************/
    1949             : /*                           IncrFeatureIdx()                           */
    1950             : /************************************************************************/
    1951             : 
    1952        8154 : void OGRParquetLayer::IncrFeatureIdx()
    1953             : {
    1954        8154 :     ++m_nFeatureIdxSelected;
    1955        8154 :     ++m_nFeatureIdx;
    1956        9309 :     if (m_iFIDArrowColumn < 0 && !m_asFeatureIdxRemapping.empty() &&
    1957        9309 :         m_oFeatureIdxRemappingIter != m_asFeatureIdxRemapping.end())
    1958             :     {
    1959         140 :         if (m_nFeatureIdxSelected == m_oFeatureIdxRemappingIter->first)
    1960             :         {
    1961          48 :             m_nFeatureIdx = m_oFeatureIdxRemappingIter->second;
    1962          48 :             ++m_oFeatureIdxRemappingIter;
    1963             :         }
    1964             :     }
    1965        8154 : }
    1966             : 
    1967             : /************************************************************************/
    1968             : /*                           ReadNextBatch()                            */
    1969             : /************************************************************************/
    1970             : 
    1971        2107 : bool OGRParquetLayer::ReadNextBatch()
    1972             : {
    1973        2107 :     m_nIdxInBatch = 0;
    1974             : 
    1975        2107 :     const int nNumGroups = m_poArrowReader->num_row_groups();
    1976        2107 :     if (nNumGroups == 0)
    1977           2 :         return false;
    1978             : 
    1979        2105 :     if (m_bSingleBatch)
    1980             :     {
    1981          32 :         CPLAssert(m_iRecordBatch == 0);
    1982          32 :         CPLAssert(m_poBatch != nullptr);
    1983          32 :         return false;
    1984             :     }
    1985             : 
    1986        2073 :     CPLAssert((m_iRecordBatch == -1 && m_poRecordBatchReader == nullptr) ||
    1987             :               (m_iRecordBatch >= 0 && m_poRecordBatchReader != nullptr));
    1988             : 
    1989        2073 :     if (m_poRecordBatchReader == nullptr)
    1990             :     {
    1991         987 :         m_asFeatureIdxRemapping.clear();
    1992             : 
    1993         987 :         bool bIterateEverything = false;
    1994         987 :         std::vector<int> anSelectedGroups;
    1995             :         const auto oIterToGeomColBBOX =
    1996         987 :             m_oMapGeomFieldIndexToGeomColBBOXParquet.find(m_iGeomFieldFilter);
    1997             :         const bool bUSEBBOXFields =
    1998         243 :             (m_poFilterGeom &&
    1999         243 :              oIterToGeomColBBOX !=
    2000        1230 :                  m_oMapGeomFieldIndexToGeomColBBOXParquet.end() &&
    2001         141 :              CPLTestBool(CPLGetConfigOption(
    2002        1128 :                  ("OGR_" + GetDriverUCName() + "_USE_BBOX").c_str(), "YES")));
    2003             :         const bool bIsGeoArrowStruct =
    2004        1974 :             (m_iGeomFieldFilter >= 0 &&
    2005         987 :              m_iGeomFieldFilter < static_cast<int>(m_aeGeomEncoding.size()) &&
    2006         979 :              m_iGeomFieldFilter <
    2007             :                  static_cast<int>(
    2008        1958 :                      m_anMapGeomFieldIndexToParquetColumns.size()) &&
    2009         979 :              m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter].size() >=
    2010        1974 :                  2 &&
    2011         306 :              OGRArrowIsGeoArrowStruct(m_aeGeomEncoding[m_iGeomFieldFilter]));
    2012             : #if PARQUET_VERSION_MAJOR >= 21
    2013             :         const bool bUseParquetGeoStat =
    2014             :             (m_poFilterGeom && m_iGeomFieldFilter >= 0 &&
    2015             :              m_geoStatsWithBBOXAvailable.find(m_iGeomFieldFilter) !=
    2016             :                  m_geoStatsWithBBOXAvailable.end() &&
    2017             :              m_iGeomFieldFilter <
    2018             :                  static_cast<int>(
    2019             :                      m_anMapGeomFieldIndexToParquetColumns.size()) &&
    2020             :              m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter].size() ==
    2021             :                  1 &&
    2022             :              m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter][0] >= 0);
    2023             : #endif
    2024        1700 :         if (m_asAttributeFilterConstraints.empty() && !bUSEBBOXFields &&
    2025         713 :             !(bIsGeoArrowStruct && m_poFilterGeom)
    2026             : #if PARQUET_VERSION_MAJOR >= 21
    2027             :             && !bUseParquetGeoStat
    2028             : #endif
    2029             :         )
    2030             :         {
    2031         667 :             bIterateEverything = true;
    2032             :         }
    2033             :         else
    2034             :         {
    2035             :             OGRField sMin;
    2036             :             OGRField sMax;
    2037         320 :             OGR_RawField_SetNull(&sMin);
    2038         320 :             OGR_RawField_SetNull(&sMax);
    2039         320 :             bool bFoundMin = false;
    2040         320 :             bool bFoundMax = false;
    2041         320 :             OGRFieldType eType = OFTMaxType;
    2042         320 :             OGRFieldSubType eSubType = OFSTNone;
    2043         640 :             std::string osMinTmp, osMaxTmp;
    2044         320 :             int64_t nFeatureIdxSelected = 0;
    2045         320 :             int64_t nFeatureIdxTotal = 0;
    2046             : 
    2047         320 :             int iXMinField = -1;
    2048         320 :             int iYMinField = -1;
    2049         320 :             int iXMaxField = -1;
    2050         320 :             int iYMaxField = -1;
    2051             : 
    2052         320 :             if (bIsGeoArrowStruct)
    2053             :             {
    2054             :                 const auto metadata =
    2055         276 :                     m_poArrowReader->parquet_reader()->metadata();
    2056         138 :                 const auto poParquetSchema = metadata->schema();
    2057         342 :                 for (int iParquetCol :
    2058         822 :                      m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter])
    2059             :                 {
    2060             :                     const auto parquetColumn =
    2061         342 :                         poParquetSchema->Column(iParquetCol);
    2062             :                     const auto parquetColumnName =
    2063         684 :                         parquetColumn->path()->ToDotString();
    2064         684 :                     if (parquetColumnName.size() > 2 &&
    2065         342 :                         parquetColumnName.find(".x") ==
    2066         342 :                             parquetColumnName.size() - 2)
    2067             :                     {
    2068         138 :                         iXMinField = iParquetCol;
    2069         138 :                         iXMaxField = iParquetCol;
    2070             :                     }
    2071         408 :                     else if (parquetColumnName.size() > 2 &&
    2072         204 :                              parquetColumnName.find(".y") ==
    2073         204 :                                  parquetColumnName.size() - 2)
    2074             :                     {
    2075         138 :                         iYMinField = iParquetCol;
    2076         138 :                         iYMaxField = iParquetCol;
    2077             :                     }
    2078             :                 }
    2079             :             }
    2080         182 :             else if (bUSEBBOXFields)
    2081             :             {
    2082          49 :                 iXMinField = oIterToGeomColBBOX->second.iParquetXMin;
    2083          49 :                 iYMinField = oIterToGeomColBBOX->second.iParquetYMin;
    2084          49 :                 iXMaxField = oIterToGeomColBBOX->second.iParquetXMax;
    2085          49 :                 iYMaxField = oIterToGeomColBBOX->second.iParquetYMax;
    2086             :             }
    2087             : 
    2088         765 :             for (int iRowGroup = 0;
    2089         765 :                  iRowGroup < nNumGroups && !bIterateEverything; ++iRowGroup)
    2090             :             {
    2091         445 :                 bool bSelectGroup = true;
    2092             :                 auto poRowGroup =
    2093         445 :                     GetReader()->parquet_reader()->RowGroup(iRowGroup);
    2094             : 
    2095         445 :                 if (iXMinField >= 0 && iYMinField >= 0 && iXMaxField >= 0 &&
    2096             :                     iYMaxField >= 0)
    2097             :                 {
    2098         195 :                     if (GetMinMaxForParquetCol(iRowGroup, iXMinField, nullptr,
    2099             :                                                true, sMin, bFoundMin, false,
    2100             :                                                sMax, bFoundMax, eType, eSubType,
    2101         194 :                                                osMinTmp, osMaxTmp) &&
    2102         389 :                         bFoundMin && eType == OFTReal)
    2103             :                     {
    2104         194 :                         const double dfGroupMinX = sMin.Real;
    2105         194 :                         if (dfGroupMinX > m_sFilterEnvelope.MaxX)
    2106             :                         {
    2107           1 :                             bSelectGroup = false;
    2108             :                         }
    2109         193 :                         else if (GetMinMaxForParquetCol(
    2110             :                                      iRowGroup, iYMinField, nullptr, true, sMin,
    2111             :                                      bFoundMin, false, sMax, bFoundMax, eType,
    2112         193 :                                      eSubType, osMinTmp, osMaxTmp) &&
    2113         386 :                                  bFoundMin && eType == OFTReal)
    2114             :                         {
    2115         193 :                             const double dfGroupMinY = sMin.Real;
    2116         193 :                             if (dfGroupMinY > m_sFilterEnvelope.MaxY)
    2117             :                             {
    2118           1 :                                 bSelectGroup = false;
    2119             :                             }
    2120         192 :                             else if (GetMinMaxForParquetCol(
    2121             :                                          iRowGroup, iXMaxField, nullptr, false,
    2122             :                                          sMin, bFoundMin, true, sMax, bFoundMax,
    2123         192 :                                          eType, eSubType, osMinTmp, osMaxTmp) &&
    2124         384 :                                      bFoundMax && eType == OFTReal)
    2125             :                             {
    2126         192 :                                 const double dfGroupMaxX = sMax.Real;
    2127         192 :                                 if (dfGroupMaxX < m_sFilterEnvelope.MinX)
    2128             :                                 {
    2129           1 :                                     bSelectGroup = false;
    2130             :                                 }
    2131         191 :                                 else if (GetMinMaxForParquetCol(
    2132             :                                              iRowGroup, iYMaxField, nullptr,
    2133             :                                              false, sMin, bFoundMin, true, sMax,
    2134             :                                              bFoundMax, eType, eSubType,
    2135         191 :                                              osMinTmp, osMaxTmp) &&
    2136         382 :                                          bFoundMax && eType == OFTReal)
    2137             :                                 {
    2138         191 :                                     const double dfGroupMaxY = sMax.Real;
    2139         191 :                                     if (dfGroupMaxY < m_sFilterEnvelope.MinY)
    2140             :                                     {
    2141           1 :                                         bSelectGroup = false;
    2142             :                                     }
    2143             :                                 }
    2144             :                             }
    2145             :                         }
    2146             :                     }
    2147             :                 }
    2148             : #if PARQUET_VERSION_MAJOR >= 21
    2149             :                 else if (bUseParquetGeoStat)
    2150             :                 {
    2151             :                     const int iParquetCol =
    2152             :                         m_anMapGeomFieldIndexToParquetColumns
    2153             :                             [m_iGeomFieldFilter][0];
    2154             :                     CPLAssert(iParquetCol >= 0);
    2155             : 
    2156             :                     const auto metadata =
    2157             :                         m_poArrowReader->parquet_reader()->metadata();
    2158             :                     const auto columnChunk =
    2159             :                         metadata->RowGroup(iRowGroup)->ColumnChunk(iParquetCol);
    2160             :                     if (auto geostats = columnChunk->geo_statistics())
    2161             :                     {
    2162             :                         if (geostats->dimension_valid()[0] &&
    2163             :                             geostats->dimension_valid()[1])
    2164             :                         {
    2165             :                             double dfMinX = geostats->lower_bound()[0];
    2166             :                             double dfMaxX = geostats->upper_bound()[0];
    2167             :                             double dfMinY = geostats->lower_bound()[1];
    2168             :                             double dfMaxY = geostats->upper_bound()[1];
    2169             : 
    2170             :                             // Deal as best as we can with wrap around bounding box
    2171             :                             if (dfMinX > dfMaxX && std::fabs(dfMinX) <= 180 &&
    2172             :                                 std::fabs(dfMaxX) <= 180)
    2173             :                             {
    2174             :                                 dfMinX = -180;
    2175             :                                 dfMaxX = 180;
    2176             :                             }
    2177             : 
    2178             :                             // Check if there is an intersection between
    2179             :                             // the geostatistics for this rowgroup and
    2180             :                             // the bbox of interest
    2181             :                             if (dfMinX > m_sFilterEnvelope.MaxX ||
    2182             :                                 dfMaxX < m_sFilterEnvelope.MinX ||
    2183             :                                 dfMinY > m_sFilterEnvelope.MaxY ||
    2184             :                                 dfMaxY < m_sFilterEnvelope.MinY)
    2185             :                             {
    2186             :                                 bSelectGroup = false;
    2187             :                             }
    2188             :                         }
    2189             :                     }
    2190             :                 }
    2191             : #endif
    2192             : 
    2193         445 :                 if (bSelectGroup)
    2194             :                 {
    2195         604 :                     for (auto &constraint : m_asAttributeFilterConstraints)
    2196             :                     {
    2197         254 :                         int iOGRField = constraint.iField;
    2198         508 :                         if (constraint.iField ==
    2199         254 :                             m_poFeatureDefn->GetFieldCount() + SPF_FID)
    2200             :                         {
    2201           9 :                             iOGRField = OGR_FID_INDEX;
    2202             :                         }
    2203         254 :                         if (constraint.nOperation != SWQ_ISNULL &&
    2204         245 :                             constraint.nOperation != SWQ_ISNOTNULL)
    2205             :                         {
    2206         232 :                             if (iOGRField == OGR_FID_INDEX &&
    2207           9 :                                 m_iFIDParquetColumn < 0)
    2208             :                             {
    2209           6 :                                 sMin.Integer64 = nFeatureIdxTotal;
    2210           6 :                                 sMax.Integer64 =
    2211           6 :                                     nFeatureIdxTotal +
    2212           6 :                                     poRowGroup->metadata()->num_rows() - 1;
    2213           6 :                                 eType = OFTInteger64;
    2214             :                             }
    2215         226 :                             else if (!GetMinMaxForOGRField(
    2216             :                                          iRowGroup, iOGRField, true, sMin,
    2217             :                                          bFoundMin, true, sMax, bFoundMax,
    2218         221 :                                          eType, eSubType, osMinTmp, osMaxTmp) ||
    2219         226 :                                      !bFoundMin || !bFoundMax)
    2220             :                             {
    2221           5 :                                 bIterateEverything = true;
    2222           5 :                                 break;
    2223             :                             }
    2224             :                         }
    2225             : 
    2226         249 :                         IsConstraintPossibleRes res =
    2227             :                             IsConstraintPossibleRes::UNKNOWN;
    2228         249 :                         if (constraint.eType ==
    2229         147 :                                 OGRArrowLayer::Constraint::Type::Integer &&
    2230         147 :                             eType == OFTInteger)
    2231             :                         {
    2232             : #if 0
    2233             :                             CPLDebug("PARQUET",
    2234             :                                      "Group %d, field %s, min = %d, max = %d",
    2235             :                                      iRowGroup,
    2236             :                                      iOGRField == OGR_FID_INDEX
    2237             :                                          ? m_osFIDColumn.c_str()
    2238             :                                          : m_poFeatureDefn->GetFieldDefn(iOGRField)
    2239             :                                                ->GetNameRef(),
    2240             :                                      sMin.Integer, sMax.Integer);
    2241             : #endif
    2242         125 :                             res = IsConstraintPossible(
    2243             :                                 constraint.nOperation,
    2244             :                                 constraint.sValue.Integer, sMin.Integer,
    2245             :                                 sMax.Integer);
    2246             :                         }
    2247         124 :                         else if (constraint.eType == OGRArrowLayer::Constraint::
    2248          35 :                                                          Type::Integer64 &&
    2249          35 :                                  eType == OFTInteger64)
    2250             :                         {
    2251             : #if 0
    2252             :                             CPLDebug("PARQUET",
    2253             :                                      "Group %d, field %s, min = " CPL_FRMT_GIB
    2254             :                                      ", max = " CPL_FRMT_GIB,
    2255             :                                      iRowGroup,
    2256             :                                      iOGRField == OGR_FID_INDEX
    2257             :                                          ? m_osFIDColumn.c_str()
    2258             :                                          : m_poFeatureDefn->GetFieldDefn(iOGRField)
    2259             :                                                ->GetNameRef(),
    2260             :                                      static_cast<GIntBig>(sMin.Integer64),
    2261             :                                      static_cast<GIntBig>(sMax.Integer64));
    2262             : #endif
    2263          35 :                             res = IsConstraintPossible(
    2264             :                                 constraint.nOperation,
    2265             :                                 constraint.sValue.Integer64, sMin.Integer64,
    2266             :                                 sMax.Integer64);
    2267             :                         }
    2268          89 :                         else if (constraint.eType ==
    2269          29 :                                      OGRArrowLayer::Constraint::Type::Real &&
    2270          29 :                                  eType == OFTReal)
    2271             :                         {
    2272             : #if 0
    2273             :                             CPLDebug("PARQUET",
    2274             :                                      "Group %d, field %s, min = %g, max = %g",
    2275             :                                      iRowGroup,
    2276             :                                      iOGRField == OGR_FID_INDEX
    2277             :                                          ? m_osFIDColumn.c_str()
    2278             :                                          : m_poFeatureDefn->GetFieldDefn(iOGRField)
    2279             :                                                ->GetNameRef(),
    2280             :                                      sMin.Real, sMax.Real);
    2281             : #endif
    2282          26 :                             res = IsConstraintPossible(constraint.nOperation,
    2283             :                                                        constraint.sValue.Real,
    2284             :                                                        sMin.Real, sMax.Real);
    2285             :                         }
    2286          63 :                         else if (constraint.eType ==
    2287          38 :                                      OGRArrowLayer::Constraint::Type::String &&
    2288          38 :                                  eType == OFTString)
    2289             :                         {
    2290             : #if 0
    2291             :                             CPLDebug("PARQUET",
    2292             :                                      "Group %d, field %s, min = %s, max = %s",
    2293             :                                      iRowGroup,
    2294             :                                      iOGRField == OGR_FID_INDEX
    2295             :                                          ? m_osFIDColumn.c_str()
    2296             :                                          : m_poFeatureDefn->GetFieldDefn(iOGRField)
    2297             :                                                ->GetNameRef(),
    2298             :                                      sMin.String, sMax.String);
    2299             : #endif
    2300          38 :                             res = IsConstraintPossible(
    2301             :                                 constraint.nOperation,
    2302          76 :                                 std::string(constraint.sValue.String),
    2303          76 :                                 std::string(sMin.String),
    2304          76 :                                 std::string(sMax.String));
    2305             :                         }
    2306          25 :                         else if (constraint.nOperation == SWQ_ISNULL ||
    2307          16 :                                  constraint.nOperation == SWQ_ISNOTNULL)
    2308             :                         {
    2309             :                             const std::vector<int> anCols =
    2310             :                                 iOGRField == OGR_FID_INDEX
    2311           0 :                                     ? std::vector<int>{m_iFIDParquetColumn}
    2312             :                                     : GetParquetColumnIndicesForArrowField(
    2313          44 :                                           GetLayerDefn()
    2314          22 :                                               ->GetFieldDefn(iOGRField)
    2315          88 :                                               ->GetNameRef());
    2316          22 :                             if (anCols.size() == 1 && anCols[0] >= 0)
    2317             :                             {
    2318             :                                 const auto metadata =
    2319          22 :                                     m_poArrowReader->parquet_reader()
    2320          44 :                                         ->metadata();
    2321             :                                 const auto rowGroupColumnChunk =
    2322          22 :                                     metadata->RowGroup(iRowGroup)->ColumnChunk(
    2323          44 :                                         anCols[0]);
    2324             :                                 const auto rowGroupStats =
    2325          44 :                                     rowGroupColumnChunk->statistics();
    2326          44 :                                 if (rowGroupColumnChunk->is_stats_set() &&
    2327          22 :                                     rowGroupStats)
    2328             :                                 {
    2329          22 :                                     res = IsConstraintPossibleRes::YES;
    2330          31 :                                     if (constraint.nOperation == SWQ_ISNULL &&
    2331           9 :                                         rowGroupStats->num_values() ==
    2332           9 :                                             poRowGroup->metadata()->num_rows())
    2333             :                                     {
    2334           5 :                                         res = IsConstraintPossibleRes::NO;
    2335             :                                     }
    2336          34 :                                     else if (constraint.nOperation ==
    2337          30 :                                                  SWQ_ISNOTNULL &&
    2338          13 :                                              rowGroupStats->num_values() == 0)
    2339             :                                     {
    2340           1 :                                         res = IsConstraintPossibleRes::NO;
    2341             :                                     }
    2342             :                                 }
    2343          22 :                             }
    2344             :                         }
    2345             :                         else
    2346             :                         {
    2347           3 :                             CPLDebug(
    2348             :                                 "PARQUET",
    2349             :                                 "Unhandled combination of constraint.eType "
    2350             :                                 "(%d) and eType (%d)",
    2351           3 :                                 static_cast<int>(constraint.eType), eType);
    2352             :                         }
    2353             : 
    2354         249 :                         if (res == IsConstraintPossibleRes::NO)
    2355             :                         {
    2356          83 :                             bSelectGroup = false;
    2357          83 :                             break;
    2358             :                         }
    2359         166 :                         else if (res == IsConstraintPossibleRes::UNKNOWN)
    2360             :                         {
    2361           3 :                             bIterateEverything = true;
    2362           3 :                             break;
    2363             :                         }
    2364             :                     }
    2365             :                 }
    2366             : 
    2367         445 :                 if (bSelectGroup)
    2368             :                 {
    2369             :                     // CPLDebug("PARQUET", "Selecting row group %d", iRowGroup);
    2370             :                     m_asFeatureIdxRemapping.emplace_back(
    2371         358 :                         std::make_pair(nFeatureIdxSelected, nFeatureIdxTotal));
    2372         358 :                     anSelectedGroups.push_back(iRowGroup);
    2373         358 :                     nFeatureIdxSelected += poRowGroup->metadata()->num_rows();
    2374             :                 }
    2375             : 
    2376         445 :                 nFeatureIdxTotal += poRowGroup->metadata()->num_rows();
    2377             :             }
    2378             :         }
    2379             : 
    2380         987 :         if (bIterateEverything)
    2381             :         {
    2382         675 :             m_asFeatureIdxRemapping.clear();
    2383         675 :             m_oFeatureIdxRemappingIter = m_asFeatureIdxRemapping.begin();
    2384         675 :             if (!CreateRecordBatchReader(0))
    2385           0 :                 return false;
    2386             :         }
    2387             :         else
    2388             :         {
    2389         312 :             m_oFeatureIdxRemappingIter = m_asFeatureIdxRemapping.begin();
    2390         312 :             if (anSelectedGroups.empty())
    2391             :             {
    2392          12 :                 return false;
    2393             :             }
    2394         300 :             CPLDebug("PARQUET", "%d/%d row groups selected",
    2395         300 :                      int(anSelectedGroups.size()),
    2396         300 :                      m_poArrowReader->num_row_groups());
    2397         300 :             m_nFeatureIdx = m_oFeatureIdxRemappingIter->second;
    2398         300 :             ++m_oFeatureIdxRemappingIter;
    2399         300 :             if (!CreateRecordBatchReader(anSelectedGroups))
    2400             :             {
    2401           0 :                 return false;
    2402             :             }
    2403             :         }
    2404             :     }
    2405             : 
    2406        4122 :     std::shared_ptr<arrow::RecordBatch> poNextBatch;
    2407             : 
    2408           0 :     do
    2409             :     {
    2410        2061 :         ++m_iRecordBatch;
    2411        2061 :         poNextBatch.reset();
    2412        2061 :         auto status = m_poRecordBatchReader->ReadNext(&poNextBatch);
    2413        2061 :         if (!status.ok())
    2414             :         {
    2415           0 :             CPLError(CE_Failure, CPLE_AppDefined, "ReadNext() failed: %s",
    2416           0 :                      status.message().c_str());
    2417           0 :             poNextBatch.reset();
    2418             :         }
    2419        2061 :         if (poNextBatch == nullptr)
    2420             :         {
    2421        1113 :             if (m_iRecordBatch == 1 && m_poBatch && m_poAttrQuery == nullptr &&
    2422         365 :                 m_poFilterGeom == nullptr)
    2423             :             {
    2424          59 :                 m_iRecordBatch = 0;
    2425          59 :                 m_bSingleBatch = true;
    2426             :             }
    2427             :             else
    2428         689 :                 m_poBatch.reset();
    2429         748 :             return false;
    2430             :         }
    2431        1313 :     } while (poNextBatch->num_rows() == 0);
    2432             : 
    2433        1313 :     SetBatch(poNextBatch);
    2434             : 
    2435        1313 :     return true;
    2436             : }
    2437             : 
    2438             : /************************************************************************/
    2439             : /*                     InvalidateCachedBatches()                        */
    2440             : /************************************************************************/
    2441             : 
    2442         953 : void OGRParquetLayer::InvalidateCachedBatches()
    2443             : {
    2444         953 :     m_bSingleBatch = false;
    2445         953 :     OGRParquetLayerBase::InvalidateCachedBatches();
    2446         953 : }
    2447             : 
    2448             : /************************************************************************/
    2449             : /*                        SetIgnoredFields()                            */
    2450             : /************************************************************************/
    2451             : 
    2452         260 : OGRErr OGRParquetLayer::SetIgnoredFields(CSLConstList papszFields)
    2453             : {
    2454         260 :     m_bIgnoredFields = false;
    2455         260 :     m_anRequestedParquetColumns.clear();
    2456         260 :     m_anMapFieldIndexToArrayIndex.clear();
    2457         260 :     m_anMapGeomFieldIndexToArrayIndex.clear();
    2458         260 :     m_nRequestedFIDColumn = -1;
    2459         260 :     OGRErr eErr = OGRLayer::SetIgnoredFields(papszFields);
    2460         260 :     int nBatchColumns = 0;
    2461         260 :     if (!m_bHasMissingMappingToParquet && eErr == OGRERR_NONE)
    2462             :     {
    2463         260 :         m_bIgnoredFields = papszFields != nullptr && papszFields[0] != nullptr;
    2464         260 :         if (m_bIgnoredFields)
    2465             :         {
    2466         197 :             if (m_iFIDParquetColumn >= 0)
    2467             :             {
    2468           6 :                 m_nRequestedFIDColumn = nBatchColumns;
    2469           6 :                 nBatchColumns++;
    2470           6 :                 m_anRequestedParquetColumns.push_back(m_iFIDParquetColumn);
    2471             :             }
    2472             : 
    2473        5971 :             for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); ++i)
    2474             :             {
    2475             :                 const auto eArrowType =
    2476        5774 :                     m_poSchema->fields()[m_anMapFieldIndexToArrowColumn[i][0]]
    2477        5774 :                         ->type()
    2478        5774 :                         ->id();
    2479        5774 :                 if (eArrowType == arrow::Type::STRUCT)
    2480             :                 {
    2481             :                     // For a struct, for the sake of simplicity in
    2482             :                     // GetNextRawFeature(), as soon as one of the member if
    2483             :                     // requested, request all Parquet columns, so that the Arrow
    2484             :                     // type doesn't change
    2485          69 :                     bool bFoundNotIgnored = false;
    2486         296 :                     for (int j = i; j < m_poFeatureDefn->GetFieldCount() &&
    2487         294 :                                     m_anMapFieldIndexToArrowColumn[i][0] ==
    2488         147 :                                         m_anMapFieldIndexToArrowColumn[j][0];
    2489             :                          ++j)
    2490             :                     {
    2491         136 :                         if (!m_poFeatureDefn->GetFieldDefn(j)->IsIgnored())
    2492             :                         {
    2493          56 :                             bFoundNotIgnored = true;
    2494          56 :                             break;
    2495             :                         }
    2496             :                     }
    2497          69 :                     if (bFoundNotIgnored)
    2498             :                     {
    2499             :                         int j;
    2500         784 :                         for (j = i; j < m_poFeatureDefn->GetFieldCount() &&
    2501         784 :                                     m_anMapFieldIndexToArrowColumn[i][0] ==
    2502         392 :                                         m_anMapFieldIndexToArrowColumn[j][0];
    2503             :                              ++j)
    2504             :                         {
    2505         336 :                             if (!m_poFeatureDefn->GetFieldDefn(j)->IsIgnored())
    2506             :                             {
    2507         330 :                                 m_anMapFieldIndexToArrayIndex.push_back(
    2508             :                                     nBatchColumns);
    2509             :                             }
    2510             :                             else
    2511             :                             {
    2512           6 :                                 m_anMapFieldIndexToArrayIndex.push_back(-1);
    2513             :                             }
    2514             : 
    2515             :                             const int iArrowCol =
    2516         336 :                                 m_anMapFieldIndexToArrowColumn[i][0];
    2517             :                             const std::string osArrowColName =
    2518         672 :                                 m_poSchema->fields()[iArrowCol]->name();
    2519             :                             const auto anParquetColsForField =
    2520             :                                 GetParquetColumnIndicesForArrowField(
    2521         672 :                                     osArrowColName.c_str());
    2522             :                             m_anRequestedParquetColumns.insert(
    2523         336 :                                 m_anRequestedParquetColumns.end(),
    2524             :                                 anParquetColsForField.begin(),
    2525         672 :                                 anParquetColsForField.end());
    2526             :                         }
    2527          56 :                         i = j - 1;
    2528          56 :                         nBatchColumns++;
    2529             :                     }
    2530             :                     else
    2531             :                     {
    2532             :                         int j;
    2533         172 :                         for (j = i; j < m_poFeatureDefn->GetFieldCount() &&
    2534         170 :                                     m_anMapFieldIndexToArrowColumn[i][0] ==
    2535          85 :                                         m_anMapFieldIndexToArrowColumn[j][0];
    2536             :                              ++j)
    2537             :                         {
    2538          74 :                             m_anMapFieldIndexToArrayIndex.push_back(-1);
    2539             :                         }
    2540          13 :                         i = j - 1;
    2541             :                     }
    2542             :                 }
    2543        5705 :                 else if (!m_poFeatureDefn->GetFieldDefn(i)->IsIgnored())
    2544             :                 {
    2545        4183 :                     m_anMapFieldIndexToArrayIndex.push_back(nBatchColumns);
    2546        4183 :                     nBatchColumns++;
    2547        4183 :                     const int iArrowCol = m_anMapFieldIndexToArrowColumn[i][0];
    2548             :                     const std::string osArrowColName =
    2549        8366 :                         m_poSchema->fields()[iArrowCol]->name();
    2550             :                     const auto anParquetColsForField =
    2551        4183 :                         GetParquetColumnIndicesForArrowField(osArrowColName);
    2552             :                     m_anRequestedParquetColumns.insert(
    2553        4183 :                         m_anRequestedParquetColumns.end(),
    2554             :                         anParquetColsForField.begin(),
    2555        8366 :                         anParquetColsForField.end());
    2556             :                 }
    2557             :                 else
    2558             :                 {
    2559        1522 :                     m_anMapFieldIndexToArrayIndex.push_back(-1);
    2560             :                 }
    2561             :             }
    2562             : 
    2563         197 :             CPLAssert(static_cast<int>(m_anMapFieldIndexToArrayIndex.size()) ==
    2564             :                       m_poFeatureDefn->GetFieldCount());
    2565             : 
    2566         406 :             for (int i = 0; i < m_poFeatureDefn->GetGeomFieldCount(); ++i)
    2567             :             {
    2568         209 :                 if (!m_poFeatureDefn->GetGeomFieldDefn(i)->IsIgnored())
    2569             :                 {
    2570             :                     const auto &anVals =
    2571         185 :                         m_anMapGeomFieldIndexToParquetColumns[i];
    2572         185 :                     CPLAssert(!anVals.empty() && anVals[0] >= 0);
    2573             :                     m_anRequestedParquetColumns.insert(
    2574         185 :                         m_anRequestedParquetColumns.end(), anVals.begin(),
    2575         370 :                         anVals.end());
    2576         185 :                     m_anMapGeomFieldIndexToArrayIndex.push_back(nBatchColumns);
    2577         185 :                     nBatchColumns++;
    2578             : 
    2579         185 :                     auto oIter = m_oMapGeomFieldIndexToGeomColBBOX.find(i);
    2580             :                     const auto oIterParquet =
    2581         185 :                         m_oMapGeomFieldIndexToGeomColBBOXParquet.find(i);
    2582         275 :                     if (oIter != m_oMapGeomFieldIndexToGeomColBBOX.end() &&
    2583          90 :                         oIterParquet !=
    2584         275 :                             m_oMapGeomFieldIndexToGeomColBBOXParquet.end())
    2585             :                     {
    2586          90 :                         oIter->second.iArrayIdx = nBatchColumns++;
    2587             :                         m_anRequestedParquetColumns.insert(
    2588          90 :                             m_anRequestedParquetColumns.end(),
    2589          90 :                             oIterParquet->second.anParquetCols.begin(),
    2590         270 :                             oIterParquet->second.anParquetCols.end());
    2591             :                     }
    2592             :                 }
    2593             :                 else
    2594             :                 {
    2595          24 :                     m_anMapGeomFieldIndexToArrayIndex.push_back(-1);
    2596             :                 }
    2597             :             }
    2598             : 
    2599         197 :             CPLAssert(
    2600             :                 static_cast<int>(m_anMapGeomFieldIndexToArrayIndex.size()) ==
    2601             :                 m_poFeatureDefn->GetGeomFieldCount());
    2602             :         }
    2603             :     }
    2604             : 
    2605         260 :     m_nExpectedBatchColumns = m_bIgnoredFields ? nBatchColumns : -1;
    2606             : 
    2607         260 :     ComputeConstraintsArrayIdx();
    2608             : 
    2609             :     // Full invalidation
    2610         260 :     InvalidateCachedBatches();
    2611             : 
    2612         260 :     return eErr;
    2613             : }
    2614             : 
    2615             : /************************************************************************/
    2616             : /*                        GetFeatureCount()                             */
    2617             : /************************************************************************/
    2618             : 
    2619        1057 : GIntBig OGRParquetLayer::GetFeatureCount(int bForce)
    2620             : {
    2621        1057 :     if (m_poAttrQuery == nullptr && m_poFilterGeom == nullptr)
    2622             :     {
    2623          55 :         auto metadata = m_poArrowReader->parquet_reader()->metadata();
    2624          55 :         if (metadata)
    2625          55 :             return metadata->num_rows();
    2626             :     }
    2627        1002 :     return OGRLayer::GetFeatureCount(bForce);
    2628             : }
    2629             : 
    2630             : /************************************************************************/
    2631             : /*                         FastGetExtent()                              */
    2632             : /************************************************************************/
    2633             : 
    2634         833 : bool OGRParquetLayer::FastGetExtent(int iGeomField, OGREnvelope *psExtent) const
    2635             : {
    2636         833 :     if (OGRParquetLayerBase::FastGetExtent(iGeomField, psExtent))
    2637         818 :         return true;
    2638             : 
    2639             :     const auto oIterToGeomColBBOX =
    2640          15 :         m_oMapGeomFieldIndexToGeomColBBOXParquet.find(iGeomField);
    2641          16 :     if (oIterToGeomColBBOX != m_oMapGeomFieldIndexToGeomColBBOXParquet.end() &&
    2642           1 :         CPLTestBool(CPLGetConfigOption("OGR_PARQUET_USE_BBOX", "YES")))
    2643             :     {
    2644           1 :         OGREnvelope sExtent;
    2645             :         OGRField sMin, sMax;
    2646           1 :         OGR_RawField_SetNull(&sMin);
    2647           1 :         OGR_RawField_SetNull(&sMax);
    2648             :         bool bFoundMin, bFoundMax;
    2649           1 :         OGRFieldType eType = OFTMaxType;
    2650           1 :         OGRFieldSubType eSubType = OFSTNone;
    2651           1 :         std::string osMinTmp, osMaxTmp;
    2652           2 :         if (GetMinMaxForParquetCol(-1, oIterToGeomColBBOX->second.iParquetXMin,
    2653             :                                    nullptr, true, sMin, bFoundMin, false, sMax,
    2654             :                                    bFoundMax, eType, eSubType, osMinTmp,
    2655           3 :                                    osMaxTmp) &&
    2656           1 :             eType == OFTReal)
    2657             :         {
    2658           1 :             sExtent.MinX = sMin.Real;
    2659             : 
    2660           1 :             if (GetMinMaxForParquetCol(
    2661           1 :                     -1, oIterToGeomColBBOX->second.iParquetYMin, nullptr, true,
    2662             :                     sMin, bFoundMin, false, sMax, bFoundMax, eType, eSubType,
    2663           3 :                     osMinTmp, osMaxTmp) &&
    2664           1 :                 eType == OFTReal)
    2665             :             {
    2666           1 :                 sExtent.MinY = sMin.Real;
    2667             : 
    2668           1 :                 if (GetMinMaxForParquetCol(
    2669           1 :                         -1, oIterToGeomColBBOX->second.iParquetXMax, nullptr,
    2670             :                         false, sMin, bFoundMin, true, sMax, bFoundMax, eType,
    2671           3 :                         eSubType, osMinTmp, osMaxTmp) &&
    2672           1 :                     eType == OFTReal)
    2673             :                 {
    2674           1 :                     sExtent.MaxX = sMax.Real;
    2675             : 
    2676           1 :                     if (GetMinMaxForParquetCol(
    2677           1 :                             -1, oIterToGeomColBBOX->second.iParquetYMax,
    2678             :                             nullptr, false, sMin, bFoundMin, true, sMax,
    2679           3 :                             bFoundMax, eType, eSubType, osMinTmp, osMaxTmp) &&
    2680           1 :                         eType == OFTReal)
    2681             :                     {
    2682           1 :                         sExtent.MaxY = sMax.Real;
    2683             : 
    2684           1 :                         CPLDebug("PARQUET",
    2685             :                                  "Using statistics of bbox.minx, bbox.miny, "
    2686             :                                  "bbox.maxx, bbox.maxy columns to get extent");
    2687           1 :                         m_oMapExtents[iGeomField] = sExtent;
    2688           1 :                         *psExtent = sExtent;
    2689           1 :                         return true;
    2690             :                     }
    2691             :                 }
    2692             :             }
    2693             :         }
    2694             :     }
    2695             : 
    2696          14 :     return false;
    2697             : }
    2698             : 
    2699             : /************************************************************************/
    2700             : /*                         TestCapability()                             */
    2701             : /************************************************************************/
    2702             : 
    2703         687 : int OGRParquetLayer::TestCapability(const char *pszCap) const
    2704             : {
    2705         687 :     if (EQUAL(pszCap, OLCFastFeatureCount))
    2706          79 :         return m_poAttrQuery == nullptr && m_poFilterGeom == nullptr;
    2707             : 
    2708         608 :     if (EQUAL(pszCap, OLCIgnoreFields))
    2709           9 :         return !m_bHasMissingMappingToParquet;
    2710             : 
    2711         599 :     if (EQUAL(pszCap, OLCFastSpatialFilter))
    2712             :     {
    2713         252 :         if (m_iGeomFieldFilter >= 0 &&
    2714         168 :             m_iGeomFieldFilter < static_cast<int>(m_aeGeomEncoding.size()) &&
    2715          84 :             OGRArrowIsGeoArrowStruct(m_aeGeomEncoding[m_iGeomFieldFilter]))
    2716             :         {
    2717          84 :             return true;
    2718             :         }
    2719             : 
    2720             : #if PARQUET_VERSION_MAJOR >= 21
    2721             :         if (m_iGeomFieldFilter >= 0 &&
    2722             :             m_iGeomFieldFilter < static_cast<int>(m_aeGeomEncoding.size()) &&
    2723             :             m_aeGeomEncoding[m_iGeomFieldFilter] == OGRArrowGeomEncoding::WKB &&
    2724             :             m_iGeomFieldFilter <
    2725             :                 static_cast<int>(
    2726             :                     m_anMapGeomFieldIndexToParquetColumns.size()) &&
    2727             :             m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter].size() ==
    2728             :                 1)
    2729             :         {
    2730             :             const int iParquetCol =
    2731             :                 m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter][0];
    2732             :             if (iParquetCol >= 0)
    2733             :             {
    2734             :                 const auto metadata =
    2735             :                     m_poArrowReader->parquet_reader()->metadata();
    2736             : 
    2737             :                 int nCountRowGroupsStatsValid = 0;
    2738             :                 const int nNumGroups = m_poArrowReader->num_row_groups();
    2739             :                 for (int iRowGroup = 0; iRowGroup < nNumGroups &&
    2740             :                                         nCountRowGroupsStatsValid == iRowGroup;
    2741             :                      ++iRowGroup)
    2742             :                 {
    2743             :                     const auto columnChunk =
    2744             :                         metadata->RowGroup(iRowGroup)->ColumnChunk(iParquetCol);
    2745             :                     if (auto geostats = columnChunk->geo_statistics())
    2746             :                     {
    2747             :                         if (geostats->dimension_valid()[0] &&
    2748             :                             geostats->dimension_valid()[1])
    2749             :                         {
    2750             :                             const double dfMinX = geostats->lower_bound()[0];
    2751             :                             const double dfMaxX = geostats->upper_bound()[0];
    2752             :                             const double dfMinY = geostats->lower_bound()[1];
    2753             :                             const double dfMaxY = geostats->upper_bound()[1];
    2754             :                             if (std::isfinite(dfMinX) &&
    2755             :                                 std::isfinite(dfMaxX) &&
    2756             :                                 std::isfinite(dfMinY) && std::isfinite(dfMaxY))
    2757             :                             {
    2758             :                                 nCountRowGroupsStatsValid++;
    2759             :                             }
    2760             :                         }
    2761             :                     }
    2762             :                 }
    2763             :                 if (nCountRowGroupsStatsValid == nNumGroups)
    2764             :                 {
    2765             :                     return true;
    2766             :                 }
    2767             :             }
    2768             :         }
    2769             : #endif
    2770             : 
    2771             :         // fallback to base method
    2772             :     }
    2773             : 
    2774         515 :     return OGRParquetLayerBase::TestCapability(pszCap);
    2775             : }
    2776             : 
    2777             : /************************************************************************/
    2778             : /*                         GetMetadataItem()                            */
    2779             : /************************************************************************/
    2780             : 
    2781         504 : const char *OGRParquetLayer::GetMetadataItem(const char *pszName,
    2782             :                                              const char *pszDomain)
    2783             : {
    2784             :     // Mostly for unit test purposes
    2785         504 :     if (pszDomain != nullptr && EQUAL(pszDomain, "_PARQUET_"))
    2786             :     {
    2787          11 :         int nRowGroupIdx = -1;
    2788          11 :         int nColumn = -1;
    2789          11 :         if (EQUAL(pszName, "NUM_ROW_GROUPS"))
    2790             :         {
    2791           3 :             return CPLSPrintf("%d", m_poArrowReader->num_row_groups());
    2792             :         }
    2793           8 :         if (EQUAL(pszName, "CREATOR"))
    2794             :         {
    2795           4 :             return CPLSPrintf("%s", m_poArrowReader->parquet_reader()
    2796           4 :                                         ->metadata()
    2797           2 :                                         ->created_by()
    2798           2 :                                         .c_str());
    2799             :         }
    2800          12 :         else if (sscanf(pszName, "ROW_GROUPS[%d]", &nRowGroupIdx) == 1 &&
    2801           6 :                  strstr(pszName, ".NUM_ROWS"))
    2802             :         {
    2803             :             try
    2804             :             {
    2805             :                 auto poRowGroup =
    2806           6 :                     m_poArrowReader->parquet_reader()->RowGroup(nRowGroupIdx);
    2807           3 :                 if (poRowGroup == nullptr)
    2808           0 :                     return nullptr;
    2809           3 :                 return CPLSPrintf("%" PRId64,
    2810           3 :                                   poRowGroup->metadata()->num_rows());
    2811             :             }
    2812           0 :             catch (const std::exception &)
    2813             :             {
    2814             :             }
    2815             :         }
    2816           6 :         else if (sscanf(pszName, "ROW_GROUPS[%d].COLUMNS[%d]", &nRowGroupIdx,
    2817           6 :                         &nColumn) == 2 &&
    2818           3 :                  strstr(pszName, ".COMPRESSION"))
    2819             :         {
    2820             :             try
    2821             :             {
    2822             :                 auto poRowGroup =
    2823           6 :                     m_poArrowReader->parquet_reader()->RowGroup(nRowGroupIdx);
    2824           3 :                 if (poRowGroup == nullptr)
    2825           0 :                     return nullptr;
    2826           6 :                 auto poColumn = poRowGroup->metadata()->ColumnChunk(nColumn);
    2827           3 :                 return CPLSPrintf("%s", arrow::util::Codec::GetCodecAsString(
    2828           3 :                                             poColumn->compression())
    2829           3 :                                             .c_str());
    2830             :             }
    2831           0 :             catch (const std::exception &)
    2832             :             {
    2833             :             }
    2834             :         }
    2835           0 :         return nullptr;
    2836             :     }
    2837         493 :     if (pszDomain != nullptr && EQUAL(pszDomain, "_PARQUET_METADATA_"))
    2838             :     {
    2839         628 :         const auto metadata = m_poArrowReader->parquet_reader()->metadata();
    2840         314 :         const auto &kv_metadata = metadata->key_value_metadata();
    2841         314 :         if (kv_metadata && kv_metadata->Contains(pszName))
    2842             :         {
    2843         311 :             auto metadataItem = kv_metadata->Get(pszName);
    2844         311 :             if (metadataItem.ok())
    2845             :             {
    2846         311 :                 return CPLSPrintf("%s", metadataItem->c_str());
    2847             :             }
    2848             :         }
    2849           3 :         return nullptr;
    2850             :     }
    2851         179 :     return OGRLayer::GetMetadataItem(pszName, pszDomain);
    2852             : }
    2853             : 
    2854             : /************************************************************************/
    2855             : /*                           GetMetadata()                              */
    2856             : /************************************************************************/
    2857             : 
    2858          61 : char **OGRParquetLayer::GetMetadata(const char *pszDomain)
    2859             : {
    2860             :     // Mostly for unit test purposes
    2861          61 :     if (pszDomain != nullptr && EQUAL(pszDomain, "_PARQUET_METADATA_"))
    2862             :     {
    2863           2 :         m_aosFeatherMetadata.Clear();
    2864           4 :         const auto metadata = m_poArrowReader->parquet_reader()->metadata();
    2865           2 :         const auto &kv_metadata = metadata->key_value_metadata();
    2866           2 :         if (kv_metadata)
    2867             :         {
    2868           8 :             for (const auto &kv : kv_metadata->sorted_pairs())
    2869             :             {
    2870             :                 m_aosFeatherMetadata.SetNameValue(kv.first.c_str(),
    2871           6 :                                                   kv.second.c_str());
    2872             :             }
    2873             :         }
    2874           2 :         return m_aosFeatherMetadata.List();
    2875             :     }
    2876             : 
    2877             :     // Mostly for unit test purposes
    2878          59 :     if (pszDomain != nullptr && EQUAL(pszDomain, "_GDAL_CREATION_OPTIONS_"))
    2879             :     {
    2880           6 :         return m_aosCreationOptions.List();
    2881             :     }
    2882             : 
    2883          53 :     return OGRLayer::GetMetadata(pszDomain);
    2884             : }
    2885             : 
    2886             : /************************************************************************/
    2887             : /*                          GetArrowStream()                            */
    2888             : /************************************************************************/
    2889             : 
    2890         135 : bool OGRParquetLayer::GetArrowStream(struct ArrowArrayStream *out_stream,
    2891             :                                      CSLConstList papszOptions)
    2892             : {
    2893             :     const char *pszMaxFeaturesInBatch =
    2894         135 :         CSLFetchNameValue(papszOptions, "MAX_FEATURES_IN_BATCH");
    2895         135 :     if (pszMaxFeaturesInBatch)
    2896             :     {
    2897          14 :         int nMaxBatchSize = atoi(pszMaxFeaturesInBatch);
    2898          14 :         if (nMaxBatchSize <= 0)
    2899           0 :             nMaxBatchSize = 1;
    2900          14 :         if (nMaxBatchSize > INT_MAX - 1)
    2901           0 :             nMaxBatchSize = INT_MAX - 1;
    2902          14 :         m_poArrowReader->set_batch_size(nMaxBatchSize);
    2903             :     }
    2904         135 :     return OGRArrowLayer::GetArrowStream(out_stream, papszOptions);
    2905             : }
    2906             : 
    2907             : /************************************************************************/
    2908             : /*                           SetNextByIndex()                           */
    2909             : /************************************************************************/
    2910             : 
    2911          14 : OGRErr OGRParquetLayer::SetNextByIndex(GIntBig nIndex)
    2912             : {
    2913          14 :     if (nIndex < 0)
    2914             :     {
    2915           4 :         m_bEOF = true;
    2916           4 :         return OGRERR_NON_EXISTING_FEATURE;
    2917             :     }
    2918             : 
    2919          20 :     const auto metadata = m_poArrowReader->parquet_reader()->metadata();
    2920          10 :     if (nIndex >= metadata->num_rows())
    2921             :     {
    2922           4 :         m_bEOF = true;
    2923           4 :         return OGRERR_NON_EXISTING_FEATURE;
    2924             :     }
    2925             : 
    2926           6 :     m_bEOF = false;
    2927             : 
    2928           6 :     if (m_bSingleBatch)
    2929             :     {
    2930           0 :         ResetReading();
    2931           0 :         m_nIdxInBatch = nIndex;
    2932           0 :         m_nFeatureIdx = nIndex;
    2933           0 :         return OGRERR_NONE;
    2934             :     }
    2935             : 
    2936           6 :     const int nNumGroups = m_poArrowReader->num_row_groups();
    2937           6 :     int64_t nAccRows = 0;
    2938           6 :     const auto nBatchSize = m_poArrowReader->properties().batch_size();
    2939           6 :     m_iRecordBatch = -1;
    2940           6 :     ResetReading();
    2941           6 :     m_iRecordBatch = 0;
    2942           7 :     for (int iGroup = 0; iGroup < nNumGroups; ++iGroup)
    2943             :     {
    2944             :         const int64_t nNextAccRows =
    2945           7 :             nAccRows + metadata->RowGroup(iGroup)->num_rows();
    2946           7 :         if (nIndex < nNextAccRows)
    2947             :         {
    2948           6 :             if (!CreateRecordBatchReader(iGroup))
    2949           0 :                 return OGRERR_FAILURE;
    2950             : 
    2951          12 :             std::shared_ptr<arrow::RecordBatch> poBatch;
    2952             :             while (true)
    2953             :             {
    2954           6 :                 auto status = m_poRecordBatchReader->ReadNext(&poBatch);
    2955           6 :                 if (!status.ok())
    2956             :                 {
    2957           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    2958           0 :                              "ReadNext() failed: %s", status.message().c_str());
    2959           0 :                     m_iRecordBatch = -1;
    2960           0 :                     ResetReading();
    2961           0 :                     return OGRERR_FAILURE;
    2962             :                 }
    2963           6 :                 if (poBatch == nullptr)
    2964             :                 {
    2965           0 :                     m_iRecordBatch = -1;
    2966           0 :                     ResetReading();
    2967           0 :                     return OGRERR_FAILURE;
    2968             :                 }
    2969           6 :                 if (nIndex < nAccRows + poBatch->num_rows())
    2970             :                 {
    2971           6 :                     break;
    2972             :                 }
    2973           0 :                 nAccRows += poBatch->num_rows();
    2974           0 :                 m_iRecordBatch++;
    2975           0 :             }
    2976           6 :             m_nIdxInBatch = nIndex - nAccRows;
    2977           6 :             m_nFeatureIdx = nIndex;
    2978           6 :             SetBatch(poBatch);
    2979           6 :             return OGRERR_NONE;
    2980             :         }
    2981           1 :         nAccRows = nNextAccRows;
    2982           1 :         m_iRecordBatch +=
    2983           1 :             (metadata->RowGroup(iGroup)->num_rows() + nBatchSize - 1) /
    2984             :             nBatchSize;
    2985             :     }
    2986             : 
    2987           0 :     m_iRecordBatch = -1;
    2988           0 :     ResetReading();
    2989           0 :     return OGRERR_FAILURE;
    2990             : }
    2991             : 
    2992             : /***********************************************************************/
    2993             : /*                            GetStats()                               */
    2994             : /***********************************************************************/
    2995             : 
    2996             : template <class STAT_TYPE> struct GetStats
    2997             : {
    2998             :     using T = typename STAT_TYPE::T;
    2999             : 
    3000         609 :     static T min(const std::shared_ptr<parquet::FileMetaData> &metadata,
    3001             :                  const int iRowGroup, const int numRowGroups, const int iCol,
    3002             :                  bool &bFound)
    3003             :     {
    3004         609 :         T v{};
    3005         609 :         bFound = false;
    3006        1226 :         for (int i = 0; i < (iRowGroup < 0 ? numRowGroups : 1); i++)
    3007             :         {
    3008         653 :             const auto columnChunk =
    3009          30 :                 metadata->RowGroup(iRowGroup < 0 ? i : iRowGroup)
    3010             :                     ->ColumnChunk(iCol);
    3011         623 :             const auto colStats = columnChunk->statistics();
    3012        1243 :             if (columnChunk->is_stats_set() && colStats &&
    3013         620 :                 colStats->HasMinMax())
    3014             :             {
    3015         614 :                 auto castStats = static_cast<STAT_TYPE *>(colStats.get());
    3016         614 :                 const auto rowGroupVal = castStats->min();
    3017         614 :                 if (i == 0 || rowGroupVal < v)
    3018             :                 {
    3019         602 :                     bFound = true;
    3020         602 :                     v = rowGroupVal;
    3021             :                 }
    3022             :             }
    3023           9 :             else if (columnChunk->num_values() > 0)
    3024             :             {
    3025           6 :                 bFound = false;
    3026           6 :                 break;
    3027             :             }
    3028             :         }
    3029         609 :         return v;
    3030             :     }
    3031             : 
    3032         598 :     static T max(const std::shared_ptr<parquet::FileMetaData> &metadata,
    3033             :                  const int iRowGroup, const int numRowGroups, const int iCol,
    3034             :                  bool &bFound)
    3035             :     {
    3036         598 :         T v{};
    3037         598 :         bFound = false;
    3038        1210 :         for (int i = 0; i < (iRowGroup < 0 ? numRowGroups : 1); i++)
    3039             :         {
    3040         642 :             const auto columnChunk =
    3041          30 :                 metadata->RowGroup(iRowGroup < 0 ? i : iRowGroup)
    3042             :                     ->ColumnChunk(iCol);
    3043         612 :             const auto colStats = columnChunk->statistics();
    3044        1222 :             if (columnChunk->is_stats_set() && colStats &&
    3045         610 :                 colStats->HasMinMax())
    3046             :             {
    3047         610 :                 auto castStats = static_cast<STAT_TYPE *>(colStats.get());
    3048         610 :                 const auto rowGroupVal = castStats->max();
    3049         610 :                 if (i == 0 || rowGroupVal > v)
    3050             :                 {
    3051         608 :                     bFound = true;
    3052         608 :                     v = rowGroupVal;
    3053             :                 }
    3054             :             }
    3055           2 :             else if (columnChunk->num_values() > 0)
    3056             :             {
    3057           0 :                 bFound = false;
    3058           0 :                 break;
    3059             :             }
    3060             :         }
    3061         598 :         return v;
    3062             :     }
    3063             : };
    3064             : 
    3065             : template <> struct GetStats<parquet::ByteArrayStatistics>
    3066             : {
    3067             :     static std::string
    3068          39 :     min(const std::shared_ptr<parquet::FileMetaData> &metadata,
    3069             :         const int iRowGroup, const int numRowGroups, const int iCol,
    3070             :         bool &bFound)
    3071             :     {
    3072          39 :         std::string v{};
    3073          39 :         bFound = false;
    3074          79 :         for (int i = 0; i < (iRowGroup < 0 ? numRowGroups : 1); i++)
    3075             :         {
    3076             :             const auto columnChunk =
    3077          40 :                 metadata->RowGroup(iRowGroup < 0 ? i : iRowGroup)
    3078          80 :                     ->ColumnChunk(iCol);
    3079          80 :             const auto colStats = columnChunk->statistics();
    3080          80 :             if (columnChunk->is_stats_set() && colStats &&
    3081          40 :                 colStats->HasMinMax())
    3082             :             {
    3083             :                 auto castStats =
    3084          40 :                     static_cast<parquet::ByteArrayStatistics *>(colStats.get());
    3085          40 :                 const auto rowGroupValRaw = castStats->min();
    3086             :                 std::string rowGroupVal(
    3087          40 :                     reinterpret_cast<const char *>(rowGroupValRaw.ptr),
    3088          80 :                     rowGroupValRaw.len);
    3089          40 :                 if (i == 0 || rowGroupVal < v)
    3090             :                 {
    3091          39 :                     bFound = true;
    3092          39 :                     v = std::move(rowGroupVal);
    3093             :                 }
    3094             :             }
    3095             :         }
    3096          39 :         return v;
    3097             :     }
    3098             : 
    3099             :     static std::string
    3100          39 :     max(const std::shared_ptr<parquet::FileMetaData> &metadata,
    3101             :         const int iRowGroup, const int numRowGroups, const int iCol,
    3102             :         bool &bFound)
    3103             :     {
    3104          39 :         std::string v{};
    3105          39 :         bFound = false;
    3106          79 :         for (int i = 0; i < (iRowGroup < 0 ? numRowGroups : 1); i++)
    3107             :         {
    3108             :             const auto columnChunk =
    3109          40 :                 metadata->RowGroup(iRowGroup < 0 ? i : iRowGroup)
    3110          40 :                     ->ColumnChunk(iCol);
    3111          40 :             const auto colStats = columnChunk->statistics();
    3112          80 :             if (columnChunk->is_stats_set() && colStats &&
    3113          40 :                 colStats->HasMinMax())
    3114             :             {
    3115             :                 auto castStats =
    3116          40 :                     static_cast<parquet::ByteArrayStatistics *>(colStats.get());
    3117          40 :                 const auto rowGroupValRaw = castStats->max();
    3118             :                 std::string rowGroupVal(
    3119          40 :                     reinterpret_cast<const char *>(rowGroupValRaw.ptr),
    3120          80 :                     rowGroupValRaw.len);
    3121          40 :                 if (i == 0 || rowGroupVal > v)
    3122             :                 {
    3123          40 :                     bFound = true;
    3124          40 :                     v = std::move(rowGroupVal);
    3125             :                 }
    3126             :             }
    3127             :             else
    3128             :             {
    3129           0 :                 bFound = false;
    3130           0 :                 break;
    3131             :             }
    3132             :         }
    3133          39 :         return v;
    3134             :     }
    3135             : };
    3136             : 
    3137             : /************************************************************************/
    3138             : /*                        GetMinMaxForOGRField()                        */
    3139             : /************************************************************************/
    3140             : 
    3141         256 : bool OGRParquetLayer::GetMinMaxForOGRField(int iRowGroup,  // -1 for all
    3142             :                                            int iOGRField, bool bComputeMin,
    3143             :                                            OGRField &sMin, bool &bFoundMin,
    3144             :                                            bool bComputeMax, OGRField &sMax,
    3145             :                                            bool &bFoundMax, OGRFieldType &eType,
    3146             :                                            OGRFieldSubType &eSubType,
    3147             :                                            std::string &osMinTmp,
    3148             :                                            std::string &osMaxTmp) const
    3149             : {
    3150         256 :     OGR_RawField_SetNull(&sMin);
    3151         256 :     OGR_RawField_SetNull(&sMax);
    3152         256 :     eType = OFTReal;
    3153         256 :     eSubType = OFSTNone;
    3154         256 :     bFoundMin = false;
    3155         256 :     bFoundMax = false;
    3156             : 
    3157             :     const std::vector<int> anCols =
    3158             :         iOGRField == OGR_FID_INDEX
    3159           5 :             ? std::vector<int>{m_iFIDParquetColumn}
    3160             :             : GetParquetColumnIndicesForArrowField(
    3161        1019 :                   GetLayerDefn()->GetFieldDefn(iOGRField)->GetNameRef());
    3162         256 :     if (anCols.empty() || anCols[0] < 0)
    3163           2 :         return false;
    3164         254 :     const int iCol = anCols[0];
    3165             :     const auto &arrowType = iOGRField == OGR_FID_INDEX
    3166         254 :                                 ? m_poFIDType
    3167         249 :                                 : GetArrowFieldTypes()[iOGRField];
    3168             : 
    3169         254 :     const bool bRet = GetMinMaxForParquetCol(
    3170             :         iRowGroup, iCol, arrowType, bComputeMin, sMin, bFoundMin, bComputeMax,
    3171             :         sMax, bFoundMax, eType, eSubType, osMinTmp, osMaxTmp);
    3172             : 
    3173         254 :     if (eType == OFTInteger64 && arrowType->id() == arrow::Type::TIMESTAMP)
    3174             :     {
    3175             :         const OGRFieldDefn oDummyFIDFieldDefn(m_osFIDColumn.c_str(),
    3176           4 :                                               OFTInteger64);
    3177             :         const OGRFieldDefn *poFieldDefn =
    3178           2 :             iOGRField == OGR_FID_INDEX ? &oDummyFIDFieldDefn
    3179             :                                        : const_cast<OGRParquetLayer *>(this)
    3180           2 :                                              ->GetLayerDefn()
    3181           2 :                                              ->GetFieldDefn(iOGRField);
    3182           2 :         if (poFieldDefn->GetType() == OFTDateTime)
    3183             :         {
    3184             :             const auto timestampType =
    3185           2 :                 static_cast<arrow::TimestampType *>(arrowType.get());
    3186           2 :             if (bFoundMin)
    3187             :             {
    3188           1 :                 const int64_t timestamp = sMin.Integer64;
    3189           1 :                 OGRArrowLayer::TimestampToOGR(timestamp, timestampType,
    3190             :                                               poFieldDefn->GetTZFlag(), &sMin);
    3191             :             }
    3192           2 :             if (bFoundMax)
    3193             :             {
    3194           1 :                 const int64_t timestamp = sMax.Integer64;
    3195           1 :                 OGRArrowLayer::TimestampToOGR(timestamp, timestampType,
    3196             :                                               poFieldDefn->GetTZFlag(), &sMax);
    3197             :             }
    3198           2 :             eType = OFTDateTime;
    3199             :         }
    3200             :     }
    3201             : 
    3202         254 :     return bRet;
    3203             : }
    3204             : 
    3205             : /************************************************************************/
    3206             : /*                        GetMinMaxForParquetCol()                      */
    3207             : /************************************************************************/
    3208             : 
    3209        1067 : bool OGRParquetLayer::GetMinMaxForParquetCol(
    3210             :     int iRowGroup,  // -1 for all
    3211             :     int iCol,
    3212             :     const std::shared_ptr<arrow::DataType> &arrowType,  // potentially nullptr
    3213             :     bool bComputeMin, OGRField &sMin, bool &bFoundMin, bool bComputeMax,
    3214             :     OGRField &sMax, bool &bFoundMax, OGRFieldType &eType,
    3215             :     OGRFieldSubType &eSubType, std::string &osMinTmp,
    3216             :     std::string &osMaxTmp) const
    3217             : {
    3218        1067 :     OGR_RawField_SetNull(&sMin);
    3219        1067 :     OGR_RawField_SetNull(&sMax);
    3220        1067 :     eType = OFTReal;
    3221        1067 :     eSubType = OFSTNone;
    3222        1067 :     bFoundMin = false;
    3223        1067 :     bFoundMax = false;
    3224             : 
    3225        2134 :     const auto metadata = GetReader()->parquet_reader()->metadata();
    3226        1067 :     const auto numRowGroups = metadata->num_row_groups();
    3227             : 
    3228        1067 :     if (numRowGroups == 0)
    3229           0 :         return false;
    3230             : 
    3231        2134 :     const auto rowGroup0 = metadata->RowGroup(0);
    3232        1067 :     if (iCol < 0 || iCol >= rowGroup0->num_columns())
    3233             :     {
    3234           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3235             :                  "GetMinMaxForParquetCol(): invalid iCol=%d", iCol);
    3236           0 :         return false;
    3237             :     }
    3238        2134 :     const auto rowGroup0columnChunk = rowGroup0->ColumnChunk(iCol);
    3239        2134 :     const auto rowGroup0Stats = rowGroup0columnChunk->statistics();
    3240        1067 :     if (!(rowGroup0columnChunk->is_stats_set() && rowGroup0Stats))
    3241             :     {
    3242           0 :         CPLDebug("PARQUET", "Statistics not available for field %s",
    3243           0 :                  rowGroup0columnChunk->path_in_schema()->ToDotString().c_str());
    3244           0 :         return false;
    3245             :     }
    3246             : 
    3247        1067 :     const auto physicalType = rowGroup0Stats->physical_type();
    3248             : 
    3249        1067 :     if (bComputeMin)
    3250             :     {
    3251         651 :         if (physicalType == parquet::Type::BOOLEAN)
    3252             :         {
    3253          54 :             eType = OFTInteger;
    3254          54 :             eSubType = OFSTBoolean;
    3255          54 :             sMin.Integer = GetStats<parquet::BoolStatistics>::min(
    3256             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    3257             :         }
    3258         597 :         else if (physicalType == parquet::Type::INT32)
    3259             :         {
    3260          78 :             if (arrowType && arrowType->id() == arrow::Type::UINT32)
    3261             :             {
    3262             :                 // With parquet file version 2.0,
    3263             :                 // statistics of uint32 fields are
    3264             :                 // stored as signed int32 values...
    3265           1 :                 eType = OFTInteger64;
    3266           1 :                 int nVal = GetStats<parquet::Int32Statistics>::min(
    3267             :                     metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    3268           1 :                 if (bFoundMin)
    3269             :                 {
    3270           1 :                     sMin.Integer64 = static_cast<uint32_t>(nVal);
    3271             :                 }
    3272             :             }
    3273             :             else
    3274             :             {
    3275          77 :                 eType = OFTInteger;
    3276          77 :                 if (arrowType && arrowType->id() == arrow::Type::INT16)
    3277           1 :                     eSubType = OFSTInt16;
    3278          77 :                 sMin.Integer = GetStats<parquet::Int32Statistics>::min(
    3279             :                     metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    3280             :             }
    3281             :         }
    3282         519 :         else if (physicalType == parquet::Type::INT64)
    3283             :         {
    3284          37 :             eType = OFTInteger64;
    3285          37 :             sMin.Integer64 = GetStats<parquet::Int64Statistics>::min(
    3286             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    3287             :         }
    3288         482 :         else if (physicalType == parquet::Type::FLOAT)
    3289             :         {
    3290         138 :             eType = OFTReal;
    3291         138 :             eSubType = OFSTFloat32;
    3292         138 :             sMin.Real = GetStats<parquet::FloatStatistics>::min(
    3293             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    3294             :         }
    3295         344 :         else if (physicalType == parquet::Type::DOUBLE)
    3296             :         {
    3297         302 :             eType = OFTReal;
    3298         302 :             sMin.Real = GetStats<parquet::DoubleStatistics>::min(
    3299             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    3300             :         }
    3301          42 :         else if (arrowType &&
    3302          53 :                  (arrowType->id() == arrow::Type::STRING ||
    3303          95 :                   arrowType->id() == arrow::Type::LARGE_STRING) &&
    3304             :                  physicalType == parquet::Type::BYTE_ARRAY)
    3305             :         {
    3306          78 :             osMinTmp = GetStats<parquet::ByteArrayStatistics>::min(
    3307          39 :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    3308          39 :             if (bFoundMin)
    3309             :             {
    3310          39 :                 eType = OFTString;
    3311          39 :                 sMin.String = &osMinTmp[0];
    3312             :             }
    3313             :         }
    3314             :     }
    3315             : 
    3316        1067 :     if (bComputeMax)
    3317             :     {
    3318         640 :         if (physicalType == parquet::Type::BOOLEAN)
    3319             :         {
    3320          54 :             eType = OFTInteger;
    3321          54 :             eSubType = OFSTBoolean;
    3322          54 :             sMax.Integer = GetStats<parquet::BoolStatistics>::max(
    3323             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    3324             :         }
    3325         586 :         else if (physicalType == parquet::Type::INT32)
    3326             :         {
    3327          78 :             if (arrowType && arrowType->id() == arrow::Type::UINT32)
    3328             :             {
    3329             :                 // With parquet file version 2.0,
    3330             :                 // statistics of uint32 fields are
    3331             :                 // stored as signed int32 values...
    3332           1 :                 eType = OFTInteger64;
    3333           1 :                 int nVal = GetStats<parquet::Int32Statistics>::max(
    3334             :                     metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    3335           1 :                 if (bFoundMax)
    3336             :                 {
    3337           1 :                     sMax.Integer64 = static_cast<uint32_t>(nVal);
    3338             :                 }
    3339             :             }
    3340             :             else
    3341             :             {
    3342          77 :                 eType = OFTInteger;
    3343          77 :                 if (arrowType && arrowType->id() == arrow::Type::INT16)
    3344           1 :                     eSubType = OFSTInt16;
    3345          77 :                 sMax.Integer = GetStats<parquet::Int32Statistics>::max(
    3346             :                     metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    3347             :             }
    3348             :         }
    3349         508 :         else if (physicalType == parquet::Type::INT64)
    3350             :         {
    3351          37 :             eType = OFTInteger64;
    3352          37 :             sMax.Integer64 = GetStats<parquet::Int64Statistics>::max(
    3353             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    3354             :         }
    3355         471 :         else if (physicalType == parquet::Type::FLOAT)
    3356             :         {
    3357         128 :             eType = OFTReal;
    3358         128 :             eSubType = OFSTFloat32;
    3359         128 :             sMax.Real = GetStats<parquet::FloatStatistics>::max(
    3360             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    3361             :         }
    3362         343 :         else if (physicalType == parquet::Type::DOUBLE)
    3363             :         {
    3364         301 :             eType = OFTReal;
    3365         301 :             sMax.Real = GetStats<parquet::DoubleStatistics>::max(
    3366             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    3367             :         }
    3368          42 :         else if (arrowType &&
    3369          53 :                  (arrowType->id() == arrow::Type::STRING ||
    3370          95 :                   arrowType->id() == arrow::Type::LARGE_STRING) &&
    3371             :                  physicalType == parquet::Type::BYTE_ARRAY)
    3372             :         {
    3373          78 :             osMaxTmp = GetStats<parquet::ByteArrayStatistics>::max(
    3374          39 :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    3375          39 :             if (bFoundMax)
    3376             :             {
    3377          39 :                 eType = OFTString;
    3378          39 :                 sMax.String = &osMaxTmp[0];
    3379             :             }
    3380             :         }
    3381             :     }
    3382             : 
    3383        1067 :     return bFoundMin || bFoundMax;
    3384             : }
    3385             : 
    3386             : /************************************************************************/
    3387             : /*                        GeomColsBBOXParquet()                         */
    3388             : /************************************************************************/
    3389             : 
    3390             : /** Return for a given geometry column (iGeom: in [0, GetGeomFieldCount()-1] range),
    3391             :  * the Parquet column number of the corresponding xmin,ymin,xmax,ymax bounding
    3392             :  * box columns, if existing.
    3393             :  */
    3394           1 : bool OGRParquetLayer::GeomColsBBOXParquet(int iGeom, int &iParquetXMin,
    3395             :                                           int &iParquetYMin, int &iParquetXMax,
    3396             :                                           int &iParquetYMax) const
    3397             : {
    3398           1 :     const auto oIter = m_oMapGeomFieldIndexToGeomColBBOXParquet.find(iGeom);
    3399             :     const bool bFound =
    3400           1 :         (oIter != m_oMapGeomFieldIndexToGeomColBBOXParquet.end());
    3401           1 :     if (bFound)
    3402             :     {
    3403           1 :         iParquetXMin = oIter->second.iParquetXMin;
    3404           1 :         iParquetYMin = oIter->second.iParquetYMin;
    3405           1 :         iParquetXMax = oIter->second.iParquetXMax;
    3406           1 :         iParquetYMax = oIter->second.iParquetYMax;
    3407             :     }
    3408           1 :     return bFound;
    3409             : }

Generated by: LCOV version 1.14