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