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