Coverage Report

Created: 2025-12-31 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/generic/ogrwarpedlayer.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  OpenGIS Simple Features Reference Implementation
4
 * Purpose:  Implements OGRWarpedLayer class
5
 * Author:   Even Rouault, even dot rouault at spatialys.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2012-2014, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#ifndef DOXYGEN_SKIP
14
15
#include <cmath>
16
17
#include "ogrwarpedlayer.h"
18
19
/************************************************************************/
20
/*                          OGRWarpedLayer()                            */
21
/************************************************************************/
22
23
OGRWarpedLayer::OGRWarpedLayer(OGRLayer *poDecoratedLayer, int iGeomField,
24
                               int bTakeOwnership,
25
                               OGRCoordinateTransformation *poCT,
26
                               OGRCoordinateTransformation *poReversedCT)
27
0
    : OGRLayerDecorator(poDecoratedLayer, bTakeOwnership),
28
0
      m_poFeatureDefn(poDecoratedLayer->GetLayerDefn()->Clone()),
29
0
      m_iGeomField(iGeomField), m_poCT(poCT), m_poReversedCT(poReversedCT),
30
0
      m_poSRS(const_cast<OGRSpatialReference *>(m_poCT->GetTargetCS()))
31
0
{
32
0
    CPLAssert(poCT != nullptr);
33
0
    SetDescription(poDecoratedLayer->GetDescription());
34
35
0
    m_poFeatureDefn->Reference();
36
0
    if (m_poFeatureDefn->GetGeomFieldCount() > 0)
37
0
        m_poFeatureDefn->GetGeomFieldDefn(m_iGeomField)->SetSpatialRef(m_poSRS);
38
39
0
    if (m_poSRS != nullptr)
40
0
    {
41
0
        m_poSRS->Reference();
42
0
    }
43
0
}
Unexecuted instantiation: OGRWarpedLayer::OGRWarpedLayer(OGRLayer*, int, int, OGRCoordinateTransformation*, OGRCoordinateTransformation*)
Unexecuted instantiation: OGRWarpedLayer::OGRWarpedLayer(OGRLayer*, int, int, OGRCoordinateTransformation*, OGRCoordinateTransformation*)
44
45
/************************************************************************/
46
/*                         ~OGRWarpedLayer()                            */
47
/************************************************************************/
48
49
OGRWarpedLayer::~OGRWarpedLayer()
50
0
{
51
0
    if (m_poFeatureDefn != nullptr)
52
0
        m_poFeatureDefn->Release();
53
0
    if (m_poSRS != nullptr)
54
0
        m_poSRS->Release();
55
0
    delete m_poCT;
56
0
    delete m_poReversedCT;
57
0
}
58
59
/************************************************************************/
60
/*                         ISetSpatialFilter()                          */
61
/************************************************************************/
62
63
OGRErr OGRWarpedLayer::ISetSpatialFilter(int iGeomField,
64
                                         const OGRGeometry *poGeom)
65
0
{
66
67
0
    m_iGeomFieldFilter = iGeomField;
68
0
    if (InstallFilter(poGeom))
69
0
        ResetReading();
70
71
0
    if (m_iGeomFieldFilter == m_iGeomField)
72
0
    {
73
0
        if (poGeom == nullptr || m_poReversedCT == nullptr)
74
0
        {
75
0
            return m_poDecoratedLayer->SetSpatialFilter(m_iGeomFieldFilter,
76
0
                                                        nullptr);
77
0
        }
78
0
        else
79
0
        {
80
0
            OGREnvelope sEnvelope;
81
0
            poGeom->getEnvelope(&sEnvelope);
82
0
            if (std::isinf(sEnvelope.MinX) && std::isinf(sEnvelope.MinY) &&
83
0
                std::isinf(sEnvelope.MaxX) && std::isinf(sEnvelope.MaxY))
84
0
            {
85
0
                return m_poDecoratedLayer->SetSpatialFilterRect(
86
0
                    m_iGeomFieldFilter, sEnvelope.MinX, sEnvelope.MinY,
87
0
                    sEnvelope.MaxX, sEnvelope.MaxY);
88
0
            }
89
0
            else if (ReprojectEnvelope(&sEnvelope, m_poReversedCT))
90
0
            {
91
0
                return m_poDecoratedLayer->SetSpatialFilterRect(
92
0
                    m_iGeomFieldFilter, sEnvelope.MinX, sEnvelope.MinY,
93
0
                    sEnvelope.MaxX, sEnvelope.MaxY);
94
0
            }
95
0
            else
96
0
            {
97
0
                return m_poDecoratedLayer->SetSpatialFilter(m_iGeomFieldFilter,
98
0
                                                            nullptr);
99
0
            }
100
0
        }
101
0
    }
102
0
    else
103
0
    {
104
0
        return m_poDecoratedLayer->SetSpatialFilter(m_iGeomFieldFilter, poGeom);
105
0
    }
106
0
}
107
108
/************************************************************************/
109
/*                         TranslateFeature()                           */
110
/************************************************************************/
111
112
void OGRWarpedLayer::TranslateFeature(
113
    std::unique_ptr<OGRFeature> poSrcFeature,
114
    std::vector<std::unique_ptr<OGRFeature>> &apoOutFeatures)
