/src/gdal/frmts/nitf/nitfdataset.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: NITF Read/Write Translator |
4 | | * Purpose: NITFDataset and driver related implementations. |
5 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2002, Frank Warmerdam |
9 | | * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com> |
10 | | * |
11 | | * Portions Copyright (c) Her majesty the Queen in right of Canada as |
12 | | * represented by the Minister of National Defence, 2006, 2020 |
13 | | * |
14 | | * SPDX-License-Identifier: MIT |
15 | | ****************************************************************************/ |
16 | | |
17 | | #include "cpl_port.h" |
18 | | #include "nitfdataset.h" |
19 | | #include "nitfdrivercore.h" |
20 | | |
21 | | #include "gdal_mdreader.h" |
22 | | |
23 | | #include <cmath> |
24 | | #include <cstdio> |
25 | | #include <cstdlib> |
26 | | #include <cstring> |
27 | | #include <algorithm> |
28 | | #include <memory> |
29 | | #include <mutex> |
30 | | #include <string> |
31 | | #include <vector> |
32 | | |
33 | | #include "cpl_conv.h" |
34 | | #include "cpl_csv.h" |
35 | | #include "cpl_error.h" |
36 | | #include "cpl_minixml.h" |
37 | | #include "cpl_progress.h" |
38 | | #include "cpl_string.h" |
39 | | #include "cpl_vsi.h" |
40 | | #include "gdal.h" |
41 | | #include "gdal_frmts.h" |
42 | | #include "gdal_priv.h" |
43 | | #include "ogr_api.h" |
44 | | #include "ogr_core.h" |
45 | | #include "ogr_srs_api.h" |
46 | | |
47 | | #ifdef EMBED_RESOURCE_FILES |
48 | | #include "embedded_resources.h" |
49 | | #endif |
50 | | |
51 | | static bool NITFPatchImageLength(const char *pszFilename, int nIMIndex, |
52 | | GUIntBig nImageOffset, GIntBig nPixelCount, |
53 | | const char *pszIC, vsi_l_offset nICOffset, |
54 | | CSLConstList papszCreationOptions); |
55 | | static bool NITFWriteExtraSegments(const char *pszFilename, |
56 | | CSLConstList papszCgmMD, |
57 | | CSLConstList papszTextMD, |
58 | | CSLConstList papszOptions); |
59 | | |
60 | | #ifdef JPEG_SUPPORTED |
61 | | static bool NITFWriteJPEGImage(GDALDataset *, VSILFILE *, vsi_l_offset, |
62 | | CSLConstList, GDALProgressFunc pfnProgress, |
63 | | void *pProgressData); |
64 | | #endif |
65 | | |
66 | | static void SetBandMetadata(NITFImage *psImage, GDALRasterBand *poBand, |
67 | | int nBand, bool bReportISUBCAT); |
68 | | |
69 | | /************************************************************************/ |
70 | | /* ==================================================================== */ |
71 | | /* NITFDataset */ |
72 | | /* ==================================================================== */ |
73 | | /************************************************************************/ |
74 | | |
75 | | /************************************************************************/ |
76 | | /* NITFDataset() */ |
77 | | /************************************************************************/ |
78 | | |
79 | | NITFDataset::NITFDataset() |
80 | 675 | { |
81 | 675 | m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
82 | 675 | m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
83 | | |
84 | 675 | poDriver = GDALDriver::FromHandle(GDALGetDriverByName("NITF")); |
85 | 675 | } |
86 | | |
87 | | /************************************************************************/ |
88 | | /* ~NITFDataset() */ |
89 | | /************************************************************************/ |
90 | | |
91 | | NITFDataset::~NITFDataset() |
92 | | |
93 | 675 | { |
94 | 675 | NITFDataset::Close(); |
95 | | |
96 | | /* -------------------------------------------------------------------- */ |
97 | | /* Free datastructures. */ |
98 | | /* -------------------------------------------------------------------- */ |
99 | | |
100 | 675 | GDALDeinitGCPs(nGCPCount, pasGCPList); |
101 | 675 | CPLFree(pasGCPList); |
102 | | |
103 | 675 | CPLFree(panJPEGBlockOffset); |
104 | 675 | CPLFree(pabyJPEGBlock); |
105 | 675 | } |
106 | | |
107 | | /************************************************************************/ |
108 | | /* Close() */ |
109 | | /************************************************************************/ |
110 | | |
111 | | CPLErr NITFDataset::Close(GDALProgressFunc, void *) |
112 | 1.02k | { |
113 | 1.02k | int bHasDroppedRef = FALSE; |
114 | 1.02k | return NITFDataset::Close(bHasDroppedRef); |
115 | 1.02k | } |
116 | | |
117 | | CPLErr NITFDataset::Close(int &bHasDroppedRef) |
118 | 1.02k | { |
119 | 1.02k | CPLErr eErr = CE_None; |
120 | 1.02k | bHasDroppedRef = FALSE; |
121 | 1.02k | if (nOpenFlags != OPEN_FLAGS_CLOSED) |
122 | 675 | { |
123 | 675 | eErr = NITFDataset::FlushCache(true); |
124 | | |
125 | 675 | bHasDroppedRef = GDALPamDataset::CloseDependentDatasets(); |
126 | | |
127 | | /* -------------------------------------------------------------------- */ |
128 | | /* If we have been writing to a JPEG2000 file, check if the */ |
129 | | /* color interpretations were set. If so, apply the settings */ |
130 | | /* to the NITF file. */ |
131 | | /* -------------------------------------------------------------------- */ |
132 | 675 | if (poJ2KDataset != nullptr && bJP2Writing) |
133 | 0 | { |
134 | 0 | for (int i = 0; i < nBands && papoBands != nullptr; i++) |
135 | 0 | { |
136 | 0 | if (papoBands[i]->GetColorInterpretation() != GCI_Undefined) |
137 | 0 | NITFSetColorInterpretation( |
138 | 0 | psImage, i + 1, papoBands[i]->GetColorInterpretation()); |
139 | 0 | } |
140 | 0 | } |
141 | | |
142 | | /* -------------------------------------------------------------------- */ |
143 | | /* Close the underlying NITF file. */ |
144 | | /* -------------------------------------------------------------------- */ |
145 | 675 | if (psFile != nullptr) |
146 | 675 | { |
147 | 675 | eErr = GDAL::Combine(eErr, NITFClose(psFile)); |
148 | 675 | psFile = nullptr; |
149 | 675 | } |
150 | | |
151 | | /* -------------------------------------------------------------------- */ |
152 | | /* If we have a jpeg2000 output file, make sure it gets closed */ |
153 | | /* and flushed out. */ |
154 | | /* -------------------------------------------------------------------- */ |
155 | 675 | if (poJ2KDataset != nullptr) |
156 | 0 | { |
157 | 0 | eErr = GDAL::Combine(eErr, poJ2KDataset->Close()); |
158 | 0 | poJ2KDataset.reset(); |
159 | 0 | bHasDroppedRef = TRUE; |
160 | 0 | } |
161 | | |
162 | | /* -------------------------------------------------------------------- */ |
163 | | /* Update file length, and COMRAT for JPEG2000 files we are */ |
164 | | /* writing to. */ |
165 | | /* -------------------------------------------------------------------- */ |
166 | 675 | if (bJP2Writing) |
167 | 0 | { |
168 | 0 | const GIntBig nPixelCount = |
169 | 0 | static_cast<GIntBig>(nRasterXSize) * nRasterYSize * nBands; |
170 | |
|
171 | 0 | eErr = GDAL::Combine( |
172 | 0 | eErr, NITFPatchImageLength(GetDescription(), m_nIMIndex, |
173 | 0 | m_nImageOffset, nPixelCount, "C8", |
174 | 0 | m_nICOffset, nullptr)); |
175 | 0 | } |
176 | | |
177 | 675 | bJP2Writing = FALSE; |
178 | | |
179 | | /* -------------------------------------------------------------------- */ |
180 | | /* If we have a jpeg output file, make sure it gets closed */ |
181 | | /* and flushed out. */ |
182 | | /* -------------------------------------------------------------------- */ |
183 | 675 | if (poJPEGDataset != nullptr) |
184 | 14 | { |
185 | 14 | eErr = GDAL::Combine(eErr, poJPEGDataset->Close()); |
186 | 14 | poJPEGDataset.reset(); |
187 | 14 | bHasDroppedRef = TRUE; |
188 | 14 | } |
189 | | |
190 | | /* -------------------------------------------------------------------- */ |
191 | | /* If the dataset was opened by Create(), we may need to write */ |
192 | | /* the CGM and TEXT segments */ |
193 | | /* -------------------------------------------------------------------- */ |
194 | 675 | if (m_nIMIndex + 1 == m_nImageCount) |
195 | 0 | { |
196 | 0 | eErr = GDAL::Combine(eErr, NITFWriteExtraSegments( |
197 | 0 | GetDescription(), papszCgmMDToWrite, |
198 | 0 | papszTextMDToWrite, |
199 | 0 | aosCreationOptions.List())); |
200 | 0 | } |
201 | | |
202 | 675 | CSLDestroy(papszTextMDToWrite); |
203 | 675 | papszTextMDToWrite = nullptr; |
204 | 675 | CSLDestroy(papszCgmMDToWrite); |
205 | 675 | papszCgmMDToWrite = nullptr; |
206 | | |
207 | 675 | eErr = GDAL::Combine(eErr, GDALPamDataset::Close()); |
208 | | |
209 | | /* -------------------------------------------------------------------- */ |
210 | | /* Destroy the raster bands if they exist. */ |
211 | | /* We must do it now since the rasterbands can be NITFWrapperRasterBand */ |
212 | | /* that derive from the GDALProxyRasterBand object, which keeps */ |
213 | | /* a reference on the JPEG/JP2K dataset, so any later call to */ |
214 | | /* FlushCache() would result in FlushCache() being called on a */ |
215 | | /* already destroyed object */ |
216 | | /* -------------------------------------------------------------------- */ |
217 | 11.3k | for (int iBand = 0; iBand < nBands; iBand++) |
218 | 10.7k | { |
219 | 10.7k | delete papoBands[iBand]; |
220 | 10.7k | } |
221 | 675 | nBands = 0; |
222 | 675 | } |
223 | 1.02k | return eErr; |
224 | 1.02k | } |
225 | | |
226 | | /************************************************************************/ |
227 | | /* CloseDependentDatasets() */ |
228 | | /************************************************************************/ |
229 | | |
230 | | int NITFDataset::CloseDependentDatasets() |
231 | 0 | { |
232 | 0 | int bHasDroppedRef = FALSE; |
233 | 0 | Close(bHasDroppedRef); |
234 | 0 | return bHasDroppedRef; |
235 | 0 | } |
236 | | |
237 | | /************************************************************************/ |
238 | | /* FlushCache() */ |
239 | | /************************************************************************/ |
240 | | |
241 | | CPLErr NITFDataset::FlushCache(bool bAtClosing) |
242 | | |
243 | 683 | { |
244 | | // If the JPEG/JP2K dataset has dirty pam info, then we should consider |
245 | | // ourselves to as well. |
246 | 683 | if (poJPEGDataset != nullptr && |
247 | 14 | (poJPEGDataset->GetMOFlags() & GMO_PAM_CLASS) && |
248 | 14 | (cpl::down_cast<GDALPamDataset *>(poJPEGDataset.get())->GetPamFlags() & |
249 | 14 | GPF_DIRTY)) |
250 | 0 | MarkPamDirty(); |
251 | | |
252 | 683 | if (poJ2KDataset != nullptr && |
253 | 0 | (poJ2KDataset->GetMOFlags() & GMO_PAM_CLASS) && |
254 | 0 | (cpl::down_cast<GDALPamDataset *>(poJ2KDataset.get())->GetPamFlags() & |
255 | 0 | GPF_DIRTY)) |
256 | 0 | MarkPamDirty(); |
257 | | |
258 | 683 | CPLErr eErr = CE_None; |
259 | 683 | if (poJ2KDataset != nullptr && bJP2Writing) |
260 | 0 | eErr = poJ2KDataset->FlushCache(bAtClosing); |
261 | | |
262 | 683 | if (GDALPamDataset::FlushCache(bAtClosing) != CE_None) |
263 | 0 | eErr = CE_Failure; |
264 | 683 | return eErr; |
265 | 683 | } |
266 | | |
267 | | #ifdef ESRI_BUILD |
268 | | |
269 | | /************************************************************************/ |
270 | | /* ExtractEsriMD() */ |
271 | | /* */ |
272 | | /* Extracts ESRI-specific required meta data from metadata */ |
273 | | /* string list papszStrList. */ |
274 | | /************************************************************************/ |
275 | | |
276 | | static char **ExtractEsriMD(char **papszMD) |
277 | | { |
278 | | char **papszEsriMD = NULL; |
279 | | |
280 | | if (papszMD) |
281 | | { |
282 | | // These are the current generic ESRI metadata. |
283 | | const char *const pEsriMDAcquisitionDate = "ESRI_MD_ACQUISITION_DATE"; |
284 | | const char *const pEsriMDAngleToNorth = "ESRI_MD_ANGLE_TO_NORTH"; |
285 | | const char *const pEsriMDCircularError = "ESRI_MD_CE"; |
286 | | const char *const pEsriMDDataType = "ESRI_MD_DATA_TYPE"; |
287 | | const char *const pEsriMDIsCloudCover = "ESRI_MD_ISCLOUDCOVER"; |
288 | | const char *const pEsriMDLinearError = "ESRI_MD_LE"; |
289 | | const char *const pEsriMDOffNaDir = "ESRI_MD_OFF_NADIR"; |
290 | | const char *const pEsriMDPercentCloudCover = |
291 | | "ESRI_MD_PERCENT_CLOUD_COVER"; |
292 | | const char *const pEsriMDProductName = "ESRI_MD_PRODUCT_NAME"; |
293 | | const char *const pEsriMDSensorAzimuth = "ESRI_MD_SENSOR_AZIMUTH"; |
294 | | const char *const pEsriMDSensorElevation = "ESRI_MD_SENSOR_ELEVATION"; |
295 | | const char *const pEsriMDSensorName = "ESRI_MD_SENSOR_NAME"; |
296 | | const char *const pEsriMDSunAzimuth = "ESRI_MD_SUN_AZIMUTH"; |
297 | | const char *const pEsriMDSunElevation = "ESRI_MD_SUN_ELEVATION"; |
298 | | |
299 | | const char *pCCImageSegment = CSLFetchNameValue(papszMD, "NITF_IID1"); |
300 | | std::string ccSegment("false"); |
301 | | |
302 | | if ((pCCImageSegment != NULL) && (strlen(pCCImageSegment) <= 10)) |
303 | | { |
304 | | char szField[11] = {0}; |
305 | | strncpy(szField, pCCImageSegment, strlen(pCCImageSegment)); |
306 | | szField[strlen(pCCImageSegment)] = '\0'; |
307 | | |
308 | | // Trim white off tag. |
309 | | while ((strlen(szField) > 0) && |
310 | | (szField[strlen(szField) - 1] == ' ')) |
311 | | szField[strlen(szField) - 1] = '\0'; |
312 | | |
313 | | if ((strlen(szField) == 2) && (STARTS_WITH_CI(szField, "CC"))) |
314 | | ccSegment.assign("true"); |
315 | | } |
316 | | |
317 | | const char *pAcquisitionDate = CSLFetchNameValue(papszMD, "NITF_FDT"); |
318 | | const char *pAngleToNorth = |
319 | | CSLFetchNameValue(papszMD, "NITF_CSEXRA_ANGLE_TO_NORTH"); |
320 | | const char *pCircularError = CSLFetchNameValue( |
321 | | papszMD, "NITF_CSEXRA_CIRCL_ERR"); // Unit in feet. |
322 | | const char *pLinearError = CSLFetchNameValue( |
323 | | papszMD, "NITF_CSEXRA_LINEAR_ERR"); // Unit in feet. |
324 | | const char *pPercentCloudCover = |
325 | | CSLFetchNameValue(papszMD, "NITF_PIAIMC_CLOUDCVR"); |
326 | | const char *pProductName = |
327 | | CSLFetchNameValue(papszMD, "NITF_CSDIDA_PRODUCT_ID"); |
328 | | const char *pSensorName = |
329 | | CSLFetchNameValue(papszMD, "NITF_PIAIMC_SENSNAME"); |
330 | | const char *pSunAzimuth = |
331 | | CSLFetchNameValue(papszMD, "NITF_CSEXRA_SUN_AZIMUTH"); |
332 | | const char *pSunElevation = |
333 | | CSLFetchNameValue(papszMD, "NITF_CSEXRA_SUN_ELEVATION"); |
334 | | |
335 | | // Get ESRI_MD_DATA_TYPE. |
336 | | const char *pImgSegFieldICAT = CSLFetchNameValue(papszMD, "NITF_ICAT"); |
337 | | |
338 | | const char *pDataType = NULL; |
339 | | if ((pImgSegFieldICAT != NULL) && |
340 | | (STARTS_WITH_CI(pImgSegFieldICAT, "DTEM"))) |
341 | | pDataType = "Elevation"; |
342 | | else |
343 | | pDataType = "Generic"; |
344 | | |
345 | | if (pAngleToNorth == NULL) |
346 | | pAngleToNorth = |
347 | | CSLFetchNameValue(papszMD, "NITF_USE00A_ANGLE_TO_NORTH"); |
348 | | |
349 | | // Percent cloud cover == 999 means that the information is not |
350 | | // available. |
351 | | if ((pPercentCloudCover != NULL) && |
352 | | (STARTS_WITH_CI(pPercentCloudCover, "999"))) |
353 | | pPercentCloudCover = NULL; |
354 | | |
355 | | pAngleToNorth = |
356 | | CSLFetchNameValue(papszMD, "NITF_USE00A_ANGLE_TO_NORTH"); |
357 | | |
358 | | if (pSunAzimuth == NULL) |
359 | | pSunAzimuth = CSLFetchNameValue(papszMD, "NITF_USE00A_SUN_AZ"); |
360 | | |
361 | | if (pSunElevation == NULL) |
362 | | pSunElevation = CSLFetchNameValue(papszMD, "NITF_USE00A_SUN_EL"); |
363 | | |
364 | | // CSLAddNameValue will not add the key/value pair if the value is NULL. |
365 | | papszEsriMD = CSLAddNameValue(papszEsriMD, pEsriMDAcquisitionDate, |
366 | | pAcquisitionDate); |
367 | | papszEsriMD = |
368 | | CSLAddNameValue(papszEsriMD, pEsriMDAngleToNorth, pAngleToNorth); |
369 | | papszEsriMD = |
370 | | CSLAddNameValue(papszEsriMD, pEsriMDCircularError, pCircularError); |
371 | | papszEsriMD = CSLAddNameValue(papszEsriMD, pEsriMDDataType, pDataType); |
372 | | papszEsriMD = CSLAddNameValue(papszEsriMD, pEsriMDIsCloudCover, |
373 | | ccSegment.c_str()); |
374 | | papszEsriMD = |
375 | | CSLAddNameValue(papszEsriMD, pEsriMDLinearError, pLinearError); |
376 | | papszEsriMD = |
377 | | CSLAddNameValue(papszEsriMD, pEsriMDProductName, pProductName); |
378 | | papszEsriMD = CSLAddNameValue(papszEsriMD, pEsriMDPercentCloudCover, |
379 | | pPercentCloudCover); |
380 | | papszEsriMD = |
381 | | CSLAddNameValue(papszEsriMD, pEsriMDSensorName, pSensorName); |
382 | | papszEsriMD = |
383 | | CSLAddNameValue(papszEsriMD, pEsriMDSunAzimuth, pSunAzimuth); |
384 | | papszEsriMD = |
385 | | CSLAddNameValue(papszEsriMD, pEsriMDSunElevation, pSunElevation); |
386 | | } |
387 | | |
388 | | return papszEsriMD; |
389 | | } |
390 | | |
391 | | #endif /* def ESRI_BUILD */ |
392 | | |
393 | | /************************************************************************/ |
394 | | /* SetBandMetadata() */ |
395 | | /************************************************************************/ |
396 | | |
397 | | static void SetBandMetadata(NITFImage *psImage, GDALRasterBand *poBand, |
398 | | int nBand, bool bReportISUBCAT) |
399 | 10.7k | { |
400 | 10.7k | const NITFBandInfo *psBandInfo = psImage->pasBandInfo + nBand - 1; |
401 | | |
402 | | /* The ISUBCAT is particularly valuable for interpreting SAR bands */ |
403 | 10.7k | if (bReportISUBCAT && strlen(psBandInfo->szISUBCAT) > 0) |
404 | 6.82k | { |
405 | 6.82k | poBand->SetMetadataItem("NITF_ISUBCAT", psBandInfo->szISUBCAT); |
406 | 6.82k | } |
407 | 10.7k | } |
408 | | |
409 | | /************************************************************************/ |
410 | | /* Open() */ |
411 | | /************************************************************************/ |
412 | | |
413 | | GDALDataset *NITFDataset::Open(GDALOpenInfo *poOpenInfo) |
414 | 1.49k | { |
415 | 1.49k | return OpenInternal(poOpenInfo, nullptr, false, -1); |
416 | 1.49k | } |
417 | | |
418 | | NITFDataset *NITFDataset::OpenInternal(GDALOpenInfo *poOpenInfo, |
419 | | GDALDataset *poWritableJ2KDataset, |
420 | | bool bOpenForCreate, int nIMIndex) |
421 | | |
422 | 1.50k | { |
423 | 1.50k | if (!NITFDriverIdentify(poOpenInfo)) |
424 | 0 | return nullptr; |
425 | | |
426 | 1.50k | const char *pszFilename = poOpenInfo->pszFilename; |
427 | | |
428 | | /* -------------------------------------------------------------------- */ |
429 | | /* Select a specific subdataset. */ |
430 | | /* -------------------------------------------------------------------- */ |
431 | 1.50k | if (STARTS_WITH_CI(pszFilename, "NITF_IM:")) |
432 | 0 | { |
433 | 0 | pszFilename += 8; |
434 | 0 | nIMIndex = atoi(pszFilename); |
435 | |
|
436 | 0 | while (*pszFilename != '\0' && *pszFilename != ':') |
437 | 0 | pszFilename++; |
438 | |
|
439 | 0 | if (*pszFilename == ':') |
440 | 0 | pszFilename++; |
441 | 0 | } |
442 | | |
443 | | /* -------------------------------------------------------------------- */ |
444 | | /* Open the file with library. */ |
445 | | /* -------------------------------------------------------------------- */ |
446 | 1.50k | NITFFile *psFile = nullptr; |
447 | | |
448 | 1.50k | if (poOpenInfo->fpL) |
449 | 1.50k | { |
450 | 1.50k | VSILFILE *fpL = poOpenInfo->fpL; |
451 | 1.50k | poOpenInfo->fpL = nullptr; |
452 | 1.50k | psFile = NITFOpenEx(fpL, pszFilename); |
453 | 1.50k | } |
454 | 0 | else |
455 | 0 | psFile = NITFOpen(pszFilename, poOpenInfo->eAccess == GA_Update); |
456 | 1.50k | if (psFile == nullptr) |
457 | 281 | { |
458 | 281 | return nullptr; |
459 | 281 | } |
460 | | |
461 | 1.22k | if (!bOpenForCreate) |
462 | 1.21k | { |
463 | 1.21k | NITFCollectAttachments(psFile); |
464 | 1.21k | NITFReconcileAttachments(psFile); |
465 | 1.21k | } |
466 | | |
467 | | /* -------------------------------------------------------------------- */ |
468 | | /* Is there an image to operate on? */ |
469 | | /* -------------------------------------------------------------------- */ |
470 | 1.22k | int nThisIM = 0; |
471 | 1.22k | NITFImage *psImage = nullptr; |
472 | | |
473 | 1.22k | int iSegment = 0; // Used after for loop. |
474 | 55.2k | for (; iSegment < psFile->nSegmentCount; iSegment++) |
475 | 55.0k | { |
476 | 55.0k | if (EQUAL(psFile->pasSegmentInfo[iSegment].szSegmentType, "IM") && |
477 | 1.09k | (nThisIM++ == nIMIndex || nIMIndex == -1)) |
478 | 1.09k | { |
479 | 1.09k | psImage = NITFImageAccess(psFile, iSegment); |
480 | 1.09k | if (psImage == nullptr) |
481 | 551 | { |
482 | 551 | NITFClose(psFile); |
483 | 551 | return nullptr; |
484 | 551 | } |
485 | 546 | break; |
486 | 1.09k | } |
487 | 55.0k | } |
488 | | |
489 | | /* -------------------------------------------------------------------- */ |
490 | | /* If no image segments found report this to the user. */ |
491 | | /* -------------------------------------------------------------------- */ |
492 | 675 | if (psImage == nullptr) |
493 | 129 | { |
494 | 129 | CPLError(CE_Warning, CPLE_AppDefined, |
495 | 129 | "The file %s appears to be an NITF file, but no image " |
496 | 129 | "blocks were found on it.", |
497 | 129 | poOpenInfo->pszFilename); |
498 | 129 | } |
499 | 546 | else if (psImage->nBitsPerSample > 16 && |
500 | 52 | (EQUAL(psImage->szIC, "C3") || EQUAL(psImage->szIC, "M3"))) |
501 | 0 | { |
502 | | // Early rejection of JPEG compressed images with invalid bit depth |
503 | | // Otherwise this will cause potentially heap buffer overflows |
504 | | // as ReadJPEGBlock() assumes that the data type size is no larger |
505 | | // than 2 bytes. |
506 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
507 | 0 | "IC=%s and ABPP=%d are not supported", psImage->szIC, |
508 | 0 | psImage->nBitsPerSample); |
509 | 0 | NITFClose(psFile); |
510 | 0 | return nullptr; |
511 | 0 | } |
512 | | |
513 | | /* -------------------------------------------------------------------- */ |
514 | | /* Create a corresponding GDALDataset. */ |
515 | | /* -------------------------------------------------------------------- */ |
516 | 675 | NITFDataset *poDS = new NITFDataset(); |
517 | | |
518 | 675 | poDS->psFile = psFile; |
519 | 675 | poDS->psImage = psImage; |
520 | 675 | poDS->eAccess = poOpenInfo->eAccess; |
521 | 675 | poDS->osNITFFilename = pszFilename; |
522 | 675 | poDS->nIMIndex = nIMIndex; |
523 | | |
524 | 675 | if (psImage) |
525 | 546 | { |
526 | 546 | if (psImage->nCols <= 0 || psImage->nRows <= 0 || |
527 | 433 | psImage->nBlockWidth <= 0 || psImage->nBlockHeight <= 0) |
528 | 113 | { |
529 | 113 | CPLError(CE_Failure, CPLE_AppDefined, |
530 | 113 | "Bad values in NITF image : nCols=%d, nRows=%d, " |
531 | 113 | "nBlockWidth=%d, nBlockHeight=%d", |
532 | 113 | psImage->nCols, psImage->nRows, psImage->nBlockWidth, |
533 | 113 | psImage->nBlockHeight); |
534 | 113 | delete poDS; |
535 | 113 | return nullptr; |
536 | 113 | } |
537 | | |
538 | 433 | poDS->nRasterXSize = psImage->nCols; |
539 | 433 | poDS->nRasterYSize = psImage->nRows; |
540 | 433 | } |
541 | 129 | else |
542 | 129 | { |
543 | 129 | poDS->nRasterXSize = 1; |
544 | 129 | poDS->nRasterYSize = 1; |
545 | 129 | } |
546 | | |
547 | | /* Can be set to NO to avoid opening the underlying JPEG2000/JPEG */ |
548 | | /* stream. Might speed up operations when just metadata is needed */ |
549 | 562 | bool bOpenUnderlyingDS = |
550 | 562 | CPLTestBool(CPLGetConfigOption("NITF_OPEN_UNDERLYING_DS", "YES")); |
551 | | |
552 | | /* -------------------------------------------------------------------- */ |
553 | | /* If the image is JPEG2000 (C8) compressed, we will need to */ |
554 | | /* open the image data as a JPEG2000 dataset. */ |
555 | | /* -------------------------------------------------------------------- */ |
556 | 562 | int nUsableBands = 0; |
557 | 562 | bool bSetColorInterpretation = true; |
558 | 562 | bool bSetColorTable = false; |
559 | | |
560 | 562 | if (psImage) |
561 | 433 | nUsableBands = psImage->nBands; |
562 | | |
563 | 562 | if (bOpenUnderlyingDS && psImage != nullptr && EQUAL(psImage->szIC, "C8")) |
564 | 1 | { |
565 | 1 | CPLString osDSName; |
566 | | |
567 | 1 | osDSName.Printf("/vsisubfile/" CPL_FRMT_GUIB "_" CPL_FRMT_GUIB ",%s", |
568 | 1 | psFile->pasSegmentInfo[iSegment].nSegmentStart, |
569 | 1 | psFile->pasSegmentInfo[iSegment].nSegmentSize, |
570 | 1 | pszFilename); |
571 | | |
572 | 1 | if (poWritableJ2KDataset != nullptr) |
573 | 0 | { |
574 | 0 | poDS->poJ2KDataset.reset(poWritableJ2KDataset); |
575 | 0 | poDS->bJP2Writing = TRUE; |
576 | 0 | poWritableJ2KDataset = nullptr; |
577 | 0 | } |
578 | 1 | else |
579 | 1 | { |
580 | | // We explicitly list the allowed drivers to avoid hostile content |
581 | | // to be opened by a random driver. |
582 | 1 | static const char *const apszDrivers[] = { |
583 | 1 | "JP2KAK", "JP2ECW", "JP2MRSID", "JP2OPENJPEG", nullptr}; |
584 | 1 | poDS->poJ2KDataset.reset(GDALDataset::Open( |
585 | 1 | osDSName, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR, apszDrivers, |
586 | 1 | nullptr, nullptr)); |
587 | | |
588 | 1 | if (poDS->poJ2KDataset == nullptr) |
589 | 1 | { |
590 | 1 | bool bFoundJPEG2000Driver = false; |
591 | 5 | for (int iDriver = 0; apszDrivers[iDriver] != nullptr; |
592 | 4 | iDriver++) |
593 | 4 | { |
594 | 4 | if (GDALGetDriverByName(apszDrivers[iDriver]) != nullptr) |
595 | 0 | bFoundJPEG2000Driver = true; |
596 | 4 | } |
597 | | |
598 | 1 | CPLError( |
599 | 1 | CE_Failure, CPLE_AppDefined, |
600 | 1 | "Unable to open JPEG2000 image within NITF file.\n%s\n%s", |
601 | 1 | !bFoundJPEG2000Driver |
602 | 1 | ? "No JPEG2000 capable driver (JP2KAK, JP2ECW, " |
603 | 1 | "JP2MRSID, " |
604 | 1 | "JP2OPENJPEG, etc...) is available." |
605 | 1 | : "One or several JPEG2000 capable drivers are " |
606 | 0 | "available but " |
607 | 0 | "the datastream could not be opened successfully.", |
608 | 1 | "You can define the NITF_OPEN_UNDERLYING_DS configuration " |
609 | 1 | "option to NO, in order to just get the metadata."); |
610 | 1 | delete poDS; |
611 | 1 | return nullptr; |
612 | 1 | } |
613 | | |
614 | 0 | if (poDS->poJ2KDataset->GetMOFlags() & GMO_PAM_CLASS) |
615 | 0 | { |
616 | 0 | cpl::down_cast<GDALPamDataset *>(poDS->poJ2KDataset.get()) |
617 | 0 | ->SetPamFlags(reinterpret_cast<GDALPamDataset *>( |
618 | 0 | poDS->poJ2KDataset.get()) |
619 | 0 | ->GetPamFlags() | |
620 | 0 | GPF_NOSAVE); |
621 | 0 | } |
622 | 0 | } |
623 | | |
624 | 0 | if (poDS->GetRasterXSize() != poDS->poJ2KDataset->GetRasterXSize() || |
625 | 0 | poDS->GetRasterYSize() != poDS->poJ2KDataset->GetRasterYSize()) |
626 | 0 | { |
627 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
628 | 0 | "JPEG2000 data stream has not the same dimensions as " |
629 | 0 | "the NITF file."); |
630 | 0 | delete poDS; |
631 | 0 | return nullptr; |
632 | 0 | } |
633 | | |
634 | 0 | if (nUsableBands == 1) |
635 | 0 | { |
636 | 0 | const char *pszIREP = |
637 | 0 | CSLFetchNameValue(psImage->papszMetadata, "NITF_IREP"); |
638 | 0 | if (pszIREP != nullptr && EQUAL(pszIREP, "RGB/LUT")) |
639 | 0 | { |
640 | 0 | if (poDS->poJ2KDataset->GetRasterCount() == 3) |
641 | 0 | { |
642 | | // Test case: |
643 | | // http://www.gwg.nga.mil/ntb/baseline/software/testfile/Jpeg2000/jp2_09/file9_jp2_2places.ntf |
644 | | /* 256-entry palette/LUT in both JP2 Header and image |
645 | | * Subheader */ |
646 | | /* In this case, the JPEG2000 driver will probably do the |
647 | | * RGB expansion. */ |
648 | 0 | nUsableBands = 3; |
649 | 0 | bSetColorInterpretation = false; |
650 | 0 | } |
651 | 0 | else if (poDS->poJ2KDataset->GetRasterCount() == 1 && |
652 | 0 | psImage->pasBandInfo[0].nSignificantLUTEntries > 0) |
653 | 0 | { |
654 | | // Test case: |
655 | | // http://www.gwg.nga.mil/ntb/baseline/software/testfile/Jpeg2000/jp2_09/file9_j2c.ntf |
656 | | |
657 | | // 256-entry/LUT in Image Subheader, JP2 header completely |
658 | | // removed. The JPEG2000 driver will decode it as a grey |
659 | | // band So we must set the color table on the wrapper band |
660 | | // or for file9_jp2_2places.ntf as well if the J2K driver |
661 | | // does do RGB expansion |
662 | 0 | bSetColorTable = true; |
663 | 0 | } |
664 | 0 | } |
665 | 0 | } |
666 | |
|
667 | 0 | if (poDS->poJ2KDataset->GetRasterCount() < nUsableBands) |
668 | 0 | { |
669 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
670 | 0 | "JPEG2000 data stream has less useful bands than " |
671 | 0 | "expected, likely because some channels have " |
672 | 0 | "differing resolutions."); |
673 | |
|
674 | 0 | nUsableBands = poDS->poJ2KDataset->GetRasterCount(); |
675 | 0 | } |
676 | 0 | } |
677 | | |
678 | | /* -------------------------------------------------------------------- */ |
679 | | /* If the image is JPEG (C3) compressed, we will need to open */ |
680 | | /* the image data as a JPEG dataset. */ |
681 | | /* -------------------------------------------------------------------- */ |
682 | 561 | else if (bOpenUnderlyingDS && psImage != nullptr && |
683 | 432 | EQUAL(psImage->szIC, "C3") && psImage->nBlocksPerRow == 1 && |
684 | 24 | psImage->nBlocksPerColumn == 1) |
685 | 18 | { |
686 | 18 | GUIntBig nJPEGStart = psFile->pasSegmentInfo[iSegment].nSegmentStart; |
687 | | |
688 | 18 | bool bError = false; |
689 | 18 | poDS->nQLevel = poDS->ScanJPEGQLevel(&nJPEGStart, &bError); |
690 | | |
691 | 18 | CPLString osDSName; |
692 | | |
693 | 18 | if (psFile->pasSegmentInfo[iSegment].nSegmentSize < |
694 | 18 | nJPEGStart - psFile->pasSegmentInfo[iSegment].nSegmentStart) |
695 | 0 | { |
696 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Corrupted segment size"); |
697 | 0 | delete poDS; |
698 | 0 | return nullptr; |
699 | 0 | } |
700 | | |
701 | 18 | osDSName.Printf( |
702 | 18 | "JPEG_SUBFILE:Q%d," CPL_FRMT_GUIB "," CPL_FRMT_GUIB ",%s", |
703 | 18 | poDS->nQLevel, nJPEGStart, |
704 | 18 | psFile->pasSegmentInfo[iSegment].nSegmentSize - |
705 | 18 | (nJPEGStart - psFile->pasSegmentInfo[iSegment].nSegmentStart), |
706 | 18 | pszFilename); |
707 | | |
708 | 18 | CPLDebug("GDAL", "NITFDataset::Open() as IC=C3 (JPEG compressed)\n"); |
709 | | |
710 | 18 | poDS->poJPEGDataset.reset(GDALDataset::Open( |
711 | 18 | osDSName, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR)); |
712 | 18 | if (poDS->poJPEGDataset == nullptr) |
713 | 4 | { |
714 | 4 | const bool bFoundJPEGDriver = |
715 | 4 | GDALGetDriverByName("JPEG") != nullptr; |
716 | 4 | CPLError(CE_Failure, CPLE_AppDefined, |
717 | 4 | "Unable to open JPEG image within NITF file.\n%s\n%s", |
718 | 4 | (!bFoundJPEGDriver) |
719 | 4 | ? "The JPEG driver is not available." |
720 | 4 | : "The JPEG driver is available but the datastream " |
721 | 4 | "could not be opened successfully.", |
722 | 4 | "You can define the NITF_OPEN_UNDERLYING_DS configuration " |
723 | 4 | "option to NO, in order to just get the metadata."); |
724 | 4 | delete poDS; |
725 | 4 | return nullptr; |
726 | 4 | } |
727 | | |
728 | | /* In some circumstances, the JPEG image can be larger than the NITF */ |
729 | | /* (NCOLS, NROWS) dimensions (#5001), so accept it as a valid case */ |
730 | | /* But reject when it is smaller than the NITF dimensions. */ |
731 | 14 | if (poDS->GetRasterXSize() > poDS->poJPEGDataset->GetRasterXSize() || |
732 | 14 | poDS->GetRasterYSize() > poDS->poJPEGDataset->GetRasterYSize()) |
733 | 0 | { |
734 | 0 | CPLError( |
735 | 0 | CE_Failure, CPLE_AppDefined, |
736 | 0 | "JPEG data stream has smaller dimensions than the NITF file."); |
737 | 0 | delete poDS; |
738 | 0 | return nullptr; |
739 | 0 | } |
740 | | |
741 | 14 | if (poDS->poJPEGDataset->GetMOFlags() & GMO_PAM_CLASS) |
742 | 14 | { |
743 | 14 | (cpl::down_cast<GDALPamDataset *>(poDS->poJPEGDataset.get())) |
744 | 14 | ->SetPamFlags((reinterpret_cast<GDALPamDataset *>( |
745 | 14 | poDS->poJPEGDataset.get())) |
746 | 14 | ->GetPamFlags() | |
747 | 14 | GPF_NOSAVE); |
748 | 14 | } |
749 | | |
750 | 14 | if (poDS->poJPEGDataset->GetRasterCount() < nUsableBands) |
751 | 0 | { |
752 | 0 | CPLError( |
753 | 0 | CE_Warning, CPLE_AppDefined, |
754 | 0 | "JPEG data stream has less useful bands than expected, likely\n" |
755 | 0 | "because some channels have differing resolutions."); |
756 | |
|
757 | 0 | nUsableBands = poDS->poJPEGDataset->GetRasterCount(); |
758 | 0 | } |
759 | 14 | } |
760 | | |
761 | | /* -------------------------------------------------------------------- */ |
762 | | /* Create band information objects. */ |
763 | | /* -------------------------------------------------------------------- */ |
764 | | |
765 | | /* Keep temporary non-based dataset bands */ |
766 | 557 | bool bIsTempBandUsed = false; |
767 | 557 | GDALDataType dtFirstBand = GDT_Unknown; |
768 | 557 | GDALDataType dtSecondBand = GDT_Unknown; |
769 | 557 | std::vector<GDALRasterBand *> apoNewBands(nUsableBands); |
770 | | |
771 | 557 | GDALDataset *poBaseDS = nullptr; |
772 | 557 | if (poDS->poJ2KDataset != nullptr) |
773 | 0 | poBaseDS = poDS->poJ2KDataset.get(); |
774 | 557 | else if (poDS->poJPEGDataset != nullptr) |
775 | 14 | poBaseDS = poDS->poJPEGDataset.get(); |
776 | | |
777 | 11.2k | for (int iBand = 0; iBand < nUsableBands; iBand++) |
778 | 10.7k | { |
779 | 10.7k | if (poBaseDS != nullptr) |
780 | 14 | { |
781 | 14 | GDALRasterBand *poBaseBand = poBaseDS->GetRasterBand(iBand + 1); |
782 | | |
783 | 14 | SetBandMetadata(psImage, poBaseBand, iBand + 1, true); |
784 | | |
785 | 14 | NITFWrapperRasterBand *poBand = |
786 | 14 | new NITFWrapperRasterBand(poDS, poBaseBand, iBand + 1); |
787 | | |
788 | 14 | NITFBandInfo *psBandInfo = psImage->pasBandInfo + iBand; |
789 | 14 | if (bSetColorInterpretation) |
790 | 14 | { |
791 | | /* FIXME? Does it make sense if the JPEG/JPEG2000 driver decodes |
792 | | */ |
793 | | /* YCbCr data as RGB. We probably don't want to set */ |
794 | | /* the color interpretation as Y, Cb, Cr */ |
795 | 14 | if (EQUAL(psBandInfo->szIREPBAND, "R")) |
796 | 0 | poBand->SetColorInterpretation(GCI_RedBand); |
797 | 14 | if (EQUAL(psBandInfo->szIREPBAND, "G")) |
798 | 0 | poBand->SetColorInterpretation(GCI_GreenBand); |
799 | 14 | if (EQUAL(psBandInfo->szIREPBAND, "B")) |
800 | 0 | poBand->SetColorInterpretation(GCI_BlueBand); |
801 | 14 | if (EQUAL(psBandInfo->szIREPBAND, "M")) |
802 | 0 | poBand->SetColorInterpretation(GCI_GrayIndex); |
803 | 14 | if (EQUAL(psBandInfo->szIREPBAND, "Y")) |
804 | 0 | poBand->SetColorInterpretation(GCI_YCbCr_YBand); |
805 | 14 | if (EQUAL(psBandInfo->szIREPBAND, "Cb")) |
806 | 0 | poBand->SetColorInterpretation(GCI_YCbCr_CbBand); |
807 | 14 | if (EQUAL(psBandInfo->szIREPBAND, "Cr")) |
808 | 0 | poBand->SetColorInterpretation(GCI_YCbCr_CrBand); |
809 | 14 | } |
810 | 14 | if (bSetColorTable) |
811 | 0 | { |
812 | 0 | poBand->SetColorTableFromNITFBandInfo(); |
813 | 0 | poBand->SetColorInterpretation(GCI_PaletteIndex); |
814 | 0 | } |
815 | | |
816 | 14 | poDS->SetBand(iBand + 1, poBand); |
817 | | |
818 | 14 | if (iBand == 0) |
819 | 14 | dtFirstBand = poBand->GetRasterDataType(); |
820 | 0 | else if (iBand == 1) |
821 | 0 | dtSecondBand = poBand->GetRasterDataType(); |
822 | 14 | } |
823 | 10.7k | else |
824 | 10.7k | { |
825 | 10.7k | bIsTempBandUsed = true; |
826 | | |
827 | 10.7k | NITFRasterBand *poBand = new NITFRasterBand(poDS, iBand + 1); |
828 | 10.7k | if (poBand->GetRasterDataType() == GDT_Unknown) |
829 | 50 | { |
830 | 50 | for (auto *poOtherBand : apoNewBands) |
831 | 1.48k | delete poOtherBand; |
832 | 50 | delete poBand; |
833 | 50 | delete poDS; |
834 | 50 | return nullptr; |
835 | 50 | } |
836 | | |
837 | 10.6k | apoNewBands[iBand] = poBand; |
838 | | |
839 | 10.6k | if (iBand == 0) |
840 | 364 | dtFirstBand = poBand->GetRasterDataType(); |
841 | 10.6k | if (iBand == 1) |
842 | 108 | dtSecondBand = poBand->GetRasterDataType(); |
843 | 10.6k | } |
844 | 10.7k | } |
845 | | |
846 | | /* -------------------------------------------------------------------- */ |
847 | | /* SAR images may store complex data in 2 bands (I and Q) */ |
848 | | /* Map onto a GDAL complex raster band */ |
849 | | /* -------------------------------------------------------------------- */ |
850 | 507 | bool bIsTempBandSet = false; |
851 | 507 | if (!bOpenForCreate && psImage && |
852 | 368 | EQUAL(psImage->szICAT, "SAR") //SAR image... |
853 | 0 | && bIsTempBandUsed && |
854 | 0 | nUsableBands == psImage->nBands |
855 | | //...with 2 bands ... (modified to allow an even number - spec seems to indicate only 2 bands allowed?) |
856 | 0 | && (nUsableBands % 2) == 0 && |
857 | 0 | dtFirstBand == dtSecondBand //...that have the same datatype... |
858 | 0 | && !GDALDataTypeIsComplex(dtFirstBand) //...and are not complex... |
859 | | //..and can be mapped directly to a complex type |
860 | 0 | && (dtFirstBand == GDT_Int16 || dtFirstBand == GDT_Int32 || |
861 | 0 | dtFirstBand == GDT_Float32 || dtFirstBand == GDT_Float64) && |
862 | 0 | CPLTestBool(CPLGetConfigOption("NITF_SAR_AS_COMPLEX_TYPE", "YES"))) |
863 | 0 | { |
864 | 0 | bool allBandsIQ = true; |
865 | 0 | for (int i = 0; i < nUsableBands; i += 2) |
866 | 0 | { |
867 | 0 | const NITFBandInfo *psBandInfo1 = psImage->pasBandInfo + i; |
868 | 0 | const NITFBandInfo *psBandInfo2 = psImage->pasBandInfo + i + 1; |
869 | | |
870 | | //check that the ISUBCAT is labelled "I" and "Q" on the 2 bands |
871 | 0 | if (!EQUAL(psBandInfo1->szISUBCAT, "I") || |
872 | 0 | !EQUAL(psBandInfo2->szISUBCAT, "Q")) |
873 | 0 | { |
874 | 0 | allBandsIQ = false; |
875 | 0 | break; |
876 | 0 | } |
877 | 0 | } |
878 | |
|
879 | 0 | if (allBandsIQ) |
880 | 0 | { |
881 | 0 | poDS->m_bHasComplexRasterBand = true; |
882 | 0 | for (int i = 0; i < (nUsableBands / 2); i++) |
883 | 0 | { |
884 | | //wrap the I and Q bands into a single complex band |
885 | 0 | const int iBandIndex = 2 * i; |
886 | 0 | const int qBandIndex = 2 * i + 1; |
887 | 0 | NITFComplexRasterBand *poBand = new NITFComplexRasterBand( |
888 | 0 | poDS, apoNewBands[iBandIndex], apoNewBands[qBandIndex], |
889 | 0 | iBandIndex + 1, qBandIndex + 1); |
890 | 0 | SetBandMetadata(psImage, poBand, i + 1, false); |
891 | 0 | poDS->SetBand(i + 1, poBand); |
892 | 0 | bIsTempBandSet = true; |
893 | 0 | } |
894 | 0 | } |
895 | 0 | } |
896 | | |
897 | 507 | if (bIsTempBandUsed && !bIsTempBandSet) |
898 | 364 | { |
899 | | // Reset properly bands that are not complex |
900 | 11.0k | for (int iBand = 0; iBand < nUsableBands; iBand++) |
901 | 10.6k | { |
902 | 10.6k | GDALRasterBand *poBand = apoNewBands[iBand]; |
903 | 10.6k | SetBandMetadata(psImage, poBand, iBand + 1, true); |
904 | 10.6k | poDS->SetBand(iBand + 1, poBand); |
905 | 10.6k | } |
906 | 364 | } |
907 | | |
908 | | /* -------------------------------------------------------------------- */ |
909 | | /* Report problems with odd bit sizes. */ |
910 | | /* -------------------------------------------------------------------- */ |
911 | 507 | if (poOpenInfo->eAccess == GA_Update && psImage != nullptr && |
912 | 10 | (psImage->nBitsPerSample % 8 != 0) && poDS->poJPEGDataset == nullptr && |
913 | 0 | poDS->poJ2KDataset == nullptr) |
914 | 0 | { |
915 | 0 | CPLError( |
916 | 0 | CE_Warning, CPLE_AppDefined, |
917 | 0 | "Image with %d bits per sample cannot be opened in update mode.", |
918 | 0 | psImage->nBitsPerSample); |
919 | 0 | delete poDS; |
920 | 0 | return nullptr; |
921 | 0 | } |
922 | | |
923 | | /* -------------------------------------------------------------------- */ |
924 | | /* Process the projection from the ICORDS. */ |
925 | | /* -------------------------------------------------------------------- */ |
926 | 507 | if (psImage == nullptr) |
927 | 129 | { |
928 | | /* nothing */ |
929 | 129 | } |
930 | 378 | else if (psImage->chICORDS == 'G' || psImage->chICORDS == 'D') |
931 | 32 | { |
932 | 32 | poDS->m_oSRS.SetWellKnownGeogCS("WGS84"); |
933 | 32 | } |
934 | 346 | else if (psImage->chICORDS == 'C') |
935 | 12 | { |
936 | 12 | poDS->m_oSRS.SetWellKnownGeogCS("WGS84"); |
937 | | |
938 | | /* convert latitudes from geocentric to geodetic form. */ |
939 | | |
940 | 12 | psImage->dfULY = |
941 | 12 | NITF_WGS84_Geocentric_Latitude_To_Geodetic_Latitude(psImage->dfULY); |
942 | 12 | psImage->dfLLY = |
943 | 12 | NITF_WGS84_Geocentric_Latitude_To_Geodetic_Latitude(psImage->dfLLY); |
944 | 12 | psImage->dfURY = |
945 | 12 | NITF_WGS84_Geocentric_Latitude_To_Geodetic_Latitude(psImage->dfURY); |
946 | 12 | psImage->dfLRY = |
947 | 12 | NITF_WGS84_Geocentric_Latitude_To_Geodetic_Latitude(psImage->dfLRY); |
948 | 12 | } |
949 | 334 | else if (psImage->chICORDS == 'S' || psImage->chICORDS == 'N') |
950 | 17 | { |
951 | | // in open-for-create mode, we don't have a valid UTM zone, which |
952 | | // would make PROJ unhappy |
953 | 17 | if (!bOpenForCreate) |
954 | 12 | { |
955 | 12 | poDS->m_oSRS.SetUTM(psImage->nZone, psImage->chICORDS == 'N'); |
956 | 12 | poDS->m_oSRS.SetWellKnownGeogCS("WGS84"); |
957 | 12 | } |
958 | 17 | } |
959 | 317 | else if (psImage->chICORDS == 'U' && psImage->nZone != 0) |
960 | 0 | { |
961 | 0 | poDS->m_oSRS.SetUTM(std::abs(psImage->nZone), psImage->nZone > 0); |
962 | 0 | poDS->m_oSRS.SetWellKnownGeogCS("WGS84"); |
963 | 0 | } |
964 | | |
965 | | /* -------------------------------------------------------------------- */ |
966 | | /* Try looking for a .nfw file. */ |
967 | | /* -------------------------------------------------------------------- */ |
968 | 507 | if (psImage && GDALReadWorldFile2(pszFilename, "nfw", poDS->m_gt.data(), |
969 | 378 | poOpenInfo->GetSiblingFiles(), nullptr)) |
970 | 0 | { |
971 | 0 | int isNorth; |
972 | 0 | int zone; |
973 | |
|
974 | 0 | poDS->bGotGeoTransform = TRUE; |
975 | | |
976 | | /* If nfw found, try looking for a header with projection info */ |
977 | | /* in space imaging style format */ |
978 | 0 | std::string osHDR = CPLResetExtensionSafe(pszFilename, "hdr"); |
979 | |
|
980 | 0 | VSILFILE *fpHDR = VSIFOpenL(osHDR.c_str(), "rt"); |
981 | |
|
982 | 0 | if (fpHDR == nullptr && VSIIsCaseSensitiveFS(osHDR.c_str())) |
983 | 0 | { |
984 | 0 | osHDR = CPLResetExtensionSafe(pszFilename, "HDR"); |
985 | 0 | fpHDR = VSIFOpenL(osHDR.c_str(), "rt"); |
986 | 0 | } |
987 | |
|
988 | 0 | if (fpHDR != nullptr) |
989 | 0 | { |
990 | 0 | CPL_IGNORE_RET_VAL(VSIFCloseL(fpHDR)); |
991 | 0 | char **papszLines = CSLLoad2(osHDR.c_str(), 16, 200, nullptr); |
992 | 0 | if (CSLCount(papszLines) == 16) |
993 | 0 | { |
994 | |
|
995 | 0 | if (psImage->chICORDS == 'N') |
996 | 0 | isNorth = 1; |
997 | 0 | else if (psImage->chICORDS == 'S') |
998 | 0 | isNorth = 0; |
999 | 0 | else if (psImage->chICORDS == 'G' || psImage->chICORDS == 'D' || |
1000 | 0 | psImage->chICORDS == 'C') |
1001 | 0 | { |
1002 | 0 | if (psImage->dfLLY + psImage->dfLRY + psImage->dfULY + |
1003 | 0 | psImage->dfURY < |
1004 | 0 | 0) |
1005 | 0 | isNorth = 0; |
1006 | 0 | else |
1007 | 0 | isNorth = 1; |
1008 | 0 | } |
1009 | 0 | else if (psImage->chICORDS == 'U') |
1010 | 0 | { |
1011 | 0 | isNorth = psImage->nZone >= 0; |
1012 | 0 | } |
1013 | 0 | else |
1014 | 0 | { |
1015 | | // Arbitrarily suppose we are in northern hemisphere. |
1016 | 0 | isNorth = 1; |
1017 | | |
1018 | | /* unless we have other information to determine the |
1019 | | * hemisphere */ |
1020 | 0 | char **papszUSE00A_MD = NITFReadSTDIDC(psImage); |
1021 | 0 | if (papszUSE00A_MD != nullptr) |
1022 | 0 | { |
1023 | 0 | const char *pszLocation = CSLFetchNameValue( |
1024 | 0 | papszUSE00A_MD, "NITF_STDIDC_LOCATION"); |
1025 | 0 | if (pszLocation && strlen(pszLocation) == 11) |
1026 | 0 | { |
1027 | 0 | isNorth = (pszLocation[4] == 'N'); |
1028 | 0 | } |
1029 | 0 | CSLDestroy(papszUSE00A_MD); |
1030 | 0 | } |
1031 | 0 | else |
1032 | 0 | { |
1033 | 0 | NITFRPC00BInfo sRPCInfo; |
1034 | 0 | if (NITFReadRPC00B(psImage, &sRPCInfo) && |
1035 | 0 | sRPCInfo.SUCCESS) |
1036 | 0 | { |
1037 | 0 | isNorth = (sRPCInfo.LAT_OFF >= 0); |
1038 | 0 | } |
1039 | 0 | } |
1040 | 0 | } |
1041 | |
|
1042 | 0 | if ((STARTS_WITH_CI(papszLines[7], |
1043 | 0 | "Selected Projection: Universal Transverse " |
1044 | 0 | "Mercator")) && |
1045 | 0 | (STARTS_WITH_CI(papszLines[8], "Zone: ")) && |
1046 | 0 | (strlen(papszLines[8]) >= 7)) |
1047 | 0 | { |
1048 | 0 | zone = atoi(&(papszLines[8][6])); |
1049 | 0 | poDS->m_oSRS.Clear(); |
1050 | 0 | poDS->m_oSRS.SetUTM(zone, isNorth); |
1051 | 0 | poDS->m_oSRS.SetWellKnownGeogCS("WGS84"); |
1052 | 0 | } |
1053 | 0 | else |
1054 | 0 | { |
1055 | | /* Couldn't find associated projection info. |
1056 | | Go back to original file for geotransform. |
1057 | | */ |
1058 | 0 | poDS->bGotGeoTransform = FALSE; |
1059 | 0 | } |
1060 | 0 | } |
1061 | 0 | else |
1062 | 0 | poDS->bGotGeoTransform = FALSE; |
1063 | 0 | CSLDestroy(papszLines); |
1064 | 0 | } |
1065 | 0 | else |
1066 | 0 | poDS->bGotGeoTransform = FALSE; |
1067 | 0 | } |
1068 | | |
1069 | | /* -------------------------------------------------------------------- */ |
1070 | | /* Does this look like a CADRG polar tile ? (#2940) */ |
1071 | | /* -------------------------------------------------------------------- */ |
1072 | 507 | const char *pszIID1 = |
1073 | 507 | (psImage) ? CSLFetchNameValue(psImage->papszMetadata, "NITF_IID1") |
1074 | 507 | : nullptr; |
1075 | 507 | const char *pszITITLE = |
1076 | 507 | (psImage) ? CSLFetchNameValue(psImage->papszMetadata, "NITF_ITITLE") |
1077 | 507 | : nullptr; |
1078 | 507 | if (psImage != nullptr && !poDS->bGotGeoTransform && |
1079 | 378 | (psImage->chICORDS == 'G' || psImage->chICORDS == 'D') && |
1080 | 32 | pszIID1 != nullptr && EQUAL(pszIID1, "CADRG") && pszITITLE != nullptr && |
1081 | 16 | strlen(pszITITLE) >= 12 && |
1082 | 16 | (pszITITLE[strlen(pszITITLE) - 1] == '9' || |
1083 | 15 | pszITITLE[strlen(pszITITLE) - 1] == 'J')) |
1084 | 1 | { |
1085 | | /* To get a perfect rectangle in Azimuthal Equidistant projection, we |
1086 | | * must use */ |
1087 | | /* the sphere and not WGS84 ellipsoid. That's a bit strange... */ |
1088 | 1 | const char *pszNorthPolarProjection = |
1089 | 1 | "PROJCS[\"ARC_System_Zone_09\",GEOGCS[\"GCS_Sphere\"," |
1090 | 1 | "DATUM[\"D_Sphere\",SPHEROID[\"Sphere\",6378137.0,0.0]]," |
1091 | 1 | "PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]]," |
1092 | 1 | "PROJECTION[\"Azimuthal_Equidistant\"]," |
1093 | 1 | "PARAMETER[\"latitude_of_center\",90]," |
1094 | 1 | "PARAMETER[\"longitude_of_center\",0]," |
1095 | 1 | "PARAMETER[\"false_easting\",0]," |
1096 | 1 | "PARAMETER[\"false_northing\",0]," |
1097 | 1 | "UNIT[\"metre\",1]]"; |
1098 | | |
1099 | 1 | const char *pszSouthPolarProjection = |
1100 | 1 | "PROJCS[\"ARC_System_Zone_18\",GEOGCS[\"GCS_Sphere\"," |
1101 | 1 | "DATUM[\"D_Sphere\",SPHEROID[\"Sphere\",6378137.0,0.0]]," |
1102 | 1 | "PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]]," |
1103 | 1 | "PROJECTION[\"Azimuthal_Equidistant\"]," |
1104 | 1 | "PARAMETER[\"latitude_of_center\",-90]," |
1105 | 1 | "PARAMETER[\"longitude_of_center\",0]," |
1106 | 1 | "PARAMETER[\"false_easting\",0]," |
1107 | 1 | "PARAMETER[\"false_northing\",0]," |
1108 | 1 | "UNIT[\"metre\",1]]"; |
1109 | | |
1110 | 1 | OGRSpatialReference oSRS_AEQD, oSRS_WGS84; |
1111 | | |
1112 | 1 | const char *pszPolarProjection = (psImage->dfULY > 0) |
1113 | 1 | ? pszNorthPolarProjection |
1114 | 1 | : pszSouthPolarProjection; |
1115 | | |
1116 | 1 | oSRS_AEQD.importFromWkt(pszPolarProjection); |
1117 | | |
1118 | 1 | oSRS_WGS84.SetWellKnownGeogCS("WGS84"); |
1119 | 1 | oSRS_WGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
1120 | | |
1121 | 1 | CPLPushErrorHandler(CPLQuietErrorHandler); |
1122 | 1 | auto poCT = std::unique_ptr<OGRCoordinateTransformation>( |
1123 | 1 | OGRCreateCoordinateTransformation(&oSRS_WGS84, &oSRS_AEQD)); |
1124 | 1 | CPLPopErrorHandler(); |
1125 | 1 | if (poCT) |
1126 | 1 | { |
1127 | 1 | double dfULX_AEQD = psImage->dfULX; |
1128 | 1 | double dfULY_AEQD = psImage->dfULY; |
1129 | 1 | double dfURX_AEQD = psImage->dfURX; |
1130 | 1 | double dfURY_AEQD = psImage->dfURY; |
1131 | 1 | double dfLLX_AEQD = psImage->dfLLX; |
1132 | 1 | double dfLLY_AEQD = psImage->dfLLY; |
1133 | 1 | double dfLRX_AEQD = psImage->dfLRX; |
1134 | 1 | double dfLRY_AEQD = psImage->dfLRY; |
1135 | 1 | double z = 0; |
1136 | 1 | int bSuccess = TRUE; |
1137 | 1 | bSuccess &= poCT->Transform(1, &dfULX_AEQD, &dfULY_AEQD, &z); |
1138 | 1 | bSuccess &= poCT->Transform(1, &dfURX_AEQD, &dfURY_AEQD, &z); |
1139 | 1 | bSuccess &= poCT->Transform(1, &dfLLX_AEQD, &dfLLY_AEQD, &z); |
1140 | 1 | bSuccess &= poCT->Transform(1, &dfLRX_AEQD, &dfLRY_AEQD, &z); |
1141 | 1 | if (bSuccess) |
1142 | 1 | { |
1143 | | /* Check that the coordinates of the 4 corners in Azimuthal |
1144 | | * Equidistant projection */ |
1145 | | /* are a rectangle */ |
1146 | 1 | if (fabs(dfULX_AEQD - dfLLX_AEQD) < 1e-6 * fabs(dfLLX_AEQD) && |
1147 | 0 | fabs(dfURX_AEQD - dfLRX_AEQD) < 1e-6 * fabs(dfLRX_AEQD) && |
1148 | 0 | fabs(dfULY_AEQD - dfURY_AEQD) < 1e-6 * fabs(dfURY_AEQD) && |
1149 | 0 | fabs(dfLLY_AEQD - dfLRY_AEQD) < 1e-6 * fabs(dfLRY_AEQD)) |
1150 | 0 | { |
1151 | 0 | poDS->m_oSRS = std::move(oSRS_AEQD); |
1152 | |
|
1153 | 0 | poDS->bGotGeoTransform = TRUE; |
1154 | 0 | poDS->m_gt.xorig = dfULX_AEQD; |
1155 | 0 | poDS->m_gt.xscale = |
1156 | 0 | (dfURX_AEQD - dfULX_AEQD) / poDS->nRasterXSize; |
1157 | 0 | poDS->m_gt.xrot = 0; |
1158 | 0 | poDS->m_gt.yorig = dfULY_AEQD; |
1159 | 0 | poDS->m_gt.yrot = 0; |
1160 | 0 | poDS->m_gt.yscale = |
1161 | 0 | (dfLLY_AEQD - dfULY_AEQD) / poDS->nRasterYSize; |
1162 | 0 | } |
1163 | 1 | } |
1164 | 1 | } |
1165 | 0 | else |
1166 | 0 | { |
1167 | | // if we cannot instantiate the transformer, then we |
1168 | | // will at least attempt to record what we believe the |
1169 | | // natural coordinate system of the image is. This is |
1170 | | // primarily used by ArcGIS (#3337) |
1171 | |
|
1172 | 0 | CPLErrorReset(); |
1173 | |
|
1174 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
1175 | 0 | "Failed to instantiate coordinate system transformer, " |
1176 | 0 | "likely PROJ.DLL/libproj.so is not available. Returning " |
1177 | 0 | "image corners as lat/long GCPs as a fallback."); |
1178 | |
|
1179 | 0 | char *pszAEQD = nullptr; |
1180 | 0 | oSRS_AEQD.exportToWkt(&(pszAEQD)); |
1181 | 0 | poDS->SetMetadataItem("GCPPROJECTIONX", pszAEQD, "IMAGE_STRUCTURE"); |
1182 | 0 | CPLFree(pszAEQD); |
1183 | 0 | } |
1184 | 1 | } |
1185 | | |
1186 | | /* -------------------------------------------------------------------- */ |
1187 | | /* Do we have RPCs? */ |
1188 | | /* -------------------------------------------------------------------- */ |
1189 | 507 | bool bHasRPC00 = false; |
1190 | 507 | NITFRPC00BInfo sRPCInfo; |
1191 | 507 | memset(&sRPCInfo, 0, |
1192 | 507 | sizeof(sRPCInfo)); /* To avoid warnings from not clever compilers */ |
1193 | | |
1194 | 507 | if (psImage && NITFReadRPC00B(psImage, &sRPCInfo) && sRPCInfo.SUCCESS) |
1195 | 0 | bHasRPC00 = true; |
1196 | | |
1197 | | /* -------------------------------------------------------------------- */ |
1198 | | /* Do we have IGEOLO data that can be treated as a */ |
1199 | | /* geotransform? Our approach should support images in an */ |
1200 | | /* affine rotated frame of reference. */ |
1201 | | /* -------------------------------------------------------------------- */ |
1202 | 507 | int nGCPCount = 0; |
1203 | 507 | GDAL_GCP *psGCPs = nullptr; |
1204 | | |
1205 | 507 | if (psImage && !poDS->bGotGeoTransform && psImage->chICORDS != ' ') |
1206 | 204 | { |
1207 | 204 | nGCPCount = 4; |
1208 | | |
1209 | 204 | psGCPs = reinterpret_cast<GDAL_GCP *>( |
1210 | 204 | CPLMalloc(sizeof(GDAL_GCP) * nGCPCount)); |
1211 | 204 | GDALInitGCPs(nGCPCount, psGCPs); |
1212 | | |
1213 | 204 | if (psImage->bIsBoxCenterOfPixel) |
1214 | 189 | { |
1215 | 189 | psGCPs[0].dfGCPPixel = 0.5; |
1216 | 189 | psGCPs[0].dfGCPLine = 0.5; |
1217 | 189 | psGCPs[1].dfGCPPixel = poDS->nRasterXSize - 0.5; |
1218 | 189 | psGCPs[1].dfGCPLine = 0.5; |
1219 | 189 | psGCPs[2].dfGCPPixel = poDS->nRasterXSize - 0.5; |
1220 | 189 | psGCPs[2].dfGCPLine = poDS->nRasterYSize - 0.5; |
1221 | 189 | psGCPs[3].dfGCPPixel = 0.5; |
1222 | 189 | psGCPs[3].dfGCPLine = poDS->nRasterYSize - 0.5; |
1223 | 189 | } |
1224 | 15 | else |
1225 | 15 | { |
1226 | 15 | psGCPs[0].dfGCPPixel = 0.0; |
1227 | 15 | psGCPs[0].dfGCPLine = 0.0; |
1228 | 15 | psGCPs[1].dfGCPPixel = poDS->nRasterXSize; |
1229 | 15 | psGCPs[1].dfGCPLine = 0.0; |
1230 | 15 | psGCPs[2].dfGCPPixel = poDS->nRasterXSize; |
1231 | 15 | psGCPs[2].dfGCPLine = poDS->nRasterYSize; |
1232 | 15 | psGCPs[3].dfGCPPixel = 0.0; |
1233 | 15 | psGCPs[3].dfGCPLine = poDS->nRasterYSize; |
1234 | 15 | } |
1235 | | |
1236 | 204 | psGCPs[0].dfGCPX = psImage->dfULX; |
1237 | 204 | psGCPs[0].dfGCPY = psImage->dfULY; |
1238 | | |
1239 | 204 | psGCPs[1].dfGCPX = psImage->dfURX; |
1240 | 204 | psGCPs[1].dfGCPY = psImage->dfURY; |
1241 | | |
1242 | 204 | psGCPs[2].dfGCPX = psImage->dfLRX; |
1243 | 204 | psGCPs[2].dfGCPY = psImage->dfLRY; |
1244 | | |
1245 | 204 | psGCPs[3].dfGCPX = psImage->dfLLX; |
1246 | 204 | psGCPs[3].dfGCPY = psImage->dfLLY; |
1247 | | |
1248 | | /* -------------------------------------------------------------------- */ |
1249 | | /* ESRI desires to use the RPCs to produce a denser and more */ |
1250 | | /* accurate set of GCPs in this case. Details are unclear at */ |
1251 | | /* this time. */ |
1252 | | /* -------------------------------------------------------------------- */ |
1253 | | #ifdef ESRI_BUILD |
1254 | | if (bHasRPC00 && |
1255 | | ((psImage->chICORDS == 'G') || (psImage->chICORDS == 'C'))) |
1256 | | { |
1257 | | if (nGCPCount == 4) |
1258 | | NITFDensifyGCPs(&psGCPs, &nGCPCount); |
1259 | | |
1260 | | NITFUpdateGCPsWithRPC(&sRPCInfo, psGCPs, &nGCPCount); |
1261 | | } |
1262 | | #endif /* def ESRI_BUILD */ |
1263 | 204 | } |
1264 | | |
1265 | | /* -------------------------------------------------------------------- */ |
1266 | | /* Convert the GCPs into a geotransform definition, if possible. */ |
1267 | | /* -------------------------------------------------------------------- */ |
1268 | 507 | if (!psImage) |
1269 | 129 | { |
1270 | | /* nothing */ |
1271 | 129 | } |
1272 | 378 | else if (poDS->bGotGeoTransform == FALSE && nGCPCount > 0 && |
1273 | 204 | GDALGCPsToGeoTransform(nGCPCount, psGCPs, poDS->m_gt.data(), |
1274 | 204 | FALSE)) |
1275 | 24 | { |
1276 | 24 | poDS->bGotGeoTransform = TRUE; |
1277 | 24 | } |
1278 | | |
1279 | | /* -------------------------------------------------------------------- */ |
1280 | | /* If we have IGEOLO that isn't north up, return it as GCPs. */ |
1281 | | /* -------------------------------------------------------------------- */ |
1282 | 354 | else if ((psImage->dfULX != 0 || psImage->dfURX != 0 || |
1283 | 337 | psImage->dfLRX != 0 || psImage->dfLLX != 0) && |
1284 | 28 | psImage->chICORDS != ' ' && (poDS->bGotGeoTransform == FALSE) && |
1285 | 28 | nGCPCount >= 4) |
1286 | 28 | { |
1287 | 28 | CPLDebug("GDAL", |
1288 | 28 | "NITFDataset::Open() was not able to derive a first order\n" |
1289 | 28 | "geotransform. It will be returned as GCPs."); |
1290 | | |
1291 | 28 | poDS->nGCPCount = nGCPCount; |
1292 | 28 | poDS->pasGCPList = psGCPs; |
1293 | | |
1294 | 28 | psGCPs = nullptr; |
1295 | 28 | nGCPCount = 0; |
1296 | | |
1297 | 28 | CPLFree(poDS->pasGCPList[0].pszId); |
1298 | 28 | poDS->pasGCPList[0].pszId = CPLStrdup("UpperLeft"); |
1299 | | |
1300 | 28 | CPLFree(poDS->pasGCPList[1].pszId); |
1301 | 28 | poDS->pasGCPList[1].pszId = CPLStrdup("UpperRight"); |
1302 | | |
1303 | 28 | CPLFree(poDS->pasGCPList[2].pszId); |
1304 | 28 | poDS->pasGCPList[2].pszId = CPLStrdup("LowerRight"); |
1305 | | |
1306 | 28 | CPLFree(poDS->pasGCPList[3].pszId); |
1307 | 28 | poDS->pasGCPList[3].pszId = CPLStrdup("LowerLeft"); |
1308 | | |
1309 | 28 | poDS->m_oGCPSRS = poDS->m_oSRS; |
1310 | 28 | } |
1311 | | |
1312 | | // This cleans up the original copy of the GCPs used to test if |
1313 | | // this IGEOLO could be used for a geotransform if we did not |
1314 | | // steal the to use as primary gcps. |
1315 | 507 | if (nGCPCount > 0) |
1316 | 176 | { |
1317 | 176 | GDALDeinitGCPs(nGCPCount, psGCPs); |
1318 | 176 | CPLFree(psGCPs); |
1319 | 176 | } |
1320 | | |
1321 | | /* -------------------------------------------------------------------- */ |
1322 | | /* Do we have PRJPSB and MAPLOB TREs to get better */ |
1323 | | /* georeferencing from? */ |
1324 | | /* -------------------------------------------------------------------- */ |
1325 | 507 | if (psImage) |
1326 | 378 | poDS->CheckGeoSDEInfo(); |
1327 | | |
1328 | | /* -------------------------------------------------------------------- */ |
1329 | | /* Do we have metadata. */ |
1330 | | /* -------------------------------------------------------------------- */ |
1331 | | |
1332 | | // File and Image level metadata. |
1333 | 507 | char **papszMergedMD = CSLDuplicate(poDS->psFile->papszMetadata); |
1334 | | |
1335 | 507 | if (psImage) |
1336 | 378 | { |
1337 | 378 | papszMergedMD = CSLInsertStrings(papszMergedMD, CSLCount(papszMergedMD), |
1338 | 378 | psImage->papszMetadata); |
1339 | | |
1340 | | // Comments. |
1341 | 378 | if (psImage->pszComments != nullptr && |
1342 | 378 | strlen(psImage->pszComments) != 0) |
1343 | 227 | papszMergedMD = CSLSetNameValue( |
1344 | 227 | papszMergedMD, "NITF_IMAGE_COMMENTS", psImage->pszComments); |
1345 | | |
1346 | | // Compression code. |
1347 | 378 | papszMergedMD = |
1348 | 378 | CSLSetNameValue(papszMergedMD, "NITF_IC", psImage->szIC); |
1349 | | |
1350 | | // IMODE |
1351 | 378 | char szIMODE[2]; |
1352 | 378 | szIMODE[0] = psImage->chIMODE; |
1353 | 378 | szIMODE[1] = '\0'; |
1354 | 378 | papszMergedMD = CSLSetNameValue(papszMergedMD, "NITF_IMODE", szIMODE); |
1355 | | |
1356 | | // ILOC/Attachment info |
1357 | 378 | if (psImage->nIDLVL != 0) |
1358 | 338 | { |
1359 | 338 | NITFSegmentInfo *psSegInfo = |
1360 | 338 | psFile->pasSegmentInfo + psImage->iSegment; |
1361 | | |
1362 | 338 | papszMergedMD = |
1363 | 338 | CSLSetNameValue(papszMergedMD, "NITF_IDLVL", |
1364 | 338 | CPLString().Printf("%d", psImage->nIDLVL)); |
1365 | 338 | papszMergedMD = |
1366 | 338 | CSLSetNameValue(papszMergedMD, "NITF_IALVL", |
1367 | 338 | CPLString().Printf("%d", psImage->nIALVL)); |
1368 | 338 | papszMergedMD = |
1369 | 338 | CSLSetNameValue(papszMergedMD, "NITF_ILOC_ROW", |
1370 | 338 | CPLString().Printf("%d", psImage->nILOCRow)); |
1371 | 338 | papszMergedMD = |
1372 | 338 | CSLSetNameValue(papszMergedMD, "NITF_ILOC_COLUMN", |
1373 | 338 | CPLString().Printf("%d", psImage->nILOCColumn)); |
1374 | 338 | papszMergedMD = |
1375 | 338 | CSLSetNameValue(papszMergedMD, "NITF_CCS_ROW", |
1376 | 338 | CPLString().Printf("%d", psSegInfo->nCCS_R)); |
1377 | 338 | papszMergedMD = |
1378 | 338 | CSLSetNameValue(papszMergedMD, "NITF_CCS_COLUMN", |
1379 | 338 | CPLString().Printf("%d", psSegInfo->nCCS_C)); |
1380 | 338 | papszMergedMD = |
1381 | 338 | CSLSetNameValue(papszMergedMD, "NITF_IMAG", psImage->szIMAG); |
1382 | 338 | } |
1383 | | |
1384 | 378 | papszMergedMD = |
1385 | 378 | NITFGenericMetadataRead(papszMergedMD, psFile, psImage, nullptr); |
1386 | | |
1387 | | // BLOCKA |
1388 | 378 | char **papszTRE_MD = NITFReadBLOCKA(psImage); |
1389 | 378 | if (papszTRE_MD != nullptr) |
1390 | 11 | { |
1391 | 11 | papszMergedMD = CSLInsertStrings( |
1392 | 11 | papszMergedMD, CSLCount(papszTRE_MD), papszTRE_MD); |
1393 | 11 | CSLDestroy(papszTRE_MD); |
1394 | 11 | } |
1395 | 378 | } |
1396 | | |
1397 | | #ifdef ESRI_BUILD |
1398 | | // Extract ESRI generic metadata. |
1399 | | char **papszESRI_MD = ExtractEsriMD(papszMergedMD); |
1400 | | if (papszESRI_MD != NULL) |
1401 | | { |
1402 | | papszMergedMD = CSLInsertStrings(papszMergedMD, CSLCount(papszESRI_MD), |
1403 | | papszESRI_MD); |
1404 | | CSLDestroy(papszESRI_MD); |
1405 | | } |
1406 | | #endif |
1407 | | |
1408 | 507 | poDS->SetMetadata(papszMergedMD); |
1409 | 507 | CSLDestroy(papszMergedMD); |
1410 | | |
1411 | | /* -------------------------------------------------------------------- */ |
1412 | | /* Image structure metadata. */ |
1413 | | /* -------------------------------------------------------------------- */ |
1414 | 507 | if (psImage == nullptr) |
1415 | 129 | /* do nothing */; |
1416 | 378 | else if (psImage->szIC[1] == '1') |
1417 | 76 | poDS->SetMetadataItem("COMPRESSION", "BILEVEL", "IMAGE_STRUCTURE"); |
1418 | 302 | else if (psImage->szIC[1] == '2') |
1419 | 9 | poDS->SetMetadataItem("COMPRESSION", "ARIDPCM", "IMAGE_STRUCTURE"); |
1420 | 293 | else if (psImage->szIC[1] == '3') |
1421 | 31 | poDS->SetMetadataItem("COMPRESSION", "JPEG", "IMAGE_STRUCTURE"); |
1422 | 262 | else if (psImage->szIC[1] == '4') |
1423 | 23 | poDS->SetMetadataItem("COMPRESSION", "VECTOR QUANTIZATION", |
1424 | 23 | "IMAGE_STRUCTURE"); |
1425 | 239 | else if (psImage->szIC[1] == '5') |
1426 | 1 | poDS->SetMetadataItem("COMPRESSION", "LOSSLESS JPEG", |
1427 | 1 | "IMAGE_STRUCTURE"); |
1428 | 238 | else if (psImage->szIC[1] == '8') |
1429 | 15 | poDS->SetMetadataItem("COMPRESSION", "JPEG2000", "IMAGE_STRUCTURE"); |
1430 | | |
1431 | | /* -------------------------------------------------------------------- */ |
1432 | | /* Do we have RPC info. */ |
1433 | | /* -------------------------------------------------------------------- */ |
1434 | | |
1435 | | // get _rpc.txt file |
1436 | 507 | const std::string osDirName = CPLGetDirnameSafe(pszFilename); |
1437 | 507 | const std::string osBaseName = CPLGetBasenameSafe(pszFilename); |
1438 | 507 | std::string osRPCTXTFilename = CPLFormFilenameSafe( |
1439 | 507 | osDirName.c_str(), std::string(osBaseName).append("_rpc").c_str(), |
1440 | 507 | "txt"); |
1441 | 507 | if (CPLCheckForFile(osRPCTXTFilename.data(), poOpenInfo->GetSiblingFiles())) |
1442 | 0 | { |
1443 | 0 | poDS->m_osRPCTXTFilename = osRPCTXTFilename; |
1444 | 0 | } |
1445 | 507 | else |
1446 | 507 | { |
1447 | 507 | osRPCTXTFilename = CPLFormFilenameSafe( |
1448 | 507 | osDirName.c_str(), std::string(osBaseName).append("_RPC").c_str(), |
1449 | 507 | "TXT"); |
1450 | 507 | CPL_IGNORE_RET_VAL(osBaseName); |
1451 | 507 | if (CPLCheckForFile(osRPCTXTFilename.data(), |
1452 | 507 | poOpenInfo->GetSiblingFiles())) |
1453 | 0 | { |
1454 | 0 | poDS->m_osRPCTXTFilename = osRPCTXTFilename; |
1455 | 0 | } |
1456 | 507 | } |
1457 | 507 | bool bHasLoadedRPCTXT = false; |
1458 | 507 | if (!poDS->m_osRPCTXTFilename.empty()) |
1459 | 0 | { |
1460 | 0 | char **papszMD = GDALLoadRPCFile(poDS->m_osRPCTXTFilename); |
1461 | 0 | if (papszMD != nullptr) |
1462 | 0 | { |
1463 | 0 | bHasLoadedRPCTXT = true; |
1464 | 0 | poDS->SetMetadata(papszMD, "RPC"); |
1465 | 0 | CSLDestroy(papszMD); |
1466 | 0 | } |
1467 | 0 | else |
1468 | 0 | { |
1469 | 0 | poDS->m_osRPCTXTFilename.clear(); |
1470 | 0 | } |
1471 | 0 | } |
1472 | | |
1473 | 507 | if (psImage && bHasRPC00 && !bHasLoadedRPCTXT) |
1474 | 0 | { |
1475 | 0 | char szValue[1280]; |
1476 | |
|
1477 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.ERR_BIAS); |
1478 | 0 | poDS->SetMetadataItem("ERR_BIAS", szValue, "RPC"); |
1479 | |
|
1480 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.ERR_RAND); |
1481 | 0 | poDS->SetMetadataItem("ERR_RAND", szValue, "RPC"); |
1482 | |
|
1483 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.LINE_OFF); |
1484 | 0 | poDS->SetMetadataItem("LINE_OFF", szValue, "RPC"); |
1485 | |
|
1486 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.LINE_SCALE); |
1487 | 0 | poDS->SetMetadataItem("LINE_SCALE", szValue, "RPC"); |
1488 | |
|
1489 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.SAMP_OFF); |
1490 | 0 | poDS->SetMetadataItem("SAMP_OFF", szValue, "RPC"); |
1491 | |
|
1492 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.SAMP_SCALE); |
1493 | 0 | poDS->SetMetadataItem("SAMP_SCALE", szValue, "RPC"); |
1494 | |
|
1495 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.LONG_OFF); |
1496 | 0 | poDS->SetMetadataItem("LONG_OFF", szValue, "RPC"); |
1497 | |
|
1498 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.LONG_SCALE); |
1499 | 0 | poDS->SetMetadataItem("LONG_SCALE", szValue, "RPC"); |
1500 | |
|
1501 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.LAT_OFF); |
1502 | 0 | poDS->SetMetadataItem("LAT_OFF", szValue, "RPC"); |
1503 | |
|
1504 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.LAT_SCALE); |
1505 | 0 | poDS->SetMetadataItem("LAT_SCALE", szValue, "RPC"); |
1506 | |
|
1507 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.HEIGHT_OFF); |
1508 | 0 | poDS->SetMetadataItem("HEIGHT_OFF", szValue, "RPC"); |
1509 | |
|
1510 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.HEIGHT_SCALE); |
1511 | 0 | poDS->SetMetadataItem("HEIGHT_SCALE", szValue, "RPC"); |
1512 | |
|
1513 | 0 | szValue[0] = '\0'; |
1514 | 0 | for (int i = 0; i < 20; i++) |
1515 | 0 | CPLsnprintf(szValue + strlen(szValue), |
1516 | 0 | sizeof(szValue) - strlen(szValue), "%.16g ", |
1517 | 0 | sRPCInfo.LINE_NUM_COEFF[i]); |
1518 | 0 | poDS->SetMetadataItem("LINE_NUM_COEFF", szValue, "RPC"); |
1519 | |
|
1520 | 0 | szValue[0] = '\0'; |
1521 | 0 | for (int i = 0; i < 20; i++) |
1522 | 0 | CPLsnprintf(szValue + strlen(szValue), |
1523 | 0 | sizeof(szValue) - strlen(szValue), "%.16g ", |
1524 | 0 | sRPCInfo.LINE_DEN_COEFF[i]); |
1525 | 0 | poDS->SetMetadataItem("LINE_DEN_COEFF", szValue, "RPC"); |
1526 | |
|
1527 | 0 | szValue[0] = '\0'; |
1528 | 0 | for (int i = 0; i < 20; i++) |
1529 | 0 | CPLsnprintf(szValue + strlen(szValue), |
1530 | 0 | sizeof(szValue) - strlen(szValue), "%.16g ", |
1531 | 0 | sRPCInfo.SAMP_NUM_COEFF[i]); |
1532 | 0 | poDS->SetMetadataItem("SAMP_NUM_COEFF", szValue, "RPC"); |
1533 | |
|
1534 | 0 | szValue[0] = '\0'; |
1535 | 0 | for (int i = 0; i < 20; i++) |
1536 | 0 | CPLsnprintf(szValue + strlen(szValue), |
1537 | 0 | sizeof(szValue) - strlen(szValue), "%.16g ", |
1538 | 0 | sRPCInfo.SAMP_DEN_COEFF[i]); |
1539 | 0 | poDS->SetMetadataItem("SAMP_DEN_COEFF", szValue, "RPC"); |
1540 | |
|
1541 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", |
1542 | 0 | sRPCInfo.LONG_OFF - sRPCInfo.LONG_SCALE); |
1543 | 0 | poDS->SetMetadataItem("MIN_LONG", szValue, "RPC"); |
1544 | |
|
1545 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", |
1546 | 0 | sRPCInfo.LONG_OFF + sRPCInfo.LONG_SCALE); |
1547 | 0 | poDS->SetMetadataItem("MAX_LONG", szValue, "RPC"); |
1548 | |
|
1549 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", |
1550 | 0 | sRPCInfo.LAT_OFF - sRPCInfo.LAT_SCALE); |
1551 | 0 | poDS->SetMetadataItem("MIN_LAT", szValue, "RPC"); |
1552 | |
|
1553 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", |
1554 | 0 | sRPCInfo.LAT_OFF + sRPCInfo.LAT_SCALE); |
1555 | 0 | poDS->SetMetadataItem("MAX_LAT", szValue, "RPC"); |
1556 | 0 | } |
1557 | | |
1558 | | /* -------------------------------------------------------------------- */ |
1559 | | /* Do we have Chip info? */ |
1560 | | /* -------------------------------------------------------------------- */ |
1561 | 507 | NITFICHIPBInfo sChipInfo; |
1562 | | |
1563 | 507 | if (psImage && NITFReadICHIPB(psImage, &sChipInfo) && |
1564 | 0 | sChipInfo.XFRM_FLAG == 0) |
1565 | 0 | { |
1566 | 0 | char szValue[1280]; |
1567 | |
|
1568 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.SCALE_FACTOR); |
1569 | 0 | poDS->SetMetadataItem("ICHIP_SCALE_FACTOR", szValue); |
1570 | | |
1571 | | // TODO: Why do these two not use CPLsnprintf? |
1572 | 0 | snprintf(szValue, sizeof(szValue), "%d", sChipInfo.ANAMORPH_CORR); |
1573 | 0 | poDS->SetMetadataItem("ICHIP_ANAMORPH_CORR", szValue); |
1574 | |
|
1575 | 0 | snprintf(szValue, sizeof(szValue), "%d", sChipInfo.SCANBLK_NUM); |
1576 | 0 | poDS->SetMetadataItem("ICHIP_SCANBLK_NUM", szValue); |
1577 | |
|
1578 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.OP_ROW_11); |
1579 | 0 | poDS->SetMetadataItem("ICHIP_OP_ROW_11", szValue); |
1580 | |
|
1581 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.OP_COL_11); |
1582 | 0 | poDS->SetMetadataItem("ICHIP_OP_COL_11", szValue); |
1583 | |
|
1584 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.OP_ROW_12); |
1585 | 0 | poDS->SetMetadataItem("ICHIP_OP_ROW_12", szValue); |
1586 | |
|
1587 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.OP_COL_12); |
1588 | 0 | poDS->SetMetadataItem("ICHIP_OP_COL_12", szValue); |
1589 | |
|
1590 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.OP_ROW_21); |
1591 | 0 | poDS->SetMetadataItem("ICHIP_OP_ROW_21", szValue); |
1592 | |
|
1593 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.OP_COL_21); |
1594 | 0 | poDS->SetMetadataItem("ICHIP_OP_COL_21", szValue); |
1595 | |
|
1596 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.OP_ROW_22); |
1597 | 0 | poDS->SetMetadataItem("ICHIP_OP_ROW_22", szValue); |
1598 | |
|
1599 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.OP_COL_22); |
1600 | 0 | poDS->SetMetadataItem("ICHIP_OP_COL_22", szValue); |
1601 | |
|
1602 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.FI_ROW_11); |
1603 | 0 | poDS->SetMetadataItem("ICHIP_FI_ROW_11", szValue); |
1604 | |
|
1605 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.FI_COL_11); |
1606 | 0 | poDS->SetMetadataItem("ICHIP_FI_COL_11", szValue); |
1607 | |
|
1608 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.FI_ROW_12); |
1609 | 0 | poDS->SetMetadataItem("ICHIP_FI_ROW_12", szValue); |
1610 | |
|
1611 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.FI_COL_12); |
1612 | 0 | poDS->SetMetadataItem("ICHIP_FI_COL_12", szValue); |
1613 | |
|
1614 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.FI_ROW_21); |
1615 | 0 | poDS->SetMetadataItem("ICHIP_FI_ROW_21", szValue); |
1616 | |
|
1617 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.FI_COL_21); |
1618 | 0 | poDS->SetMetadataItem("ICHIP_FI_COL_21", szValue); |
1619 | |
|
1620 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.FI_ROW_22); |
1621 | 0 | poDS->SetMetadataItem("ICHIP_FI_ROW_22", szValue); |
1622 | |
|
1623 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.FI_COL_22); |
1624 | 0 | poDS->SetMetadataItem("ICHIP_FI_COL_22", szValue); |
1625 | | |
1626 | | // Why not CPLsnprintf? |
1627 | 0 | snprintf(szValue, sizeof(szValue), "%d", sChipInfo.FI_ROW); |
1628 | 0 | poDS->SetMetadataItem("ICHIP_FI_ROW", szValue); |
1629 | |
|
1630 | 0 | snprintf(szValue, sizeof(szValue), "%d", sChipInfo.FI_COL); |
1631 | 0 | poDS->SetMetadataItem("ICHIP_FI_COL", szValue); |
1632 | 0 | } |
1633 | | |
1634 | 507 | const NITFSeries *series = NITFGetSeriesInfo(pszFilename); |
1635 | 507 | if (series) |
1636 | 21 | { |
1637 | 21 | poDS->SetMetadataItem("NITF_SERIES_ABBREVIATION", |
1638 | 21 | (series->abbreviation) ? series->abbreviation |
1639 | 21 | : "Unknown"); |
1640 | 21 | poDS->SetMetadataItem("NITF_SERIES_NAME", |
1641 | 21 | (series->name) ? series->name : "Unknown"); |
1642 | 21 | } |
1643 | | |
1644 | | /* -------------------------------------------------------------------- */ |
1645 | | /* If there are multiple image segments, and no specific one is */ |
1646 | | /* asker for, then setup the subdataset metadata. */ |
1647 | | /* -------------------------------------------------------------------- */ |
1648 | 507 | int nSubDSCount = 0; |
1649 | | |
1650 | 507 | { |
1651 | 507 | char **papszSubdatasets = nullptr; |
1652 | | |
1653 | 87.9k | for (iSegment = 0; iSegment < psFile->nSegmentCount; iSegment++) |
1654 | 87.4k | { |
1655 | 87.4k | if (EQUAL(psFile->pasSegmentInfo[iSegment].szSegmentType, "IM")) |
1656 | 31.2k | { |
1657 | 31.2k | CPLString oName; |
1658 | 31.2k | CPLString oValue; |
1659 | | |
1660 | 31.2k | if (nIMIndex == -1) |
1661 | 31.2k | { |
1662 | 31.2k | oName.Printf("SUBDATASET_%d_NAME", nSubDSCount + 1); |
1663 | 31.2k | oValue.Printf("NITF_IM:%d:%s", nSubDSCount, pszFilename); |
1664 | 31.2k | papszSubdatasets = |
1665 | 31.2k | CSLSetNameValue(papszSubdatasets, oName, oValue); |
1666 | | |
1667 | 31.2k | oName.Printf("SUBDATASET_%d_DESC", nSubDSCount + 1); |
1668 | 31.2k | oValue.Printf("Image %d of %s", nSubDSCount + 1, |
1669 | 31.2k | pszFilename); |
1670 | 31.2k | papszSubdatasets = |
1671 | 31.2k | CSLSetNameValue(papszSubdatasets, oName, oValue); |
1672 | 31.2k | } |
1673 | | |
1674 | 31.2k | nSubDSCount++; |
1675 | 31.2k | } |
1676 | 87.4k | } |
1677 | | |
1678 | 507 | if (nIMIndex == -1 && nSubDSCount > 1) |
1679 | 260 | { |
1680 | 260 | poDS->GDALMajorObject::SetMetadata(papszSubdatasets, "SUBDATASETS"); |
1681 | 260 | } |
1682 | | |
1683 | 507 | CSLDestroy(papszSubdatasets); |
1684 | 507 | } |
1685 | | |
1686 | | /* -------------------------------------------------------------------- */ |
1687 | | /* Initialize any PAM information. */ |
1688 | | /* -------------------------------------------------------------------- */ |
1689 | 507 | poDS->SetDescription(poOpenInfo->pszFilename); |
1690 | 507 | poDS->SetPhysicalFilename(pszFilename); |
1691 | | |
1692 | 507 | if (nSubDSCount > 1 || nIMIndex != -1) |
1693 | 260 | { |
1694 | 260 | if (nIMIndex == -1) |
1695 | 260 | { |
1696 | 260 | nIMIndex = 0; |
1697 | 260 | } |
1698 | 0 | else if (nIMIndex == 0 && nSubDSCount == 1) |
1699 | 0 | { |
1700 | | // If subdataset 0 is explicitly specified, and there's a single |
1701 | | // subdataset, and that PAM .aux.xml doesn't have a Subdataset node, |
1702 | | // then don't set the subdataset name to get metadata from the |
1703 | | // top PAM node. |
1704 | 0 | const char *pszPAMFilename = poDS->BuildPamFilename(); |
1705 | 0 | VSIStatBufL sStatBuf; |
1706 | 0 | if (pszPAMFilename != nullptr && |
1707 | 0 | VSIStatExL(pszPAMFilename, &sStatBuf, |
1708 | 0 | VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0 && |
1709 | 0 | VSI_ISREG(sStatBuf.st_mode)) |
1710 | 0 | { |
1711 | 0 | CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler); |
1712 | 0 | CPLXMLNode *psTree = CPLParseXMLFile(pszPAMFilename); |
1713 | 0 | if (psTree) |
1714 | 0 | { |
1715 | 0 | if (CPLGetXMLNode(psTree, "=PAMDataset.Subdataset") == |
1716 | 0 | nullptr) |
1717 | 0 | { |
1718 | 0 | nIMIndex = -1; |
1719 | 0 | } |
1720 | 0 | } |
1721 | 0 | CPLDestroyXMLNode(psTree); |
1722 | 0 | } |
1723 | 0 | } |
1724 | | |
1725 | 260 | if (nIMIndex >= 0) |
1726 | 260 | { |
1727 | 260 | poDS->SetSubdatasetName(CPLString().Printf("%d", nIMIndex)); |
1728 | 260 | } |
1729 | 260 | } |
1730 | 247 | else if (/* nIMIndex == -1 && */ nSubDSCount == 1) |
1731 | 118 | { |
1732 | | // GDAL 3.4.0 to 3.5.0 used to save the PAM metadata if a Subdataset |
1733 | | // node, even if there was one single subdataset. |
1734 | | // Detect that situation to automatically read it even if not explicitly |
1735 | | // specifying that single subdataset. |
1736 | 118 | const char *pszPAMFilename = poDS->BuildPamFilename(); |
1737 | 118 | VSIStatBufL sStatBuf; |
1738 | 118 | if (pszPAMFilename != nullptr && |
1739 | 118 | VSIStatExL(pszPAMFilename, &sStatBuf, |
1740 | 118 | VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0 && |
1741 | 118 | VSI_ISREG(sStatBuf.st_mode)) |
1742 | 73 | { |
1743 | 73 | CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler); |
1744 | 73 | CPLXMLNode *psTree = CPLParseXMLFile(pszPAMFilename); |
1745 | 73 | if (psTree) |
1746 | 73 | { |
1747 | 73 | const auto psSubdatasetNode = |
1748 | 73 | CPLGetXMLNode(psTree, "=PAMDataset.Subdataset"); |
1749 | 73 | if (psSubdatasetNode != nullptr && |
1750 | 0 | strcmp(CPLGetXMLValue(psSubdatasetNode, "name", ""), "0") == |
1751 | 0 | 0) |
1752 | 0 | { |
1753 | 0 | poDS->SetSubdatasetName("0"); |
1754 | 0 | poDS->SetPhysicalFilename(pszFilename); |
1755 | 0 | } |
1756 | 73 | CPLDestroyXMLNode(psTree); |
1757 | 73 | } |
1758 | 73 | } |
1759 | 118 | } |
1760 | | |
1761 | 507 | poDS->bInLoadXML = TRUE; |
1762 | 507 | poDS->TryLoadXML(poOpenInfo->GetSiblingFiles()); |
1763 | 507 | poDS->bInLoadXML = FALSE; |
1764 | | |
1765 | | /* -------------------------------------------------------------------- */ |
1766 | | /* Do we have a special overview file? If not, do we have */ |
1767 | | /* RSets that should be treated as an overview file? */ |
1768 | | /* -------------------------------------------------------------------- */ |
1769 | 507 | const char *pszOverviewFile = |
1770 | 507 | poDS->GetMetadataItem("OVERVIEW_FILE", "OVERVIEWS"); |
1771 | | |
1772 | 507 | if (pszOverviewFile == nullptr) |
1773 | 507 | { |
1774 | 507 | if (poDS->CheckForRSets(pszFilename, poOpenInfo->GetSiblingFiles())) |
1775 | 0 | pszOverviewFile = poDS->osRSetVRT; |
1776 | 507 | } |
1777 | | |
1778 | | /* -------------------------------------------------------------------- */ |
1779 | | /* If we have jpeg or jpeg2000 bands we may need to set the */ |
1780 | | /* overview file on their dataset. (#3276) */ |
1781 | | /* -------------------------------------------------------------------- */ |
1782 | 507 | GDALDataset *poSubDS = poDS->poJ2KDataset.get(); |
1783 | 507 | if (poDS->poJPEGDataset) |
1784 | 14 | poSubDS = poDS->poJPEGDataset.get(); |
1785 | | |
1786 | 507 | if (poSubDS && pszOverviewFile != nullptr) |
1787 | 0 | { |
1788 | 0 | poSubDS->SetMetadataItem("OVERVIEW_FILE", pszOverviewFile, "OVERVIEWS"); |
1789 | 0 | } |
1790 | | |
1791 | | /* -------------------------------------------------------------------- */ |
1792 | | /* If we have jpeg, or jpeg2000 bands we may need to clear */ |
1793 | | /* their PAM dirty flag too. */ |
1794 | | /* -------------------------------------------------------------------- */ |
1795 | 507 | if (poDS->poJ2KDataset != nullptr && |
1796 | 0 | (poDS->poJ2KDataset->GetMOFlags() & GMO_PAM_CLASS)) |
1797 | 0 | (cpl::down_cast<GDALPamDataset *>(poDS->poJ2KDataset.get())) |
1798 | 0 | ->SetPamFlags( |
1799 | 0 | (cpl::down_cast<GDALPamDataset *>(poDS->poJ2KDataset.get())) |
1800 | 0 | ->GetPamFlags() & |
1801 | 0 | ~GPF_DIRTY); |
1802 | 507 | if (poDS->poJPEGDataset != nullptr && |
1803 | 14 | (poDS->poJPEGDataset->GetMOFlags() & GMO_PAM_CLASS)) |
1804 | 14 | (cpl::down_cast<GDALPamDataset *>(poDS->poJPEGDataset.get())) |
1805 | 14 | ->SetPamFlags( |
1806 | 14 | (cpl::down_cast<GDALPamDataset *>(poDS->poJPEGDataset.get())) |
1807 | 14 | ->GetPamFlags() & |
1808 | 14 | ~GPF_DIRTY); |
1809 | | |
1810 | | /* -------------------------------------------------------------------- */ |
1811 | | /* Check for overviews. */ |
1812 | | /* -------------------------------------------------------------------- */ |
1813 | 507 | if (!EQUAL(poOpenInfo->pszFilename, pszFilename)) |
1814 | 0 | poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::"); |
1815 | 507 | else |
1816 | 507 | poDS->oOvManager.Initialize(poDS, pszFilename, |
1817 | 507 | poOpenInfo->GetSiblingFiles()); |
1818 | | |
1819 | | /* If there are PAM overviews, don't expose the underlying JPEG dataset */ |
1820 | | /* overviews (in case of monoblock C3) */ |
1821 | 507 | if (poDS->GetRasterCount() > 0 && poDS->GetRasterBand(1) != nullptr) |
1822 | 378 | poDS->bExposeUnderlyingJPEGDatasetOverviews = |
1823 | 378 | (reinterpret_cast<GDALPamRasterBand *>(poDS->GetRasterBand(1))) |
1824 | 378 | ->GDALPamRasterBand::GetOverviewCount() == 0; |
1825 | | |
1826 | 507 | if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VALIDATE", false)) |
1827 | 0 | { |
1828 | 0 | if (!poDS->Validate() && |
1829 | 0 | CPLFetchBool(poOpenInfo->papszOpenOptions, |
1830 | 0 | "FAIL_IF_VALIDATION_ERROR", false)) |
1831 | 0 | { |
1832 | 0 | delete poDS; |
1833 | 0 | poDS = nullptr; |
1834 | 0 | } |
1835 | 0 | } |
1836 | | |
1837 | 507 | return poDS; |
1838 | 507 | } |
1839 | | |
1840 | | /************************************************************************/ |
1841 | | /* Validate() */ |
1842 | | /************************************************************************/ |
1843 | | |
1844 | | bool NITFDataset::Validate() |
1845 | 0 | { |
1846 | 0 | bool bSuccess = InitializeTREMetadata(true); |
1847 | 0 | if (!InitializeNITFDESs(true)) |
1848 | 0 | bSuccess = false; |
1849 | 0 | return bSuccess; |
1850 | 0 | } |
1851 | | |
1852 | | /************************************************************************/ |
1853 | | /* LoadDODDatum() */ |
1854 | | /* */ |
1855 | | /* Try to turn a US military datum name into a datum definition. */ |
1856 | | /************************************************************************/ |
1857 | | |
1858 | | static OGRErr LoadDODDatum(OGRSpatialReference *poSRS, const char *pszDatumName) |
1859 | | |
1860 | 0 | { |
1861 | | /* -------------------------------------------------------------------- */ |
1862 | | /* The most common case... */ |
1863 | | /* -------------------------------------------------------------------- */ |
1864 | 0 | if (STARTS_WITH_CI(pszDatumName, "WGE ")) |
1865 | 0 | { |
1866 | 0 | poSRS->SetWellKnownGeogCS("WGS84"); |
1867 | 0 | return OGRERR_NONE; |
1868 | 0 | } |
1869 | | |
1870 | | #if defined(USE_ONLY_EMBEDDED_RESOURCE_FILES) && !defined(EMBED_RESOURCE_FILES) |
1871 | | return OGRERR_FAILURE; |
1872 | | #else |
1873 | | |
1874 | | /* -------------------------------------------------------------------- */ |
1875 | | /* All the rest we will try and load from gt_datum.csv */ |
1876 | | /* (Geotrans datum file). */ |
1877 | | /* -------------------------------------------------------------------- */ |
1878 | 0 | char szExpanded[6]; |
1879 | 0 | const char *pszGTDatum = nullptr; |
1880 | 0 | CPL_IGNORE_RET_VAL(pszGTDatum); |
1881 | 0 | #ifndef USE_ONLY_EMBEDDED_RESOURCE_FILES |
1882 | 0 | pszGTDatum = CSVFilename("gt_datum.csv"); |
1883 | 0 | #endif |
1884 | 0 | #ifdef EMBED_RESOURCE_FILES |
1885 | 0 | std::string osTmpFilename; |
1886 | | // CSVFilename() returns the same content as pszFilename if it does not |
1887 | | // find the file. |
1888 | 0 | if (!pszGTDatum || strcmp(pszGTDatum, "gt_datum.csv") == 0) |
1889 | 0 | { |
1890 | 0 | osTmpFilename = VSIMemGenerateHiddenFilename("gt_datum.csv"); |
1891 | 0 | const char *pszFileContent = NITFGetGTDatum(); |
1892 | 0 | VSIFCloseL(VSIFileFromMemBuffer( |
1893 | 0 | osTmpFilename.c_str(), |
1894 | 0 | const_cast<GByte *>( |
1895 | 0 | reinterpret_cast<const GByte *>(pszFileContent)), |
1896 | 0 | static_cast<int>(strlen(pszFileContent)), |
1897 | 0 | /* bTakeOwnership = */ false)); |
1898 | 0 | pszGTDatum = osTmpFilename.c_str(); |
1899 | 0 | } |
1900 | 0 | #endif |
1901 | |
|
1902 | 0 | strncpy(szExpanded, pszDatumName, 3); |
1903 | 0 | szExpanded[3] = '\0'; |
1904 | 0 | if (pszDatumName[3] != ' ') |
1905 | 0 | { |
1906 | 0 | size_t nLen; |
1907 | 0 | strcat(szExpanded, "-"); |
1908 | 0 | nLen = strlen(szExpanded); |
1909 | 0 | szExpanded[nLen] = pszDatumName[3]; |
1910 | 0 | szExpanded[nLen + 1] = '\0'; |
1911 | 0 | } |
1912 | |
|
1913 | 0 | CPLString osDName = |
1914 | 0 | CSVGetField(pszGTDatum, "CODE", szExpanded, CC_ApproxString, "NAME"); |
1915 | 0 | if (osDName.empty()) |
1916 | 0 | { |
1917 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1918 | 0 | "Failed to find datum %s/%s in gt_datum.csv.", pszDatumName, |
1919 | 0 | szExpanded); |
1920 | |
|
1921 | 0 | #ifdef EMBED_RESOURCE_FILES |
1922 | 0 | if (!osTmpFilename.empty()) |
1923 | 0 | { |
1924 | 0 | CSVDeaccess(osTmpFilename.c_str()); |
1925 | 0 | VSIUnlink(osTmpFilename.c_str()); |
1926 | 0 | } |
1927 | 0 | #endif |
1928 | 0 | return OGRERR_FAILURE; |
1929 | 0 | } |
1930 | | |
1931 | 0 | CPLString osEllipseCode = CSVGetField(pszGTDatum, "CODE", szExpanded, |
1932 | 0 | CC_ApproxString, "ELLIPSOID"); |
1933 | 0 | double dfDeltaX = CPLAtof( |
1934 | 0 | CSVGetField(pszGTDatum, "CODE", szExpanded, CC_ApproxString, "DELTAX")); |
1935 | 0 | double dfDeltaY = CPLAtof( |
1936 | 0 | CSVGetField(pszGTDatum, "CODE", szExpanded, CC_ApproxString, "DELTAY")); |
1937 | 0 | double dfDeltaZ = CPLAtof( |
1938 | 0 | CSVGetField(pszGTDatum, "CODE", szExpanded, CC_ApproxString, "DELTAZ")); |
1939 | |
|
1940 | 0 | #ifdef EMBED_RESOURCE_FILES |
1941 | 0 | if (!osTmpFilename.empty()) |
1942 | 0 | { |
1943 | 0 | CSVDeaccess(osTmpFilename.c_str()); |
1944 | 0 | VSIUnlink(osTmpFilename.c_str()); |
1945 | 0 | osTmpFilename.clear(); |
1946 | 0 | } |
1947 | 0 | #endif |
1948 | | |
1949 | | /* -------------------------------------------------------------------- */ |
1950 | | /* Lookup the ellipse code. */ |
1951 | | /* -------------------------------------------------------------------- */ |
1952 | 0 | const char *pszGTEllipse = nullptr; |
1953 | 0 | CPL_IGNORE_RET_VAL(pszGTEllipse); |
1954 | 0 | #ifndef USE_ONLY_EMBEDDED_RESOURCE_FILES |
1955 | 0 | pszGTEllipse = CSVFilename("gt_ellips.csv"); |
1956 | 0 | #endif |
1957 | |
|
1958 | 0 | #ifdef EMBED_RESOURCE_FILES |
1959 | | // CSVFilename() returns the same content as pszFilename if it does not |
1960 | | // find the file. |
1961 | 0 | if (!pszGTEllipse || strcmp(pszGTEllipse, "gt_ellips.csv") == 0) |
1962 | 0 | { |
1963 | 0 | osTmpFilename = VSIMemGenerateHiddenFilename("gt_ellips"); |
1964 | 0 | const char *pszFileContent = NITFGetGTEllips(); |
1965 | 0 | VSIFCloseL(VSIFileFromMemBuffer( |
1966 | 0 | osTmpFilename.c_str(), |
1967 | 0 | const_cast<GByte *>( |
1968 | 0 | reinterpret_cast<const GByte *>(pszFileContent)), |
1969 | 0 | static_cast<int>(strlen(pszFileContent)), |
1970 | 0 | /* bTakeOwnership = */ false)); |
1971 | 0 | pszGTEllipse = osTmpFilename.c_str(); |
1972 | 0 | } |
1973 | 0 | #endif |
1974 | |
|
1975 | 0 | CPLString osEName = CSVGetField(pszGTEllipse, "CODE", osEllipseCode, |
1976 | 0 | CC_ApproxString, "NAME"); |
1977 | 0 | osEName = osEName.Trim(); |
1978 | 0 | if (osEName.empty()) |
1979 | 0 | { |
1980 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1981 | 0 | "Failed to find datum %s in gt_ellips.csv.", |
1982 | 0 | osEllipseCode.c_str()); |
1983 | |
|
1984 | 0 | #ifdef EMBED_RESOURCE_FILES |
1985 | 0 | if (!osTmpFilename.empty()) |
1986 | 0 | { |
1987 | 0 | CSVDeaccess(osTmpFilename.c_str()); |
1988 | 0 | VSIUnlink(osTmpFilename.c_str()); |
1989 | 0 | } |
1990 | 0 | #endif |
1991 | 0 | return OGRERR_FAILURE; |
1992 | 0 | } |
1993 | | |
1994 | 0 | double dfA = CPLAtof( |
1995 | 0 | CSVGetField(pszGTEllipse, "CODE", osEllipseCode, CC_ApproxString, "A")); |
1996 | 0 | double dfInvF = CPLAtof(CSVGetField(pszGTEllipse, "CODE", osEllipseCode, |
1997 | 0 | CC_ApproxString, "RF")); |
1998 | | |
1999 | | /* -------------------------------------------------------------------- */ |
2000 | | /* Create geographic coordinate system. */ |
2001 | | /* -------------------------------------------------------------------- */ |
2002 | 0 | poSRS->SetGeogCS(osDName, osDName, osEName, dfA, dfInvF); |
2003 | |
|
2004 | 0 | poSRS->SetTOWGS84(dfDeltaX, dfDeltaY, dfDeltaZ); |
2005 | |
|
2006 | 0 | #ifdef EMBED_RESOURCE_FILES |
2007 | 0 | if (!osTmpFilename.empty()) |
2008 | 0 | { |
2009 | 0 | CSVDeaccess(osTmpFilename.c_str()); |
2010 | 0 | VSIUnlink(osTmpFilename.c_str()); |
2011 | 0 | osTmpFilename.clear(); |
2012 | 0 | } |
2013 | 0 | #endif |
2014 | |
|
2015 | 0 | return OGRERR_NONE; |
2016 | 0 | #endif |
2017 | 0 | } |
2018 | | |
2019 | | /************************************************************************/ |
2020 | | /* CheckGeoSDEInfo() */ |
2021 | | /* */ |
2022 | | /* Check for GeoSDE TREs (GEOPSB/PRJPSB and MAPLOB). If we */ |
2023 | | /* have them, use them to override our coordinate system and */ |
2024 | | /* geotransform info. */ |
2025 | | /************************************************************************/ |
2026 | | |
2027 | | void NITFDataset::CheckGeoSDEInfo() |
2028 | | |
2029 | 378 | { |
2030 | 378 | if (!psImage) |
2031 | 0 | return; |
2032 | | |
2033 | | /* -------------------------------------------------------------------- */ |
2034 | | /* Do we have the required TREs? */ |
2035 | | /* -------------------------------------------------------------------- */ |
2036 | 378 | int nGEOPSBSize, nPRJPSBSize, nMAPLOBSize; |
2037 | | |
2038 | 378 | const char *pszGEOPSB = |
2039 | 378 | NITFFindTRE(psFile->pachTRE, psFile->nTREBytes, "GEOPSB", &nGEOPSBSize); |
2040 | 378 | const char *pszPRJPSB = |
2041 | 378 | NITFFindTRE(psFile->pachTRE, psFile->nTREBytes, "PRJPSB", &nPRJPSBSize); |
2042 | 378 | const char *pszMAPLOB = NITFFindTRE(psImage->pachTRE, psImage->nTREBytes, |
2043 | 378 | "MAPLOB", &nMAPLOBSize); |
2044 | | |
2045 | 378 | if (pszGEOPSB == nullptr || pszPRJPSB == nullptr || pszMAPLOB == nullptr) |
2046 | 378 | return; |
2047 | | |
2048 | | /* -------------------------------------------------------------------- */ |
2049 | | /* Collect projection parameters. */ |
2050 | | /* -------------------------------------------------------------------- */ |
2051 | | |
2052 | 0 | char szParam[16]; |
2053 | 0 | if (nPRJPSBSize < 82 + 1) |
2054 | 0 | { |
2055 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2056 | 0 | "Cannot read PRJPSB TRE. Not enough bytes"); |
2057 | 0 | return; |
2058 | 0 | } |
2059 | 0 | const int nParamCount = atoi(NITFGetField(szParam, pszPRJPSB, 82, 1)); |
2060 | 0 | if (nPRJPSBSize < 83 + 15 * nParamCount + 15 + 15) |
2061 | 0 | { |
2062 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2063 | 0 | "Cannot read PRJPSB TRE. Not enough bytes"); |
2064 | 0 | return; |
2065 | 0 | } |
2066 | | |
2067 | 0 | double adfParam[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; |
2068 | 0 | for (int i = 0; i < nParamCount; i++) |
2069 | 0 | adfParam[i] = |
2070 | 0 | CPLAtof(NITFGetField(szParam, pszPRJPSB, 83 + 15 * i, 15)); |
2071 | |
|
2072 | 0 | const double dfFE = |
2073 | 0 | CPLAtof(NITFGetField(szParam, pszPRJPSB, 83 + 15 * nParamCount, 15)); |
2074 | 0 | const double dfFN = CPLAtof( |
2075 | 0 | NITFGetField(szParam, pszPRJPSB, 83 + 15 * nParamCount + 15, 15)); |
2076 | | |
2077 | | /* -------------------------------------------------------------------- */ |
2078 | | /* Try to handle the projection. */ |
2079 | | /* -------------------------------------------------------------------- */ |
2080 | 0 | OGRSpatialReference oSRS; |
2081 | 0 | oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
2082 | |
|
2083 | 0 | if (STARTS_WITH_CI(pszPRJPSB + 80, "AC")) |
2084 | 0 | oSRS.SetACEA(adfParam[1], adfParam[2], adfParam[3], adfParam[0], dfFE, |
2085 | 0 | dfFN); |
2086 | | |
2087 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "AK")) |
2088 | 0 | oSRS.SetLAEA(adfParam[1], adfParam[0], dfFE, dfFN); |
2089 | | |
2090 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "AL")) |
2091 | 0 | oSRS.SetAE(adfParam[1], adfParam[0], dfFE, dfFN); |
2092 | | |
2093 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "BF")) |
2094 | 0 | oSRS.SetBonne(adfParam[1], adfParam[0], dfFE, dfFN); |
2095 | | |
2096 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "CP")) |
2097 | 0 | oSRS.SetEquirectangular(adfParam[1], adfParam[0], dfFE, dfFN); |
2098 | | |
2099 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "CS")) |
2100 | 0 | oSRS.SetCS(adfParam[1], adfParam[0], dfFE, dfFN); |
2101 | | |
2102 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "EF")) |
2103 | 0 | oSRS.SetEckertIV(adfParam[0], dfFE, dfFN); |
2104 | | |
2105 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "ED")) |
2106 | 0 | oSRS.SetEckertVI(adfParam[0], dfFE, dfFN); |
2107 | | |
2108 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "GN")) |
2109 | 0 | oSRS.SetGnomonic(adfParam[1], adfParam[0], dfFE, dfFN); |
2110 | | |
2111 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "HX")) |
2112 | 0 | oSRS.SetHOM2PNO(adfParam[1], adfParam[3], adfParam[2], adfParam[5], |
2113 | 0 | adfParam[4], adfParam[0], dfFE, dfFN); |
2114 | | |
2115 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "KA")) |
2116 | 0 | oSRS.SetEC(adfParam[1], adfParam[2], adfParam[3], adfParam[0], dfFE, |
2117 | 0 | dfFN); |
2118 | | |
2119 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "LE")) |
2120 | 0 | oSRS.SetLCC(adfParam[1], adfParam[2], adfParam[3], adfParam[0], dfFE, |
2121 | 0 | dfFN); |
2122 | | |
2123 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "LI")) |
2124 | 0 | oSRS.SetCEA(adfParam[1], adfParam[0], dfFE, dfFN); |
2125 | | |
2126 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "MC")) |
2127 | 0 | oSRS.SetMercator(adfParam[2], adfParam[1], 1.0, dfFE, dfFN); |
2128 | | |
2129 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "MH")) |
2130 | 0 | oSRS.SetMC(0.0, adfParam[1], dfFE, dfFN); |
2131 | | |
2132 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "MP")) |
2133 | 0 | oSRS.SetMollweide(adfParam[0], dfFE, dfFN); |
2134 | | |
2135 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "NT")) |
2136 | 0 | oSRS.SetNZMG(adfParam[1], adfParam[0], dfFE, dfFN); |
2137 | | |
2138 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "OD")) |
2139 | 0 | oSRS.SetOrthographic(adfParam[1], adfParam[0], dfFE, dfFN); |
2140 | | |
2141 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "PC")) |
2142 | 0 | oSRS.SetPolyconic(adfParam[1], adfParam[0], dfFE, dfFN); |
2143 | | |
2144 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "PG")) |
2145 | 0 | oSRS.SetPS(adfParam[1], adfParam[0], 1.0, dfFE, dfFN); |
2146 | | |
2147 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "RX")) |
2148 | 0 | oSRS.SetRobinson(adfParam[0], dfFE, dfFN); |
2149 | | |
2150 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "SA")) |
2151 | 0 | oSRS.SetSinusoidal(adfParam[0], dfFE, dfFN); |
2152 | | |
2153 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "TC")) |
2154 | 0 | oSRS.SetTM(adfParam[2], adfParam[0], adfParam[1], dfFE, dfFN); |
2155 | | |
2156 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "VA")) |
2157 | 0 | oSRS.SetVDG(adfParam[0], dfFE, dfFN); |
2158 | | |
2159 | 0 | else |
2160 | 0 | { |
2161 | 0 | char szName[81]; |
2162 | 0 | oSRS.SetLocalCS(NITFGetField(szName, pszPRJPSB, 0, 80)); |
2163 | 0 | } |
2164 | | |
2165 | | /* -------------------------------------------------------------------- */ |
2166 | | /* Try to apply the datum. */ |
2167 | | /* -------------------------------------------------------------------- */ |
2168 | 0 | if (nGEOPSBSize < 86 + 4) |
2169 | 0 | { |
2170 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2171 | 0 | "Cannot read GEOPSB TRE. Not enough bytes"); |
2172 | 0 | return; |
2173 | 0 | } |
2174 | 0 | LoadDODDatum(&oSRS, NITFGetField(szParam, pszGEOPSB, 86, 4)); |
2175 | | |
2176 | | /* -------------------------------------------------------------------- */ |
2177 | | /* Get the geotransform */ |
2178 | | /* -------------------------------------------------------------------- */ |
2179 | 0 | if (nMAPLOBSize < 28 + 15) |
2180 | 0 | { |
2181 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2182 | 0 | "Cannot read MAPLOB TRE. Not enough bytes"); |
2183 | 0 | return; |
2184 | 0 | } |
2185 | | |
2186 | 0 | double dfMeterPerUnit = 1.0; |
2187 | 0 | if (STARTS_WITH_CI(pszMAPLOB + 0, "DM ")) |
2188 | 0 | dfMeterPerUnit = 0.1; |
2189 | 0 | else if (STARTS_WITH_CI(pszMAPLOB + 0, "CM ")) |
2190 | 0 | dfMeterPerUnit = 0.01; |
2191 | 0 | else if (STARTS_WITH_CI(pszMAPLOB + 0, "MM ")) |
2192 | 0 | dfMeterPerUnit = 0.001; |
2193 | 0 | else if (STARTS_WITH_CI(pszMAPLOB + 0, "UM ")) |
2194 | 0 | dfMeterPerUnit = 0.000001; |
2195 | 0 | else if (STARTS_WITH_CI(pszMAPLOB + 0, "KM ")) |
2196 | 0 | dfMeterPerUnit = 1000.0; |
2197 | 0 | else if (STARTS_WITH_CI(pszMAPLOB + 0, "M ")) |
2198 | 0 | dfMeterPerUnit = 1.0; |
2199 | 0 | else |
2200 | 0 | { |
2201 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
2202 | 0 | "MAPLOB Unit=%3.3s not recognized, geolocation may be wrong.", |
2203 | 0 | pszMAPLOB + 0); |
2204 | 0 | } |
2205 | |
|
2206 | 0 | m_gt.xorig = CPLAtof(NITFGetField(szParam, pszMAPLOB, 13, 15)); |
2207 | 0 | m_gt.xscale = |
2208 | 0 | CPLAtof(NITFGetField(szParam, pszMAPLOB, 3, 5)) * dfMeterPerUnit; |
2209 | 0 | m_gt.xrot = 0.0; |
2210 | 0 | m_gt.yorig = CPLAtof(NITFGetField(szParam, pszMAPLOB, 28, 15)); |
2211 | 0 | m_gt.yrot = 0.0; |
2212 | 0 | m_gt.yscale = |
2213 | 0 | -CPLAtof(NITFGetField(szParam, pszMAPLOB, 8, 5)) * dfMeterPerUnit; |
2214 | |
|
2215 | 0 | m_oSRS = std::move(oSRS); |
2216 | |
|
2217 | 0 | bGotGeoTransform = TRUE; |
2218 | 0 | } |
2219 | | |
2220 | | /************************************************************************/ |
2221 | | /* AdviseRead() */ |
2222 | | /************************************************************************/ |
2223 | | |
2224 | | CPLErr NITFDataset::AdviseRead(int nXOff, int nYOff, int nXSize, int nYSize, |
2225 | | int nBufXSize, int nBufYSize, GDALDataType eDT, |
2226 | | int nBandCount, int *panBandList, |
2227 | | CSLConstList papszOptions) |
2228 | | |
2229 | 7 | { |
2230 | | //go through GDALDataset::AdviseRead for the complex SAR |
2231 | 7 | if (poJ2KDataset == nullptr || m_bHasComplexRasterBand) |
2232 | 7 | return GDALDataset::AdviseRead(nXOff, nYOff, nXSize, nYSize, nBufXSize, |
2233 | 7 | nBufYSize, eDT, nBandCount, panBandList, |
2234 | 7 | papszOptions); |
2235 | 0 | else if (poJPEGDataset != nullptr) |
2236 | 0 | return poJPEGDataset->AdviseRead(nXOff, nYOff, nXSize, nYSize, |
2237 | 0 | nBufXSize, nBufYSize, eDT, nBandCount, |
2238 | 0 | panBandList, papszOptions); |
2239 | 0 | else |
2240 | 0 | return poJ2KDataset->AdviseRead(nXOff, nYOff, nXSize, nYSize, nBufXSize, |
2241 | 0 | nBufYSize, eDT, nBandCount, panBandList, |
2242 | 0 | papszOptions); |
2243 | 7 | } |
2244 | | |
2245 | | /************************************************************************/ |
2246 | | /* IRasterIO() */ |
2247 | | /************************************************************************/ |
2248 | | |
2249 | | CPLErr NITFDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, |
2250 | | int nXSize, int nYSize, void *pData, |
2251 | | int nBufXSize, int nBufYSize, |
2252 | | GDALDataType eBufType, int nBandCount, |
2253 | | BANDMAP_TYPE panBandMap, GSpacing nPixelSpace, |
2254 | | GSpacing nLineSpace, GSpacing nBandSpace, |
2255 | | GDALRasterIOExtraArg *psExtraArg) |
2256 | | |
2257 | 0 | { |
2258 | | //go through GDALDataset::IRasterIO for the complex SAR |
2259 | 0 | if (poJ2KDataset != nullptr && !m_bHasComplexRasterBand) |
2260 | 0 | return poJ2KDataset->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, |
2261 | 0 | pData, nBufXSize, nBufYSize, eBufType, |
2262 | 0 | nBandCount, panBandMap, nPixelSpace, |
2263 | 0 | nLineSpace, nBandSpace, psExtraArg); |
2264 | 0 | else if (poJPEGDataset != nullptr && !m_bHasComplexRasterBand) |
2265 | 0 | return poJPEGDataset->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, |
2266 | 0 | pData, nBufXSize, nBufYSize, eBufType, |
2267 | 0 | nBandCount, panBandMap, nPixelSpace, |
2268 | 0 | nLineSpace, nBandSpace, psExtraArg); |
2269 | 0 | else |
2270 | 0 | return GDALDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, |
2271 | 0 | pData, nBufXSize, nBufYSize, eBufType, |
2272 | 0 | nBandCount, panBandMap, nPixelSpace, |
2273 | 0 | nLineSpace, nBandSpace, psExtraArg); |
2274 | 0 | } |
2275 | | |
2276 | | /************************************************************************/ |
2277 | | /* GetGeoTransform() */ |
2278 | | /************************************************************************/ |
2279 | | |
2280 | | CPLErr NITFDataset::GetGeoTransform(GDALGeoTransform >) const |
2281 | | |
2282 | 334 | { |
2283 | 334 | gt = m_gt; |
2284 | | |
2285 | 334 | if (bGotGeoTransform) |
2286 | 29 | return CE_None; |
2287 | | |
2288 | 305 | return GDALPamDataset::GetGeoTransform(gt); |
2289 | 334 | } |
2290 | | |
2291 | | /************************************************************************/ |
2292 | | /* SetGeoTransform() */ |
2293 | | /************************************************************************/ |
2294 | | |
2295 | | CPLErr NITFDataset::SetGeoTransform(const GDALGeoTransform >) |
2296 | | |
2297 | 5 | { |
2298 | 5 | bGotGeoTransform = TRUE; |
2299 | 5 | m_gt = gt; |
2300 | | |
2301 | 5 | double dfIGEOLOULX = m_gt.xorig + 0.5 * m_gt.xscale + 0.5 * m_gt.xrot; |
2302 | 5 | double dfIGEOLOULY = m_gt.yorig + 0.5 * m_gt.yrot + 0.5 * m_gt.yscale; |
2303 | 5 | double dfIGEOLOURX = dfIGEOLOULX + m_gt.xscale * (nRasterXSize - 1); |
2304 | 5 | double dfIGEOLOURY = dfIGEOLOULY + m_gt.yrot * (nRasterXSize - 1); |
2305 | 5 | double dfIGEOLOLRX = dfIGEOLOULX + m_gt.xscale * (nRasterXSize - 1) + |
2306 | 5 | m_gt.xrot * (nRasterYSize - 1); |
2307 | 5 | double dfIGEOLOLRY = dfIGEOLOULY + m_gt.yrot * (nRasterXSize - 1) + |
2308 | 5 | m_gt.yscale * (nRasterYSize - 1); |
2309 | 5 | double dfIGEOLOLLX = dfIGEOLOULX + m_gt.xrot * (nRasterYSize - 1); |
2310 | 5 | double dfIGEOLOLLY = dfIGEOLOULY + m_gt.yscale * (nRasterYSize - 1); |
2311 | | |
2312 | 5 | if (psImage != nullptr && |
2313 | 5 | NITFWriteIGEOLO(psImage, psImage->chICORDS, psImage->nZone, dfIGEOLOULX, |
2314 | 5 | dfIGEOLOULY, dfIGEOLOURX, dfIGEOLOURY, dfIGEOLOLRX, |
2315 | 5 | dfIGEOLOLRY, dfIGEOLOLLX, dfIGEOLOLLY)) |
2316 | 4 | return CE_None; |
2317 | | |
2318 | 1 | return GDALPamDataset::SetGeoTransform(gt); |
2319 | 5 | } |
2320 | | |
2321 | | /************************************************************************/ |
2322 | | /* SetGCPs() */ |
2323 | | /************************************************************************/ |
2324 | | |
2325 | | CPLErr NITFDataset::SetGCPs(int nGCPCountIn, const GDAL_GCP *pasGCPListIn, |
2326 | | const OGRSpatialReference *poGCPSRSIn) |
2327 | 0 | { |
2328 | 0 | if (nGCPCountIn != 4) |
2329 | 0 | { |
2330 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
2331 | 0 | "NITF only supports writing 4 GCPs."); |
2332 | 0 | return CE_Failure; |
2333 | 0 | } |
2334 | | |
2335 | | /* Free previous GCPs */ |
2336 | 0 | GDALDeinitGCPs(nGCPCount, pasGCPList); |
2337 | 0 | CPLFree(pasGCPList); |
2338 | | |
2339 | | /* Duplicate in GCPs */ |
2340 | 0 | nGCPCount = nGCPCountIn; |
2341 | 0 | pasGCPList = GDALDuplicateGCPs(nGCPCount, pasGCPListIn); |
2342 | |
|
2343 | 0 | m_oGCPSRS.Clear(); |
2344 | 0 | if (poGCPSRSIn) |
2345 | 0 | m_oGCPSRS = *poGCPSRSIn; |
2346 | |
|
2347 | 0 | int iUL = -1; |
2348 | 0 | int iUR = -1; |
2349 | 0 | int iLR = -1; |
2350 | 0 | int iLL = -1; |
2351 | |
|
2352 | 0 | #define EPS_GCP 1e-5 |
2353 | 0 | for (int i = 0; i < 4; i++) |
2354 | 0 | { |
2355 | 0 | if (fabs(pasGCPList[i].dfGCPPixel - 0.5) < EPS_GCP && |
2356 | 0 | fabs(pasGCPList[i].dfGCPLine - 0.5) < EPS_GCP) |
2357 | 0 | iUL = i; |
2358 | | |
2359 | 0 | else if (fabs(pasGCPList[i].dfGCPPixel - (nRasterXSize - 0.5)) < |
2360 | 0 | EPS_GCP && |
2361 | 0 | fabs(pasGCPList[i].dfGCPLine - 0.5) < EPS_GCP) |
2362 | 0 | iUR = i; |
2363 | | |
2364 | 0 | else if (fabs(pasGCPList[i].dfGCPPixel - (nRasterXSize - 0.5)) < |
2365 | 0 | EPS_GCP && |
2366 | 0 | fabs(pasGCPList[i].dfGCPLine - (nRasterYSize - 0.5)) < EPS_GCP) |
2367 | 0 | iLR = i; |
2368 | | |
2369 | 0 | else if (fabs(pasGCPList[i].dfGCPPixel - 0.5) < EPS_GCP && |
2370 | 0 | fabs(pasGCPList[i].dfGCPLine - (nRasterYSize - 0.5)) < EPS_GCP) |
2371 | 0 | iLL = i; |
2372 | 0 | } |
2373 | |
|
2374 | 0 | if (iUL < 0 || iUR < 0 || iLR < 0 || iLL < 0) |
2375 | 0 | { |
2376 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
2377 | 0 | "The 4 GCPs image coordinates must be exactly " |
2378 | 0 | "at the *center* of the 4 corners of the image " |
2379 | 0 | "( (%.1f, %.1f), (%.1f %.1f), (%.1f %.1f), (%.1f %.1f) ).", |
2380 | 0 | 0.5, 0.5, nRasterYSize - 0.5, 0.5, nRasterXSize - 0.5, |
2381 | 0 | nRasterYSize - 0.5, nRasterXSize - 0.5, 0.5); |
2382 | 0 | return CE_Failure; |
2383 | 0 | } |
2384 | | |
2385 | 0 | double dfIGEOLOULX = pasGCPList[iUL].dfGCPX; |
2386 | 0 | double dfIGEOLOULY = pasGCPList[iUL].dfGCPY; |
2387 | 0 | double dfIGEOLOURX = pasGCPList[iUR].dfGCPX; |
2388 | 0 | double dfIGEOLOURY = pasGCPList[iUR].dfGCPY; |
2389 | 0 | double dfIGEOLOLRX = pasGCPList[iLR].dfGCPX; |
2390 | 0 | double dfIGEOLOLRY = pasGCPList[iLR].dfGCPY; |
2391 | 0 | double dfIGEOLOLLX = pasGCPList[iLL].dfGCPX; |
2392 | 0 | double dfIGEOLOLLY = pasGCPList[iLL].dfGCPY; |
2393 | | |
2394 | | /* To recompute the zone */ |
2395 | 0 | OGRSpatialReference oSRSBackup = m_oSRS; |
2396 | 0 | CPLErr eErr = SetSpatialRef(&m_oGCPSRS); |
2397 | 0 | m_oSRS = std::move(oSRSBackup); |
2398 | |
|
2399 | 0 | if (eErr != CE_None) |
2400 | 0 | return eErr; |
2401 | | |
2402 | 0 | if (NITFWriteIGEOLO(psImage, psImage->chICORDS, psImage->nZone, dfIGEOLOULX, |
2403 | 0 | dfIGEOLOULY, dfIGEOLOURX, dfIGEOLOURY, dfIGEOLOLRX, |
2404 | 0 | dfIGEOLOLRY, dfIGEOLOLLX, dfIGEOLOLLY)) |
2405 | 0 | return CE_None; |
2406 | | |
2407 | 0 | return CE_Failure; |
2408 | 0 | } |
2409 | | |
2410 | | /************************************************************************/ |
2411 | | /* GetSpatialRef() */ |
2412 | | /************************************************************************/ |
2413 | | |
2414 | | const OGRSpatialReference *NITFDataset::GetSpatialRef() const |
2415 | | |
2416 | 335 | { |
2417 | 335 | if (bGotGeoTransform) |
2418 | 29 | return &m_oSRS; |
2419 | | |
2420 | 306 | return GDALPamDataset::GetSpatialRef(); |
2421 | 335 | } |
2422 | | |
2423 | | /************************************************************************/ |
2424 | | /* SetSpatialRef() */ |
2425 | | /************************************************************************/ |
2426 | | |
2427 | | CPLErr NITFDataset::SetSpatialRef(const OGRSpatialReference *poSRS) |
2428 | | |
2429 | 1 | { |
2430 | 1 | int bNorth; |
2431 | 1 | OGRSpatialReference oSRS, oSRS_WGS84; |
2432 | | |
2433 | 1 | if (poSRS == nullptr) |
2434 | 0 | return CE_Failure; |
2435 | | |
2436 | 1 | oSRS_WGS84.SetWellKnownGeogCS("WGS84"); |
2437 | 1 | if (poSRS->IsSameGeogCS(&oSRS_WGS84) == FALSE) |
2438 | 1 | { |
2439 | 1 | CPLError(CE_Failure, CPLE_NotSupported, |
2440 | 1 | "NITF only supports WGS84 geographic and UTM projections.\n"); |
2441 | 1 | return CE_Failure; |
2442 | 1 | } |
2443 | | |
2444 | 0 | if (poSRS->IsGeographic() && poSRS->GetPrimeMeridian() == 0.0) |
2445 | 0 | { |
2446 | 0 | if (psImage->chICORDS != 'G' && psImage->chICORDS != 'D') |
2447 | 0 | { |
2448 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
2449 | 0 | "NITF file should have been created with creation option " |
2450 | 0 | "'ICORDS=G' (or 'ICORDS=D').\n"); |
2451 | 0 | return CE_Failure; |
2452 | 0 | } |
2453 | 0 | } |
2454 | 0 | else if (poSRS->GetUTMZone(&bNorth) > 0) |
2455 | 0 | { |
2456 | 0 | if (bNorth && psImage->chICORDS != 'N') |
2457 | 0 | { |
2458 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
2459 | 0 | "NITF file should have been created with creation option " |
2460 | 0 | "'ICORDS=N'.\n"); |
2461 | 0 | return CE_Failure; |
2462 | 0 | } |
2463 | 0 | else if (!bNorth && psImage->chICORDS != 'S') |
2464 | 0 | { |
2465 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
2466 | 0 | "NITF file should have been created with creation option " |
2467 | 0 | "'ICORDS=S'.\n"); |
2468 | 0 | return CE_Failure; |
2469 | 0 | } |
2470 | | |
2471 | 0 | psImage->nZone = poSRS->GetUTMZone(nullptr); |
2472 | 0 | } |
2473 | 0 | else |
2474 | 0 | { |
2475 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
2476 | 0 | "NITF only supports WGS84 geographic and UTM projections.\n"); |
2477 | 0 | return CE_Failure; |
2478 | 0 | } |
2479 | | |
2480 | 0 | m_oSRS = *poSRS; |
2481 | |
|
2482 | 0 | if (bGotGeoTransform) |
2483 | 0 | SetGeoTransform(m_gt); |
2484 | |
|
2485 | 0 | return CE_None; |
2486 | 0 | } |
2487 | | |
2488 | | #ifdef ESRI_BUILD |
2489 | | /************************************************************************/ |
2490 | | /* InitializeNITFDESMetadata() */ |
2491 | | /************************************************************************/ |
2492 | | |
2493 | | void NITFDataset::InitializeNITFDESMetadata() |
2494 | | { |
2495 | | static const char *const pszDESMetadataDomain = "NITF_DES_METADATA"; |
2496 | | static const char *const pszDESsDomain = "xml:DES"; |
2497 | | static const char *const pszMDXmlDataContentDESDATA = |
2498 | | "NITF_DES_XML_DATA_CONTENT_DESDATA"; |
2499 | | static const char *const pszXmlDataContent = "XML_DATA_CONTENT"; |
2500 | | constexpr int idxXmlDataContentDESDATA = 973; |
2501 | | static const int sizeXmlDataContent = |
2502 | | static_cast<int>(strlen(pszXmlDataContent)); |
2503 | | |
2504 | | char **ppszDESMetadataList = oSpecialMD.GetMetadata(pszDESMetadataDomain); |
2505 | | |
2506 | | if (ppszDESMetadataList != NULL) |
2507 | | return; |
2508 | | |
2509 | | char **ppszDESsList = this->GetMetadata(pszDESsDomain); |
2510 | | |
2511 | | if (ppszDESsList == NULL) |
2512 | | return; |
2513 | | |
2514 | | bool foundXmlDataContent = false; |
2515 | | char *pachNITFDES = NULL; |
2516 | | |
2517 | | // Set metadata "NITF_DES_XML_DATA_CONTENT_DESDATA". |
2518 | | // NOTE: There should only be one instance of XML_DATA_CONTENT DES. |
2519 | | |
2520 | | while (((pachNITFDES = *ppszDESsList) != NULL) && (!foundXmlDataContent)) |
2521 | | { |
2522 | | // The data stream has been Base64 encoded, need to decode it. |
2523 | | // NOTE: The actual length of the DES data stream is appended at the |
2524 | | // beginning of the encoded |
2525 | | // data and is separated by a space. |
2526 | | |
2527 | | const char *pszSpace = strchr(pachNITFDES, ' '); |
2528 | | |
2529 | | char *pszData = NULL; |
2530 | | int nDataLen = 0; |
2531 | | if (pszSpace) |
2532 | | { |
2533 | | pszData = CPLStrdup(pszSpace + 1); |
2534 | | nDataLen = |
2535 | | CPLBase64DecodeInPlace(reinterpret_cast<GByte *>(pszData)); |
2536 | | pszData[nDataLen] = 0; |
2537 | | } |
2538 | | |
2539 | | if (nDataLen > 2 + sizeXmlDataContent && STARTS_WITH_CI(pszData, "DE")) |
2540 | | { |
2541 | | // Check to see if this is a XML_DATA_CONTENT DES. |
2542 | | if (EQUALN(pszData + 2, pszXmlDataContent, sizeXmlDataContent) && |
2543 | | nDataLen > idxXmlDataContentDESDATA) |
2544 | | { |
2545 | | foundXmlDataContent = true; |
2546 | | |
2547 | | // Get the value of the DESDATA field and set metadata |
2548 | | // "NITF_DES_XML_DATA_CONTENT_DESDATA". |
2549 | | const char *pszXML = pszData + idxXmlDataContentDESDATA; |
2550 | | |
2551 | | // Set the metadata. |
2552 | | oSpecialMD.SetMetadataItem(pszMDXmlDataContentDESDATA, pszXML, |
2553 | | pszDESMetadataDomain); |
2554 | | } |
2555 | | } |
2556 | | |
2557 | | CPLFree(pszData); |
2558 | | |
2559 | | pachNITFDES = NULL; |
2560 | | ppszDESsList += 1; |
2561 | | } |
2562 | | } |
2563 | | |
2564 | | /************************************************************************/ |
2565 | | /* InitializeNITFTREs() */ |
2566 | | /************************************************************************/ |
2567 | | |
2568 | | void NITFDataset::InitializeNITFTREs() |
2569 | | { |
2570 | | static const char *const pszFileHeaderTREsDomain = "NITF_FILE_HEADER_TRES"; |
2571 | | static const char *const pszImageSegmentTREsDomain = |
2572 | | "NITF_IMAGE_SEGMENT_TRES"; |
2573 | | |
2574 | | char **ppszFileHeaderTREsList = |
2575 | | oSpecialMD.GetMetadata(pszFileHeaderTREsDomain); |
2576 | | char **ppszImageSegmentTREsList = |
2577 | | oSpecialMD.GetMetadata(pszImageSegmentTREsDomain); |
2578 | | |
2579 | | if ((ppszFileHeaderTREsList != NULL) && (ppszImageSegmentTREsList != NULL)) |
2580 | | return; |
2581 | | |
2582 | | /* -------------------------------------------------------------------- */ |
2583 | | /* Loop over TRE sources (file and image). */ |
2584 | | /* -------------------------------------------------------------------- */ |
2585 | | |
2586 | | for (int nTRESrc = 0; nTRESrc < 2; nTRESrc++) |
2587 | | { |
2588 | | int nTREBytes = 0; |
2589 | | char *pszTREData = NULL; |
2590 | | const char *pszTREsDomain = NULL; |
2591 | | CPLStringList aosList; |
2592 | | |
2593 | | /* -------------------------------------------------------------------- |
2594 | | */ |
2595 | | /* Extract file header or image segment TREs. */ |
2596 | | /* -------------------------------------------------------------------- |
2597 | | */ |
2598 | | |
2599 | | if (nTRESrc == 0) |
2600 | | { |
2601 | | if (ppszFileHeaderTREsList != NULL) |
2602 | | continue; |
2603 | | |
2604 | | nTREBytes = psFile->nTREBytes; |
2605 | | pszTREData = psFile->pachTRE; |
2606 | | pszTREsDomain = pszFileHeaderTREsDomain; |
2607 | | } |
2608 | | else |
2609 | | { |
2610 | | if (ppszImageSegmentTREsList != NULL) |
2611 | | continue; |
2612 | | |
2613 | | if (psImage) |
2614 | | { |
2615 | | nTREBytes = psImage->nTREBytes; |
2616 | | pszTREData = psImage->pachTRE; |
2617 | | pszTREsDomain = pszImageSegmentTREsDomain; |
2618 | | } |
2619 | | else |
2620 | | { |
2621 | | nTREBytes = 0; |
2622 | | pszTREData = NULL; |
2623 | | } |
2624 | | } |
2625 | | |
2626 | | /* -------------------------------------------------------------------- |
2627 | | */ |
2628 | | /* Loop over TREs. */ |
2629 | | /* -------------------------------------------------------------------- |
2630 | | */ |
2631 | | |
2632 | | while (nTREBytes >= 11) |
2633 | | { |
2634 | | char szTemp[100]; |
2635 | | char szTag[7]; |
2636 | | char *pszEscapedData = NULL; |
2637 | | int nThisTRESize = atoi(NITFGetField(szTemp, pszTREData, 6, 5)); |
2638 | | |
2639 | | if (nThisTRESize < 0) |
2640 | | { |
2641 | | NITFGetField(szTemp, pszTREData, 0, 6); |
2642 | | CPLError(CE_Failure, CPLE_AppDefined, |
2643 | | "Invalid size (%d) for TRE %s", nThisTRESize, szTemp); |
2644 | | return; |
2645 | | } |
2646 | | |
2647 | | if (nThisTRESize > nTREBytes - 11) |
2648 | | { |
2649 | | CPLError(CE_Failure, CPLE_AppDefined, |
2650 | | "Not enough bytes in TRE"); |
2651 | | return; |
2652 | | } |
2653 | | |
2654 | | strncpy(szTag, pszTREData, 6); |
2655 | | szTag[6] = '\0'; |
2656 | | |
2657 | | // trim white off tag. |
2658 | | while (strlen(szTag) > 0 && szTag[strlen(szTag) - 1] == ' ') |
2659 | | szTag[strlen(szTag) - 1] = '\0'; |
2660 | | |
2661 | | // escape data. |
2662 | | pszEscapedData = CPLEscapeString(pszTREData + 6, nThisTRESize + 5, |
2663 | | CPLES_BackslashQuotable); |
2664 | | |
2665 | | const size_t nLineLen = strlen(szTag) + strlen(pszEscapedData) + 2; |
2666 | | char *pszLine = static_cast<char *>(CPLMalloc(nLineLen)); |
2667 | | snprintf(pszLine, nLineLen, "%s=%s", szTag, pszEscapedData); |
2668 | | aosList.AddString(pszLine); |
2669 | | CPLFree(pszLine); |
2670 | | pszLine = NULL; |
2671 | | |
2672 | | CPLFree(pszEscapedData); |
2673 | | pszEscapedData = NULL; |
2674 | | |
2675 | | nTREBytes -= (nThisTRESize + 11); |
2676 | | pszTREData += (nThisTRESize + 11); |
2677 | | } |
2678 | | |
2679 | | if (!aosList.empty()) |
2680 | | oSpecialMD.SetMetadata(aosList.List(), pszTREsDomain); |
2681 | | } |
2682 | | } |
2683 | | #endif |
2684 | | |
2685 | | /************************************************************************/ |
2686 | | /* InitializeNITFDESs() */ |
2687 | | /************************************************************************/ |
2688 | | |
2689 | | bool NITFDataset::InitializeNITFDESs(bool bValidate) |
2690 | 0 | { |
2691 | 0 | char **papszDESsList = oSpecialMD.GetMetadata("xml:DES"); |
2692 | |
|
2693 | 0 | if (papszDESsList != nullptr) |
2694 | 0 | { |
2695 | 0 | return true; |
2696 | 0 | } |
2697 | | |
2698 | 0 | bool bSuccess = true; |
2699 | 0 | CPLXMLNode *psDesListNode = |
2700 | 0 | CPLCreateXMLNode(nullptr, CXT_Element, "des_list"); |
2701 | |
|
2702 | 0 | for (int iSegment = 0; iSegment < psFile->nSegmentCount; iSegment++) |
2703 | 0 | { |
2704 | 0 | NITFSegmentInfo *psSegInfo = psFile->pasSegmentInfo + iSegment; |
2705 | |
|
2706 | 0 | if (EQUAL(psSegInfo->szSegmentType, "DE")) |
2707 | 0 | { |
2708 | 0 | bool bGotError = false; |
2709 | 0 | CPLXMLNode *psDesNode = |
2710 | 0 | NITFDESGetXml(psFile, iSegment, bValidate, &bGotError); |
2711 | 0 | if (bGotError) |
2712 | 0 | bSuccess = false; |
2713 | |
|
2714 | 0 | if (psDesNode != nullptr) |
2715 | 0 | { |
2716 | 0 | CPLAddXMLChild(psDesListNode, psDesNode); |
2717 | 0 | } |
2718 | 0 | } |
2719 | 0 | } |
2720 | |
|
2721 | 0 | if (psDesListNode->psChild != nullptr) |
2722 | 0 | { |
2723 | 0 | char *pszXML = CPLSerializeXMLTree(psDesListNode); |
2724 | 0 | char *apszMD[2] = {pszXML, nullptr}; |
2725 | 0 | oSpecialMD.SetMetadata(apszMD, "xml:DES"); |
2726 | 0 | CPLFree(pszXML); |
2727 | 0 | } |
2728 | 0 | CPLDestroyXMLNode(psDesListNode); |
2729 | 0 | return bSuccess; |
2730 | 0 | } |
2731 | | |
2732 | | /************************************************************************/ |
2733 | | /* InitializeNITFMetadata() */ |
2734 | | /************************************************************************/ |
2735 | | |
2736 | | void NITFDataset::InitializeNITFMetadata() |
2737 | | |
2738 | 0 | { |
2739 | 0 | static const char *const pszDomainName = "NITF_METADATA"; |
2740 | 0 | static const char *const pszTagNITFFileHeader = "NITFFileHeader"; |
2741 | 0 | static const char *const pszTagNITFImageSubheader = "NITFImageSubheader"; |
2742 | |
|
2743 | 0 | if (oSpecialMD.GetMetadata(pszDomainName) != nullptr) |
2744 | 0 | return; |
2745 | | |
2746 | | // nHeaderLenOffset is the number of bytes to skip from the beginning of the |
2747 | | // NITF file header in order to get to the field HL (NITF file header |
2748 | | // length). |
2749 | | |
2750 | 0 | int nHeaderLen = 0; |
2751 | 0 | int nHeaderLenOffset = 0; |
2752 | | |
2753 | | // Get the NITF file header length. |
2754 | |
|
2755 | 0 | if (psFile->pachHeader != nullptr) |
2756 | 0 | { |
2757 | 0 | if ((STARTS_WITH(psFile->pachHeader, "NITF02.10")) || |
2758 | 0 | (STARTS_WITH(psFile->pachHeader, "NSIF01.00"))) |
2759 | 0 | nHeaderLenOffset = 354; |
2760 | 0 | else if ((STARTS_WITH(psFile->pachHeader, "NITF01.10")) || |
2761 | 0 | (STARTS_WITH(psFile->pachHeader, "NITF02.00"))) |
2762 | 0 | nHeaderLenOffset = |
2763 | 0 | (STARTS_WITH((psFile->pachHeader + 280), "999998")) ? 394 : 354; |
2764 | 0 | } |
2765 | |
|
2766 | 0 | char fieldHL[7]; |
2767 | |
|
2768 | 0 | if (nHeaderLenOffset > 0) |
2769 | 0 | { |
2770 | 0 | char *pszFieldHL = psFile->pachHeader + nHeaderLenOffset; |
2771 | |
|
2772 | 0 | memcpy(fieldHL, pszFieldHL, 6); |
2773 | 0 | fieldHL[6] = '\0'; |
2774 | 0 | nHeaderLen = atoi(fieldHL); |
2775 | 0 | } |
2776 | |
|
2777 | 0 | if (nHeaderLen <= 0) |
2778 | 0 | { |
2779 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Zero length NITF file header!"); |
2780 | 0 | return; |
2781 | 0 | } |
2782 | | |
2783 | 0 | char *encodedHeader = CPLBase64Encode( |
2784 | 0 | nHeaderLen, reinterpret_cast<GByte *>(psFile->pachHeader)); |
2785 | |
|
2786 | 0 | if (encodedHeader == nullptr || strlen(encodedHeader) == 0) |
2787 | 0 | { |
2788 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2789 | 0 | "Failed to encode NITF file header!"); |
2790 | 0 | CPLFree(encodedHeader); |
2791 | 0 | return; |
2792 | 0 | } |
2793 | | |
2794 | | // The length of the NITF file header plus a space is append to the |
2795 | | // beginning of the encoded string so that we can recover the length of the |
2796 | | // NITF file header when we decode it without having to pull it out the HL |
2797 | | // field again. |
2798 | | |
2799 | 0 | std::string nitfFileheaderStr(fieldHL); |
2800 | 0 | nitfFileheaderStr.append(" "); |
2801 | 0 | nitfFileheaderStr.append(encodedHeader); |
2802 | |
|
2803 | 0 | CPLFree(encodedHeader); |
2804 | |
|
2805 | 0 | oSpecialMD.SetMetadataItem(pszTagNITFFileHeader, nitfFileheaderStr.c_str(), |
2806 | 0 | pszDomainName); |
2807 | | |
2808 | | // Get the image subheader length. |
2809 | |
|
2810 | 0 | int nImageSubheaderLen = 0; |
2811 | |
|
2812 | 0 | if (psImage != nullptr && |
2813 | 0 | STARTS_WITH(psFile->pasSegmentInfo[psImage->iSegment].szSegmentType, |
2814 | 0 | "IM")) |
2815 | 0 | { |
2816 | 0 | nImageSubheaderLen = |
2817 | 0 | psFile->pasSegmentInfo[psImage->iSegment].nSegmentHeaderSize; |
2818 | 0 | } |
2819 | |
|
2820 | 0 | if (nImageSubheaderLen < 0) |
2821 | 0 | { |
2822 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2823 | 0 | "Invalid length NITF image subheader!"); |
2824 | 0 | return; |
2825 | 0 | } |
2826 | | |
2827 | 0 | if (nImageSubheaderLen > 0) |
2828 | 0 | { |
2829 | 0 | char *encodedImageSubheader = CPLBase64Encode( |
2830 | 0 | nImageSubheaderLen, reinterpret_cast<GByte *>(psImage->pachHeader)); |
2831 | |
|
2832 | 0 | if (encodedImageSubheader == nullptr || |
2833 | 0 | strlen(encodedImageSubheader) == 0) |
2834 | 0 | { |
2835 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2836 | 0 | "Failed to encode image subheader!"); |
2837 | 0 | CPLFree(encodedImageSubheader); |
2838 | 0 | return; |
2839 | 0 | } |
2840 | | |
2841 | | // The length of the image subheader plus a space is append to the |
2842 | | // beginning of the encoded string so that we can recover the actual |
2843 | | // length of the image subheader when we decode it. |
2844 | | |
2845 | 0 | char buffer[20]; |
2846 | |
|
2847 | 0 | snprintf(buffer, sizeof(buffer), "%d", nImageSubheaderLen); |
2848 | |
|
2849 | 0 | std::string imageSubheaderStr(buffer); |
2850 | 0 | imageSubheaderStr.append(" "); |
2851 | 0 | imageSubheaderStr.append(encodedImageSubheader); |
2852 | |
|
2853 | 0 | CPLFree(encodedImageSubheader); |
2854 | |
|
2855 | 0 | oSpecialMD.SetMetadataItem(pszTagNITFImageSubheader, |
2856 | 0 | imageSubheaderStr.c_str(), pszDomainName); |
2857 | 0 | } |
2858 | 0 | } |
2859 | | |
2860 | | /************************************************************************/ |
2861 | | /* InitializeCGMMetadata() */ |
2862 | | /************************************************************************/ |
2863 | | |
2864 | | void NITFDataset::InitializeCGMMetadata() |
2865 | | |
2866 | 0 | { |
2867 | 0 | if (oSpecialMD.GetMetadataItem("SEGMENT_COUNT", "CGM") != nullptr) |
2868 | 0 | return; |
2869 | | |
2870 | 0 | int iCGM = 0; |
2871 | 0 | char **papszCGMMetadata = CSLSetNameValue(nullptr, "SEGMENT_COUNT", "0"); |
2872 | | |
2873 | | /* ==================================================================== */ |
2874 | | /* Process all graphics segments. */ |
2875 | | /* ==================================================================== */ |
2876 | 0 | for (int iSegment = 0; iSegment < psFile->nSegmentCount; iSegment++) |
2877 | 0 | { |
2878 | 0 | NITFSegmentInfo *psSegment = psFile->pasSegmentInfo + iSegment; |
2879 | |
|
2880 | 0 | if (!EQUAL(psSegment->szSegmentType, "GR") && |
2881 | 0 | !EQUAL(psSegment->szSegmentType, "SY")) |
2882 | 0 | continue; |
2883 | | |
2884 | 0 | papszCGMMetadata = CSLSetNameValue( |
2885 | 0 | papszCGMMetadata, CPLString().Printf("SEGMENT_%d_SLOC_ROW", iCGM), |
2886 | 0 | CPLString().Printf("%d", psSegment->nLOC_R)); |
2887 | 0 | papszCGMMetadata = CSLSetNameValue( |
2888 | 0 | papszCGMMetadata, CPLString().Printf("SEGMENT_%d_SLOC_COL", iCGM), |
2889 | 0 | CPLString().Printf("%d", psSegment->nLOC_C)); |
2890 | |
|
2891 | 0 | papszCGMMetadata = CSLSetNameValue( |
2892 | 0 | papszCGMMetadata, CPLString().Printf("SEGMENT_%d_CCS_ROW", iCGM), |
2893 | 0 | CPLString().Printf("%d", psSegment->nCCS_R)); |
2894 | 0 | papszCGMMetadata = CSLSetNameValue( |
2895 | 0 | papszCGMMetadata, CPLString().Printf("SEGMENT_%d_CCS_COL", iCGM), |
2896 | 0 | CPLString().Printf("%d", psSegment->nCCS_C)); |
2897 | |
|
2898 | 0 | papszCGMMetadata = CSLSetNameValue( |
2899 | 0 | papszCGMMetadata, CPLString().Printf("SEGMENT_%d_SDLVL", iCGM), |
2900 | 0 | CPLString().Printf("%d", psSegment->nDLVL)); |
2901 | 0 | papszCGMMetadata = CSLSetNameValue( |
2902 | 0 | papszCGMMetadata, CPLString().Printf("SEGMENT_%d_SALVL", iCGM), |
2903 | 0 | CPLString().Printf("%d", psSegment->nALVL)); |
2904 | | |
2905 | | /* -------------------------------------------------------------------- |
2906 | | */ |
2907 | | /* Load the raw CGM data itself. */ |
2908 | | /* -------------------------------------------------------------------- |
2909 | | */ |
2910 | |
|
2911 | 0 | char *pabyCGMData = static_cast<char *>(VSI_CALLOC_VERBOSE( |
2912 | 0 | 1, static_cast<size_t>(psSegment->nSegmentSize))); |
2913 | 0 | if (pabyCGMData == nullptr) |
2914 | 0 | { |
2915 | 0 | CSLDestroy(papszCGMMetadata); |
2916 | 0 | return; |
2917 | 0 | } |
2918 | 0 | if (VSIFSeekL(psFile->fp, psSegment->nSegmentStart, SEEK_SET) != 0 || |
2919 | 0 | VSIFReadL(pabyCGMData, 1, |
2920 | 0 | static_cast<size_t>(psSegment->nSegmentSize), |
2921 | 0 | psFile->fp) != psSegment->nSegmentSize) |
2922 | 0 | { |
2923 | 0 | CPLError(CE_Warning, CPLE_FileIO, |
2924 | 0 | "Failed to read " CPL_FRMT_GUIB |
2925 | 0 | " bytes of graphic data at " CPL_FRMT_GUIB ".", |
2926 | 0 | psSegment->nSegmentSize, psSegment->nSegmentStart); |
2927 | 0 | CPLFree(pabyCGMData); |
2928 | 0 | CSLDestroy(papszCGMMetadata); |
2929 | 0 | return; |
2930 | 0 | } |
2931 | | |
2932 | 0 | char *pszEscapedCGMData = CPLEscapeString( |
2933 | 0 | pabyCGMData, static_cast<int>(psSegment->nSegmentSize), |
2934 | 0 | CPLES_BackslashQuotable); |
2935 | |
|
2936 | 0 | if (pszEscapedCGMData == nullptr) |
2937 | 0 | { |
2938 | 0 | CPLFree(pabyCGMData); |
2939 | 0 | CSLDestroy(papszCGMMetadata); |
2940 | 0 | return; |
2941 | 0 | } |
2942 | | |
2943 | 0 | papszCGMMetadata = CSLSetNameValue( |
2944 | 0 | papszCGMMetadata, CPLString().Printf("SEGMENT_%d_DATA", iCGM), |
2945 | 0 | pszEscapedCGMData); |
2946 | 0 | CPLFree(pszEscapedCGMData); |
2947 | 0 | CPLFree(pabyCGMData); |
2948 | |
|
2949 | 0 | iCGM++; |
2950 | 0 | } |
2951 | | |
2952 | | /* -------------------------------------------------------------------- */ |
2953 | | /* Record the CGM segment count. */ |
2954 | | /* -------------------------------------------------------------------- */ |
2955 | 0 | papszCGMMetadata = CSLSetNameValue(papszCGMMetadata, "SEGMENT_COUNT", |
2956 | 0 | CPLString().Printf("%d", iCGM)); |
2957 | |
|
2958 | 0 | oSpecialMD.SetMetadata(papszCGMMetadata, "CGM"); |
2959 | |
|
2960 | 0 | CSLDestroy(papszCGMMetadata); |
2961 | 0 | } |
2962 | | |
2963 | | /************************************************************************/ |
2964 | | /* InitializeTextMetadata() */ |
2965 | | /************************************************************************/ |
2966 | | |
2967 | | void NITFDataset::InitializeTextMetadata() |
2968 | | |
2969 | 0 | { |
2970 | 0 | if (oSpecialMD.GetMetadata("TEXT") != nullptr) |
2971 | 0 | return; |
2972 | | |
2973 | 0 | int iText = 0; |
2974 | | |
2975 | | /* ==================================================================== */ |
2976 | | /* Process all text segments. */ |
2977 | | /* ==================================================================== */ |
2978 | 0 | for (int iSegment = 0; iSegment < psFile->nSegmentCount; iSegment++) |
2979 | 0 | { |
2980 | 0 | NITFSegmentInfo *psSegment = psFile->pasSegmentInfo + iSegment; |
2981 | |
|
2982 | 0 | if (!EQUAL(psSegment->szSegmentType, "TX")) |
2983 | 0 | continue; |
2984 | | |
2985 | | /* -------------------------------------------------------------------- |
2986 | | */ |
2987 | | /* Load the text header */ |
2988 | | /* -------------------------------------------------------------------- |
2989 | | */ |
2990 | | |
2991 | | /* Allocate one extra byte for the NULL terminating character */ |
2992 | 0 | char *pabyHeaderData = static_cast<char *>(CPLCalloc( |
2993 | 0 | 1, static_cast<size_t>(psSegment->nSegmentHeaderSize + 1))); |
2994 | 0 | if (VSIFSeekL(psFile->fp, psSegment->nSegmentHeaderStart, SEEK_SET) != |
2995 | 0 | 0 || |
2996 | 0 | VSIFReadL(pabyHeaderData, 1, |
2997 | 0 | static_cast<size_t>(psSegment->nSegmentHeaderSize), |
2998 | 0 | psFile->fp) != psSegment->nSegmentHeaderSize) |
2999 | 0 | { |
3000 | 0 | CPLError( |
3001 | 0 | CE_Warning, CPLE_FileIO, |
3002 | 0 | "Failed to read %d bytes of text header data at " CPL_FRMT_GUIB |
3003 | 0 | ".", |
3004 | 0 | psSegment->nSegmentHeaderSize, psSegment->nSegmentHeaderStart); |
3005 | 0 | CPLFree(pabyHeaderData); |
3006 | 0 | return; |
3007 | 0 | } |
3008 | | |
3009 | 0 | oSpecialMD.SetMetadataItem(CPLString().Printf("HEADER_%d", iText), |
3010 | 0 | pabyHeaderData, "TEXT"); |
3011 | 0 | CPLFree(pabyHeaderData); |
3012 | | |
3013 | | /* -------------------------------------------------------------------- |
3014 | | */ |
3015 | | /* Load the raw TEXT data itself. */ |
3016 | | /* -------------------------------------------------------------------- |
3017 | | */ |
3018 | | /* Allocate one extra byte for the NULL terminating character */ |
3019 | 0 | char *pabyTextData = static_cast<char *>(VSI_CALLOC_VERBOSE( |
3020 | 0 | 1, static_cast<size_t>(psSegment->nSegmentSize) + 1)); |
3021 | 0 | if (pabyTextData == nullptr) |
3022 | 0 | { |
3023 | 0 | return; |
3024 | 0 | } |
3025 | 0 | if (VSIFSeekL(psFile->fp, psSegment->nSegmentStart, SEEK_SET) != 0 || |
3026 | 0 | VSIFReadL(pabyTextData, 1, |
3027 | 0 | static_cast<size_t>(psSegment->nSegmentSize), |
3028 | 0 | psFile->fp) != psSegment->nSegmentSize) |
3029 | 0 | { |
3030 | 0 | CPLError(CE_Warning, CPLE_FileIO, |
3031 | 0 | "Failed to read " CPL_FRMT_GUIB |
3032 | 0 | " bytes of text data at " CPL_FRMT_GUIB ".", |
3033 | 0 | psSegment->nSegmentSize, psSegment->nSegmentStart); |
3034 | 0 | CPLFree(pabyTextData); |
3035 | 0 | return; |
3036 | 0 | } |
3037 | | |
3038 | 0 | oSpecialMD.SetMetadataItem(CPLString().Printf("DATA_%d", iText), |
3039 | 0 | pabyTextData, "TEXT"); |
3040 | 0 | CPLFree(pabyTextData); |
3041 | |
|
3042 | 0 | iText++; |
3043 | 0 | } |
3044 | 0 | } |
3045 | | |
3046 | | /************************************************************************/ |
3047 | | /* InitializeTREMetadata() */ |
3048 | | /************************************************************************/ |
3049 | | |
3050 | | bool NITFDataset::InitializeTREMetadata(bool bValidate) |
3051 | | |
3052 | 0 | { |
3053 | 0 | if (oSpecialMD.GetMetadata("TRE") != nullptr || |
3054 | 0 | oSpecialMD.GetMetadata("xml:TRE") != nullptr) |
3055 | 0 | return true; |
3056 | | |
3057 | 0 | bool bGotError = false; |
3058 | 0 | CPLXMLNode *psTresNode = CPLCreateXMLNode(nullptr, CXT_Element, "tres"); |
3059 | | |
3060 | | /* -------------------------------------------------------------------- */ |
3061 | | /* Loop over TRE sources (file and image). */ |
3062 | | /* -------------------------------------------------------------------- */ |
3063 | 0 | for (int nTRESrc = 0; nTRESrc < 2; nTRESrc++) |
3064 | 0 | { |
3065 | 0 | int nTREBytes = 0; |
3066 | 0 | char *pszTREData = nullptr; |
3067 | |
|
3068 | 0 | if (nTRESrc == 0) |
3069 | 0 | { |
3070 | 0 | nTREBytes = psFile->nTREBytes; |
3071 | 0 | pszTREData = psFile->pachTRE; |
3072 | 0 | } |
3073 | 0 | else |
3074 | 0 | { |
3075 | 0 | if (psImage) |
3076 | 0 | { |
3077 | 0 | nTREBytes = psImage->nTREBytes; |
3078 | 0 | pszTREData = psImage->pachTRE; |
3079 | 0 | } |
3080 | 0 | else |
3081 | 0 | { |
3082 | 0 | nTREBytes = 0; |
3083 | 0 | pszTREData = nullptr; |
3084 | 0 | } |
3085 | 0 | } |
3086 | | |
3087 | | /* -------------------------------------------------------------------- |
3088 | | */ |
3089 | | /* Loop over TREs. */ |
3090 | | /* -------------------------------------------------------------------- |
3091 | | */ |
3092 | |
|
3093 | 0 | while (nTREBytes >= 11) |
3094 | 0 | { |
3095 | 0 | char szTemp[100]; |
3096 | 0 | char szTag[7]; |
3097 | 0 | const int nThisTRESize = |
3098 | 0 | atoi(NITFGetField(szTemp, pszTREData, 6, 5)); |
3099 | |
|
3100 | 0 | if (nThisTRESize < 0) |
3101 | 0 | { |
3102 | 0 | NITFGetField(szTemp, pszTREData, 0, 6); |
3103 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
3104 | 0 | "Invalid size (%d) for TRE %s", nThisTRESize, szTemp); |
3105 | 0 | CPLDestroyXMLNode(psTresNode); |
3106 | 0 | bGotError = true; |
3107 | 0 | return bGotError; |
3108 | 0 | } |
3109 | 0 | if (nThisTRESize > nTREBytes - 11) |
3110 | 0 | { |
3111 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
3112 | 0 | "Not enough bytes in TRE"); |
3113 | 0 | CPLDestroyXMLNode(psTresNode); |
3114 | 0 | bGotError = true; |
3115 | 0 | return bGotError; |
3116 | 0 | } |
3117 | | |
3118 | 0 | strncpy(szTag, pszTREData, 6); |
3119 | 0 | szTag[6] = '\0'; |
3120 | | |
3121 | | // trim white off tag. |
3122 | 0 | while (strlen(szTag) > 0 && szTag[strlen(szTag) - 1] == ' ') |
3123 | 0 | szTag[strlen(szTag) - 1] = '\0'; |
3124 | |
|
3125 | 0 | CPLXMLNode *psTreNode = |
3126 | 0 | NITFCreateXMLTre(psFile, szTag, pszTREData + 11, nThisTRESize, |
3127 | 0 | bValidate, &bGotError); |
3128 | 0 | if (psTreNode) |
3129 | 0 | { |
3130 | 0 | CPLCreateXMLNode( |
3131 | 0 | CPLCreateXMLNode(psTreNode, CXT_Attribute, "location"), |
3132 | 0 | CXT_Text, nTRESrc == 0 ? "file" : "image"); |
3133 | 0 | CPLAddXMLChild(psTresNode, psTreNode); |
3134 | 0 | } |
3135 | | |
3136 | | // escape data. |
3137 | 0 | char *pszEscapedData = CPLEscapeString( |
3138 | 0 | pszTREData + 11, nThisTRESize, CPLES_BackslashQuotable); |
3139 | 0 | if (pszEscapedData == nullptr) |
3140 | 0 | { |
3141 | 0 | bGotError = true; |
3142 | 0 | } |
3143 | 0 | else |
3144 | 0 | { |
3145 | 0 | char szUniqueTag[32]; |
3146 | 0 | strcpy(szUniqueTag, szTag); |
3147 | 0 | int nCountUnique = 2; |
3148 | 0 | while (oSpecialMD.GetMetadataItem(szUniqueTag, "TRE") != |
3149 | 0 | nullptr) |
3150 | 0 | { |
3151 | 0 | snprintf(szUniqueTag, sizeof(szUniqueTag), "%s_%d", szTag, |
3152 | 0 | nCountUnique); |
3153 | 0 | nCountUnique++; |
3154 | 0 | } |
3155 | 0 | oSpecialMD.SetMetadataItem(szUniqueTag, pszEscapedData, "TRE"); |
3156 | 0 | CPLFree(pszEscapedData); |
3157 | 0 | } |
3158 | |
|
3159 | 0 | nTREBytes -= (nThisTRESize + 11); |
3160 | 0 | pszTREData += (nThisTRESize + 11); |
3161 | 0 | } |
3162 | 0 | } |
3163 | | |
3164 | | /* -------------------------------------------------------------------- */ |
3165 | | /* Loop over TRE in DES */ |
3166 | | /* -------------------------------------------------------------------- */ |
3167 | 0 | for (int iSegment = 0; iSegment < psFile->nSegmentCount; iSegment++) |
3168 | 0 | { |
3169 | 0 | NITFSegmentInfo *psSegInfo = psFile->pasSegmentInfo + iSegment; |
3170 | 0 | if (!EQUAL(psSegInfo->szSegmentType, "DE")) |
3171 | 0 | continue; |
3172 | | |
3173 | 0 | NITFDES *psDES = NITFDESAccess(psFile, iSegment); |
3174 | 0 | if (psDES == nullptr) |
3175 | 0 | continue; |
3176 | | |
3177 | 0 | char *pabyTREData = nullptr; |
3178 | 0 | int nOffset = 0; |
3179 | 0 | char szTREName[7]; |
3180 | 0 | int nThisTRESize; |
3181 | |
|
3182 | 0 | while (NITFDESGetTRE(psDES, nOffset, szTREName, &pabyTREData, |
3183 | 0 | &nThisTRESize)) |
3184 | 0 | { |
3185 | 0 | if (nThisTRESize == 0) |
3186 | 0 | { |
3187 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
3188 | 0 | "Invalid size=0 for TRE %s", szTREName); |
3189 | 0 | break; |
3190 | 0 | } |
3191 | | |
3192 | 0 | char *pszEscapedData = CPLEscapeString(pabyTREData, nThisTRESize, |
3193 | 0 | CPLES_BackslashQuotable); |
3194 | 0 | if (pszEscapedData == nullptr) |
3195 | 0 | { |
3196 | 0 | NITFDESFreeTREData(pabyTREData); |
3197 | 0 | bGotError = true; |
3198 | 0 | break; |
3199 | 0 | } |
3200 | | |
3201 | | // trim white off tag. |
3202 | 0 | while (strlen(szTREName) > 0 && |
3203 | 0 | szTREName[strlen(szTREName) - 1] == ' ') |
3204 | 0 | szTREName[strlen(szTREName) - 1] = '\0'; |
3205 | |
|
3206 | 0 | CPLXMLNode *psTreNode = |
3207 | 0 | NITFCreateXMLTre(psFile, szTREName, pabyTREData, nThisTRESize, |
3208 | 0 | bValidate, &bGotError); |
3209 | 0 | if (psTreNode) |
3210 | 0 | { |
3211 | 0 | const char *pszDESID = |
3212 | 0 | CSLFetchNameValue(psDES->papszMetadata, "DESID"); |
3213 | 0 | CPLCreateXMLNode( |
3214 | 0 | CPLCreateXMLNode(psTreNode, CXT_Attribute, "location"), |
3215 | 0 | CXT_Text, |
3216 | 0 | pszDESID ? CPLSPrintf("des %s", pszDESID) : "des"); |
3217 | 0 | CPLAddXMLChild(psTresNode, psTreNode); |
3218 | 0 | } |
3219 | |
|
3220 | 0 | char szUniqueTag[32]; |
3221 | 0 | strcpy(szUniqueTag, szTREName); |
3222 | 0 | int nCountUnique = 2; |
3223 | 0 | while (oSpecialMD.GetMetadataItem(szUniqueTag, "TRE") != nullptr) |
3224 | 0 | { |
3225 | 0 | snprintf(szUniqueTag, sizeof(szUniqueTag), "%s_%d", szTREName, |
3226 | 0 | nCountUnique); |
3227 | 0 | nCountUnique++; |
3228 | 0 | } |
3229 | 0 | oSpecialMD.SetMetadataItem(szUniqueTag, pszEscapedData, "TRE"); |
3230 | |
|
3231 | 0 | CPLFree(pszEscapedData); |
3232 | |
|
3233 | 0 | nOffset += 11 + nThisTRESize; |
3234 | |
|
3235 | 0 | NITFDESFreeTREData(pabyTREData); |
3236 | 0 | } |
3237 | |
|
3238 | 0 | NITFDESDeaccess(psDES); |
3239 | 0 | } |
3240 | |
|
3241 | 0 | if (psTresNode->psChild != nullptr) |
3242 | 0 | { |
3243 | 0 | char *pszXML = CPLSerializeXMLTree(psTresNode); |
3244 | 0 | char *apszMD[2] = {pszXML, nullptr}; |
3245 | 0 | oSpecialMD.SetMetadata(apszMD, "xml:TRE"); |
3246 | 0 | CPLFree(pszXML); |
3247 | 0 | } |
3248 | 0 | CPLDestroyXMLNode(psTresNode); |
3249 | |
|
3250 | 0 | return !bGotError; |
3251 | 0 | } |
3252 | | |
3253 | | /************************************************************************/ |
3254 | | /* GetMetadataDomainList() */ |
3255 | | /************************************************************************/ |
3256 | | |
3257 | | char **NITFDataset::GetMetadataDomainList() |
3258 | 0 | { |
3259 | 0 | return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(), |
3260 | 0 | TRUE, "NITF_METADATA", "xml:DES", |
3261 | 0 | "NITF_DES_METADATA", "NITF_FILE_HEADER_TRES", |
3262 | 0 | "NITF_IMAGE_SEGMENT_TRES", "CGM", "TEXT", |
3263 | 0 | "TRE", "xml:TRE", "OVERVIEWS", nullptr); |
3264 | 0 | } |
3265 | | |
3266 | | /************************************************************************/ |
3267 | | /* InitializeImageStructureMetadata() */ |
3268 | | /************************************************************************/ |
3269 | | |
3270 | | void NITFDataset::InitializeImageStructureMetadata() |
3271 | 0 | { |
3272 | 0 | if (oSpecialMD.GetMetadata("IMAGE_STRUCTURE") != nullptr) |
3273 | 0 | return; |
3274 | | |
3275 | 0 | oSpecialMD.SetMetadata(GDALPamDataset::GetMetadata("IMAGE_STRUCTURE"), |
3276 | 0 | "IMAGE_STRUCTURE"); |
3277 | 0 | if (poJ2KDataset) |
3278 | 0 | { |
3279 | 0 | const char *pszReversibility = poJ2KDataset->GetMetadataItem( |
3280 | 0 | "COMPRESSION_REVERSIBILITY", "IMAGE_STRUCTURE"); |
3281 | 0 | if (pszReversibility) |
3282 | 0 | { |
3283 | 0 | oSpecialMD.SetMetadataItem("COMPRESSION_REVERSIBILITY", |
3284 | 0 | pszReversibility, "IMAGE_STRUCTURE"); |
3285 | 0 | } |
3286 | 0 | } |
3287 | 0 | } |
3288 | | |
3289 | | /************************************************************************/ |
3290 | | /* GetMetadata() */ |
3291 | | /************************************************************************/ |
3292 | | |
3293 | | CSLConstList NITFDataset::GetMetadata(const char *pszDomain) |
3294 | | |
3295 | 201 | { |
3296 | 201 | if (pszDomain != nullptr && EQUAL(pszDomain, "NITF_METADATA")) |
3297 | 0 | { |
3298 | | // InitializeNITFMetadata retrieves the NITF file header and all image |
3299 | | // segment file headers. (NOTE: The returned strings are |
3300 | | // base64-encoded). |
3301 | |
|
3302 | 0 | InitializeNITFMetadata(); |
3303 | 0 | return oSpecialMD.GetMetadata(pszDomain); |
3304 | 0 | } |
3305 | | |
3306 | 201 | if (pszDomain != nullptr && EQUAL(pszDomain, "xml:DES")) |
3307 | 0 | { |
3308 | | // InitializeNITFDESs retrieves all the DES file headers (NOTE: The |
3309 | | // returned strings are base64-encoded). |
3310 | |
|
3311 | 0 | InitializeNITFDESs(false); |
3312 | 0 | return oSpecialMD.GetMetadata(pszDomain); |
3313 | 0 | } |
3314 | | |
3315 | | #ifdef ESRI_BUILD |
3316 | | if (pszDomain != NULL && EQUAL(pszDomain, "NITF_DES_METADATA")) |
3317 | | { |
3318 | | // InitializeNITFDESs retrieves all the DES file headers (NOTE: The |
3319 | | // returned strings are base64-encoded). |
3320 | | |
3321 | | InitializeNITFDESMetadata(false); |
3322 | | return oSpecialMD.GetMetadata(pszDomain); |
3323 | | } |
3324 | | |
3325 | | if (pszDomain != NULL && EQUAL(pszDomain, "NITF_FILE_HEADER_TRES")) |
3326 | | { |
3327 | | // InitializeNITFTREs retrieves all the TREs that are resides in the |
3328 | | // NITF file header and all the TREs that are resides in the current |
3329 | | // image segment. NOTE: the returned strings are backslash-escaped |
3330 | | |
3331 | | InitializeNITFTREs(); |
3332 | | return oSpecialMD.GetMetadata(pszDomain); |
3333 | | } |
3334 | | |
3335 | | if (pszDomain != NULL && EQUAL(pszDomain, "NITF_IMAGE_SEGMENT_TRES")) |
3336 | | { |
3337 | | // InitializeNITFTREs retrieves all the TREs that are resides in the |
3338 | | // NITF file header and all the TREs that are resides in the current |
3339 | | // image segment. NOTE: the returned strings are backslash-escaped |
3340 | | |
3341 | | InitializeNITFTREs(); |
3342 | | return oSpecialMD.GetMetadata(pszDomain); |
3343 | | } |
3344 | | #endif |
3345 | | |
3346 | 201 | if (pszDomain != nullptr && EQUAL(pszDomain, "CGM")) |
3347 | 0 | { |
3348 | 0 | InitializeCGMMetadata(); |
3349 | 0 | return oSpecialMD.GetMetadata(pszDomain); |
3350 | 0 | } |
3351 | | |
3352 | 201 | if (pszDomain != nullptr && EQUAL(pszDomain, "TEXT")) |
3353 | 0 | { |
3354 | 0 | InitializeTextMetadata(); |
3355 | 0 | return oSpecialMD.GetMetadata(pszDomain); |
3356 | 0 | } |
3357 | | |
3358 | 201 | if (pszDomain != nullptr && EQUAL(pszDomain, "TRE")) |
3359 | 0 | { |
3360 | 0 | InitializeTREMetadata(false); |
3361 | 0 | return oSpecialMD.GetMetadata(pszDomain); |
3362 | 0 | } |
3363 | | |
3364 | 201 | if (pszDomain != nullptr && EQUAL(pszDomain, "xml:TRE")) |
3365 | 0 | { |
3366 | 0 | InitializeTREMetadata(false); |
3367 | 0 | return oSpecialMD.GetMetadata(pszDomain); |
3368 | 0 | } |
3369 | | |
3370 | 201 | if (pszDomain != nullptr && EQUAL(pszDomain, "IMAGE_STRUCTURE") && |
3371 | 0 | poJ2KDataset) |
3372 | 0 | { |
3373 | 0 | InitializeImageStructureMetadata(); |
3374 | 0 | return oSpecialMD.GetMetadata(pszDomain); |
3375 | 0 | } |
3376 | | |
3377 | 201 | return GDALPamDataset::GetMetadata(pszDomain); |
3378 | 201 | } |
3379 | | |
3380 | | /************************************************************************/ |
3381 | | /* GetMetadataItem() */ |
3382 | | /************************************************************************/ |
3383 | | |
3384 | | const char *NITFDataset::GetMetadataItem(const char *pszName, |
3385 | | const char *pszDomain) |
3386 | | |
3387 | 1.45k | { |
3388 | 1.45k | if (pszDomain != nullptr && EQUAL(pszDomain, "NITF_METADATA")) |
3389 | 0 | { |
3390 | | // InitializeNITFMetadata retrieves the NITF file header and all image |
3391 | | // segment file headers. (NOTE: The returned strings are |
3392 | | // base64-encoded). |
3393 | |
|
3394 | 0 | InitializeNITFMetadata(); |
3395 | 0 | return oSpecialMD.GetMetadataItem(pszName, pszDomain); |
3396 | 0 | } |
3397 | | |
3398 | | #ifdef ESRI_BUILD |
3399 | | if (pszDomain != NULL && EQUAL(pszDomain, "NITF_DES_METADATA")) |
3400 | | { |
3401 | | // InitializeNITFDESs retrieves all the DES file headers (NOTE: The |
3402 | | // returned strings are base64-encoded). |
3403 | | |
3404 | | InitializeNITFDESMetadata(false); |
3405 | | return oSpecialMD.GetMetadataItem(pszName, pszDomain); |
3406 | | } |
3407 | | |
3408 | | if (pszDomain != NULL && EQUAL(pszDomain, "NITF_FILE_HEADER_TRES")) |
3409 | | { |
3410 | | // InitializeNITFTREs retrieves all the TREs that are resides in the |
3411 | | // NITF file header and all the TREs that are resides in the current |
3412 | | // image segment. NOTE: the returned strings are backslash-escaped |
3413 | | |
3414 | | InitializeNITFTREs(); |
3415 | | return oSpecialMD.GetMetadataItem(pszName, pszDomain); |
3416 | | } |
3417 | | |
3418 | | if (pszDomain != NULL && EQUAL(pszDomain, "NITF_IMAGE_SEGMENT_TRES")) |
3419 | | { |
3420 | | // InitializeNITFTREs retrieves all the TREs that are resides in the |
3421 | | // NITF file header and all the TREs that are resides in the current |
3422 | | // image segment. NOTE: the returned strings are backslash-escaped |
3423 | | |
3424 | | InitializeNITFTREs(); |
3425 | | return oSpecialMD.GetMetadataItem(pszName, pszDomain); |
3426 | | } |
3427 | | #endif |
3428 | | |
3429 | 1.45k | if (pszDomain != nullptr && EQUAL(pszDomain, "CGM")) |
3430 | 0 | { |
3431 | 0 | InitializeCGMMetadata(); |
3432 | 0 | return oSpecialMD.GetMetadataItem(pszName, pszDomain); |
3433 | 0 | } |
3434 | | |
3435 | 1.45k | if (pszDomain != nullptr && EQUAL(pszDomain, "TEXT")) |
3436 | 0 | { |
3437 | 0 | InitializeTextMetadata(); |
3438 | 0 | return oSpecialMD.GetMetadataItem(pszName, pszDomain); |
3439 | 0 | } |
3440 | | |
3441 | 1.45k | if (pszDomain != nullptr && EQUAL(pszDomain, "TRE")) |
3442 | 0 | { |
3443 | 0 | InitializeTREMetadata(false); |
3444 | 0 | return oSpecialMD.GetMetadataItem(pszName, pszDomain); |
3445 | 0 | } |
3446 | | |
3447 | 1.45k | if (pszDomain != nullptr && EQUAL(pszDomain, "OVERVIEWS") && |
3448 | 763 | !osRSetVRT.empty()) |
3449 | 0 | return osRSetVRT; |
3450 | | |
3451 | 1.45k | if (pszDomain != nullptr && EQUAL(pszDomain, "IMAGE_STRUCTURE") && |
3452 | 368 | poJ2KDataset && EQUAL(pszName, "COMPRESSION_REVERSIBILITY")) |
3453 | 0 | { |
3454 | 0 | InitializeImageStructureMetadata(); |
3455 | 0 | return oSpecialMD.GetMetadataItem(pszName, pszDomain); |
3456 | 0 | } |
3457 | | |
3458 | | // For unit test purposes |
3459 | 1.45k | if (pszDomain != nullptr && EQUAL(pszDomain, "DEBUG") && |
3460 | 0 | EQUAL(pszName, "JPEG2000_DATASET_NAME") && poJ2KDataset) |
3461 | 0 | return poJ2KDataset->GetDescription(); |
3462 | | |
3463 | | // For unit test purposes |
3464 | 1.45k | if (pszDomain != nullptr && EQUAL(pszDomain, "DEBUG") && |
3465 | 0 | EQUAL(pszName, "COMRAT") && psImage) |
3466 | 0 | return psImage->szCOMRAT; |
3467 | | |
3468 | 1.45k | return GDALPamDataset::GetMetadataItem(pszName, pszDomain); |
3469 | 1.45k | } |
3470 | | |
3471 | | /************************************************************************/ |
3472 | | /* GetGCPCount() */ |
3473 | | /************************************************************************/ |
3474 | | |
3475 | | int NITFDataset::GetGCPCount() |
3476 | | |
3477 | 188 | { |
3478 | 188 | return nGCPCount; |
3479 | 188 | } |
3480 | | |
3481 | | /************************************************************************/ |
3482 | | /* GetGCPSpatialRef() */ |
3483 | | /************************************************************************/ |
3484 | | |
3485 | | const OGRSpatialReference *NITFDataset::GetGCPSpatialRef() const |
3486 | | |
3487 | 186 | { |
3488 | 186 | if (nGCPCount > 0 && !m_oGCPSRS.IsEmpty()) |
3489 | 15 | return &m_oGCPSRS; |
3490 | | |
3491 | 171 | return nullptr; |
3492 | 186 | } |
3493 | | |
3494 | | /************************************************************************/ |
3495 | | /* GetGCP() */ |
3496 | | /************************************************************************/ |
3497 | | |
3498 | | const GDAL_GCP *NITFDataset::GetGCPs() |
3499 | | |
3500 | 186 | { |
3501 | 186 | return pasGCPList; |
3502 | 186 | } |
3503 | | |
3504 | | /************************************************************************/ |
3505 | | /* CheckForRSets() */ |
3506 | | /* */ |
3507 | | /* Check for reduced resolution images in .r<n> files and if */ |
3508 | | /* found return filename for a virtual file wrapping them as an */ |
3509 | | /* overview file. (#3457) */ |
3510 | | /************************************************************************/ |
3511 | | |
3512 | | int NITFDataset::CheckForRSets(const char *pszNITFFilename, |
3513 | | char **papszSiblingFiles) |
3514 | | |
3515 | 507 | { |
3516 | 507 | bool isR0File = EQUAL(CPLGetExtensionSafe(pszNITFFilename).c_str(), "r0"); |
3517 | | |
3518 | | /* -------------------------------------------------------------------- */ |
3519 | | /* Check to see if we have RSets. */ |
3520 | | /* -------------------------------------------------------------------- */ |
3521 | 507 | std::vector<CPLString> aosRSetFilenames; |
3522 | | |
3523 | 507 | for (int i = 1; i <= 5; i++) |
3524 | 507 | { |
3525 | 507 | CPLString osTarget; |
3526 | 507 | VSIStatBufL sStat; |
3527 | | |
3528 | 507 | if (isR0File) |
3529 | 0 | { |
3530 | 0 | osTarget = pszNITFFilename; |
3531 | 0 | osTarget.back() = static_cast<char>('0' + i); |
3532 | 0 | } |
3533 | 507 | else |
3534 | 507 | osTarget.Printf("%s.r%d", pszNITFFilename, i); |
3535 | | |
3536 | 507 | if (papszSiblingFiles == nullptr) |
3537 | 18 | { |
3538 | 18 | if (VSIStatL(osTarget, &sStat) != 0) |
3539 | 18 | break; |
3540 | 18 | } |
3541 | 489 | else |
3542 | 489 | { |
3543 | 489 | if (CSLFindStringCaseSensitive(papszSiblingFiles, |
3544 | 489 | CPLGetFilename(osTarget)) < 0) |
3545 | 489 | break; |
3546 | 489 | } |
3547 | | |
3548 | 0 | aosRSetFilenames.push_back(std::move(osTarget)); |
3549 | 0 | } |
3550 | | |
3551 | 507 | if (aosRSetFilenames.empty()) |
3552 | 507 | { |
3553 | | //try for remoteview RRDS (with .rv%d extension) |
3554 | 507 | for (int i = 1; i <= 7; i++) |
3555 | 507 | { |
3556 | 507 | CPLString osTarget; |
3557 | 507 | VSIStatBufL sStat; |
3558 | | |
3559 | 507 | osTarget.Printf("%s.rv%d", pszNITFFilename, i); |
3560 | | |
3561 | 507 | if (VSIStatL(osTarget, &sStat) != 0) |
3562 | 507 | break; |
3563 | | |
3564 | 0 | aosRSetFilenames.push_back(std::move(osTarget)); |
3565 | 0 | } |
3566 | | |
3567 | 507 | if (aosRSetFilenames.empty()) |
3568 | 507 | return FALSE; |
3569 | 507 | } |
3570 | | |
3571 | | /* -------------------------------------------------------------------- */ |
3572 | | /* We do, so try to create a wrapping VRT file. */ |
3573 | | /* -------------------------------------------------------------------- */ |
3574 | 0 | CPLString osFragment; |
3575 | |
|
3576 | 0 | osRSetVRT.Printf("<VRTDataset rasterXSize=\"%d\" rasterYSize=\"%d\">\n", |
3577 | 0 | GetRasterXSize() / 2, GetRasterYSize() / 2); |
3578 | |
|
3579 | 0 | for (int iBand = 0; iBand < GetRasterCount(); iBand++) |
3580 | 0 | { |
3581 | 0 | GDALRasterBand *poBand = GetRasterBand(iBand + 1); |
3582 | |
|
3583 | 0 | osRSetVRT += osFragment.Printf( |
3584 | 0 | " <VRTRasterBand dataType=\"%s\" band=\"%d\">\n", |
3585 | 0 | GDALGetDataTypeName(poBand->GetRasterDataType()), iBand + 1); |
3586 | |
|
3587 | 0 | for (int i = 0; i < static_cast<int>(aosRSetFilenames.size()); i++) |
3588 | 0 | { |
3589 | 0 | char *pszEscaped = |
3590 | 0 | CPLEscapeString(aosRSetFilenames[i].c_str(), -1, CPLES_XML); |
3591 | 0 | if (i == 0) |
3592 | 0 | osRSetVRT += |
3593 | 0 | osFragment.Printf(" " |
3594 | 0 | "<SimpleSource><SourceFilename>%s</" |
3595 | 0 | "SourceFilename><SourceBand>%d</" |
3596 | 0 | "SourceBand></SimpleSource>\n", |
3597 | 0 | pszEscaped, iBand + 1); |
3598 | 0 | else |
3599 | 0 | osRSetVRT += osFragment.Printf( |
3600 | 0 | " " |
3601 | 0 | "<Overview><SourceFilename>%s</" |
3602 | 0 | "SourceFilename><SourceBand>%d</SourceBand></Overview>\n", |
3603 | 0 | pszEscaped, iBand + 1); |
3604 | 0 | CPLFree(pszEscaped); |
3605 | 0 | } |
3606 | 0 | osRSetVRT += osFragment.Printf(" </VRTRasterBand>\n"); |
3607 | 0 | } |
3608 | |
|
3609 | 0 | osRSetVRT += "</VRTDataset>\n"; |
3610 | |
|
3611 | 0 | return TRUE; |
3612 | 507 | } |
3613 | | |
3614 | | /************************************************************************/ |
3615 | | /* IBuildOverviews() */ |
3616 | | /************************************************************************/ |
3617 | | |
3618 | | CPLErr NITFDataset::IBuildOverviews(const char *pszResampling, int nOverviews, |
3619 | | const int *panOverviewList, int nListBands, |
3620 | | const int *panBandList, |
3621 | | GDALProgressFunc pfnProgress, |
3622 | | void *pProgressData, |
3623 | | CSLConstList papszOptions) |
3624 | | |
3625 | 0 | { |
3626 | | /* -------------------------------------------------------------------- */ |
3627 | | /* If we have been using RSets we will need to clear them first. */ |
3628 | | /* -------------------------------------------------------------------- */ |
3629 | 0 | if (!osRSetVRT.empty()) |
3630 | 0 | { |
3631 | 0 | oOvManager.CleanOverviews(); |
3632 | 0 | osRSetVRT = ""; |
3633 | 0 | } |
3634 | |
|
3635 | 0 | bExposeUnderlyingJPEGDatasetOverviews = FALSE; |
3636 | | |
3637 | | /* -------------------------------------------------------------------- */ |
3638 | | /* If we have an underlying JPEG2000 dataset (hopefully via */ |
3639 | | /* JP2KAK) we will try and build zero overviews as a way of */ |
3640 | | /* tricking it into clearing existing overviews-from-jpeg2000. */ |
3641 | | /* -------------------------------------------------------------------- */ |
3642 | 0 | if (poJ2KDataset != nullptr && |
3643 | 0 | !poJ2KDataset->GetMetadataItem("OVERVIEW_FILE", "OVERVIEWS")) |
3644 | 0 | poJ2KDataset->BuildOverviews(pszResampling, 0, nullptr, nListBands, |
3645 | 0 | panBandList, GDALDummyProgress, nullptr, |
3646 | 0 | /* papszOptions = */ nullptr); |
3647 | | |
3648 | | /* -------------------------------------------------------------------- */ |
3649 | | /* Use the overview manager to build requested overviews. */ |
3650 | | /* -------------------------------------------------------------------- */ |
3651 | 0 | CPLErr eErr = GDALPamDataset::IBuildOverviews( |
3652 | 0 | pszResampling, nOverviews, panOverviewList, nListBands, panBandList, |
3653 | 0 | pfnProgress, pProgressData, papszOptions); |
3654 | | |
3655 | | /* -------------------------------------------------------------------- */ |
3656 | | /* If we are working with jpeg or jpeg2000, let the underlying */ |
3657 | | /* dataset know about the overview file. */ |
3658 | | /* -------------------------------------------------------------------- */ |
3659 | 0 | GDALDataset *poSubDS = poJ2KDataset.get(); |
3660 | 0 | if (poJPEGDataset) |
3661 | 0 | poSubDS = poJPEGDataset.get(); |
3662 | |
|
3663 | 0 | const char *pszOverviewFile = GetMetadataItem("OVERVIEW_FILE", "OVERVIEWS"); |
3664 | |
|
3665 | 0 | if (poSubDS && pszOverviewFile != nullptr && eErr == CE_None && |
3666 | 0 | poSubDS->GetMetadataItem("OVERVIEW_FILE", "OVERVIEWS") == nullptr) |
3667 | 0 | { |
3668 | 0 | poSubDS->SetMetadataItem("OVERVIEW_FILE", pszOverviewFile, "OVERVIEWS"); |
3669 | 0 | } |
3670 | |
|
3671 | 0 | return eErr; |
3672 | 0 | } |
3673 | | |
3674 | | /************************************************************************/ |
3675 | | /* ScanJPEGQLevel() */ |
3676 | | /* */ |
3677 | | /* Search the NITF APP header in the jpeg data stream to find */ |
3678 | | /* out what predefined Q level tables should be used (or -1 if */ |
3679 | | /* they are inline). */ |
3680 | | /************************************************************************/ |
3681 | | |
3682 | | int NITFDataset::ScanJPEGQLevel(GUIntBig *pnDataStart, bool *pbError) |
3683 | | |
3684 | 25 | { |
3685 | 25 | if (VSIFSeekL(psFile->fp, *pnDataStart, SEEK_SET) != 0) |
3686 | 0 | { |
3687 | 0 | CPLError(CE_Failure, CPLE_FileIO, "Seek error to jpeg data stream."); |
3688 | 0 | *pbError = true; |
3689 | 0 | return 0; |
3690 | 0 | } |
3691 | | |
3692 | 25 | GByte abyHeader[100]; |
3693 | 25 | if (VSIFReadL(abyHeader, 1, sizeof(abyHeader), psFile->fp) < |
3694 | 25 | sizeof(abyHeader)) |
3695 | 0 | { |
3696 | 0 | CPLError(CE_Failure, CPLE_FileIO, "Read error to jpeg data stream."); |
3697 | 0 | *pbError = true; |
3698 | 0 | return 0; |
3699 | 0 | } |
3700 | | |
3701 | | /* -------------------------------------------------------------------- */ |
3702 | | /* Scan ahead for jpeg magic code. In some files (eg. NSIF) */ |
3703 | | /* there seems to be some extra junk before the image data stream. */ |
3704 | | /* -------------------------------------------------------------------- */ |
3705 | 25 | GUInt32 nOffset = 0; |
3706 | 290 | while (nOffset < sizeof(abyHeader) - 23 && |
3707 | 287 | (abyHeader[nOffset + 0] != 0xff || abyHeader[nOffset + 1] != 0xd8 || |
3708 | 23 | abyHeader[nOffset + 2] != 0xff)) |
3709 | 265 | nOffset++; |
3710 | | |
3711 | 25 | if (nOffset >= sizeof(abyHeader) - 23) |
3712 | 3 | { |
3713 | 3 | *pbError = true; |
3714 | 3 | return 0; |
3715 | 3 | } |
3716 | | |
3717 | 22 | *pbError = false; |
3718 | 22 | *pnDataStart += nOffset; |
3719 | | |
3720 | 22 | if (nOffset > 0) |
3721 | 1 | CPLDebug("NITF", |
3722 | 1 | "JPEG data stream at offset %d from start of data segment, " |
3723 | 1 | "NSIF?", |
3724 | 1 | nOffset); |
3725 | | |
3726 | | /* -------------------------------------------------------------------- */ |
3727 | | /* Do we have an NITF app tag? If so, pull out the Q level. */ |
3728 | | /* -------------------------------------------------------------------- */ |
3729 | 22 | if (memcmp(abyHeader + nOffset + 6, "NITF\0", 5) != 0) |
3730 | 2 | return 0; |
3731 | | |
3732 | 20 | return abyHeader[22 + nOffset]; |
3733 | 22 | } |
3734 | | |
3735 | | /************************************************************************/ |
3736 | | /* ScanJPEGBlocks() */ |
3737 | | /************************************************************************/ |
3738 | | |
3739 | | CPLErr NITFDataset::ScanJPEGBlocks() |
3740 | | |
3741 | 7 | { |
3742 | 7 | GUIntBig nJPEGStart = |
3743 | 7 | psFile->pasSegmentInfo[psImage->iSegment].nSegmentStart; |
3744 | 7 | bool bError = false; |
3745 | 7 | nQLevel = ScanJPEGQLevel(&nJPEGStart, &bError); |
3746 | 7 | if (bError) |
3747 | 1 | { |
3748 | 1 | return CE_Failure; |
3749 | 1 | } |
3750 | | |
3751 | | /* -------------------------------------------------------------------- */ |
3752 | | /* Allocate offset array */ |
3753 | | /* -------------------------------------------------------------------- */ |
3754 | 6 | panJPEGBlockOffset = static_cast<vsi_l_offset *>(VSI_CALLOC_VERBOSE( |
3755 | 6 | sizeof(vsi_l_offset), static_cast<size_t>(psImage->nBlocksPerRow) * |
3756 | 6 | psImage->nBlocksPerColumn)); |
3757 | 6 | if (panJPEGBlockOffset == nullptr) |
3758 | 0 | { |
3759 | 0 | return CE_Failure; |
3760 | 0 | } |
3761 | 6 | panJPEGBlockOffset[0] = nJPEGStart; |
3762 | | |
3763 | 6 | if (psImage->nBlocksPerRow * psImage->nBlocksPerColumn == 1) |
3764 | 0 | return CE_None; |
3765 | | |
3766 | 6 | for (int iBlock = psImage->nBlocksPerRow * psImage->nBlocksPerColumn - 1; |
3767 | 5.12k | iBlock > 0; iBlock--) |
3768 | 5.12k | panJPEGBlockOffset[iBlock] = -1; |
3769 | | |
3770 | | /* -------------------------------------------------------------------- */ |
3771 | | /* Scan through the whole image data stream identifying all */ |
3772 | | /* block boundaries. Each block starts with 0xFFD8 (SOI). */ |
3773 | | /* They also end with 0xFFD9, but we don't currently look for */ |
3774 | | /* that. */ |
3775 | | /* -------------------------------------------------------------------- */ |
3776 | 6 | int iNextBlock = 1; |
3777 | 6 | GIntBig iSegOffset = 2; |
3778 | 6 | if (psFile->pasSegmentInfo[psImage->iSegment].nSegmentSize < |
3779 | 6 | nJPEGStart - psFile->pasSegmentInfo[psImage->iSegment].nSegmentStart) |
3780 | 0 | return CE_Failure; |
3781 | 6 | GIntBig iSegSize = |
3782 | 6 | psFile->pasSegmentInfo[psImage->iSegment].nSegmentSize - |
3783 | 6 | (nJPEGStart - psFile->pasSegmentInfo[psImage->iSegment].nSegmentStart); |
3784 | 6 | GByte abyBlock[512]; |
3785 | 6 | int ignoreBytes = 0; |
3786 | | |
3787 | 48 | while (iSegOffset < iSegSize - 1) |
3788 | 47 | { |
3789 | 47 | const size_t nReadSize = std::min( |
3790 | 47 | sizeof(abyBlock), static_cast<size_t>(iSegSize - iSegOffset)); |
3791 | | |
3792 | 47 | if (VSIFSeekL(psFile->fp, panJPEGBlockOffset[0] + iSegOffset, |
3793 | 47 | SEEK_SET) != 0) |
3794 | 0 | { |
3795 | 0 | CPLError(CE_Failure, CPLE_FileIO, |
3796 | 0 | "Seek error to jpeg data stream."); |
3797 | 0 | return CE_Failure; |
3798 | 0 | } |
3799 | | |
3800 | 47 | if (VSIFReadL(abyBlock, 1, nReadSize, psFile->fp) < nReadSize) |
3801 | 5 | { |
3802 | 5 | CPLError(CE_Failure, CPLE_FileIO, |
3803 | 5 | "Read error to jpeg data stream."); |
3804 | 5 | return CE_Failure; |
3805 | 5 | } |
3806 | | |
3807 | 21.3k | for (size_t i = 0; i < nReadSize - 1; i++) |
3808 | 21.3k | { |
3809 | 21.3k | if (ignoreBytes == 0) |
3810 | 18.1k | { |
3811 | 18.1k | if (abyBlock[i] == 0xff) |
3812 | 226 | { |
3813 | | /* start-of-image marker */ |
3814 | 226 | if (abyBlock[i + 1] == 0xd8) |
3815 | 4 | { |
3816 | 4 | panJPEGBlockOffset[iNextBlock++] = |
3817 | 4 | panJPEGBlockOffset[0] + iSegOffset + i; |
3818 | | |
3819 | 4 | if (iNextBlock == |
3820 | 4 | psImage->nBlocksPerRow * psImage->nBlocksPerColumn) |
3821 | 0 | { |
3822 | 0 | return CE_None; |
3823 | 0 | } |
3824 | 4 | } |
3825 | | /* Skip application-specific data to avoid false positive |
3826 | | * while detecting */ |
3827 | | /* start-of-image markers (#2927). The size of the |
3828 | | * application data is */ |
3829 | | /* found in the two following bytes */ |
3830 | | /* We need this complex mechanism of ignoreBytes for dealing |
3831 | | * with */ |
3832 | | /* application data crossing several abyBlock ... */ |
3833 | 222 | else if (abyBlock[i + 1] >= 0xe0 && abyBlock[i + 1] < 0xf0) |
3834 | 6 | { |
3835 | 6 | ignoreBytes = -2; |
3836 | 6 | } |
3837 | 226 | } |
3838 | 18.1k | } |
3839 | 3.18k | else if (ignoreBytes < 0) |
3840 | 12 | { |
3841 | 12 | if (ignoreBytes == -1) |
3842 | 6 | { |
3843 | | /* Size of the application data */ |
3844 | 6 | ignoreBytes = abyBlock[i] * 256 + abyBlock[i + 1]; |
3845 | 6 | } |
3846 | 6 | else |
3847 | 6 | ignoreBytes++; |
3848 | 12 | } |
3849 | 3.17k | else |
3850 | 3.17k | { |
3851 | 3.17k | ignoreBytes--; |
3852 | 3.17k | } |
3853 | 21.3k | } |
3854 | | |
3855 | 42 | iSegOffset += nReadSize - 1; |
3856 | 42 | } |
3857 | | |
3858 | 1 | return CE_None; |
3859 | 6 | } |
3860 | | |
3861 | | /************************************************************************/ |
3862 | | /* ReadJPEGBlock() */ |
3863 | | /************************************************************************/ |
3864 | | |
3865 | | CPLErr NITFDataset::ReadJPEGBlock(int iBlockX, int iBlockY) |
3866 | | |
3867 | 7 | { |
3868 | 7 | CPLErr eErr; |
3869 | | |
3870 | | /* -------------------------------------------------------------------- */ |
3871 | | /* If this is our first request, do a scan for block boundaries. */ |
3872 | | /* -------------------------------------------------------------------- */ |
3873 | 7 | if (panJPEGBlockOffset == nullptr) |
3874 | 7 | { |
3875 | 7 | if (EQUAL(psImage->szIC, "M3")) |
3876 | 0 | { |
3877 | | /* -------------------------------------------------------------------- |
3878 | | */ |
3879 | | /* When a data mask subheader is present, we don't need to scan |
3880 | | */ |
3881 | | /* the whole file. We just use the psImage->panBlockStart table |
3882 | | */ |
3883 | | /* -------------------------------------------------------------------- |
3884 | | */ |
3885 | 0 | panJPEGBlockOffset = static_cast<vsi_l_offset *>( |
3886 | 0 | VSI_CALLOC_VERBOSE(sizeof(vsi_l_offset), |
3887 | 0 | static_cast<size_t>(psImage->nBlocksPerRow) * |
3888 | 0 | psImage->nBlocksPerColumn)); |
3889 | 0 | if (panJPEGBlockOffset == nullptr) |
3890 | 0 | { |
3891 | 0 | return CE_Failure; |
3892 | 0 | } |
3893 | 0 | for (int i = 0; |
3894 | 0 | i < psImage->nBlocksPerRow * psImage->nBlocksPerColumn; i++) |
3895 | 0 | { |
3896 | 0 | panJPEGBlockOffset[i] = psImage->panBlockStart[i]; |
3897 | 0 | if (panJPEGBlockOffset[i] != static_cast<vsi_l_offset>(-1) && |
3898 | 0 | panJPEGBlockOffset[i] != UINT_MAX) |
3899 | 0 | { |
3900 | 0 | vsi_l_offset nOffset = panJPEGBlockOffset[i]; |
3901 | 0 | bool bError = false; |
3902 | 0 | nQLevel = ScanJPEGQLevel(&nOffset, &bError); |
3903 | | /* The beginning of the JPEG stream should be the offset */ |
3904 | | /* from the panBlockStart table */ |
3905 | 0 | if (bError || nOffset != panJPEGBlockOffset[i]) |
3906 | 0 | { |
3907 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
3908 | 0 | "JPEG block doesn't start at expected offset"); |
3909 | 0 | return CE_Failure; |
3910 | 0 | } |
3911 | 0 | } |
3912 | 0 | } |
3913 | 0 | } |
3914 | 7 | else /* 'C3' case */ |
3915 | 7 | { |
3916 | | /* -------------------------------------------------------------------- |
3917 | | */ |
3918 | | /* Scan through the whole image data stream identifying all */ |
3919 | | /* block boundaries. */ |
3920 | | /* -------------------------------------------------------------------- |
3921 | | */ |
3922 | 7 | eErr = ScanJPEGBlocks(); |
3923 | 7 | if (eErr != CE_None) |
3924 | 6 | return eErr; |
3925 | 7 | } |
3926 | 7 | } |
3927 | | |
3928 | | /* -------------------------------------------------------------------- */ |
3929 | | /* Allocate image data block (where the uncompressed image will go) */ |
3930 | | /* -------------------------------------------------------------------- */ |
3931 | 1 | if (pabyJPEGBlock == nullptr) |
3932 | 1 | { |
3933 | | /* Allocate enough memory to hold 12bit JPEG data */ |
3934 | 1 | pabyJPEGBlock = static_cast<GByte *>(VSI_CALLOC_VERBOSE( |
3935 | 1 | psImage->nBands, static_cast<size_t>(psImage->nBlockWidth) * |
3936 | 1 | psImage->nBlockHeight * 2)); |
3937 | 1 | if (pabyJPEGBlock == nullptr) |
3938 | 0 | { |
3939 | 0 | return CE_Failure; |
3940 | 0 | } |
3941 | 1 | } |
3942 | | |
3943 | | /* -------------------------------------------------------------------- */ |
3944 | | /* Read JPEG Chunk. */ |
3945 | | /* -------------------------------------------------------------------- */ |
3946 | 1 | const int iBlock = iBlockX + iBlockY * psImage->nBlocksPerRow; |
3947 | | |
3948 | 1 | if (panJPEGBlockOffset[iBlock] == static_cast<vsi_l_offset>(-1) || |
3949 | 1 | panJPEGBlockOffset[iBlock] == UINT_MAX) |
3950 | 0 | { |
3951 | 0 | memset(pabyJPEGBlock, 0, |
3952 | 0 | static_cast<size_t>(psImage->nBands) * psImage->nBlockWidth * |
3953 | 0 | psImage->nBlockHeight * 2); |
3954 | 0 | return CE_None; |
3955 | 0 | } |
3956 | | |
3957 | 1 | CPLString osFilename; |
3958 | 1 | osFilename.Printf("JPEG_SUBFILE:Q%d," CPL_FRMT_GUIB ",%d,%s", nQLevel, |
3959 | 1 | panJPEGBlockOffset[iBlock], 0, osNITFFilename.c_str()); |
3960 | | |
3961 | 1 | GDALDataset *poDS = |
3962 | 1 | GDALDataset::FromHandle(GDALOpen(osFilename, GA_ReadOnly)); |
3963 | 1 | if (poDS == nullptr) |
3964 | 0 | return CE_Failure; |
3965 | | |
3966 | 1 | if (poDS->GetRasterXSize() != psImage->nBlockWidth || |
3967 | 1 | poDS->GetRasterYSize() != psImage->nBlockHeight) |
3968 | 0 | { |
3969 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
3970 | 0 | "JPEG block %d not same size as NITF blocksize.", iBlock); |
3971 | 0 | delete poDS; |
3972 | 0 | return CE_Failure; |
3973 | 0 | } |
3974 | | |
3975 | 1 | if (poDS->GetRasterCount() < psImage->nBands) |
3976 | 0 | { |
3977 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
3978 | 0 | "JPEG block %d has not enough bands.", iBlock); |
3979 | 0 | delete poDS; |
3980 | 0 | return CE_Failure; |
3981 | 0 | } |
3982 | | |
3983 | 1 | if (poDS->GetRasterBand(1)->GetRasterDataType() != |
3984 | 1 | GetRasterBand(1)->GetRasterDataType()) |
3985 | 0 | { |
3986 | 0 | CPLError( |
3987 | 0 | CE_Failure, CPLE_AppDefined, |
3988 | 0 | "JPEG block %d data type (%s) not consistent with band data type " |
3989 | 0 | "(%s).", |
3990 | 0 | iBlock, |
3991 | 0 | GDALGetDataTypeName(poDS->GetRasterBand(1)->GetRasterDataType()), |
3992 | 0 | GDALGetDataTypeName(GetRasterBand(1)->GetRasterDataType())); |
3993 | 0 | delete poDS; |
3994 | 0 | return CE_Failure; |
3995 | 0 | } |
3996 | | |
3997 | 1 | int anBands[3] = {1, 2, 3}; |
3998 | 1 | eErr = poDS->RasterIO(GF_Read, 0, 0, psImage->nBlockWidth, |
3999 | 1 | psImage->nBlockHeight, pabyJPEGBlock, |
4000 | 1 | psImage->nBlockWidth, psImage->nBlockHeight, |
4001 | 1 | GetRasterBand(1)->GetRasterDataType(), |
4002 | 1 | psImage->nBands, anBands, 0, 0, 0, nullptr); |
4003 | | |
4004 | 1 | delete poDS; |
4005 | | |
4006 | 1 | return eErr; |
4007 | 1 | } |
4008 | | |
4009 | | /************************************************************************/ |
4010 | | /* GetFileList() */ |
4011 | | /************************************************************************/ |
4012 | | |
4013 | | char **NITFDataset::GetFileList() |
4014 | | |
4015 | 374 | { |
4016 | 374 | char **papszFileList = GDALPamDataset::GetFileList(); |
4017 | | |
4018 | | // Small optimization to avoid useless file probing. |
4019 | 374 | if (CSLCount(papszFileList) == 0) |
4020 | 0 | return papszFileList; |
4021 | | |
4022 | | /* -------------------------------------------------------------------- */ |
4023 | | /* Check for .imd file. */ |
4024 | | /* -------------------------------------------------------------------- */ |
4025 | 374 | papszFileList = AddFile(papszFileList, "IMD", "imd"); |
4026 | | |
4027 | | /* -------------------------------------------------------------------- */ |
4028 | | /* Check for .rpb file. */ |
4029 | | /* -------------------------------------------------------------------- */ |
4030 | 374 | papszFileList = AddFile(papszFileList, "RPB", "rpb"); |
4031 | | |
4032 | 374 | if (!m_osRPCTXTFilename.empty()) |
4033 | 0 | papszFileList = CSLAddString(papszFileList, m_osRPCTXTFilename); |
4034 | | |
4035 | | /* -------------------------------------------------------------------- */ |
4036 | | /* Check for other files. */ |
4037 | | /* -------------------------------------------------------------------- */ |
4038 | 374 | papszFileList = AddFile(papszFileList, "ATT", "att"); |
4039 | 374 | papszFileList = AddFile(papszFileList, "EPH", "eph"); |
4040 | 374 | papszFileList = AddFile(papszFileList, "GEO", "geo"); |
4041 | 374 | papszFileList = AddFile(papszFileList, "XML", "xml"); |
4042 | | |
4043 | 374 | return papszFileList; |
4044 | 374 | } |
4045 | | |
4046 | | /************************************************************************/ |
4047 | | /* AddFile() */ |
4048 | | /* */ |
4049 | | /* Helper method for GetFileList() */ |
4050 | | /************************************************************************/ |
4051 | | char **NITFDataset::AddFile(char **papszFileList, const char *EXTENSION, |
4052 | | const char *extension) |
4053 | 2.24k | { |
4054 | 2.24k | VSIStatBufL sStatBuf; |
4055 | 2.24k | CPLString osTarget = CPLResetExtensionSafe(osNITFFilename, EXTENSION); |
4056 | 2.24k | if (oOvManager.GetSiblingFiles() != nullptr) |
4057 | 2.24k | { |
4058 | 2.24k | if (CSLFindStringCaseSensitive(oOvManager.GetSiblingFiles(), |
4059 | 2.24k | CPLGetFilename(osTarget)) >= 0) |
4060 | 0 | papszFileList = CSLAddString(papszFileList, osTarget); |
4061 | 2.24k | else |
4062 | 2.24k | { |
4063 | 2.24k | osTarget = CPLResetExtensionSafe(osNITFFilename, extension); |
4064 | 2.24k | if (CSLFindStringCaseSensitive(oOvManager.GetSiblingFiles(), |
4065 | 2.24k | CPLGetFilename(osTarget)) >= 0) |
4066 | 4 | papszFileList = CSLAddString(papszFileList, osTarget); |
4067 | 2.24k | } |
4068 | 2.24k | } |
4069 | 0 | else |
4070 | 0 | { |
4071 | 0 | if (VSIStatL(osTarget, &sStatBuf) == 0) |
4072 | 0 | papszFileList = CSLAddString(papszFileList, osTarget); |
4073 | 0 | else |
4074 | 0 | { |
4075 | 0 | osTarget = CPLResetExtensionSafe(osNITFFilename, extension); |
4076 | 0 | if (VSIStatL(osTarget, &sStatBuf) == 0) |
4077 | 0 | papszFileList = CSLAddString(papszFileList, osTarget); |
4078 | 0 | } |
4079 | 0 | } |
4080 | | |
4081 | 2.24k | return papszFileList; |
4082 | 2.24k | } |
4083 | | |
4084 | | /************************************************************************/ |
4085 | | /* GDALToNITFDataType() */ |
4086 | | /************************************************************************/ |
4087 | | |
4088 | | static const char *GDALToNITFDataType(GDALDataType eType) |
4089 | | |
4090 | 10 | { |
4091 | 10 | const char *pszPVType = nullptr; |
4092 | | |
4093 | 10 | switch (eType) |
4094 | 10 | { |
4095 | 6 | case GDT_UInt8: |
4096 | 8 | case GDT_UInt16: |
4097 | 8 | case GDT_UInt32: |
4098 | 8 | pszPVType = "INT"; |
4099 | 8 | break; |
4100 | | |
4101 | 0 | case GDT_Int16: |
4102 | 0 | case GDT_Int32: |
4103 | 0 | pszPVType = "SI"; |
4104 | 0 | break; |
4105 | | |
4106 | 1 | case GDT_Float32: |
4107 | 2 | case GDT_Float64: |
4108 | 2 | pszPVType = "R"; |
4109 | 2 | break; |
4110 | | |
4111 | 0 | case GDT_CInt16: |
4112 | 0 | case GDT_CInt32: |
4113 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
4114 | 0 | "NITF format does not support complex integer data."); |
4115 | 0 | return nullptr; |
4116 | | |
4117 | 0 | case GDT_CFloat32: |
4118 | 0 | pszPVType = "C"; |
4119 | 0 | break; |
4120 | | |
4121 | 0 | default: |
4122 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
4123 | 0 | "Unsupported raster pixel type (%s).", |
4124 | 0 | GDALGetDataTypeName(eType)); |
4125 | 0 | return nullptr; |
4126 | 10 | } |
4127 | | |
4128 | 10 | return pszPVType; |
4129 | 10 | } |
4130 | | |
4131 | | /************************************************************************/ |
4132 | | /* NITFJP2ECWOptions() */ |
4133 | | /* */ |
4134 | | /* Prepare JP2-in-NITF creation options based in part of the */ |
4135 | | /* NITF creation options. */ |
4136 | | /************************************************************************/ |
4137 | | |
4138 | | static char **NITFJP2ECWOptions(CSLConstList papszOptions) |
4139 | | |
4140 | 0 | { |
4141 | 0 | char **papszJP2Options = CSLAddString(nullptr, "PROFILE=NPJE"); |
4142 | 0 | papszJP2Options = CSLAddString(papszJP2Options, "CODESTREAM_ONLY=TRUE"); |
4143 | |
|
4144 | 0 | for (int i = 0; papszOptions != nullptr && papszOptions[i] != nullptr; i++) |
4145 | 0 | { |
4146 | 0 | if (STARTS_WITH_CI(papszOptions[i], "PROFILE=")) |
4147 | 0 | { |
4148 | 0 | CPLFree(papszJP2Options[0]); |
4149 | 0 | papszJP2Options[0] = CPLStrdup(papszOptions[i]); |
4150 | 0 | } |
4151 | 0 | else if (STARTS_WITH_CI(papszOptions[i], "TARGET=")) |
4152 | 0 | papszJP2Options = CSLAddString(papszJP2Options, papszOptions[i]); |
4153 | 0 | } |
4154 | |
|
4155 | 0 | return papszJP2Options; |
4156 | 0 | } |
4157 | | |
4158 | | /************************************************************************/ |
4159 | | /* NITFJP2KAKOptions() */ |
4160 | | /* */ |
4161 | | /* Prepare JP2-in-NITF creation options based in part of the */ |
4162 | | /* NITF creation options. */ |
4163 | | /************************************************************************/ |
4164 | | |
4165 | | static char **NITFJP2KAKOptions(CSLConstList papszOptions, int nABPP) |
4166 | | |
4167 | 0 | { |
4168 | 0 | char **papszJP2Options = CSLAddString(nullptr, "CODEC=J2K"); |
4169 | |
|
4170 | 0 | for (int i = 0; papszOptions != nullptr && papszOptions[i] != nullptr; i++) |
4171 | 0 | { |
4172 | 0 | if (STARTS_WITH_CI(papszOptions[i], "QUALITY=") || |
4173 | 0 | STARTS_WITH_CI(papszOptions[i], "BLOCKXSIZE=") || |
4174 | 0 | STARTS_WITH_CI(papszOptions[i], "BLOCKYSIZE=") || |
4175 | 0 | STARTS_WITH_CI(papszOptions[i], "LAYERS=") || |
4176 | 0 | STARTS_WITH_CI(papszOptions[i], "ROI=")) |
4177 | 0 | { |
4178 | 0 | papszJP2Options = CSLAddString(papszJP2Options, papszOptions[i]); |
4179 | 0 | } |
4180 | 0 | } |
4181 | |
|
4182 | 0 | papszJP2Options = |
4183 | 0 | CSLSetNameValue(papszJP2Options, "NBITS", CPLSPrintf("%d", nABPP)); |
4184 | |
|
4185 | 0 | return papszJP2Options; |
4186 | 0 | } |
4187 | | |
4188 | | /************************************************************************/ |
4189 | | /* NITFJP2OPENJPEGOptions() */ |
4190 | | /* */ |
4191 | | /* Prepare JP2-in-NITF creation options based in part of the */ |
4192 | | /* NITF creation options. */ |
4193 | | /************************************************************************/ |
4194 | | |
4195 | | static char **NITFJP2OPENJPEGOptions(GDALDriver *poJ2KDriver, |
4196 | | CSLConstList papszOptions, int nABPP) |
4197 | | |
4198 | 0 | { |
4199 | 0 | char **papszJP2Options = CSLAddString(nullptr, "CODEC=J2K"); |
4200 | |
|
4201 | 0 | const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY"); |
4202 | 0 | double dfQuality = 0; |
4203 | 0 | if (pszQuality) |
4204 | 0 | { |
4205 | 0 | for (const char *pszVal : |
4206 | 0 | CPLStringList(CSLTokenizeString2(pszQuality, ",", 0))) |
4207 | 0 | dfQuality = std::max(dfQuality, CPLAtof(pszVal)); |
4208 | 0 | } |
4209 | |
|
4210 | 0 | double dfTarget = |
4211 | 0 | CPLAtof(CSLFetchNameValueDef(papszOptions, "TARGET", "0")); |
4212 | |
|
4213 | 0 | if (dfTarget > 0 && dfTarget < 100) |
4214 | 0 | dfQuality = 100. - dfTarget; |
4215 | |
|
4216 | 0 | for (int i = 0; papszOptions != nullptr && papszOptions[i] != nullptr; i++) |
4217 | 0 | { |
4218 | 0 | if (STARTS_WITH_CI(papszOptions[i], "BLOCKXSIZE=") || |
4219 | 0 | STARTS_WITH_CI(papszOptions[i], "BLOCKYSIZE=")) |
4220 | 0 | { |
4221 | 0 | papszJP2Options = CSLAddString(papszJP2Options, papszOptions[i]); |
4222 | 0 | } |
4223 | 0 | } |
4224 | | |
4225 | | // Set it now before the NPJE profiles have a chance to override it |
4226 | 0 | if (pszQuality) |
4227 | 0 | { |
4228 | 0 | papszJP2Options = |
4229 | 0 | CSLSetNameValue(papszJP2Options, "QUALITY", pszQuality); |
4230 | 0 | } |
4231 | |
|
4232 | 0 | const char *pszProfile = CSLFetchNameValueDef(papszOptions, "PROFILE", ""); |
4233 | 0 | if (STARTS_WITH_CI(pszProfile, "NPJE")) |
4234 | 0 | { |
4235 | | // Follow STDI-0006 NCDRD "2.3 Data Compression - JPEG 2000" and |
4236 | | // ISO/IEC BIIF Profile BPJ2K01.10 |
4237 | | // (https://nsgreg.nga.mil/doc/view?i=2031&month=3&day=22&year=2021), |
4238 | | // for NPJE (Appendix D ) profile |
4239 | |
|
4240 | 0 | if (pszQuality && strchr(pszQuality, ',')) |
4241 | 0 | { |
4242 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
4243 | 0 | "Only largest value of QUALITY used when PROFILE=%s " |
4244 | 0 | "is specified", |
4245 | 0 | pszProfile); |
4246 | 0 | } |
4247 | |
|
4248 | 0 | papszJP2Options = |
4249 | 0 | CSLAddString(papszJP2Options, "@BLOCKSIZE_STRICT=YES"); |
4250 | | |
4251 | | // Empty PRECINCTS option to ask for no custom precincts |
4252 | 0 | papszJP2Options = CSLAddString(papszJP2Options, "PRECINCTS="); |
4253 | |
|
4254 | 0 | #if defined(__GNUC__) |
4255 | 0 | #pragma GCC diagnostic push |
4256 | 0 | #pragma GCC diagnostic ignored "-Warray-bounds" |
4257 | 0 | #endif |
4258 | | // See Table 2.3-3 - Target Bit Rates for Each Tile in Panchromatic |
4259 | | // Image Segments of STDI-0006 |
4260 | 0 | std::vector<double> adfBPP = { |
4261 | 0 | 0.03125, 0.0625, 0.125, 0.25, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, |
4262 | 0 | 1.1, 1.2, 1.3, 1.5, 1.7, 2.0, 2.3, 3.5, 3.9}; |
4263 | 0 | if (STARTS_WITH_CI(pszProfile, "NPJE_NUMERICALLY_LOSSLESS")) |
4264 | 0 | { |
4265 | | // given that we consider a compression ratio afterwards, we |
4266 | | // arbitrarily consider a Byte datatype, and thus lossless quality |
4267 | | // is achieved at worse with 8 bpp |
4268 | 0 | adfBPP.push_back(8.0); |
4269 | | |
4270 | | // Lossless 5x3 wavelet |
4271 | 0 | papszJP2Options = CSLAddString(papszJP2Options, "REVERSIBLE=YES"); |
4272 | 0 | } |
4273 | 0 | #if defined(__GNUC__) |
4274 | 0 | #pragma GCC diagnostic pop |
4275 | 0 | #endif |
4276 | |
|
4277 | 0 | std::string osQuality; |
4278 | 0 | for (double dfBPP : adfBPP) |
4279 | 0 | { |
4280 | 0 | if (!osQuality.empty()) |
4281 | 0 | osQuality += ','; |
4282 | | // the JP2OPENJPEG QUALITY setting is 100. / compression_ratio |
4283 | | // and compression_ratio = 8 / bpp |
4284 | 0 | double dfLayerQuality = 100.0 / (8.0 / dfBPP); |
4285 | 0 | if (dfLayerQuality > dfQuality && dfQuality != 0.0) |
4286 | 0 | { |
4287 | 0 | osQuality += CPLSPrintf("%f", dfQuality); |
4288 | 0 | break; |
4289 | 0 | } |
4290 | 0 | osQuality += CPLSPrintf("%f", dfLayerQuality); |
4291 | 0 | } |
4292 | 0 | papszJP2Options = |
4293 | 0 | CSLSetNameValue(papszJP2Options, "QUALITY", osQuality.c_str()); |
4294 | |
|
4295 | 0 | papszJP2Options = CSLAddString(papszJP2Options, "PROGRESSION=LRCP"); |
4296 | | |
4297 | | // Disable MCT |
4298 | 0 | papszJP2Options = CSLAddString(papszJP2Options, "YCC=NO"); |
4299 | | |
4300 | | // TLM option added in OpenJPEG 2.5 |
4301 | 0 | if (strstr(poJ2KDriver->GetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST), |
4302 | 0 | "TLM") != nullptr) |
4303 | 0 | { |
4304 | 0 | papszJP2Options = CSLAddString(papszJP2Options, "PLT=YES"); |
4305 | 0 | papszJP2Options = CSLAddString(papszJP2Options, "TLM=YES"); |
4306 | 0 | } |
4307 | 0 | else |
4308 | 0 | { |
4309 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
4310 | 0 | "TLM option not available in JP2OPENJPEG driver. " |
4311 | 0 | "Use OpenJPEG 2.5 or later"); |
4312 | 0 | } |
4313 | |
|
4314 | 0 | papszJP2Options = CSLAddString(papszJP2Options, "RESOLUTIONS=6"); |
4315 | 0 | } |
4316 | 0 | else if (EQUAL(pszProfile, "PROFILE_1")) |
4317 | 0 | { |
4318 | 0 | papszJP2Options = CSLAddString(papszJP2Options, "PROFILE=PROFILE_1"); |
4319 | 0 | } |
4320 | 0 | else if (EQUAL(pszProfile, "PROFILE_2")) |
4321 | 0 | { |
4322 | 0 | papszJP2Options = CSLAddString(papszJP2Options, "PROFILE=UNRESTRICTED"); |
4323 | 0 | } |
4324 | |
|
4325 | 0 | papszJP2Options = |
4326 | 0 | CSLSetNameValue(papszJP2Options, "NBITS", CPLSPrintf("%d", nABPP)); |
4327 | |
|
4328 | 0 | return papszJP2Options; |
4329 | 0 | } |
4330 | | |
4331 | | /************************************************************************/ |
4332 | | /* NITFExtractTEXTAndCGMCreationOption() */ |
4333 | | /************************************************************************/ |
4334 | | |
4335 | | static char **NITFExtractTEXTAndCGMCreationOption(GDALDataset *poSrcDS, |
4336 | | CSLConstList papszOptions, |
4337 | | char ***ppapszTextMD, |
4338 | | char ***ppapszCgmMD) |
4339 | 10 | { |
4340 | 10 | char **papszFullOptions = CSLDuplicate(papszOptions); |
4341 | | |
4342 | | /* -------------------------------------------------------------------- */ |
4343 | | /* Prepare for text segments. */ |
4344 | | /* -------------------------------------------------------------------- */ |
4345 | 10 | char **papszTextMD = CSLFetchNameValueMultiple(papszOptions, "TEXT"); |
4346 | | // Notice: CSLFetchNameValueMultiple remove the leading "TEXT=" when |
4347 | | // returning the list, which is what we want. |
4348 | | |
4349 | | // Use TEXT information from original image if no creation option is passed |
4350 | | // in. |
4351 | 10 | if (poSrcDS != nullptr && papszTextMD == nullptr) |
4352 | 10 | { |
4353 | | // Read CGM adata from original image, duplicate the list because |
4354 | | // we frees papszCgmMD at end of the function. |
4355 | 10 | papszTextMD = CSLDuplicate(poSrcDS->GetMetadata("TEXT")); |
4356 | 10 | } |
4357 | | |
4358 | 10 | int nNUMT = 0; |
4359 | 10 | for (int iOpt = 0; papszTextMD != nullptr && papszTextMD[iOpt] != nullptr; |
4360 | 10 | iOpt++) |
4361 | 0 | { |
4362 | 0 | if (!STARTS_WITH_CI(papszTextMD[iOpt], "DATA_")) |
4363 | 0 | continue; |
4364 | | |
4365 | 0 | nNUMT++; |
4366 | 0 | } |
4367 | | |
4368 | 10 | if (nNUMT > 0) |
4369 | 0 | { |
4370 | 0 | papszFullOptions = CSLAddString(papszFullOptions, |
4371 | 0 | CPLString().Printf("NUMT=%d", nNUMT)); |
4372 | 0 | } |
4373 | | |
4374 | | /* -------------------------------------------------------------------- */ |
4375 | | /* Prepare for CGM segments. */ |
4376 | | /* -------------------------------------------------------------------- */ |
4377 | 10 | char **papszCgmMD = CSLFetchNameValueMultiple(papszOptions, "CGM"); |
4378 | | // Notice: CSLFetchNameValueMultiple remove the leading "CGM=" when |
4379 | | // returning the list, which is what we want. |
4380 | | |
4381 | | // Use CGM information from original image if no creation option is passed |
4382 | | // in. |
4383 | 10 | if (poSrcDS != nullptr && papszCgmMD == nullptr) |
4384 | 10 | { |
4385 | | // Read CGM adata from original image, duplicate the list because |
4386 | | // we frees papszCgmMD at end of the function. |
4387 | 10 | papszCgmMD = CSLDuplicate(poSrcDS->GetMetadata("CGM")); |
4388 | 10 | } |
4389 | | |
4390 | | // Set NUMS based on the number of segments |
4391 | 10 | const char *pszNUMS; // graphic segment option string |
4392 | 10 | int nNUMS = 0; |
4393 | 10 | if (papszCgmMD != nullptr) |
4394 | 0 | { |
4395 | 0 | pszNUMS = CSLFetchNameValue(papszCgmMD, "SEGMENT_COUNT"); |
4396 | |
|
4397 | 0 | if (pszNUMS != nullptr) |
4398 | 0 | { |
4399 | 0 | nNUMS = atoi(pszNUMS); |
4400 | 0 | } |
4401 | 0 | papszFullOptions = CSLAddString(papszFullOptions, |
4402 | 0 | CPLString().Printf("NUMS=%d", nNUMS)); |
4403 | 0 | } |
4404 | | |
4405 | 10 | *ppapszTextMD = papszTextMD; |
4406 | 10 | *ppapszCgmMD = papszCgmMD; |
4407 | | |
4408 | 10 | return papszFullOptions; |
4409 | 10 | } |
4410 | | |
4411 | | /************************************************************************/ |
4412 | | /* NITFDatasetCreate() */ |
4413 | | /************************************************************************/ |
4414 | | |
4415 | | GDALDataset *NITFDataset::NITFDatasetCreate(const char *pszFilename, int nXSize, |
4416 | | int nYSize, int nBandsIn, |
4417 | | GDALDataType eType, |
4418 | | CSLConstList papszOptions) |
4419 | | |
4420 | 0 | { |
4421 | 0 | const char *pszPVType = GDALToNITFDataType(eType); |
4422 | 0 | if (pszPVType == nullptr) |
4423 | 0 | return nullptr; |
4424 | | |
4425 | 0 | const char *pszIC = CSLFetchNameValue(papszOptions, "IC"); |
4426 | | |
4427 | | /* -------------------------------------------------------------------- */ |
4428 | | /* We disallow any IC value except NC when creating this way. */ |
4429 | | /* -------------------------------------------------------------------- */ |
4430 | 0 | GDALDriver *poJ2KDriver = nullptr; |
4431 | |
|
4432 | 0 | if (pszIC != nullptr && EQUAL(pszIC, "C8")) |
4433 | 0 | { |
4434 | 0 | bool bHasCreate = false; |
4435 | |
|
4436 | 0 | poJ2KDriver = GetGDALDriverManager()->GetDriverByName("JP2ECW"); |
4437 | 0 | if (poJ2KDriver != nullptr) |
4438 | 0 | bHasCreate = poJ2KDriver->GetMetadataItem(GDAL_DCAP_CREATE, |
4439 | 0 | nullptr) != nullptr; |
4440 | 0 | if (!bHasCreate) |
4441 | 0 | { |
4442 | 0 | CPLError( |
4443 | 0 | CE_Failure, CPLE_AppDefined, |
4444 | 0 | "Unable to create JPEG2000 encoded NITF files. The\n" |
4445 | 0 | "JP2ECW driver is unavailable, or missing Create support."); |
4446 | 0 | return nullptr; |
4447 | 0 | } |
4448 | | |
4449 | 0 | if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "J2KLRA", "NO"))) |
4450 | 0 | { |
4451 | 0 | CPLError(CE_Warning, CPLE_NotSupported, |
4452 | 0 | "J2KLRA TRE can only be written in CreateCopy() mode, and " |
4453 | 0 | "when using the JP2OPENJPEG driver in NPJE profiles"); |
4454 | 0 | } |
4455 | 0 | } |
4456 | | |
4457 | 0 | else if (pszIC != nullptr && !EQUAL(pszIC, "NC")) |
4458 | 0 | { |
4459 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
4460 | 0 | "Unsupported compression (IC=%s) used in direct\n" |
4461 | 0 | "NITF File creation", |
4462 | 0 | pszIC); |
4463 | 0 | return nullptr; |
4464 | 0 | } |
4465 | | |
4466 | 0 | const char *const apszIgnoredOptions[] = {"SDE_TRE", "RPC00B", "RPCTXT", |
4467 | 0 | nullptr}; |
4468 | 0 | for (int i = 0; apszIgnoredOptions[i] != nullptr; ++i) |
4469 | 0 | { |
4470 | 0 | if (CSLFetchNameValue(papszOptions, apszIgnoredOptions[i])) |
4471 | 0 | { |
4472 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
4473 | 0 | "%s creation option ignored by Create() method " |
4474 | 0 | "(only valid in CreateCopy())", |
4475 | 0 | apszIgnoredOptions[i]); |
4476 | 0 | } |
4477 | 0 | } |
4478 | | |
4479 | | /* -------------------------------------------------------------------- */ |
4480 | | /* Prepare for text and CGM segments. */ |
4481 | | /* -------------------------------------------------------------------- */ |
4482 | 0 | char **papszTextMD = nullptr; |
4483 | 0 | char **papszCgmMD = nullptr; |
4484 | 0 | char **papszFullOptions = NITFExtractTEXTAndCGMCreationOption( |
4485 | 0 | nullptr, papszOptions, &papszTextMD, &papszCgmMD); |
4486 | |
|
4487 | 0 | const char *pszBlockSize = CSLFetchNameValue(papszFullOptions, "BLOCKSIZE"); |
4488 | 0 | if (pszBlockSize != nullptr && |
4489 | 0 | CSLFetchNameValue(papszFullOptions, "BLOCKXSIZE") == nullptr) |
4490 | 0 | { |
4491 | 0 | papszFullOptions = |
4492 | 0 | CSLSetNameValue(papszFullOptions, "BLOCKXSIZE", pszBlockSize); |
4493 | 0 | } |
4494 | 0 | if (pszBlockSize != nullptr && |
4495 | 0 | CSLFetchNameValue(papszFullOptions, "BLOCKYSIZE") == nullptr) |
4496 | 0 | { |
4497 | 0 | papszFullOptions = |
4498 | 0 | CSLSetNameValue(papszFullOptions, "BLOCKYSIZE", pszBlockSize); |
4499 | 0 | } |
4500 | |
|
4501 | 0 | if (const char *pszNBITS = CSLFetchNameValue(papszFullOptions, "NBITS")) |
4502 | 0 | { |
4503 | 0 | papszFullOptions = CSLSetNameValue(papszFullOptions, "ABPP", pszNBITS); |
4504 | 0 | } |
4505 | | |
4506 | | /* -------------------------------------------------------------------- */ |
4507 | | /* Create the file. */ |
4508 | | /* -------------------------------------------------------------------- */ |
4509 | |
|
4510 | 0 | int nIMIndex = 0; |
4511 | 0 | int nImageCount = 0; |
4512 | 0 | vsi_l_offset nImageOffset = 0; |
4513 | 0 | vsi_l_offset nICOffset = 0; |
4514 | 0 | if (!NITFCreateEx(pszFilename, nXSize, nYSize, nBandsIn, |
4515 | 0 | GDALGetDataTypeSizeBits(eType), pszPVType, |
4516 | 0 | papszFullOptions, &nIMIndex, &nImageCount, &nImageOffset, |
4517 | 0 | &nICOffset)) |
4518 | 0 | { |
4519 | 0 | CSLDestroy(papszTextMD); |
4520 | 0 | CSLDestroy(papszCgmMD); |
4521 | 0 | CSLDestroy(papszFullOptions); |
4522 | 0 | return nullptr; |
4523 | 0 | } |
4524 | | |
4525 | | /* -------------------------------------------------------------------- */ |
4526 | | /* Various special hacks related to JPEG2000 encoded files. */ |
4527 | | /* -------------------------------------------------------------------- */ |
4528 | 0 | GDALDataset *poWritableJ2KDataset = nullptr; |
4529 | 0 | if (poJ2KDriver) |
4530 | 0 | { |
4531 | 0 | CPLString osDSName; |
4532 | |
|
4533 | 0 | osDSName.Printf("/vsisubfile/" CPL_FRMT_GUIB "_%d,%s", |
4534 | 0 | static_cast<GUIntBig>(nImageOffset), -1, pszFilename); |
4535 | |
|
4536 | 0 | char **papszJP2Options = NITFJP2ECWOptions(papszFullOptions); |
4537 | 0 | poWritableJ2KDataset = poJ2KDriver->Create( |
4538 | 0 | osDSName, nXSize, nYSize, nBandsIn, eType, papszJP2Options); |
4539 | 0 | CSLDestroy(papszJP2Options); |
4540 | |
|
4541 | 0 | if (poWritableJ2KDataset == nullptr) |
4542 | 0 | { |
4543 | 0 | CSLDestroy(papszTextMD); |
4544 | 0 | CSLDestroy(papszCgmMD); |
4545 | 0 | return nullptr; |
4546 | 0 | } |
4547 | 0 | } |
4548 | 0 | CSLDestroy(papszFullOptions); |
4549 | | |
4550 | | /* -------------------------------------------------------------------- */ |
4551 | | /* Open the dataset in update mode. */ |
4552 | | /* -------------------------------------------------------------------- */ |
4553 | 0 | GDALOpenInfo oOpenInfo(pszFilename, GA_Update); |
4554 | 0 | NITFDataset *poDS = NITFDataset::OpenInternal( |
4555 | 0 | &oOpenInfo, poWritableJ2KDataset, true, nIMIndex); |
4556 | 0 | if (poDS) |
4557 | 0 | { |
4558 | 0 | poDS->m_nImageOffset = nImageOffset; |
4559 | 0 | poDS->m_nIMIndex = nIMIndex; |
4560 | 0 | poDS->m_nImageCount = nImageCount; |
4561 | 0 | poDS->m_nICOffset = nICOffset; |
4562 | 0 | poDS->papszTextMDToWrite = papszTextMD; |
4563 | 0 | poDS->papszCgmMDToWrite = papszCgmMD; |
4564 | 0 | poDS->aosCreationOptions.Assign(CSLDuplicate(papszOptions), true); |
4565 | 0 | } |
4566 | 0 | else |
4567 | 0 | { |
4568 | 0 | CSLDestroy(papszTextMD); |
4569 | 0 | CSLDestroy(papszCgmMD); |
4570 | 0 | } |
4571 | 0 | return poDS; |
4572 | 0 | } |
4573 | | |
4574 | | /************************************************************************/ |
4575 | | /* NITFCreateCopy() */ |
4576 | | /************************************************************************/ |
4577 | | |
4578 | | GDALDataset *NITFDataset::NITFCreateCopy(const char *pszFilename, |
4579 | | GDALDataset *poSrcDS, int bStrict, |
4580 | | CSLConstList papszOptions, |
4581 | | GDALProgressFunc pfnProgress, |
4582 | | void *pProgressData) |
4583 | | |
4584 | 10 | { |
4585 | | |
4586 | 10 | int nBands = poSrcDS->GetRasterCount(); |
4587 | 10 | if (nBands == 0) |
4588 | 0 | { |
4589 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
4590 | 0 | "Unable to export files with zero bands."); |
4591 | 0 | return nullptr; |
4592 | 0 | } |
4593 | | |
4594 | 10 | GDALRasterBand *poBand1 = poSrcDS->GetRasterBand(1); |
4595 | 10 | if (poBand1 == nullptr) |
4596 | 0 | { |
4597 | 0 | return nullptr; |
4598 | 0 | } |
4599 | | |
4600 | | /* -------------------------------------------------------------------- */ |
4601 | | /* Only allow supported compression values. */ |
4602 | | /* -------------------------------------------------------------------- */ |
4603 | 10 | bool bJPEG2000 = false; |
4604 | 10 | bool bJPEG = false; |
4605 | 10 | GDALDriver *poJ2KDriver = nullptr; |
4606 | 10 | const char *pszJPEG2000_DRIVER = |
4607 | 10 | CSLFetchNameValue(papszOptions, "JPEG2000_DRIVER"); |
4608 | 10 | if (pszJPEG2000_DRIVER != nullptr) |
4609 | 0 | poJ2KDriver = |
4610 | 0 | GetGDALDriverManager()->GetDriverByName(pszJPEG2000_DRIVER); |
4611 | | |
4612 | 10 | const char *pszIC = CSLFetchNameValue(papszOptions, "IC"); |
4613 | 10 | if (pszIC != nullptr) |
4614 | 0 | { |
4615 | 0 | if (EQUAL(pszIC, "NC")) |
4616 | 0 | /* ok */; |
4617 | 0 | else if (EQUAL(pszIC, "C8")) |
4618 | 0 | { |
4619 | 0 | if (pszJPEG2000_DRIVER == nullptr) |
4620 | 0 | { |
4621 | 0 | poJ2KDriver = GetGDALDriverManager()->GetDriverByName("JP2ECW"); |
4622 | 0 | if (poJ2KDriver == nullptr || |
4623 | 0 | poJ2KDriver->GetMetadataItem(GDAL_DCAP_CREATECOPY, |
4624 | 0 | nullptr) == nullptr) |
4625 | 0 | { |
4626 | | /* Try with JP2KAK as an alternate driver */ |
4627 | 0 | poJ2KDriver = |
4628 | 0 | GetGDALDriverManager()->GetDriverByName("JP2KAK"); |
4629 | 0 | } |
4630 | 0 | if (poJ2KDriver == nullptr) |
4631 | 0 | { |
4632 | | /* Try with JP2OPENJPEG as an alternate driver */ |
4633 | 0 | poJ2KDriver = |
4634 | 0 | GetGDALDriverManager()->GetDriverByName("JP2OPENJPEG"); |
4635 | 0 | } |
4636 | 0 | } |
4637 | 0 | if (poJ2KDriver == nullptr) |
4638 | 0 | { |
4639 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
4640 | 0 | "Unable to write JPEG2000 compressed NITF file.\n" |
4641 | 0 | "No 'subfile' JPEG2000 write supporting drivers are\n" |
4642 | 0 | "configured."); |
4643 | 0 | return nullptr; |
4644 | 0 | } |
4645 | | |
4646 | 0 | if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "J2KLRA", "NO"))) |
4647 | 0 | { |
4648 | 0 | if (!EQUAL(poJ2KDriver->GetDescription(), "JP2OPENJPEG")) |
4649 | 0 | { |
4650 | 0 | CPLError( |
4651 | 0 | CE_Warning, CPLE_NotSupported, |
4652 | 0 | "J2KLRA TRE can only be written " |
4653 | 0 | "when using the JP2OPENJPEG driver in NPJE profiles"); |
4654 | 0 | } |
4655 | 0 | else if (!STARTS_WITH_CI( |
4656 | 0 | CSLFetchNameValueDef(papszOptions, "PROFILE", ""), |
4657 | 0 | "NPJE")) |
4658 | 0 | { |
4659 | 0 | CPLError(CE_Warning, CPLE_NotSupported, |
4660 | 0 | "J2KLRA TRE can only be written in NPJE profiles"); |
4661 | 0 | } |
4662 | 0 | } |
4663 | 0 | bJPEG2000 = TRUE; |
4664 | 0 | } |
4665 | 0 | else if (EQUAL(pszIC, "C3") || EQUAL(pszIC, "M3")) |
4666 | 0 | { |
4667 | 0 | bJPEG = TRUE; |
4668 | | #ifndef JPEG_SUPPORTED |
4669 | | CPLError(CE_Failure, CPLE_AppDefined, |
4670 | | "Unable to write JPEG compressed NITF file.\n" |
4671 | | "Libjpeg is not configured into build."); |
4672 | | return nullptr; |
4673 | | #endif |
4674 | 0 | } |
4675 | 0 | else |
4676 | 0 | { |
4677 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
4678 | 0 | "Only IC=NC (uncompressed), IC=C3/M3 (JPEG) and IC=C8 " |
4679 | 0 | "(JPEG2000)\n" |
4680 | 0 | "allowed with NITF CreateCopy method."); |
4681 | 0 | return nullptr; |
4682 | 0 | } |
4683 | 0 | } |
4684 | | |
4685 | | /* -------------------------------------------------------------------- */ |
4686 | | /* Get the data type. Complex integers isn't supported by */ |
4687 | | /* NITF, so map that to complex float if we aren't in strict */ |
4688 | | /* mode. */ |
4689 | | /* -------------------------------------------------------------------- */ |
4690 | 10 | GDALDataType eType = poBand1->GetRasterDataType(); |
4691 | 10 | if (!bStrict && (eType == GDT_CInt16 || eType == GDT_CInt32)) |
4692 | 0 | eType = GDT_CFloat32; |
4693 | | |
4694 | | /* -------------------------------------------------------------------- */ |
4695 | | /* Prepare for text and CGM segments. */ |
4696 | | /* -------------------------------------------------------------------- */ |
4697 | 10 | char **papszTextMD = nullptr; |
4698 | 10 | char **papszCgmMD = nullptr; |
4699 | 10 | char **papszFullOptions = NITFExtractTEXTAndCGMCreationOption( |
4700 | 10 | poSrcDS, papszOptions, &papszTextMD, &papszCgmMD); |
4701 | | |
4702 | 10 | const char *pszBlockSize = CSLFetchNameValue(papszFullOptions, "BLOCKSIZE"); |
4703 | 10 | if (pszBlockSize != nullptr && |
4704 | 0 | CSLFetchNameValue(papszFullOptions, "BLOCKXSIZE") == nullptr) |
4705 | 0 | { |
4706 | 0 | papszFullOptions = |
4707 | 0 | CSLSetNameValue(papszFullOptions, "BLOCKXSIZE", pszBlockSize); |
4708 | 0 | } |
4709 | 10 | if (pszBlockSize != nullptr && |
4710 | 0 | CSLFetchNameValue(papszFullOptions, "BLOCKYSIZE") == nullptr) |
4711 | 0 | { |
4712 | 0 | papszFullOptions = |
4713 | 0 | CSLSetNameValue(papszFullOptions, "BLOCKYSIZE", pszBlockSize); |
4714 | 0 | } |
4715 | | |
4716 | | /* -------------------------------------------------------------------- */ |
4717 | | /* Copy over other source metadata items as creation options */ |
4718 | | /* that seem useful, unless they are already set as creation */ |
4719 | | /* options. */ |
4720 | | /* -------------------------------------------------------------------- */ |
4721 | 10 | const bool bUseSrcNITFMetadata = |
4722 | 10 | CPLFetchBool(papszOptions, "USE_SRC_NITF_METADATA", true); |
4723 | 10 | CSLConstList papszSrcMD = poSrcDS->GetMetadata(); |
4724 | | |
4725 | 147 | for (int iMD = 0; bUseSrcNITFMetadata && papszSrcMD && papszSrcMD[iMD]; |
4726 | 137 | iMD++) |
4727 | 137 | { |
4728 | 137 | bool bPreserveSrcMDAsCreationOption = false; |
4729 | 137 | if (STARTS_WITH_CI(papszSrcMD[iMD], "NITF_BLOCKA")) |
4730 | 0 | { |
4731 | 0 | bPreserveSrcMDAsCreationOption = |
4732 | 0 | CSLPartialFindString(papszOptions, "BLOCKA_") < 0 && |
4733 | 0 | CSLPartialFindString(papszOptions, "TRE=BLOCKA=") < 0; |
4734 | 0 | } |
4735 | 137 | else if (STARTS_WITH_CI(papszSrcMD[iMD], "NITF_FHDR")) |
4736 | 0 | { |
4737 | 0 | bPreserveSrcMDAsCreationOption = |
4738 | 0 | CSLFetchNameValue(papszOptions, "FHDR") == nullptr; |
4739 | 0 | } |
4740 | 137 | if (bPreserveSrcMDAsCreationOption) |
4741 | 0 | { |
4742 | 0 | char *pszName = nullptr; |
4743 | 0 | const char *pszValue = CPLParseNameValue(papszSrcMD[iMD], &pszName); |
4744 | 0 | if (pszName != nullptr && |
4745 | 0 | CSLFetchNameValue(papszFullOptions, pszName + 5) == nullptr) |
4746 | 0 | papszFullOptions = |
4747 | 0 | CSLSetNameValue(papszFullOptions, pszName + 5, pszValue); |
4748 | 0 | CPLFree(pszName); |
4749 | 0 | } |
4750 | 137 | } |
4751 | | |
4752 | | /* -------------------------------------------------------------------- */ |
4753 | | /* Copy TRE definitions as creation options, unless they are */ |
4754 | | /* already set as creation options. */ |
4755 | | /* -------------------------------------------------------------------- */ |
4756 | 10 | papszSrcMD = poSrcDS->GetMetadata("TRE"); |
4757 | | |
4758 | 10 | for (int iMD = 0; bUseSrcNITFMetadata && papszSrcMD && papszSrcMD[iMD]; |
4759 | 10 | iMD++) |
4760 | 0 | { |
4761 | 0 | CPLString osTRE; |
4762 | |
|
4763 | 0 | if (STARTS_WITH_CI(papszSrcMD[iMD], "RPFHDR") || |
4764 | 0 | STARTS_WITH_CI(papszSrcMD[iMD], "RPFIMG") || |
4765 | 0 | STARTS_WITH_CI(papszSrcMD[iMD], "RPFDES")) |
4766 | 0 | { |
4767 | | /* Do not copy RPF TRE. They contain absolute offsets */ |
4768 | | /* No chance that they make sense in the new NITF file */ |
4769 | 0 | continue; |
4770 | 0 | } |
4771 | 0 | if (STARTS_WITH_CI(papszSrcMD[iMD], "BLOCKA") && |
4772 | 0 | CSLPartialFindString(papszOptions, "BLOCKA_") >= 0) |
4773 | 0 | { |
4774 | | /* Do not copy BLOCKA TRE if there are BLOCKA_ creation options */ |
4775 | 0 | continue; |
4776 | 0 | } |
4777 | | |
4778 | 0 | osTRE = "TRE="; |
4779 | 0 | osTRE += papszSrcMD[iMD]; |
4780 | |
|
4781 | 0 | char *pszName = nullptr; |
4782 | 0 | CPLParseNameValue(papszSrcMD[iMD], &pszName); |
4783 | 0 | if (pszName != nullptr && |
4784 | 0 | CSLPartialFindString(papszOptions, CPLSPrintf("TRE=%s", pszName)) < |
4785 | 0 | 0) |
4786 | 0 | { |
4787 | 0 | papszFullOptions = CSLAddString(papszFullOptions, osTRE); |
4788 | 0 | } |
4789 | 0 | CPLFree(pszName); |
4790 | 0 | } |
4791 | | |
4792 | | /* -------------------------------------------------------------------- */ |
4793 | | /* Set if we can set IREP. */ |
4794 | | /* -------------------------------------------------------------------- */ |
4795 | 10 | if (CSLFetchNameValue(papszFullOptions, "IREP") == nullptr) |
4796 | 10 | { |
4797 | 10 | if (((poSrcDS->GetRasterCount() == 3 && bJPEG) || |
4798 | 10 | (poSrcDS->GetRasterCount() >= 3 && !bJPEG)) && |
4799 | 9 | poSrcDS->GetRasterBand(1)->GetColorInterpretation() == |
4800 | 9 | GCI_RedBand && |
4801 | 2 | poSrcDS->GetRasterBand(2)->GetColorInterpretation() == |
4802 | 2 | GCI_GreenBand && |
4803 | 0 | poSrcDS->GetRasterBand(3)->GetColorInterpretation() == GCI_BlueBand) |
4804 | 0 | { |
4805 | 0 | if (bJPEG) |
4806 | 0 | papszFullOptions = |
4807 | 0 | CSLSetNameValue(papszFullOptions, "IREP", "YCbCr601"); |
4808 | 0 | else |
4809 | 0 | papszFullOptions = |
4810 | 0 | CSLSetNameValue(papszFullOptions, "IREP", "RGB"); |
4811 | 0 | } |
4812 | 10 | else if (poSrcDS->GetRasterCount() >= 3 && !bJPEG && |
4813 | 9 | poSrcDS->GetRasterBand(1)->GetColorInterpretation() == |
4814 | 9 | GCI_BlueBand && |
4815 | 0 | poSrcDS->GetRasterBand(2)->GetColorInterpretation() == |
4816 | 0 | GCI_GreenBand && |
4817 | 0 | poSrcDS->GetRasterBand(3)->GetColorInterpretation() == |
4818 | 0 | GCI_RedBand && |
4819 | 0 | CSLFetchNameValue(papszFullOptions, "IREPBAND") == nullptr) |
4820 | 0 | { |
4821 | 0 | papszFullOptions = |
4822 | 0 | CSLSetNameValue(papszFullOptions, "IREP", "MULTI"); |
4823 | 0 | std::string osIREPBAND = "B,G,R"; |
4824 | 0 | for (int i = 4; i <= poSrcDS->GetRasterCount(); ++i) |
4825 | 0 | osIREPBAND += ",M"; |
4826 | 0 | papszFullOptions = CSLSetNameValue(papszFullOptions, "IREPBAND", |
4827 | 0 | osIREPBAND.c_str()); |
4828 | 0 | } |
4829 | 10 | else if (poSrcDS->GetRasterCount() == 1 && eType == GDT_UInt8 && |
4830 | 0 | poBand1->GetColorTable() != nullptr) |
4831 | 0 | { |
4832 | 0 | papszFullOptions = |
4833 | 0 | CSLSetNameValue(papszFullOptions, "IREP", "RGB/LUT"); |
4834 | 0 | papszFullOptions = CSLSetNameValue( |
4835 | 0 | papszFullOptions, "LUT_SIZE", |
4836 | 0 | CPLString().Printf( |
4837 | 0 | "%d", poBand1->GetColorTable()->GetColorEntryCount())); |
4838 | 0 | } |
4839 | 10 | else if (GDALDataTypeIsComplex(eType)) |
4840 | 0 | papszFullOptions = |
4841 | 0 | CSLSetNameValue(papszFullOptions, "IREP", "NODISPLY"); |
4842 | | |
4843 | 10 | else |
4844 | 10 | papszFullOptions = |
4845 | 10 | CSLSetNameValue(papszFullOptions, "IREP", "MONO"); |
4846 | 10 | } |
4847 | | |
4848 | | /* -------------------------------------------------------------------- */ |
4849 | | /* Do we have lat/long georeferencing information? */ |
4850 | | /* -------------------------------------------------------------------- */ |
4851 | 10 | const char *pszWKT = poSrcDS->GetProjectionRef(); |
4852 | 10 | if (pszWKT == nullptr || pszWKT[0] == '\0') |
4853 | 2 | pszWKT = poSrcDS->GetGCPProjection(); |
4854 | | |
4855 | 10 | GDALGeoTransform gt; |
4856 | 10 | bool bWriteGeoTransform = false; |
4857 | 10 | bool bWriteGCPs = false; |
4858 | 10 | int nZone = 0; |
4859 | 10 | OGRSpatialReference oSRS; |
4860 | 10 | OGRSpatialReference oSRS_WGS84; |
4861 | 10 | int nGCIFFlags = GCIF_PAM_DEFAULT; |
4862 | 10 | double dfIGEOLOULX = 0; |
4863 | 10 | double dfIGEOLOULY = 0; |
4864 | 10 | double dfIGEOLOURX = 0; |
4865 | 10 | double dfIGEOLOURY = 0; |
4866 | 10 | double dfIGEOLOLRX = 0; |
4867 | 10 | double dfIGEOLOLRY = 0; |
4868 | 10 | double dfIGEOLOLLX = 0; |
4869 | 10 | double dfIGEOLOLLY = 0; |
4870 | 10 | bool bManualWriteOfIGEOLO = false; |
4871 | | |
4872 | 10 | if (pszWKT != nullptr && pszWKT[0] != '\0') |
4873 | 8 | { |
4874 | 8 | oSRS.importFromWkt(pszWKT); |
4875 | | |
4876 | | /* NITF is only WGS84 */ |
4877 | 8 | oSRS_WGS84.SetWellKnownGeogCS("WGS84"); |
4878 | 8 | if (oSRS.IsSameGeogCS(&oSRS_WGS84) == FALSE) |
4879 | 8 | { |
4880 | 8 | CPLError( |
4881 | 8 | (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported, |
4882 | 8 | "NITF only supports WGS84 geographic and UTM projections.\n"); |
4883 | 8 | if (bStrict) |
4884 | 0 | { |
4885 | 0 | CSLDestroy(papszFullOptions); |
4886 | 0 | CSLDestroy(papszCgmMD); |
4887 | 0 | CSLDestroy(papszTextMD); |
4888 | 0 | return nullptr; |
4889 | 0 | } |
4890 | 8 | } |
4891 | | |
4892 | 8 | const char *pszICORDS = CSLFetchNameValue(papszFullOptions, "ICORDS"); |
4893 | | |
4894 | | /* -------------------------------------------------------------------- |
4895 | | */ |
4896 | | /* Should we write DIGEST Spatial Data Extension TRE ? */ |
4897 | | /* -------------------------------------------------------------------- |
4898 | | */ |
4899 | 8 | const char *pszSDE_TRE = CSLFetchNameValue(papszFullOptions, "SDE_TRE"); |
4900 | 8 | const bool bSDE_TRE = pszSDE_TRE && CPLTestBool(pszSDE_TRE); |
4901 | 8 | if (bSDE_TRE) |
4902 | 0 | { |
4903 | 0 | if (oSRS.IsGeographic() && oSRS.GetPrimeMeridian() == 0.0 && |
4904 | 0 | poSrcDS->GetGeoTransform(gt) == CE_None && gt.xrot == 0.0 && |
4905 | 0 | gt.yrot == 0.0 && gt.yscale < 0.0) |
4906 | 0 | { |
4907 | | /* Override ICORDS to G if necessary */ |
4908 | 0 | if (pszICORDS != nullptr && EQUAL(pszICORDS, "D")) |
4909 | 0 | { |
4910 | 0 | papszFullOptions = |
4911 | 0 | CSLSetNameValue(papszFullOptions, "ICORDS", "G"); |
4912 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
4913 | 0 | "Forcing ICORDS=G when writing GEOLOB"); |
4914 | 0 | } |
4915 | 0 | else |
4916 | 0 | { |
4917 | | /* Code a bit below will complain with other ICORDS value */ |
4918 | 0 | } |
4919 | |
|
4920 | 0 | if (CSLPartialFindString(papszFullOptions, "TRE=GEOLOB=") != -1) |
4921 | 0 | { |
4922 | 0 | CPLDebug("NITF", "GEOLOB TRE was explicitly defined " |
4923 | 0 | "before. Overriding it with current " |
4924 | 0 | "georeferencing info."); |
4925 | 0 | } |
4926 | | |
4927 | | /* Structure of SDE TRE documented here */ |
4928 | | // http://www.gwg.nga.mil/ntb/baseline/docs/digest/part2_annex_d.pdf |
4929 | | |
4930 | | /* -------------------------------------------------------------------- |
4931 | | */ |
4932 | | /* Write GEOLOB TRE */ |
4933 | | /* -------------------------------------------------------------------- |
4934 | | */ |
4935 | | // Extra (useless) bytes to avoid CLang 18 erroneous -Wformat-truncation |
4936 | 0 | constexpr int MARGIN_FOR_CLANG_18 = 2; |
4937 | 0 | char szGEOLOB[48 + 1 + MARGIN_FOR_CLANG_18]; |
4938 | 0 | const double dfARV = 360.0 / gt.xscale; |
4939 | 0 | const double dfBRV = 360.0 / -gt.yscale; |
4940 | 0 | const double dfLSO = gt.xorig; |
4941 | 0 | const double dfPSO = gt.yorig; |
4942 | 0 | CPLsnprintf(szGEOLOB, sizeof(szGEOLOB), |
4943 | 0 | "%09d%09d%#+015.10f%#+015.10f", |
4944 | 0 | static_cast<int>(dfARV + 0.5), |
4945 | 0 | static_cast<int>(dfBRV + 0.5), dfLSO, dfPSO); |
4946 | |
|
4947 | 0 | CPLString osGEOLOB("TRE=GEOLOB="); |
4948 | 0 | osGEOLOB += szGEOLOB; |
4949 | 0 | papszFullOptions = CSLAddString(papszFullOptions, osGEOLOB); |
4950 | | |
4951 | | /* -------------------------------------------------------------------- |
4952 | | */ |
4953 | | /* Write GEOPSB TRE if not already explicitly provided */ |
4954 | | /* -------------------------------------------------------------------- |
4955 | | */ |
4956 | 0 | if (CSLPartialFindString(papszFullOptions, |
4957 | 0 | "FILE_TRE=GEOPSB=") == -1 && |
4958 | 0 | CSLPartialFindString(papszFullOptions, "TRE=GEOPSB=") == -1) |
4959 | 0 | { |
4960 | 0 | char szGEOPSB[443 + 1]; |
4961 | 0 | memset(szGEOPSB, ' ', 443); |
4962 | 0 | szGEOPSB[443] = 0; |
4963 | 0 | #define WRITE_STR_NOSZ(dst, src) memcpy(dst, src, strlen(src)) |
4964 | 0 | char *pszGEOPSB = szGEOPSB; |
4965 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "GEO"); |
4966 | 0 | pszGEOPSB += 3; |
4967 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "DEG"); |
4968 | 0 | pszGEOPSB += 3; |
4969 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "World Geodetic System 1984"); |
4970 | 0 | pszGEOPSB += 80; |
4971 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "WGE"); |
4972 | 0 | pszGEOPSB += 4; |
4973 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "World Geodetic System 1984"); |
4974 | 0 | pszGEOPSB += 80; |
4975 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "WE"); |
4976 | 0 | pszGEOPSB += 3; |
4977 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "Geodetic"); |
4978 | 0 | pszGEOPSB += 80; /* DVR */ |
4979 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "GEOD"); |
4980 | 0 | pszGEOPSB += 4; /* VDCDVR */ |
4981 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "Mean Sea"); |
4982 | 0 | pszGEOPSB += 80; /* SDA */ |
4983 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "MSL"); |
4984 | 0 | pszGEOPSB += 4; /* VDCSDA */ |
4985 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "000000000000000"); |
4986 | 0 | pszGEOPSB += 15; /* ZOR */ |
4987 | 0 | pszGEOPSB += 3; /* GRD */ |
4988 | 0 | pszGEOPSB += 80; /* GRN */ |
4989 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "0000"); |
4990 | 0 | pszGEOPSB += 4; /* ZNA */ |
4991 | 0 | CPL_IGNORE_RET_VAL(pszGEOPSB); |
4992 | 0 | CPLAssert(pszGEOPSB == szGEOPSB + 443); |
4993 | |
|
4994 | 0 | CPLString osGEOPSB("FILE_TRE=GEOPSB="); |
4995 | 0 | osGEOPSB += szGEOPSB; |
4996 | 0 | papszFullOptions = CSLAddString(papszFullOptions, osGEOPSB); |
4997 | 0 | } |
4998 | 0 | else |
4999 | 0 | { |
5000 | 0 | CPLDebug("NITF", "GEOPSB TRE was explicitly defined " |
5001 | 0 | "before. Keeping it."); |
5002 | 0 | } |
5003 | 0 | } |
5004 | 0 | else |
5005 | 0 | { |
5006 | 0 | CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported, |
5007 | 0 | "Georeferencing info isn't compatible with writing a " |
5008 | 0 | "GEOLOB TRE (only geographic SRS handled for now)"); |
5009 | 0 | if (bStrict) |
5010 | 0 | { |
5011 | 0 | CSLDestroy(papszFullOptions); |
5012 | 0 | CSLDestroy(papszCgmMD); |
5013 | 0 | CSLDestroy(papszTextMD); |
5014 | 0 | return nullptr; |
5015 | 0 | } |
5016 | 0 | } |
5017 | 0 | } |
5018 | | |
5019 | 8 | bWriteGeoTransform = (poSrcDS->GetGeoTransform(gt) == CE_None); |
5020 | 8 | bWriteGCPs = (!bWriteGeoTransform && poSrcDS->GetGCPCount() == 4); |
5021 | | |
5022 | 8 | int bNorth; |
5023 | 8 | const bool bHasIGEOLO = |
5024 | 8 | CSLFetchNameValue(papszFullOptions, "IGEOLO") != nullptr; |
5025 | 8 | if (bHasIGEOLO && pszICORDS == nullptr) |
5026 | 0 | { |
5027 | 0 | CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_AppDefined, |
5028 | 0 | "IGEOLO specified, but ICORDS not.%s", |
5029 | 0 | bStrict ? "" : " Ignoring IGEOLO"); |
5030 | 0 | if (bStrict) |
5031 | 0 | { |
5032 | 0 | CSLDestroy(papszFullOptions); |
5033 | 0 | CSLDestroy(papszCgmMD); |
5034 | 0 | CSLDestroy(papszTextMD); |
5035 | 0 | return nullptr; |
5036 | 0 | } |
5037 | 0 | } |
5038 | | |
5039 | 8 | if (CSLFetchNameValue(papszFullOptions, "IGEOLO") != nullptr && |
5040 | 0 | pszICORDS != nullptr) |
5041 | 0 | { |
5042 | | // if both IGEOLO and ICORDS are specified, do not try to write |
5043 | | // computed values |
5044 | |
|
5045 | 0 | bWriteGeoTransform = false; |
5046 | 0 | bWriteGCPs = false; |
5047 | 0 | nGCIFFlags &= ~GCIF_PROJECTION; |
5048 | 0 | nGCIFFlags &= ~GCIF_GEOTRANSFORM; |
5049 | 0 | } |
5050 | 8 | else if (oSRS.IsGeographic() && oSRS.GetPrimeMeridian() == 0.0) |
5051 | 1 | { |
5052 | 1 | if (pszICORDS == nullptr) |
5053 | 1 | { |
5054 | 1 | papszFullOptions = |
5055 | 1 | CSLSetNameValue(papszFullOptions, "ICORDS", "G"); |
5056 | 1 | } |
5057 | 0 | else if (EQUAL(pszICORDS, "G") || EQUAL(pszICORDS, "D")) |
5058 | 0 | { |
5059 | | /* Do nothing */ |
5060 | 0 | } |
5061 | 0 | else |
5062 | 0 | { |
5063 | 0 | CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported, |
5064 | 0 | "Inconsistent ICORDS value with SRS : %s%s.\n", |
5065 | 0 | pszICORDS, |
5066 | 0 | (!bStrict) ? ". Setting it to G instead" : ""); |
5067 | 0 | if (bStrict) |
5068 | 0 | { |
5069 | 0 | CSLDestroy(papszFullOptions); |
5070 | 0 | CSLDestroy(papszCgmMD); |
5071 | 0 | CSLDestroy(papszTextMD); |
5072 | 0 | return nullptr; |
5073 | 0 | } |
5074 | 0 | papszFullOptions = |
5075 | 0 | CSLSetNameValue(papszFullOptions, "ICORDS", "G"); |
5076 | 0 | } |
5077 | 1 | } |
5078 | | |
5079 | 7 | else if (oSRS.GetUTMZone(&bNorth) > 0) |
5080 | 5 | { |
5081 | 5 | const char *pszComputedICORDS = bNorth ? "N" : "S"; |
5082 | 5 | nZone = oSRS.GetUTMZone(nullptr); |
5083 | 5 | if (pszICORDS == nullptr) |
5084 | 5 | { |
5085 | 5 | papszFullOptions = CSLSetNameValue(papszFullOptions, "ICORDS", |
5086 | 5 | pszComputedICORDS); |
5087 | 5 | } |
5088 | 0 | else if (EQUAL(pszICORDS, pszComputedICORDS)) |
5089 | 0 | { |
5090 | | // ok |
5091 | 0 | } |
5092 | 0 | else if ((EQUAL(pszICORDS, "G") || EQUAL(pszICORDS, "D")) && |
5093 | 0 | bWriteGeoTransform) |
5094 | 0 | { |
5095 | | // Reproject UTM corner coordinates to geographic. |
5096 | | // This can be used when there is no way to write an |
5097 | | // equatorial image whose one of the northing value is below |
5098 | | // -1e6 |
5099 | |
|
5100 | 0 | const int nXSize = poSrcDS->GetRasterXSize(); |
5101 | 0 | const int nYSize = poSrcDS->GetRasterYSize(); |
5102 | |
|
5103 | 0 | dfIGEOLOULX = gt.xorig + 0.5 * gt.xscale + 0.5 * gt.xrot; |
5104 | 0 | dfIGEOLOULY = gt.yorig + 0.5 * gt.yrot + 0.5 * gt.yscale; |
5105 | 0 | dfIGEOLOURX = dfIGEOLOULX + gt.xscale * (nXSize - 1); |
5106 | 0 | dfIGEOLOURY = dfIGEOLOULY + gt.yrot * (nXSize - 1); |
5107 | 0 | dfIGEOLOLRX = dfIGEOLOULX + gt.xscale * (nXSize - 1) + |
5108 | 0 | gt.xrot * (nYSize - 1); |
5109 | 0 | dfIGEOLOLRY = dfIGEOLOULY + gt.yrot * (nXSize - 1) + |
5110 | 0 | gt.yscale * (nYSize - 1); |
5111 | 0 | dfIGEOLOLLX = dfIGEOLOULX + gt.xrot * (nYSize - 1); |
5112 | 0 | dfIGEOLOLLY = dfIGEOLOULY + gt.yscale * (nYSize - 1); |
5113 | |
|
5114 | 0 | oSRS_WGS84.SetWellKnownGeogCS("WGS84"); |
5115 | 0 | oSRS_WGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
5116 | |
|
5117 | 0 | auto poCT = std::unique_ptr<OGRCoordinateTransformation>( |
5118 | 0 | OGRCreateCoordinateTransformation(&oSRS, &oSRS_WGS84)); |
5119 | 0 | if (poCT && poCT->Transform(1, &dfIGEOLOULX, &dfIGEOLOULY) && |
5120 | 0 | poCT->Transform(1, &dfIGEOLOURX, &dfIGEOLOURY) && |
5121 | 0 | poCT->Transform(1, &dfIGEOLOLRX, &dfIGEOLOLRY) && |
5122 | 0 | poCT->Transform(1, &dfIGEOLOLLX, &dfIGEOLOLLY)) |
5123 | 0 | { |
5124 | 0 | nZone = 0; |
5125 | 0 | bWriteGeoTransform = false; |
5126 | 0 | bManualWriteOfIGEOLO = true; |
5127 | 0 | nGCIFFlags &= ~GCIF_PROJECTION; |
5128 | 0 | nGCIFFlags &= ~GCIF_GEOTRANSFORM; |
5129 | 0 | } |
5130 | 0 | else |
5131 | 0 | { |
5132 | 0 | CPLError( |
5133 | 0 | CE_Failure, CPLE_AppDefined, |
5134 | 0 | "Cannot reproject UTM coordinates to geographic ones"); |
5135 | 0 | CSLDestroy(papszFullOptions); |
5136 | 0 | CSLDestroy(papszCgmMD); |
5137 | 0 | CSLDestroy(papszTextMD); |
5138 | 0 | return nullptr; |
5139 | 0 | } |
5140 | 0 | } |
5141 | 0 | else |
5142 | 0 | { |
5143 | 0 | CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported, |
5144 | 0 | "Inconsistent ICORDS value with SRS : %s.", pszICORDS); |
5145 | 0 | if (bStrict) |
5146 | 0 | { |
5147 | 0 | CSLDestroy(papszFullOptions); |
5148 | 0 | CSLDestroy(papszCgmMD); |
5149 | 0 | CSLDestroy(papszTextMD); |
5150 | 0 | return nullptr; |
5151 | 0 | } |
5152 | 0 | } |
5153 | 5 | } |
5154 | 2 | else |
5155 | 2 | { |
5156 | 2 | CPLError( |
5157 | 2 | (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported, |
5158 | 2 | "NITF only supports WGS84 geographic and UTM projections.\n"); |
5159 | 2 | if (bStrict) |
5160 | 0 | { |
5161 | 0 | CSLDestroy(papszFullOptions); |
5162 | 0 | CSLDestroy(papszCgmMD); |
5163 | 0 | CSLDestroy(papszTextMD); |
5164 | 0 | return nullptr; |
5165 | 0 | } |
5166 | 2 | } |
5167 | 8 | } |
5168 | | |
5169 | | /* -------------------------------------------------------------------- */ |
5170 | | /* Do we have RPC information? */ |
5171 | | /* -------------------------------------------------------------------- */ |
5172 | 10 | if (!bUseSrcNITFMetadata) |
5173 | 0 | nGCIFFlags &= ~GCIF_METADATA; |
5174 | | |
5175 | 10 | CSLConstList papszRPC = poSrcDS->GetMetadata("RPC"); |
5176 | 10 | if (papszRPC != nullptr && bUseSrcNITFMetadata && |
5177 | 0 | CPLFetchBool(papszFullOptions, "RPC00B", true)) |
5178 | 0 | { |
5179 | 0 | if (CSLPartialFindString(papszFullOptions, "TRE=RPC00B=") >= 0) |
5180 | 0 | { |
5181 | 0 | CPLDebug("NITF", |
5182 | 0 | "Both TRE=RPC00B and RPC metadata are available. " |
5183 | 0 | "Ignoring RPC metadata and re-using source TRE=RPC00B"); |
5184 | 0 | } |
5185 | 0 | else |
5186 | 0 | { |
5187 | 0 | int bPrecisionLoss = FALSE; |
5188 | 0 | char *pszRPC = |
5189 | 0 | NITFFormatRPC00BFromMetadata(papszRPC, &bPrecisionLoss); |
5190 | 0 | if (pszRPC == nullptr) |
5191 | 0 | { |
5192 | 0 | CPLError( |
5193 | 0 | (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported, |
5194 | 0 | "Cannot format a valid RPC00B TRE from the RPC metadata"); |
5195 | 0 | if (bStrict) |
5196 | 0 | { |
5197 | 0 | CSLDestroy(papszFullOptions); |
5198 | 0 | CSLDestroy(papszCgmMD); |
5199 | 0 | CSLDestroy(papszTextMD); |
5200 | 0 | return nullptr; |
5201 | 0 | } |
5202 | 0 | } |
5203 | 0 | else |
5204 | 0 | { |
5205 | 0 | CPLString osRPC00B("TRE=RPC00B="); |
5206 | 0 | osRPC00B += pszRPC; |
5207 | 0 | papszFullOptions = CSLAddString(papszFullOptions, osRPC00B); |
5208 | | |
5209 | | // If no precision loss occurred during RPC conversion, then |
5210 | | // we can suppress it from PAM |
5211 | 0 | if (!bPrecisionLoss) |
5212 | 0 | nGCIFFlags &= ~GCIF_METADATA; |
5213 | 0 | } |
5214 | 0 | CPLFree(pszRPC); |
5215 | 0 | } |
5216 | 0 | } |
5217 | 10 | else if (!CPLFetchBool(papszFullOptions, "RPC00B", true)) |
5218 | 0 | { |
5219 | 0 | int nIdx = CSLPartialFindString(papszFullOptions, "TRE=RPC00B="); |
5220 | 0 | if (nIdx >= 0) |
5221 | 0 | { |
5222 | 0 | papszFullOptions = |
5223 | 0 | CSLRemoveStrings(papszFullOptions, nIdx, 1, nullptr); |
5224 | 0 | } |
5225 | 0 | } |
5226 | | |
5227 | 10 | if (papszRPC != nullptr && CPLFetchBool(papszFullOptions, "RPCTXT", false)) |
5228 | 0 | { |
5229 | 0 | GDALWriteRPCTXTFile(pszFilename, papszRPC); |
5230 | 0 | } |
5231 | | |
5232 | | /* -------------------------------------------------------------------- */ |
5233 | | /* Create the output file. */ |
5234 | | /* -------------------------------------------------------------------- */ |
5235 | 10 | const int nXSize = poSrcDS->GetRasterXSize(); |
5236 | 10 | const int nYSize = poSrcDS->GetRasterYSize(); |
5237 | 10 | const char *pszPVType = GDALToNITFDataType(eType); |
5238 | | |
5239 | 10 | if (pszPVType == nullptr) |
5240 | 0 | { |
5241 | 0 | CSLDestroy(papszFullOptions); |
5242 | 0 | CSLDestroy(papszCgmMD); |
5243 | 0 | CSLDestroy(papszTextMD); |
5244 | 0 | return nullptr; |
5245 | 0 | } |
5246 | | |
5247 | 10 | int nABPP = GDALGetDataTypeSizeBits(eType); |
5248 | 10 | if (const char *pszABPP = CSLFetchNameValue(papszFullOptions, "ABPP")) |
5249 | 0 | { |
5250 | 0 | nABPP = atoi(pszABPP); |
5251 | 0 | } |
5252 | 10 | else if (const char *pszNBITS = CSLFetchNameValueDef( |
5253 | 10 | papszFullOptions, "NBITS", |
5254 | 10 | poBand1->GetMetadataItem("NBITS", "IMAGE_STRUCTURE"))) |
5255 | 2 | { |
5256 | 2 | papszFullOptions = CSLSetNameValue(papszFullOptions, "ABPP", pszNBITS); |
5257 | 2 | nABPP = atoi(pszNBITS); |
5258 | 2 | } |
5259 | | |
5260 | 10 | if (poJ2KDriver != nullptr && |
5261 | 0 | EQUAL(poJ2KDriver->GetDescription(), "JP2ECW")) |
5262 | 0 | { |
5263 | 0 | if (STARTS_WITH_CI( |
5264 | 0 | CSLFetchNameValueDef(papszFullOptions, "PROFILE", "NPJE"), |
5265 | 0 | "NPJE") && |
5266 | 0 | (nXSize >= 1024 || nYSize >= 1024)) |
5267 | 0 | { |
5268 | 0 | int nBlockXSize = |
5269 | 0 | atoi(CSLFetchNameValueDef(papszFullOptions, "BLOCKXSIZE", "0")); |
5270 | 0 | int nBlockYSize = |
5271 | 0 | atoi(CSLFetchNameValueDef(papszFullOptions, "BLOCKYSIZE", "0")); |
5272 | 0 | if (nBlockXSize > 0 && nBlockXSize != 1024) |
5273 | 0 | { |
5274 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
5275 | 0 | "BLOCKXSIZE != 1024 inconsistent with PROFILE=NPJE"); |
5276 | 0 | } |
5277 | 0 | if (nBlockYSize > 0 && nBlockYSize != 1024) |
5278 | 0 | { |
5279 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
5280 | 0 | "BLOCKYSIZE != 1024 inconsistent with PROFILE=NPJE"); |
5281 | 0 | } |
5282 | 0 | if (nBlockXSize == 0) |
5283 | 0 | { |
5284 | 0 | papszFullOptions = |
5285 | 0 | CSLSetNameValue(papszFullOptions, "BLOCKXSIZE", "1024"); |
5286 | 0 | } |
5287 | 0 | if (nBlockYSize == 0) |
5288 | 0 | { |
5289 | 0 | papszFullOptions = |
5290 | 0 | CSLSetNameValue(papszFullOptions, "BLOCKYSIZE", "1024"); |
5291 | 0 | } |
5292 | 0 | } |
5293 | 0 | } |
5294 | 10 | else if (poJ2KDriver != nullptr && |
5295 | 0 | EQUAL(poJ2KDriver->GetDescription(), "JP2OPENJPEG")) |
5296 | 0 | { |
5297 | 0 | const char *pszProfile = CSLFetchNameValue(papszFullOptions, "PROFILE"); |
5298 | 0 | if (pszProfile && EQUAL(pszProfile, "EPJE")) |
5299 | 0 | { |
5300 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
5301 | 0 | "PROFILE=EPJE not handled by JP2OPENJPEG driver"); |
5302 | 0 | } |
5303 | |
|
5304 | 0 | int nBlockXSize = |
5305 | 0 | atoi(CSLFetchNameValueDef(papszFullOptions, "BLOCKXSIZE", "0")); |
5306 | 0 | int nBlockYSize = |
5307 | 0 | atoi(CSLFetchNameValueDef(papszFullOptions, "BLOCKYSIZE", "0")); |
5308 | 0 | if (pszProfile && STARTS_WITH_CI(pszProfile, "NPJE") && |
5309 | 0 | ((nBlockXSize != 0 && nBlockXSize != 1024) || |
5310 | 0 | (nBlockYSize != 0 && nBlockYSize != 1024))) |
5311 | 0 | { |
5312 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
5313 | 0 | "PROFILE=NPJE implies 1024x1024 tiles"); |
5314 | 0 | } |
5315 | |
|
5316 | 0 | if (nXSize >= 1024 || nYSize >= 1024 || |
5317 | 0 | (pszProfile && STARTS_WITH_CI(pszProfile, "NPJE"))) |
5318 | 0 | { |
5319 | | // The JP2OPENJPEG driver uses 1024 block size by default. Set it |
5320 | | // explicitly for NITFCreate() purposes. |
5321 | 0 | if (nBlockXSize == 0) |
5322 | 0 | { |
5323 | 0 | papszFullOptions = |
5324 | 0 | CSLSetNameValue(papszFullOptions, "BLOCKXSIZE", "1024"); |
5325 | 0 | } |
5326 | 0 | if (nBlockYSize == 0) |
5327 | 0 | { |
5328 | 0 | papszFullOptions = |
5329 | 0 | CSLSetNameValue(papszFullOptions, "BLOCKYSIZE", "1024"); |
5330 | 0 | } |
5331 | 0 | } |
5332 | | |
5333 | | // Compose J2KLRA TRE for NPJE profiles |
5334 | 0 | if (pszProfile && STARTS_WITH_CI(pszProfile, "NPJE") && |
5335 | 0 | CPLTestBool( |
5336 | 0 | CSLFetchNameValueDef(papszFullOptions, "J2KLRA", "YES"))) |
5337 | 0 | { |
5338 | 0 | #if defined(__GNUC__) |
5339 | 0 | #pragma GCC diagnostic push |
5340 | 0 | #pragma GCC diagnostic ignored "-Warray-bounds" |
5341 | 0 | #endif |
5342 | | // See Table 2.3-3 - Target Bit Rates for Each Tile in Panchromatic |
5343 | | // Image Segments of STDI-0006 |
5344 | 0 | std::vector<double> adfBPP = { |
5345 | 0 | 0.03125, 0.0625, 0.125, 0.25, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, |
5346 | 0 | 1.1, 1.2, 1.3, 1.5, 1.7, 2.0, 2.3, 3.5, 3.9}; |
5347 | |
|
5348 | 0 | if (EQUAL(pszProfile, "NPJE") || |
5349 | 0 | EQUAL(pszProfile, "NPJE_NUMERICALLY_LOSSLESS")) |
5350 | 0 | { |
5351 | 0 | adfBPP.push_back(nABPP); |
5352 | 0 | } |
5353 | 0 | #if defined(__GNUC__) |
5354 | 0 | #pragma GCC diagnostic pop |
5355 | 0 | #endif |
5356 | |
|
5357 | 0 | double dfQuality = |
5358 | 0 | CPLAtof(CSLFetchNameValueDef(papszFullOptions, "QUALITY", "0")); |
5359 | 0 | double dfTarget = |
5360 | 0 | CPLAtof(CSLFetchNameValueDef(papszFullOptions, "TARGET", "0")); |
5361 | 0 | if (dfTarget > 0 && dfTarget < 100) |
5362 | 0 | dfQuality = 100. - dfTarget; |
5363 | |
|
5364 | 0 | if (dfQuality != 0.0) |
5365 | 0 | { |
5366 | 0 | for (size_t i = 0; i < adfBPP.size(); ++i) |
5367 | 0 | { |
5368 | | // the JP2OPENJPEG QUALITY setting is 100. / |
5369 | | // compression_ratio and compression_ratio = 8 / bpp |
5370 | 0 | double dfLayerQuality = 100.0 / (8.0 / adfBPP[i]); |
5371 | 0 | if (dfLayerQuality > dfQuality) |
5372 | 0 | { |
5373 | 0 | adfBPP[i] = dfQuality / 100.0 * nABPP; |
5374 | 0 | adfBPP.resize(i + 1); |
5375 | 0 | break; |
5376 | 0 | } |
5377 | 0 | } |
5378 | 0 | } |
5379 | |
|
5380 | 0 | CPLString osJ2KLRA("TRE=J2KLRA="); |
5381 | 0 | osJ2KLRA += '0'; // ORIG: 0=Original NPJE |
5382 | 0 | osJ2KLRA += "05"; // Number of wavelets decompositions. |
5383 | | // This corresponds to the value of the |
5384 | | // RESOLUTIONS JP2OPENJPEG creation option - 1 |
5385 | 0 | osJ2KLRA += CPLSPrintf("%05d", poSrcDS->GetRasterCount()); |
5386 | 0 | osJ2KLRA += CPLSPrintf("%03d", static_cast<int>(adfBPP.size())); |
5387 | 0 | for (size_t i = 0; i < adfBPP.size(); ++i) |
5388 | 0 | { |
5389 | 0 | osJ2KLRA += CPLSPrintf("%03d", static_cast<int>(i)); |
5390 | 0 | osJ2KLRA += CPLSPrintf("%09.6f", adfBPP[i]); |
5391 | 0 | } |
5392 | 0 | papszFullOptions = CSLAddString(papszFullOptions, osJ2KLRA); |
5393 | 0 | } |
5394 | 0 | } |
5395 | | |
5396 | 10 | int nIMIndex = 0; |
5397 | 10 | int nImageCount = 0; |
5398 | 10 | vsi_l_offset nImageOffset = 0; |
5399 | 10 | vsi_l_offset nICOffset = 0; |
5400 | 10 | if (!NITFCreateEx(pszFilename, nXSize, nYSize, poSrcDS->GetRasterCount(), |
5401 | 10 | GDALGetDataTypeSizeBits(eType), pszPVType, |
5402 | 10 | papszFullOptions, &nIMIndex, &nImageCount, &nImageOffset, |
5403 | 10 | &nICOffset)) |
5404 | 0 | { |
5405 | 0 | CSLDestroy(papszFullOptions); |
5406 | 0 | CSLDestroy(papszCgmMD); |
5407 | 0 | CSLDestroy(papszTextMD); |
5408 | 0 | return nullptr; |
5409 | 0 | } |
5410 | | |
5411 | | /* ==================================================================== */ |
5412 | | /* JPEG2000 case. We need to write the data through a J2K */ |
5413 | | /* driver in pixel interleaved form. */ |
5414 | | /* ==================================================================== */ |
5415 | 10 | NITFDataset *poDstDS = nullptr; |
5416 | | |
5417 | 10 | if (bJPEG2000) |
5418 | 0 | { |
5419 | 0 | CPLString osDSName; |
5420 | 0 | osDSName.Printf("/vsisubfile/" CPL_FRMT_GUIB "_%d,%s", |
5421 | 0 | static_cast<GUIntBig>(nImageOffset), -1, pszFilename); |
5422 | |
|
5423 | 0 | GDALDataset *poJ2KDataset = nullptr; |
5424 | 0 | if (EQUAL(poJ2KDriver->GetDescription(), "JP2ECW")) |
5425 | 0 | { |
5426 | 0 | char **papszJP2Options = NITFJP2ECWOptions(papszFullOptions); |
5427 | 0 | poJ2KDataset = poJ2KDriver->CreateCopy(osDSName, poSrcDS, FALSE, |
5428 | 0 | papszJP2Options, pfnProgress, |
5429 | 0 | pProgressData); |
5430 | 0 | CSLDestroy(papszJP2Options); |
5431 | 0 | } |
5432 | 0 | else if (EQUAL(poJ2KDriver->GetDescription(), "JP2KAK")) |
5433 | 0 | { |
5434 | 0 | char **papszJP2Options = NITFJP2KAKOptions(papszFullOptions, nABPP); |
5435 | 0 | poJ2KDataset = poJ2KDriver->CreateCopy(osDSName, poSrcDS, FALSE, |
5436 | 0 | papszJP2Options, pfnProgress, |
5437 | 0 | pProgressData); |
5438 | 0 | CSLDestroy(papszJP2Options); |
5439 | 0 | } |
5440 | 0 | else if (EQUAL(poJ2KDriver->GetDescription(), "JP2OPENJPEG")) |
5441 | 0 | { |
5442 | 0 | char **papszJP2Options = |
5443 | 0 | NITFJP2OPENJPEGOptions(poJ2KDriver, papszFullOptions, nABPP); |
5444 | 0 | poJ2KDataset = poJ2KDriver->CreateCopy(osDSName, poSrcDS, FALSE, |
5445 | 0 | papszJP2Options, pfnProgress, |
5446 | 0 | pProgressData); |
5447 | 0 | CSLDestroy(papszJP2Options); |
5448 | 0 | } |
5449 | 0 | else |
5450 | 0 | { |
5451 | | /* Jasper case */ |
5452 | 0 | const char *apszOptions[] = {"FORMAT=JPC", nullptr}; |
5453 | 0 | poJ2KDataset = poJ2KDriver->CreateCopy( |
5454 | 0 | osDSName, poSrcDS, FALSE, const_cast<char **>(apszOptions), |
5455 | 0 | pfnProgress, pProgressData); |
5456 | 0 | } |
5457 | 0 | if (poJ2KDataset == nullptr) |
5458 | 0 | { |
5459 | 0 | CSLDestroy(papszCgmMD); |
5460 | 0 | CSLDestroy(papszTextMD); |
5461 | 0 | CSLDestroy(papszFullOptions); |
5462 | 0 | return nullptr; |
5463 | 0 | } |
5464 | | |
5465 | 0 | delete poJ2KDataset; |
5466 | | |
5467 | | // Now we need to figure out the actual length of the file |
5468 | | // and correct the image segment size information. |
5469 | 0 | const GIntBig nPixelCount = |
5470 | 0 | static_cast<GIntBig>(nXSize) * nYSize * poSrcDS->GetRasterCount(); |
5471 | |
|
5472 | 0 | bool bOK = NITFPatchImageLength(pszFilename, nIMIndex, nImageOffset, |
5473 | 0 | nPixelCount, "C8", nICOffset, |
5474 | 0 | papszFullOptions); |
5475 | 0 | if (nIMIndex + 1 == nImageCount) |
5476 | 0 | { |
5477 | 0 | bOK &= NITFWriteExtraSegments(pszFilename, papszCgmMD, papszTextMD, |
5478 | 0 | papszFullOptions); |
5479 | 0 | } |
5480 | 0 | if (!bOK) |
5481 | 0 | { |
5482 | 0 | CSLDestroy(papszCgmMD); |
5483 | 0 | CSLDestroy(papszTextMD); |
5484 | 0 | CSLDestroy(papszFullOptions); |
5485 | 0 | return nullptr; |
5486 | 0 | } |
5487 | | |
5488 | 0 | GDALOpenInfo oOpenInfo(pszFilename, GA_Update); |
5489 | 0 | poDstDS = OpenInternal(&oOpenInfo, nullptr, true, |
5490 | 0 | nImageCount == 1 ? -1 : nIMIndex); |
5491 | |
|
5492 | 0 | if (poDstDS == nullptr) |
5493 | 0 | { |
5494 | 0 | CSLDestroy(papszCgmMD); |
5495 | 0 | CSLDestroy(papszTextMD); |
5496 | 0 | CSLDestroy(papszFullOptions); |
5497 | 0 | return nullptr; |
5498 | 0 | } |
5499 | 0 | } |
5500 | | |
5501 | | /* ==================================================================== */ |
5502 | | /* Loop copying bands to an uncompressed file. */ |
5503 | | /* ==================================================================== */ |
5504 | 10 | else if (bJPEG) |
5505 | 0 | { |
5506 | 0 | #ifdef JPEG_SUPPORTED |
5507 | 0 | NITFFile *psFile = NITFOpen(pszFilename, TRUE); |
5508 | 0 | if (psFile == nullptr) |
5509 | 0 | { |
5510 | 0 | CSLDestroy(papszCgmMD); |
5511 | 0 | CSLDestroy(papszTextMD); |
5512 | 0 | CSLDestroy(papszFullOptions); |
5513 | 0 | return nullptr; |
5514 | 0 | } |
5515 | | |
5516 | 0 | const bool bSuccess = |
5517 | 0 | NITFWriteJPEGImage(poSrcDS, psFile->fp, nImageOffset, |
5518 | 0 | papszFullOptions, pfnProgress, pProgressData); |
5519 | |
|
5520 | 0 | if (!bSuccess) |
5521 | 0 | { |
5522 | 0 | NITFClose(psFile); |
5523 | 0 | CSLDestroy(papszCgmMD); |
5524 | 0 | CSLDestroy(papszTextMD); |
5525 | 0 | CSLDestroy(papszFullOptions); |
5526 | 0 | return nullptr; |
5527 | 0 | } |
5528 | | |
5529 | | // Now we need to figure out the actual length of the file |
5530 | | // and correct the image segment size information. |
5531 | 0 | const GIntBig nPixelCount = |
5532 | 0 | static_cast<GIntBig>(nXSize) * nYSize * poSrcDS->GetRasterCount(); |
5533 | |
|
5534 | 0 | NITFClose(psFile); |
5535 | |
|
5536 | 0 | bool bOK = NITFPatchImageLength(pszFilename, nIMIndex, nImageOffset, |
5537 | 0 | nPixelCount, pszIC, nICOffset, |
5538 | 0 | papszFullOptions); |
5539 | 0 | if (nIMIndex + 1 == nImageCount) |
5540 | 0 | { |
5541 | 0 | bOK &= NITFWriteExtraSegments(pszFilename, papszCgmMD, papszTextMD, |
5542 | 0 | papszFullOptions); |
5543 | 0 | } |
5544 | 0 | if (!bOK) |
5545 | 0 | { |
5546 | 0 | CSLDestroy(papszCgmMD); |
5547 | 0 | CSLDestroy(papszTextMD); |
5548 | 0 | CSLDestroy(papszFullOptions); |
5549 | 0 | return nullptr; |
5550 | 0 | } |
5551 | | |
5552 | 0 | GDALOpenInfo oOpenInfo(pszFilename, GA_Update); |
5553 | 0 | poDstDS = OpenInternal(&oOpenInfo, nullptr, true, |
5554 | 0 | nImageCount == 1 ? -1 : nIMIndex); |
5555 | |
|
5556 | 0 | if (poDstDS == nullptr) |
5557 | 0 | { |
5558 | 0 | CSLDestroy(papszCgmMD); |
5559 | 0 | CSLDestroy(papszTextMD); |
5560 | 0 | CSLDestroy(papszFullOptions); |
5561 | 0 | return nullptr; |
5562 | 0 | } |
5563 | 0 | #endif /* def JPEG_SUPPORTED */ |
5564 | 0 | } |
5565 | | |
5566 | | /* ==================================================================== */ |
5567 | | /* Loop copying bands to an uncompressed file. */ |
5568 | | /* ==================================================================== */ |
5569 | 10 | else |
5570 | 10 | { |
5571 | 10 | if (nIMIndex + 1 == nImageCount) |
5572 | 10 | { |
5573 | 10 | bool bOK = NITFWriteExtraSegments(pszFilename, papszCgmMD, |
5574 | 10 | papszTextMD, papszFullOptions); |
5575 | 10 | if (!bOK) |
5576 | 0 | { |
5577 | 0 | CSLDestroy(papszCgmMD); |
5578 | 0 | CSLDestroy(papszTextMD); |
5579 | 0 | CSLDestroy(papszFullOptions); |
5580 | 0 | return nullptr; |
5581 | 0 | } |
5582 | 10 | } |
5583 | | |
5584 | | // Save error state to restore it afterwards since some operations |
5585 | | // in Open() might reset it. |
5586 | 10 | CPLErr eLastErr = CPLGetLastErrorType(); |
5587 | 10 | int nLastErrNo = CPLGetLastErrorNo(); |
5588 | 10 | CPLString osLastErrorMsg = CPLGetLastErrorMsg(); |
5589 | | |
5590 | 10 | GDALOpenInfo oOpenInfo(pszFilename, GA_Update); |
5591 | 10 | poDstDS = OpenInternal(&oOpenInfo, nullptr, true, |
5592 | 10 | nImageCount == 1 ? -1 : nIMIndex); |
5593 | | |
5594 | 10 | if (CPLGetLastErrorType() == CE_None && eLastErr != CE_None) |
5595 | 10 | CPLErrorSetState(eLastErr, nLastErrNo, osLastErrorMsg.c_str()); |
5596 | | |
5597 | 10 | if (poDstDS == nullptr) |
5598 | 0 | { |
5599 | 0 | CSLDestroy(papszCgmMD); |
5600 | 0 | CSLDestroy(papszTextMD); |
5601 | 0 | CSLDestroy(papszFullOptions); |
5602 | 0 | return nullptr; |
5603 | 0 | } |
5604 | | |
5605 | 10 | void *pData = VSIMalloc2(nXSize, GDALGetDataTypeSizeBytes(eType)); |
5606 | 10 | if (pData == nullptr) |
5607 | 0 | { |
5608 | 0 | delete poDstDS; |
5609 | 0 | CSLDestroy(papszCgmMD); |
5610 | 0 | CSLDestroy(papszTextMD); |
5611 | 0 | CSLDestroy(papszFullOptions); |
5612 | 0 | return nullptr; |
5613 | 0 | } |
5614 | | |
5615 | 10 | CPLErr eErr = CE_None; |
5616 | | |
5617 | 1.87k | for (int iBand = 0; nIMIndex >= 0 && eErr == CE_None && |
5618 | 1.87k | iBand < poSrcDS->GetRasterCount(); |
5619 | 1.86k | iBand++) |
5620 | 1.86k | { |
5621 | 1.86k | GDALRasterBand *poSrcBand = poSrcDS->GetRasterBand(iBand + 1); |
5622 | 1.86k | GDALRasterBand *poDstBand = poDstDS->GetRasterBand(iBand + 1); |
5623 | | |
5624 | | /* -------------------------------------------------------------------- |
5625 | | */ |
5626 | | /* Do we need to copy a colortable or other metadata? */ |
5627 | | /* -------------------------------------------------------------------- |
5628 | | */ |
5629 | 1.86k | GDALColorTable *poCT = poSrcBand->GetColorTable(); |
5630 | 1.86k | if (poCT != nullptr) |
5631 | 1.08k | poDstBand->SetColorTable(poCT); |
5632 | | |
5633 | | /* -------------------------------------------------------------------- |
5634 | | */ |
5635 | | /* Copy image data. */ |
5636 | | /* -------------------------------------------------------------------- |
5637 | | */ |
5638 | 8.10k | for (int iLine = 0; iLine < nYSize; iLine++) |
5639 | 6.24k | { |
5640 | 6.24k | eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, pData, |
5641 | 6.24k | nXSize, 1, eType, 0, 0, nullptr); |
5642 | 6.24k | if (eErr != CE_None) |
5643 | 3 | break; |
5644 | | |
5645 | 6.24k | eErr = poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, pData, |
5646 | 6.24k | nXSize, 1, eType, 0, 0, nullptr); |
5647 | | |
5648 | 6.24k | if (eErr != CE_None) |
5649 | 0 | break; |
5650 | | |
5651 | 6.24k | if (!pfnProgress( |
5652 | 6.24k | (iBand + (iLine + 1) / static_cast<double>(nYSize)) / |
5653 | 6.24k | static_cast<double>(poSrcDS->GetRasterCount()), |
5654 | 6.24k | nullptr, pProgressData)) |
5655 | 0 | { |
5656 | 0 | CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated"); |
5657 | 0 | eErr = CE_Failure; |
5658 | 0 | break; |
5659 | 0 | } |
5660 | 6.24k | } |
5661 | 1.86k | } |
5662 | | |
5663 | 10 | CPLFree(pData); |
5664 | | |
5665 | 10 | if (eErr != CE_None) |
5666 | 3 | { |
5667 | 3 | delete poDstDS; |
5668 | 3 | CSLDestroy(papszCgmMD); |
5669 | 3 | CSLDestroy(papszTextMD); |
5670 | 3 | CSLDestroy(papszFullOptions); |
5671 | 3 | return nullptr; |
5672 | 3 | } |
5673 | 10 | } |
5674 | | |
5675 | | /* -------------------------------------------------------------------- */ |
5676 | | /* Set the georeferencing. */ |
5677 | | /* -------------------------------------------------------------------- */ |
5678 | 7 | if (poDstDS->psImage == nullptr) |
5679 | 0 | { |
5680 | | // do nothing |
5681 | 0 | } |
5682 | 7 | else if (bManualWriteOfIGEOLO) |
5683 | 0 | { |
5684 | 0 | if (!NITFWriteIGEOLO(poDstDS->psImage, poDstDS->psImage->chICORDS, |
5685 | 0 | poDstDS->psImage->nZone, dfIGEOLOULX, dfIGEOLOULY, |
5686 | 0 | dfIGEOLOURX, dfIGEOLOURY, dfIGEOLOLRX, dfIGEOLOLRY, |
5687 | 0 | dfIGEOLOLLX, dfIGEOLOLLY)) |
5688 | 0 | { |
5689 | 0 | delete poDstDS; |
5690 | 0 | CSLDestroy(papszCgmMD); |
5691 | 0 | CSLDestroy(papszTextMD); |
5692 | 0 | CSLDestroy(papszFullOptions); |
5693 | 0 | return nullptr; |
5694 | 0 | } |
5695 | 0 | } |
5696 | 7 | else if (bWriteGeoTransform) |
5697 | 5 | { |
5698 | 5 | poDstDS->psImage->nZone = nZone; |
5699 | 5 | poDstDS->SetGeoTransform(gt); |
5700 | 5 | } |
5701 | 2 | else if (bWriteGCPs) |
5702 | 0 | { |
5703 | 0 | poDstDS->psImage->nZone = nZone; |
5704 | 0 | poDstDS->SetGCPs(poSrcDS->GetGCPCount(), poSrcDS->GetGCPs(), |
5705 | 0 | poSrcDS->GetGCPSpatialRef()); |
5706 | 0 | } |
5707 | | |
5708 | 7 | poDstDS->CloneInfo(poSrcDS, nGCIFFlags); |
5709 | | |
5710 | 7 | if ((nGCIFFlags & GCIF_METADATA) == 0) |
5711 | 0 | { |
5712 | 0 | const int nSavedMOFlags = poDstDS->GetMOFlags(); |
5713 | 0 | papszSrcMD = poSrcDS->GetMetadata(); |
5714 | 0 | if (papszSrcMD != nullptr) |
5715 | 0 | { |
5716 | 0 | if (!bUseSrcNITFMetadata) |
5717 | 0 | { |
5718 | 0 | char **papszNewMD = CSLDuplicate(poDstDS->GetMetadata()); |
5719 | 0 | bool bAdded = false; |
5720 | 0 | for (const char *pszKeyValue : cpl::Iterate(papszSrcMD)) |
5721 | 0 | { |
5722 | 0 | if (!STARTS_WITH(pszKeyValue, "NITF_")) |
5723 | 0 | { |
5724 | 0 | bAdded = true; |
5725 | 0 | papszNewMD = CSLAddString(papszNewMD, pszKeyValue); |
5726 | 0 | } |
5727 | 0 | } |
5728 | 0 | if (bAdded) |
5729 | 0 | { |
5730 | 0 | poDstDS->SetMetadata(papszNewMD); |
5731 | 0 | } |
5732 | 0 | CSLDestroy(papszNewMD); |
5733 | 0 | } |
5734 | 0 | else if (CSLCount(poDstDS->GetMetadata()) != CSLCount(papszSrcMD)) |
5735 | 0 | { |
5736 | 0 | poDstDS->SetMetadata(papszSrcMD); |
5737 | 0 | } |
5738 | 0 | } |
5739 | 0 | poDstDS->SetMOFlags(nSavedMOFlags); |
5740 | 0 | } |
5741 | | |
5742 | 7 | CSLDestroy(papszCgmMD); |
5743 | 7 | CSLDestroy(papszTextMD); |
5744 | 7 | CSLDestroy(papszFullOptions); |
5745 | | |
5746 | 7 | return poDstDS; |
5747 | 7 | } |
5748 | | |
5749 | | /************************************************************************/ |
5750 | | /* NITFPatchImageLength() */ |
5751 | | /* */ |
5752 | | /* Fixup various stuff we don't know till we have written the */ |
5753 | | /* imagery. In particular the file length, image data length */ |
5754 | | /* and the compression ratio achieved. */ |
5755 | | /************************************************************************/ |
5756 | | |
5757 | | static bool NITFPatchImageLength(const char *pszFilename, int nIMIndex, |
5758 | | GUIntBig nImageOffset, GIntBig nPixelCount, |
5759 | | const char *pszIC, vsi_l_offset nICOffset, |
5760 | | CSLConstList papszCreationOptions) |
5761 | | |
5762 | 0 | { |
5763 | 0 | VSILFILE *fpVSIL = VSIFOpenL(pszFilename, "r+b"); |
5764 | 0 | if (fpVSIL == nullptr) |
5765 | 0 | return false; |
5766 | | |
5767 | 0 | CPL_IGNORE_RET_VAL(VSIFSeekL(fpVSIL, 0, SEEK_END)); |
5768 | 0 | GUIntBig nFileLen = VSIFTellL(fpVSIL); |
5769 | | |
5770 | | /* -------------------------------------------------------------------- */ |
5771 | | /* Update total file length. */ |
5772 | | /* -------------------------------------------------------------------- */ |
5773 | 0 | if (nFileLen >= NITF_MAX_FILE_SIZE) |
5774 | 0 | { |
5775 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
5776 | 0 | "Too big file : " CPL_FRMT_GUIB |
5777 | 0 | ". Truncating to " CPL_FRMT_GUIB, |
5778 | 0 | nFileLen, NITF_MAX_FILE_SIZE - 1); |
5779 | 0 | nFileLen = NITF_MAX_FILE_SIZE - 1; |
5780 | 0 | } |
5781 | 0 | CPLString osLen = |
5782 | 0 | CPLString().Printf("%012" CPL_FRMT_GB_WITHOUT_PREFIX "u", nFileLen); |
5783 | 0 | if (VSIFSeekL(fpVSIL, 342, SEEK_SET) != 0 || |
5784 | 0 | VSIFWriteL(reinterpret_cast<const void *>(osLen.c_str()), 12, 1, |
5785 | 0 | fpVSIL) != 1) |
5786 | 0 | { |
5787 | 0 | CPLError(CE_Failure, CPLE_FileIO, "Write error"); |
5788 | 0 | CPL_IGNORE_RET_VAL(VSIFCloseL(fpVSIL)); |
5789 | 0 | return false; |
5790 | 0 | } |
5791 | | |
5792 | | /* -------------------------------------------------------------------- */ |
5793 | | /* Update the image data length. */ |
5794 | | /* -------------------------------------------------------------------- */ |
5795 | 0 | GUIntBig nImageSize = nFileLen - nImageOffset; |
5796 | 0 | if (nImageSize >= 9999999999ULL) |
5797 | 0 | { |
5798 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
5799 | 0 | "Too big image size : " CPL_FRMT_GUIB |
5800 | 0 | ". Truncating to 9999999998", |
5801 | 0 | nImageSize); |
5802 | 0 | nImageSize = 9999999998ULL; |
5803 | 0 | } |
5804 | 0 | osLen = |
5805 | 0 | CPLString().Printf("%010" CPL_FRMT_GB_WITHOUT_PREFIX "u", nImageSize); |
5806 | 0 | if (VSIFSeekL(fpVSIL, 369 + 16 * nIMIndex, SEEK_SET) != 0 || |
5807 | 0 | VSIFWriteL(reinterpret_cast<const void *>(osLen.c_str()), 10, 1, |
5808 | 0 | fpVSIL) != 1) |
5809 | 0 | { |
5810 | 0 | CPLError(CE_Failure, CPLE_FileIO, "Write error"); |
5811 | 0 | CPL_IGNORE_RET_VAL(VSIFCloseL(fpVSIL)); |
5812 | 0 | return false; |
5813 | 0 | } |
5814 | | |
5815 | | /* -------------------------------------------------------------------- */ |
5816 | | /* Update COMRAT, the compression rate variable, and CLEVEL */ |
5817 | | /* -------------------------------------------------------------------- */ |
5818 | | |
5819 | | /* Set to IC position */ |
5820 | 0 | bool bOK = VSIFSeekL(fpVSIL, nICOffset, SEEK_SET) == 0; |
5821 | | |
5822 | | /* Read IC */ |
5823 | 0 | char szICBuf[2]; |
5824 | 0 | bOK &= VSIFReadL(szICBuf, 2, 1, fpVSIL) == 1; |
5825 | | |
5826 | | /* The following line works around a "feature" of *BSD libc (at least |
5827 | | * PC-BSD 7.1) */ |
5828 | | /* that makes the position of the file offset unreliable when executing a */ |
5829 | | /* "seek, read and write" sequence. After the read(), the file offset seen |
5830 | | * by */ |
5831 | | /* the write() is approximately the size of a block further... */ |
5832 | 0 | bOK &= VSIFSeekL(fpVSIL, VSIFTellL(fpVSIL), SEEK_SET) == 0; |
5833 | |
|
5834 | 0 | if (!EQUALN(szICBuf, pszIC, 2)) |
5835 | 0 | { |
5836 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
5837 | 0 | "Unable to locate COMRAT to update in NITF header."); |
5838 | 0 | } |
5839 | 0 | else |
5840 | 0 | { |
5841 | 0 | char szCOMRAT[5]; |
5842 | |
|
5843 | 0 | if (EQUAL(pszIC, "C8")) /* jpeg2000 */ |
5844 | 0 | { |
5845 | 0 | double dfRate = static_cast<GIntBig>(nFileLen - nImageOffset) * 8 / |
5846 | 0 | static_cast<double>(nPixelCount); |
5847 | |
|
5848 | 0 | const char *pszProfile = |
5849 | 0 | CSLFetchNameValueDef(papszCreationOptions, "PROFILE", ""); |
5850 | 0 | if (STARTS_WITH_CI(pszProfile, "NPJE")) |
5851 | 0 | { |
5852 | 0 | dfRate = std::max(0.1, std::min(99.9, dfRate)); |
5853 | | |
5854 | | // We emit in Vxyz or Nxyz format with an implicit decimal place |
5855 | | // between yz and z as per spec. |
5856 | 0 | snprintf(szCOMRAT, sizeof(szCOMRAT), "%c%03u", |
5857 | 0 | EQUAL(pszProfile, "NPJE_VISUALLY_LOSSLESS") ? 'V' |
5858 | 0 | : 'N', |
5859 | | // % 1000 to please -Wformat-truncation |
5860 | 0 | static_cast<unsigned>(dfRate * 10) % 1000); |
5861 | 0 | } |
5862 | 0 | else |
5863 | 0 | { |
5864 | 0 | dfRate = std::max(0.01, std::min(99.99, dfRate)); |
5865 | | |
5866 | | // We emit in wxyz format with an implicit decimal place |
5867 | | // between wx and yz as per spec for lossy compression. |
5868 | | // We really should have a special case for lossless |
5869 | | // compression. |
5870 | 0 | snprintf(szCOMRAT, sizeof(szCOMRAT), "%04u", |
5871 | | // % 10000 to please -Wformat-truncation |
5872 | 0 | static_cast<unsigned>(dfRate * 100) % 10000); |
5873 | 0 | } |
5874 | 0 | } |
5875 | 0 | else if (EQUAL(pszIC, "C3") || EQUAL(pszIC, "M3")) /* jpeg */ |
5876 | 0 | { |
5877 | 0 | strcpy(szCOMRAT, "00.0"); |
5878 | 0 | } |
5879 | |
|
5880 | 0 | bOK &= VSIFWriteL(szCOMRAT, 4, 1, fpVSIL) == 1; |
5881 | | |
5882 | | /* ---------------------------------------------------------------- */ |
5883 | | /* Update CLEVEL. */ |
5884 | | /* ---------------------------------------------------------------- */ |
5885 | | // Get existing CLEVEL |
5886 | 0 | constexpr vsi_l_offset OFFSET_CLEVEL = 9; |
5887 | 0 | constexpr int SIZE_CLEVEL = 2; |
5888 | 0 | bOK &= VSIFSeekL(fpVSIL, OFFSET_CLEVEL, SEEK_SET) == 0; |
5889 | 0 | char szCLEVEL[SIZE_CLEVEL + 1] = {0}; |
5890 | 0 | bOK &= VSIFReadL(szCLEVEL, 1, SIZE_CLEVEL, fpVSIL) != 0; |
5891 | 0 | unsigned int nCLevel = static_cast<unsigned>(atoi(szCLEVEL)); |
5892 | 0 | if (nCLevel >= 3 && nCLevel <= 7) |
5893 | 0 | { |
5894 | 0 | const unsigned int nCLevelOri = nCLevel; |
5895 | 0 | if (nFileLen > 2147483647) |
5896 | 0 | { |
5897 | 0 | nCLevel = MAX(nCLevel, 7U); |
5898 | 0 | } |
5899 | 0 | else if (nFileLen > 1073741833) |
5900 | 0 | { |
5901 | 0 | nCLevel = MAX(nCLevel, 6U); |
5902 | 0 | } |
5903 | 0 | else if (nFileLen > 52428799) |
5904 | 0 | { |
5905 | 0 | nCLevel = MAX(nCLevel, 5U); |
5906 | 0 | } |
5907 | 0 | if (nCLevel != nCLevelOri) |
5908 | 0 | { |
5909 | 0 | CPLDebug("NITF", "Updating CLEVEL from %02u to %02u", |
5910 | 0 | nCLevelOri, nCLevel); |
5911 | | // %100 to please -Wformat-truncation |
5912 | 0 | snprintf(szCLEVEL, sizeof(szCLEVEL), "%02u", nCLevel % 100); |
5913 | 0 | bOK &= VSIFSeekL(fpVSIL, OFFSET_CLEVEL, SEEK_SET) == 0; |
5914 | 0 | bOK &= VSIFWriteL(szCLEVEL, 1, SIZE_CLEVEL, fpVSIL) != 0; |
5915 | 0 | } |
5916 | 0 | } |
5917 | 0 | else |
5918 | 0 | { |
5919 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
5920 | 0 | "Invalid CLEVEL=%s value found when updating NITF header.", |
5921 | 0 | szCLEVEL); |
5922 | 0 | } |
5923 | 0 | } |
5924 | |
|
5925 | 0 | if (VSIFCloseL(fpVSIL) != 0) |
5926 | 0 | bOK = false; |
5927 | |
|
5928 | 0 | if (!bOK) |
5929 | 0 | { |
5930 | 0 | CPLError(CE_Failure, CPLE_FileIO, "I/O error"); |
5931 | 0 | } |
5932 | |
|
5933 | 0 | return bOK; |
5934 | 0 | } |
5935 | | |
5936 | | /************************************************************************/ |
5937 | | /* NITFWriteCGMSegments() */ |
5938 | | /************************************************************************/ |
5939 | | static bool NITFWriteCGMSegments(const char *pszFilename, VSILFILE *&fpVSIL, |
5940 | | CSLConstList papszList) |
5941 | 10 | { |
5942 | 10 | char errorMessage[255] = ""; |
5943 | | |
5944 | | // size of each Cgm header entry (LS (4) + LSSH (6)) |
5945 | 10 | const int nCgmHdrEntrySz = 10; |
5946 | | |
5947 | 10 | if (papszList == nullptr) |
5948 | 10 | return true; |
5949 | | |
5950 | 0 | int nNUMS = 0; |
5951 | 0 | const char *pszNUMS = CSLFetchNameValue(papszList, "SEGMENT_COUNT"); |
5952 | 0 | if (pszNUMS != nullptr) |
5953 | 0 | { |
5954 | 0 | nNUMS = atoi(pszNUMS); |
5955 | 0 | } |
5956 | | |
5957 | | /* -------------------------------------------------------------------- */ |
5958 | | /* Open the target file if not already done. */ |
5959 | | /* -------------------------------------------------------------------- */ |
5960 | 0 | if (fpVSIL == nullptr) |
5961 | 0 | fpVSIL = VSIFOpenL(pszFilename, "r+b"); |
5962 | 0 | if (fpVSIL == nullptr) |
5963 | 0 | return false; |
5964 | | |
5965 | | // Calculates the offset for NUMS so we can update header data |
5966 | 0 | char achNUMI[4]; // 3 digits plus null character |
5967 | 0 | achNUMI[3] = '\0'; |
5968 | | |
5969 | | // NUMI offset is at a fixed offset 363 |
5970 | 0 | const vsi_l_offset nNumIOffset = 360; |
5971 | 0 | bool bOK = VSIFSeekL(fpVSIL, nNumIOffset, SEEK_SET) == 0; |
5972 | 0 | bOK &= VSIFReadL(achNUMI, 3, 1, fpVSIL) == 1; |
5973 | 0 | const int nIM = atoi(achNUMI); |
5974 | | |
5975 | | // 6 for size of LISH and 10 for size of LI |
5976 | | // NUMS offset is NumI offset plus the size of NumI + size taken up each |
5977 | | // the header data multiply by the number of data |
5978 | |
|
5979 | 0 | const vsi_l_offset nNumSOffset = nNumIOffset + 3 + nIM * (6 + 10); |
5980 | | |
5981 | | /* -------------------------------------------------------------------- */ |
5982 | | /* Confirm that the NUMS in the file header already matches the */ |
5983 | | /* number of graphic segments we want to write */ |
5984 | | /* -------------------------------------------------------------------- */ |
5985 | 0 | char achNUMS[4]; |
5986 | |
|
5987 | 0 | bOK &= VSIFSeekL(fpVSIL, nNumSOffset, SEEK_SET) == 0; |
5988 | 0 | bOK &= VSIFReadL(achNUMS, 3, 1, fpVSIL) == 1; |
5989 | 0 | achNUMS[3] = '\0'; |
5990 | |
|
5991 | 0 | if (!bOK || atoi(achNUMS) != nNUMS) |
5992 | 0 | { |
5993 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
5994 | 0 | "It appears an attempt was made to add or update graphic\n" |
5995 | 0 | "segments on an NITF file with existing segments. This\n" |
5996 | 0 | "is not currently supported by the GDAL NITF driver."); |
5997 | |
|
5998 | 0 | return false; |
5999 | 0 | } |
6000 | | |
6001 | | // allocate space for graphic header. |
6002 | | // Size of LS = 4, size of LSSH = 6, and 1 for null character |
6003 | 0 | char *pachLS = |
6004 | 0 | static_cast<char *>(CPLCalloc(nNUMS * nCgmHdrEntrySz + 1, 1)); |
6005 | | |
6006 | | /* -------------------------------------------------------------------- */ |
6007 | | /* Assume no extended data such as SXSHDL, SXSHD */ |
6008 | | /* -------------------------------------------------------------------- */ |
6009 | | |
6010 | | /* ==================================================================== */ |
6011 | | /* Write the Graphics segments at the end of the file. */ |
6012 | | /* ==================================================================== */ |
6013 | |
|
6014 | 0 | #define PLACE(location, name, text) memcpy(location, text, strlen(text)) |
6015 | |
|
6016 | 0 | for (int i = 0; bOK && i < nNUMS; i++) |
6017 | 0 | { |
6018 | | |
6019 | | // Get all the fields for current CGM segment |
6020 | 0 | const char *pszSlocRow = CSLFetchNameValue( |
6021 | 0 | papszList, CPLString().Printf("SEGMENT_%d_SLOC_ROW", i)); |
6022 | 0 | const char *pszSlocCol = CSLFetchNameValue( |
6023 | 0 | papszList, CPLString().Printf("SEGMENT_%d_SLOC_COL", i)); |
6024 | 0 | const char *pszSdlvl = CSLFetchNameValue( |
6025 | 0 | papszList, CPLString().Printf("SEGMENT_%d_SDLVL", i)); |
6026 | 0 | const char *pszSalvl = CSLFetchNameValue( |
6027 | 0 | papszList, CPLString().Printf("SEGMENT_%d_SALVL", i)); |
6028 | 0 | const char *pszData = CSLFetchNameValue( |
6029 | 0 | papszList, CPLString().Printf("SEGMENT_%d_DATA", i)); |
6030 | | |
6031 | | // Error checking |
6032 | 0 | if (pszSlocRow == nullptr) |
6033 | 0 | { |
6034 | 0 | snprintf(errorMessage, sizeof(errorMessage), |
6035 | 0 | "NITF graphic segment writing error: SLOC_ROW for segment " |
6036 | 0 | "%d is not defined", |
6037 | 0 | i); |
6038 | 0 | break; |
6039 | 0 | } |
6040 | 0 | if (pszSlocCol == nullptr) |
6041 | 0 | { |
6042 | 0 | snprintf(errorMessage, sizeof(errorMessage), |
6043 | 0 | "NITF graphic segment writing error: SLOC_COL for segment " |
6044 | 0 | "%d is not defined", |
6045 | 0 | i); |
6046 | 0 | break; |
6047 | 0 | } |
6048 | 0 | if (pszSdlvl == nullptr) |
6049 | 0 | { |
6050 | 0 | snprintf(errorMessage, sizeof(errorMessage), |
6051 | 0 | "NITF graphic segment writing error: SDLVL for segment %d " |
6052 | 0 | "is not defined", |
6053 | 0 | i); |
6054 | 0 | break; |
6055 | 0 | } |
6056 | 0 | if (pszSalvl == nullptr) |
6057 | 0 | { |
6058 | 0 | snprintf(errorMessage, sizeof(errorMessage), |
6059 | 0 | "NITF graphic segment writing error: SALVLfor segment %d " |
6060 | 0 | "is not defined", |
6061 | 0 | i); |
6062 | 0 | break; |
6063 | 0 | } |
6064 | 0 | if (pszData == nullptr) |
6065 | 0 | { |
6066 | 0 | snprintf(errorMessage, sizeof(errorMessage), |
6067 | 0 | "NITF graphic segment writing error: DATA for segment %d " |
6068 | 0 | "is not defined", |
6069 | 0 | i); |
6070 | 0 | break; |
6071 | 0 | } |
6072 | | |
6073 | 0 | const int nSlocCol = atoi(pszSlocRow); |
6074 | 0 | const int nSlocRow = atoi(pszSlocCol); |
6075 | 0 | const int nSdlvl = atoi(pszSdlvl); |
6076 | 0 | const int nSalvl = atoi(pszSalvl); |
6077 | | |
6078 | | // Create a buffer for graphics segment header, 258 is the size of |
6079 | | // the header that we will be writing. |
6080 | 0 | char achGSH[258]; |
6081 | |
|
6082 | 0 | memset(achGSH, ' ', sizeof(achGSH)); |
6083 | |
|
6084 | 0 | PLACE(achGSH + 0, SY, "SY"); |
6085 | 0 | PLACE(achGSH + 2, SID, CPLSPrintf("%010d", i)); |
6086 | 0 | PLACE(achGSH + 12, SNAME, "DEFAULT NAME "); |
6087 | 0 | PLACE(achGSH + 32, SSCLAS, "U"); |
6088 | 0 | PLACE(achGSH + 33, SSCLASY, "0"); |
6089 | 0 | PLACE(achGSH + 199, ENCRYP, "0"); |
6090 | 0 | PLACE(achGSH + 200, SFMT, "C"); |
6091 | 0 | PLACE(achGSH + 201, SSTRUCT, "0000000000000"); |
6092 | 0 | PLACE(achGSH + 214, SDLVL, CPLSPrintf("%03d", nSdlvl)); // size3 |
6093 | 0 | PLACE(achGSH + 217, SALVL, CPLSPrintf("%03d", nSalvl)); // size3 |
6094 | 0 | PLACE(achGSH + 220, SLOC, |
6095 | 0 | CPLSPrintf("%05d%05d", nSlocRow, nSlocCol)); // size 10 |
6096 | 0 | PLACE(achGSH + 230, SBAND1, "0000000000"); |
6097 | 0 | PLACE(achGSH + 240, SCOLOR, "C"); |
6098 | 0 | PLACE(achGSH + 241, SBAND2, "0000000000"); |
6099 | 0 | PLACE(achGSH + 251, SRES2, "00"); |
6100 | 0 | PLACE(achGSH + 253, SXSHDL, "00000"); |
6101 | | |
6102 | | // Move to the end of the file |
6103 | 0 | bOK &= VSIFSeekL(fpVSIL, 0, SEEK_END) == 0; |
6104 | 0 | bOK &= VSIFWriteL(achGSH, sizeof(achGSH), 1, fpVSIL) == 1; |
6105 | | |
6106 | | /* ------------------------------------------------------------------ */ |
6107 | | /* Prepare and write CGM segment data. */ |
6108 | | /* ------------------------------------------------------------------ */ |
6109 | 0 | int nCGMSize = 0; |
6110 | 0 | char *pszCgmToWrite = |
6111 | 0 | CPLUnescapeString(pszData, &nCGMSize, CPLES_BackslashQuotable); |
6112 | |
|
6113 | 0 | if (nCGMSize > 999998) |
6114 | 0 | { |
6115 | 0 | CPLError(CE_Warning, CPLE_NotSupported, |
6116 | 0 | "Length of SEGMENT_%d_DATA is %d, which is greater than " |
6117 | 0 | "999998. Truncating...", |
6118 | 0 | i + 1, nCGMSize); |
6119 | 0 | nCGMSize = 999998; |
6120 | 0 | } |
6121 | |
|
6122 | 0 | bOK &= static_cast<int>( |
6123 | 0 | VSIFWriteL(pszCgmToWrite, 1, nCGMSize, fpVSIL)) == nCGMSize; |
6124 | | |
6125 | | /* -------------------------------------------------------------------- |
6126 | | */ |
6127 | | /* Update the subheader and data size info in the file header. */ |
6128 | | /* -------------------------------------------------------------------- |
6129 | | */ |
6130 | 0 | snprintf(pachLS + nCgmHdrEntrySz * i, nCgmHdrEntrySz + 1, "%04d%06d", |
6131 | 0 | static_cast<int>(sizeof(achGSH)), nCGMSize); |
6132 | |
|
6133 | 0 | CPLFree(pszCgmToWrite); |
6134 | 0 | } // End For |
6135 | | |
6136 | | /* -------------------------------------------------------------------- */ |
6137 | | /* Write out the graphic segment info. */ |
6138 | | /* -------------------------------------------------------------------- */ |
6139 | |
|
6140 | 0 | bOK &= VSIFSeekL(fpVSIL, nNumSOffset + 3, SEEK_SET) == 0; |
6141 | 0 | bOK &= static_cast<int>(VSIFWriteL(pachLS, 1, nNUMS * nCgmHdrEntrySz, |
6142 | 0 | fpVSIL)) == nNUMS * nCgmHdrEntrySz; |
6143 | |
|
6144 | 0 | CPLFree(pachLS); |
6145 | |
|
6146 | 0 | if (strlen(errorMessage) != 0) |
6147 | 0 | { |
6148 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "%s", errorMessage); |
6149 | 0 | bOK = false; |
6150 | 0 | } |
6151 | |
|
6152 | 0 | return bOK; |
6153 | |
|
6154 | 0 | #undef PLACE |
6155 | 0 | } |
6156 | | |
6157 | | /************************************************************************/ |
6158 | | /* NITFWriteTextSegments() */ |
6159 | | /************************************************************************/ |
6160 | | |
6161 | | static bool NITFWriteTextSegments(const char *pszFilename, VSILFILE *&fpVSIL, |
6162 | | CSLConstList papszList) |
6163 | | |
6164 | 10 | { |
6165 | | /* -------------------------------------------------------------------- */ |
6166 | | /* Count the number of apparent text segments to write. There */ |
6167 | | /* is nothing at all to do if there are none to write. */ |
6168 | | /* -------------------------------------------------------------------- */ |
6169 | 10 | int nNUMT = 0; |
6170 | | |
6171 | 10 | for (int iOpt = 0; papszList != nullptr && papszList[iOpt] != nullptr; |
6172 | 10 | iOpt++) |
6173 | 0 | { |
6174 | 0 | if (STARTS_WITH_CI(papszList[iOpt], "DATA_")) |
6175 | 0 | nNUMT++; |
6176 | 0 | } |
6177 | | |
6178 | 10 | if (nNUMT == 0) |
6179 | 10 | return true; |
6180 | | |
6181 | | /* -------------------------------------------------------------------- */ |
6182 | | /* Open the target file if not already done. */ |
6183 | | /* -------------------------------------------------------------------- */ |
6184 | 0 | if (fpVSIL == nullptr) |
6185 | 0 | fpVSIL = VSIFOpenL(pszFilename, "r+b"); |
6186 | 0 | if (fpVSIL == nullptr) |
6187 | 0 | return false; |
6188 | | |
6189 | | // Get number of text field. Since there there could be multiple images |
6190 | | // or graphic segment, the offset need to be calculated dynamically. |
6191 | | |
6192 | 0 | char achNUMI[4]; // 3 digits plus null character |
6193 | 0 | achNUMI[3] = '\0'; |
6194 | | // NUMI offset is at a fixed offset 363 |
6195 | 0 | vsi_l_offset nNumIOffset = 360; |
6196 | 0 | bool bOK = VSIFSeekL(fpVSIL, nNumIOffset, SEEK_SET) == 0; |
6197 | 0 | bOK &= VSIFReadL(achNUMI, 3, 1, fpVSIL) == 1; |
6198 | 0 | int nIM = atoi(achNUMI); |
6199 | |
|
6200 | 0 | char achNUMG[4]; // 3 digits plus null character |
6201 | 0 | achNUMG[3] = '\0'; |
6202 | | |
6203 | | // 3 for size of NUMI. 6 and 10 are the field size for LISH and LI |
6204 | 0 | const vsi_l_offset nNumGOffset = nNumIOffset + 3 + nIM * (6 + 10); |
6205 | 0 | bOK &= VSIFSeekL(fpVSIL, nNumGOffset, SEEK_SET) == 0; |
6206 | 0 | bOK &= VSIFReadL(achNUMG, 3, 1, fpVSIL) == 1; |
6207 | 0 | const int nGS = atoi(achNUMG); |
6208 | | |
6209 | | // NUMT offset |
6210 | | // 3 for size of NUMG. 4 and 6 are filed size of LSSH and LS. |
6211 | | // the last + 3 is for NUMX field, which is not used |
6212 | 0 | const vsi_l_offset nNumTOffset = nNumGOffset + 3 + nGS * (4 + 6) + 3; |
6213 | | |
6214 | | /* -------------------------------------------------------------------- */ |
6215 | | /* Confirm that the NUMT in the file header already matches the */ |
6216 | | /* number of text segments we want to write, and that the */ |
6217 | | /* segment header/data size info is blank. */ |
6218 | | /* -------------------------------------------------------------------- */ |
6219 | 0 | char achNUMT[4]; |
6220 | 0 | char *pachLT = static_cast<char *>(CPLCalloc(nNUMT * 9 + 1, 1)); |
6221 | |
|
6222 | 0 | bOK &= VSIFSeekL(fpVSIL, nNumTOffset, SEEK_SET) == 0; |
6223 | 0 | bOK &= VSIFReadL(achNUMT, 3, 1, fpVSIL) == 1; |
6224 | 0 | achNUMT[3] = '\0'; |
6225 | |
|
6226 | 0 | bOK &= VSIFReadL(pachLT, nNUMT * 9, 1, fpVSIL) == 1; |
6227 | |
|
6228 | 0 | if (!bOK || atoi(achNUMT) != nNUMT) |
6229 | 0 | { |
6230 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6231 | 0 | "It appears an attempt was made to add or update text\n" |
6232 | 0 | "segments on an NITF file with existing segments. This\n" |
6233 | 0 | "is not currently supported by the GDAL NITF driver."); |
6234 | |
|
6235 | 0 | CPLFree(pachLT); |
6236 | 0 | return false; |
6237 | 0 | } |
6238 | | |
6239 | 0 | if (!STARTS_WITH_CI(pachLT, " ")) |
6240 | 0 | { |
6241 | 0 | CPLFree(pachLT); |
6242 | | // presumably the text segments are already written, do nothing. |
6243 | 0 | return true; |
6244 | 0 | } |
6245 | | |
6246 | | /* -------------------------------------------------------------------- */ |
6247 | | /* At this point we likely ought to confirm NUMDES, NUMRES, */ |
6248 | | /* UDHDL and XHDL are zero. Consider adding later... */ |
6249 | | /* -------------------------------------------------------------------- */ |
6250 | | |
6251 | | /* ==================================================================== */ |
6252 | | /* Write the text segments at the end of the file. */ |
6253 | | /* ==================================================================== */ |
6254 | 0 | #define PLACE(location, name, text) memcpy(location, text, strlen(text)) |
6255 | 0 | int iTextSeg = 0; |
6256 | |
|
6257 | 0 | for (int iOpt = 0; |
6258 | 0 | bOK && papszList != nullptr && papszList[iOpt] != nullptr; iOpt++) |
6259 | 0 | { |
6260 | 0 | if (!STARTS_WITH_CI(papszList[iOpt], "DATA_")) |
6261 | 0 | continue; |
6262 | | |
6263 | 0 | const char *pszTextToWrite = |
6264 | 0 | CPLParseNameValue(papszList[iOpt], nullptr); |
6265 | 0 | if (pszTextToWrite == nullptr) |
6266 | 0 | continue; |
6267 | | |
6268 | | /* -------------------------------------------------------------------- |
6269 | | */ |
6270 | | /* Locate corresponding header data in the buffer */ |
6271 | | /* -------------------------------------------------------------------- |
6272 | | */ |
6273 | | |
6274 | 0 | const char *pszHeaderBuffer = nullptr; |
6275 | 0 | for (int iOpt2 = 0; papszList[iOpt2] != nullptr; iOpt2++) |
6276 | 0 | { |
6277 | 0 | if (!STARTS_WITH_CI(papszList[iOpt2], "HEADER_")) |
6278 | 0 | continue; |
6279 | | |
6280 | 0 | char *pszHeaderKey = nullptr; |
6281 | 0 | CPLParseNameValue(papszList[iOpt2], &pszHeaderKey); |
6282 | 0 | char *pszDataKey = nullptr; |
6283 | 0 | CPLParseNameValue(papszList[iOpt], &pszDataKey); |
6284 | 0 | if (pszHeaderKey == nullptr || pszDataKey == nullptr) |
6285 | 0 | { |
6286 | 0 | CPLFree(pszHeaderKey); |
6287 | 0 | CPLFree(pszDataKey); |
6288 | 0 | continue; |
6289 | 0 | } |
6290 | | |
6291 | | // Point to header and data number. |
6292 | 0 | char *pszHeaderId = pszHeaderKey + 7; |
6293 | 0 | char *pszDataId = pszDataKey + 5; |
6294 | |
|
6295 | 0 | const bool bIsSameId = strcmp(pszHeaderId, pszDataId) == 0; |
6296 | 0 | CPLFree(pszHeaderKey); |
6297 | 0 | CPLFree(pszDataKey); |
6298 | | |
6299 | | // if ID matches, read the header information and exit the loop |
6300 | 0 | if (bIsSameId) |
6301 | 0 | { |
6302 | 0 | pszHeaderBuffer = CPLParseNameValue(papszList[iOpt2], nullptr); |
6303 | 0 | break; |
6304 | 0 | } |
6305 | 0 | } |
6306 | | |
6307 | | /* -------------------------------------------------------------------- |
6308 | | */ |
6309 | | /* Prepare and write text header. */ |
6310 | | /* -------------------------------------------------------------------- |
6311 | | */ |
6312 | 0 | char achTSH[282]; |
6313 | 0 | memset(achTSH, ' ', sizeof(achTSH)); |
6314 | 0 | bOK &= VSIFSeekL(fpVSIL, 0, SEEK_END) == 0; |
6315 | |
|
6316 | 0 | if (pszHeaderBuffer != nullptr) |
6317 | 0 | { |
6318 | 0 | memcpy(achTSH, pszHeaderBuffer, |
6319 | 0 | std::min(strlen(pszHeaderBuffer), sizeof(achTSH))); |
6320 | | |
6321 | | // Take care NITF2.0 date format changes |
6322 | 0 | const char chTimeZone = achTSH[20]; |
6323 | | |
6324 | | // Check for Zulu time zone character. IpachLTf that exist, then |
6325 | | // it is NITF2.0 format. |
6326 | 0 | if (chTimeZone == 'Z') |
6327 | 0 | { |
6328 | 0 | char *achOrigDate = achTSH + 12; // original date string |
6329 | | |
6330 | | // The date value taken from default NITF file date |
6331 | 0 | char achYear[3]; |
6332 | | |
6333 | | // Offset to the year |
6334 | 0 | strncpy(achYear, achOrigDate + 12, 2); |
6335 | 0 | achYear[2] = '\0'; |
6336 | 0 | const int nYear = atoi(achYear); |
6337 | | |
6338 | | // Set century. |
6339 | | // Since NITF2.0 does not track the century, we are going to |
6340 | | // assume any year number greater then 94 (the year NITF2.0 |
6341 | | // spec published), will be 1900s, otherwise, it is 2000s. |
6342 | 0 | char achNewDate[] = "20021216151629"; |
6343 | 0 | if (nYear > 94) |
6344 | 0 | memcpy(achNewDate, "19", 2); |
6345 | 0 | else |
6346 | 0 | memcpy(achNewDate, "20", 2); |
6347 | |
|
6348 | 0 | memcpy(achNewDate + 6, achOrigDate, 8); // copy cover DDhhmmss |
6349 | 0 | memcpy(achNewDate + 2, achOrigDate + 12, 2); // copy over years |
6350 | | |
6351 | | // Perform month conversion |
6352 | 0 | char *pszOrigMonth = achOrigDate + 9; |
6353 | 0 | char *pszNewMonth = achNewDate + 4; |
6354 | |
|
6355 | 0 | if (STARTS_WITH(pszOrigMonth, "JAN")) |
6356 | 0 | memcpy(pszNewMonth, "01", 2); |
6357 | 0 | else if (STARTS_WITH(pszOrigMonth, "FEB")) |
6358 | 0 | memcpy(pszNewMonth, "02", 2); |
6359 | 0 | else if (STARTS_WITH(pszOrigMonth, "MAR")) |
6360 | 0 | memcpy(pszNewMonth, "03", 2); |
6361 | 0 | else if (STARTS_WITH(pszOrigMonth, "APR")) |
6362 | 0 | memcpy(pszNewMonth, "04", 2); |
6363 | 0 | else if (STARTS_WITH(pszOrigMonth, "MAY")) |
6364 | 0 | memcpy(pszNewMonth, "05", 2); |
6365 | 0 | else if (STARTS_WITH(pszOrigMonth, "JUN")) |
6366 | 0 | memcpy(pszNewMonth, "07", 2); |
6367 | 0 | else if (STARTS_WITH(pszOrigMonth, "AUG")) |
6368 | 0 | memcpy(pszNewMonth, "08", 2); |
6369 | 0 | else if (STARTS_WITH(pszOrigMonth, "SEP")) |
6370 | 0 | memcpy(pszNewMonth, "09", 2); |
6371 | 0 | else if (STARTS_WITH(pszOrigMonth, "OCT")) |
6372 | 0 | memcpy(pszNewMonth, "10", 2); |
6373 | 0 | else if (STARTS_WITH(pszOrigMonth, "NOV")) |
6374 | 0 | memcpy(pszNewMonth, "11", 2); |
6375 | 0 | else if (STARTS_WITH(pszOrigMonth, "DEC")) |
6376 | 0 | memcpy(pszNewMonth, "12", 2); |
6377 | |
|
6378 | 0 | PLACE(achTSH + 12, TXTDT, achNewDate); |
6379 | 0 | } |
6380 | 0 | } |
6381 | 0 | else |
6382 | 0 | { // Use default value if header information is not found |
6383 | 0 | PLACE(achTSH + 0, TE, "TE"); |
6384 | 0 | PLACE(achTSH + 9, TXTALVL, "000"); |
6385 | 0 | PLACE(achTSH + 12, TXTDT, "20021216151629"); |
6386 | 0 | PLACE(achTSH + 106, TSCLAS, "U"); |
6387 | 0 | PLACE(achTSH + 273, ENCRYP, "0"); |
6388 | 0 | PLACE(achTSH + 274, TXTFMT, "STA"); |
6389 | 0 | PLACE(achTSH + 277, TXSHDL, "00000"); |
6390 | 0 | } |
6391 | |
|
6392 | 0 | bOK &= VSIFWriteL(achTSH, sizeof(achTSH), 1, fpVSIL) == 1; |
6393 | | |
6394 | | /* -------------------------------------------------------------------- |
6395 | | */ |
6396 | | /* Prepare and write text segment data. */ |
6397 | | /* -------------------------------------------------------------------- |
6398 | | */ |
6399 | |
|
6400 | 0 | int nTextLength = static_cast<int>(strlen(pszTextToWrite)); |
6401 | 0 | if (nTextLength > 99998) |
6402 | 0 | { |
6403 | 0 | CPLError(CE_Warning, CPLE_NotSupported, |
6404 | 0 | "Length of DATA_%d is %d, which is greater than 99998. " |
6405 | 0 | "Truncating...", |
6406 | 0 | iTextSeg + 1, nTextLength); |
6407 | 0 | nTextLength = 99998; |
6408 | 0 | } |
6409 | |
|
6410 | 0 | bOK &= static_cast<int>(VSIFWriteL(pszTextToWrite, 1, nTextLength, |
6411 | 0 | fpVSIL)) == nTextLength; |
6412 | | |
6413 | | /* -------------------------------------------------------------------- |
6414 | | */ |
6415 | | /* Update the subheader and data size info in the file header. */ |
6416 | | /* -------------------------------------------------------------------- |
6417 | | */ |
6418 | 0 | CPLsnprintf(pachLT + 9 * iTextSeg + 0, 9 + 1, "%04d%05d", |
6419 | 0 | static_cast<int>(sizeof(achTSH)), nTextLength); |
6420 | |
|
6421 | 0 | iTextSeg++; |
6422 | 0 | } |
6423 | | |
6424 | | /* -------------------------------------------------------------------- */ |
6425 | | /* Write out the text segment info. */ |
6426 | | /* -------------------------------------------------------------------- */ |
6427 | |
|
6428 | 0 | bOK &= VSIFSeekL(fpVSIL, nNumTOffset + 3, SEEK_SET) == 0; |
6429 | 0 | bOK &= |
6430 | 0 | static_cast<int>(VSIFWriteL(pachLT, 1, nNUMT * 9, fpVSIL)) == nNUMT * 9; |
6431 | |
|
6432 | 0 | CPLFree(pachLT); |
6433 | |
|
6434 | 0 | return bOK; |
6435 | 0 | #undef PLACE |
6436 | 0 | } |
6437 | | |
6438 | | /************************************************************************/ |
6439 | | /* NITFWriteDES() */ |
6440 | | /************************************************************************/ |
6441 | | |
6442 | | static bool NITFWriteDES(VSILFILE *&fp, const char *pszFilename, |
6443 | | vsi_l_offset nOffsetLDSH, int iDES, |
6444 | | const char *pszDESName, const GByte *pabyDESData, |
6445 | | int nArrayLen) |
6446 | 0 | { |
6447 | 0 | constexpr int LEN_DE = 2; |
6448 | 0 | constexpr int LEN_DESID = 25; |
6449 | 0 | constexpr int LEN_DESOFLW = 6; |
6450 | 0 | constexpr int LEN_DESITEM = 3; |
6451 | 0 | const int nTotalLen = LEN_DE + LEN_DESID + nArrayLen; |
6452 | |
|
6453 | 0 | const bool bIsTRE_OVERFLOW = (strcmp(pszDESName, "TRE_OVERFLOW") == 0); |
6454 | 0 | const int MIN_LEN_DES_SUBHEADER = |
6455 | 0 | 200 + (bIsTRE_OVERFLOW ? LEN_DESOFLW + LEN_DESITEM : 0); |
6456 | |
|
6457 | 0 | if (nTotalLen < MIN_LEN_DES_SUBHEADER) |
6458 | 0 | { |
6459 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6460 | 0 | "DES does not contain enough data"); |
6461 | 0 | return false; |
6462 | 0 | } |
6463 | | |
6464 | 0 | int nDESITEM = 0; |
6465 | 0 | GUIntBig nIXSOFLOffset = 0; |
6466 | 0 | if (bIsTRE_OVERFLOW) |
6467 | 0 | { |
6468 | 0 | char szDESITEM[LEN_DESITEM + 1]; |
6469 | 0 | memcpy(szDESITEM, pabyDESData + 169 + LEN_DESOFLW, LEN_DESITEM); |
6470 | 0 | szDESITEM[LEN_DESITEM] = '\0'; |
6471 | 0 | if (!isdigit(static_cast<unsigned char>(szDESITEM[0])) || |
6472 | 0 | !isdigit(static_cast<unsigned char>(szDESITEM[1])) || |
6473 | 0 | !isdigit(static_cast<unsigned char>(szDESITEM[2]))) |
6474 | 0 | { |
6475 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6476 | 0 | "Invalid value for DESITEM: '%s'", szDESITEM); |
6477 | 0 | return false; |
6478 | 0 | } |
6479 | 0 | nDESITEM = atoi(szDESITEM); |
6480 | |
|
6481 | 0 | char szDESOFLW[LEN_DESOFLW + 1]; |
6482 | 0 | memcpy(szDESOFLW, pabyDESData + 169, LEN_DESOFLW); |
6483 | 0 | szDESOFLW[LEN_DESOFLW] = '\0'; |
6484 | 0 | if (strcmp(szDESOFLW, "IXSHD ") == 0) |
6485 | 0 | { |
6486 | 0 | auto psFile = NITFOpenEx(fp, pszFilename); |
6487 | 0 | if (psFile == nullptr) |
6488 | 0 | { |
6489 | 0 | fp = nullptr; |
6490 | 0 | return false; |
6491 | 0 | } |
6492 | | |
6493 | 0 | int nImageIdx = 1; |
6494 | 0 | for (int iSegment = 0; iSegment < psFile->nSegmentCount; ++iSegment) |
6495 | 0 | { |
6496 | 0 | const auto psSegInfo = psFile->pasSegmentInfo + iSegment; |
6497 | 0 | if (!EQUAL(psSegInfo->szSegmentType, "IM")) |
6498 | 0 | continue; |
6499 | 0 | if (nImageIdx == nDESITEM) |
6500 | 0 | { |
6501 | 0 | auto psImage = NITFImageAccess(psFile, iSegment); |
6502 | 0 | if (psImage == nullptr) |
6503 | 0 | { |
6504 | 0 | nImageIdx = -1; |
6505 | 0 | break; |
6506 | 0 | } |
6507 | | |
6508 | 0 | if (psImage->nIXSOFL == -1) |
6509 | 0 | { |
6510 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6511 | 0 | "Missing IXSOFL field in image %d. " |
6512 | 0 | "RESERVE_SPACE_FOR_TRE_OVERFLOW=YES creation " |
6513 | 0 | "option likely missing.", |
6514 | 0 | nImageIdx); |
6515 | 0 | } |
6516 | 0 | else if (psImage->nIXSOFL != 0) |
6517 | 0 | { |
6518 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6519 | 0 | "Expected IXSOFL of image %d to be 0. Got %d", |
6520 | 0 | nImageIdx, psImage->nIXSOFL); |
6521 | 0 | } |
6522 | 0 | else |
6523 | 0 | { |
6524 | 0 | nIXSOFLOffset = psSegInfo->nSegmentHeaderStart + |
6525 | 0 | psImage->nIXSOFLOffsetInSubfileHeader; |
6526 | 0 | } |
6527 | |
|
6528 | 0 | NITFImageDeaccess(psImage); |
6529 | 0 | break; |
6530 | 0 | } |
6531 | 0 | ++nImageIdx; |
6532 | 0 | } |
6533 | |
|
6534 | 0 | psFile->fp = nullptr; |
6535 | 0 | NITFClose(psFile); |
6536 | |
|
6537 | 0 | if (nImageIdx != nDESITEM) |
6538 | 0 | { |
6539 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6540 | 0 | "Cannot find image matching DESITEM = %d value", |
6541 | 0 | nDESITEM); |
6542 | 0 | return false; |
6543 | 0 | } |
6544 | 0 | if (nIXSOFLOffset == 0) |
6545 | 0 | { |
6546 | 0 | return false; |
6547 | 0 | } |
6548 | 0 | } |
6549 | 0 | else if (strcmp(szDESOFLW, "UDHD ") == 0 || |
6550 | 0 | strcmp(szDESOFLW, "UDID ") == 0 || |
6551 | 0 | strcmp(szDESOFLW, "XHD ") == 0 || |
6552 | 0 | strcmp(szDESOFLW, "SXSHD ") == 0 || |
6553 | 0 | strcmp(szDESOFLW, "TXSHD ") == 0) |
6554 | 0 | { |
6555 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
6556 | 0 | "Unhandled value for DESOFLW: '%s'. " |
6557 | 0 | "Segment subheader fields will not be updated.", |
6558 | 0 | szDESOFLW); |
6559 | 0 | } |
6560 | 0 | else |
6561 | 0 | { |
6562 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6563 | 0 | "Invalid value for DESOFLW: '%s'", szDESOFLW); |
6564 | 0 | return false; |
6565 | 0 | } |
6566 | 0 | } |
6567 | | |
6568 | | // Extract DESSHL value |
6569 | 0 | constexpr int LEN_DESSHL = 4; |
6570 | 0 | char szDESSHL[LEN_DESSHL + 1]; |
6571 | 0 | const int OFFSET_DESSHL = |
6572 | 0 | 169 + (bIsTRE_OVERFLOW ? LEN_DESOFLW + LEN_DESITEM : 0); |
6573 | 0 | memcpy(szDESSHL, pabyDESData + OFFSET_DESSHL, LEN_DESSHL); |
6574 | 0 | szDESSHL[LEN_DESSHL] = '\0'; |
6575 | 0 | if (!isdigit(static_cast<unsigned char>(szDESSHL[0])) || |
6576 | 0 | !isdigit(static_cast<unsigned char>(szDESSHL[1])) || |
6577 | 0 | !isdigit(static_cast<unsigned char>(szDESSHL[2])) || |
6578 | 0 | !isdigit(static_cast<unsigned char>(szDESSHL[3]))) |
6579 | 0 | { |
6580 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Invalid value for DESSHL: '%s'", |
6581 | 0 | szDESSHL); |
6582 | 0 | return false; |
6583 | 0 | } |
6584 | 0 | const int nDESSHL = atoi(szDESSHL); |
6585 | 0 | const int nSubHeadLen = nDESSHL + MIN_LEN_DES_SUBHEADER; |
6586 | 0 | const int nDataLen = |
6587 | 0 | nTotalLen - nSubHeadLen; // Length of DESDATA field only |
6588 | 0 | if (nDataLen < 0) |
6589 | 0 | { |
6590 | 0 | CPLError( |
6591 | 0 | CE_Failure, CPLE_AppDefined, |
6592 | 0 | "Value of DESSHL = '%s' is not consistent with provided DESData", |
6593 | 0 | szDESSHL); |
6594 | 0 | return false; |
6595 | 0 | } |
6596 | | |
6597 | 0 | if (nSubHeadLen > 9998 || nDataLen > 999999998) |
6598 | 0 | { |
6599 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "DES is too big to be written"); |
6600 | 0 | return false; |
6601 | 0 | } |
6602 | | |
6603 | 0 | bool bOK = VSIFSeekL(fp, 0, SEEK_END) == 0; |
6604 | 0 | bOK &= VSIFWriteL("DE", 1, 2, fp) == 2; |
6605 | 0 | bOK &= VSIFWriteL(CPLSPrintf("%-25s", pszDESName), 1, 25, fp) == 25; |
6606 | 0 | bOK &= VSIFWriteL(pabyDESData, 1, nArrayLen, fp) == |
6607 | 0 | static_cast<size_t>(nArrayLen); |
6608 | | |
6609 | | // Update LDSH and LD in the NITF Header |
6610 | 0 | bOK &= VSIFSeekL(fp, nOffsetLDSH + iDES * 13, SEEK_SET) == 0; |
6611 | 0 | bOK &= VSIFWriteL(CPLSPrintf("%04d", nSubHeadLen), 1, 4, fp) == 4; |
6612 | 0 | bOK &= VSIFWriteL(CPLSPrintf("%09d", nDataLen), 1, 9, fp) == 9; |
6613 | |
|
6614 | 0 | if (nIXSOFLOffset > 0) |
6615 | 0 | { |
6616 | 0 | CPLDebug("NITF", "Patching IXSOFL of image %d to %d", iDES + 1, |
6617 | 0 | nDESITEM); |
6618 | 0 | bOK &= VSIFSeekL(fp, nIXSOFLOffset, SEEK_SET) == 0; |
6619 | 0 | bOK &= VSIFWriteL(CPLSPrintf("%03d", nDESITEM), 1, 3, fp) == 3; |
6620 | 0 | } |
6621 | |
|
6622 | 0 | return bOK; |
6623 | 0 | } |
6624 | | |
6625 | | /************************************************************************/ |
6626 | | /* NITFWriteDESs() */ |
6627 | | /************************************************************************/ |
6628 | | |
6629 | | static bool NITFWriteDES(const char *pszFilename, VSILFILE *&fpVSIL, |
6630 | | CSLConstList papszOptions) |
6631 | 10 | { |
6632 | 10 | if (papszOptions == nullptr) |
6633 | 0 | { |
6634 | 0 | return true; |
6635 | 0 | } |
6636 | | |
6637 | 10 | int nDESFound = 0; |
6638 | 2.82k | for (int iOption = 0; papszOptions[iOption] != nullptr; iOption++) |
6639 | 2.81k | { |
6640 | 2.81k | if (EQUALN(papszOptions[iOption], "DES=", 4)) |
6641 | 0 | { |
6642 | 0 | nDESFound++; |
6643 | 0 | } |
6644 | 2.81k | } |
6645 | 10 | if (nDESFound == 0) |
6646 | 10 | { |
6647 | 10 | return true; |
6648 | 10 | } |
6649 | | |
6650 | | /* -------------------------------------------------------------------- */ |
6651 | | /* Open the target file if not already done. */ |
6652 | | /* -------------------------------------------------------------------- */ |
6653 | 0 | if (fpVSIL == nullptr) |
6654 | 0 | fpVSIL = VSIFOpenL(pszFilename, "r+b"); |
6655 | 0 | if (fpVSIL == nullptr) |
6656 | 0 | return false; |
6657 | | |
6658 | 0 | char achNUMI[4]; // 3 digits plus null character |
6659 | 0 | achNUMI[3] = '\0'; |
6660 | | // NUMI offset is at a fixed offset 363 |
6661 | 0 | vsi_l_offset nNumIOffset = 360; |
6662 | 0 | bool bOK = VSIFSeekL(fpVSIL, nNumIOffset, SEEK_SET) == 0; |
6663 | 0 | bOK &= VSIFReadL(achNUMI, 3, 1, fpVSIL) == 1; |
6664 | 0 | int nIM = atoi(achNUMI); |
6665 | |
|
6666 | 0 | char achNUMG[4]; // 3 digits plus null character |
6667 | 0 | achNUMG[3] = '\0'; |
6668 | | |
6669 | | // 3 for size of NUMI. 6 and 10 are the field size for LISH and LI |
6670 | 0 | const vsi_l_offset nNumGOffset = nNumIOffset + 3 + nIM * (6 + 10); |
6671 | 0 | bOK &= VSIFSeekL(fpVSIL, nNumGOffset, SEEK_SET) == 0; |
6672 | 0 | bOK &= VSIFReadL(achNUMG, 3, 1, fpVSIL) == 1; |
6673 | 0 | const int nGS = atoi(achNUMG); |
6674 | | |
6675 | | // NUMT offset |
6676 | | // 3 for size of NUMG. 4 and 6 are the field size of LSSH and LS. |
6677 | | // the last + 3 is for NUMX field, which is not used |
6678 | 0 | const vsi_l_offset nNumTOffset = nNumGOffset + 3 + nGS * (4 + 6) + 3; |
6679 | 0 | char achNUMT[4]; |
6680 | 0 | bOK &= VSIFSeekL(fpVSIL, nNumTOffset, SEEK_SET) == 0; |
6681 | 0 | bOK &= VSIFReadL(achNUMT, 3, 1, fpVSIL) == 1; |
6682 | 0 | achNUMT[3] = '\0'; |
6683 | 0 | const int nNUMT = atoi(achNUMT); |
6684 | | |
6685 | | // NUMDES offset |
6686 | | // 3 for size of NUMT. 4 and 5 are the field size of LTSH and LT. |
6687 | 0 | const vsi_l_offset nNumDESOffset = nNumTOffset + 3 + (4 + 5) * nNUMT; |
6688 | 0 | char achNUMDES[4]; |
6689 | 0 | bOK &= VSIFSeekL(fpVSIL, nNumDESOffset, SEEK_SET) == 0; |
6690 | 0 | bOK &= VSIFReadL(achNUMDES, 3, 1, fpVSIL) == 1; |
6691 | 0 | achNUMDES[3] = '\0'; |
6692 | |
|
6693 | 0 | if (!bOK || atoi(achNUMDES) != nDESFound) |
6694 | 0 | { |
6695 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6696 | 0 | "It appears an attempt was made to add or update DE\n" |
6697 | 0 | "segments on an NITF file with existing segments. This\n" |
6698 | 0 | "is not currently supported by the GDAL NITF driver."); |
6699 | 0 | return false; |
6700 | 0 | } |
6701 | | |
6702 | 0 | int iDES = 0; |
6703 | 0 | for (int iOption = 0; papszOptions[iOption] != nullptr; iOption++) |
6704 | 0 | { |
6705 | 0 | if (!EQUALN(papszOptions[iOption], "DES=", 4)) |
6706 | 0 | { |
6707 | 0 | continue; |
6708 | 0 | } |
6709 | | |
6710 | | /* We don't use CPLParseNameValue() as it removes leading spaces */ |
6711 | | /* from the value (see #3088) */ |
6712 | 0 | const char *pszDelim = strchr(papszOptions[iOption] + 4, '='); |
6713 | 0 | if (pszDelim == nullptr) |
6714 | 0 | { |
6715 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6716 | 0 | "Could not parse creation options %s", |
6717 | 0 | papszOptions[iOption] + 4); |
6718 | 0 | return false; |
6719 | 0 | } |
6720 | | |
6721 | 0 | const size_t nNameLength = |
6722 | 0 | strlen(papszOptions[iOption] + 4) - strlen(pszDelim); |
6723 | 0 | if (nNameLength > 25) |
6724 | 0 | { |
6725 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6726 | 0 | "Specified DESID is too long %s", |
6727 | 0 | papszOptions[iOption] + 4); |
6728 | 0 | return false; |
6729 | 0 | } |
6730 | | |
6731 | 0 | char *pszDESName = static_cast<char *>(CPLMalloc(nNameLength + 1)); |
6732 | 0 | memcpy(pszDESName, papszOptions[iOption] + 4, nNameLength); |
6733 | 0 | pszDESName[nNameLength] = '\0'; |
6734 | |
|
6735 | 0 | const char *pszEscapedContents = pszDelim + 1; |
6736 | |
|
6737 | 0 | int nContentLength = 0; |
6738 | 0 | GByte *pabyUnescapedContents = |
6739 | 0 | reinterpret_cast<GByte *>(CPLUnescapeString( |
6740 | 0 | pszEscapedContents, &nContentLength, CPLES_BackslashQuotable)); |
6741 | |
|
6742 | 0 | if (!NITFWriteDES(fpVSIL, pszFilename, nNumDESOffset + 3, iDES, |
6743 | 0 | pszDESName, pabyUnescapedContents, nContentLength)) |
6744 | 0 | { |
6745 | 0 | CPLFree(pszDESName); |
6746 | 0 | CPLFree(pabyUnescapedContents); |
6747 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Could not write DES %d", |
6748 | 0 | iDES); |
6749 | 0 | return false; |
6750 | 0 | } |
6751 | | |
6752 | 0 | CPLFree(pszDESName); |
6753 | 0 | CPLFree(pabyUnescapedContents); |
6754 | |
|
6755 | 0 | iDES++; |
6756 | 0 | } |
6757 | | |
6758 | 0 | return bOK; |
6759 | 0 | } |
6760 | | |
6761 | | /************************************************************************/ |
6762 | | /* UpdateFileLength() */ |
6763 | | /************************************************************************/ |
6764 | | |
6765 | | static bool UpdateFileLength(VSILFILE *fp) |
6766 | 0 | { |
6767 | | |
6768 | | /* -------------------------------------------------------------------- */ |
6769 | | /* Update total file length. */ |
6770 | | /* -------------------------------------------------------------------- */ |
6771 | 0 | bool bOK = VSIFSeekL(fp, 0, SEEK_END) == 0; |
6772 | 0 | GUIntBig nFileLen = VSIFTellL(fp); |
6773 | | // Offset to file length entry |
6774 | 0 | bOK &= VSIFSeekL(fp, 342, SEEK_SET) == 0; |
6775 | 0 | if (nFileLen >= NITF_MAX_FILE_SIZE) |
6776 | 0 | { |
6777 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6778 | 0 | "Too big file : " CPL_FRMT_GUIB |
6779 | 0 | ". Truncating to " CPL_FRMT_GUIB, |
6780 | 0 | nFileLen, NITF_MAX_FILE_SIZE - 1); |
6781 | 0 | nFileLen = NITF_MAX_FILE_SIZE - 1; |
6782 | 0 | } |
6783 | 0 | CPLString osLen = |
6784 | 0 | CPLString().Printf("%012" CPL_FRMT_GB_WITHOUT_PREFIX "u", nFileLen); |
6785 | 0 | bOK &= VSIFWriteL(reinterpret_cast<const void *>(osLen.c_str()), 12, 1, |
6786 | 0 | fp) == 1; |
6787 | 0 | return bOK; |
6788 | 0 | } |
6789 | | |
6790 | | /************************************************************************/ |
6791 | | /* NITFWriteExtraSegments() */ |
6792 | | /************************************************************************/ |
6793 | | |
6794 | | static bool NITFWriteExtraSegments(const char *pszFilename, |
6795 | | CSLConstList papszCgmMD, |
6796 | | CSLConstList papszTextMD, |
6797 | | CSLConstList papszOptions) |
6798 | 10 | { |
6799 | 10 | VSILFILE *fp = nullptr; |
6800 | 10 | bool bOK = NITFWriteCGMSegments(pszFilename, fp, papszCgmMD); |
6801 | 10 | bOK &= NITFWriteTextSegments(pszFilename, fp, papszTextMD); |
6802 | 10 | bOK &= NITFWriteDES(pszFilename, fp, papszOptions); |
6803 | 10 | if (fp) |
6804 | 0 | { |
6805 | 0 | bOK &= UpdateFileLength(fp); |
6806 | |
|
6807 | 0 | if (VSIFCloseL(fp) != 0) |
6808 | 0 | bOK = false; |
6809 | |
|
6810 | 0 | if (!bOK) |
6811 | 0 | { |
6812 | 0 | CPLError(CE_Failure, CPLE_FileIO, "I/O error"); |
6813 | 0 | } |
6814 | 0 | } |
6815 | 10 | return bOK; |
6816 | 10 | } |
6817 | | |
6818 | | /************************************************************************/ |
6819 | | /* NITFWriteJPEGImage() */ |
6820 | | /************************************************************************/ |
6821 | | |
6822 | | #ifdef JPEG_SUPPORTED |
6823 | | |
6824 | | int NITFWriteJPEGBlock(GDALDataset *poSrcDS, VSILFILE *fp, int nBlockXOff, |
6825 | | int nBlockYOff, int nBlockXSize, int nBlockYSize, |
6826 | | int bProgressive, int nQuality, const GByte *pabyAPP6, |
6827 | | int nRestartInterval, GDALProgressFunc pfnProgress, |
6828 | | void *pProgressData); |
6829 | | |
6830 | | static bool NITFWriteJPEGImage(GDALDataset *poSrcDS, VSILFILE *fp, |
6831 | | vsi_l_offset nStartOffset, |
6832 | | CSLConstList papszOptions, |
6833 | | GDALProgressFunc pfnProgress, |
6834 | | void *pProgressData) |
6835 | 0 | { |
6836 | 0 | if (!pfnProgress(0.0, nullptr, pProgressData)) |
6837 | 0 | return false; |
6838 | | |
6839 | | /* -------------------------------------------------------------------- */ |
6840 | | /* Some some rudimentary checks */ |
6841 | | /* -------------------------------------------------------------------- */ |
6842 | 0 | const int nBands = poSrcDS->GetRasterCount(); |
6843 | 0 | if (nBands != 1 && nBands != 3) |
6844 | 0 | { |
6845 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
6846 | 0 | "JPEG driver doesn't support %d bands. Must be 1 (grey) " |
6847 | 0 | "or 3 (RGB) bands.\n", |
6848 | 0 | nBands); |
6849 | |
|
6850 | 0 | return false; |
6851 | 0 | } |
6852 | | |
6853 | 0 | GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType(); |
6854 | |
|
6855 | 0 | #if defined(JPEG_LIB_MK1) || defined(JPEG_DUAL_MODE_8_12) |
6856 | 0 | if (eDT != GDT_UInt8 && eDT != GDT_UInt16) |
6857 | 0 | { |
6858 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
6859 | 0 | "JPEG driver doesn't support data type %s. " |
6860 | 0 | "Only eight and twelve bit bands supported (Mk1 libjpeg).\n", |
6861 | 0 | GDALGetDataTypeName( |
6862 | 0 | poSrcDS->GetRasterBand(1)->GetRasterDataType())); |
6863 | |
|
6864 | 0 | return false; |
6865 | 0 | } |
6866 | | |
6867 | 0 | if (eDT == GDT_UInt16 || eDT == GDT_Int16) |
6868 | 0 | eDT = GDT_UInt16; |
6869 | 0 | else |
6870 | 0 | eDT = GDT_UInt8; |
6871 | |
|
6872 | | #else |
6873 | | if (eDT != GDT_UInt8) |
6874 | | { |
6875 | | CPLError(CE_Failure, CPLE_NotSupported, |
6876 | | "JPEG driver doesn't support data type %s. " |
6877 | | "Only eight bit byte bands supported.\n", |
6878 | | GDALGetDataTypeName( |
6879 | | poSrcDS->GetRasterBand(1)->GetRasterDataType())); |
6880 | | |
6881 | | return false; |
6882 | | } |
6883 | | |
6884 | | eDT = GDT_UInt8; // force to 8bit. |
6885 | | #endif |
6886 | | |
6887 | | /* -------------------------------------------------------------------- */ |
6888 | | /* What options has the user selected? */ |
6889 | | /* -------------------------------------------------------------------- */ |
6890 | 0 | int nQuality = 75; |
6891 | 0 | if (CSLFetchNameValue(papszOptions, "QUALITY") != nullptr) |
6892 | 0 | { |
6893 | 0 | nQuality = atoi(CSLFetchNameValue(papszOptions, "QUALITY")); |
6894 | 0 | if (nQuality < 10 || nQuality > 100) |
6895 | 0 | { |
6896 | 0 | CPLError(CE_Failure, CPLE_IllegalArg, |
6897 | 0 | "QUALITY=%s is not a legal value in the range 10-100.", |
6898 | 0 | CSLFetchNameValue(papszOptions, "QUALITY")); |
6899 | 0 | return false; |
6900 | 0 | } |
6901 | 0 | } |
6902 | | |
6903 | 0 | int nRestartInterval = -1; |
6904 | 0 | if (CSLFetchNameValue(papszOptions, "RESTART_INTERVAL") != nullptr) |
6905 | 0 | { |
6906 | 0 | nRestartInterval = |
6907 | 0 | atoi(CSLFetchNameValue(papszOptions, "RESTART_INTERVAL")); |
6908 | 0 | } |
6909 | |
|
6910 | 0 | const bool bProgressive = CPLFetchBool(papszOptions, "PROGRESSIVE", false); |
6911 | | |
6912 | | /* -------------------------------------------------------------------- */ |
6913 | | /* Compute blocking factors */ |
6914 | | /* -------------------------------------------------------------------- */ |
6915 | 0 | const int nXSize = poSrcDS->GetRasterXSize(); |
6916 | 0 | const int nYSize = poSrcDS->GetRasterYSize(); |
6917 | 0 | int nNPPBH = nXSize; |
6918 | 0 | int nNPPBV = nYSize; |
6919 | |
|
6920 | 0 | if (CSLFetchNameValue(papszOptions, "BLOCKXSIZE") != nullptr) |
6921 | 0 | nNPPBH = atoi(CSLFetchNameValue(papszOptions, "BLOCKXSIZE")); |
6922 | |
|
6923 | 0 | if (CSLFetchNameValue(papszOptions, "BLOCKYSIZE") != nullptr) |
6924 | 0 | nNPPBV = atoi(CSLFetchNameValue(papszOptions, "BLOCKYSIZE")); |
6925 | |
|
6926 | 0 | if (CSLFetchNameValue(papszOptions, "NPPBH") != nullptr) |
6927 | 0 | nNPPBH = atoi(CSLFetchNameValue(papszOptions, "NPPBH")); |
6928 | |
|
6929 | 0 | if (CSLFetchNameValue(papszOptions, "NPPBV") != nullptr) |
6930 | 0 | nNPPBV = atoi(CSLFetchNameValue(papszOptions, "NPPBV")); |
6931 | |
|
6932 | 0 | if (nNPPBH <= 0 || nNPPBV <= 0 || nNPPBH > 9999 || nNPPBV > 9999) |
6933 | 0 | { |
6934 | 0 | nNPPBH = 256; |
6935 | 0 | nNPPBV = 256; |
6936 | 0 | } |
6937 | |
|
6938 | 0 | const int nNBPR = DIV_ROUND_UP(nXSize, nNPPBH); |
6939 | 0 | const int nNBPC = DIV_ROUND_UP(nYSize, nNPPBV); |
6940 | | |
6941 | | /* -------------------------------------------------------------------- */ |
6942 | | /* Creates APP6 NITF application segment (required by MIL-STD-188-198) */ |
6943 | | /* see #3345 */ |
6944 | | /* -------------------------------------------------------------------- */ |
6945 | 0 | GByte abyAPP6[23]; |
6946 | 0 | memcpy(abyAPP6, "NITF", 4); |
6947 | 0 | abyAPP6[4] = 0; |
6948 | 0 | int nOffset = 5; |
6949 | | |
6950 | | /* Version : 2.0 */ |
6951 | 0 | GUInt16 nUInt16 = 0x0200; |
6952 | 0 | CPL_MSBPTR16(&nUInt16); |
6953 | 0 | memcpy(abyAPP6 + nOffset, &nUInt16, sizeof(nUInt16)); |
6954 | 0 | nOffset += sizeof(nUInt16); |
6955 | | |
6956 | | /* IMODE */ |
6957 | 0 | abyAPP6[nOffset] = (nBands == 1) ? 'B' : 'P'; |
6958 | 0 | nOffset++; |
6959 | | |
6960 | | /* Number of image blocks per row */ |
6961 | 0 | nUInt16 = static_cast<GUInt16>(nNBPR); |
6962 | 0 | CPL_MSBPTR16(&nUInt16); |
6963 | 0 | memcpy(abyAPP6 + nOffset, &nUInt16, sizeof(nUInt16)); |
6964 | 0 | nOffset += sizeof(nUInt16); |
6965 | | |
6966 | | /* Number of image blocks per column */ |
6967 | 0 | nUInt16 = static_cast<GUInt16>(nNBPC); |
6968 | 0 | CPL_MSBPTR16(&nUInt16); |
6969 | 0 | memcpy(abyAPP6 + nOffset, &nUInt16, sizeof(nUInt16)); |
6970 | 0 | nOffset += sizeof(nUInt16); |
6971 | | |
6972 | | /* Image color */ |
6973 | 0 | abyAPP6[nOffset] = (nBands == 1) ? 0 : 1; |
6974 | 0 | nOffset++; |
6975 | | |
6976 | | /* Original sample precision */ |
6977 | | /* coverity[dead_error_line] */ |
6978 | 0 | abyAPP6[nOffset] = (eDT == GDT_UInt16) ? 12 : 8; |
6979 | 0 | nOffset++; |
6980 | | |
6981 | | /* Image class */ |
6982 | 0 | abyAPP6[nOffset] = 0; |
6983 | 0 | nOffset++; |
6984 | | |
6985 | | /* JPEG coding process */ |
6986 | | /* coverity[dead_error_line] */ |
6987 | 0 | abyAPP6[nOffset] = (eDT == GDT_UInt16) ? 4 : 1; |
6988 | 0 | nOffset++; |
6989 | | |
6990 | | /* Quality */ |
6991 | 0 | abyAPP6[nOffset] = 0; |
6992 | 0 | nOffset++; |
6993 | | |
6994 | | /* Stream color */ |
6995 | 0 | abyAPP6[nOffset] = (nBands == 1) ? 0 /* Monochrome */ : 2 /* YCbCr*/; |
6996 | 0 | nOffset++; |
6997 | | |
6998 | | /* Stream bits */ |
6999 | | /* coverity[dead_error_line] */ |
7000 | 0 | abyAPP6[nOffset] = (eDT == GDT_UInt16) ? 12 : 8; |
7001 | 0 | nOffset++; |
7002 | | |
7003 | | /* Horizontal filtering */ |
7004 | 0 | abyAPP6[nOffset] = 1; |
7005 | 0 | nOffset++; |
7006 | | |
7007 | | /* Vertical filtering */ |
7008 | 0 | abyAPP6[nOffset] = 1; |
7009 | 0 | nOffset++; |
7010 | | |
7011 | | /* Reserved */ |
7012 | 0 | abyAPP6[nOffset] = 0; |
7013 | 0 | nOffset++; |
7014 | 0 | abyAPP6[nOffset] = 0; |
7015 | 0 | nOffset++; |
7016 | 0 | (void)nOffset; |
7017 | |
|
7018 | 0 | CPLAssert(nOffset == sizeof(abyAPP6)); |
7019 | | |
7020 | | /* -------------------------------------------------------------------- */ |
7021 | | /* Prepare block map if necessary */ |
7022 | | /* -------------------------------------------------------------------- */ |
7023 | |
|
7024 | 0 | bool bOK = VSIFSeekL(fp, nStartOffset, SEEK_SET) == 0; |
7025 | |
|
7026 | 0 | const char *pszIC = CSLFetchNameValue(papszOptions, "IC"); |
7027 | 0 | GUInt32 nIMDATOFF = 0; |
7028 | 0 | constexpr GUInt32 BLOCKMAP_HEADER_SIZE = 4 + 2 + 2 + 2; |
7029 | 0 | if (EQUAL(pszIC, "M3")) |
7030 | 0 | { |
7031 | | /* Prepare the block map */ |
7032 | 0 | GUInt32 nIMDATOFF_MSB = BLOCKMAP_HEADER_SIZE + nNBPC * nNBPR * 4; |
7033 | 0 | nIMDATOFF = nIMDATOFF_MSB; |
7034 | 0 | GUInt16 nBMRLNTH = 4; |
7035 | 0 | GUInt16 nTMRLNTH = 0; |
7036 | 0 | GUInt16 nTPXCDLNTH = 0; |
7037 | |
|
7038 | 0 | CPL_MSBPTR32(&nIMDATOFF_MSB); |
7039 | 0 | CPL_MSBPTR16(&nBMRLNTH); |
7040 | 0 | CPL_MSBPTR16(&nTMRLNTH); |
7041 | 0 | CPL_MSBPTR16(&nTPXCDLNTH); |
7042 | |
|
7043 | 0 | bOK &= VSIFWriteL(&nIMDATOFF_MSB, 4, 1, fp) == 1; |
7044 | 0 | bOK &= VSIFWriteL(&nBMRLNTH, 2, 1, fp) == 1; |
7045 | 0 | bOK &= VSIFWriteL(&nTMRLNTH, 2, 1, fp) == 1; |
7046 | 0 | bOK &= VSIFWriteL(&nTPXCDLNTH, 2, 1, fp) == 1; |
7047 | | |
7048 | | /* Reserve space for the table itself */ |
7049 | 0 | bOK &= VSIFSeekL(fp, static_cast<vsi_l_offset>(nNBPC) * nNBPR * 4, |
7050 | 0 | SEEK_CUR) == 0; |
7051 | 0 | } |
7052 | | |
7053 | | /* -------------------------------------------------------------------- */ |
7054 | | /* Copy each block */ |
7055 | | /* -------------------------------------------------------------------- */ |
7056 | 0 | for (int nBlockYOff = 0; bOK && nBlockYOff < nNBPC; nBlockYOff++) |
7057 | 0 | { |
7058 | 0 | for (int nBlockXOff = 0; bOK && nBlockXOff < nNBPR; nBlockXOff++) |
7059 | 0 | { |
7060 | | #ifdef DEBUG_VERBOSE |
7061 | | CPLDebug("NITF", "nBlockXOff=%d/%d, nBlockYOff=%d/%d", nBlockXOff, |
7062 | | nNBPR, nBlockYOff, nNBPC); |
7063 | | #endif |
7064 | 0 | if (EQUAL(pszIC, "M3")) |
7065 | 0 | { |
7066 | | /* Write block offset for current block */ |
7067 | |
|
7068 | 0 | const GUIntBig nCurPos = VSIFTellL(fp); |
7069 | 0 | bOK &= VSIFSeekL(fp, |
7070 | 0 | nStartOffset + BLOCKMAP_HEADER_SIZE + |
7071 | 0 | 4 * (nBlockYOff * nNBPR + nBlockXOff), |
7072 | 0 | SEEK_SET) == 0; |
7073 | 0 | const GUIntBig nBlockOffset = |
7074 | 0 | nCurPos - nStartOffset - nIMDATOFF; |
7075 | 0 | if (nBlockOffset <= UINT_MAX) |
7076 | 0 | { |
7077 | 0 | GUInt32 nBlockOffset32 = static_cast<GUInt32>(nBlockOffset); |
7078 | 0 | CPL_MSBPTR32(&nBlockOffset32); |
7079 | 0 | bOK &= VSIFWriteL(&nBlockOffset32, 4, 1, fp) == 1; |
7080 | 0 | } |
7081 | 0 | else |
7082 | 0 | { |
7083 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
7084 | 0 | "Offset for block (%d, %d) = " CPL_FRMT_GUIB |
7085 | 0 | ". Cannot fit into 32 bits...", |
7086 | 0 | nBlockXOff, nBlockYOff, nBlockOffset); |
7087 | |
|
7088 | 0 | GUInt32 nBlockOffset32 = UINT_MAX; |
7089 | 0 | for (int i = nBlockYOff * nNBPR + nBlockXOff; |
7090 | 0 | bOK && i < nNBPC * nNBPR; i++) |
7091 | 0 | { |
7092 | 0 | bOK &= VSIFWriteL(&nBlockOffset32, 4, 1, fp) == 1; |
7093 | 0 | } |
7094 | 0 | if (!bOK) |
7095 | 0 | { |
7096 | 0 | CPLError(CE_Failure, CPLE_FileIO, "I/O error"); |
7097 | 0 | } |
7098 | 0 | return bOK; |
7099 | 0 | } |
7100 | 0 | bOK &= VSIFSeekL(fp, nCurPos, SEEK_SET) == 0; |
7101 | 0 | } |
7102 | | |
7103 | 0 | if (bOK && |
7104 | 0 | !NITFWriteJPEGBlock( |
7105 | 0 | poSrcDS, fp, nBlockXOff, nBlockYOff, nNPPBH, nNPPBV, |
7106 | 0 | bProgressive, nQuality, |
7107 | 0 | (nBlockXOff == 0 && nBlockYOff == 0) ? abyAPP6 : nullptr, |
7108 | 0 | nRestartInterval, pfnProgress, pProgressData)) |
7109 | 0 | { |
7110 | 0 | return false; |
7111 | 0 | } |
7112 | 0 | } |
7113 | 0 | } |
7114 | 0 | if (!bOK) |
7115 | 0 | { |
7116 | 0 | CPLError(CE_Failure, CPLE_FileIO, "I/O error"); |
7117 | 0 | } |
7118 | 0 | return true; |
7119 | 0 | } |
7120 | | |
7121 | | #endif /* def JPEG_SUPPORTED */ |
7122 | | |
7123 | | /************************************************************************/ |
7124 | | /* GDALRegister_NITF() */ |
7125 | | /************************************************************************/ |
7126 | | |
7127 | | typedef struct |
7128 | | { |
7129 | | int nMaxLen; |
7130 | | const char *pszName; |
7131 | | const char *pszDescription; |
7132 | | } NITFFieldDescription; |
7133 | | |
7134 | | /* Keep in sync with NITFCreate */ |
7135 | | static const NITFFieldDescription asFieldDescription[] = { |
7136 | | {2, "CLEVEL", "Complexity level"}, |
7137 | | {10, "OSTAID", "Originating Station ID"}, |
7138 | | {14, "FDT", "File Date and Time"}, |
7139 | | {80, "FTITLE", "File Title"}, |
7140 | | {1, "FSCLAS", "File Security Classification"}, |
7141 | | {2, "FSCLSY", "File Classification Security System"}, |
7142 | | {11, "FSCODE", "File Codewords"}, |
7143 | | {2, "FSCTLH", "File Control and Handling"}, |
7144 | | {20, "FSREL", "File Releasing Instructions"}, |
7145 | | {2, "FSDCTP", "File Declassification Type"}, |
7146 | | {8, "FSDCDT", "File Declassification Date"}, |
7147 | | {4, "FSDCXM", "File Declassification Exemption"}, |
7148 | | {1, "FSDG", "File Downgrade"}, |
7149 | | {8, "FSDGDT", "File Downgrade Date"}, |
7150 | | {43, "FSCLTX", "File Classification Text"}, |
7151 | | {1, "FSCATP", "File Classification Authority Type"}, |
7152 | | {40, "FSCAUT", "File Classification Authority"}, |
7153 | | {1, "FSCRSN", "File Classification Reason"}, |
7154 | | {8, "FSSRDT", "File Security Source Date"}, |
7155 | | {15, "FSCTLN", "File Security Control Number"}, |
7156 | | {5, "FSCOP", "File Copy Number"}, |
7157 | | {5, "FSCPYS", "File Number of Copies"}, |
7158 | | {24, "ONAME", "Originator Name"}, |
7159 | | {18, "OPHONE", "Originator Phone Number"}, |
7160 | | {10, "IID1", "Image Identifier 1"}, |
7161 | | {14, "IDATIM", "Image Date and Time"}, |
7162 | | {17, "TGTID", "Target Identifier"}, |
7163 | | {80, "IID2", "Image Identifier 2"}, |
7164 | | {1, "ISCLAS", "Image Security Classification"}, |
7165 | | {2, "ISCLSY", "Image Classification Security System"}, |
7166 | | {11, "ISCODE", "Image Codewords"}, |
7167 | | {2, "ISCTLH", "Image Control and Handling"}, |
7168 | | {20, "ISREL", "Image Releasing Instructions"}, |
7169 | | {2, "ISDCTP", "Image Declassification Type"}, |
7170 | | {8, "ISDCDT", "Image Declassification Date"}, |
7171 | | {4, "ISDCXM", "Image Declassification Exemption"}, |
7172 | | {1, "ISDG", "Image Downgrade"}, |
7173 | | {8, "ISDGDT", "Image Downgrade Date"}, |
7174 | | {43, "ISCLTX", "Image Classification Text"}, |
7175 | | {1, "ISCATP", "Image Classification Authority Type"}, |
7176 | | {40, "ISCAUT", "Image Classification Authority"}, |
7177 | | {1, "ISCRSN", "Image Classification Reason"}, |
7178 | | {8, "ISSRDT", "Image Security Source Date"}, |
7179 | | {15, "ISCTLN", "Image Security Control Number"}, |
7180 | | {42, "ISORCE", "Image Source"}, |
7181 | | {8, "ICAT", "Image Category"}, |
7182 | | {2, "ABPP", "Actual Bits-Per-Pixel Per Band"}, |
7183 | | {1, "PJUST", "Pixel Justification"}, |
7184 | | {720, "ICOM", "Image Comments (up to 9x80 characters)"}, |
7185 | | {3, "IDLVL", "Image Display Level"}, |
7186 | | {3, "IALVL", "Image Attachment Level"}, |
7187 | | {5, "ILOCROW", "Image Location Row"}, |
7188 | | {5, "ILOCCOL", "Image Location Column"}, |
7189 | | }; |
7190 | | |
7191 | | /* Keep in sync with NITFWriteBLOCKA */ |
7192 | | static const char *const apszFieldsBLOCKA[] = { |
7193 | | "BLOCK_INSTANCE", "0", "2", "N_GRAY", "2", "5", |
7194 | | "L_LINES", "7", "5", "LAYOVER_ANGLE", "12", "3", |
7195 | | "SHADOW_ANGLE", "15", "3", "BLANKS", "18", "16", |
7196 | | "FRLC_LOC", "34", "21", "LRLC_LOC", "55", "21", |
7197 | | "LRFC_LOC", "76", "21", "FRFC_LOC", "97", "21", |
7198 | | nullptr, nullptr, nullptr}; |
7199 | | |
7200 | | /************************************************************************/ |
7201 | | /* NITFDriver */ |
7202 | | /************************************************************************/ |
7203 | | |
7204 | | class NITFDriver final : public GDALDriver |
7205 | | { |
7206 | | std::recursive_mutex m_oMutex{}; |
7207 | | bool m_bCreationOptionListInitialized = false; |
7208 | | void InitCreationOptionList(); |
7209 | | |
7210 | | public: |
7211 | | const char *GetMetadataItem(const char *pszName, |
7212 | | const char *pszDomain) override; |
7213 | | |
7214 | | CSLConstList GetMetadata(const char *pszDomain) override |
7215 | 10 | { |
7216 | 10 | std::lock_guard oLock(m_oMutex); |
7217 | 10 | InitCreationOptionList(); |
7218 | 10 | return GDALDriver::GetMetadata(pszDomain); |
7219 | 10 | } |
7220 | | }; |
7221 | | |
7222 | | /************************************************************************/ |
7223 | | /* NITFDriver::GetMetadataItem() */ |
7224 | | /************************************************************************/ |
7225 | | |
7226 | | const char *NITFDriver::GetMetadataItem(const char *pszName, |
7227 | | const char *pszDomain) |
7228 | 1.02M | { |
7229 | 1.02M | std::lock_guard oLock(m_oMutex); |
7230 | 1.02M | if (EQUAL(pszName, GDAL_DMD_CREATIONOPTIONLIST)) |
7231 | 13 | { |
7232 | 13 | InitCreationOptionList(); |
7233 | 13 | } |
7234 | 1.02M | return GDALDriver::GetMetadataItem(pszName, pszDomain); |
7235 | 1.02M | } |
7236 | | |
7237 | | /************************************************************************/ |
7238 | | /* InitCreationOptionList() */ |
7239 | | /************************************************************************/ |
7240 | | |
7241 | | void NITFDriver::InitCreationOptionList() |
7242 | 23 | { |
7243 | 23 | if (m_bCreationOptionListInitialized) |
7244 | 22 | return; |
7245 | 1 | m_bCreationOptionListInitialized = true; |
7246 | | |
7247 | 1 | const bool bHasJP2ECW = GDALGetDriverByName("JP2ECW") != nullptr; |
7248 | 1 | const bool bHasJP2KAK = GDALGetDriverByName("JP2KAK") != nullptr; |
7249 | 1 | const bool bHasJP2OPENJPEG = GDALGetDriverByName("JP2OPENJPEG") != nullptr; |
7250 | 1 | const bool bHasJPEG2000Drivers = |
7251 | 1 | bHasJP2ECW || bHasJP2KAK || bHasJP2OPENJPEG; |
7252 | | |
7253 | 1 | CPLString osCreationOptions = |
7254 | 1 | "<CreationOptionList>" |
7255 | 1 | " <Option name='IC' type='string-select' default='NC' " |
7256 | 1 | "description='Compression mode. NC=no compression. " |
7257 | 1 | #ifdef JPEG_SUPPORTED |
7258 | 1 | "C3/M3=JPEG compression. " |
7259 | 1 | #endif |
7260 | 1 | ; |
7261 | | |
7262 | 1 | if (bHasJPEG2000Drivers) |
7263 | 0 | osCreationOptions += |
7264 | 0 | "C8=JP2 compression through the JPEG2000 write capable drivers"; |
7265 | | |
7266 | 1 | osCreationOptions += "'>" |
7267 | 1 | " <Value>NC</Value>" |
7268 | 1 | #ifdef JPEG_SUPPORTED |
7269 | 1 | " <Value>C3</Value>" |
7270 | 1 | " <Value>M3</Value>" |
7271 | 1 | #endif |
7272 | 1 | ; |
7273 | | |
7274 | 1 | if (bHasJPEG2000Drivers) |
7275 | 0 | osCreationOptions += " <Value>C8</Value>"; |
7276 | | |
7277 | 1 | osCreationOptions += " </Option>"; |
7278 | | |
7279 | | #if !defined(JPEG_SUPPORTED) |
7280 | | if (bHasJPEG2000Drivers) |
7281 | | #endif |
7282 | 1 | { |
7283 | 1 | osCreationOptions += |
7284 | 1 | " <Option name='QUALITY' type='string' " |
7285 | 1 | "description='JPEG (10-100) or JPEG2000 quality, possibly as a" |
7286 | 1 | "separated list of values for JPEG2000_DRIVER=JP2OPENJPEG' " |
7287 | 1 | "default='75'/>"; |
7288 | 1 | } |
7289 | | |
7290 | 1 | #ifdef JPEG_SUPPORTED |
7291 | 1 | osCreationOptions += |
7292 | 1 | " <Option name='PROGRESSIVE' type='boolean' description='JPEG " |
7293 | 1 | "progressive mode'/>" |
7294 | 1 | " <Option name='RESTART_INTERVAL' type='int' description='Restart " |
7295 | 1 | "interval (in MCUs). -1 for auto, 0 for none, > 0 for user specified' " |
7296 | 1 | "default='-1'/>" |
7297 | 1 | #endif |
7298 | 1 | " <Option name='NUMI' type='int' default='1' description='Number of " |
7299 | 1 | "images to create (1-999). Only works with IC=NC if " |
7300 | 1 | "WRITE_ONLY_FIRST_IMAGE=NO'/>" |
7301 | 1 | " <Option name='WRITE_ONLY_FIRST_IMAGE' type='boolean' default='NO' " |
7302 | 1 | "description='To be used with NUMI. If YES, only write first image. " |
7303 | 1 | "Subsequent one must be written with APPEND_SUBDATASET=YES'/>"; |
7304 | | |
7305 | 1 | if (bHasJPEG2000Drivers) |
7306 | 0 | { |
7307 | 0 | osCreationOptions += |
7308 | 0 | " <Option name='TARGET' type='float' description='For JP2 only. " |
7309 | 0 | "Compression Percentage'/>" |
7310 | 0 | " <Option name='PROFILE' type='string-select' description='For " |
7311 | 0 | "JP2 only.'>"; |
7312 | |
|
7313 | 0 | if (bHasJP2ECW) |
7314 | 0 | { |
7315 | 0 | osCreationOptions += " <Value>BASELINE_0</Value>"; |
7316 | 0 | } |
7317 | 0 | if (bHasJP2ECW || bHasJP2OPENJPEG) |
7318 | 0 | { |
7319 | 0 | osCreationOptions += |
7320 | 0 | " <Value>BASELINE_1</Value>" |
7321 | 0 | " <Value>BASELINE_2</Value>" |
7322 | 0 | " <Value>NPJE</Value>" |
7323 | 0 | " <Value>NPJE_VISUALLY_LOSSLESS</Value>" |
7324 | 0 | " <Value>NPJE_NUMERICALLY_LOSSLESS</Value>"; |
7325 | 0 | } |
7326 | 0 | if (bHasJP2ECW) |
7327 | 0 | { |
7328 | 0 | osCreationOptions += " <Value>EPJE</Value>"; |
7329 | 0 | } |
7330 | 0 | osCreationOptions += |
7331 | 0 | " </Option>" |
7332 | 0 | " <Option name='JPEG2000_DRIVER' type='string-select' " |
7333 | 0 | "description='Short name of the JPEG2000 driver'>"; |
7334 | 0 | if (bHasJP2OPENJPEG) |
7335 | 0 | osCreationOptions += " <Value>JP2OPENJPEG</Value>"; |
7336 | 0 | if (bHasJP2ECW) |
7337 | 0 | osCreationOptions += " <Value>JP2ECW</Value>"; |
7338 | 0 | if (bHasJP2KAK) |
7339 | 0 | osCreationOptions += " <Value>JP2KAK</Value>"; |
7340 | 0 | osCreationOptions += " </Option>" |
7341 | 0 | " <Option name='J2KLRA' type='boolean' " |
7342 | 0 | "description='Write J2KLRA TRE'/>"; |
7343 | 0 | } |
7344 | | |
7345 | 1 | osCreationOptions += |
7346 | 1 | " <Option name='ICORDS' type='string-select' description='To ensure " |
7347 | 1 | "that space will be reserved for geographic corner coordinates in DMS " |
7348 | 1 | "(G), in decimal degrees (D), UTM North (N) or UTM South (S)'>" |
7349 | 1 | " <Value>G</Value>" |
7350 | 1 | " <Value>D</Value>" |
7351 | 1 | " <Value>N</Value>" |
7352 | 1 | " <Value>S</Value>" |
7353 | 1 | " </Option>" |
7354 | 1 | " <Option name='IGEOLO' type='string' description='Image corner " |
7355 | 1 | "coordinates. " |
7356 | 1 | "Normally automatically set. If specified, ICORDS must also be " |
7357 | 1 | "specified'/>" |
7358 | 1 | " <Option name='FHDR' type='string-select' description='File " |
7359 | 1 | "version' default='NITF02.10'>" |
7360 | 1 | " <Value>NITF02.10</Value>" |
7361 | 1 | " <Value>NSIF01.00</Value>" |
7362 | 1 | " </Option>" |
7363 | 1 | " <Option name='IREP' type='string' description='Set to RGB/LUT to " |
7364 | 1 | "reserve space for a color table for each output band. (Only needed " |
7365 | 1 | "for Create() method, not CreateCopy())'/>" |
7366 | 1 | " <Option name='IREPBAND' type='string' description='Comma separated " |
7367 | 1 | "list of band IREPBANDs in band order'/>" |
7368 | 1 | " <Option name='ISUBCAT' type='string' description='Comma separated " |
7369 | 1 | "list of band ISUBCATs in band order'/>" |
7370 | 1 | " <Option name='LUT_SIZE' type='integer' description='Set to control " |
7371 | 1 | "the size of pseudocolor tables for RGB/LUT bands' default='256'/>" |
7372 | 1 | " <Option name='BLOCKXSIZE' type='int' description='Set the block " |
7373 | 1 | "width'/>" |
7374 | 1 | " <Option name='BLOCKYSIZE' type='int' description='Set the block " |
7375 | 1 | "height'/>" |
7376 | 1 | " <Option name='BLOCKSIZE' type='int' description='Set the block " |
7377 | 1 | "with and height. Overridden by BLOCKXSIZE and BLOCKYSIZE'/>" |
7378 | 1 | " <Option name='TEXT' type='string' description='TEXT options as " |
7379 | 1 | "text-option-name=text-option-content'/>" |
7380 | 1 | " <Option name='CGM' type='string' description='CGM options in " |
7381 | 1 | "cgm-option-name=cgm-option-content'/>"; |
7382 | | |
7383 | 1 | for (unsigned int i = 0; |
7384 | 54 | i < sizeof(asFieldDescription) / sizeof(asFieldDescription[0]); i++) |
7385 | 53 | { |
7386 | 53 | if (EQUAL(asFieldDescription[i].pszName, "ABPP")) |
7387 | 1 | { |
7388 | 1 | osCreationOptions += |
7389 | 1 | CPLString().Printf(" <Option name='%s' alias='NBITS' " |
7390 | 1 | "type='string' description='%s' " |
7391 | 1 | "maxsize='%d'/>", |
7392 | 1 | asFieldDescription[i].pszName, |
7393 | 1 | asFieldDescription[i].pszDescription, |
7394 | 1 | asFieldDescription[i].nMaxLen); |
7395 | 1 | } |
7396 | 52 | else |
7397 | 52 | { |
7398 | 52 | osCreationOptions += CPLString().Printf( |
7399 | 52 | " <Option name='%s' type='string' description='%s' " |
7400 | 52 | "maxsize='%d'/>", |
7401 | 52 | asFieldDescription[i].pszName, |
7402 | 52 | asFieldDescription[i].pszDescription, |
7403 | 52 | asFieldDescription[i].nMaxLen); |
7404 | 52 | } |
7405 | 53 | } |
7406 | | |
7407 | 1 | osCreationOptions += |
7408 | 1 | " <Option name='TRE' type='string' description='Under the format " |
7409 | 1 | "TRE=tre-name,tre-contents'/>" |
7410 | 1 | " <Option name='FILE_TRE' type='string' description='Under the " |
7411 | 1 | "format FILE_TRE=tre-name,tre-contents'/>" |
7412 | 1 | " <Option name='RESERVE_SPACE_FOR_TRE_OVERFLOW' type='boolean' " |
7413 | 1 | "description='Set to true to reserve space for IXSOFL when writing a " |
7414 | 1 | "TRE_OVERFLOW DES'/>" |
7415 | 1 | " <Option name='BLOCKA_BLOCK_COUNT' type='int'/>" |
7416 | 1 | " <Option name='DES' type='string' description='Under the format " |
7417 | 1 | "DES=des-name=des-contents'/>" |
7418 | 1 | " <Option name='NUMDES' type='int' default='0' description='Number " |
7419 | 1 | "of DES segments. Only to be used on first image segment'/>"; |
7420 | 11 | for (unsigned int i = 0; apszFieldsBLOCKA[i] != nullptr; i += 3) |
7421 | 10 | { |
7422 | 10 | char szFieldDescription[128]; |
7423 | 10 | snprintf(szFieldDescription, sizeof(szFieldDescription), |
7424 | 10 | " <Option name='BLOCKA_%s_*' type='string' maxsize='%d'/>", |
7425 | 10 | apszFieldsBLOCKA[i], atoi(apszFieldsBLOCKA[i + 2])); |
7426 | 10 | osCreationOptions += szFieldDescription; |
7427 | 10 | } |
7428 | 1 | osCreationOptions += |
7429 | 1 | " <Option name='SDE_TRE' type='boolean' description='Write GEOLOB " |
7430 | 1 | "and GEOPSB TREs (only geographic SRS for now)' default='NO'/>" |
7431 | 1 | " <Option name='RPC00B' type='boolean' description='Write RPC00B TRE " |
7432 | 1 | "(either from source TRE, or from RPC metadata)' default='YES'/>" |
7433 | 1 | " <Option name='RPCTXT' type='boolean' description='Write out " |
7434 | 1 | "_RPC.TXT file' default='NO'/>" |
7435 | 1 | " <Option name='USE_SRC_NITF_METADATA' type='boolean' " |
7436 | 1 | "description='Whether to use NITF source metadata in NITF-to-NITF " |
7437 | 1 | "conversions' default='YES'/>"; |
7438 | 1 | osCreationOptions += "</CreationOptionList>"; |
7439 | | |
7440 | 1 | SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST, osCreationOptions); |
7441 | 1 | } |
7442 | | |
7443 | | void GDALRegister_NITF() |
7444 | | |
7445 | 22 | { |
7446 | 22 | if (GDALGetDriverByName(NITF_DRIVER_NAME) != nullptr) |
7447 | 0 | return; |
7448 | | |
7449 | 22 | GDALDriver *poDriver = new NITFDriver(); |
7450 | 22 | NITFDriverSetCommonMetadata(poDriver); |
7451 | | |
7452 | 22 | poDriver->pfnOpen = NITFDataset::Open; |
7453 | 22 | poDriver->pfnCreate = NITFDataset::NITFDatasetCreate; |
7454 | 22 | poDriver->pfnCreateCopy = NITFDataset::NITFCreateCopy; |
7455 | | |
7456 | 22 | GetGDALDriverManager()->RegisterDriver(poDriver); |
7457 | | |
7458 | | #ifdef NITF_PLUGIN |
7459 | | GDALRegister_RPFTOC(); |
7460 | | GDALRegister_ECRGTOC(); |
7461 | | #endif |
7462 | 22 | } |