/src/gdal/frmts/raw/loslasdataset.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: Horizontal Datum Formats |
4 | | * Purpose: Implementation of NOAA/NADCON los/las datum shift format. |
5 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
6 | | * Financial Support: i-cubed (http://www.i-cubed.com) |
7 | | * |
8 | | ****************************************************************************** |
9 | | * Copyright (c) 2010, Frank Warmerdam |
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 | | |
21 | | /** |
22 | | |
23 | | NOAA .LOS/.LAS Datum Grid Shift Format |
24 | | |
25 | | Also used for .geo file from https://geodesy.noaa.gov/GEOID/MEXICO97/ |
26 | | |
27 | | All values are little endian |
28 | | |
29 | | Header |
30 | | ------ |
31 | | |
32 | | char[56] "NADCON EXTRACTED REGION" or |
33 | | "GEOID EXTRACTED REGION " |
34 | | char[8] "NADGRD " or |
35 | | "GEOGRD " |
36 | | int32 grid width |
37 | | int32 grid height |
38 | | int32 z count (1) |
39 | | float32 origin longitude |
40 | | float32 grid cell width longitude |
41 | | float32 origin latitude |
42 | | float32 grid cell height latitude |
43 | | float32 angle (0.0) |
44 | | |
45 | | Data |
46 | | ---- |
47 | | |
48 | | int32 ? always 0 |
49 | | float32*gridwidth offset in arcseconds (or in metres for geoids) |
50 | | |
51 | | Note that the record length is always gridwidth*4 + 4, and |
52 | | even the header record is this length though it means some waste. |
53 | | |
54 | | **/ |
55 | | |
56 | | /************************************************************************/ |
57 | | /* ==================================================================== */ |
58 | | /* LOSLASDataset */ |
59 | | /* ==================================================================== */ |
60 | | /************************************************************************/ |
61 | | |
62 | | class LOSLASDataset final : public RawDataset |
63 | | { |
64 | | VSILFILE *fpImage; // image data file. |
65 | | |
66 | | int nRecordLength; |
67 | | |
68 | | OGRSpatialReference m_oSRS{}; |
69 | | double adfGeoTransform[6]; |
70 | | |
71 | | CPL_DISALLOW_COPY_ASSIGN(LOSLASDataset) |
72 | | |
73 | | CPLErr Close() override; |
74 | | |
75 | | public: |
76 | | LOSLASDataset(); |
77 | | ~LOSLASDataset() override; |
78 | | |
79 | | CPLErr GetGeoTransform(double *padfTransform) override; |
80 | | |
81 | | const OGRSpatialReference *GetSpatialRef() const override |
82 | 8 | { |
83 | 8 | return &m_oSRS; |
84 | 8 | } |
85 | | |
86 | | static GDALDataset *Open(GDALOpenInfo *); |
87 | | static int Identify(GDALOpenInfo *); |
88 | | }; |
89 | | |
90 | | /************************************************************************/ |
91 | | /* ==================================================================== */ |
92 | | /* LOSLASDataset */ |
93 | | /* ==================================================================== */ |
94 | | /************************************************************************/ |
95 | | |
96 | | /************************************************************************/ |
97 | | /* LOSLASDataset() */ |
98 | | /************************************************************************/ |
99 | | |
100 | 14 | LOSLASDataset::LOSLASDataset() : fpImage(nullptr), nRecordLength(0) |
101 | 14 | { |
102 | 14 | m_oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG); |
103 | 14 | m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
104 | | |
105 | 14 | memset(adfGeoTransform, 0, sizeof(adfGeoTransform)); |
106 | 14 | } |
107 | | |
108 | | /************************************************************************/ |
109 | | /* ~LOSLASDataset() */ |
110 | | /************************************************************************/ |
111 | | |
112 | | LOSLASDataset::~LOSLASDataset() |
113 | | |
114 | 14 | { |
115 | 14 | LOSLASDataset::Close(); |
116 | 14 | } |
117 | | |
118 | | /************************************************************************/ |
119 | | /* Close() */ |
120 | | /************************************************************************/ |
121 | | |
122 | | CPLErr LOSLASDataset::Close() |
123 | 20 | { |
124 | 20 | CPLErr eErr = CE_None; |
125 | 20 | if (nOpenFlags != OPEN_FLAGS_CLOSED) |
126 | 14 | { |
127 | 14 | if (LOSLASDataset::FlushCache(true) != CE_None) |
128 | 0 | eErr = CE_Failure; |
129 | | |
130 | 14 | if (fpImage) |
131 | 14 | { |
132 | 14 | if (VSIFCloseL(fpImage) != 0) |
133 | 0 | { |
134 | 0 | CPLError(CE_Failure, CPLE_FileIO, "I/O error"); |
135 | 0 | eErr = CE_Failure; |
136 | 0 | } |
137 | 14 | } |
138 | | |
139 | 14 | if (GDALPamDataset::Close() != CE_None) |
140 | 0 | eErr = CE_Failure; |
141 | 14 | } |
142 | 20 | return eErr; |
143 | 20 | } |
144 | | |
145 | | /************************************************************************/ |
146 | | /* Identify() */ |
147 | | /************************************************************************/ |
148 | | |
149 | | int LOSLASDataset::Identify(GDALOpenInfo *poOpenInfo) |
150 | | |
151 | 39.4k | { |
152 | 39.4k | if (poOpenInfo->nHeaderBytes < 64) |
153 | 25.7k | return FALSE; |
154 | | |
155 | | #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
156 | | const char *pszExt = poOpenInfo->osExtension.c_str(); |
157 | | if (!EQUAL(pszExt, "las") && !EQUAL(pszExt, "los") && !EQUAL(pszExt, "geo")) |
158 | | return FALSE; |
159 | | #endif |
160 | | |
161 | 13.6k | if (!STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader + 56, "NADGRD") && |
162 | 13.6k | !STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader + 56, "GEOGRD")) |
163 | 13.6k | return FALSE; |
164 | | |
165 | 28 | return TRUE; |
166 | 13.6k | } |
167 | | |
168 | | /************************************************************************/ |
169 | | /* Open() */ |
170 | | /************************************************************************/ |
171 | | |
172 | | GDALDataset *LOSLASDataset::Open(GDALOpenInfo *poOpenInfo) |
173 | | |
174 | 14 | { |
175 | 14 | if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr) |
176 | 0 | return nullptr; |
177 | | |
178 | | /* -------------------------------------------------------------------- */ |
179 | | /* Confirm the requested access is supported. */ |
180 | | /* -------------------------------------------------------------------- */ |
181 | 14 | if (poOpenInfo->eAccess == GA_Update) |
182 | 0 | { |
183 | 0 | ReportUpdateNotSupportedByDriver("LOSLAS"); |
184 | 0 | return nullptr; |
185 | 0 | } |
186 | | |
187 | | /* -------------------------------------------------------------------- */ |
188 | | /* Create a corresponding GDALDataset. */ |
189 | | /* -------------------------------------------------------------------- */ |
190 | 14 | auto poDS = std::make_unique<LOSLASDataset>(); |
191 | 14 | std::swap(poDS->fpImage, poOpenInfo->fpL); |
192 | | |
193 | | /* -------------------------------------------------------------------- */ |
194 | | /* Read the header. */ |
195 | | /* -------------------------------------------------------------------- */ |
196 | 14 | CPL_IGNORE_RET_VAL(VSIFSeekL(poDS->fpImage, 64, SEEK_SET)); |
197 | | |
198 | 14 | CPL_IGNORE_RET_VAL(VSIFReadL(&(poDS->nRasterXSize), 4, 1, poDS->fpImage)); |
199 | 14 | CPL_IGNORE_RET_VAL(VSIFReadL(&(poDS->nRasterYSize), 4, 1, poDS->fpImage)); |
200 | | |
201 | 14 | CPL_LSBPTR32(&(poDS->nRasterXSize)); |
202 | 14 | CPL_LSBPTR32(&(poDS->nRasterYSize)); |
203 | | |
204 | 14 | if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize) || |
205 | 14 | poDS->nRasterXSize > (INT_MAX - 4) / 4) |
206 | 8 | { |
207 | 8 | return nullptr; |
208 | 8 | } |
209 | | |
210 | 6 | CPL_IGNORE_RET_VAL(VSIFSeekL(poDS->fpImage, 76, SEEK_SET)); |
211 | | |
212 | 6 | float min_lon, min_lat, delta_lon, delta_lat; |
213 | | |
214 | 6 | CPL_IGNORE_RET_VAL(VSIFReadL(&min_lon, 4, 1, poDS->fpImage)); |
215 | 6 | CPL_IGNORE_RET_VAL(VSIFReadL(&delta_lon, 4, 1, poDS->fpImage)); |
216 | 6 | CPL_IGNORE_RET_VAL(VSIFReadL(&min_lat, 4, 1, poDS->fpImage)); |
217 | 6 | CPL_IGNORE_RET_VAL(VSIFReadL(&delta_lat, 4, 1, poDS->fpImage)); |
218 | | |
219 | 6 | CPL_LSBPTR32(&min_lon); |
220 | 6 | CPL_LSBPTR32(&delta_lon); |
221 | 6 | CPL_LSBPTR32(&min_lat); |
222 | 6 | CPL_LSBPTR32(&delta_lat); |
223 | | |
224 | 6 | poDS->nRecordLength = poDS->nRasterXSize * 4 + 4; |
225 | | |
226 | | /* -------------------------------------------------------------------- */ |
227 | | /* Create band information object. */ |
228 | | /* */ |
229 | | /* Note we are setting up to read from the last image record to */ |
230 | | /* the first since the data comes with the southern most record */ |
231 | | /* first, not the northernmost like we would want. */ |
232 | | /* -------------------------------------------------------------------- */ |
233 | 6 | auto poBand = RawRasterBand::Create( |
234 | 6 | poDS.get(), 1, poDS->fpImage, |
235 | 6 | static_cast<vsi_l_offset>(poDS->nRasterYSize) * poDS->nRecordLength + 4, |
236 | 6 | 4, -1 * poDS->nRecordLength, GDT_Float32, |
237 | 6 | RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN, |
238 | 6 | RawRasterBand::OwnFP::NO); |
239 | 6 | if (!poBand) |
240 | 0 | return nullptr; |
241 | 6 | poDS->SetBand(1, std::move(poBand)); |
242 | | |
243 | 6 | if (poOpenInfo->IsExtensionEqualToCI("las")) |
244 | 0 | { |
245 | 0 | poDS->GetRasterBand(1)->SetDescription("Latitude Offset (arc seconds)"); |
246 | 0 | } |
247 | 6 | else if (poOpenInfo->IsExtensionEqualToCI("los")) |
248 | 0 | { |
249 | 0 | poDS->GetRasterBand(1)->SetDescription( |
250 | 0 | "Longitude Offset (arc seconds)"); |
251 | 0 | poDS->GetRasterBand(1)->SetMetadataItem("positive_value", "west"); |
252 | 0 | } |
253 | 6 | else if (poOpenInfo->IsExtensionEqualToCI("geo")) |
254 | 0 | { |
255 | 0 | poDS->GetRasterBand(1)->SetDescription("Geoid undulation (meters)"); |
256 | 0 | } |
257 | | |
258 | | /* -------------------------------------------------------------------- */ |
259 | | /* Setup georeferencing. */ |
260 | | /* -------------------------------------------------------------------- */ |
261 | 6 | poDS->adfGeoTransform[0] = min_lon - delta_lon * 0.5; |
262 | 6 | poDS->adfGeoTransform[1] = delta_lon; |
263 | 6 | poDS->adfGeoTransform[2] = 0.0; |
264 | 6 | poDS->adfGeoTransform[3] = min_lat + (poDS->nRasterYSize - 0.5) * delta_lat; |
265 | 6 | poDS->adfGeoTransform[4] = 0.0; |
266 | 6 | poDS->adfGeoTransform[5] = -1.0 * delta_lat; |
267 | | |
268 | | /* -------------------------------------------------------------------- */ |
269 | | /* Initialize any PAM information. */ |
270 | | /* -------------------------------------------------------------------- */ |
271 | 6 | poDS->SetDescription(poOpenInfo->pszFilename); |
272 | 6 | poDS->TryLoadXML(); |
273 | | |
274 | | /* -------------------------------------------------------------------- */ |
275 | | /* Check for overviews. */ |
276 | | /* -------------------------------------------------------------------- */ |
277 | 6 | poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename); |
278 | | |
279 | 6 | return poDS.release(); |
280 | 6 | } |
281 | | |
282 | | /************************************************************************/ |
283 | | /* GetGeoTransform() */ |
284 | | /************************************************************************/ |
285 | | |
286 | | CPLErr LOSLASDataset::GetGeoTransform(double *padfTransform) |
287 | | |
288 | 6 | { |
289 | 6 | memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6); |
290 | 6 | return CE_None; |
291 | 6 | } |
292 | | |
293 | | /************************************************************************/ |
294 | | /* GDALRegister_LOSLAS() */ |
295 | | /************************************************************************/ |
296 | | |
297 | | void GDALRegister_LOSLAS() |
298 | | |
299 | 2 | { |
300 | 2 | if (GDALGetDriverByName("LOSLAS") != nullptr) |
301 | 0 | return; |
302 | | |
303 | 2 | GDALDriver *poDriver = new GDALDriver(); |
304 | | |
305 | 2 | poDriver->SetDescription("LOSLAS"); |
306 | 2 | poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES"); |
307 | 2 | poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, |
308 | 2 | "NADCON .los/.las Datum Grid Shift"); |
309 | 2 | poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES"); |
310 | | |
311 | 2 | poDriver->pfnOpen = LOSLASDataset::Open; |
312 | 2 | poDriver->pfnIdentify = LOSLASDataset::Identify; |
313 | | |
314 | 2 | GetGDALDriverManager()->RegisterDriver(poDriver); |
315 | 2 | } |