/src/gdal/frmts/raw/gtxdataset.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: Vertical Datum Transformation |
4 | | * Purpose: Implementation of NOAA .gtx vertical datum shift file format. |
5 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2010, Frank Warmerdam |
9 | | * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com> |
10 | | * |
11 | | * SPDX-License-Identifier: MIT |
12 | | ****************************************************************************/ |
13 | | |
14 | | #include "cpl_string.h" |
15 | | #include "gdal_frmts.h" |
16 | | #include "ogr_srs_api.h" |
17 | | #include "rawdataset.h" |
18 | | |
19 | | #include <algorithm> |
20 | | #include <limits> |
21 | | |
22 | | /** |
23 | | |
24 | | NOAA .GTX Vertical Datum Grid Shift Format |
25 | | |
26 | | All values are bigendian |
27 | | |
28 | | Header |
29 | | ------ |
30 | | |
31 | | float64 latitude_of_origin |
32 | | float64 longitude_of_origin (0-360) |
33 | | float64 cell size (x?y?) |
34 | | float64 cell size (x?y?) |
35 | | int32 length in pixels |
36 | | int32 width in pixels |
37 | | |
38 | | Data |
39 | | ---- |
40 | | |
41 | | float32 * width in pixels * length in pixels |
42 | | |
43 | | Values are an offset in meters between two vertical datums. |
44 | | |
45 | | **/ |
46 | | |
47 | | /************************************************************************/ |
48 | | /* ==================================================================== */ |
49 | | /* GTXDataset */ |
50 | | /* ==================================================================== */ |
51 | | /************************************************************************/ |
52 | | |
53 | | class GTXDataset final : public RawDataset |
54 | | { |
55 | | VSILFILE *fpImage = nullptr; // image data file. |
56 | | |
57 | | OGRSpatialReference m_oSRS{}; |
58 | | GDALGeoTransform m_gt{}; |
59 | | |
60 | | CPL_DISALLOW_COPY_ASSIGN(GTXDataset) |
61 | | |
62 | | CPLErr Close() override; |
63 | | |
64 | | public: |
65 | | GTXDataset() |
66 | 51 | { |
67 | 51 | m_oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG); |
68 | 51 | m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
69 | 51 | } |
70 | | |
71 | | ~GTXDataset() override; |
72 | | |
73 | | CPLErr GetGeoTransform(GDALGeoTransform >) const override; |
74 | | CPLErr SetGeoTransform(const GDALGeoTransform >) override; |
75 | | |
76 | | const OGRSpatialReference *GetSpatialRef() const override |
77 | 0 | { |
78 | 0 | return &m_oSRS; |
79 | 0 | } |
80 | | |
81 | | static GDALDataset *Open(GDALOpenInfo *); |
82 | | static int Identify(GDALOpenInfo *); |
83 | | static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize, |
84 | | int nBands, GDALDataType eType, |
85 | | char **papszOptions); |
86 | | }; |
87 | | |
88 | | /************************************************************************/ |
89 | | /* ==================================================================== */ |
90 | | /* GTXRasterBand */ |
91 | | /* ==================================================================== */ |
92 | | /************************************************************************/ |
93 | | |
94 | | class GTXRasterBand final : public RawRasterBand |
95 | | { |
96 | | CPL_DISALLOW_COPY_ASSIGN(GTXRasterBand) |
97 | | |
98 | | public: |
99 | | GTXRasterBand(GDALDataset *poDS, int nBand, VSILFILE *fpRaw, |
100 | | vsi_l_offset nImgOffset, int nPixelOffset, int nLineOffset, |
101 | | GDALDataType eDataType, int bNativeOrder); |
102 | | |
103 | | ~GTXRasterBand() override; |
104 | | |
105 | | double GetNoDataValue(int *pbSuccess = nullptr) override; |
106 | | }; |
107 | | |
108 | | /************************************************************************/ |
109 | | /* GTXRasterBand() */ |
110 | | /************************************************************************/ |
111 | | |
112 | | GTXRasterBand::GTXRasterBand(GDALDataset *poDSIn, int nBandIn, |
113 | | VSILFILE *fpRawIn, vsi_l_offset nImgOffsetIn, |
114 | | int nPixelOffsetIn, int nLineOffsetIn, |
115 | | GDALDataType eDataTypeIn, int bNativeOrderIn) |
116 | 26 | : RawRasterBand(poDSIn, nBandIn, fpRawIn, nImgOffsetIn, nPixelOffsetIn, |
117 | 26 | nLineOffsetIn, eDataTypeIn, bNativeOrderIn, |
118 | 26 | RawRasterBand::OwnFP::NO) |
119 | 26 | { |
120 | 26 | } |
121 | | |
122 | | /************************************************************************/ |
123 | | /* ~GTXRasterBand() */ |
124 | | /************************************************************************/ |
125 | | |
126 | | GTXRasterBand::~GTXRasterBand() |
127 | 26 | { |
128 | 26 | } |
129 | | |
130 | | /************************************************************************/ |
131 | | /* GetNoDataValue() */ |
132 | | /************************************************************************/ |
133 | | |
134 | | double GTXRasterBand::GetNoDataValue(int *pbSuccess) |
135 | 0 | { |
136 | 0 | if (pbSuccess) |
137 | 0 | *pbSuccess = TRUE; |
138 | 0 | int bSuccess = FALSE; |
139 | 0 | double dfNoData = GDALPamRasterBand::GetNoDataValue(&bSuccess); |
140 | 0 | if (bSuccess) |
141 | 0 | { |
142 | 0 | return dfNoData; |
143 | 0 | } |
144 | 0 | return -88.8888; |
145 | 0 | } |
146 | | |
147 | | /************************************************************************/ |
148 | | /* ==================================================================== */ |
149 | | /* GTXDataset */ |
150 | | /* ==================================================================== */ |
151 | | /************************************************************************/ |
152 | | |
153 | | /************************************************************************/ |
154 | | /* ~GTXDataset() */ |
155 | | /************************************************************************/ |
156 | | |
157 | | GTXDataset::~GTXDataset() |
158 | | |
159 | 51 | { |
160 | 51 | GTXDataset::Close(); |
161 | 51 | } |
162 | | |
163 | | /************************************************************************/ |
164 | | /* Close() */ |
165 | | /************************************************************************/ |
166 | | |
167 | | CPLErr GTXDataset::Close() |
168 | 51 | { |
169 | 51 | CPLErr eErr = CE_None; |
170 | 51 | if (nOpenFlags != OPEN_FLAGS_CLOSED) |
171 | 51 | { |
172 | 51 | if (GTXDataset::FlushCache(true) != CE_None) |
173 | 0 | eErr = CE_Failure; |
174 | | |
175 | 51 | if (fpImage) |
176 | 51 | { |
177 | 51 | if (VSIFCloseL(fpImage) != 0) |
178 | 0 | { |
179 | 0 | CPLError(CE_Failure, CPLE_FileIO, "I/O error"); |
180 | 0 | eErr = CE_Failure; |
181 | 0 | } |
182 | 51 | } |
183 | | |
184 | 51 | if (GDALPamDataset::Close() != CE_None) |
185 | 0 | eErr = CE_Failure; |
186 | 51 | } |
187 | 51 | return eErr; |
188 | 51 | } |
189 | | |
190 | | /************************************************************************/ |
191 | | /* Identify() */ |
192 | | /************************************************************************/ |
193 | | |
194 | | int GTXDataset::Identify(GDALOpenInfo *poOpenInfo) |
195 | | |
196 | 534k | { |
197 | 534k | if (poOpenInfo->nHeaderBytes < 40) |
198 | 459k | return FALSE; |
199 | | |
200 | 74.9k | if (!poOpenInfo->IsExtensionEqualToCI("gtx")) |
201 | 74.8k | return FALSE; |
202 | | |
203 | 102 | return TRUE; |
204 | 74.9k | } |
205 | | |
206 | | /************************************************************************/ |
207 | | /* Open() */ |
208 | | /************************************************************************/ |
209 | | |
210 | | GDALDataset *GTXDataset::Open(GDALOpenInfo *poOpenInfo) |
211 | | |
212 | 51 | { |
213 | 51 | if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr) |
214 | 0 | return nullptr; |
215 | | |
216 | | /* -------------------------------------------------------------------- */ |
217 | | /* Create a corresponding GDALDataset. */ |
218 | | /* -------------------------------------------------------------------- */ |
219 | 51 | auto poDS = std::make_unique<GTXDataset>(); |
220 | | |
221 | 51 | poDS->eAccess = poOpenInfo->eAccess; |
222 | 51 | std::swap(poDS->fpImage, poOpenInfo->fpL); |
223 | | |
224 | | /* -------------------------------------------------------------------- */ |
225 | | /* Read the header. */ |
226 | | /* -------------------------------------------------------------------- */ |
227 | 51 | double gt[6] = {0}; |
228 | 51 | CPL_IGNORE_RET_VAL(VSIFReadL(>[3], 8, 1, poDS->fpImage)); |
229 | 51 | CPL_IGNORE_RET_VAL(VSIFReadL(>[0], 8, 1, poDS->fpImage)); |
230 | 51 | CPL_IGNORE_RET_VAL(VSIFReadL(>[5], 8, 1, poDS->fpImage)); |
231 | 51 | CPL_IGNORE_RET_VAL(VSIFReadL(>[1], 8, 1, poDS->fpImage)); |
232 | | |
233 | 51 | CPL_IGNORE_RET_VAL(VSIFReadL(&(poDS->nRasterYSize), 4, 1, poDS->fpImage)); |
234 | 51 | CPL_IGNORE_RET_VAL(VSIFReadL(&(poDS->nRasterXSize), 4, 1, poDS->fpImage)); |
235 | | |
236 | 51 | CPL_MSBPTR32(&(poDS->nRasterYSize)); |
237 | 51 | CPL_MSBPTR32(&(poDS->nRasterXSize)); |
238 | | |
239 | 51 | CPL_MSBPTR64(>[0]); |
240 | 51 | CPL_MSBPTR64(>[1]); |
241 | 51 | CPL_MSBPTR64(>[3]); |
242 | 51 | CPL_MSBPTR64(>[5]); |
243 | | |
244 | 51 | poDS->m_gt = GDALGeoTransform(gt); |
245 | 51 | poDS->m_gt[3] += |
246 | 51 | poDS->m_gt[5] * (static_cast<double>(poDS->nRasterYSize) - 1); |
247 | | |
248 | 51 | poDS->m_gt[0] -= poDS->m_gt[1] * 0.5; |
249 | 51 | poDS->m_gt[3] += poDS->m_gt[5] * 0.5; |
250 | | |
251 | 51 | poDS->m_gt[5] *= -1; |
252 | | |
253 | 51 | if (CPLFetchBool(poOpenInfo->papszOpenOptions, |
254 | 51 | "SHIFT_ORIGIN_IN_MINUS_180_PLUS_180", false)) |
255 | 0 | { |
256 | 0 | if (poDS->m_gt[0] < -180.0 - poDS->m_gt[1]) |
257 | 0 | poDS->m_gt[0] += 360.0; |
258 | 0 | else if (poDS->m_gt[0] > 180.0) |
259 | 0 | poDS->m_gt[0] -= 360.0; |
260 | 0 | } |
261 | | |
262 | 51 | if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize) || |
263 | 51 | static_cast<vsi_l_offset>(poDS->nRasterXSize) * poDS->nRasterYSize > |
264 | 40 | std::numeric_limits<vsi_l_offset>::max() / sizeof(double)) |
265 | 18 | { |
266 | 18 | return nullptr; |
267 | 18 | } |
268 | | |
269 | | /* -------------------------------------------------------------------- */ |
270 | | /* Guess the data type. Since October 1, 2009, it should be */ |
271 | | /* Float32. Before it was double. */ |
272 | | /* -------------------------------------------------------------------- */ |
273 | 33 | CPL_IGNORE_RET_VAL(VSIFSeekL(poDS->fpImage, 0, SEEK_END)); |
274 | 33 | const vsi_l_offset nSize = VSIFTellL(poDS->fpImage); |
275 | | |
276 | 33 | GDALDataType eDT = GDT_Float32; |
277 | 33 | if (nSize - 40 == sizeof(double) * |
278 | 33 | static_cast<vsi_l_offset>(poDS->nRasterXSize) * |
279 | 33 | poDS->nRasterYSize) |
280 | 0 | eDT = GDT_Float64; |
281 | 33 | const int nDTSize = GDALGetDataTypeSizeBytes(eDT); |
282 | 33 | if (nDTSize <= 0 || poDS->nRasterXSize > INT_MAX / nDTSize) |
283 | 7 | { |
284 | 7 | return nullptr; |
285 | 7 | } |
286 | | |
287 | | /* -------------------------------------------------------------------- */ |
288 | | /* Create band information object. */ |
289 | | /* -------------------------------------------------------------------- */ |
290 | 26 | auto poBand = std::make_unique<GTXRasterBand>( |
291 | 26 | poDS.get(), 1, poDS->fpImage, |
292 | 26 | static_cast<vsi_l_offset>(poDS->nRasterYSize - 1) * poDS->nRasterXSize * |
293 | 26 | nDTSize + |
294 | 26 | 40, |
295 | 26 | nDTSize, poDS->nRasterXSize * -nDTSize, eDT, !CPL_IS_LSB); |
296 | 26 | if (!poBand->IsValid()) |
297 | 0 | return nullptr; |
298 | 26 | poDS->SetBand(1, std::move(poBand)); |
299 | | |
300 | | /* -------------------------------------------------------------------- */ |
301 | | /* Initialize any PAM information. */ |
302 | | /* -------------------------------------------------------------------- */ |
303 | 26 | poDS->SetDescription(poOpenInfo->pszFilename); |
304 | 26 | poDS->TryLoadXML(); |
305 | | |
306 | | /* -------------------------------------------------------------------- */ |
307 | | /* Check for overviews. */ |
308 | | /* -------------------------------------------------------------------- */ |
309 | 26 | poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename); |
310 | | |
311 | 26 | return poDS.release(); |
312 | 26 | } |
313 | | |
314 | | /************************************************************************/ |
315 | | /* GetGeoTransform() */ |
316 | | /************************************************************************/ |
317 | | |
318 | | CPLErr GTXDataset::GetGeoTransform(GDALGeoTransform >) const |
319 | | |
320 | 0 | { |
321 | 0 | gt = m_gt; |
322 | 0 | return CE_None; |
323 | 0 | } |
324 | | |
325 | | /************************************************************************/ |
326 | | /* SetGeoTransform() */ |
327 | | /************************************************************************/ |
328 | | |
329 | | CPLErr GTXDataset::SetGeoTransform(const GDALGeoTransform >) |
330 | 0 | { |
331 | 0 | if (gt[2] != 0.0 || gt[4] != 0.0) |
332 | 0 | { |
333 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
334 | 0 | "Attempt to write skewed or rotated geotransform to gtx."); |
335 | 0 | return CE_Failure; |
336 | 0 | } |
337 | | |
338 | 0 | m_gt = gt; |
339 | |
|
340 | 0 | const double dfXOrigin = m_gt[0] + 0.5 * m_gt[1]; |
341 | 0 | const double dfYOrigin = m_gt[3] + (nRasterYSize - 0.5) * m_gt[5]; |
342 | 0 | const double dfWidth = m_gt[1]; |
343 | 0 | const double dfHeight = -m_gt[5]; |
344 | |
|
345 | 0 | unsigned char header[32] = {'\0'}; |
346 | 0 | memcpy(header + 0, &dfYOrigin, 8); |
347 | 0 | CPL_MSBPTR64(header + 0); |
348 | |
|
349 | 0 | memcpy(header + 8, &dfXOrigin, 8); |
350 | 0 | CPL_MSBPTR64(header + 8); |
351 | |
|
352 | 0 | memcpy(header + 16, &dfHeight, 8); |
353 | 0 | CPL_MSBPTR64(header + 16); |
354 | |
|
355 | 0 | memcpy(header + 24, &dfWidth, 8); |
356 | 0 | CPL_MSBPTR64(header + 24); |
357 | |
|
358 | 0 | if (VSIFSeekL(fpImage, SEEK_SET, 0) != 0 || |
359 | 0 | VSIFWriteL(header, 32, 1, fpImage) != 1) |
360 | 0 | { |
361 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
362 | 0 | "Attempt to write geotransform header to GTX failed."); |
363 | 0 | return CE_Failure; |
364 | 0 | } |
365 | | |
366 | 0 | return CE_None; |
367 | 0 | } |
368 | | |
369 | | /************************************************************************/ |
370 | | /* Create() */ |
371 | | /************************************************************************/ |
372 | | |
373 | | GDALDataset *GTXDataset::Create(const char *pszFilename, int nXSize, int nYSize, |
374 | | int /* nBands */, GDALDataType eType, |
375 | | char ** /* papszOptions */) |
376 | 0 | { |
377 | 0 | if (eType != GDT_Float32) |
378 | 0 | { |
379 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
380 | 0 | "Attempt to create gtx file with unsupported data type '%s'.", |
381 | 0 | GDALGetDataTypeName(eType)); |
382 | 0 | return nullptr; |
383 | 0 | } |
384 | | |
385 | 0 | if (!EQUAL(CPLGetExtensionSafe(pszFilename).c_str(), "gtx")) |
386 | 0 | { |
387 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
388 | 0 | "Attempt to create gtx file with extension other than gtx."); |
389 | 0 | return nullptr; |
390 | 0 | } |
391 | | |
392 | | /* -------------------------------------------------------------------- */ |
393 | | /* Try to create the file. */ |
394 | | /* -------------------------------------------------------------------- */ |
395 | 0 | VSILFILE *fp = VSIFOpenL(pszFilename, "wb"); |
396 | 0 | if (fp == nullptr) |
397 | 0 | { |
398 | 0 | CPLError(CE_Failure, CPLE_OpenFailed, |
399 | 0 | "Attempt to create file `%s' failed.\n", pszFilename); |
400 | 0 | return nullptr; |
401 | 0 | } |
402 | | |
403 | | /* -------------------------------------------------------------------- */ |
404 | | /* Write out the header with stub georeferencing. */ |
405 | | /* -------------------------------------------------------------------- */ |
406 | | |
407 | 0 | unsigned char header[40] = {'\0'}; |
408 | 0 | double dfYOrigin = 0.0; |
409 | 0 | memcpy(header + 0, &dfYOrigin, 8); |
410 | 0 | CPL_MSBPTR64(header + 0); |
411 | |
|
412 | 0 | double dfXOrigin = 0.0; |
413 | 0 | memcpy(header + 8, &dfXOrigin, 8); |
414 | 0 | CPL_MSBPTR64(header + 8); |
415 | |
|
416 | 0 | double dfYSize = 0.01; |
417 | 0 | memcpy(header + 16, &dfYSize, 8); |
418 | 0 | CPL_MSBPTR64(header + 16); |
419 | |
|
420 | 0 | double dfXSize = 0.01; |
421 | 0 | memcpy(header + 24, &dfXSize, 8); |
422 | 0 | CPL_MSBPTR64(header + 24); |
423 | |
|
424 | 0 | GInt32 nYSize32 = nYSize; |
425 | 0 | memcpy(header + 32, &nYSize32, 4); |
426 | 0 | CPL_MSBPTR32(header + 32); |
427 | |
|
428 | 0 | GInt32 nXSize32 = nXSize; |
429 | 0 | memcpy(header + 36, &nXSize32, 4); |
430 | 0 | CPL_MSBPTR32(header + 36); |
431 | |
|
432 | 0 | CPL_IGNORE_RET_VAL(VSIFWriteL(header, 40, 1, fp)); |
433 | 0 | CPL_IGNORE_RET_VAL(VSIFCloseL(fp)); |
434 | |
|
435 | 0 | return GDALDataset::FromHandle(GDALOpen(pszFilename, GA_Update)); |
436 | 0 | } |
437 | | |
438 | | /************************************************************************/ |
439 | | /* GDALRegister_GTX() */ |
440 | | /************************************************************************/ |
441 | | |
442 | | void GDALRegister_GTX() |
443 | | |
444 | 24 | { |
445 | 24 | if (GDALGetDriverByName("GTX") != nullptr) |
446 | 0 | return; |
447 | | |
448 | 24 | GDALDriver *poDriver = new GDALDriver(); |
449 | | |
450 | 24 | poDriver->SetDescription("GTX"); |
451 | 24 | poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES"); |
452 | 24 | poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "NOAA Vertical Datum .GTX"); |
453 | 24 | poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "gtx"); |
454 | 24 | poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES"); |
455 | | // poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, |
456 | | // "frmt_various.html#GTX" ); |
457 | 24 | poDriver->SetMetadataItem( |
458 | 24 | GDAL_DMD_OPENOPTIONLIST, |
459 | 24 | "<OpenOptionList>" |
460 | 24 | " <Option name='SHIFT_ORIGIN_IN_MINUS_180_PLUS_180' type='boolean' " |
461 | 24 | "description='Whether to apply a +/-360 deg shift to the longitude of " |
462 | 24 | "the top left corner so that it is in the [-180,180] range' " |
463 | 24 | "default='NO'/>" |
464 | 24 | "</OpenOptionList>"); |
465 | | |
466 | 24 | poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Float32"); |
467 | | |
468 | 24 | poDriver->pfnOpen = GTXDataset::Open; |
469 | 24 | poDriver->pfnIdentify = GTXDataset::Identify; |
470 | 24 | poDriver->pfnCreate = GTXDataset::Create; |
471 | | |
472 | 24 | GetGDALDriverManager()->RegisterDriver(poDriver); |
473 | 24 | } |