Coverage Report

Created: 2026-09-26 08:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/map/mapdataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  OziExplorer .MAP Driver
4
 * Purpose:  GDALDataset driver for OziExplorer .MAP files
5
 * Author:   Jean-Claude Repetto, <jrepetto at @free dot fr>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2012, Jean-Claude Repetto
9
 * Copyright (c) 2012, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "gdal_frmts.h"
15
#include "gdal_pam.h"
16
#include "gdal_proxy.h"
17
#include "ogr_geometry.h"
18
#include "ogr_spatialref.h"
19
20
/************************************************************************/
21
/* ==================================================================== */
22
/*                                MAPDataset                            */
23
/* ==================================================================== */
24
/************************************************************************/
25
26
class MAPDataset final : public GDALDataset
27
{
28
    GDALDataset *poImageDS{};
29
30
    OGRSpatialReference m_oSRS{};
31
    int bGeoTransformValid{};
32
    GDALGeoTransform m_gt{};
33
    int nGCPCount{};
34
    GDAL_GCP *pasGCPList{};
35
    OGRPolygon *poNeatLine{};
36
    CPLString osImgFilename{};
37
38
    CPL_DISALLOW_COPY_ASSIGN(MAPDataset)
39
40
  public:
41
    MAPDataset();
42
    ~MAPDataset() override;
43
44
    const OGRSpatialReference *GetSpatialRef() const override;
45
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
46
    int GetGCPCount() override;
47
    const OGRSpatialReference *GetGCPSpatialRef() const override;
48
    const GDAL_GCP *GetGCPs() override;
49
    char **GetFileList() override;
50
51
    int CloseDependentDatasets() override;
52
53
    static GDALDataset *Open(GDALOpenInfo *);
54
    static int Identify(GDALOpenInfo *poOpenInfo);
55
};
56
57
/************************************************************************/
58
/* ==================================================================== */
59
/*                         MAPWrapperRasterBand                         */
60
/* ==================================================================== */
61
/************************************************************************/
62
class MAPWrapperRasterBand final : public GDALProxyRasterBand
63
{
64
    GDALRasterBand *poBaseBand{};
65
66
    CPL_DISALLOW_COPY_ASSIGN(MAPWrapperRasterBand)
67
68
  protected:
69
    virtual GDALRasterBand *
70
    RefUnderlyingRasterBand(bool /*bForceOpen*/) const override;
71
72
  public:
73
    explicit MAPWrapperRasterBand(GDALRasterBand *poBaseBandIn)
74
15
    {
75
15
        this->poBaseBand = poBaseBandIn;
76
15
        eDataType = poBaseBand->GetRasterDataType();
77
15
        poBaseBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
78
15
    }
79
};
80
81
GDALRasterBand *
82
MAPWrapperRasterBand::RefUnderlyingRasterBand(bool /*bForceOpen*/) const
83
0
{
84
0
    return poBaseBand;
85
0
}
86
87
/************************************************************************/
88
/* ==================================================================== */
89
/*                             MAPDataset                               */
90
/* ==================================================================== */
91
/************************************************************************/
92
93
MAPDataset::MAPDataset()
94
224
    : poImageDS(nullptr), bGeoTransformValid(false), nGCPCount(0),
95
224
      pasGCPList(nullptr), poNeatLine(nullptr)
96
224
{
97
224
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
98
224
}
99
100
/************************************************************************/
101
/*                            ~MAPDataset()                             */
102
/************************************************************************/
103
104
MAPDataset::~MAPDataset()
105
106
224
{
107
224
    if (poImageDS != nullptr)
108
15
    {
109
15
        GDALClose(poImageDS);
110
15
        poImageDS = nullptr;
111
15
    }
112
113
224
    if (nGCPCount)
114
0
    {
115
0
        GDALDeinitGCPs(nGCPCount, pasGCPList);
116
0
        CPLFree(pasGCPList);
117
0
    }
118
119
224
    if (poNeatLine != nullptr)
120
5
    {
121
5
        delete poNeatLine;
122
5
        poNeatLine = nullptr;
123
5
    }
124
224
}
125
126
/************************************************************************/
127
/*                       CloseDependentDatasets()                       */
128
/************************************************************************/
129
130
int MAPDataset::CloseDependentDatasets()
131
0
{
132
0
    int bRet = GDALDataset::CloseDependentDatasets();
133
0
    if (poImageDS != nullptr)
134
0
    {
135
0
        GDALClose(poImageDS);
136
0
        poImageDS = nullptr;
137
0
        bRet = TRUE;
138
0
    }
139
0
    return bRet;
140
0
}
141
142
/************************************************************************/
143
/*                              Identify()                              */
144
/************************************************************************/
145
146
int MAPDataset::Identify(GDALOpenInfo *poOpenInfo)
147
148
513k
{
149
513k
    if (poOpenInfo->nHeaderBytes < 200 ||
150
96.9k
        !poOpenInfo->IsExtensionEqualToCI("MAP"))
151
511k
        return FALSE;
152
153
1.26k
    if (strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
154
1.26k
               "OziExplorer Map Data File") == nullptr)
