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