115
0
{
116
0
    apoOutFeatures.push_back(
117
0
        SrcFeatureToWarpedFeature(std::move(poSrcFeature)));
118
0
}
119
120
/************************************************************************/
121
/*                     SrcFeatureToWarpedFeature()                      */
122
/************************************************************************/
123
124
std::unique_ptr<OGRFeature>
125
OGRWarpedLayer::SrcFeatureToWarpedFeature(std::unique_ptr<OGRFeature> poFeature)
126
0
{
127
    // This is safe to do here as they have matching attribute and geometry
128
    // fields
129
0
    OGRLayer *poThisLayer = this;
130
0
    poFeature->SetFDefnUnsafe(poThisLayer->GetLayerDefn());
131
132
0
    OGRGeometry *poGeom = poFeature->GetGeomFieldRef(m_iGeomField);
133
0
    if (poGeom)
134
0
    {
135
0
        auto poNewGeom = OGRGeometryFactory::transformWithOptions(
136
0
            poGeom, m_poCT, nullptr, m_transformCacheForward);
137
0
        poFeature->SetGeomFieldDirectly(m_iGeomField, poNewGeom);
138
0
    }
139
140
0
    return poFeature;
141
0
}
142
143
/************************************************************************/
144
/*                     WarpedFeatureToSrcFeature()                      */
145
/************************************************************************/
146
147
std::unique_ptr<OGRFeature>
148
OGRWarpedLayer::WarpedFeatureToSrcFeature(std::unique_ptr<OGRFeature> poFeature)
149
0
{
150
    // This is safe to do here as they have matching attribute and geometry
151
    // fields
152
0
    poFeature->SetFDefnUnsafe(m_poDecoratedLayer->GetLayerDefn());
153
154
0
    OGRGeometry *poGeom = poFeature->GetGeomFieldRef(m_iGeomField);
155
0
    if (poGeom)
156
0
    {
157
0
        if (!m_poReversedCT)
158
0
            return nullptr;
159
0
        auto poNewGeom = OGRGeometryFactory::transformWithOptions(
160
0
            poGeom, m_poReversedCT, nullptr, m_transformCacheReverse);
161
0
        if (!poNewGeom)
162
0
            return nullptr;
163
0
        poFeature->SetGeomFieldDirectly(m_iGeomField, poNewGeom);
164
0
    }
165
166
0
    return poFeature;
167
0
}
168
169
/************************************************************************/
170
/*                          GetNextFeature()                            */
171
/************************************************************************/
172
173
OGRFeature *OGRWarpedLayer::GetNextFeature()
174
0
{
175
0
    while (true)
176
0
    {
177
0
        auto poFeature =
178
0
            std::unique_ptr<OGRFeature>(m_poDecoratedLayer->GetNextFeature());
179
0
        if (!poFeature)
180
0
            return nullptr;
181
182
0
        auto poFeatureNew = SrcFeatureToWarpedFeature(std::move(poFeature));
183
0
        const OGRGeometry *poGeom = poFeatureNew->GetGeomFieldRef(m_iGeomField);
184
0
        if (m_poFilterGeom != nullptr && !FilterGeometry(poGeom))
185
0
        {
186
0
            continue;
187
0
        }
188
189
0
        return poFeatureNew.release();
190
0
    }
191
0
}
192
193
/************************************************************************/
194
/*                             GetFeature()                             */
195
/************************************************************************/
196
197
OGRFeature *OGRWarpedLayer::GetFeature(GIntBig nFID)
198
0
{
199
0
    auto poFeature =
200
0
        std::unique_ptr<OGRFeature>(m_poDecoratedLayer->GetFeature(nFID));
201
0
    if (poFeature)
202
0
    {
203
0
        poFeature = SrcFeatureToWarpedFeature(std::move(poFeature));
204
0
    }
205
0
    return poFeature.release();
206
0
}
207
208
/************************************************************************/
209
/*                             ISetFeature()                            */
210
/************************************************************************/
211
212
OGRErr OGRWarpedLayer::ISetFeature(OGRFeature *poFeature)
213
0
{
214
0
    auto poFeatureNew = WarpedFeatureToSrcFeature(
215
0
        std::unique_ptr<OGRFeature>(poFeature->Clone()));
216
0
    if (!poFeatureNew)
217
0
        return OGRERR_FAILURE;
218
219
0
    return m_poDecoratedLayer->SetFeature(poFeatureNew.get());
220
0
}
221
222
/************************************************************************/
223
/*                          ISetFeatureUniqPtr()                        */
224
/************************************************************************/
225
226
OGRErr OGRWarpedLayer::ISetFeatureUniqPtr(std::unique_ptr<OGRFeature> poFeature)
227
0
{
228
0
    auto poFeatureNew = WarpedFeatureToSrcFeature(std::move(poFeature));
229
0
    if (!poFeatureNew)
230
0
        return OGRERR_FAILURE;
231
232
0
    return m_poDecoratedLayer->SetFeature(std::move(poFeatureNew));
233
0
}
234
235
/************************************************************************/
236
/*                            ICreateFeature()                          */
237
/************************************************************************/
238
239
OGRErr OGRWarpedLayer::ICreateFeature(OGRFeature *poFeature)
240
0
{
241
0
    auto poFeatureNew = WarpedFeatureToSrcFeature(
242
0
        std::unique_ptr<OGRFeature>(poFeature->Clone()));
243
0
    if (!poFeatureNew)
244
0
        return OGRERR_FAILURE;
245
246
0
    return m_poDecoratedLayer->CreateFeature(poFeatureNew.get());
247
0
}
248
249
/************************************************************************/
250
/*                          ICreateFeatureUniqPtr()                     */
251
/************************************************************************/
252
253
OGRErr
254
OGRWarpedLayer::ICreateFeatureUniqPtr(std::unique_ptr<OGRFeature> poFeature,
255
                                      GIntBig *pnFID)
