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