Coverage Report

Created: 2026-04-10 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_vector_index.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  gdal "vector index" subcommand
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdalalg_vector_index.h"
14
15
#include "cpl_conv.h"
16
#include "gdal_priv.h"
17
#include "gdal_utils_priv.h"
18
#include "ogrsf_frmts.h"
19
#include "commonutils.h"
20
21
#include <algorithm>
22
#include <cassert>
23
#include <utility>
24
25
//! @cond Doxygen_Suppress
26
27
#ifndef _
28
0
#define _(x) (x)
29
#endif
30
31
/************************************************************************/
32
/*         GDALVectorIndexAlgorithm::GDALVectorIndexAlgorithm()         */
33
/************************************************************************/
34
35
GDALVectorIndexAlgorithm::GDALVectorIndexAlgorithm()
36
0
    : GDALVectorOutputAbstractAlgorithm(NAME, DESCRIPTION, HELP_URL)
37
0
{
38
0
    AddProgressArg();
39
0
    AddInputDatasetArg(&m_inputDatasets, GDAL_OF_VECTOR)
40
0
        .SetAutoOpenDataset(false)
41
0
        .SetDatasetInputFlags(GADV_NAME);
42
0
    GDALVectorOutputAbstractAlgorithm::AddAllOutputArgs();
43
44
0
    AddArg("recursive", 0,
45
0
           _("Whether input directories should be explored recursively."),
46
0
           &m_recursive);
47
0
    AddArg("filename-filter", 0,
48
0
           _("Pattern that the filenames in input directories should follow "
49
0
             "('*' and '?' wildcard)"),
50
0
           &m_filenameFilter);
51
0
    AddArg("location-name", 0, _("Name of the field with the vector path"),
52
0
           &m_locationName)
53
0
        .SetDefault(m_locationName)
54
0
        .SetMinCharCount(1);
55
0
    AddAbsolutePathArg(
56
0
        &m_writeAbsolutePaths,
57
0
        _("Whether the path to the input datasets should be stored as an "
58
0
          "absolute path"));
59
0
    AddArg("dst-crs", 0, _("Destination CRS"), &m_crs)
60
0
        .SetIsCRSArg()
61
0
        .AddHiddenAlias("t_srs");
62
63
0
    {
64
0
        auto &arg =
65
0
            AddArg("metadata", 0, _("Add dataset metadata item"), &m_metadata)
66
0
                .SetMetaVar("<KEY>=<VALUE>")
67
0
                .SetPackedValuesAllowed(false);
68
0
        arg.AddValidationAction([this, &arg]()
69
0
                                { return ParseAndValidateKeyValue(arg); });
70
0
        arg.AddHiddenAlias("mo");
71
0
    }
72
0
    AddArg("source-crs-field-name", 0,
73
0
           _("Name of the field to store the CRS of each dataset"),
74
0
           &m_sourceCrsName)
75
0
        .SetMinCharCount(1);
76
0
    auto &sourceCRSFormatArg =
77
0
        AddArg("source-crs-format", 0,
78
0
               _("Format in which the CRS of each dataset must be written"),
79
0
               &m_sourceCrsFormat)
80
0
            .SetMinCharCount(1)
81
0
            .SetDefault(m_sourceCrsFormat)
82
0
            .SetChoices("auto", "WKT", "EPSG", "PROJ");
83
0
    AddArg("source-layer-name", 0,
84
0
           _("Add layer of specified name from each source file in the tile "
85
0
             "index"),
86
0
           &m_layerNames);
87
0
    AddArg("source-layer-index", 0,
88
0
           _("Add layer of specified index (0-based) from each source file in "
89
0
             "the tile index"),
90
0
           &m_layerIndices);
91
0
    AddArg("accept-different-crs", 0,
92
0
           _("Whether layers with different CRS are accepted"),
93
0
           &m_acceptDifferentCRS);
94
0
    AddArg("accept-different-schemas", 0,
95
0
           _("Whether layers with different schemas are accepted"),
96
0
           &m_acceptDifferentSchemas);
97
0
    AddArg("dataset-name-only", 0,
98
0
           _("Whether to write the dataset name only, instead of suffixed with "
99
0
             "the layer index"),
100
0
           &m_datasetNameOnly);
101
102
    // Hidden
103
0
    AddArg("called-from-ogrtindex", 0,
104
0
           _("Whether we are called from ogrtindex"), &m_calledFromOgrTIndex)
105
0
        .SetHidden();
106
    // Hidden. For compatibility with ogrtindex
107
0
    AddArg("skip-different-crs", 0,
108
0
           _("Skip layers that are not in the same CRS as the first layer"),
109
0
           &m_skipDifferentCRS)
110
0
        .SetHidden();
111
112
0
    AddValidationAction(
113
0
        [this, &sourceCRSFormatArg]()
114
0
        {
115
0
            if (m_acceptDifferentCRS && m_skipDifferentCRS)
116
0
            {
117
0
                ReportError(CE_Failure, CPLE_IllegalArg,
118
0
                            "Options 'accept-different-crs' and "
119
0
                            "'skip-different-crs' are mutually exclusive");
120
0
                return false;
121
0
            }
122
123
0
            if (sourceCRSFormatArg.IsExplicitlySet() && m_sourceCrsName.empty())
124
0
            {
125
0
                ReportError(CE_Failure, CPLE_IllegalArg,
126
0
                            "Option 'source-crs-name' must be specified when "
127
0
                            "'source-crs-format' is specified");
128
0
                return false;
129
0
            }
130
131
0
            if (!m_crs.empty() && m_skipDifferentCRS)
132
0
            {
133
0
                ReportError(
134
0
                    CE_Warning, CPLE_AppDefined,
135
0
                    "--skip-different-crs ignored when --dst-crs specified");
136
0
            }
137
138
0
            return true;
139
0
        });
140
0
}
141
142
/************************************************************************/
143
/*                      GDALVectorDatasetIterator                       */
144
/************************************************************************/
145
146
struct GDALVectorDatasetIterator
147
{
148
    const std::vector<GDALArgDatasetValue> &inputs;
149
    const bool bRecursive;
150
    const std::vector<std::string> &filenameFilters;
151
    const std::vector<std::string> &aosLayerNamesOfInterest;
152
    const std::vector<int> &aosLayerIndicesOfInterest;
153
    std::string osCurDir{};
154
    size_t iCurSrc = 0;
155
    VSIDIR *psDir = nullptr;
156
157
    CPL_DISALLOW_COPY_ASSIGN(GDALVectorDatasetIterator)
158
159
    GDALVectorDatasetIterator(
160
        const std::vector<GDALArgDatasetValue> &inputsIn, bool bRecursiveIn,
161
        const std::vector<std::string> &filenameFiltersIn,
162
        const std::vector<std::string> &aosLayerNamesOfInterestIn,
163
        const std::vector<int> &aosLayerIndicesOfInterestIn)
164
0
        : inputs(inputsIn), bRecursive(bRecursiveIn),
165
0
          filenameFilters(filenameFiltersIn),
166
0
          aosLayerNamesOfInterest(aosLayerNamesOfInterestIn),
167
0
          aosLayerIndicesOfInterest(aosLayerIndicesOfInterestIn)
168
0
    {
169
0
    }
170
171
    void reset()
172
0
    {
173
0
        if (psDir)
174
0
            VSICloseDir(psDir);
175
0
        psDir = nullptr;
176
0
        iCurSrc = 0;
177
0
    }
178
179
    std::vector<int> GetLayerIndices(GDALDataset *poDS) const
180
0
    {
181
0
        std::vector<int> ret;
182
0
        const int nLayerCount = poDS->GetLayerCount();
183
0
        for (int i = 0; i < nLayerCount; ++i)
184
0
        {
185
0
            auto poLayer = poDS->GetLayer(i);
186
0
            if ((aosLayerNamesOfInterest.empty() &&
187
0
                 aosLayerIndicesOfInterest.empty()) ||
188
0
                (!aosLayerNamesOfInterest.empty() &&
189
0
                 std::find(aosLayerNamesOfInterest.begin(),
190
0
                           aosLayerNamesOfInterest.end(),
191
0
                           poLayer->GetDescription()) !=
192
0
                     aosLayerNamesOfInterest.end()) ||
193
0
                (!aosLayerIndicesOfInterest.empty() &&
194
0
                 std::find(aosLayerIndicesOfInterest.begin(),
195
0
                           aosLayerIndicesOfInterest.end(),
196
0
                           i) != aosLayerIndicesOfInterest.end()))
197
0
            {
198
0
                ret.push_back(i);
199
0
            }
200
0
        }
201
0
        return ret;
202
0
    }
203
204
    bool MatchPattern(const std::string &filename) const
205
0
    {
206
0
        for (const auto &osFilter : filenameFilters)
207
0
        {
208
0
            if (GDALPatternMatch(filename.c_str(), osFilter.c_str()))
209
0
            {
210
0
                return true;
211
0
            }
212
0
        }
213
0
        return filenameFilters.empty();
214
0
    }
215
216
    std::pair<std::unique_ptr<GDALDataset>, std::vector<int>> next()
217
0
    {
218
0
        std::pair<std::unique_ptr<GDALDataset>, std::vector<int>> emptyRet;
219
220
0
        while (true)
221
0
        {
222
0
            if (!psDir)
223
0
            {
224
0
                if (iCurSrc == inputs.size())
225
0
                {
226
0
                    break;
227
0
                }
228
229
0
                VSIStatBufL sStatBuf;
230
0
                const std::string &osCurName = inputs[iCurSrc++].GetName();
231
0
                if (MatchPattern(osCurName))
232
0
                {
233
0
                    auto poSrcDS = std::unique_ptr<GDALDataset>(
234
0
                        GDALDataset::Open(osCurName.c_str(), GDAL_OF_VECTOR,
235
0
                                          nullptr, nullptr, nullptr));
236
0
                    if (poSrcDS)
237
0
                    {
238
0
                        auto anLayerIndices = GetLayerIndices(poSrcDS.get());
239
0
                        if (!anLayerIndices.empty())
240
0
                        {
241
0
                            return {std::move(poSrcDS),
242
0
                                    std::move(anLayerIndices)};
243
0
                        }
244
0
                    }
245
0
                }
246
247
0
                if (VSIStatL(osCurName.c_str(), &sStatBuf) == 0 &&
248
0
                    VSI_ISDIR(sStatBuf.st_mode) &&
249
0
                    !cpl::ends_with(osCurName, ".gdb"))
250
0
                {
251
0
                    osCurDir = osCurName;
252
0
                    psDir = VSIOpenDir(osCurDir.c_str(),
253
0
                                       /*nDepth=*/bRecursive ? -1 : 0, nullptr);
254
0
                    if (!psDir)
255
0
                    {
256
0
                        CPLError(CE_Failure, CPLE_AppDefined,
257
0
                                 "Cannot open directory %s", osCurDir.c_str());
258
0
                        return emptyRet;
259
0
                    }
260
0
                }
261
0
                else
262
0
                {
263
0
                    return emptyRet;
264
0
                }
265
0
            }
266
267
0
            auto psEntry = VSIGetNextDirEntry(psDir);
268
0
            if (!psEntry)
269
0
            {
270
0
                VSICloseDir(psDir);
271
0
                psDir = nullptr;
272
0
                continue;
273
0
            }
274
275
0
            if (!MatchPattern(CPLGetFilename(psEntry->pszName)))
276
0
            {
277
0
                continue;
278
0
            }
279
280
0
            const std::string osFilename = CPLFormFilenameSafe(
281
0
                osCurDir.c_str(), psEntry->pszName, nullptr);
282
0
            auto poSrcDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
283
0
                osFilename.c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr));
284
0
            if (poSrcDS)
285
0
            {
286
0
                auto anLayerIndices = GetLayerIndices(poSrcDS.get());
287
0
                if (!anLayerIndices.empty())
288
0
                {
289
0
                    return {std::move(poSrcDS), std::move(anLayerIndices)};
290
0
                }
291
0
            }
292
0
        }
