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 2020 : bool FileGDBTable::Create(const char *pszFilename, int nTablxOffsetSize,
58 : FileGDBTableGeometryType eTableGeomType,
59 : bool bGeomTypeHasZ, bool bGeomTypeHasM)
60 : {
61 2020 : CPLAssert(m_fpTable == nullptr);
62 :
63 2020 : m_eGDBTableVersion = GDBTableVersion::V3;
64 2020 : m_bUpdate = true;
65 2020 : m_eTableGeomType = eTableGeomType;
66 2020 : m_nTablxOffsetSize = nTablxOffsetSize;
67 2020 : m_bGeomTypeHasZ = bGeomTypeHasZ;
68 2020 : m_bGeomTypeHasM = bGeomTypeHasM;
69 2020 : m_bHasReadGDBIndexes = TRUE;
70 :
71 2020 : 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 2020 : m_osFilename = pszFilename;
79 2020 : m_osFilenameWithLayerName = m_osFilename;
80 2020 : m_fpTable = VSIFOpenL(pszFilename, "wb+");
81 2020 : 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 4040 : CPLResetExtensionSafe(pszFilename, "gdbtablx");
90 2020 : m_fpTableX = VSIFOpenL(osTableXName.c_str(), "wb+");
91 2020 : 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 2020 : if (!WriteHeader(m_fpTable))
99 0 : return false;
100 :
101 2020 : if (!WriteHeaderX(m_fpTableX))
102 0 : return false;
103 :
104 2020 : m_bDirtyTableXTrailer = true;
105 :
106 2020 : 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 2065 : bool FileGDBTable::WriteHeader(VSILFILE *fpTable)
131 : {
132 : // Could be useful in case we get something wrong...
133 : const char *pszCreator =
134 2065 : CPLGetConfigOption("OPENFILEGDB_CREATOR", "GDAL " GDAL_RELEASE_NAME);
135 :
136 2065 : m_nFileSize = 0;
137 2065 : m_bDirtyHeader = true;
138 2065 : m_bDirtyFieldDescriptors = true;
139 2065 : m_nOffsetFieldDesc = 0;
140 2065 : m_nFieldDescLength = 0;
141 :
142 2065 : VSIFSeekL(fpTable, 0, SEEK_SET);
143 :
144 : bool bRet =
145 2065 : WriteUInt32(fpTable, 3) && // version number
146 : // number of valid rows
147 2065 : WriteUInt32(fpTable, static_cast<uint32_t>(m_nValidRecordCount)) &&
148 2065 : WriteUInt32(fpTable,
149 2065 : m_nHeaderBufferMaxSize) && // largest size of a feature
150 : // record / field description
151 2065 : WriteUInt32(fpTable, 5) && // magic value
152 2065 : WriteUInt32(fpTable, 0) && // magic value
153 2065 : WriteUInt32(fpTable, 0) && // magic value
154 6195 : WriteUInt64(fpTable, m_nFileSize) &&
155 2065 : WriteUInt64(fpTable, m_nOffsetFieldDesc);
156 :
157 2065 : 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 2065 : bRet =
162 4130 : WriteUInt32(fpTable, static_cast<uint32_t>(strlen(pszCreator))) &&
163 2065 : VSIFWriteL(pszCreator, strlen(pszCreator), 1, fpTable) == 1;
164 : }
165 :
166 2065 : if (!bRet)
167 : {
168 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot write .gdbtable header");
169 0 : return false;
170 : }
171 :
172 2065 : m_nFileSize = VSIFTellL(fpTable);
173 2065 : return true;
174 : }
175 :
176 : /************************************************************************/
177 : /* WriteHeaderX() */
178 : /************************************************************************/
179 :
180 2051 : bool FileGDBTable::WriteHeaderX(VSILFILE *fpTableX)
181 : {
182 2051 : VSIFSeekL(fpTableX, 0, SEEK_SET);
183 2051 : if (!WriteUInt32(fpTableX, 3) || // version number
184 2051 : !WriteUInt32(fpTableX, static_cast<uint32_t>(m_n1024BlocksPresent)) ||
185 6153 : !WriteUInt32(fpTableX, static_cast<uint32_t>(m_nTotalRecordCount)) ||
186 2051 : !WriteUInt32(fpTableX, m_nTablxOffsetSize))
187 : {
188 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot write .gdbtablx header");
189 0 : return false;
190 : }
191 2051 : return true;
192 : }
193 :
194 : /************************************************************************/
195 : /* Sync() */
196 : /************************************************************************/
197 :
198 12607 : bool FileGDBTable::Sync(VSILFILE *fpTable, VSILFILE *fpTableX)
199 : {
200 12607 : if (!m_bUpdate)
201 4371 : return true;
202 :
203 8236 : if (fpTable == nullptr)
204 8152 : fpTable = m_fpTable;
205 :
206 8236 : if (fpTableX == nullptr)
207 8152 : fpTableX = m_fpTableX;
208 :
209 8236 : bool bRet = true;
210 :
211 8236 : if (m_bDirtyGdbIndexesFile)
212 : {
213 329 : m_bDirtyGdbIndexesFile = false;
214 329 : CreateGdbIndexesFile();
215 : }
216 :
217 8236 : if (m_bDirtyIndices)
218 : {
219 3052 : m_bDirtyIndices = false;
220 3052 : RefreshIndices();
221 : }
222 :
223 8236 : if (m_bDirtyFieldDescriptors && fpTable)
224 683 : bRet = WriteFieldDescriptors(fpTable);
225 :
226 8236 : if (m_bDirtyGeomFieldBBox && fpTable)
227 : {
228 162 : VSIFSeekL(fpTable, m_nOffsetFieldDesc + m_nGeomFieldBBoxSubOffset,
229 : SEEK_SET);
230 162 : const auto poGeomField = cpl::down_cast<const FileGDBGeomField *>(
231 162 : m_apoFields[m_iGeomField].get());
232 162 : bRet &= WriteFloat64(fpTable, poGeomField->GetXMin());
233 162 : bRet &= WriteFloat64(fpTable, poGeomField->GetYMin());
234 162 : bRet &= WriteFloat64(fpTable, poGeomField->GetXMax());
235 162 : bRet &= WriteFloat64(fpTable, poGeomField->GetYMax());
236 162 : if (m_bGeomTypeHasZ)
237 : {
238 50 : bRet &= WriteFloat64(fpTable, poGeomField->GetZMin());
239 50 : bRet &= WriteFloat64(fpTable, poGeomField->GetZMax());
240 : }
241 162 : m_bDirtyGeomFieldBBox = false;
242 : }
243 :
244 8236 : if (m_bDirtyGeomFieldSpatialIndexGridRes && fpTable)
245 : {
246 127 : VSIFSeekL(fpTable,
247 127 : m_nOffsetFieldDesc + m_nGeomFieldSpatialIndexGridResSubOffset,
248 : SEEK_SET);
249 127 : const auto poGeomField = cpl::down_cast<const FileGDBGeomField *>(
250 127 : m_apoFields[m_iGeomField].get());
251 : const auto &adfSpatialIndexGridResolution =
252 127 : poGeomField->GetSpatialIndexGridResolution();
253 254 : for (double dfSize : adfSpatialIndexGridResolution)
254 127 : bRet &= WriteFloat64(fpTable, dfSize);
255 127 : m_bDirtyGeomFieldSpatialIndexGridRes = false;
256 : }
257 :
258 8236 : if (m_bDirtyHeader && fpTable)
259 : {
260 3731 : VSIFSeekL(fpTable, 4, SEEK_SET);
261 3731 : bRet &=
262 3731 : WriteUInt32(fpTable, static_cast<uint32_t>(m_nValidRecordCount));
263 3731 : m_nHeaderBufferMaxSize =
264 7462 : std::max(m_nHeaderBufferMaxSize,
265 3731 : std::max(m_nRowBufferMaxSize, m_nFieldDescLength));
266 3731 : bRet &= WriteUInt32(fpTable, m_nHeaderBufferMaxSize);
267 :
268 3731 : VSIFSeekL(fpTable, 24, SEEK_SET);
269 3731 : bRet &= WriteUInt64(fpTable, m_nFileSize);
270 3731 : bRet &= WriteUInt64(fpTable, m_nOffsetFieldDesc);
271 :
272 3731 : VSIFSeekL(fpTable, 0, SEEK_END);
273 3731 : CPLAssert(VSIFTellL(fpTable) == m_nFileSize);
274 3731 : m_bDirtyHeader = false;
275 : }
276 :
277 8236 : if (m_bDirtyTableXHeader && fpTableX)
278 : {
279 2979 : VSIFSeekL(fpTableX, 4, SEEK_SET);
280 2979 : bRet &=
281 2979 : WriteUInt32(fpTableX, static_cast<uint32_t>(m_n1024BlocksPresent));
282 2979 : bRet &=
283 2979 : WriteUInt32(fpTableX, static_cast<uint32_t>(m_nTotalRecordCount));
284 2979 : m_bDirtyTableXHeader = false;
285 : }
286 :
287 8236 : if (m_bDirtyTableXTrailer && fpTableX)
288 : {
289 2565 : m_nOffsetTableXTrailer =
290 2565 : TABLX_HEADER_SIZE +
291 2565 : m_nTablxOffsetSize * TABLX_FEATURES_PER_PAGE *
292 2565 : static_cast<vsi_l_offset>(m_n1024BlocksPresent);
293 2565 : VSIFSeekL(fpTableX, m_nOffsetTableXTrailer, SEEK_SET);
294 7695 : const uint32_t n1024BlocksTotal = static_cast<uint32_t>(
295 2565 : DIV_ROUND_UP(m_nTotalRecordCount, TABLX_FEATURES_PER_PAGE));
296 2565 : 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 7695 : DIV_ROUND_UP(
304 : DIV_ROUND_UP(static_cast<uint32_t>(m_abyTablXBlockMap.size()),
305 : 4),
306 7695 : 32) *
307 2565 : 32;
308 2565 : m_abyTablXBlockMap.resize(nBitmapInt32Words * 4);
309 2565 : bRet &= WriteUInt32(fpTableX, nBitmapInt32Words);
310 2565 : bRet &= WriteUInt32(fpTableX, n1024BlocksTotal);
311 2565 : bRet &=
312 2565 : WriteUInt32(fpTableX, static_cast<uint32_t>(m_n1024BlocksPresent));
313 2565 : uint32_t nTrailingZero32BitWords = 0;
314 2565 : for (int i = static_cast<int>(m_abyTablXBlockMap.size() / 4) - 1;
315 3464 : 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 2565 : const uint32_t nLeadingNonZero32BitWords =
327 : nBitmapInt32Words - nTrailingZero32BitWords;
328 2565 : bRet &= WriteUInt32(fpTableX, nLeadingNonZero32BitWords);
329 2565 : 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 2565 : m_bDirtyTableXTrailer = false;
348 : }
349 :
350 8236 : if (m_bFreelistCanBeDeleted)
351 : {
352 5 : DeleteFreeList();
353 : }
354 :
355 8236 : if (fpTable)
356 8173 : VSIFFlushL(fpTable);
357 :
358 8236 : if (fpTableX)
359 8172 : VSIFFlushL(fpTableX);
360 :
361 8236 : 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 278 : static bool EncodeEnvelope(std::vector<GByte> &abyBuffer,
384 : const FileGDBGeomField *poGeomField,
385 : const OGRGeometry *poGeom)
386 : {
387 278 : OGREnvelope oEnvelope;
388 278 : poGeom->getEnvelope(&oEnvelope);
389 :
390 : double dfVal;
391 :
392 278 : dfVal = (oEnvelope.MinX - poGeomField->GetXOrigin()) *
393 278 : poGeomField->GetXYScale();
394 278 : CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode X value");
395 278 : WriteVarUInt(abyBuffer, static_cast<uint64_t>(dfVal + 0.5));
396 :
397 278 : dfVal = (oEnvelope.MinY - poGeomField->GetYOrigin()) *
398 278 : poGeomField->GetXYScale();
399 278 : CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode Y value");
400 278 : WriteVarUInt(abyBuffer, static_cast<uint64_t>(dfVal + 0.5));
401 :
402 278 : dfVal = (oEnvelope.MaxX - oEnvelope.MinX) * poGeomField->GetXYScale();
403 278 : CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode X value");
404 278 : WriteVarUInt(abyBuffer, static_cast<uint64_t>(dfVal + 0.5));
405 :
406 278 : dfVal = (oEnvelope.MaxY - oEnvelope.MinY) * poGeomField->GetXYScale();
407 278 : CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode Y value");
408 278 : WriteVarUInt(abyBuffer, static_cast<uint64_t>(dfVal + 0.5));
409 :
410 278 : return true;
411 : }
412 :
413 : /************************************************************************/
414 : /* EncodeGeometry() */
415 : /************************************************************************/
416 :
417 4094 : bool FileGDBTable::EncodeGeometry(const FileGDBGeomField *poGeomField,
418 : const OGRGeometry *poGeom)
419 : {
420 4094 : m_abyGeomBuffer.clear();
421 :
422 4094 : const auto bIs3D = poGeom->Is3D();
423 4094 : const auto bIsMeasured = poGeom->IsMeasured();
424 :
425 : const auto WriteEndOfCurveOrSurface =
426 36515 : [this, bIs3D, bIsMeasured, poGeomField, poGeom](int nCurveDescrCount)
427 : {
428 236 : WriteVarUInt(m_abyGeomBuffer, static_cast<uint32_t>(m_adfX.size()));
429 236 : if (m_adfX.empty())
430 8 : return true;
431 228 : WriteVarUInt(m_abyGeomBuffer,
432 228 : static_cast<uint32_t>(m_anNumberPointsPerPart.size()));
433 228 : if (nCurveDescrCount > 0)
434 12 : WriteVarUInt(m_abyGeomBuffer, nCurveDescrCount);
435 :
436 228 : if (!EncodeEnvelope(m_abyGeomBuffer, poGeomField, poGeom))
437 0 : return false;
438 :
439 284 : for (int iPart = 0;
440 284 : iPart < static_cast<int>(m_anNumberPointsPerPart.size()) - 1;
441 : ++iPart)
442 : {
443 56 : WriteVarUInt(m_abyGeomBuffer, m_anNumberPointsPerPart[iPart]);
444 : }
445 :
446 : {
447 228 : int64_t nLastX = 0;
448 228 : int64_t nLastY = 0;
449 3805 : for (size_t i = 0; i < m_adfX.size(); ++i)
450 : {
451 : double dfVal =
452 3577 : std::round((m_adfX[i] - poGeomField->GetXOrigin()) *
453 3577 : poGeomField->GetXYScale());
454 3577 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastX,
455 : "Cannot encode X value");
456 3577 : const int64_t nX = static_cast<int64_t>(dfVal);
457 3577 : WriteVarInt(m_abyGeomBuffer, nX - nLastX);
458 :
459 3577 : dfVal = std::round((m_adfY[i] - poGeomField->GetYOrigin()) *
460 3577 : poGeomField->GetXYScale());
461 3577 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastY,
462 : "Cannot encode Y value");
463 3577 : const int64_t nY = static_cast<int64_t>(dfVal);
464 3577 : WriteVarInt(m_abyGeomBuffer, nY - nLastY);
465 :
466 3577 : nLastX = nX;
467 3577 : nLastY = nY;
468 : }
469 : }
470 :
471 228 : 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 228 : 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 228 : 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 228 : return true;
513 4094 : };
514 :
515 : const auto ReserveXYZMArrays =
516 1220 : [this, bIs3D, bIsMeasured](const size_t nAdditionalSize)
517 : {
518 252 : size_t nNewMinSize = m_adfX.size() + nAdditionalSize;
519 252 : if (nNewMinSize > m_adfX.capacity())
520 : {
521 111 : size_t nNewCapacity = nNewMinSize;
522 111 : if (m_adfX.capacity() < std::numeric_limits<size_t>::max() / 2)
523 : {
524 111 : nNewCapacity = std::max(nNewCapacity, 2 * m_adfX.capacity());
525 : }
526 111 : m_adfX.reserve(nNewCapacity);
527 111 : m_adfY.reserve(nNewCapacity);
528 111 : if (bIs3D)
529 33 : m_adfZ.reserve(nNewCapacity);
530 111 : if (bIsMeasured)
531 17 : m_adfM.reserve(nNewCapacity);
532 : }
533 252 : };
534 :
535 4094 : const auto eFlatType = wkbFlatten(poGeom->getGeometryType());
536 4094 : 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 162 : case wkbPolygon:
917 : case wkbCurvePolygon:
918 : case wkbMultiPolygon:
919 : case wkbMultiSurface:
920 : {
921 162 : m_abyCurvePart.clear();
922 162 : m_anNumberPointsPerPart.clear();
923 162 : m_adfX.clear();
924 162 : m_adfY.clear();
925 162 : m_adfZ.clear();
926 162 : m_adfM.clear();
927 :
928 162 : int nCurveDescrCount = 0;
929 : const auto ProcessSurface =
930 175 : [this, bIs3D, bIsMeasured, &nCurveDescrCount,
931 10165 : &ReserveXYZMArrays](const OGRSurface *poSurface)
932 : {
933 175 : if (const auto poPolygon =
934 175 : dynamic_cast<const OGRPolygon *>(poSurface))
935 : {
936 164 : bool bFirstRing = true;
937 :
938 164 : std::size_t nTotalSize = 0;
939 345 : for (const auto *poLS : *poPolygon)
940 : {
941 181 : nTotalSize += poLS->getNumPoints();
942 : }
943 164 : ReserveXYZMArrays(nTotalSize);
944 :
945 345 : for (const auto *poLS : *poPolygon)
946 : {
947 181 : const int nNumPoints = poLS->getNumPoints();
948 181 : m_anNumberPointsPerPart.push_back(nNumPoints);
949 181 : const bool bIsClockwise = poLS->isClockwise();
950 181 : const bool bReverseOrder = bFirstRing != bIsClockwise;
951 181 : bFirstRing = false;
952 2494 : for (int i = 0; i < nNumPoints; ++i)
953 : {
954 2313 : const int j =
955 2313 : bReverseOrder ? nNumPoints - 1 - i : i;
956 2313 : m_adfX.push_back(poLS->getX(j));
957 2313 : m_adfY.push_back(poLS->getY(j));
958 2313 : if (bIs3D)
959 162 : m_adfZ.push_back(poLS->getZ(j));
960 2313 : if (bIsMeasured)
961 42 : m_adfM.push_back(poLS->getM(j));
962 : }
963 : }
964 : }
965 11 : else if (const auto poCurvePoly =
966 11 : dynamic_cast<const OGRCurvePolygon *>(poSurface))
967 : {
968 11 : bool bFirstRing = true;
969 26 : for (const auto *poRing : *poCurvePoly)
970 : {
971 15 : const bool bIsClockwise = poRing->isClockwise();
972 15 : const bool bReverseOrder = bFirstRing != bIsClockwise;
973 15 : bFirstRing = false;
974 15 : if (auto poCC =
975 15 : dynamic_cast<const OGRCompoundCurve *>(poRing))
976 : {
977 3 : const size_t nSizeBefore = m_adfX.size();
978 3 : bool bFirstSubCurve = true;
979 3 : const int nNumCurves = poCC->getNumCurves();
980 12 : for (int iSubCurve = 0; iSubCurve < nNumCurves;
981 : ++iSubCurve)
982 : {
983 15 : const OGRCurve *poSubCurve = poCC->getCurve(
984 6 : bReverseOrder ? nNumCurves - 1 - iSubCurve
985 : : iSubCurve);
986 9 : if (auto poLS =
987 0 : dynamic_cast<const OGRLineString *>(
988 9 : poSubCurve))
989 : {
990 6 : const int nNumPoints = poLS->getNumPoints();
991 18 : for (int i = (bFirstSubCurve ? 0 : 1);
992 18 : i < nNumPoints; ++i)
993 : {
994 12 : const int j = bReverseOrder
995 12 : ? nNumPoints - 1 - i
996 : : i;
997 12 : m_adfX.push_back(poLS->getX(j));
998 12 : m_adfY.push_back(poLS->getY(j));
999 12 : if (bIs3D)
1000 0 : m_adfZ.push_back(poLS->getZ(j));
1001 12 : if (bIsMeasured)
1002 0 : m_adfM.push_back(poLS->getM(j));
1003 : }
1004 : }
1005 0 : else if (auto poCS = dynamic_cast<
1006 : const OGRCircularString *>(
1007 3 : poSubCurve))
1008 : {
1009 3 : const int nNumPoints = poCS->getNumPoints();
1010 9 : for (int i = 0; i < nNumPoints; i++)
1011 : {
1012 6 : if (i > 0 || bFirstSubCurve)
1013 : {
1014 3 : const int j =
1015 : bReverseOrder
1016 3 : ? nNumPoints - 1 - i
1017 : : i;
1018 3 : m_adfX.push_back(poCS->getX(j));
1019 3 : m_adfY.push_back(poCS->getY(j));
1020 3 : if (bIs3D)
1021 0 : m_adfZ.push_back(poCS->getZ(j));
1022 3 : if (bIsMeasured)
1023 0 : m_adfM.push_back(poCS->getM(j));
1024 : }
1025 6 : if (i + 1 < nNumPoints)
1026 : {
1027 3 : ++nCurveDescrCount;
1028 3 : ++i;
1029 3 : const int j =
1030 : bReverseOrder
1031 3 : ? nNumPoints - 1 - i
1032 : : i;
1033 3 : WriteVarUInt(
1034 3 : m_abyCurvePart,
1035 : static_cast<uint32_t>(
1036 3 : m_adfX.size() - 1));
1037 3 : WriteUInt8(m_abyCurvePart,
1038 : EXT_SHAPE_SEGMENT_ARC);
1039 3 : WriteFloat64(m_abyCurvePart,
1040 : poCS->getX(j));
1041 3 : WriteFloat64(m_abyCurvePart,
1042 : poCS->getY(j));
1043 3 : WriteUInt32(m_abyCurvePart,
1044 : (1 << 7)); // DefinedIP
1045 : }
1046 : }
1047 : }
1048 : else
1049 : {
1050 0 : CPLAssert(false);
1051 : }
1052 9 : bFirstSubCurve = false;
1053 : }
1054 6 : m_anNumberPointsPerPart.push_back(
1055 3 : static_cast<uint32_t>(m_adfX.size() -
1056 : nSizeBefore));
1057 : }
1058 12 : else if (const auto poLS =
1059 0 : dynamic_cast<const OGRLineString *>(
1060 12 : poRing))
1061 : {
1062 7 : const int nNumPoints = poLS->getNumPoints();
1063 7 : m_anNumberPointsPerPart.push_back(nNumPoints);
1064 38 : for (int i = 0; i < nNumPoints; ++i)
1065 : {
1066 31 : const int j =
1067 31 : bReverseOrder ? nNumPoints - 1 - i : i;
1068 31 : m_adfX.push_back(poLS->getX(j));
1069 31 : m_adfY.push_back(poLS->getY(j));
1070 31 : if (bIs3D)
1071 0 : m_adfZ.push_back(poLS->getZ(j));
1072 31 : if (bIsMeasured)
1073 0 : m_adfM.push_back(poLS->getM(j));
1074 : }
1075 : }
1076 5 : else if (const auto poCS =
1077 0 : dynamic_cast<const OGRCircularString *>(
1078 5 : poRing))
1079 : {
1080 5 : const int nNumPoints = poCS->getNumPoints();
1081 5 : const size_t nSizeBefore = m_adfX.size();
1082 20 : for (int i = 0; i < nNumPoints; i++)
1083 : {
1084 15 : int j = bReverseOrder ? nNumPoints - 1 - i : i;
1085 15 : m_adfX.push_back(poCS->getX(j));
1086 15 : m_adfY.push_back(poCS->getY(j));
1087 15 : if (bIs3D)
1088 3 : m_adfZ.push_back(poCS->getZ(j));
1089 15 : if (bIsMeasured)
1090 3 : m_adfM.push_back(poCS->getM(j));
1091 15 : if (i + 1 < nNumPoints)
1092 : {
1093 10 : ++nCurveDescrCount;
1094 10 : ++i;
1095 10 : j = bReverseOrder ? nNumPoints - 1 - i : i;
1096 10 : WriteVarUInt(m_abyCurvePart,
1097 : static_cast<uint32_t>(
1098 10 : m_adfX.size() - 1));
1099 10 : WriteUInt8(m_abyCurvePart,
1100 : EXT_SHAPE_SEGMENT_ARC);
1101 10 : WriteFloat64(m_abyCurvePart, poCS->getX(j));
1102 10 : WriteFloat64(m_abyCurvePart, poCS->getY(j));
1103 10 : WriteUInt32(m_abyCurvePart,
1104 : (1 << 7)); // DefinedIP
1105 : }
1106 : }
1107 10 : m_anNumberPointsPerPart.push_back(
1108 5 : static_cast<uint32_t>(m_adfX.size() -
1109 : nSizeBefore));
1110 : }
1111 : else
1112 : {
1113 0 : CPLAssert(false);
1114 : }
1115 : }
1116 : }
1117 : else
1118 : {
1119 0 : CPLAssert(false);
1120 : }
1121 175 : };
1122 :
1123 162 : if (eFlatType == wkbMultiPolygon || eFlatType == wkbMultiSurface)
1124 : {
1125 88 : const auto poMultiSurface = poGeom->toMultiSurface();
1126 189 : for (const auto *poSurface : *poMultiSurface)
1127 : {
1128 101 : ProcessSurface(poSurface);
1129 88 : }
1130 : }
1131 : else
1132 : {
1133 74 : ProcessSurface(poGeom->toSurface());
1134 : }
1135 :
1136 162 : if (nCurveDescrCount > 0)
1137 : {
1138 7 : WriteVarUInt(m_abyGeomBuffer,
1139 7 : SHPT_GENERALPOLYGON | (1U << 29) | // has curves
1140 7 : ((bIsMeasured ? 1U : 0U) << 30) |
1141 7 : ((bIs3D ? 1U : 0U) << 31));
1142 : }
1143 155 : else if (bIs3D)
1144 : {
1145 32 : if (bIsMeasured)
1146 : {
1147 5 : WriteUInt8(m_abyGeomBuffer,
1148 : static_cast<uint8_t>(SHPT_POLYGONZM));
1149 : }
1150 : else
1151 : {
1152 27 : WriteUInt8(m_abyGeomBuffer,
1153 : static_cast<uint8_t>(SHPT_POLYGONZ));
1154 : }
1155 : }
1156 : else
1157 : {
1158 123 : if (bIsMeasured)
1159 : {
1160 3 : WriteUInt8(m_abyGeomBuffer,
1161 : static_cast<uint8_t>(SHPT_POLYGONM));
1162 : }
1163 : else
1164 : {
1165 120 : WriteUInt8(m_abyGeomBuffer,
1166 : static_cast<uint8_t>(SHPT_POLYGON));
1167 : }
1168 : }
1169 :
1170 162 : return WriteEndOfCurveOrSurface(nCurveDescrCount);
1171 : }
1172 :
1173 23 : case wkbTIN:
1174 : case wkbPolyhedralSurface:
1175 : case wkbGeometryCollection:
1176 : {
1177 23 : int nParts = 0;
1178 46 : std::vector<int> anPartStart;
1179 46 : std::vector<int> anPartType;
1180 23 : int nPoints = 0;
1181 46 : std::vector<OGRRawPoint> aoPoints;
1182 46 : std::vector<double> adfZ;
1183 : OGRErr eErr =
1184 23 : OGRCreateMultiPatch(poGeom, TRUE, nParts, anPartStart,
1185 : anPartType, nPoints, aoPoints, adfZ);
1186 23 : if (eErr != OGRERR_NONE)
1187 2 : return false;
1188 :
1189 21 : WriteUInt8(m_abyGeomBuffer, static_cast<uint8_t>(SHPT_MULTIPATCH));
1190 21 : WriteVarUInt(m_abyGeomBuffer, nPoints);
1191 21 : if (nPoints != 0)
1192 : {
1193 : // Apparently we must write the size of the extended buffer
1194 : // shape representation, even if we don't exactly follow this
1195 : // format when writing to FileGDB files...
1196 21 : int nShapeBufferSize =
1197 : 4; // All types start with integer type number.
1198 21 : nShapeBufferSize += 16 * 2; // xy bbox.
1199 21 : nShapeBufferSize += 4; // nparts.
1200 21 : nShapeBufferSize += 4; // npoints.
1201 21 : nShapeBufferSize += 4 * nParts; // panPartStart[nparts].
1202 21 : nShapeBufferSize += 4 * nParts; // panPartType[nparts].
1203 21 : nShapeBufferSize += 8 * 2 * nPoints; // xy points.
1204 21 : nShapeBufferSize += 16; // z bbox.
1205 21 : nShapeBufferSize += 8 * nPoints; // z points.
1206 21 : WriteVarUInt(m_abyGeomBuffer, nShapeBufferSize);
1207 :
1208 21 : WriteVarUInt(m_abyGeomBuffer, nParts);
1209 :
1210 21 : if (!EncodeEnvelope(m_abyGeomBuffer, poGeomField, poGeom))
1211 : {
1212 0 : return false;
1213 : }
1214 :
1215 62 : for (int i = 0; i < nParts - 1; i++)
1216 : {
1217 41 : WriteVarUInt(m_abyGeomBuffer,
1218 41 : anPartStart[i + 1] - anPartStart[i]);
1219 : }
1220 :
1221 83 : for (int i = 0; i < nParts; i++)
1222 : {
1223 62 : WriteVarUInt(m_abyGeomBuffer, anPartType[i]);
1224 : }
1225 :
1226 : {
1227 21 : int64_t nLastX = 0;
1228 21 : int64_t nLastY = 0;
1229 278 : for (int i = 0; i < nPoints; ++i)
1230 : {
1231 257 : double dfVal = std::round(
1232 257 : (aoPoints[i].x - poGeomField->GetXOrigin()) *
1233 257 : poGeomField->GetXYScale());
1234 257 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastX,
1235 : "Cannot encode value");
1236 257 : const int64_t nX = static_cast<int64_t>(dfVal);
1237 257 : WriteVarInt(m_abyGeomBuffer, nX - nLastX);
1238 :
1239 257 : dfVal = std::round(
1240 257 : (aoPoints[i].y - poGeomField->GetYOrigin()) *
1241 257 : poGeomField->GetXYScale());
1242 257 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastY,
1243 : "Cannot encode Y value");
1244 257 : const int64_t nY = static_cast<int64_t>(dfVal);
1245 257 : WriteVarInt(m_abyGeomBuffer, nY - nLastY);
1246 :
1247 257 : nLastX = nX;
1248 257 : nLastY = nY;
1249 : }
1250 : }
1251 :
1252 : {
1253 21 : int64_t nLastZ = 0;
1254 278 : for (int i = 0; i < nPoints; ++i)
1255 : {
1256 : double dfVal =
1257 257 : std::round((adfZ[i] - poGeomField->GetZOrigin()) *
1258 257 : poGeomField->GetZScale());
1259 257 : CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastZ,
1260 : "Bad Z value");
1261 257 : const int64_t nZ = static_cast<int64_t>(dfVal);
1262 257 : WriteVarInt(m_abyGeomBuffer, nZ - nLastZ);
1263 :
1264 257 : nLastZ = nZ;
1265 : }
1266 : }
1267 : }
1268 21 : return true;
1269 : }
1270 :
1271 0 : default:
1272 : {
1273 0 : CPLError(CE_Failure, CPLE_NotSupported,
1274 : "Unsupported geometry type");
1275 0 : return false;
1276 : }
1277 : }
1278 : }
1279 :
1280 : /************************************************************************/
1281 : /* EncodeFeature() */
1282 : /************************************************************************/
1283 :
1284 33593 : bool FileGDBTable::EncodeFeature(const std::vector<OGRField> &asRawFields,
1285 : const OGRGeometry *poGeom, int iSkipField)
1286 : {
1287 33593 : m_abyBuffer.clear();
1288 33593 : if (iSkipField >= 0 && m_apoFields[iSkipField]->IsNullable())
1289 22 : m_abyBuffer.resize(BIT_ARRAY_SIZE_IN_BYTES(m_nCountNullableFields - 1),
1290 22 : 0xFF);
1291 : else
1292 33571 : m_abyBuffer.resize(m_nNullableFieldsSizeInBytes, 0xFF);
1293 :
1294 33593 : if (asRawFields.size() != m_apoFields.size())
1295 : {
1296 0 : CPLError(CE_Failure, CPLE_AppDefined, "Bad size of asRawFields");
1297 0 : return false;
1298 : }
1299 33593 : int iNullableField = 0;
1300 172448 : for (int i = 0; i < static_cast<int>(m_apoFields.size()); ++i)
1301 : {
1302 138861 : if (i == iSkipField)
1303 26 : continue;
1304 138835 : auto &poField = m_apoFields[i];
1305 138835 : if (poField->GetType() == FGFT_OBJECTID)
1306 : {
1307 : // Implicit field
1308 25120 : continue;
1309 : }
1310 113715 : if (i == m_iGeomField)
1311 : {
1312 5081 : if (poGeom == nullptr)
1313 : {
1314 987 : if (!poField->IsNullable())
1315 : {
1316 1 : CPLError(CE_Failure, CPLE_AppDefined,
1317 : "Attempting to write null geometry in "
1318 : "non-nullable geometry field");
1319 1 : return false;
1320 : }
1321 986 : iNullableField++;
1322 986 : continue;
1323 : }
1324 :
1325 : auto poGeomField =
1326 4094 : cpl::down_cast<FileGDBGeomField *>(poField.get());
1327 4094 : if (!EncodeGeometry(poGeomField, poGeom))
1328 2 : return false;
1329 4092 : if (!poGeom->IsEmpty())
1330 : {
1331 4080 : OGREnvelope3D oEnvelope;
1332 4080 : poGeom->getEnvelope(&oEnvelope);
1333 4080 : m_bDirtyGeomFieldBBox = true;
1334 4080 : if (std::isnan(poGeomField->GetXMin()))
1335 : {
1336 156 : poGeomField->SetXYMinMax(oEnvelope.MinX, oEnvelope.MinY,
1337 : oEnvelope.MaxX, oEnvelope.MaxY);
1338 156 : poGeomField->SetZMinMax(oEnvelope.MinZ, oEnvelope.MaxZ);
1339 : }
1340 : else
1341 : {
1342 3924 : poGeomField->SetXYMinMax(
1343 3924 : std::min(poGeomField->GetXMin(), oEnvelope.MinX),
1344 3924 : std::min(poGeomField->GetYMin(), oEnvelope.MinY),
1345 3924 : std::max(poGeomField->GetXMax(), oEnvelope.MaxX),
1346 3924 : std::max(poGeomField->GetYMax(), oEnvelope.MaxY));
1347 3924 : poGeomField->SetZMinMax(
1348 3924 : std::min(poGeomField->GetZMin(), oEnvelope.MinZ),
1349 7848 : std::max(poGeomField->GetZMax(), oEnvelope.MaxZ));
1350 : }
1351 : }
1352 :
1353 4092 : if (m_abyGeomBuffer.size() + m_abyBuffer.size() >
1354 : static_cast<size_t>(INT_MAX))
1355 : {
1356 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large feature");
1357 0 : return false;
1358 : }
1359 :
1360 4092 : WriteVarUInt(m_abyBuffer, m_abyGeomBuffer.size());
1361 4092 : m_abyBuffer.insert(m_abyBuffer.end(), m_abyGeomBuffer.begin(),
1362 8184 : m_abyGeomBuffer.end());
1363 :
1364 4092 : if (poField->IsNullable())
1365 : {
1366 4090 : m_abyBuffer[iNullableField / 8] &= ~(1 << (iNullableField % 8));
1367 4090 : iNullableField++;
1368 : }
1369 4092 : continue;
1370 : }
1371 :
1372 217268 : if (OGR_RawField_IsNull(&asRawFields[i]) ||
1373 108634 : OGR_RawField_IsUnset(&asRawFields[i]))
1374 : {
1375 6016 : if (!poField->IsNullable())
1376 : {
1377 3 : CPLError(CE_Failure, CPLE_AppDefined,
1378 : "Attempting to write null/empty field in non-nullable "
1379 : "field");
1380 3 : return false;
1381 : }
1382 6013 : iNullableField++;
1383 6013 : continue;
1384 : }
1385 :
1386 102618 : switch (poField->GetType())
1387 : {
1388 0 : case FGFT_UNDEFINED:
1389 : {
1390 0 : CPLAssert(false);
1391 : break;
1392 : }
1393 :
1394 4216 : case FGFT_INT16:
1395 : {
1396 4216 : WriteInt16(m_abyBuffer,
1397 4216 : static_cast<int16_t>(asRawFields[i].Integer));
1398 4216 : break;
1399 : }
1400 :
1401 4585 : case FGFT_INT32:
1402 : {
1403 4585 : WriteInt32(m_abyBuffer, asRawFields[i].Integer);
1404 4585 : break;
1405 : }
1406 :
1407 344 : case FGFT_FLOAT32:
1408 : {
1409 344 : WriteFloat32(m_abyBuffer,
1410 344 : static_cast<float>(asRawFields[i].Real));
1411 344 : break;
1412 : }
1413 :
1414 4603 : case FGFT_FLOAT64:
1415 : {
1416 4603 : WriteFloat64(m_abyBuffer, asRawFields[i].Real);
1417 4603 : break;
1418 : }
1419 :
1420 57671 : case FGFT_STRING:
1421 : case FGFT_XML:
1422 : {
1423 57671 : if (m_bStringsAreUTF8 || poField->GetType() == FGFT_XML)
1424 : {
1425 57670 : const auto nLen = strlen(asRawFields[i].String);
1426 57670 : WriteVarUInt(m_abyBuffer, nLen);
1427 57670 : if (nLen > 0)
1428 : {
1429 56060 : if (nLen + m_abyBuffer.size() >
1430 : static_cast<size_t>(INT_MAX))
1431 : {
1432 0 : CPLError(CE_Failure, CPLE_AppDefined,
1433 : "Too large feature");
1434 0 : return false;
1435 : }
1436 0 : m_abyBuffer.insert(m_abyBuffer.end(),
1437 : reinterpret_cast<const uint8_t *>(
1438 56060 : asRawFields[i].String),
1439 : reinterpret_cast<const uint8_t *>(
1440 112120 : asRawFields[i].String) +
1441 112120 : nLen);
1442 : }
1443 : }
1444 : else
1445 : {
1446 1 : WriteUTF16String(m_abyBuffer, asRawFields[i].String,
1447 : NUMBER_OF_BYTES_ON_VARUINT);
1448 : }
1449 57671 : break;
1450 : }
1451 :
1452 236 : case FGFT_DATETIME:
1453 : case FGFT_DATE:
1454 : {
1455 236 : WriteFloat64(m_abyBuffer,
1456 : FileGDBOGRDateToDoubleDate(
1457 236 : &asRawFields[i], /* bConvertToUTC = */ true,
1458 236 : poField->IsHighPrecision()));
1459 236 : break;
1460 : }
1461 :
1462 0 : case FGFT_OBJECTID:
1463 : {
1464 0 : CPLAssert(false); // not possible given above processing
1465 : break;
1466 : }
1467 :
1468 0 : case FGFT_GEOMETRY:
1469 : {
1470 0 : CPLAssert(false); // not possible given above processing
1471 : break;
1472 : }
1473 :
1474 359 : case FGFT_BINARY:
1475 : {
1476 359 : WriteVarUInt(m_abyBuffer, asRawFields[i].Binary.nCount);
1477 359 : if (asRawFields[i].Binary.nCount)
1478 : {
1479 359 : if (static_cast<size_t>(asRawFields[i].Binary.nCount) +
1480 359 : m_abyBuffer.size() >
1481 : static_cast<size_t>(INT_MAX))
1482 : {
1483 0 : CPLError(CE_Failure, CPLE_AppDefined,
1484 : "Too large feature");
1485 0 : return false;
1486 : }
1487 0 : m_abyBuffer.insert(m_abyBuffer.end(),
1488 359 : asRawFields[i].Binary.paData,
1489 718 : asRawFields[i].Binary.paData +
1490 718 : asRawFields[i].Binary.nCount);
1491 : }
1492 359 : break;
1493 : }
1494 :
1495 0 : case FGFT_RASTER:
1496 : {
1497 : // Not handled for now
1498 0 : CPLAssert(false);
1499 : break;
1500 : }
1501 :
1502 30564 : case FGFT_GUID:
1503 : case FGFT_GLOBALID:
1504 : {
1505 30564 : const auto nLen = strlen(asRawFields[i].String);
1506 30564 : if (nLen != 38)
1507 : {
1508 0 : CPLError(CE_Failure, CPLE_AppDefined,
1509 : "Bad size for UUID field");
1510 0 : return false;
1511 : }
1512 61128 : std::vector<unsigned> anVals(16);
1513 30564 : sscanf(asRawFields[i].String,
1514 : "{%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%"
1515 : "02X%02X%02X%02X}",
1516 30564 : &anVals[3], &anVals[2], &anVals[1], &anVals[0],
1517 30564 : &anVals[5], &anVals[4], &anVals[7], &anVals[6],
1518 30564 : &anVals[8], &anVals[9], &anVals[10], &anVals[11],
1519 30564 : &anVals[12], &anVals[13], &anVals[14], &anVals[15]);
1520 519588 : for (auto v : anVals)
1521 : {
1522 489024 : m_abyBuffer.push_back(static_cast<uint8_t>(v));
1523 : }
1524 30564 : break;
1525 : }
1526 :
1527 22 : case FGFT_INT64:
1528 : {
1529 22 : WriteInt64(m_abyBuffer, asRawFields[i].Integer64);
1530 22 : break;
1531 : }
1532 :
1533 6 : case FGFT_TIME:
1534 : {
1535 6 : WriteFloat64(m_abyBuffer,
1536 6 : FileGDBOGRTimeToDoubleTime(&asRawFields[i]));
1537 6 : break;
1538 : }
1539 :
1540 12 : case FGFT_DATETIME_WITH_OFFSET:
1541 : {
1542 12 : WriteFloat64(m_abyBuffer, FileGDBOGRDateToDoubleDate(
1543 12 : &asRawFields[i],
1544 : /* bConvertToUTC = */ false,
1545 : /* bIsHighPrecision = */ true));
1546 12 : if (asRawFields[i].Date.TZFlag > 1)
1547 : {
1548 12 : WriteInt16(m_abyBuffer,
1549 : static_cast<int16_t>(
1550 12 : (asRawFields[i].Date.TZFlag - 100) * 15));
1551 : }
1552 : else
1553 : {
1554 0 : WriteInt16(m_abyBuffer, 0);
1555 : }
1556 12 : break;
1557 : }
1558 : }
1559 :
1560 102618 : if (poField->IsNullable())
1561 : {
1562 42568 : m_abyBuffer[iNullableField / 8] &= ~(1 << (iNullableField % 8));
1563 42568 : iNullableField++;
1564 : }
1565 : }
1566 :
1567 33587 : if (m_abyBuffer.size() > static_cast<size_t>(INT_MAX))
1568 : {
1569 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large feature");
1570 0 : return false;
1571 : }
1572 :
1573 33587 : return true;
1574 : }
1575 :
1576 : /************************************************************************/
1577 : /* SeekIntoTableXForNewFeature() */
1578 : /************************************************************************/
1579 :
1580 33503 : bool FileGDBTable::SeekIntoTableXForNewFeature(int nObjectID)
1581 : {
1582 : int iCorrectedRow;
1583 33503 : bool bWriteEmptyPageAtEnd = false;
1584 33503 : const uint32_t nPageSize = TABLX_FEATURES_PER_PAGE * m_nTablxOffsetSize;
1585 33503 : const int nTotalRecordCount = static_cast<int>(m_nTotalRecordCount);
1586 :
1587 33503 : if (m_abyTablXBlockMap.empty())
1588 : {
1589 : // Is the OID to write in the current allocated pages, or in the next
1590 : // page ?
1591 33473 : if ((nObjectID - 1) / TABLX_FEATURES_PER_PAGE <=
1592 33473 : ((nTotalRecordCount == 0)
1593 33473 : ? 0
1594 31532 : : (1 + (nTotalRecordCount - 1) / TABLX_FEATURES_PER_PAGE)))
1595 : {
1596 33451 : iCorrectedRow = nObjectID - 1;
1597 33451 : const auto n1024BlocksPresentBefore = m_n1024BlocksPresent;
1598 33451 : m_n1024BlocksPresent =
1599 33451 : DIV_ROUND_UP(std::max(nTotalRecordCount, nObjectID),
1600 : TABLX_FEATURES_PER_PAGE);
1601 33451 : bWriteEmptyPageAtEnd =
1602 33451 : m_n1024BlocksPresent > n1024BlocksPresentBefore;
1603 : }
1604 : else
1605 : {
1606 : // No, then we have a sparse table, and need to use a bitmap
1607 22 : m_abyTablXBlockMap.resize(
1608 22 : (DIV_ROUND_UP(nObjectID, TABLX_FEATURES_PER_PAGE) + 7) / 8);
1609 22 : for (int i = 0;
1610 26 : i < DIV_ROUND_UP(nTotalRecordCount, TABLX_FEATURES_PER_PAGE);
1611 : ++i)
1612 4 : m_abyTablXBlockMap[i / 8] |= (1 << (i % 8));
1613 22 : const int iBlock = (nObjectID - 1) / TABLX_FEATURES_PER_PAGE;
1614 22 : m_abyTablXBlockMap[iBlock / 8] |= (1 << (iBlock % 8));
1615 22 : iCorrectedRow =
1616 22 : DIV_ROUND_UP(nTotalRecordCount, TABLX_FEATURES_PER_PAGE) *
1617 : TABLX_FEATURES_PER_PAGE +
1618 22 : ((nObjectID - 1) % TABLX_FEATURES_PER_PAGE);
1619 22 : m_n1024BlocksPresent++;
1620 22 : bWriteEmptyPageAtEnd = true;
1621 : }
1622 : }
1623 : else
1624 : {
1625 30 : const int iBlock = (nObjectID - 1) / TABLX_FEATURES_PER_PAGE;
1626 :
1627 30 : if (nObjectID <= nTotalRecordCount)
1628 : {
1629 16 : CPLAssert(iBlock / 8 < static_cast<int>(m_abyTablXBlockMap.size()));
1630 16 : if (TEST_BIT(m_abyTablXBlockMap.data(), iBlock) == 0)
1631 : {
1632 : // This requires rewriting the gdbtablx file to insert
1633 : // a new page
1634 10 : GUInt32 nCountBlocksBefore = 0;
1635 16 : for (int i = 0; i < iBlock; i++)
1636 6 : nCountBlocksBefore +=
1637 6 : TEST_BIT(m_abyTablXBlockMap.data(), i) != 0;
1638 :
1639 10 : std::vector<GByte> abyTmp(nPageSize);
1640 10 : uint64_t nOffset =
1641 10 : TABLX_HEADER_SIZE + m_n1024BlocksPresent * nPageSize;
1642 22 : for (int i = static_cast<int>(m_n1024BlocksPresent - 1);
1643 22 : i >= static_cast<int>(nCountBlocksBefore); --i)
1644 : {
1645 12 : nOffset -= nPageSize;
1646 12 : VSIFSeekL(m_fpTableX, nOffset, SEEK_SET);
1647 12 : if (VSIFReadL(abyTmp.data(), nPageSize, 1, m_fpTableX) != 1)
1648 : {
1649 0 : CPLError(CE_Failure, CPLE_FileIO,
1650 : "Cannot read .gdtablx page at offset %u",
1651 : static_cast<uint32_t>(nOffset));
1652 0 : return false;
1653 : }
1654 12 : VSIFSeekL(m_fpTableX, VSIFTellL(m_fpTableX), SEEK_SET);
1655 12 : if (VSIFWriteL(abyTmp.data(), nPageSize, 1, m_fpTableX) !=
1656 : 1)
1657 : {
1658 0 : CPLError(CE_Failure, CPLE_FileIO,
1659 : "Cannot rewrite .gdtablx page of offset %u",
1660 : static_cast<uint32_t>(nOffset));
1661 0 : return false;
1662 : }
1663 : }
1664 10 : abyTmp.clear();
1665 10 : abyTmp.resize(nPageSize);
1666 10 : nOffset = TABLX_HEADER_SIZE +
1667 10 : static_cast<uint64_t>(nCountBlocksBefore) * nPageSize;
1668 10 : VSIFSeekL(m_fpTableX, nOffset, SEEK_SET);
1669 10 : if (VSIFWriteL(abyTmp.data(), nPageSize, 1, m_fpTableX) != 1)
1670 : {
1671 0 : CPLError(CE_Failure, CPLE_FileIO,
1672 : "Cannot write empty .gdtablx page of offset %u",
1673 : static_cast<uint32_t>(nOffset));
1674 0 : return false;
1675 : }
1676 10 : m_abyTablXBlockMap[iBlock / 8] |= (1 << (iBlock % 8));
1677 10 : m_n1024BlocksPresent++;
1678 10 : m_bDirtyTableXTrailer = true;
1679 10 : m_nOffsetTableXTrailer = 0;
1680 10 : m_nCountBlocksBeforeIBlockIdx = iBlock;
1681 10 : m_nCountBlocksBeforeIBlockValue = nCountBlocksBefore;
1682 : }
1683 : }
1684 28 : else if (DIV_ROUND_UP(nObjectID, TABLX_FEATURES_PER_PAGE) >
1685 14 : DIV_ROUND_UP(nTotalRecordCount, TABLX_FEATURES_PER_PAGE))
1686 : {
1687 8 : m_abyTablXBlockMap.resize(
1688 8 : (DIV_ROUND_UP(nObjectID, TABLX_FEATURES_PER_PAGE) + 7) / 8);
1689 8 : m_abyTablXBlockMap[iBlock / 8] |= (1 << (iBlock % 8));
1690 8 : m_n1024BlocksPresent++;
1691 8 : bWriteEmptyPageAtEnd = true;
1692 : }
1693 :
1694 30 : GUInt32 nCountBlocksBefore = 0;
1695 : // In case of sequential access, optimization to avoid recomputing
1696 : // the number of blocks since the beginning of the map
1697 30 : if (iBlock >= m_nCountBlocksBeforeIBlockIdx)
1698 : {
1699 30 : nCountBlocksBefore = m_nCountBlocksBeforeIBlockValue;
1700 70 : for (int i = m_nCountBlocksBeforeIBlockIdx; i < iBlock; i++)
1701 40 : nCountBlocksBefore +=
1702 40 : TEST_BIT(m_abyTablXBlockMap.data(), i) != 0;
1703 : }
1704 : else
1705 : {
1706 0 : nCountBlocksBefore = 0;
1707 0 : for (int i = 0; i < iBlock; i++)
1708 0 : nCountBlocksBefore +=
1709 0 : TEST_BIT(m_abyTablXBlockMap.data(), i) != 0;
1710 : }
1711 :
1712 30 : m_nCountBlocksBeforeIBlockIdx = iBlock;
1713 30 : m_nCountBlocksBeforeIBlockValue = nCountBlocksBefore;
1714 30 : iCorrectedRow = nCountBlocksBefore * TABLX_FEATURES_PER_PAGE +
1715 30 : ((nObjectID - 1) % TABLX_FEATURES_PER_PAGE);
1716 : }
1717 :
1718 33503 : if (bWriteEmptyPageAtEnd)
1719 : {
1720 1971 : m_bDirtyTableXTrailer = true;
1721 1971 : m_nOffsetTableXTrailer = 0;
1722 1971 : std::vector<GByte> abyTmp(nPageSize);
1723 1971 : uint64_t nOffset =
1724 : TABLX_HEADER_SIZE +
1725 1971 : static_cast<uint64_t>(m_n1024BlocksPresent - 1) * nPageSize;
1726 1971 : VSIFSeekL(m_fpTableX, nOffset, SEEK_SET);
1727 1971 : if (VSIFWriteL(abyTmp.data(), nPageSize, 1, m_fpTableX) != 1)
1728 : {
1729 0 : CPLError(CE_Failure, CPLE_FileIO,
1730 : "Cannot write empty .gdtablx page of offset %u",
1731 : static_cast<uint32_t>(nOffset));
1732 0 : return false;
1733 : }
1734 : }
1735 :
1736 33503 : const uint64_t nOffset =
1737 : TABLX_HEADER_SIZE +
1738 33503 : static_cast<uint64_t>(iCorrectedRow) * m_nTablxOffsetSize;
1739 33503 : VSIFSeekL(m_fpTableX, nOffset, SEEK_SET);
1740 :
1741 33503 : return true;
1742 : }
1743 :
1744 : /************************************************************************/
1745 : /* WriteFeatureOffset() */
1746 : /************************************************************************/
1747 :
1748 2752 : void FileGDBTable::WriteFeatureOffset(uint64_t nFeatureOffset,
1749 : GByte *pabyBuffer)
1750 : {
1751 2752 : CPL_LSBPTR64(&nFeatureOffset);
1752 2752 : memcpy(pabyBuffer, &nFeatureOffset, m_nTablxOffsetSize);
1753 2752 : }
1754 :
1755 : /************************************************************************/
1756 : /* WriteFeatureOffset() */
1757 : /************************************************************************/
1758 :
1759 36179 : bool FileGDBTable::WriteFeatureOffset(uint64_t nFeatureOffset)
1760 : {
1761 36179 : CPL_LSBPTR64(&nFeatureOffset);
1762 36179 : return VSIFWriteL(&nFeatureOffset, m_nTablxOffsetSize, 1, m_fpTableX) == 1;
1763 : }
1764 :
1765 : /************************************************************************/
1766 : /* CreateFeature() */
1767 : /************************************************************************/
1768 :
1769 33511 : bool FileGDBTable::CreateFeature(const std::vector<OGRField> &asRawFields,
1770 : const OGRGeometry *poGeom, int *pnFID)
1771 : {
1772 33511 : if (!m_bUpdate)
1773 0 : return false;
1774 :
1775 33511 : if (m_bDirtyFieldDescriptors && !WriteFieldDescriptors(m_fpTable))
1776 0 : return false;
1777 :
1778 : int nObjectID;
1779 33511 : if (pnFID != nullptr && *pnFID > 0)
1780 : {
1781 130 : if (*pnFID <= m_nTotalRecordCount &&
1782 23 : GetOffsetInTableForRow((*pnFID) - 1) != 0)
1783 : {
1784 2 : CPLError(
1785 : CE_Failure, CPLE_AppDefined,
1786 : "Cannot create feature of ID %d because one already exists",
1787 : *pnFID);
1788 2 : return false;
1789 : }
1790 105 : nObjectID = *pnFID;
1791 : }
1792 : else
1793 : {
1794 33404 : if (m_nTotalRecordCount == std::numeric_limits<int>::max())
1795 : {
1796 0 : CPLError(CE_Failure, CPLE_AppDefined,
1797 : "Maximum number of records per table reached");
1798 0 : return false;
1799 : }
1800 33404 : nObjectID = static_cast<int>(m_nTotalRecordCount + 1);
1801 : }
1802 :
1803 : try
1804 : {
1805 33509 : if (!EncodeFeature(asRawFields, poGeom, -1))
1806 6 : return false;
1807 : }
1808 0 : catch (const std::exception &e)
1809 : {
1810 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
1811 0 : return false;
1812 : }
1813 :
1814 33503 : const uint64_t nFreeOffset = GetOffsetOfFreeAreaFromFreeList(
1815 33503 : static_cast<uint32_t>(sizeof(uint32_t) + m_abyBuffer.size()));
1816 33503 : if (nFreeOffset == OFFSET_MINUS_ONE)
1817 : {
1818 30887 : if (((m_nFileSize + m_abyBuffer.size()) >> (8 * m_nTablxOffsetSize)) !=
1819 : 0)
1820 : {
1821 0 : CPLError(CE_Failure, CPLE_AppDefined,
1822 : "Maximum file size for m_nTablxOffsetSize = %u reached",
1823 : m_nTablxOffsetSize);
1824 0 : return false;
1825 : }
1826 : }
1827 :
1828 33503 : if (!SeekIntoTableXForNewFeature(nObjectID))
1829 0 : return false;
1830 :
1831 33503 : if (nFreeOffset == OFFSET_MINUS_ONE)
1832 : {
1833 30887 : VSIFSeekL(m_fpTable, m_nFileSize, SEEK_SET);
1834 : }
1835 : else
1836 : {
1837 2616 : VSIFSeekL(m_fpTable, nFreeOffset, SEEK_SET);
1838 : }
1839 33503 : if (!WriteUInt32(m_fpTable, static_cast<uint32_t>(m_abyBuffer.size())))
1840 0 : return false;
1841 67003 : if (!m_abyBuffer.empty() &&
1842 33500 : VSIFWriteL(m_abyBuffer.data(), 1, m_abyBuffer.size(), m_fpTable) !=
1843 33500 : m_abyBuffer.size())
1844 : {
1845 0 : return false;
1846 : }
1847 :
1848 33503 : if (!WriteFeatureOffset(nFreeOffset == OFFSET_MINUS_ONE ? m_nFileSize
1849 : : nFreeOffset))
1850 0 : return false;
1851 33503 : if (pnFID)
1852 9512 : *pnFID = nObjectID;
1853 :
1854 33503 : m_nRowBlobLength = static_cast<uint32_t>(m_abyBuffer.size());
1855 33503 : if (m_nRowBlobLength > m_nHeaderBufferMaxSize)
1856 : {
1857 5671 : m_nHeaderBufferMaxSize = m_nRowBlobLength;
1858 : }
1859 33503 : m_nRowBufferMaxSize = std::max(m_nRowBufferMaxSize, m_nRowBlobLength);
1860 33503 : if (nFreeOffset == OFFSET_MINUS_ONE)
1861 : {
1862 30887 : m_nFileSize += sizeof(uint32_t) + m_nRowBlobLength;
1863 : }
1864 :
1865 33503 : m_nTotalRecordCount =
1866 33503 : std::max(m_nTotalRecordCount, static_cast<int64_t>(nObjectID));
1867 33503 : m_nValidRecordCount++;
1868 :
1869 33503 : m_bDirtyHeader = true;
1870 33503 : m_bDirtyTableXHeader = true;
1871 :
1872 33503 : m_bDirtyIndices = true;
1873 :
1874 33503 : return true;
1875 : }
1876 :
1877 : /************************************************************************/
1878 : /* UpdateFeature() */
1879 : /************************************************************************/
1880 :
1881 58 : bool FileGDBTable::UpdateFeature(int64_t nFID,
1882 : const std::vector<OGRField> &asRawFields,
1883 : const OGRGeometry *poGeom)
1884 : {
1885 58 : if (!m_bUpdate)
1886 0 : return false;
1887 :
1888 58 : if (m_bDirtyFieldDescriptors && !WriteFieldDescriptors(m_fpTable))
1889 0 : return false;
1890 :
1891 58 : vsi_l_offset nOffsetInTableX = 0;
1892 : vsi_l_offset nOffsetInTable =
1893 58 : GetOffsetInTableForRow(nFID - 1, &nOffsetInTableX);
1894 58 : if (nOffsetInTable == 0)
1895 0 : return false;
1896 :
1897 : try
1898 : {
1899 58 : if (!EncodeFeature(asRawFields, poGeom, -1))
1900 0 : return false;
1901 : }
1902 0 : catch (const std::exception &e)
1903 : {
1904 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
1905 0 : return false;
1906 : }
1907 :
1908 58 : VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
1909 58 : uint32_t nOldFeatureSize = 0;
1910 58 : if (!ReadUInt32(m_fpTable, nOldFeatureSize))
1911 0 : return false;
1912 :
1913 58 : m_nCurRow = -1;
1914 :
1915 58 : if (m_abyBuffer.size() <= nOldFeatureSize)
1916 : {
1917 : // Can rewrite-in-place
1918 31 : VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
1919 :
1920 31 : if (!WriteUInt32(m_fpTable, static_cast<uint32_t>(m_abyBuffer.size())))
1921 0 : return false;
1922 62 : if (!m_abyBuffer.empty() &&
1923 31 : VSIFWriteL(m_abyBuffer.data(), 1, m_abyBuffer.size(), m_fpTable) !=
1924 31 : m_abyBuffer.size())
1925 : {
1926 0 : return false;
1927 : }
1928 :
1929 31 : m_nRowBlobLength = 0;
1930 31 : const size_t nSizeToBlank = nOldFeatureSize - m_abyBuffer.size();
1931 31 : if (nSizeToBlank > 0)
1932 : {
1933 : // Blank unused areas of the old feature
1934 18 : m_abyBuffer.clear();
1935 : try
1936 : {
1937 18 : m_abyBuffer.resize(nSizeToBlank);
1938 18 : CPL_IGNORE_RET_VAL(VSIFWriteL(m_abyBuffer.data(), 1,
1939 : m_abyBuffer.size(), m_fpTable));
1940 : }
1941 0 : catch (const std::exception &e)
1942 : {
1943 0 : CPLDebug("OpenFileGDB",
1944 : "Could not blank no longer part of feature: %s",
1945 0 : e.what());
1946 : }
1947 : }
1948 : }
1949 : else
1950 : {
1951 : // Updated feature is larger than older one: check if there's a chunk
1952 : // we can reuse by examining the .freelist, and if not, append at end
1953 : // of .gdbtable
1954 27 : const uint64_t nFreeOffset = GetOffsetOfFreeAreaFromFreeList(
1955 27 : static_cast<uint32_t>(sizeof(uint32_t) + m_abyBuffer.size()));
1956 :
1957 27 : if (nFreeOffset == OFFSET_MINUS_ONE)
1958 : {
1959 27 : if (((m_nFileSize + m_abyBuffer.size()) >>
1960 27 : (8 * m_nTablxOffsetSize)) != 0)
1961 : {
1962 0 : CPLError(
1963 : CE_Failure, CPLE_AppDefined,
1964 : "Maximum file size for m_nTablxOffsetSize = %u reached",
1965 : m_nTablxOffsetSize);
1966 0 : return false;
1967 : }
1968 :
1969 27 : VSIFSeekL(m_fpTable, m_nFileSize, SEEK_SET);
1970 : }
1971 : else
1972 : {
1973 0 : VSIFSeekL(m_fpTable, nFreeOffset, SEEK_SET);
1974 : }
1975 :
1976 27 : if (!WriteUInt32(m_fpTable, static_cast<uint32_t>(m_abyBuffer.size())))
1977 0 : return false;
1978 54 : if (!m_abyBuffer.empty() &&
1979 27 : VSIFWriteL(m_abyBuffer.data(), 1, m_abyBuffer.size(), m_fpTable) !=
1980 27 : m_abyBuffer.size())
1981 : {
1982 0 : return false;
1983 : }
1984 :
1985 : // Update offset of feature in .gdbtablx
1986 27 : VSIFSeekL(m_fpTableX, nOffsetInTableX, SEEK_SET);
1987 27 : if (!WriteFeatureOffset(nFreeOffset == OFFSET_MINUS_ONE ? m_nFileSize
1988 : : nFreeOffset))
1989 0 : return false;
1990 :
1991 27 : m_nRowBlobLength = static_cast<uint32_t>(m_abyBuffer.size());
1992 27 : if (m_nRowBlobLength > m_nHeaderBufferMaxSize)
1993 : {
1994 18 : m_bDirtyHeader = true;
1995 18 : m_nHeaderBufferMaxSize = m_nRowBlobLength;
1996 : }
1997 27 : m_nRowBufferMaxSize = std::max(m_nRowBufferMaxSize, m_nRowBlobLength);
1998 27 : if (nFreeOffset == OFFSET_MINUS_ONE)
1999 : {
2000 27 : m_bDirtyHeader = true;
2001 27 : m_nFileSize += sizeof(uint32_t) + m_nRowBlobLength;
2002 : }
2003 :
2004 27 : AddEntryToFreelist(nOffsetInTable, sizeof(uint32_t) + nOldFeatureSize);
2005 :
2006 : // Blank previously used area
2007 27 : VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
2008 27 : const uint32_t nNegatedOldFeatureSize =
2009 27 : static_cast<uint32_t>(-static_cast<int>(nOldFeatureSize));
2010 27 : if (!WriteUInt32(m_fpTable, nNegatedOldFeatureSize))
2011 0 : return false;
2012 27 : m_abyBuffer.clear();
2013 : try
2014 : {
2015 27 : m_abyBuffer.resize(nOldFeatureSize);
2016 27 : CPL_IGNORE_RET_VAL(VSIFWriteL(m_abyBuffer.data(), 1,
2017 : m_abyBuffer.size(), m_fpTable));
2018 : }
2019 0 : catch (const std::exception &e)
2020 : {
2021 0 : CPLDebug("OpenFileGDB", "Could not blank old feature: %s",
2022 0 : e.what());
2023 : }
2024 : }
2025 :
2026 58 : m_bDirtyIndices = true;
2027 :
2028 58 : return true;
2029 : }
2030 :
2031 : /************************************************************************/
2032 : /* DeleteFeature() */
2033 : /************************************************************************/
2034 :
2035 2649 : bool FileGDBTable::DeleteFeature(int64_t nFID)
2036 : {
2037 2649 : if (!m_bUpdate)
2038 0 : return false;
2039 :
2040 2649 : if (m_bDirtyFieldDescriptors && !WriteFieldDescriptors(m_fpTable))
2041 0 : return false;
2042 :
2043 2649 : vsi_l_offset nOffsetInTableX = 0;
2044 : vsi_l_offset nOffsetInTable =
2045 2649 : GetOffsetInTableForRow(nFID - 1, &nOffsetInTableX);
2046 2649 : if (nOffsetInTable == 0)
2047 0 : return false;
2048 :
2049 : // Set 0 as offset for the feature in .gdbtablx
2050 2649 : VSIFSeekL(m_fpTableX, nOffsetInTableX, SEEK_SET);
2051 2649 : if (!WriteFeatureOffset(0))
2052 0 : return false;
2053 :
2054 : // Negate the size of the feature in .gdbtable
2055 2649 : VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
2056 2649 : uint32_t nFeatureSize = 0;
2057 2649 : if (!ReadUInt32(m_fpTable, nFeatureSize))
2058 0 : return false;
2059 2649 : if (nFeatureSize > static_cast<uint32_t>(INT_MAX))
2060 0 : return false;
2061 2649 : const int nDeletedFeatureSize =
2062 2649 : static_cast<uint32_t>(-static_cast<int32_t>(nFeatureSize));
2063 2649 : VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
2064 2649 : if (!WriteUInt32(m_fpTable, nDeletedFeatureSize))
2065 0 : return false;
2066 :
2067 2649 : AddEntryToFreelist(nOffsetInTable, sizeof(uint32_t) + nFeatureSize);
2068 :
2069 : // Blank feature content
2070 2649 : m_nCurRow = -1;
2071 2649 : m_abyBuffer.clear();
2072 : try
2073 : {
2074 2649 : m_abyBuffer.resize(nFeatureSize);
2075 2649 : CPL_IGNORE_RET_VAL(
2076 2649 : VSIFWriteL(m_abyBuffer.data(), 1, m_abyBuffer.size(), m_fpTable));
2077 : }
2078 0 : catch (const std::exception &e)
2079 : {
2080 0 : CPLDebug("OpenFileGDB", "Could not blank deleted feature: %s",
2081 0 : e.what());
2082 : }
2083 :
2084 2649 : m_nValidRecordCount--;
2085 2649 : m_bDirtyHeader = true;
2086 :
2087 2649 : m_bDirtyIndices = true;
2088 :
2089 2649 : return true;
2090 : }
2091 :
2092 : /************************************************************************/
2093 : /* WholeFileRewriter::~WholeFileRewriter() */
2094 : /************************************************************************/
2095 :
2096 45 : FileGDBTable::WholeFileRewriter::~WholeFileRewriter()
2097 : {
2098 45 : if (m_bIsInit)
2099 6 : Rollback();
2100 45 : }
2101 :
2102 : /************************************************************************/
2103 : /* WholeFileRewriter::Begin() */
2104 : /************************************************************************/
2105 :
2106 45 : bool FileGDBTable::WholeFileRewriter::Begin()
2107 : {
2108 45 : m_bOldDirtyIndices = m_oTable.m_bDirtyIndices;
2109 45 : m_oTable.RemoveIndices();
2110 45 : m_oTable.m_bDirtyIndices = false;
2111 45 : if (!m_oTable.Sync())
2112 0 : return false;
2113 :
2114 : // On Windows, we might have issues renaming opened files, even if trying
2115 : // to close them before, so updating opened files is less risky.
2116 45 : m_bModifyInPlace =
2117 45 : CPLTestBool(CPLGetConfigOption("OPENFILEGDB_MODIFY_IN_PLACE",
2118 : #ifdef _WIN32
2119 : "YES"
2120 : #else
2121 : "NO"
2122 : #endif
2123 : ));
2124 :
2125 135 : m_osGdbTablx = CPLFormFilenameSafe(
2126 90 : CPLGetPathSafe(m_oTable.m_osFilename.c_str()).c_str(),
2127 135 : CPLGetBasenameSafe(m_oTable.m_osFilename.c_str()).c_str(), "gdbtablx");
2128 :
2129 90 : m_osBackupGdbTable = CPLResetExtensionSafe(m_oTable.m_osFilename.c_str(),
2130 45 : "_backup.gdbtable");
2131 : VSIStatBufL sStat;
2132 45 : if (VSIStatL(m_osBackupGdbTable.c_str(), &sStat) == 0)
2133 : {
2134 0 : CPLError(CE_Failure, CPLE_AppDefined,
2135 : "Cannot create backup file %s as it already exists",
2136 : m_osBackupGdbTable.c_str());
2137 0 : return false;
2138 : }
2139 :
2140 : m_osBackupGdbTablx =
2141 45 : CPLResetExtensionSafe(m_osGdbTablx.c_str(), "_backup.gdbtablx");
2142 :
2143 45 : if (m_bModifyInPlace)
2144 : {
2145 : // Create backups of .gdtable and .gdtablx if something wrongs happen
2146 14 : if (CPLCopyFile(m_osBackupGdbTable.c_str(),
2147 28 : m_oTable.m_osFilename.c_str()) != 0)
2148 : {
2149 0 : VSIUnlink(m_osBackupGdbTable.c_str());
2150 0 : m_osBackupGdbTable.clear();
2151 0 : return false;
2152 : }
2153 :
2154 14 : if (CPLCopyFile(m_osBackupGdbTablx.c_str(), m_osGdbTablx.c_str()) != 0)
2155 : {
2156 0 : VSIUnlink(m_osBackupGdbTable.c_str());
2157 0 : VSIUnlink(m_osBackupGdbTablx.c_str());
2158 0 : m_osBackupGdbTable.clear();
2159 0 : m_osBackupGdbTablx.clear();
2160 0 : return false;
2161 : }
2162 :
2163 14 : m_osBackupValidFilename = m_oTable.m_osFilename + ".backup_valid";
2164 14 : VSILFILE *fp = VSIFOpenL(m_osBackupValidFilename.c_str(), "wb");
2165 14 : if (fp != nullptr)
2166 14 : VSIFCloseL(fp);
2167 :
2168 14 : m_fpOldGdbtable = VSIFOpenL(m_osBackupGdbTable.c_str(), "rb");
2169 14 : if (m_fpOldGdbtable == nullptr)
2170 : {
2171 0 : VSIUnlink(m_osBackupValidFilename.c_str());
2172 0 : VSIUnlink(m_osBackupGdbTable.c_str());
2173 0 : VSIUnlink(m_osBackupGdbTablx.c_str());
2174 0 : m_osBackupValidFilename.clear();
2175 0 : m_osBackupGdbTable.clear();
2176 0 : m_osBackupGdbTablx.clear();
2177 0 : return false;
2178 : }
2179 :
2180 14 : m_fpOldGdbtablx = m_oTable.m_fpTableX;
2181 14 : m_fpTable = m_oTable.m_fpTable;
2182 14 : m_fpTableX = m_oTable.m_fpTableX;
2183 : }
2184 : else
2185 : {
2186 62 : m_osTmpGdbTable = CPLResetExtensionSafe(m_oTable.m_osFilename.c_str(),
2187 31 : "_compress.gdbtable");
2188 : m_osTmpGdbTablx =
2189 31 : CPLResetExtensionSafe(m_osGdbTablx.c_str(), "_compress.gdbtablx");
2190 :
2191 31 : m_fpOldGdbtable = m_oTable.m_fpTable;
2192 31 : m_fpOldGdbtablx = m_oTable.m_fpTableX;
2193 :
2194 31 : m_fpTable = VSIFOpenL(m_osTmpGdbTable.c_str(), "wb+");
2195 31 : if (m_fpTable == nullptr)
2196 : {
2197 0 : return false;
2198 : }
2199 :
2200 31 : m_fpTableX = VSIFOpenL(m_osTmpGdbTablx.c_str(), "wb+");
2201 31 : if (m_fpTableX == nullptr)
2202 : {
2203 0 : VSIFCloseL(m_fpTable);
2204 0 : m_fpTable = nullptr;
2205 0 : VSIUnlink(m_osTmpGdbTable.c_str());
2206 0 : return false;
2207 : }
2208 :
2209 31 : if (!m_oTable.WriteHeaderX(m_fpTableX))
2210 : {
2211 0 : VSIFCloseL(m_fpTable);
2212 0 : m_fpTable = nullptr;
2213 0 : VSIFCloseL(m_fpTableX);
2214 0 : m_fpTableX = nullptr;
2215 0 : VSIUnlink(m_osTmpGdbTable.c_str());
2216 0 : VSIUnlink(m_osTmpGdbTablx.c_str());
2217 0 : m_osTmpGdbTable.clear();
2218 0 : m_osTmpGdbTablx.clear();
2219 0 : return false;
2220 : }
2221 : }
2222 :
2223 45 : m_nOldFileSize = m_oTable.m_nFileSize;
2224 45 : m_nOldOffsetFieldDesc = m_oTable.m_nOffsetFieldDesc;
2225 45 : m_nOldFieldDescLength = m_oTable.m_nFieldDescLength;
2226 45 : m_bIsInit = true;
2227 :
2228 45 : if (!m_oTable.WriteHeader(m_fpTable))
2229 : {
2230 0 : Rollback();
2231 0 : return false;
2232 : }
2233 45 : if (m_bModifyInPlace)
2234 : {
2235 14 : VSIFTruncateL(m_fpTable, m_oTable.m_nFileSize);
2236 : }
2237 :
2238 : // Rewrite field descriptors
2239 45 : if (!m_oTable.Sync(m_fpTable, m_fpTableX))
2240 : {
2241 0 : Rollback();
2242 0 : return false;
2243 : }
2244 :
2245 45 : VSIFSeekL(m_fpTable, m_oTable.m_nFileSize, SEEK_SET);
2246 :
2247 45 : return true;
2248 : }
2249 :
2250 : /************************************************************************/
2251 : /* WholeFileRewriter::Commit() */
2252 : /************************************************************************/
2253 :
2254 39 : bool FileGDBTable::WholeFileRewriter::Commit()
2255 : {
2256 39 : m_oTable.m_bDirtyTableXTrailer = true;
2257 39 : m_oTable.m_bDirtyHeader = true;
2258 39 : if (!m_oTable.Sync(m_fpTable, m_fpTableX))
2259 : {
2260 0 : Rollback();
2261 0 : return false;
2262 : }
2263 :
2264 39 : if (m_bModifyInPlace)
2265 : {
2266 12 : VSIFCloseL(m_fpOldGdbtable);
2267 12 : VSIUnlink(m_osBackupValidFilename.c_str());
2268 12 : VSIUnlink(m_osBackupGdbTable.c_str());
2269 12 : VSIUnlink(m_osBackupGdbTablx.c_str());
2270 : }
2271 : else
2272 : {
2273 27 : VSIFCloseL(m_oTable.m_fpTable);
2274 27 : VSIFCloseL(m_oTable.m_fpTableX);
2275 27 : m_oTable.m_fpTable = nullptr;
2276 27 : m_oTable.m_fpTableX = nullptr;
2277 :
2278 : const bool bUseWIN32CodePath =
2279 27 : CPLTestBool(CPLGetConfigOption("OPENFILEGDB_SIMUL_WIN32",
2280 : #ifdef _WIN32
2281 : "YES"
2282 : #else
2283 : "NO"
2284 : #endif
2285 : ));
2286 :
2287 27 : if (bUseWIN32CodePath)
2288 : {
2289 : // Renaming over an open file doesn't work on Windows
2290 12 : VSIFCloseL(m_fpTable);
2291 12 : VSIFCloseL(m_fpTableX);
2292 12 : m_fpTable = nullptr;
2293 12 : m_fpTableX = nullptr;
2294 :
2295 : // _wrename() on Windows doesn't honour POSIX semantics and forbids
2296 : // renaming over an existing file, hence create a temporary backup
2297 12 : if (VSIRename(m_oTable.m_osFilename.c_str(),
2298 12 : m_osBackupGdbTable.c_str()) != 0)
2299 : {
2300 0 : m_oTable.m_fpTable =
2301 0 : VSIFOpenL(m_oTable.m_osFilename.c_str(), "rb+");
2302 0 : m_oTable.m_fpTableX = VSIFOpenL(m_osGdbTablx.c_str(), "rb+");
2303 0 : Rollback();
2304 0 : return false;
2305 : }
2306 :
2307 12 : if (VSIRename(m_osGdbTablx.c_str(), m_osBackupGdbTablx.c_str()) !=
2308 : 0)
2309 : {
2310 0 : CPLError(CE_Failure, CPLE_FileIO,
2311 : "Renaming of %s onto %s failed, but renaming of "
2312 : "%s onto %s succeeded. Dataset in corrupt state",
2313 : m_osGdbTablx.c_str(), m_osBackupGdbTablx.c_str(),
2314 0 : m_oTable.m_osFilename.c_str(),
2315 : m_osBackupGdbTable.c_str());
2316 0 : Rollback();
2317 0 : return false;
2318 : }
2319 : }
2320 : else
2321 : {
2322 15 : m_oTable.m_fpTable = m_fpTable;
2323 15 : m_oTable.m_fpTableX = m_fpTableX;
2324 : }
2325 :
2326 27 : if (VSIRename(m_osTmpGdbTable.c_str(), m_oTable.m_osFilename.c_str()) !=
2327 : 0)
2328 : {
2329 0 : CPLError(CE_Failure, CPLE_FileIO, "Renaming of %s onto %s failed",
2330 0 : m_osTmpGdbTable.c_str(), m_oTable.m_osFilename.c_str());
2331 0 : Rollback();
2332 0 : return false;
2333 : }
2334 :
2335 27 : if (VSIRename(m_osTmpGdbTablx.c_str(), m_osGdbTablx.c_str()) != 0)
2336 : {
2337 0 : CPLError(CE_Failure, CPLE_FileIO, "Renaming of %s onto %s failed",
2338 : m_osTmpGdbTablx.c_str(), m_osGdbTablx.c_str());
2339 0 : Rollback();
2340 0 : return false;
2341 : }
2342 :
2343 27 : if (bUseWIN32CodePath)
2344 : {
2345 24 : m_oTable.m_fpTable =
2346 12 : VSIFOpenL(m_oTable.m_osFilename.c_str(), "rb+");
2347 12 : m_oTable.m_fpTableX = VSIFOpenL(m_osGdbTablx.c_str(), "rb+");
2348 12 : VSIUnlink(m_osBackupGdbTable.c_str());
2349 12 : VSIUnlink(m_osBackupGdbTablx.c_str());
2350 : }
2351 : }
2352 :
2353 39 : m_oTable.DeleteFreeList();
2354 39 : if (m_bOldDirtyIndices)
2355 : {
2356 8 : m_oTable.m_bDirtyIndices = true;
2357 8 : m_oTable.Sync();
2358 : }
2359 :
2360 39 : m_bIsInit = false;
2361 :
2362 39 : return true;
2363 : }
2364 :
2365 : /************************************************************************/
2366 : /* WholeFileRewriter::Rollback() */
2367 : /************************************************************************/
2368 :
2369 6 : void FileGDBTable::WholeFileRewriter::Rollback()
2370 : {
2371 6 : CPLAssert(m_bIsInit);
2372 6 : m_bIsInit = false;
2373 :
2374 6 : if (m_bModifyInPlace)
2375 : {
2376 2 : VSIFCloseL(m_fpOldGdbtable);
2377 2 : m_fpOldGdbtable = nullptr;
2378 :
2379 : // Try to restore from backup files in case of failure
2380 2 : if (CPLCopyFile(m_oTable.m_osFilename.c_str(),
2381 4 : m_osBackupGdbTable.c_str()) == 0 &&
2382 2 : CPLCopyFile(m_osGdbTablx.c_str(), m_osBackupGdbTablx.c_str()) == 0)
2383 : {
2384 2 : VSIUnlink(m_osBackupValidFilename.c_str());
2385 2 : VSIUnlink(m_osBackupGdbTable.c_str());
2386 2 : VSIUnlink(m_osBackupGdbTablx.c_str());
2387 : }
2388 : else
2389 : {
2390 0 : CPLError(CE_Failure, CPLE_AppDefined,
2391 : "%s and %s are corrupted, and couldn't be restored from "
2392 : "their backups %s and %s. You'll have to manually replace "
2393 : "the former files by the latter ones.",
2394 0 : m_oTable.m_osFilename.c_str(), m_osGdbTablx.c_str(),
2395 : m_osBackupGdbTable.c_str(), m_osBackupGdbTablx.c_str());
2396 : }
2397 : }
2398 : else
2399 : {
2400 4 : VSIFCloseL(m_fpTable);
2401 4 : VSIFCloseL(m_fpTableX);
2402 4 : m_fpTable = nullptr;
2403 4 : m_fpTableX = nullptr;
2404 4 : VSIUnlink(m_osTmpGdbTable.c_str());
2405 4 : VSIUnlink(m_osTmpGdbTablx.c_str());
2406 : }
2407 :
2408 6 : m_oTable.m_nFileSize = m_nOldFileSize;
2409 6 : m_oTable.m_nOffsetFieldDesc = m_nOldOffsetFieldDesc;
2410 6 : m_oTable.m_nFieldDescLength = m_nOldFieldDescLength;
2411 :
2412 6 : m_oTable.m_bDirtyFieldDescriptors = false;
2413 6 : m_oTable.m_bDirtyTableXHeader = false;
2414 6 : m_oTable.m_bDirtyTableXTrailer = false;
2415 6 : m_oTable.m_bDirtyHeader = false;
2416 6 : }
2417 :
2418 : /************************************************************************/
2419 : /* Repack() */
2420 : /************************************************************************/
2421 :
2422 6 : bool FileGDBTable::Repack(GDALProgressFunc pfnProgress, void *pProgressData)
2423 : {
2424 6 : if (!m_bUpdate || !Sync())
2425 0 : return false;
2426 :
2427 6 : bool bRepackNeeded = false;
2428 6 : if (m_nOffsetFieldDesc > 40)
2429 : {
2430 : // If the field descriptor section is not at offset 40, it is possible
2431 : // that there's our "ghost area" there.
2432 6 : GByte abyBuffer[8] = {0};
2433 6 : VSIFSeekL(m_fpTable, 40, SEEK_SET);
2434 6 : VSIFReadL(abyBuffer, 1, sizeof(abyBuffer), m_fpTable);
2435 12 : if (!(memcmp(abyBuffer + 4, "GDAL", 4) == 0 &&
2436 6 : static_cast<uint64_t>(40) + sizeof(uint32_t) +
2437 6 : GetUInt32(abyBuffer, 0) ==
2438 6 : m_nOffsetFieldDesc))
2439 : {
2440 0 : CPLDebug("OpenFileGDB",
2441 : "Repack(%s): field descriptors not at beginning of file",
2442 : m_osFilename.c_str());
2443 0 : bRepackNeeded = true;
2444 : }
2445 : }
2446 :
2447 6 : uint64_t nExpectedOffset =
2448 6 : m_nOffsetFieldDesc + sizeof(uint32_t) + m_nFieldDescLength;
2449 :
2450 12 : std::vector<GByte> abyBufferOffsets;
2451 6 : abyBufferOffsets.resize(TABLX_FEATURES_PER_PAGE * m_nTablxOffsetSize);
2452 :
2453 6 : constexpr double RATIO_SCAN = 0.2;
2454 :
2455 : // Scan all features
2456 12 : for (uint32_t iPage = 0; !bRepackNeeded && iPage < m_n1024BlocksPresent;
2457 : ++iPage)
2458 : {
2459 6 : const vsi_l_offset nOffsetInTableX =
2460 6 : TABLX_HEADER_SIZE + m_nTablxOffsetSize *
2461 6 : static_cast<vsi_l_offset>(iPage) *
2462 : TABLX_FEATURES_PER_PAGE;
2463 6 : VSIFSeekL(m_fpTableX, nOffsetInTableX, SEEK_SET);
2464 6 : if (VSIFReadL(abyBufferOffsets.data(),
2465 6 : m_nTablxOffsetSize * TABLX_FEATURES_PER_PAGE, 1,
2466 6 : m_fpTableX) != 1)
2467 0 : return false;
2468 :
2469 6 : GByte *pabyBufferOffsets = abyBufferOffsets.data();
2470 5127 : for (int i = 0; i < TABLX_FEATURES_PER_PAGE;
2471 5121 : i++, pabyBufferOffsets += m_nTablxOffsetSize)
2472 : {
2473 5122 : const uint64_t nOffset = ReadFeatureOffset(pabyBufferOffsets);
2474 5122 : if (nOffset != 0)
2475 : {
2476 9 : if (!bRepackNeeded && nOffset != nExpectedOffset)
2477 : {
2478 1 : bRepackNeeded = true;
2479 1 : CPLDebug("OpenFileGDB",
2480 : "Repack(%s): feature at offset " CPL_FRMT_GUIB
2481 : " instead of " CPL_FRMT_GUIB ". Repack needed",
2482 : m_osFilename.c_str(),
2483 : static_cast<GUIntBig>(nOffset),
2484 : static_cast<GUIntBig>(nExpectedOffset));
2485 1 : break;
2486 : }
2487 :
2488 : // Read feature size
2489 8 : VSIFSeekL(m_fpTable, nOffset, SEEK_SET);
2490 8 : uint32_t nFeatureSize = 0;
2491 8 : if (!ReadUInt32(m_fpTable, nFeatureSize))
2492 0 : return false;
2493 :
2494 8 : nExpectedOffset += sizeof(uint32_t);
2495 8 : nExpectedOffset += nFeatureSize;
2496 : }
2497 : }
2498 :
2499 6 : bRepackNeeded =
2500 1 : (!pfnProgress ||
2501 1 : pfnProgress(RATIO_SCAN * static_cast<double>(iPage + 1) /
2502 1 : m_n1024BlocksPresent,
2503 7 : "", pProgressData)) &&
2504 : bRepackNeeded;
2505 : }
2506 :
2507 6 : if (!bRepackNeeded)
2508 : {
2509 5 : if (pfnProgress)
2510 1 : pfnProgress(1.0, "", pProgressData);
2511 :
2512 5 : if (m_nFileSize > nExpectedOffset)
2513 : {
2514 1 : CPLDebug("OpenFileGDB",
2515 : "Deleted features at end of file. Truncating it");
2516 :
2517 1 : m_nFileSize = nExpectedOffset;
2518 1 : VSIFTruncateL(m_fpTable, m_nFileSize);
2519 1 : m_bDirtyHeader = true;
2520 :
2521 1 : DeleteFreeList();
2522 :
2523 1 : return Sync();
2524 : }
2525 :
2526 4 : CPLDebug("OpenFileGDB", "Repack(%s): file already compacted",
2527 : m_osFilename.c_str());
2528 4 : return true;
2529 : }
2530 :
2531 2 : WholeFileRewriter oWholeFileRewriter(*this);
2532 1 : if (!oWholeFileRewriter.Begin())
2533 0 : return false;
2534 :
2535 1 : uint32_t nRowBufferMaxSize = 0;
2536 1 : m_nCurRow = -1;
2537 :
2538 : // Rewrite all features
2539 2 : for (uint32_t iPage = 0; iPage < m_n1024BlocksPresent; ++iPage)
2540 : {
2541 1 : const vsi_l_offset nOffsetInTableX =
2542 1 : TABLX_HEADER_SIZE + m_nTablxOffsetSize *
2543 1 : static_cast<vsi_l_offset>(iPage) *
2544 : TABLX_FEATURES_PER_PAGE;
2545 1 : VSIFSeekL(oWholeFileRewriter.m_fpOldGdbtablx, nOffsetInTableX,
2546 : SEEK_SET);
2547 1 : if (VSIFReadL(abyBufferOffsets.data(),
2548 1 : m_nTablxOffsetSize * TABLX_FEATURES_PER_PAGE, 1,
2549 1 : oWholeFileRewriter.m_fpOldGdbtablx) != 1)
2550 0 : return false;
2551 :
2552 1 : GByte *pabyBufferOffsets = abyBufferOffsets.data();
2553 1025 : for (int i = 0; i < TABLX_FEATURES_PER_PAGE;
2554 1024 : i++, pabyBufferOffsets += m_nTablxOffsetSize)
2555 : {
2556 1024 : const uint64_t nOffset = ReadFeatureOffset(pabyBufferOffsets);
2557 1024 : if (nOffset != 0)
2558 : {
2559 : // Read feature size
2560 1 : VSIFSeekL(oWholeFileRewriter.m_fpOldGdbtable, nOffset,
2561 : SEEK_SET);
2562 1 : uint32_t nFeatureSize = 0;
2563 1 : if (!ReadUInt32(oWholeFileRewriter.m_fpOldGdbtable,
2564 : nFeatureSize))
2565 0 : return false;
2566 :
2567 : // Read feature data
2568 1 : if (nFeatureSize > m_abyBuffer.size())
2569 : {
2570 : try
2571 : {
2572 0 : m_abyBuffer.resize(nFeatureSize);
2573 : }
2574 0 : catch (const std::exception &e)
2575 : {
2576 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
2577 0 : return false;
2578 : }
2579 : }
2580 1 : if (VSIFReadL(m_abyBuffer.data(), nFeatureSize, 1,
2581 1 : oWholeFileRewriter.m_fpOldGdbtable) != 1)
2582 0 : return false;
2583 :
2584 : // Update offset of updated feature
2585 1 : WriteFeatureOffset(m_nFileSize, pabyBufferOffsets);
2586 :
2587 : // Write feature size
2588 1 : if (!WriteUInt32(oWholeFileRewriter.m_fpTable, nFeatureSize))
2589 0 : return false;
2590 1 : if (VSIFWriteL(m_abyBuffer.data(), nFeatureSize, 1,
2591 1 : oWholeFileRewriter.m_fpTable) != 1)
2592 0 : return false;
2593 :
2594 1 : if (nFeatureSize > nRowBufferMaxSize)
2595 1 : nRowBufferMaxSize = nFeatureSize;
2596 1 : m_nFileSize += sizeof(uint32_t) + nFeatureSize;
2597 : }
2598 : }
2599 1 : VSIFSeekL(oWholeFileRewriter.m_fpTableX, nOffsetInTableX, SEEK_SET);
2600 1 : if (VSIFWriteL(abyBufferOffsets.data(),
2601 1 : m_nTablxOffsetSize * TABLX_FEATURES_PER_PAGE, 1,
2602 1 : oWholeFileRewriter.m_fpTableX) != 1)
2603 0 : return false;
2604 :
2605 1 : if (pfnProgress &&
2606 0 : !pfnProgress(RATIO_SCAN + (1.0 - RATIO_SCAN) *
2607 0 : static_cast<double>(iPage + 1) /
2608 0 : m_n1024BlocksPresent,
2609 : "", pProgressData))
2610 : {
2611 0 : return false;
2612 : }
2613 : }
2614 :
2615 1 : m_nRowBufferMaxSize = nRowBufferMaxSize;
2616 1 : m_nHeaderBufferMaxSize = std::max(m_nFieldDescLength, m_nRowBufferMaxSize);
2617 :
2618 1 : return oWholeFileRewriter.Commit();
2619 : }
2620 :
2621 : /************************************************************************/
2622 : /* RecomputeExtent() */
2623 : /************************************************************************/
2624 :
2625 2 : void FileGDBTable::RecomputeExtent()
2626 : {
2627 2 : if (!m_bUpdate || m_iGeomField < 0)
2628 0 : return;
2629 :
2630 : // Scan all features
2631 2 : OGREnvelope sLayerEnvelope;
2632 2 : OGREnvelope sFeatureEnvelope;
2633 6 : for (int64_t iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat)
2634 : {
2635 4 : iCurFeat = GetAndSelectNextNonEmptyRow(iCurFeat);
2636 4 : if (iCurFeat < 0)
2637 0 : break;
2638 4 : const auto psGeomField = GetFieldValue(m_iGeomField);
2639 4 : if (psGeomField && GetFeatureExtent(psGeomField, &sFeatureEnvelope))
2640 : {
2641 2 : sLayerEnvelope.Merge(sFeatureEnvelope);
2642 : }
2643 : }
2644 :
2645 2 : m_bDirtyGeomFieldBBox = true;
2646 : auto poGeomField =
2647 2 : cpl::down_cast<FileGDBGeomField *>(m_apoFields[m_iGeomField].get());
2648 2 : if (sLayerEnvelope.IsInit())
2649 : {
2650 1 : poGeomField->SetXYMinMax(sLayerEnvelope.MinX, sLayerEnvelope.MinY,
2651 : sLayerEnvelope.MaxX, sLayerEnvelope.MaxY);
2652 : }
2653 : else
2654 : {
2655 1 : poGeomField->SetXYMinMax(
2656 : FileGDBGeomField::ESRI_NAN, FileGDBGeomField::ESRI_NAN,
2657 : FileGDBGeomField::ESRI_NAN, FileGDBGeomField::ESRI_NAN);
2658 : }
2659 : }
2660 :
2661 : } /* namespace OpenFileGDB */
|