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 : }
|