Coverage Report

Created: 2026-08-11 08:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/til/tildataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  EarthWatch .TIL Driver
4
 * Purpose:  Implementation of the TILDataset class.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2009, Frank Warmerdam
9
 * Copyright (c) 2009-2011, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_multiproc.h"
15
#include "cpl_string.h"
16
#include "cplkeywordparser.h"
17
#include "gdal_mdreader.h"
18
#include "gdal_frmts.h"
19
#include "gdal_pam.h"
20
#include "ogr_spatialref.h"
21
#include "vrtdataset.h"
22
23
/************************************************************************/
24
/* ==================================================================== */
25
/*                              TILDataset                              */
26
/* ==================================================================== */
27
/************************************************************************/
28
29
class TILDataset final : public GDALPamDataset
30
{
31
    VRTDataset *poVRTDS;
32
    std::vector<std::string> m_aosFilenames;
33
34
    char **papszMetadataFiles;
35
36
  protected:
37
    int CloseDependentDatasets() override;
38
39
  public:
40
    TILDataset();
41
    ~TILDataset() override;
42
43
    char **GetFileList(void) override;
44
45
    static GDALDataset *Open(GDALOpenInfo *);
46
    static int Identify(GDALOpenInfo *poOpenInfo);
47
};
48
49
/************************************************************************/
50
/* ==================================================================== */
51
/*                            TILRasterBand                             */
52
/* ==================================================================== */
53
/************************************************************************/
54
55
class TILRasterBand final : public GDALPamRasterBand
56
{
57
    friend class TILDataset;
58
59
    VRTSourcedRasterBand *poVRTBand;
60
61
  public:
62
    TILRasterBand(TILDataset *, int, VRTSourcedRasterBand *);
63
64
    CPLErr IReadBlock(int, int, void *) override;
65
    CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
66
                     GDALDataType, GSpacing nPixelSpace, GSpacing nLineSpace,
67
                     GDALRasterIOExtraArg *psExtraArg) override;
68
};
69
70
/************************************************************************/
71
/*                           TILRasterBand()                            */
72
/************************************************************************/
73
74
TILRasterBand::TILRasterBand(TILDataset *poTILDS, int nBandIn,
75
                             VRTSourcedRasterBand *poVRTBandIn)
76
77
0
{
78
0
    poDS = poTILDS;
79
0
    poVRTBand = poVRTBandIn;
80
0
    nBand = nBandIn;
81
0
    eDataType = poVRTBandIn->GetRasterDataType();
82
83
0
    poVRTBandIn->GetBlockSize(&nBlockXSize, &nBlockYSize);
84
0
}
85
86
/************************************************************************/
87
/*                             IReadBlock()                             */
88
/************************************************************************/
89
90
CPLErr TILRasterBand::IReadBlock(int iBlockX, int iBlockY, void *pBuffer)
91
92
0
{
93
0
    return poVRTBand->ReadBlock(iBlockX, iBlockY, pBuffer);
94
0
}
95
96
/************************************************************************/
97
/*                             IRasterIO()                              */
98
/************************************************************************/
99
100
CPLErr TILRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
101
                                int nXSize, int nYSize, void *pData,
102
                                int nBufXSize, int nBufYSize,
103
                                GDALDataType eBufType, GSpacing nPixelSpace,
104
                                GSpacing nLineSpace,
105
                                GDALRasterIOExtraArg *psExtraArg)
106
107
0
{
108
0
    if (GetOverviewCount() > 0)
109
0
    {
110
0
        return GDALPamRasterBand::IRasterIO(
111
0
            eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
112
0
            eBufType, nPixelSpace, nLineSpace, psExtraArg);
113
0
    }
114
115
    // If not exist TIL overviews, try to use band source overviews.
116
0
    return poVRTBand->IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
117
0
                                nBufXSize, nBufYSize, eBufType, nPixelSpace,
118
0
                                nLineSpace, psExtraArg);
