/src/gdal/frmts/jdem/jdemdataset.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: JDEM Reader |
4 | | * Purpose: All code for Japanese DEM Reader |
5 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2000, Frank Warmerdam <warmerdam@pobox.com> |
9 | | * Copyright (c) 2009-2012, Even Rouault <even dot rouault at spatialys.com> |
10 | | * |
11 | | * SPDX-License-Identifier: MIT |
12 | | ****************************************************************************/ |
13 | | |
14 | | #include "cpl_port.h" |
15 | | #include "gdal_frmts.h" |
16 | | #include "gdal_pam.h" |
17 | | #include "gdal_driver.h" |
18 | | #include "gdal_drivermanager.h" |
19 | | #include "gdal_openinfo.h" |
20 | | #include "gdal_cpp_functions.h" |
21 | | |
22 | | #include <algorithm> |
23 | | |
24 | | constexpr int HEADER_SIZE = 1011; |
25 | | |
26 | | /************************************************************************/ |
27 | | /* JDEMGetField() */ |
28 | | /************************************************************************/ |
29 | | |
30 | | static int JDEMGetField(const char *pszField, int nWidth) |
31 | | |
32 | 29 | { |
33 | 29 | char szWork[32] = {}; |
34 | 29 | CPLAssert(nWidth < static_cast<int>(sizeof(szWork))); |
35 | | |
36 | 29 | strncpy(szWork, pszField, nWidth); |
37 | 29 | szWork[nWidth] = '\0'; |
38 | | |
39 | 29 | return atoi(szWork); |
40 | 29 | } |
41 | | |
42 | | /************************************************************************/ |
43 | | /* JDEMGetAngle() */ |
44 | | /************************************************************************/ |
45 | | |
46 | | static double JDEMGetAngle(const char *pszField) |
47 | | |
48 | 24 | { |
49 | 24 | const int nAngle = JDEMGetField(pszField, 7); |
50 | | |
51 | | // Note, this isn't very general purpose, but it would appear |
52 | | // from the field widths that angles are never negative. Nice |
53 | | // to be a country in the "first quadrant". |
54 | | |
55 | 24 | const int nDegree = nAngle / 10000; |
56 | 24 | const int nMin = (nAngle / 100) % 100; |
57 | 24 | const int nSec = nAngle % 100; |
58 | | |
59 | 24 | return nDegree + nMin / 60.0 + nSec / 3600.0; |
60 | 24 | } |
61 | | |
62 | | /************************************************************************/ |
63 | | /* ==================================================================== */ |
64 | | /* JDEMDataset */ |
65 | | /* ==================================================================== */ |
66 | | /************************************************************************/ |
67 | | |
68 | | class JDEMRasterBand; |
69 | | |
70 | | class JDEMDataset final : public GDALPamDataset |
71 | | { |
72 | | friend class JDEMRasterBand; |
73 | | |
74 | | VSILFILE *m_fp = nullptr; |
75 | | GByte m_abyHeader[HEADER_SIZE]; |
76 | | OGRSpatialReference m_oSRS{}; |
77 | | |
78 | | public: |
79 | | JDEMDataset(); |
80 | | ~JDEMDataset() override; |
81 | | |
82 | | static GDALDataset *Open(GDALOpenInfo *); |
83 | | static int Identify(GDALOpenInfo *); |
84 | | |
85 | | CPLErr GetGeoTransform(GDALGeoTransform >) const override; |
86 | | const OGRSpatialReference *GetSpatialRef() const override; |
87 | | }; |
88 | | |
89 | | /************************************************************************/ |
90 | | /* ==================================================================== */ |
91 | | /* JDEMRasterBand */ |
92 | | /* ==================================================================== */ |
93 | | /************************************************************************/ |
94 | | |
95 | | class JDEMRasterBand final : public GDALPamRasterBand |
96 | | { |
97 | | friend class JDEMDataset; |
98 | | |
99 | | int m_nRecordSize = 0; |
100 | | char *m_pszRecord = nullptr; |
101 | | bool m_bBufferAllocFailed = false; |
102 | | |
103 | | public: |
104 | | JDEMRasterBand(JDEMDataset *, int); |
105 | | ~JDEMRasterBand() override; |
106 | | |
107 | | CPLErr IReadBlock(int, int, void *) override; |
108 | | }; |
109 | | |
110 | | /************************************************************************/ |
111 | | /* JDEMRasterBand() */ |
112 | | /************************************************************************/ |
113 | | |
114 | | JDEMRasterBand::JDEMRasterBand(JDEMDataset *poDSIn, int nBandIn) |
115 | | : // Cannot overflow as nBlockXSize <= 999. |
116 | 2 | m_nRecordSize(poDSIn->GetRasterXSize() * 5 + 9 + 2) |
117 | 2 | { |
118 | 2 | poDS = poDSIn; |
119 | 2 | nBand = nBandIn; |
120 | | |
121 | 2 | eDataType = GDT_Float32; |
122 | | |
123 | 2 | nBlockXSize = poDS->GetRasterXSize(); |
124 | 2 | nBlockYSize = 1; |
125 | 2 | } |
126 | | |
127 | | /************************************************************************/ |
128 | | /* ~JDEMRasterBand() */ |
129 | | /************************************************************************/ |
130 | | |
131 | | JDEMRasterBand::~JDEMRasterBand() |
132 | 2 | { |
133 | 2 | VSIFree(m_pszRecord); |
134 | 2 | } |
135 | | |
136 | | /************************************************************************/ |
137 | | /* IReadBlock() */ |
138 | | /************************************************************************/ |
139 | | |
140 | | CPLErr JDEMRasterBand::IReadBlock(int /* nBlockXOff */, int nBlockYOff, |
141 | | void *pImage) |
142 | | |
143 | 2 | { |
144 | 2 | JDEMDataset *poGDS = cpl::down_cast<JDEMDataset *>(poDS); |
145 | | |
146 | 2 | if (m_pszRecord == nullptr) |
147 | 2 | { |
148 | 2 | if (m_bBufferAllocFailed) |
149 | 0 | return CE_Failure; |
150 | | |
151 | 2 | m_pszRecord = static_cast<char *>(VSI_MALLOC_VERBOSE(m_nRecordSize)); |
152 | 2 | if (m_pszRecord == nullptr) |
153 | 0 | { |
154 | 0 | m_bBufferAllocFailed = true; |
155 | 0 | return CE_Failure; |
156 | 0 | } |
157 | 2 | } |
158 | | |
159 | 2 | CPL_IGNORE_RET_VAL( |
160 | 2 | VSIFSeekL(poGDS->m_fp, 1011 + m_nRecordSize * nBlockYOff, SEEK_SET)); |
161 | | |
162 | 2 | if (VSIFReadL(m_pszRecord, m_nRecordSize, 1, poGDS->m_fp) != 1) |
163 | 0 | { |
164 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Cannot read scanline %d", |
165 | 0 | nBlockYOff); |
166 | 0 | return CE_Failure; |
167 | 0 | } |
168 | | |
169 | 2 | if (!EQUALN(reinterpret_cast<char *>(poGDS->m_abyHeader), m_pszRecord, 6)) |
170 | 1 | { |
171 | 1 | CPLError(CE_Failure, CPLE_AppDefined, |
172 | 1 | "JDEM Scanline corrupt. Perhaps file was not transferred " |
173 | 1 | "in binary mode?"); |
174 | 1 | return CE_Failure; |
175 | 1 | } |
176 | | |
177 | 1 | if (JDEMGetField(m_pszRecord + 6, 3) != nBlockYOff + 1) |
178 | 1 | { |
179 | 1 | CPLError(CE_Failure, CPLE_AppDefined, |
180 | 1 | "JDEM scanline out of order, JDEM driver does not " |
181 | 1 | "currently support partial datasets."); |
182 | 1 | return CE_Failure; |
183 | 1 | } |
184 | | |
185 | 0 | for (int i = 0; i < nBlockXSize; i++) |
186 | 0 | static_cast<float *>(pImage)[i] = |
187 | 0 | JDEMGetField(m_pszRecord + 9 + 5 * i, 5) * 0.1f; |
188 | |
|
189 | 0 | return CE_None; |
190 | 1 | } |
191 | | |
192 | | /************************************************************************/ |
193 | | /* ==================================================================== */ |
194 | | /* JDEMDataset */ |
195 | | /* ==================================================================== */ |
196 | | /************************************************************************/ |
197 | | |
198 | | /************************************************************************/ |
199 | | /* JDEMDataset() */ |
200 | | /************************************************************************/ |
201 | | |
202 | | JDEMDataset::JDEMDataset() |
203 | 2 | { |
204 | 2 | std::fill_n(m_abyHeader, CPL_ARRAYSIZE(m_abyHeader), static_cast<GByte>(0)); |
205 | 2 | m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
206 | 2 | m_oSRS.importFromEPSG(4301); // Tokyo geographic CRS |
207 | 2 | } |
208 | | |
209 | | /************************************************************************/ |
210 | | /* ~JDEMDataset() */ |
211 | | /************************************************************************/ |
212 | | |
213 | | JDEMDataset::~JDEMDataset() |
214 | | |
215 | 2 | { |
216 | 2 | FlushCache(true); |
217 | 2 | if (m_fp != nullptr) |
218 | 2 | CPL_IGNORE_RET_VAL(VSIFCloseL(m_fp)); |
219 | 2 | } |
220 | | |
221 | | /************************************************************************/ |
222 | | /* GetGeoTransform() */ |
223 | | /************************************************************************/ |
224 | | |
225 | | CPLErr JDEMDataset::GetGeoTransform(GDALGeoTransform >) const |
226 | | |
227 | 2 | { |
228 | 2 | const char *psHeader = reinterpret_cast<const char *>(m_abyHeader); |
229 | | |
230 | 2 | const double dfLLLat = JDEMGetAngle(psHeader + 29); |
231 | 2 | const double dfLLLong = JDEMGetAngle(psHeader + 36); |
232 | 2 | const double dfURLat = JDEMGetAngle(psHeader + 43); |
233 | 2 | const double dfURLong = JDEMGetAngle(psHeader + 50); |
234 | | |
235 | 2 | gt[0] = dfLLLong; |
236 | 2 | gt[3] = dfURLat; |
237 | 2 | gt[1] = (dfURLong - dfLLLong) / GetRasterXSize(); |
238 | 2 | gt[2] = 0.0; |
239 | | |
240 | 2 | gt[4] = 0.0; |
241 | 2 | gt[5] = -1 * (dfURLat - dfLLLat) / GetRasterYSize(); |
242 | | |
243 | 2 | return CE_None; |
244 | 2 | } |
245 | | |
246 | | /************************************************************************/ |
247 | | /* GetSpatialRef() */ |
248 | | /************************************************************************/ |
249 | | |
250 | | const OGRSpatialReference *JDEMDataset::GetSpatialRef() const |
251 | | |
252 | 2 | { |
253 | 2 | return &m_oSRS; |
254 | 2 | } |
255 | | |
256 | | /************************************************************************/ |
257 | | /* Identify() */ |
258 | | /************************************************************************/ |
259 | | |
260 | | int JDEMDataset::Identify(GDALOpenInfo *poOpenInfo) |
261 | | |
262 | 601k | { |
263 | 601k | if (poOpenInfo->nHeaderBytes < HEADER_SIZE) |
264 | 544k | return FALSE; |
265 | | |
266 | | // Confirm that the header has what appears to be dates in the |
267 | | // expected locations. |
268 | | // Check if century values seem reasonable. |
269 | 56.8k | const char *psHeader = reinterpret_cast<char *>(poOpenInfo->pabyHeader); |
270 | 56.8k | if ((!STARTS_WITH_CI(psHeader + 11, "19") && |
271 | 56.8k | !STARTS_WITH_CI(psHeader + 11, "20")) || |
272 | 46 | (!STARTS_WITH_CI(psHeader + 15, "19") && |
273 | 42 | !STARTS_WITH_CI(psHeader + 15, "20")) || |
274 | 6 | (!STARTS_WITH_CI(psHeader + 19, "19") && |
275 | 2 | !STARTS_WITH_CI(psHeader + 19, "20"))) |
276 | 56.8k | { |
277 | 56.8k | return FALSE; |
278 | 56.8k | } |
279 | | |
280 | | // Check the extent too. In particular, that we are in the first quadrant, |
281 | | // as this is only for Japan. |
282 | 4 | const double dfLLLat = JDEMGetAngle(psHeader + 29); |
283 | 4 | const double dfLLLong = JDEMGetAngle(psHeader + 36); |
284 | 4 | const double dfURLat = JDEMGetAngle(psHeader + 43); |
285 | 4 | const double dfURLong = JDEMGetAngle(psHeader + 50); |
286 | 4 | if (dfLLLat > 90 || dfLLLat < 0 || dfLLLong > 180 || dfLLLong < 0 || |
287 | 4 | dfURLat > 90 || dfURLat < 0 || dfURLong > 180 || dfURLong < 0 || |
288 | 4 | dfLLLat > dfURLat || dfLLLong > dfURLong) |
289 | 0 | { |
290 | 0 | return FALSE; |
291 | 0 | } |
292 | | |
293 | 4 | return TRUE; |
294 | 4 | } |
295 | | |
296 | | /************************************************************************/ |
297 | | /* Open() */ |
298 | | /************************************************************************/ |
299 | | |
300 | | GDALDataset *JDEMDataset::Open(GDALOpenInfo *poOpenInfo) |
301 | | |
302 | 2 | { |
303 | | // Confirm that the header is compatible with a JDEM dataset. |
304 | 2 | if (!Identify(poOpenInfo)) |
305 | 0 | return nullptr; |
306 | | |
307 | | // Confirm the requested access is supported. |
308 | 2 | if (poOpenInfo->eAccess == GA_Update) |
309 | 0 | { |
310 | 0 | ReportUpdateNotSupportedByDriver("JDEM"); |
311 | 0 | return nullptr; |
312 | 0 | } |
313 | | |
314 | | // Check that the file pointer from GDALOpenInfo* is available. |
315 | 2 | if (poOpenInfo->fpL == nullptr) |
316 | 0 | { |
317 | 0 | return nullptr; |
318 | 0 | } |
319 | | |
320 | | // Create a corresponding GDALDataset. |
321 | 2 | auto poDS = std::make_unique<JDEMDataset>(); |
322 | | |
323 | | // Borrow the file pointer from GDALOpenInfo*. |
324 | 2 | std::swap(poDS->m_fp, poOpenInfo->fpL); |
325 | | |
326 | | // Store the header (we have already checked it is at least HEADER_SIZE |
327 | | // byte large). |
328 | 2 | memcpy(poDS->m_abyHeader, poOpenInfo->pabyHeader, HEADER_SIZE); |
329 | | |
330 | 2 | const char *psHeader = reinterpret_cast<const char *>(poDS->m_abyHeader); |
331 | 2 | poDS->nRasterXSize = JDEMGetField(psHeader + 23, 3); |
332 | 2 | poDS->nRasterYSize = JDEMGetField(psHeader + 26, 3); |
333 | 2 | if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize)) |
334 | 0 | { |
335 | 0 | return nullptr; |
336 | 0 | } |
337 | | |
338 | | // Create band information objects. |
339 | 2 | poDS->SetBand(1, new JDEMRasterBand(poDS.get(), 1)); |
340 | | |
341 | | // Initialize any PAM information. |
342 | 2 | poDS->SetDescription(poOpenInfo->pszFilename); |
343 | 2 | poDS->TryLoadXML(); |
344 | | |
345 | | // Check for overviews. |
346 | 2 | poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename); |
347 | | |
348 | 2 | return poDS.release(); |
349 | 2 | } |
350 | | |
351 | | /************************************************************************/ |
352 | | /* GDALRegister_JDEM() */ |
353 | | /************************************************************************/ |
354 | | |
355 | | void GDALRegister_JDEM() |
356 | | |
357 | 22 | { |
358 | 22 | if (GDALGetDriverByName("JDEM") != nullptr) |
359 | 0 | return; |
360 | | |
361 | 22 | GDALDriver *poDriver = new GDALDriver(); |
362 | | |
363 | 22 | poDriver->SetDescription("JDEM"); |
364 | 22 | poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES"); |
365 | 22 | poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Japanese DEM (.mem)"); |
366 | 22 | poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/jdem.html"); |
367 | 22 | poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "mem"); |
368 | 22 | poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES"); |
369 | | |
370 | 22 | poDriver->pfnOpen = JDEMDataset::Open; |
371 | 22 | poDriver->pfnIdentify = JDEMDataset::Identify; |
372 | | |
373 | 22 | GetGDALDriverManager()->RegisterDriver(poDriver); |
374 | 22 | } |