Coverage Report

Created: 2026-11-12 08:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/pds/pdsdrivercore.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  Planetary drivers
5
 * Author:   Even Rouault
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2023, Even Rouault
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdal_frmts.h"
14
#include "gdalplugindriverproxy.h"
15
16
#include "pdsdrivercore.h"
17
18
#include "nasakeywordhandler.h"
19
20
/************************************************************************/
21
/*                    GetVICARLabelOffsetFromPDS3()                     */
22
/************************************************************************/
23
24
vsi_l_offset GetVICARLabelOffsetFromPDS3(const char *pszHdr, VSILFILE *fp,
25
                                         std::string &osVICARHeader)
26
0
{
27
0
    const char *pszPDSVersionID = strstr(pszHdr, "PDS_VERSION_ID");
28
0
    int nOffset = 0;
29
0
    if (pszPDSVersionID)
30
0
        nOffset = static_cast<int>(pszPDSVersionID - pszHdr);
31
32
0
    NASAKeywordHandler oKeywords;
33
0
    if (oKeywords.Ingest(fp, nOffset))
34
0
    {
35
0
        const int nRecordBytes =
36
0
            atoi(oKeywords.GetKeyword("RECORD_BYTES", "0"));
37
0
        const int nImageHeader =
38
0
            atoi(oKeywords.GetKeyword("^IMAGE_HEADER", "0"));
39
0
        if (nRecordBytes > 0 && nImageHeader > 0)
40
0
        {
41
0
            const auto nImgHeaderOffset =
42
0
                static_cast<vsi_l_offset>(nImageHeader - 1) * nRecordBytes;
43
0
            osVICARHeader.resize(1024);
44
0
            size_t nMemb;
45
0
            if (VSIFSeekL(fp, nImgHeaderOffset, SEEK_SET) == 0 &&
46
0
                (nMemb = VSIFReadL(&osVICARHeader[0], 1, osVICARHeader.size(),
47
0
                                   fp)) != 0 &&
48
0
                osVICARHeader.find("LBLSIZE") != std::string::npos)
49
0
            {
50
0
                osVICARHeader.resize(nMemb);
51
0
                return nImgHeaderOffset;
52
0
            }
53
0
        }
54
0
    }
55
0
    return 0;
56
0
}
57
58
/************************************************************************/
59
/*                         PDSDriverIdentify()                          */
60
/************************************************************************/
61
62
int PDSDriverIdentify(GDALOpenInfo *poOpenInfo)
63
64
557k
{
65
557k
    if (poOpenInfo->pabyHeader == nullptr || poOpenInfo->fpL == nullptr)
66
429k
        return FALSE;
67
68
127k
    const char *pszHdr = reinterpret_cast<char *>(poOpenInfo->pabyHeader);
69
127k
    if (strstr(pszHdr, "PDS_VERSION_ID") == nullptr &&
70
124k
        strstr(pszHdr, "ODL_VERSION_ID") == nullptr)
71
114k
    {
72
114k
        return FALSE;
73
114k
    }
74
75
    // Some PDS3 images include a VICAR header pointed by ^IMAGE_HEADER.
76
    // If the user sets GDAL_TRY_PDS3_WITH_VICAR=YES, then we will gracefully
77
    // hand over the file to the VICAR dataset.
78
13.5k
    std::string unused;
79
13.5k
    if (CPLTestBool(CPLGetConfigOption("GDAL_TRY_PDS3_WITH_VICAR", "NO")) &&
80
0
        !STARTS_WITH(poOpenInfo->pszFilename, "/vsisubfile/") &&
81
0
        GetVICARLabelOffsetFromPDS3(pszHdr, poOpenInfo->fpL, unused) > 0)
82
0
    {
83
0
        CPLDebug("PDS3", "File is detected to have a VICAR header. "
84
0
                         "Handing it over to the VICAR driver");
85
0
        return FALSE;
86
0
    }
87
88
13.5k
    return TRUE;
89
13.5k
}
90
91
/************************************************************************/
92
/*                     PDSDriverSetCommonMetadata()                     */
93
/************************************************************************/
94
95
void PDSDriverSetCommonMetadata(GDALDriver *poDriver)
96
24
{
97
24
    poDriver->SetDescription(PDS_DRIVER_NAME);
98
24
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
99
24
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "NASA Planetary Data System");
100
24
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/pds.html");
101
24
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
102
103
24
    poDriver->pfnIdentify = PDSDriverIdentify;