256
0
{
257
0
    auto poFeatureNew = WarpedFeatureToSrcFeature(std::move(poFeature));
258
0
    if (!poFeatureNew)
259
0
        return OGRERR_FAILURE;
260
261
0
    return m_poDecoratedLayer->CreateFeature(std::move(poFeatureNew), pnFID);
262
0
}
263
264
/************************************************************************/
265
/*                            IUpsertFeature()                          */
266
/************************************************************************/
267
268
OGRErr OGRWarpedLayer::IUpsertFeature(OGRFeature *poFeature)
269
0
{
270
0
    auto poFeatureNew = WarpedFeatureToSrcFeature(
271
0
        std::unique_ptr<OGRFeature>(poFeature->Clone()));
272
0
    if (poFeatureNew == nullptr)
273
0
        return OGRERR_FAILURE;
274
275
0
    return m_poDecoratedLayer->UpsertFeature(poFeatureNew.get());
276
0
}
277
278
/************************************************************************/
279
/*                            IUpdateFeature()                          */
280
/************************************************************************/
281
282
OGRErr OGRWarpedLayer::IUpdateFeature(OGRFeature *poFeature,
283
                                      int nUpdatedFieldsCount,
284
                                      const int *panUpdatedFieldsIdx,
285
                                      int nUpdatedGeomFieldsCount,
286
                                      const int *panUpdatedGeomFieldsIdx,
287
                                      bool bUpdateStyleString)
