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