/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 | | GDALGeoTransform m_gt{}; |
70 | | |
71 | | CPL_DISALLOW_COPY_ASSIGN(LOSLASDataset) |
72 | | |
73 | | CPLErr Close() override; |
74 | | |
75 | | public: |
76 | | LOSLASDataset(); |
77 | | ~LOSLASDataset() override; |
78 | | |
79 | | CPLErr GetGeoTransform(GDALGeoTransform >) const override; |
80 | | |
81 | | const OGRSpatialReference *GetSpatialRef() const override |
82 | 6 | { |
83 | 6 | return &m_oSRS; |
84 | 6 | } |
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 | 16 | LOSLASDataset::LOSLASDataset() : fpImage(nullptr), nRecordLength(0) |
101 | 16 | { |
102 | 16 | m_oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG); |
103 | 16 | m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
104 | 16 | } |
105 | | |
106 | | /************************************************************************/ |
107 | | /* ~LOSLASDataset() */ |
108 | | /************************************************************************/ |
109 | | |
110 | | LOSLASDataset::~LOSLASDataset() |
111 | | |
112 | 16 | { |
113 | 16 | LOSLASDataset::Close(); |
114 | 16 | } |
115 | | |
116 | | /************************************************************************/ |
117 | | /* Close() */ |
118 | | /************************************************************************/ |
119 | | |
120 | | CPLErr LOSLASDataset::Close() |
121 | 19 | { |
122 | 19 | CPLErr eErr = CE_None; |
123 | 19 | if (nOpenFlags != OPEN_FLAGS_CLOSED) |
124 | 16 | { |
125 | 16 | if (LOSLASDataset::FlushCache(true) != CE_None) |
126 | 0 | eErr = CE_Failure; |
127 | | |
128 | 16 | if (fpImage) |
129 | 16 | { |
130 | 16 | if (VSIFCloseL(fpImage) != 0) |
131 | 0 | { |
132 | 0 | CPLError(CE_Failure, CPLE_FileIO, "I/O error"); |
133 | 0 | eErr = CE_Failure; |
134 | 0 | } |
135 | 16 | } |
136 | | |
137 | 16 | if (GDALPamDataset::Close() != CE_None) |
138 | 0 | eErr = CE_Failure; |
139 | 16 | } |
140 | 19 | return eErr; |
141 | 19 | } |
142 | | |
143 | | /************************************************************************/ |
144 | | /* Identify() */ |
145 | | /************************************************************************/ |
146 | | |
147 | | int LOSLASDataset::Identify(GDALOpenInfo *poOpenInfo) |
148 | | |
149 | 534k | { |
150 | 534k | if (poOpenInfo->nHeaderBytes < 64) |
151 | 459k | return FALSE; |
152 | | |
153 | | #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
154 | | const char *pszExt = poOpenInfo->osExtension.c_str(); |
155 | | if (!EQUAL(pszExt, "las") && !EQUAL(pszExt, "los") && !EQUAL(pszExt, "geo")) |
156 | | return FALSE; |
157 | | #endif |
158 | | |
159 | 74.1k | if (!STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader) + |
160 | 74.1k | 56, |
161 | 74.1k | "NADGRD") && |
162 | 74.1k | !STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader) + |
163 | 74.1k | 56, |
164 | 74.1k | "GEOGRD")) |
165 | 74.1k | return FALSE; |
166 | | |
167 | 32 | return TRUE; |
168 | 74.1k | } |
169 | | |
170 | | /************************************************************************/ |
171 | | /* Open() */ |
172 | | /************************************************************************/ |
173 | | |
174 | | GDALDataset *LOSLASDataset::Open(GDALOpenInfo *poOpenInfo) |
175 | | |
176 | 16 | { |
177 | 16 | if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr) |
178 | 0 | return nullptr; |
179 | | |
180 | | /* -------------------------------------------------------------------- */ |
181 | | /* Confirm the requested access is supported. */ |
182 | | /* -------------------------------------------------------------------- */ |
183 | 16 | if (poOpenInfo->eAccess == GA_Update) |
184 | 0 | { |
185 | 0 | ReportUpdateNotSupportedByDriver("LOSLAS"); |
186 | 0 | return nullptr; |
187 | 0 | } |
188 | | |
189 | | /* -------------------------------------------------------------------- */ |
190 | | /* Create a corresponding GDALDataset. */ |
191 | | /* -------------------------------------------------------------------- */ |
192 | 16 | auto poDS = std::make_unique<LOSLASDataset>(); |
193 | 16 | std::swap(poDS->fpImage, poOpenInfo->fpL); |
194 | | |
195 | | /* -------------------------------------------------------------------- */ |
196 | | /* Read the header. */ |
197 | | /* -------------------------------------------------------------------- */ |
198 | 16 | CPL_IGNORE_RET_VAL(VSIFSeekL(poDS->fpImage, 64, SEEK_SET)); |
199 | | |
200 | 16 | CPL_IGNORE_RET_VAL(VSIFReadL(&(poDS->nRasterXSize), 4, 1, poDS->fpImage)); |
201 | 16 | CPL_IGNORE_RET_VAL(VSIFReadL(&(poDS->nRasterYSize), 4, 1, poDS->fpImage)); |
202 | | |
203 | 16 | CPL_LSBPTR32(&(poDS->nRasterXSize)); |
204 | 16 | CPL_LSBPTR32(&(poDS->nRasterYSize)); |
205 | | |
206 | 16 | if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize) || |
207 | 16 | poDS->nRasterXSize > (INT_MAX - 4) / 4) |
208 | 13 | { |
209 | 13 | return nullptr; |
210 | 13 | } |
211 | | |
212 | 3 | CPL_IGNORE_RET_VAL(VSIFSeekL(poDS->fpImage, 76, SEEK_SET)); |
213 | | |
214 | 3 | float min_lon, min_lat, delta_lon, delta_lat; |
215 | | |
216 | 3 | CPL_IGNORE_RET_VAL(VSIFReadL(&min_lon, 4, 1, poDS->fpImage)); |
217 | 3 | CPL_IGNORE_RET_VAL(VSIFReadL(&delta_lon, 4, 1, poDS->fpImage)); |
218 | 3 | CPL_IGNORE_RET_VAL(VSIFReadL(&min_lat, 4, 1, poDS->fpImage)); |
219 | 3 | CPL_IGNORE_RET_VAL(VSIFReadL(&delta_lat, 4, 1, poDS->fpImage)); |
220 | | |
221 | 3 | CPL_LSBPTR32(&min_lon); |
222 | 3 | CPL_LSBPTR32(&delta_lon); |
223 | 3 | CPL_LSBPTR32(&min_lat); |
224 | 3 | CPL_LSBPTR32(&delta_lat); |
225 | | |
226 | 3 | poDS->nRecordLength = poDS->nRasterXSize * 4 + 4; |
227 | | |
228 | | /* -------------------------------------------------------------------- */ |
229 | | /* Create band information object. */ |
230 | | /* */ |
231 | | /* Note we are setting up to read from the last image record to */ |
232 | | /* the first since the data comes with the southern most record */ |
233 | | /* first, not the northernmost like we would want. */ |
234 | | /* -------------------------------------------------------------------- */ |
235 | 3 | auto poBand = RawRasterBand::Create( |
236 | 3 | poDS.get(), 1, poDS->fpImage, |
237 | 3 | static_cast<vsi_l_offset>(poDS->nRasterYSize) * poDS->nRecordLength + 4, |
238 | 3 | 4, -1 * poDS->nRecordLength, GDT_Float32, |
239 | 3 | RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN, |
240 | 3 | RawRasterBand::OwnFP::NO); |
241 | 3 | if (!poBand) |
242 | 0 | return nullptr; |
243 | 3 | poDS->SetBand(1, std::move(poBand)); |
244 | | |
245 | 3 | if (poOpenInfo->IsExtensionEqualToCI("las")) |
246 | 0 | { |
247 | 0 | poDS->GetRasterBand(1)->SetDescription("Latitude Offset (arc seconds)"); |
248 | 0 | } |
249 | 3 | else if (poOpenInfo->IsExtensionEqualToCI("los")) |
250 | 0 | { |
251 | 0 | poDS->GetRasterBand(1)->SetDescription( |
252 | 0 | "Longitude Offset (arc seconds)"); |
253 | 0 | poDS->GetRasterBand(1)->SetMetadataItem("positive_value", "west"); |
254 | 0 | } |
255 | 3 | else if (poOpenInfo->IsExtensionEqualToCI("geo")) |
256 | 0 | { |
257 | 0 | poDS->GetRasterBand(1)->SetDescription("Geoid undulation (meters)"); |
258 | 0 | } |
259 | | |
260 | | /* -------------------------------------------------------------------- */ |
261 | | /* Setup georeferencing. */ |
262 | | /* -------------------------------------------------------------------- */ |
263 | 3 | poDS->m_gt[0] = min_lon - delta_lon * 0.5; |
264 | 3 | poDS->m_gt[1] = delta_lon; |
265 | 3 | poDS->m_gt[2] = 0.0; |
266 | 3 | poDS->m_gt[3] = min_lat + (poDS->nRasterYSize - 0.5) * delta_lat; |
267 | 3 | poDS->m_gt[4] = 0.0; |
268 | 3 | poDS->m_gt[5] = -1.0 * delta_lat; |
269 | | |
270 | | /* -------------------------------------------------------------------- */ |
271 | | /* Initialize any PAM information. */ |
272 | | /* -------------------------------------------------------------------- */ |
273 | 3 | poDS->SetDescription(poOpenInfo->pszFilename); |
274 | 3 | poDS->TryLoadXML(); |
275 | | |
276 | | /* -------------------------------------------------------------------- */ |
277 | | /* Check for overviews. */ |
278 | | /* -------------------------------------------------------------------- */ |
279 | 3 | poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename); |
280 | | |
281 | 3 | return poDS.release(); |
282 | 3 | } |
283 | | |
284 | | /************************************************************************/ |
285 | | /* GetGeoTransform() */ |
286 | | /************************************************************************/ |
287 | | |
288 | | CPLErr LOSLASDataset::GetGeoTransform(GDALGeoTransform >) const |
289 | | |
290 | 6 | { |
291 | 6 | gt = m_gt; |
292 | 6 | return CE_None; |
293 | 6 | } |
294 | | |
295 | | /************************************************************************/ |
296 | | /* GDALRegister_LOSLAS() */ |
297 | | /************************************************************************/ |
298 | | |
299 | | void GDALRegister_LOSLAS() |
300 | | |
301 | 24 | { |
302 | 24 | if (GDALGetDriverByName("LOSLAS") != nullptr) |
303 | 0 | return; |
304 | | |
305 | 24 | GDALDriver *poDriver = new GDALDriver(); |
306 | | |
307 | 24 | poDriver->SetDescription("LOSLAS"); |
308 | 24 | poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES"); |
309 | 24 | poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, |
310 | 24 | "NADCON .los/.las Datum Grid Shift"); |
311 | 24 | poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES"); |
312 | | |
313 | 24 | poDriver->pfnOpen = LOSLASDataset::Open; |
314 | 24 | poDriver->pfnIdentify = LOSLASDataset::Identify; |
315 | | |
316 | 24 | GetGDALDriverManager()->RegisterDriver(poDriver); |
317 | 24 | } |