Line data Source code
1 : /******************************************************************************
2 : * Purpose: Racurs PHOTOMOD tiled format reader (http://www.racurs.ru)
3 : * Author: Andrew Sudorgin (drons [a] list dot ru)
4 : ******************************************************************************
5 : * Copyright (c) 2016, Andrew Sudorgin
6 : *
7 : * SPDX-License-Identifier: MIT
8 : ****************************************************************************/
9 :
10 : #include "cpl_minixml.h"
11 : #include "gdal.h"
12 : #include "gdal_priv.h"
13 : #include "gdal_proxy.h"
14 : #include "../vrt/vrtdataset.h"
15 :
16 : enum ph_format
17 : {
18 : ph_megatiff,
19 : ph_xdem
20 : };
21 :
22 : #define PH_PRF_DRIVER "PRF"
23 : #define PH_PRF_EXT "prf"
24 : #define PH_DEM_EXT "x-dem"
25 : #define PH_GEOREF_SHIFT_Y (1.0)
26 :
27 : class PhPrfBand final : public VRTSourcedRasterBand
28 : {
29 : std::vector<GDALRasterBand *> osOverview{};
30 :
31 : public:
32 7 : PhPrfBand(GDALDataset *poDataset, int nBandCount, GDALDataType eType,
33 : int nXSize, int nYSize)
34 7 : : VRTSourcedRasterBand(poDataset, nBandCount, eType, nXSize, nYSize)
35 : {
36 7 : }
37 :
38 3 : void AddOverview(GDALRasterBand *ov)
39 : {
40 3 : osOverview.push_back(ov);
41 3 : }
42 :
43 : int GetOverviewCount() override;
44 :
45 1 : GDALRasterBand *GetOverview(int i) override
46 : {
47 1 : size_t n = static_cast<size_t>(i);
48 1 : if (n < osOverview.size())
49 : {
50 1 : return osOverview[n];
51 : }
52 : else
53 : {
54 0 : return VRTSourcedRasterBand::GetOverview(i);
55 : }
56 : }
57 : };
58 :
59 2 : int PhPrfBand::GetOverviewCount()
60 : {
61 2 : if (!osOverview.empty())
62 : {
63 1 : return static_cast<int>(osOverview.size());
64 : }
65 : else
66 : {
67 1 : return VRTSourcedRasterBand::GetOverviewCount();
68 : }
69 : }
70 :
71 : class PhPrfDataset final : public VRTDataset
72 : {
73 : std::vector<GDALDataset *> osSubTiles{};
74 :
75 : public:
76 : PhPrfDataset(GDALAccess eAccess, int nSizeX, int nSizeY, int nBandCount,
77 : GDALDataType eType, const char *pszName);
78 : ~PhPrfDataset() override;
79 : bool AddTile(const char *pszPartName, GDALAccess eAccess, int nWidth,
80 : int nHeight, int nOffsetX, int nOffsetY, int nScale);
81 : int CloseDependentDatasets() override;
82 : static int Identify(GDALOpenInfo *poOpenInfo);
83 : static GDALDataset *Open(GDALOpenInfo *poOpenInfo);
84 : };
85 :
86 7 : PhPrfDataset::PhPrfDataset(GDALAccess _eAccess, int nSizeX, int nSizeY,
87 : int nBandCount, GDALDataType eType,
88 7 : const char *pszName)
89 7 : : VRTDataset(nSizeX, nSizeY)
90 : {
91 7 : poDriver = GetGDALDriverManager()->GetDriverByName(PH_PRF_DRIVER);
92 7 : eAccess = _eAccess;
93 7 : SetWritable(FALSE); // Avoid rewrite of *.prf file with 'vrt' file
94 7 : SetDescription(pszName);
95 :
96 14 : for (int i = 0; i != nBandCount; ++i)
97 : {
98 7 : PhPrfBand *poBand = new PhPrfBand(this, i + 1, eType, nSizeX, nSizeY);
99 7 : SetBand(i + 1, poBand);
100 : }
101 7 : }
102 :
103 14 : PhPrfDataset::~PhPrfDataset()
104 : {
105 7 : PhPrfDataset::CloseDependentDatasets();
106 14 : }
107 :
108 39 : bool PhPrfDataset::AddTile(const char *pszPartName, GDALAccess eAccessType,
109 : int nWidth, int nHeight, int nOffsetX, int nOffsetY,
110 : int nScale)
111 : {
112 : GDALProxyPoolDataset *poTileDataset;
113 39 : poTileDataset = new GDALProxyPoolDataset(pszPartName, nWidth, nHeight,
114 39 : eAccessType, FALSE);
115 :
116 78 : for (int nBand = 1; nBand != GetRasterCount() + 1; ++nBand)
117 : {
118 39 : PhPrfBand *poBand = dynamic_cast<PhPrfBand *>(GetRasterBand(nBand));
119 :
120 39 : if (poBand == nullptr)
121 : {
122 0 : delete poTileDataset;
123 0 : return false;
124 : }
125 :
126 : // Block sizes (nBlockXSize&nBlockYSize) passed as zeros.
127 : // They will be loaded when RefUnderlyingRasterBand
128 : // function is called on first open of tile's dataset 'poTileDataset'.
129 39 : poTileDataset->AddSrcBandDescription(poBand->GetRasterDataType(), 0, 0);
130 39 : GDALRasterBand *poTileBand = poTileDataset->GetRasterBand(nBand);
131 :
132 39 : if (0 == nScale)
133 : {
134 36 : poBand->AddSimpleSource(poTileBand, 0, 0, nWidth, nHeight, nOffsetX,
135 : nOffsetY, nWidth, nHeight);
136 : }
137 : else
138 : {
139 3 : poBand->AddOverview(poTileBand);
140 : }
141 : }
142 :
143 39 : osSubTiles.push_back(poTileDataset);
144 :
145 39 : return true;
146 : }
147 :
148 7 : int PhPrfDataset::CloseDependentDatasets()
149 : {
150 7 : int bDroppedRef = VRTDataset::CloseDependentDatasets();
151 46 : for (std::vector<GDALDataset *>::iterator ii(osSubTiles.begin());
152 46 : ii != osSubTiles.end(); ++ii)
153 : {
154 39 : delete (*ii);
155 39 : bDroppedRef = TRUE;
156 : }
157 7 : osSubTiles.clear();
158 7 : return bDroppedRef;
159 : }
160 :
161 56273 : int PhPrfDataset::Identify(GDALOpenInfo *poOpenInfo)
162 : {
163 56273 : if (poOpenInfo->pabyHeader == nullptr || poOpenInfo->nHeaderBytes < 20)
164 : {
165 53257 : return FALSE;
166 : }
167 :
168 3016 : if (strstr(reinterpret_cast<char *>(poOpenInfo->pabyHeader), "phini") ==
169 : nullptr)
170 : {
171 3009 : return FALSE;
172 : }
173 :
174 7 : if (poOpenInfo->IsExtensionEqualToCI(PH_PRF_EXT))
175 : {
176 4 : return TRUE;
177 : }
178 3 : else if (poOpenInfo->IsExtensionEqualToCI(PH_DEM_EXT))
179 : {
180 3 : return TRUE;
181 : }
182 :
183 0 : return FALSE;
184 : }
185 :
186 745 : static void GetXmlNameValuePair(const CPLXMLNode *psElt, CPLString &osName,
187 : CPLString &osValue)
188 : {
189 2079 : for (const CPLXMLNode *psAttr = psElt->psChild; psAttr != nullptr;
190 1334 : psAttr = psAttr->psNext)
191 : {
192 1334 : if (psAttr->eType != CXT_Attribute || psAttr->pszValue == nullptr ||
193 819 : psAttr->psChild == nullptr || psAttr->psChild->pszValue == nullptr)
194 : {
195 515 : continue;
196 : }
197 819 : if (EQUAL(psAttr->pszValue, "n"))
198 : {
199 438 : osName = psAttr->psChild->pszValue;
200 : }
201 381 : else if (EQUAL(psAttr->pszValue, "v"))
202 : {
203 381 : osValue = psAttr->psChild->pszValue;
204 : }
205 : }
206 745 : }
207 :
208 39 : static CPLString GetXmlAttribute(const CPLXMLNode *psElt,
209 : const CPLString &osAttrName,
210 : const CPLString &osDef = CPLString())
211 : {
212 39 : for (const CPLXMLNode *psAttr = psElt->psChild; psAttr != nullptr;
213 0 : psAttr = psAttr->psNext)
214 : {
215 39 : if (psAttr->eType != CXT_Attribute || psAttr->pszValue == nullptr ||
216 39 : psAttr->psChild == nullptr || psAttr->psChild->pszValue == nullptr)
217 : {
218 0 : continue;
219 : }
220 39 : if (EQUAL(psAttr->pszValue, osAttrName))
221 : {
222 39 : return psAttr->psChild->pszValue;
223 : }
224 : }
225 0 : return osDef;
226 : }
227 :
228 4 : static bool ParseGeoref(const CPLXMLNode *psGeorefElt, double *padfGeoTrans)
229 : {
230 4 : bool abOk[6] = {false, false, false, false, false, false};
231 : static const char *const apszGeoKeys[6] = {"A_0", "A_1", "A_2",
232 : "B_0", "B_1", "B_2"};
233 32 : for (const CPLXMLNode *elt = psGeorefElt->psChild; elt != nullptr;
234 28 : elt = elt->psNext)
235 : {
236 56 : CPLString osName;
237 56 : CPLString osValue;
238 28 : GetXmlNameValuePair(elt, osName, osValue);
239 196 : for (int k = 0; k != 6; ++k)
240 : {
241 168 : if (EQUAL(osName, apszGeoKeys[k]))
242 : {
243 24 : padfGeoTrans[k] = CPLAtof(osValue);
244 24 : abOk[k] = true;
245 : }
246 : }
247 : }
248 :
249 24 : for (int k = 0; k != 6; ++k)
250 : {
251 24 : if (!abOk[k])
252 : {
253 0 : break;
254 : }
255 24 : if (k == 5)
256 : {
257 4 : padfGeoTrans[3] -= PH_GEOREF_SHIFT_Y * padfGeoTrans[4];
258 4 : padfGeoTrans[3] -= PH_GEOREF_SHIFT_Y * padfGeoTrans[5];
259 4 : return true;
260 : }
261 : }
262 0 : return false;
263 : }
264 :
265 3 : static bool ParseDemShift(const CPLXMLNode *psDemShiftElt, double *padfDemShift)
266 : {
267 3 : bool abOk[6] = {false, false, false, false, false, false};
268 : static const char *const apszDemShiftKeys[6] = {"x", "y", "z", "", "", ""};
269 :
270 15 : for (const CPLXMLNode *elt = psDemShiftElt->psChild; elt != nullptr;
271 12 : elt = elt->psNext)
272 : {
273 24 : CPLString osName;
274 24 : CPLString osValue;
275 12 : GetXmlNameValuePair(elt, osName, osValue);
276 48 : for (int k = 0; k != 3; ++k)
277 : {
278 36 : if (EQUAL(osName, apszDemShiftKeys[k]))
279 : {
280 9 : padfDemShift[k] = CPLAtof(osValue);
281 9 : abOk[k] = true;
282 : }
283 : }
284 : }
285 3 : return abOk[0] && abOk[1] && abOk[2];
286 : }
287 :
288 7 : static GDALDataType ParseChannelsInfo(const CPLXMLNode *psElt)
289 : {
290 14 : CPLString osType;
291 14 : CPLString osBytesPS;
292 14 : CPLString osChannels;
293 :
294 35 : for (const CPLXMLNode *psChild = psElt->psChild; psChild != nullptr;
295 28 : psChild = psChild->psNext)
296 : {
297 28 : if (psChild->eType != CXT_Element)
298 : {
299 7 : continue;
300 : }
301 :
302 42 : CPLString osName;
303 42 : CPLString osValue;
304 :
305 21 : GetXmlNameValuePair(psChild, osName, osValue);
306 :
307 21 : if (EQUAL(osName, "type"))
308 : {
309 7 : osType = std::move(osValue);
310 : }
311 14 : else if (EQUAL(osName, "bytes_ps"))
312 : {
313 7 : osBytesPS = std::move(osValue);
314 : }
315 7 : else if (EQUAL(osName, "channels"))
316 : {
317 7 : osChannels = std::move(osValue);
318 : }
319 : }
320 :
321 7 : const int nDataTypeSize = atoi(osBytesPS);
322 7 : if (osType == "U")
323 : {
324 4 : switch (nDataTypeSize)
325 : {
326 0 : case 1:
327 0 : return GDT_Byte;
328 4 : case 2:
329 4 : return GDT_UInt16;
330 0 : case 4:
331 0 : return GDT_UInt32;
332 0 : default:
333 0 : CPLError(CE_Failure, CPLE_OpenFailed,
334 : "Unsupported datatype size %d", nDataTypeSize);
335 0 : return GDT_Unknown;
336 : }
337 : }
338 3 : else if (osType == "F")
339 : {
340 3 : switch (nDataTypeSize)
341 : {
342 3 : case 4:
343 3 : return GDT_Float32;
344 0 : case 8:
345 0 : return GDT_Float64;
346 0 : default:
347 0 : CPLError(CE_Failure, CPLE_OpenFailed,
348 : "Unsupported datatype size %d", nDataTypeSize);
349 0 : return GDT_Unknown;
350 : }
351 : }
352 :
353 0 : return GDT_Unknown;
354 : }
355 :
356 7 : GDALDataset *PhPrfDataset::Open(GDALOpenInfo *poOpenInfo)
357 : {
358 : ph_format eFormat;
359 :
360 7 : if (poOpenInfo->IsExtensionEqualToCI(PH_PRF_EXT))
361 : {
362 4 : eFormat = ph_megatiff;
363 : }
364 3 : else if (poOpenInfo->IsExtensionEqualToCI(PH_DEM_EXT))
365 : {
366 3 : eFormat = ph_xdem;
367 : }
368 : else
369 : {
370 0 : return nullptr;
371 : }
372 :
373 14 : CPLXMLTreeCloser oDoc(CPLParseXMLFile(poOpenInfo->pszFilename));
374 :
375 7 : if (oDoc.get() == nullptr)
376 : {
377 0 : return nullptr;
378 : }
379 :
380 7 : const CPLXMLNode *psPhIni(CPLSearchXMLNode(oDoc.get(), "=phini"));
381 7 : if (psPhIni == nullptr)
382 : {
383 0 : return nullptr;
384 : }
385 :
386 7 : int nSizeX = 0;
387 7 : int nSizeY = 0;
388 7 : int nBandCount = 0;
389 7 : GDALDataType eResultDatatype = GDT_Unknown;
390 14 : CPLString osPartsBasePath(CPLGetPathSafe(poOpenInfo->pszFilename));
391 14 : CPLString osPartsPath(osPartsBasePath + "/" +
392 21 : CPLGetBasenameSafe(poOpenInfo->pszFilename));
393 14 : CPLString osPartsExt;
394 7 : double adfGeoTrans[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
395 7 : bool bGeoTransOk = false;
396 :
397 7 : double adfDemShift[3] = {0.0, 0.0, 0.0};
398 7 : bool bDemShiftOk = false;
399 7 : const int nDemMDCount = 7;
400 7 : bool abDemMetadataOk[nDemMDCount] = {false, false, false, false,
401 : false, false, false};
402 7 : double adfDemMetadata[nDemMDCount] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
403 : static const char *const apszDemKeys[nDemMDCount] = {
404 : "XR_0", "XR_1", "YR_0", "YR_1", "ZR_0", "ZR_1", "BadZ"};
405 7 : if (eFormat == ph_megatiff)
406 : {
407 4 : osPartsExt = ".tif";
408 : }
409 3 : else if (eFormat == ph_xdem)
410 : {
411 3 : osPartsExt = ".demtif";
412 : }
413 :
414 187 : for (const CPLXMLNode *psElt = psPhIni->psChild; psElt != nullptr;
415 180 : psElt = psElt->psNext)
416 : {
417 180 : if (!EQUAL(psElt->pszValue, "s") || psElt->eType != CXT_Element)
418 : {
419 176 : continue;
420 : }
421 :
422 8 : CPLString osName;
423 8 : CPLString osValue;
424 :
425 4 : GetXmlNameValuePair(psElt, osName, osValue);
426 :
427 4 : if (EQUAL(osName, "parts_ext"))
428 : {
429 4 : osPartsExt = "." + osValue;
430 : }
431 : }
432 :
433 187 : for (const CPLXMLNode *psElt = psPhIni->psChild; psElt != nullptr;
434 180 : psElt = psElt->psNext)
435 : {
436 360 : CPLString osName;
437 360 : CPLString osValue;
438 :
439 180 : GetXmlNameValuePair(psElt, osName, osValue);
440 :
441 180 : if (EQUAL(osName, "ChannelsInfo"))
442 : {
443 7 : eResultDatatype = ParseChannelsInfo(psElt);
444 : }
445 173 : else if (EQUAL(osName, "Width"))
446 : {
447 7 : nSizeX = atoi(osValue);
448 : }
449 166 : else if (EQUAL(osName, "Height"))
450 : {
451 7 : nSizeY = atoi(osValue);
452 : }
453 159 : else if (EQUAL(osName, "QChans"))
454 : {
455 7 : nBandCount = atoi(osValue);
456 : }
457 152 : else if (EQUAL(osName, "GeoRef"))
458 : {
459 4 : bGeoTransOk = ParseGeoref(psElt, adfGeoTrans);
460 : }
461 148 : else if (EQUAL(osName, "DemShift"))
462 : {
463 3 : bDemShiftOk = ParseDemShift(psElt, adfDemShift);
464 : }
465 : else
466 : {
467 1160 : for (int n = 0; n != nDemMDCount; ++n)
468 : {
469 1015 : if (EQUAL(osName, apszDemKeys[n]))
470 : {
471 21 : adfDemMetadata[n] = CPLAtof(osValue);
472 21 : abDemMetadataOk[n] = true;
473 : }
474 : }
475 : }
476 : }
477 :
478 7 : if (eResultDatatype == GDT_Unknown)
479 : {
480 0 : CPLError(CE_Failure, CPLE_OpenFailed,
481 : "GDAL Dataset datatype not found");
482 0 : return nullptr;
483 : }
484 :
485 7 : if (nSizeX <= 0 || nSizeY <= 0 || nBandCount <= 0)
486 : {
487 0 : return nullptr;
488 : }
489 :
490 : PhPrfDataset *poDataset =
491 : new PhPrfDataset(GA_ReadOnly, nSizeX, nSizeY, nBandCount,
492 7 : eResultDatatype, poOpenInfo->pszFilename);
493 :
494 7 : if (!GDALCheckDatasetDimensions(poDataset->GetRasterXSize(),
495 : poDataset->GetRasterYSize()))
496 : {
497 0 : delete poDataset;
498 0 : return nullptr;
499 : }
500 :
501 187 : for (const CPLXMLNode *psElt = psPhIni->psChild; psElt != nullptr;
502 180 : psElt = psElt->psNext)
503 : {
504 180 : int nWidth = 0;
505 180 : int nHeight = 0;
506 180 : int nOffsetX = 0;
507 180 : int nOffsetY = 0;
508 180 : int nScale = 0;
509 :
510 680 : for (const CPLXMLNode *psItem = psElt->psChild; psItem != nullptr;
511 500 : psItem = psItem->psNext)
512 : {
513 1000 : CPLString osName;
514 1000 : CPLString osValue;
515 :
516 500 : GetXmlNameValuePair(psItem, osName, osValue);
517 :
518 500 : if (EQUAL(osName, "Width"))
519 : {
520 39 : nWidth = atoi(osValue);
521 : }
522 461 : else if (EQUAL(osName, "Height"))
523 : {
524 39 : nHeight = atoi(osValue);
525 : }
526 422 : else if (EQUAL(osName, "DispX"))
527 : {
528 36 : nOffsetX = atoi(osValue);
529 : }
530 386 : else if (EQUAL(osName, "DispY"))
531 : {
532 36 : nOffsetY = atoi(osValue);
533 : }
534 350 : else if (EQUAL(osName, "Scale"))
535 : {
536 3 : nScale = atoi(osValue);
537 : }
538 : }
539 :
540 180 : if (nWidth == 0 || nHeight == 0)
541 : {
542 141 : continue;
543 : }
544 :
545 78 : CPLString osPartName(osPartsPath + "/" + GetXmlAttribute(psElt, "n") +
546 39 : osPartsExt);
547 :
548 39 : if (!poDataset->AddTile(osPartName, GA_ReadOnly, nWidth, nHeight,
549 : nOffsetX, nOffsetY, nScale))
550 : {
551 0 : delete poDataset;
552 0 : return nullptr;
553 : }
554 : }
555 :
556 7 : if (eFormat == ph_megatiff && bGeoTransOk)
557 : {
558 4 : poDataset->SetGeoTransform(adfGeoTrans);
559 : }
560 :
561 7 : if (eFormat == ph_xdem)
562 : {
563 3 : GDALRasterBand *poFirstBand = poDataset->GetRasterBand(1);
564 :
565 3 : if (poFirstBand != nullptr)
566 : {
567 3 : poFirstBand->SetUnitType("m"); // Always meters.
568 : }
569 :
570 3 : if (abDemMetadataOk[0] && abDemMetadataOk[1] && abDemMetadataOk[2] &&
571 3 : abDemMetadataOk[3] && nSizeX > 1 && nSizeY > 1)
572 : {
573 3 : adfGeoTrans[0] = adfDemMetadata[0];
574 3 : adfGeoTrans[1] =
575 3 : (adfDemMetadata[1] - adfDemMetadata[0]) / (nSizeX - 1);
576 3 : adfGeoTrans[2] = 0;
577 3 : adfGeoTrans[3] = adfDemMetadata[3];
578 3 : adfGeoTrans[4] = 0;
579 3 : adfGeoTrans[5] =
580 3 : (adfDemMetadata[2] - adfDemMetadata[3]) / (nSizeY - 1);
581 :
582 3 : adfGeoTrans[0] -= 0.5 * adfGeoTrans[1];
583 3 : adfGeoTrans[3] -= 0.5 * adfGeoTrans[5];
584 :
585 3 : if (bDemShiftOk)
586 : {
587 3 : adfGeoTrans[0] += adfDemShift[0];
588 3 : adfGeoTrans[3] += adfDemShift[1];
589 : }
590 :
591 3 : poDataset->SetGeoTransform(adfGeoTrans);
592 : }
593 :
594 3 : if (abDemMetadataOk[4] && abDemMetadataOk[5])
595 : {
596 3 : poFirstBand->SetMetadataItem("STATISTICS_MINIMUM",
597 3 : CPLSPrintf("%f", adfDemMetadata[4]));
598 3 : poFirstBand->SetMetadataItem("STATISTICS_MAXIMUM",
599 3 : CPLSPrintf("%f", adfDemMetadata[5]));
600 : }
601 :
602 3 : if (abDemMetadataOk[6])
603 : {
604 3 : poFirstBand->SetNoDataValue(adfDemMetadata[6]);
605 : }
606 :
607 3 : if (bDemShiftOk)
608 : {
609 3 : poFirstBand->SetOffset(adfDemShift[2]);
610 : }
611 : }
612 :
613 : const std::string osPrj =
614 7 : CPLResetExtensionSafe(poOpenInfo->pszFilename, "prj");
615 7 : VSILFILE *const fp = VSIFOpenL(osPrj.c_str(), "rt");
616 7 : if (fp != nullptr)
617 : {
618 3 : const size_t nBufMax = 100000;
619 3 : char *const pszWKT = static_cast<char *>(CPLMalloc(nBufMax));
620 3 : const size_t nBytes = VSIFReadL(pszWKT, 1, nBufMax - 1, fp);
621 3 : VSIFCloseL(fp);
622 3 : if (nBytes > 0 && nBytes < nBufMax - 1)
623 : {
624 3 : auto poSRS = new OGRSpatialReference();
625 3 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
626 3 : pszWKT[nBytes] = '\0';
627 3 : if (poSRS->importFromWkt(pszWKT) == OGRERR_NONE)
628 : {
629 3 : poDataset->SetSpatialRef(poSRS);
630 : }
631 3 : delete poSRS;
632 : }
633 3 : CPLFree(pszWKT);
634 : }
635 :
636 7 : return poDataset;
637 : }
638 :
639 1911 : void GDALRegister_PRF()
640 : {
641 1911 : if (GDALGetDriverByName(PH_PRF_DRIVER) != nullptr)
642 282 : return;
643 :
644 1629 : GDALDriver *poDriver = new GDALDriver;
645 :
646 1629 : poDriver->SetDescription(PH_PRF_DRIVER);
647 1629 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Racurs PHOTOMOD PRF");
648 1629 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
649 1629 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
650 1629 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "prf");
651 1629 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/prf.html");
652 1629 : poDriver->pfnIdentify = PhPrfDataset::Identify;
653 1629 : poDriver->pfnOpen = PhPrfDataset::Open;
654 1629 : GetGDALDriverManager()->RegisterDriver(poDriver);
655 : }
|