155
821
        return FALSE;
156
157
448
    return TRUE;
158
1.26k
}
159
160
/************************************************************************/
161
/*                                Open()                                */
162
/************************************************************************/
163
164
GDALDataset *MAPDataset::Open(GDALOpenInfo *poOpenInfo)
165
224
{
166
224
    if (!Identify(poOpenInfo))
167
0
        return nullptr;
168
169
    /* -------------------------------------------------------------------- */
170
    /*      Confirm the requested access is supported.                      */
171
    /* -------------------------------------------------------------------- */
172
224
    if (poOpenInfo->eAccess == GA_Update)
173
0
    {
174
0
        ReportUpdateNotSupportedByDriver("MAP");
175
0
        return nullptr;
176
0
    }
177
178
    /* -------------------------------------------------------------------- */
179
    /*      Create a corresponding GDALDataset.                             */
180
    /* -------------------------------------------------------------------- */
181
182
224
    auto poDS = std::make_unique<MAPDataset>();
183
184
    /* -------------------------------------------------------------------- */
185
    /*      Try to load and parse the .MAP file.                            */
186
    /* -------------------------------------------------------------------- */
187
188
224
    char *pszWKT = nullptr;
189
224
    bool bOziFileOK = CPL_TO_BOOL(
190
224
        GDALLoadOziMapFile(poOpenInfo->pszFilename, poDS->m_gt.data(), &pszWKT,
191
224
                           &poDS->nGCPCount, &poDS->pasGCPList));
192
224
    if (pszWKT)
193
0
    {
194
0
        poDS->m_oSRS.importFromWkt(pszWKT);
195
0
        CPLFree(pszWKT);
196
0
    }
197
198
224
    if (bOziFileOK && poDS->nGCPCount == 0)
199
0
        poDS->bGeoTransformValid = TRUE;
200
201
    /* We need to read again the .map file because the GDALLoadOziMapFile
202
       function does not returns all required data . An API change is necessary
203
       : maybe in GDAL 2.0 ? */
204
205
224
    const CPLStringList aosLines(
206
224
        CSLLoad2(poOpenInfo->pszFilename, 200, 200, nullptr));
207
224
    if (aosLines.empty())
208
2
    {
209
2
        return nullptr;
210
2
    }
211
212
222
    const int nLines = aosLines.size();
213
222
    if (nLines < 3)
214
2
    {
215
2
        return nullptr;
216
2
    }
217
218
    /* -------------------------------------------------------------------- */
219
    /*      We need to open the image in order to establish                 */
220
    /*      details like the band count and types.                          */
221
    /* -------------------------------------------------------------------- */
222
220
    poDS->osImgFilename = aosLines[2];
223
220
    if (CPLHasPathTraversal(poDS->osImgFilename.c_str()))
224
1
    {
225
1
        CPLError(CE_Failure, CPLE_NotSupported, "Path traversal detected in %s",
226
1
                 poDS->osImgFilename.c_str());
227
1
        return nullptr;
228
1
    }
229
230
219
    const CPLString osPath = CPLGetPathSafe(poOpenInfo->pszFilename);
231
219
    if (CPLIsFilenameRelative(poDS->osImgFilename))
232
141
    {
233
141
        poDS->osImgFilename =
234
141
            CPLFormCIFilenameSafe(osPath, poDS->osImgFilename, nullptr);
235
141
    }
236
78
    else
237
78
    {
238
78
        VSIStatBufL sStat;
239
78
        if (VSIStatL(poDS->osImgFilename, &sStat) != 0)
240
78
        {
241
78
            poDS->osImgFilename = CPLGetFilename(poDS->osImgFilename);
242
78
            poDS->osImgFilename =
243
78
                CPLFormCIFilenameSafe(osPath, poDS->osImgFilename, nullptr);
244
78
        }
245
78
    }
246
247
    /* -------------------------------------------------------------------- */
248
    /*      Try and open the file.                                          */
249
    /* -------------------------------------------------------------------- */
250
219
    poDS->poImageDS = GDALDataset::Open(poDS->osImgFilename,
251
219
                                        GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR);
252
219
    if (poDS->poImageDS == nullptr || poDS->poImageDS->GetRasterCount() == 0)
253
204
    {
254
204
        return nullptr;
255
204
    }
256
257
    /* -------------------------------------------------------------------- */
258
    /*      Attach the bands.                                               */
259
    /* -------------------------------------------------------------------- */
260
15
    poDS->nRasterXSize = poDS->poImageDS->GetRasterXSize();
261
15
    poDS->nRasterYSize = poDS->poImageDS->GetRasterYSize();
262
15
    if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
263
0
    {
264
0
        return nullptr;
265
0
    }
266
267
30
    for (int iBand = 1; iBand <= poDS->poImageDS->GetRasterCount(); iBand++)
268
15
        poDS->SetBand(iBand, std::make_unique<MAPWrapperRasterBand>(
269
15
                                 poDS->poImageDS->GetRasterBand(iBand)));
270
271
    /* -------------------------------------------------------------------- */
272
    /*      Add the neatline/cutline, if required                           */
273
    /* -------------------------------------------------------------------- */
274
275
    /* First, we need to check if it is necessary to define a neatline */
276
15
    bool bNeatLine = false;
277
943
    for (int iLine = 10; iLine < nLines; iLine++)
278
933
    {
279
933
        if (STARTS_WITH_CI(aosLines[iLine], "MMPXY,"))
280
39
        {
281
39
            const CPLStringList aosTokens(
282
39
                CSLTokenizeString2(aosLines[iLine], ",",
283
39
                                   CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES));
284
285
39
            if (aosTokens.size() != 4)
286
34
            {
287
34
                continue;
288
34
            }
289
290
5
            const int x = atoi(aosTokens[2]);
291
5
            const int y = atoi(aosTokens[3]);
292
5
            if ((x != 0 && x != poDS->nRasterXSize) ||
293
0
                (y != 0 && y != poDS->nRasterYSize))
294
5
            {
295
5
                bNeatLine = true;
296
5
                break;
297
5
            }
298
5
        }
299
933
    }
300
301
    /* Create and fill the neatline polygon */
302
15
    if (bNeatLine)
303
5
    {
304
5
        poDS->poNeatLine =
305
5
            new OGRPolygon(); /* Create a polygon to store the neatline */
306
5
        OGRLinearRing *poRing = new OGRLinearRing();
307
308
5
        if (poDS->bGeoTransformValid) /* Compute the projected coordinates of
309
                                         the corners */
310
0
        {
311
0
            for (int iLine = 10; iLine < nLines; iLine++)
312
0
            {
313
0
                if (STARTS_WITH_CI(aosLines[iLine], "MMPXY,"))
314
0
                {
315
0
                    const CPLStringList aosTokens(CSLTokenizeString2(
316
0
                        aosLines[iLine], ",",
317
0
                        CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES));
318
319
0
                    if (aosTokens.size() != 4)
320
0
                    {
321
0
                        continue;
322
0
                    }
323
324
0
                    const double x = CPLAtofM(aosTokens[2]);
325
0
                    const double y = CPLAtofM(aosTokens[3]);
326
0
                    const double X = poDS->m_gt.xorig + x * poDS->m_gt.xscale +
327
0
                                     y * poDS->m_gt.xrot;
328
0
                    const double Y = poDS->m_gt.yorig + x * poDS->m_gt.yrot +
329
0
                                     y * poDS->m_gt.yscale;
330
0
                    poRing->addPoint(X, Y);
331
0
                    CPLDebug("CORNER MMPXY", "%f, %f, %f, %f", x, y, X, Y);
332
0
                }
333
0
            }
334
0
        }
335
5
        else /* Convert the geographic coordinates to projected coordinates */
336
5
        {
337
5
            std::unique_ptr<OGRCoordinateTransformation> poTransform;
338
5
            if (!poDS->m_oSRS.IsEmpty())
339
0
            {
340
0
                OGRSpatialReference *poLongLat = poDS->m_oSRS.CloneGeogCS();
341
0
                if (poLongLat)
342
0
                {
343
0
                    poLongLat->SetAxisMappingStrategy(
344
0
                        OAMS_TRADITIONAL_GIS_ORDER);
345
0
                    poTransform.reset(OGRCreateCoordinateTransformation(
346
0
                        poLongLat, &poDS->m_oSRS));
347
0
                    poLongLat->Release();
348
0
                }
349
0
            }
350
351
587
            for (int iLine = 10; iLine < nLines; iLine++)
352
582
            {
353
582
                if (STARTS_WITH_CI(aosLines[iLine], "MMPLL,"))
354
8
                {
355
8
                    CPLDebug("MMPLL", "%s", aosLines[iLine]);
356
357
8
                    const CPLStringList aosTokens(CSLTokenizeString2(
358
8
                        aosLines[iLine], ",",
359
8
                        CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES));
360
361
8
                    if (aosTokens.size() != 4)
362
8
                    {
363
8
                        continue;
364
8
                    }
365
366
0
                    double dfLon = CPLAtofM(aosTokens[2]);
367
0
                    double dfLat = CPLAtofM(aosTokens[3]);
368
369
0
                    if (poTransform)
370
0
                        poTransform->Transform(1, &dfLon, &dfLat);
371
0
                    poRing->addPoint(dfLon, dfLat);
372
0
                    CPLDebug("CORNER MMPLL", "%f, %f", dfLon, dfLat);
373
0
                }
374
582
            }
375
5
        }
376
377
5
        poRing->closeRings();
378
5
        poDS->poNeatLine->addRingDirectly(poRing);
379
380
5
        char *pszNeatLineWkt = nullptr;
381
5
        poDS->poNeatLine->exportToWkt(&pszNeatLineWkt);
382
5
        CPLDebug("NEATLINE", "%s", pszNeatLineWkt);
383
5
        poDS->SetMetadataItem("NEATLINE", pszNeatLineWkt);
384
5
        CPLFree(pszNeatLineWkt);
385
5
    }
386
387
15
    return poDS.release();
388
15
}
389
390
/************************************************************************/
391
/*                           GetSpatialRef()                            */
392
/************************************************************************/
393
394
const OGRSpatialReference *MAPDataset::GetSpatialRef() const
395
0
{
396
0
    return (!m_oSRS.IsEmpty() && nGCPCount == 0) ? &m_oSRS : nullptr;
397
0
}
398
399
/************************************************************************/
400
/*                          GetGeoTransform()                           */
401
/************************************************************************/
402
403
CPLErr MAPDataset::GetGeoTransform(GDALGeoTransform &gt) const
404
405
0
{
406
0
    gt = m_gt;
407
408
0
    return (nGCPCount == 0) ? CE_None : CE_Failure;
409
0
}
410
411
/************************************************************************/
412
/*                            GetGCPCount()                             */
413
/************************************************************************/
414
415
int MAPDataset::GetGCPCount()
416
0
{
417
0
    return nGCPCount;
418
0
}
419
420
/************************************************************************/
421
/*                          GetGCPSpatialRef()                          */
422
/************************************************************************/
423
424
const OGRSpatialReference *MAPDataset::GetGCPSpatialRef() const
425
0
{
426
0
    return (!m_oSRS.IsEmpty() && nGCPCount != 0) ? &m_oSRS : nullptr;
427
0
}
428
429
/************************************************************************/
430
/*                              GetGCPs()                               */
431
/************************************************************************/
432
433
const GDAL_GCP *MAPDataset::GetGCPs()
434
0
{
435
0
    return pasGCPList;
436
0
}
437
438
/************************************************************************/
439
/*                            GetFileList()                             */
440
/************************************************************************/
441
442
char **MAPDataset::GetFileList()
443
0
{
444
0
    char **papszFileList = GDALDataset::GetFileList();
445
446
0
    papszFileList = CSLAddString(papszFileList, osImgFilename);
447
448
0
    return papszFileList;
449
0
}
450
451
/************************************************************************/
452
/*                          GDALRegister_MAP()                          */
453
/************************************************************************/
454
455
void GDALRegister_MAP()
456
457
24
{
458
24
    if (GDALGetDriverByName("MAP") != nullptr)
459
0
        return;
460
461
24
    GDALDriver *poDriver = new GDALDriver();
462
463
24
    poDriver->SetDescription("MAP");
464
24
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
465
24
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "OziExplorer .MAP");
466
24
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/map.html");
467
468
24
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
469
470
24
    poDriver->pfnOpen = MAPDataset::Open;
471
24
    poDriver->pfnIdentify = MAPDataset::Identify;
472
473
24
    GetGDALDriverManager()->RegisterDriver(poDriver);
474
24
}