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

Generated by: LCOV version 1.14