104
24
    poDriver->SetMetadataItem(GDAL_DCAP_OPEN, "YES");
105
24
}
106
107
/************************************************************************/
108
/*                         PDS4DriverIdentify()                         */
109
/************************************************************************/
110
111
int PDS4DriverIdentify(GDALOpenInfo *poOpenInfo)
112
739k
{
113
739k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "PDS4:"))
114
0
        return TRUE;
115
739k
    if (poOpenInfo->nHeaderBytes == 0)
116
487k
        return FALSE;
117
118
252k
    const auto HasProductSomethingRootElement = [](const char *pszStr)
119
257k
    {
120
257k
        return strstr(pszStr, "Product_Observational") != nullptr ||
121
252k
               strstr(pszStr, "Product_Ancillary") != nullptr ||
122
249k
               strstr(pszStr, "Product_Browse") != nullptr ||
123
249k
               strstr(pszStr, "Product_Collection") != nullptr;
124
257k
    };
125
252k
    const auto HasPDS4Schema = [](const char *pszStr)
126
257k
    { return strstr(pszStr, "://pds.nasa.gov/pds4/pds/v1") != nullptr; };
127
128
261k
    for (int i = 0; i < 2; ++i)
129
257k
    {
130
257k
        const char *pszHeader =
131
257k
            reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
132
257k
        int nMatches = 0;
133
257k
        if (HasProductSomethingRootElement(pszHeader))
134
9.69k
            nMatches++;
135
257k
        if (HasPDS4Schema(pszHeader))
136
5.17k
            nMatches++;
137
257k
        if (nMatches == 2)
138
2.57k
        {
139
2.57k
            return TRUE;
140
2.57k
        }
141
254k
        if (i == 0)
142
249k
        {
143
249k
            if (nMatches == 0 || poOpenInfo->nHeaderBytes >= 8192)
144
244k
                break;
145
            // If we have found one of the 2 matching elements to identify
146
            // PDS4 products, but have only ingested the default 1024 bytes,
147
            // then try to ingest more.
148
5.03k
            poOpenInfo->TryToIngest(8192);
149
5.03k
        }
150
254k
    }
151
249k
    return FALSE;
152
252k
}
153
154
/************************************************************************/
155
/*                    PDS4DriverSetCommonMetadata()                     */
156
/************************************************************************/
157
158
void PDS4DriverSetCommonMetadata(GDALDriver *poDriver)
159
24
{
160
24
    poDriver->SetDescription(PDS4_DRIVER_NAME);
161
24
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
162
24
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
163
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
164
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
165
24
    poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
166
24
    poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES");
167
24
    poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS,
168
24
                              "Name Type WidthPrecision");
169
24
    poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
170
171
24
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
172
24
                              "NASA Planetary Data System 4");
173
24
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/pds4.html");
174
24
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "xml");
175
24
    poDriver->SetMetadataItem(
176
24
        GDAL_DMD_CREATIONDATATYPES,
177
24
        "Byte Int8 UInt16 Int16 UInt32 Int32 UInt64 Int64 "
178
24
        "Float32 Float64 CFloat32 CFloat64");
179
24
    poDriver->SetMetadataItem(GDAL_DMD_OPENOPTIONLIST, "<OpenOptionList/>");
180
24
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
181
24
    poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
182
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_SUBDATASETS, "YES");
183
24
    poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
184
185
24
    poDriver->SetMetadataItem(
186
24
        GDAL_DMD_OPENOPTIONLIST,
187
24
        "<OpenOptionList>"
188
24
        "  <Option name='LAT' type='string' scope='vector' description="
189
24
        "'Name of a field containing a Latitude value' default='Latitude'/>"
190
24
        "  <Option name='LONG' type='string' scope='vector' description="
191
24
        "'Name of a field containing a Longitude value' default='Longitude'/>"
192
24
        "  <Option name='ALT' type='string' scope='vector' description="
193
24
        "'Name of a field containing a Altitude value' default='Altitude'/>"
194
24
        "  <Option name='WKT' type='string' scope='vector' description="
195
24
        "'Name of a field containing a geometry encoded in the WKT format' "
196
24
        "default='WKT'/>"
197
24
        "  <Option name='KEEP_GEOM_COLUMNS' scope='vector' type='boolean' "
198
24
        "description="
199
24
        "'whether to add original x/y/geometry columns as regular fields.' "
200
24
        "default='NO' />"
201
24
        "</OpenOptionList>");
