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 1989 : bool FileGDBTable::Create(const char *pszFilename, int nTablxOffsetSize,
58 : FileGDBTableGeometryType eTableGeomType,
59 : bool bGeomTypeHasZ, bool bGeomTypeHasM)
60 : {
61 1989 : CPLAssert(m_fpTable == nullptr);
62 :
63 1989 : m_eGDBTableVersion = GDBTableVersion::V3;
64 1989 : m_bUpdate = true;
65 1989 : m_eTableGeomType = eTableGeomType;
66 1989 : m_nTablxOffsetSize = nTablxOffsetSize;
67 1989 : m_bGeomTypeHasZ = bGeomTypeHasZ;
68 1989 : m_bGeomTypeHasM = bGeomTypeHasM;
69 1989 : m_bHasReadGDBIndexes = TRUE;
70 :
71 1989 : 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 1989 : m_osFilename = pszFilename;
79 1989 : m_osFilenameWithLayerName = m_osFilename;
80 1989 : m_fpTable = VSIFOpenL(pszFilename, "wb+");
81 1989 : 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 3978 : CPLResetExtensionSafe(pszFilename, "gdbtablx");
90 1989 : m_fpTableX = VSIFOpenL(osTableXName.c_str(), "wb+");
91 1989 : 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 1989 : if (!WriteHeader(m_fpTable))
99 0 : return false;
100 :
101 1989 : if (!WriteHeaderX(m_fpTableX))
102 0 : return false;
103 :
104 1989 : m_bDirtyTableXTrailer = true;
105 :
106 1989 : 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 2034 : bool FileGDBTable::WriteHeader(VSILFILE *fpTable)
131 : {
132 : // Could be useful in case we get something wrong...
133 : const char *pszCreator =
134 2034 : CPLGetConfigOption("OPENFILEGDB_CREATOR", "GDAL " GDAL_RELEASE_NAME);
135 :
136 2034 : m_nFileSize = 0;
137 2034 : m_bDirtyHeader = true;
138 2034 : m_bDirtyFieldDescriptors = true;
139 2034 : m_nOffsetFieldDesc = 0;
140 2034 : m_nFieldDescLength = 0;
141 :
142 2034 : VSIFSeekL(fpTable, 0, SEEK_SET);
143 :
144 : bool bRet =
145 2034 : WriteUInt32(fpTable, 3) && // version number
146 : // number of valid rows
147 2034 : WriteUInt32(fpTable, static_cast<uint32_t>(m_nValidRecordCount)) &&
148 2034 : WriteUInt32(fpTable,
149 2034 : m_nHeaderBufferMaxSize) && // largest size of a feature
150 : // record / field description
151 2034 : WriteUInt32(fpTable, 5) && // magic value
152 2034 : WriteUInt32(fpTable, 0) && // magic value
153 2034 : WriteUInt32(fpTable, 0) && // magic value
154 6102 : WriteUInt64(fpTable, m_nFileSize) &&
155 2034 : WriteUInt64(fpTable, m_nOffsetFieldDesc);
156 :
157 2034 : 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 2034 : bRet =
162 4068 : WriteUInt32(fpTable, static_cast<uint32_t>(strlen(pszCreator))) &&
163 2034 : VSIFWriteL(pszCreator, strlen(pszCreator), 1, fpTable) == 1;
164 : }
165 :
166 2034 : if (!bRet)
167 : {
168 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot write .gdbtable header");
169 0 : return false;
170 : }
171 :
172 2034 : m_nFileSize = VSIFTellL(fpTable);
173 2034 : return true;
174 : }
175 :
176 : /************************************************************************/
177 : /* WriteHeaderX() */
178 : /************************************************************************/
179 :
180 2020 : bool FileGDBTable::WriteHeaderX(VSILFILE *fpTableX)
181 : {
182 2020 : VSIFSeekL(fpTableX, 0, SEEK_SET);
183 2020 : if (!WriteUInt32(fpTableX, 3) || // version number
184 2020 : !WriteUInt32(fpTableX, static_cast<uint32_t>(m_n1024BlocksPresent)) ||
185 6060 : !WriteUInt32(fpTableX, static_cast<uint32_t>(m_nTotalRecordCount)) ||
186 2020 : !WriteUInt32(fpTableX, m_nTablxOffsetSize))
187 : {
188 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot write .gdbtablx header");
189 0 : return false;
190 : }
191 2020 : return true;
192 : }
193 :
194 : /************************************************************************/
195 : /* Sync() */
196 : /************************************************************************/
197 :
198 12431 : bool FileGDBTable::Sync(VSILFILE *fpTable, VSILFILE *fpTableX)
199 : {
200 12431 : if (!m_bUpdate)
201 4338 : return true;
202 :
203 8093 : if (fpTable == nullptr)
204 8009 : fpTable = m_fpTable;
205 :
206 8093 : if (fpTableX == nullptr)
207 8009 : fpTableX = m_fpTableX;
208 :
209 8093 : bool bRet = true;
210 :
211 8093 : if (m_bDirtyGdbIndexesFile)
212 : {
213 326 : m_bDirtyGdbIndexesFile = false;
214 326 : CreateGdbIndexesFile();
215 : }
216 :
217 8093 : if (m_bDirtyIndices)
218 : {
219 3006 : m_bDirtyIndices = false;
220 3006 : RefreshIndices();
221 : }
222 :
223 8093 : if (m_bDirtyFieldDescriptors && fpTable)
224 674 : bRet = WriteFieldDescriptors(fpTable);
225 :
226 8093 : if (m_bDirtyGeomFieldBBox && fpTable)
227 : {
228 159 : VSIFSeekL(fpTable, m_nOffsetFieldDesc + m_nGeomFieldBBoxSubOffset,
229 : SEEK_SET);
230 159 : const auto poGeomField = cpl::down_cast<const FileGDBGeomField *>(
231 159 : m_apoFields[m_iGeomField].get());
232 159 : bRet &= WriteFloat64(fpTable, poGeomField->GetXMin());
233 159 : bRet &= WriteFloat64(fpTable, poGeomField->GetYMin());
234 159 : bRet &= WriteFloat64(fpTable, poGeomField->GetXMax());
235 159 : bRet &= WriteFloat64(fpTable, poGeomField->GetYMax());
236 159 : if (m_bGeomTypeHasZ)
237 : {
238 50 : bRet &= WriteFloat64(fpTable, poGeomField->GetZMin());
239 50 : bRet &= WriteFloat64(fpTable, poGeomField->GetZMax());
240 : }
241 159 : m_bDirtyGeomFieldBBox = false;
242 : }
243 :
244 8093 : if (m_bDirtyGeomFieldSpatialIndexGridRes && fpTable)
245 : {
246 124 : VSIFSeekL(fpTable,
247 124 : m_nOffsetFieldDesc + m_nGeomFieldSpatialIndexGridResSubOffset,
248 : SEEK_SET);
249 124 : const auto poGeomField = cpl::down_cast<const FileGDBGeomField *>(
250 124 : m_apoFields[m_iGeomField].get());
251 : const auto &adfSpatialIndexGridResolution =
252 124 : poGeomField->GetSpatialIndexGridResolution();
253 248 : for (double dfSize : adfSpatialIndexGridResolution)
254 124 : bRet &= WriteFloat64(fpTable, dfSize);
255 124 : m_bDirtyGeomFieldSpatialIndexGridRes = false;
256 : }
257 :
258 8093 : if (m_bDirtyHeader && fpTable)
259 : {
260 3678 : VSIFSeekL(fpTable, 4, SEEK_SET);
261 3678 : bRet &=
262 3678 : WriteUInt32(fpTable, static_cast<uint32_t>(m_nValidRecordCount));
263 3678 : m_nHeaderBufferMaxSize =
264 7356 : std::max(m_nHeaderBufferMaxSize,
265 3678 : std::max(m_nRowBufferMaxSize, m_nFieldDescLength));
266 3678 : bRet &= WriteUInt32(fpTable, m_nHeaderBufferMaxSize);
267 :
268 3678 : VSIFSeekL(fpTable, 24, SEEK_SET);
269 3678 : bRet &= WriteUInt64(fpTable, m_nFileSize);
270 3678 : bRet &= WriteUInt64(fpTable, m_nOffsetFieldDesc);
271 :
272 3678 : VSIFSeekL(fpTable, 0, SEEK_END);
273 3678 : CPLAssert(VSIFTellL(fpTable) == m_nFileSize);
274 3678 : m_bDirtyHeader = false;
275 : }
276 :
277 8093 : if (m_bDirtyTableXHeader && fpTableX)
278 : {
279 2936 : VSIFSeekL(fpTableX, 4, SEEK_SET);
280 2936 : bRet &=
281 2936 : WriteUInt32(fpTableX, static_cast<uint32_t>(m_n1024BlocksPresent));
282 2936 : bRet &=
283 2936 : WriteUInt32(fpTableX, static_cast<uint32_t>(m_nTotalRecordCount));
284 2936 : m_bDirtyTableXHeader = false;
285 : }
286 :
287 8093 : if (m_bDirtyTableXTrailer && fpTableX)
288 : {
289 2527 : m_nOffsetTableXTrailer =
290 2527 : TABLX_HEADER_SIZE +
291 2527 : m_nTablxOffsetSize * TABLX_FEATURES_PER_PAGE *
292 2527 : static_cast<vsi_l_offset>(m_n1024BlocksPresent);
293 2527 : VSIFSeekL(fpTableX, m_nOffsetTableXTrailer, SEEK_SET);
294 7581 : const uint32_t n1024BlocksTotal = static_cast<uint32_t>(
295 2527 : DIV_ROUND_UP(m_nTotalRecordCount, TABLX_FEATURES_PER_PAGE));
296 2527 : 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 7581 : DIV_ROUND_UP(
304 : DIV_ROUND_UP(static_cast<uint32_t>(m_abyTablXBlockMap.size()),
305 : 4),
306 7581 : 32) *
307 2527 : 32;
308 2527 : m_abyTablXBlockMap.resize(nBitmapInt32Words * 4);
309 2527 : bRet &= WriteUInt32(fpTableX, nBitmapInt32Words);
310 2527 : bRet &= WriteUInt32(fpTableX, n1024BlocksTotal);
311 2527 : bRet &=
312 2527 : WriteUInt32(fpTableX, static_cast<uint32_t>(m_n1024BlocksPresent));
313 2527 : uint32_t nTrailingZero32BitWords = 0;
314 2527 : for (int i = static_cast<int>(m_abyTablXBlockMap.size() / 4) - 1;
315 3426 : 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 2527 : const uint32_t nLeadingNonZero32BitWords =
327 : nBitmapInt32Words - nTrailingZero32BitWords;
328 2527 : bRet &= WriteUInt32(fpTableX, nLeadingNonZero32BitWords);
329 2527 : 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 2527 : m_bDirtyTableXTrailer = false;
348 : }
349 :
350 8093 : if (m_bFreelistCanBeDeleted)
351 : {
352 6 : DeleteFreeList();
353 : }
354 :
355 8093 : if (fpTable)
356 8034 : VSIFFlushL(fpTable);
357 :
358 8093 : if (fpTableX)
359 8033 : VSIFFlushL(fpTableX);
360 :
361 8093 : 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 257 : static bool EncodeEnvelope(std::vector<GByte> &abyBuffer,
384 : const FileGDBGeomField *poGeomField,
385 : const OGRGeometry *poGeom)
386 : {
387 257 : OGREnvelope oEnvelope;
388 257 : poGeom->getEnvelope(&oEnvelope);
389 :
390 : double dfVal;
391 :
392 257 : dfVal = (oEnvelope.MinX - poGeomField->GetXOrigin()) *
393 257 : poGeomField->GetXYScale();
394 257 : CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode X value");
395 257 : WriteVarUInt(abyBuffer, static_cast<uint64_t>(dfVal + 0.5));
396 :
397 257 : dfVal = (oEnvelope.MinY - poGeomField->GetYOrigin()) *
398 257 : poGeomField->GetXYScale();
399 257 : CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode Y value");
400 257 : WriteVarUInt(abyBuffer, static_cast<uint64_t>(dfVal + 0.5));
401 :
402 257 : dfVal = (oEnvelope.MaxX - oEnvelope.MinX) * poGeomField->GetXYScale();
403 257 : CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode X value");
404 257 : WriteVarUInt(abyBuffer, static_cast<uint64_t>(dfVal + 0.5));
405 :
406 257 : dfVal = (oEnvelope.MaxY - oEnvelope.MinY) * poGeomField->GetXYScale();
407 257 : CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode Y value");
408 257 : WriteVarUInt(abyBuffer, static_cast<uint64_t>(dfVal + 0.5));
409 :
410 257 : return true;
411 : }
412 :
413 : /************************************************************************/
414 : /* EncodeGeometry() */
415 : /************************************************************************/
416 :
417 4073 : bool FileGDBTable::EncodeGeometry(const FileGDBGeomField *poGeomField,
418 : const OGRGeometry *poGeom)
419 : {
420 4073 : m_abyGeomBuffer.clear();
421 :
422 4073 : const auto bIs3D = poGeom->Is3D();
423 4073 : const auto bIsMeasured = poGeom->IsMeasured();
424 :
425 : const auto WriteEndOfCurveOrSurface =
426 31838 : [this, bIs3D, bIsMeasured, poGeomField, poGeom](int nCurveDescrCount)
427 : {
428 215 : WriteVarUInt(m_abyGeomBuffer, static_cast<uint32_t>(m_adfX.size()));
429 215 : if (m_adfX.empty())
430 8 : return true;
431 207 : WriteVarUInt(m_abyGeomBuffer,
432 207 : static_cast<uint32_t>(m_anNumberPointsPerPart.size()));
433 207 : if (nCurveDescrCount > 0)
434 12 : WriteVarUInt(m_abyGeomBuffer, nCurveDescrCount);
435 :
436 207 : if (!EncodeEnvelope(m_abyGeomBuffer, poGeomField, poGeom))
437 0 : return false;
438 :
439 263 : for (int iPart = 0;
440 263 : iPart < static_cast<int>(m_anNumberPointsPerPart.size()) - 1;
441 : ++iPart)
442 : {
443 56 : WriteVarUInt(m_abyGeomBuffer, m_anNumberPointsPerPart[iPart]);
444 : }
445 :
446 : {
447 207 : int64_t nLastX = 0;
448 207 : int64_t nLastY = 0;
449 3290 : for (size_t i = 0; i < m_adfX.size(); ++i)
450 : {
451 : double dfVal =
452 3083 : std::round((m_adfX[i] - poGeomField->GetXOrigin()) *
453 3083 : poGeomField->GetXYScale());
454 3083 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastX,
455 : "Cannot encode X value");
456 3083 : const int64_t nX = static_cast<int64_t>(dfVal);
457 3083 : WriteVarInt(m_abyGeomBuffer, nX - nLastX);
458 :
459 3083 : dfVal = std::round((m_adfY[i] - poGeomField->GetYOrigin()) *
460 3083 : poGeomField->GetXYScale());
461 3083 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastY,
462 : "Cannot encode Y value");
463 3083 : const int64_t nY = static_cast<int64_t>(dfVal);
464 3083 : WriteVarInt(m_abyGeomBuffer, nY - nLastY);
465 :
466 3083 : nLastX = nX;
467 3083 : nLastY = nY;
468 : }
469 : }
470 :
471 207 : if (bIs3D)
472 : {
473 61 : int64_t nLastZ = 0;
474 295 : for (size_t i = 0; i < m_adfZ.size(); ++i)
475 : {
476 : double dfVal =
477 234 : std::round((m_adfZ[i] - poGeomField->GetZOrigin()) *
478 234 : poGeomField->GetZScale());
479 234 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastZ,
480 : "Cannot encode Z value");
481 234 : const int64_t nZ = static_cast<int64_t>(dfVal);
482 234 : WriteVarInt(m_abyGeomBuffer, nZ - nLastZ);
483 :
484 234 : nLastZ = nZ;
485 : }
486 : }
487 :
488 207 : if (bIsMeasured)
489 : {
490 13 : int64_t nLastM = 0;
491 79 : for (size_t i = 0; i < m_adfM.size(); ++i)
492 : {
493 : double dfVal =
494 66 : std::round((m_adfM[i] - poGeomField->GetMOrigin()) *
495 66 : poGeomField->GetMScale());
496 66 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastM,
497 : "Cannot encode M value");
498 66 : const int64_t nM = static_cast<int64_t>(dfVal);
499 66 : WriteVarInt(m_abyGeomBuffer, nM - nLastM);
500 :
501 66 : nLastM = nM;
502 : }
503 : }
504 :
505 207 : 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 207 : return true;
513 4073 : };
514 :
515 : const auto ReserveXYZMArrays =
516 1136 : [this, bIs3D, bIsMeasured](const size_t nAdditionalSize)
517 : {
518 231 : size_t nNewMinSize = m_adfX.size() + nAdditionalSize;
519 231 : if (nNewMinSize > m_adfX.capacity())
520 : {
521 104 : size_t nNewCapacity = nNewMinSize;
522 104 : if (m_adfX.capacity() < std::numeric_limits<size_t>::max() / 2)
523 : {
524 104 : nNewCapacity = std::max(nNewCapacity, 2 * m_adfX.capacity());
525 : }
526 104 : m_adfX.reserve(nNewCapacity);
527 104 : m_adfY.reserve(nNewCapacity);
528 104 : if (bIs3D)
529 33 : m_adfZ.reserve(nNewCapacity);
530 104 : if (bIsMeasured)
531 17 : m_adfM.reserve(nNewCapacity);
532 : }
533 231 : };
534 :
535 4073 : const auto eFlatType = wkbFlatten(poGeom->getGeometryType());
536 4073 : switch (eFlatType)
537 : {
538 3806 : case wkbPoint:
539 : {
540 3806 : if (bIs3D)
541 : {
542 24 : if (bIsMeasured)
543 : {
544 4 : WriteUInt8(m_abyGeomBuffer,
545 : static_cast<uint8_t>(SHPT_POINTZM));
546 : }
547 : else
548 : {
549 20 : WriteUInt8(m_abyGeomBuffer,
550 : static_cast<uint8_t>(SHPT_POINTZ));
551 : }
552 : }
553 : else
554 : {
555 3782 : if (bIsMeasured)
556 : {
557 2 : WriteUInt8(m_abyGeomBuffer,
558 : static_cast<uint8_t>(SHPT_POINTM));
559 : }
560 : else
561 : {
562 3780 : WriteUInt8(m_abyGeomBuffer,
563 : static_cast<uint8_t>(SHPT_POINT));
564 : }
565 : }
566 3806 : const auto poPoint = poGeom->toPoint();
567 3806 : 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 3802 : dfVal = (poPoint->getX() - poGeomField->GetXOrigin()) *
581 3802 : poGeomField->GetXYScale() +
582 : 1;
583 3802 : CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode value");
584 3802 : WriteVarUInt(m_abyGeomBuffer,
585 3802 : static_cast<uint64_t>(dfVal + 0.5));
586 :
587 3802 : dfVal = (poPoint->getY() - poGeomField->GetYOrigin()) *
588 3802 : poGeomField->GetXYScale() +
589 : 1;
590 3802 : CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode Y value");
591 3802 : WriteVarUInt(m_abyGeomBuffer,
592 3802 : static_cast<uint64_t>(dfVal + 0.5));
593 :
594 3802 : if (bIs3D)
595 : {
596 22 : dfVal = (poPoint->getZ() - poGeomField->GetZOrigin()) *
597 22 : poGeomField->GetZScale() +
598 : 1;
599 22 : CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal,
600 : "Cannot encode Z value");
601 22 : WriteVarUInt(m_abyGeomBuffer,
602 22 : static_cast<uint64_t>(dfVal + 0.5));
603 : }
604 :
605 3802 : 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 3806 : return true;
618 : }
619 :
620 29 : case wkbMultiPoint:
621 : {
622 29 : if (bIs3D)
623 : {
624 14 : if (bIsMeasured)
625 : {
626 1 : WriteUInt8(m_abyGeomBuffer,
627 : static_cast<uint8_t>(SHPT_MULTIPOINTZM));
628 : }
629 : else
630 : {
631 13 : WriteUInt8(m_abyGeomBuffer,
632 : static_cast<uint8_t>(SHPT_MULTIPOINTZ));
633 : }
634 : }
635 : else
636 : {
637 15 : if (bIsMeasured)
638 : {
639 1 : WriteUInt8(m_abyGeomBuffer,
640 : static_cast<uint8_t>(SHPT_MULTIPOINTM));
641 : }
642 : else
643 : {
644 14 : WriteUInt8(m_abyGeomBuffer,
645 : static_cast<uint8_t>(SHPT_MULTIPOINT));
646 : }
647 : }
648 :
649 29 : const auto poMultiPoint = poGeom->toMultiPoint();
650 29 : const auto nNumGeoms = poMultiPoint->getNumGeometries();
651 29 : WriteVarUInt(m_abyGeomBuffer, nNumGeoms);
652 29 : if (nNumGeoms == 0)
653 0 : return true;
654 :
655 29 : if (!EncodeEnvelope(m_abyGeomBuffer, poGeomField, poGeom))
656 0 : return false;
657 :
658 : {
659 29 : int64_t nLastX = 0;
660 29 : int64_t nLastY = 0;
661 86 : for (const auto *poPoint : *poMultiPoint)
662 : {
663 57 : const double dfX = poPoint->getX();
664 57 : const double dfY = poPoint->getY();
665 :
666 : double dfVal =
667 57 : std::round((dfX - poGeomField->GetXOrigin()) *
668 57 : poGeomField->GetXYScale());
669 57 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastX,
670 : "Cannot encode value");
671 57 : const int64_t nX = static_cast<int64_t>(dfVal);
672 57 : WriteVarInt(m_abyGeomBuffer, nX - nLastX);
673 :
674 57 : dfVal = std::round((dfY - poGeomField->GetYOrigin()) *
675 57 : poGeomField->GetXYScale());
676 57 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastY,
677 : "Cannot encode Y value");
678 57 : const int64_t nY = static_cast<int64_t>(dfVal);
679 57 : WriteVarInt(m_abyGeomBuffer, nY - nLastY);
680 :
681 57 : nLastX = nX;
682 57 : nLastY = nY;
683 : }
684 : }
685 :
686 29 : if (bIs3D)
687 : {
688 14 : int64_t nLastZ = 0;
689 42 : for (const auto *poPoint : *poMultiPoint)
690 : {
691 28 : const double dfZ = poPoint->getZ();
692 :
693 : double dfVal =
694 28 : std::round((dfZ - poGeomField->GetZOrigin()) *
695 28 : poGeomField->GetZScale());
696 28 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastZ,
697 : "Bad Z value");
698 28 : const int64_t nZ = static_cast<int64_t>(dfVal);
699 28 : WriteVarInt(m_abyGeomBuffer, nZ - nLastZ);
700 :
701 28 : nLastZ = nZ;
702 : }
703 : }
704 :
705 29 : 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 29 : return true;
725 : }
726 :
727 74 : case wkbLineString:
728 : case wkbCircularString:
729 : case wkbCompoundCurve:
730 : case wkbMultiLineString:
731 : case wkbMultiCurve:
732 : {
733 74 : m_abyCurvePart.clear();
734 74 : m_anNumberPointsPerPart.clear();
735 74 : m_adfX.clear();
736 74 : m_adfY.clear();
737 74 : m_adfZ.clear();
738 74 : m_adfM.clear();
739 :
740 74 : int nCurveDescrCount = 0;
741 : const auto ProcessCurve =
742 88 : [this, bIs3D, bIsMeasured, &nCurveDescrCount,
743 5122 : &ReserveXYZMArrays](const OGRCurve *poCurve)
744 : {
745 88 : 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 83 : else if (const auto poLS =
816 83 : dynamic_cast<const OGRLineString *>(poCurve))
817 : {
818 80 : const int nNumPoints = poLS->getNumPoints();
819 80 : m_anNumberPointsPerPart.push_back(nNumPoints);
820 80 : ReserveXYZMArrays(nNumPoints);
821 1264 : for (int i = 0; i < nNumPoints; ++i)
822 : {
823 1184 : m_adfX.push_back(poLS->getX(i));
824 1184 : m_adfY.push_back(poLS->getY(i));
825 1184 : if (bIs3D)
826 64 : m_adfZ.push_back(poLS->getZ(i));
827 1184 : 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 88 : };
866 :
867 74 : if (eFlatType == wkbMultiLineString || eFlatType == wkbMultiCurve)
868 : {
869 35 : const auto poMultiCurve = poGeom->toMultiCurve();
870 84 : for (const auto *poCurve : *poMultiCurve)
871 : {
872 49 : ProcessCurve(poCurve);
873 35 : }
874 : }
875 : else
876 : {
877 39 : ProcessCurve(poGeom->toCurve());
878 : }
879 :
880 74 : 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 69 : else if (bIs3D)
888 : {
889 30 : if (bIsMeasured)
890 : {
891 3 : WriteUInt8(m_abyGeomBuffer,
892 : static_cast<uint8_t>(SHPT_ARCZM));
893 : }
894 : else
895 : {
896 27 : WriteUInt8(m_abyGeomBuffer,
897 : static_cast<uint8_t>(SHPT_ARCZ));
898 : }
899 : }
900 : else
901 : {
902 39 : if (bIsMeasured)
903 : {
904 3 : WriteUInt8(m_abyGeomBuffer,
905 : static_cast<uint8_t>(SHPT_ARCM));
906 : }
907 : else
908 : {
909 36 : WriteUInt8(m_abyGeomBuffer, static_cast<uint8_t>(SHPT_ARC));
910 : }
911 : }
912 :
913 74 : return WriteEndOfCurveOrSurface(nCurveDescrCount);
914 : }
915 :
916 141 : case wkbPolygon:
917 : case wkbCurvePolygon:
918 : case wkbMultiPolygon:
919 : case wkbMultiSurface:
920 : {
921 141 : m_abyCurvePart.clear();
922 141 : m_anNumberPointsPerPart.clear();
923 141 : m_adfX.clear();
924 141 : m_adfY.clear();
925 141 : m_adfZ.clear();
926 141 : m_adfM.clear();
927 :
928 141 : int nCurveDescrCount = 0;
929 : const auto ProcessSurface =
930 154 : [this, bIs3D, bIsMeasured, &nCurveDescrCount,
931 8147 : &ReserveXYZMArrays](const OGRSurface *poSurface)
932 : {
933 154 : if (const auto poPolygon =
934 154 : dynamic_cast<const OGRPolygon *>(poSurface))
935 : {
936 143 : bool bFirstRing = true;
937 :
938 143 : std::size_t nTotalSize = 0;
939 303 : for (const auto *poLS : *poPolygon)
940 : {
941 160 : nTotalSize += poLS->getNumPoints();
942 : }
943 143 : ReserveXYZMArrays(nTotalSize);
944 :
945 303 : for (const auto *poLS : *poPolygon)
946 : {
947 160 : const int nNumPoints = poLS->getNumPoints();
948 160 : m_anNumberPointsPerPart.push_back(nNumPoints);
949 : const bool bIsClockwise =
950 160 : CPL_TO_BOOL(poLS->isClockwise());
951 160 : const bool bReverseOrder =
952 319 : (bFirstRing && !bIsClockwise) ||
953 159 : (!bFirstRing && bIsClockwise);
954 160 : bFirstRing = false;
955 1979 : for (int i = 0; i < nNumPoints; ++i)
956 : {
957 1819 : const int j =
958 1819 : bReverseOrder ? nNumPoints - 1 - i : i;
959 1819 : m_adfX.push_back(poLS->getX(j));
960 1819 : m_adfY.push_back(poLS->getY(j));
961 1819 : if (bIs3D)
962 162 : m_adfZ.push_back(poLS->getZ(j));
963 1819 : if (bIsMeasured)
964 42 : 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 154 : };
1128 :
1129 141 : if (eFlatType == wkbMultiPolygon || eFlatType == wkbMultiSurface)
1130 : {
1131 87 : const auto poMultiSurface = poGeom->toMultiSurface();
1132 187 : for (const auto *poSurface : *poMultiSurface)
1133 : {
1134 100 : ProcessSurface(poSurface);
1135 87 : }
1136 : }
1137 : else
1138 : {
1139 54 : ProcessSurface(poGeom->toSurface());
1140 : }
1141 :
1142 141 : 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 134 : else if (bIs3D)
1150 : {
1151 32 : if (bIsMeasured)
1152 : {
1153 5 : WriteUInt8(m_abyGeomBuffer,
1154 : static_cast<uint8_t>(SHPT_POLYGONZM));
1155 : }
1156 : else
1157 : {
1158 27 : WriteUInt8(m_abyGeomBuffer,
1159 : static_cast<uint8_t>(SHPT_POLYGONZ));
1160 : }
1161 : }
1162 : else
1163 : {
1164 102 : if (bIsMeasured)
1165 : {
1166 3 : WriteUInt8(m_abyGeomBuffer,
1167 : static_cast<uint8_t>(SHPT_POLYGONM));
1168 : }
1169 : else
1170 : {
1171 99 : WriteUInt8(m_abyGeomBuffer,
1172 : static_cast<uint8_t>(SHPT_POLYGON));
1173 : }
1174 : }
1175 :
1176 141 : return WriteEndOfCurveOrSurface(nCurveDescrCount);
1177 : }
1178 :
1179 23 : case wkbTIN:
1180 : case wkbPolyhedralSurface:
1181 : case wkbGeometryCollection:
1182 : {
1183 23 : int nParts = 0;
1184 46 : std::vector<int> anPartStart;
1185 46 : std::vector<int> anPartType;
1186 23 : int nPoints = 0;
1187 46 : std::vector<OGRRawPoint> aoPoints;
1188 46 : std::vector<double> adfZ;
1189 : OGRErr eErr =
1190 23 : OGRCreateMultiPatch(poGeom, TRUE, nParts, anPartStart,
1191 : anPartType, nPoints, aoPoints, adfZ);
1192 23 : if (eErr != OGRERR_NONE)
1193 2 : return false;
1194 :
1195 21 : WriteUInt8(m_abyGeomBuffer, static_cast<uint8_t>(SHPT_MULTIPATCH));
1196 21 : WriteVarUInt(m_abyGeomBuffer, nPoints);
1197 21 : 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 21 : int nShapeBufferSize =
1203 : 4; // All types start with integer type number.
1204 21 : nShapeBufferSize += 16 * 2; // xy bbox.
1205 21 : nShapeBufferSize += 4; // nparts.
1206 21 : nShapeBufferSize += 4; // npoints.
1207 21 : nShapeBufferSize += 4 * nParts; // panPartStart[nparts].
1208 21 : nShapeBufferSize += 4 * nParts; // panPartType[nparts].
1209 21 : nShapeBufferSize += 8 * 2 * nPoints; // xy points.
1210 21 : nShapeBufferSize += 16; // z bbox.
1211 21 : nShapeBufferSize += 8 * nPoints; // z points.
1212 21 : WriteVarUInt(m_abyGeomBuffer, nShapeBufferSize);
1213 :
1214 21 : WriteVarUInt(m_abyGeomBuffer, nParts);
1215 :
1216 21 : if (!EncodeEnvelope(m_abyGeomBuffer, poGeomField, poGeom))
1217 : {
1218 0 : return false;
1219 : }
1220 :
1221 62 : for (int i = 0; i < nParts - 1; i++)
1222 : {
1223 41 : WriteVarUInt(m_abyGeomBuffer,
1224 41 : anPartStart[i + 1] - anPartStart[i]);
1225 : }
1226 :
1227 83 : for (int i = 0; i < nParts; i++)
1228 : {
1229 62 : WriteVarUInt(m_abyGeomBuffer, anPartType[i]);
1230 : }
1231 :
1232 : {
1233 21 : int64_t nLastX = 0;
1234 21 : int64_t nLastY = 0;
1235 278 : for (int i = 0; i < nPoints; ++i)
1236 : {
1237 257 : double dfVal = std::round(
1238 257 : (aoPoints[i].x - poGeomField->GetXOrigin()) *
1239 257 : poGeomField->GetXYScale());
1240 257 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastX,
1241 : "Cannot encode value");
1242 257 : const int64_t nX = static_cast<int64_t>(dfVal);
1243 257 : WriteVarInt(m_abyGeomBuffer, nX - nLastX);
1244 :
1245 257 : dfVal = std::round(
1246 257 : (aoPoints[i].y - poGeomField->GetYOrigin()) *
1247 257 : poGeomField->GetXYScale());
1248 257 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastY,
1249 : "Cannot encode Y value");
1250 257 : const int64_t nY = static_cast<int64_t>(dfVal);
1251 257 : WriteVarInt(m_abyGeomBuffer, nY - nLastY);
1252 :
1253 257 : nLastX = nX;
1254 257 : nLastY = nY;
1255 : }
1256 : }
1257 :
1258 : {
1259 21 : int64_t nLastZ = 0;
1260 278 : for (int i = 0; i < nPoints; ++i)
1261 : {
1262 : double dfVal =
1263 257 : std::round((adfZ[i] - poGeomField->GetZOrigin()) *
1264 257 : poGeomField->GetZScale());
1265 257 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastZ,
1266 : "Bad Z value");
1267 257 : const int64_t nZ = static_cast<int64_t>(dfVal);
1268 257 : WriteVarInt(m_abyGeomBuffer, nZ - nLastZ);
1269 :
1270 257 : nLastZ = nZ;
1271 : }
1272 : }
1273 : }
1274 21 : 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 33179 : bool FileGDBTable::EncodeFeature(const std::vector<OGRField> &asRawFields,
1291 : const OGRGeometry *poGeom, int iSkipField)
1292 : {
1293 33179 : m_abyBuffer.clear();
1294 33179 : 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 33157 : m_abyBuffer.resize(m_nNullableFieldsSizeInBytes, 0xFF);
1299 :
1300 33179 : if (asRawFields.size() != m_apoFields.size())
1301 : {
1302 0 : CPLError(CE_Failure, CPLE_AppDefined, "Bad size of asRawFields");
1303 0 : return false;
1304 : }
1305 33179 : int iNullableField = 0;
1306 170025 : for (int i = 0; i < static_cast<int>(m_apoFields.size()); ++i)
1307 : {
1308 136852 : if (i == iSkipField)
1309 26 : continue;
1310 136826 : auto &poField = m_apoFields[i];
1311 136826 : if (poField->GetType() == FGFT_OBJECTID)
1312 : {
1313 : // Implicit field
1314 24846 : continue;
1315 : }
1316 111980 : if (i == m_iGeomField)
1317 : {
1318 5046 : if (poGeom == nullptr)
1319 : {
1320 973 : if (!poField->IsNullable())
1321 : {
1322 1 : CPLError(CE_Failure, CPLE_AppDefined,
1323 : "Attempting to write null geometry in "
1324 : "non-nullable geometry field");
1325 1 : return false;
1326 : }
1327 972 : iNullableField++;
1328 972 : continue;
1329 : }
1330 :
1331 : auto poGeomField =
1332 4073 : cpl::down_cast<FileGDBGeomField *>(poField.get());
1333 4073 : if (!EncodeGeometry(poGeomField, poGeom))
1334 2 : return false;
1335 4071 : if (!poGeom->IsEmpty())
1336 : {
1337 4059 : OGREnvelope3D oEnvelope;
1338 4059 : poGeom->getEnvelope(&oEnvelope);
1339 4059 : m_bDirtyGeomFieldBBox = true;
1340 4059 : if (std::isnan(poGeomField->GetXMin()))
1341 : {
1342 153 : poGeomField->SetXYMinMax(oEnvelope.MinX, oEnvelope.MinY,
1343 : oEnvelope.MaxX, oEnvelope.MaxY);
1344 153 : poGeomField->SetZMinMax(oEnvelope.MinZ, oEnvelope.MaxZ);
1345 : }
1346 : else
1347 : {
1348 3906 : poGeomField->SetXYMinMax(
1349 3906 : std::min(poGeomField->GetXMin(), oEnvelope.MinX),
1350 3906 : std::min(poGeomField->GetYMin(), oEnvelope.MinY),
1351 3906 : std::max(poGeomField->GetXMax(), oEnvelope.MaxX),
1352 3906 : std::max(poGeomField->GetYMax(), oEnvelope.MaxY));
1353 3906 : poGeomField->SetZMinMax(
1354 3906 : std::min(poGeomField->GetZMin(), oEnvelope.MinZ),
1355 7812 : std::max(poGeomField->GetZMax(), oEnvelope.MaxZ));
1356 : }
1357 : }
1358 :
1359 4071 : 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 4071 : WriteVarUInt(m_abyBuffer, m_abyGeomBuffer.size());
1367 4071 : m_abyBuffer.insert(m_abyBuffer.end(), m_abyGeomBuffer.begin(),
1368 8142 : m_abyGeomBuffer.end());
1369 :
1370 4071 : if (poField->IsNullable())
1371 : {
1372 4069 : m_abyBuffer[iNullableField / 8] &= ~(1 << (iNullableField % 8));
1373 4069 : iNullableField++;
1374 : }
1375 4071 : continue;
1376 : }
1377 :
1378 213868 : if (OGR_RawField_IsNull(&asRawFields[i]) ||
1379 106934 : OGR_RawField_IsUnset(&asRawFields[i]))
1380 : {
1381 5918 : if (!poField->IsNullable())
1382 : {
1383 3 : CPLError(CE_Failure, CPLE_AppDefined,
1384 : "Attempting to write null/empty field in non-nullable "
1385 : "field");
1386 3 : return false;
1387 : }
1388 5915 : iNullableField++;
1389 5915 : continue;
1390 : }
1391 :
1392 101016 : switch (poField->GetType())
1393 : {
1394 0 : case FGFT_UNDEFINED:
1395 : {
1396 0 : CPLAssert(false);
1397 : break;
1398 : }
1399 :
1400 4152 : case FGFT_INT16:
1401 : {
1402 4152 : WriteInt16(m_abyBuffer,
1403 4152 : static_cast<int16_t>(asRawFields[i].Integer));
1404 4152 : break;
1405 : }
1406 :
1407 4525 : case FGFT_INT32:
1408 : {
1409 4525 : WriteInt32(m_abyBuffer, asRawFields[i].Integer);
1410 4525 : break;
1411 : }
1412 :
1413 344 : case FGFT_FLOAT32:
1414 : {
1415 344 : WriteFloat32(m_abyBuffer,
1416 344 : static_cast<float>(asRawFields[i].Real));
1417 344 : break;
1418 : }
1419 :
1420 4513 : case FGFT_FLOAT64:
1421 : {
1422 4513 : WriteFloat64(m_abyBuffer, asRawFields[i].Real);
1423 4513 : break;
1424 : }
1425 :
1426 56799 : case FGFT_STRING:
1427 : case FGFT_XML:
1428 : {
1429 56799 : if (m_bStringsAreUTF8 || poField->GetType() == FGFT_XML)
1430 : {
1431 56798 : const auto nLen = strlen(asRawFields[i].String);
1432 56798 : WriteVarUInt(m_abyBuffer, nLen);
1433 56798 : if (nLen > 0)
1434 : {
1435 55216 : 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 55216 : asRawFields[i].String),
1445 : reinterpret_cast<const uint8_t *>(
1446 110432 : asRawFields[i].String) +
1447 110432 : nLen);
1448 : }
1449 : }
1450 : else
1451 : {
1452 1 : WriteUTF16String(m_abyBuffer, asRawFields[i].String,
1453 : NUMBER_OF_BYTES_ON_VARUINT);
1454 : }
1455 56799 : break;
1456 : }
1457 :
1458 236 : case FGFT_DATETIME:
1459 : case FGFT_DATE:
1460 : {
1461 236 : WriteFloat64(m_abyBuffer,
1462 : FileGDBOGRDateToDoubleDate(
1463 236 : &asRawFields[i], /* bConvertToUTC = */ true,
1464 236 : poField->IsHighPrecision()));
1465 236 : 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 359 : case FGFT_BINARY:
1481 : {
1482 359 : WriteVarUInt(m_abyBuffer, asRawFields[i].Binary.nCount);
1483 359 : if (asRawFields[i].Binary.nCount)
1484 : {
1485 359 : if (static_cast<size_t>(asRawFields[i].Binary.nCount) +
1486 359 : 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 359 : asRawFields[i].Binary.paData,
1495 718 : asRawFields[i].Binary.paData +
1496 718 : asRawFields[i].Binary.nCount);
1497 : }
1498 359 : break;
1499 : }
1500 :
1501 0 : case FGFT_RASTER:
1502 : {
1503 : // Not handled for now
1504 0 : CPLAssert(false);
1505 : break;
1506 : }
1507 :
1508 30068 : case FGFT_GUID:
1509 : case FGFT_GLOBALID:
1510 : {
1511 30068 : const auto nLen = strlen(asRawFields[i].String);
1512 30068 : if (nLen != 38)
1513 : {
1514 0 : CPLError(CE_Failure, CPLE_AppDefined,
1515 : "Bad size for UUID field");
1516 0 : return false;
1517 : }
1518 60136 : std::vector<unsigned> anVals(16);
1519 30068 : sscanf(asRawFields[i].String,
1520 : "{%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%"
1521 : "02X%02X%02X%02X}",
1522 30068 : &anVals[3], &anVals[2], &anVals[1], &anVals[0],
1523 30068 : &anVals[5], &anVals[4], &anVals[7], &anVals[6],
1524 30068 : &anVals[8], &anVals[9], &anVals[10], &anVals[11],
1525 30068 : &anVals[12], &anVals[13], &anVals[14], &anVals[15]);
1526 511156 : for (auto v : anVals)
1527 : {
1528 481088 : m_abyBuffer.push_back(static_cast<uint8_t>(v));
1529 : }
1530 30068 : 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 101016 : if (poField->IsNullable())
1567 : {
1568 41947 : m_abyBuffer[iNullableField / 8] &= ~(1 << (iNullableField % 8));
1569 41947 : iNullableField++;
1570 : }
1571 : }
1572 :
1573 33173 : 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 33173 : return true;
1580 : }
1581 :
1582 : /************************************************************************/
1583 : /* SeekIntoTableXForNewFeature() */
1584 : /************************************************************************/
1585 :
1586 33090 : bool FileGDBTable::SeekIntoTableXForNewFeature(int nObjectID)
1587 : {
1588 : int iCorrectedRow;
1589 33090 : bool bWriteEmptyPageAtEnd = false;
1590 33090 : const uint32_t nPageSize = TABLX_FEATURES_PER_PAGE * m_nTablxOffsetSize;
1591 33090 : const int nTotalRecordCount = static_cast<int>(m_nTotalRecordCount);
1592 :
1593 33090 : if (m_abyTablXBlockMap.empty())
1594 : {
1595 : // Is the OID to write in the current allocated pages, or in the next
1596 : // page ?
1597 33060 : if ((nObjectID - 1) / TABLX_FEATURES_PER_PAGE <=
1598 33060 : ((nTotalRecordCount == 0)
1599 33060 : ? 0
1600 31149 : : (1 + (nTotalRecordCount - 1) / TABLX_FEATURES_PER_PAGE)))
1601 : {
1602 33038 : iCorrectedRow = nObjectID - 1;
1603 33038 : const auto n1024BlocksPresentBefore = m_n1024BlocksPresent;
1604 33038 : m_n1024BlocksPresent =
1605 33038 : DIV_ROUND_UP(std::max(nTotalRecordCount, nObjectID),
1606 : TABLX_FEATURES_PER_PAGE);
1607 33038 : bWriteEmptyPageAtEnd =
1608 33038 : 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 33090 : if (bWriteEmptyPageAtEnd)
1725 : {
1726 1941 : m_bDirtyTableXTrailer = true;
1727 1941 : m_nOffsetTableXTrailer = 0;
1728 1941 : std::vector<GByte> abyTmp(nPageSize);
1729 1941 : uint64_t nOffset =
1730 : TABLX_HEADER_SIZE +
1731 1941 : static_cast<uint64_t>(m_n1024BlocksPresent - 1) * nPageSize;
1732 1941 : VSIFSeekL(m_fpTableX, nOffset, SEEK_SET);
1733 1941 : 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 33090 : const uint64_t nOffset =
1743 : TABLX_HEADER_SIZE +
1744 33090 : static_cast<uint64_t>(iCorrectedRow) * m_nTablxOffsetSize;
1745 33090 : VSIFSeekL(m_fpTableX, nOffset, SEEK_SET);
1746 :
1747 33090 : return true;
1748 : }
1749 :
1750 : /************************************************************************/
1751 : /* WriteFeatureOffset() */
1752 : /************************************************************************/
1753 :
1754 2750 : void FileGDBTable::WriteFeatureOffset(uint64_t nFeatureOffset,
1755 : GByte *pabyBuffer)
1756 : {
1757 2750 : CPL_LSBPTR64(&nFeatureOffset);
1758 2750 : memcpy(pabyBuffer, &nFeatureOffset, m_nTablxOffsetSize);
1759 2750 : }
1760 :
1761 : /************************************************************************/
1762 : /* WriteFeatureOffset() */
1763 : /************************************************************************/
1764 :
1765 35764 : bool FileGDBTable::WriteFeatureOffset(uint64_t nFeatureOffset)
1766 : {
1767 35764 : CPL_LSBPTR64(&nFeatureOffset);
1768 35764 : return VSIFWriteL(&nFeatureOffset, m_nTablxOffsetSize, 1, m_fpTableX) == 1;
1769 : }
1770 :
1771 : /************************************************************************/
1772 : /* CreateFeature() */
1773 : /************************************************************************/
1774 :
1775 33098 : bool FileGDBTable::CreateFeature(const std::vector<OGRField> &asRawFields,
1776 : const OGRGeometry *poGeom, int *pnFID)
1777 : {
1778 33098 : if (!m_bUpdate)
1779 0 : return false;
1780 :
1781 33098 : if (m_bDirtyFieldDescriptors && !WriteFieldDescriptors(m_fpTable))
1782 0 : return false;
1783 :
1784 : int nObjectID;
1785 33098 : if (pnFID != nullptr && *pnFID > 0)
1786 : {
1787 129 : if (*pnFID <= m_nTotalRecordCount &&
1788 23 : 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 104 : nObjectID = *pnFID;
1797 : }
1798 : else
1799 : {
1800 32992 : 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 32992 : nObjectID = static_cast<int>(m_nTotalRecordCount + 1);
1807 : }
1808 :
1809 : try
1810 : {
1811 33096 : if (!EncodeFeature(asRawFields, poGeom, -1))
1812 6 : 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 33090 : const uint64_t nFreeOffset = GetOffsetOfFreeAreaFromFreeList(
1821 33090 : static_cast<uint32_t>(sizeof(uint32_t) + m_abyBuffer.size()));
1822 33090 : if (nFreeOffset == OFFSET_MINUS_ONE)
1823 : {
1824 30474 : 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 33090 : if (!SeekIntoTableXForNewFeature(nObjectID))
1835 0 : return false;
1836 :
1837 33090 : if (nFreeOffset == OFFSET_MINUS_ONE)
1838 : {
1839 30474 : VSIFSeekL(m_fpTable, m_nFileSize, SEEK_SET);
1840 : }
1841 : else
1842 : {
1843 2616 : VSIFSeekL(m_fpTable, nFreeOffset, SEEK_SET);
1844 : }
1845 33090 : if (!WriteUInt32(m_fpTable, static_cast<uint32_t>(m_abyBuffer.size())))
1846 0 : return false;
1847 66177 : if (!m_abyBuffer.empty() &&
1848 33087 : VSIFWriteL(m_abyBuffer.data(), 1, m_abyBuffer.size(), m_fpTable) !=
1849 33087 : m_abyBuffer.size())
1850 : {
1851 0 : return false;
1852 : }
1853 :
1854 33090 : if (!WriteFeatureOffset(nFreeOffset == OFFSET_MINUS_ONE ? m_nFileSize
1855 : : nFreeOffset))
1856 0 : return false;
1857 33090 : if (pnFID)
1858 9491 : *pnFID = nObjectID;
1859 :
1860 33090 : m_nRowBlobLength = static_cast<uint32_t>(m_abyBuffer.size());
1861 33090 : if (m_nRowBlobLength > m_nHeaderBufferMaxSize)
1862 : {
1863 5571 : m_nHeaderBufferMaxSize = m_nRowBlobLength;
1864 : }
1865 33090 : m_nRowBufferMaxSize = std::max(m_nRowBufferMaxSize, m_nRowBlobLength);
1866 33090 : if (nFreeOffset == OFFSET_MINUS_ONE)
1867 : {
1868 30474 : m_nFileSize += sizeof(uint32_t) + m_nRowBlobLength;
1869 : }
1870 :
1871 33090 : m_nTotalRecordCount =
1872 33090 : std::max(m_nTotalRecordCount, static_cast<int64_t>(nObjectID));
1873 33090 : m_nValidRecordCount++;
1874 :
1875 33090 : m_bDirtyHeader = true;
1876 33090 : m_bDirtyTableXHeader = true;
1877 :
1878 33090 : m_bDirtyIndices = true;
1879 :
1880 33090 : return true;
1881 : }
1882 :
1883 : /************************************************************************/
1884 : /* UpdateFeature() */
1885 : /************************************************************************/
1886 :
1887 57 : bool FileGDBTable::UpdateFeature(int64_t nFID,
1888 : const std::vector<OGRField> &asRawFields,
1889 : const OGRGeometry *poGeom)
1890 : {
1891 57 : if (!m_bUpdate)
1892 0 : return false;
1893 :
1894 57 : if (m_bDirtyFieldDescriptors && !WriteFieldDescriptors(m_fpTable))
1895 0 : return false;
1896 :
1897 57 : vsi_l_offset nOffsetInTableX = 0;
1898 : vsi_l_offset nOffsetInTable =
1899 57 : GetOffsetInTableForRow(nFID - 1, &nOffsetInTableX);
1900 57 : if (nOffsetInTable == 0)
1901 0 : return false;
1902 :
1903 : try
1904 : {
1905 57 : 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 57 : VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
1915 57 : uint32_t nOldFeatureSize = 0;
1916 57 : if (!ReadUInt32(m_fpTable, nOldFeatureSize))
1917 0 : return false;
1918 :
1919 57 : m_nCurRow = -1;
1920 :
1921 57 : if (m_abyBuffer.size() <= nOldFeatureSize)
1922 : {
1923 : // Can rewrite-in-place
1924 30 : VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
1925 :
1926 30 : if (!WriteUInt32(m_fpTable, static_cast<uint32_t>(m_abyBuffer.size())))
1927 0 : return false;
1928 60 : if (!m_abyBuffer.empty() &&
1929 30 : VSIFWriteL(m_abyBuffer.data(), 1, m_abyBuffer.size(), m_fpTable) !=
1930 30 : m_abyBuffer.size())
1931 : {
1932 0 : return false;
1933 : }
1934 :
1935 30 : m_nRowBlobLength = 0;
1936 30 : const size_t nSizeToBlank = nOldFeatureSize - m_abyBuffer.size();
1937 30 : if (nSizeToBlank > 0)
1938 : {
1939 : // Blank unused areas of the old feature
1940 17 : m_abyBuffer.clear();
1941 : try
1942 : {
1943 17 : m_abyBuffer.resize(nSizeToBlank);
1944 17 : 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 27 : const uint64_t nFreeOffset = GetOffsetOfFreeAreaFromFreeList(
1961 27 : static_cast<uint32_t>(sizeof(uint32_t) + m_abyBuffer.size()));
1962 :
1963 27 : if (nFreeOffset == OFFSET_MINUS_ONE)
1964 : {
1965 27 : if (((m_nFileSize + m_abyBuffer.size()) >>
1966 27 : (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 27 : VSIFSeekL(m_fpTable, m_nFileSize, SEEK_SET);
1976 : }
1977 : else
1978 : {
1979 0 : VSIFSeekL(m_fpTable, nFreeOffset, SEEK_SET);
1980 : }
1981 :
1982 27 : if (!WriteUInt32(m_fpTable, static_cast<uint32_t>(m_abyBuffer.size())))
1983 0 : return false;
1984 54 : if (!m_abyBuffer.empty() &&
1985 27 : VSIFWriteL(m_abyBuffer.data(), 1, m_abyBuffer.size(), m_fpTable) !=
1986 27 : m_abyBuffer.size())
1987 : {
1988 0 : return false;
1989 : }
1990 :
1991 : // Update offset of feature in .gdbtablx
1992 27 : VSIFSeekL(m_fpTableX, nOffsetInTableX, SEEK_SET);
1993 27 : if (!WriteFeatureOffset(nFreeOffset == OFFSET_MINUS_ONE ? m_nFileSize
1994 : : nFreeOffset))
1995 0 : return false;
1996 :
1997 27 : m_nRowBlobLength = static_cast<uint32_t>(m_abyBuffer.size());
1998 27 : if (m_nRowBlobLength > m_nHeaderBufferMaxSize)
1999 : {
2000 18 : m_bDirtyHeader = true;
2001 18 : m_nHeaderBufferMaxSize = m_nRowBlobLength;
2002 : }
2003 27 : m_nRowBufferMaxSize = std::max(m_nRowBufferMaxSize, m_nRowBlobLength);
2004 27 : if (nFreeOffset == OFFSET_MINUS_ONE)
2005 : {
2006 27 : m_bDirtyHeader = true;
2007 27 : m_nFileSize += sizeof(uint32_t) + m_nRowBlobLength;
2008 : }
2009 :
2010 27 : AddEntryToFreelist(nOffsetInTable, sizeof(uint32_t) + nOldFeatureSize);
2011 :
2012 : // Blank previously used area
2013 27 : VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
2014 27 : const uint32_t nNegatedOldFeatureSize =
2015 27 : static_cast<uint32_t>(-static_cast<int>(nOldFeatureSize));
2016 27 : if (!WriteUInt32(m_fpTable, nNegatedOldFeatureSize))
2017 0 : return false;
2018 27 : m_abyBuffer.clear();
2019 : try
2020 : {
2021 27 : m_abyBuffer.resize(nOldFeatureSize);
2022 27 : 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 57 : m_bDirtyIndices = true;
2033 :
2034 57 : return true;
2035 : }
2036 :
2037 : /************************************************************************/
2038 : /* DeleteFeature() */
2039 : /************************************************************************/
2040 :
2041 2647 : bool FileGDBTable::DeleteFeature(int64_t nFID)
2042 : {
2043 2647 : if (!m_bUpdate)
2044 0 : return false;
2045 :
2046 2647 : if (m_bDirtyFieldDescriptors && !WriteFieldDescriptors(m_fpTable))
2047 0 : return false;
2048 :
2049 2647 : vsi_l_offset nOffsetInTableX = 0;
2050 : vsi_l_offset nOffsetInTable =
2051 2647 : GetOffsetInTableForRow(nFID - 1, &nOffsetInTableX);
2052 2647 : if (nOffsetInTable == 0)
2053 0 : return false;
2054 :
2055 : // Set 0 as offset for the feature in .gdbtablx
2056 2647 : VSIFSeekL(m_fpTableX, nOffsetInTableX, SEEK_SET);
2057 2647 : if (!WriteFeatureOffset(0))
2058 0 : return false;
2059 :
2060 : // Negate the size of the feature in .gdbtable
2061 2647 : VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
2062 2647 : uint32_t nFeatureSize = 0;
2063 2647 : if (!ReadUInt32(m_fpTable, nFeatureSize))
2064 0 : return false;
2065 2647 : if (nFeatureSize > static_cast<uint32_t>(INT_MAX))
2066 0 : return false;
2067 2647 : const int nDeletedFeatureSize =
2068 2647 : static_cast<uint32_t>(-static_cast<int32_t>(nFeatureSize));
2069 2647 : VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
2070 2647 : if (!WriteUInt32(m_fpTable, nDeletedFeatureSize))
2071 0 : return false;
2072 :
2073 2647 : AddEntryToFreelist(nOffsetInTable, sizeof(uint32_t) + nFeatureSize);
2074 :
2075 : // Blank feature content
2076 2647 : m_nCurRow = -1;
2077 2647 : m_abyBuffer.clear();
2078 : try
2079 : {
2080 2647 : m_abyBuffer.resize(nFeatureSize);
2081 2647 : CPL_IGNORE_RET_VAL(
2082 2647 : 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 2647 : m_nValidRecordCount--;
2091 2647 : m_bDirtyHeader = true;
2092 :
2093 2647 : m_bDirtyIndices = true;
2094 :
2095 2647 : 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 */
|