Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: Parquet Translator
4 : * Purpose: Implements OGRParquetDriver.
5 : * Author: Even Rouault, <even.rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2022, Planet Labs
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #ifdef STANDALONE
14 : #include "gdal_version.h"
15 : #else
16 : #undef DO_NOT_DEFINE_GDAL_DATE_NAME
17 : #include "gdal_version_full/gdal_version.h"
18 : #endif
19 :
20 : #include "ogr_parquet.h"
21 :
22 : #include "../arrow_common/ograrrowwriterlayer.hpp"
23 :
24 : #include "ogr_wkb.h"
25 :
26 : #include <utility>
27 :
28 : /************************************************************************/
29 : /* OGRParquetWriterLayer() */
30 : /************************************************************************/
31 :
32 265 : OGRParquetWriterLayer::OGRParquetWriterLayer(
33 : OGRParquetWriterDataset *poDataset, arrow::MemoryPool *poMemoryPool,
34 : const std::shared_ptr<arrow::io::OutputStream> &poOutputStream,
35 265 : const char *pszLayerName)
36 : : OGRArrowWriterLayer(poMemoryPool, poOutputStream, pszLayerName),
37 265 : m_poDataset(poDataset)
38 : {
39 265 : m_bWriteFieldArrowExtensionName = CPLTestBool(
40 : CPLGetConfigOption("OGR_PARQUET_WRITE_ARROW_EXTENSION_NAME", "NO"));
41 265 : }
42 :
43 : /************************************************************************/
44 : /* Close() */
45 : /************************************************************************/
46 :
47 262 : bool OGRParquetWriterLayer::Close()
48 : {
49 262 : if (m_poTmpGPKGLayer)
50 : {
51 2 : if (!CopyTmpGpkgLayerToFinalFile())
52 0 : return false;
53 : }
54 :
55 262 : if (m_bInitializationOK)
56 : {
57 262 : if (!FinalizeWriting())
58 0 : return false;
59 : }
60 :
61 262 : return true;
62 : }
63 :
64 : /************************************************************************/
65 : /* CopyTmpGpkgLayerToFinalFile() */
66 : /************************************************************************/
67 :
68 2 : bool OGRParquetWriterLayer::CopyTmpGpkgLayerToFinalFile()
69 : {
70 2 : if (!m_poTmpGPKGLayer)
71 : {
72 0 : return true;
73 : }
74 :
75 2 : CPLDebug("PARQUET", "CopyTmpGpkgLayerToFinalFile(): start...");
76 :
77 2 : VSIUnlink(m_poTmpGPKG->GetDescription());
78 :
79 4 : OGRFeature oFeat(m_poFeatureDefn);
80 :
81 : // Interval in terms of features between 2 debug progress report messages
82 2 : constexpr int PROGRESS_FC_INTERVAL = 100 * 1000;
83 :
84 : // First, write features without geometries
85 : {
86 2 : auto poTmpLayer = std::unique_ptr<OGRLayer>(m_poTmpGPKG->ExecuteSQL(
87 : "SELECT serialized_feature FROM tmp WHERE fid NOT IN (SELECT id "
88 : "FROM rtree_tmp_geom)",
89 2 : nullptr, nullptr));
90 2 : if (!poTmpLayer)
91 0 : return false;
92 1004 : for (const auto &poSrcFeature : poTmpLayer.get())
93 : {
94 1002 : int nBytesFeature = 0;
95 : const GByte *pabyFeatureData =
96 1002 : poSrcFeature->GetFieldAsBinary(0, &nBytesFeature);
97 1002 : if (!oFeat.DeserializeFromBinary(pabyFeatureData, nBytesFeature))
98 : {
99 0 : CPLError(CE_Failure, CPLE_AppDefined,
100 : "Cannot deserialize feature");
101 0 : return false;
102 : }
103 1002 : if (OGRArrowWriterLayer::ICreateFeature(&oFeat) != OGRERR_NONE)
104 : {
105 0 : return false;
106 : }
107 :
108 1002 : if ((m_nFeatureCount % PROGRESS_FC_INTERVAL) == 0)
109 : {
110 0 : CPLDebugProgress(
111 : "PARQUET",
112 : "CopyTmpGpkgLayerToFinalFile(): %.02f%% progress",
113 0 : 100.0 * double(m_nFeatureCount) /
114 0 : double(m_nTmpFeatureCount));
115 : }
116 : }
117 :
118 2 : if (!FlushFeatures())
119 : {
120 0 : return false;
121 : }
122 : }
123 :
124 : // Now walk through the GPKG RTree for features with geometries
125 : // Cf https://github.com/sqlite/sqlite/blob/master/ext/rtree/rtree.c
126 : // for the description of the content of the rtree _node table
127 4 : std::vector<std::pair<int64_t, int>> aNodeNoDepthPair;
128 2 : int nTreeDepth = 0;
129 : // Queue the root node
130 : aNodeNoDepthPair.emplace_back(
131 2 : std::make_pair(/* nodeNo = */ 1, /* depth = */ 0));
132 2 : int nCountWrittenFeaturesSinceLastFlush = 0;
133 50 : while (!aNodeNoDepthPair.empty())
134 : {
135 48 : const auto &oLastPair = aNodeNoDepthPair.back();
136 48 : const int64_t nNodeNo = oLastPair.first;
137 48 : const int nCurDepth = oLastPair.second;
138 : //CPLDebug("PARQUET", "Reading nodeNode=%d, curDepth=%d", int(nNodeNo), nCurDepth);
139 48 : aNodeNoDepthPair.pop_back();
140 :
141 48 : auto poRTreeLayer = std::unique_ptr<OGRLayer>(m_poTmpGPKG->ExecuteSQL(
142 : CPLSPrintf("SELECT data FROM rtree_tmp_geom_node WHERE nodeno "
143 : "= " CPL_FRMT_GIB,
144 : static_cast<GIntBig>(nNodeNo)),
145 48 : nullptr, nullptr));
146 48 : if (!poRTreeLayer)
147 : {
148 0 : CPLError(CE_Failure, CPLE_AppDefined,
149 : "Cannot read node " CPL_FRMT_GIB,
150 : static_cast<GIntBig>(nNodeNo));
151 0 : return false;
152 : }
153 : const auto poRTreeFeature =
154 48 : std::unique_ptr<const OGRFeature>(poRTreeLayer->GetNextFeature());
155 48 : if (!poRTreeFeature)
156 : {
157 0 : CPLError(CE_Failure, CPLE_AppDefined,
158 : "Cannot read node " CPL_FRMT_GIB,
159 : static_cast<GIntBig>(nNodeNo));
160 0 : return false;
161 : }
162 :
163 48 : int nNodeBytes = 0;
164 : const GByte *pabyNodeData =
165 48 : poRTreeFeature->GetFieldAsBinary(0, &nNodeBytes);
166 48 : constexpr int BLOB_HEADER_SIZE = 4;
167 48 : if (nNodeBytes < BLOB_HEADER_SIZE)
168 : {
169 0 : CPLError(CE_Failure, CPLE_AppDefined,
170 : "Not enough bytes when reading node " CPL_FRMT_GIB,
171 : static_cast<GIntBig>(nNodeNo));
172 0 : return false;
173 : }
174 48 : if (nNodeNo == 1)
175 : {
176 : // Get the RTree depth from the root node
177 2 : nTreeDepth = (pabyNodeData[0] << 8) | pabyNodeData[1];
178 : //CPLDebug("PARQUET", "nTreeDepth = %d", nTreeDepth);
179 : }
180 :
181 48 : const int nCellCount = (pabyNodeData[2] << 8) | pabyNodeData[3];
182 48 : constexpr int SIZEOF_CELL = 24; // int64_t + 4 float
183 48 : if (nNodeBytes < BLOB_HEADER_SIZE + SIZEOF_CELL * nCellCount)
184 : {
185 0 : CPLError(CE_Failure, CPLE_AppDefined,
186 : "Not enough bytes when reading node " CPL_FRMT_GIB,
187 : static_cast<GIntBig>(nNodeNo));
188 0 : return false;
189 : }
190 :
191 48 : size_t nOffset = BLOB_HEADER_SIZE;
192 48 : if (nCurDepth == nTreeDepth)
193 : {
194 : // Leaf node: it references feature IDs.
195 :
196 : // If we are about to go above m_nRowGroupSize, flush past
197 : // features now, to improve the spatial compacity of the row group.
198 46 : if (m_nRowGroupSize > nCellCount &&
199 46 : nCountWrittenFeaturesSinceLastFlush + nCellCount >
200 46 : m_nRowGroupSize)
201 : {
202 14 : nCountWrittenFeaturesSinceLastFlush = 0;
203 14 : if (!FlushFeatures())
204 : {
205 0 : return false;
206 : }
207 : }
208 :
209 : // nCellCount shouldn't be over 51 normally, but even 65535
210 : // would be fine...
211 : // coverity[tainted_data]
212 1248 : for (int i = 0; i < nCellCount; ++i)
213 : {
214 : int64_t nFID;
215 1202 : memcpy(&nFID, pabyNodeData + nOffset, sizeof(int64_t));
216 1202 : CPL_MSBPTR64(&nFID);
217 :
218 : const auto poSrcFeature = std::unique_ptr<const OGRFeature>(
219 1202 : m_poTmpGPKGLayer->GetFeature(nFID));
220 1202 : if (!poSrcFeature)
221 : {
222 0 : CPLError(CE_Failure, CPLE_AppDefined,
223 : "Cannot get feature " CPL_FRMT_GIB,
224 : static_cast<GIntBig>(nFID));
225 0 : return false;
226 : }
227 :
228 1202 : int nBytesFeature = 0;
229 : const GByte *pabyFeatureData =
230 1202 : poSrcFeature->GetFieldAsBinary(0, &nBytesFeature);
231 1202 : if (!oFeat.DeserializeFromBinary(pabyFeatureData,
232 : nBytesFeature))
233 : {
234 0 : CPLError(CE_Failure, CPLE_AppDefined,
235 : "Cannot deserialize feature");
236 0 : return false;
237 : }
238 1202 : if (OGRArrowWriterLayer::ICreateFeature(&oFeat) != OGRERR_NONE)
239 : {
240 0 : return false;
241 : }
242 :
243 1202 : nOffset += SIZEOF_CELL;
244 :
245 1202 : ++nCountWrittenFeaturesSinceLastFlush;
246 :
247 1202 : if ((m_nFeatureCount % PROGRESS_FC_INTERVAL) == 0 ||
248 1202 : m_nFeatureCount == m_nTmpFeatureCount / 2)
249 : {
250 2 : CPLDebugProgress(
251 : "PARQUET",
252 : "CopyTmpGpkgLayerToFinalFile(): %.02f%% progress",
253 2 : 100.0 * double(m_nFeatureCount) /
254 2 : double(m_nTmpFeatureCount));
255 : }
256 : }
257 : }
258 : else
259 : {
260 : // Non-leaf node: it references child nodes.
261 :
262 : // nCellCount shouldn't be over 51 normally, but even 65535
263 : // would be fine...
264 : // coverity[tainted_data]
265 48 : for (int i = 0; i < nCellCount; ++i)
266 : {
267 : int64_t nNode;
268 46 : memcpy(&nNode, pabyNodeData + nOffset, sizeof(int64_t));
269 46 : CPL_MSBPTR64(&nNode);
270 : aNodeNoDepthPair.emplace_back(
271 46 : std::make_pair(nNode, nCurDepth + 1));
272 46 : nOffset += SIZEOF_CELL;
273 : }
274 : }
275 : }
276 :
277 2 : CPLDebug("PARQUET",
278 : "CopyTmpGpkgLayerToFinalFile(): 100%%, successfully finished");
279 2 : return true;
280 : }
281 :
282 : /************************************************************************/
283 : /* IsSupportedGeometryType() */
284 : /************************************************************************/
285 :
286 269 : bool OGRParquetWriterLayer::IsSupportedGeometryType(
287 : OGRwkbGeometryType eGType) const
288 : {
289 269 : const auto eFlattenType = wkbFlatten(eGType);
290 269 : if (!OGR_GT_HasM(eGType) && eFlattenType <= wkbGeometryCollection)
291 : {
292 268 : return true;
293 : }
294 :
295 : const auto osConfigOptionName =
296 3 : "OGR_" + GetDriverUCName() + "_ALLOW_ALL_DIMS";
297 1 : if (CPLTestBool(CPLGetConfigOption(osConfigOptionName.c_str(), "NO")))
298 : {
299 0 : return true;
300 : }
301 :
302 1 : CPLError(CE_Failure, CPLE_NotSupported,
303 : "Only 2D and Z geometry types are supported (unless the "
304 : "%s configuration option is set to YES)",
305 : osConfigOptionName.c_str());
306 1 : return false;
307 : }
308 :
309 : /************************************************************************/
310 : /* SetOptions() */
311 : /************************************************************************/
312 :
313 265 : bool OGRParquetWriterLayer::SetOptions(CSLConstList papszOptions,
314 : const OGRSpatialReference *poSpatialRef,
315 : OGRwkbGeometryType eGType)
316 : {
317 265 : m_bWriteBBoxStruct = CPLTestBool(CSLFetchNameValueDef(
318 : papszOptions, "WRITE_COVERING_BBOX",
319 : CPLGetConfigOption("OGR_PARQUET_WRITE_COVERING_BBOX", "YES")));
320 :
321 265 : if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "SORT_BY_BBOX", "NO")))
322 : {
323 6 : const std::string osTmpGPKG(std::string(m_poDataset->GetDescription()) +
324 3 : ".tmp.gpkg");
325 3 : auto poGPKGDrv = GetGDALDriverManager()->GetDriverByName("GPKG");
326 3 : if (!poGPKGDrv)
327 : {
328 1 : CPLError(
329 : CE_Failure, CPLE_AppDefined,
330 : "Driver GPKG required for SORT_BY_BBOX layer creation option");
331 1 : return false;
332 : }
333 2 : m_poTmpGPKG.reset(poGPKGDrv->Create(osTmpGPKG.c_str(), 0, 0, 0,
334 : GDT_Unknown, nullptr));
335 2 : if (!m_poTmpGPKG)
336 0 : return false;
337 2 : m_poTmpGPKG->MarkSuppressOnClose();
338 2 : m_poTmpGPKGLayer = m_poTmpGPKG->CreateLayer("tmp");
339 2 : if (!m_poTmpGPKGLayer)
340 0 : return false;
341 : // Serialized feature
342 2 : m_poTmpGPKGLayer->CreateField(
343 2 : std::make_unique<OGRFieldDefn>("serialized_feature", OFTBinary)
344 2 : .get());
345 2 : CPL_IGNORE_RET_VAL(m_poTmpGPKGLayer->StartTransaction());
346 : }
347 :
348 : const char *pszGeomEncoding =
349 264 : CSLFetchNameValue(papszOptions, "GEOMETRY_ENCODING");
350 264 : m_eGeomEncoding = OGRArrowGeomEncoding::WKB;
351 264 : if (pszGeomEncoding)
352 : {
353 148 : if (EQUAL(pszGeomEncoding, "WKB"))
354 0 : m_eGeomEncoding = OGRArrowGeomEncoding::WKB;
355 148 : else if (EQUAL(pszGeomEncoding, "WKT"))
356 8 : m_eGeomEncoding = OGRArrowGeomEncoding::WKT;
357 140 : else if (EQUAL(pszGeomEncoding, "GEOARROW_INTERLEAVED"))
358 : {
359 : static bool bHasWarned = false;
360 28 : if (!bHasWarned)
361 : {
362 1 : bHasWarned = true;
363 1 : CPLError(
364 : CE_Warning, CPLE_AppDefined,
365 : "Use of GEOMETRY_ENCODING=GEOARROW_INTERLEAVED is not "
366 : "recommended. "
367 : "GeoParquet 1.1 uses GEOMETRY_ENCODING=GEOARROW (struct) "
368 : "instead.");
369 : }
370 28 : m_eGeomEncoding = OGRArrowGeomEncoding::GEOARROW_FSL_GENERIC;
371 : }
372 112 : else if (EQUAL(pszGeomEncoding, "GEOARROW") ||
373 0 : EQUAL(pszGeomEncoding, "GEOARROW_STRUCT"))
374 112 : m_eGeomEncoding = OGRArrowGeomEncoding::GEOARROW_STRUCT_GENERIC;
375 : else
376 : {
377 0 : CPLError(CE_Failure, CPLE_NotSupported,
378 : "Unsupported GEOMETRY_ENCODING = %s", pszGeomEncoding);
379 0 : return false;
380 : }
381 : }
382 :
383 : const char *pszCoordPrecision =
384 264 : CSLFetchNameValue(papszOptions, "COORDINATE_PRECISION");
385 264 : if (pszCoordPrecision)
386 0 : m_nWKTCoordinatePrecision = atoi(pszCoordPrecision);
387 :
388 264 : m_bForceCounterClockwiseOrientation =
389 264 : EQUAL(CSLFetchNameValueDef(papszOptions, "POLYGON_ORIENTATION",
390 : "COUNTERCLOCKWISE"),
391 : "COUNTERCLOCKWISE");
392 :
393 264 : if (eGType != wkbNone)
394 : {
395 243 : if (!IsSupportedGeometryType(eGType))
396 : {
397 1 : return false;
398 : }
399 :
400 242 : m_poFeatureDefn->SetGeomType(eGType);
401 242 : auto eGeomEncoding = m_eGeomEncoding;
402 242 : if (eGeomEncoding == OGRArrowGeomEncoding::GEOARROW_FSL_GENERIC ||
403 214 : eGeomEncoding == OGRArrowGeomEncoding::GEOARROW_STRUCT_GENERIC)
404 : {
405 140 : const auto eEncodingType = eGeomEncoding;
406 140 : eGeomEncoding = GetPreciseArrowGeomEncoding(eEncodingType, eGType);
407 140 : if (eGeomEncoding == eEncodingType)
408 0 : return false;
409 : }
410 242 : m_aeGeomEncoding.push_back(eGeomEncoding);
411 242 : m_poFeatureDefn->GetGeomFieldDefn(0)->SetName(
412 : CSLFetchNameValueDef(papszOptions, "GEOMETRY_NAME", "geometry"));
413 242 : if (poSpatialRef)
414 : {
415 25 : auto poSRS = poSpatialRef->Clone();
416 25 : m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
417 25 : poSRS->Release();
418 : }
419 : }
420 :
421 263 : m_osFIDColumn = CSLFetchNameValueDef(papszOptions, "FID", "");
422 :
423 263 : const char *pszCompression = CSLFetchNameValue(papszOptions, "COMPRESSION");
424 263 : if (pszCompression == nullptr)
425 : {
426 777 : auto oResult = arrow::util::Codec::GetCompressionType("snappy");
427 259 : if (oResult.ok() && arrow::util::Codec::IsAvailable(*oResult))
428 : {
429 259 : pszCompression = "SNAPPY";
430 : }
431 : else
432 : {
433 0 : pszCompression = "NONE";
434 : }
435 : }
436 :
437 263 : if (EQUAL(pszCompression, "NONE"))
438 0 : pszCompression = "UNCOMPRESSED";
439 : auto oResult = arrow::util::Codec::GetCompressionType(
440 526 : CPLString(pszCompression).tolower());
441 263 : if (!oResult.ok())
442 : {
443 1 : CPLError(CE_Failure, CPLE_NotSupported,
444 : "Unrecognized compression method: %s", pszCompression);
445 1 : return false;
446 : }
447 262 : m_eCompression = *oResult;
448 262 : if (!arrow::util::Codec::IsAvailable(m_eCompression))
449 : {
450 0 : CPLError(CE_Failure, CPLE_NotSupported,
451 : "Compression method %s is known, but libarrow has not "
452 : "been built with support for it",
453 : pszCompression);
454 0 : return false;
455 : }
456 :
457 262 : m_oWriterPropertiesBuilder.compression(m_eCompression);
458 : const std::string osCreator =
459 262 : CSLFetchNameValueDef(papszOptions, "CREATOR", "");
460 262 : if (!osCreator.empty())
461 1 : m_oWriterPropertiesBuilder.created_by(osCreator);
462 : else
463 261 : m_oWriterPropertiesBuilder.created_by("GDAL " GDAL_RELEASE_NAME
464 : ", using " CREATED_BY_VERSION);
465 :
466 : // Undocumented option. Not clear it is useful besides unit test purposes
467 262 : if (!CPLTestBool(CSLFetchNameValueDef(papszOptions, "STATISTICS", "YES")))
468 1 : m_oWriterPropertiesBuilder.disable_statistics();
469 :
470 262 : if (m_eGeomEncoding == OGRArrowGeomEncoding::WKB && eGType != wkbNone)
471 : {
472 101 : m_oWriterPropertiesBuilder.disable_statistics(
473 303 : parquet::schema::ColumnPath::FromDotString(
474 101 : m_poFeatureDefn->GetGeomFieldDefn(0)->GetNameRef()));
475 : }
476 :
477 : const char *pszRowGroupSize =
478 262 : CSLFetchNameValue(papszOptions, "ROW_GROUP_SIZE");
479 262 : if (pszRowGroupSize)
480 : {
481 5 : auto nRowGroupSize = static_cast<int64_t>(atoll(pszRowGroupSize));
482 5 : if (nRowGroupSize > 0)
483 : {
484 5 : if (nRowGroupSize > INT_MAX)
485 0 : nRowGroupSize = INT_MAX;
486 5 : m_nRowGroupSize = nRowGroupSize;
487 : }
488 : }
489 :
490 262 : m_bEdgesSpherical = EQUAL(
491 : CSLFetchNameValueDef(papszOptions, "EDGES", "PLANAR"), "SPHERICAL");
492 :
493 262 : m_bInitializationOK = true;
494 262 : return true;
495 : }
496 :
497 : /************************************************************************/
498 : /* CloseFileWriter() */
499 : /************************************************************************/
500 :
501 262 : bool OGRParquetWriterLayer::CloseFileWriter()
502 : {
503 524 : auto status = m_poFileWriter->Close();
504 262 : if (!status.ok())
505 : {
506 0 : CPLError(CE_Failure, CPLE_AppDefined,
507 : "FileWriter::Close() failed with %s",
508 0 : status.message().c_str());
509 : }
510 524 : return status.ok();
511 : }
512 :
513 : /************************************************************************/
514 : /* IdentifyCRS() */
515 : /************************************************************************/
516 :
517 24 : static OGRSpatialReference IdentifyCRS(const OGRSpatialReference *poSRS)
518 : {
519 24 : OGRSpatialReference oSRSIdentified(*poSRS);
520 :
521 24 : if (poSRS->GetAuthorityName(nullptr) == nullptr)
522 : {
523 : // Try to find a registered CRS that matches the input one
524 4 : int nEntries = 0;
525 4 : int *panConfidence = nullptr;
526 : OGRSpatialReferenceH *pahSRS =
527 4 : poSRS->FindMatches(nullptr, &nEntries, &panConfidence);
528 :
529 : // If there are several matches >= 90%, take the only one
530 : // that is EPSG
531 4 : int iOtherAuthority = -1;
532 4 : int iEPSG = -1;
533 4 : const char *const apszOptions[] = {
534 : "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES", nullptr};
535 4 : int iConfidenceBestMatch = -1;
536 6 : for (int iSRS = 0; iSRS < nEntries; iSRS++)
537 : {
538 4 : auto poCandidateCRS = OGRSpatialReference::FromHandle(pahSRS[iSRS]);
539 4 : if (panConfidence[iSRS] < iConfidenceBestMatch ||
540 4 : panConfidence[iSRS] < 70)
541 : {
542 : break;
543 : }
544 3 : if (poSRS->IsSame(poCandidateCRS, apszOptions))
545 : {
546 : const char *pszAuthName =
547 3 : poCandidateCRS->GetAuthorityName(nullptr);
548 3 : if (pszAuthName != nullptr && EQUAL(pszAuthName, "EPSG"))
549 : {
550 2 : iOtherAuthority = -2;
551 2 : if (iEPSG < 0)
552 : {
553 2 : iConfidenceBestMatch = panConfidence[iSRS];
554 2 : iEPSG = iSRS;
555 : }
556 : else
557 : {
558 0 : iEPSG = -1;
559 0 : break;
560 : }
561 : }
562 1 : else if (iEPSG < 0 && pszAuthName != nullptr)
563 : {
564 1 : if (EQUAL(pszAuthName, "OGC"))
565 : {
566 : const char *pszAuthCode =
567 1 : poCandidateCRS->GetAuthorityCode(nullptr);
568 1 : if (pszAuthCode && EQUAL(pszAuthCode, "CRS84"))
569 : {
570 1 : iOtherAuthority = iSRS;
571 1 : break;
572 : }
573 : }
574 0 : else if (iOtherAuthority == -1)
575 : {
576 0 : iConfidenceBestMatch = panConfidence[iSRS];
577 0 : iOtherAuthority = iSRS;
578 : }
579 : else
580 0 : iOtherAuthority = -2;
581 : }
582 : }
583 : }
584 4 : if (iEPSG >= 0)
585 : {
586 2 : oSRSIdentified = *OGRSpatialReference::FromHandle(pahSRS[iEPSG]);
587 : }
588 2 : else if (iOtherAuthority >= 0)
589 : {
590 : oSRSIdentified =
591 1 : *OGRSpatialReference::FromHandle(pahSRS[iOtherAuthority]);
592 : }
593 4 : OSRFreeSRSArray(pahSRS);
594 4 : CPLFree(panConfidence);
595 : }
596 :
597 24 : return oSRSIdentified;
598 : }
599 :
600 : /************************************************************************/
601 : /* RemoveIDFromMemberOfEnsembles() */
602 : /************************************************************************/
603 :
604 314 : static void RemoveIDFromMemberOfEnsembles(CPLJSONObject &obj)
605 : {
606 : // Remove "id" from members of datum ensembles for compatibility with
607 : // older PROJ versions
608 : // Cf https://github.com/opengeospatial/geoparquet/discussions/110
609 : // and https://github.com/OSGeo/PROJ/pull/3221
610 314 : if (obj.GetType() == CPLJSONObject::Type::Object)
611 : {
612 398 : for (auto &subObj : obj.GetChildren())
613 : {
614 304 : RemoveIDFromMemberOfEnsembles(subObj);
615 : }
616 : }
617 242 : else if (obj.GetType() == CPLJSONObject::Type::Array &&
618 242 : obj.GetName() == "members")
619 : {
620 0 : for (auto &subObj : obj.ToArray())
621 : {
622 0 : subObj.Delete("id");
623 : }
624 : }
625 314 : }
626 :
627 : /************************************************************************/
628 : /* GetGeoMetadata() */
629 : /************************************************************************/
630 :
631 262 : std::string OGRParquetWriterLayer::GetGeoMetadata() const
632 : {
633 : // Just for unit testing purposes
634 : const char *pszGeoMetadata =
635 262 : CPLGetConfigOption("OGR_PARQUET_GEO_METADATA", nullptr);
636 262 : if (pszGeoMetadata)
637 16 : return pszGeoMetadata;
638 :
639 481 : if (m_poFeatureDefn->GetGeomFieldCount() != 0 &&
640 235 : CPLTestBool(CPLGetConfigOption("OGR_PARQUET_WRITE_GEO", "YES")))
641 : {
642 468 : CPLJSONObject oRoot;
643 234 : oRoot.Add("version", "1.1.0");
644 234 : oRoot.Add("primary_column",
645 234 : m_poFeatureDefn->GetGeomFieldDefn(0)->GetNameRef());
646 468 : CPLJSONObject oColumns;
647 234 : oRoot.Add("columns", oColumns);
648 485 : for (int i = 0; i < m_poFeatureDefn->GetGeomFieldCount(); ++i)
649 : {
650 251 : const auto poGeomFieldDefn = m_poFeatureDefn->GetGeomFieldDefn(i);
651 502 : CPLJSONObject oColumn;
652 251 : oColumns.Add(poGeomFieldDefn->GetNameRef(), oColumn);
653 251 : oColumn.Add("encoding",
654 251 : GetGeomEncodingAsString(m_aeGeomEncoding[i], true));
655 :
656 251 : if (CPLTestBool(CPLGetConfigOption("OGR_PARQUET_WRITE_CRS", "YES")))
657 : {
658 250 : const auto poSRS = poGeomFieldDefn->GetSpatialRef();
659 250 : if (poSRS)
660 : {
661 48 : OGRSpatialReference oSRSIdentified(IdentifyCRS(poSRS));
662 :
663 : const char *pszAuthName =
664 24 : oSRSIdentified.GetAuthorityName(nullptr);
665 : const char *pszAuthCode =
666 24 : oSRSIdentified.GetAuthorityCode(nullptr);
667 :
668 24 : bool bOmitCRS = false;
669 24 : if (pszAuthName != nullptr && pszAuthCode != nullptr &&
670 23 : ((EQUAL(pszAuthName, "EPSG") &&
671 20 : EQUAL(pszAuthCode, "4326")) ||
672 12 : (EQUAL(pszAuthName, "OGC") &&
673 3 : EQUAL(pszAuthCode, "CRS84"))))
674 : {
675 : // To make things less confusing for non-geo-aware
676 : // consumers, omit EPSG:4326 / OGC:CRS84 CRS by default
677 14 : bOmitCRS = CPLTestBool(CPLGetConfigOption(
678 : "OGR_PARQUET_CRS_OMIT_IF_WGS84", "YES"));
679 : }
680 :
681 24 : if (bOmitCRS)
682 : {
683 : // do nothing
684 : }
685 10 : else if (EQUAL(CPLGetConfigOption(
686 : "OGR_PARQUET_CRS_ENCODING", "PROJJSON"),
687 : "PROJJSON"))
688 : {
689 : // CRS encoded as PROJJSON for GeoParquet >= 0.4.0
690 10 : char *pszPROJJSON = nullptr;
691 10 : oSRSIdentified.exportToPROJJSON(&pszPROJJSON, nullptr);
692 20 : CPLJSONDocument oCRSDoc;
693 10 : CPL_IGNORE_RET_VAL(oCRSDoc.LoadMemory(pszPROJJSON));
694 10 : CPLFree(pszPROJJSON);
695 10 : CPLJSONObject oCRSRoot = oCRSDoc.GetRoot();
696 10 : RemoveIDFromMemberOfEnsembles(oCRSRoot);
697 10 : oColumn.Add("crs", oCRSRoot);
698 : }
699 : else
700 : {
701 : // WKT was used in GeoParquet <= 0.3.0
702 0 : const char *const apszOptions[] = {
703 : "FORMAT=WKT2_2019", "MULTILINE=NO", nullptr};
704 0 : char *pszWKT = nullptr;
705 0 : oSRSIdentified.exportToWkt(&pszWKT, apszOptions);
706 0 : if (pszWKT)
707 0 : oColumn.Add("crs", pszWKT);
708 0 : CPLFree(pszWKT);
709 : }
710 :
711 24 : const double dfCoordEpoch = poSRS->GetCoordinateEpoch();
712 24 : if (dfCoordEpoch > 0)
713 2 : oColumn.Add("epoch", dfCoordEpoch);
714 : }
715 : else
716 : {
717 226 : oColumn.AddNull("crs");
718 : }
719 : }
720 :
721 251 : if (m_bEdgesSpherical)
722 : {
723 1 : oColumn.Add("edges", "spherical");
724 : }
725 :
726 475 : if (m_aoEnvelopes[i].IsInit() &&
727 224 : CPLTestBool(
728 : CPLGetConfigOption("OGR_PARQUET_WRITE_BBOX", "YES")))
729 : {
730 224 : bool bHasZ = false;
731 407 : for (const auto eGeomType : m_oSetWrittenGeometryTypes[i])
732 : {
733 266 : bHasZ = OGR_GT_HasZ(eGeomType);
734 266 : if (bHasZ)
735 83 : break;
736 : }
737 224 : CPLJSONArray oBBOX;
738 224 : oBBOX.Add(m_aoEnvelopes[i].MinX);
739 224 : oBBOX.Add(m_aoEnvelopes[i].MinY);
740 224 : if (bHasZ)
741 83 : oBBOX.Add(m_aoEnvelopes[i].MinZ);
742 224 : oBBOX.Add(m_aoEnvelopes[i].MaxX);
743 224 : oBBOX.Add(m_aoEnvelopes[i].MaxY);
744 224 : if (bHasZ)
745 83 : oBBOX.Add(m_aoEnvelopes[i].MaxZ);
746 224 : oColumn.Add("bbox", oBBOX);
747 : }
748 :
749 : // Bounding box column definition
750 432 : if (m_bWriteBBoxStruct &&
751 181 : CPLTestBool(CPLGetConfigOption(
752 : "OGR_PARQUET_WRITE_COVERING_BBOX_IN_METADATA", "YES")))
753 : {
754 362 : CPLJSONObject oCovering;
755 181 : oColumn.Add("covering", oCovering);
756 362 : CPLJSONObject oBBOX;
757 181 : oCovering.Add("bbox", oBBOX);
758 : const auto AddComponent =
759 2172 : [this, i, &oBBOX](const char *pszComponent)
760 : {
761 724 : CPLJSONArray oArray;
762 724 : oArray.Add(m_apoFieldsBBOX[i]->name());
763 724 : oArray.Add(pszComponent);
764 724 : oBBOX.Add(pszComponent, oArray);
765 724 : };
766 181 : AddComponent("xmin");
767 181 : AddComponent("ymin");
768 181 : AddComponent("xmax");
769 181 : AddComponent("ymax");
770 : }
771 :
772 282 : const auto GetStringGeometryType = [](OGRwkbGeometryType eType)
773 : {
774 282 : const auto eFlattenType = wkbFlatten(eType);
775 282 : std::string osType = "Unknown";
776 282 : if (wkbPoint == eFlattenType)
777 66 : osType = "Point";
778 216 : else if (wkbLineString == eFlattenType)
779 34 : osType = "LineString";
780 182 : else if (wkbPolygon == eFlattenType)
781 53 : osType = "Polygon";
782 129 : else if (wkbMultiPoint == eFlattenType)
783 26 : osType = "MultiPoint";
784 103 : else if (wkbMultiLineString == eFlattenType)
785 29 : osType = "MultiLineString";
786 74 : else if (wkbMultiPolygon == eFlattenType)
787 69 : osType = "MultiPolygon";
788 5 : else if (wkbGeometryCollection == eFlattenType)
789 5 : osType = "GeometryCollection";
790 282 : if (osType != "Unknown")
791 : {
792 : // M and ZM not supported officially currently, but it
793 : // doesn't hurt to anticipate
794 282 : if (OGR_GT_HasZ(eType) && OGR_GT_HasM(eType))
795 8 : osType += " ZM";
796 274 : else if (OGR_GT_HasZ(eType))
797 91 : osType += " Z";
798 183 : else if (OGR_GT_HasM(eType))
799 8 : osType += " M";
800 : }
801 282 : return osType;
802 : };
803 :
804 251 : if (m_bForceCounterClockwiseOrientation)
805 250 : oColumn.Add("orientation", "counterclockwise");
806 :
807 251 : CPLJSONArray oArray;
808 533 : for (const auto eType : m_oSetWrittenGeometryTypes[i])
809 : {
810 282 : oArray.Add(GetStringGeometryType(eType));
811 : }
812 251 : oColumn.Add("geometry_types", oArray);
813 : }
814 :
815 234 : return oRoot.Format(CPLJSONObject::PrettyFormat::Plain);
816 : }
817 12 : return std::string();
818 : }
819 :
820 : /************************************************************************/
821 : /* PerformStepsBeforeFinalFlushGroup() */
822 : /************************************************************************/
823 :
824 262 : void OGRParquetWriterLayer::PerformStepsBeforeFinalFlushGroup()
825 : {
826 262 : if (m_poKeyValueMetadata)
827 : {
828 524 : const std::string osGeoMetadata = GetGeoMetadata();
829 524 : auto poTmpSchema = m_poSchema;
830 262 : if (!osGeoMetadata.empty())
831 : {
832 : // HACK: it would be good for Arrow to provide a clean way to alter
833 : // key value metadata before finalizing.
834 : // We need to write metadata at end to write the bounding box.
835 250 : const_cast<arrow::KeyValueMetadata *>(m_poKeyValueMetadata.get())
836 250 : ->Append("geo", osGeoMetadata);
837 :
838 250 : auto kvMetadata = poTmpSchema->metadata()
839 9 : ? poTmpSchema->metadata()->Copy()
840 259 : : std::make_shared<arrow::KeyValueMetadata>();
841 250 : kvMetadata->Append("geo", osGeoMetadata);
842 250 : poTmpSchema = poTmpSchema->WithMetadata(kvMetadata);
843 : }
844 :
845 262 : if (CPLTestBool(
846 : CPLGetConfigOption("OGR_PARQUET_WRITE_ARROW_SCHEMA", "YES")))
847 : {
848 : auto status =
849 524 : ::arrow::ipc::SerializeSchema(*poTmpSchema, m_poMemoryPool);
850 262 : if (status.ok())
851 : {
852 : // The serialized schema is not UTF-8, which is required for
853 : // Thrift
854 524 : const std::string schema_as_string = (*status)->ToString();
855 : const std::string schema_base64 =
856 262 : ::arrow::util::base64_encode(schema_as_string);
857 262 : static const std::string kArrowSchemaKey = "ARROW:schema";
858 : const_cast<arrow::KeyValueMetadata *>(
859 262 : m_poKeyValueMetadata.get())
860 262 : ->Append(kArrowSchemaKey, schema_base64);
861 : }
862 : }
863 :
864 : // Put GDAL metadata into a gdal:metadata domain
865 524 : CPLJSONObject oMultiMetadata;
866 262 : bool bHasMultiMetadata = false;
867 266 : auto &l_oMDMD = oMDMD.GetDomainList() && *(oMDMD.GetDomainList())
868 266 : ? oMDMD
869 258 : : m_poDataset->GetMultiDomainMetadata();
870 268 : for (CSLConstList papszDomainIter = l_oMDMD.GetDomainList();
871 268 : papszDomainIter && *papszDomainIter; ++papszDomainIter)
872 : {
873 6 : const char *pszDomain = *papszDomainIter;
874 6 : CSLConstList papszMD = l_oMDMD.GetMetadata(pszDomain);
875 6 : if (STARTS_WITH(pszDomain, "json:") && papszMD && papszMD[0])
876 : {
877 1 : CPLJSONDocument oDoc;
878 1 : if (oDoc.LoadMemory(papszMD[0]))
879 : {
880 1 : bHasMultiMetadata = true;
881 1 : oMultiMetadata.Add(pszDomain, oDoc.GetRoot());
882 1 : continue;
883 0 : }
884 : }
885 5 : else if (STARTS_WITH(pszDomain, "xml:") && papszMD && papszMD[0])
886 : {
887 1 : bHasMultiMetadata = true;
888 1 : oMultiMetadata.Add(pszDomain, papszMD[0]);
889 1 : continue;
890 : }
891 8 : CPLJSONObject oMetadata;
892 4 : bool bHasMetadata = false;
893 8 : for (CSLConstList papszMDIter = papszMD;
894 8 : papszMDIter && *papszMDIter; ++papszMDIter)
895 : {
896 4 : char *pszKey = nullptr;
897 4 : const char *pszValue = CPLParseNameValue(*papszMDIter, &pszKey);
898 4 : if (pszKey && pszValue)
899 : {
900 4 : bHasMetadata = true;
901 4 : bHasMultiMetadata = true;
902 4 : oMetadata.Add(pszKey, pszValue);
903 : }
904 4 : CPLFree(pszKey);
905 : }
906 4 : if (bHasMetadata)
907 4 : oMultiMetadata.Add(pszDomain, oMetadata);
908 : }
909 262 : if (bHasMultiMetadata)
910 : {
911 4 : const_cast<arrow::KeyValueMetadata *>(m_poKeyValueMetadata.get())
912 4 : ->Append(
913 : "gdal:metadata",
914 8 : oMultiMetadata.Format(CPLJSONObject::PrettyFormat::Plain));
915 : }
916 : }
917 262 : }
918 :
919 : /************************************************************************/
920 : /* Open() */
921 : /************************************************************************/
922 :
923 : // Same as parquet::arrow::FileWriter::Open(), except we also
924 : // return KeyValueMetadata
925 : static arrow::Status
926 262 : Open(const ::arrow::Schema &schema, ::arrow::MemoryPool *pool,
927 : std::shared_ptr<::arrow::io::OutputStream> sink,
928 : std::shared_ptr<parquet::WriterProperties> properties,
929 : std::shared_ptr<parquet::ArrowWriterProperties> arrow_properties,
930 : std::unique_ptr<parquet::arrow::FileWriter> *writer,
931 : std::shared_ptr<const arrow::KeyValueMetadata> *outMetadata)
932 : {
933 262 : std::shared_ptr<parquet::SchemaDescriptor> parquet_schema;
934 524 : RETURN_NOT_OK(parquet::arrow::ToParquetSchema(
935 : &schema, *properties, *arrow_properties, &parquet_schema));
936 :
937 : auto schema_node = std::static_pointer_cast<parquet::schema::GroupNode>(
938 524 : parquet_schema->schema_root());
939 :
940 262 : auto metadata = schema.metadata()
941 14 : ? schema.metadata()->Copy()
942 538 : : std::make_shared<arrow::KeyValueMetadata>();
943 262 : *outMetadata = metadata;
944 :
945 262 : std::unique_ptr<parquet::ParquetFileWriter> base_writer;
946 262 : PARQUET_CATCH_NOT_OK(base_writer = parquet::ParquetFileWriter::Open(
947 : std::move(sink), std::move(schema_node),
948 : std::move(properties), metadata));
949 :
950 262 : auto schema_ptr = std::make_shared<::arrow::Schema>(schema);
951 : return parquet::arrow::FileWriter::Make(
952 524 : pool, std::move(base_writer), std::move(schema_ptr),
953 786 : std::move(arrow_properties), writer);
954 : }
955 :
956 : /************************************************************************/
957 : /* CreateSchema() */
958 : /************************************************************************/
959 :
960 262 : void OGRParquetWriterLayer::CreateSchema()
961 : {
962 262 : CreateSchemaCommon();
963 262 : }
964 :
965 : /************************************************************************/
966 : /* CreateGeomField() */
967 : /************************************************************************/
968 :
969 27 : OGRErr OGRParquetWriterLayer::CreateGeomField(const OGRGeomFieldDefn *poField,
970 : int bApproxOK)
971 : {
972 27 : OGRErr eErr = OGRArrowWriterLayer::CreateGeomField(poField, bApproxOK);
973 53 : if (eErr == OGRERR_NONE &&
974 26 : m_aeGeomEncoding.back() == OGRArrowGeomEncoding::WKB)
975 : {
976 2 : m_oWriterPropertiesBuilder.disable_statistics(
977 6 : parquet::schema::ColumnPath::FromDotString(
978 2 : m_poFeatureDefn
979 2 : ->GetGeomFieldDefn(m_poFeatureDefn->GetGeomFieldCount() - 1)
980 : ->GetNameRef()));
981 : }
982 27 : return eErr;
983 : }
984 :
985 : /************************************************************************/
986 : /* CreateWriter() */
987 : /************************************************************************/
988 :
989 262 : void OGRParquetWriterLayer::CreateWriter()
990 : {
991 262 : CPLAssert(m_poFileWriter == nullptr);
992 :
993 262 : if (m_poSchema == nullptr)
994 : {
995 40 : CreateSchema();
996 : }
997 : else
998 : {
999 222 : FinalizeSchema();
1000 : }
1001 :
1002 : auto arrowWriterProperties =
1003 262 : parquet::ArrowWriterProperties::Builder().store_schema()->build();
1004 786 : CPL_IGNORE_RET_VAL(Open(*m_poSchema, m_poMemoryPool, m_poOutputStream,
1005 524 : m_oWriterPropertiesBuilder.build(),
1006 262 : std::move(arrowWriterProperties), &m_poFileWriter,
1007 : &m_poKeyValueMetadata));
1008 262 : }
1009 :
1010 : /************************************************************************/
1011 : /* ICreateFeature() */
1012 : /************************************************************************/
1013 :
1014 3066 : OGRErr OGRParquetWriterLayer::ICreateFeature(OGRFeature *poFeature)
1015 : {
1016 : // If not using SORT_BY_BBOX=YES layer creation option, we can directly
1017 : // write features to the final Parquet file
1018 3066 : if (!m_poTmpGPKGLayer)
1019 862 : return OGRArrowWriterLayer::ICreateFeature(poFeature);
1020 :
1021 : // SORT_BY_BBOX=YES case: we write for now a serialized version of poFeature
1022 : // in a temporary GeoPackage file.
1023 :
1024 2204 : GIntBig nFID = poFeature->GetFID();
1025 2204 : if (!m_osFIDColumn.empty() && nFID == OGRNullFID)
1026 : {
1027 1102 : nFID = m_nTmpFeatureCount;
1028 1102 : poFeature->SetFID(nFID);
1029 : }
1030 2204 : ++m_nTmpFeatureCount;
1031 :
1032 4408 : std::vector<GByte> abyBuffer;
1033 : // Serialize the source feature as a single array of bytes to preserve it
1034 : // fully
1035 2204 : if (!poFeature->SerializeToBinary(abyBuffer))
1036 : {
1037 0 : return OGRERR_FAILURE;
1038 : }
1039 :
1040 : // SQLite3 limitation: a row must fit in slightly less than 1 GB.
1041 2204 : constexpr int SOME_MARGIN = 128;
1042 2204 : if (abyBuffer.size() > 1024 * 1024 * 1024 - SOME_MARGIN)
1043 : {
1044 0 : CPLError(CE_Failure, CPLE_NotSupported,
1045 : "Features larger than 1 GB are not supported");
1046 0 : return OGRERR_FAILURE;
1047 : }
1048 :
1049 4408 : OGRFeature oFeat(m_poTmpGPKGLayer->GetLayerDefn());
1050 2204 : oFeat.SetFID(nFID);
1051 2204 : oFeat.SetField(0, static_cast<int>(abyBuffer.size()), abyBuffer.data());
1052 2204 : const auto poSrcGeom = poFeature->GetGeometryRef();
1053 2204 : if (poSrcGeom && !poSrcGeom->IsEmpty())
1054 : {
1055 : // For the purpose of building an RTree, just use the bounding box of
1056 : // the geometry as the geometry.
1057 1202 : OGREnvelope sEnvelope;
1058 1202 : poSrcGeom->getEnvelope(&sEnvelope);
1059 2404 : auto poPoly = std::make_unique<OGRPolygon>();
1060 2404 : auto poLR = std::make_unique<OGRLinearRing>();
1061 1202 : poLR->addPoint(sEnvelope.MinX, sEnvelope.MinY);
1062 1202 : poLR->addPoint(sEnvelope.MinX, sEnvelope.MaxY);
1063 1202 : poLR->addPoint(sEnvelope.MaxX, sEnvelope.MaxY);
1064 1202 : poLR->addPoint(sEnvelope.MaxX, sEnvelope.MinY);
1065 1202 : poLR->addPoint(sEnvelope.MinX, sEnvelope.MinY);
1066 1202 : poPoly->addRingDirectly(poLR.release());
1067 1202 : oFeat.SetGeometryDirectly(poPoly.release());
1068 : }
1069 2204 : return m_poTmpGPKGLayer->CreateFeature(&oFeat);
1070 : }
1071 :
1072 : /************************************************************************/
1073 : /* FlushGroup() */
1074 : /************************************************************************/
1075 :
1076 246 : bool OGRParquetWriterLayer::FlushGroup()
1077 : {
1078 492 : auto status = m_poFileWriter->NewRowGroup(m_apoBuilders[0]->length());
1079 246 : if (!status.ok())
1080 : {
1081 0 : CPLError(CE_Failure, CPLE_AppDefined, "NewRowGroup() failed with %s",
1082 0 : status.message().c_str());
1083 0 : ClearArrayBuilers();
1084 0 : return false;
1085 : }
1086 :
1087 246 : auto ret = WriteArrays(
1088 995 : [this](const std::shared_ptr<arrow::Field> &field,
1089 995 : const std::shared_ptr<arrow::Array> &array)
1090 : {
1091 1990 : auto l_status = m_poFileWriter->WriteColumnChunk(*array);
1092 995 : if (!l_status.ok())
1093 : {
1094 0 : CPLError(CE_Failure, CPLE_AppDefined,
1095 : "WriteColumnChunk() failed for field %s: %s",
1096 0 : field->name().c_str(), l_status.message().c_str());
1097 0 : return false;
1098 : }
1099 995 : return true;
1100 : });
1101 :
1102 246 : ClearArrayBuilers();
1103 246 : return ret;
1104 : }
1105 :
1106 : /************************************************************************/
1107 : /* FixupWKBGeometryBeforeWriting() */
1108 : /************************************************************************/
1109 :
1110 43 : void OGRParquetWriterLayer::FixupWKBGeometryBeforeWriting(GByte *pabyWkb,
1111 : size_t nLen)
1112 : {
1113 43 : if (!m_bForceCounterClockwiseOrientation)
1114 0 : return;
1115 :
1116 43 : OGRWKBFixupCounterClockWiseExternalRing(pabyWkb, nLen);
1117 : }
1118 :
1119 : /************************************************************************/
1120 : /* FixupGeometryBeforeWriting() */
1121 : /************************************************************************/
1122 :
1123 1334 : void OGRParquetWriterLayer::FixupGeometryBeforeWriting(OGRGeometry *poGeom)
1124 : {
1125 1334 : if (!m_bForceCounterClockwiseOrientation)
1126 3 : return;
1127 :
1128 1331 : const auto eFlattenType = wkbFlatten(poGeom->getGeometryType());
1129 : // Polygon rings MUST follow the right-hand rule for orientation
1130 : // (counterclockwise external rings, clockwise internal rings)
1131 1331 : if (eFlattenType == wkbPolygon)
1132 : {
1133 44 : bool bFirstRing = true;
1134 91 : for (auto poRing : poGeom->toPolygon())
1135 : {
1136 55 : if ((bFirstRing && poRing->isClockwise()) ||
1137 8 : (!bFirstRing && !poRing->isClockwise()))
1138 : {
1139 42 : poRing->reversePoints();
1140 : }
1141 47 : bFirstRing = false;
1142 : }
1143 : }
1144 1287 : else if (eFlattenType == wkbMultiPolygon ||
1145 : eFlattenType == wkbGeometryCollection)
1146 : {
1147 35 : for (auto poSubGeom : poGeom->toGeometryCollection())
1148 : {
1149 21 : FixupGeometryBeforeWriting(poSubGeom);
1150 : }
1151 : }
1152 : }
1153 :
1154 : /************************************************************************/
1155 : /* WriteArrowBatch() */
1156 : /************************************************************************/
1157 :
1158 : #if PARQUET_VERSION_MAJOR > 10
1159 : inline bool
1160 14 : OGRParquetWriterLayer::WriteArrowBatch(const struct ArrowSchema *schema,
1161 : struct ArrowArray *array,
1162 : CSLConstList papszOptions)
1163 : {
1164 14 : if (m_poTmpGPKGLayer)
1165 : {
1166 : // When using SORT_BY_BBOX=YES option, we can't directly write the
1167 : // input array, because we need to sort features. Hence we fallback
1168 : // to the OGRLayer base implementation, which will ultimately call
1169 : // OGRParquetWriterLayer::ICreateFeature()
1170 0 : return OGRLayer::WriteArrowBatch(schema, array, papszOptions);
1171 : }
1172 :
1173 28 : return WriteArrowBatchInternal(
1174 : schema, array, papszOptions,
1175 28 : [this](const std::shared_ptr<arrow::RecordBatch> &poBatch)
1176 : {
1177 28 : auto status = m_poFileWriter->NewBufferedRowGroup();
1178 14 : if (!status.ok())
1179 : {
1180 0 : CPLError(CE_Failure, CPLE_AppDefined,
1181 : "NewBufferedRowGroup() failed with %s",
1182 0 : status.message().c_str());
1183 0 : return false;
1184 : }
1185 :
1186 14 : status = m_poFileWriter->WriteRecordBatch(*poBatch);
1187 14 : if (!status.ok())
1188 : {
1189 0 : CPLError(CE_Failure, CPLE_AppDefined,
1190 : "WriteRecordBatch() failed: %s",
1191 0 : status.message().c_str());
1192 0 : return false;
1193 : }
1194 :
1195 14 : return true;
1196 14 : });
1197 : }
1198 : #endif
1199 :
1200 : /************************************************************************/
1201 : /* TestCapability() */
1202 : /************************************************************************/
1203 :
1204 475 : inline int OGRParquetWriterLayer::TestCapability(const char *pszCap)
1205 : {
1206 : #if PARQUET_VERSION_MAJOR <= 10
1207 : if (EQUAL(pszCap, OLCFastWriteArrowBatch))
1208 : return false;
1209 : #endif
1210 :
1211 475 : if (m_poTmpGPKGLayer && EQUAL(pszCap, OLCFastWriteArrowBatch))
1212 : {
1213 : // When using SORT_BY_BBOX=YES option, we can't directly write the
1214 : // input array, because we need to sort features. So this is not
1215 : // fast
1216 1 : return false;
1217 : }
1218 :
1219 474 : return OGRArrowWriterLayer::TestCapability(pszCap);
1220 : }
1221 :
1222 : /************************************************************************/
1223 : /* CreateFieldFromArrowSchema() */
1224 : /************************************************************************/
1225 :
1226 : #if PARQUET_VERSION_MAJOR > 10
1227 396 : bool OGRParquetWriterLayer::CreateFieldFromArrowSchema(
1228 : const struct ArrowSchema *schema, CSLConstList papszOptions)
1229 : {
1230 396 : if (m_poTmpGPKGLayer)
1231 : {
1232 : // When using SORT_BY_BBOX=YES option, we can't directly write the
1233 : // input array, because we need to sort features. But this process
1234 : // only supports the base Arrow types supported by
1235 : // OGRLayer::WriteArrowBatch()
1236 0 : return OGRLayer::CreateFieldFromArrowSchema(schema, papszOptions);
1237 : }
1238 :
1239 396 : return OGRArrowWriterLayer::CreateFieldFromArrowSchema(schema,
1240 396 : papszOptions);
1241 : }
1242 : #endif
1243 :
1244 : /************************************************************************/
1245 : /* IsArrowSchemaSupported() */
1246 : /************************************************************************/
1247 :
1248 : #if PARQUET_VERSION_MAJOR > 10
1249 1077 : bool OGRParquetWriterLayer::IsArrowSchemaSupported(
1250 : const struct ArrowSchema *schema, CSLConstList papszOptions,
1251 : std::string &osErrorMsg) const
1252 : {
1253 1077 : if (m_poTmpGPKGLayer)
1254 : {
1255 : // When using SORT_BY_BBOX=YES option, we can't directly write the
1256 : // input array, because we need to sort features. But this process
1257 : // only supports the base Arrow types supported by
1258 : // OGRLayer::WriteArrowBatch()
1259 0 : return OGRLayer::IsArrowSchemaSupported(schema, papszOptions,
1260 0 : osErrorMsg);
1261 : }
1262 :
1263 1077 : if (schema->format[0] == 'e' && schema->format[1] == 0)
1264 : {
1265 1 : osErrorMsg = "float16 not supported";
1266 1 : return false;
1267 : }
1268 1076 : if (schema->format[0] == 'v' && schema->format[1] == 'u')
1269 : {
1270 1 : osErrorMsg = "StringView not supported";
1271 1 : return false;
1272 : }
1273 1075 : if (schema->format[0] == 'v' && schema->format[1] == 'z')
1274 : {
1275 1 : osErrorMsg = "BinaryView not supported";
1276 1 : return false;
1277 : }
1278 1074 : if (schema->format[0] == '+' && schema->format[1] == 'v')
1279 : {
1280 0 : if (schema->format[2] == 'l')
1281 : {
1282 0 : osErrorMsg = "ListView not supported";
1283 0 : return false;
1284 : }
1285 0 : else if (schema->format[2] == 'L')
1286 : {
1287 0 : osErrorMsg = "LargeListView not supported";
1288 0 : return false;
1289 : }
1290 : }
1291 2136 : for (int64_t i = 0; i < schema->n_children; ++i)
1292 : {
1293 1065 : if (!IsArrowSchemaSupported(schema->children[i], papszOptions,
1294 : osErrorMsg))
1295 : {
1296 3 : return false;
1297 : }
1298 : }
1299 1071 : return true;
1300 : }
1301 : #endif
1302 :
1303 : /************************************************************************/
1304 : /* SetMetadata() */
1305 : /************************************************************************/
1306 :
1307 7 : CPLErr OGRParquetWriterLayer::SetMetadata(char **papszMetadata,
1308 : const char *pszDomain)
1309 : {
1310 7 : if (!pszDomain || !EQUAL(pszDomain, "SHAPEFILE"))
1311 : {
1312 5 : return OGRLayer::SetMetadata(papszMetadata, pszDomain);
1313 : }
1314 2 : return CE_None;
1315 : }
1316 :
1317 : /************************************************************************/
1318 : /* GetDataset() */
1319 : /************************************************************************/
1320 :
1321 17 : GDALDataset *OGRParquetWriterLayer::GetDataset()
1322 : {
1323 17 : return m_poDataset;
1324 : }
|