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