288
0
{
289
0
    auto poFeatureNew = WarpedFeatureToSrcFeature(
290
0
        std::unique_ptr<OGRFeature>(poFeature->Clone()));
291
0
    if (!poFeatureNew)
292
0
        return OGRERR_FAILURE;
293
294
0
    return m_poDecoratedLayer->UpdateFeature(
295
0
        poFeatureNew.get(), nUpdatedFieldsCount, panUpdatedFieldsIdx,
296
0
        nUpdatedGeomFieldsCount, panUpdatedGeomFieldsIdx, bUpdateStyleString);
297
0
}
298
299
/************************************************************************/
300
/*                            GetLayerDefn()                           */
301
/************************************************************************/
302
303
const OGRFeatureDefn *OGRWarpedLayer::GetLayerDefn() const
304
0
{
305
0
    return m_poFeatureDefn;
306
0
}
307
308
/************************************************************************/
309
/*                            GetSpatialRef()                           */
310
/************************************************************************/
311
312
const OGRSpatialReference *OGRWarpedLayer::GetSpatialRef() const
313
0
{
314
0
    if (m_iGeomField == 0)
315
0
        return m_poSRS;
316
0
    else
317
0
        return OGRLayer::GetSpatialRef();
318
0
}
319
320
/************************************************************************/
321
/*                           GetFeatureCount()                          */
322
/************************************************************************/
323
324
GIntBig OGRWarpedLayer::GetFeatureCount(int bForce)
325
0
{
326
0
    if (m_poFilterGeom == nullptr)
327
0
        return m_poDecoratedLayer->GetFeatureCount(bForce);
328
329
0
    return OGRLayer::GetFeatureCount(bForce);
330
0
}
331
332
/************************************************************************/
333
/*                             IGetExtent()                             */
334
/************************************************************************/
335
336
OGRErr OGRWarpedLayer::IGetExtent(int iGeomField, OGREnvelope *psExtent,
337
                                  bool bForce)
338
0
{
339
0
    if (iGeomField == m_iGeomField)
340
0
    {
341
0
        if (sStaticEnvelope.IsInit())
342
0
        {
343
0
            *psExtent = sStaticEnvelope;
344
0
            return OGRERR_NONE;
345
0
        }
346
347
0
        OGREnvelope sExtent;
348
0
        OGRErr eErr =
349
0
            m_poDecoratedLayer->GetExtent(m_iGeomField, &sExtent, bForce);
350
0
        if (eErr != OGRERR_NONE)
351
0
            return eErr;
352
353
0
        if (ReprojectEnvelope(&sExtent, m_poCT))
354
0
        {
355
0
            *psExtent = sExtent;
356
0
            return OGRERR_NONE;
357
0
        }
358
0
        else
359
0
            return OGRERR_FAILURE;
360
0
    }
361
0
    else
362
0
        return m_poDecoratedLayer->GetExtent(iGeomField, psExtent, bForce);
363
0
}
364
365
/************************************************************************/
366
/*                    TransformAndUpdateBBAndReturnX()                  */
367
/************************************************************************/
368
369
static double TransformAndUpdateBBAndReturnX(OGRCoordinateTransformation *poCT,
370
                                             double dfX, double dfY,
371
                                             double &dfMinX, double &dfMinY,
372
                                             double &dfMaxX, double &dfMaxY)
373
0
{
374
0
    int bSuccess = FALSE;
375
0
    poCT->Transform(1, &dfX, &dfY, nullptr, nullptr, &bSuccess);
376
0
    if (bSuccess)
377
0
    {
378
0
        if (dfX < dfMinX)
379
0
            dfMinX = dfX;
380
0
        if (dfY < dfMinY)
381
0
            dfMinY = dfY;
382
0
        if (dfX > dfMaxX)
383
0
            dfMaxX = dfX;
384
0
        if (dfY > dfMaxY)
385
0
            dfMaxY = dfY;
386
0
        return dfX;
387
0
    }
388
389
0
    return 0.0;
390
0
}
391
392
/************************************************************************/
393
/*                            FindXDiscontinuity()                      */
394
/************************************************************************/
395
396
static void FindXDiscontinuity(OGRCoordinateTransformation *poCT, double dfX1,
397
                               double dfX2, double dfY, double &dfMinX,
398
                               double &dfMinY, double &dfMaxX, double &dfMaxY,
399
                               int nRecLevel = 0)
