Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: Horizontal Datum Formats
4 : * Purpose: Implementation of NTv2 datum shift format used in Canada, France,
5 : * Australia and elsewhere.
6 : * Author: Frank Warmerdam, warmerdam@pobox.com
7 : * Financial Support: i-cubed (http://www.i-cubed.com)
8 : *
9 : ******************************************************************************
10 : * Copyright (c) 2010, Frank Warmerdam
11 : * Copyright (c) 2010-2012, Even Rouault <even dot rouault at spatialys.com>
12 : *
13 : * SPDX-License-Identifier: MIT
14 : ****************************************************************************/
15 :
16 : // TODO(schwehr): There are a lot of magic numbers in this driver that should
17 : // be changed to constants and documented.
18 :
19 : #include "cpl_string.h"
20 : #include "gdal_frmts.h"
21 : #include "ogr_srs_api.h"
22 : #include "rawdataset.h"
23 :
24 : #include <algorithm>
25 :
26 : // Format documentation: https://github.com/Esri/ntv2-file-routines
27 : // Original archived specification:
28 : // https://web.archive.org/web/20091227232322/http://www.mgs.gov.on.ca/stdprodconsume/groups/content/@mgs/@iandit/documents/resourcelist/stel02_047447.pdf
29 :
30 : /**
31 : * The header for the file, and each grid consists of 11 16byte records.
32 : * The first half is an ASCII label, and the second half is the value
33 : * often in a little endian int or float.
34 : *
35 : * Example:
36 :
37 : 00000000 4e 55 4d 5f 4f 52 45 43 0b 00 00 00 00 00 00 00 |NUM_OREC........|
38 : 00000010 4e 55 4d 5f 53 52 45 43 0b 00 00 00 00 00 00 00 |NUM_SREC........|
39 : 00000020 4e 55 4d 5f 46 49 4c 45 01 00 00 00 00 00 00 00 |NUM_FILE........|
40 : 00000030 47 53 5f 54 59 50 45 20 53 45 43 4f 4e 44 53 20 |GS_TYPE SECONDS |
41 : 00000040 56 45 52 53 49 4f 4e 20 49 47 4e 30 37 5f 30 31 |VERSION IGN07_01|
42 : 00000050 53 59 53 54 45 4d 5f 46 4e 54 46 20 20 20 20 20 |SYSTEM_FNTF |
43 : 00000060 53 59 53 54 45 4d 5f 54 52 47 46 39 33 20 20 20 |SYSTEM_TRGF93 |
44 : 00000070 4d 41 4a 4f 52 5f 46 20 cd cc cc 4c c2 54 58 41 |MAJOR_F ...L.TXA|
45 : 00000080 4d 49 4e 4f 52 5f 46 20 00 00 00 c0 88 3f 58 41 |MINOR_F .....?XA|
46 : 00000090 4d 41 4a 4f 52 5f 54 20 00 00 00 40 a6 54 58 41 |MAJOR_T ...@.TXA|
47 : 000000a0 4d 49 4e 4f 52 5f 54 20 27 e0 1a 14 c4 3f 58 41 |MINOR_T '....?XA|
48 : 000000b0 53 55 42 5f 4e 41 4d 45 46 52 41 4e 43 45 20 20 |SUB_NAMEFRANCE |
49 : 000000c0 50 41 52 45 4e 54 20 20 4e 4f 4e 45 20 20 20 20 |PARENT NONE |
50 : 000000d0 43 52 45 41 54 45 44 20 33 31 2f 31 30 2f 30 37 |CREATED 31/10/07|
51 : 000000e0 55 50 44 41 54 45 44 20 20 20 20 20 20 20 20 20 |UPDATED |
52 : 000000f0 53 5f 4c 41 54 20 20 20 00 00 00 00 80 04 02 41 |S_LAT .......A|
53 : 00000100 4e 5f 4c 41 54 20 20 20 00 00 00 00 00 da 06 41 |N_LAT .......A|
54 : 00000110 45 5f 4c 4f 4e 47 20 20 00 00 00 00 00 94 e1 c0 |E_LONG ........|
55 : 00000120 57 5f 4c 4f 4e 47 20 20 00 00 00 00 00 56 d3 40 |W_LONG .....V.@|
56 : 00000130 4c 41 54 5f 49 4e 43 20 00 00 00 00 00 80 76 40 |LAT_INC ......v@|
57 : 00000140 4c 4f 4e 47 5f 49 4e 43 00 00 00 00 00 80 76 40 |LONG_INC......v@|
58 : 00000150 47 53 5f 43 4f 55 4e 54 a4 43 00 00 00 00 00 00 |GS_COUNT.C......|
59 : 00000160 94 f7 c1 3e 70 ee a3 3f 2a c7 84 3d ff 42 af 3d |...>p..?*..=.B.=|
60 :
61 : the actual grid data is a raster with 4 float32 bands (lat offset, long
62 : offset, lat error, long error). The offset values are in arc seconds.
63 : The grid is flipped in the x and y axis from our usual GDAL orientation.
64 : That is, the first pixel is the south east corner with scanlines going
65 : east to west, and rows from south to north. As a GDAL dataset we represent
66 : these both in the more conventional orientation.
67 : */
68 :
69 : constexpr size_t knREGULAR_RECORD_SIZE = 16;
70 : // This one is for velocity grids such as the NAD83(CRSR)v7 / NAD83v70VG.gvb
71 : // which is the only example I know actually of that format variant.
72 : constexpr size_t knMAX_RECORD_SIZE = 24;
73 :
74 : /************************************************************************/
75 : /* ==================================================================== */
76 : /* NTv2Dataset */
77 : /* ==================================================================== */
78 : /************************************************************************/
79 :
80 : class NTv2Dataset final : public RawDataset
81 : {
82 : public:
83 : RawRasterBand::ByteOrder m_eByteOrder = RawRasterBand::NATIVE_BYTE_ORDER;
84 : bool m_bMustSwap = false;
85 : VSILFILE *fpImage = nullptr; // image data file.
86 :
87 : size_t nRecordSize = 0;
88 : vsi_l_offset nGridOffset = 0;
89 :
90 : OGRSpatialReference m_oSRS{};
91 : double adfGeoTransform[6];
92 :
93 : void CaptureMetadataItem(const char *pszItem);
94 :
95 : bool OpenGrid(const char *pachGridHeader, vsi_l_offset nDataStart);
96 :
97 : CPL_DISALLOW_COPY_ASSIGN(NTv2Dataset)
98 :
99 : CPLErr Close() override;
100 :
101 : public:
102 : NTv2Dataset();
103 : ~NTv2Dataset() override;
104 :
105 : CPLErr SetGeoTransform(double *padfTransform) override;
106 : CPLErr GetGeoTransform(double *padfTransform) override;
107 :
108 6 : const OGRSpatialReference *GetSpatialRef() const override
109 : {
110 6 : return &m_oSRS;
111 : }
112 :
113 : CPLErr FlushCache(bool bAtClosing) override;
114 :
115 : static GDALDataset *Open(GDALOpenInfo *);
116 : static int Identify(GDALOpenInfo *);
117 : static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
118 : int nBandsIn, GDALDataType eType,
119 : char **papszOptions);
120 : };
121 :
122 : /************************************************************************/
123 : /* ==================================================================== */
124 : /* NTv2Dataset */
125 : /* ==================================================================== */
126 : /************************************************************************/
127 :
128 : /************************************************************************/
129 : /* NTv2Dataset() */
130 : /************************************************************************/
131 :
132 22 : NTv2Dataset::NTv2Dataset()
133 : {
134 22 : m_oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
135 22 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
136 :
137 22 : adfGeoTransform[0] = 0.0;
138 22 : adfGeoTransform[1] = 0.0; // TODO(schwehr): Should this be 1.0?
139 22 : adfGeoTransform[2] = 0.0;
140 22 : adfGeoTransform[3] = 0.0;
141 22 : adfGeoTransform[4] = 0.0;
142 22 : adfGeoTransform[5] = 0.0; // TODO(schwehr): Should this be 1.0?
143 22 : }
144 :
145 : /************************************************************************/
146 : /* ~NTv2Dataset() */
147 : /************************************************************************/
148 :
149 44 : NTv2Dataset::~NTv2Dataset()
150 :
151 : {
152 22 : NTv2Dataset::Close();
153 44 : }
154 :
155 : /************************************************************************/
156 : /* Close() */
157 : /************************************************************************/
158 :
159 44 : CPLErr NTv2Dataset::Close()
160 : {
161 44 : CPLErr eErr = CE_None;
162 44 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
163 : {
164 22 : if (NTv2Dataset::FlushCache(true) != CE_None)
165 0 : eErr = CE_Failure;
166 :
167 22 : if (fpImage)
168 : {
169 22 : if (VSIFCloseL(fpImage) != 0)
170 : {
171 0 : CPLError(CE_Failure, CPLE_FileIO, "I/O error");
172 0 : eErr = CE_Failure;
173 : }
174 : }
175 :
176 22 : if (GDALPamDataset::Close() != CE_None)
177 0 : eErr = CE_Failure;
178 : }
179 44 : return eErr;
180 : }
181 :
182 : /************************************************************************/
183 : /* SwapPtr32IfNecessary() */
184 : /************************************************************************/
185 :
186 78 : static void SwapPtr32IfNecessary(bool bMustSwap, void *ptr)
187 : {
188 78 : if (bMustSwap)
189 : {
190 35 : CPL_SWAP32PTR(static_cast<GByte *>(ptr));
191 : }
192 78 : }
193 :
194 : /************************************************************************/
195 : /* SwapPtr64IfNecessary() */
196 : /************************************************************************/
197 :
198 326 : static void SwapPtr64IfNecessary(bool bMustSwap, void *ptr)
199 : {
200 326 : if (bMustSwap)
201 : {
202 143 : CPL_SWAP64PTR(static_cast<GByte *>(ptr));
203 : }
204 326 : }
205 :
206 : /************************************************************************/
207 : /* FlushCache() */
208 : /************************************************************************/
209 :
210 23 : CPLErr NTv2Dataset::FlushCache(bool bAtClosing)
211 :
212 : {
213 : /* -------------------------------------------------------------------- */
214 : /* Nothing to do in readonly mode, or if nothing seems to have */
215 : /* changed metadata wise. */
216 : /* -------------------------------------------------------------------- */
217 23 : if (eAccess != GA_Update || !(GetPamFlags() & GPF_DIRTY))
218 : {
219 19 : return RawDataset::FlushCache(bAtClosing);
220 : }
221 :
222 : /* -------------------------------------------------------------------- */
223 : /* Load grid and file headers. */
224 : /* -------------------------------------------------------------------- */
225 4 : const int nRecords = 11;
226 4 : char achFileHeader[nRecords * knMAX_RECORD_SIZE] = {'\0'};
227 4 : char achGridHeader[nRecords * knMAX_RECORD_SIZE] = {'\0'};
228 :
229 4 : bool bOK = VSIFSeekL(fpImage, 0, SEEK_SET) == 0;
230 4 : bOK &=
231 4 : VSIFReadL(achFileHeader, nRecords, nRecordSize, fpImage) == nRecordSize;
232 :
233 4 : bOK &= VSIFSeekL(fpImage, nGridOffset, SEEK_SET) == 0;
234 4 : bOK &=
235 4 : VSIFReadL(achGridHeader, nRecords, nRecordSize, fpImage) == nRecordSize;
236 :
237 : /* -------------------------------------------------------------------- */
238 : /* Update the grid, and file headers with any available */
239 : /* metadata. If all available metadata is recognised then mark */
240 : /* things "clean" from a PAM point of view. */
241 : /* -------------------------------------------------------------------- */
242 4 : char **papszMD = GetMetadata();
243 4 : bool bSomeLeftOver = false;
244 :
245 52 : for (int i = 0; papszMD != nullptr && papszMD[i] != nullptr; i++)
246 : {
247 48 : const size_t nMinLen = 8;
248 48 : char *pszKey = nullptr;
249 48 : const char *pszValue = CPLParseNameValue(papszMD[i], &pszKey);
250 48 : if (pszKey == nullptr)
251 0 : continue;
252 :
253 48 : if (EQUAL(pszKey, "GS_TYPE"))
254 : {
255 4 : memcpy(achFileHeader + 3 * nRecordSize + 8, " ", 8);
256 4 : memcpy(achFileHeader + 3 * nRecordSize + 8, pszValue,
257 4 : std::min(nMinLen, strlen(pszValue)));
258 : }
259 44 : else if (EQUAL(pszKey, "VERSION"))
260 : {
261 4 : memcpy(achFileHeader + 4 * nRecordSize + 8, " ", 8);
262 4 : memcpy(achFileHeader + 4 * nRecordSize + 8, pszValue,
263 4 : std::min(nMinLen, strlen(pszValue)));
264 : }
265 40 : else if (EQUAL(pszKey, "SYSTEM_F"))
266 : {
267 4 : memcpy(achFileHeader + 5 * nRecordSize + 8, " ", 8);
268 4 : memcpy(achFileHeader + 5 * nRecordSize + 8, pszValue,
269 4 : std::min(nMinLen, strlen(pszValue)));
270 : }
271 36 : else if (EQUAL(pszKey, "SYSTEM_T"))
272 : {
273 4 : memcpy(achFileHeader + 6 * nRecordSize + 8, " ", 8);
274 4 : memcpy(achFileHeader + 6 * nRecordSize + 8, pszValue,
275 4 : std::min(nMinLen, strlen(pszValue)));
276 : }
277 32 : else if (EQUAL(pszKey, "MAJOR_F"))
278 : {
279 4 : double dfValue = CPLAtof(pszValue);
280 4 : SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
281 4 : memcpy(achFileHeader + 7 * nRecordSize + 8, &dfValue, 8);
282 : }
283 28 : else if (EQUAL(pszKey, "MINOR_F"))
284 : {
285 4 : double dfValue = CPLAtof(pszValue);
286 4 : SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
287 4 : memcpy(achFileHeader + 8 * nRecordSize + 8, &dfValue, 8);
288 : }
289 24 : else if (EQUAL(pszKey, "MAJOR_T"))
290 : {
291 4 : double dfValue = CPLAtof(pszValue);
292 4 : SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
293 4 : memcpy(achFileHeader + 9 * nRecordSize + 8, &dfValue, 8);
294 : }
295 20 : else if (EQUAL(pszKey, "MINOR_T"))
296 : {
297 4 : double dfValue = CPLAtof(pszValue);
298 4 : SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
299 4 : memcpy(achFileHeader + 10 * nRecordSize + 8, &dfValue, 8);
300 : }
301 16 : else if (EQUAL(pszKey, "SUB_NAME"))
302 : {
303 4 : memcpy(achGridHeader + /*0*nRecordSize+*/ 8, " ", 8);
304 4 : memcpy(achGridHeader + /*0*nRecordSize+*/ 8, pszValue,
305 4 : std::min(nMinLen, strlen(pszValue)));
306 : }
307 12 : else if (EQUAL(pszKey, "PARENT"))
308 : {
309 4 : memcpy(achGridHeader + 1 * nRecordSize + 8, " ", 8);
310 4 : memcpy(achGridHeader + 1 * nRecordSize + 8, pszValue,
311 4 : std::min(nMinLen, strlen(pszValue)));
312 : }
313 8 : else if (EQUAL(pszKey, "CREATED"))
314 : {
315 4 : memcpy(achGridHeader + 2 * nRecordSize + 8, " ", 8);
316 4 : memcpy(achGridHeader + 2 * nRecordSize + 8, pszValue,
317 4 : std::min(nMinLen, strlen(pszValue)));
318 : }
319 4 : else if (EQUAL(pszKey, "UPDATED"))
320 : {
321 4 : memcpy(achGridHeader + 3 * nRecordSize + 8, " ", 8);
322 4 : memcpy(achGridHeader + 3 * nRecordSize + 8, pszValue,
323 4 : std::min(nMinLen, strlen(pszValue)));
324 : }
325 : else
326 : {
327 0 : bSomeLeftOver = true;
328 : }
329 :
330 48 : CPLFree(pszKey);
331 : }
332 :
333 : /* -------------------------------------------------------------------- */
334 : /* Load grid and file headers. */
335 : /* -------------------------------------------------------------------- */
336 4 : bOK &= VSIFSeekL(fpImage, 0, SEEK_SET) == 0;
337 4 : bOK &= VSIFWriteL(achFileHeader, nRecords, nRecordSize, fpImage) ==
338 4 : nRecordSize;
339 :
340 4 : bOK &= VSIFSeekL(fpImage, nGridOffset, SEEK_SET) == 0;
341 4 : bOK &= VSIFWriteL(achGridHeader, nRecords, nRecordSize, fpImage) ==
342 4 : nRecordSize;
343 :
344 : /* -------------------------------------------------------------------- */
345 : /* Clear flags if we got everything, then let pam and below do */
346 : /* their flushing. */
347 : /* -------------------------------------------------------------------- */
348 4 : if (!bSomeLeftOver)
349 4 : SetPamFlags(GetPamFlags() & (~GPF_DIRTY));
350 :
351 4 : if (RawDataset::FlushCache(bAtClosing) != CE_None)
352 0 : bOK = false;
353 4 : return bOK ? CE_None : CE_Failure;
354 : }
355 :
356 : /************************************************************************/
357 : /* Identify() */
358 : /************************************************************************/
359 :
360 51483 : int NTv2Dataset::Identify(GDALOpenInfo *poOpenInfo)
361 :
362 : {
363 51483 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NTv2:"))
364 8 : return TRUE;
365 :
366 51475 : if (poOpenInfo->nHeaderBytes < 64)
367 48068 : return FALSE;
368 :
369 3407 : const char *pszHeader =
370 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
371 3407 : if (!STARTS_WITH_CI(pszHeader + 0, "NUM_OREC"))
372 3369 : return FALSE;
373 :
374 38 : if (!STARTS_WITH_CI(pszHeader + knREGULAR_RECORD_SIZE, "NUM_SREC") &&
375 0 : !STARTS_WITH_CI(pszHeader + knMAX_RECORD_SIZE, "NUM_SREC"))
376 0 : return FALSE;
377 :
378 38 : return TRUE;
379 : }
380 :
381 : /************************************************************************/
382 : /* Open() */
383 : /************************************************************************/
384 :
385 22 : GDALDataset *NTv2Dataset::Open(GDALOpenInfo *poOpenInfo)
386 :
387 : {
388 22 : if (!Identify(poOpenInfo))
389 0 : return nullptr;
390 :
391 : /* -------------------------------------------------------------------- */
392 : /* Are we targeting a particular grid? */
393 : /* -------------------------------------------------------------------- */
394 44 : CPLString osFilename;
395 22 : int iTargetGrid = -1;
396 :
397 22 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NTv2:"))
398 : {
399 4 : const char *pszRest = poOpenInfo->pszFilename + 5;
400 :
401 4 : iTargetGrid = atoi(pszRest);
402 8 : while (*pszRest != '\0' && *pszRest != ':')
403 4 : pszRest++;
404 :
405 4 : if (*pszRest == ':')
406 4 : pszRest++;
407 :
408 4 : osFilename = pszRest;
409 : }
410 : else
411 : {
412 18 : osFilename = poOpenInfo->pszFilename;
413 : }
414 :
415 : /* -------------------------------------------------------------------- */
416 : /* Create a corresponding GDALDataset. */
417 : /* -------------------------------------------------------------------- */
418 44 : auto poDS = std::make_unique<NTv2Dataset>();
419 22 : poDS->eAccess = poOpenInfo->eAccess;
420 :
421 : /* -------------------------------------------------------------------- */
422 : /* Open the file. */
423 : /* -------------------------------------------------------------------- */
424 22 : if (poOpenInfo->eAccess == GA_ReadOnly)
425 16 : poDS->fpImage = VSIFOpenL(osFilename, "rb");
426 : else
427 6 : poDS->fpImage = VSIFOpenL(osFilename, "rb+");
428 :
429 22 : if (poDS->fpImage == nullptr)
430 : {
431 0 : return nullptr;
432 : }
433 :
434 : /* -------------------------------------------------------------------- */
435 : /* Read the file header. */
436 : /* -------------------------------------------------------------------- */
437 22 : char achHeader[11 * knMAX_RECORD_SIZE] = {0};
438 :
439 44 : if (VSIFSeekL(poDS->fpImage, 0, SEEK_SET) != 0 ||
440 22 : VSIFReadL(achHeader, 1, 64, poDS->fpImage) != 64)
441 : {
442 0 : return nullptr;
443 : }
444 :
445 22 : poDS->nRecordSize =
446 22 : STARTS_WITH_CI(achHeader + knMAX_RECORD_SIZE, "NUM_SREC")
447 22 : ? knMAX_RECORD_SIZE
448 : : knREGULAR_RECORD_SIZE;
449 22 : if (VSIFReadL(achHeader + 64, 1, 11 * poDS->nRecordSize - 64,
450 44 : poDS->fpImage) != 11 * poDS->nRecordSize - 64)
451 : {
452 0 : return nullptr;
453 : }
454 :
455 13 : const bool bIsLE = achHeader[8] == 11 && achHeader[9] == 0 &&
456 35 : achHeader[10] == 0 && achHeader[11] == 0;
457 9 : const bool bIsBE = achHeader[8] == 0 && achHeader[9] == 0 &&
458 31 : achHeader[10] == 0 && achHeader[11] == 11;
459 22 : if (!bIsLE && !bIsBE)
460 : {
461 0 : return nullptr;
462 : }
463 22 : poDS->m_eByteOrder = bIsLE ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
464 : : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN;
465 22 : poDS->m_bMustSwap = poDS->m_eByteOrder != RawRasterBand::NATIVE_BYTE_ORDER;
466 :
467 22 : SwapPtr32IfNecessary(poDS->m_bMustSwap,
468 22 : achHeader + 2 * poDS->nRecordSize + 8);
469 22 : GInt32 nSubFileCount = 0;
470 22 : memcpy(&nSubFileCount, achHeader + 2 * poDS->nRecordSize + 8, 4);
471 22 : if (nSubFileCount <= 0 || nSubFileCount >= 1024)
472 : {
473 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid value for NUM_FILE : %d",
474 : nSubFileCount);
475 0 : return nullptr;
476 : }
477 :
478 22 : poDS->CaptureMetadataItem(achHeader + 3 * poDS->nRecordSize);
479 22 : poDS->CaptureMetadataItem(achHeader + 4 * poDS->nRecordSize);
480 22 : poDS->CaptureMetadataItem(achHeader + 5 * poDS->nRecordSize);
481 22 : poDS->CaptureMetadataItem(achHeader + 6 * poDS->nRecordSize);
482 :
483 22 : double dfValue = 0.0;
484 22 : memcpy(&dfValue, achHeader + 7 * poDS->nRecordSize + 8, 8);
485 22 : SwapPtr64IfNecessary(poDS->m_bMustSwap, &dfValue);
486 44 : CPLString osFValue;
487 22 : osFValue.Printf("%.15g", dfValue);
488 22 : poDS->SetMetadataItem("MAJOR_F", osFValue);
489 :
490 22 : memcpy(&dfValue, achHeader + 8 * poDS->nRecordSize + 8, 8);
491 22 : SwapPtr64IfNecessary(poDS->m_bMustSwap, &dfValue);
492 22 : osFValue.Printf("%.15g", dfValue);
493 22 : poDS->SetMetadataItem("MINOR_F", osFValue);
494 :
495 22 : memcpy(&dfValue, achHeader + 9 * poDS->nRecordSize + 8, 8);
496 22 : SwapPtr64IfNecessary(poDS->m_bMustSwap, &dfValue);
497 22 : osFValue.Printf("%.15g", dfValue);
498 22 : poDS->SetMetadataItem("MAJOR_T", osFValue);
499 :
500 22 : memcpy(&dfValue, achHeader + 10 * poDS->nRecordSize + 8, 8);
501 22 : SwapPtr64IfNecessary(poDS->m_bMustSwap, &dfValue);
502 22 : osFValue.Printf("%.15g", dfValue);
503 22 : poDS->SetMetadataItem("MINOR_T", osFValue);
504 :
505 : /* ==================================================================== */
506 : /* Loop over grids. */
507 : /* ==================================================================== */
508 22 : vsi_l_offset nGridOffset = 11 * poDS->nRecordSize;
509 :
510 50 : for (int iGrid = 0; iGrid < nSubFileCount; iGrid++)
511 : {
512 56 : if (VSIFSeekL(poDS->fpImage, nGridOffset, SEEK_SET) < 0 ||
513 28 : VSIFReadL(achHeader, 11, poDS->nRecordSize, poDS->fpImage) !=
514 28 : poDS->nRecordSize)
515 : {
516 0 : CPLError(CE_Failure, CPLE_AppDefined,
517 : "Cannot read header for subfile %d", iGrid);
518 0 : return nullptr;
519 : }
520 :
521 196 : for (int i = 4; i <= 9; i++)
522 168 : SwapPtr64IfNecessary(poDS->m_bMustSwap,
523 168 : achHeader + i * poDS->nRecordSize + 8);
524 :
525 28 : SwapPtr32IfNecessary(poDS->m_bMustSwap,
526 28 : achHeader + 10 * poDS->nRecordSize + 8);
527 :
528 28 : GUInt32 nGSCount = 0;
529 28 : memcpy(&nGSCount, achHeader + 10 * poDS->nRecordSize + 8, 4);
530 :
531 28 : CPLString osSubName;
532 28 : osSubName.assign(achHeader + 8, 8);
533 28 : osSubName.Trim();
534 :
535 : // If this is our target grid, open it as a dataset.
536 28 : if (iTargetGrid == iGrid || (iTargetGrid == -1 && iGrid == 0))
537 : {
538 22 : if (!poDS->OpenGrid(achHeader, nGridOffset))
539 : {
540 0 : return nullptr;
541 : }
542 : }
543 :
544 : // If we are opening the file as a whole, list subdatasets.
545 28 : if (iTargetGrid == -1)
546 : {
547 40 : CPLString osKey;
548 40 : CPLString osValue;
549 20 : osKey.Printf("SUBDATASET_%d_NAME", iGrid);
550 20 : osValue.Printf("NTv2:%d:%s", iGrid, osFilename.c_str());
551 20 : poDS->SetMetadataItem(osKey, osValue, "SUBDATASETS");
552 :
553 20 : osKey.Printf("SUBDATASET_%d_DESC", iGrid);
554 20 : osValue.Printf("%s", osSubName.c_str());
555 20 : poDS->SetMetadataItem(osKey, osValue, "SUBDATASETS");
556 : }
557 :
558 28 : nGridOffset +=
559 28 : (11 + static_cast<vsi_l_offset>(nGSCount)) * poDS->nRecordSize;
560 : }
561 :
562 : /* -------------------------------------------------------------------- */
563 : /* Initialize any PAM information. */
564 : /* -------------------------------------------------------------------- */
565 22 : poDS->SetDescription(poOpenInfo->pszFilename);
566 22 : poDS->TryLoadXML();
567 :
568 : /* -------------------------------------------------------------------- */
569 : /* Check for overviews. */
570 : /* -------------------------------------------------------------------- */
571 22 : poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
572 :
573 22 : return poDS.release();
574 : }
575 :
576 : /************************************************************************/
577 : /* OpenGrid() */
578 : /* */
579 : /* Note that the caller will already have byte swapped needed */
580 : /* portions of the header. */
581 : /************************************************************************/
582 :
583 22 : bool NTv2Dataset::OpenGrid(const char *pachHeader, vsi_l_offset nGridOffsetIn)
584 :
585 : {
586 22 : nGridOffset = nGridOffsetIn;
587 :
588 : /* -------------------------------------------------------------------- */
589 : /* Read the grid header. */
590 : /* -------------------------------------------------------------------- */
591 22 : CaptureMetadataItem(pachHeader + 0 * nRecordSize);
592 22 : CaptureMetadataItem(pachHeader + 1 * nRecordSize);
593 22 : CaptureMetadataItem(pachHeader + 2 * nRecordSize);
594 22 : CaptureMetadataItem(pachHeader + 3 * nRecordSize);
595 :
596 : double s_lat, n_lat, e_long, w_long, lat_inc, long_inc;
597 22 : memcpy(&s_lat, pachHeader + 4 * nRecordSize + 8, 8);
598 22 : memcpy(&n_lat, pachHeader + 5 * nRecordSize + 8, 8);
599 22 : memcpy(&e_long, pachHeader + 6 * nRecordSize + 8, 8);
600 22 : memcpy(&w_long, pachHeader + 7 * nRecordSize + 8, 8);
601 22 : memcpy(&lat_inc, pachHeader + 8 * nRecordSize + 8, 8);
602 22 : memcpy(&long_inc, pachHeader + 9 * nRecordSize + 8, 8);
603 :
604 22 : e_long *= -1;
605 22 : w_long *= -1;
606 :
607 22 : if (long_inc == 0.0 || lat_inc == 0.0)
608 0 : return false;
609 22 : const double dfXSize = floor((e_long - w_long) / long_inc + 1.5);
610 22 : const double dfYSize = floor((n_lat - s_lat) / lat_inc + 1.5);
611 22 : if (!(dfXSize >= 0 && dfXSize < INT_MAX) ||
612 22 : !(dfYSize >= 0 && dfYSize < INT_MAX))
613 0 : return false;
614 22 : nRasterXSize = static_cast<int>(dfXSize);
615 22 : nRasterYSize = static_cast<int>(dfYSize);
616 :
617 22 : const int l_nBands = nRecordSize == knREGULAR_RECORD_SIZE ? 4 : 6;
618 22 : const int nPixelSize = l_nBands * 4;
619 :
620 22 : if (!GDALCheckDatasetDimensions(nRasterXSize, nRasterYSize))
621 0 : return false;
622 22 : if (nRasterXSize > INT_MAX / nPixelSize)
623 0 : return false;
624 :
625 : /* -------------------------------------------------------------------- */
626 : /* Create band information object. */
627 : /* */
628 : /* We use unusual offsets to remap from bottom to top, to top */
629 : /* to bottom orientation, and also to remap east to west, to */
630 : /* west to east. */
631 : /* -------------------------------------------------------------------- */
632 110 : for (int iBand = 0; iBand < l_nBands; iBand++)
633 : {
634 : auto poBand = RawRasterBand::Create(
635 : this, iBand + 1, fpImage,
636 88 : nGridOffset + 4 * iBand + 11 * nRecordSize +
637 88 : static_cast<vsi_l_offset>(nRasterXSize - 1) * nPixelSize +
638 88 : static_cast<vsi_l_offset>(nRasterYSize - 1) * nPixelSize *
639 88 : nRasterXSize,
640 88 : -nPixelSize, -nPixelSize * nRasterXSize, GDT_Float32, m_eByteOrder,
641 88 : RawRasterBand::OwnFP::NO);
642 88 : if (!poBand)
643 0 : return false;
644 88 : SetBand(iBand + 1, std::move(poBand));
645 : }
646 :
647 22 : if (l_nBands == 4)
648 : {
649 22 : GetRasterBand(1)->SetDescription("Latitude Offset (arc seconds)");
650 22 : GetRasterBand(2)->SetDescription("Longitude Offset (arc seconds)");
651 22 : GetRasterBand(2)->SetMetadataItem("positive_value", "west");
652 22 : GetRasterBand(3)->SetDescription("Latitude Error");
653 22 : GetRasterBand(4)->SetDescription("Longitude Error");
654 : }
655 : else
656 : {
657 : // A bit surprising that the order is easting, northing here, contrary
658 : // to the classic NTv2 order.... Verified on NAD83v70VG.gvb
659 : // (https://webapp.geod.nrcan.gc.ca/geod/process/download-helper.php?file_id=NAD83v70VG)
660 : // against the TRX software
661 : // (https://webapp.geod.nrcan.gc.ca/geod/process/download-helper.php?file_id=trx)
662 : // https://webapp.geod.nrcan.gc.ca/geod/tools-outils/nad83-docs.php
663 : // Unfortunately I couldn't find an official documentation of the format
664 : // !
665 0 : GetRasterBand(1)->SetDescription("East velocity (mm/year)");
666 0 : GetRasterBand(2)->SetDescription("North velocity (mm/year)");
667 0 : GetRasterBand(3)->SetDescription("Up velocity (mm/year)");
668 0 : GetRasterBand(4)->SetDescription("East velocity Error (mm/year)");
669 0 : GetRasterBand(5)->SetDescription("North velocity Error (mm/year)");
670 0 : GetRasterBand(6)->SetDescription("Up velocity Error (mm/year)");
671 : }
672 :
673 : /* -------------------------------------------------------------------- */
674 : /* Setup georeferencing. */
675 : /* -------------------------------------------------------------------- */
676 22 : adfGeoTransform[0] = (w_long - long_inc * 0.5) / 3600.0;
677 22 : adfGeoTransform[1] = long_inc / 3600.0;
678 22 : adfGeoTransform[2] = 0.0;
679 22 : adfGeoTransform[3] = (n_lat + lat_inc * 0.5) / 3600.0;
680 22 : adfGeoTransform[4] = 0.0;
681 22 : adfGeoTransform[5] = (-1 * lat_inc) / 3600.0;
682 :
683 22 : return true;
684 : }
685 :
686 : /************************************************************************/
687 : /* CaptureMetadataItem() */
688 : /************************************************************************/
689 :
690 176 : void NTv2Dataset::CaptureMetadataItem(const char *pszItem)
691 :
692 : {
693 352 : CPLString osKey;
694 352 : CPLString osValue;
695 :
696 176 : osKey.assign(pszItem, 8);
697 176 : osValue.assign(pszItem + 8, 8);
698 :
699 176 : SetMetadataItem(osKey.Trim(), osValue.Trim());
700 176 : }
701 :
702 : /************************************************************************/
703 : /* GetGeoTransform() */
704 : /************************************************************************/
705 :
706 8 : CPLErr NTv2Dataset::GetGeoTransform(double *padfTransform)
707 :
708 : {
709 8 : memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
710 8 : return CE_None;
711 : }
712 :
713 : /************************************************************************/
714 : /* SetGeoTransform() */
715 : /************************************************************************/
716 :
717 4 : CPLErr NTv2Dataset::SetGeoTransform(double *padfTransform)
718 :
719 : {
720 4 : if (eAccess == GA_ReadOnly)
721 : {
722 0 : CPLError(CE_Failure, CPLE_NoWriteAccess,
723 : "Unable to update geotransform on readonly file.");
724 0 : return CE_Failure;
725 : }
726 :
727 4 : if (padfTransform[2] != 0.0 || padfTransform[4] != 0.0)
728 : {
729 0 : CPLError(CE_Failure, CPLE_AppDefined,
730 : "Rotated and sheared geotransforms not supported for NTv2.");
731 0 : return CE_Failure;
732 : }
733 :
734 4 : memcpy(adfGeoTransform, padfTransform, sizeof(double) * 6);
735 :
736 : /* -------------------------------------------------------------------- */
737 : /* Update grid header. */
738 : /* -------------------------------------------------------------------- */
739 4 : char achHeader[11 * knMAX_RECORD_SIZE] = {'\0'};
740 :
741 : // read grid header
742 4 : CPL_IGNORE_RET_VAL(VSIFSeekL(fpImage, nGridOffset, SEEK_SET));
743 4 : CPL_IGNORE_RET_VAL(VSIFReadL(achHeader, 11, nRecordSize, fpImage));
744 :
745 : // S_LAT
746 4 : double dfValue =
747 4 : 3600 * (adfGeoTransform[3] + (nRasterYSize - 0.5) * adfGeoTransform[5]);
748 4 : SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
749 4 : memcpy(achHeader + 4 * nRecordSize + 8, &dfValue, 8);
750 :
751 : // N_LAT
752 4 : dfValue = 3600 * (adfGeoTransform[3] + 0.5 * adfGeoTransform[5]);
753 4 : SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
754 4 : memcpy(achHeader + 5 * nRecordSize + 8, &dfValue, 8);
755 :
756 : // E_LONG
757 4 : dfValue = -3600 *
758 4 : (adfGeoTransform[0] + (nRasterXSize - 0.5) * adfGeoTransform[1]);
759 4 : SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
760 4 : memcpy(achHeader + 6 * nRecordSize + 8, &dfValue, 8);
761 :
762 : // W_LONG
763 4 : dfValue = -3600 * (adfGeoTransform[0] + 0.5 * adfGeoTransform[1]);
764 4 : SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
765 4 : memcpy(achHeader + 7 * nRecordSize + 8, &dfValue, 8);
766 :
767 : // LAT_INC
768 4 : dfValue = -3600 * adfGeoTransform[5];
769 4 : SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
770 4 : memcpy(achHeader + 8 * nRecordSize + 8, &dfValue, 8);
771 :
772 : // LONG_INC
773 4 : dfValue = 3600 * adfGeoTransform[1];
774 4 : SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
775 4 : memcpy(achHeader + 9 * nRecordSize + 8, &dfValue, 8);
776 :
777 : // write grid header.
778 4 : CPL_IGNORE_RET_VAL(VSIFSeekL(fpImage, nGridOffset, SEEK_SET));
779 4 : CPL_IGNORE_RET_VAL(VSIFWriteL(achHeader, 11, nRecordSize, fpImage));
780 :
781 4 : return CE_None;
782 : }
783 :
784 : /************************************************************************/
785 : /* Create() */
786 : /************************************************************************/
787 :
788 57 : GDALDataset *NTv2Dataset::Create(const char *pszFilename, int nXSize,
789 : int nYSize, int nBandsIn, GDALDataType eType,
790 : char **papszOptions)
791 : {
792 57 : if (eType != GDT_Float32)
793 : {
794 44 : CPLError(CE_Failure, CPLE_AppDefined,
795 : "Attempt to create NTv2 file with unsupported data type '%s'.",
796 : GDALGetDataTypeName(eType));
797 44 : return nullptr;
798 : }
799 13 : if (nBandsIn != 4)
800 : {
801 5 : CPLError(CE_Failure, CPLE_AppDefined,
802 : "Attempt to create NTv2 file with unsupported "
803 : "band number '%d'.",
804 : nBandsIn);
805 5 : return nullptr;
806 : }
807 :
808 : /* -------------------------------------------------------------------- */
809 : /* Are we extending an existing file? */
810 : /* -------------------------------------------------------------------- */
811 8 : const bool bAppend = CPLFetchBool(papszOptions, "APPEND_SUBDATASET", false);
812 :
813 : /* -------------------------------------------------------------------- */
814 : /* Try to open or create file. */
815 : /* -------------------------------------------------------------------- */
816 8 : VSILFILE *fp = nullptr;
817 8 : if (bAppend)
818 3 : fp = VSIFOpenL(pszFilename, "rb+");
819 : else
820 5 : fp = VSIFOpenL(pszFilename, "wb");
821 :
822 8 : if (fp == nullptr)
823 : {
824 2 : CPLError(CE_Failure, CPLE_OpenFailed,
825 : "Attempt to open/create file `%s' failed.\n", pszFilename);
826 2 : return nullptr;
827 : }
828 :
829 : /* -------------------------------------------------------------------- */
830 : /* Create a file level header if we are creating new. */
831 : /* -------------------------------------------------------------------- */
832 6 : char achHeader[11 * 16] = {'\0'};
833 6 : const char *pszValue = nullptr;
834 6 : GUInt32 nNumFile = 1;
835 6 : bool bMustSwap = false;
836 6 : bool bIsLE = false;
837 :
838 6 : if (!bAppend)
839 : {
840 4 : memset(achHeader, 0, sizeof(achHeader));
841 :
842 4 : bIsLE =
843 4 : EQUAL(CSLFetchNameValueDef(papszOptions, "ENDIANNESS", "LE"), "LE");
844 : #ifdef CPL_LSB
845 4 : bMustSwap = !bIsLE;
846 : #else
847 : bMustSwap = bIsLE;
848 : #endif
849 :
850 4 : memcpy(achHeader + 0 * 16, "NUM_OREC", 8);
851 4 : int nNumOrec = 11;
852 4 : SwapPtr32IfNecessary(bMustSwap, &nNumOrec);
853 4 : memcpy(achHeader + 0 * 16 + 8, &nNumOrec, 4);
854 :
855 4 : memcpy(achHeader + 1 * 16, "NUM_SREC", 8);
856 4 : int nNumSrec = 11;
857 4 : SwapPtr32IfNecessary(bMustSwap, &nNumSrec);
858 4 : memcpy(achHeader + 1 * 16 + 8, &nNumSrec, 4);
859 :
860 4 : memcpy(achHeader + 2 * 16, "NUM_FILE", 8);
861 4 : SwapPtr32IfNecessary(bMustSwap, &nNumFile);
862 4 : memcpy(achHeader + 2 * 16 + 8, &nNumFile, 4);
863 4 : SwapPtr32IfNecessary(bMustSwap, &nNumFile);
864 :
865 4 : const size_t nMinLen = 16;
866 4 : memcpy(achHeader + 3 * 16, "GS_TYPE ", 16);
867 4 : pszValue = CSLFetchNameValueDef(papszOptions, "GS_TYPE", "SECONDS");
868 4 : memcpy(achHeader + 3 * 16 + 8, pszValue,
869 4 : std::min(nMinLen, strlen(pszValue)));
870 :
871 4 : memcpy(achHeader + 4 * 16, "VERSION ", 16);
872 4 : pszValue = CSLFetchNameValueDef(papszOptions, "VERSION", "");
873 4 : memcpy(achHeader + 4 * 16 + 8, pszValue,
874 4 : std::min(nMinLen, strlen(pszValue)));
875 :
876 4 : memcpy(achHeader + 5 * 16, "SYSTEM_F ", 16);
877 4 : pszValue = CSLFetchNameValueDef(papszOptions, "SYSTEM_F", "");
878 4 : memcpy(achHeader + 5 * 16 + 8, pszValue,
879 4 : std::min(nMinLen, strlen(pszValue)));
880 :
881 4 : memcpy(achHeader + 6 * 16, "SYSTEM_T ", 16);
882 4 : pszValue = CSLFetchNameValueDef(papszOptions, "SYSTEM_T", "");
883 4 : memcpy(achHeader + 6 * 16 + 8, pszValue,
884 4 : std::min(nMinLen, strlen(pszValue)));
885 :
886 4 : memcpy(achHeader + 7 * 16, "MAJOR_F ", 8);
887 4 : memcpy(achHeader + 8 * 16, "MINOR_F ", 8);
888 4 : memcpy(achHeader + 9 * 16, "MAJOR_T ", 8);
889 4 : memcpy(achHeader + 10 * 16, "MINOR_T ", 8);
890 :
891 4 : CPL_IGNORE_RET_VAL(VSIFWriteL(achHeader, 1, sizeof(achHeader), fp));
892 : }
893 :
894 : /* -------------------------------------------------------------------- */
895 : /* Otherwise update the header with an increased subfile count, */
896 : /* and advanced to the last record of the file. */
897 : /* -------------------------------------------------------------------- */
898 : else
899 : {
900 2 : CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 0, SEEK_SET));
901 2 : CPL_IGNORE_RET_VAL(VSIFReadL(achHeader, 1, 16, fp));
902 :
903 3 : bIsLE = achHeader[8] == 11 && achHeader[9] == 0 && achHeader[10] == 0 &&
904 1 : achHeader[11] == 0;
905 1 : const bool bIsBE = achHeader[8] == 0 && achHeader[9] == 0 &&
906 3 : achHeader[10] == 0 && achHeader[11] == 11;
907 2 : if (!bIsLE && !bIsBE)
908 : {
909 0 : VSIFCloseL(fp);
910 0 : return nullptr;
911 : }
912 : #ifdef CPL_LSB
913 2 : bMustSwap = bIsBE;
914 : #else
915 : bMustSwap = bIsLE;
916 : #endif
917 :
918 2 : CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 2 * 16 + 8, SEEK_SET));
919 2 : CPL_IGNORE_RET_VAL(VSIFReadL(&nNumFile, 1, 4, fp));
920 2 : SwapPtr32IfNecessary(bMustSwap, &nNumFile);
921 :
922 2 : nNumFile++;
923 :
924 2 : SwapPtr32IfNecessary(bMustSwap, &nNumFile);
925 2 : CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 2 * 16 + 8, SEEK_SET));
926 2 : CPL_IGNORE_RET_VAL(VSIFWriteL(&nNumFile, 1, 4, fp));
927 2 : SwapPtr32IfNecessary(bMustSwap, &nNumFile);
928 :
929 2 : CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 0, SEEK_END));
930 2 : const vsi_l_offset nEnd = VSIFTellL(fp);
931 2 : CPL_IGNORE_RET_VAL(VSIFSeekL(fp, nEnd - 16, SEEK_SET));
932 : }
933 :
934 : /* -------------------------------------------------------------------- */
935 : /* Write the grid header. */
936 : /* -------------------------------------------------------------------- */
937 6 : memset(achHeader, 0, sizeof(achHeader));
938 :
939 6 : const size_t nMinLen = 16;
940 :
941 6 : memcpy(achHeader + 0 * 16, "SUB_NAME ", 16);
942 6 : pszValue = CSLFetchNameValueDef(papszOptions, "SUB_NAME", "");
943 6 : memcpy(achHeader + 0 * 16 + 8, pszValue,
944 6 : std::min(nMinLen, strlen(pszValue)));
945 :
946 6 : memcpy(achHeader + 1 * 16, "PARENT ", 16);
947 6 : pszValue = CSLFetchNameValueDef(papszOptions, "PARENT", "NONE");
948 6 : memcpy(achHeader + 1 * 16 + 8, pszValue,
949 6 : std::min(nMinLen, strlen(pszValue)));
950 :
951 6 : memcpy(achHeader + 2 * 16, "CREATED ", 16);
952 6 : pszValue = CSLFetchNameValueDef(papszOptions, "CREATED", "");
953 6 : memcpy(achHeader + 2 * 16 + 8, pszValue,
954 6 : std::min(nMinLen, strlen(pszValue)));
955 :
956 6 : memcpy(achHeader + 3 * 16, "UPDATED ", 16);
957 6 : pszValue = CSLFetchNameValueDef(papszOptions, "UPDATED", "");
958 6 : memcpy(achHeader + 3 * 16 + 8, pszValue,
959 6 : std::min(nMinLen, strlen(pszValue)));
960 :
961 : double dfValue;
962 :
963 6 : memcpy(achHeader + 4 * 16, "S_LAT ", 8);
964 6 : dfValue = 0;
965 6 : SwapPtr64IfNecessary(bMustSwap, &dfValue);
966 6 : memcpy(achHeader + 4 * 16 + 8, &dfValue, 8);
967 :
968 6 : memcpy(achHeader + 5 * 16, "N_LAT ", 8);
969 6 : dfValue = nYSize - 1;
970 6 : SwapPtr64IfNecessary(bMustSwap, &dfValue);
971 6 : memcpy(achHeader + 5 * 16 + 8, &dfValue, 8);
972 :
973 6 : memcpy(achHeader + 6 * 16, "E_LONG ", 8);
974 6 : dfValue = -1 * (nXSize - 1);
975 6 : SwapPtr64IfNecessary(bMustSwap, &dfValue);
976 6 : memcpy(achHeader + 6 * 16 + 8, &dfValue, 8);
977 :
978 6 : memcpy(achHeader + 7 * 16, "W_LONG ", 8);
979 6 : dfValue = 0;
980 6 : SwapPtr64IfNecessary(bMustSwap, &dfValue);
981 6 : memcpy(achHeader + 7 * 16 + 8, &dfValue, 8);
982 :
983 6 : memcpy(achHeader + 8 * 16, "LAT_INC ", 8);
984 6 : dfValue = 1;
985 6 : SwapPtr64IfNecessary(bMustSwap, &dfValue);
986 6 : memcpy(achHeader + 8 * 16 + 8, &dfValue, 8);
987 :
988 6 : memcpy(achHeader + 9 * 16, "LONG_INC", 8);
989 6 : memcpy(achHeader + 9 * 16 + 8, &dfValue, 8);
990 :
991 6 : memcpy(achHeader + 10 * 16, "GS_COUNT", 8);
992 6 : GUInt32 nGSCount = nXSize * nYSize;
993 6 : SwapPtr32IfNecessary(bMustSwap, &nGSCount);
994 6 : memcpy(achHeader + 10 * 16 + 8, &nGSCount, 4);
995 :
996 6 : CPL_IGNORE_RET_VAL(VSIFWriteL(achHeader, 1, sizeof(achHeader), fp));
997 :
998 : /* -------------------------------------------------------------------- */
999 : /* Write zeroed grid data. */
1000 : /* -------------------------------------------------------------------- */
1001 6 : memset(achHeader, 0, 16);
1002 :
1003 : // Use -1 (0x000080bf) as the default error value.
1004 6 : memset(achHeader + ((bIsLE) ? 10 : 9), 0x80, 1);
1005 6 : memset(achHeader + ((bIsLE) ? 11 : 8), 0xbf, 1);
1006 6 : memset(achHeader + ((bIsLE) ? 14 : 13), 0x80, 1);
1007 6 : memset(achHeader + ((bIsLE) ? 15 : 12), 0xbf, 1);
1008 :
1009 24 : for (int i = 0; i < nXSize * nYSize; i++)
1010 18 : CPL_IGNORE_RET_VAL(VSIFWriteL(achHeader, 1, 16, fp));
1011 :
1012 : /* -------------------------------------------------------------------- */
1013 : /* Write the end record. */
1014 : /* -------------------------------------------------------------------- */
1015 6 : memcpy(achHeader, "END ", 8);
1016 6 : memset(achHeader + 8, 0, 8);
1017 6 : CPL_IGNORE_RET_VAL(VSIFWriteL(achHeader, 1, 16, fp));
1018 6 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
1019 :
1020 6 : if (nNumFile == 1)
1021 4 : return GDALDataset::FromHandle(GDALOpen(pszFilename, GA_Update));
1022 :
1023 4 : CPLString osSubDSName;
1024 2 : osSubDSName.Printf("NTv2:%d:%s", nNumFile - 1, pszFilename);
1025 2 : return GDALDataset::FromHandle(GDALOpen(osSubDSName, GA_Update));
1026 : }
1027 :
1028 : /************************************************************************/
1029 : /* GDALRegister_NTv2() */
1030 : /************************************************************************/
1031 :
1032 1595 : void GDALRegister_NTv2()
1033 :
1034 : {
1035 1595 : if (GDALGetDriverByName("NTv2") != nullptr)
1036 302 : return;
1037 :
1038 1293 : GDALDriver *poDriver = new GDALDriver();
1039 :
1040 1293 : poDriver->SetDescription("NTv2");
1041 1293 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1042 1293 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "NTv2 Datum Grid Shift");
1043 1293 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "gsb gvb");
1044 1293 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1045 1293 : poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
1046 1293 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Float32");
1047 :
1048 1293 : poDriver->pfnOpen = NTv2Dataset::Open;
1049 1293 : poDriver->pfnIdentify = NTv2Dataset::Identify;
1050 1293 : poDriver->pfnCreate = NTv2Dataset::Create;
1051 :
1052 1293 : GetGDALDriverManager()->RegisterDriver(poDriver);
1053 : }
|