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