202
203
24
    poDriver->SetMetadataItem(
204
24
        GDAL_DMD_CREATIONOPTIONLIST,
205
24
        "<CreationOptionList>"
206
24
        "  <Option name='IMAGE_FILENAME' type='string' scope='raster' "
207
24
        "description="
208
24
        "'Image filename'/>"
209
24
        "  <Option name='IMAGE_EXTENSION' type='string' scope='raster' "
210
24
        "description="
211
24
        "'Extension of the binary raw/geotiff file'/>"
212
24
        "  <Option name='CREATE_LABEL_ONLY' scope='raster' type='boolean' "
213
24
        "description="
214
24
        "'whether to create only the XML label when converting from an "
215
24
        "existing raw format.' default='NO' />"
216
24
        "  <Option name='IMAGE_FORMAT' type='string-select' scope='raster' "
217
24
        "description='Format of the image file' default='RAW'>"
218
24
        "     <Value>RAW</Value>"
219
24
        "     <Value>GEOTIFF</Value>"
220
24
        "  </Option>"
221
#ifdef notdef
222
        "  <Option name='GEOTIFF_OPTIONS' type='string' scope='raster' "
223
        "description='Comma separated list of KEY=VALUE tuples to forward "
224
        "to the GeoTIFF driver'/>"
225
#endif
226
24
        "  <Option name='INTERLEAVE' type='string-select' scope='raster' "
227
24
        "description="
228
24
        "'Pixel organization' default='BSQ'>"
229
24
        "     <Value>BSQ</Value>"
230
24
        "     <Value>BIP</Value>"
231
24
        "     <Value>BIL</Value>"
232
24
        "  </Option>"
233
24
        "  <Option name='VAR_*' type='string' scope='raster,vector' "
234
24
        "description="
235
24
        "'Value to substitute to a variable in the template'/>"
236
24
        "  <Option name='TEMPLATE' type='string' scope='raster,vector' "
237
24
        "description="
238
24
        "'.xml template to use'/>"
239
24
        "  <Option name='USE_SRC_LABEL' type='boolean' scope='raster' "
240
24
        "description='Whether to use source label in PDS4 to PDS4 conversions' "
241
24
        "default='YES'/>"
242
24
        "  <Option name='LATITUDE_TYPE' type='string-select' "
243
24
        "scope='raster,vector' "
244
24
        "description='Value of latitude_type' default='Planetocentric'>"
245
24
        "     <Value>Planetocentric</Value>"
246
24
        "     <Value>Planetographic</Value>"
247
24
        "  </Option>"
248
24
        "  <Option name='LONGITUDE_DIRECTION' type='string-select' "
249
24
        "scope='raster,vector' "
250
24
        "description='Value of longitude_direction' "
251
24
        "default='Positive East'>"
252
24
        "     <Value>Positive East</Value>"
253
24
        "     <Value>Positive West</Value>"
254
24
        "  </Option>"
255
24
        "  <Option name='RADII' type='string' scope='raster,vector' "
256
24
        "description='Value of form "
257
24
        "semi_major_radius,semi_minor_radius to override the ones of the SRS'/>"
258
24
        "  <Option name='ARRAY_TYPE' type='string-select' scope='raster' "
259
24
        "description='Name of the "
260
24
        "Array XML element' default='Array_3D_Image'>"
261
24
        "     <Value>Array</Value>"
262
24
        "     <Value>Array_2D</Value>"
263
24
        "     <Value>Array_2D_Image</Value>"
264
24
        "     <Value>Array_2D_Map</Value>"
265
24
        "     <Value>Array_2D_Spectrum</Value>"
266
24
        "     <Value>Array_3D</Value>"
267
24
        "     <Value>Array_3D_Image</Value>"
268
24
        "     <Value>Array_3D_Movie</Value>"
269
24
        "     <Value>Array_3D_Spectrum</Value>"
270
24
        "  </Option>"
271
24
        "  <Option name='ARRAY_IDENTIFIER' type='string' scope='raster' "
272
24
        "description='Identifier to put in the Array element'/>"
273
24
        "  <Option name='UNIT' type='string' scope='raster' "
274
24
        "description='Name of the unit of the array elements'/>"
275
24
        "  <Option name='BOUNDING_DEGREES' type='string' scope='raster,vector' "
276
24
        "description='Manually set bounding box with the syntax "
277
24
        "west_lon,south_lat,east_lon,north_lat'/>"
278
24
        "  <Option name='PROPAGATE_SRC_METADATA' type='boolean' scope='raster' "
279
24
        "description='Whether to propagate particular metadata domains, such "
280
24
        "as json:ISIS3' default='YES'/>"
281
24
        "</CreationOptionList>");