293
0
        return emptyRet;
294
0
    }
295
};
296
297
/************************************************************************/
298
/*                 GDALVectorIndexAlgorithm::RunImpl()                  */
299
/************************************************************************/
300
301
bool GDALVectorIndexAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
302
                                       void *pProgressData)
303
0
{
304
0
    CPLStringList aosSources;
305
0
    for (auto &srcDS : m_inputDatasets)
306
0
    {
307
0
        if (srcDS.GetDatasetRef())
308
0
        {
309
0
            ReportError(
310
0
                CE_Failure, CPLE_IllegalArg,
311
0
                "Input datasets must be provided by name, not as object");
312
0
            return false;
313
0
        }
314
0
        aosSources.push_back(srcDS.GetName());
315
0
    }
316
317
0
    std::string osCWD;
318
0
    if (m_writeAbsolutePaths)
319
0
    {
320
0
        char *pszCurrentPath = CPLGetCurrentDir();
321
0
        if (pszCurrentPath == nullptr)
322
0
        {
323
0
            ReportError(
324
0
                CE_Failure, CPLE_AppDefined,
325
0
                "This system does not support the CPLGetCurrentDir call.");
326
0
            return false;
327
0
        }
328
0
        osCWD = pszCurrentPath;
329
0
        CPLFree(pszCurrentPath);
330
0
    }
331
332
0
    auto setupRet = SetupOutputDataset();
333
0
    if (!setupRet.outDS)
334
0
        return false;
335
336
0
    const auto poOutDrv = setupRet.outDS->GetDriver();
337
338
0
    GDALVectorDatasetIterator oIterator(m_inputDatasets, m_recursive,
339
0
                                        m_filenameFilter, m_layerNames,
340
0
                                        m_layerIndices);
341
342
0
    if (m_outputLayerName.empty())
343
0
        m_outputLayerName = "tileindex";
344
345
0
    OGRSpatialReferenceRefCountedPtr poTargetCRS;
346
0
    if (!m_crs.empty())
347
0
    {
348
0
        poTargetCRS = OGRSpatialReferenceRefCountedPtr::makeInstance();
349
0
        poTargetCRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
350
        // already checked by GDALAlgorithmArg framework
351
0
        CPL_IGNORE_RET_VAL(poTargetCRS->SetFromUserInput(m_crs.c_str()));
352
0
    }
353
354
0
    std::set<std::string> setAlreadyReferencedLayers;
355
356
0
    const size_t nMaxFieldSize = [poOutDrv]()
357
0
    {
358
0
        const char *pszVal =
359
0
            poOutDrv ? poOutDrv->GetMetadataItem(GDAL_DMD_MAX_STRING_LENGTH)
360
0
                     : nullptr;
361
0
        return pszVal ? atoi(pszVal) : 0;
362
0
    }();
363
364
0
    OGRLayer *poDstLayer = setupRet.layer;
365
0
    int nLocationFieldIdx = -1;
366
0
    int nSourceCRSFieldIdx = -1;
367
368
0
    OGRFeatureDefnRefCountedPtr poRefFeatureDefn;
369
0
    if (poDstLayer)
370
0
    {
371
0
        nLocationFieldIdx =
372
0
            poDstLayer->GetLayerDefn()->GetFieldIndex(m_locationName.c_str());
373
0
        if (nLocationFieldIdx < 0)
374
0
        {
375
0
            ReportError(CE_Failure, CPLE_AppDefined,
376
0
                        "Unable to find field '%s' in output layer.",
377
0
                        m_locationName.c_str());
378
0
            return false;
379
0
        }
380
381
0
        if (!m_sourceCrsName.empty())
382
0
        {
383
0
            nSourceCRSFieldIdx = poDstLayer->GetLayerDefn()->GetFieldIndex(
384
0
                m_sourceCrsName.c_str());
385
0
            if (nSourceCRSFieldIdx < 0)
386
0
            {
387
0
                ReportError(CE_Failure, CPLE_AppDefined,
388
0
                            "Unable to find field '%s' in output layer.",
389
0
                            m_sourceCrsName.c_str());
390
0
                return false;
391
0
            }
392
0
        }
393
394
0
        if (!poTargetCRS)
395
0
        {
396
0
            const auto poSrcCRS = poDstLayer->GetSpatialRef();
397
0
            if (poSrcCRS)
398
0
                poTargetCRS =
399
0
                    OGRSpatialReferenceRefCountedPtr::makeClone(poSrcCRS);
400
0
        }
401
402
0
        for (auto &&poFeature : poDstLayer)
403
0
        {
404
0
            std::string osLocation =
405
0
                poFeature->GetFieldAsString(nLocationFieldIdx);
406
407
0
            if (!poRefFeatureDefn)
408
0
            {
409
0
                const auto nCommaPos = osLocation.rfind(',');
410
0
                if (nCommaPos != std::string::npos)
411
0
                {
412
0
                    auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
413
0
                        osLocation.substr(0, nCommaPos).c_str(),
414
0
                        GDAL_OF_VECTOR));
415
0
                    if (poDS)
416
0
                    {
417
0
                        if (auto poLayer = poDS->GetLayer(
418
0
                                atoi(osLocation.substr(nCommaPos + 1).c_str())))
419
0
                        {
420
0
                            poRefFeatureDefn.reset(
421
0
                                poLayer->GetLayerDefn()->Clone());
422
0
                        }
423
0
                    }
424
0
                }
425
0
            }
426
427
0
            setAlreadyReferencedLayers.insert(std::move(osLocation));
428
0
        }