400
0
{
401
0
    double dfXMid = (dfX1 + dfX2) / 2;
402
403
0
    double dfWrkX1 = TransformAndUpdateBBAndReturnX(poCT, dfX1, dfY, dfMinX,
404
0
                                                    dfMinY, dfMaxX, dfMaxY);
405
0
    double dfWrkXMid = TransformAndUpdateBBAndReturnX(poCT, dfXMid, dfY, dfMinX,
406
0
                                                      dfMinY, dfMaxX, dfMaxY);
407
0
    double dfWrkX2 = TransformAndUpdateBBAndReturnX(poCT, dfX2, dfY, dfMinX,
408
0
                                                    dfMinY, dfMaxX, dfMaxY);
409
410
0
    double dfDX1 = dfWrkXMid - dfWrkX1;
411
0
    double dfDX2 = dfWrkX2 - dfWrkXMid;
412
413
0
    if (dfDX1 * dfDX2 < 0 && nRecLevel < 30)
414
0
    {
415
0
        FindXDiscontinuity(poCT, dfX1, dfXMid, dfY, dfMinX, dfMinY, dfMaxX,
416
0
                           dfMaxY, nRecLevel + 1);
417
0
        FindXDiscontinuity(poCT, dfXMid, dfX2, dfY, dfMinX, dfMinY, dfMaxX,
418
0
                           dfMaxY, nRecLevel + 1);
419
0
    }
420
0
}
421
422
/************************************************************************/
423
/*                            ReprojectEnvelope()                       */
424
/************************************************************************/
425
426
int OGRWarpedLayer::ReprojectEnvelope(OGREnvelope *psEnvelope,
427
                                      OGRCoordinateTransformation *poCT)
