Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: OpenGIS Simple Features Reference Implementation
4 : * Purpose: Implements writing of FileGDB tables
5 : * Author: Even Rouault, <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2022, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_port.h"
14 :
15 : #include "gdal_version_full/gdal_version.h"
16 :
17 : #include "filegdbtable.h"
18 :
19 : #include <algorithm>
20 : #include <cinttypes>
21 : #include <cmath>
22 : #include <cwchar>
23 : #include <errno.h>
24 : #include <limits.h>
25 : #include <stddef.h>
26 : #include <stdio.h>
27 : #include <string.h>
28 : #include <time.h>
29 : #include <limits>
30 : #include <set>
31 : #include <string>
32 : #include <vector>
33 :
34 : #include "cpl_conv.h"
35 : #include "cpl_error.h"
36 : #include "cpl_string.h"
37 : #include "cpl_time.h"
38 : #include "cpl_vsi.h"
39 : #include "filegdbtable_priv.h"
40 : #include "gdal_priv_templates.hpp"
41 : #include "ogr_api.h"
42 : #include "ogr_core.h"
43 : #include "ogr_geometry.h"
44 : #include "ogrpgeogeometry.h"
45 :
46 : namespace OpenFileGDB
47 : {
48 :
49 : constexpr uint8_t EXT_SHAPE_SEGMENT_ARC = 1;
50 : constexpr int TABLX_HEADER_SIZE = 16;
51 : constexpr int TABLX_FEATURES_PER_PAGE = 1024;
52 :
53 : /************************************************************************/
54 : /* Create() */
55 : /************************************************************************/
56 :
57 1884 : bool FileGDBTable::Create(const char *pszFilename, int nTablxOffsetSize,
58 : FileGDBTableGeometryType eTableGeomType,
59 : bool bGeomTypeHasZ, bool bGeomTypeHasM)
60 : {
61 1884 : CPLAssert(m_fpTable == nullptr);
62 :
63 1884 : m_eGDBTableVersion = GDBTableVersion::V3;
64 1884 : m_bUpdate = true;
65 1884 : m_eTableGeomType = eTableGeomType;
66 1884 : m_nTablxOffsetSize = nTablxOffsetSize;
67 1884 : m_bGeomTypeHasZ = bGeomTypeHasZ;
68 1884 : m_bGeomTypeHasM = bGeomTypeHasM;
69 1884 : m_bHasReadGDBIndexes = TRUE;
70 :
71 1884 : if (!EQUAL(CPLGetExtensionSafe(pszFilename).c_str(), "gdbtable"))
72 : {
73 0 : CPLError(CE_Failure, CPLE_AppDefined,
74 : "FileGDB table extension must be gdbtable");
75 0 : return false;
76 : }
77 :
78 1884 : m_osFilename = pszFilename;
79 1884 : m_osFilenameWithLayerName = m_osFilename;
80 1884 : m_fpTable = VSIFOpenL(pszFilename, "wb+");
81 1884 : if (m_fpTable == nullptr)
82 : {
83 0 : CPLError(CE_Failure, CPLE_OpenFailed, "Cannot create %s: %s",
84 0 : m_osFilename.c_str(), VSIStrerror(errno));
85 0 : return false;
86 : }
87 :
88 : const std::string osTableXName =
89 3768 : CPLResetExtensionSafe(pszFilename, "gdbtablx");
90 1884 : m_fpTableX = VSIFOpenL(osTableXName.c_str(), "wb+");
91 1884 : if (m_fpTableX == nullptr)
92 : {
93 0 : CPLError(CE_Failure, CPLE_OpenFailed, "Cannot create %s: %s",
94 0 : osTableXName.c_str(), VSIStrerror(errno));
95 0 : return false;
96 : }
97 :
98 1884 : if (!WriteHeader(m_fpTable))
99 0 : return false;
100 :
101 1884 : if (!WriteHeaderX(m_fpTableX))
102 0 : return false;
103 :
104 1884 : m_bDirtyTableXTrailer = true;
105 :
106 1884 : return true;
107 : }
108 :
109 : /************************************************************************/
110 : /* SetTextUTF16() */
111 : /************************************************************************/
112 :
113 1 : bool FileGDBTable::SetTextUTF16()
114 : {
115 1 : if (m_nOffsetFieldDesc != 0)
116 : {
117 0 : CPLError(CE_Failure, CPLE_NotSupported,
118 : "SetTextUTF16() should be called immediately after Create()");
119 0 : return false;
120 : }
121 :
122 1 : m_bStringsAreUTF8 = false;
123 1 : return true;
124 : }
125 :
126 : /************************************************************************/
127 : /* WriteHeader() */
128 : /************************************************************************/
129 :
130 1929 : bool FileGDBTable::WriteHeader(VSILFILE *fpTable)
131 : {
132 : // Could be useful in case we get something wrong...
133 : const char *pszCreator =
134 1929 : CPLGetConfigOption("OPENFILEGDB_CREATOR", "GDAL " GDAL_RELEASE_NAME);
135 :
136 1929 : m_nFileSize = 0;
137 1929 : m_bDirtyHeader = true;
138 1929 : m_bDirtyFieldDescriptors = true;
139 1929 : m_nOffsetFieldDesc = 0;
140 1929 : m_nFieldDescLength = 0;
141 :
142 1929 : VSIFSeekL(fpTable, 0, SEEK_SET);
143 :
144 : bool bRet =
145 1929 : WriteUInt32(fpTable, 3) && // version number
146 : // number of valid rows
147 1929 : WriteUInt32(fpTable, static_cast<uint32_t>(m_nValidRecordCount)) &&
148 1929 : WriteUInt32(fpTable,
149 1929 : m_nHeaderBufferMaxSize) && // largest size of a feature
150 : // record / field description
151 1929 : WriteUInt32(fpTable, 5) && // magic value
152 1929 : WriteUInt32(fpTable, 0) && // magic value
153 1929 : WriteUInt32(fpTable, 0) && // magic value
154 5787 : WriteUInt64(fpTable, m_nFileSize) &&
155 1929 : WriteUInt64(fpTable, m_nOffsetFieldDesc);
156 :
157 1929 : if (bRet && pszCreator[0] != '\0')
158 : {
159 : // Writing the creator is not part of the "spec", but we just use
160 : // the fact that there might be ghost areas in the file
161 1929 : bRet =
162 3858 : WriteUInt32(fpTable, static_cast<uint32_t>(strlen(pszCreator))) &&
163 1929 : VSIFWriteL(pszCreator, strlen(pszCreator), 1, fpTable) == 1;
164 : }
165 :
166 1929 : if (!bRet)
167 : {
168 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot write .gdbtable header");
169 0 : return false;
170 : }
171 :
172 1929 : m_nFileSize = VSIFTellL(fpTable);
173 1929 : return true;
174 : }
175 :
176 : /************************************************************************/
177 : /* WriteHeaderX() */
178 : /************************************************************************/
179 :
180 1915 : bool FileGDBTable::WriteHeaderX(VSILFILE *fpTableX)
181 : {
182 1915 : VSIFSeekL(fpTableX, 0, SEEK_SET);
183 1915 : if (!WriteUInt32(fpTableX, 3) || // version number
184 1915 : !WriteUInt32(fpTableX, static_cast<uint32_t>(m_n1024BlocksPresent)) ||
185 5745 : !WriteUInt32(fpTableX, static_cast<uint32_t>(m_nTotalRecordCount)) ||
186 1915 : !WriteUInt32(fpTableX, m_nTablxOffsetSize))
187 : {
188 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot write .gdbtablx header");
189 0 : return false;
190 : }
191 1915 : return true;
192 : }
193 :
194 : /************************************************************************/
195 : /* Sync() */
196 : /************************************************************************/
197 :
198 11486 : bool FileGDBTable::Sync(VSILFILE *fpTable, VSILFILE *fpTableX)
199 : {
200 11486 : if (!m_bUpdate)
201 3944 : return true;
202 :
203 7542 : if (fpTable == nullptr)
204 7458 : fpTable = m_fpTable;
205 :
206 7542 : if (fpTableX == nullptr)
207 7458 : fpTableX = m_fpTableX;
208 :
209 7542 : bool bRet = true;
210 :
211 7542 : if (m_bDirtyGdbIndexesFile)
212 : {
213 284 : m_bDirtyGdbIndexesFile = false;
214 284 : CreateGdbIndexesFile();
215 : }
216 :
217 7542 : if (m_bDirtyIndices)
218 : {
219 2774 : m_bDirtyIndices = false;
220 2774 : RefreshIndices();
221 : }
222 :
223 7542 : if (m_bDirtyFieldDescriptors && fpTable)
224 656 : bRet = WriteFieldDescriptors(fpTable);
225 :
226 7542 : if (m_bDirtyGeomFieldBBox && fpTable)
227 : {
228 122 : VSIFSeekL(fpTable, m_nOffsetFieldDesc + m_nGeomFieldBBoxSubOffset,
229 : SEEK_SET);
230 122 : const auto poGeomField = cpl::down_cast<const FileGDBGeomField *>(
231 122 : m_apoFields[m_iGeomField].get());
232 122 : bRet &= WriteFloat64(fpTable, poGeomField->GetXMin());
233 122 : bRet &= WriteFloat64(fpTable, poGeomField->GetYMin());
234 122 : bRet &= WriteFloat64(fpTable, poGeomField->GetXMax());
235 122 : bRet &= WriteFloat64(fpTable, poGeomField->GetYMax());
236 122 : if (m_bGeomTypeHasZ)
237 : {
238 33 : bRet &= WriteFloat64(fpTable, poGeomField->GetZMin());
239 33 : bRet &= WriteFloat64(fpTable, poGeomField->GetZMax());
240 : }
241 122 : m_bDirtyGeomFieldBBox = false;
242 : }
243 :
244 7542 : if (m_bDirtyGeomFieldSpatialIndexGridRes && fpTable)
245 : {
246 96 : VSIFSeekL(fpTable,
247 96 : m_nOffsetFieldDesc + m_nGeomFieldSpatialIndexGridResSubOffset,
248 : SEEK_SET);
249 96 : const auto poGeomField = cpl::down_cast<const FileGDBGeomField *>(
250 96 : m_apoFields[m_iGeomField].get());
251 : const auto &adfSpatialIndexGridResolution =
252 96 : poGeomField->GetSpatialIndexGridResolution();
253 192 : for (double dfSize : adfSpatialIndexGridResolution)
254 96 : bRet &= WriteFloat64(fpTable, dfSize);
255 96 : m_bDirtyGeomFieldSpatialIndexGridRes = false;
256 : }
257 :
258 7542 : if (m_bDirtyHeader && fpTable)
259 : {
260 3428 : VSIFSeekL(fpTable, 4, SEEK_SET);
261 3428 : bRet &=
262 3428 : WriteUInt32(fpTable, static_cast<uint32_t>(m_nValidRecordCount));
263 3428 : m_nHeaderBufferMaxSize =
264 6856 : std::max(m_nHeaderBufferMaxSize,
265 3428 : std::max(m_nRowBufferMaxSize, m_nFieldDescLength));
266 3428 : bRet &= WriteUInt32(fpTable, m_nHeaderBufferMaxSize);
267 :
268 3428 : VSIFSeekL(fpTable, 24, SEEK_SET);
269 3428 : bRet &= WriteUInt64(fpTable, m_nFileSize);
270 3428 : bRet &= WriteUInt64(fpTable, m_nOffsetFieldDesc);
271 :
272 3428 : VSIFSeekL(fpTable, 0, SEEK_END);
273 3428 : CPLAssert(VSIFTellL(fpTable) == m_nFileSize);
274 3428 : m_bDirtyHeader = false;
275 : }
276 :
277 7542 : if (m_bDirtyTableXHeader && fpTableX)
278 : {
279 2704 : VSIFSeekL(fpTableX, 4, SEEK_SET);
280 2704 : bRet &=
281 2704 : WriteUInt32(fpTableX, static_cast<uint32_t>(m_n1024BlocksPresent));
282 2704 : bRet &=
283 2704 : WriteUInt32(fpTableX, static_cast<uint32_t>(m_nTotalRecordCount));
284 2704 : m_bDirtyTableXHeader = false;
285 : }
286 :
287 7542 : if (m_bDirtyTableXTrailer && fpTableX)
288 : {
289 2404 : m_nOffsetTableXTrailer =
290 2404 : TABLX_HEADER_SIZE +
291 2404 : m_nTablxOffsetSize * TABLX_FEATURES_PER_PAGE *
292 2404 : static_cast<vsi_l_offset>(m_n1024BlocksPresent);
293 2404 : VSIFSeekL(fpTableX, m_nOffsetTableXTrailer, SEEK_SET);
294 7212 : const uint32_t n1024BlocksTotal = static_cast<uint32_t>(
295 2404 : DIV_ROUND_UP(m_nTotalRecordCount, TABLX_FEATURES_PER_PAGE));
296 2404 : if (!m_abyTablXBlockMap.empty())
297 : {
298 31 : CPLAssert(m_abyTablXBlockMap.size() >= (n1024BlocksTotal + 7) / 8);
299 : }
300 : // Size of the bitmap in terms of 32-bit words, rounded to a multiple
301 : // of 32.
302 : const uint32_t nBitmapInt32Words =
303 7212 : DIV_ROUND_UP(
304 : DIV_ROUND_UP(static_cast<uint32_t>(m_abyTablXBlockMap.size()),
305 : 4),
306 7212 : 32) *
307 2404 : 32;
308 2404 : m_abyTablXBlockMap.resize(nBitmapInt32Words * 4);
309 2404 : bRet &= WriteUInt32(fpTableX, nBitmapInt32Words);
310 2404 : bRet &= WriteUInt32(fpTableX, n1024BlocksTotal);
311 2404 : bRet &=
312 2404 : WriteUInt32(fpTableX, static_cast<uint32_t>(m_n1024BlocksPresent));
313 2404 : uint32_t nTrailingZero32BitWords = 0;
314 2404 : for (int i = static_cast<int>(m_abyTablXBlockMap.size() / 4) - 1;
315 3303 : i >= 0; --i)
316 : {
317 930 : if (m_abyTablXBlockMap[4 * i] != 0 ||
318 901 : m_abyTablXBlockMap[4 * i + 1] != 0 ||
319 2732 : m_abyTablXBlockMap[4 * i + 2] != 0 ||
320 901 : m_abyTablXBlockMap[4 * i + 3] != 0)
321 : {
322 31 : break;
323 : }
324 899 : nTrailingZero32BitWords++;
325 : }
326 2404 : const uint32_t nLeadingNonZero32BitWords =
327 : nBitmapInt32Words - nTrailingZero32BitWords;
328 2404 : bRet &= WriteUInt32(fpTableX, nLeadingNonZero32BitWords);
329 2404 : if (!m_abyTablXBlockMap.empty())
330 : {
331 : #ifdef DEBUG
332 31 : uint32_t nCountBlocks = 0;
333 4194430 : for (uint32_t i = 0; i < n1024BlocksTotal; i++)
334 4194400 : nCountBlocks += TEST_BIT(m_abyTablXBlockMap.data(), i) != 0;
335 31 : if (nCountBlocks != m_n1024BlocksPresent)
336 : {
337 0 : CPLError(CE_Failure, CPLE_AppDefined,
338 : "Sync(): nCountBlocks(=%u) != "
339 : "m_n1024BlocksPresent(=%" PRIu64 ")",
340 : nCountBlocks, m_n1024BlocksPresent);
341 : }
342 : #endif
343 31 : bRet &= VSIFWriteL(m_abyTablXBlockMap.data(), 1,
344 : m_abyTablXBlockMap.size(),
345 31 : fpTableX) == m_abyTablXBlockMap.size();
346 : }
347 2404 : m_bDirtyTableXTrailer = false;
348 : }
349 :
350 7542 : if (m_bFreelistCanBeDeleted)
351 : {
352 6 : DeleteFreeList();
353 : }
354 :
355 7542 : if (fpTable)
356 7484 : VSIFFlushL(fpTable);
357 :
358 7542 : if (fpTableX)
359 7483 : VSIFFlushL(fpTableX);
360 :
361 7542 : return bRet;
362 : }
363 :
364 : /************************************************************************/
365 : /* EncodeEnvelope() */
366 : /************************************************************************/
367 :
368 : #define CHECK_CAN_BE_ENCODED_ON_VARUINT(v, msg) \
369 : if (!GDALIsValueInRange<uint64_t>(v)) \
370 : { \
371 : CPLError(CE_Failure, CPLE_AppDefined, msg); \
372 : return false; \
373 : }
374 :
375 : #define CHECK_CAN_BE_ENCODED_ON_VARINT(v, oldV, msg) \
376 : if (!GDALIsValueInRange<int64_t>(v) || \
377 : !GDALIsValueInRange<int64_t>((v) - (oldV))) \
378 : { \
379 : CPLError(CE_Failure, CPLE_AppDefined, msg); \
380 : return false; \
381 : }
382 :
383 87 : static bool EncodeEnvelope(std::vector<GByte> &abyBuffer,
384 : const FileGDBGeomField *poGeomField,
385 : const OGRGeometry *poGeom)
386 : {
387 87 : OGREnvelope oEnvelope;
388 87 : poGeom->getEnvelope(&oEnvelope);
389 :
390 : double dfVal;
391 :
392 87 : dfVal = (oEnvelope.MinX - poGeomField->GetXOrigin()) *
393 87 : poGeomField->GetXYScale();
394 87 : CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode X value");
395 87 : WriteVarUInt(abyBuffer, static_cast<uint64_t>(dfVal + 0.5));
396 :
397 87 : dfVal = (oEnvelope.MinY - poGeomField->GetYOrigin()) *
398 87 : poGeomField->GetXYScale();
399 87 : CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode Y value");
400 87 : WriteVarUInt(abyBuffer, static_cast<uint64_t>(dfVal + 0.5));
401 :
402 87 : dfVal = (oEnvelope.MaxX - oEnvelope.MinX) * poGeomField->GetXYScale();
403 87 : CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode X value");
404 87 : WriteVarUInt(abyBuffer, static_cast<uint64_t>(dfVal + 0.5));
405 :
406 87 : dfVal = (oEnvelope.MaxY - oEnvelope.MinY) * poGeomField->GetXYScale();
407 87 : CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode Y value");
408 87 : WriteVarUInt(abyBuffer, static_cast<uint64_t>(dfVal + 0.5));
409 :
410 87 : return true;
411 : }
412 :
413 : /************************************************************************/
414 : /* EncodeGeometry() */
415 : /************************************************************************/
416 :
417 3881 : bool FileGDBTable::EncodeGeometry(const FileGDBGeomField *poGeomField,
418 : const OGRGeometry *poGeom)
419 : {
420 3881 : m_abyGeomBuffer.clear();
421 :
422 3881 : const auto bIs3D = poGeom->Is3D();
423 3881 : const auto bIsMeasured = poGeom->IsMeasured();
424 :
425 : const auto WriteEndOfCurveOrSurface =
426 16039 : [this, bIs3D, bIsMeasured, poGeomField, poGeom](int nCurveDescrCount)
427 : {
428 85 : WriteVarUInt(m_abyGeomBuffer, static_cast<uint32_t>(m_adfX.size()));
429 85 : if (m_adfX.empty())
430 8 : return true;
431 77 : WriteVarUInt(m_abyGeomBuffer,
432 77 : static_cast<uint32_t>(m_anNumberPointsPerPart.size()));
433 77 : if (nCurveDescrCount > 0)
434 12 : WriteVarUInt(m_abyGeomBuffer, nCurveDescrCount);
435 :
436 77 : if (!EncodeEnvelope(m_abyGeomBuffer, poGeomField, poGeom))
437 0 : return false;
438 :
439 102 : for (int iPart = 0;
440 102 : iPart < static_cast<int>(m_anNumberPointsPerPart.size()) - 1;
441 : ++iPart)
442 : {
443 25 : WriteVarUInt(m_abyGeomBuffer, m_anNumberPointsPerPart[iPart]);
444 : }
445 :
446 : {
447 77 : int64_t nLastX = 0;
448 77 : int64_t nLastY = 0;
449 1664 : for (size_t i = 0; i < m_adfX.size(); ++i)
450 : {
451 : double dfVal =
452 1587 : std::round((m_adfX[i] - poGeomField->GetXOrigin()) *
453 1587 : poGeomField->GetXYScale());
454 1587 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastX,
455 : "Cannot encode X value");
456 1587 : const int64_t nX = static_cast<int64_t>(dfVal);
457 1587 : WriteVarInt(m_abyGeomBuffer, nX - nLastX);
458 :
459 1587 : dfVal = std::round((m_adfY[i] - poGeomField->GetYOrigin()) *
460 1587 : poGeomField->GetXYScale());
461 1587 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastY,
462 : "Cannot encode Y value");
463 1587 : const int64_t nY = static_cast<int64_t>(dfVal);
464 1587 : WriteVarInt(m_abyGeomBuffer, nY - nLastY);
465 :
466 1587 : nLastX = nX;
467 1587 : nLastY = nY;
468 : }
469 : }
470 :
471 77 : if (bIs3D)
472 : {
473 20 : int64_t nLastZ = 0;
474 107 : for (size_t i = 0; i < m_adfZ.size(); ++i)
475 : {
476 : double dfVal =
477 87 : std::round((m_adfZ[i] - poGeomField->GetZOrigin()) *
478 87 : poGeomField->GetZScale());
479 87 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastZ,
480 : "Cannot encode Z value");
481 87 : const int64_t nZ = static_cast<int64_t>(dfVal);
482 87 : WriteVarInt(m_abyGeomBuffer, nZ - nLastZ);
483 :
484 87 : nLastZ = nZ;
485 : }
486 : }
487 :
488 77 : if (bIsMeasured)
489 : {
490 12 : int64_t nLastM = 0;
491 71 : for (size_t i = 0; i < m_adfM.size(); ++i)
492 : {
493 : double dfVal =
494 59 : std::round((m_adfM[i] - poGeomField->GetMOrigin()) *
495 59 : poGeomField->GetMScale());
496 59 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastM,
497 : "Cannot encode M value");
498 59 : const int64_t nM = static_cast<int64_t>(dfVal);
499 59 : WriteVarInt(m_abyGeomBuffer, nM - nLastM);
500 :
501 59 : nLastM = nM;
502 : }
503 : }
504 :
505 77 : if (!m_abyCurvePart.empty())
506 : {
507 12 : m_abyGeomBuffer.insert(m_abyGeomBuffer.end(),
508 : m_abyCurvePart.begin(),
509 24 : m_abyCurvePart.end());
510 : }
511 :
512 77 : return true;
513 3881 : };
514 :
515 : const auto ReserveXYZMArrays =
516 604 : [this, bIs3D, bIsMeasured](const size_t nAdditionalSize)
517 : {
518 81 : size_t nNewMinSize = m_adfX.size() + nAdditionalSize;
519 81 : if (nNewMinSize > m_adfX.capacity())
520 : {
521 67 : size_t nNewCapacity = nNewMinSize;
522 67 : if (m_adfX.capacity() < std::numeric_limits<size_t>::max() / 2)
523 : {
524 67 : nNewCapacity = std::max(nNewCapacity, 2 * m_adfX.capacity());
525 : }
526 67 : m_adfX.reserve(nNewCapacity);
527 67 : m_adfY.reserve(nNewCapacity);
528 67 : if (bIs3D)
529 24 : m_adfZ.reserve(nNewCapacity);
530 67 : if (bIsMeasured)
531 16 : m_adfM.reserve(nNewCapacity);
532 : }
533 81 : };
534 :
535 3881 : const auto eFlatType = wkbFlatten(poGeom->getGeometryType());
536 3881 : switch (eFlatType)
537 : {
538 3784 : case wkbPoint:
539 : {
540 3784 : if (bIs3D)
541 : {
542 14 : if (bIsMeasured)
543 : {
544 4 : WriteUInt8(m_abyGeomBuffer,
545 : static_cast<uint8_t>(SHPT_POINTZM));
546 : }
547 : else
548 : {
549 10 : WriteUInt8(m_abyGeomBuffer,
550 : static_cast<uint8_t>(SHPT_POINTZ));
551 : }
552 : }
553 : else
554 : {
555 3770 : if (bIsMeasured)
556 : {
557 2 : WriteUInt8(m_abyGeomBuffer,
558 : static_cast<uint8_t>(SHPT_POINTM));
559 : }
560 : else
561 : {
562 3768 : WriteUInt8(m_abyGeomBuffer,
563 : static_cast<uint8_t>(SHPT_POINT));
564 : }
565 : }
566 3784 : const auto poPoint = poGeom->toPoint();
567 3784 : if (poPoint->IsEmpty())
568 : {
569 4 : WriteUInt8(m_abyGeomBuffer, 0);
570 4 : WriteUInt8(m_abyGeomBuffer, 0);
571 4 : if (bIs3D)
572 2 : WriteUInt8(m_abyGeomBuffer, 0);
573 4 : if (bIsMeasured)
574 2 : WriteUInt8(m_abyGeomBuffer, 0);
575 : }
576 : else
577 : {
578 : double dfVal;
579 :
580 3780 : dfVal = (poPoint->getX() - poGeomField->GetXOrigin()) *
581 3780 : poGeomField->GetXYScale() +
582 : 1;
583 3780 : CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode value");
584 3780 : WriteVarUInt(m_abyGeomBuffer,
585 3780 : static_cast<uint64_t>(dfVal + 0.5));
586 :
587 3780 : dfVal = (poPoint->getY() - poGeomField->GetYOrigin()) *
588 3780 : poGeomField->GetXYScale() +
589 : 1;
590 3780 : CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode Y value");
591 3780 : WriteVarUInt(m_abyGeomBuffer,
592 3780 : static_cast<uint64_t>(dfVal + 0.5));
593 :
594 3780 : if (bIs3D)
595 : {
596 12 : dfVal = (poPoint->getZ() - poGeomField->GetZOrigin()) *
597 12 : poGeomField->GetZScale() +
598 : 1;
599 12 : CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal,
600 : "Cannot encode Z value");
601 12 : WriteVarUInt(m_abyGeomBuffer,
602 12 : static_cast<uint64_t>(dfVal + 0.5));
603 : }
604 :
605 3780 : if (bIsMeasured)
606 : {
607 4 : dfVal = (poPoint->getM() - poGeomField->GetMOrigin()) *
608 4 : poGeomField->GetMScale() +
609 : 1;
610 4 : CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal,
611 : "Cannot encode M value");
612 4 : WriteVarUInt(m_abyGeomBuffer,
613 4 : static_cast<uint64_t>(dfVal + 0.5));
614 : }
615 : }
616 :
617 3784 : return true;
618 : }
619 :
620 9 : case wkbMultiPoint:
621 : {
622 9 : if (bIs3D)
623 : {
624 4 : if (bIsMeasured)
625 : {
626 1 : WriteUInt8(m_abyGeomBuffer,
627 : static_cast<uint8_t>(SHPT_MULTIPOINTZM));
628 : }
629 : else
630 : {
631 3 : WriteUInt8(m_abyGeomBuffer,
632 : static_cast<uint8_t>(SHPT_MULTIPOINTZ));
633 : }
634 : }
635 : else
636 : {
637 5 : if (bIsMeasured)
638 : {
639 1 : WriteUInt8(m_abyGeomBuffer,
640 : static_cast<uint8_t>(SHPT_MULTIPOINTM));
641 : }
642 : else
643 : {
644 4 : WriteUInt8(m_abyGeomBuffer,
645 : static_cast<uint8_t>(SHPT_MULTIPOINT));
646 : }
647 : }
648 :
649 9 : const auto poMultiPoint = poGeom->toMultiPoint();
650 9 : const auto nNumGeoms = poMultiPoint->getNumGeometries();
651 9 : WriteVarUInt(m_abyGeomBuffer, nNumGeoms);
652 9 : if (nNumGeoms == 0)
653 0 : return true;
654 :
655 9 : if (!EncodeEnvelope(m_abyGeomBuffer, poGeomField, poGeom))
656 0 : return false;
657 :
658 : {
659 9 : int64_t nLastX = 0;
660 9 : int64_t nLastY = 0;
661 26 : for (const auto *poPoint : *poMultiPoint)
662 : {
663 17 : const double dfX = poPoint->getX();
664 17 : const double dfY = poPoint->getY();
665 :
666 : double dfVal =
667 17 : std::round((dfX - poGeomField->GetXOrigin()) *
668 17 : poGeomField->GetXYScale());
669 17 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastX,
670 : "Cannot encode value");
671 17 : const int64_t nX = static_cast<int64_t>(dfVal);
672 17 : WriteVarInt(m_abyGeomBuffer, nX - nLastX);
673 :
674 17 : dfVal = std::round((dfY - poGeomField->GetYOrigin()) *
675 17 : poGeomField->GetXYScale());
676 17 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastY,
677 : "Cannot encode Y value");
678 17 : const int64_t nY = static_cast<int64_t>(dfVal);
679 17 : WriteVarInt(m_abyGeomBuffer, nY - nLastY);
680 :
681 17 : nLastX = nX;
682 17 : nLastY = nY;
683 : }
684 : }
685 :
686 9 : if (bIs3D)
687 : {
688 4 : int64_t nLastZ = 0;
689 12 : for (const auto *poPoint : *poMultiPoint)
690 : {
691 8 : const double dfZ = poPoint->getZ();
692 :
693 : double dfVal =
694 8 : std::round((dfZ - poGeomField->GetZOrigin()) *
695 8 : poGeomField->GetZScale());
696 8 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastZ,
697 : "Bad Z value");
698 8 : const int64_t nZ = static_cast<int64_t>(dfVal);
699 8 : WriteVarInt(m_abyGeomBuffer, nZ - nLastZ);
700 :
701 8 : nLastZ = nZ;
702 : }
703 : }
704 :
705 9 : if (bIsMeasured)
706 : {
707 2 : int64_t nLastM = 0;
708 8 : for (const auto *poPoint : *poMultiPoint)
709 : {
710 6 : const double dfM = poPoint->getM();
711 :
712 : double dfVal =
713 6 : std::round((dfM - poGeomField->GetMOrigin()) *
714 6 : poGeomField->GetMScale());
715 6 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastM,
716 : "Bad M value");
717 6 : const int64_t nM = static_cast<int64_t>(dfVal);
718 6 : WriteVarInt(m_abyGeomBuffer, nM - nLastM);
719 :
720 6 : nLastM = nM;
721 : }
722 : }
723 :
724 9 : return true;
725 : }
726 :
727 33 : case wkbLineString:
728 : case wkbCircularString:
729 : case wkbCompoundCurve:
730 : case wkbMultiLineString:
731 : case wkbMultiCurve:
732 : {
733 33 : m_abyCurvePart.clear();
734 33 : m_anNumberPointsPerPart.clear();
735 33 : m_adfX.clear();
736 33 : m_adfY.clear();
737 33 : m_adfZ.clear();
738 33 : m_adfM.clear();
739 :
740 33 : int nCurveDescrCount = 0;
741 : const auto ProcessCurve =
742 37 : [this, bIs3D, bIsMeasured, &nCurveDescrCount,
743 4572 : &ReserveXYZMArrays](const OGRCurve *poCurve)
744 : {
745 37 : if (auto poCC = dynamic_cast<const OGRCompoundCurve *>(poCurve))
746 : {
747 5 : const size_t nSizeBefore = m_adfX.size();
748 :
749 5 : std::size_t nTotalSize = 0;
750 12 : for (const auto *poSubCurve : *poCC)
751 : {
752 7 : nTotalSize += poSubCurve->getNumPoints();
753 : }
754 5 : ReserveXYZMArrays(nTotalSize);
755 :
756 5 : bool bFirstSubCurve = true;
757 12 : for (const auto *poSubCurve : *poCC)
758 : {
759 7 : if (const auto poLS =
760 7 : dynamic_cast<const OGRLineString *>(poSubCurve))
761 : {
762 4 : const int nNumPoints = poLS->getNumPoints();
763 11 : for (int i = (bFirstSubCurve ? 0 : 1);
764 11 : i < nNumPoints; ++i)
765 : {
766 7 : m_adfX.push_back(poLS->getX(i));
767 7 : m_adfY.push_back(poLS->getY(i));
768 7 : if (bIs3D)
769 1 : m_adfZ.push_back(poLS->getZ(i));
770 7 : if (bIsMeasured)
771 1 : m_adfM.push_back(poLS->getM(i));
772 : }
773 : }
774 3 : else if (const auto poCS =
775 0 : dynamic_cast<const OGRCircularString *>(
776 3 : poSubCurve))
777 : {
778 3 : const int nNumPoints = poCS->getNumPoints();
779 9 : for (int i = 0; i < nNumPoints; i++)
780 : {
781 6 : if (i > 0 || bFirstSubCurve)
782 : {
783 6 : m_adfX.push_back(poCS->getX(i));
784 6 : m_adfY.push_back(poCS->getY(i));
785 6 : if (bIs3D)
786 2 : m_adfZ.push_back(poCS->getZ(i));
787 6 : if (bIsMeasured)
788 2 : m_adfM.push_back(poCS->getM(i));
789 : }
790 6 : if (i + 1 < nNumPoints)
791 : {
792 3 : ++nCurveDescrCount;
793 3 : ++i;
794 3 : WriteVarUInt(m_abyCurvePart,
795 : static_cast<uint32_t>(
796 3 : m_adfX.size() - 1));
797 3 : WriteUInt8(m_abyCurvePart,
798 : EXT_SHAPE_SEGMENT_ARC);
799 3 : WriteFloat64(m_abyCurvePart, poCS->getX(i));
800 3 : WriteFloat64(m_abyCurvePart, poCS->getY(i));
801 3 : WriteUInt32(m_abyCurvePart,
802 : (1 << 7)); // DefinedIP
803 : }
804 : }
805 : }
806 : else
807 : {
808 0 : CPLAssert(false);
809 : }
810 7 : bFirstSubCurve = false;
811 : }
812 10 : m_anNumberPointsPerPart.push_back(
813 5 : static_cast<uint32_t>(m_adfX.size() - nSizeBefore));
814 : }
815 32 : else if (const auto poLS =
816 32 : dynamic_cast<const OGRLineString *>(poCurve))
817 : {
818 29 : const int nNumPoints = poLS->getNumPoints();
819 29 : m_anNumberPointsPerPart.push_back(nNumPoints);
820 29 : ReserveXYZMArrays(nNumPoints);
821 1111 : for (int i = 0; i < nNumPoints; ++i)
822 : {
823 1082 : m_adfX.push_back(poLS->getX(i));
824 1082 : m_adfY.push_back(poLS->getY(i));
825 1082 : if (bIs3D)
826 24 : m_adfZ.push_back(poLS->getZ(i));
827 1082 : if (bIsMeasured)
828 16 : m_adfM.push_back(poLS->getM(i));
829 : }
830 : }
831 3 : else if (const auto poCS =
832 3 : dynamic_cast<const OGRCircularString *>(poCurve))
833 : {
834 3 : const int nNumPoints = poCS->getNumPoints();
835 3 : const size_t nSizeBefore = m_adfX.size();
836 3 : ReserveXYZMArrays(nNumPoints);
837 9 : for (int i = 0; i < nNumPoints; i++)
838 : {
839 6 : m_adfX.push_back(poCS->getX(i));
840 6 : m_adfY.push_back(poCS->getY(i));
841 6 : if (bIs3D)
842 2 : m_adfZ.push_back(poCS->getZ(i));
843 6 : if (bIsMeasured)
844 2 : m_adfM.push_back(poCS->getM(i));
845 6 : if (i + 1 < nNumPoints)
846 : {
847 3 : ++nCurveDescrCount;
848 3 : ++i;
849 3 : WriteVarUInt(
850 3 : m_abyCurvePart,
851 3 : static_cast<uint32_t>(m_adfX.size() - 1));
852 3 : WriteUInt8(m_abyCurvePart, EXT_SHAPE_SEGMENT_ARC);
853 3 : WriteFloat64(m_abyCurvePart, poCS->getX(i));
854 3 : WriteFloat64(m_abyCurvePart, poCS->getY(i));
855 3 : WriteUInt32(m_abyCurvePart, (1 << 7)); // DefinedIP
856 : }
857 : }
858 6 : m_anNumberPointsPerPart.push_back(
859 3 : static_cast<uint32_t>(m_adfX.size() - nSizeBefore));
860 : }
861 : else
862 : {
863 0 : CPLAssert(false);
864 : }
865 37 : };
866 :
867 33 : if (eFlatType == wkbMultiLineString || eFlatType == wkbMultiCurve)
868 : {
869 15 : const auto poMultiCurve = poGeom->toMultiCurve();
870 34 : for (const auto *poCurve : *poMultiCurve)
871 : {
872 19 : ProcessCurve(poCurve);
873 15 : }
874 : }
875 : else
876 : {
877 18 : ProcessCurve(poGeom->toCurve());
878 : }
879 :
880 33 : if (nCurveDescrCount > 0)
881 : {
882 5 : WriteVarUInt(m_abyGeomBuffer,
883 5 : SHPT_GENERALPOLYLINE | (1U << 29) | // has curves
884 5 : ((bIsMeasured ? 1U : 0U) << 30) |
885 5 : ((bIs3D ? 1U : 0U) << 31));
886 : }
887 28 : else if (bIs3D)
888 : {
889 10 : if (bIsMeasured)
890 : {
891 3 : WriteUInt8(m_abyGeomBuffer,
892 : static_cast<uint8_t>(SHPT_ARCZM));
893 : }
894 : else
895 : {
896 7 : WriteUInt8(m_abyGeomBuffer,
897 : static_cast<uint8_t>(SHPT_ARCZ));
898 : }
899 : }
900 : else
901 : {
902 18 : if (bIsMeasured)
903 : {
904 3 : WriteUInt8(m_abyGeomBuffer,
905 : static_cast<uint8_t>(SHPT_ARCM));
906 : }
907 : else
908 : {
909 15 : WriteUInt8(m_abyGeomBuffer, static_cast<uint8_t>(SHPT_ARC));
910 : }
911 : }
912 :
913 33 : return WriteEndOfCurveOrSurface(nCurveDescrCount);
914 : }
915 :
916 52 : case wkbPolygon:
917 : case wkbCurvePolygon:
918 : case wkbMultiPolygon:
919 : case wkbMultiSurface:
920 : {
921 52 : m_abyCurvePart.clear();
922 52 : m_anNumberPointsPerPart.clear();
923 52 : m_adfX.clear();
924 52 : m_adfY.clear();
925 52 : m_adfZ.clear();
926 52 : m_adfM.clear();
927 :
928 52 : int nCurveDescrCount = 0;
929 : const auto ProcessSurface =
930 55 : [this, bIs3D, bIsMeasured, &nCurveDescrCount,
931 2248 : &ReserveXYZMArrays](const OGRSurface *poSurface)
932 : {
933 55 : if (const auto poPolygon =
934 55 : dynamic_cast<const OGRPolygon *>(poSurface))
935 : {
936 44 : bool bFirstRing = true;
937 :
938 44 : std::size_t nTotalSize = 0;
939 94 : for (const auto *poLS : *poPolygon)
940 : {
941 50 : nTotalSize += poLS->getNumPoints();
942 : }
943 44 : ReserveXYZMArrays(nTotalSize);
944 :
945 94 : for (const auto *poLS : *poPolygon)
946 : {
947 50 : const int nNumPoints = poLS->getNumPoints();
948 50 : m_anNumberPointsPerPart.push_back(nNumPoints);
949 : const bool bIsClockwise =
950 50 : CPL_TO_BOOL(poLS->isClockwise());
951 50 : const bool bReverseOrder =
952 99 : (bFirstRing && !bIsClockwise) ||
953 49 : (!bFirstRing && bIsClockwise);
954 50 : bFirstRing = false;
955 475 : for (int i = 0; i < nNumPoints; ++i)
956 : {
957 425 : const int j =
958 425 : bReverseOrder ? nNumPoints - 1 - i : i;
959 425 : m_adfX.push_back(poLS->getX(j));
960 425 : m_adfY.push_back(poLS->getY(j));
961 425 : if (bIs3D)
962 55 : m_adfZ.push_back(poLS->getZ(j));
963 425 : if (bIsMeasured)
964 35 : m_adfM.push_back(poLS->getM(j));
965 : }
966 : }
967 : }
968 11 : else if (const auto poCurvePoly =
969 11 : dynamic_cast<const OGRCurvePolygon *>(poSurface))
970 : {
971 11 : bool bFirstRing = true;
972 26 : for (const auto *poRing : *poCurvePoly)
973 : {
974 : const bool bIsClockwise =
975 15 : CPL_TO_BOOL(poRing->isClockwise());
976 15 : const bool bReverseOrder =
977 27 : (bFirstRing && !bIsClockwise) ||
978 12 : (!bFirstRing && bIsClockwise);
979 15 : bFirstRing = false;
980 15 : if (auto poCC =
981 15 : dynamic_cast<const OGRCompoundCurve *>(poRing))
982 : {
983 3 : const size_t nSizeBefore = m_adfX.size();
984 3 : bool bFirstSubCurve = true;
985 3 : const int nNumCurves = poCC->getNumCurves();
986 12 : for (int iSubCurve = 0; iSubCurve < nNumCurves;
987 : ++iSubCurve)
988 : {
989 15 : const OGRCurve *poSubCurve = poCC->getCurve(
990 6 : bReverseOrder ? nNumCurves - 1 - iSubCurve
991 : : iSubCurve);
992 9 : if (auto poLS =
993 0 : dynamic_cast<const OGRLineString *>(
994 9 : poSubCurve))
995 : {
996 6 : const int nNumPoints = poLS->getNumPoints();
997 18 : for (int i = (bFirstSubCurve ? 0 : 1);
998 18 : i < nNumPoints; ++i)
999 : {
1000 12 : const int j = bReverseOrder
1001 12 : ? nNumPoints - 1 - i
1002 : : i;
1003 12 : m_adfX.push_back(poLS->getX(j));
1004 12 : m_adfY.push_back(poLS->getY(j));
1005 12 : if (bIs3D)
1006 0 : m_adfZ.push_back(poLS->getZ(j));
1007 12 : if (bIsMeasured)
1008 0 : m_adfM.push_back(poLS->getM(j));
1009 : }
1010 : }
1011 0 : else if (auto poCS = dynamic_cast<
1012 : const OGRCircularString *>(
1013 3 : poSubCurve))
1014 : {
1015 3 : const int nNumPoints = poCS->getNumPoints();
1016 9 : for (int i = 0; i < nNumPoints; i++)
1017 : {
1018 6 : if (i > 0 || bFirstSubCurve)
1019 : {
1020 3 : const int j =
1021 : bReverseOrder
1022 3 : ? nNumPoints - 1 - i
1023 : : i;
1024 3 : m_adfX.push_back(poCS->getX(j));
1025 3 : m_adfY.push_back(poCS->getY(j));
1026 3 : if (bIs3D)
1027 0 : m_adfZ.push_back(poCS->getZ(j));
1028 3 : if (bIsMeasured)
1029 0 : m_adfM.push_back(poCS->getM(j));
1030 : }
1031 6 : if (i + 1 < nNumPoints)
1032 : {
1033 3 : ++nCurveDescrCount;
1034 3 : ++i;
1035 3 : const int j =
1036 : bReverseOrder
1037 3 : ? nNumPoints - 1 - i
1038 : : i;
1039 3 : WriteVarUInt(
1040 3 : m_abyCurvePart,
1041 : static_cast<uint32_t>(
1042 3 : m_adfX.size() - 1));
1043 3 : WriteUInt8(m_abyCurvePart,
1044 : EXT_SHAPE_SEGMENT_ARC);
1045 3 : WriteFloat64(m_abyCurvePart,
1046 : poCS->getX(j));
1047 3 : WriteFloat64(m_abyCurvePart,
1048 : poCS->getY(j));
1049 3 : WriteUInt32(m_abyCurvePart,
1050 : (1 << 7)); // DefinedIP
1051 : }
1052 : }
1053 : }
1054 : else
1055 : {
1056 0 : CPLAssert(false);
1057 : }
1058 9 : bFirstSubCurve = false;
1059 : }
1060 6 : m_anNumberPointsPerPart.push_back(
1061 3 : static_cast<uint32_t>(m_adfX.size() -
1062 : nSizeBefore));
1063 : }
1064 12 : else if (const auto poLS =
1065 0 : dynamic_cast<const OGRLineString *>(
1066 12 : poRing))
1067 : {
1068 7 : const int nNumPoints = poLS->getNumPoints();
1069 7 : m_anNumberPointsPerPart.push_back(nNumPoints);
1070 38 : for (int i = 0; i < nNumPoints; ++i)
1071 : {
1072 31 : const int j =
1073 31 : bReverseOrder ? nNumPoints - 1 - i : i;
1074 31 : m_adfX.push_back(poLS->getX(j));
1075 31 : m_adfY.push_back(poLS->getY(j));
1076 31 : if (bIs3D)
1077 0 : m_adfZ.push_back(poLS->getZ(j));
1078 31 : if (bIsMeasured)
1079 0 : m_adfM.push_back(poLS->getM(j));
1080 : }
1081 : }
1082 5 : else if (const auto poCS =
1083 0 : dynamic_cast<const OGRCircularString *>(
1084 5 : poRing))
1085 : {
1086 5 : const int nNumPoints = poCS->getNumPoints();
1087 5 : const size_t nSizeBefore = m_adfX.size();
1088 20 : for (int i = 0; i < nNumPoints; i++)
1089 : {
1090 15 : int j = bReverseOrder ? nNumPoints - 1 - i : i;
1091 15 : m_adfX.push_back(poCS->getX(j));
1092 15 : m_adfY.push_back(poCS->getY(j));
1093 15 : if (bIs3D)
1094 3 : m_adfZ.push_back(poCS->getZ(j));
1095 15 : if (bIsMeasured)
1096 3 : m_adfM.push_back(poCS->getM(j));
1097 15 : if (i + 1 < nNumPoints)
1098 : {
1099 10 : ++nCurveDescrCount;
1100 10 : ++i;
1101 10 : j = bReverseOrder ? nNumPoints - 1 - i : i;
1102 10 : WriteVarUInt(m_abyCurvePart,
1103 : static_cast<uint32_t>(
1104 10 : m_adfX.size() - 1));
1105 10 : WriteUInt8(m_abyCurvePart,
1106 : EXT_SHAPE_SEGMENT_ARC);
1107 10 : WriteFloat64(m_abyCurvePart, poCS->getX(j));
1108 10 : WriteFloat64(m_abyCurvePart, poCS->getY(j));
1109 10 : WriteUInt32(m_abyCurvePart,
1110 : (1 << 7)); // DefinedIP
1111 : }
1112 : }
1113 10 : m_anNumberPointsPerPart.push_back(
1114 5 : static_cast<uint32_t>(m_adfX.size() -
1115 : nSizeBefore));
1116 : }
1117 : else
1118 : {
1119 0 : CPLAssert(false);
1120 : }
1121 : }
1122 : }
1123 : else
1124 : {
1125 0 : CPLAssert(false);
1126 : }
1127 55 : };
1128 :
1129 52 : if (eFlatType == wkbMultiPolygon || eFlatType == wkbMultiSurface)
1130 : {
1131 19 : const auto poMultiSurface = poGeom->toMultiSurface();
1132 41 : for (const auto *poSurface : *poMultiSurface)
1133 : {
1134 22 : ProcessSurface(poSurface);
1135 19 : }
1136 : }
1137 : else
1138 : {
1139 33 : ProcessSurface(poGeom->toSurface());
1140 : }
1141 :
1142 52 : if (nCurveDescrCount > 0)
1143 : {
1144 7 : WriteVarUInt(m_abyGeomBuffer,
1145 7 : SHPT_GENERALPOLYGON | (1U << 29) | // has curves
1146 7 : ((bIsMeasured ? 1U : 0U) << 30) |
1147 7 : ((bIs3D ? 1U : 0U) << 31));
1148 : }
1149 45 : else if (bIs3D)
1150 : {
1151 11 : if (bIsMeasured)
1152 : {
1153 4 : WriteUInt8(m_abyGeomBuffer,
1154 : static_cast<uint8_t>(SHPT_POLYGONZM));
1155 : }
1156 : else
1157 : {
1158 7 : WriteUInt8(m_abyGeomBuffer,
1159 : static_cast<uint8_t>(SHPT_POLYGONZ));
1160 : }
1161 : }
1162 : else
1163 : {
1164 34 : if (bIsMeasured)
1165 : {
1166 3 : WriteUInt8(m_abyGeomBuffer,
1167 : static_cast<uint8_t>(SHPT_POLYGONM));
1168 : }
1169 : else
1170 : {
1171 31 : WriteUInt8(m_abyGeomBuffer,
1172 : static_cast<uint8_t>(SHPT_POLYGON));
1173 : }
1174 : }
1175 :
1176 52 : return WriteEndOfCurveOrSurface(nCurveDescrCount);
1177 : }
1178 :
1179 3 : case wkbTIN:
1180 : case wkbPolyhedralSurface:
1181 : case wkbGeometryCollection:
1182 : {
1183 3 : int nParts = 0;
1184 6 : std::vector<int> anPartStart;
1185 6 : std::vector<int> anPartType;
1186 3 : int nPoints = 0;
1187 6 : std::vector<OGRRawPoint> aoPoints;
1188 6 : std::vector<double> adfZ;
1189 : OGRErr eErr =
1190 3 : OGRCreateMultiPatch(poGeom, TRUE, nParts, anPartStart,
1191 : anPartType, nPoints, aoPoints, adfZ);
1192 3 : if (eErr != OGRERR_NONE)
1193 2 : return false;
1194 :
1195 1 : WriteUInt8(m_abyGeomBuffer, static_cast<uint8_t>(SHPT_MULTIPATCH));
1196 1 : WriteVarUInt(m_abyGeomBuffer, nPoints);
1197 1 : if (nPoints != 0)
1198 : {
1199 : // Apparently we must write the size of the extended buffer
1200 : // shape representation, even if we don't exactly follow this
1201 : // format when writing to FileGDB files...
1202 1 : int nShapeBufferSize =
1203 : 4; // All types start with integer type number.
1204 1 : nShapeBufferSize += 16 * 2; // xy bbox.
1205 1 : nShapeBufferSize += 4; // nparts.
1206 1 : nShapeBufferSize += 4; // npoints.
1207 1 : nShapeBufferSize += 4 * nParts; // panPartStart[nparts].
1208 1 : nShapeBufferSize += 4 * nParts; // panPartType[nparts].
1209 1 : nShapeBufferSize += 8 * 2 * nPoints; // xy points.
1210 1 : nShapeBufferSize += 16; // z bbox.
1211 1 : nShapeBufferSize += 8 * nPoints; // z points.
1212 1 : WriteVarUInt(m_abyGeomBuffer, nShapeBufferSize);
1213 :
1214 1 : WriteVarUInt(m_abyGeomBuffer, nParts);
1215 :
1216 1 : if (!EncodeEnvelope(m_abyGeomBuffer, poGeomField, poGeom))
1217 : {
1218 0 : return false;
1219 : }
1220 :
1221 2 : for (int i = 0; i < nParts - 1; i++)
1222 : {
1223 1 : WriteVarUInt(m_abyGeomBuffer,
1224 1 : anPartStart[i + 1] - anPartStart[i]);
1225 : }
1226 :
1227 3 : for (int i = 0; i < nParts; i++)
1228 : {
1229 2 : WriteVarUInt(m_abyGeomBuffer, anPartType[i]);
1230 : }
1231 :
1232 : {
1233 1 : int64_t nLastX = 0;
1234 1 : int64_t nLastY = 0;
1235 8 : for (int i = 0; i < nPoints; ++i)
1236 : {
1237 7 : double dfVal = std::round(
1238 7 : (aoPoints[i].x - poGeomField->GetXOrigin()) *
1239 7 : poGeomField->GetXYScale());
1240 7 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastX,
1241 : "Cannot encode value");
1242 7 : const int64_t nX = static_cast<int64_t>(dfVal);
1243 7 : WriteVarInt(m_abyGeomBuffer, nX - nLastX);
1244 :
1245 7 : dfVal = std::round(
1246 7 : (aoPoints[i].y - poGeomField->GetYOrigin()) *
1247 7 : poGeomField->GetXYScale());
1248 7 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastY,
1249 : "Cannot encode Y value");
1250 7 : const int64_t nY = static_cast<int64_t>(dfVal);
1251 7 : WriteVarInt(m_abyGeomBuffer, nY - nLastY);
1252 :
1253 7 : nLastX = nX;
1254 7 : nLastY = nY;
1255 : }
1256 : }
1257 :
1258 : {
1259 1 : int64_t nLastZ = 0;
1260 8 : for (int i = 0; i < nPoints; ++i)
1261 : {
1262 : double dfVal =
1263 7 : std::round((adfZ[i] - poGeomField->GetZOrigin()) *
1264 7 : poGeomField->GetZScale());
1265 7 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastZ,
1266 : "Bad Z value");
1267 7 : const int64_t nZ = static_cast<int64_t>(dfVal);
1268 7 : WriteVarInt(m_abyGeomBuffer, nZ - nLastZ);
1269 :
1270 7 : nLastZ = nZ;
1271 : }
1272 : }
1273 : }
1274 1 : return true;
1275 : }
1276 :
1277 0 : default:
1278 : {
1279 0 : CPLError(CE_Failure, CPLE_NotSupported,
1280 : "Unsupported geometry type");
1281 0 : return false;
1282 : }
1283 : }
1284 : }
1285 :
1286 : /************************************************************************/
1287 : /* EncodeFeature() */
1288 : /************************************************************************/
1289 :
1290 31975 : bool FileGDBTable::EncodeFeature(const std::vector<OGRField> &asRawFields,
1291 : const OGRGeometry *poGeom, int iSkipField)
1292 : {
1293 31975 : m_abyBuffer.clear();
1294 31975 : if (iSkipField >= 0 && m_apoFields[iSkipField]->IsNullable())
1295 22 : m_abyBuffer.resize(BIT_ARRAY_SIZE_IN_BYTES(m_nCountNullableFields - 1),
1296 22 : 0xFF);
1297 : else
1298 31953 : m_abyBuffer.resize(m_nNullableFieldsSizeInBytes, 0xFF);
1299 :
1300 31975 : if (asRawFields.size() != m_apoFields.size())
1301 : {
1302 0 : CPLError(CE_Failure, CPLE_AppDefined, "Bad size of asRawFields");
1303 0 : return false;
1304 : }
1305 31975 : int iNullableField = 0;
1306 160905 : for (int i = 0; i < static_cast<int>(m_apoFields.size()); ++i)
1307 : {
1308 128934 : if (i == iSkipField)
1309 26 : continue;
1310 128908 : auto &poField = m_apoFields[i];
1311 128908 : if (poField->GetType() == FGFT_OBJECTID)
1312 : {
1313 : // Implicit field
1314 23958 : continue;
1315 : }
1316 104950 : if (i == m_iGeomField)
1317 : {
1318 4773 : if (poGeom == nullptr)
1319 : {
1320 892 : if (!poField->IsNullable())
1321 : {
1322 0 : CPLError(CE_Failure, CPLE_AppDefined,
1323 : "Attempting to write null geometry in "
1324 : "non-nullable geometry field");
1325 0 : return false;
1326 : }
1327 892 : iNullableField++;
1328 892 : continue;
1329 : }
1330 :
1331 : auto poGeomField =
1332 3881 : cpl::down_cast<FileGDBGeomField *>(poField.get());
1333 3881 : if (!EncodeGeometry(poGeomField, poGeom))
1334 2 : return false;
1335 3879 : if (!poGeom->IsEmpty())
1336 : {
1337 3867 : OGREnvelope3D oEnvelope;
1338 3867 : poGeom->getEnvelope(&oEnvelope);
1339 3867 : m_bDirtyGeomFieldBBox = true;
1340 3867 : if (std::isnan(poGeomField->GetXMin()))
1341 : {
1342 117 : poGeomField->SetXYMinMax(oEnvelope.MinX, oEnvelope.MinY,
1343 : oEnvelope.MaxX, oEnvelope.MaxY);
1344 117 : poGeomField->SetZMinMax(oEnvelope.MinZ, oEnvelope.MaxZ);
1345 : }
1346 : else
1347 : {
1348 3750 : poGeomField->SetXYMinMax(
1349 3750 : std::min(poGeomField->GetXMin(), oEnvelope.MinX),
1350 3750 : std::min(poGeomField->GetYMin(), oEnvelope.MinY),
1351 3750 : std::max(poGeomField->GetXMax(), oEnvelope.MaxX),
1352 3750 : std::max(poGeomField->GetYMax(), oEnvelope.MaxY));
1353 3750 : poGeomField->SetZMinMax(
1354 3750 : std::min(poGeomField->GetZMin(), oEnvelope.MinZ),
1355 7500 : std::max(poGeomField->GetZMax(), oEnvelope.MaxZ));
1356 : }
1357 : }
1358 :
1359 3879 : if (m_abyGeomBuffer.size() + m_abyBuffer.size() >
1360 : static_cast<size_t>(INT_MAX))
1361 : {
1362 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large feature");
1363 0 : return false;
1364 : }
1365 :
1366 3879 : WriteVarUInt(m_abyBuffer, m_abyGeomBuffer.size());
1367 3879 : m_abyBuffer.insert(m_abyBuffer.end(), m_abyGeomBuffer.begin(),
1368 7758 : m_abyGeomBuffer.end());
1369 :
1370 3879 : if (poField->IsNullable())
1371 : {
1372 3879 : m_abyBuffer[iNullableField / 8] &= ~(1 << (iNullableField % 8));
1373 3879 : iNullableField++;
1374 : }
1375 3879 : continue;
1376 : }
1377 :
1378 200354 : if (OGR_RawField_IsNull(&asRawFields[i]) ||
1379 100177 : OGR_RawField_IsUnset(&asRawFields[i]))
1380 : {
1381 5565 : if (!poField->IsNullable())
1382 : {
1383 2 : CPLError(CE_Failure, CPLE_AppDefined,
1384 : "Attempting to write null/empty field in non-nullable "
1385 : "field");
1386 2 : return false;
1387 : }
1388 5563 : iNullableField++;
1389 5563 : continue;
1390 : }
1391 :
1392 94612 : switch (poField->GetType())
1393 : {
1394 0 : case FGFT_UNDEFINED:
1395 : {
1396 0 : CPLAssert(false);
1397 : break;
1398 : }
1399 :
1400 3668 : case FGFT_INT16:
1401 : {
1402 3668 : WriteInt16(m_abyBuffer,
1403 3668 : static_cast<int16_t>(asRawFields[i].Integer));
1404 3668 : break;
1405 : }
1406 :
1407 3888 : case FGFT_INT32:
1408 : {
1409 3888 : WriteInt32(m_abyBuffer, asRawFields[i].Integer);
1410 3888 : break;
1411 : }
1412 :
1413 4 : case FGFT_FLOAT32:
1414 : {
1415 4 : WriteFloat32(m_abyBuffer,
1416 4 : static_cast<float>(asRawFields[i].Real));
1417 4 : break;
1418 : }
1419 :
1420 4064 : case FGFT_FLOAT64:
1421 : {
1422 4064 : WriteFloat64(m_abyBuffer, asRawFields[i].Real);
1423 4064 : break;
1424 : }
1425 :
1426 54281 : case FGFT_STRING:
1427 : case FGFT_XML:
1428 : {
1429 54281 : if (m_bStringsAreUTF8 || poField->GetType() == FGFT_XML)
1430 : {
1431 54280 : const auto nLen = strlen(asRawFields[i].String);
1432 54280 : WriteVarUInt(m_abyBuffer, nLen);
1433 54280 : if (nLen > 0)
1434 : {
1435 52785 : if (nLen + m_abyBuffer.size() >
1436 : static_cast<size_t>(INT_MAX))
1437 : {
1438 0 : CPLError(CE_Failure, CPLE_AppDefined,
1439 : "Too large feature");
1440 0 : return false;
1441 : }
1442 0 : m_abyBuffer.insert(m_abyBuffer.end(),
1443 : reinterpret_cast<const uint8_t *>(
1444 52785 : asRawFields[i].String),
1445 : reinterpret_cast<const uint8_t *>(
1446 105570 : asRawFields[i].String) +
1447 105570 : nLen);
1448 : }
1449 : }
1450 : else
1451 : {
1452 1 : WriteUTF16String(m_abyBuffer, asRawFields[i].String,
1453 : NUMBER_OF_BYTES_ON_VARUINT);
1454 : }
1455 54281 : break;
1456 : }
1457 :
1458 66 : case FGFT_DATETIME:
1459 : case FGFT_DATE:
1460 : {
1461 66 : WriteFloat64(m_abyBuffer,
1462 : FileGDBOGRDateToDoubleDate(
1463 66 : &asRawFields[i], /* bConvertToUTC = */ true,
1464 66 : poField->IsHighPrecision()));
1465 66 : break;
1466 : }
1467 :
1468 0 : case FGFT_OBJECTID:
1469 : {
1470 0 : CPLAssert(false); // not possible given above processing
1471 : break;
1472 : }
1473 :
1474 0 : case FGFT_GEOMETRY:
1475 : {
1476 0 : CPLAssert(false); // not possible given above processing
1477 : break;
1478 : }
1479 :
1480 19 : case FGFT_BINARY:
1481 : {
1482 19 : WriteVarUInt(m_abyBuffer, asRawFields[i].Binary.nCount);
1483 19 : if (asRawFields[i].Binary.nCount)
1484 : {
1485 19 : if (static_cast<size_t>(asRawFields[i].Binary.nCount) +
1486 19 : m_abyBuffer.size() >
1487 : static_cast<size_t>(INT_MAX))
1488 : {
1489 0 : CPLError(CE_Failure, CPLE_AppDefined,
1490 : "Too large feature");
1491 0 : return false;
1492 : }
1493 0 : m_abyBuffer.insert(m_abyBuffer.end(),
1494 19 : asRawFields[i].Binary.paData,
1495 38 : asRawFields[i].Binary.paData +
1496 38 : asRawFields[i].Binary.nCount);
1497 : }
1498 19 : break;
1499 : }
1500 :
1501 0 : case FGFT_RASTER:
1502 : {
1503 : // Not handled for now
1504 0 : CPLAssert(false);
1505 : break;
1506 : }
1507 :
1508 28602 : case FGFT_GUID:
1509 : case FGFT_GLOBALID:
1510 : {
1511 28602 : const auto nLen = strlen(asRawFields[i].String);
1512 28602 : if (nLen != 38)
1513 : {
1514 0 : CPLError(CE_Failure, CPLE_AppDefined,
1515 : "Bad size for UUID field");
1516 0 : return false;
1517 : }
1518 57204 : std::vector<unsigned> anVals(16);
1519 28602 : sscanf(asRawFields[i].String,
1520 : "{%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%"
1521 : "02X%02X%02X%02X}",
1522 28602 : &anVals[3], &anVals[2], &anVals[1], &anVals[0],
1523 28602 : &anVals[5], &anVals[4], &anVals[7], &anVals[6],
1524 28602 : &anVals[8], &anVals[9], &anVals[10], &anVals[11],
1525 28602 : &anVals[12], &anVals[13], &anVals[14], &anVals[15]);
1526 486234 : for (auto v : anVals)
1527 : {
1528 457632 : m_abyBuffer.push_back(static_cast<uint8_t>(v));
1529 : }
1530 28602 : break;
1531 : }
1532 :
1533 2 : case FGFT_INT64:
1534 : {
1535 2 : WriteInt64(m_abyBuffer, asRawFields[i].Integer64);
1536 2 : break;
1537 : }
1538 :
1539 6 : case FGFT_TIME:
1540 : {
1541 6 : WriteFloat64(m_abyBuffer,
1542 6 : FileGDBOGRTimeToDoubleTime(&asRawFields[i]));
1543 6 : break;
1544 : }
1545 :
1546 12 : case FGFT_DATETIME_WITH_OFFSET:
1547 : {
1548 12 : WriteFloat64(m_abyBuffer, FileGDBOGRDateToDoubleDate(
1549 12 : &asRawFields[i],
1550 : /* bConvertToUTC = */ false,
1551 : /* bIsHighPrecision = */ true));
1552 12 : if (asRawFields[i].Date.TZFlag > 1)
1553 : {
1554 12 : WriteInt16(m_abyBuffer,
1555 : static_cast<int16_t>(
1556 12 : (asRawFields[i].Date.TZFlag - 100) * 15));
1557 : }
1558 : else
1559 : {
1560 0 : WriteInt16(m_abyBuffer, 0);
1561 : }
1562 12 : break;
1563 : }
1564 : }
1565 :
1566 94612 : if (poField->IsNullable())
1567 : {
1568 38004 : m_abyBuffer[iNullableField / 8] &= ~(1 << (iNullableField % 8));
1569 38004 : iNullableField++;
1570 : }
1571 : }
1572 :
1573 31971 : if (m_abyBuffer.size() > static_cast<size_t>(INT_MAX))
1574 : {
1575 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large feature");
1576 0 : return false;
1577 : }
1578 :
1579 31971 : return true;
1580 : }
1581 :
1582 : /************************************************************************/
1583 : /* SeekIntoTableXForNewFeature() */
1584 : /************************************************************************/
1585 :
1586 31894 : bool FileGDBTable::SeekIntoTableXForNewFeature(int nObjectID)
1587 : {
1588 : int iCorrectedRow;
1589 31894 : bool bWriteEmptyPageAtEnd = false;
1590 31894 : const uint32_t nPageSize = TABLX_FEATURES_PER_PAGE * m_nTablxOffsetSize;
1591 31894 : const int nTotalRecordCount = static_cast<int>(m_nTotalRecordCount);
1592 :
1593 31894 : if (m_abyTablXBlockMap.empty())
1594 : {
1595 : // Is the OID to write in the current allocated pages, or in the next
1596 : // page ?
1597 31864 : if ((nObjectID - 1) / TABLX_FEATURES_PER_PAGE <=
1598 31864 : ((nTotalRecordCount == 0)
1599 31864 : ? 0
1600 30058 : : (1 + (nTotalRecordCount - 1) / TABLX_FEATURES_PER_PAGE)))
1601 : {
1602 31842 : iCorrectedRow = nObjectID - 1;
1603 31842 : const auto n1024BlocksPresentBefore = m_n1024BlocksPresent;
1604 31842 : m_n1024BlocksPresent =
1605 31842 : DIV_ROUND_UP(std::max(nTotalRecordCount, nObjectID),
1606 : TABLX_FEATURES_PER_PAGE);
1607 31842 : bWriteEmptyPageAtEnd =
1608 31842 : m_n1024BlocksPresent > n1024BlocksPresentBefore;
1609 : }
1610 : else
1611 : {
1612 : // No, then we have a sparse table, and need to use a bitmap
1613 22 : m_abyTablXBlockMap.resize(
1614 22 : (DIV_ROUND_UP(nObjectID, TABLX_FEATURES_PER_PAGE) + 7) / 8);
1615 22 : for (int i = 0;
1616 26 : i < DIV_ROUND_UP(nTotalRecordCount, TABLX_FEATURES_PER_PAGE);
1617 : ++i)
1618 4 : m_abyTablXBlockMap[i / 8] |= (1 << (i % 8));
1619 22 : const int iBlock = (nObjectID - 1) / TABLX_FEATURES_PER_PAGE;
1620 22 : m_abyTablXBlockMap[iBlock / 8] |= (1 << (iBlock % 8));
1621 22 : iCorrectedRow =
1622 22 : DIV_ROUND_UP(nTotalRecordCount, TABLX_FEATURES_PER_PAGE) *
1623 : TABLX_FEATURES_PER_PAGE +
1624 22 : ((nObjectID - 1) % TABLX_FEATURES_PER_PAGE);
1625 22 : m_n1024BlocksPresent++;
1626 22 : bWriteEmptyPageAtEnd = true;
1627 : }
1628 : }
1629 : else
1630 : {
1631 30 : const int iBlock = (nObjectID - 1) / TABLX_FEATURES_PER_PAGE;
1632 :
1633 30 : if (nObjectID <= nTotalRecordCount)
1634 : {
1635 16 : CPLAssert(iBlock / 8 < static_cast<int>(m_abyTablXBlockMap.size()));
1636 16 : if (TEST_BIT(m_abyTablXBlockMap.data(), iBlock) == 0)
1637 : {
1638 : // This requires rewriting the gdbtablx file to insert
1639 : // a new page
1640 10 : GUInt32 nCountBlocksBefore = 0;
1641 16 : for (int i = 0; i < iBlock; i++)
1642 6 : nCountBlocksBefore +=
1643 6 : TEST_BIT(m_abyTablXBlockMap.data(), i) != 0;
1644 :
1645 10 : std::vector<GByte> abyTmp(nPageSize);
1646 10 : uint64_t nOffset =
1647 10 : TABLX_HEADER_SIZE + m_n1024BlocksPresent * nPageSize;
1648 22 : for (int i = static_cast<int>(m_n1024BlocksPresent - 1);
1649 22 : i >= static_cast<int>(nCountBlocksBefore); --i)
1650 : {
1651 12 : nOffset -= nPageSize;
1652 12 : VSIFSeekL(m_fpTableX, nOffset, SEEK_SET);
1653 12 : if (VSIFReadL(abyTmp.data(), nPageSize, 1, m_fpTableX) != 1)
1654 : {
1655 0 : CPLError(CE_Failure, CPLE_FileIO,
1656 : "Cannot read .gdtablx page at offset %u",
1657 : static_cast<uint32_t>(nOffset));
1658 0 : return false;
1659 : }
1660 12 : VSIFSeekL(m_fpTableX, VSIFTellL(m_fpTableX), SEEK_SET);
1661 12 : if (VSIFWriteL(abyTmp.data(), nPageSize, 1, m_fpTableX) !=
1662 : 1)
1663 : {
1664 0 : CPLError(CE_Failure, CPLE_FileIO,
1665 : "Cannot rewrite .gdtablx page of offset %u",
1666 : static_cast<uint32_t>(nOffset));
1667 0 : return false;
1668 : }
1669 : }
1670 10 : abyTmp.clear();
1671 10 : abyTmp.resize(nPageSize);
1672 10 : nOffset = TABLX_HEADER_SIZE +
1673 10 : static_cast<uint64_t>(nCountBlocksBefore) * nPageSize;
1674 10 : VSIFSeekL(m_fpTableX, nOffset, SEEK_SET);
1675 10 : if (VSIFWriteL(abyTmp.data(), nPageSize, 1, m_fpTableX) != 1)
1676 : {
1677 0 : CPLError(CE_Failure, CPLE_FileIO,
1678 : "Cannot write empty .gdtablx page of offset %u",
1679 : static_cast<uint32_t>(nOffset));
1680 0 : return false;
1681 : }
1682 10 : m_abyTablXBlockMap[iBlock / 8] |= (1 << (iBlock % 8));
1683 10 : m_n1024BlocksPresent++;
1684 10 : m_bDirtyTableXTrailer = true;
1685 10 : m_nOffsetTableXTrailer = 0;
1686 10 : m_nCountBlocksBeforeIBlockIdx = iBlock;
1687 10 : m_nCountBlocksBeforeIBlockValue = nCountBlocksBefore;
1688 : }
1689 : }
1690 28 : else if (DIV_ROUND_UP(nObjectID, TABLX_FEATURES_PER_PAGE) >
1691 14 : DIV_ROUND_UP(nTotalRecordCount, TABLX_FEATURES_PER_PAGE))
1692 : {
1693 8 : m_abyTablXBlockMap.resize(
1694 8 : (DIV_ROUND_UP(nObjectID, TABLX_FEATURES_PER_PAGE) + 7) / 8);
1695 8 : m_abyTablXBlockMap[iBlock / 8] |= (1 << (iBlock % 8));
1696 8 : m_n1024BlocksPresent++;
1697 8 : bWriteEmptyPageAtEnd = true;
1698 : }
1699 :
1700 30 : GUInt32 nCountBlocksBefore = 0;
1701 : // In case of sequential access, optimization to avoid recomputing
1702 : // the number of blocks since the beginning of the map
1703 30 : if (iBlock >= m_nCountBlocksBeforeIBlockIdx)
1704 : {
1705 30 : nCountBlocksBefore = m_nCountBlocksBeforeIBlockValue;
1706 70 : for (int i = m_nCountBlocksBeforeIBlockIdx; i < iBlock; i++)
1707 40 : nCountBlocksBefore +=
1708 40 : TEST_BIT(m_abyTablXBlockMap.data(), i) != 0;
1709 : }
1710 : else
1711 : {
1712 0 : nCountBlocksBefore = 0;
1713 0 : for (int i = 0; i < iBlock; i++)
1714 0 : nCountBlocksBefore +=
1715 0 : TEST_BIT(m_abyTablXBlockMap.data(), i) != 0;
1716 : }
1717 :
1718 30 : m_nCountBlocksBeforeIBlockIdx = iBlock;
1719 30 : m_nCountBlocksBeforeIBlockValue = nCountBlocksBefore;
1720 30 : iCorrectedRow = nCountBlocksBefore * TABLX_FEATURES_PER_PAGE +
1721 30 : ((nObjectID - 1) % TABLX_FEATURES_PER_PAGE);
1722 : }
1723 :
1724 31894 : if (bWriteEmptyPageAtEnd)
1725 : {
1726 1836 : m_bDirtyTableXTrailer = true;
1727 1836 : m_nOffsetTableXTrailer = 0;
1728 1836 : std::vector<GByte> abyTmp(nPageSize);
1729 1836 : uint64_t nOffset =
1730 : TABLX_HEADER_SIZE +
1731 1836 : static_cast<uint64_t>(m_n1024BlocksPresent - 1) * nPageSize;
1732 1836 : VSIFSeekL(m_fpTableX, nOffset, SEEK_SET);
1733 1836 : if (VSIFWriteL(abyTmp.data(), nPageSize, 1, m_fpTableX) != 1)
1734 : {
1735 0 : CPLError(CE_Failure, CPLE_FileIO,
1736 : "Cannot write empty .gdtablx page of offset %u",
1737 : static_cast<uint32_t>(nOffset));
1738 0 : return false;
1739 : }
1740 : }
1741 :
1742 31894 : const uint64_t nOffset =
1743 : TABLX_HEADER_SIZE +
1744 31894 : static_cast<uint64_t>(iCorrectedRow) * m_nTablxOffsetSize;
1745 31894 : VSIFSeekL(m_fpTableX, nOffset, SEEK_SET);
1746 :
1747 31894 : return true;
1748 : }
1749 :
1750 : /************************************************************************/
1751 : /* WriteFeatureOffset() */
1752 : /************************************************************************/
1753 :
1754 2746 : void FileGDBTable::WriteFeatureOffset(uint64_t nFeatureOffset,
1755 : GByte *pabyBuffer)
1756 : {
1757 2746 : CPL_LSBPTR64(&nFeatureOffset);
1758 2746 : memcpy(pabyBuffer, &nFeatureOffset, m_nTablxOffsetSize);
1759 2746 : }
1760 :
1761 : /************************************************************************/
1762 : /* WriteFeatureOffset() */
1763 : /************************************************************************/
1764 :
1765 34564 : bool FileGDBTable::WriteFeatureOffset(uint64_t nFeatureOffset)
1766 : {
1767 34564 : CPL_LSBPTR64(&nFeatureOffset);
1768 34564 : return VSIFWriteL(&nFeatureOffset, m_nTablxOffsetSize, 1, m_fpTableX) == 1;
1769 : }
1770 :
1771 : /************************************************************************/
1772 : /* CreateFeature() */
1773 : /************************************************************************/
1774 :
1775 31900 : bool FileGDBTable::CreateFeature(const std::vector<OGRField> &asRawFields,
1776 : const OGRGeometry *poGeom, int *pnFID)
1777 : {
1778 31900 : if (!m_bUpdate)
1779 0 : return false;
1780 :
1781 31900 : if (m_bDirtyFieldDescriptors && !WriteFieldDescriptors(m_fpTable))
1782 0 : return false;
1783 :
1784 : int nObjectID;
1785 31900 : if (pnFID != nullptr && *pnFID > 0)
1786 : {
1787 127 : if (*pnFID <= m_nTotalRecordCount &&
1788 22 : GetOffsetInTableForRow((*pnFID) - 1) != 0)
1789 : {
1790 2 : CPLError(
1791 : CE_Failure, CPLE_AppDefined,
1792 : "Cannot create feature of ID %d because one already exists",
1793 : *pnFID);
1794 2 : return false;
1795 : }
1796 103 : nObjectID = *pnFID;
1797 : }
1798 : else
1799 : {
1800 31795 : if (m_nTotalRecordCount == std::numeric_limits<int>::max())
1801 : {
1802 0 : CPLError(CE_Failure, CPLE_AppDefined,
1803 : "Maximum number of records per table reached");
1804 0 : return false;
1805 : }
1806 31795 : nObjectID = static_cast<int>(m_nTotalRecordCount + 1);
1807 : }
1808 :
1809 : try
1810 : {
1811 31898 : if (!EncodeFeature(asRawFields, poGeom, -1))
1812 4 : return false;
1813 : }
1814 0 : catch (const std::exception &e)
1815 : {
1816 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
1817 0 : return false;
1818 : }
1819 :
1820 31894 : const uint64_t nFreeOffset = GetOffsetOfFreeAreaFromFreeList(
1821 31894 : static_cast<uint32_t>(sizeof(uint32_t) + m_abyBuffer.size()));
1822 31894 : if (nFreeOffset == OFFSET_MINUS_ONE)
1823 : {
1824 29279 : if (((m_nFileSize + m_abyBuffer.size()) >> (8 * m_nTablxOffsetSize)) !=
1825 : 0)
1826 : {
1827 0 : CPLError(CE_Failure, CPLE_AppDefined,
1828 : "Maximum file size for m_nTablxOffsetSize = %u reached",
1829 : m_nTablxOffsetSize);
1830 0 : return false;
1831 : }
1832 : }
1833 :
1834 31894 : if (!SeekIntoTableXForNewFeature(nObjectID))
1835 0 : return false;
1836 :
1837 31894 : if (nFreeOffset == OFFSET_MINUS_ONE)
1838 : {
1839 29279 : VSIFSeekL(m_fpTable, m_nFileSize, SEEK_SET);
1840 : }
1841 : else
1842 : {
1843 2615 : VSIFSeekL(m_fpTable, nFreeOffset, SEEK_SET);
1844 : }
1845 31894 : if (!WriteUInt32(m_fpTable, static_cast<uint32_t>(m_abyBuffer.size())))
1846 0 : return false;
1847 63785 : if (!m_abyBuffer.empty() &&
1848 31891 : VSIFWriteL(m_abyBuffer.data(), 1, m_abyBuffer.size(), m_fpTable) !=
1849 31891 : m_abyBuffer.size())
1850 : {
1851 0 : return false;
1852 : }
1853 :
1854 31894 : if (!WriteFeatureOffset(nFreeOffset == OFFSET_MINUS_ONE ? m_nFileSize
1855 : : nFreeOffset))
1856 0 : return false;
1857 31894 : if (pnFID)
1858 9276 : *pnFID = nObjectID;
1859 :
1860 31894 : m_nRowBlobLength = static_cast<uint32_t>(m_abyBuffer.size());
1861 31894 : if (m_nRowBlobLength > m_nHeaderBufferMaxSize)
1862 : {
1863 5305 : m_nHeaderBufferMaxSize = m_nRowBlobLength;
1864 : }
1865 31894 : m_nRowBufferMaxSize = std::max(m_nRowBufferMaxSize, m_nRowBlobLength);
1866 31894 : if (nFreeOffset == OFFSET_MINUS_ONE)
1867 : {
1868 29279 : m_nFileSize += sizeof(uint32_t) + m_nRowBlobLength;
1869 : }
1870 :
1871 31894 : m_nTotalRecordCount =
1872 31894 : std::max(m_nTotalRecordCount, static_cast<int64_t>(nObjectID));
1873 31894 : m_nValidRecordCount++;
1874 :
1875 31894 : m_bDirtyHeader = true;
1876 31894 : m_bDirtyTableXHeader = true;
1877 :
1878 31894 : m_bDirtyIndices = true;
1879 :
1880 31894 : return true;
1881 : }
1882 :
1883 : /************************************************************************/
1884 : /* UpdateFeature() */
1885 : /************************************************************************/
1886 :
1887 51 : bool FileGDBTable::UpdateFeature(int64_t nFID,
1888 : const std::vector<OGRField> &asRawFields,
1889 : const OGRGeometry *poGeom)
1890 : {
1891 51 : if (!m_bUpdate)
1892 0 : return false;
1893 :
1894 51 : if (m_bDirtyFieldDescriptors && !WriteFieldDescriptors(m_fpTable))
1895 0 : return false;
1896 :
1897 51 : vsi_l_offset nOffsetInTableX = 0;
1898 : vsi_l_offset nOffsetInTable =
1899 51 : GetOffsetInTableForRow(nFID - 1, &nOffsetInTableX);
1900 51 : if (nOffsetInTable == 0)
1901 0 : return false;
1902 :
1903 : try
1904 : {
1905 51 : if (!EncodeFeature(asRawFields, poGeom, -1))
1906 0 : return false;
1907 : }
1908 0 : catch (const std::exception &e)
1909 : {
1910 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
1911 0 : return false;
1912 : }
1913 :
1914 51 : VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
1915 51 : uint32_t nOldFeatureSize = 0;
1916 51 : if (!ReadUInt32(m_fpTable, nOldFeatureSize))
1917 0 : return false;
1918 :
1919 51 : m_nCurRow = -1;
1920 :
1921 51 : if (m_abyBuffer.size() <= nOldFeatureSize)
1922 : {
1923 : // Can rewrite-in-place
1924 27 : VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
1925 :
1926 27 : if (!WriteUInt32(m_fpTable, static_cast<uint32_t>(m_abyBuffer.size())))
1927 0 : return false;
1928 54 : if (!m_abyBuffer.empty() &&
1929 27 : VSIFWriteL(m_abyBuffer.data(), 1, m_abyBuffer.size(), m_fpTable) !=
1930 27 : m_abyBuffer.size())
1931 : {
1932 0 : return false;
1933 : }
1934 :
1935 27 : m_nRowBlobLength = 0;
1936 27 : const size_t nSizeToBlank = nOldFeatureSize - m_abyBuffer.size();
1937 27 : if (nSizeToBlank > 0)
1938 : {
1939 : // Blank unused areas of the old feature
1940 14 : m_abyBuffer.clear();
1941 : try
1942 : {
1943 14 : m_abyBuffer.resize(nSizeToBlank);
1944 14 : CPL_IGNORE_RET_VAL(VSIFWriteL(m_abyBuffer.data(), 1,
1945 : m_abyBuffer.size(), m_fpTable));
1946 : }
1947 0 : catch (const std::exception &e)
1948 : {
1949 0 : CPLDebug("OpenFileGDB",
1950 : "Could not blank no longer part of feature: %s",
1951 0 : e.what());
1952 : }
1953 : }
1954 : }
1955 : else
1956 : {
1957 : // Updated feature is larger than older one: check if there's a chunk
1958 : // we can reuse by examining the .freelist, and if not, append at end
1959 : // of .gdbtable
1960 24 : const uint64_t nFreeOffset = GetOffsetOfFreeAreaFromFreeList(
1961 24 : static_cast<uint32_t>(sizeof(uint32_t) + m_abyBuffer.size()));
1962 :
1963 24 : if (nFreeOffset == OFFSET_MINUS_ONE)
1964 : {
1965 24 : if (((m_nFileSize + m_abyBuffer.size()) >>
1966 24 : (8 * m_nTablxOffsetSize)) != 0)
1967 : {
1968 0 : CPLError(
1969 : CE_Failure, CPLE_AppDefined,
1970 : "Maximum file size for m_nTablxOffsetSize = %u reached",
1971 : m_nTablxOffsetSize);
1972 0 : return false;
1973 : }
1974 :
1975 24 : VSIFSeekL(m_fpTable, m_nFileSize, SEEK_SET);
1976 : }
1977 : else
1978 : {
1979 0 : VSIFSeekL(m_fpTable, nFreeOffset, SEEK_SET);
1980 : }
1981 :
1982 24 : if (!WriteUInt32(m_fpTable, static_cast<uint32_t>(m_abyBuffer.size())))
1983 0 : return false;
1984 48 : if (!m_abyBuffer.empty() &&
1985 24 : VSIFWriteL(m_abyBuffer.data(), 1, m_abyBuffer.size(), m_fpTable) !=
1986 24 : m_abyBuffer.size())
1987 : {
1988 0 : return false;
1989 : }
1990 :
1991 : // Update offset of feature in .gdbtablx
1992 24 : VSIFSeekL(m_fpTableX, nOffsetInTableX, SEEK_SET);
1993 24 : if (!WriteFeatureOffset(nFreeOffset == OFFSET_MINUS_ONE ? m_nFileSize
1994 : : nFreeOffset))
1995 0 : return false;
1996 :
1997 24 : m_nRowBlobLength = static_cast<uint32_t>(m_abyBuffer.size());
1998 24 : if (m_nRowBlobLength > m_nHeaderBufferMaxSize)
1999 : {
2000 18 : m_bDirtyHeader = true;
2001 18 : m_nHeaderBufferMaxSize = m_nRowBlobLength;
2002 : }
2003 24 : m_nRowBufferMaxSize = std::max(m_nRowBufferMaxSize, m_nRowBlobLength);
2004 24 : if (nFreeOffset == OFFSET_MINUS_ONE)
2005 : {
2006 24 : m_bDirtyHeader = true;
2007 24 : m_nFileSize += sizeof(uint32_t) + m_nRowBlobLength;
2008 : }
2009 :
2010 24 : AddEntryToFreelist(nOffsetInTable, sizeof(uint32_t) + nOldFeatureSize);
2011 :
2012 : // Blank previously used area
2013 24 : VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
2014 24 : const uint32_t nNegatedOldFeatureSize =
2015 24 : static_cast<uint32_t>(-static_cast<int>(nOldFeatureSize));
2016 24 : if (!WriteUInt32(m_fpTable, nNegatedOldFeatureSize))
2017 0 : return false;
2018 24 : m_abyBuffer.clear();
2019 : try
2020 : {
2021 24 : m_abyBuffer.resize(nOldFeatureSize);
2022 24 : CPL_IGNORE_RET_VAL(VSIFWriteL(m_abyBuffer.data(), 1,
2023 : m_abyBuffer.size(), m_fpTable));
2024 : }
2025 0 : catch (const std::exception &e)
2026 : {
2027 0 : CPLDebug("OpenFileGDB", "Could not blank old feature: %s",
2028 0 : e.what());
2029 : }
2030 : }
2031 :
2032 51 : m_bDirtyIndices = true;
2033 :
2034 51 : return true;
2035 : }
2036 :
2037 : /************************************************************************/
2038 : /* DeleteFeature() */
2039 : /************************************************************************/
2040 :
2041 2646 : bool FileGDBTable::DeleteFeature(int64_t nFID)
2042 : {
2043 2646 : if (!m_bUpdate)
2044 0 : return false;
2045 :
2046 2646 : if (m_bDirtyFieldDescriptors && !WriteFieldDescriptors(m_fpTable))
2047 0 : return false;
2048 :
2049 2646 : vsi_l_offset nOffsetInTableX = 0;
2050 : vsi_l_offset nOffsetInTable =
2051 2646 : GetOffsetInTableForRow(nFID - 1, &nOffsetInTableX);
2052 2646 : if (nOffsetInTable == 0)
2053 0 : return false;
2054 :
2055 : // Set 0 as offset for the feature in .gdbtablx
2056 2646 : VSIFSeekL(m_fpTableX, nOffsetInTableX, SEEK_SET);
2057 2646 : if (!WriteFeatureOffset(0))
2058 0 : return false;
2059 :
2060 : // Negate the size of the feature in .gdbtable
2061 2646 : VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
2062 2646 : uint32_t nFeatureSize = 0;
2063 2646 : if (!ReadUInt32(m_fpTable, nFeatureSize))
2064 0 : return false;
2065 2646 : if (nFeatureSize > static_cast<uint32_t>(INT_MAX))
2066 0 : return false;
2067 2646 : const int nDeletedFeatureSize =
2068 2646 : static_cast<uint32_t>(-static_cast<int32_t>(nFeatureSize));
2069 2646 : VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
2070 2646 : if (!WriteUInt32(m_fpTable, nDeletedFeatureSize))
2071 0 : return false;
2072 :
2073 2646 : AddEntryToFreelist(nOffsetInTable, sizeof(uint32_t) + nFeatureSize);
2074 :
2075 : // Blank feature content
2076 2646 : m_nCurRow = -1;
2077 2646 : m_abyBuffer.clear();
2078 : try
2079 : {
2080 2646 : m_abyBuffer.resize(nFeatureSize);
2081 2646 : CPL_IGNORE_RET_VAL(
2082 2646 : VSIFWriteL(m_abyBuffer.data(), 1, m_abyBuffer.size(), m_fpTable));
2083 : }
2084 0 : catch (const std::exception &e)
2085 : {
2086 0 : CPLDebug("OpenFileGDB", "Could not blank deleted feature: %s",
2087 0 : e.what());
2088 : }
2089 :
2090 2646 : m_nValidRecordCount--;
2091 2646 : m_bDirtyHeader = true;
2092 :
2093 2646 : m_bDirtyIndices = true;
2094 :
2095 2646 : return true;
2096 : }
2097 :
2098 : /************************************************************************/
2099 : /* WholeFileRewriter::~WholeFileRewriter() */
2100 : /************************************************************************/
2101 :
2102 45 : FileGDBTable::WholeFileRewriter::~WholeFileRewriter()
2103 : {
2104 45 : if (m_bIsInit)
2105 6 : Rollback();
2106 45 : }
2107 :
2108 : /************************************************************************/
2109 : /* WholeFileRewriter::Begin() */
2110 : /************************************************************************/
2111 :
2112 45 : bool FileGDBTable::WholeFileRewriter::Begin()
2113 : {
2114 45 : m_bOldDirtyIndices = m_oTable.m_bDirtyIndices;
2115 45 : m_oTable.RemoveIndices();
2116 45 : m_oTable.m_bDirtyIndices = false;
2117 45 : if (!m_oTable.Sync())
2118 0 : return false;
2119 :
2120 : // On Windows, we might have issues renaming opened files, even if trying
2121 : // to close them before, so updating opened files is less risky.
2122 45 : m_bModifyInPlace =
2123 45 : CPLTestBool(CPLGetConfigOption("OPENFILEGDB_MODIFY_IN_PLACE",
2124 : #ifdef _WIN32
2125 : "YES"
2126 : #else
2127 : "NO"
2128 : #endif
2129 : ));
2130 :
2131 135 : m_osGdbTablx = CPLFormFilenameSafe(
2132 90 : CPLGetPathSafe(m_oTable.m_osFilename.c_str()).c_str(),
2133 135 : CPLGetBasenameSafe(m_oTable.m_osFilename.c_str()).c_str(), "gdbtablx");
2134 :
2135 90 : m_osBackupGdbTable = CPLResetExtensionSafe(m_oTable.m_osFilename.c_str(),
2136 45 : "_backup.gdbtable");
2137 : VSIStatBufL sStat;
2138 45 : if (VSIStatL(m_osBackupGdbTable.c_str(), &sStat) == 0)
2139 : {
2140 0 : CPLError(CE_Failure, CPLE_AppDefined,
2141 : "Cannot create backup file %s as it already exists",
2142 : m_osBackupGdbTable.c_str());
2143 0 : return false;
2144 : }
2145 :
2146 : m_osBackupGdbTablx =
2147 45 : CPLResetExtensionSafe(m_osGdbTablx.c_str(), "_backup.gdbtablx");
2148 :
2149 45 : if (m_bModifyInPlace)
2150 : {
2151 : // Create backups of .gdtable and .gdtablx if something wrongs happen
2152 14 : if (CPLCopyFile(m_osBackupGdbTable.c_str(),
2153 28 : m_oTable.m_osFilename.c_str()) != 0)
2154 : {
2155 0 : VSIUnlink(m_osBackupGdbTable.c_str());
2156 0 : m_osBackupGdbTable.clear();
2157 0 : return false;
2158 : }
2159 :
2160 14 : if (CPLCopyFile(m_osBackupGdbTablx.c_str(), m_osGdbTablx.c_str()) != 0)
2161 : {
2162 0 : VSIUnlink(m_osBackupGdbTable.c_str());
2163 0 : VSIUnlink(m_osBackupGdbTablx.c_str());
2164 0 : m_osBackupGdbTable.clear();
2165 0 : m_osBackupGdbTablx.clear();
2166 0 : return false;
2167 : }
2168 :
2169 14 : m_osBackupValidFilename = m_oTable.m_osFilename + ".backup_valid";
2170 14 : VSILFILE *fp = VSIFOpenL(m_osBackupValidFilename.c_str(), "wb");
2171 14 : if (fp != nullptr)
2172 14 : VSIFCloseL(fp);
2173 :
2174 14 : m_fpOldGdbtable = VSIFOpenL(m_osBackupGdbTable.c_str(), "rb");
2175 14 : if (m_fpOldGdbtable == nullptr)
2176 : {
2177 0 : VSIUnlink(m_osBackupValidFilename.c_str());
2178 0 : VSIUnlink(m_osBackupGdbTable.c_str());
2179 0 : VSIUnlink(m_osBackupGdbTablx.c_str());
2180 0 : m_osBackupValidFilename.clear();
2181 0 : m_osBackupGdbTable.clear();
2182 0 : m_osBackupGdbTablx.clear();
2183 0 : return false;
2184 : }
2185 :
2186 14 : m_fpOldGdbtablx = m_oTable.m_fpTableX;
2187 14 : m_fpTable = m_oTable.m_fpTable;
2188 14 : m_fpTableX = m_oTable.m_fpTableX;
2189 : }
2190 : else
2191 : {
2192 62 : m_osTmpGdbTable = CPLResetExtensionSafe(m_oTable.m_osFilename.c_str(),
2193 31 : "_compress.gdbtable");
2194 : m_osTmpGdbTablx =
2195 31 : CPLResetExtensionSafe(m_osGdbTablx.c_str(), "_compress.gdbtablx");
2196 :
2197 31 : m_fpOldGdbtable = m_oTable.m_fpTable;
2198 31 : m_fpOldGdbtablx = m_oTable.m_fpTableX;
2199 :
2200 31 : m_fpTable = VSIFOpenL(m_osTmpGdbTable.c_str(), "wb+");
2201 31 : if (m_fpTable == nullptr)
2202 : {
2203 0 : return false;
2204 : }
2205 :
2206 31 : m_fpTableX = VSIFOpenL(m_osTmpGdbTablx.c_str(), "wb+");
2207 31 : if (m_fpTableX == nullptr)
2208 : {
2209 0 : VSIFCloseL(m_fpTable);
2210 0 : m_fpTable = nullptr;
2211 0 : VSIUnlink(m_osTmpGdbTable.c_str());
2212 0 : return false;
2213 : }
2214 :
2215 31 : if (!m_oTable.WriteHeaderX(m_fpTableX))
2216 : {
2217 0 : VSIFCloseL(m_fpTable);
2218 0 : m_fpTable = nullptr;
2219 0 : VSIFCloseL(m_fpTableX);
2220 0 : m_fpTableX = nullptr;
2221 0 : VSIUnlink(m_osTmpGdbTable.c_str());
2222 0 : VSIUnlink(m_osTmpGdbTablx.c_str());
2223 0 : m_osTmpGdbTable.clear();
2224 0 : m_osTmpGdbTablx.clear();
2225 0 : return false;
2226 : }
2227 : }
2228 :
2229 45 : m_nOldFileSize = m_oTable.m_nFileSize;
2230 45 : m_nOldOffsetFieldDesc = m_oTable.m_nOffsetFieldDesc;
2231 45 : m_nOldFieldDescLength = m_oTable.m_nFieldDescLength;
2232 45 : m_bIsInit = true;
2233 :
2234 45 : if (!m_oTable.WriteHeader(m_fpTable))
2235 : {
2236 0 : Rollback();
2237 0 : return false;
2238 : }
2239 45 : if (m_bModifyInPlace)
2240 : {
2241 14 : VSIFTruncateL(m_fpTable, m_oTable.m_nFileSize);
2242 : }
2243 :
2244 : // Rewrite field descriptors
2245 45 : if (!m_oTable.Sync(m_fpTable, m_fpTableX))
2246 : {
2247 0 : Rollback();
2248 0 : return false;
2249 : }
2250 :
2251 45 : VSIFSeekL(m_fpTable, m_oTable.m_nFileSize, SEEK_SET);
2252 :
2253 45 : return true;
2254 : }
2255 :
2256 : /************************************************************************/
2257 : /* WholeFileRewriter::Commit() */
2258 : /************************************************************************/
2259 :
2260 39 : bool FileGDBTable::WholeFileRewriter::Commit()
2261 : {
2262 39 : m_oTable.m_bDirtyTableXTrailer = true;
2263 39 : m_oTable.m_bDirtyHeader = true;
2264 39 : if (!m_oTable.Sync(m_fpTable, m_fpTableX))
2265 : {
2266 0 : Rollback();
2267 0 : return false;
2268 : }
2269 :
2270 39 : if (m_bModifyInPlace)
2271 : {
2272 12 : VSIFCloseL(m_fpOldGdbtable);
2273 12 : VSIUnlink(m_osBackupValidFilename.c_str());
2274 12 : VSIUnlink(m_osBackupGdbTable.c_str());
2275 12 : VSIUnlink(m_osBackupGdbTablx.c_str());
2276 : }
2277 : else
2278 : {
2279 27 : VSIFCloseL(m_oTable.m_fpTable);
2280 27 : VSIFCloseL(m_oTable.m_fpTableX);
2281 27 : m_oTable.m_fpTable = nullptr;
2282 27 : m_oTable.m_fpTableX = nullptr;
2283 :
2284 : const bool bUseWIN32CodePath =
2285 27 : CPLTestBool(CPLGetConfigOption("OPENFILEGDB_SIMUL_WIN32",
2286 : #ifdef _WIN32
2287 : "YES"
2288 : #else
2289 : "NO"
2290 : #endif
2291 : ));
2292 :
2293 27 : if (bUseWIN32CodePath)
2294 : {
2295 : // Renaming over an open file doesn't work on Windows
2296 12 : VSIFCloseL(m_fpTable);
2297 12 : VSIFCloseL(m_fpTableX);
2298 12 : m_fpTable = nullptr;
2299 12 : m_fpTableX = nullptr;
2300 :
2301 : // _wrename() on Windows doesn't honour POSIX semantics and forbids
2302 : // renaming over an existing file, hence create a temporary backup
2303 12 : if (VSIRename(m_oTable.m_osFilename.c_str(),
2304 12 : m_osBackupGdbTable.c_str()) != 0)
2305 : {
2306 0 : m_oTable.m_fpTable =
2307 0 : VSIFOpenL(m_oTable.m_osFilename.c_str(), "rb+");
2308 0 : m_oTable.m_fpTableX = VSIFOpenL(m_osGdbTablx.c_str(), "rb+");
2309 0 : Rollback();
2310 0 : return false;
2311 : }
2312 :
2313 12 : if (VSIRename(m_osGdbTablx.c_str(), m_osBackupGdbTablx.c_str()) !=
2314 : 0)
2315 : {
2316 0 : CPLError(CE_Failure, CPLE_FileIO,
2317 : "Renaming of %s onto %s failed, but renaming of "
2318 : "%s onto %s succeeded. Dataset in corrupt state",
2319 : m_osGdbTablx.c_str(), m_osBackupGdbTablx.c_str(),
2320 0 : m_oTable.m_osFilename.c_str(),
2321 : m_osBackupGdbTable.c_str());
2322 0 : Rollback();
2323 0 : return false;
2324 : }
2325 : }
2326 : else
2327 : {
2328 15 : m_oTable.m_fpTable = m_fpTable;
2329 15 : m_oTable.m_fpTableX = m_fpTableX;
2330 : }
2331 :
2332 27 : if (VSIRename(m_osTmpGdbTable.c_str(), m_oTable.m_osFilename.c_str()) !=
2333 : 0)
2334 : {
2335 0 : CPLError(CE_Failure, CPLE_FileIO, "Renaming of %s onto %s failed",
2336 0 : m_osTmpGdbTable.c_str(), m_oTable.m_osFilename.c_str());
2337 0 : Rollback();
2338 0 : return false;
2339 : }
2340 :
2341 27 : if (VSIRename(m_osTmpGdbTablx.c_str(), m_osGdbTablx.c_str()) != 0)
2342 : {
2343 0 : CPLError(CE_Failure, CPLE_FileIO, "Renaming of %s onto %s failed",
2344 : m_osTmpGdbTablx.c_str(), m_osGdbTablx.c_str());
2345 0 : Rollback();
2346 0 : return false;
2347 : }
2348 :
2349 27 : if (bUseWIN32CodePath)
2350 : {
2351 24 : m_oTable.m_fpTable =
2352 12 : VSIFOpenL(m_oTable.m_osFilename.c_str(), "rb+");
2353 12 : m_oTable.m_fpTableX = VSIFOpenL(m_osGdbTablx.c_str(), "rb+");
2354 12 : VSIUnlink(m_osBackupGdbTable.c_str());
2355 12 : VSIUnlink(m_osBackupGdbTablx.c_str());
2356 : }
2357 : }
2358 :
2359 39 : m_oTable.DeleteFreeList();
2360 39 : if (m_bOldDirtyIndices)
2361 : {
2362 8 : m_oTable.m_bDirtyIndices = true;
2363 8 : m_oTable.Sync();
2364 : }
2365 :
2366 39 : m_bIsInit = false;
2367 :
2368 39 : return true;
2369 : }
2370 :
2371 : /************************************************************************/
2372 : /* WholeFileRewriter::Rollback() */
2373 : /************************************************************************/
2374 :
2375 6 : void FileGDBTable::WholeFileRewriter::Rollback()
2376 : {
2377 6 : CPLAssert(m_bIsInit);
2378 6 : m_bIsInit = false;
2379 :
2380 6 : if (m_bModifyInPlace)
2381 : {
2382 2 : VSIFCloseL(m_fpOldGdbtable);
2383 2 : m_fpOldGdbtable = nullptr;
2384 :
2385 : // Try to restore from backup files in case of failure
2386 2 : if (CPLCopyFile(m_oTable.m_osFilename.c_str(),
2387 4 : m_osBackupGdbTable.c_str()) == 0 &&
2388 2 : CPLCopyFile(m_osGdbTablx.c_str(), m_osBackupGdbTablx.c_str()) == 0)
2389 : {
2390 2 : VSIUnlink(m_osBackupValidFilename.c_str());
2391 2 : VSIUnlink(m_osBackupGdbTable.c_str());
2392 2 : VSIUnlink(m_osBackupGdbTablx.c_str());
2393 : }
2394 : else
2395 : {
2396 0 : CPLError(CE_Failure, CPLE_AppDefined,
2397 : "%s and %s are corrupted, and couldn't be restored from "
2398 : "their backups %s and %s. You'll have to manually replace "
2399 : "the former files by the latter ones.",
2400 0 : m_oTable.m_osFilename.c_str(), m_osGdbTablx.c_str(),
2401 : m_osBackupGdbTable.c_str(), m_osBackupGdbTablx.c_str());
2402 : }
2403 : }
2404 : else
2405 : {
2406 4 : VSIFCloseL(m_fpTable);
2407 4 : VSIFCloseL(m_fpTableX);
2408 4 : m_fpTable = nullptr;
2409 4 : m_fpTableX = nullptr;
2410 4 : VSIUnlink(m_osTmpGdbTable.c_str());
2411 4 : VSIUnlink(m_osTmpGdbTablx.c_str());
2412 : }
2413 :
2414 6 : m_oTable.m_nFileSize = m_nOldFileSize;
2415 6 : m_oTable.m_nOffsetFieldDesc = m_nOldOffsetFieldDesc;
2416 6 : m_oTable.m_nFieldDescLength = m_nOldFieldDescLength;
2417 :
2418 6 : m_oTable.m_bDirtyFieldDescriptors = false;
2419 6 : m_oTable.m_bDirtyTableXHeader = false;
2420 6 : m_oTable.m_bDirtyTableXTrailer = false;
2421 6 : m_oTable.m_bDirtyHeader = false;
2422 6 : }
2423 :
2424 : /************************************************************************/
2425 : /* Repack() */
2426 : /************************************************************************/
2427 :
2428 4 : bool FileGDBTable::Repack()
2429 : {
2430 4 : if (!m_bUpdate || !Sync())
2431 0 : return false;
2432 :
2433 4 : bool bRepackNeeded = false;
2434 4 : if (m_nOffsetFieldDesc > 40)
2435 : {
2436 : // If the field descriptor section is not at offset 40, it is possible
2437 : // that there's our "ghost area" there.
2438 4 : GByte abyBuffer[8] = {0};
2439 4 : VSIFSeekL(m_fpTable, 40, SEEK_SET);
2440 4 : VSIFReadL(abyBuffer, 1, sizeof(abyBuffer), m_fpTable);
2441 8 : if (!(memcmp(abyBuffer + 4, "GDAL", 4) == 0 &&
2442 4 : static_cast<uint64_t>(40) + sizeof(uint32_t) +
2443 4 : GetUInt32(abyBuffer, 0) ==
2444 4 : m_nOffsetFieldDesc))
2445 : {
2446 0 : CPLDebug("OpenFileGDB",
2447 : "Repack(%s): field descriptors not at beginning of file",
2448 : m_osFilename.c_str());
2449 0 : bRepackNeeded = true;
2450 : }
2451 : }
2452 :
2453 4 : uint64_t nExpectedOffset =
2454 4 : m_nOffsetFieldDesc + sizeof(uint32_t) + m_nFieldDescLength;
2455 :
2456 8 : std::vector<GByte> abyBufferOffsets;
2457 4 : abyBufferOffsets.resize(TABLX_FEATURES_PER_PAGE * m_nTablxOffsetSize);
2458 :
2459 : // Scan all features
2460 8 : for (uint32_t iPage = 0; !bRepackNeeded && iPage < m_n1024BlocksPresent;
2461 : ++iPage)
2462 : {
2463 4 : const vsi_l_offset nOffsetInTableX =
2464 4 : TABLX_HEADER_SIZE + m_nTablxOffsetSize *
2465 4 : static_cast<vsi_l_offset>(iPage) *
2466 : TABLX_FEATURES_PER_PAGE;
2467 4 : VSIFSeekL(m_fpTableX, nOffsetInTableX, SEEK_SET);
2468 4 : if (VSIFReadL(abyBufferOffsets.data(),
2469 4 : m_nTablxOffsetSize * TABLX_FEATURES_PER_PAGE, 1,
2470 4 : m_fpTableX) != 1)
2471 0 : return false;
2472 :
2473 4 : GByte *pabyBufferOffsets = abyBufferOffsets.data();
2474 3077 : for (int i = 0; i < TABLX_FEATURES_PER_PAGE;
2475 3073 : i++, pabyBufferOffsets += m_nTablxOffsetSize)
2476 : {
2477 3074 : const uint64_t nOffset = ReadFeatureOffset(pabyBufferOffsets);
2478 3074 : if (nOffset != 0)
2479 : {
2480 7 : if (!bRepackNeeded && nOffset != nExpectedOffset)
2481 : {
2482 1 : bRepackNeeded = true;
2483 1 : CPLDebug("OpenFileGDB",
2484 : "Repack(%s): feature at offset " CPL_FRMT_GUIB
2485 : " instead of " CPL_FRMT_GUIB ". Repack needed",
2486 : m_osFilename.c_str(),
2487 : static_cast<GUIntBig>(nOffset),
2488 : static_cast<GUIntBig>(nExpectedOffset));
2489 1 : break;
2490 : }
2491 :
2492 : // Read feature size
2493 6 : VSIFSeekL(m_fpTable, nOffset, SEEK_SET);
2494 6 : uint32_t nFeatureSize = 0;
2495 6 : if (!ReadUInt32(m_fpTable, nFeatureSize))
2496 0 : return false;
2497 :
2498 6 : nExpectedOffset += sizeof(uint32_t);
2499 6 : nExpectedOffset += nFeatureSize;
2500 : }
2501 : }
2502 : }
2503 :
2504 4 : if (!bRepackNeeded)
2505 : {
2506 3 : if (m_nFileSize > nExpectedOffset)
2507 : {
2508 1 : CPLDebug("OpenFileGDB",
2509 : "Deleted features at end of file. Truncating it");
2510 :
2511 1 : m_nFileSize = nExpectedOffset;
2512 1 : VSIFTruncateL(m_fpTable, m_nFileSize);
2513 1 : m_bDirtyHeader = true;
2514 :
2515 1 : DeleteFreeList();
2516 :
2517 1 : return Sync();
2518 : }
2519 :
2520 2 : CPLDebug("OpenFileGDB", "Repack(%s): file already compacted",
2521 : m_osFilename.c_str());
2522 2 : return true;
2523 : }
2524 :
2525 2 : WholeFileRewriter oWholeFileRewriter(*this);
2526 1 : if (!oWholeFileRewriter.Begin())
2527 0 : return false;
2528 :
2529 1 : uint32_t nRowBufferMaxSize = 0;
2530 1 : m_nCurRow = -1;
2531 :
2532 : // Rewrite all features
2533 2 : for (uint32_t iPage = 0; iPage < m_n1024BlocksPresent; ++iPage)
2534 : {
2535 1 : const vsi_l_offset nOffsetInTableX =
2536 1 : TABLX_HEADER_SIZE + m_nTablxOffsetSize *
2537 1 : static_cast<vsi_l_offset>(iPage) *
2538 : TABLX_FEATURES_PER_PAGE;
2539 1 : VSIFSeekL(oWholeFileRewriter.m_fpOldGdbtablx, nOffsetInTableX,
2540 : SEEK_SET);
2541 1 : if (VSIFReadL(abyBufferOffsets.data(),
2542 1 : m_nTablxOffsetSize * TABLX_FEATURES_PER_PAGE, 1,
2543 1 : oWholeFileRewriter.m_fpOldGdbtablx) != 1)
2544 0 : return false;
2545 :
2546 1 : GByte *pabyBufferOffsets = abyBufferOffsets.data();
2547 1025 : for (int i = 0; i < TABLX_FEATURES_PER_PAGE;
2548 1024 : i++, pabyBufferOffsets += m_nTablxOffsetSize)
2549 : {
2550 1024 : const uint64_t nOffset = ReadFeatureOffset(pabyBufferOffsets);
2551 1024 : if (nOffset != 0)
2552 : {
2553 : // Read feature size
2554 1 : VSIFSeekL(oWholeFileRewriter.m_fpOldGdbtable, nOffset,
2555 : SEEK_SET);
2556 1 : uint32_t nFeatureSize = 0;
2557 1 : if (!ReadUInt32(oWholeFileRewriter.m_fpOldGdbtable,
2558 : nFeatureSize))
2559 0 : return false;
2560 :
2561 : // Read feature data
2562 1 : if (nFeatureSize > m_abyBuffer.size())
2563 : {
2564 : try
2565 : {
2566 0 : m_abyBuffer.resize(nFeatureSize);
2567 : }
2568 0 : catch (const std::exception &e)
2569 : {
2570 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
2571 0 : return false;
2572 : }
2573 : }
2574 1 : if (VSIFReadL(m_abyBuffer.data(), nFeatureSize, 1,
2575 1 : oWholeFileRewriter.m_fpOldGdbtable) != 1)
2576 0 : return false;
2577 :
2578 : // Update offset of updated feature
2579 1 : WriteFeatureOffset(m_nFileSize, pabyBufferOffsets);
2580 :
2581 : // Write feature size
2582 1 : if (!WriteUInt32(oWholeFileRewriter.m_fpTable, nFeatureSize))
2583 0 : return false;
2584 1 : if (VSIFWriteL(m_abyBuffer.data(), nFeatureSize, 1,
2585 1 : oWholeFileRewriter.m_fpTable) != 1)
2586 0 : return false;
2587 :
2588 1 : if (nFeatureSize > nRowBufferMaxSize)
2589 1 : nRowBufferMaxSize = nFeatureSize;
2590 1 : m_nFileSize += sizeof(uint32_t) + nFeatureSize;
2591 : }
2592 : }
2593 1 : VSIFSeekL(oWholeFileRewriter.m_fpTableX, nOffsetInTableX, SEEK_SET);
2594 1 : if (VSIFWriteL(abyBufferOffsets.data(),
2595 1 : m_nTablxOffsetSize * TABLX_FEATURES_PER_PAGE, 1,
2596 1 : oWholeFileRewriter.m_fpTableX) != 1)
2597 0 : return false;
2598 : }
2599 :
2600 1 : m_nRowBufferMaxSize = nRowBufferMaxSize;
2601 1 : m_nHeaderBufferMaxSize = std::max(m_nFieldDescLength, m_nRowBufferMaxSize);
2602 :
2603 1 : return oWholeFileRewriter.Commit();
2604 : }
2605 :
2606 : /************************************************************************/
2607 : /* RecomputeExtent() */
2608 : /************************************************************************/
2609 :
2610 2 : void FileGDBTable::RecomputeExtent()
2611 : {
2612 2 : if (!m_bUpdate || m_iGeomField < 0)
2613 0 : return;
2614 :
2615 : // Scan all features
2616 2 : OGREnvelope sLayerEnvelope;
2617 2 : OGREnvelope sFeatureEnvelope;
2618 6 : for (int64_t iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat)
2619 : {
2620 4 : iCurFeat = GetAndSelectNextNonEmptyRow(iCurFeat);
2621 4 : if (iCurFeat < 0)
2622 0 : break;
2623 4 : const auto psGeomField = GetFieldValue(m_iGeomField);
2624 4 : if (psGeomField && GetFeatureExtent(psGeomField, &sFeatureEnvelope))
2625 : {
2626 2 : sLayerEnvelope.Merge(sFeatureEnvelope);
2627 : }
2628 : }
2629 :
2630 2 : m_bDirtyGeomFieldBBox = true;
2631 : auto poGeomField =
2632 2 : cpl::down_cast<FileGDBGeomField *>(m_apoFields[m_iGeomField].get());
2633 2 : if (sLayerEnvelope.IsInit())
2634 : {
2635 1 : poGeomField->SetXYMinMax(sLayerEnvelope.MinX, sLayerEnvelope.MinY,
2636 : sLayerEnvelope.MaxX, sLayerEnvelope.MaxY);
2637 : }
2638 : else
2639 : {
2640 1 : poGeomField->SetXYMinMax(
2641 : FileGDBGeomField::ESRI_NAN, FileGDBGeomField::ESRI_NAN,
2642 : FileGDBGeomField::ESRI_NAN, FileGDBGeomField::ESRI_NAN);
2643 : }
2644 : }
2645 :
2646 : } /* namespace OpenFileGDB */
|