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