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 1418 : OGRParquetLayerBase::OGRParquetLayerBase(OGRParquetDataset *poDS,
39 : const char *pszLayerName,
40 1418 : CSLConstList papszOpenOptions)
41 : : OGRArrowLayer(poDS, pszLayerName,
42 1418 : 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 2836 : m_osCRS(CSLFetchNameValueDef(papszOpenOptions, "CRS", ""))
50 : {
51 1418 : }
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 8062 : void OGRParquetLayerBase::ResetReading()
67 : {
68 8062 : if (m_iRecordBatch != 0)
69 : {
70 7594 : m_poRecordBatchReader.reset();
71 : }
72 8062 : OGRArrowLayer::ResetReading();
73 8062 : }
74 :
75 : /************************************************************************/
76 : /* InvalidateCachedBatches() */
77 : /************************************************************************/
78 :
79 2201 : void OGRParquetLayerBase::InvalidateCachedBatches()
80 : {
81 2201 : m_iRecordBatch = -1;
82 2201 : ResetReading();
83 2201 : }
84 :
85 : /************************************************************************/
86 : /* LoadGeoMetadata() */
87 : /************************************************************************/
88 :
89 1418 : void OGRParquetLayerBase::LoadGeoMetadata(
90 : const std::shared_ptr<const arrow::KeyValueMetadata> &kv_metadata)
91 : {
92 1418 : if (kv_metadata && kv_metadata->Contains("geo"))
93 : {
94 2688 : auto geo = kv_metadata->Get("geo");
95 1344 : if (geo.ok())
96 : {
97 1344 : CPLDebug("PARQUET", "geo = %s", geo->c_str());
98 2688 : CPLJSONDocument oDoc;
99 1344 : if (oDoc.LoadMemory(*geo))
100 : {
101 2686 : auto oRoot = oDoc.GetRoot();
102 4029 : const auto osVersion = oRoot.GetString("version");
103 3281 : if (osVersion != "0.1.0" && osVersion != "0.2.0" &&
104 2906 : osVersion != "0.3.0" && osVersion != "0.4.0" &&
105 2902 : osVersion != "1.0.0-beta.1" && osVersion != "1.0.0-rc.1" &&
106 3279 : 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 4029 : auto oColumns = oRoot.GetObj("columns");
115 1343 : if (oColumns.IsValid())
116 : {
117 2701 : for (const auto &oColumn : oColumns.GetChildren())
118 : {
119 1359 : 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 1418 : }
131 :
132 : /************************************************************************/
133 : /* ParseGeometryColumnCovering() */
134 : /************************************************************************/
135 :
136 : //! Parse bounding box column definition
137 : /*static */
138 2708 : 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 8124 : const auto oCovering = oJSONDef["covering"];
144 4096 : if (oCovering.IsValid() &&
145 1388 : oCovering.GetType() == CPLJSONObject::Type::Object)
146 : {
147 2776 : const auto oBBOX = oCovering["bbox"];
148 1388 : if (oBBOX.IsValid() && oBBOX.GetType() == CPLJSONObject::Type::Object)
149 : {
150 2776 : const auto oXMin = oBBOX["xmin"];
151 2776 : const auto oYMin = oBBOX["ymin"];
152 2776 : const auto oXMax = oBBOX["xmax"];
153 2776 : const auto oYMax = oBBOX["ymax"];
154 2776 : if (oXMin.IsValid() && oYMin.IsValid() && oXMax.IsValid() &&
155 1388 : oYMax.IsValid() &&
156 1388 : oXMin.GetType() == CPLJSONObject::Type::Array &&
157 1388 : oYMin.GetType() == CPLJSONObject::Type::Array &&
158 4164 : oXMax.GetType() == CPLJSONObject::Type::Array &&
159 1388 : oYMax.GetType() == CPLJSONObject::Type::Array)
160 : {
161 1388 : const auto osXMinArray = oXMin.ToArray();
162 1388 : const auto osYMinArray = oYMin.ToArray();
163 1388 : const auto osXMaxArray = oXMax.ToArray();
164 1388 : const auto osYMaxArray = oYMax.ToArray();
165 1388 : if (osXMinArray.Size() == 2 && osYMinArray.Size() == 2 &&
166 1388 : osXMaxArray.Size() == 2 && osYMaxArray.Size() == 2 &&
167 2776 : osXMinArray[0].GetType() == CPLJSONObject::Type::String &&
168 2776 : osXMinArray[1].GetType() == CPLJSONObject::Type::String &&
169 2776 : osYMinArray[0].GetType() == CPLJSONObject::Type::String &&
170 2776 : osYMinArray[1].GetType() == CPLJSONObject::Type::String &&
171 2776 : osXMaxArray[0].GetType() == CPLJSONObject::Type::String &&
172 2776 : osXMaxArray[1].GetType() == CPLJSONObject::Type::String &&
173 2776 : osYMaxArray[0].GetType() == CPLJSONObject::Type::String &&
174 4164 : osYMaxArray[1].GetType() == CPLJSONObject::Type::String &&
175 4164 : osXMinArray[0].ToString() == osYMinArray[0].ToString() &&
176 5552 : osXMinArray[0].ToString() == osXMaxArray[0].ToString() &&
177 2776 : osXMinArray[0].ToString() == osYMaxArray[0].ToString())
178 : {
179 1388 : osBBOXColumn = osXMinArray[0].ToString();
180 1388 : osXMin = osXMinArray[1].ToString();
181 1388 : osYMin = osYMinArray[1].ToString();
182 1388 : osXMax = osXMaxArray[1].ToString();
183 1388 : osYMax = osYMaxArray[1].ToString();
184 1388 : return true;
185 : }
186 : }
187 : }
188 : }
189 1320 : 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(nullptr);
515 : const char *pszAuthCode = poSRS->GetAuthorityCode(nullptr);
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 32716 : 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 65432 : const auto &field_kv_metadata = field->metadata();
550 65432 : std::string osExtensionName;
551 32716 : 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 32716 : std::shared_ptr<arrow::DataType> fieldType = field->type();
580 32716 : const auto fieldTypeId = fieldType->id();
581 32716 : 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 32716 : bool bRegularField = true;
595 :
596 32716 : auto oIter = m_oMapGeometryColumns.find(field->name());
597 : // cppcheck-suppress knownConditionTrueFalse
598 64073 : if (bRegularField && (oIter != m_oMapGeometryColumns.end() ||
599 31357 : STARTS_WITH(osExtensionName.c_str(), "ogc.") ||
600 31357 : STARTS_WITH(osExtensionName.c_str(), "geoarrow.")))
601 : {
602 2722 : CPLJSONObject oJSONDef;
603 1361 : if (oIter != m_oMapGeometryColumns.end())
604 1359 : oJSONDef = oIter->second;
605 4083 : auto osEncoding = oJSONDef.GetString("encoding");
606 1361 : if (osEncoding.empty() && !osExtensionName.empty())
607 2 : osEncoding = osExtensionName;
608 :
609 1361 : OGRwkbGeometryType eGeomType = wkbUnknown;
610 1361 : auto eGeomEncoding = OGRArrowGeomEncoding::WKB;
611 1361 : if (IsValidGeometryEncoding(field, osEncoding,
612 2722 : oIter != m_oMapGeometryColumns.end(),
613 : eGeomType, eGeomEncoding))
614 : {
615 1361 : bRegularField = false;
616 2722 : OGRGeomFieldDefn oField(field->name().c_str(), wkbUnknown);
617 :
618 4083 : auto oCRS = oJSONDef["crs"];
619 1361 : OGRSpatialReference *poSRS = nullptr;
620 1361 : if (!oCRS.IsValid())
621 : {
622 50 : if (!m_oMapGeometryColumns.empty())
623 : {
624 : // WGS 84 is implied if no crs member is found.
625 48 : poSRS = new OGRSpatialReference();
626 48 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
627 48 : 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 1361 : if (poSRS)
670 : {
671 461 : const double dfCoordEpoch = oJSONDef.GetDouble("epoch");
672 461 : if (dfCoordEpoch > 0)
673 4 : poSRS->SetCoordinateEpoch(dfCoordEpoch);
674 :
675 461 : oField.SetSpatialRef(poSRS);
676 :
677 461 : poSRS->Release();
678 : }
679 :
680 1361 : 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 1361 : if (oJSONDef.GetString("edges") == "spherical")
696 : {
697 5 : SetMetadataItem("EDGES", "SPHERICAL");
698 : }
699 :
700 : // m_aeGeomEncoding be filled before calling
701 : // ComputeGeometryColumnType()
702 1361 : m_aeGeomEncoding.push_back(eGeomEncoding);
703 1361 : if (eGeomType == wkbUnknown)
704 : {
705 : // geometry_types since 1.0.0-beta1. Was geometry_type
706 : // before
707 1812 : auto oType = oJSONDef.GetObj("geometry_types");
708 604 : if (!oType.IsValid())
709 377 : oType = oJSONDef.GetObj("geometry_type");
710 604 : 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 603 : else if (oType.GetType() == CPLJSONObject::Type::Array)
718 : {
719 454 : const auto oTypeArray = oType.ToArray();
720 227 : if (oTypeArray.Size() == 1)
721 : {
722 114 : eGeomType =
723 114 : 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 1361 : oField.SetType(eGeomType);
786 1361 : oField.SetNullable(field->nullable());
787 1361 : m_poFeatureDefn->AddGeomFieldDefn(&oField);
788 1361 : 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 62655 : if (bRegularField && osExtensionName.empty() &&
795 95371 : 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 65432 : 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 2000 : int OGRParquetLayerBase::GetNumCPUs()
877 : {
878 2000 : const char *pszNumThreads = nullptr;
879 : int nNumThreads =
880 2000 : GDALGetNumThreads(pszNumThreads,
881 : /* nMaxVal = */ -1,
882 : /* bDefaultToAllCPUs = */ false, &pszNumThreads);
883 2000 : if (pszNumThreads == nullptr)
884 0 : nNumThreads = std::min(4, CPLGetNumCPUs());
885 2000 : if (nNumThreads > 1)
886 : {
887 0 : CPL_IGNORE_RET_VAL(arrow::SetCpuThreadPoolCapacity(nNumThreads));
888 : }
889 2000 : return nNumThreads;
890 : }
891 :
892 : /************************************************************************/
893 : /* OGRParquetLayer() */
894 : /************************************************************************/
895 :
896 1058 : OGRParquetLayer::OGRParquetLayer(
897 : OGRParquetDataset *poDS, const char *pszLayerName,
898 : std::unique_ptr<parquet::arrow::FileReader> &&arrow_reader,
899 1058 : CSLConstList papszOpenOptions)
900 : : OGRParquetLayerBase(poDS, pszLayerName, papszOpenOptions),
901 1058 : m_poArrowReader(std::move(arrow_reader))
902 : {
903 1058 : EstablishFeatureDefn();
904 1058 : CPLAssert(static_cast<int>(m_aeGeomEncoding.size()) ==
905 : m_poFeatureDefn->GetGeomFieldCount());
906 :
907 1058 : m_oFeatureIdxRemappingIter = m_asFeatureIdxRemapping.begin();
908 1058 : }
909 :
910 : /************************************************************************/
911 : /* EstablishFeatureDefn() */
912 : /************************************************************************/
913 :
914 1058 : void OGRParquetLayer::EstablishFeatureDefn()
915 : {
916 1058 : const auto metadata = m_poArrowReader->parquet_reader()->metadata();
917 1058 : const auto &kv_metadata = metadata->key_value_metadata();
918 :
919 1058 : LoadGeoMetadata(kv_metadata);
920 : const auto oMapFieldNameToGDALSchemaFieldDefn =
921 1058 : LoadGDALSchema(kv_metadata.get());
922 :
923 1058 : LoadGDALMetadata(kv_metadata.get());
924 :
925 1058 : 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 1058 : if (!m_poArrowReader->GetSchema(&m_poSchema).ok())
952 : {
953 0 : return;
954 : }
955 :
956 : const bool bUseBBOX =
957 1058 : 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 2116 : std::set<std::string> oSetBBOXColumns;
962 1058 : if (bUseBBOX)
963 : {
964 2070 : for (const auto &iter : m_oMapGeometryColumns)
965 : {
966 2034 : std::string osBBOXColumn;
967 2034 : std::string osXMin, osYMin, osXMax, osYMax;
968 1017 : if (ParseGeometryColumnCovering(iter.second, osBBOXColumn, osXMin,
969 : osYMin, osXMax, osYMax))
970 : {
971 520 : oSetBBOXColumns.insert(std::move(osBBOXColumn));
972 : }
973 : }
974 : }
975 :
976 1058 : const auto &fields = m_poSchema->fields();
977 1058 : const auto poParquetSchema = metadata->schema();
978 :
979 : // Map from Parquet column name (with dot separator) to Parquet index
980 2116 : std::map<std::string, int> oMapParquetColumnNameToIdx;
981 1058 : const int nParquetColumns = poParquetSchema->num_columns();
982 37317 : for (int iParquetCol = 0; iParquetCol < nParquetColumns; ++iParquetCol)
983 : {
984 36259 : const auto parquetColumn = poParquetSchema->Column(iParquetCol);
985 36259 : const auto parquetColumnName = parquetColumn->path()->ToDotString();
986 36259 : 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 1004 : if ((m_oMapGeometryColumns.empty() ||
992 : // Below is for release 2024-01-17-alpha.0
993 2062 : (m_oMapGeometryColumns.find("geometry") !=
994 2062 : m_oMapGeometryColumns.end() &&
995 2546 : !m_oMapGeometryColumns["geometry"].GetObj("covering").IsValid() &&
996 1922 : m_oMapGeometryColumns["geometry"].GetString("encoding") == "WKB")) &&
997 365 : bUseBBOX &&
998 1423 : oMapParquetColumnNameToIdx.find("geometry") !=
999 1743 : oMapParquetColumnNameToIdx.end() &&
1000 1378 : oMapParquetColumnNameToIdx.find("bbox.minx") !=
1001 1379 : oMapParquetColumnNameToIdx.end() &&
1002 1059 : oMapParquetColumnNameToIdx.find("bbox.miny") !=
1003 1060 : oMapParquetColumnNameToIdx.end() &&
1004 1059 : oMapParquetColumnNameToIdx.find("bbox.maxx") !=
1005 3175 : oMapParquetColumnNameToIdx.end() &&
1006 1059 : oMapParquetColumnNameToIdx.find("bbox.maxy") !=
1007 1059 : 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 2114 : else if (m_oMapGeometryColumns.find("geometry") !=
1050 2048 : m_oMapGeometryColumns.end() &&
1051 1974 : bUseBBOX &&
1052 2540 : !m_oMapGeometryColumns["geometry"].GetObj("covering").IsValid() &&
1053 1865 : m_oMapGeometryColumns["geometry"].GetString("encoding") == "WKB" &&
1054 1369 : oMapParquetColumnNameToIdx.find("geometry") !=
1055 1681 : oMapParquetColumnNameToIdx.end() &&
1056 1369 : oMapParquetColumnNameToIdx.find("bbox.xmin") !=
1057 1371 : oMapParquetColumnNameToIdx.end() &&
1058 1059 : oMapParquetColumnNameToIdx.find("bbox.ymin") !=
1059 1061 : oMapParquetColumnNameToIdx.end() &&
1060 1059 : oMapParquetColumnNameToIdx.find("bbox.xmax") !=
1061 4164 : oMapParquetColumnNameToIdx.end() &&
1062 1059 : oMapParquetColumnNameToIdx.find("bbox.ymax") !=
1063 1059 : oMapParquetColumnNameToIdx.end())
1064 : {
1065 6 : CPLJSONObject oDef = m_oMapGeometryColumns["geometry"];
1066 4 : CPLJSONObject oCovering;
1067 2 : oDef.Add("covering", oCovering);
1068 2 : CPLJSONObject oBBOX;
1069 2 : oCovering.Add("bbox", oBBOX);
1070 : {
1071 2 : CPLJSONArray oArray;
1072 2 : oArray.Add("bbox");
1073 2 : oArray.Add("xmin");
1074 2 : oBBOX.Add("xmin", oArray);
1075 : }
1076 : {
1077 2 : CPLJSONArray oArray;
1078 2 : oArray.Add("bbox");
1079 2 : oArray.Add("ymin");
1080 2 : oBBOX.Add("ymin", oArray);
1081 : }
1082 : {
1083 2 : CPLJSONArray oArray;
1084 2 : oArray.Add("bbox");
1085 2 : oArray.Add("xmax");
1086 2 : oBBOX.Add("xmax", oArray);
1087 : }
1088 : {
1089 2 : CPLJSONArray oArray;
1090 2 : oArray.Add("bbox");
1091 2 : oArray.Add("ymax");
1092 2 : oBBOX.Add("ymax", oArray);
1093 : }
1094 2 : oSetBBOXColumns.insert("bbox");
1095 2 : m_oMapGeometryColumns["geometry"] = std::move(oDef);
1096 : }
1097 :
1098 1058 : int iParquetCol = 0;
1099 27344 : for (int i = 0; i < m_poSchema->num_fields(); ++i)
1100 : {
1101 26286 : const auto &field = fields[i];
1102 :
1103 : bool bParquetColValid =
1104 26286 : CheckMatchArrowParquetColumnNames(iParquetCol, field);
1105 26286 : if (!bParquetColValid)
1106 0 : m_bHasMissingMappingToParquet = true;
1107 :
1108 26330 : 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 545 : continue;
1120 : }
1121 :
1122 26264 : if (oSetBBOXColumns.find(field->name()) != oSetBBOXColumns.end())
1123 : {
1124 523 : m_oSetBBoxArrowColumns.insert(i);
1125 523 : if (bParquetColValid)
1126 523 : iParquetCol++;
1127 523 : 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 25741 : };
1143 :
1144 77223 : const bool bGeometryField = DealWithGeometryColumn(
1145 : i, field, ComputeGeometryColumnTypeLambda,
1146 25741 : bParquetColValid ? poParquetSchema->Column(iParquetCol) : nullptr,
1147 25741 : metadata.get(), bParquetColValid ? iParquetCol : -1);
1148 25741 : if (bGeometryField)
1149 : {
1150 1031 : const auto oIter = m_oMapGeometryColumns.find(field->name());
1151 1031 : if (bUseBBOX && oIter != m_oMapGeometryColumns.end())
1152 : {
1153 1017 : ProcessGeometryColumnCovering(field, oIter->second,
1154 : oMapParquetColumnNameToIdx);
1155 : }
1156 :
1157 3033 : if (bParquetColValid &&
1158 2002 : (field->type()->id() == arrow::Type::STRUCT ||
1159 971 : 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 538 : m_anMapGeomFieldIndexToParquetColumns.push_back(
1181 538 : {bParquetColValid ? iParquetCol : -1});
1182 538 : if (bParquetColValid)
1183 538 : iParquetCol++;
1184 : }
1185 : }
1186 : else
1187 : {
1188 24710 : CreateFieldFromSchema(field, bParquetColValid, iParquetCol, {i},
1189 : oMapFieldNameToGDALSchemaFieldDefn);
1190 : }
1191 : }
1192 :
1193 1058 : CPLAssert(static_cast<int>(m_anMapFieldIndexToArrowColumn.size()) ==
1194 : m_poFeatureDefn->GetFieldCount());
1195 1058 : CPLAssert(static_cast<int>(m_anMapGeomFieldIndexToArrowColumn.size()) ==
1196 : m_poFeatureDefn->GetGeomFieldCount());
1197 1058 : CPLAssert(static_cast<int>(m_anMapGeomFieldIndexToParquetColumns.size()) ==
1198 : m_poFeatureDefn->GetGeomFieldCount());
1199 :
1200 1058 : if (!fields.empty())
1201 : {
1202 : try
1203 : {
1204 2061 : auto poRowGroup = m_poArrowReader->parquet_reader()->RowGroup(0);
1205 1004 : if (poRowGroup)
1206 : {
1207 2008 : auto poColumn = poRowGroup->metadata()->ColumnChunk(0);
1208 1004 : CPLDebug("PARQUET", "Compression (of first column): %s",
1209 : arrow::util::Codec::GetCodecAsString(
1210 1004 : 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 1017 : 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 2034 : std::string osBBOXColumn;
1235 2034 : std::string osXMin, osYMin, osXMax, osYMax;
1236 1017 : if (ParseGeometryColumnCovering(oJSONGeometryColumn, osBBOXColumn, osXMin,
1237 : osYMin, osXMax, osYMax))
1238 : {
1239 523 : OGRArrowLayer::GeomColBBOX sDesc;
1240 523 : sDesc.iArrowCol = m_poSchema->GetFieldIndex(osBBOXColumn);
1241 1046 : const auto fieldBBOX = m_poSchema->GetFieldByName(osBBOXColumn);
1242 1046 : if (sDesc.iArrowCol >= 0 && fieldBBOX &&
1243 523 : fieldBBOX->type()->id() == arrow::Type::STRUCT)
1244 : {
1245 : const auto fieldBBOXStruct =
1246 1046 : std::static_pointer_cast<arrow::StructType>(fieldBBOX->type());
1247 1046 : const auto fieldXMin = fieldBBOXStruct->GetFieldByName(osXMin);
1248 1046 : const auto fieldYMin = fieldBBOXStruct->GetFieldByName(osYMin);
1249 1046 : const auto fieldXMax = fieldBBOXStruct->GetFieldByName(osXMax);
1250 1046 : const auto fieldYMax = fieldBBOXStruct->GetFieldByName(osYMax);
1251 523 : const int nXMinIdx = fieldBBOXStruct->GetFieldIndex(osXMin);
1252 523 : const int nYMinIdx = fieldBBOXStruct->GetFieldIndex(osYMin);
1253 523 : const int nXMaxIdx = fieldBBOXStruct->GetFieldIndex(osXMax);
1254 523 : const int nYMaxIdx = fieldBBOXStruct->GetFieldIndex(osYMax);
1255 : const auto oIterParquetIdxXMin = oMapParquetColumnNameToIdx.find(
1256 523 : std::string(osBBOXColumn).append(".").append(osXMin));
1257 : const auto oIterParquetIdxYMin = oMapParquetColumnNameToIdx.find(
1258 523 : std::string(osBBOXColumn).append(".").append(osYMin));
1259 : const auto oIterParquetIdxXMax = oMapParquetColumnNameToIdx.find(
1260 523 : std::string(osBBOXColumn).append(".").append(osXMax));
1261 : const auto oIterParquetIdxYMax = oMapParquetColumnNameToIdx.find(
1262 523 : std::string(osBBOXColumn).append(".").append(osYMax));
1263 523 : if (nXMinIdx >= 0 && nYMinIdx >= 0 && nXMaxIdx >= 0 &&
1264 1046 : nYMaxIdx >= 0 && fieldXMin && fieldYMin && fieldXMax &&
1265 1046 : fieldYMax &&
1266 1046 : oIterParquetIdxXMin != oMapParquetColumnNameToIdx.end() &&
1267 1046 : oIterParquetIdxYMin != oMapParquetColumnNameToIdx.end() &&
1268 1046 : oIterParquetIdxXMax != oMapParquetColumnNameToIdx.end() &&
1269 1046 : oIterParquetIdxYMax != oMapParquetColumnNameToIdx.end() &&
1270 525 : (fieldXMin->type()->id() == arrow::Type::FLOAT ||
1271 2 : fieldXMin->type()->id() == arrow::Type::DOUBLE) &&
1272 523 : fieldXMin->type()->id() == fieldYMin->type()->id() &&
1273 1569 : fieldXMin->type()->id() == fieldXMax->type()->id() &&
1274 523 : fieldXMin->type()->id() == fieldYMax->type()->id())
1275 : {
1276 523 : CPLDebug("PARQUET",
1277 : "Bounding box column '%s' detected for "
1278 : "geometry column '%s'",
1279 523 : osBBOXColumn.c_str(), field->name().c_str());
1280 523 : sDesc.iArrowSubfieldXMin = nXMinIdx;
1281 523 : sDesc.iArrowSubfieldYMin = nYMinIdx;
1282 523 : sDesc.iArrowSubfieldXMax = nXMaxIdx;
1283 523 : sDesc.iArrowSubfieldYMax = nYMaxIdx;
1284 523 : sDesc.bIsFloat =
1285 523 : (fieldXMin->type()->id() == arrow::Type::FLOAT);
1286 :
1287 : m_oMapGeomFieldIndexToGeomColBBOX
1288 523 : [m_poFeatureDefn->GetGeomFieldCount() - 1] =
1289 523 : std::move(sDesc);
1290 :
1291 523 : GeomColBBOXParquet sDescParquet;
1292 523 : sDescParquet.iParquetXMin = oIterParquetIdxXMin->second;
1293 523 : sDescParquet.iParquetYMin = oIterParquetIdxYMin->second;
1294 523 : sDescParquet.iParquetXMax = oIterParquetIdxXMax->second;
1295 523 : sDescParquet.iParquetYMax = oIterParquetIdxYMax->second;
1296 5579 : for (const auto &iterParquetCols : oMapParquetColumnNameToIdx)
1297 : {
1298 5056 : if (STARTS_WITH(
1299 : iterParquetCols.first.c_str(),
1300 : std::string(osBBOXColumn).append(".").c_str()))
1301 : {
1302 2092 : sDescParquet.anParquetCols.push_back(
1303 2092 : iterParquetCols.second);
1304 : }
1305 : }
1306 : m_oMapGeomFieldIndexToGeomColBBOXParquet
1307 1046 : [m_poFeatureDefn->GetGeomFieldCount() - 1] =
1308 1046 : std::move(sDescParquet);
1309 : }
1310 : }
1311 : }
1312 1017 : }
1313 :
1314 : /************************************************************************/
1315 : /* FindNode() */
1316 : /************************************************************************/
1317 :
1318 291898 : static const parquet::schema::Node *FindNode(const parquet::schema::Node *node,
1319 : const std::string &arrowFieldName)
1320 : {
1321 291898 : CPLAssert(node);
1322 291898 : if (node->name() == arrowFieldName)
1323 : {
1324 4837 : return node;
1325 : }
1326 287061 : else if (node->is_group())
1327 : {
1328 : const auto groupNode =
1329 113420 : cpl::down_cast<const parquet::schema::GroupNode *>(node);
1330 395642 : for (int i = 0; i < groupNode->field_count(); ++i)
1331 : {
1332 : const auto found =
1333 287059 : FindNode(groupNode->field(i).get(), arrowFieldName);
1334 287059 : if (found)
1335 4837 : return found;
1336 : }
1337 : }
1338 282224 : return nullptr;
1339 : }
1340 :
1341 : /************************************************************************/
1342 : /* CollectLeaveNodes() */
1343 : /************************************************************************/
1344 :
1345 12811 : static void CollectLeaveNodes(
1346 : const parquet::schema::Node *node,
1347 : const std::map<const parquet::schema::Node *, int> &oMapNodeToColIdx,
1348 : std::vector<int> &anParquetCols)
1349 : {
1350 12811 : CPLAssert(node);
1351 12811 : if (node->is_primitive())
1352 : {
1353 6976 : const auto it = oMapNodeToColIdx.find(node);
1354 6976 : if (it != oMapNodeToColIdx.end())
1355 6976 : anParquetCols.push_back(it->second);
1356 : }
1357 5835 : else if (node->is_group())
1358 : {
1359 : const auto groupNode =
1360 5835 : cpl::down_cast<const parquet::schema::GroupNode *>(node);
1361 13809 : for (int i = 0; i < groupNode->field_count(); ++i)
1362 : {
1363 7974 : CollectLeaveNodes(groupNode->field(i).get(), oMapNodeToColIdx,
1364 : anParquetCols);
1365 : }
1366 : }
1367 12811 : }
1368 :
1369 : /************************************************************************/
1370 : /* GetParquetColumnIndicesForArrowField() */
1371 : /************************************************************************/
1372 :
1373 4839 : std::vector<int> OGRParquetLayer::GetParquetColumnIndicesForArrowField(
1374 : const std::string &arrowFieldName) const
1375 : {
1376 9678 : const auto metadata = m_poArrowReader->parquet_reader()->metadata();
1377 4839 : const auto schema = metadata->schema();
1378 :
1379 4839 : std::vector<int> anParquetCols;
1380 4839 : const auto *rootNode = schema->schema_root().get();
1381 4839 : const auto *fieldNode = FindNode(rootNode, arrowFieldName);
1382 4839 : if (!fieldNode)
1383 : {
1384 2 : CPLDebug("Parquet",
1385 : "Cannot find Parquet node corresponding to Arrow field %s",
1386 : arrowFieldName.c_str());
1387 2 : return anParquetCols;
1388 : }
1389 :
1390 : /// Build mapping from schema node to column index
1391 9674 : std::map<const parquet::schema::Node *, int> oMapNodeToColIdx;
1392 4837 : const int num_cols = schema->num_columns();
1393 505128 : for (int i = 0; i < num_cols; ++i)
1394 : {
1395 500291 : const auto *node = schema->Column(i)->schema_node().get();
1396 500291 : oMapNodeToColIdx[node] = i;
1397 : }
1398 :
1399 4837 : CollectLeaveNodes(fieldNode, oMapNodeToColIdx, anParquetCols);
1400 :
1401 4837 : return anParquetCols;
1402 : }
1403 :
1404 : /************************************************************************/
1405 : /* CheckMatchArrowParquetColumnNames() */
1406 : /************************************************************************/
1407 :
1408 28638 : bool OGRParquetLayer::CheckMatchArrowParquetColumnNames(
1409 : int &iParquetCol, const std::shared_ptr<arrow::Field> &field) const
1410 : {
1411 57276 : const auto metadata = m_poArrowReader->parquet_reader()->metadata();
1412 28638 : const auto poParquetSchema = metadata->schema();
1413 28638 : const int nParquetColumns = poParquetSchema->num_columns();
1414 28638 : const auto &fieldName = field->name();
1415 28638 : const int iParquetColBefore = iParquetCol;
1416 :
1417 29462 : while (iParquetCol < nParquetColumns)
1418 : {
1419 29462 : const auto parquetColumn = poParquetSchema->Column(iParquetCol);
1420 29462 : const auto parquetColumnName = parquetColumn->path()->ToDotString();
1421 62840 : if (fieldName == parquetColumnName ||
1422 16689 : (parquetColumnName.size() > fieldName.size() &&
1423 16689 : STARTS_WITH(parquetColumnName.c_str(), fieldName.c_str()) &&
1424 15865 : parquetColumnName[fieldName.size()] == '.'))
1425 : {
1426 28638 : return true;
1427 : }
1428 : else
1429 : {
1430 824 : iParquetCol++;
1431 : }
1432 : }
1433 :
1434 0 : CPLError(CE_Warning, CPLE_AppDefined,
1435 : "Cannot match Arrow column name %s with a Parquet one",
1436 : fieldName.c_str());
1437 0 : iParquetCol = iParquetColBefore;
1438 0 : return false;
1439 : }
1440 :
1441 : /************************************************************************/
1442 : /* CreateFieldFromSchema() */
1443 : /************************************************************************/
1444 :
1445 27062 : void OGRParquetLayer::CreateFieldFromSchema(
1446 : const std::shared_ptr<arrow::Field> &field, bool bParquetColValid,
1447 : int &iParquetCol, const std::vector<int> &path,
1448 : const std::map<std::string, std::unique_ptr<OGRFieldDefn>>
1449 : &oMapFieldNameToGDALSchemaFieldDefn)
1450 : {
1451 27062 : OGRFieldDefn oField(field->name().c_str(), OFTString);
1452 27062 : OGRFieldType eType = OFTString;
1453 27062 : OGRFieldSubType eSubType = OFSTNone;
1454 27062 : bool bTypeOK = true;
1455 :
1456 27062 : auto type = field->type();
1457 27062 : if (type->id() == arrow::Type::DICTIONARY && path.size() == 1)
1458 : {
1459 : const auto dictionaryType =
1460 604 : std::static_pointer_cast<arrow::DictionaryType>(field->type());
1461 604 : auto indexType = dictionaryType->index_type();
1462 604 : if (dictionaryType->value_type()->id() == arrow::Type::STRING &&
1463 302 : IsIntegerArrowType(indexType->id()))
1464 : {
1465 302 : if (bParquetColValid)
1466 : {
1467 604 : std::string osDomainName(field->name() + "Domain");
1468 302 : m_poDS->RegisterDomainName(osDomainName,
1469 302 : m_poFeatureDefn->GetFieldCount());
1470 302 : oField.SetDomainName(osDomainName);
1471 : }
1472 302 : type = std::move(indexType);
1473 : }
1474 : else
1475 : {
1476 0 : bTypeOK = false;
1477 : }
1478 : }
1479 :
1480 27062 : int nParquetColIncrement = 1;
1481 27062 : switch (type->id())
1482 : {
1483 671 : case arrow::Type::STRUCT:
1484 : {
1485 1342 : const auto subfields = field->Flatten();
1486 : const std::string osExtensionName =
1487 1342 : GetFieldExtensionName(field, type, GetDriverUCName().c_str());
1488 5 : if (osExtensionName == EXTENSION_NAME_ARROW_TIMESTAMP_WITH_OFFSET &&
1489 10 : subfields.size() == 2 &&
1490 5 : subfields[0]->name() ==
1491 681 : field->name() + "." + ATSWO_TIMESTAMP_FIELD_NAME &&
1492 10 : subfields[0]->type()->id() == arrow::Type::TIMESTAMP &&
1493 5 : subfields[1]->name() ==
1494 681 : field->name() + "." + ATSWO_OFFSET_MINUTES_FIELD_NAME &&
1495 5 : subfields[1]->type()->id() == arrow::Type::INT16)
1496 : {
1497 5 : oField.SetType(OFTDateTime);
1498 5 : oField.SetTZFlag(OGR_TZFLAG_MIXED_TZ);
1499 5 : oField.SetNullable(field->nullable());
1500 5 : m_poFeatureDefn->AddFieldDefn(&oField);
1501 5 : m_anMapFieldIndexToArrowColumn.push_back(path);
1502 5 : m_apoArrowDataTypes.push_back(std::move(type));
1503 : }
1504 : else
1505 : {
1506 1332 : auto newpath = path;
1507 666 : newpath.push_back(0);
1508 3018 : for (int j = 0; j < static_cast<int>(subfields.size()); j++)
1509 : {
1510 2352 : const auto &subfield = subfields[j];
1511 2352 : bParquetColValid = CheckMatchArrowParquetColumnNames(
1512 : iParquetCol, subfield);
1513 2352 : if (!bParquetColValid)
1514 0 : m_bHasMissingMappingToParquet = true;
1515 2352 : newpath.back() = j;
1516 2352 : CreateFieldFromSchema(subfield, bParquetColValid,
1517 : iParquetCol, newpath,
1518 : oMapFieldNameToGDALSchemaFieldDefn);
1519 : }
1520 : }
1521 671 : return; // return intended, not break
1522 : }
1523 :
1524 5353 : case arrow::Type::MAP:
1525 : {
1526 : // A arrow map maps to 2 Parquet columns
1527 5353 : nParquetColIncrement = 2;
1528 5353 : break;
1529 : }
1530 :
1531 21038 : default:
1532 21038 : break;
1533 : }
1534 :
1535 26391 : if (bTypeOK)
1536 : {
1537 26391 : bTypeOK = MapArrowTypeToOGR(type, field, oField, eType, eSubType, path,
1538 : oMapFieldNameToGDALSchemaFieldDefn);
1539 26391 : if (bTypeOK)
1540 : {
1541 26101 : m_apoArrowDataTypes.push_back(std::move(type));
1542 : }
1543 : }
1544 :
1545 26391 : if (bParquetColValid)
1546 26391 : iParquetCol += nParquetColIncrement;
1547 : }
1548 :
1549 : /************************************************************************/
1550 : /* BuildDomain() */
1551 : /************************************************************************/
1552 :
1553 : std::unique_ptr<OGRFieldDomain>
1554 16 : OGRParquetLayer::BuildDomain(const std::string &osDomainName,
1555 : int iFieldIndex) const
1556 : {
1557 16 : const int iArrowCol = m_anMapFieldIndexToArrowColumn[iFieldIndex][0];
1558 32 : const std::string osArrowColName = m_poSchema->fields()[iArrowCol]->name();
1559 16 : CPLAssert(m_poSchema->fields()[iArrowCol]->type()->id() ==
1560 : arrow::Type::DICTIONARY);
1561 : const auto anParquetColsForField =
1562 48 : GetParquetColumnIndicesForArrowField(osArrowColName.c_str());
1563 16 : CPLAssert(!anParquetColsForField.empty());
1564 16 : const auto oldBatchSize = m_poArrowReader->properties().batch_size();
1565 16 : m_poArrowReader->set_batch_size(1);
1566 : #if PARQUET_VERSION_MAJOR >= 21
1567 : std::unique_ptr<arrow::RecordBatchReader> poRecordBatchReader;
1568 : auto result =
1569 : m_poArrowReader->GetRecordBatchReader({0}, anParquetColsForField);
1570 : if (result.ok())
1571 : poRecordBatchReader = std::move(*result);
1572 : #else
1573 16 : std::shared_ptr<arrow::RecordBatchReader> poRecordBatchReader;
1574 16 : CPL_IGNORE_RET_VAL(m_poArrowReader->GetRecordBatchReader(
1575 : {0}, anParquetColsForField, &poRecordBatchReader));
1576 : #endif
1577 16 : if (poRecordBatchReader != nullptr)
1578 : {
1579 0 : std::shared_ptr<arrow::RecordBatch> poBatch;
1580 16 : auto status = poRecordBatchReader->ReadNext(&poBatch);
1581 16 : if (!status.ok())
1582 : {
1583 0 : CPLError(CE_Failure, CPLE_AppDefined, "ReadNext() failed: %s",
1584 0 : status.message().c_str());
1585 : }
1586 16 : else if (poBatch)
1587 : {
1588 16 : m_poArrowReader->set_batch_size(oldBatchSize);
1589 16 : return BuildDomainFromBatch(osDomainName, poBatch, 0);
1590 : }
1591 : }
1592 0 : m_poArrowReader->set_batch_size(oldBatchSize);
1593 0 : return nullptr;
1594 : }
1595 :
1596 : /************************************************************************/
1597 : /* ComputeGeometryColumnType() */
1598 : /************************************************************************/
1599 :
1600 : OGRwkbGeometryType
1601 297 : OGRParquetLayer::ComputeGeometryColumnType(int iGeomCol, int iParquetCol) const
1602 : {
1603 : // Compute type of geometry column by iterating over each geometry, and
1604 : // looking at the WKB geometry type in the first 5 bytes of each geometry.
1605 :
1606 297 : OGRwkbGeometryType eGeomType = wkbNone;
1607 :
1608 594 : std::vector<int> anRowGroups;
1609 297 : const int nNumGroups = m_poArrowReader->num_row_groups();
1610 297 : anRowGroups.reserve(nNumGroups);
1611 884 : for (int i = 0; i < nNumGroups; ++i)
1612 587 : anRowGroups.push_back(i);
1613 : #if PARQUET_VERSION_MAJOR >= 21
1614 : std::unique_ptr<arrow::RecordBatchReader> poRecordBatchReader;
1615 : auto result =
1616 : m_poArrowReader->GetRecordBatchReader(anRowGroups, {iParquetCol});
1617 : if (result.ok())
1618 : poRecordBatchReader = std::move(*result);
1619 : #else
1620 0 : std::shared_ptr<arrow::RecordBatchReader> poRecordBatchReader;
1621 297 : CPL_IGNORE_RET_VAL(m_poArrowReader->GetRecordBatchReader(
1622 : anRowGroups, {iParquetCol}, &poRecordBatchReader));
1623 : #endif
1624 297 : if (poRecordBatchReader != nullptr)
1625 : {
1626 594 : std::shared_ptr<arrow::RecordBatch> poBatch;
1627 : while (true)
1628 : {
1629 596 : auto status = poRecordBatchReader->ReadNext(&poBatch);
1630 596 : if (!status.ok())
1631 : {
1632 0 : CPLError(CE_Failure, CPLE_AppDefined, "ReadNext() failed: %s",
1633 0 : status.message().c_str());
1634 0 : break;
1635 : }
1636 596 : else if (!poBatch)
1637 295 : break;
1638 :
1639 301 : eGeomType = ComputeGeometryColumnTypeProcessBatch(poBatch, iGeomCol,
1640 : 0, eGeomType);
1641 301 : if (eGeomType == wkbUnknown)
1642 2 : break;
1643 299 : }
1644 : }
1645 :
1646 594 : return eGeomType == wkbNone ? wkbUnknown : eGeomType;
1647 : }
1648 :
1649 : /************************************************************************/
1650 : /* GetFeatureExplicitFID() */
1651 : /************************************************************************/
1652 :
1653 4 : OGRFeature *OGRParquetLayer::GetFeatureExplicitFID(GIntBig nFID)
1654 : {
1655 8 : std::vector<int> anRowGroups;
1656 4 : const int nNumGroups = m_poArrowReader->num_row_groups();
1657 4 : anRowGroups.reserve(nNumGroups);
1658 16 : for (int i = 0; i < nNumGroups; ++i)
1659 12 : anRowGroups.push_back(i);
1660 : #if PARQUET_VERSION_MAJOR >= 21
1661 : std::unique_ptr<arrow::RecordBatchReader> poRecordBatchReader;
1662 : auto result = m_bIgnoredFields
1663 : ? m_poArrowReader->GetRecordBatchReader(
1664 : anRowGroups, m_anRequestedParquetColumns)
1665 : : m_poArrowReader->GetRecordBatchReader(anRowGroups);
1666 : if (result.ok())
1667 : {
1668 : poRecordBatchReader = std::move(*result);
1669 : }
1670 : #else
1671 4 : std::shared_ptr<arrow::RecordBatchReader> poRecordBatchReader;
1672 4 : if (m_bIgnoredFields)
1673 : {
1674 4 : CPL_IGNORE_RET_VAL(m_poArrowReader->GetRecordBatchReader(
1675 2 : anRowGroups, m_anRequestedParquetColumns, &poRecordBatchReader));
1676 : }
1677 : else
1678 : {
1679 2 : CPL_IGNORE_RET_VAL(m_poArrowReader->GetRecordBatchReader(
1680 : anRowGroups, &poRecordBatchReader));
1681 : }
1682 : #endif
1683 4 : if (poRecordBatchReader != nullptr)
1684 : {
1685 4 : std::shared_ptr<arrow::RecordBatch> poBatch;
1686 : while (true)
1687 : {
1688 14 : auto status = poRecordBatchReader->ReadNext(&poBatch);
1689 14 : if (!status.ok())
1690 : {
1691 0 : CPLError(CE_Failure, CPLE_AppDefined, "ReadNext() failed: %s",
1692 0 : status.message().c_str());
1693 0 : break;
1694 : }
1695 14 : else if (!poBatch)
1696 2 : break;
1697 :
1698 12 : const auto array = poBatch->column(
1699 12 : m_bIgnoredFields ? m_nRequestedFIDColumn : m_iFIDArrowColumn);
1700 12 : const auto arrayPtr = array.get();
1701 12 : const auto arrayTypeId = array->type_id();
1702 30 : for (int64_t nIdxInBatch = 0; nIdxInBatch < poBatch->num_rows();
1703 : nIdxInBatch++)
1704 : {
1705 20 : if (!array->IsNull(nIdxInBatch))
1706 : {
1707 20 : if (arrayTypeId == arrow::Type::INT64)
1708 : {
1709 20 : const auto castArray =
1710 : static_cast<const arrow::Int64Array *>(arrayPtr);
1711 20 : if (castArray->Value(nIdxInBatch) == nFID)
1712 : {
1713 2 : return ReadFeature(nIdxInBatch, poBatch->columns());
1714 : }
1715 : }
1716 0 : else if (arrayTypeId == arrow::Type::INT32)
1717 : {
1718 0 : const auto castArray =
1719 : static_cast<const arrow::Int32Array *>(arrayPtr);
1720 0 : if (castArray->Value(nIdxInBatch) == nFID)
1721 : {
1722 0 : return ReadFeature(nIdxInBatch, poBatch->columns());
1723 : }
1724 : }
1725 : }
1726 : }
1727 10 : }
1728 : }
1729 2 : return nullptr;
1730 : }
1731 :
1732 : /************************************************************************/
1733 : /* GetFeatureByIndex() */
1734 : /************************************************************************/
1735 :
1736 64 : OGRFeature *OGRParquetLayer::GetFeatureByIndex(GIntBig nFID)
1737 : {
1738 :
1739 64 : if (nFID < 0)
1740 5 : return nullptr;
1741 :
1742 118 : const auto metadata = m_poArrowReader->parquet_reader()->metadata();
1743 59 : const int nNumGroups = m_poArrowReader->num_row_groups();
1744 59 : int64_t nAccRows = 0;
1745 72 : for (int iGroup = 0; iGroup < nNumGroups; ++iGroup)
1746 : {
1747 : const int64_t nNextAccRows =
1748 63 : nAccRows + metadata->RowGroup(iGroup)->num_rows();
1749 63 : if (nFID < nNextAccRows)
1750 : {
1751 : #if PARQUET_VERSION_MAJOR >= 21
1752 : std::unique_ptr<arrow::RecordBatchReader> poRecordBatchReader;
1753 : auto result = m_bIgnoredFields
1754 : ? m_poArrowReader->GetRecordBatchReader(
1755 : {iGroup}, m_anRequestedParquetColumns)
1756 : : m_poArrowReader->GetRecordBatchReader({iGroup});
1757 : if (result.ok())
1758 : {
1759 : poRecordBatchReader = std::move(*result);
1760 : }
1761 : else
1762 : {
1763 : CPLError(CE_Failure, CPLE_AppDefined,
1764 : "GetRecordBatchReader() failed: %s",
1765 : result.status().message().c_str());
1766 : return nullptr;
1767 : }
1768 : #else
1769 50 : std::shared_ptr<arrow::RecordBatchReader> poRecordBatchReader;
1770 : {
1771 0 : arrow::Status status;
1772 50 : if (m_bIgnoredFields)
1773 : {
1774 0 : status = m_poArrowReader->GetRecordBatchReader(
1775 0 : {iGroup}, m_anRequestedParquetColumns,
1776 0 : &poRecordBatchReader);
1777 : }
1778 : else
1779 : {
1780 100 : status = m_poArrowReader->GetRecordBatchReader(
1781 50 : {iGroup}, &poRecordBatchReader);
1782 : }
1783 50 : if (poRecordBatchReader == nullptr)
1784 : {
1785 0 : CPLError(CE_Failure, CPLE_AppDefined,
1786 : "GetRecordBatchReader() failed: %s",
1787 0 : status.message().c_str());
1788 0 : return nullptr;
1789 : }
1790 : }
1791 : #endif
1792 :
1793 50 : const int64_t nExpectedIdxInGroup = nFID - nAccRows;
1794 50 : int64_t nIdxInGroup = 0;
1795 : while (true)
1796 : {
1797 0 : std::shared_ptr<arrow::RecordBatch> poBatch;
1798 50 : arrow::Status status = poRecordBatchReader->ReadNext(&poBatch);
1799 50 : if (!status.ok())
1800 : {
1801 0 : CPLError(CE_Failure, CPLE_AppDefined,
1802 0 : "ReadNext() failed: %s", status.message().c_str());
1803 0 : return nullptr;
1804 : }
1805 50 : if (poBatch == nullptr)
1806 : {
1807 0 : return nullptr;
1808 : }
1809 50 : if (nExpectedIdxInGroup < nIdxInGroup + poBatch->num_rows())
1810 : {
1811 50 : const auto nIdxInBatch = nExpectedIdxInGroup - nIdxInGroup;
1812 : auto poFeature =
1813 50 : ReadFeature(nIdxInBatch, poBatch->columns());
1814 50 : poFeature->SetFID(nFID);
1815 50 : return poFeature;
1816 : }
1817 0 : nIdxInGroup += poBatch->num_rows();
1818 0 : }
1819 : }
1820 13 : nAccRows = nNextAccRows;
1821 : }
1822 9 : return nullptr;
1823 : }
1824 :
1825 : /************************************************************************/
1826 : /* GetFeature() */
1827 : /************************************************************************/
1828 :
1829 68 : OGRFeature *OGRParquetLayer::GetFeature(GIntBig nFID)
1830 : {
1831 68 : if (!m_osFIDColumn.empty())
1832 : {
1833 4 : return GetFeatureExplicitFID(nFID);
1834 : }
1835 : else
1836 : {
1837 64 : return GetFeatureByIndex(nFID);
1838 : }
1839 : }
1840 :
1841 : /************************************************************************/
1842 : /* ResetReading() */
1843 : /************************************************************************/
1844 :
1845 4561 : void OGRParquetLayer::ResetReading()
1846 : {
1847 4561 : OGRParquetLayerBase::ResetReading();
1848 4561 : m_oFeatureIdxRemappingIter = m_asFeatureIdxRemapping.begin();
1849 4561 : m_nFeatureIdxSelected = 0;
1850 4561 : if (!m_asFeatureIdxRemapping.empty())
1851 : {
1852 2202 : m_nFeatureIdx = m_oFeatureIdxRemappingIter->second;
1853 2202 : ++m_oFeatureIdxRemappingIter;
1854 : }
1855 4561 : }
1856 :
1857 : /************************************************************************/
1858 : /* CreateRecordBatchReader() */
1859 : /************************************************************************/
1860 :
1861 689 : bool OGRParquetLayer::CreateRecordBatchReader(int iStartingRowGroup)
1862 : {
1863 1378 : std::vector<int> anRowGroups;
1864 689 : const int nNumGroups = m_poArrowReader->num_row_groups();
1865 689 : anRowGroups.reserve(nNumGroups - iStartingRowGroup);
1866 1731 : for (int i = iStartingRowGroup; i < nNumGroups; ++i)
1867 1042 : anRowGroups.push_back(i);
1868 1378 : return CreateRecordBatchReader(anRowGroups);
1869 : }
1870 :
1871 989 : bool OGRParquetLayer::CreateRecordBatchReader(
1872 : const std::vector<int> &anRowGroups)
1873 : {
1874 : #if PARQUET_VERSION_MAJOR >= 21
1875 : auto result = m_bIgnoredFields
1876 : ? m_poArrowReader->GetRecordBatchReader(
1877 : anRowGroups, m_anRequestedParquetColumns)
1878 : : m_poArrowReader->GetRecordBatchReader(anRowGroups);
1879 : if (result.ok())
1880 : {
1881 : m_poRecordBatchReader = std::move(*result);
1882 : return true;
1883 : }
1884 : else
1885 : {
1886 : CPLError(CE_Failure, CPLE_AppDefined,
1887 : "GetRecordBatchReader() failed: %s",
1888 : result.status().message().c_str());
1889 : return false;
1890 : }
1891 : #else
1892 989 : arrow::Status status;
1893 989 : if (m_bIgnoredFields)
1894 : {
1895 470 : status = m_poArrowReader->GetRecordBatchReader(
1896 235 : anRowGroups, m_anRequestedParquetColumns, &m_poRecordBatchReader);
1897 : }
1898 : else
1899 : {
1900 1508 : status = m_poArrowReader->GetRecordBatchReader(anRowGroups,
1901 754 : &m_poRecordBatchReader);
1902 : }
1903 989 : if (m_poRecordBatchReader == nullptr)
1904 : {
1905 0 : CPLError(CE_Failure, CPLE_AppDefined,
1906 0 : "GetRecordBatchReader() failed: %s", status.message().c_str());
1907 0 : return false;
1908 : }
1909 989 : return true;
1910 : #endif
1911 : }
1912 :
1913 : /************************************************************************/
1914 : /* IsConstraintPossible() */
1915 : /************************************************************************/
1916 :
1917 : enum class IsConstraintPossibleRes
1918 : {
1919 : YES,
1920 : NO,
1921 : UNKNOWN
1922 : };
1923 :
1924 : template <class T>
1925 224 : static IsConstraintPossibleRes IsConstraintPossible(int nOperation, T v, T min,
1926 : T max)
1927 : {
1928 224 : if (nOperation == SWQ_EQ)
1929 : {
1930 146 : if (v < min || v > max)
1931 : {
1932 59 : return IsConstraintPossibleRes::NO;
1933 : }
1934 : }
1935 78 : else if (nOperation == SWQ_NE)
1936 : {
1937 38 : if (v == min && v == max)
1938 : {
1939 0 : return IsConstraintPossibleRes::NO;
1940 : }
1941 : }
1942 40 : else if (nOperation == SWQ_LE)
1943 : {
1944 10 : if (v < min)
1945 : {
1946 4 : return IsConstraintPossibleRes::NO;
1947 : }
1948 : }
1949 30 : else if (nOperation == SWQ_LT)
1950 : {
1951 10 : if (v <= min)
1952 : {
1953 4 : return IsConstraintPossibleRes::NO;
1954 : }
1955 : }
1956 20 : else if (nOperation == SWQ_GE)
1957 : {
1958 10 : if (v > max)
1959 : {
1960 4 : return IsConstraintPossibleRes::NO;
1961 : }
1962 : }
1963 10 : else if (nOperation == SWQ_GT)
1964 : {
1965 10 : if (v >= max)
1966 : {
1967 6 : return IsConstraintPossibleRes::NO;
1968 : }
1969 : }
1970 : else
1971 : {
1972 0 : CPLDebug("PARQUET",
1973 : "IsConstraintPossible: Unhandled operation type: %d",
1974 : nOperation);
1975 0 : return IsConstraintPossibleRes::UNKNOWN;
1976 : }
1977 147 : return IsConstraintPossibleRes::YES;
1978 : }
1979 :
1980 : /************************************************************************/
1981 : /* IncrFeatureIdx() */
1982 : /************************************************************************/
1983 :
1984 8180 : void OGRParquetLayer::IncrFeatureIdx()
1985 : {
1986 8180 : ++m_nFeatureIdxSelected;
1987 8180 : ++m_nFeatureIdx;
1988 9335 : if (m_iFIDArrowColumn < 0 && !m_asFeatureIdxRemapping.empty() &&
1989 9335 : m_oFeatureIdxRemappingIter != m_asFeatureIdxRemapping.end())
1990 : {
1991 140 : if (m_nFeatureIdxSelected == m_oFeatureIdxRemappingIter->first)
1992 : {
1993 48 : m_nFeatureIdx = m_oFeatureIdxRemappingIter->second;
1994 48 : ++m_oFeatureIdxRemappingIter;
1995 : }
1996 : }
1997 8180 : }
1998 :
1999 : /************************************************************************/
2000 : /* ReadNextBatch() */
2001 : /************************************************************************/
2002 :
2003 2115 : bool OGRParquetLayer::ReadNextBatch()
2004 : {
2005 2115 : m_nIdxInBatch = 0;
2006 :
2007 2115 : const int nNumGroups = m_poArrowReader->num_row_groups();
2008 2115 : if (nNumGroups == 0)
2009 2 : return false;
2010 :
2011 2113 : if (m_bSingleBatch)
2012 : {
2013 32 : CPLAssert(m_iRecordBatch == 0);
2014 32 : CPLAssert(m_poBatch != nullptr);
2015 32 : return false;
2016 : }
2017 :
2018 2081 : CPLAssert((m_iRecordBatch == -1 && m_poRecordBatchReader == nullptr) ||
2019 : (m_iRecordBatch >= 0 && m_poRecordBatchReader != nullptr));
2020 :
2021 2081 : if (m_poRecordBatchReader == nullptr)
2022 : {
2023 995 : m_asFeatureIdxRemapping.clear();
2024 :
2025 995 : bool bIterateEverything = false;
2026 995 : std::vector<int> anSelectedGroups;
2027 : const auto oIterToGeomColBBOX =
2028 995 : m_oMapGeomFieldIndexToGeomColBBOXParquet.find(m_iGeomFieldFilter);
2029 : const bool bUSEBBOXFields =
2030 243 : (m_poFilterGeom &&
2031 243 : oIterToGeomColBBOX !=
2032 1238 : m_oMapGeomFieldIndexToGeomColBBOXParquet.end() &&
2033 141 : CPLTestBool(CPLGetConfigOption(
2034 1136 : ("OGR_" + GetDriverUCName() + "_USE_BBOX").c_str(), "YES")));
2035 : const bool bIsGeoArrowStruct =
2036 1990 : (m_iGeomFieldFilter >= 0 &&
2037 995 : m_iGeomFieldFilter < static_cast<int>(m_aeGeomEncoding.size()) &&
2038 985 : m_iGeomFieldFilter <
2039 : static_cast<int>(
2040 1970 : m_anMapGeomFieldIndexToParquetColumns.size()) &&
2041 985 : m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter].size() >=
2042 1990 : 2 &&
2043 306 : OGRArrowIsGeoArrowStruct(m_aeGeomEncoding[m_iGeomFieldFilter]));
2044 : #if PARQUET_VERSION_MAJOR >= 21
2045 : const bool bUseParquetGeoStat =
2046 : (m_poFilterGeom && m_iGeomFieldFilter >= 0 &&
2047 : m_geoStatsWithBBOXAvailable.find(m_iGeomFieldFilter) !=
2048 : m_geoStatsWithBBOXAvailable.end() &&
2049 : m_iGeomFieldFilter <
2050 : static_cast<int>(
2051 : m_anMapGeomFieldIndexToParquetColumns.size()) &&
2052 : m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter].size() ==
2053 : 1 &&
2054 : m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter][0] >= 0);
2055 : #endif
2056 1716 : if (m_asAttributeFilterConstraints.empty() && !bUSEBBOXFields &&
2057 721 : !(bIsGeoArrowStruct && m_poFilterGeom)
2058 : #if PARQUET_VERSION_MAJOR >= 21
2059 : && !bUseParquetGeoStat
2060 : #endif
2061 : )
2062 : {
2063 675 : bIterateEverything = true;
2064 : }
2065 : else
2066 : {
2067 : OGRField sMin;
2068 : OGRField sMax;
2069 320 : OGR_RawField_SetNull(&sMin);
2070 320 : OGR_RawField_SetNull(&sMax);
2071 320 : bool bFoundMin = false;
2072 320 : bool bFoundMax = false;
2073 320 : OGRFieldType eType = OFTMaxType;
2074 320 : OGRFieldSubType eSubType = OFSTNone;
2075 640 : std::string osMinTmp, osMaxTmp;
2076 320 : int64_t nFeatureIdxSelected = 0;
2077 320 : int64_t nFeatureIdxTotal = 0;
2078 :
2079 320 : int iXMinField = -1;
2080 320 : int iYMinField = -1;
2081 320 : int iXMaxField = -1;
2082 320 : int iYMaxField = -1;
2083 :
2084 320 : if (bIsGeoArrowStruct)
2085 : {
2086 : const auto metadata =
2087 276 : m_poArrowReader->parquet_reader()->metadata();
2088 138 : const auto poParquetSchema = metadata->schema();
2089 342 : for (int iParquetCol :
2090 822 : m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter])
2091 : {
2092 : const auto parquetColumn =
2093 342 : poParquetSchema->Column(iParquetCol);
2094 : const auto parquetColumnName =
2095 684 : parquetColumn->path()->ToDotString();
2096 684 : if (parquetColumnName.size() > 2 &&
2097 342 : parquetColumnName.find(".x") ==
2098 342 : parquetColumnName.size() - 2)
2099 : {
2100 138 : iXMinField = iParquetCol;
2101 138 : iXMaxField = iParquetCol;
2102 : }
2103 408 : else if (parquetColumnName.size() > 2 &&
2104 204 : parquetColumnName.find(".y") ==
2105 204 : parquetColumnName.size() - 2)
2106 : {
2107 138 : iYMinField = iParquetCol;
2108 138 : iYMaxField = iParquetCol;
2109 : }
2110 : }
2111 : }
2112 182 : else if (bUSEBBOXFields)
2113 : {
2114 49 : iXMinField = oIterToGeomColBBOX->second.iParquetXMin;
2115 49 : iYMinField = oIterToGeomColBBOX->second.iParquetYMin;
2116 49 : iXMaxField = oIterToGeomColBBOX->second.iParquetXMax;
2117 49 : iYMaxField = oIterToGeomColBBOX->second.iParquetYMax;
2118 : }
2119 :
2120 765 : for (int iRowGroup = 0;
2121 765 : iRowGroup < nNumGroups && !bIterateEverything; ++iRowGroup)
2122 : {
2123 445 : bool bSelectGroup = true;
2124 : auto poRowGroup =
2125 445 : GetReader()->parquet_reader()->RowGroup(iRowGroup);
2126 :
2127 445 : if (iXMinField >= 0 && iYMinField >= 0 && iXMaxField >= 0 &&
2128 : iYMaxField >= 0)
2129 : {
2130 195 : if (GetMinMaxForParquetCol(iRowGroup, iXMinField, nullptr,
2131 : true, sMin, bFoundMin, false,
2132 : sMax, bFoundMax, eType, eSubType,
2133 194 : osMinTmp, osMaxTmp) &&
2134 389 : bFoundMin && eType == OFTReal)
2135 : {
2136 194 : const double dfGroupMinX = sMin.Real;
2137 194 : if (dfGroupMinX > m_sFilterEnvelope.MaxX)
2138 : {
2139 1 : bSelectGroup = false;
2140 : }
2141 193 : else if (GetMinMaxForParquetCol(
2142 : iRowGroup, iYMinField, nullptr, true, sMin,
2143 : bFoundMin, false, sMax, bFoundMax, eType,
2144 193 : eSubType, osMinTmp, osMaxTmp) &&
2145 386 : bFoundMin && eType == OFTReal)
2146 : {
2147 193 : const double dfGroupMinY = sMin.Real;
2148 193 : if (dfGroupMinY > m_sFilterEnvelope.MaxY)
2149 : {
2150 1 : bSelectGroup = false;
2151 : }
2152 192 : else if (GetMinMaxForParquetCol(
2153 : iRowGroup, iXMaxField, nullptr, false,
2154 : sMin, bFoundMin, true, sMax, bFoundMax,
2155 192 : eType, eSubType, osMinTmp, osMaxTmp) &&
2156 384 : bFoundMax && eType == OFTReal)
2157 : {
2158 192 : const double dfGroupMaxX = sMax.Real;
2159 192 : if (dfGroupMaxX < m_sFilterEnvelope.MinX)
2160 : {
2161 1 : bSelectGroup = false;
2162 : }
2163 191 : else if (GetMinMaxForParquetCol(
2164 : iRowGroup, iYMaxField, nullptr,
2165 : false, sMin, bFoundMin, true, sMax,
2166 : bFoundMax, eType, eSubType,
2167 191 : osMinTmp, osMaxTmp) &&
2168 382 : bFoundMax && eType == OFTReal)
2169 : {
2170 191 : const double dfGroupMaxY = sMax.Real;
2171 191 : if (dfGroupMaxY < m_sFilterEnvelope.MinY)
2172 : {
2173 1 : bSelectGroup = false;
2174 : }
2175 : }
2176 : }
2177 : }
2178 : }
2179 : }
2180 : #if PARQUET_VERSION_MAJOR >= 21
2181 : else if (bUseParquetGeoStat)
2182 : {
2183 : const int iParquetCol =
2184 : m_anMapGeomFieldIndexToParquetColumns
2185 : [m_iGeomFieldFilter][0];
2186 : CPLAssert(iParquetCol >= 0);
2187 :
2188 : const auto metadata =
2189 : m_poArrowReader->parquet_reader()->metadata();
2190 : const auto columnChunk =
2191 : metadata->RowGroup(iRowGroup)->ColumnChunk(iParquetCol);
2192 : if (auto geostats = columnChunk->geo_statistics())
2193 : {
2194 : if (geostats->dimension_valid()[0] &&
2195 : geostats->dimension_valid()[1])
2196 : {
2197 : double dfMinX = geostats->lower_bound()[0];
2198 : double dfMaxX = geostats->upper_bound()[0];
2199 : double dfMinY = geostats->lower_bound()[1];
2200 : double dfMaxY = geostats->upper_bound()[1];
2201 :
2202 : // Deal as best as we can with wrap around bounding box
2203 : if (dfMinX > dfMaxX && std::fabs(dfMinX) <= 180 &&
2204 : std::fabs(dfMaxX) <= 180)
2205 : {
2206 : dfMinX = -180;
2207 : dfMaxX = 180;
2208 : }
2209 :
2210 : // Check if there is an intersection between
2211 : // the geostatistics for this rowgroup and
2212 : // the bbox of interest
2213 : if (dfMinX > m_sFilterEnvelope.MaxX ||
2214 : dfMaxX < m_sFilterEnvelope.MinX ||
2215 : dfMinY > m_sFilterEnvelope.MaxY ||
2216 : dfMaxY < m_sFilterEnvelope.MinY)
2217 : {
2218 : bSelectGroup = false;
2219 : }
2220 : }
2221 : }
2222 : }
2223 : #endif
2224 :
2225 445 : if (bSelectGroup)
2226 : {
2227 604 : for (auto &constraint : m_asAttributeFilterConstraints)
2228 : {
2229 254 : int iOGRField = constraint.iField;
2230 508 : if (constraint.iField ==
2231 254 : m_poFeatureDefn->GetFieldCount() + SPF_FID)
2232 : {
2233 9 : iOGRField = OGR_FID_INDEX;
2234 : }
2235 254 : if (constraint.nOperation != SWQ_ISNULL &&
2236 245 : constraint.nOperation != SWQ_ISNOTNULL)
2237 : {
2238 232 : if (iOGRField == OGR_FID_INDEX &&
2239 9 : m_iFIDParquetColumn < 0)
2240 : {
2241 6 : sMin.Integer64 = nFeatureIdxTotal;
2242 6 : sMax.Integer64 =
2243 6 : nFeatureIdxTotal +
2244 6 : poRowGroup->metadata()->num_rows() - 1;
2245 6 : eType = OFTInteger64;
2246 : }
2247 226 : else if (!GetMinMaxForOGRField(
2248 : iRowGroup, iOGRField, true, sMin,
2249 : bFoundMin, true, sMax, bFoundMax,
2250 221 : eType, eSubType, osMinTmp, osMaxTmp) ||
2251 226 : !bFoundMin || !bFoundMax)
2252 : {
2253 5 : bIterateEverything = true;
2254 5 : break;
2255 : }
2256 : }
2257 :
2258 249 : IsConstraintPossibleRes res =
2259 : IsConstraintPossibleRes::UNKNOWN;
2260 249 : if (constraint.eType ==
2261 147 : OGRArrowLayer::Constraint::Type::Integer &&
2262 147 : eType == OFTInteger)
2263 : {
2264 : #if 0
2265 : CPLDebug("PARQUET",
2266 : "Group %d, field %s, min = %d, max = %d",
2267 : iRowGroup,
2268 : iOGRField == OGR_FID_INDEX
2269 : ? m_osFIDColumn.c_str()
2270 : : m_poFeatureDefn->GetFieldDefn(iOGRField)
2271 : ->GetNameRef(),
2272 : sMin.Integer, sMax.Integer);
2273 : #endif
2274 125 : res = IsConstraintPossible(
2275 : constraint.nOperation,
2276 : constraint.sValue.Integer, sMin.Integer,
2277 : sMax.Integer);
2278 : }
2279 124 : else if (constraint.eType == OGRArrowLayer::Constraint::
2280 35 : Type::Integer64 &&
2281 35 : eType == OFTInteger64)
2282 : {
2283 : #if 0
2284 : CPLDebug("PARQUET",
2285 : "Group %d, field %s, min = " CPL_FRMT_GIB
2286 : ", max = " CPL_FRMT_GIB,
2287 : iRowGroup,
2288 : iOGRField == OGR_FID_INDEX
2289 : ? m_osFIDColumn.c_str()
2290 : : m_poFeatureDefn->GetFieldDefn(iOGRField)
2291 : ->GetNameRef(),
2292 : static_cast<GIntBig>(sMin.Integer64),
2293 : static_cast<GIntBig>(sMax.Integer64));
2294 : #endif
2295 35 : res = IsConstraintPossible(
2296 : constraint.nOperation,
2297 : constraint.sValue.Integer64, sMin.Integer64,
2298 : sMax.Integer64);
2299 : }
2300 89 : else if (constraint.eType ==
2301 29 : OGRArrowLayer::Constraint::Type::Real &&
2302 29 : eType == OFTReal)
2303 : {
2304 : #if 0
2305 : CPLDebug("PARQUET",
2306 : "Group %d, field %s, min = %g, max = %g",
2307 : iRowGroup,
2308 : iOGRField == OGR_FID_INDEX
2309 : ? m_osFIDColumn.c_str()
2310 : : m_poFeatureDefn->GetFieldDefn(iOGRField)
2311 : ->GetNameRef(),
2312 : sMin.Real, sMax.Real);
2313 : #endif
2314 26 : res = IsConstraintPossible(constraint.nOperation,
2315 : constraint.sValue.Real,
2316 : sMin.Real, sMax.Real);
2317 : }
2318 63 : else if (constraint.eType ==
2319 38 : OGRArrowLayer::Constraint::Type::String &&
2320 38 : eType == OFTString)
2321 : {
2322 : #if 0
2323 : CPLDebug("PARQUET",
2324 : "Group %d, field %s, min = %s, max = %s",
2325 : iRowGroup,
2326 : iOGRField == OGR_FID_INDEX
2327 : ? m_osFIDColumn.c_str()
2328 : : m_poFeatureDefn->GetFieldDefn(iOGRField)
2329 : ->GetNameRef(),
2330 : sMin.String, sMax.String);
2331 : #endif
2332 38 : res = IsConstraintPossible(
2333 : constraint.nOperation,
2334 76 : std::string(constraint.sValue.String),
2335 76 : std::string(sMin.String),
2336 76 : std::string(sMax.String));
2337 : }
2338 25 : else if (constraint.nOperation == SWQ_ISNULL ||
2339 16 : constraint.nOperation == SWQ_ISNOTNULL)
2340 : {
2341 : const std::vector<int> anCols =
2342 : iOGRField == OGR_FID_INDEX
2343 0 : ? std::vector<int>{m_iFIDParquetColumn}
2344 : : GetParquetColumnIndicesForArrowField(
2345 44 : GetLayerDefn()
2346 22 : ->GetFieldDefn(iOGRField)
2347 88 : ->GetNameRef());
2348 22 : if (anCols.size() == 1 && anCols[0] >= 0)
2349 : {
2350 : const auto metadata =
2351 22 : m_poArrowReader->parquet_reader()
2352 44 : ->metadata();
2353 : const auto rowGroupColumnChunk =
2354 22 : metadata->RowGroup(iRowGroup)->ColumnChunk(
2355 44 : anCols[0]);
2356 : const auto rowGroupStats =
2357 44 : rowGroupColumnChunk->statistics();
2358 44 : if (rowGroupColumnChunk->is_stats_set() &&
2359 22 : rowGroupStats)
2360 : {
2361 22 : res = IsConstraintPossibleRes::YES;
2362 31 : if (constraint.nOperation == SWQ_ISNULL &&
2363 9 : rowGroupStats->num_values() ==
2364 9 : poRowGroup->metadata()->num_rows())
2365 : {
2366 5 : res = IsConstraintPossibleRes::NO;
2367 : }
2368 34 : else if (constraint.nOperation ==
2369 30 : SWQ_ISNOTNULL &&
2370 13 : rowGroupStats->num_values() == 0)
2371 : {
2372 1 : res = IsConstraintPossibleRes::NO;
2373 : }
2374 : }
2375 22 : }
2376 : }
2377 : else
2378 : {
2379 3 : CPLDebug(
2380 : "PARQUET",
2381 : "Unhandled combination of constraint.eType "
2382 : "(%d) and eType (%d)",
2383 3 : static_cast<int>(constraint.eType), eType);
2384 : }
2385 :
2386 249 : if (res == IsConstraintPossibleRes::NO)
2387 : {
2388 83 : bSelectGroup = false;
2389 83 : break;
2390 : }
2391 166 : else if (res == IsConstraintPossibleRes::UNKNOWN)
2392 : {
2393 3 : bIterateEverything = true;
2394 3 : break;
2395 : }
2396 : }
2397 : }
2398 :
2399 445 : if (bSelectGroup)
2400 : {
2401 : // CPLDebug("PARQUET", "Selecting row group %d", iRowGroup);
2402 : m_asFeatureIdxRemapping.emplace_back(
2403 358 : std::make_pair(nFeatureIdxSelected, nFeatureIdxTotal));
2404 358 : anSelectedGroups.push_back(iRowGroup);
2405 358 : nFeatureIdxSelected += poRowGroup->metadata()->num_rows();
2406 : }
2407 :
2408 445 : nFeatureIdxTotal += poRowGroup->metadata()->num_rows();
2409 : }
2410 : }
2411 :
2412 995 : if (bIterateEverything)
2413 : {
2414 683 : m_asFeatureIdxRemapping.clear();
2415 683 : m_oFeatureIdxRemappingIter = m_asFeatureIdxRemapping.begin();
2416 683 : if (!CreateRecordBatchReader(0))
2417 0 : return false;
2418 : }
2419 : else
2420 : {
2421 312 : m_oFeatureIdxRemappingIter = m_asFeatureIdxRemapping.begin();
2422 312 : if (anSelectedGroups.empty())
2423 : {
2424 12 : return false;
2425 : }
2426 300 : CPLDebug("PARQUET", "%d/%d row groups selected",
2427 300 : int(anSelectedGroups.size()),
2428 300 : m_poArrowReader->num_row_groups());
2429 300 : m_nFeatureIdx = m_oFeatureIdxRemappingIter->second;
2430 300 : ++m_oFeatureIdxRemappingIter;
2431 300 : if (!CreateRecordBatchReader(anSelectedGroups))
2432 : {
2433 0 : return false;
2434 : }
2435 : }
2436 : }
2437 :
2438 4138 : std::shared_ptr<arrow::RecordBatch> poNextBatch;
2439 :
2440 0 : do
2441 : {
2442 2069 : ++m_iRecordBatch;
2443 2069 : poNextBatch.reset();
2444 2069 : auto status = m_poRecordBatchReader->ReadNext(&poNextBatch);
2445 2069 : if (!status.ok())
2446 : {
2447 0 : CPLError(CE_Failure, CPLE_AppDefined, "ReadNext() failed: %s",
2448 0 : status.message().c_str());
2449 0 : poNextBatch.reset();
2450 : }
2451 2069 : if (poNextBatch == nullptr)
2452 : {
2453 1113 : if (m_iRecordBatch == 1 && m_poBatch && m_poAttrQuery == nullptr &&
2454 365 : m_poFilterGeom == nullptr)
2455 : {
2456 59 : m_iRecordBatch = 0;
2457 59 : m_bSingleBatch = true;
2458 : }
2459 : else
2460 689 : m_poBatch.reset();
2461 748 : return false;
2462 : }
2463 1321 : } while (poNextBatch->num_rows() == 0);
2464 :
2465 1321 : SetBatch(poNextBatch);
2466 :
2467 1321 : return true;
2468 : }
2469 :
2470 : /************************************************************************/
2471 : /* InvalidateCachedBatches() */
2472 : /************************************************************************/
2473 :
2474 953 : void OGRParquetLayer::InvalidateCachedBatches()
2475 : {
2476 953 : m_bSingleBatch = false;
2477 953 : OGRParquetLayerBase::InvalidateCachedBatches();
2478 953 : }
2479 :
2480 : /************************************************************************/
2481 : /* SetIgnoredFields() */
2482 : /************************************************************************/
2483 :
2484 260 : OGRErr OGRParquetLayer::SetIgnoredFields(CSLConstList papszFields)
2485 : {
2486 260 : m_bIgnoredFields = false;
2487 260 : m_anRequestedParquetColumns.clear();
2488 260 : m_anMapFieldIndexToArrayIndex.clear();
2489 260 : m_anMapGeomFieldIndexToArrayIndex.clear();
2490 260 : m_nRequestedFIDColumn = -1;
2491 260 : OGRErr eErr = OGRLayer::SetIgnoredFields(papszFields);
2492 260 : int nBatchColumns = 0;
2493 260 : if (!m_bHasMissingMappingToParquet && eErr == OGRERR_NONE)
2494 : {
2495 260 : m_bIgnoredFields = papszFields != nullptr && papszFields[0] != nullptr;
2496 260 : if (m_bIgnoredFields)
2497 : {
2498 197 : if (m_iFIDParquetColumn >= 0)
2499 : {
2500 6 : m_nRequestedFIDColumn = nBatchColumns;
2501 6 : nBatchColumns++;
2502 6 : m_anRequestedParquetColumns.push_back(m_iFIDParquetColumn);
2503 : }
2504 :
2505 5971 : for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); ++i)
2506 : {
2507 : const auto eArrowType =
2508 5774 : m_poSchema->fields()[m_anMapFieldIndexToArrowColumn[i][0]]
2509 5774 : ->type()
2510 5774 : ->id();
2511 5774 : if (eArrowType == arrow::Type::STRUCT)
2512 : {
2513 : // For a struct, for the sake of simplicity in
2514 : // GetNextRawFeature(), as soon as one of the member if
2515 : // requested, request all Parquet columns, so that the Arrow
2516 : // type doesn't change
2517 69 : bool bFoundNotIgnored = false;
2518 296 : for (int j = i; j < m_poFeatureDefn->GetFieldCount() &&
2519 294 : m_anMapFieldIndexToArrowColumn[i][0] ==
2520 147 : m_anMapFieldIndexToArrowColumn[j][0];
2521 : ++j)
2522 : {
2523 136 : if (!m_poFeatureDefn->GetFieldDefn(j)->IsIgnored())
2524 : {
2525 56 : bFoundNotIgnored = true;
2526 56 : break;
2527 : }
2528 : }
2529 69 : if (bFoundNotIgnored)
2530 : {
2531 : int j;
2532 784 : for (j = i; j < m_poFeatureDefn->GetFieldCount() &&
2533 784 : m_anMapFieldIndexToArrowColumn[i][0] ==
2534 392 : m_anMapFieldIndexToArrowColumn[j][0];
2535 : ++j)
2536 : {
2537 336 : if (!m_poFeatureDefn->GetFieldDefn(j)->IsIgnored())
2538 : {
2539 330 : m_anMapFieldIndexToArrayIndex.push_back(
2540 : nBatchColumns);
2541 : }
2542 : else
2543 : {
2544 6 : m_anMapFieldIndexToArrayIndex.push_back(-1);
2545 : }
2546 :
2547 : const int iArrowCol =
2548 336 : m_anMapFieldIndexToArrowColumn[i][0];
2549 : const std::string osArrowColName =
2550 672 : m_poSchema->fields()[iArrowCol]->name();
2551 : const auto anParquetColsForField =
2552 : GetParquetColumnIndicesForArrowField(
2553 672 : osArrowColName.c_str());
2554 : m_anRequestedParquetColumns.insert(
2555 336 : m_anRequestedParquetColumns.end(),
2556 : anParquetColsForField.begin(),
2557 672 : anParquetColsForField.end());
2558 : }
2559 56 : i = j - 1;
2560 56 : nBatchColumns++;
2561 : }
2562 : else
2563 : {
2564 : int j;
2565 172 : for (j = i; j < m_poFeatureDefn->GetFieldCount() &&
2566 170 : m_anMapFieldIndexToArrowColumn[i][0] ==
2567 85 : m_anMapFieldIndexToArrowColumn[j][0];
2568 : ++j)
2569 : {
2570 74 : m_anMapFieldIndexToArrayIndex.push_back(-1);
2571 : }
2572 13 : i = j - 1;
2573 : }
2574 : }
2575 5705 : else if (!m_poFeatureDefn->GetFieldDefn(i)->IsIgnored())
2576 : {
2577 4183 : m_anMapFieldIndexToArrayIndex.push_back(nBatchColumns);
2578 4183 : nBatchColumns++;
2579 4183 : const int iArrowCol = m_anMapFieldIndexToArrowColumn[i][0];
2580 : const std::string osArrowColName =
2581 8366 : m_poSchema->fields()[iArrowCol]->name();
2582 : const auto anParquetColsForField =
2583 4183 : GetParquetColumnIndicesForArrowField(osArrowColName);
2584 : m_anRequestedParquetColumns.insert(
2585 4183 : m_anRequestedParquetColumns.end(),
2586 : anParquetColsForField.begin(),
2587 8366 : anParquetColsForField.end());
2588 : }
2589 : else
2590 : {
2591 1522 : m_anMapFieldIndexToArrayIndex.push_back(-1);
2592 : }
2593 : }
2594 :
2595 197 : CPLAssert(static_cast<int>(m_anMapFieldIndexToArrayIndex.size()) ==
2596 : m_poFeatureDefn->GetFieldCount());
2597 :
2598 406 : for (int i = 0; i < m_poFeatureDefn->GetGeomFieldCount(); ++i)
2599 : {
2600 209 : if (!m_poFeatureDefn->GetGeomFieldDefn(i)->IsIgnored())
2601 : {
2602 : const auto &anVals =
2603 185 : m_anMapGeomFieldIndexToParquetColumns[i];
2604 185 : CPLAssert(!anVals.empty() && anVals[0] >= 0);
2605 : m_anRequestedParquetColumns.insert(
2606 185 : m_anRequestedParquetColumns.end(), anVals.begin(),
2607 370 : anVals.end());
2608 185 : m_anMapGeomFieldIndexToArrayIndex.push_back(nBatchColumns);
2609 185 : nBatchColumns++;
2610 :
2611 185 : auto oIter = m_oMapGeomFieldIndexToGeomColBBOX.find(i);
2612 : const auto oIterParquet =
2613 185 : m_oMapGeomFieldIndexToGeomColBBOXParquet.find(i);
2614 275 : if (oIter != m_oMapGeomFieldIndexToGeomColBBOX.end() &&
2615 90 : oIterParquet !=
2616 275 : m_oMapGeomFieldIndexToGeomColBBOXParquet.end())
2617 : {
2618 90 : oIter->second.iArrayIdx = nBatchColumns++;
2619 : m_anRequestedParquetColumns.insert(
2620 90 : m_anRequestedParquetColumns.end(),
2621 90 : oIterParquet->second.anParquetCols.begin(),
2622 270 : oIterParquet->second.anParquetCols.end());
2623 : }
2624 : }
2625 : else
2626 : {
2627 24 : m_anMapGeomFieldIndexToArrayIndex.push_back(-1);
2628 : }
2629 : }
2630 :
2631 197 : CPLAssert(
2632 : static_cast<int>(m_anMapGeomFieldIndexToArrayIndex.size()) ==
2633 : m_poFeatureDefn->GetGeomFieldCount());
2634 : }
2635 : }
2636 :
2637 260 : m_nExpectedBatchColumns = m_bIgnoredFields ? nBatchColumns : -1;
2638 :
2639 260 : ComputeConstraintsArrayIdx();
2640 :
2641 : // Full invalidation
2642 260 : InvalidateCachedBatches();
2643 :
2644 260 : return eErr;
2645 : }
2646 :
2647 : /************************************************************************/
2648 : /* GetFeatureCount() */
2649 : /************************************************************************/
2650 :
2651 1057 : GIntBig OGRParquetLayer::GetFeatureCount(int bForce)
2652 : {
2653 1057 : if (m_poAttrQuery == nullptr && m_poFilterGeom == nullptr)
2654 : {
2655 55 : auto metadata = m_poArrowReader->parquet_reader()->metadata();
2656 55 : if (metadata)
2657 55 : return metadata->num_rows();
2658 : }
2659 1002 : return OGRLayer::GetFeatureCount(bForce);
2660 : }
2661 :
2662 : /************************************************************************/
2663 : /* FastGetExtent() */
2664 : /************************************************************************/
2665 :
2666 833 : bool OGRParquetLayer::FastGetExtent(int iGeomField, OGREnvelope *psExtent) const
2667 : {
2668 833 : if (OGRParquetLayerBase::FastGetExtent(iGeomField, psExtent))
2669 818 : return true;
2670 :
2671 : const auto oIterToGeomColBBOX =
2672 15 : m_oMapGeomFieldIndexToGeomColBBOXParquet.find(iGeomField);
2673 16 : if (oIterToGeomColBBOX != m_oMapGeomFieldIndexToGeomColBBOXParquet.end() &&
2674 1 : CPLTestBool(CPLGetConfigOption("OGR_PARQUET_USE_BBOX", "YES")))
2675 : {
2676 1 : OGREnvelope sExtent;
2677 : OGRField sMin, sMax;
2678 1 : OGR_RawField_SetNull(&sMin);
2679 1 : OGR_RawField_SetNull(&sMax);
2680 : bool bFoundMin, bFoundMax;
2681 1 : OGRFieldType eType = OFTMaxType;
2682 1 : OGRFieldSubType eSubType = OFSTNone;
2683 1 : std::string osMinTmp, osMaxTmp;
2684 2 : if (GetMinMaxForParquetCol(-1, oIterToGeomColBBOX->second.iParquetXMin,
2685 : nullptr, true, sMin, bFoundMin, false, sMax,
2686 : bFoundMax, eType, eSubType, osMinTmp,
2687 3 : osMaxTmp) &&
2688 1 : eType == OFTReal)
2689 : {
2690 1 : sExtent.MinX = sMin.Real;
2691 :
2692 1 : if (GetMinMaxForParquetCol(
2693 1 : -1, oIterToGeomColBBOX->second.iParquetYMin, nullptr, true,
2694 : sMin, bFoundMin, false, sMax, bFoundMax, eType, eSubType,
2695 3 : osMinTmp, osMaxTmp) &&
2696 1 : eType == OFTReal)
2697 : {
2698 1 : sExtent.MinY = sMin.Real;
2699 :
2700 1 : if (GetMinMaxForParquetCol(
2701 1 : -1, oIterToGeomColBBOX->second.iParquetXMax, nullptr,
2702 : false, sMin, bFoundMin, true, sMax, bFoundMax, eType,
2703 3 : eSubType, osMinTmp, osMaxTmp) &&
2704 1 : eType == OFTReal)
2705 : {
2706 1 : sExtent.MaxX = sMax.Real;
2707 :
2708 1 : if (GetMinMaxForParquetCol(
2709 1 : -1, oIterToGeomColBBOX->second.iParquetYMax,
2710 : nullptr, false, sMin, bFoundMin, true, sMax,
2711 3 : bFoundMax, eType, eSubType, osMinTmp, osMaxTmp) &&
2712 1 : eType == OFTReal)
2713 : {
2714 1 : sExtent.MaxY = sMax.Real;
2715 :
2716 1 : CPLDebug("PARQUET",
2717 : "Using statistics of bbox.minx, bbox.miny, "
2718 : "bbox.maxx, bbox.maxy columns to get extent");
2719 1 : m_oMapExtents[iGeomField] = sExtent;
2720 1 : *psExtent = sExtent;
2721 1 : return true;
2722 : }
2723 : }
2724 : }
2725 : }
2726 : }
2727 :
2728 14 : return false;
2729 : }
2730 :
2731 : /************************************************************************/
2732 : /* TestCapability() */
2733 : /************************************************************************/
2734 :
2735 687 : int OGRParquetLayer::TestCapability(const char *pszCap) const
2736 : {
2737 687 : if (EQUAL(pszCap, OLCFastFeatureCount))
2738 79 : return m_poAttrQuery == nullptr && m_poFilterGeom == nullptr;
2739 :
2740 608 : if (EQUAL(pszCap, OLCIgnoreFields))
2741 9 : return !m_bHasMissingMappingToParquet;
2742 :
2743 599 : if (EQUAL(pszCap, OLCFastSpatialFilter))
2744 : {
2745 252 : if (m_iGeomFieldFilter >= 0 &&
2746 168 : m_iGeomFieldFilter < static_cast<int>(m_aeGeomEncoding.size()) &&
2747 84 : OGRArrowIsGeoArrowStruct(m_aeGeomEncoding[m_iGeomFieldFilter]))
2748 : {
2749 84 : return true;
2750 : }
2751 :
2752 : #if PARQUET_VERSION_MAJOR >= 21
2753 : if (m_iGeomFieldFilter >= 0 &&
2754 : m_iGeomFieldFilter < static_cast<int>(m_aeGeomEncoding.size()) &&
2755 : m_aeGeomEncoding[m_iGeomFieldFilter] == OGRArrowGeomEncoding::WKB &&
2756 : m_iGeomFieldFilter <
2757 : static_cast<int>(
2758 : m_anMapGeomFieldIndexToParquetColumns.size()) &&
2759 : m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter].size() ==
2760 : 1)
2761 : {
2762 : const int iParquetCol =
2763 : m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter][0];
2764 : if (iParquetCol >= 0)
2765 : {
2766 : const auto metadata =
2767 : m_poArrowReader->parquet_reader()->metadata();
2768 :
2769 : int nCountRowGroupsStatsValid = 0;
2770 : const int nNumGroups = m_poArrowReader->num_row_groups();
2771 : for (int iRowGroup = 0; iRowGroup < nNumGroups &&
2772 : nCountRowGroupsStatsValid == iRowGroup;
2773 : ++iRowGroup)
2774 : {
2775 : const auto columnChunk =
2776 : metadata->RowGroup(iRowGroup)->ColumnChunk(iParquetCol);
2777 : if (auto geostats = columnChunk->geo_statistics())
2778 : {
2779 : if (geostats->dimension_valid()[0] &&
2780 : geostats->dimension_valid()[1])
2781 : {
2782 : const double dfMinX = geostats->lower_bound()[0];
2783 : const double dfMaxX = geostats->upper_bound()[0];
2784 : const double dfMinY = geostats->lower_bound()[1];
2785 : const double dfMaxY = geostats->upper_bound()[1];
2786 : if (std::isfinite(dfMinX) &&
2787 : std::isfinite(dfMaxX) &&
2788 : std::isfinite(dfMinY) && std::isfinite(dfMaxY))
2789 : {
2790 : nCountRowGroupsStatsValid++;
2791 : }
2792 : }
2793 : }
2794 : }
2795 : if (nCountRowGroupsStatsValid == nNumGroups)
2796 : {
2797 : return true;
2798 : }
2799 : }
2800 : }
2801 : #endif
2802 :
2803 : // fallback to base method
2804 : }
2805 :
2806 515 : return OGRParquetLayerBase::TestCapability(pszCap);
2807 : }
2808 :
2809 : /************************************************************************/
2810 : /* GetMetadataItem() */
2811 : /************************************************************************/
2812 :
2813 510 : const char *OGRParquetLayer::GetMetadataItem(const char *pszName,
2814 : const char *pszDomain)
2815 : {
2816 : // Mostly for unit test purposes
2817 510 : if (pszDomain != nullptr && EQUAL(pszDomain, "_PARQUET_"))
2818 : {
2819 11 : int nRowGroupIdx = -1;
2820 11 : int nColumn = -1;
2821 11 : if (EQUAL(pszName, "NUM_ROW_GROUPS"))
2822 : {
2823 3 : return CPLSPrintf("%d", m_poArrowReader->num_row_groups());
2824 : }
2825 8 : if (EQUAL(pszName, "CREATOR"))
2826 : {
2827 4 : return CPLSPrintf("%s", m_poArrowReader->parquet_reader()
2828 4 : ->metadata()
2829 2 : ->created_by()
2830 2 : .c_str());
2831 : }
2832 12 : else if (sscanf(pszName, "ROW_GROUPS[%d]", &nRowGroupIdx) == 1 &&
2833 6 : strstr(pszName, ".NUM_ROWS"))
2834 : {
2835 : try
2836 : {
2837 : auto poRowGroup =
2838 6 : m_poArrowReader->parquet_reader()->RowGroup(nRowGroupIdx);
2839 3 : if (poRowGroup == nullptr)
2840 0 : return nullptr;
2841 3 : return CPLSPrintf("%" PRId64,
2842 3 : poRowGroup->metadata()->num_rows());
2843 : }
2844 0 : catch (const std::exception &)
2845 : {
2846 : }
2847 : }
2848 6 : else if (sscanf(pszName, "ROW_GROUPS[%d].COLUMNS[%d]", &nRowGroupIdx,
2849 6 : &nColumn) == 2 &&
2850 3 : strstr(pszName, ".COMPRESSION"))
2851 : {
2852 : try
2853 : {
2854 : auto poRowGroup =
2855 6 : m_poArrowReader->parquet_reader()->RowGroup(nRowGroupIdx);
2856 3 : if (poRowGroup == nullptr)
2857 0 : return nullptr;
2858 6 : auto poColumn = poRowGroup->metadata()->ColumnChunk(nColumn);
2859 3 : return CPLSPrintf("%s", arrow::util::Codec::GetCodecAsString(
2860 3 : poColumn->compression())
2861 3 : .c_str());
2862 : }
2863 0 : catch (const std::exception &)
2864 : {
2865 : }
2866 : }
2867 0 : return nullptr;
2868 : }
2869 499 : if (pszDomain != nullptr && EQUAL(pszDomain, "_PARQUET_METADATA_"))
2870 : {
2871 628 : const auto metadata = m_poArrowReader->parquet_reader()->metadata();
2872 314 : const auto &kv_metadata = metadata->key_value_metadata();
2873 314 : if (kv_metadata && kv_metadata->Contains(pszName))
2874 : {
2875 311 : auto metadataItem = kv_metadata->Get(pszName);
2876 311 : if (metadataItem.ok())
2877 : {
2878 311 : return CPLSPrintf("%s", metadataItem->c_str());
2879 : }
2880 : }
2881 3 : return nullptr;
2882 : }
2883 185 : return OGRLayer::GetMetadataItem(pszName, pszDomain);
2884 : }
2885 :
2886 : /************************************************************************/
2887 : /* GetMetadata() */
2888 : /************************************************************************/
2889 :
2890 61 : CSLConstList OGRParquetLayer::GetMetadata(const char *pszDomain)
2891 : {
2892 : // Mostly for unit test purposes
2893 61 : if (pszDomain != nullptr && EQUAL(pszDomain, "_PARQUET_METADATA_"))
2894 : {
2895 2 : m_aosFeatherMetadata.Clear();
2896 4 : const auto metadata = m_poArrowReader->parquet_reader()->metadata();
2897 2 : const auto &kv_metadata = metadata->key_value_metadata();
2898 2 : if (kv_metadata)
2899 : {
2900 8 : for (const auto &kv : kv_metadata->sorted_pairs())
2901 : {
2902 : m_aosFeatherMetadata.SetNameValue(kv.first.c_str(),
2903 6 : kv.second.c_str());
2904 : }
2905 : }
2906 2 : return m_aosFeatherMetadata.List();
2907 : }
2908 :
2909 : // Mostly for unit test purposes
2910 59 : if (pszDomain != nullptr && EQUAL(pszDomain, "_GDAL_CREATION_OPTIONS_"))
2911 : {
2912 6 : return m_aosCreationOptions.List();
2913 : }
2914 :
2915 53 : return OGRLayer::GetMetadata(pszDomain);
2916 : }
2917 :
2918 : /************************************************************************/
2919 : /* GetArrowStream() */
2920 : /************************************************************************/
2921 :
2922 135 : bool OGRParquetLayer::GetArrowStream(struct ArrowArrayStream *out_stream,
2923 : CSLConstList papszOptions)
2924 : {
2925 : const char *pszMaxFeaturesInBatch =
2926 135 : CSLFetchNameValue(papszOptions, "MAX_FEATURES_IN_BATCH");
2927 135 : if (pszMaxFeaturesInBatch)
2928 : {
2929 14 : int nMaxBatchSize = atoi(pszMaxFeaturesInBatch);
2930 14 : if (nMaxBatchSize <= 0)
2931 0 : nMaxBatchSize = 1;
2932 14 : if (nMaxBatchSize > INT_MAX - 1)
2933 0 : nMaxBatchSize = INT_MAX - 1;
2934 14 : m_poArrowReader->set_batch_size(nMaxBatchSize);
2935 : }
2936 135 : return OGRArrowLayer::GetArrowStream(out_stream, papszOptions);
2937 : }
2938 :
2939 : /************************************************************************/
2940 : /* SetNextByIndex() */
2941 : /************************************************************************/
2942 :
2943 14 : OGRErr OGRParquetLayer::SetNextByIndex(GIntBig nIndex)
2944 : {
2945 14 : if (nIndex < 0)
2946 : {
2947 4 : m_bEOF = true;
2948 4 : return OGRERR_NON_EXISTING_FEATURE;
2949 : }
2950 :
2951 20 : const auto metadata = m_poArrowReader->parquet_reader()->metadata();
2952 10 : if (nIndex >= metadata->num_rows())
2953 : {
2954 4 : m_bEOF = true;
2955 4 : return OGRERR_NON_EXISTING_FEATURE;
2956 : }
2957 :
2958 6 : m_bEOF = false;
2959 :
2960 6 : if (m_bSingleBatch)
2961 : {
2962 0 : ResetReading();
2963 0 : m_nIdxInBatch = nIndex;
2964 0 : m_nFeatureIdx = nIndex;
2965 0 : return OGRERR_NONE;
2966 : }
2967 :
2968 6 : const int nNumGroups = m_poArrowReader->num_row_groups();
2969 6 : int64_t nAccRows = 0;
2970 6 : const auto nBatchSize = m_poArrowReader->properties().batch_size();
2971 6 : m_iRecordBatch = -1;
2972 6 : ResetReading();
2973 6 : m_iRecordBatch = 0;
2974 7 : for (int iGroup = 0; iGroup < nNumGroups; ++iGroup)
2975 : {
2976 7 : const auto nRowsInRowGroup = metadata->RowGroup(iGroup)->num_rows();
2977 7 : const int64_t nNextAccRows = nAccRows + nRowsInRowGroup;
2978 7 : if (nIndex < nNextAccRows)
2979 : {
2980 6 : if (!CreateRecordBatchReader(iGroup))
2981 0 : return OGRERR_FAILURE;
2982 :
2983 12 : std::shared_ptr<arrow::RecordBatch> poBatch;
2984 : while (true)
2985 : {
2986 6 : auto status = m_poRecordBatchReader->ReadNext(&poBatch);
2987 6 : if (!status.ok())
2988 : {
2989 0 : CPLError(CE_Failure, CPLE_AppDefined,
2990 0 : "ReadNext() failed: %s", status.message().c_str());
2991 0 : m_iRecordBatch = -1;
2992 0 : ResetReading();
2993 0 : return OGRERR_FAILURE;
2994 : }
2995 6 : if (poBatch == nullptr)
2996 : {
2997 0 : m_iRecordBatch = -1;
2998 0 : ResetReading();
2999 0 : return OGRERR_FAILURE;
3000 : }
3001 6 : if (nIndex < nAccRows + poBatch->num_rows())
3002 : {
3003 6 : break;
3004 : }
3005 0 : nAccRows += poBatch->num_rows();
3006 0 : m_iRecordBatch++;
3007 0 : }
3008 6 : m_nIdxInBatch = nIndex - nAccRows;
3009 6 : m_nFeatureIdx = nIndex;
3010 6 : SetBatch(poBatch);
3011 6 : return OGRERR_NONE;
3012 : }
3013 1 : nAccRows = nNextAccRows;
3014 1 : m_iRecordBatch +=
3015 1 : static_cast<int>(cpl::div_round_up(nRowsInRowGroup, nBatchSize));
3016 : }
3017 :
3018 0 : m_iRecordBatch = -1;
3019 0 : ResetReading();
3020 0 : return OGRERR_FAILURE;
3021 : }
3022 :
3023 : /************************************************************************/
3024 : /* GetStats() */
3025 : /************************************************************************/
3026 :
3027 : template <class STAT_TYPE> struct GetStats
3028 : {
3029 : using T = typename STAT_TYPE::T;
3030 :
3031 609 : static T min(const std::shared_ptr<parquet::FileMetaData> &metadata,
3032 : const int iRowGroup, const int numRowGroups, const int iCol,
3033 : bool &bFound)
3034 : {
3035 609 : T v{};
3036 609 : bFound = false;
3037 1226 : for (int i = 0; i < (iRowGroup < 0 ? numRowGroups : 1); i++)
3038 : {
3039 653 : const auto columnChunk =
3040 30 : metadata->RowGroup(iRowGroup < 0 ? i : iRowGroup)
3041 : ->ColumnChunk(iCol);
3042 623 : const auto colStats = columnChunk->statistics();
3043 1243 : if (columnChunk->is_stats_set() && colStats &&
3044 620 : colStats->HasMinMax())
3045 : {
3046 614 : auto castStats = static_cast<STAT_TYPE *>(colStats.get());
3047 614 : const auto rowGroupVal = castStats->min();
3048 614 : if (i == 0 || rowGroupVal < v)
3049 : {
3050 602 : bFound = true;
3051 602 : v = rowGroupVal;
3052 : }
3053 : }
3054 9 : else if (columnChunk->num_values() > 0)
3055 : {
3056 6 : bFound = false;
3057 6 : break;
3058 : }
3059 : }
3060 609 : return v;
3061 : }
3062 :
3063 598 : static T max(const std::shared_ptr<parquet::FileMetaData> &metadata,
3064 : const int iRowGroup, const int numRowGroups, const int iCol,
3065 : bool &bFound)
3066 : {
3067 598 : T v{};
3068 598 : bFound = false;
3069 1210 : for (int i = 0; i < (iRowGroup < 0 ? numRowGroups : 1); i++)
3070 : {
3071 642 : const auto columnChunk =
3072 30 : metadata->RowGroup(iRowGroup < 0 ? i : iRowGroup)
3073 : ->ColumnChunk(iCol);
3074 612 : const auto colStats = columnChunk->statistics();
3075 1222 : if (columnChunk->is_stats_set() && colStats &&
3076 610 : colStats->HasMinMax())
3077 : {
3078 610 : auto castStats = static_cast<STAT_TYPE *>(colStats.get());
3079 610 : const auto rowGroupVal = castStats->max();
3080 610 : if (i == 0 || rowGroupVal > v)
3081 : {
3082 608 : bFound = true;
3083 608 : v = rowGroupVal;
3084 : }
3085 : }
3086 2 : else if (columnChunk->num_values() > 0)
3087 : {
3088 0 : bFound = false;
3089 0 : break;
3090 : }
3091 : }
3092 598 : return v;
3093 : }
3094 : };
3095 :
3096 : template <> struct GetStats<parquet::ByteArrayStatistics>
3097 : {
3098 : static std::string
3099 39 : min(const std::shared_ptr<parquet::FileMetaData> &metadata,
3100 : const int iRowGroup, const int numRowGroups, const int iCol,
3101 : bool &bFound)
3102 : {
3103 39 : std::string v{};
3104 39 : bFound = false;
3105 79 : for (int i = 0; i < (iRowGroup < 0 ? numRowGroups : 1); i++)
3106 : {
3107 : const auto columnChunk =
3108 40 : metadata->RowGroup(iRowGroup < 0 ? i : iRowGroup)
3109 80 : ->ColumnChunk(iCol);
3110 80 : const auto colStats = columnChunk->statistics();
3111 80 : if (columnChunk->is_stats_set() && colStats &&
3112 40 : colStats->HasMinMax())
3113 : {
3114 : auto castStats =
3115 40 : static_cast<parquet::ByteArrayStatistics *>(colStats.get());
3116 40 : const auto rowGroupValRaw = castStats->min();
3117 : std::string rowGroupVal(
3118 40 : reinterpret_cast<const char *>(rowGroupValRaw.ptr),
3119 80 : rowGroupValRaw.len);
3120 40 : if (i == 0 || rowGroupVal < v)
3121 : {
3122 39 : bFound = true;
3123 39 : v = std::move(rowGroupVal);
3124 : }
3125 : }
3126 : }
3127 39 : return v;
3128 : }
3129 :
3130 : static std::string
3131 39 : max(const std::shared_ptr<parquet::FileMetaData> &metadata,
3132 : const int iRowGroup, const int numRowGroups, const int iCol,
3133 : bool &bFound)
3134 : {
3135 39 : std::string v{};
3136 39 : bFound = false;
3137 79 : for (int i = 0; i < (iRowGroup < 0 ? numRowGroups : 1); i++)
3138 : {
3139 : const auto columnChunk =
3140 40 : metadata->RowGroup(iRowGroup < 0 ? i : iRowGroup)
3141 40 : ->ColumnChunk(iCol);
3142 40 : const auto colStats = columnChunk->statistics();
3143 80 : if (columnChunk->is_stats_set() && colStats &&
3144 40 : colStats->HasMinMax())
3145 : {
3146 : auto castStats =
3147 40 : static_cast<parquet::ByteArrayStatistics *>(colStats.get());
3148 40 : const auto rowGroupValRaw = castStats->max();
3149 : std::string rowGroupVal(
3150 40 : reinterpret_cast<const char *>(rowGroupValRaw.ptr),
3151 80 : rowGroupValRaw.len);
3152 40 : if (i == 0 || rowGroupVal > v)
3153 : {
3154 40 : bFound = true;
3155 40 : v = std::move(rowGroupVal);
3156 : }
3157 : }
3158 : else
3159 : {
3160 0 : bFound = false;
3161 0 : break;
3162 : }
3163 : }
3164 39 : return v;
3165 : }
3166 : };
3167 :
3168 : /************************************************************************/
3169 : /* GetMinMaxForOGRField() */
3170 : /************************************************************************/
3171 :
3172 256 : bool OGRParquetLayer::GetMinMaxForOGRField(int iRowGroup, // -1 for all
3173 : int iOGRField, bool bComputeMin,
3174 : OGRField &sMin, bool &bFoundMin,
3175 : bool bComputeMax, OGRField &sMax,
3176 : bool &bFoundMax, OGRFieldType &eType,
3177 : OGRFieldSubType &eSubType,
3178 : std::string &osMinTmp,
3179 : std::string &osMaxTmp) const
3180 : {
3181 256 : OGR_RawField_SetNull(&sMin);
3182 256 : OGR_RawField_SetNull(&sMax);
3183 256 : eType = OFTReal;
3184 256 : eSubType = OFSTNone;
3185 256 : bFoundMin = false;
3186 256 : bFoundMax = false;
3187 :
3188 : const std::vector<int> anCols =
3189 : iOGRField == OGR_FID_INDEX
3190 5 : ? std::vector<int>{m_iFIDParquetColumn}
3191 : : GetParquetColumnIndicesForArrowField(
3192 1019 : GetLayerDefn()->GetFieldDefn(iOGRField)->GetNameRef());
3193 256 : if (anCols.empty() || anCols[0] < 0)
3194 2 : return false;
3195 254 : const int iCol = anCols[0];
3196 : const auto &arrowType = iOGRField == OGR_FID_INDEX
3197 254 : ? m_poFIDType
3198 249 : : GetArrowFieldTypes()[iOGRField];
3199 :
3200 254 : const bool bRet = GetMinMaxForParquetCol(
3201 : iRowGroup, iCol, arrowType, bComputeMin, sMin, bFoundMin, bComputeMax,
3202 : sMax, bFoundMax, eType, eSubType, osMinTmp, osMaxTmp);
3203 :
3204 254 : if (eType == OFTInteger64 && arrowType->id() == arrow::Type::TIMESTAMP)
3205 : {
3206 : const OGRFieldDefn oDummyFIDFieldDefn(m_osFIDColumn.c_str(),
3207 4 : OFTInteger64);
3208 : const OGRFieldDefn *poFieldDefn =
3209 2 : iOGRField == OGR_FID_INDEX ? &oDummyFIDFieldDefn
3210 : : const_cast<OGRParquetLayer *>(this)
3211 2 : ->GetLayerDefn()
3212 2 : ->GetFieldDefn(iOGRField);
3213 2 : if (poFieldDefn->GetType() == OFTDateTime)
3214 : {
3215 : const auto timestampType =
3216 2 : static_cast<arrow::TimestampType *>(arrowType.get());
3217 2 : if (bFoundMin)
3218 : {
3219 1 : const int64_t timestamp = sMin.Integer64;
3220 1 : OGRArrowLayer::TimestampToOGR(timestamp, timestampType,
3221 : poFieldDefn->GetTZFlag(), &sMin);
3222 : }
3223 2 : if (bFoundMax)
3224 : {
3225 1 : const int64_t timestamp = sMax.Integer64;
3226 1 : OGRArrowLayer::TimestampToOGR(timestamp, timestampType,
3227 : poFieldDefn->GetTZFlag(), &sMax);
3228 : }
3229 2 : eType = OFTDateTime;
3230 : }
3231 : }
3232 :
3233 254 : return bRet;
3234 : }
3235 :
3236 : /************************************************************************/
3237 : /* GetMinMaxForParquetCol() */
3238 : /************************************************************************/
3239 :
3240 1067 : bool OGRParquetLayer::GetMinMaxForParquetCol(
3241 : int iRowGroup, // -1 for all
3242 : int iCol,
3243 : const std::shared_ptr<arrow::DataType> &arrowType, // potentially nullptr
3244 : bool bComputeMin, OGRField &sMin, bool &bFoundMin, bool bComputeMax,
3245 : OGRField &sMax, bool &bFoundMax, OGRFieldType &eType,
3246 : OGRFieldSubType &eSubType, std::string &osMinTmp,
3247 : std::string &osMaxTmp) const
3248 : {
3249 1067 : OGR_RawField_SetNull(&sMin);
3250 1067 : OGR_RawField_SetNull(&sMax);
3251 1067 : eType = OFTReal;
3252 1067 : eSubType = OFSTNone;
3253 1067 : bFoundMin = false;
3254 1067 : bFoundMax = false;
3255 :
3256 2134 : const auto metadata = GetReader()->parquet_reader()->metadata();
3257 1067 : const auto numRowGroups = metadata->num_row_groups();
3258 :
3259 1067 : if (numRowGroups == 0)
3260 0 : return false;
3261 :
3262 2134 : const auto rowGroup0 = metadata->RowGroup(0);
3263 1067 : if (iCol < 0 || iCol >= rowGroup0->num_columns())
3264 : {
3265 0 : CPLError(CE_Failure, CPLE_AppDefined,
3266 : "GetMinMaxForParquetCol(): invalid iCol=%d", iCol);
3267 0 : return false;
3268 : }
3269 2134 : const auto rowGroup0columnChunk = rowGroup0->ColumnChunk(iCol);
3270 2134 : const auto rowGroup0Stats = rowGroup0columnChunk->statistics();
3271 1067 : if (!(rowGroup0columnChunk->is_stats_set() && rowGroup0Stats))
3272 : {
3273 0 : CPLDebug("PARQUET", "Statistics not available for field %s",
3274 0 : rowGroup0columnChunk->path_in_schema()->ToDotString().c_str());
3275 0 : return false;
3276 : }
3277 :
3278 1067 : const auto physicalType = rowGroup0Stats->physical_type();
3279 :
3280 1067 : if (bComputeMin)
3281 : {
3282 651 : if (physicalType == parquet::Type::BOOLEAN)
3283 : {
3284 54 : eType = OFTInteger;
3285 54 : eSubType = OFSTBoolean;
3286 54 : sMin.Integer = GetStats<parquet::BoolStatistics>::min(
3287 : metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
3288 : }
3289 597 : else if (physicalType == parquet::Type::INT32)
3290 : {
3291 78 : if (arrowType && arrowType->id() == arrow::Type::UINT32)
3292 : {
3293 : // With parquet file version 2.0,
3294 : // statistics of uint32 fields are
3295 : // stored as signed int32 values...
3296 1 : eType = OFTInteger64;
3297 1 : int nVal = GetStats<parquet::Int32Statistics>::min(
3298 : metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
3299 1 : if (bFoundMin)
3300 : {
3301 1 : sMin.Integer64 = static_cast<uint32_t>(nVal);
3302 : }
3303 : }
3304 : else
3305 : {
3306 77 : eType = OFTInteger;
3307 77 : if (arrowType && arrowType->id() == arrow::Type::INT16)
3308 1 : eSubType = OFSTInt16;
3309 77 : sMin.Integer = GetStats<parquet::Int32Statistics>::min(
3310 : metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
3311 : }
3312 : }
3313 519 : else if (physicalType == parquet::Type::INT64)
3314 : {
3315 37 : eType = OFTInteger64;
3316 37 : sMin.Integer64 = GetStats<parquet::Int64Statistics>::min(
3317 : metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
3318 : }
3319 482 : else if (physicalType == parquet::Type::FLOAT)
3320 : {
3321 138 : eType = OFTReal;
3322 138 : eSubType = OFSTFloat32;
3323 138 : sMin.Real = GetStats<parquet::FloatStatistics>::min(
3324 : metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
3325 : }
3326 344 : else if (physicalType == parquet::Type::DOUBLE)
3327 : {
3328 302 : eType = OFTReal;
3329 302 : sMin.Real = GetStats<parquet::DoubleStatistics>::min(
3330 : metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
3331 : }
3332 42 : else if (arrowType &&
3333 53 : (arrowType->id() == arrow::Type::STRING ||
3334 95 : arrowType->id() == arrow::Type::LARGE_STRING) &&
3335 : physicalType == parquet::Type::BYTE_ARRAY)
3336 : {
3337 78 : osMinTmp = GetStats<parquet::ByteArrayStatistics>::min(
3338 39 : metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
3339 39 : if (bFoundMin)
3340 : {
3341 39 : eType = OFTString;
3342 39 : sMin.String = &osMinTmp[0];
3343 : }
3344 : }
3345 : }
3346 :
3347 1067 : if (bComputeMax)
3348 : {
3349 640 : if (physicalType == parquet::Type::BOOLEAN)
3350 : {
3351 54 : eType = OFTInteger;
3352 54 : eSubType = OFSTBoolean;
3353 54 : sMax.Integer = GetStats<parquet::BoolStatistics>::max(
3354 : metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
3355 : }
3356 586 : else if (physicalType == parquet::Type::INT32)
3357 : {
3358 78 : if (arrowType && arrowType->id() == arrow::Type::UINT32)
3359 : {
3360 : // With parquet file version 2.0,
3361 : // statistics of uint32 fields are
3362 : // stored as signed int32 values...
3363 1 : eType = OFTInteger64;
3364 1 : int nVal = GetStats<parquet::Int32Statistics>::max(
3365 : metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
3366 1 : if (bFoundMax)
3367 : {
3368 1 : sMax.Integer64 = static_cast<uint32_t>(nVal);
3369 : }
3370 : }
3371 : else
3372 : {
3373 77 : eType = OFTInteger;
3374 77 : if (arrowType && arrowType->id() == arrow::Type::INT16)
3375 1 : eSubType = OFSTInt16;
3376 77 : sMax.Integer = GetStats<parquet::Int32Statistics>::max(
3377 : metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
3378 : }
3379 : }
3380 508 : else if (physicalType == parquet::Type::INT64)
3381 : {
3382 37 : eType = OFTInteger64;
3383 37 : sMax.Integer64 = GetStats<parquet::Int64Statistics>::max(
3384 : metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
3385 : }
3386 471 : else if (physicalType == parquet::Type::FLOAT)
3387 : {
3388 128 : eType = OFTReal;
3389 128 : eSubType = OFSTFloat32;
3390 128 : sMax.Real = GetStats<parquet::FloatStatistics>::max(
3391 : metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
3392 : }
3393 343 : else if (physicalType == parquet::Type::DOUBLE)
3394 : {
3395 301 : eType = OFTReal;
3396 301 : sMax.Real = GetStats<parquet::DoubleStatistics>::max(
3397 : metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
3398 : }
3399 42 : else if (arrowType &&
3400 53 : (arrowType->id() == arrow::Type::STRING ||
3401 95 : arrowType->id() == arrow::Type::LARGE_STRING) &&
3402 : physicalType == parquet::Type::BYTE_ARRAY)
3403 : {
3404 78 : osMaxTmp = GetStats<parquet::ByteArrayStatistics>::max(
3405 39 : metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
3406 39 : if (bFoundMax)
3407 : {
3408 39 : eType = OFTString;
3409 39 : sMax.String = &osMaxTmp[0];
3410 : }
3411 : }
3412 : }
3413 :
3414 1067 : return bFoundMin || bFoundMax;
3415 : }
3416 :
3417 : /************************************************************************/
3418 : /* GeomColsBBOXParquet() */
3419 : /************************************************************************/
3420 :
3421 : /** Return for a given geometry column (iGeom: in [0, GetGeomFieldCount()-1] range),
3422 : * the Parquet column number of the corresponding xmin,ymin,xmax,ymax bounding
3423 : * box columns, if existing.
3424 : */
3425 1 : bool OGRParquetLayer::GeomColsBBOXParquet(int iGeom, int &iParquetXMin,
3426 : int &iParquetYMin, int &iParquetXMax,
3427 : int &iParquetYMax) const
3428 : {
3429 1 : const auto oIter = m_oMapGeomFieldIndexToGeomColBBOXParquet.find(iGeom);
3430 : const bool bFound =
3431 1 : (oIter != m_oMapGeomFieldIndexToGeomColBBOXParquet.end());
3432 1 : if (bFound)
3433 : {
3434 1 : iParquetXMin = oIter->second.iParquetXMin;
3435 1 : iParquetYMin = oIter->second.iParquetYMin;
3436 1 : iParquetXMax = oIter->second.iParquetXMax;
3437 1 : iParquetYMax = oIter->second.iParquetYMax;
3438 : }
3439 1 : return bFound;
3440 : }
|