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