429
0
    }
430
0
    else
431
0
    {
432
0
        auto [poSrcDS, anLayerIndices] = oIterator.next();
433
0
        oIterator.reset();
434
0
        if (!poSrcDS)
435
0
        {
436
0
            ReportError(CE_Failure, CPLE_AppDefined, "No layer to index");
437
0
            return false;
438
0
        }
439
440
0
        if (!poTargetCRS)
441
0
        {
442
0
            const auto poSrcCRS =
443
0
                poSrcDS->GetLayer(anLayerIndices[0])->GetSpatialRef();
444
0
            if (poSrcCRS)
445
0
                poTargetCRS =
446
0
                    OGRSpatialReferenceRefCountedPtr::makeClone(poSrcCRS);
447
0
        }
448
449
0
        poDstLayer = setupRet.outDS->CreateLayer(m_outputLayerName.c_str(),
450
0
                                                 poTargetCRS.get(), wkbPolygon);
451
0
        if (!poDstLayer)
452
0
            return false;
453
454
0
        OGRFieldDefn oLocation(m_locationName.c_str(), OFTString);
455
0
        oLocation.SetWidth(static_cast<int>(nMaxFieldSize));
456
0
        if (poDstLayer->CreateField(&oLocation) != OGRERR_NONE)
457
0
            return false;
458
0
        nLocationFieldIdx = poDstLayer->GetLayerDefn()->GetFieldCount() - 1;
459
460
0
        if (!m_sourceCrsName.empty())
461
0
        {
462
0
            OGRFieldDefn oSrcSRSNameField(m_sourceCrsName.c_str(), OFTString);
463
0
            if (poDstLayer->CreateField(&oSrcSRSNameField) != OGRERR_NONE)
464
0
                return false;
465
0
            nSourceCRSFieldIdx =
466
0
                poDstLayer->GetLayerDefn()->GetFieldCount() - 1;
467
0
        }
468
469
0
        if (!m_metadata.empty())
470
0
        {
471
0
            poDstLayer->SetMetadata(CPLStringList(m_metadata).List());
472
0
        }
473
0
    }
