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