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