/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 <algorithm> |
24 | | #include <cmath> |
25 | | #include <cstdio> |
26 | | #include <cstdlib> |
27 | | #include <cstring> |
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 | | #include "offsetpatcher.h" |
52 | | #include "rpfframewriter.h" |
53 | | |
54 | | static bool NITFPatchImageLength(const char *pszFilename, VSILFILE *fp, |
55 | | int nIMIndex, GUIntBig nImageOffset, |
56 | | GIntBig nPixelCount, const char *pszIC, |
57 | | vsi_l_offset nICOffset, |
58 | | CSLConstList papszCreationOptions); |
59 | | static bool |
60 | | NITFWriteExtraSegments(const char *pszFilename, VSILFILE *fpIn, |
61 | | CSLConstList papszCgmMD, CSLConstList papszTextMD, |
62 | | GDALOffsetPatcher::OffsetPatcher *offsetPatcher, |
63 | | const CPLStringList &aosOptions, int nReciprocalScale); |
64 | | |
65 | | #ifdef JPEG_SUPPORTED |
66 | | static bool NITFWriteJPEGImage(GDALDataset *, VSILFILE *, vsi_l_offset, |
67 | | CSLConstList, GDALProgressFunc pfnProgress, |
68 | | void *pProgressData); |
69 | | #endif |
70 | | |
71 | | static void SetBandMetadata(NITFImage *psImage, GDALRasterBand *poBand, |
72 | | int nBand, bool bReportISUBCAT); |
73 | | |
74 | | static bool NITFWriteCGMSegments(const char *pszFilename, VSILFILE *&fpVSIL, |
75 | | CSLConstList papszList); |
76 | | static bool NITFWriteTextSegments(const char *pszFilename, VSILFILE *&fpVSIL, |
77 | | CSLConstList papszList); |
78 | | static bool UpdateFileLength(VSILFILE *fp); |
79 | | |
80 | | /************************************************************************/ |
81 | | /* ==================================================================== */ |
82 | | /* NITFDataset */ |
83 | | /* ==================================================================== */ |
84 | | /************************************************************************/ |
85 | | |
86 | | /************************************************************************/ |
87 | | /* NITFDataset() */ |
88 | | /************************************************************************/ |
89 | | |
90 | | NITFDataset::NITFDataset() |
91 | 914 | { |
92 | 914 | m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
93 | 914 | m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
94 | | |
95 | 914 | poDriver = GDALDriver::FromHandle(GDALGetDriverByName("NITF")); |
96 | 914 | } |
97 | | |
98 | | /************************************************************************/ |
99 | | /* ~NITFDataset() */ |
100 | | /************************************************************************/ |
101 | | |
102 | | NITFDataset::~NITFDataset() |
103 | | |
104 | 914 | { |
105 | 914 | NITFDataset::Close(); |
106 | | |
107 | | /* -------------------------------------------------------------------- */ |
108 | | /* Free datastructures. */ |
109 | | /* -------------------------------------------------------------------- */ |
110 | | |
111 | 914 | GDALDeinitGCPs(nGCPCount, pasGCPList); |
112 | 914 | CPLFree(pasGCPList); |
113 | | |
114 | 914 | CPLFree(panJPEGBlockOffset); |
115 | 914 | CPLFree(pabyJPEGBlock); |
116 | 914 | } |
117 | | |
118 | | /************************************************************************/ |
119 | | /* Close() */ |
120 | | /************************************************************************/ |
121 | | |
122 | | CPLErr NITFDataset::Close(GDALProgressFunc, void *) |
123 | 1.45k | { |
124 | 1.45k | int bHasDroppedRef = FALSE; |
125 | 1.45k | return NITFDataset::Close(bHasDroppedRef); |
126 | 1.45k | } |
127 | | |
128 | | CPLErr NITFDataset::Close(int &bHasDroppedRef) |
129 | 1.45k | { |
130 | 1.45k | CPLErr eErr = CE_None; |
131 | 1.45k | bHasDroppedRef = FALSE; |
132 | 1.45k | if (nOpenFlags != OPEN_FLAGS_CLOSED) |
133 | 914 | { |
134 | 914 | eErr = NITFDataset::FlushCache(true); |
135 | | |
136 | 914 | bHasDroppedRef = GDALPamDataset::CloseDependentDatasets(); |
137 | | |
138 | | /* -------------------------------------------------------------------- */ |
139 | | /* If we have been writing to a JPEG2000 file, check if the */ |
140 | | /* color interpretations were set. If so, apply the settings */ |
141 | | /* to the NITF file. */ |
142 | | /* -------------------------------------------------------------------- */ |
143 | 914 | if (poJ2KDataset != nullptr && bJP2Writing) |
144 | 0 | { |
145 | 0 | for (int i = 0; i < nBands && papoBands != nullptr; i++) |
146 | 0 | { |
147 | 0 | if (papoBands[i]->GetColorInterpretation() != GCI_Undefined) |
148 | 0 | NITFSetColorInterpretation( |
149 | 0 | psImage, i + 1, papoBands[i]->GetColorInterpretation()); |
150 | 0 | } |
151 | 0 | } |
152 | | |
153 | | /* -------------------------------------------------------------------- */ |
154 | | /* Close the underlying NITF file. */ |
155 | | /* -------------------------------------------------------------------- */ |
156 | 914 | if (psFile != nullptr) |
157 | 914 | { |
158 | 914 | eErr = GDAL::Combine(eErr, NITFClose(psFile)); |
159 | 914 | psFile = nullptr; |
160 | 914 | } |
161 | | |
162 | | /* -------------------------------------------------------------------- */ |
163 | | /* If we have a jpeg2000 output file, make sure it gets closed */ |
164 | | /* and flushed out. */ |
165 | | /* -------------------------------------------------------------------- */ |
166 | 914 | if (poJ2KDataset != nullptr) |
167 | 0 | { |
168 | 0 | eErr = GDAL::Combine(eErr, poJ2KDataset->Close()); |
169 | 0 | poJ2KDataset.reset(); |
170 | 0 | bHasDroppedRef = TRUE; |
171 | 0 | } |
172 | | |
173 | | /* -------------------------------------------------------------------- */ |
174 | | /* Update file length, and COMRAT for JPEG2000 files we are */ |
175 | | /* writing to. */ |
176 | | /* -------------------------------------------------------------------- */ |
177 | 914 | if (bJP2Writing) |
178 | 0 | { |
179 | 0 | const GIntBig nPixelCount = |
180 | 0 | static_cast<GIntBig>(nRasterXSize) * nRasterYSize * nBands; |
181 | |
|
182 | 0 | eErr = GDAL::Combine( |
183 | 0 | eErr, NITFPatchImageLength( |
184 | 0 | GetDescription(), nullptr, m_nIMIndex, m_nImageOffset, |
185 | 0 | nPixelCount, "C8", m_nICOffset, nullptr)); |
186 | 0 | } |
187 | | |
188 | 914 | bJP2Writing = FALSE; |
189 | | |
190 | | /* -------------------------------------------------------------------- */ |
191 | | /* If we have a jpeg output file, make sure it gets closed */ |
192 | | /* and flushed out. */ |
193 | | /* -------------------------------------------------------------------- */ |
194 | 914 | if (poJPEGDataset != nullptr) |
195 | 14 | { |
196 | 14 | eErr = GDAL::Combine(eErr, poJPEGDataset->Close()); |
197 | 14 | poJPEGDataset.reset(); |
198 | 14 | bHasDroppedRef = TRUE; |
199 | 14 | } |
200 | | |
201 | | /* -------------------------------------------------------------------- */ |
202 | | /* If the dataset was opened by Create(), we may need to write */ |
203 | | /* the CGM and TEXT segments */ |
204 | | /* -------------------------------------------------------------------- */ |
205 | 914 | if (m_nIMIndex + 1 == m_nImageCount) |
206 | 0 | { |
207 | 0 | eErr = GDAL::Combine( |
208 | 0 | eErr, NITFWriteExtraSegments( |
209 | 0 | GetDescription(), nullptr, papszCgmMDToWrite, |
210 | 0 | papszTextMDToWrite, nullptr, aosCreationOptions, 0)); |
211 | 0 | } |
212 | | |
213 | 914 | CSLDestroy(papszTextMDToWrite); |
214 | 914 | papszTextMDToWrite = nullptr; |
215 | 914 | CSLDestroy(papszCgmMDToWrite); |
216 | 914 | papszCgmMDToWrite = nullptr; |
217 | | |
218 | 914 | eErr = GDAL::Combine(eErr, GDALPamDataset::Close()); |
219 | | |
220 | | /* -------------------------------------------------------------------- */ |
221 | | /* Destroy the raster bands if they exist. */ |
222 | | /* We must do it now since the rasterbands can be NITFWrapperRasterBand */ |
223 | | /* that derive from the GDALProxyRasterBand object, which keeps */ |
224 | | /* a reference on the JPEG/JP2K dataset, so any later call to */ |
225 | | /* FlushCache() would result in FlushCache() being called on a */ |
226 | | /* already destroyed object */ |
227 | | /* -------------------------------------------------------------------- */ |
228 | 14.7k | for (int iBand = 0; iBand < nBands; iBand++) |
229 | 13.8k | { |
230 | 13.8k | delete papoBands[iBand]; |
231 | 13.8k | } |
232 | 914 | nBands = 0; |
233 | 914 | } |
234 | 1.45k | return eErr; |
235 | 1.45k | } |
236 | | |
237 | | /************************************************************************/ |
238 | | /* CloseDependentDatasets() */ |
239 | | /************************************************************************/ |
240 | | |
241 | | int NITFDataset::CloseDependentDatasets() |
242 | 0 | { |
243 | 0 | int bHasDroppedRef = FALSE; |
244 | 0 | Close(bHasDroppedRef); |
245 | 0 | return bHasDroppedRef; |
246 | 0 | } |
247 | | |
248 | | /************************************************************************/ |
249 | | /* FlushCache() */ |
250 | | /************************************************************************/ |
251 | | |
252 | | CPLErr NITFDataset::FlushCache(bool bAtClosing) |
253 | | |
254 | 930 | { |
255 | | // If the JPEG/JP2K dataset has dirty pam info, then we should consider |
256 | | // ourselves to as well. |
257 | 930 | if (poJPEGDataset != nullptr && |
258 | 14 | (poJPEGDataset->GetMOFlags() & GMO_PAM_CLASS) && |
259 | 14 | (cpl::down_cast<GDALPamDataset *>(poJPEGDataset.get())->GetPamFlags() & |
260 | 14 | GPF_DIRTY)) |
261 | 0 | MarkPamDirty(); |
262 | | |
263 | 930 | if (poJ2KDataset != nullptr && |
264 | 0 | (poJ2KDataset->GetMOFlags() & GMO_PAM_CLASS) && |
265 | 0 | (cpl::down_cast<GDALPamDataset *>(poJ2KDataset.get())->GetPamFlags() & |
266 | 0 | GPF_DIRTY)) |
267 | 0 | MarkPamDirty(); |
268 | | |
269 | 930 | CPLErr eErr = CE_None; |
270 | 930 | if (poJ2KDataset != nullptr && bJP2Writing) |
271 | 0 | eErr = poJ2KDataset->FlushCache(bAtClosing); |
272 | | |
273 | 930 | if (GDALPamDataset::FlushCache(bAtClosing) != CE_None) |
274 | 0 | eErr = CE_Failure; |
275 | 930 | return eErr; |
276 | 930 | } |
277 | | |
278 | | #ifdef ESRI_BUILD |
279 | | |
280 | | /************************************************************************/ |
281 | | /* ExtractEsriMD() */ |
282 | | /* */ |
283 | | /* Extracts ESRI-specific required meta data from metadata */ |
284 | | /* string list papszStrList. */ |
285 | | /************************************************************************/ |
286 | | |
287 | | static char **ExtractEsriMD(char **papszMD) |
288 | | { |
289 | | char **papszEsriMD = NULL; |
290 | | |
291 | | if (papszMD) |
292 | | { |
293 | | // These are the current generic ESRI metadata. |
294 | | const char *const pEsriMDAcquisitionDate = "ESRI_MD_ACQUISITION_DATE"; |
295 | | const char *const pEsriMDAngleToNorth = "ESRI_MD_ANGLE_TO_NORTH"; |
296 | | const char *const pEsriMDCircularError = "ESRI_MD_CE"; |
297 | | const char *const pEsriMDDataType = "ESRI_MD_DATA_TYPE"; |
298 | | const char *const pEsriMDIsCloudCover = "ESRI_MD_ISCLOUDCOVER"; |
299 | | const char *const pEsriMDLinearError = "ESRI_MD_LE"; |
300 | | const char *const pEsriMDOffNaDir = "ESRI_MD_OFF_NADIR"; |
301 | | const char *const pEsriMDPercentCloudCover = |
302 | | "ESRI_MD_PERCENT_CLOUD_COVER"; |
303 | | const char *const pEsriMDProductName = "ESRI_MD_PRODUCT_NAME"; |
304 | | const char *const pEsriMDSensorAzimuth = "ESRI_MD_SENSOR_AZIMUTH"; |
305 | | const char *const pEsriMDSensorElevation = "ESRI_MD_SENSOR_ELEVATION"; |
306 | | const char *const pEsriMDSensorName = "ESRI_MD_SENSOR_NAME"; |
307 | | const char *const pEsriMDSunAzimuth = "ESRI_MD_SUN_AZIMUTH"; |
308 | | const char *const pEsriMDSunElevation = "ESRI_MD_SUN_ELEVATION"; |
309 | | |
310 | | const char *pCCImageSegment = CSLFetchNameValue(papszMD, "NITF_IID1"); |
311 | | std::string ccSegment("false"); |
312 | | |
313 | | if ((pCCImageSegment != NULL) && (strlen(pCCImageSegment) <= 10)) |
314 | | { |
315 | | char szField[11] = {0}; |
316 | | strncpy(szField, pCCImageSegment, strlen(pCCImageSegment)); |
317 | | szField[strlen(pCCImageSegment)] = '\0'; |
318 | | |
319 | | // Trim white off tag. |
320 | | while ((strlen(szField) > 0) && |
321 | | (szField[strlen(szField) - 1] == ' ')) |
322 | | szField[strlen(szField) - 1] = '\0'; |
323 | | |
324 | | if ((strlen(szField) == 2) && (STARTS_WITH_CI(szField, "CC"))) |
325 | | ccSegment.assign("true"); |
326 | | } |
327 | | |
328 | | const char *pAcquisitionDate = CSLFetchNameValue(papszMD, "NITF_FDT"); |
329 | | const char *pAngleToNorth = |
330 | | CSLFetchNameValue(papszMD, "NITF_CSEXRA_ANGLE_TO_NORTH"); |
331 | | const char *pCircularError = CSLFetchNameValue( |
332 | | papszMD, "NITF_CSEXRA_CIRCL_ERR"); // Unit in feet. |
333 | | const char *pLinearError = CSLFetchNameValue( |
334 | | papszMD, "NITF_CSEXRA_LINEAR_ERR"); // Unit in feet. |
335 | | const char *pPercentCloudCover = |
336 | | CSLFetchNameValue(papszMD, "NITF_PIAIMC_CLOUDCVR"); |
337 | | const char *pProductName = |
338 | | CSLFetchNameValue(papszMD, "NITF_CSDIDA_PRODUCT_ID"); |
339 | | const char *pSensorName = |
340 | | CSLFetchNameValue(papszMD, "NITF_PIAIMC_SENSNAME"); |
341 | | const char *pSunAzimuth = |
342 | | CSLFetchNameValue(papszMD, "NITF_CSEXRA_SUN_AZIMUTH"); |
343 | | const char *pSunElevation = |
344 | | CSLFetchNameValue(papszMD, "NITF_CSEXRA_SUN_ELEVATION"); |
345 | | |
346 | | // Get ESRI_MD_DATA_TYPE. |
347 | | const char *pImgSegFieldICAT = CSLFetchNameValue(papszMD, "NITF_ICAT"); |
348 | | |
349 | | const char *pDataType = NULL; |
350 | | if ((pImgSegFieldICAT != NULL) && |
351 | | (STARTS_WITH_CI(pImgSegFieldICAT, "DTEM"))) |
352 | | pDataType = "Elevation"; |
353 | | else |
354 | | pDataType = "Generic"; |
355 | | |
356 | | if (pAngleToNorth == NULL) |
357 | | pAngleToNorth = |
358 | | CSLFetchNameValue(papszMD, "NITF_USE00A_ANGLE_TO_NORTH"); |
359 | | |
360 | | // Percent cloud cover == 999 means that the information is not |
361 | | // available. |
362 | | if ((pPercentCloudCover != NULL) && |
363 | | (STARTS_WITH_CI(pPercentCloudCover, "999"))) |
364 | | pPercentCloudCover = NULL; |
365 | | |
366 | | pAngleToNorth = |
367 | | CSLFetchNameValue(papszMD, "NITF_USE00A_ANGLE_TO_NORTH"); |
368 | | |
369 | | if (pSunAzimuth == NULL) |
370 | | pSunAzimuth = CSLFetchNameValue(papszMD, "NITF_USE00A_SUN_AZ"); |
371 | | |
372 | | if (pSunElevation == NULL) |
373 | | pSunElevation = CSLFetchNameValue(papszMD, "NITF_USE00A_SUN_EL"); |
374 | | |
375 | | // CSLAddNameValue will not add the key/value pair if the value is NULL. |
376 | | papszEsriMD = CSLAddNameValue(papszEsriMD, pEsriMDAcquisitionDate, |
377 | | pAcquisitionDate); |
378 | | papszEsriMD = |
379 | | CSLAddNameValue(papszEsriMD, pEsriMDAngleToNorth, pAngleToNorth); |
380 | | papszEsriMD = |
381 | | CSLAddNameValue(papszEsriMD, pEsriMDCircularError, pCircularError); |
382 | | papszEsriMD = CSLAddNameValue(papszEsriMD, pEsriMDDataType, pDataType); |
383 | | papszEsriMD = CSLAddNameValue(papszEsriMD, pEsriMDIsCloudCover, |
384 | | ccSegment.c_str()); |
385 | | papszEsriMD = |
386 | | CSLAddNameValue(papszEsriMD, pEsriMDLinearError, pLinearError); |
387 | | papszEsriMD = |
388 | | CSLAddNameValue(papszEsriMD, pEsriMDProductName, pProductName); |
389 | | papszEsriMD = CSLAddNameValue(papszEsriMD, pEsriMDPercentCloudCover, |
390 | | pPercentCloudCover); |
391 | | papszEsriMD = |
392 | | CSLAddNameValue(papszEsriMD, pEsriMDSensorName, pSensorName); |
393 | | papszEsriMD = |
394 | | CSLAddNameValue(papszEsriMD, pEsriMDSunAzimuth, pSunAzimuth); |
395 | | papszEsriMD = |
396 | | CSLAddNameValue(papszEsriMD, pEsriMDSunElevation, pSunElevation); |
397 | | } |
398 | | |
399 | | return papszEsriMD; |
400 | | } |
401 | | |
402 | | #endif /* def ESRI_BUILD */ |
403 | | |
404 | | /************************************************************************/ |
405 | | /* SetBandMetadata() */ |
406 | | /************************************************************************/ |
407 | | |
408 | | static void SetBandMetadata(NITFImage *psImage, GDALRasterBand *poBand, |
409 | | int nBand, bool bReportISUBCAT) |
410 | 13.8k | { |
411 | 13.8k | const NITFBandInfo *psBandInfo = psImage->pasBandInfo + nBand - 1; |
412 | | |
413 | | /* The ISUBCAT is particularly valuable for interpreting SAR bands */ |
414 | 13.8k | if (bReportISUBCAT && strlen(psBandInfo->szISUBCAT) > 0) |
415 | 7.86k | { |
416 | 7.86k | poBand->SetMetadataItem("NITF_ISUBCAT", psBandInfo->szISUBCAT); |
417 | 7.86k | } |
418 | 13.8k | } |
419 | | |
420 | | /************************************************************************/ |
421 | | /* Open() */ |
422 | | /************************************************************************/ |
423 | | |
424 | | GDALDataset *NITFDataset::Open(GDALOpenInfo *poOpenInfo) |
425 | 2.08k | { |
426 | 2.08k | return OpenInternal(poOpenInfo, nullptr, false, -1); |
427 | 2.08k | } |
428 | | |
429 | | NITFDataset *NITFDataset::OpenInternal(GDALOpenInfo *poOpenInfo, |
430 | | GDALDataset *poWritableJ2KDataset, |
431 | | bool bOpenForCreate, int nIMIndex) |
432 | | |
433 | 2.10k | { |
434 | 2.10k | if (!NITFDriverIdentify(poOpenInfo)) |
435 | 0 | return nullptr; |
436 | | |
437 | 2.10k | const char *pszFilename = poOpenInfo->pszFilename; |
438 | | |
439 | | /* -------------------------------------------------------------------- */ |
440 | | /* Select a specific subdataset. */ |
441 | | /* -------------------------------------------------------------------- */ |
442 | 2.10k | if (STARTS_WITH_CI(pszFilename, "NITF_IM:")) |
443 | 0 | { |
444 | 0 | pszFilename += 8; |
445 | 0 | nIMIndex = atoi(pszFilename); |
446 | |
|
447 | 0 | while (*pszFilename != '\0' && *pszFilename != ':') |
448 | 0 | pszFilename++; |
449 | |
|
450 | 0 | if (*pszFilename == ':') |
451 | 0 | pszFilename++; |
452 | 0 | } |
453 | | |
454 | | /* -------------------------------------------------------------------- */ |
455 | | /* Open the file with library. */ |
456 | | /* -------------------------------------------------------------------- */ |
457 | 2.10k | NITFFile *psFile = nullptr; |
458 | | |
459 | 2.10k | if (poOpenInfo->fpL) |
460 | 2.10k | { |
461 | 2.10k | VSILFILE *fpL = poOpenInfo->fpL; |
462 | 2.10k | poOpenInfo->fpL = nullptr; |
463 | 2.10k | psFile = NITFOpenEx(fpL, pszFilename); |
464 | 2.10k | } |
465 | 0 | else |
466 | 0 | psFile = NITFOpen(pszFilename, poOpenInfo->eAccess == GA_Update); |
467 | 2.10k | if (psFile == nullptr) |
468 | 370 | { |
469 | 370 | return nullptr; |
470 | 370 | } |
471 | | |
472 | 1.73k | if (!bOpenForCreate) |
473 | 1.71k | { |
474 | 1.71k | NITFCollectAttachments(psFile); |
475 | 1.71k | NITFReconcileAttachments(psFile); |
476 | 1.71k | } |
477 | | |
478 | | /* -------------------------------------------------------------------- */ |
479 | | /* Is there an image to operate on? */ |
480 | | /* -------------------------------------------------------------------- */ |
481 | 1.73k | int nThisIM = 0; |
482 | 1.73k | NITFImage *psImage = nullptr; |
483 | | |
484 | 1.73k | int iSegment = 0; // Used after for loop. |
485 | 51.2k | for (; iSegment < psFile->nSegmentCount; iSegment++) |
486 | 51.0k | { |
487 | 51.0k | if (EQUAL(psFile->pasSegmentInfo[iSegment].szSegmentType, "IM") && |
488 | 1.55k | (nThisIM++ == nIMIndex || nIMIndex == -1)) |
489 | 1.55k | { |
490 | 1.55k | psImage = NITFImageAccess(psFile, iSegment); |
491 | 1.55k | if (psImage == nullptr) |
492 | 820 | { |
493 | 820 | NITFClose(psFile); |
494 | 820 | return nullptr; |
495 | 820 | } |
496 | 736 | break; |
497 | 1.55k | } |
498 | 51.0k | } |
499 | | |
500 | | /* -------------------------------------------------------------------- */ |
501 | | /* If no image segments found report this to the user. */ |
502 | | /* -------------------------------------------------------------------- */ |
503 | 914 | if (psImage == nullptr) |
504 | 178 | { |
505 | 178 | CPLError(CE_Warning, CPLE_AppDefined, |
506 | 178 | "The file %s appears to be an NITF file, but no image " |
507 | 178 | "blocks were found on it.", |
508 | 178 | poOpenInfo->pszFilename); |
509 | 178 | } |
510 | 736 | else if (psImage->nBitsPerSample > 16 && |
511 | 53 | (EQUAL(psImage->szIC, "C3") || EQUAL(psImage->szIC, "M3"))) |
512 | 0 | { |
513 | | // Early rejection of JPEG compressed images with invalid bit depth |
514 | | // Otherwise this will cause potentially heap buffer overflows |
515 | | // as ReadJPEGBlock() assumes that the data type size is no larger |
516 | | // than 2 bytes. |
517 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
518 | 0 | "IC=%s and ABPP=%d are not supported", psImage->szIC, |
519 | 0 | psImage->nBitsPerSample); |
520 | 0 | NITFClose(psFile); |
521 | 0 | return nullptr; |
522 | 0 | } |
523 | | |
524 | | /* -------------------------------------------------------------------- */ |
525 | | /* Create a corresponding GDALDataset. */ |
526 | | /* -------------------------------------------------------------------- */ |
527 | 914 | NITFDataset *poDS = new NITFDataset(); |
528 | | |
529 | 914 | poDS->psFile = psFile; |
530 | 914 | poDS->psImage = psImage; |
531 | 914 | poDS->eAccess = poOpenInfo->eAccess; |
532 | 914 | poDS->osNITFFilename = pszFilename; |
533 | 914 | poDS->nIMIndex = nIMIndex; |
534 | | |
535 | 914 | if (psImage) |
536 | 736 | { |
537 | 736 | if (psImage->nCols <= 0 || psImage->nRows <= 0 || |
538 | 580 | psImage->nBlockWidth <= 0 || psImage->nBlockHeight <= 0) |
539 | 156 | { |
540 | 156 | CPLError(CE_Failure, CPLE_AppDefined, |
541 | 156 | "Bad values in NITF image : nCols=%d, nRows=%d, " |
542 | 156 | "nBlockWidth=%d, nBlockHeight=%d", |
543 | 156 | psImage->nCols, psImage->nRows, psImage->nBlockWidth, |
544 | 156 | psImage->nBlockHeight); |
545 | 156 | delete poDS; |
546 | 156 | return nullptr; |
547 | 156 | } |
548 | | |
549 | 580 | poDS->nRasterXSize = psImage->nCols; |
550 | 580 | poDS->nRasterYSize = psImage->nRows; |
551 | 580 | } |
552 | 178 | else |
553 | 178 | { |
554 | 178 | poDS->nRasterXSize = 1; |
555 | 178 | poDS->nRasterYSize = 1; |
556 | 178 | } |
557 | | |
558 | | /* Can be set to NO to avoid opening the underlying JPEG2000/JPEG */ |
559 | | /* stream. Might speed up operations when just metadata is needed */ |
560 | 758 | bool bOpenUnderlyingDS = |
561 | 758 | CPLTestBool(CPLGetConfigOption("NITF_OPEN_UNDERLYING_DS", "YES")); |
562 | | |
563 | | /* -------------------------------------------------------------------- */ |
564 | | /* If the image is JPEG2000 (C8) compressed, we will need to */ |
565 | | /* open the image data as a JPEG2000 dataset. */ |
566 | | /* -------------------------------------------------------------------- */ |
567 | 758 | int nUsableBands = 0; |
568 | 758 | bool bSetColorInterpretation = true; |
569 | 758 | bool bSetColorTable = false; |
570 | | |
571 | 758 | if (psImage) |
572 | 580 | nUsableBands = psImage->nBands; |
573 | | |
574 | 758 | if (bOpenUnderlyingDS && psImage != nullptr && EQUAL(psImage->szIC, "C8")) |
575 | 1 | { |
576 | 1 | CPLString osDSName; |
577 | | |
578 | 1 | osDSName.Printf("/vsisubfile/" CPL_FRMT_GUIB "_" CPL_FRMT_GUIB ",%s", |
579 | 1 | psFile->pasSegmentInfo[iSegment].nSegmentStart, |
580 | 1 | psFile->pasSegmentInfo[iSegment].nSegmentSize, |
581 | 1 | pszFilename); |
582 | | |
583 | 1 | if (poWritableJ2KDataset != nullptr) |
584 | 0 | { |
585 | 0 | poDS->poJ2KDataset.reset(poWritableJ2KDataset); |
586 | 0 | poDS->bJP2Writing = TRUE; |
587 | 0 | poWritableJ2KDataset = nullptr; |
588 | 0 | } |
589 | 1 | else |
590 | 1 | { |
591 | | // We explicitly list the allowed drivers to avoid hostile content |
592 | | // to be opened by a random driver. |
593 | 1 | static const char *const apszDrivers[] = { |
594 | 1 | "JP2KAK", "JP2ECW", "JP2MRSID", "JP2OPENJPEG", nullptr}; |
595 | 1 | poDS->poJ2KDataset.reset(GDALDataset::Open( |
596 | 1 | osDSName, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR, apszDrivers, |
597 | 1 | nullptr, nullptr)); |
598 | | |
599 | 1 | if (poDS->poJ2KDataset == nullptr) |
600 | 1 | { |
601 | 1 | bool bFoundJPEG2000Driver = false; |
602 | 5 | for (int iDriver = 0; apszDrivers[iDriver] != nullptr; |
603 | 4 | iDriver++) |
604 | 4 | { |
605 | 4 | if (GDALGetDriverByName(apszDrivers[iDriver]) != nullptr) |
606 | 0 | bFoundJPEG2000Driver = true; |
607 | 4 | } |
608 | | |
609 | 1 | CPLError( |
610 | 1 | CE_Failure, CPLE_AppDefined, |
611 | 1 | "Unable to open JPEG2000 image within NITF file.\n%s\n%s", |
612 | 1 | !bFoundJPEG2000Driver |
613 | 1 | ? "No JPEG2000 capable driver (JP2KAK, JP2ECW, " |
614 | 1 | "JP2MRSID, " |
615 | 1 | "JP2OPENJPEG, etc...) is available." |
616 | 1 | : "One or several JPEG2000 capable drivers are " |
617 | 0 | "available but " |
618 | 0 | "the datastream could not be opened successfully.", |
619 | 1 | "You can define the NITF_OPEN_UNDERLYING_DS configuration " |
620 | 1 | "option to NO, in order to just get the metadata."); |
621 | 1 | delete poDS; |
622 | 1 | return nullptr; |
623 | 1 | } |
624 | | |
625 | 0 | if (poDS->poJ2KDataset->GetMOFlags() & GMO_PAM_CLASS) |
626 | 0 | { |
627 | 0 | cpl::down_cast<GDALPamDataset *>(poDS->poJ2KDataset.get()) |
628 | 0 | ->SetPamFlags(reinterpret_cast<GDALPamDataset *>( |
629 | 0 | poDS->poJ2KDataset.get()) |
630 | 0 | ->GetPamFlags() | |
631 | 0 | GPF_NOSAVE); |
632 | 0 | } |
633 | 0 | } |
634 | | |
635 | 0 | if (poDS->GetRasterXSize() != poDS->poJ2KDataset->GetRasterXSize() || |
636 | 0 | poDS->GetRasterYSize() != poDS->poJ2KDataset->GetRasterYSize()) |
637 | 0 | { |
638 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
639 | 0 | "JPEG2000 data stream has not the same dimensions as " |
640 | 0 | "the NITF file."); |
641 | 0 | delete poDS; |
642 | 0 | return nullptr; |
643 | 0 | } |
644 | | |
645 | 0 | if (nUsableBands == 1) |
646 | 0 | { |
647 | 0 | const char *pszIREP = |
648 | 0 | CSLFetchNameValue(psImage->papszMetadata, "NITF_IREP"); |
649 | 0 | if (pszIREP != nullptr && EQUAL(pszIREP, "RGB/LUT")) |
650 | 0 | { |
651 | 0 | if (poDS->poJ2KDataset->GetRasterCount() == 3) |
652 | 0 | { |
653 | | // Test case: |
654 | | // http://www.gwg.nga.mil/ntb/baseline/software/testfile/Jpeg2000/jp2_09/file9_jp2_2places.ntf |
655 | | /* 256-entry palette/LUT in both JP2 Header and image |
656 | | * Subheader */ |
657 | | /* In this case, the JPEG2000 driver will probably do the |
658 | | * RGB expansion. */ |
659 | 0 | nUsableBands = 3; |
660 | 0 | bSetColorInterpretation = false; |
661 | 0 | } |
662 | 0 | else if (poDS->poJ2KDataset->GetRasterCount() == 1 && |
663 | 0 | psImage->pasBandInfo[0].nSignificantLUTEntries > 0) |
664 | 0 | { |
665 | | // Test case: |
666 | | // http://www.gwg.nga.mil/ntb/baseline/software/testfile/Jpeg2000/jp2_09/file9_j2c.ntf |
667 | | |
668 | | // 256-entry/LUT in Image Subheader, JP2 header completely |
669 | | // removed. The JPEG2000 driver will decode it as a grey |
670 | | // band So we must set the color table on the wrapper band |
671 | | // or for file9_jp2_2places.ntf as well if the J2K driver |
672 | | // does do RGB expansion |
673 | 0 | bSetColorTable = true; |
674 | 0 | } |
675 | 0 | } |
676 | 0 | } |
677 | |
|
678 | 0 | if (poDS->poJ2KDataset->GetRasterCount() < nUsableBands) |
679 | 0 | { |
680 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
681 | 0 | "JPEG2000 data stream has less useful bands than " |
682 | 0 | "expected, likely because some channels have " |
683 | 0 | "differing resolutions."); |
684 | |
|
685 | 0 | nUsableBands = poDS->poJ2KDataset->GetRasterCount(); |
686 | 0 | } |
687 | 0 | } |
688 | | |
689 | | /* -------------------------------------------------------------------- */ |
690 | | /* If the image is JPEG (C3) compressed, we will need to open */ |
691 | | /* the image data as a JPEG dataset. */ |
692 | | /* -------------------------------------------------------------------- */ |
693 | 757 | else if (bOpenUnderlyingDS && psImage != nullptr && |
694 | 579 | EQUAL(psImage->szIC, "C3") && psImage->nBlocksPerRow == 1 && |
695 | 24 | psImage->nBlocksPerColumn == 1) |
696 | 18 | { |
697 | 18 | GUIntBig nJPEGStart = psFile->pasSegmentInfo[iSegment].nSegmentStart; |
698 | | |
699 | 18 | bool bError = false; |
700 | 18 | poDS->nQLevel = poDS->ScanJPEGQLevel(&nJPEGStart, &bError); |
701 | | |
702 | 18 | CPLString osDSName; |
703 | | |
704 | 18 | if (psFile->pasSegmentInfo[iSegment].nSegmentSize < |
705 | 18 | nJPEGStart - psFile->pasSegmentInfo[iSegment].nSegmentStart) |
706 | 0 | { |
707 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Corrupted segment size"); |
708 | 0 | delete poDS; |
709 | 0 | return nullptr; |
710 | 0 | } |
711 | | |
712 | 18 | osDSName.Printf( |
713 | 18 | "JPEG_SUBFILE:Q%d," CPL_FRMT_GUIB "," CPL_FRMT_GUIB ",%s", |
714 | 18 | poDS->nQLevel, nJPEGStart, |
715 | 18 | psFile->pasSegmentInfo[iSegment].nSegmentSize - |
716 | 18 | (nJPEGStart - psFile->pasSegmentInfo[iSegment].nSegmentStart), |
717 | 18 | pszFilename); |
718 | | |
719 | 18 | CPLDebug("GDAL", "NITFDataset::Open() as IC=C3 (JPEG compressed)\n"); |
720 | | |
721 | 18 | poDS->poJPEGDataset.reset(GDALDataset::Open( |
722 | 18 | osDSName, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR)); |
723 | 18 | if (poDS->poJPEGDataset == nullptr) |
724 | 4 | { |
725 | 4 | const bool bFoundJPEGDriver = |
726 | 4 | GDALGetDriverByName("JPEG") != nullptr; |
727 | 4 | CPLError(CE_Failure, CPLE_AppDefined, |
728 | 4 | "Unable to open JPEG image within NITF file.\n%s\n%s", |
729 | 4 | (!bFoundJPEGDriver) |
730 | 4 | ? "The JPEG driver is not available." |
731 | 4 | : "The JPEG driver is available but the datastream " |
732 | 4 | "could not be opened successfully.", |
733 | 4 | "You can define the NITF_OPEN_UNDERLYING_DS configuration " |
734 | 4 | "option to NO, in order to just get the metadata."); |
735 | 4 | delete poDS; |
736 | 4 | return nullptr; |
737 | 4 | } |
738 | | |
739 | | /* In some circumstances, the JPEG image can be larger than the NITF */ |
740 | | /* (NCOLS, NROWS) dimensions (#5001), so accept it as a valid case */ |
741 | | /* But reject when it is smaller than the NITF dimensions. */ |
742 | 14 | if (poDS->GetRasterXSize() > poDS->poJPEGDataset->GetRasterXSize() || |
743 | 14 | poDS->GetRasterYSize() > poDS->poJPEGDataset->GetRasterYSize()) |
744 | 0 | { |
745 | 0 | CPLError( |
746 | 0 | CE_Failure, CPLE_AppDefined, |
747 | 0 | "JPEG data stream has smaller dimensions than the NITF file."); |
748 | 0 | delete poDS; |
749 | 0 | return nullptr; |
750 | 0 | } |
751 | | |
752 | 14 | if (poDS->poJPEGDataset->GetMOFlags() & GMO_PAM_CLASS) |
753 | 14 | { |
754 | 14 | (cpl::down_cast<GDALPamDataset *>(poDS->poJPEGDataset.get())) |
755 | 14 | ->SetPamFlags((reinterpret_cast<GDALPamDataset *>( |
756 | 14 | poDS->poJPEGDataset.get())) |
757 | 14 | ->GetPamFlags() | |
758 | 14 | GPF_NOSAVE); |
759 | 14 | } |
760 | | |
761 | 14 | if (poDS->poJPEGDataset->GetRasterCount() < nUsableBands) |
762 | 0 | { |
763 | 0 | CPLError( |
764 | 0 | CE_Warning, CPLE_AppDefined, |
765 | 0 | "JPEG data stream has less useful bands than expected, likely\n" |
766 | 0 | "because some channels have differing resolutions."); |
767 | |
|
768 | 0 | nUsableBands = poDS->poJPEGDataset->GetRasterCount(); |
769 | 0 | } |
770 | 14 | } |
771 | | |
772 | | /* -------------------------------------------------------------------- */ |
773 | | /* Create band information objects. */ |
774 | | /* -------------------------------------------------------------------- */ |
775 | | |
776 | | /* Keep temporary non-based dataset bands */ |
777 | 753 | bool bIsTempBandUsed = false; |
778 | 753 | GDALDataType dtFirstBand = GDT_Unknown; |
779 | 753 | GDALDataType dtSecondBand = GDT_Unknown; |
780 | 753 | std::vector<GDALRasterBand *> apoNewBands(nUsableBands); |
781 | | |
782 | 753 | GDALDataset *poBaseDS = nullptr; |
783 | 753 | if (poDS->poJ2KDataset != nullptr) |
784 | 0 | poBaseDS = poDS->poJ2KDataset.get(); |
785 | 753 | else if (poDS->poJPEGDataset != nullptr) |
786 | 14 | poBaseDS = poDS->poJPEGDataset.get(); |
787 | | |
788 | 14.5k | for (int iBand = 0; iBand < nUsableBands; iBand++) |
789 | 13.8k | { |
790 | 13.8k | if (poBaseDS != nullptr) |
791 | 14 | { |
792 | 14 | GDALRasterBand *poBaseBand = poBaseDS->GetRasterBand(iBand + 1); |
793 | | |
794 | 14 | SetBandMetadata(psImage, poBaseBand, iBand + 1, true); |
795 | | |
796 | 14 | NITFWrapperRasterBand *poBand = |
797 | 14 | new NITFWrapperRasterBand(poDS, poBaseBand, iBand + 1); |
798 | | |
799 | 14 | NITFBandInfo *psBandInfo = psImage->pasBandInfo + iBand; |
800 | 14 | if (bSetColorInterpretation) |
801 | 14 | { |
802 | | /* FIXME? Does it make sense if the JPEG/JPEG2000 driver decodes |
803 | | */ |
804 | | /* YCbCr data as RGB. We probably don't want to set */ |
805 | | /* the color interpretation as Y, Cb, Cr */ |
806 | 14 | if (EQUAL(psBandInfo->szIREPBAND, "R")) |
807 | 0 | poBand->SetColorInterpretation(GCI_RedBand); |
808 | 14 | if (EQUAL(psBandInfo->szIREPBAND, "G")) |
809 | 0 | poBand->SetColorInterpretation(GCI_GreenBand); |
810 | 14 | if (EQUAL(psBandInfo->szIREPBAND, "B")) |
811 | 0 | poBand->SetColorInterpretation(GCI_BlueBand); |
812 | 14 | if (EQUAL(psBandInfo->szIREPBAND, "M")) |
813 | 0 | poBand->SetColorInterpretation(GCI_GrayIndex); |
814 | 14 | if (EQUAL(psBandInfo->szIREPBAND, "Y")) |
815 | 0 | poBand->SetColorInterpretation(GCI_YCbCr_YBand); |
816 | 14 | if (EQUAL(psBandInfo->szIREPBAND, "Cb")) |
817 | 0 | poBand->SetColorInterpretation(GCI_YCbCr_CbBand); |
818 | 14 | if (EQUAL(psBandInfo->szIREPBAND, "Cr")) |
819 | 0 | poBand->SetColorInterpretation(GCI_YCbCr_CrBand); |
820 | 14 | } |
821 | 14 | if (bSetColorTable) |
822 | 0 | { |
823 | 0 | poBand->SetColorTableFromNITFBandInfo(); |
824 | 0 | poBand->SetColorInterpretation(GCI_PaletteIndex); |
825 | 0 | } |
826 | | |
827 | 14 | poDS->SetBand(iBand + 1, poBand); |
828 | | |
829 | 14 | if (iBand == 0) |
830 | 14 | dtFirstBand = poBand->GetRasterDataType(); |
831 | 0 | else if (iBand == 1) |
832 | 0 | dtSecondBand = poBand->GetRasterDataType(); |
833 | 14 | } |
834 | 13.8k | else |
835 | 13.8k | { |
836 | 13.8k | bIsTempBandUsed = true; |
837 | | |
838 | 13.8k | NITFRasterBand *poBand = new NITFRasterBand(poDS, iBand + 1); |
839 | 13.8k | if (poBand->GetRasterDataType() == GDT_Unknown) |
840 | 61 | { |
841 | 61 | for (auto *poOtherBand : apoNewBands) |
842 | 1.36k | delete poOtherBand; |
843 | 61 | delete poBand; |
844 | 61 | delete poDS; |
845 | 61 | return nullptr; |
846 | 61 | } |
847 | | |
848 | 13.7k | apoNewBands[iBand] = poBand; |
849 | | |
850 | 13.7k | if (iBand == 0) |
851 | 500 | dtFirstBand = poBand->GetRasterDataType(); |
852 | 13.7k | if (iBand == 1) |
853 | 114 | dtSecondBand = poBand->GetRasterDataType(); |
854 | 13.7k | } |
855 | 13.8k | } |
856 | | |
857 | | /* -------------------------------------------------------------------- */ |
858 | | /* SAR images may store complex data in 2 bands (I and Q) */ |
859 | | /* Map onto a GDAL complex raster band */ |
860 | | /* -------------------------------------------------------------------- */ |
861 | 692 | bool bIsTempBandSet = false; |
862 | 692 | if (!bOpenForCreate && psImage && |
863 | 494 | EQUAL(psImage->szICAT, "SAR") //SAR image... |
864 | 0 | && bIsTempBandUsed && |
865 | 0 | nUsableBands == psImage->nBands |
866 | | //...with 2 bands ... (modified to allow an even number - spec seems to indicate only 2 bands allowed?) |
867 | 0 | && (nUsableBands % 2) == 0 && |
868 | 0 | dtFirstBand == dtSecondBand //...that have the same datatype... |
869 | 0 | && !GDALDataTypeIsComplex(dtFirstBand) //...and are not complex... |
870 | | //..and can be mapped directly to a complex type |
871 | 0 | && (dtFirstBand == GDT_Int16 || dtFirstBand == GDT_Int32 || |
872 | 0 | dtFirstBand == GDT_Float32 || dtFirstBand == GDT_Float64) && |
873 | 0 | CPLTestBool(CPLGetConfigOption("NITF_SAR_AS_COMPLEX_TYPE", "YES"))) |
874 | 0 | { |
875 | 0 | bool allBandsIQ = true; |
876 | 0 | for (int i = 0; i < nUsableBands; i += 2) |
877 | 0 | { |
878 | 0 | const NITFBandInfo *psBandInfo1 = psImage->pasBandInfo + i; |
879 | 0 | const NITFBandInfo *psBandInfo2 = psImage->pasBandInfo + i + 1; |
880 | | |
881 | | //check that the ISUBCAT is labelled "I" and "Q" on the 2 bands |
882 | 0 | if (!EQUAL(psBandInfo1->szISUBCAT, "I") || |
883 | 0 | !EQUAL(psBandInfo2->szISUBCAT, "Q")) |
884 | 0 | { |
885 | 0 | allBandsIQ = false; |
886 | 0 | break; |
887 | 0 | } |
888 | 0 | } |
889 | |
|
890 | 0 | if (allBandsIQ) |
891 | 0 | { |
892 | 0 | poDS->m_bHasComplexRasterBand = true; |
893 | 0 | for (int i = 0; i < (nUsableBands / 2); i++) |
894 | 0 | { |
895 | | //wrap the I and Q bands into a single complex band |
896 | 0 | const int iBandIndex = 2 * i; |
897 | 0 | const int qBandIndex = 2 * i + 1; |
898 | 0 | NITFComplexRasterBand *poBand = new NITFComplexRasterBand( |
899 | 0 | poDS, apoNewBands[iBandIndex], apoNewBands[qBandIndex], |
900 | 0 | iBandIndex + 1, qBandIndex + 1); |
901 | 0 | SetBandMetadata(psImage, poBand, i + 1, false); |
902 | 0 | poDS->SetBand(i + 1, poBand); |
903 | 0 | bIsTempBandSet = true; |
904 | 0 | } |
905 | 0 | } |
906 | 0 | } |
907 | | |
908 | 692 | if (bIsTempBandUsed && !bIsTempBandSet) |
909 | 500 | { |
910 | | // Reset properly bands that are not complex |
911 | 14.2k | for (int iBand = 0; iBand < nUsableBands; iBand++) |
912 | 13.7k | { |
913 | 13.7k | GDALRasterBand *poBand = apoNewBands[iBand]; |
914 | 13.7k | SetBandMetadata(psImage, poBand, iBand + 1, true); |
915 | 13.7k | poDS->SetBand(iBand + 1, poBand); |
916 | 13.7k | } |
917 | 500 | } |
918 | | |
919 | | /* -------------------------------------------------------------------- */ |
920 | | /* Report problems with odd bit sizes. */ |
921 | | /* -------------------------------------------------------------------- */ |
922 | 692 | if (poOpenInfo->eAccess == GA_Update && psImage != nullptr && |
923 | 20 | (psImage->nBitsPerSample % 8 != 0) && poDS->poJPEGDataset == nullptr && |
924 | 0 | poDS->poJ2KDataset == nullptr) |
925 | 0 | { |
926 | 0 | CPLError( |
927 | 0 | CE_Warning, CPLE_AppDefined, |
928 | 0 | "Image with %d bits per sample cannot be opened in update mode.", |
929 | 0 | psImage->nBitsPerSample); |
930 | 0 | delete poDS; |
931 | 0 | return nullptr; |
932 | 0 | } |
933 | | |
934 | | /* -------------------------------------------------------------------- */ |
935 | | /* Process the projection from the ICORDS. */ |
936 | | /* -------------------------------------------------------------------- */ |
937 | 692 | if (psImage == nullptr) |
938 | 178 | { |
939 | | /* nothing */ |
940 | 178 | } |
941 | 514 | else if (psImage->chICORDS == 'G' || psImage->chICORDS == 'D') |
942 | 36 | { |
943 | 36 | poDS->m_oSRS.SetWellKnownGeogCS("WGS84"); |
944 | 36 | } |
945 | 478 | else if (psImage->chICORDS == 'C') |
946 | 5 | { |
947 | 5 | poDS->m_oSRS.SetWellKnownGeogCS("WGS84"); |
948 | | |
949 | | /* convert latitudes from geocentric to geodetic form. */ |
950 | | |
951 | 5 | psImage->dfULY = |
952 | 5 | NITF_WGS84_Geocentric_Latitude_To_Geodetic_Latitude(psImage->dfULY); |
953 | 5 | psImage->dfLLY = |
954 | 5 | NITF_WGS84_Geocentric_Latitude_To_Geodetic_Latitude(psImage->dfLLY); |
955 | 5 | psImage->dfURY = |
956 | 5 | NITF_WGS84_Geocentric_Latitude_To_Geodetic_Latitude(psImage->dfURY); |
957 | 5 | psImage->dfLRY = |
958 | 5 | NITF_WGS84_Geocentric_Latitude_To_Geodetic_Latitude(psImage->dfLRY); |
959 | 5 | } |
960 | 473 | else if (psImage->chICORDS == 'S' || psImage->chICORDS == 'N') |
961 | 45 | { |
962 | | // in open-for-create mode, we don't have a valid UTM zone, which |
963 | | // would make PROJ unhappy |
964 | 45 | if (!bOpenForCreate) |
965 | 39 | { |
966 | 39 | poDS->m_oSRS.SetUTM(psImage->nZone, psImage->chICORDS == 'N'); |
967 | 39 | poDS->m_oSRS.SetWellKnownGeogCS("WGS84"); |
968 | 39 | } |
969 | 45 | } |
970 | 428 | else if (psImage->chICORDS == 'U' && psImage->nZone != 0) |
971 | 0 | { |
972 | 0 | poDS->m_oSRS.SetUTM(std::abs(psImage->nZone), psImage->nZone > 0); |
973 | 0 | poDS->m_oSRS.SetWellKnownGeogCS("WGS84"); |
974 | 0 | } |
975 | | |
976 | | /* -------------------------------------------------------------------- */ |
977 | | /* Try looking for a .nfw file. */ |
978 | | /* -------------------------------------------------------------------- */ |
979 | 692 | if (psImage && GDALReadWorldFile2(pszFilename, "nfw", poDS->m_gt.data(), |
980 | 514 | poOpenInfo->GetSiblingFiles(), nullptr)) |
981 | 0 | { |
982 | 0 | int isNorth; |
983 | 0 | int zone; |
984 | |
|
985 | 0 | poDS->bGotGeoTransform = TRUE; |
986 | | |
987 | | /* If nfw found, try looking for a header with projection info */ |
988 | | /* in space imaging style format */ |
989 | 0 | std::string osHDR = CPLResetExtensionSafe(pszFilename, "hdr"); |
990 | |
|
991 | 0 | VSILFILE *fpHDR = VSIFOpenL(osHDR.c_str(), "rt"); |
992 | |
|
993 | 0 | if (fpHDR == nullptr && VSIIsCaseSensitiveFS(osHDR.c_str())) |
994 | 0 | { |
995 | 0 | osHDR = CPLResetExtensionSafe(pszFilename, "HDR"); |
996 | 0 | fpHDR = VSIFOpenL(osHDR.c_str(), "rt"); |
997 | 0 | } |
998 | |
|
999 | 0 | if (fpHDR != nullptr) |
1000 | 0 | { |
1001 | 0 | CPL_IGNORE_RET_VAL(VSIFCloseL(fpHDR)); |
1002 | 0 | char **papszLines = CSLLoad2(osHDR.c_str(), 16, 200, nullptr); |
1003 | 0 | if (CSLCount(papszLines) == 16) |
1004 | 0 | { |
1005 | |
|
1006 | 0 | if (psImage->chICORDS == 'N') |
1007 | 0 | isNorth = 1; |
1008 | 0 | else if (psImage->chICORDS == 'S') |
1009 | 0 | isNorth = 0; |
1010 | 0 | else if (psImage->chICORDS == 'G' || psImage->chICORDS == 'D' || |
1011 | 0 | psImage->chICORDS == 'C') |
1012 | 0 | { |
1013 | 0 | if (psImage->dfLLY + psImage->dfLRY + psImage->dfULY + |
1014 | 0 | psImage->dfURY < |
1015 | 0 | 0) |
1016 | 0 | isNorth = 0; |
1017 | 0 | else |
1018 | 0 | isNorth = 1; |
1019 | 0 | } |
1020 | 0 | else if (psImage->chICORDS == 'U') |
1021 | 0 | { |
1022 | 0 | isNorth = psImage->nZone >= 0; |
1023 | 0 | } |
1024 | 0 | else |
1025 | 0 | { |
1026 | | // Arbitrarily suppose we are in northern hemisphere. |
1027 | 0 | isNorth = 1; |
1028 | | |
1029 | | /* unless we have other information to determine the |
1030 | | * hemisphere */ |
1031 | 0 | char **papszUSE00A_MD = NITFReadSTDIDC(psImage); |
1032 | 0 | if (papszUSE00A_MD != nullptr) |
1033 | 0 | { |
1034 | 0 | const char *pszLocation = CSLFetchNameValue( |
1035 | 0 | papszUSE00A_MD, "NITF_STDIDC_LOCATION"); |
1036 | 0 | if (pszLocation && strlen(pszLocation) == 11) |
1037 | 0 | { |
1038 | 0 | isNorth = (pszLocation[4] == 'N'); |
1039 | 0 | } |
1040 | 0 | CSLDestroy(papszUSE00A_MD); |
1041 | 0 | } |
1042 | 0 | else |
1043 | 0 | { |
1044 | 0 | NITFRPC00BInfo sRPCInfo; |
1045 | 0 | if (NITFReadRPC00B(psImage, &sRPCInfo) && |
1046 | 0 | sRPCInfo.SUCCESS) |
1047 | 0 | { |
1048 | 0 | isNorth = (sRPCInfo.LAT_OFF >= 0); |
1049 | 0 | } |
1050 | 0 | } |
1051 | 0 | } |
1052 | |
|
1053 | 0 | if ((STARTS_WITH_CI(papszLines[7], |
1054 | 0 | "Selected Projection: Universal Transverse " |
1055 | 0 | "Mercator")) && |
1056 | 0 | (STARTS_WITH_CI(papszLines[8], "Zone: ")) && |
1057 | 0 | (strlen(papszLines[8]) >= 7)) |
1058 | 0 | { |
1059 | 0 | zone = atoi(&(papszLines[8][6])); |
1060 | 0 | poDS->m_oSRS.Clear(); |
1061 | 0 | poDS->m_oSRS.SetUTM(zone, isNorth); |
1062 | 0 | poDS->m_oSRS.SetWellKnownGeogCS("WGS84"); |
1063 | 0 | } |
1064 | 0 | else |
1065 | 0 | { |
1066 | | /* Couldn't find associated projection info. |
1067 | | Go back to original file for geotransform. |
1068 | | */ |
1069 | 0 | poDS->bGotGeoTransform = FALSE; |
1070 | 0 | } |
1071 | 0 | } |
1072 | 0 | else |
1073 | 0 | poDS->bGotGeoTransform = FALSE; |
1074 | 0 | CSLDestroy(papszLines); |
1075 | 0 | } |
1076 | 0 | else |
1077 | 0 | poDS->bGotGeoTransform = FALSE; |
1078 | 0 | } |
1079 | | |
1080 | | /* -------------------------------------------------------------------- */ |
1081 | | /* Does this look like a CADRG polar tile ? (#2940) */ |
1082 | | /* -------------------------------------------------------------------- */ |
1083 | 692 | const char *pszIID1 = |
1084 | 692 | (psImage) ? CSLFetchNameValue(psImage->papszMetadata, "NITF_IID1") |
1085 | 692 | : nullptr; |
1086 | 692 | const char *pszITITLE = |
1087 | 692 | (psImage) ? CSLFetchNameValue(psImage->papszMetadata, "NITF_ITITLE") |
1088 | 692 | : nullptr; |
1089 | 692 | if (psImage != nullptr && !poDS->bGotGeoTransform && |
1090 | 514 | (psImage->chICORDS == 'G' || psImage->chICORDS == 'D') && |
1091 | 36 | pszIID1 != nullptr && EQUAL(pszIID1, "CADRG") && pszITITLE != nullptr && |
1092 | 18 | strlen(pszITITLE) >= 12 && |
1093 | 18 | (pszITITLE[strlen(pszITITLE) - 1] == '9' || |
1094 | 15 | pszITITLE[strlen(pszITITLE) - 1] == 'J')) |
1095 | 3 | { |
1096 | 3 | OGRSpatialReference oSRS_AEQD, oSRS_WGS84; |
1097 | | |
1098 | 3 | const char *pszPolarProjection = (psImage->dfULY > 0) |
1099 | 3 | ? pszNorthPolarProjection |
1100 | 3 | : pszSouthPolarProjection; |
1101 | | |
1102 | 3 | oSRS_AEQD.importFromWkt(pszPolarProjection); |
1103 | | |
1104 | 3 | oSRS_WGS84.SetWellKnownGeogCS("WGS84"); |
1105 | 3 | oSRS_WGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
1106 | | |
1107 | 3 | CPLPushErrorHandler(CPLQuietErrorHandler); |
1108 | 3 | auto poCT = std::unique_ptr<OGRCoordinateTransformation>( |
1109 | 3 | OGRCreateCoordinateTransformation(&oSRS_WGS84, &oSRS_AEQD)); |
1110 | 3 | CPLPopErrorHandler(); |
1111 | 3 | if (poCT) |
1112 | 3 | { |
1113 | 3 | double dfULX_AEQD = psImage->dfULX; |
1114 | 3 | double dfULY_AEQD = psImage->dfULY; |
1115 | 3 | double dfURX_AEQD = psImage->dfURX; |
1116 | 3 | double dfURY_AEQD = psImage->dfURY; |
1117 | 3 | double dfLLX_AEQD = psImage->dfLLX; |
1118 | 3 | double dfLLY_AEQD = psImage->dfLLY; |
1119 | 3 | double dfLRX_AEQD = psImage->dfLRX; |
1120 | 3 | double dfLRY_AEQD = psImage->dfLRY; |
1121 | 3 | double z = 0; |
1122 | 3 | int bSuccess = TRUE; |
1123 | 3 | bSuccess &= poCT->Transform(1, &dfULX_AEQD, &dfULY_AEQD, &z); |
1124 | 3 | bSuccess &= poCT->Transform(1, &dfURX_AEQD, &dfURY_AEQD, &z); |
1125 | 3 | bSuccess &= poCT->Transform(1, &dfLLX_AEQD, &dfLLY_AEQD, &z); |
1126 | 3 | bSuccess &= poCT->Transform(1, &dfLRX_AEQD, &dfLRY_AEQD, &z); |
1127 | 3 | if (bSuccess) |
1128 | 3 | { |
1129 | | /* Check that the coordinates of the 4 corners in Azimuthal |
1130 | | * Equidistant projection */ |
1131 | | /* are a rectangle */ |
1132 | 3 | if (fabs(dfULX_AEQD - dfLLX_AEQD) < 1e-6 * fabs(dfLLX_AEQD) && |
1133 | 2 | fabs(dfURX_AEQD - dfLRX_AEQD) < 1e-6 * fabs(dfLRX_AEQD) && |
1134 | 2 | fabs(dfULY_AEQD - dfURY_AEQD) < 1e-6 * fabs(dfURY_AEQD) && |
1135 | 2 | fabs(dfLLY_AEQD - dfLRY_AEQD) < 1e-6 * fabs(dfLRY_AEQD)) |
1136 | 2 | { |
1137 | 2 | poDS->m_oSRS = std::move(oSRS_AEQD); |
1138 | | |
1139 | 2 | poDS->bGotGeoTransform = TRUE; |
1140 | 2 | poDS->m_gt.xorig = dfULX_AEQD; |
1141 | 2 | poDS->m_gt.xscale = |
1142 | 2 | (dfURX_AEQD - dfULX_AEQD) / poDS->nRasterXSize; |
1143 | 2 | poDS->m_gt.xrot = 0; |
1144 | 2 | poDS->m_gt.yorig = dfULY_AEQD; |
1145 | 2 | poDS->m_gt.yrot = 0; |
1146 | 2 | poDS->m_gt.yscale = |
1147 | 2 | (dfLLY_AEQD - dfULY_AEQD) / poDS->nRasterYSize; |
1148 | 2 | } |
1149 | 3 | } |
1150 | 3 | } |
1151 | 0 | else |
1152 | 0 | { |
1153 | | // if we cannot instantiate the transformer, then we |
1154 | | // will at least attempt to record what we believe the |
1155 | | // natural coordinate system of the image is. This is |
1156 | | // primarily used by ArcGIS (#3337) |
1157 | |
|
1158 | 0 | CPLErrorReset(); |
1159 | |
|
1160 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
1161 | 0 | "Failed to instantiate coordinate system transformer, " |
1162 | 0 | "likely PROJ.DLL/libproj.so is not available. Returning " |
1163 | 0 | "image corners as lat/long GCPs as a fallback."); |
1164 | |
|
1165 | 0 | char *pszAEQD = nullptr; |
1166 | 0 | oSRS_AEQD.exportToWkt(&(pszAEQD)); |
1167 | 0 | poDS->SetMetadataItem("GCPPROJECTIONX", pszAEQD, "IMAGE_STRUCTURE"); |
1168 | 0 | CPLFree(pszAEQD); |
1169 | 0 | } |
1170 | 3 | } |
1171 | | |
1172 | | /* -------------------------------------------------------------------- */ |
1173 | | /* Do we have RPCs? */ |
1174 | | /* -------------------------------------------------------------------- */ |
1175 | 692 | bool bHasRPC00 = false; |
1176 | 692 | NITFRPC00BInfo sRPCInfo; |
1177 | 692 | memset(&sRPCInfo, 0, |
1178 | 692 | sizeof(sRPCInfo)); /* To avoid warnings from not clever compilers */ |
1179 | | |
1180 | 692 | if (psImage && NITFReadRPC00B(psImage, &sRPCInfo) && sRPCInfo.SUCCESS) |
1181 | 0 | bHasRPC00 = true; |
1182 | | |
1183 | | /* -------------------------------------------------------------------- */ |
1184 | | /* Do we have IGEOLO data that can be treated as a */ |
1185 | | /* geotransform? Our approach should support images in an */ |
1186 | | /* affine rotated frame of reference. */ |
1187 | | /* -------------------------------------------------------------------- */ |
1188 | 692 | int nGCPCount = 0; |
1189 | 692 | GDAL_GCP *psGCPs = nullptr; |
1190 | | |
1191 | 692 | if (psImage && !poDS->bGotGeoTransform && psImage->chICORDS != ' ') |
1192 | 272 | { |
1193 | 272 | nGCPCount = 4; |
1194 | | |
1195 | 272 | psGCPs = reinterpret_cast<GDAL_GCP *>( |
1196 | 272 | CPLMalloc(sizeof(GDAL_GCP) * nGCPCount)); |
1197 | 272 | GDALInitGCPs(nGCPCount, psGCPs); |
1198 | | |
1199 | 272 | if (psImage->bIsBoxCenterOfPixel) |
1200 | 257 | { |
1201 | 257 | psGCPs[0].dfGCPPixel = 0.5; |
1202 | 257 | psGCPs[0].dfGCPLine = 0.5; |
1203 | 257 | psGCPs[1].dfGCPPixel = poDS->nRasterXSize - 0.5; |
1204 | 257 | psGCPs[1].dfGCPLine = 0.5; |
1205 | 257 | psGCPs[2].dfGCPPixel = poDS->nRasterXSize - 0.5; |
1206 | 257 | psGCPs[2].dfGCPLine = poDS->nRasterYSize - 0.5; |
1207 | 257 | psGCPs[3].dfGCPPixel = 0.5; |
1208 | 257 | psGCPs[3].dfGCPLine = poDS->nRasterYSize - 0.5; |
1209 | 257 | } |
1210 | 15 | else |
1211 | 15 | { |
1212 | 15 | psGCPs[0].dfGCPPixel = 0.0; |
1213 | 15 | psGCPs[0].dfGCPLine = 0.0; |
1214 | 15 | psGCPs[1].dfGCPPixel = poDS->nRasterXSize; |
1215 | 15 | psGCPs[1].dfGCPLine = 0.0; |
1216 | 15 | psGCPs[2].dfGCPPixel = poDS->nRasterXSize; |
1217 | 15 | psGCPs[2].dfGCPLine = poDS->nRasterYSize; |
1218 | 15 | psGCPs[3].dfGCPPixel = 0.0; |
1219 | 15 | psGCPs[3].dfGCPLine = poDS->nRasterYSize; |
1220 | 15 | } |
1221 | | |
1222 | 272 | psGCPs[0].dfGCPX = psImage->dfULX; |
1223 | 272 | psGCPs[0].dfGCPY = psImage->dfULY; |
1224 | | |
1225 | 272 | psGCPs[1].dfGCPX = psImage->dfURX; |
1226 | 272 | psGCPs[1].dfGCPY = psImage->dfURY; |
1227 | | |
1228 | 272 | psGCPs[2].dfGCPX = psImage->dfLRX; |
1229 | 272 | psGCPs[2].dfGCPY = psImage->dfLRY; |
1230 | | |
1231 | 272 | psGCPs[3].dfGCPX = psImage->dfLLX; |
1232 | 272 | psGCPs[3].dfGCPY = psImage->dfLLY; |
1233 | | |
1234 | | /* -------------------------------------------------------------------- */ |
1235 | | /* ESRI desires to use the RPCs to produce a denser and more */ |
1236 | | /* accurate set of GCPs in this case. Details are unclear at */ |
1237 | | /* this time. */ |
1238 | | /* -------------------------------------------------------------------- */ |
1239 | | #ifdef ESRI_BUILD |
1240 | | if (bHasRPC00 && |
1241 | | ((psImage->chICORDS == 'G') || (psImage->chICORDS == 'C'))) |
1242 | | { |
1243 | | if (nGCPCount == 4) |
1244 | | NITFDensifyGCPs(&psGCPs, &nGCPCount); |
1245 | | |
1246 | | NITFUpdateGCPsWithRPC(&sRPCInfo, psGCPs, &nGCPCount); |
1247 | | } |
1248 | | #endif /* def ESRI_BUILD */ |
1249 | 272 | } |
1250 | | |
1251 | | /* -------------------------------------------------------------------- */ |
1252 | | /* Convert the GCPs into a geotransform definition, if possible. */ |
1253 | | /* -------------------------------------------------------------------- */ |
1254 | 692 | if (!psImage) |
1255 | 178 | { |
1256 | | /* nothing */ |
1257 | 178 | } |
1258 | 514 | else if (poDS->bGotGeoTransform == FALSE && nGCPCount > 0 && |
1259 | 272 | GDALGCPsToGeoTransform(nGCPCount, psGCPs, poDS->m_gt.data(), |
1260 | 272 | FALSE)) |
1261 | 23 | { |
1262 | 23 | poDS->bGotGeoTransform = TRUE; |
1263 | 23 | } |
1264 | | |
1265 | | /* -------------------------------------------------------------------- */ |
1266 | | /* If we have IGEOLO that isn't north up, return it as GCPs. */ |
1267 | | /* -------------------------------------------------------------------- */ |
1268 | 491 | else if ((psImage->dfULX != 0 || psImage->dfURX != 0 || |
1269 | 476 | psImage->dfLRX != 0 || psImage->dfLLX != 0) && |
1270 | 25 | psImage->chICORDS != ' ' && (poDS->bGotGeoTransform == FALSE) && |
1271 | 23 | nGCPCount >= 4) |
1272 | 23 | { |
1273 | 23 | CPLDebug("GDAL", |
1274 | 23 | "NITFDataset::Open() was not able to derive a first order\n" |
1275 | 23 | "geotransform. It will be returned as GCPs."); |
1276 | | |
1277 | 23 | poDS->nGCPCount = nGCPCount; |
1278 | 23 | poDS->pasGCPList = psGCPs; |
1279 | | |
1280 | 23 | psGCPs = nullptr; |
1281 | 23 | nGCPCount = 0; |
1282 | | |
1283 | 23 | CPLFree(poDS->pasGCPList[0].pszId); |
1284 | 23 | poDS->pasGCPList[0].pszId = CPLStrdup("UpperLeft"); |
1285 | | |
1286 | 23 | CPLFree(poDS->pasGCPList[1].pszId); |
1287 | 23 | poDS->pasGCPList[1].pszId = CPLStrdup("UpperRight"); |
1288 | | |
1289 | 23 | CPLFree(poDS->pasGCPList[2].pszId); |
1290 | 23 | poDS->pasGCPList[2].pszId = CPLStrdup("LowerRight"); |
1291 | | |
1292 | 23 | CPLFree(poDS->pasGCPList[3].pszId); |
1293 | 23 | poDS->pasGCPList[3].pszId = CPLStrdup("LowerLeft"); |
1294 | | |
1295 | 23 | poDS->m_oGCPSRS = poDS->m_oSRS; |
1296 | 23 | } |
1297 | | |
1298 | | // This cleans up the original copy of the GCPs used to test if |
1299 | | // this IGEOLO could be used for a geotransform if we did not |
1300 | | // steal the to use as primary gcps. |
1301 | 692 | if (nGCPCount > 0) |
1302 | 249 | { |
1303 | 249 | GDALDeinitGCPs(nGCPCount, psGCPs); |
1304 | 249 | CPLFree(psGCPs); |
1305 | 249 | } |
1306 | | |
1307 | | /* -------------------------------------------------------------------- */ |
1308 | | /* Do we have PRJPSB and MAPLOB TREs to get better */ |
1309 | | /* georeferencing from? */ |
1310 | | /* -------------------------------------------------------------------- */ |
1311 | 692 | if (psImage) |
1312 | 514 | poDS->CheckGeoSDEInfo(); |
1313 | | |
1314 | | /* -------------------------------------------------------------------- */ |
1315 | | /* Do we have metadata. */ |
1316 | | /* -------------------------------------------------------------------- */ |
1317 | | |
1318 | | // File and Image level metadata. |
1319 | 692 | char **papszMergedMD = CSLDuplicate(poDS->psFile->papszMetadata); |
1320 | | |
1321 | 692 | if (psImage) |
1322 | 514 | { |
1323 | 514 | papszMergedMD = CSLInsertStrings(papszMergedMD, CSLCount(papszMergedMD), |
1324 | 514 | psImage->papszMetadata); |
1325 | | |
1326 | | // Comments. |
1327 | 514 | if (psImage->pszComments != nullptr && |
1328 | 514 | strlen(psImage->pszComments) != 0) |
1329 | 319 | papszMergedMD = CSLSetNameValue( |
1330 | 319 | papszMergedMD, "NITF_IMAGE_COMMENTS", psImage->pszComments); |
1331 | | |
1332 | | // Compression code. |
1333 | 514 | papszMergedMD = |
1334 | 514 | CSLSetNameValue(papszMergedMD, "NITF_IC", psImage->szIC); |
1335 | | |
1336 | | // IMODE |
1337 | 514 | char szIMODE[2]; |
1338 | 514 | szIMODE[0] = psImage->chIMODE; |
1339 | 514 | szIMODE[1] = '\0'; |
1340 | 514 | papszMergedMD = CSLSetNameValue(papszMergedMD, "NITF_IMODE", szIMODE); |
1341 | | |
1342 | | // ILOC/Attachment info |
1343 | 514 | if (psImage->nIDLVL != 0) |
1344 | 479 | { |
1345 | 479 | NITFSegmentInfo *psSegInfo = |
1346 | 479 | psFile->pasSegmentInfo + psImage->iSegment; |
1347 | | |
1348 | 479 | papszMergedMD = |
1349 | 479 | CSLSetNameValue(papszMergedMD, "NITF_IDLVL", |
1350 | 479 | CPLString().Printf("%d", psImage->nIDLVL)); |
1351 | 479 | papszMergedMD = |
1352 | 479 | CSLSetNameValue(papszMergedMD, "NITF_IALVL", |
1353 | 479 | CPLString().Printf("%d", psImage->nIALVL)); |
1354 | 479 | papszMergedMD = |
1355 | 479 | CSLSetNameValue(papszMergedMD, "NITF_ILOC_ROW", |
1356 | 479 | CPLString().Printf("%d", psImage->nILOCRow)); |
1357 | 479 | papszMergedMD = |
1358 | 479 | CSLSetNameValue(papszMergedMD, "NITF_ILOC_COLUMN", |
1359 | 479 | CPLString().Printf("%d", psImage->nILOCColumn)); |
1360 | 479 | papszMergedMD = |
1361 | 479 | CSLSetNameValue(papszMergedMD, "NITF_CCS_ROW", |
1362 | 479 | CPLString().Printf("%d", psSegInfo->nCCS_R)); |
1363 | 479 | papszMergedMD = |
1364 | 479 | CSLSetNameValue(papszMergedMD, "NITF_CCS_COLUMN", |
1365 | 479 | CPLString().Printf("%d", psSegInfo->nCCS_C)); |
1366 | 479 | papszMergedMD = |
1367 | 479 | CSLSetNameValue(papszMergedMD, "NITF_IMAG", psImage->szIMAG); |
1368 | 479 | } |
1369 | | |
1370 | 514 | papszMergedMD = |
1371 | 514 | NITFGenericMetadataRead(papszMergedMD, psFile, psImage, nullptr); |
1372 | | |
1373 | | // BLOCKA |
1374 | 514 | char **papszTRE_MD = NITFReadBLOCKA(psImage); |
1375 | 514 | if (papszTRE_MD != nullptr) |
1376 | 10 | { |
1377 | 10 | papszMergedMD = CSLInsertStrings( |
1378 | 10 | papszMergedMD, CSLCount(papszTRE_MD), papszTRE_MD); |
1379 | 10 | CSLDestroy(papszTRE_MD); |
1380 | 10 | } |
1381 | 514 | } |
1382 | | |
1383 | | #ifdef ESRI_BUILD |
1384 | | // Extract ESRI generic metadata. |
1385 | | char **papszESRI_MD = ExtractEsriMD(papszMergedMD); |
1386 | | if (papszESRI_MD != NULL) |
1387 | | { |
1388 | | papszMergedMD = CSLInsertStrings(papszMergedMD, CSLCount(papszESRI_MD), |
1389 | | papszESRI_MD); |
1390 | | CSLDestroy(papszESRI_MD); |
1391 | | } |
1392 | | #endif |
1393 | | |
1394 | 692 | poDS->SetMetadata(papszMergedMD); |
1395 | 692 | CSLDestroy(papszMergedMD); |
1396 | | |
1397 | | /* -------------------------------------------------------------------- */ |
1398 | | /* Image structure metadata. */ |
1399 | | /* -------------------------------------------------------------------- */ |
1400 | 692 | if (psImage == nullptr) |
1401 | 178 | /* do nothing */; |
1402 | 514 | else if (psImage->szIC[1] == '1') |
1403 | 121 | poDS->SetMetadataItem("COMPRESSION", "BILEVEL", "IMAGE_STRUCTURE"); |
1404 | 393 | else if (psImage->szIC[1] == '2') |
1405 | 7 | poDS->SetMetadataItem("COMPRESSION", "ARIDPCM", "IMAGE_STRUCTURE"); |
1406 | 386 | else if (psImage->szIC[1] == '3') |
1407 | 29 | poDS->SetMetadataItem("COMPRESSION", "JPEG", "IMAGE_STRUCTURE"); |
1408 | 357 | else if (psImage->szIC[1] == '4') |
1409 | 26 | poDS->SetMetadataItem("COMPRESSION", "VECTOR QUANTIZATION", |
1410 | 26 | "IMAGE_STRUCTURE"); |
1411 | 331 | else if (psImage->szIC[1] == '5') |
1412 | 0 | poDS->SetMetadataItem("COMPRESSION", "LOSSLESS JPEG", |
1413 | 0 | "IMAGE_STRUCTURE"); |
1414 | 331 | else if (psImage->szIC[1] == '8') |
1415 | 20 | poDS->SetMetadataItem("COMPRESSION", "JPEG2000", "IMAGE_STRUCTURE"); |
1416 | | |
1417 | | /* -------------------------------------------------------------------- */ |
1418 | | /* Do we have RPC info. */ |
1419 | | /* -------------------------------------------------------------------- */ |
1420 | | |
1421 | | // get _rpc.txt file |
1422 | 692 | const std::string osDirName = CPLGetDirnameSafe(pszFilename); |
1423 | 692 | const std::string osBaseName = CPLGetBasenameSafe(pszFilename); |
1424 | 692 | std::string osRPCTXTFilename = CPLFormFilenameSafe( |
1425 | 692 | osDirName.c_str(), std::string(osBaseName).append("_rpc").c_str(), |
1426 | 692 | "txt"); |
1427 | 692 | if (CPLCheckForFile(osRPCTXTFilename.data(), poOpenInfo->GetSiblingFiles())) |
1428 | 0 | { |
1429 | 0 | poDS->m_osRPCTXTFilename = osRPCTXTFilename; |
1430 | 0 | } |
1431 | 692 | else |
1432 | 692 | { |
1433 | 692 | osRPCTXTFilename = CPLFormFilenameSafe( |
1434 | 692 | osDirName.c_str(), std::string(osBaseName).append("_RPC").c_str(), |
1435 | 692 | "TXT"); |
1436 | 692 | CPL_IGNORE_RET_VAL(osBaseName); |
1437 | 692 | if (CPLCheckForFile(osRPCTXTFilename.data(), |
1438 | 692 | poOpenInfo->GetSiblingFiles())) |
1439 | 0 | { |
1440 | 0 | poDS->m_osRPCTXTFilename = osRPCTXTFilename; |
1441 | 0 | } |
1442 | 692 | } |
1443 | 692 | bool bHasLoadedRPCTXT = false; |
1444 | 692 | if (!poDS->m_osRPCTXTFilename.empty()) |
1445 | 0 | { |
1446 | 0 | char **papszMD = GDALLoadRPCFile(poDS->m_osRPCTXTFilename); |
1447 | 0 | if (papszMD != nullptr) |
1448 | 0 | { |
1449 | 0 | bHasLoadedRPCTXT = true; |
1450 | 0 | poDS->SetMetadata(papszMD, "RPC"); |
1451 | 0 | CSLDestroy(papszMD); |
1452 | 0 | } |
1453 | 0 | else |
1454 | 0 | { |
1455 | 0 | poDS->m_osRPCTXTFilename.clear(); |
1456 | 0 | } |
1457 | 0 | } |
1458 | | |
1459 | 692 | if (psImage && bHasRPC00 && !bHasLoadedRPCTXT) |
1460 | 0 | { |
1461 | 0 | char szValue[1280]; |
1462 | |
|
1463 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.ERR_BIAS); |
1464 | 0 | poDS->SetMetadataItem("ERR_BIAS", szValue, "RPC"); |
1465 | |
|
1466 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.ERR_RAND); |
1467 | 0 | poDS->SetMetadataItem("ERR_RAND", szValue, "RPC"); |
1468 | |
|
1469 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.LINE_OFF); |
1470 | 0 | poDS->SetMetadataItem("LINE_OFF", szValue, "RPC"); |
1471 | |
|
1472 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.LINE_SCALE); |
1473 | 0 | poDS->SetMetadataItem("LINE_SCALE", szValue, "RPC"); |
1474 | |
|
1475 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.SAMP_OFF); |
1476 | 0 | poDS->SetMetadataItem("SAMP_OFF", szValue, "RPC"); |
1477 | |
|
1478 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.SAMP_SCALE); |
1479 | 0 | poDS->SetMetadataItem("SAMP_SCALE", szValue, "RPC"); |
1480 | |
|
1481 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.LONG_OFF); |
1482 | 0 | poDS->SetMetadataItem("LONG_OFF", szValue, "RPC"); |
1483 | |
|
1484 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.LONG_SCALE); |
1485 | 0 | poDS->SetMetadataItem("LONG_SCALE", szValue, "RPC"); |
1486 | |
|
1487 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.LAT_OFF); |
1488 | 0 | poDS->SetMetadataItem("LAT_OFF", szValue, "RPC"); |
1489 | |
|
1490 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.LAT_SCALE); |
1491 | 0 | poDS->SetMetadataItem("LAT_SCALE", szValue, "RPC"); |
1492 | |
|
1493 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.HEIGHT_OFF); |
1494 | 0 | poDS->SetMetadataItem("HEIGHT_OFF", szValue, "RPC"); |
1495 | |
|
1496 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sRPCInfo.HEIGHT_SCALE); |
1497 | 0 | poDS->SetMetadataItem("HEIGHT_SCALE", szValue, "RPC"); |
1498 | |
|
1499 | 0 | szValue[0] = '\0'; |
1500 | 0 | for (int i = 0; i < 20; i++) |
1501 | 0 | CPLsnprintf(szValue + strlen(szValue), |
1502 | 0 | sizeof(szValue) - strlen(szValue), "%.16g ", |
1503 | 0 | sRPCInfo.LINE_NUM_COEFF[i]); |
1504 | 0 | poDS->SetMetadataItem("LINE_NUM_COEFF", szValue, "RPC"); |
1505 | |
|
1506 | 0 | szValue[0] = '\0'; |
1507 | 0 | for (int i = 0; i < 20; i++) |
1508 | 0 | CPLsnprintf(szValue + strlen(szValue), |
1509 | 0 | sizeof(szValue) - strlen(szValue), "%.16g ", |
1510 | 0 | sRPCInfo.LINE_DEN_COEFF[i]); |
1511 | 0 | poDS->SetMetadataItem("LINE_DEN_COEFF", 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.SAMP_NUM_COEFF[i]); |
1518 | 0 | poDS->SetMetadataItem("SAMP_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.SAMP_DEN_COEFF[i]); |
1525 | 0 | poDS->SetMetadataItem("SAMP_DEN_COEFF", szValue, "RPC"); |
1526 | |
|
1527 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", |
1528 | 0 | sRPCInfo.LONG_OFF - sRPCInfo.LONG_SCALE); |
1529 | 0 | poDS->SetMetadataItem("MIN_LONG", szValue, "RPC"); |
1530 | |
|
1531 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", |
1532 | 0 | sRPCInfo.LONG_OFF + sRPCInfo.LONG_SCALE); |
1533 | 0 | poDS->SetMetadataItem("MAX_LONG", szValue, "RPC"); |
1534 | |
|
1535 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", |
1536 | 0 | sRPCInfo.LAT_OFF - sRPCInfo.LAT_SCALE); |
1537 | 0 | poDS->SetMetadataItem("MIN_LAT", szValue, "RPC"); |
1538 | |
|
1539 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", |
1540 | 0 | sRPCInfo.LAT_OFF + sRPCInfo.LAT_SCALE); |
1541 | 0 | poDS->SetMetadataItem("MAX_LAT", szValue, "RPC"); |
1542 | 0 | } |
1543 | | |
1544 | | /* -------------------------------------------------------------------- */ |
1545 | | /* Do we have Chip info? */ |
1546 | | /* -------------------------------------------------------------------- */ |
1547 | 692 | NITFICHIPBInfo sChipInfo; |
1548 | | |
1549 | 692 | if (psImage && NITFReadICHIPB(psImage, &sChipInfo) && |
1550 | 0 | sChipInfo.XFRM_FLAG == 0) |
1551 | 0 | { |
1552 | 0 | char szValue[1280]; |
1553 | |
|
1554 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.SCALE_FACTOR); |
1555 | 0 | poDS->SetMetadataItem("ICHIP_SCALE_FACTOR", szValue); |
1556 | | |
1557 | | // TODO: Why do these two not use CPLsnprintf? |
1558 | 0 | snprintf(szValue, sizeof(szValue), "%d", sChipInfo.ANAMORPH_CORR); |
1559 | 0 | poDS->SetMetadataItem("ICHIP_ANAMORPH_CORR", szValue); |
1560 | |
|
1561 | 0 | snprintf(szValue, sizeof(szValue), "%d", sChipInfo.SCANBLK_NUM); |
1562 | 0 | poDS->SetMetadataItem("ICHIP_SCANBLK_NUM", szValue); |
1563 | |
|
1564 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.OP_ROW_11); |
1565 | 0 | poDS->SetMetadataItem("ICHIP_OP_ROW_11", szValue); |
1566 | |
|
1567 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.OP_COL_11); |
1568 | 0 | poDS->SetMetadataItem("ICHIP_OP_COL_11", szValue); |
1569 | |
|
1570 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.OP_ROW_12); |
1571 | 0 | poDS->SetMetadataItem("ICHIP_OP_ROW_12", szValue); |
1572 | |
|
1573 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.OP_COL_12); |
1574 | 0 | poDS->SetMetadataItem("ICHIP_OP_COL_12", szValue); |
1575 | |
|
1576 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.OP_ROW_21); |
1577 | 0 | poDS->SetMetadataItem("ICHIP_OP_ROW_21", szValue); |
1578 | |
|
1579 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.OP_COL_21); |
1580 | 0 | poDS->SetMetadataItem("ICHIP_OP_COL_21", szValue); |
1581 | |
|
1582 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.OP_ROW_22); |
1583 | 0 | poDS->SetMetadataItem("ICHIP_OP_ROW_22", szValue); |
1584 | |
|
1585 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.OP_COL_22); |
1586 | 0 | poDS->SetMetadataItem("ICHIP_OP_COL_22", szValue); |
1587 | |
|
1588 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.FI_ROW_11); |
1589 | 0 | poDS->SetMetadataItem("ICHIP_FI_ROW_11", szValue); |
1590 | |
|
1591 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.FI_COL_11); |
1592 | 0 | poDS->SetMetadataItem("ICHIP_FI_COL_11", szValue); |
1593 | |
|
1594 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.FI_ROW_12); |
1595 | 0 | poDS->SetMetadataItem("ICHIP_FI_ROW_12", szValue); |
1596 | |
|
1597 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.FI_COL_12); |
1598 | 0 | poDS->SetMetadataItem("ICHIP_FI_COL_12", szValue); |
1599 | |
|
1600 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.FI_ROW_21); |
1601 | 0 | poDS->SetMetadataItem("ICHIP_FI_ROW_21", szValue); |
1602 | |
|
1603 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.FI_COL_21); |
1604 | 0 | poDS->SetMetadataItem("ICHIP_FI_COL_21", szValue); |
1605 | |
|
1606 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.FI_ROW_22); |
1607 | 0 | poDS->SetMetadataItem("ICHIP_FI_ROW_22", szValue); |
1608 | |
|
1609 | 0 | CPLsnprintf(szValue, sizeof(szValue), "%.16g", sChipInfo.FI_COL_22); |
1610 | 0 | poDS->SetMetadataItem("ICHIP_FI_COL_22", szValue); |
1611 | | |
1612 | | // Why not CPLsnprintf? |
1613 | 0 | snprintf(szValue, sizeof(szValue), "%d", sChipInfo.FI_ROW); |
1614 | 0 | poDS->SetMetadataItem("ICHIP_FI_ROW", szValue); |
1615 | |
|
1616 | 0 | snprintf(szValue, sizeof(szValue), "%d", sChipInfo.FI_COL); |
1617 | 0 | poDS->SetMetadataItem("ICHIP_FI_COL", szValue); |
1618 | 0 | } |
1619 | | |
1620 | 692 | const NITFSeries *series = NITFGetSeriesInfo(pszFilename); |
1621 | 692 | if (series) |
1622 | 14 | { |
1623 | 14 | poDS->SetMetadataItem("NITF_SERIES_ABBREVIATION", |
1624 | 14 | (series->abbreviation) ? series->abbreviation |
1625 | 14 | : "Unknown"); |
1626 | 14 | poDS->SetMetadataItem("NITF_SERIES_NAME", |
1627 | 14 | (series->name) ? series->name : "Unknown"); |
1628 | 14 | } |
1629 | | |
1630 | | /* -------------------------------------------------------------------- */ |
1631 | | /* If there are multiple image segments, and no specific one is */ |
1632 | | /* asker for, then setup the subdataset metadata. */ |
1633 | | /* -------------------------------------------------------------------- */ |
1634 | 692 | int nSubDSCount = 0; |
1635 | | |
1636 | 692 | { |
1637 | 692 | char **papszSubdatasets = nullptr; |
1638 | | |
1639 | 106k | for (iSegment = 0; iSegment < psFile->nSegmentCount; iSegment++) |
1640 | 105k | { |
1641 | 105k | if (EQUAL(psFile->pasSegmentInfo[iSegment].szSegmentType, "IM")) |
1642 | 53.2k | { |
1643 | 53.2k | CPLString oName; |
1644 | 53.2k | CPLString oValue; |
1645 | | |
1646 | 53.2k | if (nIMIndex == -1) |
1647 | 53.2k | { |
1648 | 53.2k | oName.Printf("SUBDATASET_%d_NAME", nSubDSCount + 1); |
1649 | 53.2k | oValue.Printf("NITF_IM:%d:%s", nSubDSCount, pszFilename); |
1650 | 53.2k | papszSubdatasets = |
1651 | 53.2k | CSLSetNameValue(papszSubdatasets, oName, oValue); |
1652 | | |
1653 | 53.2k | oName.Printf("SUBDATASET_%d_DESC", nSubDSCount + 1); |
1654 | 53.2k | oValue.Printf("Image %d of %s", nSubDSCount + 1, |
1655 | 53.2k | pszFilename); |
1656 | 53.2k | papszSubdatasets = |
1657 | 53.2k | CSLSetNameValue(papszSubdatasets, oName, oValue); |
1658 | 53.2k | } |
1659 | | |
1660 | 53.2k | nSubDSCount++; |
1661 | 53.2k | } |
1662 | 105k | } |
1663 | | |
1664 | 692 | if (nIMIndex == -1 && nSubDSCount > 1) |
1665 | 384 | { |
1666 | 384 | poDS->GDALMajorObject::SetMetadata(papszSubdatasets, "SUBDATASETS"); |
1667 | 384 | } |
1668 | | |
1669 | 692 | CSLDestroy(papszSubdatasets); |
1670 | 692 | } |
1671 | | |
1672 | | /* -------------------------------------------------------------------- */ |
1673 | | /* Initialize any PAM information. */ |
1674 | | /* -------------------------------------------------------------------- */ |
1675 | 692 | poDS->SetDescription(poOpenInfo->pszFilename); |
1676 | 692 | poDS->SetPhysicalFilename(pszFilename); |
1677 | | |
1678 | 692 | if (nSubDSCount > 1 || nIMIndex != -1) |
1679 | 384 | { |
1680 | 384 | if (nIMIndex == -1) |
1681 | 384 | { |
1682 | 384 | nIMIndex = 0; |
1683 | 384 | } |
1684 | 0 | else if (nIMIndex == 0 && nSubDSCount == 1) |
1685 | 0 | { |
1686 | | // If subdataset 0 is explicitly specified, and there's a single |
1687 | | // subdataset, and that PAM .aux.xml doesn't have a Subdataset node, |
1688 | | // then don't set the subdataset name to get metadata from the |
1689 | | // top PAM node. |
1690 | 0 | const char *pszPAMFilename = poDS->BuildPamFilename(); |
1691 | 0 | VSIStatBufL sStatBuf; |
1692 | 0 | if (pszPAMFilename != nullptr && |
1693 | 0 | VSIStatExL(pszPAMFilename, &sStatBuf, |
1694 | 0 | VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0 && |
1695 | 0 | VSI_ISREG(sStatBuf.st_mode)) |
1696 | 0 | { |
1697 | 0 | CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler); |
1698 | 0 | CPLXMLNode *psTree = CPLParseXMLFile(pszPAMFilename); |
1699 | 0 | if (psTree) |
1700 | 0 | { |
1701 | 0 | if (CPLGetXMLNode(psTree, "=PAMDataset.Subdataset") == |
1702 | 0 | nullptr) |
1703 | 0 | { |
1704 | 0 | nIMIndex = -1; |
1705 | 0 | } |
1706 | 0 | } |
1707 | 0 | CPLDestroyXMLNode(psTree); |
1708 | 0 | } |
1709 | 0 | } |
1710 | | |
1711 | 384 | if (nIMIndex >= 0) |
1712 | 384 | { |
1713 | 384 | poDS->SetSubdatasetName(CPLString().Printf("%d", nIMIndex)); |
1714 | 384 | } |
1715 | 384 | } |
1716 | 308 | else if (/* nIMIndex == -1 && */ nSubDSCount == 1) |
1717 | 130 | { |
1718 | | // GDAL 3.4.0 to 3.5.0 used to save the PAM metadata if a Subdataset |
1719 | | // node, even if there was one single subdataset. |
1720 | | // Detect that situation to automatically read it even if not explicitly |
1721 | | // specifying that single subdataset. |
1722 | 130 | const char *pszPAMFilename = poDS->BuildPamFilename(); |
1723 | 130 | VSIStatBufL sStatBuf; |
1724 | 130 | if (pszPAMFilename != nullptr && |
1725 | 130 | VSIStatExL(pszPAMFilename, &sStatBuf, |
1726 | 130 | VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0 && |
1727 | 73 | VSI_ISREG(sStatBuf.st_mode)) |
1728 | 73 | { |
1729 | 73 | CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler); |
1730 | 73 | CPLXMLNode *psTree = CPLParseXMLFile(pszPAMFilename); |
1731 | 73 | if (psTree) |
1732 | 73 | { |
1733 | 73 | const auto psSubdatasetNode = |
1734 | 73 | CPLGetXMLNode(psTree, "=PAMDataset.Subdataset"); |
1735 | 73 | if (psSubdatasetNode != nullptr && |
1736 | 0 | strcmp(CPLGetXMLValue(psSubdatasetNode, "name", ""), "0") == |
1737 | 0 | 0) |
1738 | 0 | { |
1739 | 0 | poDS->SetSubdatasetName("0"); |
1740 | 0 | poDS->SetPhysicalFilename(pszFilename); |
1741 | 0 | } |
1742 | 73 | CPLDestroyXMLNode(psTree); |
1743 | 73 | } |
1744 | 73 | } |
1745 | 130 | } |
1746 | | |
1747 | 692 | poDS->bInLoadXML = TRUE; |
1748 | 692 | poDS->TryLoadXML(poOpenInfo->GetSiblingFiles()); |
1749 | 692 | poDS->bInLoadXML = FALSE; |
1750 | | |
1751 | | /* -------------------------------------------------------------------- */ |
1752 | | /* Do we have a special overview file? If not, do we have */ |
1753 | | /* RSets that should be treated as an overview file? */ |
1754 | | /* -------------------------------------------------------------------- */ |
1755 | 692 | const char *pszOverviewFile = |
1756 | 692 | poDS->GetMetadataItem("OVERVIEW_FILE", "OVERVIEWS"); |
1757 | | |
1758 | 692 | if (pszOverviewFile == nullptr) |
1759 | 692 | { |
1760 | 692 | if (poDS->CheckForRSets(pszFilename, poOpenInfo->GetSiblingFiles())) |
1761 | 0 | pszOverviewFile = poDS->osRSetVRT; |
1762 | 692 | } |
1763 | | |
1764 | | /* -------------------------------------------------------------------- */ |
1765 | | /* If we have jpeg or jpeg2000 bands we may need to set the */ |
1766 | | /* overview file on their dataset. (#3276) */ |
1767 | | /* -------------------------------------------------------------------- */ |
1768 | 692 | GDALDataset *poSubDS = poDS->poJ2KDataset.get(); |
1769 | 692 | if (poDS->poJPEGDataset) |
1770 | 14 | poSubDS = poDS->poJPEGDataset.get(); |
1771 | | |
1772 | 692 | if (poSubDS && pszOverviewFile != nullptr) |
1773 | 0 | { |
1774 | 0 | poSubDS->SetMetadataItem("OVERVIEW_FILE", pszOverviewFile, "OVERVIEWS"); |
1775 | 0 | } |
1776 | | |
1777 | | /* -------------------------------------------------------------------- */ |
1778 | | /* If we have jpeg, or jpeg2000 bands we may need to clear */ |
1779 | | /* their PAM dirty flag too. */ |
1780 | | /* -------------------------------------------------------------------- */ |
1781 | 692 | if (poDS->poJ2KDataset != nullptr && |
1782 | 0 | (poDS->poJ2KDataset->GetMOFlags() & GMO_PAM_CLASS)) |
1783 | 0 | (cpl::down_cast<GDALPamDataset *>(poDS->poJ2KDataset.get())) |
1784 | 0 | ->SetPamFlags( |
1785 | 0 | (cpl::down_cast<GDALPamDataset *>(poDS->poJ2KDataset.get())) |
1786 | 0 | ->GetPamFlags() & |
1787 | 0 | ~GPF_DIRTY); |
1788 | 692 | if (poDS->poJPEGDataset != nullptr && |
1789 | 14 | (poDS->poJPEGDataset->GetMOFlags() & GMO_PAM_CLASS)) |
1790 | 14 | (cpl::down_cast<GDALPamDataset *>(poDS->poJPEGDataset.get())) |
1791 | 14 | ->SetPamFlags( |
1792 | 14 | (cpl::down_cast<GDALPamDataset *>(poDS->poJPEGDataset.get())) |
1793 | 14 | ->GetPamFlags() & |
1794 | 14 | ~GPF_DIRTY); |
1795 | | |
1796 | | /* -------------------------------------------------------------------- */ |
1797 | | /* Check for overviews. */ |
1798 | | /* -------------------------------------------------------------------- */ |
1799 | 692 | if (!EQUAL(poOpenInfo->pszFilename, pszFilename)) |
1800 | 0 | poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::"); |
1801 | 692 | else |
1802 | 692 | poDS->oOvManager.Initialize(poDS, pszFilename, |
1803 | 692 | poOpenInfo->GetSiblingFiles()); |
1804 | | |
1805 | | /* If there are PAM overviews, don't expose the underlying JPEG dataset */ |
1806 | | /* overviews (in case of monoblock C3) */ |
1807 | 692 | if (poDS->GetRasterCount() > 0 && poDS->GetRasterBand(1) != nullptr) |
1808 | 514 | poDS->bExposeUnderlyingJPEGDatasetOverviews = |
1809 | 514 | (reinterpret_cast<GDALPamRasterBand *>(poDS->GetRasterBand(1))) |
1810 | 514 | ->GDALPamRasterBand::GetOverviewCount() == 0; |
1811 | | |
1812 | 692 | if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VALIDATE", false)) |
1813 | 0 | { |
1814 | 0 | if (!poDS->Validate() && |
1815 | 0 | CPLFetchBool(poOpenInfo->papszOpenOptions, |
1816 | 0 | "FAIL_IF_VALIDATION_ERROR", false)) |
1817 | 0 | { |
1818 | 0 | delete poDS; |
1819 | 0 | poDS = nullptr; |
1820 | 0 | } |
1821 | 0 | } |
1822 | | |
1823 | 692 | return poDS; |
1824 | 692 | } |
1825 | | |
1826 | | /************************************************************************/ |
1827 | | /* Validate() */ |
1828 | | /************************************************************************/ |
1829 | | |
1830 | | bool NITFDataset::Validate() |
1831 | 0 | { |
1832 | 0 | bool bSuccess = InitializeTREMetadata(true); |
1833 | 0 | if (!InitializeNITFDESs(true)) |
1834 | 0 | bSuccess = false; |
1835 | 0 | return bSuccess; |
1836 | 0 | } |
1837 | | |
1838 | | /************************************************************************/ |
1839 | | /* LoadDODDatum() */ |
1840 | | /* */ |
1841 | | /* Try to turn a US military datum name into a datum definition. */ |
1842 | | /************************************************************************/ |
1843 | | |
1844 | | static OGRErr LoadDODDatum(OGRSpatialReference *poSRS, const char *pszDatumName) |
1845 | | |
1846 | 0 | { |
1847 | | /* -------------------------------------------------------------------- */ |
1848 | | /* The most common case... */ |
1849 | | /* -------------------------------------------------------------------- */ |
1850 | 0 | if (STARTS_WITH_CI(pszDatumName, "WGE ")) |
1851 | 0 | { |
1852 | 0 | poSRS->SetWellKnownGeogCS("WGS84"); |
1853 | 0 | return OGRERR_NONE; |
1854 | 0 | } |
1855 | | |
1856 | | #if defined(USE_ONLY_EMBEDDED_RESOURCE_FILES) && !defined(EMBED_RESOURCE_FILES) |
1857 | | return OGRERR_FAILURE; |
1858 | | #else |
1859 | | |
1860 | | /* -------------------------------------------------------------------- */ |
1861 | | /* All the rest we will try and load from gt_datum.csv */ |
1862 | | /* (Geotrans datum file). */ |
1863 | | /* -------------------------------------------------------------------- */ |
1864 | 0 | char szExpanded[6]; |
1865 | 0 | const char *pszGTDatum = nullptr; |
1866 | 0 | CPL_IGNORE_RET_VAL(pszGTDatum); |
1867 | 0 | #ifndef USE_ONLY_EMBEDDED_RESOURCE_FILES |
1868 | 0 | pszGTDatum = CSVFilename("gt_datum.csv"); |
1869 | 0 | #endif |
1870 | 0 | #ifdef EMBED_RESOURCE_FILES |
1871 | 0 | std::string osTmpFilename; |
1872 | | // CSVFilename() returns the same content as pszFilename if it does not |
1873 | | // find the file. |
1874 | 0 | if (!pszGTDatum || strcmp(pszGTDatum, "gt_datum.csv") == 0) |
1875 | 0 | { |
1876 | 0 | osTmpFilename = VSIMemGenerateHiddenFilename("gt_datum.csv"); |
1877 | 0 | const char *pszFileContent = NITFGetGTDatum(); |
1878 | 0 | VSIFCloseL(VSIFileFromMemBuffer( |
1879 | 0 | osTmpFilename.c_str(), |
1880 | 0 | const_cast<GByte *>( |
1881 | 0 | reinterpret_cast<const GByte *>(pszFileContent)), |
1882 | 0 | static_cast<int>(strlen(pszFileContent)), |
1883 | 0 | /* bTakeOwnership = */ false)); |
1884 | 0 | pszGTDatum = osTmpFilename.c_str(); |
1885 | 0 | } |
1886 | 0 | #endif |
1887 | |
|
1888 | 0 | strncpy(szExpanded, pszDatumName, 3); |
1889 | 0 | szExpanded[3] = '\0'; |
1890 | 0 | if (pszDatumName[3] != ' ') |
1891 | 0 | { |
1892 | 0 | size_t nLen; |
1893 | 0 | strcat(szExpanded, "-"); |
1894 | 0 | nLen = strlen(szExpanded); |
1895 | 0 | szExpanded[nLen] = pszDatumName[3]; |
1896 | 0 | szExpanded[nLen + 1] = '\0'; |
1897 | 0 | } |
1898 | |
|
1899 | 0 | CPLString osDName = |
1900 | 0 | CSVGetField(pszGTDatum, "CODE", szExpanded, CC_ApproxString, "NAME"); |
1901 | 0 | if (osDName.empty()) |
1902 | 0 | { |
1903 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1904 | 0 | "Failed to find datum %s/%s in gt_datum.csv.", pszDatumName, |
1905 | 0 | szExpanded); |
1906 | |
|
1907 | 0 | #ifdef EMBED_RESOURCE_FILES |
1908 | 0 | if (!osTmpFilename.empty()) |
1909 | 0 | { |
1910 | 0 | CSVDeaccess(osTmpFilename.c_str()); |
1911 | 0 | VSIUnlink(osTmpFilename.c_str()); |
1912 | 0 | } |
1913 | 0 | #endif |
1914 | 0 | return OGRERR_FAILURE; |
1915 | 0 | } |
1916 | | |
1917 | 0 | CPLString osEllipseCode = CSVGetField(pszGTDatum, "CODE", szExpanded, |
1918 | 0 | CC_ApproxString, "ELLIPSOID"); |
1919 | 0 | double dfDeltaX = CPLAtof( |
1920 | 0 | CSVGetField(pszGTDatum, "CODE", szExpanded, CC_ApproxString, "DELTAX")); |
1921 | 0 | double dfDeltaY = CPLAtof( |
1922 | 0 | CSVGetField(pszGTDatum, "CODE", szExpanded, CC_ApproxString, "DELTAY")); |
1923 | 0 | double dfDeltaZ = CPLAtof( |
1924 | 0 | CSVGetField(pszGTDatum, "CODE", szExpanded, CC_ApproxString, "DELTAZ")); |
1925 | |
|
1926 | 0 | #ifdef EMBED_RESOURCE_FILES |
1927 | 0 | if (!osTmpFilename.empty()) |
1928 | 0 | { |
1929 | 0 | CSVDeaccess(osTmpFilename.c_str()); |
1930 | 0 | VSIUnlink(osTmpFilename.c_str()); |
1931 | 0 | osTmpFilename.clear(); |
1932 | 0 | } |
1933 | 0 | #endif |
1934 | | |
1935 | | /* -------------------------------------------------------------------- */ |
1936 | | /* Lookup the ellipse code. */ |
1937 | | /* -------------------------------------------------------------------- */ |
1938 | 0 | const char *pszGTEllipse = nullptr; |
1939 | 0 | CPL_IGNORE_RET_VAL(pszGTEllipse); |
1940 | 0 | #ifndef USE_ONLY_EMBEDDED_RESOURCE_FILES |
1941 | 0 | pszGTEllipse = CSVFilename("gt_ellips.csv"); |
1942 | 0 | #endif |
1943 | |
|
1944 | 0 | #ifdef EMBED_RESOURCE_FILES |
1945 | | // CSVFilename() returns the same content as pszFilename if it does not |
1946 | | // find the file. |
1947 | 0 | if (!pszGTEllipse || strcmp(pszGTEllipse, "gt_ellips.csv") == 0) |
1948 | 0 | { |
1949 | 0 | osTmpFilename = VSIMemGenerateHiddenFilename("gt_ellips"); |
1950 | 0 | const char *pszFileContent = NITFGetGTEllips(); |
1951 | 0 | VSIFCloseL(VSIFileFromMemBuffer( |
1952 | 0 | osTmpFilename.c_str(), |
1953 | 0 | const_cast<GByte *>( |
1954 | 0 | reinterpret_cast<const GByte *>(pszFileContent)), |
1955 | 0 | static_cast<int>(strlen(pszFileContent)), |
1956 | 0 | /* bTakeOwnership = */ false)); |
1957 | 0 | pszGTEllipse = osTmpFilename.c_str(); |
1958 | 0 | } |
1959 | 0 | #endif |
1960 | |
|
1961 | 0 | CPLString osEName = CSVGetField(pszGTEllipse, "CODE", osEllipseCode, |
1962 | 0 | CC_ApproxString, "NAME"); |
1963 | 0 | osEName = osEName.Trim(); |
1964 | 0 | if (osEName.empty()) |
1965 | 0 | { |
1966 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1967 | 0 | "Failed to find datum %s in gt_ellips.csv.", |
1968 | 0 | osEllipseCode.c_str()); |
1969 | |
|
1970 | 0 | #ifdef EMBED_RESOURCE_FILES |
1971 | 0 | if (!osTmpFilename.empty()) |
1972 | 0 | { |
1973 | 0 | CSVDeaccess(osTmpFilename.c_str()); |
1974 | 0 | VSIUnlink(osTmpFilename.c_str()); |
1975 | 0 | } |
1976 | 0 | #endif |
1977 | 0 | return OGRERR_FAILURE; |
1978 | 0 | } |
1979 | | |
1980 | 0 | double dfA = CPLAtof( |
1981 | 0 | CSVGetField(pszGTEllipse, "CODE", osEllipseCode, CC_ApproxString, "A")); |
1982 | 0 | double dfInvF = CPLAtof(CSVGetField(pszGTEllipse, "CODE", osEllipseCode, |
1983 | 0 | CC_ApproxString, "RF")); |
1984 | | |
1985 | | /* -------------------------------------------------------------------- */ |
1986 | | /* Create geographic coordinate system. */ |
1987 | | /* -------------------------------------------------------------------- */ |
1988 | 0 | poSRS->SetGeogCS(osDName, osDName, osEName, dfA, dfInvF); |
1989 | |
|
1990 | 0 | poSRS->SetTOWGS84(dfDeltaX, dfDeltaY, dfDeltaZ); |
1991 | |
|
1992 | 0 | #ifdef EMBED_RESOURCE_FILES |
1993 | 0 | if (!osTmpFilename.empty()) |
1994 | 0 | { |
1995 | 0 | CSVDeaccess(osTmpFilename.c_str()); |
1996 | 0 | VSIUnlink(osTmpFilename.c_str()); |
1997 | 0 | osTmpFilename.clear(); |
1998 | 0 | } |
1999 | 0 | #endif |
2000 | |
|
2001 | 0 | return OGRERR_NONE; |
2002 | 0 | #endif |
2003 | 0 | } |
2004 | | |
2005 | | /************************************************************************/ |
2006 | | /* CheckGeoSDEInfo() */ |
2007 | | /* */ |
2008 | | /* Check for GeoSDE TREs (GEOPSB/PRJPSB and MAPLOB). If we */ |
2009 | | /* have them, use them to override our coordinate system and */ |
2010 | | /* geotransform info. */ |
2011 | | /************************************************************************/ |
2012 | | |
2013 | | void NITFDataset::CheckGeoSDEInfo() |
2014 | | |
2015 | 514 | { |
2016 | 514 | if (!psImage) |
2017 | 0 | return; |
2018 | | |
2019 | | /* -------------------------------------------------------------------- */ |
2020 | | /* Do we have the required TREs? */ |
2021 | | /* -------------------------------------------------------------------- */ |
2022 | 514 | int nGEOPSBSize, nPRJPSBSize, nMAPLOBSize; |
2023 | | |
2024 | 514 | const char *pszGEOPSB = |
2025 | 514 | NITFFindTRE(psFile->pachTRE, psFile->nTREBytes, "GEOPSB", &nGEOPSBSize); |
2026 | 514 | const char *pszPRJPSB = |
2027 | 514 | NITFFindTRE(psFile->pachTRE, psFile->nTREBytes, "PRJPSB", &nPRJPSBSize); |
2028 | 514 | const char *pszMAPLOB = NITFFindTRE(psImage->pachTRE, psImage->nTREBytes, |
2029 | 514 | "MAPLOB", &nMAPLOBSize); |
2030 | | |
2031 | 514 | if (pszGEOPSB == nullptr || pszPRJPSB == nullptr || pszMAPLOB == nullptr) |
2032 | 514 | return; |
2033 | | |
2034 | | /* -------------------------------------------------------------------- */ |
2035 | | /* Collect projection parameters. */ |
2036 | | /* -------------------------------------------------------------------- */ |
2037 | | |
2038 | 0 | char szParam[16]; |
2039 | 0 | if (nPRJPSBSize < 82 + 1) |
2040 | 0 | { |
2041 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2042 | 0 | "Cannot read PRJPSB TRE. Not enough bytes"); |
2043 | 0 | return; |
2044 | 0 | } |
2045 | 0 | const int nParamCount = atoi(NITFGetField(szParam, pszPRJPSB, 82, 1)); |
2046 | 0 | if (nPRJPSBSize < 83 + 15 * nParamCount + 15 + 15) |
2047 | 0 | { |
2048 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2049 | 0 | "Cannot read PRJPSB TRE. Not enough bytes"); |
2050 | 0 | return; |
2051 | 0 | } |
2052 | | |
2053 | 0 | double adfParam[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; |
2054 | 0 | for (int i = 0; i < nParamCount; i++) |
2055 | 0 | adfParam[i] = |
2056 | 0 | CPLAtof(NITFGetField(szParam, pszPRJPSB, 83 + 15 * i, 15)); |
2057 | |
|
2058 | 0 | const double dfFE = |
2059 | 0 | CPLAtof(NITFGetField(szParam, pszPRJPSB, 83 + 15 * nParamCount, 15)); |
2060 | 0 | const double dfFN = CPLAtof( |
2061 | 0 | NITFGetField(szParam, pszPRJPSB, 83 + 15 * nParamCount + 15, 15)); |
2062 | | |
2063 | | /* -------------------------------------------------------------------- */ |
2064 | | /* Try to handle the projection. */ |
2065 | | /* -------------------------------------------------------------------- */ |
2066 | 0 | OGRSpatialReference oSRS; |
2067 | 0 | oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
2068 | |
|
2069 | 0 | if (STARTS_WITH_CI(pszPRJPSB + 80, "AC")) |
2070 | 0 | oSRS.SetACEA(adfParam[1], adfParam[2], adfParam[3], adfParam[0], dfFE, |
2071 | 0 | dfFN); |
2072 | | |
2073 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "AK")) |
2074 | 0 | oSRS.SetLAEA(adfParam[1], adfParam[0], dfFE, dfFN); |
2075 | | |
2076 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "AL")) |
2077 | 0 | oSRS.SetAE(adfParam[1], adfParam[0], dfFE, dfFN); |
2078 | | |
2079 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "BF")) |
2080 | 0 | oSRS.SetBonne(adfParam[1], adfParam[0], dfFE, dfFN); |
2081 | | |
2082 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "CP")) |
2083 | 0 | oSRS.SetEquirectangular(adfParam[1], adfParam[0], dfFE, dfFN); |
2084 | | |
2085 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "CS")) |
2086 | 0 | oSRS.SetCS(adfParam[1], adfParam[0], dfFE, dfFN); |
2087 | | |
2088 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "EF")) |
2089 | 0 | oSRS.SetEckertIV(adfParam[0], dfFE, dfFN); |
2090 | | |
2091 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "ED")) |
2092 | 0 | oSRS.SetEckertVI(adfParam[0], dfFE, dfFN); |
2093 | | |
2094 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "GN")) |
2095 | 0 | oSRS.SetGnomonic(adfParam[1], adfParam[0], dfFE, dfFN); |
2096 | | |
2097 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "HX")) |
2098 | 0 | oSRS.SetHOM2PNO(adfParam[1], adfParam[3], adfParam[2], adfParam[5], |
2099 | 0 | adfParam[4], adfParam[0], dfFE, dfFN); |
2100 | | |
2101 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "KA")) |
2102 | 0 | oSRS.SetEC(adfParam[1], adfParam[2], adfParam[3], adfParam[0], dfFE, |
2103 | 0 | dfFN); |
2104 | | |
2105 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "LE")) |
2106 | 0 | oSRS.SetLCC(adfParam[1], adfParam[2], adfParam[3], adfParam[0], dfFE, |
2107 | 0 | dfFN); |
2108 | | |
2109 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "LI")) |
2110 | 0 | oSRS.SetCEA(adfParam[1], adfParam[0], dfFE, dfFN); |
2111 | | |
2112 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "MC")) |
2113 | 0 | oSRS.SetMercator(adfParam[2], adfParam[1], 1.0, dfFE, dfFN); |
2114 | | |
2115 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "MH")) |
2116 | 0 | oSRS.SetMC(0.0, adfParam[1], dfFE, dfFN); |
2117 | | |
2118 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "MP")) |
2119 | 0 | oSRS.SetMollweide(adfParam[0], dfFE, dfFN); |
2120 | | |
2121 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "NT")) |
2122 | 0 | oSRS.SetNZMG(adfParam[1], adfParam[0], dfFE, dfFN); |
2123 | | |
2124 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "OD")) |
2125 | 0 | oSRS.SetOrthographic(adfParam[1], adfParam[0], dfFE, dfFN); |
2126 | | |
2127 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "PC")) |
2128 | 0 | oSRS.SetPolyconic(adfParam[1], adfParam[0], dfFE, dfFN); |
2129 | | |
2130 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "PG")) |
2131 | 0 | oSRS.SetPS(adfParam[1], adfParam[0], 1.0, dfFE, dfFN); |
2132 | | |
2133 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "RX")) |
2134 | 0 | oSRS.SetRobinson(adfParam[0], dfFE, dfFN); |
2135 | | |
2136 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "SA")) |
2137 | 0 | oSRS.SetSinusoidal(adfParam[0], dfFE, dfFN); |
2138 | | |
2139 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "TC")) |
2140 | 0 | oSRS.SetTM(adfParam[2], adfParam[0], adfParam[1], dfFE, dfFN); |
2141 | | |
2142 | 0 | else if (STARTS_WITH_CI(pszPRJPSB + 80, "VA")) |
2143 | 0 | oSRS.SetVDG(adfParam[0], dfFE, dfFN); |
2144 | | |
2145 | 0 | else |
2146 | 0 | { |
2147 | 0 | char szName[81]; |
2148 | 0 | oSRS.SetLocalCS(NITFGetField(szName, pszPRJPSB, 0, 80)); |
2149 | 0 | } |
2150 | | |
2151 | | /* -------------------------------------------------------------------- */ |
2152 | | /* Try to apply the datum. */ |
2153 | | /* -------------------------------------------------------------------- */ |
2154 | 0 | if (nGEOPSBSize < 86 + 4) |
2155 | 0 | { |
2156 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2157 | 0 | "Cannot read GEOPSB TRE. Not enough bytes"); |
2158 | 0 | return; |
2159 | 0 | } |
2160 | 0 | LoadDODDatum(&oSRS, NITFGetField(szParam, pszGEOPSB, 86, 4)); |
2161 | | |
2162 | | /* -------------------------------------------------------------------- */ |
2163 | | /* Get the geotransform */ |
2164 | | /* -------------------------------------------------------------------- */ |
2165 | 0 | if (nMAPLOBSize < 28 + 15) |
2166 | 0 | { |
2167 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2168 | 0 | "Cannot read MAPLOB TRE. Not enough bytes"); |
2169 | 0 | return; |
2170 | 0 | } |
2171 | | |
2172 | 0 | double dfMeterPerUnit = 1.0; |
2173 | 0 | if (STARTS_WITH_CI(pszMAPLOB + 0, "DM ")) |
2174 | 0 | dfMeterPerUnit = 0.1; |
2175 | 0 | else if (STARTS_WITH_CI(pszMAPLOB + 0, "CM ")) |
2176 | 0 | dfMeterPerUnit = 0.01; |
2177 | 0 | else if (STARTS_WITH_CI(pszMAPLOB + 0, "MM ")) |
2178 | 0 | dfMeterPerUnit = 0.001; |
2179 | 0 | else if (STARTS_WITH_CI(pszMAPLOB + 0, "UM ")) |
2180 | 0 | dfMeterPerUnit = 0.000001; |
2181 | 0 | else if (STARTS_WITH_CI(pszMAPLOB + 0, "KM ")) |
2182 | 0 | dfMeterPerUnit = 1000.0; |
2183 | 0 | else if (STARTS_WITH_CI(pszMAPLOB + 0, "M ")) |
2184 | 0 | dfMeterPerUnit = 1.0; |
2185 | 0 | else |
2186 | 0 | { |
2187 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
2188 | 0 | "MAPLOB Unit=%3.3s not recognized, geolocation may be wrong.", |
2189 | 0 | pszMAPLOB + 0); |
2190 | 0 | } |
2191 | |
|
2192 | 0 | m_gt.xorig = CPLAtof(NITFGetField(szParam, pszMAPLOB, 13, 15)); |
2193 | 0 | m_gt.xscale = |
2194 | 0 | CPLAtof(NITFGetField(szParam, pszMAPLOB, 3, 5)) * dfMeterPerUnit; |
2195 | 0 | m_gt.xrot = 0.0; |
2196 | 0 | m_gt.yorig = CPLAtof(NITFGetField(szParam, pszMAPLOB, 28, 15)); |
2197 | 0 | m_gt.yrot = 0.0; |
2198 | 0 | m_gt.yscale = |
2199 | 0 | -CPLAtof(NITFGetField(szParam, pszMAPLOB, 8, 5)) * dfMeterPerUnit; |
2200 | |
|
2201 | 0 | m_oSRS = std::move(oSRS); |
2202 | |
|
2203 | 0 | bGotGeoTransform = TRUE; |
2204 | 0 | } |
2205 | | |
2206 | | /************************************************************************/ |
2207 | | /* AdviseRead() */ |
2208 | | /************************************************************************/ |
2209 | | |
2210 | | CPLErr NITFDataset::AdviseRead(int nXOff, int nYOff, int nXSize, int nYSize, |
2211 | | int nBufXSize, int nBufYSize, GDALDataType eDT, |
2212 | | int nBandCount, int *panBandList, |
2213 | | CSLConstList papszOptions) |
2214 | | |
2215 | 8 | { |
2216 | | //go through GDALDataset::AdviseRead for the complex SAR |
2217 | 8 | if (poJ2KDataset == nullptr || m_bHasComplexRasterBand) |
2218 | 8 | return GDALDataset::AdviseRead(nXOff, nYOff, nXSize, nYSize, nBufXSize, |
2219 | 8 | nBufYSize, eDT, nBandCount, panBandList, |
2220 | 8 | papszOptions); |
2221 | 0 | else if (poJPEGDataset != nullptr) |
2222 | 0 | return poJPEGDataset->AdviseRead(nXOff, nYOff, nXSize, nYSize, |
2223 | 0 | nBufXSize, nBufYSize, eDT, nBandCount, |
2224 | 0 | panBandList, papszOptions); |
2225 | 0 | else |
2226 | 0 | return poJ2KDataset->AdviseRead(nXOff, nYOff, nXSize, nYSize, nBufXSize, |
2227 | 0 | nBufYSize, eDT, nBandCount, panBandList, |
2228 | 0 | papszOptions); |
2229 | 8 | } |
2230 | | |
2231 | | /************************************************************************/ |
2232 | | /* IRasterIO() */ |
2233 | | /************************************************************************/ |
2234 | | |
2235 | | CPLErr NITFDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, |
2236 | | int nXSize, int nYSize, void *pData, |
2237 | | int nBufXSize, int nBufYSize, |
2238 | | GDALDataType eBufType, int nBandCount, |
2239 | | BANDMAP_TYPE panBandMap, GSpacing nPixelSpace, |
2240 | | GSpacing nLineSpace, GSpacing nBandSpace, |
2241 | | GDALRasterIOExtraArg *psExtraArg) |
2242 | | |
2243 | 0 | { |
2244 | | //go through GDALDataset::IRasterIO for the complex SAR |
2245 | 0 | if (poJ2KDataset != nullptr && !m_bHasComplexRasterBand) |
2246 | 0 | return poJ2KDataset->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, |
2247 | 0 | pData, nBufXSize, nBufYSize, eBufType, |
2248 | 0 | nBandCount, panBandMap, nPixelSpace, |
2249 | 0 | nLineSpace, nBandSpace, psExtraArg); |
2250 | 0 | else if (poJPEGDataset != nullptr && !m_bHasComplexRasterBand) |
2251 | 0 | return poJPEGDataset->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, |
2252 | 0 | pData, nBufXSize, nBufYSize, eBufType, |
2253 | 0 | nBandCount, panBandMap, nPixelSpace, |
2254 | 0 | nLineSpace, nBandSpace, psExtraArg); |
2255 | 0 | else |
2256 | 0 | return GDALDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, |
2257 | 0 | pData, nBufXSize, nBufYSize, eBufType, |
2258 | 0 | nBandCount, panBandMap, nPixelSpace, |
2259 | 0 | nLineSpace, nBandSpace, psExtraArg); |
2260 | 0 | } |
2261 | | |
2262 | | /************************************************************************/ |
2263 | | /* GetGeoTransform() */ |
2264 | | /************************************************************************/ |
2265 | | |
2266 | | CPLErr NITFDataset::GetGeoTransform(GDALGeoTransform >) const |
2267 | | |
2268 | 523 | { |
2269 | 523 | gt = m_gt; |
2270 | | |
2271 | 523 | if (bGotGeoTransform) |
2272 | 32 | return CE_None; |
2273 | | |
2274 | 491 | return GDALPamDataset::GetGeoTransform(gt); |
2275 | 523 | } |
2276 | | |
2277 | | /************************************************************************/ |
2278 | | /* SetGeoTransform() */ |
2279 | | /************************************************************************/ |
2280 | | |
2281 | | CPLErr NITFDataset::SetGeoTransform(const GDALGeoTransform >) |
2282 | | |
2283 | 7 | { |
2284 | 7 | bGotGeoTransform = TRUE; |
2285 | 7 | m_gt = gt; |
2286 | | |
2287 | 7 | double dfIGEOLOULX = m_gt.xorig + 0.5 * m_gt.xscale + 0.5 * m_gt.xrot; |
2288 | 7 | double dfIGEOLOULY = m_gt.yorig + 0.5 * m_gt.yrot + 0.5 * m_gt.yscale; |
2289 | 7 | double dfIGEOLOURX = dfIGEOLOULX + m_gt.xscale * (nRasterXSize - 1); |
2290 | 7 | double dfIGEOLOURY = dfIGEOLOULY + m_gt.yrot * (nRasterXSize - 1); |
2291 | 7 | double dfIGEOLOLRX = dfIGEOLOULX + m_gt.xscale * (nRasterXSize - 1) + |
2292 | 7 | m_gt.xrot * (nRasterYSize - 1); |
2293 | 7 | double dfIGEOLOLRY = dfIGEOLOULY + m_gt.yrot * (nRasterXSize - 1) + |
2294 | 7 | m_gt.yscale * (nRasterYSize - 1); |
2295 | 7 | double dfIGEOLOLLX = dfIGEOLOULX + m_gt.xrot * (nRasterYSize - 1); |
2296 | 7 | double dfIGEOLOLLY = dfIGEOLOULY + m_gt.yscale * (nRasterYSize - 1); |
2297 | | |
2298 | 7 | if (psImage != nullptr && |
2299 | 7 | NITFWriteIGEOLO(psImage, psImage->chICORDS, psImage->nZone, dfIGEOLOULX, |
2300 | 7 | dfIGEOLOULY, dfIGEOLOURX, dfIGEOLOURY, dfIGEOLOLRX, |
2301 | 7 | dfIGEOLOLRY, dfIGEOLOLLX, dfIGEOLOLLY)) |
2302 | 3 | return CE_None; |
2303 | | |
2304 | 4 | return GDALPamDataset::SetGeoTransform(gt); |
2305 | 7 | } |
2306 | | |
2307 | | /************************************************************************/ |
2308 | | /* SetGCPs() */ |
2309 | | /************************************************************************/ |
2310 | | |
2311 | | CPLErr NITFDataset::SetGCPs(int nGCPCountIn, const GDAL_GCP *pasGCPListIn, |
2312 | | const OGRSpatialReference *poGCPSRSIn) |
2313 | 1 | { |
2314 | 1 | if (nGCPCountIn != 4) |
2315 | 1 | { |
2316 | 1 | CPLError(CE_Failure, CPLE_NotSupported, |
2317 | 1 | "NITF only supports writing 4 GCPs."); |
2318 | 1 | return CE_Failure; |
2319 | 1 | } |
2320 | | |
2321 | | /* Free previous GCPs */ |
2322 | 0 | GDALDeinitGCPs(nGCPCount, pasGCPList); |
2323 | 0 | CPLFree(pasGCPList); |
2324 | | |
2325 | | /* Duplicate in GCPs */ |
2326 | 0 | nGCPCount = nGCPCountIn; |
2327 | 0 | pasGCPList = GDALDuplicateGCPs(nGCPCount, pasGCPListIn); |
2328 | |
|
2329 | 0 | m_oGCPSRS.Clear(); |
2330 | 0 | if (poGCPSRSIn) |
2331 | 0 | m_oGCPSRS = *poGCPSRSIn; |
2332 | |
|
2333 | 0 | int iUL = -1; |
2334 | 0 | int iUR = -1; |
2335 | 0 | int iLR = -1; |
2336 | 0 | int iLL = -1; |
2337 | |
|
2338 | 0 | #define EPS_GCP 1e-5 |
2339 | 0 | for (int i = 0; i < 4; i++) |
2340 | 0 | { |
2341 | 0 | if (fabs(pasGCPList[i].dfGCPPixel - 0.5) < EPS_GCP && |
2342 | 0 | fabs(pasGCPList[i].dfGCPLine - 0.5) < EPS_GCP) |
2343 | 0 | iUL = i; |
2344 | | |
2345 | 0 | else if (fabs(pasGCPList[i].dfGCPPixel - (nRasterXSize - 0.5)) < |
2346 | 0 | EPS_GCP && |
2347 | 0 | fabs(pasGCPList[i].dfGCPLine - 0.5) < EPS_GCP) |
2348 | 0 | iUR = i; |
2349 | | |
2350 | 0 | else if (fabs(pasGCPList[i].dfGCPPixel - (nRasterXSize - 0.5)) < |
2351 | 0 | EPS_GCP && |
2352 | 0 | fabs(pasGCPList[i].dfGCPLine - (nRasterYSize - 0.5)) < EPS_GCP) |
2353 | 0 | iLR = i; |
2354 | | |
2355 | 0 | else if (fabs(pasGCPList[i].dfGCPPixel - 0.5) < EPS_GCP && |
2356 | 0 | fabs(pasGCPList[i].dfGCPLine - (nRasterYSize - 0.5)) < EPS_GCP) |
2357 | 0 | iLL = i; |
2358 | 0 | } |
2359 | |
|
2360 | 0 | if (iUL < 0 || iUR < 0 || iLR < 0 || iLL < 0) |
2361 | 0 | { |
2362 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
2363 | 0 | "The 4 GCPs image coordinates must be exactly " |
2364 | 0 | "at the *center* of the 4 corners of the image " |
2365 | 0 | "( (%.1f, %.1f), (%.1f %.1f), (%.1f %.1f), (%.1f %.1f) ).", |
2366 | 0 | 0.5, 0.5, nRasterYSize - 0.5, 0.5, nRasterXSize - 0.5, |
2367 | 0 | nRasterYSize - 0.5, nRasterXSize - 0.5, 0.5); |
2368 | 0 | return CE_Failure; |
2369 | 0 | } |
2370 | | |
2371 | 0 | double dfIGEOLOULX = pasGCPList[iUL].dfGCPX; |
2372 | 0 | double dfIGEOLOULY = pasGCPList[iUL].dfGCPY; |
2373 | 0 | double dfIGEOLOURX = pasGCPList[iUR].dfGCPX; |
2374 | 0 | double dfIGEOLOURY = pasGCPList[iUR].dfGCPY; |
2375 | 0 | double dfIGEOLOLRX = pasGCPList[iLR].dfGCPX; |
2376 | 0 | double dfIGEOLOLRY = pasGCPList[iLR].dfGCPY; |
2377 | 0 | double dfIGEOLOLLX = pasGCPList[iLL].dfGCPX; |
2378 | 0 | double dfIGEOLOLLY = pasGCPList[iLL].dfGCPY; |
2379 | | |
2380 | | /* To recompute the zone */ |
2381 | 0 | OGRSpatialReference oSRSBackup = m_oSRS; |
2382 | 0 | CPLErr eErr = SetSpatialRef(&m_oGCPSRS); |
2383 | 0 | m_oSRS = std::move(oSRSBackup); |
2384 | |
|
2385 | 0 | if (eErr != CE_None) |
2386 | 0 | return eErr; |
2387 | | |
2388 | 0 | if (NITFWriteIGEOLO(psImage, psImage->chICORDS, psImage->nZone, dfIGEOLOULX, |
2389 | 0 | dfIGEOLOULY, dfIGEOLOURX, dfIGEOLOURY, dfIGEOLOLRX, |
2390 | 0 | dfIGEOLOLRY, dfIGEOLOLLX, dfIGEOLOLLY)) |
2391 | 0 | return CE_None; |
2392 | | |
2393 | 0 | return CE_Failure; |
2394 | 0 | } |
2395 | | |
2396 | | /************************************************************************/ |
2397 | | /* GetSpatialRef() */ |
2398 | | /************************************************************************/ |
2399 | | |
2400 | | const OGRSpatialReference *NITFDataset::GetSpatialRef() const |
2401 | | |
2402 | 528 | { |
2403 | 528 | if (bGotGeoTransform) |
2404 | 32 | return &m_oSRS; |
2405 | | |
2406 | 496 | return GDALPamDataset::GetSpatialRef(); |
2407 | 528 | } |
2408 | | |
2409 | | /************************************************************************/ |
2410 | | /* SetSpatialRef() */ |
2411 | | /************************************************************************/ |
2412 | | |
2413 | | CPLErr NITFDataset::SetSpatialRef(const OGRSpatialReference *poSRS) |
2414 | | |
2415 | 5 | { |
2416 | 5 | int bNorth; |
2417 | 5 | OGRSpatialReference oSRS, oSRS_WGS84; |
2418 | | |
2419 | 5 | if (poSRS == nullptr) |
2420 | 0 | return CE_Failure; |
2421 | | |
2422 | 5 | oSRS_WGS84.SetWellKnownGeogCS("WGS84"); |
2423 | 5 | if (poSRS->IsSameGeogCS(&oSRS_WGS84) == FALSE) |
2424 | 4 | { |
2425 | 4 | CPLError(CE_Failure, CPLE_NotSupported, |
2426 | 4 | "NITF only supports WGS84 geographic and UTM projections."); |
2427 | 4 | return CE_Failure; |
2428 | 4 | } |
2429 | | |
2430 | 1 | if (poSRS->IsGeographic() && poSRS->GetPrimeMeridian() == 0.0) |
2431 | 0 | { |
2432 | 0 | if (psImage->chICORDS != 'G' && psImage->chICORDS != 'D') |
2433 | 0 | { |
2434 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
2435 | 0 | "NITF file should have been created with creation option " |
2436 | 0 | "'ICORDS=G' (or 'ICORDS=D')."); |
2437 | 0 | return CE_Failure; |
2438 | 0 | } |
2439 | 0 | } |
2440 | 1 | else if (poSRS->GetUTMZone(&bNorth) > 0) |
2441 | 0 | { |
2442 | 0 | if (bNorth && psImage->chICORDS != 'N') |
2443 | 0 | { |
2444 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
2445 | 0 | "NITF file should have been created with creation option " |
2446 | 0 | "'ICORDS=N'."); |
2447 | 0 | return CE_Failure; |
2448 | 0 | } |
2449 | 0 | else if (!bNorth && psImage->chICORDS != 'S') |
2450 | 0 | { |
2451 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
2452 | 0 | "NITF file should have been created with creation option " |
2453 | 0 | "'ICORDS=S'."); |
2454 | 0 | return CE_Failure; |
2455 | 0 | } |
2456 | | |
2457 | 0 | psImage->nZone = poSRS->GetUTMZone(nullptr); |
2458 | 0 | } |
2459 | 1 | else |
2460 | 1 | { |
2461 | 1 | CPLError(CE_Failure, CPLE_NotSupported, |
2462 | 1 | "NITF only supports WGS84 geographic and UTM projections."); |
2463 | 1 | return CE_Failure; |
2464 | 1 | } |
2465 | | |
2466 | 0 | m_oSRS = *poSRS; |
2467 | |
|
2468 | 0 | if (bGotGeoTransform) |
2469 | 0 | SetGeoTransform(m_gt); |
2470 | |
|
2471 | 0 | return CE_None; |
2472 | 1 | } |
2473 | | |
2474 | | #ifdef ESRI_BUILD |
2475 | | /************************************************************************/ |
2476 | | /* InitializeNITFDESMetadata() */ |
2477 | | /************************************************************************/ |
2478 | | |
2479 | | void NITFDataset::InitializeNITFDESMetadata() |
2480 | | { |
2481 | | static const char *const pszDESMetadataDomain = "NITF_DES_METADATA"; |
2482 | | static const char *const pszDESsDomain = "xml:DES"; |
2483 | | static const char *const pszMDXmlDataContentDESDATA = |
2484 | | "NITF_DES_XML_DATA_CONTENT_DESDATA"; |
2485 | | static const char *const pszXmlDataContent = "XML_DATA_CONTENT"; |
2486 | | constexpr int idxXmlDataContentDESDATA = 973; |
2487 | | static const int sizeXmlDataContent = |
2488 | | static_cast<int>(strlen(pszXmlDataContent)); |
2489 | | |
2490 | | char **ppszDESMetadataList = oSpecialMD.GetMetadata(pszDESMetadataDomain); |
2491 | | |
2492 | | if (ppszDESMetadataList != NULL) |
2493 | | return; |
2494 | | |
2495 | | char **ppszDESsList = this->GetMetadata(pszDESsDomain); |
2496 | | |
2497 | | if (ppszDESsList == NULL) |
2498 | | return; |
2499 | | |
2500 | | bool foundXmlDataContent = false; |
2501 | | char *pachNITFDES = NULL; |
2502 | | |
2503 | | // Set metadata "NITF_DES_XML_DATA_CONTENT_DESDATA". |
2504 | | // NOTE: There should only be one instance of XML_DATA_CONTENT DES. |
2505 | | |
2506 | | while (((pachNITFDES = *ppszDESsList) != NULL) && (!foundXmlDataContent)) |
2507 | | { |
2508 | | // The data stream has been Base64 encoded, need to decode it. |
2509 | | // NOTE: The actual length of the DES data stream is appended at the |
2510 | | // beginning of the encoded |
2511 | | // data and is separated by a space. |
2512 | | |
2513 | | const char *pszSpace = strchr(pachNITFDES, ' '); |
2514 | | |
2515 | | char *pszData = NULL; |
2516 | | int nDataLen = 0; |
2517 | | if (pszSpace) |
2518 | | { |
2519 | | pszData = CPLStrdup(pszSpace + 1); |
2520 | | nDataLen = |
2521 | | CPLBase64DecodeInPlace(reinterpret_cast<GByte *>(pszData)); |
2522 | | pszData[nDataLen] = 0; |
2523 | | } |
2524 | | |
2525 | | if (nDataLen > 2 + sizeXmlDataContent && STARTS_WITH_CI(pszData, "DE")) |
2526 | | { |
2527 | | // Check to see if this is a XML_DATA_CONTENT DES. |
2528 | | if (EQUALN(pszData + 2, pszXmlDataContent, sizeXmlDataContent) && |
2529 | | nDataLen > idxXmlDataContentDESDATA) |
2530 | | { |
2531 | | foundXmlDataContent = true; |
2532 | | |
2533 | | // Get the value of the DESDATA field and set metadata |
2534 | | // "NITF_DES_XML_DATA_CONTENT_DESDATA". |
2535 | | const char *pszXML = pszData + idxXmlDataContentDESDATA; |
2536 | | |
2537 | | // Set the metadata. |
2538 | | oSpecialMD.SetMetadataItem(pszMDXmlDataContentDESDATA, pszXML, |
2539 | | pszDESMetadataDomain); |
2540 | | } |
2541 | | } |
2542 | | |
2543 | | CPLFree(pszData); |
2544 | | |
2545 | | pachNITFDES = NULL; |
2546 | | ppszDESsList += 1; |
2547 | | } |
2548 | | } |
2549 | | |
2550 | | /************************************************************************/ |
2551 | | /* InitializeNITFTREs() */ |
2552 | | /************************************************************************/ |
2553 | | |
2554 | | void NITFDataset::InitializeNITFTREs() |
2555 | | { |
2556 | | static const char *const pszFileHeaderTREsDomain = "NITF_FILE_HEADER_TRES"; |
2557 | | static const char *const pszImageSegmentTREsDomain = |
2558 | | "NITF_IMAGE_SEGMENT_TRES"; |
2559 | | |
2560 | | char **ppszFileHeaderTREsList = |
2561 | | oSpecialMD.GetMetadata(pszFileHeaderTREsDomain); |
2562 | | char **ppszImageSegmentTREsList = |
2563 | | oSpecialMD.GetMetadata(pszImageSegmentTREsDomain); |
2564 | | |
2565 | | if ((ppszFileHeaderTREsList != NULL) && (ppszImageSegmentTREsList != NULL)) |
2566 | | return; |
2567 | | |
2568 | | /* -------------------------------------------------------------------- */ |
2569 | | /* Loop over TRE sources (file and image). */ |
2570 | | /* -------------------------------------------------------------------- */ |
2571 | | |
2572 | | for (int nTRESrc = 0; nTRESrc < 2; nTRESrc++) |
2573 | | { |
2574 | | int nTREBytes = 0; |
2575 | | char *pszTREData = NULL; |
2576 | | const char *pszTREsDomain = NULL; |
2577 | | CPLStringList aosList; |
2578 | | |
2579 | | /* -------------------------------------------------------------------- |
2580 | | */ |
2581 | | /* Extract file header or image segment TREs. */ |
2582 | | /* -------------------------------------------------------------------- |
2583 | | */ |
2584 | | |
2585 | | if (nTRESrc == 0) |
2586 | | { |
2587 | | if (ppszFileHeaderTREsList != NULL) |
2588 | | continue; |
2589 | | |
2590 | | nTREBytes = psFile->nTREBytes; |
2591 | | pszTREData = psFile->pachTRE; |
2592 | | pszTREsDomain = pszFileHeaderTREsDomain; |
2593 | | } |
2594 | | else |
2595 | | { |
2596 | | if (ppszImageSegmentTREsList != NULL) |
2597 | | continue; |
2598 | | |
2599 | | if (psImage) |
2600 | | { |
2601 | | nTREBytes = psImage->nTREBytes; |
2602 | | pszTREData = psImage->pachTRE; |
2603 | | pszTREsDomain = pszImageSegmentTREsDomain; |
2604 | | } |
2605 | | else |
2606 | | { |
2607 | | nTREBytes = 0; |
2608 | | pszTREData = NULL; |
2609 | | } |
2610 | | } |
2611 | | |
2612 | | /* -------------------------------------------------------------------- |
2613 | | */ |
2614 | | /* Loop over TREs. */ |
2615 | | /* -------------------------------------------------------------------- |
2616 | | */ |
2617 | | |
2618 | | while (nTREBytes >= 11) |
2619 | | { |
2620 | | char szTemp[100]; |
2621 | | char szTag[7]; |
2622 | | char *pszEscapedData = NULL; |
2623 | | int nThisTRESize = atoi(NITFGetField(szTemp, pszTREData, 6, 5)); |
2624 | | |
2625 | | if (nThisTRESize < 0) |
2626 | | { |
2627 | | NITFGetField(szTemp, pszTREData, 0, 6); |
2628 | | CPLError(CE_Failure, CPLE_AppDefined, |
2629 | | "Invalid size (%d) for TRE %s", nThisTRESize, szTemp); |
2630 | | return; |
2631 | | } |
2632 | | |
2633 | | if (nThisTRESize > nTREBytes - 11) |
2634 | | { |
2635 | | CPLError(CE_Failure, CPLE_AppDefined, |
2636 | | "Not enough bytes in TRE"); |
2637 | | return; |
2638 | | } |
2639 | | |
2640 | | strncpy(szTag, pszTREData, 6); |
2641 | | szTag[6] = '\0'; |
2642 | | |
2643 | | // trim white off tag. |
2644 | | while (strlen(szTag) > 0 && szTag[strlen(szTag) - 1] == ' ') |
2645 | | szTag[strlen(szTag) - 1] = '\0'; |
2646 | | |
2647 | | // escape data. |
2648 | | pszEscapedData = CPLEscapeString(pszTREData + 6, nThisTRESize + 5, |
2649 | | CPLES_BackslashQuotable); |
2650 | | |
2651 | | const size_t nLineLen = strlen(szTag) + strlen(pszEscapedData) + 2; |
2652 | | char *pszLine = static_cast<char *>(CPLMalloc(nLineLen)); |
2653 | | snprintf(pszLine, nLineLen, "%s=%s", szTag, pszEscapedData); |
2654 | | aosList.AddString(pszLine); |
2655 | | CPLFree(pszLine); |
2656 | | pszLine = NULL; |
2657 | | |
2658 | | CPLFree(pszEscapedData); |
2659 | | pszEscapedData = NULL; |
2660 | | |
2661 | | nTREBytes -= (nThisTRESize + 11); |
2662 | | pszTREData += (nThisTRESize + 11); |
2663 | | } |
2664 | | |
2665 | | if (!aosList.empty()) |
2666 | | oSpecialMD.SetMetadata(aosList.List(), pszTREsDomain); |
2667 | | } |
2668 | | } |
2669 | | #endif |
2670 | | |
2671 | | /************************************************************************/ |
2672 | | /* InitializeNITFDESs() */ |
2673 | | /************************************************************************/ |
2674 | | |
2675 | | bool NITFDataset::InitializeNITFDESs(bool bValidate) |
2676 | 0 | { |
2677 | 0 | char **papszDESsList = oSpecialMD.GetMetadata("xml:DES"); |
2678 | |
|
2679 | 0 | if (papszDESsList != nullptr) |
2680 | 0 | { |
2681 | 0 | return true; |
2682 | 0 | } |
2683 | | |
2684 | 0 | bool bSuccess = true; |
2685 | 0 | CPLXMLNode *psDesListNode = |
2686 | 0 | CPLCreateXMLNode(nullptr, CXT_Element, "des_list"); |
2687 | |
|
2688 | 0 | for (int iSegment = 0; iSegment < psFile->nSegmentCount; iSegment++) |
2689 | 0 | { |
2690 | 0 | NITFSegmentInfo *psSegInfo = psFile->pasSegmentInfo + iSegment; |
2691 | |
|
2692 | 0 | if (EQUAL(psSegInfo->szSegmentType, "DE")) |
2693 | 0 | { |
2694 | 0 | bool bGotError = false; |
2695 | 0 | CPLXMLNode *psDesNode = |
2696 | 0 | NITFDESGetXml(psFile, iSegment, bValidate, &bGotError); |
2697 | 0 | if (bGotError) |
2698 | 0 | bSuccess = false; |
2699 | |
|
2700 | 0 | if (psDesNode != nullptr) |
2701 | 0 | { |
2702 | 0 | CPLAddXMLChild(psDesListNode, psDesNode); |
2703 | 0 | } |
2704 | 0 | } |
2705 | 0 | } |
2706 | |
|
2707 | 0 | if (psDesListNode->psChild != nullptr) |
2708 | 0 | { |
2709 | 0 | char *pszXML = CPLSerializeXMLTree(psDesListNode); |
2710 | 0 | char *apszMD[2] = {pszXML, nullptr}; |
2711 | 0 | oSpecialMD.SetMetadata(apszMD, "xml:DES"); |
2712 | 0 | CPLFree(pszXML); |
2713 | 0 | } |
2714 | 0 | CPLDestroyXMLNode(psDesListNode); |
2715 | 0 | return bSuccess; |
2716 | 0 | } |
2717 | | |
2718 | | /************************************************************************/ |
2719 | | /* InitializeNITFMetadata() */ |
2720 | | /************************************************************************/ |
2721 | | |
2722 | | void NITFDataset::InitializeNITFMetadata() |
2723 | | |
2724 | 0 | { |
2725 | 0 | static const char *const pszDomainName = "NITF_METADATA"; |
2726 | 0 | static const char *const pszTagNITFFileHeader = "NITFFileHeader"; |
2727 | 0 | static const char *const pszTagNITFImageSubheader = "NITFImageSubheader"; |
2728 | |
|
2729 | 0 | if (oSpecialMD.GetMetadata(pszDomainName) != nullptr) |
2730 | 0 | return; |
2731 | | |
2732 | | // nHeaderLenOffset is the number of bytes to skip from the beginning of the |
2733 | | // NITF file header in order to get to the field HL (NITF file header |
2734 | | // length). |
2735 | | |
2736 | 0 | int nHeaderLen = 0; |
2737 | 0 | int nHeaderLenOffset = 0; |
2738 | | |
2739 | | // Get the NITF file header length. |
2740 | |
|
2741 | 0 | if (psFile->pachHeader != nullptr) |
2742 | 0 | { |
2743 | 0 | if ((STARTS_WITH(psFile->pachHeader, "NITF02.10")) || |
2744 | 0 | (STARTS_WITH(psFile->pachHeader, "NSIF01.00"))) |
2745 | 0 | nHeaderLenOffset = 354; |
2746 | 0 | else if ((STARTS_WITH(psFile->pachHeader, "NITF01.10")) || |
2747 | 0 | (STARTS_WITH(psFile->pachHeader, "NITF02.00"))) |
2748 | 0 | nHeaderLenOffset = |
2749 | 0 | (STARTS_WITH((psFile->pachHeader + 280), "999998")) ? 394 : 354; |
2750 | 0 | } |
2751 | |
|
2752 | 0 | char fieldHL[7]; |
2753 | |
|
2754 | 0 | if (nHeaderLenOffset > 0) |
2755 | 0 | { |
2756 | 0 | char *pszFieldHL = psFile->pachHeader + nHeaderLenOffset; |
2757 | |
|
2758 | 0 | memcpy(fieldHL, pszFieldHL, 6); |
2759 | 0 | fieldHL[6] = '\0'; |
2760 | 0 | nHeaderLen = atoi(fieldHL); |
2761 | 0 | } |
2762 | |
|
2763 | 0 | if (nHeaderLen <= 0) |
2764 | 0 | { |
2765 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Zero length NITF file header!"); |
2766 | 0 | return; |
2767 | 0 | } |
2768 | | |
2769 | 0 | char *encodedHeader = CPLBase64Encode( |
2770 | 0 | nHeaderLen, reinterpret_cast<GByte *>(psFile->pachHeader)); |
2771 | |
|
2772 | 0 | if (encodedHeader == nullptr || strlen(encodedHeader) == 0) |
2773 | 0 | { |
2774 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2775 | 0 | "Failed to encode NITF file header!"); |
2776 | 0 | CPLFree(encodedHeader); |
2777 | 0 | return; |
2778 | 0 | } |
2779 | | |
2780 | | // The length of the NITF file header plus a space is append to the |
2781 | | // beginning of the encoded string so that we can recover the length of the |
2782 | | // NITF file header when we decode it without having to pull it out the HL |
2783 | | // field again. |
2784 | | |
2785 | 0 | std::string nitfFileheaderStr(fieldHL); |
2786 | 0 | nitfFileheaderStr.append(" "); |
2787 | 0 | nitfFileheaderStr.append(encodedHeader); |
2788 | |
|
2789 | 0 | CPLFree(encodedHeader); |
2790 | |
|
2791 | 0 | oSpecialMD.SetMetadataItem(pszTagNITFFileHeader, nitfFileheaderStr.c_str(), |
2792 | 0 | pszDomainName); |
2793 | | |
2794 | | // Get the image subheader length. |
2795 | |
|
2796 | 0 | int nImageSubheaderLen = 0; |
2797 | |
|
2798 | 0 | if (psImage != nullptr && |
2799 | 0 | STARTS_WITH(psFile->pasSegmentInfo[psImage->iSegment].szSegmentType, |
2800 | 0 | "IM")) |
2801 | 0 | { |
2802 | 0 | nImageSubheaderLen = |
2803 | 0 | psFile->pasSegmentInfo[psImage->iSegment].nSegmentHeaderSize; |
2804 | 0 | } |
2805 | |
|
2806 | 0 | if (nImageSubheaderLen < 0) |
2807 | 0 | { |
2808 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2809 | 0 | "Invalid length NITF image subheader!"); |
2810 | 0 | return; |
2811 | 0 | } |
2812 | | |
2813 | 0 | if (nImageSubheaderLen > 0) |
2814 | 0 | { |
2815 | 0 | char *encodedImageSubheader = CPLBase64Encode( |
2816 | 0 | nImageSubheaderLen, reinterpret_cast<GByte *>(psImage->pachHeader)); |
2817 | |
|
2818 | 0 | if (encodedImageSubheader == nullptr || |
2819 | 0 | strlen(encodedImageSubheader) == 0) |
2820 | 0 | { |
2821 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2822 | 0 | "Failed to encode image subheader!"); |
2823 | 0 | CPLFree(encodedImageSubheader); |
2824 | 0 | return; |
2825 | 0 | } |
2826 | | |
2827 | | // The length of the image subheader plus a space is append to the |
2828 | | // beginning of the encoded string so that we can recover the actual |
2829 | | // length of the image subheader when we decode it. |
2830 | | |
2831 | 0 | char buffer[20]; |
2832 | |
|
2833 | 0 | snprintf(buffer, sizeof(buffer), "%d", nImageSubheaderLen); |
2834 | |
|
2835 | 0 | std::string imageSubheaderStr(buffer); |
2836 | 0 | imageSubheaderStr.append(" "); |
2837 | 0 | imageSubheaderStr.append(encodedImageSubheader); |
2838 | |
|
2839 | 0 | CPLFree(encodedImageSubheader); |
2840 | |
|
2841 | 0 | oSpecialMD.SetMetadataItem(pszTagNITFImageSubheader, |
2842 | 0 | imageSubheaderStr.c_str(), pszDomainName); |
2843 | 0 | } |
2844 | 0 | } |
2845 | | |
2846 | | /************************************************************************/ |
2847 | | /* InitializeCGMMetadata() */ |
2848 | | /************************************************************************/ |
2849 | | |
2850 | | void NITFDataset::InitializeCGMMetadata() |
2851 | | |
2852 | 0 | { |
2853 | 0 | if (oSpecialMD.GetMetadataItem("SEGMENT_COUNT", "CGM") != nullptr) |
2854 | 0 | return; |
2855 | | |
2856 | 0 | int iCGM = 0; |
2857 | 0 | char **papszCGMMetadata = CSLSetNameValue(nullptr, "SEGMENT_COUNT", "0"); |
2858 | | |
2859 | | /* ==================================================================== */ |
2860 | | /* Process all graphics segments. */ |
2861 | | /* ==================================================================== */ |
2862 | 0 | for (int iSegment = 0; iSegment < psFile->nSegmentCount; iSegment++) |
2863 | 0 | { |
2864 | 0 | NITFSegmentInfo *psSegment = psFile->pasSegmentInfo + iSegment; |
2865 | |
|
2866 | 0 | if (!EQUAL(psSegment->szSegmentType, "GR") && |
2867 | 0 | !EQUAL(psSegment->szSegmentType, "SY")) |
2868 | 0 | continue; |
2869 | | |
2870 | 0 | papszCGMMetadata = CSLSetNameValue( |
2871 | 0 | papszCGMMetadata, CPLString().Printf("SEGMENT_%d_SLOC_ROW", iCGM), |
2872 | 0 | CPLString().Printf("%d", psSegment->nLOC_R)); |
2873 | 0 | papszCGMMetadata = CSLSetNameValue( |
2874 | 0 | papszCGMMetadata, CPLString().Printf("SEGMENT_%d_SLOC_COL", iCGM), |
2875 | 0 | CPLString().Printf("%d", psSegment->nLOC_C)); |
2876 | |
|
2877 | 0 | papszCGMMetadata = CSLSetNameValue( |
2878 | 0 | papszCGMMetadata, CPLString().Printf("SEGMENT_%d_CCS_ROW", iCGM), |
2879 | 0 | CPLString().Printf("%d", psSegment->nCCS_R)); |
2880 | 0 | papszCGMMetadata = CSLSetNameValue( |
2881 | 0 | papszCGMMetadata, CPLString().Printf("SEGMENT_%d_CCS_COL", iCGM), |
2882 | 0 | CPLString().Printf("%d", psSegment->nCCS_C)); |
2883 | |
|
2884 | 0 | papszCGMMetadata = CSLSetNameValue( |
2885 | 0 | papszCGMMetadata, CPLString().Printf("SEGMENT_%d_SDLVL", iCGM), |
2886 | 0 | CPLString().Printf("%d", psSegment->nDLVL)); |
2887 | 0 | papszCGMMetadata = CSLSetNameValue( |
2888 | 0 | papszCGMMetadata, CPLString().Printf("SEGMENT_%d_SALVL", iCGM), |
2889 | 0 | CPLString().Printf("%d", psSegment->nALVL)); |
2890 | | |
2891 | | /* -------------------------------------------------------------------- |
2892 | | */ |
2893 | | /* Load the raw CGM data itself. */ |
2894 | | /* -------------------------------------------------------------------- |
2895 | | */ |
2896 | |
|
2897 | 0 | char *pabyCGMData = static_cast<char *>(VSI_CALLOC_VERBOSE( |
2898 | 0 | 1, static_cast<size_t>(psSegment->nSegmentSize))); |
2899 | 0 | if (pabyCGMData == nullptr) |
2900 | 0 | { |
2901 | 0 | CSLDestroy(papszCGMMetadata); |
2902 | 0 | return; |
2903 | 0 | } |
2904 | 0 | if (VSIFSeekL(psFile->fp, psSegment->nSegmentStart, SEEK_SET) != 0 || |
2905 | 0 | VSIFReadL(pabyCGMData, 1, |
2906 | 0 | static_cast<size_t>(psSegment->nSegmentSize), |
2907 | 0 | psFile->fp) != psSegment->nSegmentSize) |
2908 | 0 | { |
2909 | 0 | CPLError(CE_Warning, CPLE_FileIO, |
2910 | 0 | "Failed to read " CPL_FRMT_GUIB |
2911 | 0 | " bytes of graphic data at " CPL_FRMT_GUIB ".", |
2912 | 0 | psSegment->nSegmentSize, psSegment->nSegmentStart); |
2913 | 0 | CPLFree(pabyCGMData); |
2914 | 0 | CSLDestroy(papszCGMMetadata); |
2915 | 0 | return; |
2916 | 0 | } |
2917 | | |
2918 | 0 | char *pszEscapedCGMData = CPLEscapeString( |
2919 | 0 | pabyCGMData, static_cast<int>(psSegment->nSegmentSize), |
2920 | 0 | CPLES_BackslashQuotable); |
2921 | |
|
2922 | 0 | if (pszEscapedCGMData == nullptr) |
2923 | 0 | { |
2924 | 0 | CPLFree(pabyCGMData); |
2925 | 0 | CSLDestroy(papszCGMMetadata); |
2926 | 0 | return; |
2927 | 0 | } |
2928 | | |
2929 | 0 | papszCGMMetadata = CSLSetNameValue( |
2930 | 0 | papszCGMMetadata, CPLString().Printf("SEGMENT_%d_DATA", iCGM), |
2931 | 0 | pszEscapedCGMData); |
2932 | 0 | CPLFree(pszEscapedCGMData); |
2933 | 0 | CPLFree(pabyCGMData); |
2934 | |
|
2935 | 0 | iCGM++; |
2936 | 0 | } |
2937 | | |
2938 | | /* -------------------------------------------------------------------- */ |
2939 | | /* Record the CGM segment count. */ |
2940 | | /* -------------------------------------------------------------------- */ |
2941 | 0 | papszCGMMetadata = CSLSetNameValue(papszCGMMetadata, "SEGMENT_COUNT", |
2942 | 0 | CPLString().Printf("%d", iCGM)); |
2943 | |
|
2944 | 0 | oSpecialMD.SetMetadata(papszCGMMetadata, "CGM"); |
2945 | |
|
2946 | 0 | CSLDestroy(papszCGMMetadata); |
2947 | 0 | } |
2948 | | |
2949 | | /************************************************************************/ |
2950 | | /* InitializeTextMetadata() */ |
2951 | | /************************************************************************/ |
2952 | | |
2953 | | void NITFDataset::InitializeTextMetadata() |
2954 | | |
2955 | 0 | { |
2956 | 0 | if (oSpecialMD.GetMetadata("TEXT") != nullptr) |
2957 | 0 | return; |
2958 | | |
2959 | 0 | int iText = 0; |
2960 | | |
2961 | | /* ==================================================================== */ |
2962 | | /* Process all text segments. */ |
2963 | | /* ==================================================================== */ |
2964 | 0 | for (int iSegment = 0; iSegment < psFile->nSegmentCount; iSegment++) |
2965 | 0 | { |
2966 | 0 | NITFSegmentInfo *psSegment = psFile->pasSegmentInfo + iSegment; |
2967 | |
|
2968 | 0 | if (!EQUAL(psSegment->szSegmentType, "TX")) |
2969 | 0 | continue; |
2970 | | |
2971 | | /* -------------------------------------------------------------------- |
2972 | | */ |
2973 | | /* Load the text header */ |
2974 | | /* -------------------------------------------------------------------- |
2975 | | */ |
2976 | | |
2977 | | /* Allocate one extra byte for the NULL terminating character */ |
2978 | 0 | char *pabyHeaderData = static_cast<char *>(CPLCalloc( |
2979 | 0 | 1, static_cast<size_t>(psSegment->nSegmentHeaderSize + 1))); |
2980 | 0 | if (VSIFSeekL(psFile->fp, psSegment->nSegmentHeaderStart, SEEK_SET) != |
2981 | 0 | 0 || |
2982 | 0 | VSIFReadL(pabyHeaderData, 1, |
2983 | 0 | static_cast<size_t>(psSegment->nSegmentHeaderSize), |
2984 | 0 | psFile->fp) != psSegment->nSegmentHeaderSize) |
2985 | 0 | { |
2986 | 0 | CPLError( |
2987 | 0 | CE_Warning, CPLE_FileIO, |
2988 | 0 | "Failed to read %d bytes of text header data at " CPL_FRMT_GUIB |
2989 | 0 | ".", |
2990 | 0 | psSegment->nSegmentHeaderSize, psSegment->nSegmentHeaderStart); |
2991 | 0 | CPLFree(pabyHeaderData); |
2992 | 0 | return; |
2993 | 0 | } |
2994 | | |
2995 | 0 | oSpecialMD.SetMetadataItem(CPLString().Printf("HEADER_%d", iText), |
2996 | 0 | pabyHeaderData, "TEXT"); |
2997 | 0 | CPLFree(pabyHeaderData); |
2998 | | |
2999 | | /* -------------------------------------------------------------------- |
3000 | | */ |
3001 | | /* Load the raw TEXT data itself. */ |
3002 | | /* -------------------------------------------------------------------- |
3003 | | */ |
3004 | | /* Allocate one extra byte for the NULL terminating character */ |
3005 | 0 | char *pabyTextData = static_cast<char *>(VSI_CALLOC_VERBOSE( |
3006 | 0 | 1, static_cast<size_t>(psSegment->nSegmentSize) + 1)); |
3007 | 0 | if (pabyTextData == nullptr) |
3008 | 0 | { |
3009 | 0 | return; |
3010 | 0 | } |
3011 | 0 | if (VSIFSeekL(psFile->fp, psSegment->nSegmentStart, SEEK_SET) != 0 || |
3012 | 0 | VSIFReadL(pabyTextData, 1, |
3013 | 0 | static_cast<size_t>(psSegment->nSegmentSize), |
3014 | 0 | psFile->fp) != psSegment->nSegmentSize) |
3015 | 0 | { |
3016 | 0 | CPLError(CE_Warning, CPLE_FileIO, |
3017 | 0 | "Failed to read " CPL_FRMT_GUIB |
3018 | 0 | " bytes of text data at " CPL_FRMT_GUIB ".", |
3019 | 0 | psSegment->nSegmentSize, psSegment->nSegmentStart); |
3020 | 0 | CPLFree(pabyTextData); |
3021 | 0 | return; |
3022 | 0 | } |
3023 | | |
3024 | 0 | oSpecialMD.SetMetadataItem(CPLString().Printf("DATA_%d", iText), |
3025 | 0 | pabyTextData, "TEXT"); |
3026 | 0 | CPLFree(pabyTextData); |
3027 | |
|
3028 | 0 | iText++; |
3029 | 0 | } |
3030 | 0 | } |
3031 | | |
3032 | | /************************************************************************/ |
3033 | | /* InitializeTREMetadata() */ |
3034 | | /************************************************************************/ |
3035 | | |
3036 | | bool NITFDataset::InitializeTREMetadata(bool bValidate) |
3037 | | |
3038 | 0 | { |
3039 | 0 | if (oSpecialMD.GetMetadata("TRE") != nullptr || |
3040 | 0 | oSpecialMD.GetMetadata("xml:TRE") != nullptr) |
3041 | 0 | return true; |
3042 | | |
3043 | 0 | bool bGotError = false; |
3044 | 0 | CPLXMLNode *psTresNode = CPLCreateXMLNode(nullptr, CXT_Element, "tres"); |
3045 | 0 | bool bFoundRPFIMG = false; |
3046 | | |
3047 | | /* -------------------------------------------------------------------- */ |
3048 | | /* Loop over TRE sources (file and image). */ |
3049 | | /* -------------------------------------------------------------------- */ |
3050 | 0 | for (int nTRESrc = 0; nTRESrc < 2; nTRESrc++) |
3051 | 0 | { |
3052 | 0 | int nTREBytes = 0; |
3053 | 0 | char *pszTREData = nullptr; |
3054 | |
|
3055 | 0 | if (nTRESrc == 0) |
3056 | 0 | { |
3057 | 0 | nTREBytes = psFile->nTREBytes; |
3058 | 0 | pszTREData = psFile->pachTRE; |
3059 | 0 | } |
3060 | 0 | else |
3061 | 0 | { |
3062 | 0 | if (psImage) |
3063 | 0 | { |
3064 | 0 | nTREBytes = psImage->nTREBytes; |
3065 | 0 | pszTREData = psImage->pachTRE; |
3066 | 0 | } |
3067 | 0 | else |
3068 | 0 | { |
3069 | 0 | nTREBytes = 0; |
3070 | 0 | pszTREData = nullptr; |
3071 | 0 | } |
3072 | 0 | } |
3073 | | |
3074 | | /* -------------------------------------------------------------------- |
3075 | | */ |
3076 | | /* Loop over TREs. */ |
3077 | | /* -------------------------------------------------------------------- |
3078 | | */ |
3079 | |
|
3080 | 0 | while (nTREBytes >= 11) |
3081 | 0 | { |
3082 | 0 | char szTemp[100]; |
3083 | 0 | char szTag[7]; |
3084 | 0 | const int nThisTRESize = |
3085 | 0 | atoi(NITFGetField(szTemp, pszTREData, 6, 5)); |
3086 | |
|
3087 | 0 | if (nThisTRESize < 0) |
3088 | 0 | { |
3089 | 0 | NITFGetField(szTemp, pszTREData, 0, 6); |
3090 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
3091 | 0 | "Invalid size (%d) for TRE %s", nThisTRESize, szTemp); |
3092 | 0 | CPLDestroyXMLNode(psTresNode); |
3093 | 0 | bGotError = true; |
3094 | 0 | return bGotError; |
3095 | 0 | } |
3096 | 0 | if (nThisTRESize > nTREBytes - 11) |
3097 | 0 | { |
3098 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
3099 | 0 | "Not enough bytes in TRE"); |
3100 | 0 | CPLDestroyXMLNode(psTresNode); |
3101 | 0 | bGotError = true; |
3102 | 0 | return bGotError; |
3103 | 0 | } |
3104 | | |
3105 | 0 | strncpy(szTag, pszTREData, 6); |
3106 | 0 | szTag[6] = '\0'; |
3107 | | |
3108 | | // trim white off tag. |
3109 | 0 | while (strlen(szTag) > 0 && szTag[strlen(szTag) - 1] == ' ') |
3110 | 0 | szTag[strlen(szTag) - 1] = '\0'; |
3111 | |
|
3112 | 0 | if (strcmp(szTag, "RPFIMG") == 0) |
3113 | 0 | bFoundRPFIMG = true; |
3114 | |
|
3115 | 0 | CPLXMLNode *psTreNode = |
3116 | 0 | NITFCreateXMLTre(psFile, szTag, pszTREData + 11, nThisTRESize, |
3117 | 0 | bValidate, &bGotError); |
3118 | 0 | if (psTreNode) |
3119 | 0 | { |
3120 | 0 | CPLCreateXMLNode( |
3121 | 0 | CPLCreateXMLNode(psTreNode, CXT_Attribute, "location"), |
3122 | 0 | CXT_Text, nTRESrc == 0 ? "file" : "image"); |
3123 | 0 | CPLAddXMLChild(psTresNode, psTreNode); |
3124 | 0 | } |
3125 | | |
3126 | | // escape data. |
3127 | 0 | char *pszEscapedData = CPLEscapeString( |
3128 | 0 | pszTREData + 11, nThisTRESize, CPLES_BackslashQuotable); |
3129 | 0 | if (pszEscapedData == nullptr) |
3130 | 0 | { |
3131 | 0 | bGotError = true; |
3132 | 0 | } |
3133 | 0 | else |
3134 | 0 | { |
3135 | 0 | char szUniqueTag[32]; |
3136 | 0 | strcpy(szUniqueTag, szTag); |
3137 | 0 | int nCountUnique = 2; |
3138 | 0 | while (oSpecialMD.GetMetadataItem(szUniqueTag, "TRE") != |
3139 | 0 | nullptr) |
3140 | 0 | { |
3141 | 0 | snprintf(szUniqueTag, sizeof(szUniqueTag), "%s_%d", szTag, |
3142 | 0 | nCountUnique); |
3143 | 0 | nCountUnique++; |
3144 | 0 | } |
3145 | 0 | oSpecialMD.SetMetadataItem(szUniqueTag, pszEscapedData, "TRE"); |
3146 | 0 | CPLFree(pszEscapedData); |
3147 | 0 | } |
3148 | |
|
3149 | 0 | nTREBytes -= (nThisTRESize + 11); |
3150 | 0 | pszTREData += (nThisTRESize + 11); |
3151 | 0 | } |
3152 | 0 | } |
3153 | | |
3154 | | /* -------------------------------------------------------------------- */ |
3155 | | /* Loop over TRE in DES */ |
3156 | | /* -------------------------------------------------------------------- */ |
3157 | 0 | for (int iSegment = 0; iSegment < psFile->nSegmentCount; iSegment++) |
3158 | 0 | { |
3159 | 0 | NITFSegmentInfo *psSegInfo = psFile->pasSegmentInfo + iSegment; |
3160 | 0 | if (!EQUAL(psSegInfo->szSegmentType, "DE")) |
3161 | 0 | continue; |
3162 | | |
3163 | 0 | NITFDES *psDES = NITFDESAccess(psFile, iSegment); |
3164 | 0 | if (psDES == nullptr) |
3165 | 0 | continue; |
3166 | | |
3167 | 0 | char *pabyTREData = nullptr; |
3168 | 0 | int nOffset = 0; |
3169 | 0 | char szTREName[7]; |
3170 | 0 | int nThisTRESize; |
3171 | |
|
3172 | 0 | while (NITFDESGetTRE(psDES, nOffset, szTREName, &pabyTREData, |
3173 | 0 | &nThisTRESize)) |
3174 | 0 | { |
3175 | 0 | if (nThisTRESize == 0) |
3176 | 0 | { |
3177 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
3178 | 0 | "Invalid size=0 for TRE %s", szTREName); |
3179 | 0 | break; |
3180 | 0 | } |
3181 | | |
3182 | 0 | if (!(bFoundRPFIMG && strcmp(szTREName, "RPFDES") == 0)) |
3183 | 0 | { |
3184 | 0 | char *pszEscapedData = CPLEscapeString( |
3185 | 0 | pabyTREData, nThisTRESize, CPLES_BackslashQuotable); |
3186 | 0 | if (pszEscapedData == nullptr) |
3187 | 0 | { |
3188 | 0 | NITFDESFreeTREData(pabyTREData); |
3189 | 0 | bGotError = true; |
3190 | 0 | break; |
3191 | 0 | } |
3192 | | |
3193 | | // trim white off tag. |
3194 | 0 | while (strlen(szTREName) > 0 && |
3195 | 0 | szTREName[strlen(szTREName) - 1] == ' ') |
3196 | 0 | szTREName[strlen(szTREName) - 1] = '\0'; |
3197 | |
|
3198 | 0 | CPLXMLNode *psTreNode = |
3199 | 0 | NITFCreateXMLTre(psFile, szTREName, pabyTREData, |
3200 | 0 | nThisTRESize, bValidate, &bGotError); |
3201 | 0 | if (psTreNode) |
3202 | 0 | { |
3203 | 0 | const char *pszDESID = |
3204 | 0 | CSLFetchNameValue(psDES->papszMetadata, "DESID"); |
3205 | 0 | CPLCreateXMLNode( |
3206 | 0 | CPLCreateXMLNode(psTreNode, CXT_Attribute, "location"), |
3207 | 0 | CXT_Text, |
3208 | 0 | pszDESID ? CPLSPrintf("des %s", pszDESID) : "des"); |
3209 | 0 | CPLAddXMLChild(psTresNode, psTreNode); |
3210 | 0 | } |
3211 | |
|
3212 | 0 | char szUniqueTag[32]; |
3213 | 0 | strcpy(szUniqueTag, szTREName); |
3214 | 0 | int nCountUnique = 2; |
3215 | 0 | while (oSpecialMD.GetMetadataItem(szUniqueTag, "TRE") != |
3216 | 0 | nullptr) |
3217 | 0 | { |
3218 | 0 | snprintf(szUniqueTag, sizeof(szUniqueTag), "%s_%d", |
3219 | 0 | szTREName, nCountUnique); |
3220 | 0 | nCountUnique++; |
3221 | 0 | } |
3222 | 0 | oSpecialMD.SetMetadataItem(szUniqueTag, pszEscapedData, "TRE"); |
3223 | |
|
3224 | 0 | CPLFree(pszEscapedData); |
3225 | 0 | } |
3226 | | |
3227 | 0 | nOffset += 11 + nThisTRESize; |
3228 | |
|
3229 | 0 | NITFDESFreeTREData(pabyTREData); |
3230 | 0 | } |
3231 | |
|
3232 | 0 | NITFDESDeaccess(psDES); |
3233 | 0 | } |
3234 | |
|
3235 | 0 | if (psTresNode->psChild != nullptr) |
3236 | 0 | { |
3237 | 0 | char *pszXML = CPLSerializeXMLTree(psTresNode); |
3238 | 0 | char *apszMD[2] = {pszXML, nullptr}; |
3239 | 0 | oSpecialMD.SetMetadata(apszMD, "xml:TRE"); |
3240 | 0 | CPLFree(pszXML); |
3241 | 0 | } |
3242 | 0 | CPLDestroyXMLNode(psTresNode); |
3243 | |
|
3244 | 0 | return !bGotError; |
3245 | 0 | } |
3246 | | |
3247 | | /************************************************************************/ |
3248 | | /* GetMetadataDomainList() */ |
3249 | | /************************************************************************/ |
3250 | | |
3251 | | char **NITFDataset::GetMetadataDomainList() |
3252 | 0 | { |
3253 | 0 | return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(), |
3254 | 0 | TRUE, "NITF_METADATA", "xml:DES", |
3255 | 0 | "NITF_DES_METADATA", "NITF_FILE_HEADER_TRES", |
3256 | 0 | "NITF_IMAGE_SEGMENT_TRES", "CGM", "TEXT", |
3257 | 0 | "TRE", "xml:TRE", "OVERVIEWS", nullptr); |
3258 | 0 | } |
3259 | | |
3260 | | /************************************************************************/ |
3261 | | /* InitializeImageStructureMetadata() */ |
3262 | | /************************************************************************/ |
3263 | | |
3264 | | void NITFDataset::InitializeImageStructureMetadata() |
3265 | 0 | { |
3266 | 0 | if (oSpecialMD.GetMetadata("IMAGE_STRUCTURE") != nullptr) |
3267 | 0 | return; |
3268 | | |
3269 | 0 | oSpecialMD.SetMetadata(GDALPamDataset::GetMetadata("IMAGE_STRUCTURE"), |
3270 | 0 | "IMAGE_STRUCTURE"); |
3271 | 0 | if (poJ2KDataset) |
3272 | 0 | { |
3273 | 0 | const char *pszReversibility = poJ2KDataset->GetMetadataItem( |
3274 | 0 | "COMPRESSION_REVERSIBILITY", "IMAGE_STRUCTURE"); |
3275 | 0 | if (pszReversibility) |
3276 | 0 | { |
3277 | 0 | oSpecialMD.SetMetadataItem("COMPRESSION_REVERSIBILITY", |
3278 | 0 | pszReversibility, "IMAGE_STRUCTURE"); |
3279 | 0 | } |
3280 | 0 | } |
3281 | 0 | } |
3282 | | |
3283 | | /************************************************************************/ |
3284 | | /* GetMetadata() */ |
3285 | | /************************************************************************/ |
3286 | | |
3287 | | CSLConstList NITFDataset::GetMetadata(const char *pszDomain) |
3288 | | |
3289 | 237 | { |
3290 | 237 | if (pszDomain != nullptr && EQUAL(pszDomain, "NITF_METADATA")) |
3291 | 0 | { |
3292 | | // InitializeNITFMetadata retrieves the NITF file header and all image |
3293 | | // segment file headers. (NOTE: The returned strings are |
3294 | | // base64-encoded). |
3295 | |
|
3296 | 0 | InitializeNITFMetadata(); |
3297 | 0 | return oSpecialMD.GetMetadata(pszDomain); |
3298 | 0 | } |
3299 | | |
3300 | 237 | if (pszDomain != nullptr && EQUAL(pszDomain, "xml:DES")) |
3301 | 0 | { |
3302 | | // InitializeNITFDESs retrieves all the DES file headers (NOTE: The |
3303 | | // returned strings are base64-encoded). |
3304 | |
|
3305 | 0 | InitializeNITFDESs(false); |
3306 | 0 | return oSpecialMD.GetMetadata(pszDomain); |
3307 | 0 | } |
3308 | | |
3309 | | #ifdef ESRI_BUILD |
3310 | | if (pszDomain != NULL && EQUAL(pszDomain, "NITF_DES_METADATA")) |
3311 | | { |
3312 | | // InitializeNITFDESs retrieves all the DES file headers (NOTE: The |
3313 | | // returned strings are base64-encoded). |
3314 | | |
3315 | | InitializeNITFDESMetadata(false); |
3316 | | return oSpecialMD.GetMetadata(pszDomain); |
3317 | | } |
3318 | | |
3319 | | if (pszDomain != NULL && EQUAL(pszDomain, "NITF_FILE_HEADER_TRES")) |
3320 | | { |
3321 | | // InitializeNITFTREs retrieves all the TREs that are resides in the |
3322 | | // NITF file header and all the TREs that are resides in the current |
3323 | | // image segment. NOTE: the returned strings are backslash-escaped |
3324 | | |
3325 | | InitializeNITFTREs(); |
3326 | | return oSpecialMD.GetMetadata(pszDomain); |
3327 | | } |
3328 | | |
3329 | | if (pszDomain != NULL && EQUAL(pszDomain, "NITF_IMAGE_SEGMENT_TRES")) |
3330 | | { |
3331 | | // InitializeNITFTREs retrieves all the TREs that are resides in the |
3332 | | // NITF file header and all the TREs that are resides in the current |
3333 | | // image segment. NOTE: the returned strings are backslash-escaped |
3334 | | |
3335 | | InitializeNITFTREs(); |
3336 | | return oSpecialMD.GetMetadata(pszDomain); |
3337 | | } |
3338 | | #endif |
3339 | | |
3340 | 237 | if (pszDomain != nullptr && EQUAL(pszDomain, "CGM")) |
3341 | 0 | { |
3342 | 0 | InitializeCGMMetadata(); |
3343 | 0 | return oSpecialMD.GetMetadata(pszDomain); |
3344 | 0 | } |
3345 | | |
3346 | 237 | if (pszDomain != nullptr && EQUAL(pszDomain, "TEXT")) |
3347 | 0 | { |
3348 | 0 | InitializeTextMetadata(); |
3349 | 0 | return oSpecialMD.GetMetadata(pszDomain); |
3350 | 0 | } |
3351 | | |
3352 | 237 | if (pszDomain != nullptr && EQUAL(pszDomain, "TRE")) |
3353 | 0 | { |
3354 | 0 | InitializeTREMetadata(false); |
3355 | 0 | return oSpecialMD.GetMetadata(pszDomain); |
3356 | 0 | } |
3357 | | |
3358 | 237 | if (pszDomain != nullptr && EQUAL(pszDomain, "xml:TRE")) |
3359 | 0 | { |
3360 | 0 | InitializeTREMetadata(false); |
3361 | 0 | return oSpecialMD.GetMetadata(pszDomain); |
3362 | 0 | } |
3363 | | |
3364 | 237 | if (pszDomain != nullptr && EQUAL(pszDomain, "IMAGE_STRUCTURE") && |
3365 | 0 | poJ2KDataset) |
3366 | 0 | { |
3367 | 0 | InitializeImageStructureMetadata(); |
3368 | 0 | return oSpecialMD.GetMetadata(pszDomain); |
3369 | 0 | } |
3370 | | |
3371 | 237 | return GDALPamDataset::GetMetadata(pszDomain); |
3372 | 237 | } |
3373 | | |
3374 | | /************************************************************************/ |
3375 | | /* GetMetadataItem() */ |
3376 | | /************************************************************************/ |
3377 | | |
3378 | | const char *NITFDataset::GetMetadataItem(const char *pszName, |
3379 | | const char *pszDomain) |
3380 | | |
3381 | 1.73k | { |
3382 | 1.73k | if (pszDomain != nullptr && EQUAL(pszDomain, "NITF_METADATA")) |
3383 | 0 | { |
3384 | | // InitializeNITFMetadata retrieves the NITF file header and all image |
3385 | | // segment file headers. (NOTE: The returned strings are |
3386 | | // base64-encoded). |
3387 | |
|
3388 | 0 | InitializeNITFMetadata(); |
3389 | 0 | return oSpecialMD.GetMetadataItem(pszName, pszDomain); |
3390 | 0 | } |
3391 | | |
3392 | | #ifdef ESRI_BUILD |
3393 | | if (pszDomain != NULL && EQUAL(pszDomain, "NITF_DES_METADATA")) |
3394 | | { |
3395 | | // InitializeNITFDESs retrieves all the DES file headers (NOTE: The |
3396 | | // returned strings are base64-encoded). |
3397 | | |
3398 | | InitializeNITFDESMetadata(false); |
3399 | | return oSpecialMD.GetMetadataItem(pszName, pszDomain); |
3400 | | } |
3401 | | |
3402 | | if (pszDomain != NULL && EQUAL(pszDomain, "NITF_FILE_HEADER_TRES")) |
3403 | | { |
3404 | | // InitializeNITFTREs retrieves all the TREs that are resides in the |
3405 | | // NITF file header and all the TREs that are resides in the current |
3406 | | // image segment. NOTE: the returned strings are backslash-escaped |
3407 | | |
3408 | | InitializeNITFTREs(); |
3409 | | return oSpecialMD.GetMetadataItem(pszName, pszDomain); |
3410 | | } |
3411 | | |
3412 | | if (pszDomain != NULL && EQUAL(pszDomain, "NITF_IMAGE_SEGMENT_TRES")) |
3413 | | { |
3414 | | // InitializeNITFTREs retrieves all the TREs that are resides in the |
3415 | | // NITF file header and all the TREs that are resides in the current |
3416 | | // image segment. NOTE: the returned strings are backslash-escaped |
3417 | | |
3418 | | InitializeNITFTREs(); |
3419 | | return oSpecialMD.GetMetadataItem(pszName, pszDomain); |
3420 | | } |
3421 | | #endif |
3422 | | |
3423 | 1.73k | if (pszDomain != nullptr && EQUAL(pszDomain, "CGM")) |
3424 | 0 | { |
3425 | 0 | InitializeCGMMetadata(); |
3426 | 0 | return oSpecialMD.GetMetadataItem(pszName, pszDomain); |
3427 | 0 | } |
3428 | | |
3429 | 1.73k | if (pszDomain != nullptr && EQUAL(pszDomain, "TEXT")) |
3430 | 0 | { |
3431 | 0 | InitializeTextMetadata(); |
3432 | 0 | return oSpecialMD.GetMetadataItem(pszName, pszDomain); |
3433 | 0 | } |
3434 | | |
3435 | 1.73k | if (pszDomain != nullptr && EQUAL(pszDomain, "TRE")) |
3436 | 0 | { |
3437 | 0 | InitializeTREMetadata(false); |
3438 | 0 | return oSpecialMD.GetMetadataItem(pszName, pszDomain); |
3439 | 0 | } |
3440 | | |
3441 | 1.73k | if (pszDomain != nullptr && EQUAL(pszDomain, "OVERVIEWS") && |
3442 | 964 | !osRSetVRT.empty()) |
3443 | 0 | return osRSetVRT; |
3444 | | |
3445 | 1.73k | if (pszDomain != nullptr && EQUAL(pszDomain, "IMAGE_STRUCTURE") && |
3446 | 406 | poJ2KDataset && EQUAL(pszName, "COMPRESSION_REVERSIBILITY")) |
3447 | 0 | { |
3448 | 0 | InitializeImageStructureMetadata(); |
3449 | 0 | return oSpecialMD.GetMetadataItem(pszName, pszDomain); |
3450 | 0 | } |
3451 | | |
3452 | | // For unit test purposes |
3453 | 1.73k | if (pszDomain != nullptr && EQUAL(pszDomain, "DEBUG") && |
3454 | 0 | EQUAL(pszName, "JPEG2000_DATASET_NAME") && poJ2KDataset) |
3455 | 0 | return poJ2KDataset->GetDescription(); |
3456 | | |
3457 | | // For unit test purposes |
3458 | 1.73k | if (pszDomain != nullptr && EQUAL(pszDomain, "DEBUG") && |
3459 | 0 | EQUAL(pszName, "COMRAT") && psImage) |
3460 | 0 | return psImage->szCOMRAT; |
3461 | | |
3462 | 1.73k | return GDALPamDataset::GetMetadataItem(pszName, pszDomain); |
3463 | 1.73k | } |
3464 | | |
3465 | | /************************************************************************/ |
3466 | | /* GetGCPCount() */ |
3467 | | /************************************************************************/ |
3468 | | |
3469 | | int NITFDataset::GetGCPCount() |
3470 | | |
3471 | 218 | { |
3472 | 218 | return nGCPCount; |
3473 | 218 | } |
3474 | | |
3475 | | /************************************************************************/ |
3476 | | /* GetGCPSpatialRef() */ |
3477 | | /************************************************************************/ |
3478 | | |
3479 | | const OGRSpatialReference *NITFDataset::GetGCPSpatialRef() const |
3480 | | |
3481 | 215 | { |
3482 | 215 | if (nGCPCount > 0 && !m_oGCPSRS.IsEmpty()) |
3483 | 16 | return &m_oGCPSRS; |
3484 | | |
3485 | 199 | return nullptr; |
3486 | 215 | } |
3487 | | |
3488 | | /************************************************************************/ |
3489 | | /* GetGCP() */ |
3490 | | /************************************************************************/ |
3491 | | |
3492 | | const GDAL_GCP *NITFDataset::GetGCPs() |
3493 | | |
3494 | 215 | { |
3495 | 215 | return pasGCPList; |
3496 | 215 | } |
3497 | | |
3498 | | /************************************************************************/ |
3499 | | /* CheckForRSets() */ |
3500 | | /* */ |
3501 | | /* Check for reduced resolution images in .r<n> files and if */ |
3502 | | /* found return filename for a virtual file wrapping them as an */ |
3503 | | /* overview file. (#3457) */ |
3504 | | /************************************************************************/ |
3505 | | |
3506 | | int NITFDataset::CheckForRSets(const char *pszNITFFilename, |
3507 | | char **papszSiblingFiles) |
3508 | | |
3509 | 692 | { |
3510 | 692 | bool isR0File = EQUAL(CPLGetExtensionSafe(pszNITFFilename).c_str(), "r0"); |
3511 | | |
3512 | | /* -------------------------------------------------------------------- */ |
3513 | | /* Check to see if we have RSets. */ |
3514 | | /* -------------------------------------------------------------------- */ |
3515 | 692 | std::vector<CPLString> aosRSetFilenames; |
3516 | | |
3517 | 692 | for (int i = 1; i <= 5; i++) |
3518 | 692 | { |
3519 | 692 | CPLString osTarget; |
3520 | 692 | VSIStatBufL sStat; |
3521 | | |
3522 | 692 | if (isR0File) |
3523 | 0 | { |
3524 | 0 | osTarget = pszNITFFilename; |
3525 | 0 | osTarget.back() = static_cast<char>('0' + i); |
3526 | 0 | } |
3527 | 692 | else |
3528 | 692 | osTarget.Printf("%s.r%d", pszNITFFilename, i); |
3529 | | |
3530 | 692 | if (papszSiblingFiles == nullptr) |
3531 | 14 | { |
3532 | 14 | if (VSIStatL(osTarget, &sStat) != 0) |
3533 | 14 | break; |
3534 | 14 | } |
3535 | 678 | else |
3536 | 678 | { |
3537 | 678 | if (CSLFindStringCaseSensitive(papszSiblingFiles, |
3538 | 678 | CPLGetFilename(osTarget)) < 0) |
3539 | 678 | break; |
3540 | 678 | } |
3541 | | |
3542 | 0 | aosRSetFilenames.push_back(std::move(osTarget)); |
3543 | 0 | } |
3544 | | |
3545 | 692 | if (aosRSetFilenames.empty()) |
3546 | 692 | { |
3547 | | //try for remoteview RRDS (with .rv%d extension) |
3548 | 692 | for (int i = 1; i <= 7; i++) |
3549 | 692 | { |
3550 | 692 | CPLString osTarget; |
3551 | 692 | VSIStatBufL sStat; |
3552 | | |
3553 | 692 | osTarget.Printf("%s.rv%d", pszNITFFilename, i); |
3554 | | |
3555 | 692 | if (VSIStatL(osTarget, &sStat) != 0) |
3556 | 692 | break; |
3557 | | |
3558 | 0 | aosRSetFilenames.push_back(std::move(osTarget)); |
3559 | 0 | } |
3560 | | |
3561 | 692 | if (aosRSetFilenames.empty()) |
3562 | 692 | return FALSE; |
3563 | 692 | } |
3564 | | |
3565 | | /* -------------------------------------------------------------------- */ |
3566 | | /* We do, so try to create a wrapping VRT file. */ |
3567 | | /* -------------------------------------------------------------------- */ |
3568 | 0 | CPLString osFragment; |
3569 | |
|
3570 | 0 | osRSetVRT.Printf("<VRTDataset rasterXSize=\"%d\" rasterYSize=\"%d\">\n", |
3571 | 0 | GetRasterXSize() / 2, GetRasterYSize() / 2); |
3572 | |
|
3573 | 0 | for (int iBand = 0; iBand < GetRasterCount(); iBand++) |
3574 | 0 | { |
3575 | 0 | GDALRasterBand *poBand = GetRasterBand(iBand + 1); |
3576 | |
|
3577 | 0 | osRSetVRT += osFragment.Printf( |
3578 | 0 | " <VRTRasterBand dataType=\"%s\" band=\"%d\">\n", |
3579 | 0 | GDALGetDataTypeName(poBand->GetRasterDataType()), iBand + 1); |
3580 | |
|
3581 | 0 | for (int i = 0; i < static_cast<int>(aosRSetFilenames.size()); i++) |
3582 | 0 | { |
3583 | 0 | char *pszEscaped = |
3584 | 0 | CPLEscapeString(aosRSetFilenames[i].c_str(), -1, CPLES_XML); |
3585 | 0 | if (i == 0) |
3586 | 0 | osRSetVRT += |
3587 | 0 | osFragment.Printf(" " |
3588 | 0 | "<SimpleSource><SourceFilename>%s</" |
3589 | 0 | "SourceFilename><SourceBand>%d</" |
3590 | 0 | "SourceBand></SimpleSource>\n", |
3591 | 0 | pszEscaped, iBand + 1); |
3592 | 0 | else |
3593 | 0 | osRSetVRT += osFragment.Printf( |
3594 | 0 | " " |
3595 | 0 | "<Overview><SourceFilename>%s</" |
3596 | 0 | "SourceFilename><SourceBand>%d</SourceBand></Overview>\n", |
3597 | 0 | pszEscaped, iBand + 1); |
3598 | 0 | CPLFree(pszEscaped); |
3599 | 0 | } |
3600 | 0 | osRSetVRT += osFragment.Printf(" </VRTRasterBand>\n"); |
3601 | 0 | } |
3602 | |
|
3603 | 0 | osRSetVRT += "</VRTDataset>\n"; |
3604 | |
|
3605 | 0 | return TRUE; |
3606 | 692 | } |
3607 | | |
3608 | | /************************************************************************/ |
3609 | | /* IBuildOverviews() */ |
3610 | | /************************************************************************/ |
3611 | | |
3612 | | CPLErr NITFDataset::IBuildOverviews(const char *pszResampling, int nOverviews, |
3613 | | const int *panOverviewList, int nListBands, |
3614 | | const int *panBandList, |
3615 | | GDALProgressFunc pfnProgress, |
3616 | | void *pProgressData, |
3617 | | CSLConstList papszOptions) |
3618 | | |
3619 | 0 | { |
3620 | | /* -------------------------------------------------------------------- */ |
3621 | | /* If we have been using RSets we will need to clear them first. */ |
3622 | | /* -------------------------------------------------------------------- */ |
3623 | 0 | if (!osRSetVRT.empty()) |
3624 | 0 | { |
3625 | 0 | oOvManager.CleanOverviews(); |
3626 | 0 | osRSetVRT = ""; |
3627 | 0 | } |
3628 | |
|
3629 | 0 | bExposeUnderlyingJPEGDatasetOverviews = FALSE; |
3630 | | |
3631 | | /* -------------------------------------------------------------------- */ |
3632 | | /* If we have an underlying JPEG2000 dataset (hopefully via */ |
3633 | | /* JP2KAK) we will try and build zero overviews as a way of */ |
3634 | | /* tricking it into clearing existing overviews-from-jpeg2000. */ |
3635 | | /* -------------------------------------------------------------------- */ |
3636 | 0 | if (poJ2KDataset != nullptr && |
3637 | 0 | !poJ2KDataset->GetMetadataItem("OVERVIEW_FILE", "OVERVIEWS")) |
3638 | 0 | poJ2KDataset->BuildOverviews(pszResampling, 0, nullptr, nListBands, |
3639 | 0 | panBandList, GDALDummyProgress, nullptr, |
3640 | 0 | /* papszOptions = */ nullptr); |
3641 | | |
3642 | | /* -------------------------------------------------------------------- */ |
3643 | | /* Use the overview manager to build requested overviews. */ |
3644 | | /* -------------------------------------------------------------------- */ |
3645 | 0 | CPLErr eErr = GDALPamDataset::IBuildOverviews( |
3646 | 0 | pszResampling, nOverviews, panOverviewList, nListBands, panBandList, |
3647 | 0 | pfnProgress, pProgressData, papszOptions); |
3648 | | |
3649 | | /* -------------------------------------------------------------------- */ |
3650 | | /* If we are working with jpeg or jpeg2000, let the underlying */ |
3651 | | /* dataset know about the overview file. */ |
3652 | | /* -------------------------------------------------------------------- */ |
3653 | 0 | GDALDataset *poSubDS = poJ2KDataset.get(); |
3654 | 0 | if (poJPEGDataset) |
3655 | 0 | poSubDS = poJPEGDataset.get(); |
3656 | |
|
3657 | 0 | const char *pszOverviewFile = GetMetadataItem("OVERVIEW_FILE", "OVERVIEWS"); |
3658 | |
|
3659 | 0 | if (poSubDS && pszOverviewFile != nullptr && eErr == CE_None && |
3660 | 0 | poSubDS->GetMetadataItem("OVERVIEW_FILE", "OVERVIEWS") == nullptr) |
3661 | 0 | { |
3662 | 0 | poSubDS->SetMetadataItem("OVERVIEW_FILE", pszOverviewFile, "OVERVIEWS"); |
3663 | 0 | } |
3664 | |
|
3665 | 0 | return eErr; |
3666 | 0 | } |
3667 | | |
3668 | | /************************************************************************/ |
3669 | | /* ScanJPEGQLevel() */ |
3670 | | /* */ |
3671 | | /* Search the NITF APP header in the jpeg data stream to find */ |
3672 | | /* out what predefined Q level tables should be used (or -1 if */ |
3673 | | /* they are inline). */ |
3674 | | /************************************************************************/ |
3675 | | |
3676 | | int NITFDataset::ScanJPEGQLevel(GUIntBig *pnDataStart, bool *pbError) |
3677 | | |
3678 | 25 | { |
3679 | 25 | if (VSIFSeekL(psFile->fp, *pnDataStart, SEEK_SET) != 0) |
3680 | 0 | { |
3681 | 0 | CPLError(CE_Failure, CPLE_FileIO, "Seek error to jpeg data stream."); |
3682 | 0 | *pbError = true; |
3683 | 0 | return 0; |
3684 | 0 | } |
3685 | | |
3686 | 25 | GByte abyHeader[100]; |
3687 | 25 | if (VSIFReadL(abyHeader, 1, sizeof(abyHeader), psFile->fp) < |
3688 | 25 | sizeof(abyHeader)) |
3689 | 0 | { |
3690 | 0 | CPLError(CE_Failure, CPLE_FileIO, "Read error to jpeg data stream."); |
3691 | 0 | *pbError = true; |
3692 | 0 | return 0; |
3693 | 0 | } |
3694 | | |
3695 | | /* -------------------------------------------------------------------- */ |
3696 | | /* Scan ahead for jpeg magic code. In some files (eg. NSIF) */ |
3697 | | /* there seems to be some extra junk before the image data stream. */ |
3698 | | /* -------------------------------------------------------------------- */ |
3699 | 25 | GUInt32 nOffset = 0; |
3700 | 290 | while (nOffset < sizeof(abyHeader) - 23 && |
3701 | 287 | (abyHeader[nOffset + 0] != 0xff || abyHeader[nOffset + 1] != 0xd8 || |
3702 | 23 | abyHeader[nOffset + 2] != 0xff)) |
3703 | 265 | nOffset++; |
3704 | | |
3705 | 25 | if (nOffset >= sizeof(abyHeader) - 23) |
3706 | 3 | { |
3707 | 3 | *pbError = true; |
3708 | 3 | return 0; |
3709 | 3 | } |
3710 | | |
3711 | 22 | *pbError = false; |
3712 | 22 | *pnDataStart += nOffset; |
3713 | | |
3714 | 22 | if (nOffset > 0) |
3715 | 1 | CPLDebug("NITF", |
3716 | 1 | "JPEG data stream at offset %d from start of data segment, " |
3717 | 1 | "NSIF?", |
3718 | 1 | nOffset); |
3719 | | |
3720 | | /* -------------------------------------------------------------------- */ |
3721 | | /* Do we have an NITF app tag? If so, pull out the Q level. */ |
3722 | | /* -------------------------------------------------------------------- */ |
3723 | 22 | if (memcmp(abyHeader + nOffset + 6, "NITF\0", 5) != 0) |
3724 | 2 | return 0; |
3725 | | |
3726 | 20 | return abyHeader[22 + nOffset]; |
3727 | 22 | } |
3728 | | |
3729 | | /************************************************************************/ |
3730 | | /* ScanJPEGBlocks() */ |
3731 | | /************************************************************************/ |
3732 | | |
3733 | | CPLErr NITFDataset::ScanJPEGBlocks() |
3734 | | |
3735 | 7 | { |
3736 | 7 | GUIntBig nJPEGStart = |
3737 | 7 | psFile->pasSegmentInfo[psImage->iSegment].nSegmentStart; |
3738 | 7 | bool bError = false; |
3739 | 7 | nQLevel = ScanJPEGQLevel(&nJPEGStart, &bError); |
3740 | 7 | if (bError) |
3741 | 1 | { |
3742 | 1 | return CE_Failure; |
3743 | 1 | } |
3744 | | |
3745 | | /* -------------------------------------------------------------------- */ |
3746 | | /* Allocate offset array */ |
3747 | | /* -------------------------------------------------------------------- */ |
3748 | 6 | panJPEGBlockOffset = static_cast<vsi_l_offset *>(VSI_CALLOC_VERBOSE( |
3749 | 6 | sizeof(vsi_l_offset), static_cast<size_t>(psImage->nBlocksPerRow) * |
3750 | 6 | psImage->nBlocksPerColumn)); |
3751 | 6 | if (panJPEGBlockOffset == nullptr) |
3752 | 0 | { |
3753 | 0 | return CE_Failure; |
3754 | 0 | } |
3755 | 6 | panJPEGBlockOffset[0] = nJPEGStart; |
3756 | | |
3757 | 6 | if (psImage->nBlocksPerRow * psImage->nBlocksPerColumn == 1) |
3758 | 0 | return CE_None; |
3759 | | |
3760 | 6 | for (int iBlock = psImage->nBlocksPerRow * psImage->nBlocksPerColumn - 1; |
3761 | 5.12k | iBlock > 0; iBlock--) |
3762 | 5.12k | panJPEGBlockOffset[iBlock] = -1; |
3763 | | |
3764 | | /* -------------------------------------------------------------------- */ |
3765 | | /* Scan through the whole image data stream identifying all */ |
3766 | | /* block boundaries. Each block starts with 0xFFD8 (SOI). */ |
3767 | | /* They also end with 0xFFD9, but we don't currently look for */ |
3768 | | /* that. */ |
3769 | | /* -------------------------------------------------------------------- */ |
3770 | 6 | int iNextBlock = 1; |
3771 | 6 | GIntBig iSegOffset = 2; |
3772 | 6 | if (psFile->pasSegmentInfo[psImage->iSegment].nSegmentSize < |
3773 | 6 | nJPEGStart - psFile->pasSegmentInfo[psImage->iSegment].nSegmentStart) |
3774 | 0 | return CE_Failure; |
3775 | 6 | GIntBig iSegSize = |
3776 | 6 | psFile->pasSegmentInfo[psImage->iSegment].nSegmentSize - |
3777 | 6 | (nJPEGStart - psFile->pasSegmentInfo[psImage->iSegment].nSegmentStart); |
3778 | 6 | GByte abyBlock[512]; |
3779 | 6 | int ignoreBytes = 0; |
3780 | | |
3781 | 48 | while (iSegOffset < iSegSize - 1) |
3782 | 47 | { |
3783 | 47 | const size_t nReadSize = std::min( |
3784 | 47 | sizeof(abyBlock), static_cast<size_t>(iSegSize - iSegOffset)); |
3785 | | |
3786 | 47 | if (VSIFSeekL(psFile->fp, panJPEGBlockOffset[0] + iSegOffset, |
3787 | 47 | SEEK_SET) != 0) |
3788 | 0 | { |
3789 | 0 | CPLError(CE_Failure, CPLE_FileIO, |
3790 | 0 | "Seek error to jpeg data stream."); |
3791 | 0 | return CE_Failure; |
3792 | 0 | } |
3793 | | |
3794 | 47 | if (VSIFReadL(abyBlock, 1, nReadSize, psFile->fp) < nReadSize) |
3795 | 5 | { |
3796 | 5 | CPLError(CE_Failure, CPLE_FileIO, |
3797 | 5 | "Read error to jpeg data stream."); |
3798 | 5 | return CE_Failure; |
3799 | 5 | } |
3800 | | |
3801 | 21.3k | for (size_t i = 0; i < nReadSize - 1; i++) |
3802 | 21.3k | { |
3803 | 21.3k | if (ignoreBytes == 0) |
3804 | 18.1k | { |
3805 | 18.1k | if (abyBlock[i] == 0xff) |
3806 | 226 | { |
3807 | | /* start-of-image marker */ |
3808 | 226 | if (abyBlock[i + 1] == 0xd8) |
3809 | 4 | { |
3810 | 4 | panJPEGBlockOffset[iNextBlock++] = |
3811 | 4 | panJPEGBlockOffset[0] + iSegOffset + i; |
3812 | | |
3813 | 4 | if (iNextBlock == |
3814 | 4 | psImage->nBlocksPerRow * psImage->nBlocksPerColumn) |
3815 | 0 | { |
3816 | 0 | return CE_None; |
3817 | 0 | } |
3818 | 4 | } |
3819 | | /* Skip application-specific data to avoid false positive |
3820 | | * while detecting */ |
3821 | | /* start-of-image markers (#2927). The size of the |
3822 | | * application data is */ |
3823 | | /* found in the two following bytes */ |
3824 | | /* We need this complex mechanism of ignoreBytes for dealing |
3825 | | * with */ |
3826 | | /* application data crossing several abyBlock ... */ |
3827 | 222 | else if (abyBlock[i + 1] >= 0xe0 && abyBlock[i + 1] < 0xf0) |
3828 | 6 | { |
3829 | 6 | ignoreBytes = -2; |
3830 | 6 | } |
3831 | 226 | } |
3832 | 18.1k | } |
3833 | 3.18k | else if (ignoreBytes < 0) |
3834 | 12 | { |
3835 | 12 | if (ignoreBytes == -1) |
3836 | 6 | { |
3837 | | /* Size of the application data */ |
3838 | 6 | ignoreBytes = abyBlock[i] * 256 + abyBlock[i + 1]; |
3839 | 6 | } |
3840 | 6 | else |
3841 | 6 | ignoreBytes++; |
3842 | 12 | } |
3843 | 3.17k | else |
3844 | 3.17k | { |
3845 | 3.17k | ignoreBytes--; |
3846 | 3.17k | } |
3847 | 21.3k | } |
3848 | | |
3849 | 42 | iSegOffset += nReadSize - 1; |
3850 | 42 | } |
3851 | | |
3852 | 1 | return CE_None; |
3853 | 6 | } |
3854 | | |
3855 | | /************************************************************************/ |
3856 | | /* ReadJPEGBlock() */ |
3857 | | /************************************************************************/ |
3858 | | |
3859 | | CPLErr NITFDataset::ReadJPEGBlock(int iBlockX, int iBlockY) |
3860 | | |
3861 | 7 | { |
3862 | 7 | CPLErr eErr; |
3863 | | |
3864 | | /* -------------------------------------------------------------------- */ |
3865 | | /* If this is our first request, do a scan for block boundaries. */ |
3866 | | /* -------------------------------------------------------------------- */ |
3867 | 7 | if (panJPEGBlockOffset == nullptr) |
3868 | 7 | { |
3869 | 7 | if (EQUAL(psImage->szIC, "M3")) |
3870 | 0 | { |
3871 | | /* -------------------------------------------------------------------- |
3872 | | */ |
3873 | | /* When a data mask subheader is present, we don't need to scan |
3874 | | */ |
3875 | | /* the whole file. We just use the psImage->panBlockStart table |
3876 | | */ |
3877 | | /* -------------------------------------------------------------------- |
3878 | | */ |
3879 | 0 | panJPEGBlockOffset = static_cast<vsi_l_offset *>( |
3880 | 0 | VSI_CALLOC_VERBOSE(sizeof(vsi_l_offset), |
3881 | 0 | static_cast<size_t>(psImage->nBlocksPerRow) * |
3882 | 0 | psImage->nBlocksPerColumn)); |
3883 | 0 | if (panJPEGBlockOffset == nullptr) |
3884 | 0 | { |
3885 | 0 | return CE_Failure; |
3886 | 0 | } |
3887 | 0 | for (int i = 0; |
3888 | 0 | i < psImage->nBlocksPerRow * psImage->nBlocksPerColumn; i++) |
3889 | 0 | { |
3890 | 0 | panJPEGBlockOffset[i] = psImage->panBlockStart[i]; |
3891 | 0 | if (panJPEGBlockOffset[i] != static_cast<vsi_l_offset>(-1) && |
3892 | 0 | panJPEGBlockOffset[i] != UINT_MAX) |
3893 | 0 | { |
3894 | 0 | vsi_l_offset nOffset = panJPEGBlockOffset[i]; |
3895 | 0 | bool bError = false; |
3896 | 0 | nQLevel = ScanJPEGQLevel(&nOffset, &bError); |
3897 | | /* The beginning of the JPEG stream should be the offset */ |
3898 | | /* from the panBlockStart table */ |
3899 | 0 | if (bError || nOffset != panJPEGBlockOffset[i]) |
3900 | 0 | { |
3901 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
3902 | 0 | "JPEG block doesn't start at expected offset"); |
3903 | 0 | return CE_Failure; |
3904 | 0 | } |
3905 | 0 | } |
3906 | 0 | } |
3907 | 0 | } |
3908 | 7 | else /* 'C3' case */ |
3909 | 7 | { |
3910 | | /* -------------------------------------------------------------------- |
3911 | | */ |
3912 | | /* Scan through the whole image data stream identifying all */ |
3913 | | /* block boundaries. */ |
3914 | | /* -------------------------------------------------------------------- |
3915 | | */ |
3916 | 7 | eErr = ScanJPEGBlocks(); |
3917 | 7 | if (eErr != CE_None) |
3918 | 6 | return eErr; |
3919 | 7 | } |
3920 | 7 | } |
3921 | | |
3922 | | /* -------------------------------------------------------------------- */ |
3923 | | /* Allocate image data block (where the uncompressed image will go) */ |
3924 | | /* -------------------------------------------------------------------- */ |
3925 | 1 | if (pabyJPEGBlock == nullptr) |
3926 | 1 | { |
3927 | | /* Allocate enough memory to hold 12bit JPEG data */ |
3928 | 1 | pabyJPEGBlock = static_cast<GByte *>(VSI_CALLOC_VERBOSE( |
3929 | 1 | psImage->nBands, static_cast<size_t>(psImage->nBlockWidth) * |
3930 | 1 | psImage->nBlockHeight * 2)); |
3931 | 1 | if (pabyJPEGBlock == nullptr) |
3932 | 0 | { |
3933 | 0 | return CE_Failure; |
3934 | 0 | } |
3935 | 1 | } |
3936 | | |
3937 | | /* -------------------------------------------------------------------- */ |
3938 | | /* Read JPEG Chunk. */ |
3939 | | /* -------------------------------------------------------------------- */ |
3940 | 1 | const int iBlock = iBlockX + iBlockY * psImage->nBlocksPerRow; |
3941 | | |
3942 | 1 | if (panJPEGBlockOffset[iBlock] == static_cast<vsi_l_offset>(-1) || |
3943 | 1 | panJPEGBlockOffset[iBlock] == UINT_MAX) |
3944 | 0 | { |
3945 | 0 | memset(pabyJPEGBlock, 0, |
3946 | 0 | static_cast<size_t>(psImage->nBands) * psImage->nBlockWidth * |
3947 | 0 | psImage->nBlockHeight * 2); |
3948 | 0 | return CE_None; |
3949 | 0 | } |
3950 | | |
3951 | 1 | CPLString osFilename; |
3952 | 1 | osFilename.Printf("JPEG_SUBFILE:Q%d," CPL_FRMT_GUIB ",%d,%s", nQLevel, |
3953 | 1 | panJPEGBlockOffset[iBlock], 0, osNITFFilename.c_str()); |
3954 | | |
3955 | 1 | GDALDataset *poDS = |
3956 | 1 | GDALDataset::FromHandle(GDALOpen(osFilename, GA_ReadOnly)); |
3957 | 1 | if (poDS == nullptr) |
3958 | 0 | return CE_Failure; |
3959 | | |
3960 | 1 | if (poDS->GetRasterXSize() != psImage->nBlockWidth || |
3961 | 1 | poDS->GetRasterYSize() != psImage->nBlockHeight) |
3962 | 0 | { |
3963 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
3964 | 0 | "JPEG block %d not same size as NITF blocksize.", iBlock); |
3965 | 0 | delete poDS; |
3966 | 0 | return CE_Failure; |
3967 | 0 | } |
3968 | | |
3969 | 1 | if (poDS->GetRasterCount() < psImage->nBands) |
3970 | 0 | { |
3971 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
3972 | 0 | "JPEG block %d has not enough bands.", iBlock); |
3973 | 0 | delete poDS; |
3974 | 0 | return CE_Failure; |
3975 | 0 | } |
3976 | | |
3977 | 1 | if (poDS->GetRasterBand(1)->GetRasterDataType() != |
3978 | 1 | GetRasterBand(1)->GetRasterDataType()) |
3979 | 0 | { |
3980 | 0 | CPLError( |
3981 | 0 | CE_Failure, CPLE_AppDefined, |
3982 | 0 | "JPEG block %d data type (%s) not consistent with band data type " |
3983 | 0 | "(%s).", |
3984 | 0 | iBlock, |
3985 | 0 | GDALGetDataTypeName(poDS->GetRasterBand(1)->GetRasterDataType()), |
3986 | 0 | GDALGetDataTypeName(GetRasterBand(1)->GetRasterDataType())); |
3987 | 0 | delete poDS; |
3988 | 0 | return CE_Failure; |
3989 | 0 | } |
3990 | | |
3991 | 1 | int anBands[3] = {1, 2, 3}; |
3992 | 1 | eErr = poDS->RasterIO(GF_Read, 0, 0, psImage->nBlockWidth, |
3993 | 1 | psImage->nBlockHeight, pabyJPEGBlock, |
3994 | 1 | psImage->nBlockWidth, psImage->nBlockHeight, |
3995 | 1 | GetRasterBand(1)->GetRasterDataType(), |
3996 | 1 | psImage->nBands, anBands, 0, 0, 0, nullptr); |
3997 | | |
3998 | 1 | delete poDS; |
3999 | | |
4000 | 1 | return eErr; |
4001 | 1 | } |
4002 | | |
4003 | | /************************************************************************/ |
4004 | | /* GetFileList() */ |
4005 | | /************************************************************************/ |
4006 | | |
4007 | | char **NITFDataset::GetFileList() |
4008 | | |
4009 | 432 | { |
4010 | 432 | char **papszFileList = GDALPamDataset::GetFileList(); |
4011 | | |
4012 | | // Small optimization to avoid useless file probing. |
4013 | 432 | if (CSLCount(papszFileList) == 0) |
4014 | 0 | return papszFileList; |
4015 | | |
4016 | | /* -------------------------------------------------------------------- */ |
4017 | | /* Check for .imd file. */ |
4018 | | /* -------------------------------------------------------------------- */ |
4019 | 432 | papszFileList = AddFile(papszFileList, "IMD", "imd"); |
4020 | | |
4021 | | /* -------------------------------------------------------------------- */ |
4022 | | /* Check for .rpb file. */ |
4023 | | /* -------------------------------------------------------------------- */ |
4024 | 432 | papszFileList = AddFile(papszFileList, "RPB", "rpb"); |
4025 | | |
4026 | 432 | if (!m_osRPCTXTFilename.empty()) |
4027 | 0 | papszFileList = CSLAddString(papszFileList, m_osRPCTXTFilename); |
4028 | | |
4029 | | /* -------------------------------------------------------------------- */ |
4030 | | /* Check for other files. */ |
4031 | | /* -------------------------------------------------------------------- */ |
4032 | 432 | papszFileList = AddFile(papszFileList, "ATT", "att"); |
4033 | 432 | papszFileList = AddFile(papszFileList, "EPH", "eph"); |
4034 | 432 | papszFileList = AddFile(papszFileList, "GEO", "geo"); |
4035 | 432 | papszFileList = AddFile(papszFileList, "XML", "xml"); |
4036 | | |
4037 | 432 | return papszFileList; |
4038 | 432 | } |
4039 | | |
4040 | | /************************************************************************/ |
4041 | | /* AddFile() */ |
4042 | | /* */ |
4043 | | /* Helper method for GetFileList() */ |
4044 | | /************************************************************************/ |
4045 | | char **NITFDataset::AddFile(char **papszFileList, const char *EXTENSION, |
4046 | | const char *extension) |
4047 | 2.59k | { |
4048 | 2.59k | VSIStatBufL sStatBuf; |
4049 | 2.59k | CPLString osTarget = CPLResetExtensionSafe(osNITFFilename, EXTENSION); |
4050 | 2.59k | if (oOvManager.GetSiblingFiles() != nullptr) |
4051 | 2.59k | { |
4052 | 2.59k | if (CSLFindStringCaseSensitive(oOvManager.GetSiblingFiles(), |
4053 | 2.59k | CPLGetFilename(osTarget)) >= 0) |
4054 | 0 | papszFileList = CSLAddString(papszFileList, osTarget); |
4055 | 2.59k | else |
4056 | 2.59k | { |
4057 | 2.59k | osTarget = CPLResetExtensionSafe(osNITFFilename, extension); |
4058 | 2.59k | if (CSLFindStringCaseSensitive(oOvManager.GetSiblingFiles(), |
4059 | 2.59k | CPLGetFilename(osTarget)) >= 0) |
4060 | 4 | papszFileList = CSLAddString(papszFileList, osTarget); |
4061 | 2.59k | } |
4062 | 2.59k | } |
4063 | 0 | else |
4064 | 0 | { |
4065 | 0 | if (VSIStatL(osTarget, &sStatBuf) == 0) |
4066 | 0 | papszFileList = CSLAddString(papszFileList, osTarget); |
4067 | 0 | else |
4068 | 0 | { |
4069 | 0 | osTarget = CPLResetExtensionSafe(osNITFFilename, extension); |
4070 | 0 | if (VSIStatL(osTarget, &sStatBuf) == 0) |
4071 | 0 | papszFileList = CSLAddString(papszFileList, osTarget); |
4072 | 0 | } |
4073 | 0 | } |
4074 | | |
4075 | 2.59k | return papszFileList; |
4076 | 2.59k | } |
4077 | | |
4078 | | /************************************************************************/ |
4079 | | /* GDALToNITFDataType() */ |
4080 | | /************************************************************************/ |
4081 | | |
4082 | | static const char *GDALToNITFDataType(GDALDataType eType) |
4083 | | |
4084 | 20 | { |
4085 | 20 | const char *pszPVType = nullptr; |
4086 | | |
4087 | 20 | switch (eType) |
4088 | 20 | { |
4089 | 9 | case GDT_UInt8: |
4090 | 15 | case GDT_UInt16: |
4091 | 15 | case GDT_UInt32: |
4092 | 15 | pszPVType = "INT"; |
4093 | 15 | break; |
4094 | | |
4095 | 1 | case GDT_Int16: |
4096 | 1 | case GDT_Int32: |
4097 | 1 | pszPVType = "SI"; |
4098 | 1 | break; |
4099 | | |
4100 | 1 | case GDT_Float32: |
4101 | 4 | case GDT_Float64: |
4102 | 4 | pszPVType = "R"; |
4103 | 4 | break; |
4104 | | |
4105 | 0 | case GDT_CInt16: |
4106 | 0 | case GDT_CInt32: |
4107 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
4108 | 0 | "NITF format does not support complex integer data."); |
4109 | 0 | return nullptr; |
4110 | | |
4111 | 0 | case GDT_CFloat32: |
4112 | 0 | pszPVType = "C"; |
4113 | 0 | break; |
4114 | | |
4115 | 0 | default: |
4116 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
4117 | 0 | "Unsupported raster pixel type (%s).", |
4118 | 0 | GDALGetDataTypeName(eType)); |
4119 | 0 | return nullptr; |
4120 | 20 | } |
4121 | | |
4122 | 20 | return pszPVType; |
4123 | 20 | } |
4124 | | |
4125 | | /************************************************************************/ |
4126 | | /* NITFJP2ECWOptions() */ |
4127 | | /* */ |
4128 | | /* Prepare JP2-in-NITF creation options based in part of the */ |
4129 | | /* NITF creation options. */ |
4130 | | /************************************************************************/ |
4131 | | |
4132 | | static CPLStringList NITFJP2ECWOptions(const CPLStringList &aosOptionsIn) |
4133 | | |
4134 | 0 | { |
4135 | 0 | CPLStringList aoJP2ECWOptions; |
4136 | 0 | aoJP2ECWOptions.AddString("PROFILE=NPJE"); |
4137 | 0 | aoJP2ECWOptions.AddString("CODESTREAM_ONLY=TRUE"); |
4138 | |
|
4139 | 0 | for (int i = 0; i < aosOptionsIn.size(); ++i) |
4140 | 0 | { |
4141 | 0 | if (STARTS_WITH_CI(aosOptionsIn[i], "PROFILE=")) |
4142 | 0 | { |
4143 | 0 | aoJP2ECWOptions.SetNameValue("PROFILE", |
4144 | 0 | aosOptionsIn[i] + strlen("PROFILE=")); |
4145 | 0 | } |
4146 | 0 | else if (STARTS_WITH_CI(aosOptionsIn[i], "TARGET=")) |
4147 | 0 | aoJP2ECWOptions.AddString(aosOptionsIn[i]); |
4148 | 0 | } |
4149 | |
|
4150 | 0 | return aoJP2ECWOptions; |
4151 | 0 | } |
4152 | | |
4153 | | /************************************************************************/ |
4154 | | /* NITFJP2KAKOptions() */ |
4155 | | /* */ |
4156 | | /* Prepare JP2-in-NITF creation options based in part of the */ |
4157 | | /* NITF creation options. */ |
4158 | | /************************************************************************/ |
4159 | | |
4160 | | static CPLStringList NITFJP2KAKOptions(const CPLStringList &aosOptionsIn, |
4161 | | int nABPP) |
4162 | | |
4163 | 0 | { |
4164 | 0 | CPLStringList aoJP2KAKOptions; |
4165 | 0 | aoJP2KAKOptions.AddString("CODEC=J2K"); |
4166 | |
|
4167 | 0 | for (int i = 0; i < aosOptionsIn.size(); ++i) |
4168 | 0 | { |
4169 | 0 | if (STARTS_WITH_CI(aosOptionsIn[i], "QUALITY=") || |
4170 | 0 | STARTS_WITH_CI(aosOptionsIn[i], "BLOCKXSIZE=") || |
4171 | 0 | STARTS_WITH_CI(aosOptionsIn[i], "BLOCKYSIZE=") || |
4172 | 0 | STARTS_WITH_CI(aosOptionsIn[i], "LAYERS=") || |
4173 | 0 | STARTS_WITH_CI(aosOptionsIn[i], "ROI=")) |
4174 | 0 | { |
4175 | 0 | aoJP2KAKOptions.AddString(aosOptionsIn[i]); |
4176 | 0 | } |
4177 | 0 | } |
4178 | |
|
4179 | 0 | aoJP2KAKOptions.SetNameValue("NBITS", CPLSPrintf("%d", nABPP)); |
4180 | |
|
4181 | 0 | return aoJP2KAKOptions; |
4182 | 0 | } |
4183 | | |
4184 | | /************************************************************************/ |
4185 | | /* NITFJP2OPENJPEGOptions() */ |
4186 | | /* */ |
4187 | | /* Prepare JP2-in-NITF creation options based in part of the */ |
4188 | | /* NITF creation options. */ |
4189 | | /************************************************************************/ |
4190 | | |
4191 | | static CPLStringList NITFJP2OPENJPEGOptions(GDALDriver *poJ2KDriver, |
4192 | | const CPLStringList &aosOptionsIn, |
4193 | | int nABPP) |
4194 | | |
4195 | 0 | { |
4196 | 0 | CPLStringList aoJP2OJPOptions; |
4197 | 0 | aoJP2OJPOptions.AddString("CODEC=J2K"); |
4198 | |
|
4199 | 0 | const char *pszQuality = aosOptionsIn.FetchNameValue("QUALITY"); |
4200 | 0 | double dfQuality = 0; |
4201 | 0 | if (pszQuality) |
4202 | 0 | { |
4203 | 0 | for (const char *pszVal : |
4204 | 0 | CPLStringList(CSLTokenizeString2(pszQuality, ",", 0))) |
4205 | 0 | dfQuality = std::max(dfQuality, CPLAtof(pszVal)); |
4206 | 0 | } |
4207 | |
|
4208 | 0 | double dfTarget = CPLAtof(aosOptionsIn.FetchNameValueDef("TARGET", "0")); |
4209 | |
|
4210 | 0 | if (dfTarget > 0 && dfTarget < 100) |
4211 | 0 | dfQuality = 100. - dfTarget; |
4212 | |
|
4213 | 0 | for (int i = 0; i < aosOptionsIn.size(); ++i) |
4214 | 0 | { |
4215 | 0 | if (STARTS_WITH_CI(aosOptionsIn[i], "BLOCKXSIZE=") || |
4216 | 0 | STARTS_WITH_CI(aosOptionsIn[i], "BLOCKYSIZE=")) |
4217 | 0 | { |
4218 | 0 | aoJP2OJPOptions.AddString(aosOptionsIn[i]); |
4219 | 0 | } |
4220 | 0 | } |
4221 | | |
4222 | | // Set it now before the NPJE profiles have a chance to override it |
4223 | 0 | if (pszQuality) |
4224 | 0 | { |
4225 | 0 | aoJP2OJPOptions.SetNameValue("QUALITY", pszQuality); |
4226 | 0 | } |
4227 | |
|
4228 | 0 | const char *pszProfile = aosOptionsIn.FetchNameValueDef("PROFILE", ""); |
4229 | 0 | if (STARTS_WITH_CI(pszProfile, "NPJE")) |
4230 | 0 | { |
4231 | | // Follow STDI-0006 NCDRD "2.3 Data Compression - JPEG 2000" and |
4232 | | // ISO/IEC BIIF Profile BPJ2K01.10 |
4233 | | // (https://nsgreg.nga.mil/doc/view?i=2031&month=3&day=22&year=2021), |
4234 | | // for NPJE (Appendix D ) profile |
4235 | |
|
4236 | 0 | if (pszQuality && strchr(pszQuality, ',')) |
4237 | 0 | { |
4238 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
4239 | 0 | "Only largest value of QUALITY used when PROFILE=%s " |
4240 | 0 | "is specified", |
4241 | 0 | pszProfile); |
4242 | 0 | } |
4243 | |
|
4244 | 0 | aoJP2OJPOptions.AddString("@BLOCKSIZE_STRICT=YES"); |
4245 | | |
4246 | | // Empty PRECINCTS option to ask for no custom precincts |
4247 | 0 | aoJP2OJPOptions.AddString("PRECINCTS="); |
4248 | |
|
4249 | 0 | #if defined(__GNUC__) |
4250 | 0 | #pragma GCC diagnostic push |
4251 | 0 | #pragma GCC diagnostic ignored "-Warray-bounds" |
4252 | 0 | #endif |
4253 | | // See Table 2.3-3 - Target Bit Rates for Each Tile in Panchromatic |
4254 | | // Image Segments of STDI-0006 |
4255 | 0 | std::vector<double> adfBPP = { |
4256 | 0 | 0.03125, 0.0625, 0.125, 0.25, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, |
4257 | 0 | 1.1, 1.2, 1.3, 1.5, 1.7, 2.0, 2.3, 3.5, 3.9}; |
4258 | 0 | if (STARTS_WITH_CI(pszProfile, "NPJE_NUMERICALLY_LOSSLESS")) |
4259 | 0 | { |
4260 | | // given that we consider a compression ratio afterwards, we |
4261 | | // arbitrarily consider a Byte datatype, and thus lossless quality |
4262 | | // is achieved at worse with 8 bpp |
4263 | 0 | adfBPP.push_back(8.0); |
4264 | | |
4265 | | // Lossless 5x3 wavelet |
4266 | 0 | aoJP2OJPOptions.AddString("REVERSIBLE=YES"); |
4267 | 0 | } |
4268 | 0 | #if defined(__GNUC__) |
4269 | 0 | #pragma GCC diagnostic pop |
4270 | 0 | #endif |
4271 | |
|
4272 | 0 | std::string osQuality; |
4273 | 0 | for (double dfBPP : adfBPP) |
4274 | 0 | { |
4275 | 0 | if (!osQuality.empty()) |
4276 | 0 | osQuality += ','; |
4277 | | // the JP2OPENJPEG QUALITY setting is 100. / compression_ratio |
4278 | | // and compression_ratio = 8 / bpp |
4279 | 0 | double dfLayerQuality = 100.0 / (8.0 / dfBPP); |
4280 | 0 | if (dfLayerQuality > dfQuality && dfQuality != 0.0) |
4281 | 0 | { |
4282 | 0 | osQuality += CPLSPrintf("%f", dfQuality); |
4283 | 0 | break; |
4284 | 0 | } |
4285 | 0 | osQuality += CPLSPrintf("%f", dfLayerQuality); |
4286 | 0 | } |
4287 | 0 | aoJP2OJPOptions.SetNameValue("QUALITY", osQuality.c_str()); |
4288 | |
|
4289 | 0 | aoJP2OJPOptions.AddString("PROGRESSION=LRCP"); |
4290 | | |
4291 | | // Disable MCT |
4292 | 0 | aoJP2OJPOptions.AddString("YCC=NO"); |
4293 | | |
4294 | | // TLM option added in OpenJPEG 2.5 |
4295 | 0 | if (strstr(poJ2KDriver->GetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST), |
4296 | 0 | "TLM") != nullptr) |
4297 | 0 | { |
4298 | 0 | aoJP2OJPOptions.AddString("PLT=YES"); |
4299 | 0 | aoJP2OJPOptions.AddString("TLM=YES"); |
4300 | 0 | } |
4301 | 0 | else |
4302 | 0 | { |
4303 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
4304 | 0 | "TLM option not available in JP2OPENJPEG driver. " |
4305 | 0 | "Use OpenJPEG 2.5 or later"); |
4306 | 0 | } |
4307 | |
|
4308 | 0 | aoJP2OJPOptions.AddString("RESOLUTIONS=6"); |
4309 | 0 | } |
4310 | 0 | else if (EQUAL(pszProfile, "PROFILE_1")) |
4311 | 0 | { |
4312 | 0 | aoJP2OJPOptions.AddString("PROFILE=PROFILE_1"); |
4313 | 0 | } |
4314 | 0 | else if (EQUAL(pszProfile, "PROFILE_2")) |
4315 | 0 | { |
4316 | 0 | aoJP2OJPOptions.AddString("PROFILE=UNRESTRICTED"); |
4317 | 0 | } |
4318 | |
|
4319 | 0 | aoJP2OJPOptions.SetNameValue("NBITS", CPLSPrintf("%d", nABPP)); |
4320 | |
|
4321 | 0 | return aoJP2OJPOptions; |
4322 | 0 | } |
4323 | | |
4324 | | /************************************************************************/ |
4325 | | /* NITFExtractTEXTAndCGMCreationOption() */ |
4326 | | /************************************************************************/ |
4327 | | |
4328 | | static CPLStringList NITFExtractTEXTAndCGMCreationOption( |
4329 | | GDALDataset *poSrcDS, CSLConstList papszOptions, CPLStringList &aosTextMD, |
4330 | | CPLStringList &aosCgmMD) |
4331 | 20 | { |
4332 | 20 | CPLStringList aosOptions(CSLDuplicate(papszOptions)); |
4333 | | |
4334 | | /* -------------------------------------------------------------------- */ |
4335 | | /* Prepare for text segments. */ |
4336 | | /* -------------------------------------------------------------------- */ |
4337 | 20 | aosTextMD = CSLFetchNameValueMultiple(papszOptions, "TEXT"); |
4338 | | // Notice: CSLFetchNameValueMultiple remove the leading "TEXT=" when |
4339 | | // returning the list, which is what we want. |
4340 | | |
4341 | | // Use TEXT information from original image if no creation option is passed |
4342 | | // in. |
4343 | 20 | if (poSrcDS != nullptr && aosTextMD.empty()) |
4344 | 20 | { |
4345 | | // Read CGM adata from original image, duplicate the list because |
4346 | | // we frees papszCgmMD at end of the function. |
4347 | 20 | aosTextMD = CSLDuplicate(poSrcDS->GetMetadata("TEXT")); |
4348 | 20 | } |
4349 | | |
4350 | 20 | int nNUMT = 0; |
4351 | 20 | for (int iOpt = 0; iOpt < aosTextMD.size(); iOpt++) |
4352 | 0 | { |
4353 | 0 | if (!STARTS_WITH_CI(aosTextMD[iOpt], "DATA_")) |
4354 | 0 | continue; |
4355 | | |
4356 | 0 | nNUMT++; |
4357 | 0 | } |
4358 | | |
4359 | 20 | if (nNUMT > 0) |
4360 | 0 | { |
4361 | 0 | aosOptions.SetNameValue("NUMT", std::to_string(nNUMT).c_str()); |
4362 | 0 | } |
4363 | | |
4364 | | /* -------------------------------------------------------------------- */ |
4365 | | /* Prepare for CGM segments. */ |
4366 | | /* -------------------------------------------------------------------- */ |
4367 | 20 | aosCgmMD = CSLFetchNameValueMultiple(papszOptions, "CGM"); |
4368 | | // Notice: CSLFetchNameValueMultiple remove the leading "CGM=" when |
4369 | | // returning the list, which is what we want. |
4370 | | |
4371 | | // Use CGM information from original image if no creation option is passed |
4372 | | // in. |
4373 | 20 | if (poSrcDS != nullptr && aosCgmMD.empty()) |
4374 | 20 | { |
4375 | | // Read CGM adata from original image, duplicate the list because |
4376 | | // we frees papszCgmMD at end of the function. |
4377 | 20 | aosCgmMD = CSLDuplicate(poSrcDS->GetMetadata("CGM")); |
4378 | 20 | } |
4379 | | |
4380 | | // Set NUMS based on the number of segments |
4381 | 20 | const char *pszNUMS; // graphic segment option string |
4382 | 20 | int nNUMS = 0; |
4383 | 20 | if (!aosCgmMD.empty()) |
4384 | 0 | { |
4385 | 0 | pszNUMS = aosCgmMD.FetchNameValue("SEGMENT_COUNT"); |
4386 | |
|
4387 | 0 | if (pszNUMS != nullptr) |
4388 | 0 | { |
4389 | 0 | nNUMS = atoi(pszNUMS); |
4390 | 0 | } |
4391 | 0 | aosOptions.SetNameValue("NUMS", std::to_string(nNUMS).c_str()); |
4392 | 0 | } |
4393 | | |
4394 | 20 | return aosOptions; |
4395 | 20 | } |
4396 | | |
4397 | | /************************************************************************/ |
4398 | | /* NITFDatasetCreate() */ |
4399 | | /************************************************************************/ |
4400 | | |
4401 | | GDALDataset *NITFDataset::NITFDatasetCreate(const char *pszFilename, int nXSize, |
4402 | | int nYSize, int nBandsIn, |
4403 | | GDALDataType eType, |
4404 | | CSLConstList papszOptions) |
4405 | | |
4406 | 0 | { |
4407 | 0 | const char *pszPVType = GDALToNITFDataType(eType); |
4408 | 0 | if (pszPVType == nullptr) |
4409 | 0 | return nullptr; |
4410 | | |
4411 | 0 | const char *pszProduct = CSLFetchNameValue(papszOptions, "PRODUCT_TYPE"); |
4412 | 0 | if (pszProduct && EQUAL(pszProduct, "CADRG")) |
4413 | 0 | { |
4414 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
4415 | 0 | "CADRG creation only supported in CreateCopy()"); |
4416 | 0 | return nullptr; |
4417 | 0 | } |
4418 | | |
4419 | 0 | const char *pszIC = CSLFetchNameValue(papszOptions, "IC"); |
4420 | | |
4421 | | /* -------------------------------------------------------------------- */ |
4422 | | /* We disallow any IC value except NC when creating this way. */ |
4423 | | /* -------------------------------------------------------------------- */ |
4424 | 0 | GDALDriver *poJ2KDriver = nullptr; |
4425 | |
|
4426 | 0 | if (pszIC != nullptr && EQUAL(pszIC, "C8")) |
4427 | 0 | { |
4428 | 0 | bool bHasCreate = false; |
4429 | |
|
4430 | 0 | poJ2KDriver = GetGDALDriverManager()->GetDriverByName("JP2ECW"); |
4431 | 0 | if (poJ2KDriver != nullptr) |
4432 | 0 | bHasCreate = poJ2KDriver->GetMetadataItem(GDAL_DCAP_CREATE, |
4433 | 0 | nullptr) != nullptr; |
4434 | 0 | if (!bHasCreate) |
4435 | 0 | { |
4436 | 0 | CPLError( |
4437 | 0 | CE_Failure, CPLE_AppDefined, |
4438 | 0 | "Unable to create JPEG2000 encoded NITF files. The\n" |
4439 | 0 | "JP2ECW driver is unavailable, or missing Create support."); |
4440 | 0 | return nullptr; |
4441 | 0 | } |
4442 | | |
4443 | 0 | if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "J2KLRA", "NO"))) |
4444 | 0 | { |
4445 | 0 | CPLError(CE_Warning, CPLE_NotSupported, |
4446 | 0 | "J2KLRA TRE can only be written in CreateCopy() mode, and " |
4447 | 0 | "when using the JP2OPENJPEG driver in NPJE profiles"); |
4448 | 0 | } |
4449 | 0 | } |
4450 | | |
4451 | 0 | else if (pszIC != nullptr && !EQUAL(pszIC, "NC")) |
4452 | 0 | { |
4453 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
4454 | 0 | "Unsupported compression (IC=%s) used in direct\n" |
4455 | 0 | "NITF File creation", |
4456 | 0 | pszIC); |
4457 | 0 | return nullptr; |
4458 | 0 | } |
4459 | | |
4460 | 0 | const char *const apszIgnoredOptions[] = {"SDE_TRE", "RPC00B", "RPCTXT", |
4461 | 0 | nullptr}; |
4462 | 0 | for (int i = 0; apszIgnoredOptions[i] != nullptr; ++i) |
4463 | 0 | { |
4464 | 0 | if (CSLFetchNameValue(papszOptions, apszIgnoredOptions[i])) |
4465 | 0 | { |
4466 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
4467 | 0 | "%s creation option ignored by Create() method " |
4468 | 0 | "(only valid in CreateCopy())", |
4469 | 0 | apszIgnoredOptions[i]); |
4470 | 0 | } |
4471 | 0 | } |
4472 | | |
4473 | | /* -------------------------------------------------------------------- */ |
4474 | | /* Prepare for text and CGM segments. */ |
4475 | | /* -------------------------------------------------------------------- */ |
4476 | 0 | CPLStringList aosTextMD, aosCgmMD; |
4477 | 0 | CPLStringList aosOptions(NITFExtractTEXTAndCGMCreationOption( |
4478 | 0 | nullptr, papszOptions, aosTextMD, aosCgmMD)); |
4479 | |
|
4480 | 0 | const char *pszBlockSize = aosOptions.FetchNameValue("BLOCKSIZE"); |
4481 | 0 | if (pszBlockSize != nullptr && |
4482 | 0 | aosOptions.FetchNameValue("BLOCKXSIZE") == nullptr) |
4483 | 0 | { |
4484 | 0 | aosOptions.SetNameValue("BLOCKXSIZE", pszBlockSize); |
4485 | 0 | } |
4486 | 0 | if (pszBlockSize != nullptr && |
4487 | 0 | aosOptions.FetchNameValue("BLOCKYSIZE") == nullptr) |
4488 | 0 | { |
4489 | 0 | aosOptions.SetNameValue("BLOCKYSIZE", pszBlockSize); |
4490 | 0 | } |
4491 | |
|
4492 | 0 | if (const char *pszNBITS = aosOptions.FetchNameValue("NBITS")) |
4493 | 0 | { |
4494 | 0 | aosOptions.SetNameValue("ABPP", pszNBITS); |
4495 | 0 | } |
4496 | | |
4497 | | /* -------------------------------------------------------------------- */ |
4498 | | /* Create the file. */ |
4499 | | /* -------------------------------------------------------------------- */ |
4500 | |
|
4501 | 0 | int nIMIndex = 0; |
4502 | 0 | int nImageCount = 0; |
4503 | 0 | vsi_l_offset nImageOffset = 0; |
4504 | 0 | vsi_l_offset nICOffset = 0; |
4505 | 0 | if (!NITFCreateEx(pszFilename, nXSize, nYSize, nBandsIn, |
4506 | 0 | GDALGetDataTypeSizeBits(eType), pszPVType, |
4507 | 0 | aosOptions.List(), &nIMIndex, &nImageCount, &nImageOffset, |
4508 | 0 | &nICOffset, nullptr)) |
4509 | 0 | { |
4510 | 0 | return nullptr; |
4511 | 0 | } |
4512 | | |
4513 | | /* -------------------------------------------------------------------- */ |
4514 | | /* Various special hacks related to JPEG2000 encoded files. */ |
4515 | | /* -------------------------------------------------------------------- */ |
4516 | 0 | GDALDataset *poWritableJ2KDataset = nullptr; |
4517 | 0 | if (poJ2KDriver) |
4518 | 0 | { |
4519 | 0 | CPLString osDSName; |
4520 | |
|
4521 | 0 | osDSName.Printf("/vsisubfile/" CPL_FRMT_GUIB "_%d,%s", |
4522 | 0 | static_cast<GUIntBig>(nImageOffset), -1, pszFilename); |
4523 | |
|
4524 | 0 | poWritableJ2KDataset = |
4525 | 0 | poJ2KDriver->Create(osDSName, nXSize, nYSize, nBandsIn, eType, |
4526 | 0 | NITFJP2ECWOptions(aosOptions).List()); |
4527 | |
|
4528 | 0 | if (poWritableJ2KDataset == nullptr) |
4529 | 0 | { |
4530 | 0 | return nullptr; |
4531 | 0 | } |
4532 | 0 | } |
4533 | | |
4534 | | /* -------------------------------------------------------------------- */ |
4535 | | /* Open the dataset in update mode. */ |
4536 | | /* -------------------------------------------------------------------- */ |
4537 | 0 | GDALOpenInfo oOpenInfo(pszFilename, GA_Update); |
4538 | 0 | NITFDataset *poDS = NITFDataset::OpenInternal( |
4539 | 0 | &oOpenInfo, poWritableJ2KDataset, true, nIMIndex); |
4540 | 0 | if (poDS) |
4541 | 0 | { |
4542 | 0 | poDS->m_nImageOffset = nImageOffset; |
4543 | 0 | poDS->m_nIMIndex = nIMIndex; |
4544 | 0 | poDS->m_nImageCount = nImageCount; |
4545 | 0 | poDS->m_nICOffset = nICOffset; |
4546 | 0 | poDS->papszTextMDToWrite = aosTextMD.StealList(); |
4547 | 0 | poDS->papszCgmMDToWrite = aosCgmMD.StealList(); |
4548 | 0 | poDS->aosCreationOptions.Assign(CSLDuplicate(papszOptions), true); |
4549 | 0 | } |
4550 | 0 | return poDS; |
4551 | 0 | } |
4552 | | |
4553 | | /************************************************************************/ |
4554 | | /* NITFCreateCopy() */ |
4555 | | /************************************************************************/ |
4556 | | |
4557 | | GDALDataset *NITFDataset::NITFCreateCopy(const char *pszFilename, |
4558 | | GDALDataset *poSrcDS, int bStrict, |
4559 | | CSLConstList papszOptions, |
4560 | | GDALProgressFunc pfnProgress, |
4561 | | void *pProgressData) |
4562 | 21 | { |
4563 | 21 | CADRGCreateCopyContext copyContext; |
4564 | 21 | return CreateCopy(pszFilename, poSrcDS, bStrict, papszOptions, pfnProgress, |
4565 | 21 | pProgressData, /* nRecLevel = */ 0, ©Context) |
4566 | 21 | .release(); |
4567 | 21 | } |
4568 | | |
4569 | | /************************************************************************/ |
4570 | | /* NITFDataset::CreateCopy() */ |
4571 | | /************************************************************************/ |
4572 | | |
4573 | | std::unique_ptr<GDALDataset> |
4574 | | NITFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS, |
4575 | | int bStrict, CSLConstList papszOptions, |
4576 | | GDALProgressFunc pfnProgress, void *pProgressData, |
4577 | | int nRecLevel, CADRGCreateCopyContext *copyContext) |
4578 | | |
4579 | 21 | { |
4580 | 21 | if (nRecLevel == 3) |
4581 | 0 | { |
4582 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
4583 | 0 | "NITFDataset::CreateCopy(): programming error: too deep " |
4584 | 0 | "recursion"); |
4585 | 0 | return nullptr; |
4586 | 0 | } |
4587 | | |
4588 | 21 | int nBands = poSrcDS->GetRasterCount(); |
4589 | 21 | if (nBands == 0) |
4590 | 1 | { |
4591 | 1 | CPLError(CE_Failure, CPLE_NotSupported, |
4592 | 1 | "Unable to export files with zero bands."); |
4593 | 1 | return nullptr; |
4594 | 1 | } |
4595 | | |
4596 | 20 | GDALRasterBand *poBand1 = poSrcDS->GetRasterBand(1); |
4597 | 20 | if (poBand1 == nullptr) |
4598 | 0 | { |
4599 | 0 | return nullptr; |
4600 | 0 | } |
4601 | | |
4602 | 20 | const char *pszProductType = |
4603 | 20 | CSLFetchNameValue(papszOptions, "PRODUCT_TYPE"); |
4604 | 20 | const bool bIsCADRG = (pszProductType && EQUAL(pszProductType, "CADRG")); |
4605 | | |
4606 | | /* -------------------------------------------------------------------- */ |
4607 | | /* Only allow supported compression values. */ |
4608 | | /* -------------------------------------------------------------------- */ |
4609 | 20 | bool bJPEG2000 = false; |
4610 | 20 | bool bJPEG = false; |
4611 | 20 | GDALDriver *poJ2KDriver = nullptr; |
4612 | 20 | const char *pszJPEG2000_DRIVER = |
4613 | 20 | CSLFetchNameValue(papszOptions, "JPEG2000_DRIVER"); |
4614 | 20 | if (pszJPEG2000_DRIVER != nullptr) |
4615 | 0 | poJ2KDriver = |
4616 | 0 | GetGDALDriverManager()->GetDriverByName(pszJPEG2000_DRIVER); |
4617 | | |
4618 | 20 | const char *pszIC = CSLFetchNameValue(papszOptions, "IC"); |
4619 | 20 | if (pszIC != nullptr) |
4620 | 0 | { |
4621 | 0 | if (bIsCADRG) |
4622 | 0 | { |
4623 | 0 | if (!EQUAL(pszIC, "C4")) |
4624 | 0 | { |
4625 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
4626 | 0 | "CADRG only supports IC=C4"); |
4627 | 0 | return nullptr; |
4628 | 0 | } |
4629 | 0 | } |
4630 | 0 | else if (EQUAL(pszIC, "C4")) |
4631 | 0 | { |
4632 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
4633 | 0 | "IC=C4 only supported for PRODUCT_TYPE=CADRG"); |
4634 | 0 | return nullptr; |
4635 | 0 | } |
4636 | | |
4637 | 0 | if (EQUAL(pszIC, "NC")) |
4638 | 0 | /* ok */; |
4639 | 0 | else if (EQUAL(pszIC, "C8")) |
4640 | 0 | { |
4641 | 0 | if (pszJPEG2000_DRIVER == nullptr) |
4642 | 0 | { |
4643 | 0 | poJ2KDriver = GetGDALDriverManager()->GetDriverByName("JP2ECW"); |
4644 | 0 | if (poJ2KDriver == nullptr || |
4645 | 0 | poJ2KDriver->GetMetadataItem(GDAL_DCAP_CREATECOPY, |
4646 | 0 | nullptr) == nullptr) |
4647 | 0 | { |
4648 | | /* Try with JP2KAK as an alternate driver */ |
4649 | 0 | poJ2KDriver = |
4650 | 0 | GetGDALDriverManager()->GetDriverByName("JP2KAK"); |
4651 | 0 | } |
4652 | 0 | if (poJ2KDriver == nullptr) |
4653 | 0 | { |
4654 | | /* Try with JP2OPENJPEG as an alternate driver */ |
4655 | 0 | poJ2KDriver = |
4656 | 0 | GetGDALDriverManager()->GetDriverByName("JP2OPENJPEG"); |
4657 | 0 | } |
4658 | 0 | } |
4659 | 0 | if (poJ2KDriver == nullptr) |
4660 | 0 | { |
4661 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
4662 | 0 | "Unable to write JPEG2000 compressed NITF file.\n" |
4663 | 0 | "No 'subfile' JPEG2000 write supporting drivers are\n" |
4664 | 0 | "configured."); |
4665 | 0 | return nullptr; |
4666 | 0 | } |
4667 | | |
4668 | 0 | if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "J2KLRA", "NO"))) |
4669 | 0 | { |
4670 | 0 | if (!EQUAL(poJ2KDriver->GetDescription(), "JP2OPENJPEG")) |
4671 | 0 | { |
4672 | 0 | CPLError( |
4673 | 0 | CE_Warning, CPLE_NotSupported, |
4674 | 0 | "J2KLRA TRE can only be written " |
4675 | 0 | "when using the JP2OPENJPEG driver in NPJE profiles"); |
4676 | 0 | } |
4677 | 0 | else if (!STARTS_WITH_CI( |
4678 | 0 | CSLFetchNameValueDef(papszOptions, "PROFILE", ""), |
4679 | 0 | "NPJE")) |
4680 | 0 | { |
4681 | 0 | CPLError(CE_Warning, CPLE_NotSupported, |
4682 | 0 | "J2KLRA TRE can only be written in NPJE profiles"); |
4683 | 0 | } |
4684 | 0 | } |
4685 | 0 | bJPEG2000 = TRUE; |
4686 | 0 | } |
4687 | 0 | else if (EQUAL(pszIC, "C3") || EQUAL(pszIC, "M3")) |
4688 | 0 | { |
4689 | 0 | bJPEG = TRUE; |
4690 | | #ifndef JPEG_SUPPORTED |
4691 | | CPLError(CE_Failure, CPLE_AppDefined, |
4692 | | "Unable to write JPEG compressed NITF file.\n" |
4693 | | "Libjpeg is not configured into build."); |
4694 | | return nullptr; |
4695 | | #endif |
4696 | 0 | } |
4697 | 0 | else |
4698 | 0 | { |
4699 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
4700 | 0 | "Only IC=NC (uncompressed), IC=C3/M3 (JPEG) and IC=C8 " |
4701 | 0 | "(JPEG2000)\n" |
4702 | 0 | "allowed with NITF CreateCopy method."); |
4703 | 0 | return nullptr; |
4704 | 0 | } |
4705 | 0 | } |
4706 | | |
4707 | | /* -------------------------------------------------------------------- */ |
4708 | | /* Get the data type. Complex integers isn't supported by */ |
4709 | | /* NITF, so map that to complex float if we aren't in strict */ |
4710 | | /* mode. */ |
4711 | | /* -------------------------------------------------------------------- */ |
4712 | 20 | GDALDataType eType = poBand1->GetRasterDataType(); |
4713 | 20 | if (!bStrict && (eType == GDT_CInt16 || eType == GDT_CInt32)) |
4714 | 0 | eType = GDT_CFloat32; |
4715 | | |
4716 | | /* -------------------------------------------------------------------- */ |
4717 | | /* CADRG related checks and pre-processing */ |
4718 | | /* -------------------------------------------------------------------- */ |
4719 | 20 | if (bIsCADRG) |
4720 | 0 | { |
4721 | 0 | auto ret = |
4722 | 0 | CADRGCreateCopy(pszFilename, poSrcDS, bStrict, papszOptions, |
4723 | 0 | pfnProgress, pProgressData, nRecLevel, copyContext); |
4724 | 0 | if (std::holds_alternative<std::unique_ptr<GDALDataset>>(ret)) |
4725 | 0 | { |
4726 | 0 | return std::move(std::get<std::unique_ptr<GDALDataset>>(ret)); |
4727 | 0 | } |
4728 | 0 | CPLAssert(std::holds_alternative<bool>(ret)); |
4729 | 0 | const bool bGoOn = std::get<bool>(ret); |
4730 | 0 | if (!bGoOn) |
4731 | 0 | { |
4732 | 0 | return nullptr; |
4733 | 0 | } |
4734 | 0 | } |
4735 | | |
4736 | | /* -------------------------------------------------------------------- */ |
4737 | | /* Prepare for text and CGM segments. */ |
4738 | | /* -------------------------------------------------------------------- */ |
4739 | 20 | CPLStringList aosTextMD, aosCgmMD; |
4740 | 20 | CPLStringList aosOptions(NITFExtractTEXTAndCGMCreationOption( |
4741 | 20 | poSrcDS, papszOptions, aosTextMD, aosCgmMD)); |
4742 | | |
4743 | 20 | const char *pszBlockSize = aosOptions.FetchNameValue("BLOCKSIZE"); |
4744 | 20 | if (bIsCADRG) |
4745 | 0 | { |
4746 | 0 | if (pszBlockSize && atoi(pszBlockSize) != 256) |
4747 | 0 | { |
4748 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
4749 | 0 | "CADRG only supports BLOCKSIZE=256"); |
4750 | 0 | return nullptr; |
4751 | 0 | } |
4752 | 0 | else |
4753 | 0 | { |
4754 | 0 | pszBlockSize = "256"; |
4755 | 0 | } |
4756 | 0 | } |
4757 | 20 | if (pszBlockSize != nullptr && |
4758 | 0 | aosOptions.FetchNameValue("BLOCKXSIZE") == nullptr) |
4759 | 0 | { |
4760 | 0 | aosOptions.SetNameValue("BLOCKXSIZE", pszBlockSize); |
4761 | 0 | } |
4762 | 20 | if (pszBlockSize != nullptr && |
4763 | 0 | aosOptions.FetchNameValue("BLOCKYSIZE") == nullptr) |
4764 | 0 | { |
4765 | 0 | aosOptions.SetNameValue("BLOCKYSIZE", pszBlockSize); |
4766 | 0 | } |
4767 | | |
4768 | | /* -------------------------------------------------------------------- */ |
4769 | | /* Copy over other source metadata items as creation options */ |
4770 | | /* that seem useful, unless they are already set as creation */ |
4771 | | /* options. */ |
4772 | | /* -------------------------------------------------------------------- */ |
4773 | 20 | const bool bUseSrcNITFMetadata = |
4774 | 20 | CPLFetchBool(papszOptions, "USE_SRC_NITF_METADATA", true); |
4775 | 20 | CSLConstList papszSrcMD = poSrcDS->GetMetadata(); |
4776 | | |
4777 | 240 | for (int iMD = 0; bUseSrcNITFMetadata && papszSrcMD && papszSrcMD[iMD]; |
4778 | 220 | iMD++) |
4779 | 220 | { |
4780 | 220 | bool bPreserveSrcMDAsCreationOption = false; |
4781 | 220 | if (STARTS_WITH_CI(papszSrcMD[iMD], "NITF_BLOCKA")) |
4782 | 0 | { |
4783 | 0 | bPreserveSrcMDAsCreationOption = |
4784 | 0 | CSLPartialFindString(papszOptions, "BLOCKA_") < 0 && |
4785 | 0 | CSLPartialFindString(papszOptions, "TRE=BLOCKA=") < 0; |
4786 | 0 | } |
4787 | 220 | else if (STARTS_WITH_CI(papszSrcMD[iMD], "NITF_FHDR")) |
4788 | 0 | { |
4789 | 0 | bPreserveSrcMDAsCreationOption = |
4790 | 0 | CSLFetchNameValue(papszOptions, "FHDR") == nullptr; |
4791 | 0 | } |
4792 | 220 | if (bPreserveSrcMDAsCreationOption) |
4793 | 0 | { |
4794 | 0 | char *pszName = nullptr; |
4795 | 0 | const char *pszValue = CPLParseNameValue(papszSrcMD[iMD], &pszName); |
4796 | 0 | if (pszName && aosOptions.FetchNameValue(pszName + 5) == nullptr) |
4797 | 0 | { |
4798 | 0 | aosOptions.SetNameValue(pszName + 5, pszValue); |
4799 | 0 | } |
4800 | 0 | CPLFree(pszName); |
4801 | 0 | } |
4802 | 220 | } |
4803 | | |
4804 | | /* -------------------------------------------------------------------- */ |
4805 | | /* Copy TRE definitions as creation options, unless they are */ |
4806 | | /* already set as creation options. */ |
4807 | | /* -------------------------------------------------------------------- */ |
4808 | 20 | papszSrcMD = poSrcDS->GetMetadata("TRE"); |
4809 | | |
4810 | 20 | for (int iMD = 0; bUseSrcNITFMetadata && papszSrcMD && papszSrcMD[iMD]; |
4811 | 20 | iMD++) |
4812 | 0 | { |
4813 | 0 | CPLString osTRE; |
4814 | |
|
4815 | 0 | if (STARTS_WITH_CI(papszSrcMD[iMD], "RPFHDR") || |
4816 | 0 | STARTS_WITH_CI(papszSrcMD[iMD], "RPFIMG") || |
4817 | 0 | STARTS_WITH_CI(papszSrcMD[iMD], "RPFDES")) |
4818 | 0 | { |
4819 | | /* Do not copy RPF TRE. They contain absolute offsets */ |
4820 | | /* No chance that they make sense in the new NITF file */ |
4821 | 0 | continue; |
4822 | 0 | } |
4823 | 0 | if (STARTS_WITH_CI(papszSrcMD[iMD], "BLOCKA") && |
4824 | 0 | CSLPartialFindString(papszOptions, "BLOCKA_") >= 0) |
4825 | 0 | { |
4826 | | /* Do not copy BLOCKA TRE if there are BLOCKA_ creation options */ |
4827 | 0 | continue; |
4828 | 0 | } |
4829 | | |
4830 | 0 | osTRE = "TRE="; |
4831 | 0 | osTRE += papszSrcMD[iMD]; |
4832 | |
|
4833 | 0 | char *pszName = nullptr; |
4834 | 0 | CPLParseNameValue(papszSrcMD[iMD], &pszName); |
4835 | 0 | if (pszName != nullptr && |
4836 | 0 | CSLPartialFindString(papszOptions, CPLSPrintf("TRE=%s", pszName)) < |
4837 | 0 | 0) |
4838 | 0 | { |
4839 | 0 | aosOptions.AddString(osTRE); |
4840 | 0 | } |
4841 | 0 | CPLFree(pszName); |
4842 | 0 | } |
4843 | | |
4844 | | /* -------------------------------------------------------------------- */ |
4845 | | /* Set if we can set IREP. */ |
4846 | | /* -------------------------------------------------------------------- */ |
4847 | 20 | if (aosOptions.FetchNameValue("IREP") == nullptr) |
4848 | 20 | { |
4849 | 20 | if (((poSrcDS->GetRasterCount() == 3 && bJPEG) || |
4850 | 20 | (poSrcDS->GetRasterCount() >= 3 && !bJPEG)) && |
4851 | 19 | poSrcDS->GetRasterBand(1)->GetColorInterpretation() == |
4852 | 19 | GCI_RedBand && |
4853 | 5 | poSrcDS->GetRasterBand(2)->GetColorInterpretation() == |
4854 | 5 | GCI_GreenBand && |
4855 | 0 | poSrcDS->GetRasterBand(3)->GetColorInterpretation() == GCI_BlueBand) |
4856 | 0 | { |
4857 | 0 | if (bJPEG) |
4858 | 0 | aosOptions.SetNameValue("IREP", "YCbCr601"); |
4859 | 0 | else |
4860 | 0 | aosOptions.SetNameValue("IREP", "RGB"); |
4861 | 0 | } |
4862 | 20 | else if (poSrcDS->GetRasterCount() >= 3 && !bJPEG && |
4863 | 19 | poSrcDS->GetRasterBand(1)->GetColorInterpretation() == |
4864 | 19 | GCI_BlueBand && |
4865 | 0 | poSrcDS->GetRasterBand(2)->GetColorInterpretation() == |
4866 | 0 | GCI_GreenBand && |
4867 | 0 | poSrcDS->GetRasterBand(3)->GetColorInterpretation() == |
4868 | 0 | GCI_RedBand && |
4869 | 0 | aosOptions.FetchNameValue("IREPBAND") == nullptr) |
4870 | 0 | { |
4871 | 0 | aosOptions.SetNameValue("IREP", "MULTI"); |
4872 | 0 | std::string osIREPBAND = "B,G,R"; |
4873 | 0 | for (int i = 4; i <= poSrcDS->GetRasterCount(); ++i) |
4874 | 0 | osIREPBAND += ",M"; |
4875 | 0 | aosOptions.SetNameValue("IREPBAND", osIREPBAND.c_str()); |
4876 | 0 | } |
4877 | 20 | else if (poSrcDS->GetRasterCount() == 1 && eType == GDT_UInt8 && |
4878 | 0 | poBand1->GetColorTable() != nullptr) |
4879 | 0 | { |
4880 | 0 | aosOptions.SetNameValue("IREP", "RGB/LUT"); |
4881 | 0 | aosOptions.SetNameValue( |
4882 | 0 | "LUT_SIZE", |
4883 | 0 | CPLString().Printf( |
4884 | 0 | "%d", poBand1->GetColorTable()->GetColorEntryCount())); |
4885 | 0 | } |
4886 | 20 | else if (GDALDataTypeIsComplex(eType)) |
4887 | 0 | aosOptions.SetNameValue("IREP", "NODISPLY"); |
4888 | | |
4889 | 20 | else |
4890 | 20 | aosOptions.SetNameValue("IREP", "MONO"); |
4891 | 20 | } |
4892 | | |
4893 | | /* -------------------------------------------------------------------- */ |
4894 | | /* Do we have lat/long georeferencing information? */ |
4895 | | /* -------------------------------------------------------------------- */ |
4896 | 20 | GDALGeoTransform gt; |
4897 | 20 | bool bWriteGeoTransform = false; |
4898 | 20 | bool bWriteGCPs = false; |
4899 | 20 | int nZone = 0; |
4900 | | |
4901 | 20 | int nGCIFFlags = GCIF_PAM_DEFAULT; |
4902 | 20 | double dfIGEOLOULX = 0; |
4903 | 20 | double dfIGEOLOULY = 0; |
4904 | 20 | double dfIGEOLOURX = 0; |
4905 | 20 | double dfIGEOLOURY = 0; |
4906 | 20 | double dfIGEOLOLRX = 0; |
4907 | 20 | double dfIGEOLOLRY = 0; |
4908 | 20 | double dfIGEOLOLLX = 0; |
4909 | 20 | double dfIGEOLOLLY = 0; |
4910 | 20 | bool bManualWriteOfIGEOLO = false; |
4911 | | |
4912 | 20 | const OGRSpatialReference *poSrcSRS = poSrcDS->GetSpatialRef(); |
4913 | 20 | if (!poSrcSRS) |
4914 | 4 | poSrcSRS = poSrcDS->GetGCPSpatialRef(); |
4915 | 20 | if (poSrcSRS) |
4916 | 17 | { |
4917 | 17 | OGRSpatialReference oSRS_WGS84; |
4918 | 17 | oSRS_WGS84.SetWellKnownGeogCS("WGS84"); |
4919 | 17 | oSRS_WGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
4920 | | |
4921 | 17 | const bool bIsCADRGPolarAzimuthalEquidistant = |
4922 | 17 | bIsCADRG && poSrcSRS->IsProjected() && |
4923 | 0 | EQUAL(poSrcSRS->GetAttrValue("PROJECTION"), |
4924 | 17 | SRS_PT_AZIMUTHAL_EQUIDISTANT) && |
4925 | 0 | poSrcSRS->GetSemiMajor() == SRS_WGS84_SEMIMAJOR && |
4926 | 0 | poSrcSRS->GetInvFlattening() == 0.0 && |
4927 | 0 | poSrcSRS->GetPrimeMeridian() == 0.0 && |
4928 | 0 | poSrcSRS->GetProjParm(SRS_PP_LONGITUDE_OF_CENTER) == 0.0 && |
4929 | 0 | std::fabs(poSrcSRS->GetProjParm(SRS_PP_LATITUDE_OF_CENTER)) == 90.0; |
4930 | 17 | if (bIsCADRG && !poSrcSRS->IsGeographic() && |
4931 | 0 | !bIsCADRGPolarAzimuthalEquidistant) |
4932 | 0 | { |
4933 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
4934 | 0 | "CADRG only support geographic CRS or polar azimuthal " |
4935 | 0 | "equidistant"); |
4936 | 0 | return nullptr; |
4937 | 0 | } |
4938 | | |
4939 | 17 | if (!bIsCADRGPolarAzimuthalEquidistant) |
4940 | 17 | { |
4941 | | /* NITF is only WGS84 */ |
4942 | 17 | if (!poSrcSRS->IsSameGeogCS(&oSRS_WGS84)) |
4943 | 16 | { |
4944 | 16 | CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported, |
4945 | 16 | "NITF only supports WGS84 geographic and UTM " |
4946 | 16 | "projections."); |
4947 | 16 | if (bStrict) |
4948 | 0 | { |
4949 | 0 | return nullptr; |
4950 | 0 | } |
4951 | 16 | } |
4952 | 17 | } |
4953 | | |
4954 | 17 | const char *pszICORDS = aosOptions.FetchNameValue("ICORDS"); |
4955 | | |
4956 | | /* -------------------------------------------------------------------- |
4957 | | */ |
4958 | | /* Should we write DIGEST Spatial Data Extension TRE ? */ |
4959 | | /* -------------------------------------------------------------------- |
4960 | | */ |
4961 | 17 | const char *pszSDE_TRE = aosOptions.FetchNameValue("SDE_TRE"); |
4962 | 17 | const bool bSDE_TRE = pszSDE_TRE && CPLTestBool(pszSDE_TRE); |
4963 | 17 | if (bSDE_TRE) |
4964 | 0 | { |
4965 | 0 | if (poSrcSRS->IsGeographic() && |
4966 | 0 | poSrcSRS->GetPrimeMeridian() == 0.0 && |
4967 | 0 | poSrcDS->GetGeoTransform(gt) == CE_None && gt.xrot == 0.0 && |
4968 | 0 | gt.yrot == 0.0 && gt.yscale < 0.0) |
4969 | 0 | { |
4970 | | /* Override ICORDS to G if necessary */ |
4971 | 0 | if (pszICORDS != nullptr && EQUAL(pszICORDS, "D")) |
4972 | 0 | { |
4973 | 0 | aosOptions.SetNameValue("ICORDS", "G"); |
4974 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
4975 | 0 | "Forcing ICORDS=G when writing GEOLOB"); |
4976 | 0 | } |
4977 | 0 | else |
4978 | 0 | { |
4979 | | /* Code a bit below will complain with other ICORDS value */ |
4980 | 0 | } |
4981 | |
|
4982 | 0 | if (CSLPartialFindString(aosOptions.List(), "TRE=GEOLOB=") != |
4983 | 0 | -1) |
4984 | 0 | { |
4985 | 0 | CPLDebug("NITF", "GEOLOB TRE was explicitly defined " |
4986 | 0 | "before. Overriding it with current " |
4987 | 0 | "georeferencing info."); |
4988 | 0 | } |
4989 | | |
4990 | | /* Structure of SDE TRE documented here */ |
4991 | | // http://www.gwg.nga.mil/ntb/baseline/docs/digest/part2_annex_d.pdf |
4992 | | |
4993 | | /* -------------------------------------------------------------------- |
4994 | | */ |
4995 | | /* Write GEOLOB TRE */ |
4996 | | /* -------------------------------------------------------------------- |
4997 | | */ |
4998 | | // Extra (useless) bytes to avoid CLang 18 erroneous -Wformat-truncation |
4999 | 0 | constexpr int MARGIN_FOR_CLANG_18 = 2; |
5000 | 0 | char szGEOLOB[48 + 1 + MARGIN_FOR_CLANG_18]; |
5001 | 0 | const double dfARV = 360.0 / gt.xscale; |
5002 | 0 | const double dfBRV = 360.0 / -gt.yscale; |
5003 | 0 | const double dfLSO = gt.xorig; |
5004 | 0 | const double dfPSO = gt.yorig; |
5005 | 0 | CPLsnprintf(szGEOLOB, sizeof(szGEOLOB), |
5006 | 0 | "%09d%09d%#+015.10f%#+015.10f", |
5007 | 0 | static_cast<int>(dfARV + 0.5), |
5008 | 0 | static_cast<int>(dfBRV + 0.5), dfLSO, dfPSO); |
5009 | |
|
5010 | 0 | CPLString osGEOLOB("TRE=GEOLOB="); |
5011 | 0 | osGEOLOB += szGEOLOB; |
5012 | 0 | aosOptions.AddString(osGEOLOB); |
5013 | | |
5014 | | /* -------------------------------------------------------------------- |
5015 | | */ |
5016 | | /* Write GEOPSB TRE if not already explicitly provided */ |
5017 | | /* -------------------------------------------------------------------- |
5018 | | */ |
5019 | 0 | if (CSLPartialFindString(aosOptions.List(), |
5020 | 0 | "FILE_TRE=GEOPSB=") == -1 && |
5021 | 0 | CSLPartialFindString(aosOptions.List(), "TRE=GEOPSB=") == |
5022 | 0 | -1) |
5023 | 0 | { |
5024 | 0 | char szGEOPSB[443 + 1]; |
5025 | 0 | memset(szGEOPSB, ' ', 443); |
5026 | 0 | szGEOPSB[443] = 0; |
5027 | 0 | #define WRITE_STR_NOSZ(dst, src) memcpy(dst, src, strlen(src)) |
5028 | 0 | char *pszGEOPSB = szGEOPSB; |
5029 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "GEO"); |
5030 | 0 | pszGEOPSB += 3; |
5031 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "DEG"); |
5032 | 0 | pszGEOPSB += 3; |
5033 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "World Geodetic System 1984"); |
5034 | 0 | pszGEOPSB += 80; |
5035 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "WGE"); |
5036 | 0 | pszGEOPSB += 4; |
5037 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "World Geodetic System 1984"); |
5038 | 0 | pszGEOPSB += 80; |
5039 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "WE"); |
5040 | 0 | pszGEOPSB += 3; |
5041 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "Geodetic"); |
5042 | 0 | pszGEOPSB += 80; /* DVR */ |
5043 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "GEOD"); |
5044 | 0 | pszGEOPSB += 4; /* VDCDVR */ |
5045 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "Mean Sea"); |
5046 | 0 | pszGEOPSB += 80; /* SDA */ |
5047 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "MSL"); |
5048 | 0 | pszGEOPSB += 4; /* VDCSDA */ |
5049 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "000000000000000"); |
5050 | 0 | pszGEOPSB += 15; /* ZOR */ |
5051 | 0 | pszGEOPSB += 3; /* GRD */ |
5052 | 0 | pszGEOPSB += 80; /* GRN */ |
5053 | 0 | WRITE_STR_NOSZ(pszGEOPSB, "0000"); |
5054 | 0 | pszGEOPSB += 4; /* ZNA */ |
5055 | 0 | CPL_IGNORE_RET_VAL(pszGEOPSB); |
5056 | 0 | CPLAssert(pszGEOPSB == szGEOPSB + 443); |
5057 | |
|
5058 | 0 | CPLString osGEOPSB("FILE_TRE=GEOPSB="); |
5059 | 0 | osGEOPSB += szGEOPSB; |
5060 | 0 | aosOptions.AddString(osGEOPSB); |
5061 | 0 | } |
5062 | 0 | else |
5063 | 0 | { |
5064 | 0 | CPLDebug("NITF", "GEOPSB TRE was explicitly defined " |
5065 | 0 | "before. Keeping it."); |
5066 | 0 | } |
5067 | 0 | } |
5068 | 0 | else |
5069 | 0 | { |
5070 | 0 | CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported, |
5071 | 0 | "Georeferencing info isn't compatible with writing a " |
5072 | 0 | "GEOLOB TRE (only geographic SRS handled for now)"); |
5073 | 0 | if (bStrict) |
5074 | 0 | { |
5075 | 0 | return nullptr; |
5076 | 0 | } |
5077 | 0 | } |
5078 | 0 | } |
5079 | | |
5080 | 17 | bWriteGeoTransform = (poSrcDS->GetGeoTransform(gt) == CE_None); |
5081 | 17 | bWriteGCPs = (!bWriteGeoTransform && poSrcDS->GetGCPCount() == 4); |
5082 | | |
5083 | 17 | int bNorth; |
5084 | 17 | const bool bHasIGEOLO = aosOptions.FetchNameValue("IGEOLO") != nullptr; |
5085 | 17 | if (bHasIGEOLO && pszICORDS == nullptr) |
5086 | 0 | { |
5087 | 0 | CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_AppDefined, |
5088 | 0 | "IGEOLO specified, but ICORDS not.%s", |
5089 | 0 | bStrict ? "" : " Ignoring IGEOLO"); |
5090 | 0 | if (bStrict) |
5091 | 0 | { |
5092 | 0 | return nullptr; |
5093 | 0 | } |
5094 | 0 | } |
5095 | | |
5096 | 17 | if (aosOptions.FetchNameValue("IGEOLO") != nullptr && |
5097 | 0 | pszICORDS != nullptr) |
5098 | 0 | { |
5099 | | // if both IGEOLO and ICORDS are specified, do not try to write |
5100 | | // computed values |
5101 | |
|
5102 | 0 | bWriteGeoTransform = false; |
5103 | 0 | bWriteGCPs = false; |
5104 | 0 | nGCIFFlags &= ~GCIF_PROJECTION; |
5105 | 0 | nGCIFFlags &= ~GCIF_GEOTRANSFORM; |
5106 | 0 | } |
5107 | 17 | else if (poSrcSRS->IsGeographic() && |
5108 | 4 | poSrcSRS->GetPrimeMeridian() == 0.0) |
5109 | 4 | { |
5110 | 4 | if (pszICORDS == nullptr) |
5111 | 4 | { |
5112 | 4 | aosOptions.SetNameValue("ICORDS", "G"); |
5113 | 4 | } |
5114 | 0 | else if (EQUAL(pszICORDS, "G") || EQUAL(pszICORDS, "D")) |
5115 | 0 | { |
5116 | | /* Do nothing */ |
5117 | 0 | } |
5118 | 0 | else |
5119 | 0 | { |
5120 | 0 | CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported, |
5121 | 0 | "Inconsistent ICORDS value with SRS : %s%s.\n", |
5122 | 0 | pszICORDS, |
5123 | 0 | (!bStrict) ? ". Setting it to G instead" : ""); |
5124 | 0 | if (bStrict) |
5125 | 0 | { |
5126 | 0 | return nullptr; |
5127 | 0 | } |
5128 | 0 | aosOptions.SetNameValue("ICORDS", "G"); |
5129 | 0 | } |
5130 | 4 | } |
5131 | | |
5132 | 13 | else if (poSrcSRS->GetUTMZone(&bNorth) > 0) |
5133 | 6 | { |
5134 | 6 | const char *pszComputedICORDS = bNorth ? "N" : "S"; |
5135 | 6 | nZone = poSrcSRS->GetUTMZone(nullptr); |
5136 | 6 | if (pszICORDS == nullptr) |
5137 | 6 | { |
5138 | 6 | aosOptions.SetNameValue("ICORDS", pszComputedICORDS); |
5139 | 6 | } |
5140 | 0 | else if (EQUAL(pszICORDS, pszComputedICORDS)) |
5141 | 0 | { |
5142 | | // ok |
5143 | 0 | } |
5144 | 0 | else if ((EQUAL(pszICORDS, "G") || EQUAL(pszICORDS, "D")) && |
5145 | 0 | bWriteGeoTransform) |
5146 | 0 | { |
5147 | | // Reproject UTM corner coordinates to geographic. |
5148 | | // This can be used when there is no way to write an |
5149 | | // equatorial image whose one of the northing value is below |
5150 | | // -1e6 |
5151 | |
|
5152 | 0 | const int nXSize = poSrcDS->GetRasterXSize(); |
5153 | 0 | const int nYSize = poSrcDS->GetRasterYSize(); |
5154 | |
|
5155 | 0 | dfIGEOLOULX = gt.xorig + 0.5 * gt.xscale + 0.5 * gt.xrot; |
5156 | 0 | dfIGEOLOULY = gt.yorig + 0.5 * gt.yrot + 0.5 * gt.yscale; |
5157 | 0 | dfIGEOLOURX = dfIGEOLOULX + gt.xscale * (nXSize - 1); |
5158 | 0 | dfIGEOLOURY = dfIGEOLOULY + gt.yrot * (nXSize - 1); |
5159 | 0 | dfIGEOLOLRX = dfIGEOLOULX + gt.xscale * (nXSize - 1) + |
5160 | 0 | gt.xrot * (nYSize - 1); |
5161 | 0 | dfIGEOLOLRY = dfIGEOLOULY + gt.yrot * (nXSize - 1) + |
5162 | 0 | gt.yscale * (nYSize - 1); |
5163 | 0 | dfIGEOLOLLX = dfIGEOLOULX + gt.xrot * (nYSize - 1); |
5164 | 0 | dfIGEOLOLLY = dfIGEOLOULY + gt.yscale * (nYSize - 1); |
5165 | |
|
5166 | 0 | auto poCT = std::unique_ptr<OGRCoordinateTransformation>( |
5167 | 0 | OGRCreateCoordinateTransformation(poSrcSRS, &oSRS_WGS84)); |
5168 | 0 | if (poCT && poCT->Transform(1, &dfIGEOLOULX, &dfIGEOLOULY) && |
5169 | 0 | poCT->Transform(1, &dfIGEOLOURX, &dfIGEOLOURY) && |
5170 | 0 | poCT->Transform(1, &dfIGEOLOLRX, &dfIGEOLOLRY) && |
5171 | 0 | poCT->Transform(1, &dfIGEOLOLLX, &dfIGEOLOLLY)) |
5172 | 0 | { |
5173 | 0 | nZone = 0; |
5174 | 0 | bWriteGeoTransform = false; |
5175 | 0 | bManualWriteOfIGEOLO = true; |
5176 | 0 | nGCIFFlags &= ~GCIF_PROJECTION; |
5177 | 0 | nGCIFFlags &= ~GCIF_GEOTRANSFORM; |
5178 | 0 | } |
5179 | 0 | else |
5180 | 0 | { |
5181 | 0 | CPLError( |
5182 | 0 | CE_Failure, CPLE_AppDefined, |
5183 | 0 | "Cannot reproject UTM coordinates to geographic ones"); |
5184 | 0 | return nullptr; |
5185 | 0 | } |
5186 | 0 | } |
5187 | 0 | else |
5188 | 0 | { |
5189 | 0 | CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported, |
5190 | 0 | "Inconsistent ICORDS value with SRS : %s.", pszICORDS); |
5191 | 0 | if (bStrict) |
5192 | 0 | { |
5193 | 0 | return nullptr; |
5194 | 0 | } |
5195 | 0 | } |
5196 | 6 | } |
5197 | 7 | else if (bIsCADRGPolarAzimuthalEquidistant) |
5198 | 0 | { |
5199 | 0 | OGREnvelope sExtent; |
5200 | 0 | poSrcDS->GetExtent(&sExtent); |
5201 | |
|
5202 | 0 | aosOptions.SetNameValue("ICORDS", "G"); |
5203 | 0 | dfIGEOLOULX = sExtent.MinX; |
5204 | 0 | dfIGEOLOULY = sExtent.MaxY; |
5205 | 0 | dfIGEOLOURX = sExtent.MaxX; |
5206 | 0 | dfIGEOLOURY = sExtent.MaxY; |
5207 | 0 | dfIGEOLOLLX = sExtent.MinX; |
5208 | 0 | dfIGEOLOLLY = sExtent.MinY; |
5209 | 0 | dfIGEOLOLRX = sExtent.MaxX; |
5210 | 0 | dfIGEOLOLRY = sExtent.MinY; |
5211 | 0 | auto poCT = std::unique_ptr<OGRCoordinateTransformation>( |
5212 | 0 | OGRCreateCoordinateTransformation(poSrcSRS, &oSRS_WGS84)); |
5213 | 0 | if (poCT && poCT->Transform(1, &dfIGEOLOULX, &dfIGEOLOULY) && |
5214 | 0 | poCT->Transform(1, &dfIGEOLOURX, &dfIGEOLOURY) && |
5215 | 0 | poCT->Transform(1, &dfIGEOLOLRX, &dfIGEOLOLRY) && |
5216 | 0 | poCT->Transform(1, &dfIGEOLOLLX, &dfIGEOLOLLY)) |
5217 | 0 | { |
5218 | 0 | nZone = 0; |
5219 | 0 | bWriteGeoTransform = false; |
5220 | 0 | bManualWriteOfIGEOLO = true; |
5221 | 0 | nGCIFFlags &= ~GCIF_PROJECTION; |
5222 | 0 | nGCIFFlags &= ~GCIF_GEOTRANSFORM; |
5223 | 0 | } |
5224 | 0 | else |
5225 | 0 | { |
5226 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
5227 | 0 | "Cannot reproject azimuthal equidistant coordinates " |
5228 | 0 | "to geographic ones"); |
5229 | 0 | return nullptr; |
5230 | 0 | } |
5231 | 0 | } |
5232 | 7 | else |
5233 | 7 | { |
5234 | 7 | CPLError( |
5235 | 7 | (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported, |
5236 | 7 | "NITF only supports WGS84 geographic and UTM projections."); |
5237 | 7 | if (bStrict) |
5238 | 0 | { |
5239 | 0 | return nullptr; |
5240 | 0 | } |
5241 | 7 | } |
5242 | 17 | } |
5243 | | |
5244 | | /* -------------------------------------------------------------------- */ |
5245 | | /* Do we have RPC information? */ |
5246 | | /* -------------------------------------------------------------------- */ |
5247 | 20 | if (!bUseSrcNITFMetadata) |
5248 | 0 | nGCIFFlags &= ~GCIF_METADATA; |
5249 | | |
5250 | 20 | CSLConstList papszRPC = poSrcDS->GetMetadata("RPC"); |
5251 | 20 | if (papszRPC != nullptr && bUseSrcNITFMetadata && |
5252 | 0 | CPLFetchBool(aosOptions.List(), "RPC00B", true)) |
5253 | 0 | { |
5254 | 0 | if (CSLPartialFindString(aosOptions.List(), "TRE=RPC00B=") >= 0) |
5255 | 0 | { |
5256 | 0 | CPLDebug("NITF", |
5257 | 0 | "Both TRE=RPC00B and RPC metadata are available. " |
5258 | 0 | "Ignoring RPC metadata and re-using source TRE=RPC00B"); |
5259 | 0 | } |
5260 | 0 | else |
5261 | 0 | { |
5262 | 0 | int bPrecisionLoss = FALSE; |
5263 | 0 | char *pszRPC = |
5264 | 0 | NITFFormatRPC00BFromMetadata(papszRPC, &bPrecisionLoss); |
5265 | 0 | if (pszRPC == nullptr) |
5266 | 0 | { |
5267 | 0 | CPLError( |
5268 | 0 | (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported, |
5269 | 0 | "Cannot format a valid RPC00B TRE from the RPC metadata"); |
5270 | 0 | if (bStrict) |
5271 | 0 | { |
5272 | 0 | return nullptr; |
5273 | 0 | } |
5274 | 0 | } |
5275 | 0 | else |
5276 | 0 | { |
5277 | 0 | CPLString osRPC00B("TRE=RPC00B="); |
5278 | 0 | osRPC00B += pszRPC; |
5279 | 0 | aosOptions.AddString(osRPC00B); |
5280 | | |
5281 | | // If no precision loss occurred during RPC conversion, then |
5282 | | // we can suppress it from PAM |
5283 | 0 | if (!bPrecisionLoss) |
5284 | 0 | nGCIFFlags &= ~GCIF_METADATA; |
5285 | 0 | } |
5286 | 0 | CPLFree(pszRPC); |
5287 | 0 | } |
5288 | 0 | } |
5289 | 20 | else if (!CPLFetchBool(aosOptions.List(), "RPC00B", true)) |
5290 | 0 | { |
5291 | 0 | int nIdx = CSLPartialFindString(aosOptions.List(), "TRE=RPC00B="); |
5292 | 0 | if (nIdx >= 0) |
5293 | 0 | { |
5294 | 0 | aosOptions = |
5295 | 0 | CSLRemoveStrings(aosOptions.StealList(), nIdx, 1, nullptr); |
5296 | 0 | } |
5297 | 0 | } |
5298 | | |
5299 | 20 | if (papszRPC != nullptr && CPLFetchBool(aosOptions.List(), "RPCTXT", false)) |
5300 | 0 | { |
5301 | 0 | GDALWriteRPCTXTFile(pszFilename, papszRPC); |
5302 | 0 | } |
5303 | | |
5304 | | /* -------------------------------------------------------------------- */ |
5305 | | /* Create the output file. */ |
5306 | | /* -------------------------------------------------------------------- */ |
5307 | 20 | const int nXSize = poSrcDS->GetRasterXSize(); |
5308 | 20 | const int nYSize = poSrcDS->GetRasterYSize(); |
5309 | 20 | const char *pszPVType = GDALToNITFDataType(eType); |
5310 | | |
5311 | 20 | if (pszPVType == nullptr) |
5312 | 0 | { |
5313 | 0 | return nullptr; |
5314 | 0 | } |
5315 | | |
5316 | 20 | int nABPP = GDALGetDataTypeSizeBits(eType); |
5317 | 20 | if (const char *pszABPP = aosOptions.FetchNameValue("ABPP")) |
5318 | 0 | { |
5319 | 0 | nABPP = atoi(pszABPP); |
5320 | 0 | } |
5321 | 20 | else if (const char *pszNBITS = aosOptions.FetchNameValueDef( |
5322 | 20 | "NBITS", poBand1->GetMetadataItem("NBITS", "IMAGE_STRUCTURE"))) |
5323 | 5 | { |
5324 | 5 | aosOptions.SetNameValue("ABPP", pszNBITS); |
5325 | 5 | nABPP = atoi(pszNBITS); |
5326 | 5 | } |
5327 | | |
5328 | 20 | if (poJ2KDriver != nullptr && |
5329 | 0 | EQUAL(poJ2KDriver->GetDescription(), "JP2ECW")) |
5330 | 0 | { |
5331 | 0 | if (STARTS_WITH_CI(aosOptions.FetchNameValueDef("PROFILE", "NPJE"), |
5332 | 0 | "NPJE") && |
5333 | 0 | (nXSize >= 1024 || nYSize >= 1024)) |
5334 | 0 | { |
5335 | 0 | int nBlockXSize = |
5336 | 0 | atoi(aosOptions.FetchNameValueDef("BLOCKXSIZE", "0")); |
5337 | 0 | int nBlockYSize = |
5338 | 0 | atoi(aosOptions.FetchNameValueDef("BLOCKYSIZE", "0")); |
5339 | 0 | if (nBlockXSize > 0 && nBlockXSize != 1024) |
5340 | 0 | { |
5341 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
5342 | 0 | "BLOCKXSIZE != 1024 inconsistent with PROFILE=NPJE"); |
5343 | 0 | } |
5344 | 0 | if (nBlockYSize > 0 && nBlockYSize != 1024) |
5345 | 0 | { |
5346 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
5347 | 0 | "BLOCKYSIZE != 1024 inconsistent with PROFILE=NPJE"); |
5348 | 0 | } |
5349 | 0 | if (nBlockXSize == 0) |
5350 | 0 | { |
5351 | 0 | aosOptions.SetNameValue("BLOCKXSIZE", "1024"); |
5352 | 0 | } |
5353 | 0 | if (nBlockYSize == 0) |
5354 | 0 | { |
5355 | 0 | aosOptions.SetNameValue("BLOCKYSIZE", "1024"); |
5356 | 0 | } |
5357 | 0 | } |
5358 | 0 | } |
5359 | 20 | else if (poJ2KDriver != nullptr && |
5360 | 0 | EQUAL(poJ2KDriver->GetDescription(), "JP2OPENJPEG")) |
5361 | 0 | { |
5362 | 0 | const char *pszProfile = aosOptions.FetchNameValue("PROFILE"); |
5363 | 0 | if (pszProfile && EQUAL(pszProfile, "EPJE")) |
5364 | 0 | { |
5365 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
5366 | 0 | "PROFILE=EPJE not handled by JP2OPENJPEG driver"); |
5367 | 0 | } |
5368 | |
|
5369 | 0 | int nBlockXSize = atoi(aosOptions.FetchNameValueDef("BLOCKXSIZE", "0")); |
5370 | 0 | int nBlockYSize = atoi(aosOptions.FetchNameValueDef("BLOCKYSIZE", "0")); |
5371 | 0 | if (pszProfile && STARTS_WITH_CI(pszProfile, "NPJE") && |
5372 | 0 | ((nBlockXSize != 0 && nBlockXSize != 1024) || |
5373 | 0 | (nBlockYSize != 0 && nBlockYSize != 1024))) |
5374 | 0 | { |
5375 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
5376 | 0 | "PROFILE=NPJE implies 1024x1024 tiles"); |
5377 | 0 | } |
5378 | |
|
5379 | 0 | if (nXSize >= 1024 || nYSize >= 1024 || |
5380 | 0 | (pszProfile && STARTS_WITH_CI(pszProfile, "NPJE"))) |
5381 | 0 | { |
5382 | | // The JP2OPENJPEG driver uses 1024 block size by default. Set it |
5383 | | // explicitly for NITFCreate() purposes. |
5384 | 0 | if (nBlockXSize == 0) |
5385 | 0 | { |
5386 | 0 | aosOptions.SetNameValue("BLOCKXSIZE", "1024"); |
5387 | 0 | } |
5388 | 0 | if (nBlockYSize == 0) |
5389 | 0 | { |
5390 | 0 | aosOptions.SetNameValue("BLOCKYSIZE", "1024"); |
5391 | 0 | } |
5392 | 0 | } |
5393 | | |
5394 | | // Compose J2KLRA TRE for NPJE profiles |
5395 | 0 | if (pszProfile && STARTS_WITH_CI(pszProfile, "NPJE") && |
5396 | 0 | CPLTestBool(aosOptions.FetchNameValueDef("J2KLRA", "YES"))) |
5397 | 0 | { |
5398 | 0 | #if defined(__GNUC__) |
5399 | 0 | #pragma GCC diagnostic push |
5400 | 0 | #pragma GCC diagnostic ignored "-Warray-bounds" |
5401 | 0 | #endif |
5402 | | // See Table 2.3-3 - Target Bit Rates for Each Tile in Panchromatic |
5403 | | // Image Segments of STDI-0006 |
5404 | 0 | std::vector<double> adfBPP = { |
5405 | 0 | 0.03125, 0.0625, 0.125, 0.25, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, |
5406 | 0 | 1.1, 1.2, 1.3, 1.5, 1.7, 2.0, 2.3, 3.5, 3.9}; |
5407 | |
|
5408 | 0 | if (EQUAL(pszProfile, "NPJE") || |
5409 | 0 | EQUAL(pszProfile, "NPJE_NUMERICALLY_LOSSLESS")) |
5410 | 0 | { |
5411 | 0 | adfBPP.push_back(nABPP); |
5412 | 0 | } |
5413 | 0 | #if defined(__GNUC__) |
5414 | 0 | #pragma GCC diagnostic pop |
5415 | 0 | #endif |
5416 | |
|
5417 | 0 | double dfQuality = |
5418 | 0 | CPLAtof(aosOptions.FetchNameValueDef("QUALITY", "0")); |
5419 | 0 | double dfTarget = |
5420 | 0 | CPLAtof(aosOptions.FetchNameValueDef("TARGET", "0")); |
5421 | 0 | if (dfTarget > 0 && dfTarget < 100) |
5422 | 0 | dfQuality = 100. - dfTarget; |
5423 | |
|
5424 | 0 | if (dfQuality != 0.0) |
5425 | 0 | { |
5426 | 0 | for (size_t i = 0; i < adfBPP.size(); ++i) |
5427 | 0 | { |
5428 | | // the JP2OPENJPEG QUALITY setting is 100. / |
5429 | | // compression_ratio and compression_ratio = 8 / bpp |
5430 | 0 | double dfLayerQuality = 100.0 / (8.0 / adfBPP[i]); |
5431 | 0 | if (dfLayerQuality > dfQuality) |
5432 | 0 | { |
5433 | 0 | adfBPP[i] = dfQuality / 100.0 * nABPP; |
5434 | 0 | adfBPP.resize(i + 1); |
5435 | 0 | break; |
5436 | 0 | } |
5437 | 0 | } |
5438 | 0 | } |
5439 | |
|
5440 | 0 | CPLString osJ2KLRA("TRE=J2KLRA="); |
5441 | 0 | osJ2KLRA += '0'; // ORIG: 0=Original NPJE |
5442 | 0 | osJ2KLRA += "05"; // Number of wavelets decompositions. |
5443 | | // This corresponds to the value of the |
5444 | | // RESOLUTIONS JP2OPENJPEG creation option - 1 |
5445 | 0 | osJ2KLRA += CPLSPrintf("%05d", poSrcDS->GetRasterCount()); |
5446 | 0 | osJ2KLRA += CPLSPrintf("%03d", static_cast<int>(adfBPP.size())); |
5447 | 0 | for (size_t i = 0; i < adfBPP.size(); ++i) |
5448 | 0 | { |
5449 | 0 | osJ2KLRA += CPLSPrintf("%03d", static_cast<int>(i)); |
5450 | 0 | osJ2KLRA += CPLSPrintf("%09.6f", adfBPP[i]); |
5451 | 0 | } |
5452 | 0 | aosOptions.AddString(osJ2KLRA); |
5453 | 0 | } |
5454 | 0 | } |
5455 | | |
5456 | 20 | GDALOffsetPatcher::OffsetPatcher offsetPatcher; |
5457 | 20 | std::unique_ptr<CADRGInformation> CADRGInfo; |
5458 | | |
5459 | 20 | if (bIsCADRG) |
5460 | 0 | { |
5461 | 0 | aosOptions.SetNameValue("FHDR", "NITF02.00"); |
5462 | 0 | pszIC = "C4"; |
5463 | 0 | aosOptions.SetNameValue("IC", pszIC); |
5464 | 0 | if (aosOptions.FetchNameValue("IID1") == nullptr) |
5465 | 0 | { |
5466 | 0 | aosOptions.SetNameValue("IID1", "CADRG"); |
5467 | 0 | } |
5468 | 0 | if (aosOptions.FetchNameValue("FTITLE") == nullptr) |
5469 | 0 | { |
5470 | 0 | aosOptions.SetNameValue("FTITLE", CPLGetFilename(pszFilename)); |
5471 | 0 | } |
5472 | 0 | if (aosOptions.FetchNameValue("ITITLE") == nullptr) |
5473 | 0 | { |
5474 | 0 | aosOptions.SetNameValue("ITITLE", CPLGetFilename(pszFilename)); |
5475 | 0 | } |
5476 | 0 | if (aosOptions.FetchNameValue("ICAT") == nullptr) |
5477 | 0 | { |
5478 | 0 | aosOptions.SetNameValue("ICAT", "MAP"); |
5479 | 0 | } |
5480 | 0 | if (aosOptions.FetchNameValue("IMAG") == nullptr) |
5481 | 0 | { |
5482 | | // 0.67 = 100 (microns ADRG) / 150 (microns CADRG) |
5483 | 0 | aosOptions.SetNameValue("IMAG", "0.67"); |
5484 | 0 | } |
5485 | |
|
5486 | 0 | const int nRPFDESIdx = aosOptions.PartialFindString("DES=RPFDES="); |
5487 | 0 | if (nRPFDESIdx >= 0) |
5488 | 0 | aosOptions.RemoveStrings(nRPFDESIdx); |
5489 | | // +1 for the created RPFDES |
5490 | 0 | const int nDES = |
5491 | 0 | 1 + |
5492 | 0 | CPLStringList(CSLFetchNameValueMultiple(aosOptions.List(), "DES")) |
5493 | 0 | .size(); |
5494 | 0 | aosOptions.SetNameValue("NUMDES", CPLSPrintf("%d", nDES)); |
5495 | |
|
5496 | 0 | if (copyContext->nReciprocalScale == 0) |
5497 | 0 | { |
5498 | 0 | bool bGotDPI = false; |
5499 | 0 | { |
5500 | 0 | CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler); |
5501 | 0 | copyContext->nReciprocalScale = |
5502 | 0 | RPFGetCADRGClosestReciprocalScale(poSrcDS, 0, bGotDPI); |
5503 | 0 | } |
5504 | 0 | if (!bGotDPI) |
5505 | 0 | { |
5506 | | // the one from ADRG typically |
5507 | 0 | constexpr double DEFAULT_DPI = 254; |
5508 | 0 | copyContext->nReciprocalScale = |
5509 | 0 | RPFGetCADRGClosestReciprocalScale(poSrcDS, DEFAULT_DPI, |
5510 | 0 | bGotDPI); |
5511 | 0 | if (!copyContext->nReciprocalScale) |
5512 | 0 | return nullptr; |
5513 | 0 | } |
5514 | 0 | } |
5515 | | |
5516 | 0 | CADRGInfo = RPFFrameCreateCADRG_TREs(&offsetPatcher, pszFilename, |
5517 | 0 | poSrcDS, aosOptions, *copyContext); |
5518 | 0 | if (!CADRGInfo) |
5519 | 0 | { |
5520 | 0 | return nullptr; |
5521 | 0 | } |
5522 | | |
5523 | 0 | aosOptions.SetNameValue( |
5524 | 0 | "LUT_SIZE", |
5525 | 0 | CPLSPrintf("%d", CADRG_MAX_COLOR_ENTRY_COUNT + |
5526 | 0 | (CADRGInfo->HasTransparentPixels() ? 1 : 0))); |
5527 | 0 | } |
5528 | | |
5529 | 20 | int nIMIndex = 0; |
5530 | 20 | int nImageCount = 0; |
5531 | 20 | vsi_l_offset nImageOffset = 0; |
5532 | 20 | vsi_l_offset nICOffset = 0; |
5533 | 20 | if (!NITFCreateEx(pszFilename, nXSize, nYSize, poSrcDS->GetRasterCount(), |
5534 | 20 | GDALGetDataTypeSizeBits(eType), pszPVType, |
5535 | 20 | aosOptions.List(), &nIMIndex, &nImageCount, &nImageOffset, |
5536 | 20 | &nICOffset, &offsetPatcher)) |
5537 | 0 | { |
5538 | 0 | return nullptr; |
5539 | 0 | } |
5540 | | |
5541 | | /* ==================================================================== */ |
5542 | | /* JPEG2000 case. We need to write the data through a J2K */ |
5543 | | /* driver in pixel interleaved form. */ |
5544 | | /* ==================================================================== */ |
5545 | 20 | std::unique_ptr<NITFDataset> poDstDS; |
5546 | | |
5547 | 20 | if (bJPEG2000) |
5548 | 0 | { |
5549 | 0 | CPLString osDSName; |
5550 | 0 | osDSName.Printf("/vsisubfile/" CPL_FRMT_GUIB "_%d,%s", |
5551 | 0 | static_cast<GUIntBig>(nImageOffset), -1, pszFilename); |
5552 | |
|
5553 | 0 | std::unique_ptr<GDALDataset> poJ2KDataset; |
5554 | 0 | if (EQUAL(poJ2KDriver->GetDescription(), "JP2ECW")) |
5555 | 0 | { |
5556 | 0 | poJ2KDataset.reset(poJ2KDriver->CreateCopy( |
5557 | 0 | osDSName, poSrcDS, FALSE, NITFJP2ECWOptions(aosOptions).List(), |
5558 | 0 | pfnProgress, pProgressData)); |
5559 | 0 | } |
5560 | 0 | else if (EQUAL(poJ2KDriver->GetDescription(), "JP2KAK")) |
5561 | 0 | { |
5562 | 0 | poJ2KDataset.reset(poJ2KDriver->CreateCopy( |
5563 | 0 | osDSName, poSrcDS, FALSE, |
5564 | 0 | NITFJP2KAKOptions(aosOptions, nABPP).List(), pfnProgress, |
5565 | 0 | pProgressData)); |
5566 | 0 | } |
5567 | 0 | else if (EQUAL(poJ2KDriver->GetDescription(), "JP2OPENJPEG")) |
5568 | 0 | { |
5569 | 0 | poJ2KDataset.reset(poJ2KDriver->CreateCopy( |
5570 | 0 | osDSName, poSrcDS, FALSE, |
5571 | 0 | NITFJP2OPENJPEGOptions(poJ2KDriver, aosOptions, nABPP).List(), |
5572 | 0 | pfnProgress, pProgressData)); |
5573 | 0 | } |
5574 | 0 | if (poJ2KDataset == nullptr) |
5575 | 0 | { |
5576 | 0 | return nullptr; |
5577 | 0 | } |
5578 | | |
5579 | 0 | poJ2KDataset.reset(); |
5580 | | |
5581 | | // Now we need to figure out the actual length of the file |
5582 | | // and correct the image segment size information. |
5583 | 0 | const GIntBig nPixelCount = |
5584 | 0 | static_cast<GIntBig>(nXSize) * nYSize * poSrcDS->GetRasterCount(); |
5585 | |
|
5586 | 0 | bool bOK = NITFPatchImageLength(pszFilename, nullptr, nIMIndex, |
5587 | 0 | nImageOffset, nPixelCount, "C8", |
5588 | 0 | nICOffset, aosOptions.List()); |
5589 | 0 | if (nIMIndex + 1 == nImageCount) |
5590 | 0 | { |
5591 | 0 | bOK &= NITFWriteExtraSegments(pszFilename, nullptr, aosCgmMD.List(), |
5592 | 0 | aosTextMD.List(), nullptr, aosOptions, |
5593 | 0 | 0); |
5594 | 0 | } |
5595 | 0 | if (!bOK) |
5596 | 0 | { |
5597 | 0 | return nullptr; |
5598 | 0 | } |
5599 | | |
5600 | 0 | GDALOpenInfo oOpenInfo(pszFilename, GA_Update); |
5601 | 0 | poDstDS.reset(OpenInternal(&oOpenInfo, nullptr, true, |
5602 | 0 | nImageCount == 1 ? -1 : nIMIndex)); |
5603 | |
|
5604 | 0 | if (poDstDS == nullptr) |
5605 | 0 | { |
5606 | 0 | return nullptr; |
5607 | 0 | } |
5608 | 0 | } |
5609 | | |
5610 | | /* ==================================================================== */ |
5611 | | /* Loop copying bands to an uncompressed file. */ |
5612 | | /* ==================================================================== */ |
5613 | 20 | else if (bJPEG) |
5614 | 0 | { |
5615 | 0 | #ifdef JPEG_SUPPORTED |
5616 | 0 | std::unique_ptr<NITFFile, decltype(&NITFClose)> psFile( |
5617 | 0 | NITFOpen(pszFilename, TRUE), NITFClose); |
5618 | 0 | if (psFile == nullptr) |
5619 | 0 | { |
5620 | 0 | return nullptr; |
5621 | 0 | } |
5622 | | |
5623 | 0 | const bool bSuccess = |
5624 | 0 | NITFWriteJPEGImage(poSrcDS, psFile->fp, nImageOffset, |
5625 | 0 | aosOptions.List(), pfnProgress, pProgressData); |
5626 | |
|
5627 | 0 | if (!bSuccess) |
5628 | 0 | { |
5629 | 0 | return nullptr; |
5630 | 0 | } |
5631 | | |
5632 | | // Now we need to figure out the actual length of the file |
5633 | | // and correct the image segment size information. |
5634 | 0 | const GIntBig nPixelCount = |
5635 | 0 | static_cast<GIntBig>(nXSize) * nYSize * poSrcDS->GetRasterCount(); |
5636 | |
|
5637 | 0 | psFile.reset(); |
5638 | |
|
5639 | 0 | bool bOK = NITFPatchImageLength(pszFilename, nullptr, nIMIndex, |
5640 | 0 | nImageOffset, nPixelCount, pszIC, |
5641 | 0 | nICOffset, aosOptions.List()); |
5642 | 0 | if (nIMIndex + 1 == nImageCount) |
5643 | 0 | { |
5644 | 0 | bOK &= NITFWriteExtraSegments(pszFilename, nullptr, aosCgmMD.List(), |
5645 | 0 | aosTextMD.List(), nullptr, aosOptions, |
5646 | 0 | 0); |
5647 | 0 | } |
5648 | 0 | if (!bOK) |
5649 | 0 | { |
5650 | 0 | return nullptr; |
5651 | 0 | } |
5652 | | |
5653 | 0 | GDALOpenInfo oOpenInfo(pszFilename, GA_Update); |
5654 | 0 | poDstDS.reset(OpenInternal(&oOpenInfo, nullptr, true, |
5655 | 0 | nImageCount == 1 ? -1 : nIMIndex)); |
5656 | |
|
5657 | 0 | if (poDstDS == nullptr) |
5658 | 0 | { |
5659 | 0 | return nullptr; |
5660 | 0 | } |
5661 | 0 | #endif /* def JPEG_SUPPORTED */ |
5662 | 0 | } |
5663 | | |
5664 | 20 | else if (bIsCADRG) |
5665 | 0 | { |
5666 | 0 | auto fp = VSIFilesystemHandler::OpenStatic(pszFilename, "rb+"); |
5667 | 0 | if (!fp) |
5668 | 0 | { |
5669 | 0 | CPLError(CE_Failure, CPLE_FileIO, |
5670 | 0 | "Cannot re-open %s in read/write mode", pszFilename); |
5671 | 0 | return nullptr; |
5672 | 0 | } |
5673 | | |
5674 | 0 | if (!RPFFrameWriteCADRG_ImageContent(&offsetPatcher, fp.get(), poSrcDS, |
5675 | 0 | CADRGInfo.get())) |
5676 | 0 | { |
5677 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
5678 | 0 | "RPFFrameWriteCADRG_ImageContent() failed"); |
5679 | 0 | return nullptr; |
5680 | 0 | } |
5681 | | |
5682 | | // Now we need to figure out the actual length of the file |
5683 | | // and correct the image segment size information. |
5684 | 0 | const GIntBig nPixelCount = |
5685 | 0 | static_cast<GIntBig>(nXSize) * nYSize * poSrcDS->GetRasterCount(); |
5686 | |
|
5687 | 0 | bool bOK = NITFPatchImageLength(pszFilename, fp.get(), nIMIndex, |
5688 | 0 | nImageOffset, nPixelCount, pszIC, |
5689 | 0 | nICOffset, aosOptions.List()); |
5690 | | |
5691 | | // This will call RPFFrameWriteCADRG_RPFDES() |
5692 | 0 | bOK &= NITFWriteExtraSegments( |
5693 | 0 | pszFilename, fp.get(), aosCgmMD.List(), aosTextMD.List(), |
5694 | 0 | &offsetPatcher, aosOptions, copyContext->nReciprocalScale); |
5695 | |
|
5696 | 0 | if (!bOK || !offsetPatcher.Finalize(fp.get())) |
5697 | 0 | return nullptr; |
5698 | | |
5699 | 0 | fp.reset(); |
5700 | |
|
5701 | 0 | GDALOpenInfo oOpenInfo(pszFilename, GA_Update); |
5702 | 0 | poDstDS.reset(OpenInternal(&oOpenInfo, nullptr, true, -1)); |
5703 | 0 | if (poDstDS == nullptr) |
5704 | 0 | { |
5705 | 0 | return nullptr; |
5706 | 0 | } |
5707 | | |
5708 | 0 | const GDALColorTable *poCT = poSrcDS->GetRasterBand(1)->GetColorTable(); |
5709 | 0 | GDALColorTable oDstCT; |
5710 | 0 | const int nMaxCTEntries = CADRG_MAX_COLOR_ENTRY_COUNT + |
5711 | 0 | (CADRGInfo->HasTransparentPixels() ? 1 : 0); |
5712 | 0 | for (int i = 0; i < nMaxCTEntries; ++i) |
5713 | 0 | { |
5714 | 0 | if (i < poCT->GetColorEntryCount()) |
5715 | 0 | oDstCT.SetColorEntry(i, poCT->GetColorEntry(i)); |
5716 | 0 | else |
5717 | 0 | { |
5718 | 0 | GDALColorEntry entry = {0, 0, 0, 0}; |
5719 | 0 | oDstCT.SetColorEntry(i, &entry); |
5720 | 0 | } |
5721 | 0 | } |
5722 | 0 | poDstDS->GetRasterBand(1)->SetColorTable(&oDstCT); |
5723 | 0 | } |
5724 | | |
5725 | | /* ==================================================================== */ |
5726 | | /* Loop copying bands to an uncompressed file. */ |
5727 | | /* ==================================================================== */ |
5728 | 20 | else |
5729 | 20 | { |
5730 | 20 | if (nIMIndex + 1 == nImageCount) |
5731 | 20 | { |
5732 | 20 | bool bOK = NITFWriteExtraSegments(pszFilename, nullptr, |
5733 | 20 | aosCgmMD.List(), aosTextMD.List(), |
5734 | 20 | nullptr, aosOptions, 0); |
5735 | 20 | if (!bOK) |
5736 | 0 | { |
5737 | 0 | return nullptr; |
5738 | 0 | } |
5739 | 20 | } |
5740 | | |
5741 | | // Save error state to restore it afterwards since some operations |
5742 | | // in Open() might reset it. |
5743 | 20 | CPLErr eLastErr = CPLGetLastErrorType(); |
5744 | 20 | int nLastErrNo = CPLGetLastErrorNo(); |
5745 | 20 | CPLString osLastErrorMsg = CPLGetLastErrorMsg(); |
5746 | | |
5747 | 20 | GDALOpenInfo oOpenInfo(pszFilename, GA_Update); |
5748 | 20 | poDstDS.reset(OpenInternal(&oOpenInfo, nullptr, true, |
5749 | 20 | nImageCount == 1 ? -1 : nIMIndex)); |
5750 | | |
5751 | 20 | if (CPLGetLastErrorType() == CE_None && eLastErr != CE_None) |
5752 | 20 | CPLErrorSetState(eLastErr, nLastErrNo, osLastErrorMsg.c_str()); |
5753 | | |
5754 | 20 | if (poDstDS == nullptr) |
5755 | 0 | { |
5756 | 0 | return nullptr; |
5757 | 0 | } |
5758 | | |
5759 | 20 | std::unique_ptr<void, VSIFreeReleaser> pData( |
5760 | 20 | VSIMalloc2(nXSize, GDALGetDataTypeSizeBytes(eType))); |
5761 | 20 | if (pData == nullptr) |
5762 | 0 | { |
5763 | 0 | return nullptr; |
5764 | 0 | } |
5765 | | |
5766 | 20 | CPLErr eErr = CE_None; |
5767 | | |
5768 | 2.68k | for (int iBand = 0; nIMIndex >= 0 && eErr == CE_None && |
5769 | 2.68k | iBand < poSrcDS->GetRasterCount(); |
5770 | 2.66k | iBand++) |
5771 | 2.66k | { |
5772 | 2.66k | GDALRasterBand *poSrcBand = poSrcDS->GetRasterBand(iBand + 1); |
5773 | 2.66k | GDALRasterBand *poDstBand = poDstDS->GetRasterBand(iBand + 1); |
5774 | | |
5775 | | /* -------------------------------------------------------------------- |
5776 | | */ |
5777 | | /* Do we need to copy a colortable or other metadata? */ |
5778 | | /* -------------------------------------------------------------------- |
5779 | | */ |
5780 | 2.66k | GDALColorTable *poCT = poSrcBand->GetColorTable(); |
5781 | 2.66k | if (poCT != nullptr) |
5782 | 1.10k | poDstBand->SetColorTable(poCT); |
5783 | | |
5784 | | /* -------------------------------------------------------------------- |
5785 | | */ |
5786 | | /* Copy image data. */ |
5787 | | /* -------------------------------------------------------------------- |
5788 | | */ |
5789 | 26.7k | for (int iLine = 0; iLine < nYSize; iLine++) |
5790 | 24.0k | { |
5791 | 24.0k | eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, |
5792 | 24.0k | pData.get(), nXSize, 1, eType, 0, 0, |
5793 | 24.0k | nullptr); |
5794 | 24.0k | if (eErr != CE_None) |
5795 | 5 | break; |
5796 | | |
5797 | 24.0k | eErr = poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, |
5798 | 24.0k | pData.get(), nXSize, 1, eType, 0, 0, |
5799 | 24.0k | nullptr); |
5800 | | |
5801 | 24.0k | if (eErr != CE_None) |
5802 | 0 | break; |
5803 | | |
5804 | 24.0k | if (!pfnProgress( |
5805 | 24.0k | (iBand + (iLine + 1) / static_cast<double>(nYSize)) / |
5806 | 24.0k | static_cast<double>(poSrcDS->GetRasterCount()), |
5807 | 24.0k | nullptr, pProgressData)) |
5808 | 0 | { |
5809 | 0 | CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated"); |
5810 | 0 | eErr = CE_Failure; |
5811 | 0 | break; |
5812 | 0 | } |
5813 | 24.0k | } |
5814 | 2.66k | } |
5815 | | |
5816 | 20 | if (eErr != CE_None) |
5817 | 5 | { |
5818 | 5 | return nullptr; |
5819 | 5 | } |
5820 | 20 | } |
5821 | | |
5822 | | /* -------------------------------------------------------------------- */ |
5823 | | /* Set the georeferencing. */ |
5824 | | /* -------------------------------------------------------------------- */ |
5825 | 15 | if (poDstDS->psImage == nullptr) |
5826 | 0 | { |
5827 | | // do nothing |
5828 | 0 | } |
5829 | 15 | else if (bManualWriteOfIGEOLO) |
5830 | 0 | { |
5831 | 0 | if (!NITFWriteIGEOLO(poDstDS->psImage, poDstDS->psImage->chICORDS, |
5832 | 0 | poDstDS->psImage->nZone, dfIGEOLOULX, dfIGEOLOULY, |
5833 | 0 | dfIGEOLOURX, dfIGEOLOURY, dfIGEOLOLRX, dfIGEOLOLRY, |
5834 | 0 | dfIGEOLOLLX, dfIGEOLOLLY)) |
5835 | 0 | { |
5836 | 0 | return nullptr; |
5837 | 0 | } |
5838 | 0 | } |
5839 | 15 | else if (bWriteGeoTransform) |
5840 | 7 | { |
5841 | 7 | poDstDS->psImage->nZone = nZone; |
5842 | 7 | poDstDS->SetGeoTransform(gt); |
5843 | 7 | } |
5844 | 8 | else if (bWriteGCPs) |
5845 | 0 | { |
5846 | 0 | poDstDS->psImage->nZone = nZone; |
5847 | 0 | poDstDS->SetGCPs(poSrcDS->GetGCPCount(), poSrcDS->GetGCPs(), |
5848 | 0 | poSrcDS->GetGCPSpatialRef()); |
5849 | 0 | } |
5850 | | |
5851 | 15 | poDstDS->CloneInfo(poSrcDS, nGCIFFlags); |
5852 | | |
5853 | 15 | if ((nGCIFFlags & GCIF_METADATA) == 0) |
5854 | 0 | { |
5855 | 0 | const int nSavedMOFlags = poDstDS->GetMOFlags(); |
5856 | 0 | papszSrcMD = poSrcDS->GetMetadata(); |
5857 | 0 | if (papszSrcMD != nullptr) |
5858 | 0 | { |
5859 | 0 | if (!bUseSrcNITFMetadata) |
5860 | 0 | { |
5861 | 0 | CPLStringList aosNewMD(CSLDuplicate(poDstDS->GetMetadata())); |
5862 | 0 | bool bAdded = false; |
5863 | 0 | for (const char *pszKeyValue : cpl::Iterate(papszSrcMD)) |
5864 | 0 | { |
5865 | 0 | if (!STARTS_WITH(pszKeyValue, "NITF_")) |
5866 | 0 | { |
5867 | 0 | bAdded = true; |
5868 | 0 | aosNewMD.AddString(pszKeyValue); |
5869 | 0 | } |
5870 | 0 | } |
5871 | 0 | if (bAdded) |
5872 | 0 | { |
5873 | 0 | poDstDS->SetMetadata(aosNewMD.List()); |
5874 | 0 | } |
5875 | 0 | } |
5876 | 0 | else if (CSLCount(poDstDS->GetMetadata()) != CSLCount(papszSrcMD)) |
5877 | 0 | { |
5878 | 0 | poDstDS->SetMetadata(papszSrcMD); |
5879 | 0 | } |
5880 | 0 | } |
5881 | 0 | poDstDS->SetMOFlags(nSavedMOFlags); |
5882 | 0 | } |
5883 | | |
5884 | 15 | if (bIsCADRG) |
5885 | 0 | { |
5886 | 0 | GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly); |
5887 | 0 | poDstDS.reset(); |
5888 | 0 | poDstDS.reset(OpenInternal(&oOpenInfo, nullptr, true, -1)); |
5889 | 0 | } |
5890 | | |
5891 | 15 | if (poDstDS && pfnProgress && !pfnProgress(1.0, "", pProgressData)) |
5892 | 0 | { |
5893 | 0 | CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated"); |
5894 | 0 | poDstDS.reset(); |
5895 | 0 | } |
5896 | | |
5897 | 15 | return poDstDS; |
5898 | 15 | } |
5899 | | |
5900 | | /************************************************************************/ |
5901 | | /* NITFHasFSDWNGField() */ |
5902 | | /************************************************************************/ |
5903 | | |
5904 | | static bool NITFHasFSDWNGField(VSILFILE *fpVSIL) |
5905 | 0 | { |
5906 | 0 | char szFHDR[9 + 1] = {0}; |
5907 | 0 | bool bOK = VSIFSeekL(fpVSIL, 0, SEEK_SET) == 0; |
5908 | 0 | bOK &= VSIFReadL(szFHDR, 9, 1, fpVSIL) == 1; |
5909 | 0 | if (EQUAL(szFHDR, "NITF02.00")) |
5910 | 0 | { |
5911 | | // Read FSDWNG field |
5912 | 0 | char szFSDWNG[6 + 1] = {0}; |
5913 | 0 | bOK &= VSIFSeekL(fpVSIL, 280, SEEK_SET) == 0; |
5914 | 0 | bOK &= VSIFReadL(szFSDWNG, 1, 6, fpVSIL) == 6; |
5915 | 0 | if (bOK && EQUAL(szFSDWNG, "999998")) |
5916 | 0 | { |
5917 | 0 | return true; |
5918 | 0 | } |
5919 | 0 | } |
5920 | 0 | return false; |
5921 | 0 | } |
5922 | | |
5923 | | /************************************************************************/ |
5924 | | /* NITFPatchImageLength() */ |
5925 | | /* */ |
5926 | | /* Fixup various stuff we don't know till we have written the */ |
5927 | | /* imagery. In particular the file length, image data length */ |
5928 | | /* and the compression ratio achieved. */ |
5929 | | /************************************************************************/ |
5930 | | |
5931 | | static bool NITFPatchImageLength(const char *pszFilename, VSILFILE *fp, |
5932 | | int nIMIndex, GUIntBig nImageOffset, |
5933 | | GIntBig nPixelCount, const char *pszIC, |
5934 | | vsi_l_offset nICOffset, |
5935 | | CSLConstList papszCreationOptions) |
5936 | | |
5937 | 0 | { |
5938 | 0 | VSILFILE *fpVSIL = fp ? fp : VSIFOpenL(pszFilename, "r+b"); |
5939 | 0 | if (fpVSIL == nullptr) |
5940 | 0 | return false; |
5941 | | |
5942 | 0 | CPL_IGNORE_RET_VAL(VSIFSeekL(fpVSIL, 0, SEEK_END)); |
5943 | 0 | GUIntBig nFileLen = VSIFTellL(fpVSIL); |
5944 | | |
5945 | | /* -------------------------------------------------------------------- */ |
5946 | | /* Update total file length. */ |
5947 | | /* -------------------------------------------------------------------- */ |
5948 | 0 | if (nFileLen >= NITF_MAX_FILE_SIZE) |
5949 | 0 | { |
5950 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
5951 | 0 | "Too big file : " CPL_FRMT_GUIB |
5952 | 0 | ". Truncating to " CPL_FRMT_GUIB, |
5953 | 0 | nFileLen, NITF_MAX_FILE_SIZE - 1); |
5954 | 0 | nFileLen = NITF_MAX_FILE_SIZE - 1; |
5955 | 0 | } |
5956 | 0 | CPLString osLen = |
5957 | 0 | CPLString().Printf("%012" CPL_FRMT_GB_WITHOUT_PREFIX "u", nFileLen); |
5958 | 0 | const int nExtraOffset = NITFHasFSDWNGField(fpVSIL) ? 40 : 0; |
5959 | 0 | if (VSIFSeekL(fpVSIL, 342 + nExtraOffset, SEEK_SET) != 0 || |
5960 | 0 | VSIFWriteL(reinterpret_cast<const void *>(osLen.c_str()), 12, 1, |
5961 | 0 | fpVSIL) != 1) |
5962 | 0 | { |
5963 | 0 | CPLError(CE_Failure, CPLE_FileIO, "Write error"); |
5964 | 0 | if (!fp) |
5965 | 0 | CPL_IGNORE_RET_VAL(VSIFCloseL(fpVSIL)); |
5966 | 0 | return false; |
5967 | 0 | } |
5968 | | |
5969 | | /* -------------------------------------------------------------------- */ |
5970 | | /* Update the image data length. */ |
5971 | | /* -------------------------------------------------------------------- */ |
5972 | 0 | GUIntBig nImageSize = nFileLen - nImageOffset; |
5973 | 0 | if (nImageSize >= 9999999999ULL) |
5974 | 0 | { |
5975 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
5976 | 0 | "Too big image size : " CPL_FRMT_GUIB |
5977 | 0 | ". Truncating to 9999999998", |
5978 | 0 | nImageSize); |
5979 | 0 | nImageSize = 9999999998ULL; |
5980 | 0 | } |
5981 | 0 | osLen = |
5982 | 0 | CPLString().Printf("%010" CPL_FRMT_GB_WITHOUT_PREFIX "u", nImageSize); |
5983 | 0 | if (VSIFSeekL(fpVSIL, 369 + nExtraOffset + 16 * nIMIndex, SEEK_SET) != 0 || |
5984 | 0 | VSIFWriteL(reinterpret_cast<const void *>(osLen.c_str()), 10, 1, |
5985 | 0 | fpVSIL) != 1) |
5986 | 0 | { |
5987 | 0 | CPLError(CE_Failure, CPLE_FileIO, "Write error"); |
5988 | 0 | if (!fp) |
5989 | 0 | CPL_IGNORE_RET_VAL(VSIFCloseL(fpVSIL)); |
5990 | 0 | return false; |
5991 | 0 | } |
5992 | | |
5993 | | /* -------------------------------------------------------------------- */ |
5994 | | /* Update COMRAT, the compression rate variable, and CLEVEL */ |
5995 | | /* -------------------------------------------------------------------- */ |
5996 | | |
5997 | | /* Set to IC position */ |
5998 | 0 | bool bOK = VSIFSeekL(fpVSIL, nICOffset, SEEK_SET) == 0; |
5999 | | |
6000 | | /* Read IC */ |
6001 | 0 | char szICBuf[2]; |
6002 | 0 | bOK &= VSIFReadL(szICBuf, 2, 1, fpVSIL) == 1; |
6003 | | |
6004 | | /* The following line works around a "feature" of *BSD libc (at least |
6005 | | * PC-BSD 7.1) */ |
6006 | | /* that makes the position of the file offset unreliable when executing a */ |
6007 | | /* "seek, read and write" sequence. After the read(), the file offset seen |
6008 | | * by */ |
6009 | | /* the write() is approximately the size of a block further... */ |
6010 | 0 | bOK &= VSIFSeekL(fpVSIL, VSIFTellL(fpVSIL), SEEK_SET) == 0; |
6011 | |
|
6012 | 0 | if (!EQUALN(szICBuf, pszIC, 2)) |
6013 | 0 | { |
6014 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
6015 | 0 | "Unable to locate COMRAT to update in NITF header."); |
6016 | 0 | } |
6017 | 0 | else |
6018 | 0 | { |
6019 | 0 | char szCOMRAT[5] = "00.0"; |
6020 | |
|
6021 | 0 | if (EQUAL(pszIC, "C8")) /* jpeg2000 */ |
6022 | 0 | { |
6023 | 0 | double dfRate = static_cast<GIntBig>(nFileLen - nImageOffset) * 8 / |
6024 | 0 | static_cast<double>(nPixelCount); |
6025 | |
|
6026 | 0 | const char *pszProfile = |
6027 | 0 | CSLFetchNameValueDef(papszCreationOptions, "PROFILE", ""); |
6028 | 0 | if (STARTS_WITH_CI(pszProfile, "NPJE")) |
6029 | 0 | { |
6030 | 0 | dfRate = std::max(0.1, std::min(99.9, dfRate)); |
6031 | | |
6032 | | // We emit in Vxyz or Nxyz format with an implicit decimal place |
6033 | | // between yz and z as per spec. |
6034 | 0 | snprintf(szCOMRAT, sizeof(szCOMRAT), "%c%03u", |
6035 | 0 | EQUAL(pszProfile, "NPJE_VISUALLY_LOSSLESS") ? 'V' |
6036 | 0 | : 'N', |
6037 | | // % 1000 to please -Wformat-truncation |
6038 | 0 | static_cast<unsigned>(dfRate * 10) % 1000); |
6039 | 0 | } |
6040 | 0 | else |
6041 | 0 | { |
6042 | 0 | dfRate = std::max(0.01, std::min(99.99, dfRate)); |
6043 | | |
6044 | | // We emit in wxyz format with an implicit decimal place |
6045 | | // between wx and yz as per spec for lossy compression. |
6046 | | // We really should have a special case for lossless |
6047 | | // compression. |
6048 | 0 | snprintf(szCOMRAT, sizeof(szCOMRAT), "%04u", |
6049 | | // % 10000 to please -Wformat-truncation |
6050 | 0 | static_cast<unsigned>(dfRate * 100) % 10000); |
6051 | 0 | } |
6052 | 0 | } |
6053 | 0 | else if (EQUAL(pszIC, "C4") || EQUAL(pszIC, "M4")) // VQ |
6054 | 0 | { |
6055 | 0 | snprintf(szCOMRAT, sizeof(szCOMRAT), "0.75"); |
6056 | 0 | } |
6057 | |
|
6058 | 0 | bOK &= VSIFWriteL(szCOMRAT, 4, 1, fpVSIL) == 1; |
6059 | | |
6060 | | /* ---------------------------------------------------------------- */ |
6061 | | /* Update CLEVEL. */ |
6062 | | /* ---------------------------------------------------------------- */ |
6063 | | // Get existing CLEVEL |
6064 | 0 | constexpr vsi_l_offset OFFSET_CLEVEL = 9; |
6065 | 0 | constexpr int SIZE_CLEVEL = 2; |
6066 | 0 | bOK &= VSIFSeekL(fpVSIL, OFFSET_CLEVEL, SEEK_SET) == 0; |
6067 | 0 | char szCLEVEL[SIZE_CLEVEL + 1] = {0}; |
6068 | 0 | bOK &= VSIFReadL(szCLEVEL, 1, SIZE_CLEVEL, fpVSIL) != 0; |
6069 | 0 | unsigned int nCLevel = static_cast<unsigned>(atoi(szCLEVEL)); |
6070 | 0 | if (nCLevel >= 3 && nCLevel <= 7) |
6071 | 0 | { |
6072 | 0 | const unsigned int nCLevelOri = nCLevel; |
6073 | 0 | if (nFileLen > 2147483647) |
6074 | 0 | { |
6075 | 0 | nCLevel = std::max(nCLevel, 7U); |
6076 | 0 | } |
6077 | 0 | else if (nFileLen > 1073741833) |
6078 | 0 | { |
6079 | 0 | nCLevel = std::max(nCLevel, 6U); |
6080 | 0 | } |
6081 | 0 | else if (nFileLen > 52428799) |
6082 | 0 | { |
6083 | 0 | nCLevel = std::max(nCLevel, 5U); |
6084 | 0 | } |
6085 | 0 | if (nCLevel != nCLevelOri) |
6086 | 0 | { |
6087 | 0 | CPLDebug("NITF", "Updating CLEVEL from %02u to %02u", |
6088 | 0 | nCLevelOri, nCLevel); |
6089 | | // %100 to please -Wformat-truncation |
6090 | 0 | snprintf(szCLEVEL, sizeof(szCLEVEL), "%02u", nCLevel % 100); |
6091 | 0 | bOK &= VSIFSeekL(fpVSIL, OFFSET_CLEVEL, SEEK_SET) == 0; |
6092 | 0 | bOK &= VSIFWriteL(szCLEVEL, 1, SIZE_CLEVEL, fpVSIL) != 0; |
6093 | 0 | } |
6094 | 0 | } |
6095 | 0 | else |
6096 | 0 | { |
6097 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
6098 | 0 | "Invalid CLEVEL=%s value found when updating NITF header.", |
6099 | 0 | szCLEVEL); |
6100 | 0 | } |
6101 | 0 | } |
6102 | |
|
6103 | 0 | if (!fp && VSIFCloseL(fpVSIL) != 0) |
6104 | 0 | bOK = false; |
6105 | |
|
6106 | 0 | if (!bOK) |
6107 | 0 | { |
6108 | 0 | CPLError(CE_Failure, CPLE_FileIO, "I/O error"); |
6109 | 0 | } |
6110 | |
|
6111 | 0 | return bOK; |
6112 | 0 | } |
6113 | | |
6114 | | /************************************************************************/ |
6115 | | /* NITFWriteCGMSegments() */ |
6116 | | /************************************************************************/ |
6117 | | static bool NITFWriteCGMSegments(const char *pszFilename, VSILFILE *&fpVSIL, |
6118 | | CSLConstList papszList) |
6119 | 20 | { |
6120 | 20 | char errorMessage[255] = ""; |
6121 | | |
6122 | | // size of each Cgm header entry (LS (4) + LSSH (6)) |
6123 | 20 | const int nCgmHdrEntrySz = 10; |
6124 | | |
6125 | 20 | if (papszList == nullptr) |
6126 | 20 | return true; |
6127 | | |
6128 | 0 | int nNUMS = 0; |
6129 | 0 | const char *pszNUMS = CSLFetchNameValue(papszList, "SEGMENT_COUNT"); |
6130 | 0 | if (pszNUMS != nullptr) |
6131 | 0 | { |
6132 | 0 | nNUMS = atoi(pszNUMS); |
6133 | 0 | } |
6134 | | |
6135 | | /* -------------------------------------------------------------------- */ |
6136 | | /* Open the target file if not already done. */ |
6137 | | /* -------------------------------------------------------------------- */ |
6138 | 0 | if (fpVSIL == nullptr) |
6139 | 0 | fpVSIL = VSIFOpenL(pszFilename, "r+b"); |
6140 | 0 | if (fpVSIL == nullptr) |
6141 | 0 | return false; |
6142 | | |
6143 | | // Calculates the offset for NUMS so we can update header data |
6144 | 0 | char achNUMI[4]; // 3 digits plus null character |
6145 | 0 | achNUMI[3] = '\0'; |
6146 | | |
6147 | | // NUMI offset is at a fixed offset 360 |
6148 | 0 | const vsi_l_offset nNumIOffset = |
6149 | 0 | 360 + (NITFHasFSDWNGField(fpVSIL) ? 40 : 0); |
6150 | 0 | bool bOK = VSIFSeekL(fpVSIL, nNumIOffset, SEEK_SET) == 0; |
6151 | 0 | bOK &= VSIFReadL(achNUMI, 3, 1, fpVSIL) == 1; |
6152 | 0 | const int nIM = atoi(achNUMI); |
6153 | | |
6154 | | // 6 for size of LISH and 10 for size of LI |
6155 | | // NUMS offset is NumI offset plus the size of NumI + size taken up each |
6156 | | // the header data multiply by the number of data |
6157 | |
|
6158 | 0 | const vsi_l_offset nNumSOffset = nNumIOffset + 3 + nIM * (6 + 10); |
6159 | | |
6160 | | /* -------------------------------------------------------------------- */ |
6161 | | /* Confirm that the NUMS in the file header already matches the */ |
6162 | | /* number of graphic segments we want to write */ |
6163 | | /* -------------------------------------------------------------------- */ |
6164 | 0 | char achNUMS[4]; |
6165 | |
|
6166 | 0 | bOK &= VSIFSeekL(fpVSIL, nNumSOffset, SEEK_SET) == 0; |
6167 | 0 | bOK &= VSIFReadL(achNUMS, 3, 1, fpVSIL) == 1; |
6168 | 0 | achNUMS[3] = '\0'; |
6169 | |
|
6170 | 0 | if (!bOK || atoi(achNUMS) != nNUMS) |
6171 | 0 | { |
6172 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6173 | 0 | "It appears an attempt was made to add or update graphic\n" |
6174 | 0 | "segments on an NITF file with existing segments. This\n" |
6175 | 0 | "is not currently supported by the GDAL NITF driver."); |
6176 | |
|
6177 | 0 | return false; |
6178 | 0 | } |
6179 | | |
6180 | | // allocate space for graphic header. |
6181 | | // Size of LS = 4, size of LSSH = 6, and 1 for null character |
6182 | 0 | char *pachLS = |
6183 | 0 | static_cast<char *>(CPLCalloc(nNUMS * nCgmHdrEntrySz + 1, 1)); |
6184 | | |
6185 | | /* -------------------------------------------------------------------- */ |
6186 | | /* Assume no extended data such as SXSHDL, SXSHD */ |
6187 | | /* -------------------------------------------------------------------- */ |
6188 | | |
6189 | | /* ==================================================================== */ |
6190 | | /* Write the Graphics segments at the end of the file. */ |
6191 | | /* ==================================================================== */ |
6192 | |
|
6193 | 0 | #define PLACE(location, name, text) memcpy(location, text, strlen(text)) |
6194 | |
|
6195 | 0 | for (int i = 0; bOK && i < nNUMS; i++) |
6196 | 0 | { |
6197 | | |
6198 | | // Get all the fields for current CGM segment |
6199 | 0 | const char *pszSlocRow = CSLFetchNameValue( |
6200 | 0 | papszList, CPLString().Printf("SEGMENT_%d_SLOC_ROW", i)); |
6201 | 0 | const char *pszSlocCol = CSLFetchNameValue( |
6202 | 0 | papszList, CPLString().Printf("SEGMENT_%d_SLOC_COL", i)); |
6203 | 0 | const char *pszSdlvl = CSLFetchNameValue( |
6204 | 0 | papszList, CPLString().Printf("SEGMENT_%d_SDLVL", i)); |
6205 | 0 | const char *pszSalvl = CSLFetchNameValue( |
6206 | 0 | papszList, CPLString().Printf("SEGMENT_%d_SALVL", i)); |
6207 | 0 | const char *pszData = CSLFetchNameValue( |
6208 | 0 | papszList, CPLString().Printf("SEGMENT_%d_DATA", i)); |
6209 | | |
6210 | | // Error checking |
6211 | 0 | if (pszSlocRow == nullptr) |
6212 | 0 | { |
6213 | 0 | snprintf(errorMessage, sizeof(errorMessage), |
6214 | 0 | "NITF graphic segment writing error: SLOC_ROW for segment " |
6215 | 0 | "%d is not defined", |
6216 | 0 | i); |
6217 | 0 | break; |
6218 | 0 | } |
6219 | 0 | if (pszSlocCol == nullptr) |
6220 | 0 | { |
6221 | 0 | snprintf(errorMessage, sizeof(errorMessage), |
6222 | 0 | "NITF graphic segment writing error: SLOC_COL for segment " |
6223 | 0 | "%d is not defined", |
6224 | 0 | i); |
6225 | 0 | break; |
6226 | 0 | } |
6227 | 0 | if (pszSdlvl == nullptr) |
6228 | 0 | { |
6229 | 0 | snprintf(errorMessage, sizeof(errorMessage), |
6230 | 0 | "NITF graphic segment writing error: SDLVL for segment %d " |
6231 | 0 | "is not defined", |
6232 | 0 | i); |
6233 | 0 | break; |
6234 | 0 | } |
6235 | 0 | if (pszSalvl == nullptr) |
6236 | 0 | { |
6237 | 0 | snprintf(errorMessage, sizeof(errorMessage), |
6238 | 0 | "NITF graphic segment writing error: SALVLfor segment %d " |
6239 | 0 | "is not defined", |
6240 | 0 | i); |
6241 | 0 | break; |
6242 | 0 | } |
6243 | 0 | if (pszData == nullptr) |
6244 | 0 | { |
6245 | 0 | snprintf(errorMessage, sizeof(errorMessage), |
6246 | 0 | "NITF graphic segment writing error: DATA for segment %d " |
6247 | 0 | "is not defined", |
6248 | 0 | i); |
6249 | 0 | break; |
6250 | 0 | } |
6251 | | |
6252 | 0 | const int nSlocCol = atoi(pszSlocRow); |
6253 | 0 | const int nSlocRow = atoi(pszSlocCol); |
6254 | 0 | const int nSdlvl = atoi(pszSdlvl); |
6255 | 0 | const int nSalvl = atoi(pszSalvl); |
6256 | | |
6257 | | // Create a buffer for graphics segment header, 258 is the size of |
6258 | | // the header that we will be writing. |
6259 | 0 | char achGSH[258]; |
6260 | |
|
6261 | 0 | memset(achGSH, ' ', sizeof(achGSH)); |
6262 | |
|
6263 | 0 | PLACE(achGSH + 0, SY, "SY"); |
6264 | 0 | PLACE(achGSH + 2, SID, CPLSPrintf("%010d", i)); |
6265 | 0 | PLACE(achGSH + 12, SNAME, "DEFAULT NAME "); |
6266 | 0 | PLACE(achGSH + 32, SSCLAS, "U"); |
6267 | 0 | PLACE(achGSH + 33, SSCLASY, "0"); |
6268 | 0 | PLACE(achGSH + 199, ENCRYP, "0"); |
6269 | 0 | PLACE(achGSH + 200, SFMT, "C"); |
6270 | 0 | PLACE(achGSH + 201, SSTRUCT, "0000000000000"); |
6271 | 0 | PLACE(achGSH + 214, SDLVL, CPLSPrintf("%03d", nSdlvl)); // size3 |
6272 | 0 | PLACE(achGSH + 217, SALVL, CPLSPrintf("%03d", nSalvl)); // size3 |
6273 | 0 | PLACE(achGSH + 220, SLOC, |
6274 | 0 | CPLSPrintf("%05d%05d", nSlocRow, nSlocCol)); // size 10 |
6275 | 0 | PLACE(achGSH + 230, SBAND1, "0000000000"); |
6276 | 0 | PLACE(achGSH + 240, SCOLOR, "C"); |
6277 | 0 | PLACE(achGSH + 241, SBAND2, "0000000000"); |
6278 | 0 | PLACE(achGSH + 251, SRES2, "00"); |
6279 | 0 | PLACE(achGSH + 253, SXSHDL, "00000"); |
6280 | | |
6281 | | // Move to the end of the file |
6282 | 0 | bOK &= VSIFSeekL(fpVSIL, 0, SEEK_END) == 0; |
6283 | 0 | bOK &= VSIFWriteL(achGSH, sizeof(achGSH), 1, fpVSIL) == 1; |
6284 | | |
6285 | | /* ------------------------------------------------------------------ */ |
6286 | | /* Prepare and write CGM segment data. */ |
6287 | | /* ------------------------------------------------------------------ */ |
6288 | 0 | int nCGMSize = 0; |
6289 | 0 | char *pszCgmToWrite = |
6290 | 0 | CPLUnescapeString(pszData, &nCGMSize, CPLES_BackslashQuotable); |
6291 | |
|
6292 | 0 | if (nCGMSize > 999998) |
6293 | 0 | { |
6294 | 0 | CPLError(CE_Warning, CPLE_NotSupported, |
6295 | 0 | "Length of SEGMENT_%d_DATA is %d, which is greater than " |
6296 | 0 | "999998. Truncating...", |
6297 | 0 | i + 1, nCGMSize); |
6298 | 0 | nCGMSize = 999998; |
6299 | 0 | } |
6300 | |
|
6301 | 0 | bOK &= static_cast<int>( |
6302 | 0 | VSIFWriteL(pszCgmToWrite, 1, nCGMSize, fpVSIL)) == nCGMSize; |
6303 | | |
6304 | | /* -------------------------------------------------------------------- |
6305 | | */ |
6306 | | /* Update the subheader and data size info in the file header. */ |
6307 | | /* -------------------------------------------------------------------- |
6308 | | */ |
6309 | 0 | snprintf(pachLS + nCgmHdrEntrySz * i, nCgmHdrEntrySz + 1, "%04d%06d", |
6310 | 0 | static_cast<int>(sizeof(achGSH)), nCGMSize); |
6311 | |
|
6312 | 0 | CPLFree(pszCgmToWrite); |
6313 | 0 | } // End For |
6314 | | |
6315 | | /* -------------------------------------------------------------------- */ |
6316 | | /* Write out the graphic segment info. */ |
6317 | | /* -------------------------------------------------------------------- */ |
6318 | |
|
6319 | 0 | bOK &= VSIFSeekL(fpVSIL, nNumSOffset + 3, SEEK_SET) == 0; |
6320 | 0 | bOK &= static_cast<int>(VSIFWriteL(pachLS, 1, nNUMS * nCgmHdrEntrySz, |
6321 | 0 | fpVSIL)) == nNUMS * nCgmHdrEntrySz; |
6322 | |
|
6323 | 0 | CPLFree(pachLS); |
6324 | |
|
6325 | 0 | if (strlen(errorMessage) != 0) |
6326 | 0 | { |
6327 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "%s", errorMessage); |
6328 | 0 | bOK = false; |
6329 | 0 | } |
6330 | |
|
6331 | 0 | return bOK; |
6332 | |
|
6333 | 0 | #undef PLACE |
6334 | 0 | } |
6335 | | |
6336 | | /************************************************************************/ |
6337 | | /* NITFWriteTextSegments() */ |
6338 | | /************************************************************************/ |
6339 | | |
6340 | | static bool NITFWriteTextSegments(const char *pszFilename, VSILFILE *&fpVSIL, |
6341 | | CSLConstList papszList) |
6342 | | |
6343 | 20 | { |
6344 | | /* -------------------------------------------------------------------- */ |
6345 | | /* Count the number of apparent text segments to write. There */ |
6346 | | /* is nothing at all to do if there are none to write. */ |
6347 | | /* -------------------------------------------------------------------- */ |
6348 | 20 | int nNUMT = 0; |
6349 | | |
6350 | 20 | for (int iOpt = 0; papszList != nullptr && papszList[iOpt] != nullptr; |
6351 | 20 | iOpt++) |
6352 | 0 | { |
6353 | 0 | if (STARTS_WITH_CI(papszList[iOpt], "DATA_")) |
6354 | 0 | nNUMT++; |
6355 | 0 | } |
6356 | | |
6357 | 20 | if (nNUMT == 0) |
6358 | 20 | return true; |
6359 | | |
6360 | | /* -------------------------------------------------------------------- */ |
6361 | | /* Open the target file if not already done. */ |
6362 | | /* -------------------------------------------------------------------- */ |
6363 | 0 | if (fpVSIL == nullptr) |
6364 | 0 | fpVSIL = VSIFOpenL(pszFilename, "r+b"); |
6365 | 0 | if (fpVSIL == nullptr) |
6366 | 0 | return false; |
6367 | | |
6368 | | // Get number of text field. Since there there could be multiple images |
6369 | | // or graphic segment, the offset need to be calculated dynamically. |
6370 | | |
6371 | 0 | char achNUMI[4]; // 3 digits plus null character |
6372 | 0 | achNUMI[3] = '\0'; |
6373 | | // NUMI offset is at a fixed offset 360 |
6374 | 0 | const vsi_l_offset nNumIOffset = |
6375 | 0 | 360 + (NITFHasFSDWNGField(fpVSIL) ? 40 : 0); |
6376 | 0 | bool bOK = VSIFSeekL(fpVSIL, nNumIOffset, SEEK_SET) == 0; |
6377 | 0 | bOK &= VSIFReadL(achNUMI, 3, 1, fpVSIL) == 1; |
6378 | 0 | int nIM = atoi(achNUMI); |
6379 | |
|
6380 | 0 | char achNUMG[4]; // 3 digits plus null character |
6381 | 0 | achNUMG[3] = '\0'; |
6382 | | |
6383 | | // 3 for size of NUMI. 6 and 10 are the field size for LISH and LI |
6384 | 0 | const vsi_l_offset nNumGOffset = nNumIOffset + 3 + nIM * (6 + 10); |
6385 | 0 | bOK &= VSIFSeekL(fpVSIL, nNumGOffset, SEEK_SET) == 0; |
6386 | 0 | bOK &= VSIFReadL(achNUMG, 3, 1, fpVSIL) == 1; |
6387 | 0 | const int nGS = atoi(achNUMG); |
6388 | | |
6389 | | // NUMT offset |
6390 | | // 3 for size of NUMG. 4 and 6 are filed size of LSSH and LS. |
6391 | | // the last + 3 is for NUMX field, which is not used |
6392 | 0 | const vsi_l_offset nNumTOffset = nNumGOffset + 3 + nGS * (4 + 6) + 3; |
6393 | | |
6394 | | /* -------------------------------------------------------------------- */ |
6395 | | /* Confirm that the NUMT in the file header already matches the */ |
6396 | | /* number of text segments we want to write, and that the */ |
6397 | | /* segment header/data size info is blank. */ |
6398 | | /* -------------------------------------------------------------------- */ |
6399 | 0 | char achNUMT[4]; |
6400 | 0 | char *pachLT = static_cast<char *>(CPLCalloc(nNUMT * 9 + 1, 1)); |
6401 | |
|
6402 | 0 | bOK &= VSIFSeekL(fpVSIL, nNumTOffset, SEEK_SET) == 0; |
6403 | 0 | bOK &= VSIFReadL(achNUMT, 3, 1, fpVSIL) == 1; |
6404 | 0 | achNUMT[3] = '\0'; |
6405 | |
|
6406 | 0 | bOK &= VSIFReadL(pachLT, nNUMT * 9, 1, fpVSIL) == 1; |
6407 | |
|
6408 | 0 | if (!bOK || atoi(achNUMT) != nNUMT) |
6409 | 0 | { |
6410 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6411 | 0 | "It appears an attempt was made to add or update text\n" |
6412 | 0 | "segments on an NITF file with existing segments. This\n" |
6413 | 0 | "is not currently supported by the GDAL NITF driver."); |
6414 | |
|
6415 | 0 | CPLFree(pachLT); |
6416 | 0 | return false; |
6417 | 0 | } |
6418 | | |
6419 | 0 | if (!STARTS_WITH_CI(pachLT, " ")) |
6420 | 0 | { |
6421 | 0 | CPLFree(pachLT); |
6422 | | // presumably the text segments are already written, do nothing. |
6423 | 0 | return true; |
6424 | 0 | } |
6425 | | |
6426 | | /* -------------------------------------------------------------------- */ |
6427 | | /* At this point we likely ought to confirm NUMDES, NUMRES, */ |
6428 | | /* UDHDL and XHDL are zero. Consider adding later... */ |
6429 | | /* -------------------------------------------------------------------- */ |
6430 | | |
6431 | | /* ==================================================================== */ |
6432 | | /* Write the text segments at the end of the file. */ |
6433 | | /* ==================================================================== */ |
6434 | 0 | #define PLACE(location, name, text) memcpy(location, text, strlen(text)) |
6435 | 0 | int iTextSeg = 0; |
6436 | |
|
6437 | 0 | for (int iOpt = 0; |
6438 | 0 | bOK && papszList != nullptr && papszList[iOpt] != nullptr; iOpt++) |
6439 | 0 | { |
6440 | 0 | if (!STARTS_WITH_CI(papszList[iOpt], "DATA_")) |
6441 | 0 | continue; |
6442 | | |
6443 | 0 | const char *pszTextToWrite = |
6444 | 0 | CPLParseNameValue(papszList[iOpt], nullptr); |
6445 | 0 | if (pszTextToWrite == nullptr) |
6446 | 0 | continue; |
6447 | | |
6448 | | /* -------------------------------------------------------------------- |
6449 | | */ |
6450 | | /* Locate corresponding header data in the buffer */ |
6451 | | /* -------------------------------------------------------------------- |
6452 | | */ |
6453 | | |
6454 | 0 | const char *pszHeaderBuffer = nullptr; |
6455 | 0 | for (int iOpt2 = 0; papszList[iOpt2] != nullptr; iOpt2++) |
6456 | 0 | { |
6457 | 0 | if (!STARTS_WITH_CI(papszList[iOpt2], "HEADER_")) |
6458 | 0 | continue; |
6459 | | |
6460 | 0 | char *pszHeaderKey = nullptr; |
6461 | 0 | CPLParseNameValue(papszList[iOpt2], &pszHeaderKey); |
6462 | 0 | char *pszDataKey = nullptr; |
6463 | 0 | CPLParseNameValue(papszList[iOpt], &pszDataKey); |
6464 | 0 | if (pszHeaderKey == nullptr || pszDataKey == nullptr) |
6465 | 0 | { |
6466 | 0 | CPLFree(pszHeaderKey); |
6467 | 0 | CPLFree(pszDataKey); |
6468 | 0 | continue; |
6469 | 0 | } |
6470 | | |
6471 | | // Point to header and data number. |
6472 | 0 | char *pszHeaderId = pszHeaderKey + 7; |
6473 | 0 | char *pszDataId = pszDataKey + 5; |
6474 | |
|
6475 | 0 | const bool bIsSameId = strcmp(pszHeaderId, pszDataId) == 0; |
6476 | 0 | CPLFree(pszHeaderKey); |
6477 | 0 | CPLFree(pszDataKey); |
6478 | | |
6479 | | // if ID matches, read the header information and exit the loop |
6480 | 0 | if (bIsSameId) |
6481 | 0 | { |
6482 | 0 | pszHeaderBuffer = CPLParseNameValue(papszList[iOpt2], nullptr); |
6483 | 0 | break; |
6484 | 0 | } |
6485 | 0 | } |
6486 | | |
6487 | | /* -------------------------------------------------------------------- |
6488 | | */ |
6489 | | /* Prepare and write text header. */ |
6490 | | /* -------------------------------------------------------------------- |
6491 | | */ |
6492 | 0 | char achTSH[282]; |
6493 | 0 | memset(achTSH, ' ', sizeof(achTSH)); |
6494 | 0 | bOK &= VSIFSeekL(fpVSIL, 0, SEEK_END) == 0; |
6495 | |
|
6496 | 0 | if (pszHeaderBuffer != nullptr) |
6497 | 0 | { |
6498 | 0 | memcpy(achTSH, pszHeaderBuffer, |
6499 | 0 | std::min(strlen(pszHeaderBuffer), sizeof(achTSH))); |
6500 | | |
6501 | | // Take care NITF2.0 date format changes |
6502 | 0 | const char chTimeZone = achTSH[20]; |
6503 | | |
6504 | | // Check for Zulu time zone character. IpachLTf that exist, then |
6505 | | // it is NITF2.0 format. |
6506 | 0 | if (chTimeZone == 'Z') |
6507 | 0 | { |
6508 | 0 | char *achOrigDate = achTSH + 12; // original date string |
6509 | | |
6510 | | // The date value taken from default NITF file date |
6511 | 0 | char achYear[3]; |
6512 | | |
6513 | | // Offset to the year |
6514 | 0 | strncpy(achYear, achOrigDate + 12, 2); |
6515 | 0 | achYear[2] = '\0'; |
6516 | 0 | const int nYear = atoi(achYear); |
6517 | | |
6518 | | // Set century. |
6519 | | // Since NITF2.0 does not track the century, we are going to |
6520 | | // assume any year number greater then 94 (the year NITF2.0 |
6521 | | // spec published), will be 1900s, otherwise, it is 2000s. |
6522 | 0 | char achNewDate[] = "20021216151629"; |
6523 | 0 | if (nYear > 94) |
6524 | 0 | memcpy(achNewDate, "19", 2); |
6525 | 0 | else |
6526 | 0 | memcpy(achNewDate, "20", 2); |
6527 | |
|
6528 | 0 | memcpy(achNewDate + 6, achOrigDate, 8); // copy cover DDhhmmss |
6529 | 0 | memcpy(achNewDate + 2, achOrigDate + 12, 2); // copy over years |
6530 | | |
6531 | | // Perform month conversion |
6532 | 0 | char *pszOrigMonth = achOrigDate + 9; |
6533 | 0 | char *pszNewMonth = achNewDate + 4; |
6534 | |
|
6535 | 0 | if (STARTS_WITH(pszOrigMonth, "JAN")) |
6536 | 0 | memcpy(pszNewMonth, "01", 2); |
6537 | 0 | else if (STARTS_WITH(pszOrigMonth, "FEB")) |
6538 | 0 | memcpy(pszNewMonth, "02", 2); |
6539 | 0 | else if (STARTS_WITH(pszOrigMonth, "MAR")) |
6540 | 0 | memcpy(pszNewMonth, "03", 2); |
6541 | 0 | else if (STARTS_WITH(pszOrigMonth, "APR")) |
6542 | 0 | memcpy(pszNewMonth, "04", 2); |
6543 | 0 | else if (STARTS_WITH(pszOrigMonth, "MAY")) |
6544 | 0 | memcpy(pszNewMonth, "05", 2); |
6545 | 0 | else if (STARTS_WITH(pszOrigMonth, "JUN")) |
6546 | 0 | memcpy(pszNewMonth, "07", 2); |
6547 | 0 | else if (STARTS_WITH(pszOrigMonth, "AUG")) |
6548 | 0 | memcpy(pszNewMonth, "08", 2); |
6549 | 0 | else if (STARTS_WITH(pszOrigMonth, "SEP")) |
6550 | 0 | memcpy(pszNewMonth, "09", 2); |
6551 | 0 | else if (STARTS_WITH(pszOrigMonth, "OCT")) |
6552 | 0 | memcpy(pszNewMonth, "10", 2); |
6553 | 0 | else if (STARTS_WITH(pszOrigMonth, "NOV")) |
6554 | 0 | memcpy(pszNewMonth, "11", 2); |
6555 | 0 | else if (STARTS_WITH(pszOrigMonth, "DEC")) |
6556 | 0 | memcpy(pszNewMonth, "12", 2); |
6557 | |
|
6558 | 0 | PLACE(achTSH + 12, TXTDT, achNewDate); |
6559 | 0 | } |
6560 | 0 | } |
6561 | 0 | else |
6562 | 0 | { // Use default value if header information is not found |
6563 | 0 | PLACE(achTSH + 0, TE, "TE"); |
6564 | 0 | PLACE(achTSH + 9, TXTALVL, "000"); |
6565 | 0 | PLACE(achTSH + 12, TXTDT, "20021216151629"); |
6566 | 0 | PLACE(achTSH + 106, TSCLAS, "U"); |
6567 | 0 | PLACE(achTSH + 273, ENCRYP, "0"); |
6568 | 0 | PLACE(achTSH + 274, TXTFMT, "STA"); |
6569 | 0 | PLACE(achTSH + 277, TXSHDL, "00000"); |
6570 | 0 | } |
6571 | |
|
6572 | 0 | bOK &= VSIFWriteL(achTSH, sizeof(achTSH), 1, fpVSIL) == 1; |
6573 | | |
6574 | | /* -------------------------------------------------------------------- |
6575 | | */ |
6576 | | /* Prepare and write text segment data. */ |
6577 | | /* -------------------------------------------------------------------- |
6578 | | */ |
6579 | |
|
6580 | 0 | int nTextLength = static_cast<int>(strlen(pszTextToWrite)); |
6581 | 0 | if (nTextLength > 99998) |
6582 | 0 | { |
6583 | 0 | CPLError(CE_Warning, CPLE_NotSupported, |
6584 | 0 | "Length of DATA_%d is %d, which is greater than 99998. " |
6585 | 0 | "Truncating...", |
6586 | 0 | iTextSeg + 1, nTextLength); |
6587 | 0 | nTextLength = 99998; |
6588 | 0 | } |
6589 | |
|
6590 | 0 | bOK &= static_cast<int>(VSIFWriteL(pszTextToWrite, 1, nTextLength, |
6591 | 0 | fpVSIL)) == nTextLength; |
6592 | | |
6593 | | /* -------------------------------------------------------------------- |
6594 | | */ |
6595 | | /* Update the subheader and data size info in the file header. */ |
6596 | | /* -------------------------------------------------------------------- |
6597 | | */ |
6598 | 0 | CPLsnprintf(pachLT + 9 * iTextSeg + 0, 9 + 1, "%04d%05d", |
6599 | 0 | static_cast<int>(sizeof(achTSH)), nTextLength); |
6600 | |
|
6601 | 0 | iTextSeg++; |
6602 | 0 | } |
6603 | | |
6604 | | /* -------------------------------------------------------------------- */ |
6605 | | /* Write out the text segment info. */ |
6606 | | /* -------------------------------------------------------------------- */ |
6607 | |
|
6608 | 0 | bOK &= VSIFSeekL(fpVSIL, nNumTOffset + 3, SEEK_SET) == 0; |
6609 | 0 | bOK &= |
6610 | 0 | static_cast<int>(VSIFWriteL(pachLT, 1, nNUMT * 9, fpVSIL)) == nNUMT * 9; |
6611 | |
|
6612 | 0 | CPLFree(pachLT); |
6613 | |
|
6614 | 0 | return bOK; |
6615 | 0 | #undef PLACE |
6616 | 0 | } |
6617 | | |
6618 | | /************************************************************************/ |
6619 | | /* NITFWriteDES() */ |
6620 | | /************************************************************************/ |
6621 | | |
6622 | | static bool NITFWriteDES(VSILFILE *&fp, const char *pszFilename, |
6623 | | vsi_l_offset nOffsetLDSH, int iDES, |
6624 | | const char *pszDESName, const GByte *pabyDESData, |
6625 | | int nArrayLen) |
6626 | 0 | { |
6627 | 0 | constexpr int LEN_DE = 2; |
6628 | 0 | constexpr int LEN_DESID = 25; |
6629 | 0 | constexpr int LEN_DESOFLW = 6; |
6630 | 0 | constexpr int LEN_DESITEM = 3; |
6631 | 0 | const int nTotalLen = LEN_DE + LEN_DESID + nArrayLen; |
6632 | |
|
6633 | 0 | const bool bIsTRE_OVERFLOW = (strcmp(pszDESName, "TRE_OVERFLOW") == 0); |
6634 | 0 | const int MIN_LEN_DES_SUBHEADER = |
6635 | 0 | 200 + (bIsTRE_OVERFLOW ? LEN_DESOFLW + LEN_DESITEM : 0); |
6636 | |
|
6637 | 0 | if (nTotalLen < MIN_LEN_DES_SUBHEADER) |
6638 | 0 | { |
6639 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6640 | 0 | "DES does not contain enough data"); |
6641 | 0 | return false; |
6642 | 0 | } |
6643 | | |
6644 | 0 | int nDESITEM = 0; |
6645 | 0 | GUIntBig nIXSOFLOffset = 0; |
6646 | 0 | if (bIsTRE_OVERFLOW) |
6647 | 0 | { |
6648 | 0 | char szDESITEM[LEN_DESITEM + 1]; |
6649 | 0 | memcpy(szDESITEM, pabyDESData + 169 + LEN_DESOFLW, LEN_DESITEM); |
6650 | 0 | szDESITEM[LEN_DESITEM] = '\0'; |
6651 | 0 | if (!isdigit(static_cast<unsigned char>(szDESITEM[0])) || |
6652 | 0 | !isdigit(static_cast<unsigned char>(szDESITEM[1])) || |
6653 | 0 | !isdigit(static_cast<unsigned char>(szDESITEM[2]))) |
6654 | 0 | { |
6655 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6656 | 0 | "Invalid value for DESITEM: '%s'", szDESITEM); |
6657 | 0 | return false; |
6658 | 0 | } |
6659 | 0 | nDESITEM = atoi(szDESITEM); |
6660 | |
|
6661 | 0 | char szDESOFLW[LEN_DESOFLW + 1]; |
6662 | 0 | memcpy(szDESOFLW, pabyDESData + 169, LEN_DESOFLW); |
6663 | 0 | szDESOFLW[LEN_DESOFLW] = '\0'; |
6664 | 0 | if (strcmp(szDESOFLW, "IXSHD ") == 0) |
6665 | 0 | { |
6666 | 0 | auto psFile = NITFOpenEx(fp, pszFilename); |
6667 | 0 | if (psFile == nullptr) |
6668 | 0 | { |
6669 | 0 | fp = nullptr; |
6670 | 0 | return false; |
6671 | 0 | } |
6672 | | |
6673 | 0 | int nImageIdx = 1; |
6674 | 0 | for (int iSegment = 0; iSegment < psFile->nSegmentCount; ++iSegment) |
6675 | 0 | { |
6676 | 0 | const auto psSegInfo = psFile->pasSegmentInfo + iSegment; |
6677 | 0 | if (!EQUAL(psSegInfo->szSegmentType, "IM")) |
6678 | 0 | continue; |
6679 | 0 | if (nImageIdx == nDESITEM) |
6680 | 0 | { |
6681 | 0 | auto psImage = NITFImageAccess(psFile, iSegment); |
6682 | 0 | if (psImage == nullptr) |
6683 | 0 | { |
6684 | 0 | nImageIdx = -1; |
6685 | 0 | break; |
6686 | 0 | } |
6687 | | |
6688 | 0 | if (psImage->nIXSOFL == -1) |
6689 | 0 | { |
6690 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6691 | 0 | "Missing IXSOFL field in image %d. " |
6692 | 0 | "RESERVE_SPACE_FOR_TRE_OVERFLOW=YES creation " |
6693 | 0 | "option likely missing.", |
6694 | 0 | nImageIdx); |
6695 | 0 | } |
6696 | 0 | else if (psImage->nIXSOFL != 0) |
6697 | 0 | { |
6698 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6699 | 0 | "Expected IXSOFL of image %d to be 0. Got %d", |
6700 | 0 | nImageIdx, psImage->nIXSOFL); |
6701 | 0 | } |
6702 | 0 | else |
6703 | 0 | { |
6704 | 0 | nIXSOFLOffset = psSegInfo->nSegmentHeaderStart + |
6705 | 0 | psImage->nIXSOFLOffsetInSubfileHeader; |
6706 | 0 | } |
6707 | |
|
6708 | 0 | NITFImageDeaccess(psImage); |
6709 | 0 | break; |
6710 | 0 | } |
6711 | 0 | ++nImageIdx; |
6712 | 0 | } |
6713 | |
|
6714 | 0 | psFile->fp = nullptr; |
6715 | 0 | NITFClose(psFile); |
6716 | |
|
6717 | 0 | if (nImageIdx != nDESITEM) |
6718 | 0 | { |
6719 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6720 | 0 | "Cannot find image matching DESITEM = %d value", |
6721 | 0 | nDESITEM); |
6722 | 0 | return false; |
6723 | 0 | } |
6724 | 0 | if (nIXSOFLOffset == 0) |
6725 | 0 | { |
6726 | 0 | return false; |
6727 | 0 | } |
6728 | 0 | } |
6729 | 0 | else if (strcmp(szDESOFLW, "UDHD ") == 0 || |
6730 | 0 | strcmp(szDESOFLW, "UDID ") == 0 || |
6731 | 0 | strcmp(szDESOFLW, "XHD ") == 0 || |
6732 | 0 | strcmp(szDESOFLW, "SXSHD ") == 0 || |
6733 | 0 | strcmp(szDESOFLW, "TXSHD ") == 0) |
6734 | 0 | { |
6735 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
6736 | 0 | "Unhandled value for DESOFLW: '%s'. " |
6737 | 0 | "Segment subheader fields will not be updated.", |
6738 | 0 | szDESOFLW); |
6739 | 0 | } |
6740 | 0 | else |
6741 | 0 | { |
6742 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6743 | 0 | "Invalid value for DESOFLW: '%s'", szDESOFLW); |
6744 | 0 | return false; |
6745 | 0 | } |
6746 | 0 | } |
6747 | | |
6748 | | // Extract DESSHL value |
6749 | 0 | constexpr int LEN_DESSHL = 4; |
6750 | 0 | char szDESSHL[LEN_DESSHL + 1]; |
6751 | 0 | const int OFFSET_DESSHL = |
6752 | 0 | 169 + (bIsTRE_OVERFLOW ? LEN_DESOFLW + LEN_DESITEM : 0); |
6753 | 0 | memcpy(szDESSHL, pabyDESData + OFFSET_DESSHL, LEN_DESSHL); |
6754 | 0 | szDESSHL[LEN_DESSHL] = '\0'; |
6755 | 0 | if (!isdigit(static_cast<unsigned char>(szDESSHL[0])) || |
6756 | 0 | !isdigit(static_cast<unsigned char>(szDESSHL[1])) || |
6757 | 0 | !isdigit(static_cast<unsigned char>(szDESSHL[2])) || |
6758 | 0 | !isdigit(static_cast<unsigned char>(szDESSHL[3]))) |
6759 | 0 | { |
6760 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Invalid value for DESSHL: '%s'", |
6761 | 0 | szDESSHL); |
6762 | 0 | return false; |
6763 | 0 | } |
6764 | 0 | const int nDESSHL = atoi(szDESSHL); |
6765 | 0 | const int nSubHeadLen = nDESSHL + MIN_LEN_DES_SUBHEADER; |
6766 | 0 | const int nDataLen = |
6767 | 0 | nTotalLen - nSubHeadLen; // Length of DESDATA field only |
6768 | 0 | if (nDataLen < 0) |
6769 | 0 | { |
6770 | 0 | CPLError( |
6771 | 0 | CE_Failure, CPLE_AppDefined, |
6772 | 0 | "Value of DESSHL = '%s' is not consistent with provided DESData", |
6773 | 0 | szDESSHL); |
6774 | 0 | return false; |
6775 | 0 | } |
6776 | | |
6777 | 0 | if (nSubHeadLen > 9998 || nDataLen > 999999998) |
6778 | 0 | { |
6779 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "DES is too big to be written"); |
6780 | 0 | return false; |
6781 | 0 | } |
6782 | | |
6783 | 0 | bool bOK = VSIFSeekL(fp, 0, SEEK_END) == 0; |
6784 | 0 | bOK &= VSIFWriteL("DE", 1, 2, fp) == 2; |
6785 | 0 | bOK &= VSIFWriteL(CPLSPrintf("%-25s", pszDESName), 1, 25, fp) == 25; |
6786 | 0 | bOK &= VSIFWriteL(pabyDESData, 1, nArrayLen, fp) == |
6787 | 0 | static_cast<size_t>(nArrayLen); |
6788 | | |
6789 | | // Update LDSH and LD in the NITF Header |
6790 | 0 | bOK &= VSIFSeekL(fp, nOffsetLDSH + iDES * 13, SEEK_SET) == 0; |
6791 | 0 | bOK &= VSIFWriteL(CPLSPrintf("%04d", nSubHeadLen), 1, 4, fp) == 4; |
6792 | 0 | bOK &= VSIFWriteL(CPLSPrintf("%09d", nDataLen), 1, 9, fp) == 9; |
6793 | |
|
6794 | 0 | if (nIXSOFLOffset > 0) |
6795 | 0 | { |
6796 | 0 | CPLDebug("NITF", "Patching IXSOFL of image %d to %d", iDES + 1, |
6797 | 0 | nDESITEM); |
6798 | 0 | bOK &= VSIFSeekL(fp, nIXSOFLOffset, SEEK_SET) == 0; |
6799 | 0 | bOK &= VSIFWriteL(CPLSPrintf("%03d", nDESITEM), 1, 3, fp) == 3; |
6800 | 0 | } |
6801 | |
|
6802 | 0 | return bOK; |
6803 | 0 | } |
6804 | | |
6805 | | /************************************************************************/ |
6806 | | /* NITFWriteDES() */ |
6807 | | /************************************************************************/ |
6808 | | |
6809 | | static bool NITFWriteDES(const char *pszFilename, VSILFILE *&fpVSIL, |
6810 | | GDALOffsetPatcher::OffsetPatcher *offsetPatcher, |
6811 | | const CPLStringList &aosOptions, int nReciprocalScale) |
6812 | 20 | { |
6813 | 20 | int nDESFound = offsetPatcher ? 1 : 0; |
6814 | 20 | for (const char *pszOpt : aosOptions) |
6815 | 5.50k | { |
6816 | 5.50k | if (cpl::starts_with(std::string_view(pszOpt), |
6817 | 5.50k | std::string_view("DES="))) |
6818 | 0 | { |
6819 | 0 | nDESFound++; |
6820 | 0 | } |
6821 | 5.50k | } |
6822 | 20 | if (nDESFound == 0) |
6823 | 20 | { |
6824 | 20 | return true; |
6825 | 20 | } |
6826 | | |
6827 | | /* -------------------------------------------------------------------- */ |
6828 | | /* Open the target file if not already done. */ |
6829 | | /* -------------------------------------------------------------------- */ |
6830 | 0 | if (fpVSIL == nullptr) |
6831 | 0 | fpVSIL = VSIFOpenL(pszFilename, "r+b"); |
6832 | 0 | if (fpVSIL == nullptr) |
6833 | 0 | return false; |
6834 | | |
6835 | | // NUMI offset is at a fixed offset 360, unless there is a FSDWNG field |
6836 | 0 | const vsi_l_offset nNumIOffset = |
6837 | 0 | 360 + (NITFHasFSDWNGField(fpVSIL) ? 40 : 0); |
6838 | |
|
6839 | 0 | char achNUMI[4]; // 3 digits plus null character |
6840 | 0 | achNUMI[3] = '\0'; |
6841 | 0 | bool bOK = VSIFSeekL(fpVSIL, nNumIOffset, SEEK_SET) == 0; |
6842 | 0 | bOK &= VSIFReadL(achNUMI, 3, 1, fpVSIL) == 1; |
6843 | 0 | int nIM = atoi(achNUMI); |
6844 | |
|
6845 | 0 | char achNUMG[4]; // 3 digits plus null character |
6846 | 0 | achNUMG[3] = '\0'; |
6847 | | |
6848 | | // 3 for size of NUMI. 6 and 10 are the field size for LISH and LI |
6849 | 0 | const vsi_l_offset nNumGOffset = nNumIOffset + 3 + nIM * (6 + 10); |
6850 | 0 | bOK &= VSIFSeekL(fpVSIL, nNumGOffset, SEEK_SET) == 0; |
6851 | 0 | bOK &= VSIFReadL(achNUMG, 3, 1, fpVSIL) == 1; |
6852 | 0 | const int nGS = atoi(achNUMG); |
6853 | | |
6854 | | // NUMT offset |
6855 | | // 3 for size of NUMG. 4 and 6 are the field size of LSSH and LS. |
6856 | | // the last + 3 is for NUMX field, which is not used |
6857 | 0 | const vsi_l_offset nNumTOffset = nNumGOffset + 3 + nGS * (4 + 6) + 3; |
6858 | 0 | char achNUMT[4]; |
6859 | 0 | bOK &= VSIFSeekL(fpVSIL, nNumTOffset, SEEK_SET) == 0; |
6860 | 0 | bOK &= VSIFReadL(achNUMT, 3, 1, fpVSIL) == 1; |
6861 | 0 | achNUMT[3] = '\0'; |
6862 | 0 | const int nNUMT = atoi(achNUMT); |
6863 | | |
6864 | | // NUMDES offset |
6865 | | // 3 for size of NUMT. 4 and 5 are the field size of LTSH and LT. |
6866 | 0 | const vsi_l_offset nNumDESOffset = nNumTOffset + 3 + (4 + 5) * nNUMT; |
6867 | 0 | char achNUMDES[4]; |
6868 | 0 | bOK &= VSIFSeekL(fpVSIL, nNumDESOffset, SEEK_SET) == 0; |
6869 | 0 | bOK &= VSIFReadL(achNUMDES, 3, 1, fpVSIL) == 1; |
6870 | 0 | achNUMDES[3] = '\0'; |
6871 | |
|
6872 | 0 | if (!bOK || atoi(achNUMDES) != nDESFound) |
6873 | 0 | { |
6874 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6875 | 0 | "It appears an attempt was made to add or update DE\n" |
6876 | 0 | "segments on an NITF file with existing segments. This\n" |
6877 | 0 | "is not currently supported by the GDAL NITF driver."); |
6878 | 0 | return false; |
6879 | 0 | } |
6880 | | |
6881 | 0 | const auto nOffsetLDSH = nNumDESOffset + 3; |
6882 | |
|
6883 | 0 | int iDES = 0; |
6884 | 0 | if (offsetPatcher) |
6885 | 0 | { |
6886 | 0 | bOK = RPFFrameWriteCADRG_RPFDES(offsetPatcher, fpVSIL, nOffsetLDSH, |
6887 | 0 | aosOptions, nReciprocalScale); |
6888 | 0 | iDES = 1; |
6889 | 0 | } |
6890 | |
|
6891 | 0 | for (int iOption = 0; bOK && iOption < aosOptions.size(); iOption++) |
6892 | 0 | { |
6893 | 0 | if (!cpl::starts_with(std::string_view(aosOptions[iOption]), |
6894 | 0 | std::string_view("DES="))) |
6895 | 0 | { |
6896 | 0 | continue; |
6897 | 0 | } |
6898 | | |
6899 | | /* We don't use CPLParseNameValue() as it removes leading spaces */ |
6900 | | /* from the value (see #3088) */ |
6901 | 0 | const char *pszDESOpt = aosOptions[iOption] + 4; |
6902 | 0 | const char *pszDelim = strchr(pszDESOpt, '='); |
6903 | 0 | if (pszDelim == nullptr) |
6904 | 0 | { |
6905 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6906 | 0 | "Could not parse creation options %s", pszDESOpt); |
6907 | 0 | return false; |
6908 | 0 | } |
6909 | | |
6910 | 0 | const size_t nNameLength = strlen(pszDESOpt) - strlen(pszDelim); |
6911 | 0 | if (nNameLength > 25) |
6912 | 0 | { |
6913 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6914 | 0 | "Specified DESID is too long %s", pszDESOpt); |
6915 | 0 | return false; |
6916 | 0 | } |
6917 | | |
6918 | 0 | char *pszDESName = static_cast<char *>(CPLMalloc(nNameLength + 1)); |
6919 | 0 | memcpy(pszDESName, pszDESOpt, nNameLength); |
6920 | 0 | pszDESName[nNameLength] = '\0'; |
6921 | |
|
6922 | 0 | const char *pszEscapedContents = pszDelim + 1; |
6923 | |
|
6924 | 0 | int nContentLength = 0; |
6925 | 0 | GByte *pabyUnescapedContents = |
6926 | 0 | reinterpret_cast<GByte *>(CPLUnescapeString( |
6927 | 0 | pszEscapedContents, &nContentLength, CPLES_BackslashQuotable)); |
6928 | |
|
6929 | 0 | if (!NITFWriteDES(fpVSIL, pszFilename, nOffsetLDSH, iDES, pszDESName, |
6930 | 0 | pabyUnescapedContents, nContentLength)) |
6931 | 0 | { |
6932 | 0 | CPLFree(pszDESName); |
6933 | 0 | CPLFree(pabyUnescapedContents); |
6934 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Could not write DES %d", |
6935 | 0 | iDES); |
6936 | 0 | return false; |
6937 | 0 | } |
6938 | | |
6939 | 0 | CPLFree(pszDESName); |
6940 | 0 | CPLFree(pabyUnescapedContents); |
6941 | |
|
6942 | 0 | iDES++; |
6943 | 0 | } |
6944 | | |
6945 | 0 | return bOK; |
6946 | 0 | } |
6947 | | |
6948 | | /************************************************************************/ |
6949 | | /* UpdateFileLength() */ |
6950 | | /************************************************************************/ |
6951 | | |
6952 | | static bool UpdateFileLength(VSILFILE *fp) |
6953 | 0 | { |
6954 | | |
6955 | | /* -------------------------------------------------------------------- */ |
6956 | | /* Update total file length. */ |
6957 | | /* -------------------------------------------------------------------- */ |
6958 | 0 | bool bOK = VSIFSeekL(fp, 0, SEEK_END) == 0; |
6959 | 0 | GUIntBig nFileLen = VSIFTellL(fp); |
6960 | | |
6961 | | // Offset to file length entry |
6962 | 0 | const vsi_l_offset nFLOffset = 342 + (NITFHasFSDWNGField(fp) ? 40 : 0); |
6963 | 0 | bOK &= VSIFSeekL(fp, nFLOffset, SEEK_SET) == 0; |
6964 | 0 | if (nFileLen >= NITF_MAX_FILE_SIZE) |
6965 | 0 | { |
6966 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6967 | 0 | "Too big file : " CPL_FRMT_GUIB |
6968 | 0 | ". Truncating to " CPL_FRMT_GUIB, |
6969 | 0 | nFileLen, NITF_MAX_FILE_SIZE - 1); |
6970 | 0 | nFileLen = NITF_MAX_FILE_SIZE - 1; |
6971 | 0 | } |
6972 | 0 | CPLString osLen = |
6973 | 0 | CPLString().Printf("%012" CPL_FRMT_GB_WITHOUT_PREFIX "u", nFileLen); |
6974 | 0 | bOK &= VSIFWriteL(reinterpret_cast<const void *>(osLen.c_str()), 12, 1, |
6975 | 0 | fp) == 1; |
6976 | 0 | return bOK; |
6977 | 0 | } |
6978 | | |
6979 | | /************************************************************************/ |
6980 | | /* NITFWriteExtraSegments() */ |
6981 | | /************************************************************************/ |
6982 | | |
6983 | | static bool |
6984 | | NITFWriteExtraSegments(const char *pszFilename, VSILFILE *fpIn, |
6985 | | CSLConstList papszCgmMD, CSLConstList papszTextMD, |
6986 | | GDALOffsetPatcher::OffsetPatcher *offsetPatcher, |
6987 | | const CPLStringList &aosOptions, int nReciprocalScale) |
6988 | 20 | { |
6989 | 20 | VSILFILE *fp = fpIn; |
6990 | 20 | bool bOK = NITFWriteCGMSegments(pszFilename, fp, papszCgmMD); |
6991 | 20 | bOK &= NITFWriteTextSegments(pszFilename, fp, papszTextMD); |
6992 | 20 | bOK &= NITFWriteDES(pszFilename, fp, offsetPatcher, aosOptions, |
6993 | 20 | nReciprocalScale); |
6994 | 20 | if (fp) |
6995 | 0 | { |
6996 | 0 | bOK &= UpdateFileLength(fp); |
6997 | |
|
6998 | 0 | if (fp != fpIn && VSIFCloseL(fp) != 0) |
6999 | 0 | bOK = false; |
7000 | |
|
7001 | 0 | if (!bOK) |
7002 | 0 | { |
7003 | 0 | CPLError(CE_Failure, CPLE_FileIO, "I/O error"); |
7004 | 0 | } |
7005 | 0 | } |
7006 | 20 | return bOK; |
7007 | 20 | } |
7008 | | |
7009 | | /************************************************************************/ |
7010 | | /* NITFWriteJPEGImage() */ |
7011 | | /************************************************************************/ |
7012 | | |
7013 | | #ifdef JPEG_SUPPORTED |
7014 | | |
7015 | | int NITFWriteJPEGBlock(GDALDataset *poSrcDS, VSILFILE *fp, int nBlockXOff, |
7016 | | int nBlockYOff, int nBlockXSize, int nBlockYSize, |
7017 | | int bProgressive, int nQuality, const GByte *pabyAPP6, |
7018 | | int nRestartInterval, GDALProgressFunc pfnProgress, |
7019 | | void *pProgressData); |
7020 | | |
7021 | | static bool NITFWriteJPEGImage(GDALDataset *poSrcDS, VSILFILE *fp, |
7022 | | vsi_l_offset nStartOffset, |
7023 | | CSLConstList papszOptions, |
7024 | | GDALProgressFunc pfnProgress, |
7025 | | void *pProgressData) |
7026 | 0 | { |
7027 | 0 | if (!pfnProgress(0.0, nullptr, pProgressData)) |
7028 | 0 | return false; |
7029 | | |
7030 | | /* -------------------------------------------------------------------- */ |
7031 | | /* Some some rudimentary checks */ |
7032 | | /* -------------------------------------------------------------------- */ |
7033 | 0 | const int nBands = poSrcDS->GetRasterCount(); |
7034 | 0 | if (nBands != 1 && nBands != 3) |
7035 | 0 | { |
7036 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
7037 | 0 | "JPEG driver doesn't support %d bands. Must be 1 (grey) " |
7038 | 0 | "or 3 (RGB) bands.\n", |
7039 | 0 | nBands); |
7040 | |
|
7041 | 0 | return false; |
7042 | 0 | } |
7043 | | |
7044 | 0 | GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType(); |
7045 | |
|
7046 | 0 | #if defined(JPEG_LIB_MK1) || defined(JPEG_DUAL_MODE_8_12) |
7047 | 0 | if (eDT != GDT_UInt8 && eDT != GDT_UInt16) |
7048 | 0 | { |
7049 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
7050 | 0 | "JPEG driver doesn't support data type %s. " |
7051 | 0 | "Only eight and twelve bit bands supported (Mk1 libjpeg).\n", |
7052 | 0 | GDALGetDataTypeName( |
7053 | 0 | poSrcDS->GetRasterBand(1)->GetRasterDataType())); |
7054 | |
|
7055 | 0 | return false; |
7056 | 0 | } |
7057 | | |
7058 | 0 | if (eDT == GDT_UInt16 || eDT == GDT_Int16) |
7059 | 0 | eDT = GDT_UInt16; |
7060 | 0 | else |
7061 | 0 | eDT = GDT_UInt8; |
7062 | |
|
7063 | | #else |
7064 | | if (eDT != GDT_UInt8) |
7065 | | { |
7066 | | CPLError(CE_Failure, CPLE_NotSupported, |
7067 | | "JPEG driver doesn't support data type %s. " |
7068 | | "Only eight bit byte bands supported.\n", |
7069 | | GDALGetDataTypeName( |
7070 | | poSrcDS->GetRasterBand(1)->GetRasterDataType())); |
7071 | | |
7072 | | return false; |
7073 | | } |
7074 | | |
7075 | | eDT = GDT_UInt8; // force to 8bit. |
7076 | | #endif |
7077 | | |
7078 | | /* -------------------------------------------------------------------- */ |
7079 | | /* What options has the user selected? */ |
7080 | | /* -------------------------------------------------------------------- */ |
7081 | 0 | int nQuality = 75; |
7082 | 0 | if (CSLFetchNameValue(papszOptions, "QUALITY") != nullptr) |
7083 | 0 | { |
7084 | 0 | nQuality = atoi(CSLFetchNameValue(papszOptions, "QUALITY")); |
7085 | 0 | if (nQuality < 10 || nQuality > 100) |
7086 | 0 | { |
7087 | 0 | CPLError(CE_Failure, CPLE_IllegalArg, |
7088 | 0 | "QUALITY=%s is not a legal value in the range 10-100.", |
7089 | 0 | CSLFetchNameValue(papszOptions, "QUALITY")); |
7090 | 0 | return false; |
7091 | 0 | } |
7092 | 0 | } |
7093 | | |
7094 | 0 | int nRestartInterval = -1; |
7095 | 0 | if (CSLFetchNameValue(papszOptions, "RESTART_INTERVAL") != nullptr) |
7096 | 0 | { |
7097 | 0 | nRestartInterval = |
7098 | 0 | atoi(CSLFetchNameValue(papszOptions, "RESTART_INTERVAL")); |
7099 | 0 | } |
7100 | |
|
7101 | 0 | const bool bProgressive = CPLFetchBool(papszOptions, "PROGRESSIVE", false); |
7102 | | |
7103 | | /* -------------------------------------------------------------------- */ |
7104 | | /* Compute blocking factors */ |
7105 | | /* -------------------------------------------------------------------- */ |
7106 | 0 | const int nXSize = poSrcDS->GetRasterXSize(); |
7107 | 0 | const int nYSize = poSrcDS->GetRasterYSize(); |
7108 | 0 | int nNPPBH = nXSize; |
7109 | 0 | int nNPPBV = nYSize; |
7110 | |
|
7111 | 0 | if (CSLFetchNameValue(papszOptions, "BLOCKXSIZE") != nullptr) |
7112 | 0 | nNPPBH = atoi(CSLFetchNameValue(papszOptions, "BLOCKXSIZE")); |
7113 | |
|
7114 | 0 | if (CSLFetchNameValue(papszOptions, "BLOCKYSIZE") != nullptr) |
7115 | 0 | nNPPBV = atoi(CSLFetchNameValue(papszOptions, "BLOCKYSIZE")); |
7116 | |
|
7117 | 0 | if (CSLFetchNameValue(papszOptions, "NPPBH") != nullptr) |
7118 | 0 | nNPPBH = atoi(CSLFetchNameValue(papszOptions, "NPPBH")); |
7119 | |
|
7120 | 0 | if (CSLFetchNameValue(papszOptions, "NPPBV") != nullptr) |
7121 | 0 | nNPPBV = atoi(CSLFetchNameValue(papszOptions, "NPPBV")); |
7122 | |
|
7123 | 0 | if (nNPPBH <= 0 || nNPPBV <= 0 || nNPPBH > 9999 || nNPPBV > 9999) |
7124 | 0 | { |
7125 | 0 | nNPPBH = 256; |
7126 | 0 | nNPPBV = 256; |
7127 | 0 | } |
7128 | |
|
7129 | 0 | const int nNBPR = DIV_ROUND_UP(nXSize, nNPPBH); |
7130 | 0 | const int nNBPC = DIV_ROUND_UP(nYSize, nNPPBV); |
7131 | | |
7132 | | /* -------------------------------------------------------------------- */ |
7133 | | /* Creates APP6 NITF application segment (required by MIL-STD-188-198) */ |
7134 | | /* see #3345 */ |
7135 | | /* -------------------------------------------------------------------- */ |
7136 | 0 | GByte abyAPP6[23]; |
7137 | 0 | memcpy(abyAPP6, "NITF", 4); |
7138 | 0 | abyAPP6[4] = 0; |
7139 | 0 | int nOffset = 5; |
7140 | | |
7141 | | /* Version : 2.0 */ |
7142 | 0 | GUInt16 nUInt16 = 0x0200; |
7143 | 0 | CPL_MSBPTR16(&nUInt16); |
7144 | 0 | memcpy(abyAPP6 + nOffset, &nUInt16, sizeof(nUInt16)); |
7145 | 0 | nOffset += sizeof(nUInt16); |
7146 | | |
7147 | | /* IMODE */ |
7148 | 0 | abyAPP6[nOffset] = (nBands == 1) ? 'B' : 'P'; |
7149 | 0 | nOffset++; |
7150 | | |
7151 | | /* Number of image blocks per row */ |
7152 | 0 | nUInt16 = static_cast<GUInt16>(nNBPR); |
7153 | 0 | CPL_MSBPTR16(&nUInt16); |
7154 | 0 | memcpy(abyAPP6 + nOffset, &nUInt16, sizeof(nUInt16)); |
7155 | 0 | nOffset += sizeof(nUInt16); |
7156 | | |
7157 | | /* Number of image blocks per column */ |
7158 | 0 | nUInt16 = static_cast<GUInt16>(nNBPC); |
7159 | 0 | CPL_MSBPTR16(&nUInt16); |
7160 | 0 | memcpy(abyAPP6 + nOffset, &nUInt16, sizeof(nUInt16)); |
7161 | 0 | nOffset += sizeof(nUInt16); |
7162 | | |
7163 | | /* Image color */ |
7164 | 0 | abyAPP6[nOffset] = (nBands == 1) ? 0 : 1; |
7165 | 0 | nOffset++; |
7166 | | |
7167 | | /* Original sample precision */ |
7168 | | /* coverity[dead_error_line] */ |
7169 | 0 | abyAPP6[nOffset] = (eDT == GDT_UInt16) ? 12 : 8; |
7170 | 0 | nOffset++; |
7171 | | |
7172 | | /* Image class */ |
7173 | 0 | abyAPP6[nOffset] = 0; |
7174 | 0 | nOffset++; |
7175 | | |
7176 | | /* JPEG coding process */ |
7177 | | /* coverity[dead_error_line] */ |
7178 | 0 | abyAPP6[nOffset] = (eDT == GDT_UInt16) ? 4 : 1; |
7179 | 0 | nOffset++; |
7180 | | |
7181 | | /* Quality */ |
7182 | 0 | abyAPP6[nOffset] = 0; |
7183 | 0 | nOffset++; |
7184 | | |
7185 | | /* Stream color */ |
7186 | 0 | abyAPP6[nOffset] = (nBands == 1) ? 0 /* Monochrome */ : 2 /* YCbCr*/; |
7187 | 0 | nOffset++; |
7188 | | |
7189 | | /* Stream bits */ |
7190 | | /* coverity[dead_error_line] */ |
7191 | 0 | abyAPP6[nOffset] = (eDT == GDT_UInt16) ? 12 : 8; |
7192 | 0 | nOffset++; |
7193 | | |
7194 | | /* Horizontal filtering */ |
7195 | 0 | abyAPP6[nOffset] = 1; |
7196 | 0 | nOffset++; |
7197 | | |
7198 | | /* Vertical filtering */ |
7199 | 0 | abyAPP6[nOffset] = 1; |
7200 | 0 | nOffset++; |
7201 | | |
7202 | | /* Reserved */ |
7203 | 0 | abyAPP6[nOffset] = 0; |
7204 | 0 | nOffset++; |
7205 | 0 | abyAPP6[nOffset] = 0; |
7206 | 0 | nOffset++; |
7207 | 0 | (void)nOffset; |
7208 | |
|
7209 | 0 | CPLAssert(nOffset == sizeof(abyAPP6)); |
7210 | | |
7211 | | /* -------------------------------------------------------------------- */ |
7212 | | /* Prepare block map if necessary */ |
7213 | | /* -------------------------------------------------------------------- */ |
7214 | |
|
7215 | 0 | bool bOK = VSIFSeekL(fp, nStartOffset, SEEK_SET) == 0; |
7216 | |
|
7217 | 0 | const char *pszIC = CSLFetchNameValue(papszOptions, "IC"); |
7218 | 0 | GUInt32 nIMDATOFF = 0; |
7219 | 0 | constexpr GUInt32 BLOCKMAP_HEADER_SIZE = 4 + 2 + 2 + 2; |
7220 | 0 | if (EQUAL(pszIC, "M3")) |
7221 | 0 | { |
7222 | | /* Prepare the block map */ |
7223 | 0 | GUInt32 nIMDATOFF_MSB = BLOCKMAP_HEADER_SIZE + nNBPC * nNBPR * 4; |
7224 | 0 | nIMDATOFF = nIMDATOFF_MSB; |
7225 | 0 | GUInt16 nBMRLNTH = 4; |
7226 | 0 | GUInt16 nTMRLNTH = 0; |
7227 | 0 | GUInt16 nTPXCDLNTH = 0; |
7228 | |
|
7229 | 0 | CPL_MSBPTR32(&nIMDATOFF_MSB); |
7230 | 0 | CPL_MSBPTR16(&nBMRLNTH); |
7231 | 0 | CPL_MSBPTR16(&nTMRLNTH); |
7232 | 0 | CPL_MSBPTR16(&nTPXCDLNTH); |
7233 | |
|
7234 | 0 | bOK &= VSIFWriteL(&nIMDATOFF_MSB, 4, 1, fp) == 1; |
7235 | 0 | bOK &= VSIFWriteL(&nBMRLNTH, 2, 1, fp) == 1; |
7236 | 0 | bOK &= VSIFWriteL(&nTMRLNTH, 2, 1, fp) == 1; |
7237 | 0 | bOK &= VSIFWriteL(&nTPXCDLNTH, 2, 1, fp) == 1; |
7238 | | |
7239 | | /* Reserve space for the table itself */ |
7240 | 0 | bOK &= VSIFSeekL(fp, static_cast<vsi_l_offset>(nNBPC) * nNBPR * 4, |
7241 | 0 | SEEK_CUR) == 0; |
7242 | 0 | } |
7243 | | |
7244 | | /* -------------------------------------------------------------------- */ |
7245 | | /* Copy each block */ |
7246 | | /* -------------------------------------------------------------------- */ |
7247 | 0 | for (int nBlockYOff = 0; bOK && nBlockYOff < nNBPC; nBlockYOff++) |
7248 | 0 | { |
7249 | 0 | for (int nBlockXOff = 0; bOK && nBlockXOff < nNBPR; nBlockXOff++) |
7250 | 0 | { |
7251 | | #ifdef DEBUG_VERBOSE |
7252 | | CPLDebug("NITF", "nBlockXOff=%d/%d, nBlockYOff=%d/%d", nBlockXOff, |
7253 | | nNBPR, nBlockYOff, nNBPC); |
7254 | | #endif |
7255 | 0 | if (EQUAL(pszIC, "M3")) |
7256 | 0 | { |
7257 | | /* Write block offset for current block */ |
7258 | |
|
7259 | 0 | const GUIntBig nCurPos = VSIFTellL(fp); |
7260 | 0 | bOK &= VSIFSeekL(fp, |
7261 | 0 | nStartOffset + BLOCKMAP_HEADER_SIZE + |
7262 | 0 | 4 * (nBlockYOff * nNBPR + nBlockXOff), |
7263 | 0 | SEEK_SET) == 0; |
7264 | 0 | const GUIntBig nBlockOffset = |
7265 | 0 | nCurPos - nStartOffset - nIMDATOFF; |
7266 | 0 | if (nBlockOffset <= UINT_MAX) |
7267 | 0 | { |
7268 | 0 | GUInt32 nBlockOffset32 = static_cast<GUInt32>(nBlockOffset); |
7269 | 0 | CPL_MSBPTR32(&nBlockOffset32); |
7270 | 0 | bOK &= VSIFWriteL(&nBlockOffset32, 4, 1, fp) == 1; |
7271 | 0 | } |
7272 | 0 | else |
7273 | 0 | { |
7274 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
7275 | 0 | "Offset for block (%d, %d) = " CPL_FRMT_GUIB |
7276 | 0 | ". Cannot fit into 32 bits...", |
7277 | 0 | nBlockXOff, nBlockYOff, nBlockOffset); |
7278 | |
|
7279 | 0 | GUInt32 nBlockOffset32 = UINT_MAX; |
7280 | 0 | for (int i = nBlockYOff * nNBPR + nBlockXOff; |
7281 | 0 | bOK && i < nNBPC * nNBPR; i++) |
7282 | 0 | { |
7283 | 0 | bOK &= VSIFWriteL(&nBlockOffset32, 4, 1, fp) == 1; |
7284 | 0 | } |
7285 | 0 | if (!bOK) |
7286 | 0 | { |
7287 | 0 | CPLError(CE_Failure, CPLE_FileIO, "I/O error"); |
7288 | 0 | } |
7289 | 0 | return bOK; |
7290 | 0 | } |
7291 | 0 | bOK &= VSIFSeekL(fp, nCurPos, SEEK_SET) == 0; |
7292 | 0 | } |
7293 | | |
7294 | 0 | if (bOK && |
7295 | 0 | !NITFWriteJPEGBlock( |
7296 | 0 | poSrcDS, fp, nBlockXOff, nBlockYOff, nNPPBH, nNPPBV, |
7297 | 0 | bProgressive, nQuality, |
7298 | 0 | (nBlockXOff == 0 && nBlockYOff == 0) ? abyAPP6 : nullptr, |
7299 | 0 | nRestartInterval, pfnProgress, pProgressData)) |
7300 | 0 | { |
7301 | 0 | return false; |
7302 | 0 | } |
7303 | 0 | } |
7304 | 0 | } |
7305 | 0 | if (!bOK) |
7306 | 0 | { |
7307 | 0 | CPLError(CE_Failure, CPLE_FileIO, "I/O error"); |
7308 | 0 | } |
7309 | 0 | return true; |
7310 | 0 | } |
7311 | | |
7312 | | #endif /* def JPEG_SUPPORTED */ |
7313 | | |
7314 | | /************************************************************************/ |
7315 | | /* GDALRegister_NITF() */ |
7316 | | /************************************************************************/ |
7317 | | |
7318 | | typedef struct |
7319 | | { |
7320 | | int nMaxLen; |
7321 | | const char *pszName; |
7322 | | const char *pszDescription; |
7323 | | } NITFFieldDescription; |
7324 | | |
7325 | | /* Keep in sync with NITFCreate */ |
7326 | | static const NITFFieldDescription asFieldDescription[] = { |
7327 | | {2, "CLEVEL", "Complexity level"}, |
7328 | | {10, "OSTAID", "Originating Station ID"}, |
7329 | | {14, "FDT", "File Date and Time"}, |
7330 | | {80, "FTITLE", "File Title"}, |
7331 | | {1, "FSCLAS", "File Security Classification"}, |
7332 | | {2, "FSCLSY", "File Classification Security System (NITF02.10/NSIF only)"}, |
7333 | | {11, "FSCODE", "File Codewords"}, |
7334 | | {2, "FSCTLH", "File Control and Handling"}, |
7335 | | {20, "FSREL", "File Releasing Instructions"}, |
7336 | | {2, "FSDCTP", "File Declassification Type (NITF02.10/NSIF only)"}, |
7337 | | {8, "FSDCDT", "File Declassification Date (NITF02.10/NSIF only)"}, |
7338 | | {4, "FSDCXM", "File Declassification Exemption (NITF02.10/NSIF only)"}, |
7339 | | {1, "FSDG", "File Downgrade (NITF02.10/NSIF only)"}, |
7340 | | {8, "FSDGDT", "File Downgrade Date (NITF02.10/NSIF only)"}, |
7341 | | {6, "FSDWNG", "File Security Downgrade (NITF02.00 only)"}, |
7342 | | {40, "FSDEVT", "File Downgrading event (NITF02.00 only)"}, |
7343 | | {43, "FSCLTX", "File Classification Text (NITF02.10/NSIF only)"}, |
7344 | | {1, "FSCATP", "File Classification Authority Type (NITF02.10/NSIF only)"}, |
7345 | | {40, "FSCAUT", "File Classification Authority"}, |
7346 | | {1, "FSCRSN", "File Classification Reason (NITF02.10/NSIF only)"}, |
7347 | | {8, "FSSRDT", "File Security Source Date (NITF02.10/NSIF only)"}, |
7348 | | {15, "FSCTLN", "File Security Control Number"}, |
7349 | | {5, "FSCOP", "File Copy Number"}, |
7350 | | {5, "FSCPYS", "File Number of Copies"}, |
7351 | | {24, "ONAME", "Originator Name"}, |
7352 | | {18, "OPHONE", "Originator Phone Number"}, |
7353 | | {10, "IID1", "Image Identifier 1"}, |
7354 | | {14, "IDATIM", "Image Date and Time"}, |
7355 | | {17, "TGTID", "Target Identifier"}, |
7356 | | {80, "IID2", "Image Identifier 2 (NITF02.10/NSIF only)"}, |
7357 | | {80, "ITITLE", "Image Title (NITF02.00 only)"}, |
7358 | | {1, "ISCLAS", "Image Security Classification"}, |
7359 | | {2, "ISCLSY", "Image Classification Security System (NITF02.10/NSIF only)"}, |
7360 | | {11, "ISCODE", "Image Codewords"}, |
7361 | | {2, "ISCTLH", "Image Control and Handling"}, |
7362 | | {20, "ISREL", "Image Releasing Instructions (NITF02.10/NSIF only)"}, |
7363 | | {2, "ISDCTP", "Image Declassification Type (NITF02.10/NSIF only)"}, |
7364 | | {8, "ISDCDT", "Image Declassification Date (NITF02.10/NSIF only)"}, |
7365 | | {4, "ISDCXM", "Image Declassification Exemption (NITF02.10/NSIF only)"}, |
7366 | | {1, "ISDG", "Image Downgrade (NITF02.10/NSIF only)"}, |
7367 | | {8, "ISDGDT", "Image Downgrade Date (NITF02.10/NSIF only)"}, |
7368 | | {6, "ISDWNG", "Image Security Downgrade (NITF02.00 only)"}, |
7369 | | {40, "ISDEVT", "Image Downgrading event (NITF02.00 only)"}, |
7370 | | {43, "ISCLTX", "Image Classification Text (NITF02.10/NSIF only)"}, |
7371 | | {1, "ISCATP", "Image Classification Authority Type (NITF02.10/NSIF only)"}, |
7372 | | {40, "ISCAUT", "Image Classification Authority"}, |
7373 | | {1, "ISCRSN", "Image Classification Reason (NITF02.10/NSIF only)"}, |
7374 | | {8, "ISSRDT", "Image Security Source Date (NITF02.10/NSIF only)"}, |
7375 | | {15, "ISCTLN", "Image Security Control Number (NITF02.10/NSIF only)"}, |
7376 | | {42, "ISORCE", "Image Source"}, |
7377 | | {8, "ICAT", "Image Category"}, |
7378 | | {2, "ABPP", "Actual Bits-Per-Pixel Per Band"}, |
7379 | | {1, "PJUST", "Pixel Justification"}, |
7380 | | {720, "ICOM", "Image Comments (up to 9x80 characters)"}, |
7381 | | {3, "IDLVL", "Image Display Level"}, |
7382 | | {3, "IALVL", "Image Attachment Level"}, |
7383 | | {5, "ILOCROW", "Image Location Row"}, |
7384 | | {5, "ILOCCOL", "Image Location Column"}, |
7385 | | }; |
7386 | | |
7387 | | /* Keep in sync with NITFWriteBLOCKA */ |
7388 | | static const char *const apszFieldsBLOCKA[] = { |
7389 | | "BLOCK_INSTANCE", "0", "2", "N_GRAY", "2", "5", |
7390 | | "L_LINES", "7", "5", "LAYOVER_ANGLE", "12", "3", |
7391 | | "SHADOW_ANGLE", "15", "3", "BLANKS", "18", "16", |
7392 | | "FRLC_LOC", "34", "21", "LRLC_LOC", "55", "21", |
7393 | | "LRFC_LOC", "76", "21", "FRFC_LOC", "97", "21", |
7394 | | nullptr, nullptr, nullptr}; |
7395 | | |
7396 | | /************************************************************************/ |
7397 | | /* NITFDriver */ |
7398 | | /************************************************************************/ |
7399 | | |
7400 | | class NITFDriver final : public GDALDriver |
7401 | | { |
7402 | | std::recursive_mutex m_oMutex{}; |
7403 | | bool m_bCreationOptionListInitialized = false; |
7404 | | void InitCreationOptionList(); |
7405 | | |
7406 | | public: |
7407 | | const char *GetMetadataItem(const char *pszName, |
7408 | | const char *pszDomain) override; |
7409 | | |
7410 | | CSLConstList GetMetadata(const char *pszDomain) override |
7411 | 21 | { |
7412 | 21 | std::lock_guard oLock(m_oMutex); |
7413 | 21 | InitCreationOptionList(); |
7414 | 21 | return GDALDriver::GetMetadata(pszDomain); |
7415 | 21 | } |
7416 | | }; |
7417 | | |
7418 | | /************************************************************************/ |
7419 | | /* NITFDriver::GetMetadataItem() */ |
7420 | | /************************************************************************/ |
7421 | | |
7422 | | const char *NITFDriver::GetMetadataItem(const char *pszName, |
7423 | | const char *pszDomain) |
7424 | 1.37M | { |
7425 | 1.37M | std::lock_guard oLock(m_oMutex); |
7426 | 1.37M | if (EQUAL(pszName, GDAL_DMD_CREATIONOPTIONLIST)) |
7427 | 27 | { |
7428 | 27 | InitCreationOptionList(); |
7429 | 27 | } |
7430 | 1.37M | return GDALDriver::GetMetadataItem(pszName, pszDomain); |
7431 | 1.37M | } |
7432 | | |
7433 | | /************************************************************************/ |
7434 | | /* InitCreationOptionList() */ |
7435 | | /************************************************************************/ |
7436 | | |
7437 | | void NITFDriver::InitCreationOptionList() |
7438 | 48 | { |
7439 | 48 | if (m_bCreationOptionListInitialized) |
7440 | 47 | return; |
7441 | 1 | m_bCreationOptionListInitialized = true; |
7442 | | |
7443 | 1 | const bool bHasJP2ECW = GDALGetDriverByName("JP2ECW") != nullptr; |
7444 | 1 | const bool bHasJP2KAK = GDALGetDriverByName("JP2KAK") != nullptr; |
7445 | 1 | const bool bHasJP2OPENJPEG = GDALGetDriverByName("JP2OPENJPEG") != nullptr; |
7446 | 1 | const bool bHasJPEG2000Drivers = |
7447 | 1 | bHasJP2ECW || bHasJP2KAK || bHasJP2OPENJPEG; |
7448 | | |
7449 | 1 | CPLString osCreationOptions = |
7450 | 1 | "<CreationOptionList>" |
7451 | 1 | " <Option name='IC' type='string-select' default='NC' " |
7452 | 1 | "description='Compression mode. NC=no compression. " |
7453 | 1 | #ifdef JPEG_SUPPORTED |
7454 | 1 | "C3/M3=JPEG compression. " |
7455 | 1 | #endif |
7456 | 1 | "C4=VQ compression (only for PRODUCT_TYPE=CADRG). "; |
7457 | | |
7458 | 1 | if (bHasJPEG2000Drivers) |
7459 | 0 | osCreationOptions += |
7460 | 0 | "C8=JP2 compression through the JPEG2000 write capable drivers"; |
7461 | | |
7462 | 1 | osCreationOptions += "'>" |
7463 | 1 | " <Value>NC</Value>" |
7464 | 1 | #ifdef JPEG_SUPPORTED |
7465 | 1 | " <Value>C3</Value>" |
7466 | 1 | " <Value>M3</Value>" |
7467 | 1 | #endif |
7468 | 1 | " <Value>C4</Value>"; |
7469 | | |
7470 | 1 | if (bHasJPEG2000Drivers) |
7471 | 0 | osCreationOptions += " <Value>C8</Value>"; |
7472 | | |
7473 | 1 | osCreationOptions += " </Option>"; |
7474 | | |
7475 | | #if !defined(JPEG_SUPPORTED) |
7476 | | if (bHasJPEG2000Drivers) |
7477 | | #endif |
7478 | 1 | { |
7479 | 1 | osCreationOptions += |
7480 | 1 | " <Option name='QUALITY' type='string' " |
7481 | 1 | "description='JPEG (10-100) or JPEG2000 quality, possibly as a" |
7482 | 1 | "separated list of values for JPEG2000_DRIVER=JP2OPENJPEG' " |
7483 | 1 | "default='75'/>"; |
7484 | 1 | } |
7485 | | |
7486 | 1 | #ifdef JPEG_SUPPORTED |
7487 | 1 | osCreationOptions += |
7488 | 1 | " <Option name='PROGRESSIVE' type='boolean' description='JPEG " |
7489 | 1 | "progressive mode'/>" |
7490 | 1 | " <Option name='RESTART_INTERVAL' type='int' description='Restart " |
7491 | 1 | "interval (in MCUs). -1 for auto, 0 for none, > 0 for user specified' " |
7492 | 1 | "default='-1'/>"; |
7493 | 1 | #endif |
7494 | 1 | osCreationOptions += |
7495 | 1 | " <Option name='NUMI' type='int' default='1' description='Number of " |
7496 | 1 | "images to create (1-999). Only works with IC=NC if " |
7497 | 1 | "WRITE_ONLY_FIRST_IMAGE=NO'/>" |
7498 | 1 | " <Option name='WRITE_ONLY_FIRST_IMAGE' type='boolean' default='NO' " |
7499 | 1 | "description='To be used with NUMI. If YES, only write first image. " |
7500 | 1 | "Subsequent one must be written with APPEND_SUBDATASET=YES'/>"; |
7501 | | |
7502 | 1 | if (bHasJPEG2000Drivers) |
7503 | 0 | { |
7504 | 0 | osCreationOptions += |
7505 | 0 | " <Option name='TARGET' type='float' description='For JP2 only. " |
7506 | 0 | "Compression Percentage'/>" |
7507 | 0 | " <Option name='PROFILE' type='string-select' description='For " |
7508 | 0 | "JP2 only.'>"; |
7509 | |
|
7510 | 0 | if (bHasJP2ECW) |
7511 | 0 | { |
7512 | 0 | osCreationOptions += " <Value>BASELINE_0</Value>"; |
7513 | 0 | } |
7514 | 0 | if (bHasJP2ECW || bHasJP2OPENJPEG) |
7515 | 0 | { |
7516 | 0 | osCreationOptions += |
7517 | 0 | " <Value>BASELINE_1</Value>" |
7518 | 0 | " <Value>BASELINE_2</Value>" |
7519 | 0 | " <Value>NPJE</Value>" |
7520 | 0 | " <Value>NPJE_VISUALLY_LOSSLESS</Value>" |
7521 | 0 | " <Value>NPJE_NUMERICALLY_LOSSLESS</Value>"; |
7522 | 0 | } |
7523 | 0 | if (bHasJP2ECW) |
7524 | 0 | { |
7525 | 0 | osCreationOptions += " <Value>EPJE</Value>"; |
7526 | 0 | } |
7527 | 0 | osCreationOptions += |
7528 | 0 | " </Option>" |
7529 | 0 | " <Option name='JPEG2000_DRIVER' type='string-select' " |
7530 | 0 | "description='Short name of the JPEG2000 driver'>"; |
7531 | 0 | if (bHasJP2OPENJPEG) |
7532 | 0 | osCreationOptions += " <Value>JP2OPENJPEG</Value>"; |
7533 | 0 | if (bHasJP2ECW) |
7534 | 0 | osCreationOptions += " <Value>JP2ECW</Value>"; |
7535 | 0 | if (bHasJP2KAK) |
7536 | 0 | osCreationOptions += " <Value>JP2KAK</Value>"; |
7537 | 0 | osCreationOptions += " </Option>" |
7538 | 0 | " <Option name='J2KLRA' type='boolean' " |
7539 | 0 | "description='Write J2KLRA TRE'/>"; |
7540 | 0 | } |
7541 | | |
7542 | 1 | osCreationOptions += |
7543 | 1 | " <Option name='ICORDS' type='string-select' description='To ensure " |
7544 | 1 | "that space will be reserved for geographic corner coordinates in DMS " |
7545 | 1 | "(G), in decimal degrees (D), UTM North (N) or UTM South (S)'>" |
7546 | 1 | " <Value>G</Value>" |
7547 | 1 | " <Value>D</Value>" |
7548 | 1 | " <Value>N</Value>" |
7549 | 1 | " <Value>S</Value>" |
7550 | 1 | " </Option>" |
7551 | 1 | " <Option name='IGEOLO' type='string' description='Image corner " |
7552 | 1 | "coordinates. " |
7553 | 1 | "Normally automatically set. If specified, ICORDS must also be " |
7554 | 1 | "specified'/>" |
7555 | 1 | " <Option name='FHDR' type='string-select' description='File " |
7556 | 1 | "version' default='NITF02.10'>" |
7557 | 1 | " <Value>NITF02.10</Value>" |
7558 | 1 | " <Value>NSIF01.00</Value>" |
7559 | 1 | " <Value>NITF02.00</Value>" |
7560 | 1 | " </Option>" |
7561 | 1 | " <Option name='IREP' type='string' description='Set to RGB/LUT to " |
7562 | 1 | "reserve space for a color table for each output band. (Only needed " |
7563 | 1 | "for Create() method, not CreateCopy())'/>" |
7564 | 1 | " <Option name='IREPBAND' type='string' description='Comma separated " |
7565 | 1 | "list of band IREPBANDs in band order'/>" |
7566 | 1 | " <Option name='ISUBCAT' type='string' description='Comma separated " |
7567 | 1 | "list of band ISUBCATs in band order'/>" |
7568 | 1 | " <Option name='LUT_SIZE' type='integer' description='Set to control " |
7569 | 1 | "the size of pseudocolor tables for RGB/LUT bands' default='256'/>" |
7570 | 1 | " <Option name='BLOCKXSIZE' type='int' description='Set the block " |
7571 | 1 | "width'/>" |
7572 | 1 | " <Option name='BLOCKYSIZE' type='int' description='Set the block " |
7573 | 1 | "height'/>" |
7574 | 1 | " <Option name='BLOCKSIZE' type='int' description='Set the block " |
7575 | 1 | "with and height. Overridden by BLOCKXSIZE and BLOCKYSIZE'/>" |
7576 | 1 | " <Option name='TEXT' type='string' description='TEXT options as " |
7577 | 1 | "text-option-name=text-option-content'/>" |
7578 | 1 | " <Option name='CGM' type='string' description='CGM options in " |
7579 | 1 | "cgm-option-name=cgm-option-content'/>"; |
7580 | | |
7581 | 1 | for (unsigned int i = 0; |
7582 | 59 | i < sizeof(asFieldDescription) / sizeof(asFieldDescription[0]); i++) |
7583 | 58 | { |
7584 | 58 | if (EQUAL(asFieldDescription[i].pszName, "ABPP")) |
7585 | 1 | { |
7586 | 1 | osCreationOptions += |
7587 | 1 | CPLString().Printf(" <Option name='%s' alias='NBITS' " |
7588 | 1 | "type='string' description='%s' " |
7589 | 1 | "maxsize='%d'/>", |
7590 | 1 | asFieldDescription[i].pszName, |
7591 | 1 | asFieldDescription[i].pszDescription, |
7592 | 1 | asFieldDescription[i].nMaxLen); |
7593 | 1 | } |
7594 | 57 | else |
7595 | 57 | { |
7596 | 57 | osCreationOptions += CPLString().Printf( |
7597 | 57 | " <Option name='%s' type='string' description='%s' " |
7598 | 57 | "maxsize='%d'/>", |
7599 | 57 | asFieldDescription[i].pszName, |
7600 | 57 | asFieldDescription[i].pszDescription, |
7601 | 57 | asFieldDescription[i].nMaxLen); |
7602 | 57 | } |
7603 | 58 | } |
7604 | | |
7605 | 1 | osCreationOptions += |
7606 | 1 | " <Option name='TRE' type='string' description='Under the format " |
7607 | 1 | "TRE=tre-name,tre-contents'/>" |
7608 | 1 | " <Option name='FILE_TRE' type='string' description='Under the " |
7609 | 1 | "format FILE_TRE=tre-name,tre-contents'/>" |
7610 | 1 | " <Option name='RESERVE_SPACE_FOR_TRE_OVERFLOW' type='boolean' " |
7611 | 1 | "description='Set to true to reserve space for IXSOFL when writing a " |
7612 | 1 | "TRE_OVERFLOW DES'/>" |
7613 | 1 | " <Option name='BLOCKA_BLOCK_COUNT' type='int'/>" |
7614 | 1 | " <Option name='DES' type='string' description='Under the format " |
7615 | 1 | "DES=des-name=des-contents'/>" |
7616 | 1 | " <Option name='NUMDES' type='int' default='0' description='Number " |
7617 | 1 | "of DES segments. Only to be used on first image segment'/>"; |
7618 | 11 | for (unsigned int i = 0; apszFieldsBLOCKA[i] != nullptr; i += 3) |
7619 | 10 | { |
7620 | 10 | char szFieldDescription[128]; |
7621 | 10 | snprintf(szFieldDescription, sizeof(szFieldDescription), |
7622 | 10 | " <Option name='BLOCKA_%s_*' type='string' maxsize='%d'/>", |
7623 | 10 | apszFieldsBLOCKA[i], atoi(apszFieldsBLOCKA[i + 2])); |
7624 | 10 | osCreationOptions += szFieldDescription; |
7625 | 10 | } |
7626 | 1 | osCreationOptions += |
7627 | 1 | " <Option name='SDE_TRE' type='boolean' description='Write GEOLOB " |
7628 | 1 | "and GEOPSB TREs (only geographic SRS for now)' default='NO'/>" |
7629 | 1 | " <Option name='RPC00B' type='boolean' description='Write RPC00B TRE " |
7630 | 1 | "(either from source TRE, or from RPC metadata)' default='YES'/>" |
7631 | 1 | " <Option name='RPCTXT' type='boolean' description='Write out " |
7632 | 1 | "_RPC.TXT file' default='NO'/>" |
7633 | 1 | " <Option name='USE_SRC_NITF_METADATA' type='boolean' " |
7634 | 1 | "description='Whether to use NITF source metadata in NITF-to-NITF " |
7635 | 1 | "conversions' default='YES'/>" |
7636 | 1 | " <Option name='PRODUCT_TYPE' type='string-select' description='" |
7637 | 1 | "Sub-specification the output dataset should respect' " |
7638 | 1 | "default='REGULAR'>" |
7639 | 1 | " <Value>REGULAR</Value>" |
7640 | 1 | " <Value>CADRG</Value>" |
7641 | 1 | " </Option>" |
7642 | 1 | " <Option name='SCALE' type='string' min='1000' max='20000000' " |
7643 | 1 | "description='Reciprocal scale to use when generating output frames. " |
7644 | 1 | "Special value GUESS can be used. Only used when PRODUCT_TYPE=CADRG'/>" |
7645 | 1 | " <Option name='COLOR_QUANTIZATION_BITS' type='int' min='5' max='8' " |
7646 | 1 | "default='5' description='" |
7647 | 1 | "Number of bits per R,G,B color component used during color palette " |
7648 | 1 | "computation. The higher the better quality and slower computation " |
7649 | 1 | "time. Only used when PRODUCT_TYPE=CADRG'/>" |
7650 | 1 | " <Option name='COLOR_TABLE_PER_FRAME' type='boolean' default='NO' " |
7651 | 1 | "description='Whether the color table should be optimized on the whole " |
7652 | 1 | "input dataset, or per output frame. " |
7653 | 1 | "Only used when PRODUCT_TYPE=CADRG'/>" |
7654 | 1 | " <Option name='DPI' type='float' description='" |
7655 | 1 | "Dot-Per-Inch value that may need to be specified together with SCALE. " |
7656 | 1 | "Only used when PRODUCT_TYPE=CADRG' min='1' max='7200'/>" |
7657 | 1 | " <Option name='ZONE' type='string' description='" |
7658 | 1 | "ARC Zone to which restrict generation of CADRG frames (1 to 9, A to " |
7659 | 1 | "H, J)." |
7660 | 1 | "Only used when PRODUCT_TYPE=CADRG'/>" |
7661 | 1 | " <Option name='SERIES_CODE' type='string-select' description='" |
7662 | 1 | "Two-letter code specifying the map/chart type. Only used when " |
7663 | 1 | "PRODUCT_TYPE=CADRG' default='MM'>" |
7664 | 1 | " <Value>GN</Value>" |
7665 | 1 | " <Value>JN</Value>" |
7666 | 1 | " <Value>ON</Value>" |
7667 | 1 | " <Value>TP</Value>" |
7668 | 1 | " <Value>LF</Value>" |
7669 | 1 | " <Value>JG</Value>" |
7670 | 1 | " <Value>JA</Value>" |
7671 | 1 | " <Value>JR</Value>" |
7672 | 1 | " <Value>TF</Value>" |
7673 | 1 | " <Value>AT</Value>" |
7674 | 1 | " <Value>TC</Value>" |
7675 | 1 | " <Value>TL</Value>" |
7676 | 1 | " <Value>HA</Value>" |
7677 | 1 | " <Value>CO</Value>" |
7678 | 1 | " <Value>OA</Value>" |
7679 | 1 | " <Value>CG</Value>" |
7680 | 1 | " <Value>CM</Value>" |
7681 | 1 | " <Value>MM</Value>" |
7682 | 1 | " <OtherValues/>" |
7683 | 1 | " </Option>" |
7684 | 1 | " <Option name='RESAMPLING' type='string-select' description='" |
7685 | 1 | "Resampling method used during CADRG frame creation' " |
7686 | 1 | "default='CUBIC'>" |
7687 | 1 | " <Value>CUBIC</Value>" |
7688 | 1 | " <Value>BILINEAR</Value>" |
7689 | 1 | " <Value>LANCZOS</Value>" |
7690 | 1 | " <Value>NEAREST</Value>" |
7691 | 1 | " </Option>" |
7692 | 1 | " <Option name='VERSION_NUMBER' type='string' description='" |
7693 | 1 | "Two letter version number (using letters among 0-9, A-H and J). " |
7694 | 1 | "Only used when PRODUCT_TYPE=CADRG' default='01'/>" |
7695 | 1 | " <Option name='PRODUCER_CODE_ID' type='string' description='" |
7696 | 1 | "One letter code indicating the data producer. Only used when " |
7697 | 1 | "PRODUCT_TYPE=CADRG' default='0'/>" |
7698 | 1 | " <Option name='SECURITY_COUNTRY_CODE' type='string' description='" |
7699 | 1 | "Two letter country ISO code of the security classification'/>" |
7700 | 1 | " <Option name='CURRENCY_DATE' type='string' description='" |
7701 | 1 | "Date of the most recent revision to the RPF product, as YYYYMMDD. " |
7702 | 1 | "Can also be set to empty string or special value NOW. " |
7703 | 1 | "Only used when PRODUCT_TYPE=CADRG'/>" |
7704 | 1 | " <Option name='PRODUCTION_DATE' type='string' description='" |
7705 | 1 | "Date that the source data was transferred to RPF format, as YYYYMMDD. " |
7706 | 1 | "Can also be set to empty string or special value NOW. " |
7707 | 1 | "Only used when PRODUCT_TYPE=CADRG'/>" |
7708 | 1 | " <Option name='SIGNIFICANT_DATE' type='string' description='" |
7709 | 1 | "Date describing the basic date of the source produc, as YYYYMMDD. " |
7710 | 1 | "Can also be set to empty string or special value NOW. " |
7711 | 1 | "Only used when PRODUCT_TYPE=CADRG'/>" |
7712 | 1 | " <Option name='DATA_SERIES_DESIGNATION' type='string' " |
7713 | 1 | "description='Short title for the identification of a group of products" |
7714 | 1 | " usually having the same scale and/or cartographic specification " |
7715 | 1 | "(e.g. JOG 1501A). Up to 10 characters. Only used when " |
7716 | 1 | "PRODUCT_TYPE=CADRG'/>" |
7717 | 1 | " <Option name='MAP_DESIGNATION' type='string' " |
7718 | 1 | "description='Designation, within the data series, of the hardcopy " |
7719 | 1 | "source (e.g. G18 if the hardcopy source is ONC G18). Up to 8 " |
7720 | 1 | "characters. Only used when PRODUCT_TYPE=CADRG'/>" |
7721 | 1 | "</CreationOptionList>"; |
7722 | | |
7723 | 1 | SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST, osCreationOptions); |
7724 | 1 | } |
7725 | | |
7726 | | void GDALRegister_NITF() |
7727 | | |
7728 | 22 | { |
7729 | 22 | if (GDALGetDriverByName(NITF_DRIVER_NAME) != nullptr) |
7730 | 0 | return; |
7731 | | |
7732 | 22 | GDALDriver *poDriver = new NITFDriver(); |
7733 | 22 | NITFDriverSetCommonMetadata(poDriver); |
7734 | | |
7735 | 22 | poDriver->pfnOpen = NITFDataset::Open; |
7736 | 22 | poDriver->pfnCreate = NITFDataset::NITFDatasetCreate; |
7737 | 22 | poDriver->pfnCreateCopy = NITFDataset::NITFCreateCopy; |
7738 | | |
7739 | 22 | GetGDALDriverManager()->RegisterDriver(poDriver); |
7740 | | |
7741 | | #ifdef NITF_PLUGIN |
7742 | | GDALRegister_RPFTOC(); |
7743 | | GDALRegister_ECRGTOC(); |
7744 | | #endif |
7745 | 22 | } |