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