282
283
24
    poDriver->SetMetadataItem(
284
24
        GDAL_DS_LAYER_CREATIONOPTIONLIST,
285
24
        "<LayerCreationOptionList>"
286
24
        "  <Option name='TABLE_TYPE' type='string-select' description='Type of "
287
24
        "table' default='DELIMITED'>"
288
24
        "     <Value>DELIMITED</Value>"
289
24
        "     <Value>CHARACTER</Value>"
290
24
        "     <Value>BINARY</Value>"
291
24
        "  </Option>"
292
24
        "  <Option name='LINE_ENDING' type='string-select' description="
293
24
        "'end-of-line sequence. Only applies for "
294
24
        "TABLE_TYPE=DELIMITED/CHARACTER' "
295
24
        "default='CRLF'>"
296
24
        "    <Value>CRLF</Value>"
297
24
        "    <Value>LF</Value>"
298
24
        "  </Option>"
299
24
        "  <Option name='GEOM_COLUMNS' type='string-select' description='How "
300
24
        "geometry is encoded' default='AUTO'>"
301
24
        "     <Value>AUTO</Value>"
302
24
        "     <Value>WKT</Value>"
303
24
        "     <Value>LONG_LAT</Value>"
304
24
        "  </Option>"
305
24
        "  <Option name='CREATE_VRT' type='boolean' description='Whether to "
306
24
        "generate "
307
24
        "a OGR VRT file. Only applies for TABLE_TYPE=DELIMITED' default='YES'/>"
308
24
        "  <Option name='LAT' type='string' description="
309
24
        "'Name of a field containing a Latitude value' default='Latitude'/>"
310
24
        "  <Option name='LONG' type='string' description="
311
24
        "'Name of a field containing a Longitude value' default='Longitude'/>"
312
24
        "  <Option name='ALT' type='string' description="
313
24
        "'Name of a field containing a Altitude value' default='Altitude'/>"
314
24
        "  <Option name='WKT' type='string' description="
315
24
        "'Name of a field containing a WKT value' default='WKT'/>"
316
24
        "</LayerCreationOptionList>");
317
318
24
    poDriver->SetMetadataItem(
319
24
        GDAL_DMD_CREATIONFIELDDATATYPES,
320
24
        "Integer Integer64 Real String Date DateTime Time");
321
24
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES, "Boolean");
322
323
24
    poDriver->pfnIdentify = PDS4DriverIdentify;
324
24
    poDriver->SetMetadataItem(GDAL_DCAP_OPEN, "YES");
325
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE, "YES");
326
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATECOPY, "YES");
327
328
24
    poDriver->SetMetadataItem(GDAL_DCAP_UPDATE, "YES");
329
24
    poDriver->SetMetadataItem(GDAL_DMD_UPDATE_ITEMS, "Features");
330
24
}
331
332
/************************************************************************/
333
/*                        ISIS2DriverIdentify()                         */
334
/************************************************************************/
335
336
int ISIS2DriverIdentify(GDALOpenInfo *poOpenInfo)
337
556k
{
338
556k
    if (poOpenInfo->pabyHeader == nullptr)
339
429k
        return FALSE;
340
341
126k
    if (strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
342
126k
               "^QUBE") == nullptr)
343
115k
        return FALSE;
344
345
11.2k
    return TRUE;
346
126k
}
347
348
/************************************************************************/
349
/*                    ISIS2DriverSetCommonMetadata()                    */
350
/************************************************************************/
351
352
void ISIS2DriverSetCommonMetadata(GDALDriver *poDriver)
353
24
{
354
24
    poDriver->SetDescription(ISIS2_DRIVER_NAME);
355
24
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
356
24
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
357
24
                              "USGS Astrogeology ISIS cube (Version 2)");
