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