119
0
}
120
121
/************************************************************************/
122
/* ==================================================================== */
123
/*                             TILDataset                               */
124
/* ==================================================================== */
125
/************************************************************************/
126
127
/************************************************************************/
128
/*                             TILDataset()                             */
129
/************************************************************************/
130
131
0
TILDataset::TILDataset() : poVRTDS(nullptr), papszMetadataFiles(nullptr)
132
0
{
133
0
}
134
135
/************************************************************************/
136
/*                            ~TILDataset()                             */
137
/************************************************************************/
138
139
TILDataset::~TILDataset()
140
141
0
{
142
0
    TILDataset::CloseDependentDatasets();
143
0
    CSLDestroy(papszMetadataFiles);
144
0
}
145
146
/************************************************************************/
147
/*                       CloseDependentDatasets()                       */
148
/************************************************************************/
149
150
int TILDataset::CloseDependentDatasets()
151
0
{
152
0
    int bHasDroppedRef = GDALPamDataset::CloseDependentDatasets();
153
154
0
    if (poVRTDS)
155
0
    {
156
0
        bHasDroppedRef = TRUE;
157
0
        delete poVRTDS;
158
0
        poVRTDS = nullptr;
159
0
    }
160
161
0
    return bHasDroppedRef;
162
0
}
163
164
/************************************************************************/
165
/*                              Identify()                              */
166
/************************************************************************/
167
168
int TILDataset::Identify(GDALOpenInfo *poOpenInfo)
169
170
558k
{
171
558k
    if (poOpenInfo->nHeaderBytes < 200 ||
172
103k
        !poOpenInfo->IsExtensionEqualToCI("TIL"))
173
558k
        return FALSE;
174
175
1
    if (strstr((const char *)poOpenInfo->pabyHeader, "numTiles") == nullptr)
176
1
        return FALSE;
177
178
0
    return TRUE;
179
1
}
180
181
/************************************************************************/
182
/*                                Open()                                */
183
/************************************************************************/
184
185
GDALDataset *TILDataset::Open(GDALOpenInfo *poOpenInfo)
186
187
0
{
188
0
    if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
189
0
        return nullptr;
190
191
    /* -------------------------------------------------------------------- */
192
    /*      Confirm the requested access is supported.                      */
193
    /* -------------------------------------------------------------------- */
194
0
    if (poOpenInfo->eAccess == GA_Update)
195
0
    {
196
0
        ReportUpdateNotSupportedByDriver("TIL");
197
0
        return nullptr;
198
0
    }
199
200
0
    CPLString osDirname = CPLGetDirnameSafe(poOpenInfo->pszFilename);
201
202
    // get metadata reader
203
204
0
    GDALMDReaderManager mdreadermanager;
205
0
    GDALMDReaderBase *mdreader = mdreadermanager.GetReader(
206
0
        poOpenInfo->pszFilename, poOpenInfo->GetSiblingFiles(), MDR_DG);
207
208
0
    if (nullptr == mdreader)
209
0
    {
210
0
        CPLError(CE_Failure, CPLE_OpenFailed,
211
0
                 "Unable to open .TIL dataset due to missing metadata file.");
212
0
        return nullptr;
213
0
    }
214
    /* -------------------------------------------------------------------- */
215
    /*      Try to find the corresponding .IMD file.                        */
216
    /* -------------------------------------------------------------------- */
217
0
    char **papszIMD = mdreader->GetMetadataDomain(GDAL_MDD_IMD);
218
219
0
    if (papszIMD == nullptr)
220
0
    {
221
0
        CPLError(CE_Failure, CPLE_OpenFailed,
222
0
                 "Unable to open .TIL dataset due to missing .IMD file.");
223
0
        return nullptr;
224
0
    }
225
226
0
    if (CSLFetchNameValue(papszIMD, "numRows") == nullptr ||
227
0
        CSLFetchNameValue(papszIMD, "numColumns") == nullptr ||
228
0
        CSLFetchNameValue(papszIMD, "bitsPerPixel") == nullptr)
229
0
    {
230
0
        CPLError(CE_Failure, CPLE_OpenFailed,
231
0
                 "Missing a required field in the .IMD file.");
232
0
        return nullptr;
233
0
    }
234
235
    /* -------------------------------------------------------------------- */
236
    /*      Try to load and parse the .TIL file.                            */
237
    /* -------------------------------------------------------------------- */
238
0
    VSILFILE *fp = poOpenInfo->fpL;
239
0
    poOpenInfo->fpL = nullptr;
240
241
0
    CPLKeywordParser oParser;
242
243
0
    if (!oParser.Ingest(fp))
244
0
    {
245
0
        VSIFCloseL(fp);
246
0
        return nullptr;
247
0
    }
248
249
0
    VSIFCloseL(fp);
250
251
0
    char **papszTIL = oParser.GetAllKeywords();
252
253
    /* -------------------------------------------------------------------- */
254
    /*      Create a corresponding GDALDataset.                             */
255
    /* -------------------------------------------------------------------- */
256
0
    auto poDS = std::make_unique<TILDataset>();
257
0
    poDS->papszMetadataFiles = mdreader->GetMetadataFiles();
258
0
    mdreader->FillMetadata(&poDS->oMDMD);
259
0
    poDS->nRasterXSize =
260
0
        atoi(CSLFetchNameValueDef(papszIMD, "numColumns", "0"));
261
0
    poDS->nRasterYSize = atoi(CSLFetchNameValueDef(papszIMD, "numRows", "0"));
262
0
    if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
263
0
    {
264
0
        return nullptr;
265
0
    }
266
267
    /* -------------------------------------------------------------------- */
268
    /*      We need to open one of the images in order to establish         */
269
    /*      details like the band count and types.                          */
270
    /* -------------------------------------------------------------------- */
271
0
    const char *pszFilename = CSLFetchNameValue(papszTIL, "TILE_1.filename");
272
0
    if (pszFilename == nullptr)
273
0
    {
274
0
        CPLError(CE_Failure, CPLE_AppDefined,
275
0
                 "Missing TILE_1.filename in .TIL file.");
276
0
        return nullptr;
277
0
    }
278
279
    // trim double quotes.
280
0
    if (pszFilename[0] == '"')
281
0
        pszFilename++;
282
0
    if (pszFilename[strlen(pszFilename) - 1] == '"')
283
0
        const_cast<char *>(pszFilename)[strlen(pszFilename) - 1] = '\0';
284
0
    if (CPLHasPathTraversal(pszFilename))
285
0
    {
286
0
        CPLError(CE_Failure, CPLE_NotSupported, "Path traversal detected in %s",
287
0
                 pszFilename);
288
0
        return nullptr;
289
0
    }
290
291
0
    CPLString osFilename = CPLFormFilenameSafe(osDirname, pszFilename, nullptr);
292
0
    auto poTemplateDS = std::unique_ptr<GDALDataset>(
293
0
        GDALDataset::Open(osFilename, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
294
0
    if (poTemplateDS == nullptr || poTemplateDS->GetRasterCount() == 0)
295
0
    {
296
0
        return nullptr;
297
0
    }
298
299
0
    GDALRasterBand *poTemplateBand = poTemplateDS->GetRasterBand(1);
300
0
    const GDALDataType eDT = poTemplateBand->GetRasterDataType();
301
0
    const int nBandCount = poTemplateDS->GetRasterCount();
302
303
    // we suppose the first tile have the same projection as others (usually so)
304
0
    CPLString pszProjection(poTemplateDS->GetProjectionRef());
305
0
    if (!pszProjection.empty())
306
0
        poDS->SetProjection(pszProjection);
307
308
    // we suppose the first tile have the same GeoTransform as others (usually
309
    // so)
310
0
    GDALGeoTransform gt;
311
0
    if (poTemplateDS->GetGeoTransform(gt) == CE_None)
312
0
    {
313
        // According to
314
        // https://www.digitalglobe.com/sites/default/files/ISD_External.pdf,
315
        // ulx=originX and is "Easting of the center of the upper left pixel of
316
        // the image."
317
0
        gt.xorig = CPLAtof(CSLFetchNameValueDef(
318
0
                       papszIMD, "MAP_PROJECTED_PRODUCT.ULX", "0")) -
319
0
                   gt.xscale / 2;
320
0
        gt.yorig = CPLAtof(CSLFetchNameValueDef(
321
0
                       papszIMD, "MAP_PROJECTED_PRODUCT.ULY", "0")) -
322
0
                   gt.yscale / 2;
323
0
        poDS->SetGeoTransform(gt);
324
0
    }
325
326
0
    poTemplateBand = nullptr;
327
0
    poTemplateDS.reset();
328
329
    /* -------------------------------------------------------------------- */
330
    /*      Create and initialize the corresponding VRT dataset used to     */
331
    /*      manage the tiled data access.                                   */
332
    /* -------------------------------------------------------------------- */
333
0
    poDS->poVRTDS = new VRTDataset(poDS->nRasterXSize, poDS->nRasterYSize);
334
335
0
    for (int iBand = 0; iBand < nBandCount; iBand++)
336
0
        poDS->poVRTDS->AddBand(eDT, nullptr);
337
338
    /* Don't try to write a VRT file */
339
0
    poDS->poVRTDS->SetWritable(FALSE);
340
341
    /* -------------------------------------------------------------------- */
342
    /*      Create band information objects.                                */
343
    /* -------------------------------------------------------------------- */
344
0
    for (int iBand = 1; iBand <= nBandCount; iBand++)
345
0
        poDS->SetBand(
346
0
            iBand, new TILRasterBand(poDS.get(), iBand,
347
0
                                     reinterpret_cast<VRTSourcedRasterBand *>(
348
0
                                         poDS->poVRTDS->GetRasterBand(iBand))));
349
350
    /* -------------------------------------------------------------------- */
351
    /*      Add tiles as sources for each band.                             */
352
    /* -------------------------------------------------------------------- */
353
0
    const int nTileCount =
354
0
        atoi(CSLFetchNameValueDef(papszTIL, "numTiles", "0"));
355
0
    int iTile = 0;
356
357
0
    for (iTile = 1; iTile <= nTileCount; iTile++)
358
0
    {
359
0
        CPLString osKey;
360
0
        osKey.Printf("TILE_%d.filename", iTile);
361
0
        pszFilename = CSLFetchNameValue(papszTIL, osKey);
362
0
        if (pszFilename == nullptr)
363
0
        {
364
0
            CPLError(CE_Failure, CPLE_AppDefined,
365
0
                     "Missing TILE_%d.filename in .TIL file.", iTile);
366
0
            return nullptr;
367
0
        }
368
369
        // trim double quotes.
370
0
        if (pszFilename[0] == '"')
371
0
            pszFilename++;
372
0
        if (pszFilename[strlen(pszFilename) - 1] == '"')
373
0
            const_cast<char *>(pszFilename)[strlen(pszFilename) - 1] = '\0';
374
0
        if (CPLHasPathTraversal(pszFilename))
375
0
        {
376
0
            CPLError(CE_Failure, CPLE_NotSupported,
377
0
                     "Path traversal detected in %s", pszFilename);
378
0
            return nullptr;
379
0
        }
380
381
0
        osFilename = CPLFormFilenameSafe(osDirname, pszFilename, nullptr);
382
0
        poDS->m_aosFilenames.push_back(osFilename);
383
384
0
        osKey.Printf("TILE_%d.ULColOffset", iTile);
385
0
        const int nULX = atoi(CSLFetchNameValueDef(papszTIL, osKey, "0"));
386
387
0
        osKey.Printf("TILE_%d.ULRowOffset", iTile);
388
0
        const int nULY = atoi(CSLFetchNameValueDef(papszTIL, osKey, "0"));
389
390
0
        osKey.Printf("TILE_%d.LRColOffset", iTile);
391
0
        const int nLRX = atoi(CSLFetchNameValueDef(papszTIL, osKey, "0"));
392
393
0
        osKey.Printf("TILE_%d.LRRowOffset", iTile);
394
0
        const int nLRY = atoi(CSLFetchNameValueDef(papszTIL, osKey, "0"));
395
396
0
        for (int iBand = 1; iBand <= nBandCount; iBand++)
397
0
        {
398
0
            VRTSourcedRasterBand *poVRTBand =
399
0
                reinterpret_cast<VRTSourcedRasterBand *>(
400
0
                    poDS->poVRTDS->GetRasterBand(iBand));
401
402
0
            poVRTBand->AddSimpleSource(osFilename, iBand, 0, 0, nLRX - nULX + 1,
403
0
                                       nLRY - nULY + 1, nULX, nULY,
404
0
                                       nLRX - nULX + 1, nLRY - nULY + 1);
405
0
        }
406
0
    }
407
408
    /* -------------------------------------------------------------------- */
409
    /*      Initialize any PAM information.                                 */
410
    /* -------------------------------------------------------------------- */
411
0
    poDS->SetDescription(poOpenInfo->pszFilename);
412
0
    poDS->TryLoadXML();
413
414
    /* -------------------------------------------------------------------- */
415
    /*      Check for overviews.                                            */
416
    /* -------------------------------------------------------------------- */
417
0
    poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
418
419
0
    return poDS.release();
420
0
}
421
422
/************************************************************************/
423
/*                            GetFileList()                             */
424
/************************************************************************/
425
426
char **TILDataset::GetFileList()
427
428
0
{
429
0
    char **papszFileList = GDALPamDataset::GetFileList();
430
431
0
    for (const auto &osFilename : m_aosFilenames)
432
0
        papszFileList = CSLAddString(papszFileList, osFilename.c_str());
433
434
0
    if (nullptr != papszMetadataFiles)
435
0
    {
436
0
        for (int i = 0; papszMetadataFiles[i] != nullptr; i++)
437
0
        {
438
0
            papszFileList = CSLAddString(papszFileList, papszMetadataFiles[i]);
439
0
        }
440
0
    }
441
442
0
    return papszFileList;
443
0
}
444
445
/************************************************************************/
446
/*                          GDALRegister_TIL()                          */
447
/************************************************************************/
448
449
void GDALRegister_TIL()
450
451
22
{
452
22
    if (GDALGetDriverByName("TIL") != nullptr)
453
0
        return;
454
455
22
    GDALDriver *poDriver = new GDALDriver();
456
457
22
    poDriver->SetDescription("TIL");
458
22
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
459
22
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "EarthWatch .TIL");
460
22
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/til.html");
461
22
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
462
463
22
    poDriver->pfnOpen = TILDataset::Open;
464
22
    poDriver->pfnIdentify = TILDataset::Identify;
465
466
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
467
22
}