358
24
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/isis2.html");
359
24
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
360
361
24
    poDriver->pfnIdentify = ISIS2DriverIdentify;
362
24
    poDriver->SetMetadataItem(GDAL_DCAP_OPEN, "YES");
363
24
}
364
365
/************************************************************************/
366
/*                        ISIS3DriverIdentify()                         */
367
/************************************************************************/
368
369
int ISIS3DriverIdentify(GDALOpenInfo *poOpenInfo)
370
556k
{
371
556k
    if (poOpenInfo->fpL != nullptr && poOpenInfo->pabyHeader != nullptr &&
372
127k
        strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
373
127k
               "IsisCube") != nullptr)
374
12.4k
        return TRUE;
375
376
544k
    return FALSE;
377
556k
}
378
379
/************************************************************************/
380
/*                    ISIS3DriverSetCommonMetadata()                    */
381
/************************************************************************/
382
383
void ISIS3DriverSetCommonMetadata(GDALDriver *poDriver)
384
24
{
385
24
    poDriver->SetDescription(ISIS3_DRIVER_NAME);
386
24
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
387
24
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
388
24
                              "USGS Astrogeology ISIS cube (Version 3)");
389
24
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/isis3.html");
390
24
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
391
24
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "lbl cub");
392
24
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
393
24
                              "Byte UInt16 Int16 Float32");
394
24
    poDriver->SetMetadataItem(GDAL_DMD_OPENOPTIONLIST, "<OpenOptionList/>");
395
396
24
    poDriver->SetMetadataItem(
397
24
        GDAL_DMD_OPENOPTIONLIST,
398
24
        "<OpenOptionList>"
399
24
        "  <Option name='INCLUDE_OFFLINE_CONTENT' type='boolean' default='YES' "
400
24
        "description='Whether to include a _data member in "
401
24
        "json:ISIS3 metadata with offline content of label objects'/>"
402
24
        "  <Option name='MAX_SIZE_OFFLINE_CONTENT' type='string' "
403
24
        "default='100000000' description='Maximum size of offline content to "
404
24
        "include in _data member, in bytes' min='0'/>"
405
24
        "</OpenOptionList>");
