Coverage Report

Created: 2026-09-26 08:22

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