Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: Virtual GDAL Datasets
4 : * Purpose: Implementation of VRTDriver
5 : * Author: Frank Warmerdam <warmerdam@pobox.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2003, Frank Warmerdam <warmerdam@pobox.com>
9 : * Copyright (c) 2009-2013, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * Permission is hereby granted, free of charge, to any person obtaining a
12 : * copy of this software and associated documentation files (the "Software"),
13 : * to deal in the Software without restriction, including without limitation
14 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 : * and/or sell copies of the Software, and to permit persons to whom the
16 : * Software is furnished to do so, subject to the following conditions:
17 : *
18 : * The above copyright notice and this permission notice shall be included
19 : * in all copies or substantial portions of the Software.
20 : *
21 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 : * DEALINGS IN THE SOFTWARE.
28 : ****************************************************************************/
29 :
30 : #include "vrtdataset.h"
31 :
32 : #include "cpl_minixml.h"
33 : #include "cpl_string.h"
34 : #include "gdal_alg_priv.h"
35 : #include "gdal_frmts.h"
36 :
37 : #include <mutex>
38 :
39 : /*! @cond Doxygen_Suppress */
40 :
41 : /************************************************************************/
42 : /* VRTDriver() */
43 : /************************************************************************/
44 :
45 2438 : VRTDriver::VRTDriver() : papszSourceParsers(nullptr)
46 : {
47 : #if 0
48 : pDeserializerData = GDALRegisterTransformDeserializer(
49 : "WarpedOverviewTransformer",
50 : VRTWarpedOverviewTransform,
51 : VRTDeserializeWarpedOverviewTransformer );
52 : #endif
53 2438 : }
54 :
55 : /************************************************************************/
56 : /* ~VRTDriver() */
57 : /************************************************************************/
58 :
59 3412 : VRTDriver::~VRTDriver()
60 :
61 : {
62 1706 : CSLDestroy(papszSourceParsers);
63 1706 : VRTDerivedRasterBand::Cleanup();
64 : #if 0
65 : if( pDeserializerData )
66 : {
67 : GDALUnregisterTransformDeserializer( pDeserializerData );
68 : }
69 : #endif
70 3412 : }
71 :
72 : /************************************************************************/
73 : /* GetMetadataDomainList() */
74 : /************************************************************************/
75 :
76 0 : char **VRTDriver::GetMetadataDomainList()
77 : {
78 0 : return BuildMetadataDomainList(GDALDriver::GetMetadataDomainList(), TRUE,
79 0 : "SourceParsers", nullptr);
80 : }
81 :
82 : /************************************************************************/
83 : /* GetMetadata() */
84 : /************************************************************************/
85 :
86 467 : char **VRTDriver::GetMetadata(const char *pszDomain)
87 :
88 : {
89 467 : if (pszDomain && EQUAL(pszDomain, "SourceParsers"))
90 0 : return papszSourceParsers;
91 :
92 467 : return GDALDriver::GetMetadata(pszDomain);
93 : }
94 :
95 : /************************************************************************/
96 : /* SetMetadata() */
97 : /************************************************************************/
98 :
99 0 : CPLErr VRTDriver::SetMetadata(char **papszMetadata, const char *pszDomain)
100 :
101 : {
102 0 : if (pszDomain && EQUAL(pszDomain, "SourceParsers"))
103 : {
104 0 : m_oMapSourceParser.clear();
105 0 : CSLDestroy(papszSourceParsers);
106 0 : papszSourceParsers = CSLDuplicate(papszMetadata);
107 0 : return CE_None;
108 : }
109 :
110 0 : return GDALDriver::SetMetadata(papszMetadata, pszDomain);
111 : }
112 :
113 : /************************************************************************/
114 : /* AddSourceParser() */
115 : /************************************************************************/
116 :
117 7314 : void VRTDriver::AddSourceParser(const char *pszElementName,
118 : VRTSourceParser pfnParser)
119 :
120 : {
121 7314 : m_oMapSourceParser[pszElementName] = pfnParser;
122 :
123 : // Below won't work on architectures with "capability pointers"
124 :
125 7314 : char szPtrValue[128] = {'\0'};
126 : void *ptr;
127 7314 : CPL_STATIC_ASSERT(sizeof(pfnParser) == sizeof(void *));
128 7314 : memcpy(&ptr, &pfnParser, sizeof(void *));
129 7314 : int nRet = CPLPrintPointer(szPtrValue, ptr, sizeof(szPtrValue));
130 7314 : szPtrValue[nRet] = 0;
131 :
132 7314 : papszSourceParsers =
133 7314 : CSLSetNameValue(papszSourceParsers, pszElementName, szPtrValue);
134 7314 : }
135 :
136 : /************************************************************************/
137 : /* ParseSource() */
138 : /************************************************************************/
139 :
140 : VRTSource *
141 3013 : VRTDriver::ParseSource(const CPLXMLNode *psSrc, const char *pszVRTPath,
142 : std::map<CPLString, GDALDataset *> &oMapSharedSources)
143 :
144 : {
145 :
146 3013 : if (psSrc == nullptr || psSrc->eType != CXT_Element)
147 : {
148 0 : CPLError(CE_Failure, CPLE_AppDefined,
149 : "Corrupt or empty VRT source XML document.");
150 0 : return nullptr;
151 : }
152 :
153 3013 : if (!m_oMapSourceParser.empty())
154 : {
155 3013 : auto oIter = m_oMapSourceParser.find(psSrc->pszValue);
156 3013 : if (oIter != m_oMapSourceParser.end())
157 : {
158 1503 : return oIter->second(psSrc, pszVRTPath, oMapSharedSources);
159 : }
160 1510 : return nullptr;
161 : }
162 :
163 : // Below won't work on architectures with "capability pointers"
164 :
165 : const char *pszParserFunc =
166 0 : CSLFetchNameValue(papszSourceParsers, psSrc->pszValue);
167 0 : if (pszParserFunc == nullptr)
168 0 : return nullptr;
169 :
170 : VRTSourceParser pfnParser;
171 0 : CPL_STATIC_ASSERT(sizeof(pfnParser) == sizeof(void *));
172 : void *ptr =
173 0 : CPLScanPointer(pszParserFunc, static_cast<int>(strlen(pszParserFunc)));
174 0 : memcpy(&pfnParser, &ptr, sizeof(void *));
175 :
176 0 : if (pfnParser == nullptr)
177 0 : return nullptr;
178 :
179 0 : return pfnParser(psSrc, pszVRTPath, oMapSharedSources);
180 : }
181 :
182 : /************************************************************************/
183 : /* VRTCreateCopy() */
184 : /************************************************************************/
185 :
186 323 : static GDALDataset *VRTCreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
187 : int /* bStrict */, char **papszOptions,
188 : GDALProgressFunc /* pfnProgress */,
189 : void * /* pProgressData */)
190 : {
191 323 : CPLAssert(nullptr != poSrcDS);
192 :
193 : /* -------------------------------------------------------------------- */
194 : /* If the source dataset is a virtual dataset then just write */
195 : /* it to disk as a special case to avoid extra layers of */
196 : /* indirection. */
197 : /* -------------------------------------------------------------------- */
198 323 : if (auto poSrcVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
199 : {
200 :
201 : /* --------------------------------------------------------------------
202 : */
203 : /* Convert tree to a single block of XML text. */
204 : /* --------------------------------------------------------------------
205 : */
206 104 : char *pszVRTPath = CPLStrdup(CPLGetPath(pszFilename));
207 104 : poSrcVRTDS->UnsetPreservedRelativeFilenames();
208 104 : CPLXMLNode *psDSTree = poSrcVRTDS->SerializeToXML(pszVRTPath);
209 :
210 104 : char *pszXML = CPLSerializeXMLTree(psDSTree);
211 :
212 104 : CPLDestroyXMLNode(psDSTree);
213 :
214 104 : CPLFree(pszVRTPath);
215 :
216 : /* --------------------------------------------------------------------
217 : */
218 : /* Write to disk. */
219 : /* --------------------------------------------------------------------
220 : */
221 104 : GDALDataset *pCopyDS = nullptr;
222 :
223 104 : if (0 != strlen(pszFilename))
224 : {
225 102 : VSILFILE *fpVRT = VSIFOpenL(pszFilename, "wb");
226 102 : if (fpVRT == nullptr)
227 : {
228 1 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot create %s",
229 : pszFilename);
230 1 : CPLFree(pszXML);
231 1 : return nullptr;
232 : }
233 :
234 101 : bool bRet = VSIFWriteL(pszXML, strlen(pszXML), 1, fpVRT) > 0;
235 101 : if (VSIFCloseL(fpVRT) != 0)
236 0 : bRet = false;
237 :
238 101 : if (bRet)
239 101 : pCopyDS = GDALDataset::Open(
240 : pszFilename,
241 : GDAL_OF_RASTER | GDAL_OF_MULTIDIM_RASTER | GDAL_OF_UPDATE);
242 : }
243 : else
244 : {
245 : /* No destination file is given, so pass serialized XML directly. */
246 2 : pCopyDS = GDALDataset::Open(pszXML, GDAL_OF_RASTER |
247 : GDAL_OF_MULTIDIM_RASTER |
248 : GDAL_OF_UPDATE);
249 : }
250 :
251 103 : CPLFree(pszXML);
252 :
253 103 : return pCopyDS;
254 : }
255 :
256 : /* -------------------------------------------------------------------- */
257 : /* Multidimensional raster ? */
258 : /* -------------------------------------------------------------------- */
259 438 : auto poSrcGroup = poSrcDS->GetRootGroup();
260 219 : if (poSrcGroup != nullptr)
261 : {
262 : auto poDstDS = std::unique_ptr<GDALDataset>(
263 4 : VRTDataset::CreateMultiDimensional(pszFilename, nullptr, nullptr));
264 2 : if (!poDstDS)
265 0 : return nullptr;
266 4 : auto poDstGroup = poDstDS->GetRootGroup();
267 2 : if (!poDstGroup)
268 0 : return nullptr;
269 2 : if (GDALDriver::DefaultCreateCopyMultiDimensional(
270 2 : poSrcDS, poDstDS.get(), false, nullptr, nullptr, nullptr) !=
271 : CE_None)
272 0 : return nullptr;
273 2 : return poDstDS.release();
274 : }
275 :
276 : /* -------------------------------------------------------------------- */
277 : /* Create the virtual dataset. */
278 : /* -------------------------------------------------------------------- */
279 217 : VRTDataset *poVRTDS = static_cast<VRTDataset *>(VRTDataset::Create(
280 : pszFilename, poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(), 0,
281 : GDT_Byte, papszOptions));
282 217 : if (poVRTDS == nullptr)
283 0 : return nullptr;
284 :
285 : /* -------------------------------------------------------------------- */
286 : /* Do we have a geotransform? */
287 : /* -------------------------------------------------------------------- */
288 217 : double adfGeoTransform[6] = {0.0};
289 :
290 217 : if (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None)
291 : {
292 191 : poVRTDS->SetGeoTransform(adfGeoTransform);
293 : }
294 :
295 : /* -------------------------------------------------------------------- */
296 : /* Copy projection */
297 : /* -------------------------------------------------------------------- */
298 217 : poVRTDS->SetSpatialRef(poSrcDS->GetSpatialRef());
299 :
300 : /* -------------------------------------------------------------------- */
301 : /* Emit dataset level metadata. */
302 : /* -------------------------------------------------------------------- */
303 : const char *pszCopySrcMDD =
304 217 : CSLFetchNameValueDef(papszOptions, "COPY_SRC_MDD", "AUTO");
305 217 : char **papszSrcMDD = CSLFetchNameValueMultiple(papszOptions, "SRC_MDD");
306 217 : if (EQUAL(pszCopySrcMDD, "AUTO") || CPLTestBool(pszCopySrcMDD) ||
307 : papszSrcMDD)
308 : {
309 217 : if (!papszSrcMDD || CSLFindString(papszSrcMDD, "") >= 0 ||
310 1 : CSLFindString(papszSrcMDD, "_DEFAULT_") >= 0)
311 : {
312 215 : poVRTDS->SetMetadata(poSrcDS->GetMetadata());
313 : }
314 :
315 : /* -------------------------------------------------------------------- */
316 : /* Copy any special domains that should be transportable. */
317 : /* -------------------------------------------------------------------- */
318 216 : constexpr const char *apszDefaultDomains[] = {"RPC", "IMD",
319 : "GEOLOCATION"};
320 864 : for (const char *pszDomain : apszDefaultDomains)
321 : {
322 648 : if (!papszSrcMDD || CSLFindString(papszSrcMDD, pszDomain) >= 0)
323 : {
324 642 : char **papszMD = poSrcDS->GetMetadata(pszDomain);
325 642 : if (papszMD)
326 0 : poVRTDS->SetMetadata(papszMD, pszDomain);
327 : }
328 : }
329 :
330 216 : if ((!EQUAL(pszCopySrcMDD, "AUTO") && CPLTestBool(pszCopySrcMDD)) ||
331 : papszSrcMDD)
332 : {
333 3 : char **papszDomainList = poSrcDS->GetMetadataDomainList();
334 3 : constexpr const char *apszReservedDomains[] = {
335 : "IMAGE_STRUCTURE", "DERIVED_SUBDATASETS"};
336 15 : for (char **papszIter = papszDomainList; papszIter && *papszIter;
337 : ++papszIter)
338 : {
339 12 : const char *pszDomain = *papszIter;
340 18 : if (pszDomain[0] != 0 &&
341 6 : (!papszSrcMDD ||
342 6 : CSLFindString(papszSrcMDD, pszDomain) >= 0))
343 : {
344 5 : bool bCanCopy = true;
345 20 : for (const char *pszOtherDomain : apszDefaultDomains)
346 : {
347 15 : if (EQUAL(pszDomain, pszOtherDomain))
348 : {
349 0 : bCanCopy = false;
350 0 : break;
351 : }
352 : }
353 5 : if (!papszSrcMDD)
354 : {
355 6 : for (const char *pszOtherDomain : apszReservedDomains)
356 : {
357 5 : if (EQUAL(pszDomain, pszOtherDomain))
358 : {
359 2 : bCanCopy = false;
360 2 : break;
361 : }
362 : }
363 : }
364 5 : if (bCanCopy)
365 : {
366 3 : poVRTDS->SetMetadata(poSrcDS->GetMetadata(pszDomain),
367 3 : pszDomain);
368 : }
369 : }
370 : }
371 3 : CSLDestroy(papszDomainList);
372 : }
373 : }
374 217 : CSLDestroy(papszSrcMDD);
375 :
376 : {
377 : const char *pszInterleave =
378 217 : poSrcDS->GetMetadataItem("INTERLEAVE", "IMAGE_STRUCTURE");
379 217 : if (pszInterleave)
380 : {
381 168 : poVRTDS->SetMetadataItem("INTERLEAVE", pszInterleave,
382 168 : "IMAGE_STRUCTURE");
383 : }
384 : }
385 : {
386 : const char *pszCompression =
387 217 : poSrcDS->GetMetadataItem("COMPRESSION", "IMAGE_STRUCTURE");
388 217 : if (pszCompression)
389 : {
390 5 : poVRTDS->SetMetadataItem("COMPRESSION", pszCompression,
391 5 : "IMAGE_STRUCTURE");
392 : }
393 : }
394 :
395 : /* -------------------------------------------------------------------- */
396 : /* GCPs */
397 : /* -------------------------------------------------------------------- */
398 217 : if (poSrcDS->GetGCPCount() > 0)
399 : {
400 1 : poVRTDS->SetGCPs(poSrcDS->GetGCPCount(), poSrcDS->GetGCPs(),
401 1 : poSrcDS->GetGCPSpatialRef());
402 : }
403 :
404 : /* -------------------------------------------------------------------- */
405 : /* Loop over all the bands. */
406 : /* -------------------------------------------------------------------- */
407 501 : for (int iBand = 0; iBand < poSrcDS->GetRasterCount(); iBand++)
408 : {
409 284 : GDALRasterBand *poSrcBand = poSrcDS->GetRasterBand(iBand + 1);
410 :
411 : /* --------------------------------------------------------------------
412 : */
413 : /* Create the band with the appropriate band type. */
414 : /* --------------------------------------------------------------------
415 : */
416 568 : CPLStringList aosAddBandOptions;
417 284 : int nBlockXSize = poVRTDS->GetBlockXSize();
418 284 : int nBlockYSize = poVRTDS->GetBlockYSize();
419 284 : if (!poVRTDS->IsBlockSizeSpecified())
420 : {
421 283 : poSrcBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
422 : }
423 : aosAddBandOptions.SetNameValue("BLOCKXSIZE",
424 284 : CPLSPrintf("%d", nBlockXSize));
425 : aosAddBandOptions.SetNameValue("BLOCKYSIZE",
426 284 : CPLSPrintf("%d", nBlockYSize));
427 284 : poVRTDS->AddBand(poSrcBand->GetRasterDataType(), aosAddBandOptions);
428 :
429 : VRTSourcedRasterBand *poVRTBand = static_cast<VRTSourcedRasterBand *>(
430 284 : poVRTDS->GetRasterBand(iBand + 1));
431 :
432 : /* --------------------------------------------------------------------
433 : */
434 : /* Setup source mapping. */
435 : /* --------------------------------------------------------------------
436 : */
437 284 : poVRTBand->AddSimpleSource(poSrcBand);
438 :
439 : /* --------------------------------------------------------------------
440 : */
441 : /* Emit various band level metadata. */
442 : /* --------------------------------------------------------------------
443 : */
444 284 : poVRTBand->CopyCommonInfoFrom(poSrcBand);
445 :
446 : const char *pszCompression =
447 284 : poSrcBand->GetMetadataItem("COMPRESSION", "IMAGE_STRUCTURE");
448 284 : if (pszCompression)
449 : {
450 3 : poVRTBand->SetMetadataItem("COMPRESSION", pszCompression,
451 3 : "IMAGE_STRUCTURE");
452 : }
453 :
454 : /* --------------------------------------------------------------------
455 : */
456 : /* Add specific mask band. */
457 : /* --------------------------------------------------------------------
458 : */
459 284 : if ((poSrcBand->GetMaskFlags() &
460 284 : (GMF_PER_DATASET | GMF_ALL_VALID | GMF_NODATA)) == 0)
461 : {
462 : VRTSourcedRasterBand *poVRTMaskBand = new VRTSourcedRasterBand(
463 0 : poVRTDS, 0, poSrcBand->GetMaskBand()->GetRasterDataType(),
464 0 : poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize());
465 0 : poVRTMaskBand->AddMaskBandSource(poSrcBand);
466 0 : poVRTBand->SetMaskBand(poVRTMaskBand);
467 : }
468 : }
469 :
470 : /* -------------------------------------------------------------------- */
471 : /* Add dataset mask band */
472 : /* -------------------------------------------------------------------- */
473 217 : if (poSrcDS->GetRasterCount() != 0 &&
474 433 : poSrcDS->GetRasterBand(1) != nullptr &&
475 216 : poSrcDS->GetRasterBand(1)->GetMaskFlags() == GMF_PER_DATASET)
476 : {
477 2 : GDALRasterBand *poSrcBand = poSrcDS->GetRasterBand(1);
478 : VRTSourcedRasterBand *poVRTMaskBand = new VRTSourcedRasterBand(
479 2 : poVRTDS, 0, poSrcBand->GetMaskBand()->GetRasterDataType(),
480 2 : poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize());
481 2 : poVRTMaskBand->AddMaskBandSource(poSrcBand);
482 2 : poVRTDS->SetMaskBand(poVRTMaskBand);
483 : }
484 :
485 217 : if (strcmp(pszFilename, "") != 0)
486 : {
487 178 : CPLErrorReset();
488 178 : poVRTDS->FlushCache(true);
489 178 : if (CPLGetLastErrorType() != CE_None)
490 : {
491 11 : delete poVRTDS;
492 11 : poVRTDS = nullptr;
493 : }
494 : }
495 :
496 217 : return poVRTDS;
497 : }
498 :
499 : /************************************************************************/
500 : /* GDALRegister_VRT() */
501 : /************************************************************************/
502 :
503 4722 : void GDALRegister_VRT()
504 :
505 : {
506 4722 : if (GDALGetDriverByName("VRT") != nullptr)
507 3503 : return;
508 :
509 : static std::once_flag flag;
510 1219 : std::call_once(flag,
511 1218 : []()
512 : {
513 : // First register the pixel functions
514 1218 : GDALRegisterDefaultPixelFunc();
515 :
516 : // Register functions for VRTProcessedDataset
517 1218 : GDALVRTRegisterDefaultProcessedDatasetFuncs();
518 1218 : });
519 :
520 1219 : VRTDriver *poDriver = new VRTDriver();
521 :
522 1219 : poDriver->SetDescription("VRT");
523 1219 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
524 1219 : poDriver->SetMetadataItem(GDAL_DCAP_MULTIDIM_RASTER, "YES");
525 1219 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Virtual Raster");
526 1219 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "vrt");
527 1219 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/vrt.html");
528 1219 : poDriver->SetMetadataItem(
529 : GDAL_DMD_CREATIONDATATYPES,
530 : "Byte Int8 Int16 UInt16 Int32 UInt32 Int64 UInt64 "
531 : "Float32 Float64 "
532 1219 : "CInt16 CInt32 CFloat32 CFloat64");
533 1219 : poDriver->SetMetadataItem(
534 : GDAL_DMD_CREATIONOPTIONLIST,
535 : "<CreationOptionList>\n"
536 : " <Option name='SUBCLASS' type='string-select' "
537 : "default='VRTDataset'>\n"
538 : " <Value>VRTDataset</Value>\n"
539 : " <Value>VRTWarpedDataset</Value>\n"
540 : " </Option>\n"
541 : " <Option name='BLOCKXSIZE' type='int' description='Block width'/>\n"
542 : " <Option name='BLOCKYSIZE' type='int' description='Block height'/>\n"
543 1219 : "</CreationOptionList>\n");
544 :
545 1219 : poDriver->pfnOpen = VRTDataset::Open;
546 1219 : poDriver->pfnCreateCopy = VRTCreateCopy;
547 1219 : poDriver->pfnCreate = VRTDataset::Create;
548 1219 : poDriver->pfnCreateMultiDimensional = VRTDataset::CreateMultiDimensional;
549 1219 : poDriver->pfnIdentify = VRTDataset::Identify;
550 1219 : poDriver->pfnDelete = VRTDataset::Delete;
551 :
552 1219 : poDriver->SetMetadataItem(
553 : GDAL_DMD_OPENOPTIONLIST,
554 : "<OpenOptionList>"
555 : " <Option name='ROOT_PATH' type='string' description='Root path to "
556 : "evaluate "
557 : "relative paths inside the VRT. Mainly useful for inlined VRT, or "
558 : "in-memory "
559 : "VRT, where their own directory does not make sense'/>"
560 1219 : "</OpenOptionList>");
561 :
562 1219 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
563 1219 : poDriver->SetMetadataItem(GDAL_DCAP_COORDINATE_EPOCH, "YES");
564 :
565 1219 : poDriver->AddSourceParser("SimpleSource", VRTParseCoreSources);
566 1219 : poDriver->AddSourceParser("ComplexSource", VRTParseCoreSources);
567 1219 : poDriver->AddSourceParser("AveragedSource", VRTParseCoreSources);
568 1219 : poDriver->AddSourceParser("NoDataFromMaskSource", VRTParseCoreSources);
569 1219 : poDriver->AddSourceParser("KernelFilteredSource", VRTParseFilterSources);
570 1219 : poDriver->AddSourceParser("ArraySource", VRTParseArraySource);
571 :
572 1219 : GetGDALDriverManager()->RegisterDriver(poDriver);
573 : }
574 :
575 : /*! @endcond */
|