406
407
24
    poDriver->SetMetadataItem(
408
24
        GDAL_DMD_CREATIONOPTIONLIST,
409
24
        "<CreationOptionList>"
410
24
        "  <Option name='DATA_LOCATION' type='string-select' "
411
24
        "description='Location of pixel data' default='LABEL'>"
412
24
        "     <Value>LABEL</Value>"
413
24
        "     <Value>EXTERNAL</Value>"
414
24
        "     <Value>GEOTIFF</Value>"
415
24
        "  </Option>"
416
24
        "  <Option name='GEOTIFF_AS_REGULAR_EXTERNAL' type='boolean' "
417
24
        "description='Whether the GeoTIFF file, if uncompressed, should be "
418
24
        "registered as a regular raw file' default='YES'/>"
419
24
        "  <Option name='GEOTIFF_OPTIONS' type='string' "
420
24
        "description='Comma separated list of KEY=VALUE tuples to forward "
421
24
        "to the GeoTIFF driver'/>"
422
24
        "  <Option name='EXTERNAL_FILENAME' type='string' "
423
24
        "description='Override default external filename. "
424
24
        "Only for DATA_LOCATION=EXTERNAL or GEOTIFF'/>"
425
24
        "  <Option name='TILED' type='boolean' "
426
24
        "description='Whether the pixel data should be tiled' default='NO'/>"
427
24
        "  <Option name='BLOCKXSIZE' type='int' "
428
24
        "description='Tile width' default='256'/>"
429
24
        "  <Option name='BLOCKYSIZE' type='int' "
430
24
        "description='Tile height' default='256'/>"
431
24
        "  <Option name='COMMENT' type='string' "
432
24
        "description='Comment to add into the label'/>"
433
24
        "  <Option name='LATITUDE_TYPE' type='string-select' "
434
24
        "description='Value of Mapping.LatitudeType' default='Planetocentric'>"
435
24
        "     <Value>Planetocentric</Value>"
436
24
        "     <Value>Planetographic</Value>"
437
24
        "  </Option>"
438
24
        "  <Option name='LONGITUDE_DIRECTION' type='string-select' "
439
24
        "description='Value of Mapping.LongitudeDirection' "
440
24
        "default='PositiveEast'>"
441
24
        "     <Value>PositiveEast</Value>"
442
24
        "     <Value>PositiveWest</Value>"
443
24
        "  </Option>"
444
24
        "  <Option name='TARGET_NAME' type='string' description='Value of "
445
24
        "Mapping.TargetName'/>"
446
24
        "  <Option name='FORCE_360' type='boolean' "
447
24
        "description='Whether to force longitudes in [0,360] range' "
448
24
        "default='NO'/>"
449
24
        "  <Option name='WRITE_BOUNDING_DEGREES' type='boolean' "
450
24
        "description='Whether to write Min/MaximumLong/Latitude values' "
451
24
        "default='YES'/>"
452
24
        "  <Option name='BOUNDING_DEGREES' type='string' "
453
24
        "description='Manually set bounding box with the syntax "
454
24
        "min_long,min_lat,max_long,max_lat'/>"
455
24
        "  <Option name='USE_SRC_LABEL' type='boolean' "
456
24
        "description='Whether to use source label in ISIS3 to ISIS3 "
457
24
        "conversions' "
458
24
        "default='YES'/>"
459
24
        "  <Option name='USE_SRC_MAPPING' type='boolean' "
460
24
        "description='Whether to use Mapping group from source label in "
461
24
        "ISIS3 to ISIS3 conversions' "
462
24
        "default='NO'/>"
463
24
        "  <Option name='USE_SRC_HISTORY' type='boolean' "
464
24
        "description='Whether to use content pointed by the History object in "
465
24
        "ISIS3 to ISIS3 conversions' "
466
24
        "default='YES'/>"
467
24
        "  <Option name='ADD_GDAL_HISTORY' type='boolean' "
468
24
        "description='Whether to add GDAL specific history in the content "
469
24
        "pointed "
470
24
        "by the History object in "
471
24
        "ISIS3 to ISIS3 conversions' "
472
24
        "default='YES'/>"
473
24
        "  <Option name='GDAL_HISTORY' type='string' "
474
24
        "description='Manually defined GDAL history. Must be formatted as "
475
24
        "ISIS3 "
476
24
        "PDL. If not specified, it is automatically composed.'/>"
477
24
        "</CreationOptionList>");
478
479
24
    poDriver->pfnIdentify = ISIS3DriverIdentify;
480
24
    poDriver->SetMetadataItem(GDAL_DCAP_OPEN, "YES");