474
475
0
    double dfPct = 0;
476
0
    double dfIncrement = 0.1;
477
0
    int nRemainingIters = 5;
478
479
0
    bool bOK = true;
480
0
    bool bFirstWarningForNonMatchingAttributes = false;
481
0
    while (bOK)
482
0
    {
483
0
        auto [poSrcDS, anLayerIndices] = oIterator.next();
484
0
        if (!poSrcDS)
485
0
            break;
486
487
0
        dfPct += dfIncrement;
488
0
        if (pfnProgress && !pfnProgress(dfPct, "", pProgressData))
489
0
        {
490
0
            bOK = false;
491
0
            break;
492
0
        }
493
0
        --nRemainingIters;
494
0
        if (nRemainingIters == 0)
495
0
        {
496
0
            dfIncrement /= 2;
497
0
            nRemainingIters = 5;
498
0
        }
499
500
0
        std::string osFilename = poSrcDS->GetDescription();
501
0
        VSIStatBufL sStatBuf;
502
0
        if (m_writeAbsolutePaths && CPLIsFilenameRelative(osFilename.c_str()) &&
503
0
            VSIStatL(osFilename.c_str(), &sStatBuf) == 0)
504
0
        {
505
0
            osFilename =
506
0
                CPLFormFilenameSafe(osCWD.c_str(), osFilename.c_str(), nullptr)
507
0
                    .c_str();
508
0
        }
509
510
0
        for (int iLayer : anLayerIndices)
511
0
        {
512
0
            auto poSrcLayer = poSrcDS->GetLayer(iLayer);
513
514
0
            const std::string osLocation =
515
0
                m_datasetNameOnly
516
0
                    ? osFilename
517
0
                    : CPLOPrintf("%s,%d", osFilename.c_str(), iLayer);
518
0
            if (cpl::contains(setAlreadyReferencedLayers, osLocation))
519
0
            {
520
0
                ReportError(CE_Warning, CPLE_AppDefined,
521
0
                            "'%s' already referenced in tile index",
522
0
                            osLocation.c_str());
523
0
                continue;
524
0
            }
525
526
0
            const OGRSpatialReference *poSrcCRS = poSrcLayer->GetSpatialRef();
527
            // If not set target srs, test that the current file uses same
528
            // projection as others.
529
0
            if (m_crs.empty())
530
0
            {
531
0
                if ((poTargetCRS && poSrcCRS &&
532
0
                     !poTargetCRS->IsSame(poSrcCRS)) ||
533
0
                    ((poTargetCRS != nullptr) != (poSrcCRS != nullptr)))
534
0
                {
535
0
                    ReportError(
536
0
                        CE_Warning, CPLE_AppDefined,
537
0
                        "Warning: layer %s of %s is not using the same "
538
0
                        "CRS as other files in the "
539
0
                        "tileindex. This may cause problems when using it "
540
0
                        "in MapServer for example%s",
541
0
                        poSrcLayer->GetDescription(), poSrcDS->GetDescription(),
542
0
                        m_skipDifferentCRS || !m_acceptDifferentCRS
543
0
                            ? ". Skipping it"
544
0
                        : !m_skipDifferentCRS && m_calledFromOgrTIndex
545
0
                            ? ". You may specify -skip_differerence_srs to "
546
0
                              "skip it"
547
0
                            : "");
548
0
                    if (m_skipDifferentCRS || !m_acceptDifferentCRS)
549
0
                        continue;
550
0
                }
551
0
            }
552
553
0
            OGRFeature oFeat(poDstLayer->GetLayerDefn());
554
0
            oFeat.SetField(nLocationFieldIdx, osLocation.c_str());
555
556
0
            if (nSourceCRSFieldIdx >= 0 && poSrcCRS)
557
0
            {
558
0
                const char *pszAuthorityCode =
559
0
                    poSrcCRS->GetAuthorityCode(nullptr);
560
0
                const char *pszAuthorityName =
561
0
                    poSrcCRS->GetAuthorityName(nullptr);
562
0
                const std::string osWKT = poSrcCRS->exportToWkt();
563
0
                if (m_sourceCrsFormat == "auto")
564
0
                {
565
0
                    if (pszAuthorityName != nullptr &&
566
0
                        pszAuthorityCode != nullptr)
567
0
                    {
568
0
                        oFeat.SetField(nSourceCRSFieldIdx,
569
0
                                       CPLSPrintf("%s:%s", pszAuthorityName,
570
0
                                                  pszAuthorityCode));
571
0
                    }
572
0
                    else if (nMaxFieldSize == 0 ||
573
0
                             osWKT.size() <= nMaxFieldSize)
574
0
                    {
575
0
                        oFeat.SetField(nSourceCRSFieldIdx, osWKT.c_str());
576
0
                    }
577
0
                    else
578
0
                    {
579
0
                        char *pszProj4 = nullptr;
580
0
                        if (poSrcCRS->exportToProj4(&pszProj4) == OGRERR_NONE)
581
0
                        {
582
0
                            oFeat.SetField(nSourceCRSFieldIdx, pszProj4);
583
0
                        }
584
0
                        else
585
0
                        {
586
0
                            oFeat.SetField(nSourceCRSFieldIdx, osWKT.c_str());
587
0
                        }
588
0
                        CPLFree(pszProj4);
589
0
                    }
590
0
                }
591
0
                else if (m_sourceCrsFormat == "WKT")
592
0
                {
593
0
                    if (nMaxFieldSize == 0 || osWKT.size() <= nMaxFieldSize)
594
0
                    {
595
0
                        oFeat.SetField(nSourceCRSFieldIdx, osWKT.c_str());
596
0
                    }
597
0
                    else
598
0
                    {
599
0
                        ReportError(
600
0
                            CE_Warning, CPLE_AppDefined,
601
0
                            "Cannot write WKT for file %s as it is too long",
602
0
                            osFilename.c_str());
603
0
                    }
604
0
                }
605
0
                else if (m_sourceCrsFormat == "PROJ")
606
0
                {
607
0
                    char *pszProj4 = nullptr;
608
0
                    if (poSrcCRS->exportToProj4(&pszProj4) == OGRERR_NONE)
609
0
                    {
610
0
                        oFeat.SetField(nSourceCRSFieldIdx, pszProj4);
611
0
                    }
612
0
                    CPLFree(pszProj4);
613
0
                }
614
0
                else
615
0
                {
616
0
                    CPLAssert(m_sourceCrsFormat == "EPSG");
617
0
                    if (pszAuthorityName != nullptr &&
618
0
                        pszAuthorityCode != nullptr)
619
0
                    {
620
0
                        oFeat.SetField(nSourceCRSFieldIdx,
621
0
                                       CPLSPrintf("%s:%s", pszAuthorityName,
622
0
                                                  pszAuthorityCode));
623
0
                    }
624
0
                }
625
0
            }
626
627
            // Check if all layers in dataset have the same attributes schema
628
0
            if (poRefFeatureDefn == nullptr)
629
0
            {
630
0
                poRefFeatureDefn.reset(poSrcLayer->GetLayerDefn()->Clone());
631
0
            }
632
0
            else if (!m_acceptDifferentSchemas)
633
0
            {
634
0
                const OGRFeatureDefn *poFeatureDefnCur =
635
0
                    poSrcLayer->GetLayerDefn();
636
0
                assert(nullptr != poFeatureDefnCur);
637
638
0
                const auto EmitHint =
639
0
                    [this, &bFirstWarningForNonMatchingAttributes]()
640
0
                {
641
0
                    if (bFirstWarningForNonMatchingAttributes)
642
0
                    {
643
0
                        ReportError(
644
0
                            CE_Warning, CPLE_AppDefined,
645
0
                            "Note : you can override this "
646
0
                            "behavior with %s option, "
647
0
                            "but this may result in a tileindex incompatible "
648
0
                            "with MapServer",
649
0
                            m_calledFromOgrTIndex
650
0
                                ? "-accept_different_schemas"
651
0
                                : "--accept-different-schemas");
652
0
                        bFirstWarningForNonMatchingAttributes = false;
653
0
                    }
654
0
                };
655
656
0
                const int fieldCount = poFeatureDefnCur->GetFieldCount();
657
0
                if (fieldCount != poRefFeatureDefn->GetFieldCount())
658
0
                {
659
0
                    ReportError(CE_Warning, CPLE_AppDefined,
660
0
                                "Number of attributes of layer %s of %s "
661
0
                                "does not match. Skipping it.",
662
0
                                poSrcLayer->GetDescription(),
663
0
                                poSrcDS->GetDescription());
664
0
                    EmitHint();
665
0
                    continue;
666
0
                }
667
668
0
                bool bSkip = false;
669
0
                for (int fn = 0; fn < fieldCount; fn++)
670
0
                {
671
0
                    const OGRFieldDefn *poFieldThis =
672
0
                        poFeatureDefnCur->GetFieldDefn(fn);
673
0
                    const OGRFieldDefn *poFieldRef =
674
0
                        poRefFeatureDefn->GetFieldDefn(fn);
675
0
                    if (!(poFieldThis->GetType() == poFieldRef->GetType() &&
676
0
                          poFieldThis->GetWidth() == poFieldRef->GetWidth() &&
677
0
                          poFieldThis->GetPrecision() ==
678
0
                              poFieldRef->GetPrecision() &&
679
0
                          strcmp(poFieldThis->GetNameRef(),
680
0
                                 poFieldRef->GetNameRef()) == 0))
681
0
                    {
682
0
                        ReportError(CE_Warning, CPLE_AppDefined,
683
0
                                    "Schema of attributes of layer %s of %s "
684
0
                                    "does not match. Skipping it.",
685
0
                                    poSrcLayer->GetDescription(),
686
0
                                    poSrcDS->GetDescription());
687
0
                        EmitHint();
688
0
                        bSkip = true;
689
0
                        break;
690
0
                    }
691
0
                }
692
693
0
                if (bSkip)
694
0
                    continue;
695
0
            }
696
697
            // Get layer extents, and create a corresponding polygon.
698
0
            OGREnvelope sExtents;
699
0
            if (poSrcLayer->GetExtent(&sExtents, TRUE) != OGRERR_NONE)
700
0
            {
701
0
                ReportError(CE_Warning, CPLE_AppDefined,
702
0
                            "GetExtent() failed on layer %s of %s, skipping.",
703
0
                            poSrcLayer->GetDescription(),
704
0
                            poSrcDS->GetDescription());
705
0
                continue;
706
0
            }
707
708
0
            OGRPolygon oExtentGeom(sExtents);
709
710
            // If set target srs, do the forward transformation of all points.
711
0
            if (!m_crs.empty() && poSrcCRS && poTargetCRS &&
712
0
                !poSrcCRS->IsSame(poTargetCRS.get()))
713
0
            {
714
0
                auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
715
0
                    OGRCreateCoordinateTransformation(poSrcCRS,
716
0
                                                      poTargetCRS.get()));
717
0
                if (poCT == nullptr ||
718
0
                    oExtentGeom.transform(poCT.get()) == OGRERR_FAILURE)
719
0
                {
720
0
                    ReportError(CE_Warning, CPLE_AppDefined,
721
0
                                "Cannot reproject extent of layer %s of %s to "
722
0
                                "the target CRS, skipping.",
723
0
                                poSrcLayer->GetDescription(),
724
0
                                poSrcDS->GetDescription());
725
0
                    continue;
726
0
                }
727
0
            }
728
729
0
            oFeat.SetGeometry(&oExtentGeom);
730
731
0
            bOK = bOK && (poDstLayer->CreateFeature(&oFeat) == OGRERR_NONE);
732
0
        }
733
0
    }
734
735
0
    if (bOK && pfnProgress)
736
0
        pfnProgress(1.0, "", pProgressData);
737
738
0
    if (bOK && setupRet.newDS && !m_outputDataset.GetDatasetRef())
739
0
    {
740
0
        m_outputDataset.Set(std::move(setupRet.newDS));
741
0
    }
742
743
0
    return bOK;
744
0
}
745
746
//! @endcond