428
0
{
429
0
    const int NSTEP = 20;
430
0
    double dfXStep = (psEnvelope->MaxX - psEnvelope->MinX) / NSTEP;
431
0
    double dfYStep = (psEnvelope->MaxY - psEnvelope->MinY) / NSTEP;
432
433
0
    double *padfX = static_cast<double *>(
434
0
        VSI_MALLOC_VERBOSE((NSTEP + 1) * (NSTEP + 1) * sizeof(double)));
435
0
    double *padfY = static_cast<double *>(
436
0
        VSI_MALLOC_VERBOSE((NSTEP + 1) * (NSTEP + 1) * sizeof(double)));
437
0
    int *pabSuccess = static_cast<int *>(
438
0
        VSI_MALLOC_VERBOSE((NSTEP + 1) * (NSTEP + 1) * sizeof(int)));
439
0
    if (padfX == nullptr || padfY == nullptr || pabSuccess == nullptr)
440
0
    {
441
0
        VSIFree(padfX);
442
0
        VSIFree(padfY);
443
0
        VSIFree(pabSuccess);
444
0
        return FALSE;
445
0
    }
446
447
0
    for (int j = 0; j <= NSTEP; j++)
448
0
    {
449
0
        for (int i = 0; i <= NSTEP; i++)
450
0
        {
451
0
            padfX[j * (NSTEP + 1) + i] = psEnvelope->MinX + i * dfXStep;
452
0
            padfY[j * (NSTEP + 1) + i] = psEnvelope->MinY + j * dfYStep;
453
0
        }
454
0
    }
455
456
0
    int bRet = FALSE;
457
458
0
    if (poCT->Transform((NSTEP + 1) * (NSTEP + 1), padfX, padfY, nullptr,
459
0
                        nullptr, pabSuccess))
460
0
    {
461
0
        double dfMinX = 0.0;
462
0
        double dfMinY = 0.0;
463
0
        double dfMaxX = 0.0;
464
0
        double dfMaxY = 0.0;
465
0
        int bSet = FALSE;
466
0
        for (int j = 0; j <= NSTEP; j++)
467
0
        {
468
0
            double dfXOld = 0.0;
469
0
            double dfDXOld = 0.0;
470
0
            int iOld = -1;
471
0
            int iOldOld = -1;
472
0
            for (int i = 0; i <= NSTEP; i++)
473
0
            {
474
0
                if (pabSuccess[j * (NSTEP + 1) + i])
475
0
                {
476
0
                    double dfX = padfX[j * (NSTEP + 1) + i];
477
0
                    double dfY = padfY[j * (NSTEP + 1) + i];
478
479
0
                    if (!bSet)
480
0
                    {
481
0
                        dfMinX = dfX;
482
0
                        dfMaxX = dfX;
483
0
                        dfMinY = dfY;
484
0
                        dfMaxY = dfY;
485
0
                        bSet = TRUE;
486
0
                    }
487
0
                    else
488
0
                    {
489
0
                        if (dfX < dfMinX)
490
0
                            dfMinX = dfX;
491
0
                        if (dfY < dfMinY)
492
0
                            dfMinY = dfY;
493
0
                        if (dfX > dfMaxX)
494
0
                            dfMaxX = dfX;
495
0
                        if (dfY > dfMaxY)
496
0
                            dfMaxY = dfY;
497
0
                    }
498
499
0
                    if (iOld >= 0)
500
0
                    {
501
0
                        double dfDXNew = dfX - dfXOld;
502
0
                        if (iOldOld >= 0 && dfDXNew * dfDXOld < 0)
503
0
                        {
504
0
                            FindXDiscontinuity(
505
0
                                poCT, psEnvelope->MinX + iOldOld * dfXStep,
506
0
                                psEnvelope->MinX + i * dfXStep,
507
0
                                psEnvelope->MinY + j * dfYStep, dfMinX, dfMinY,
508
0
                                dfMaxX, dfMaxY);
509
0
                        }
510
0
                        dfDXOld = dfDXNew;
511
0
                    }
512
513
0
                    dfXOld = dfX;
514
0
                    iOldOld = iOld;
515
0
                    iOld = i;
516
0
                }
517
0
            }
518
0
        }
519
0
        if (bSet)
520
0
        {
521
0
            psEnvelope->MinX = dfMinX;
522
0
            psEnvelope->MinY = dfMinY;
523
0
            psEnvelope->MaxX = dfMaxX;
524
0
            psEnvelope->MaxY = dfMaxY;
525
0
            bRet = TRUE;
526
0
        }
527
0
    }
528
529
0
    VSIFree(padfX);
530
0
    VSIFree(padfY);
531
0
    VSIFree(pabSuccess);
532
533
0
    return bRet;
534
0
}
535
536
/************************************************************************/
537
/*                             TestCapability()                         */
538
/************************************************************************/
539
540
int OGRWarpedLayer::TestCapability(const char *pszCapability) const
541
0
{
542
0
    if (EQUAL(pszCapability, OLCFastGetExtent) && sStaticEnvelope.IsInit())
543
0
        return TRUE;
544
545
0
    int bVal = m_poDecoratedLayer->TestCapability(pszCapability);
546
547
0
    if (EQUAL(pszCapability, OLCFastGetArrowStream))
548
0
        return false;
549
550
0
    if (EQUAL(pszCapability, OLCFastSpatialFilter) ||
551
0
        EQUAL(pszCapability, OLCRandomWrite) ||
552
0
        EQUAL(pszCapability, OLCSequentialWrite))
553
0
    {
554
0
        if (bVal)
555
0
            bVal = m_poReversedCT != nullptr;
556
0
    }
557
0
    else if (EQUAL(pszCapability, OLCFastFeatureCount))
558
0
    {
559
0
        if (bVal)
560
0
            bVal = m_poFilterGeom == nullptr;
561
0
    }
562
563
0
    return bVal;
564
0
}
565
566
/************************************************************************/
567
/*                              SetExtent()                             */
568
/************************************************************************/
569
570
void OGRWarpedLayer::SetExtent(double dfXMin, double dfYMin, double dfXMax,
571
                               double dfYMax)
572
0
{
573
0
    sStaticEnvelope.MinX = dfXMin;
574
0
    sStaticEnvelope.MinY = dfYMin;
575
0
    sStaticEnvelope.MaxX = dfXMax;
576
0
    sStaticEnvelope.MaxY = dfYMax;
577
0
}
578
579
/************************************************************************/
580
/*                            GetArrowStream()                          */
581
/************************************************************************/
582
583
bool OGRWarpedLayer::GetArrowStream(struct ArrowArrayStream *out_stream,
584
                                    CSLConstList papszOptions)
585
0
{
586
0
    return OGRLayer::GetArrowStream(out_stream, papszOptions);
587
0
}
588
589
#endif /* #ifndef DOXYGEN_SKIP */