481
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE, "YES");
482
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATECOPY, "YES");
483
24
}
484
485
/************************************************************************/
486
/*                        VICARGetLabelOffset()                         */
487
/************************************************************************/
488
489
vsi_l_offset VICARGetLabelOffset(GDALOpenInfo *poOpenInfo)
490
491
738k
{
492
738k
    if (poOpenInfo->pabyHeader == nullptr || poOpenInfo->fpL == nullptr)
493
480k
        return static_cast<vsi_l_offset>(-1);
494
495
257k
    const auto HasFoundVICARKeywords = [](const char *pszHeader)
496
257k
    {
497
121k
        return strstr(pszHeader, "LBLSIZE") != nullptr &&
498
665
               strstr(pszHeader, "FORMAT") != nullptr &&
499
541
               strstr(pszHeader, "NL") != nullptr &&
500
538
               strstr(pszHeader, "NS") != nullptr &&
501
536
               strstr(pszHeader, "NB") != nullptr;
502
121k
    };
503
504
257k
    std::string osHeader;
505
257k
    const char *pszHeader =
506
257k
        reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
507
    // Some PDS3 images include a VICAR header pointed by ^IMAGE_HEADER.
508
    // If the user sets GDAL_TRY_PDS3_WITH_VICAR=YES, then we will gracefully
509
    // hand over the file to the VICAR dataset.
510
257k
    vsi_l_offset nOffset = 0;
511
257k
    const bool bTryPDS3WithVicar =
512
257k
        CPLTestBool(CPLGetConfigOption("GDAL_TRY_PDS3_WITH_VICAR", "NO")) &&
513
0
        !STARTS_WITH(poOpenInfo->pszFilename, "/vsisubfile/");
514
257k
    if (bTryPDS3WithVicar && (nOffset = GetVICARLabelOffsetFromPDS3(
515
0
                                  pszHeader, poOpenInfo->fpL, osHeader)) > 0)
516
0
    {
517
0
        pszHeader = osHeader.c_str();
518
0
    }
519
520
257k
    if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0 &&
521
136k
        (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0)
522
136k
    {
523
        // If opening in vector-only mode, then check when have NBB != 0
524
136k
        const char *pszNBB = strstr(pszHeader, "NBB");
525
136k
        if (pszNBB == nullptr)
526
135k
            return static_cast<vsi_l_offset>(-1);
527
1.18k
        const char *pszEqualSign = strchr(pszNBB, '=');
528
1.18k
        if (pszEqualSign == nullptr)
529
377
            return static_cast<vsi_l_offset>(-1);
530
803
        if (atoi(pszEqualSign + 1) == 0)
531
344
            return static_cast<vsi_l_offset>(-1);
532
803
    }
533
534
121k
    if (HasFoundVICARKeywords(pszHeader))
535
536
    {
536
        // If we find VICAR keywords, but the file starts with PDS_VERSION_ID,
537
        // it might be a PDS3 image that includes a VICAR header. Check if
538
        // this is the case.
539
536
        if (nOffset == 0 && STARTS_WITH(pszHeader, "PDS_VERSION_ID"))
540
2
        {
541
2
            if (!bTryPDS3WithVicar &&
542
2
                (!GDALGetDriverByName("PDS") ||
543
2
                 poOpenInfo->IsSingleAllowedDriver("VICAR")))
544
0
            {
545
0
                const auto nOffset2 = GetVICARLabelOffsetFromPDS3(
546
0
                    pszHeader, poOpenInfo->fpL, osHeader);
547
0
                if (nOffset2 > 0 && HasFoundVICARKeywords(osHeader.c_str()))
548
0
                {
549
0
                    return nOffset2;
550
0
                }
551
0
            }
552
2
        }
553
536
        return nOffset;
554
536
    }
555
120k
    return static_cast<vsi_l_offset>(-1);
556
121k
}
557
558
/************************************************************************/
559
/*                        VICARDriverIdentify()                         */
560
/************************************************************************/
561
562
static int VICARDriverIdentify(GDALOpenInfo *poOpenInfo)
563
737k
{
564
737k
    return VICARGetLabelOffset(poOpenInfo) != static_cast<vsi_l_offset>(-1);
565
737k
}
566
567
/************************************************************************/
568
/*                    VICARDriverSetCommonMetadata()                    */
569
/************************************************************************/
570
571
void VICARDriverSetCommonMetadata(GDALDriver *poDriver)
572
24
{
573
24
    poDriver->SetDescription(VICAR_DRIVER_NAME);
574
24
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
575
24
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
576
24
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "MIPL VICAR file");
577
24
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/vicar.html");
578
24
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
579
24
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
580
24
                              "Byte Int16 Int32 Float32 Float64 CFloat32");
581
24
    poDriver->SetMetadataItem(
582
24
        GDAL_DMD_CREATIONOPTIONLIST,
583
24
        "<CreationOptionList>"
584
24
        "  <Option name='GEOREF_FORMAT' type='string-select' "
585
24
        "description='How to encode georeferencing information' "
586
24
        "default='MIPL'>"
587
24
        "     <Value>MIPL</Value>"
588
24
        "     <Value>GEOTIFF</Value>"
589
24
        "  </Option>"
590
24
        "  <Option name='COORDINATE_SYSTEM_NAME' type='string-select' "
591
24
        "description='Value of MAP.COORDINATE_SYSTEM_NAME' "
592
24
        "default='PLANETOCENTRIC'>"
593
24
        "     <Value>PLANETOCENTRIC</Value>"
594
24
        "     <Value>PLANETOGRAPHIC</Value>"
595
24
        "  </Option>"
596
24
        "  <Option name='POSITIVE_LONGITUDE_DIRECTION' type='string-select' "
597
24
        "description='Value of MAP.POSITIVE_LONGITUDE_DIRECTION' "
598
24
        "default='EAST'>"
599
24
        "     <Value>EAST</Value>"
600
24
        "     <Value>WEST</Value>"
601
24
        "  </Option>"
602
24
        "  <Option name='TARGET_NAME' type='string' description='Value of "
603
24
        "MAP.TARGET_NAME'/>"
604
24
        "  <Option name='USE_SRC_LABEL' type='boolean' "
605
24
        "description='Whether to use source label in VICAR to VICAR "
606
24
        "conversions' "
607
24
        "default='YES'/>"
608
24
        "  <Option name='USE_SRC_MAP' type='boolean' "
609
24
        "description='Whether to use MAP property from source label in "
610
24
        "VICAR to VICAR conversions' "
611
24
        "default='NO'/>"
612
24
        "  <Option name='LABEL' type='string' "
613
24
        "description='Label to use, either as a JSON string or a filename "
614
24
        "containing one'/>"
615
24
        "  <Option name='COMPRESS' type='string-select' "
616
24
        "description='Compression method' default='NONE'>"
617
24
        "     <Value>NONE</Value>"
618
24
        "     <Value>BASIC</Value>"
619
24
        "     <Value>BASIC2</Value>"
620
24
        "  </Option>"
621
24
        "</CreationOptionList>");
