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

Generated by: LCOV version 1.14