622
623
24
    poDriver->pfnIdentify = VICARDriverIdentify;
624
24
    poDriver->SetMetadataItem(GDAL_DCAP_OPEN, "YES");
625
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE, "YES");
626
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATECOPY, "YES");
627
24
}
628
629
/************************************************************************/
630
/*                      DeclareDeferredPDSPlugin()                      */
631
/************************************************************************/
632
633
#ifdef PLUGIN_FILENAME
634
void DeclareDeferredPDSPlugin()
635
{
636
    if (GDALGetDriverByName(PDS_DRIVER_NAME) != nullptr)
637
    {
638
        return;
639
    }
640
    {
641
        auto poDriver = new GDALPluginDriverProxy(PLUGIN_FILENAME);
642
#ifdef PLUGIN_INSTALLATION_MESSAGE
643
        poDriver->SetMetadataItem(GDAL_DMD_PLUGIN_INSTALLATION_MESSAGE,
644
                                  PLUGIN_INSTALLATION_MESSAGE);
645
#endif
646
        PDSDriverSetCommonMetadata(poDriver);
647
        GetGDALDriverManager()->DeclareDeferredPluginDriver(poDriver);
648
    }
649
    {
650
        auto poDriver = new GDALPluginDriverProxy(PLUGIN_FILENAME);
651
#ifdef PLUGIN_INSTALLATION_MESSAGE
652
        poDriver->SetMetadataItem(GDAL_DMD_PLUGIN_INSTALLATION_MESSAGE,
653
                                  PLUGIN_INSTALLATION_MESSAGE);
654
#endif
655
        PDS4DriverSetCommonMetadata(poDriver);
656
        GetGDALDriverManager()->DeclareDeferredPluginDriver(poDriver);
657
    }
658
    {
659
        auto poDriver = new GDALPluginDriverProxy(PLUGIN_FILENAME);
660
#ifdef PLUGIN_INSTALLATION_MESSAGE
661
        poDriver->SetMetadataItem(GDAL_DMD_PLUGIN_INSTALLATION_MESSAGE,
662
                                  PLUGIN_INSTALLATION_MESSAGE);
663
#endif
664
        ISIS2DriverSetCommonMetadata(poDriver);
665
        GetGDALDriverManager()->DeclareDeferredPluginDriver(poDriver);
666
    }
667
    {
668
        auto poDriver = new GDALPluginDriverProxy(PLUGIN_FILENAME);
669
#ifdef PLUGIN_INSTALLATION_MESSAGE
670
        poDriver->SetMetadataItem(GDAL_DMD_PLUGIN_INSTALLATION_MESSAGE,
671
                                  PLUGIN_INSTALLATION_MESSAGE);
672
#endif
673
        ISIS3DriverSetCommonMetadata(poDriver);
674
        GetGDALDriverManager()->DeclareDeferredPluginDriver(poDriver);
675
    }
676
    {
677
        auto poDriver = new GDALPluginDriverProxy(PLUGIN_FILENAME);
678
#ifdef PLUGIN_INSTALLATION_MESSAGE
679
        poDriver->SetMetadataItem(GDAL_DMD_PLUGIN_INSTALLATION_MESSAGE,
680
                                  PLUGIN_INSTALLATION_MESSAGE);
681
#endif
682
        VICARDriverSetCommonMetadata(poDriver);
683
        GetGDALDriverManager()->DeclareDeferredPluginDriver(poDriver);
684
    }
685
}
686
#endif