/src/gdal/frmts/tsx/tsxdataset.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: TerraSAR-X XML Product Support |
4 | | * Purpose: Support for TerraSAR-X XML Metadata files |
5 | | * Author: Philippe Vachon <philippe@cowpig.ca> |
6 | | * Description: This driver adds support for reading metadata and georef data |
7 | | * associated with TerraSAR-X products. |
8 | | * |
9 | | ****************************************************************************** |
10 | | * Copyright (c) 2007, Philippe Vachon <philippe@cowpig.ca> |
11 | | * Copyright (c) 2009-2012, Even Rouault <even dot rouault at spatialys.com> |
12 | | * |
13 | | * SPDX-License-Identifier: MIT |
14 | | ****************************************************************************/ |
15 | | |
16 | | #include "cpl_minixml.h" |
17 | | #include "gdal_frmts.h" |
18 | | #include "gdal_pam.h" |
19 | | #include "gdal_driver.h" |
20 | | #include "gdal_drivermanager.h" |
21 | | #include "gdal_openinfo.h" |
22 | | #include "gdal_cpp_functions.h" |
23 | | #include "ogr_spatialref.h" |
24 | | |
25 | 0 | #define MAX_GCPS 5000 // this should be more than enough ground control points |
26 | | |
27 | | namespace gdal::TSX |
28 | | { |
29 | | enum ePolarization |
30 | | { |
31 | | HH = 0, |
32 | | HV, |
33 | | VH, |
34 | | VV |
35 | | }; |
36 | | |
37 | | enum eProductType |
38 | | { |
39 | | eSSC = 0, |
40 | | eMGD, |
41 | | eEEC, |
42 | | eGEC, |
43 | | eUnknown |
44 | | }; |
45 | | } // namespace gdal::TSX |
46 | | |
47 | | using namespace gdal::TSX; |
48 | | |
49 | | /************************************************************************/ |
50 | | /* Helper Functions */ |
51 | | /************************************************************************/ |
52 | | |
53 | | /* GetFilePath: return a relative path to a file within an XML node. |
54 | | * Returns Null on failure |
55 | | */ |
56 | | static CPLString GetFilePath(CPLXMLNode *psXMLNode, const char **pszNodeType) |
57 | 0 | { |
58 | 0 | const char *pszDirectory = |
59 | 0 | CPLGetXMLValue(psXMLNode, "file.location.path", ""); |
60 | 0 | const char *pszFilename = |
61 | 0 | CPLGetXMLValue(psXMLNode, "file.location.filename", ""); |
62 | 0 | *pszNodeType = CPLGetXMLValue(psXMLNode, "type", " "); |
63 | |
|
64 | 0 | if (pszDirectory == nullptr || pszFilename == nullptr) |
65 | 0 | { |
66 | 0 | return ""; |
67 | 0 | } |
68 | | |
69 | 0 | return CPLString(pszDirectory) + '/' + pszFilename; |
70 | 0 | } |
71 | | |
72 | | /************************************************************************/ |
73 | | /* ==================================================================== */ |
74 | | /* TSXDataset */ |
75 | | /* ==================================================================== */ |
76 | | /************************************************************************/ |
77 | | |
78 | | class TSXDataset final : public GDALPamDataset |
79 | | { |
80 | | int nGCPCount; |
81 | | GDAL_GCP *pasGCPList; |
82 | | |
83 | | OGRSpatialReference m_oGCPSRS{}; |
84 | | |
85 | | OGRSpatialReference m_oSRS{}; |
86 | | GDALGeoTransform m_gt{}; |
87 | | bool bHaveGeoTransform; |
88 | | |
89 | | eProductType nProduct; |
90 | | |
91 | | public: |
92 | | TSXDataset(); |
93 | | ~TSXDataset() override; |
94 | | |
95 | | int GetGCPCount() override; |
96 | | const OGRSpatialReference *GetGCPSpatialRef() const override; |
97 | | const GDAL_GCP *GetGCPs() override; |
98 | | |
99 | | CPLErr GetGeoTransform(GDALGeoTransform >) const override; |
100 | | const OGRSpatialReference *GetSpatialRef() const override; |
101 | | |
102 | | static GDALDataset *Open(GDALOpenInfo *poOpenInfo); |
103 | | static int Identify(GDALOpenInfo *poOpenInfo); |
104 | | |
105 | | private: |
106 | | bool getGCPsFromGEOREF_XML(const char *pszGeorefFilename); |
107 | | }; |
108 | | |
109 | | /************************************************************************/ |
110 | | /* ==================================================================== */ |
111 | | /* TSXRasterBand */ |
112 | | /* ==================================================================== */ |
113 | | /************************************************************************/ |
114 | | |
115 | | class TSXRasterBand final : public GDALPamRasterBand |
116 | | { |
117 | | GDALDataset *poBand; |
118 | | ePolarization ePol; |
119 | | |
120 | | public: |
121 | | TSXRasterBand(TSXDataset *poDSIn, GDALDataType eDataType, |
122 | | ePolarization ePol, GDALDataset *poBand); |
123 | | ~TSXRasterBand() override; |
124 | | |
125 | | CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage) override; |
126 | | |
127 | | static GDALDataset *Open(GDALOpenInfo *poOpenInfo); |
128 | | }; |
129 | | |
130 | | /************************************************************************/ |
131 | | /* TSXRasterBand */ |
132 | | /************************************************************************/ |
133 | | |
134 | | TSXRasterBand::TSXRasterBand(TSXDataset *poDSIn, GDALDataType eDataTypeIn, |
135 | | ePolarization ePolIn, GDALDataset *poBandIn) |
136 | 0 | : poBand(poBandIn), ePol(ePolIn) |
137 | 0 | { |
138 | 0 | poDS = poDSIn; |
139 | 0 | eDataType = eDataTypeIn; |
140 | |
|
141 | 0 | switch (ePol) |
142 | 0 | { |
143 | 0 | case HH: |
144 | 0 | SetMetadataItem("POLARIMETRIC_INTERP", "HH"); |
145 | 0 | break; |
146 | 0 | case HV: |
147 | 0 | SetMetadataItem("POLARIMETRIC_INTERP", "HV"); |
148 | 0 | break; |
149 | 0 | case VH: |
150 | 0 | SetMetadataItem("POLARIMETRIC_INTERP", "VH"); |
151 | 0 | break; |
152 | 0 | case VV: |
153 | 0 | SetMetadataItem("POLARIMETRIC_INTERP", "VV"); |
154 | 0 | break; |
155 | 0 | } |
156 | | |
157 | | /* now setup the actual raster reader */ |
158 | 0 | GDALRasterBand *poSrcBand = poBandIn->GetRasterBand(1); |
159 | 0 | poSrcBand->GetBlockSize(&nBlockXSize, &nBlockYSize); |
160 | 0 | } |
161 | | |
162 | | /************************************************************************/ |
163 | | /* TSXRasterBand() */ |
164 | | /************************************************************************/ |
165 | | |
166 | | TSXRasterBand::~TSXRasterBand() |
167 | 0 | { |
168 | 0 | if (poBand != nullptr) |
169 | 0 | GDALClose(reinterpret_cast<GDALRasterBandH>(poBand)); |
170 | 0 | } |
171 | | |
172 | | /************************************************************************/ |
173 | | /* IReadBlock() */ |
174 | | /************************************************************************/ |
175 | | |
176 | | CPLErr TSXRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage) |
177 | 0 | { |
178 | 0 | int nRequestYSize; |
179 | | |
180 | | /* Check if the last strip is partial so we can avoid over-requesting */ |
181 | 0 | if ((nBlockYOff + 1) * nBlockYSize > nRasterYSize) |
182 | 0 | { |
183 | 0 | nRequestYSize = nRasterYSize - nBlockYOff * nBlockYSize; |
184 | 0 | memset(pImage, 0, |
185 | 0 | static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) * |
186 | 0 | nBlockXSize * nBlockYSize); |
187 | 0 | } |
188 | 0 | else |
189 | 0 | { |
190 | 0 | nRequestYSize = nBlockYSize; |
191 | 0 | } |
192 | | |
193 | | /* Read Complex Data */ |
194 | 0 | if (eDataType == GDT_CInt16) |
195 | 0 | { |
196 | 0 | return poBand->RasterIO( |
197 | 0 | GF_Read, nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize, |
198 | 0 | nBlockXSize, nRequestYSize, pImage, nBlockXSize, nRequestYSize, |
199 | 0 | GDT_CInt16, 1, nullptr, 4, nBlockXSize * 4, 0, nullptr); |
200 | 0 | } |
201 | | |
202 | | // Detected Product |
203 | 0 | return poBand->RasterIO( |
204 | 0 | GF_Read, nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize, |
205 | 0 | nBlockXSize, nRequestYSize, pImage, nBlockXSize, nRequestYSize, |
206 | 0 | GDT_UInt16, 1, nullptr, 2, nBlockXSize * 2, 0, nullptr); |
207 | 0 | } |
208 | | |
209 | | /************************************************************************/ |
210 | | /* ==================================================================== */ |
211 | | /* TSXDataset */ |
212 | | /* ==================================================================== */ |
213 | | /************************************************************************/ |
214 | | |
215 | | /************************************************************************/ |
216 | | /* TSXDataset() */ |
217 | | /************************************************************************/ |
218 | | |
219 | | TSXDataset::TSXDataset() |
220 | 0 | : nGCPCount(0), pasGCPList(nullptr), bHaveGeoTransform(false), |
221 | 0 | nProduct(eUnknown) |
222 | 0 | { |
223 | 0 | m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
224 | 0 | m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
225 | 0 | } |
226 | | |
227 | | /************************************************************************/ |
228 | | /* ~TSXDataset() */ |
229 | | /************************************************************************/ |
230 | | |
231 | | TSXDataset::~TSXDataset() |
232 | 0 | { |
233 | 0 | FlushCache(true); |
234 | |
|
235 | 0 | if (nGCPCount > 0) |
236 | 0 | { |
237 | 0 | GDALDeinitGCPs(nGCPCount, pasGCPList); |
238 | 0 | CPLFree(pasGCPList); |
239 | 0 | } |
240 | 0 | } |
241 | | |
242 | | /************************************************************************/ |
243 | | /* Identify() */ |
244 | | /************************************************************************/ |
245 | | |
246 | | int TSXDataset::Identify(GDALOpenInfo *poOpenInfo) |
247 | 339k | { |
248 | 339k | if (poOpenInfo->fpL == nullptr || poOpenInfo->nHeaderBytes < 260) |
249 | 272k | { |
250 | 272k | if (poOpenInfo->bIsDirectory) |
251 | 3.38k | { |
252 | 3.38k | const CPLString osFilename = CPLFormCIFilenameSafe( |
253 | 3.38k | poOpenInfo->pszFilename, |
254 | 3.38k | CPLGetFilename(poOpenInfo->pszFilename), "xml"); |
255 | | |
256 | | /* Check if the filename contains TSX1_SAR (TerraSAR-X) or TDX1_SAR |
257 | | * (TanDEM-X) or PAZ1_SAR (PAZ) */ |
258 | 3.38k | if (!(STARTS_WITH_CI(CPLGetBasenameSafe(osFilename).c_str(), |
259 | 3.38k | "TSX1_SAR") || |
260 | 3.38k | STARTS_WITH_CI(CPLGetBasenameSafe(osFilename).c_str(), |
261 | 3.38k | "TDX1_SAR") || |
262 | 3.38k | STARTS_WITH_CI(CPLGetBasenameSafe(osFilename).c_str(), |
263 | 3.38k | "PAZ1_SAR"))) |
264 | 3.38k | return 0; |
265 | | |
266 | 0 | VSIStatBufL sStat; |
267 | 0 | if (VSIStatL(osFilename, &sStat) == 0) |
268 | 0 | return 1; |
269 | 0 | } |
270 | | |
271 | 268k | return 0; |
272 | 272k | } |
273 | | |
274 | | /* Check if the filename contains TSX1_SAR (TerraSAR-X) or TDX1_SAR |
275 | | * (TanDEM-X) or PAZ1_SAR (PAZ) */ |
276 | 67.0k | if (!(STARTS_WITH_CI(CPLGetBasenameSafe(poOpenInfo->pszFilename).c_str(), |
277 | 67.0k | "TSX1_SAR") || |
278 | 67.0k | STARTS_WITH_CI(CPLGetBasenameSafe(poOpenInfo->pszFilename).c_str(), |
279 | 67.0k | "TDX1_SAR") || |
280 | 67.0k | STARTS_WITH_CI(CPLGetBasenameSafe(poOpenInfo->pszFilename).c_str(), |
281 | 67.0k | "PAZ1_SAR"))) |
282 | 67.0k | return 0; |
283 | | |
284 | | /* finally look for the <level1Product tag */ |
285 | 1 | if (!STARTS_WITH_CI(reinterpret_cast<char *>(poOpenInfo->pabyHeader), |
286 | 1 | "<level1Product")) |
287 | 1 | return 0; |
288 | | |
289 | 0 | return 1; |
290 | 1 | } |
291 | | |
292 | | /************************************************************************/ |
293 | | /* getGCPsFromGEOREF_XML() */ |
294 | | /*Reads georeferencing information from the TerraSAR-X GEOREF.XML file */ |
295 | | /*and writes the information to the dataset's gcp list and projection */ |
296 | | /*string. */ |
297 | | /*Returns true on success. */ |
298 | | /************************************************************************/ |
299 | | bool TSXDataset::getGCPsFromGEOREF_XML(const char *pszGeorefFilename) |
300 | 0 | { |
301 | | // open GEOREF.xml |
302 | 0 | CPLXMLNode *psGeorefData = CPLParseXMLFile(pszGeorefFilename); |
303 | 0 | if (psGeorefData == nullptr) |
304 | 0 | return false; |
305 | | |
306 | | // get the ellipsoid and semi-major, semi-minor axes |
307 | 0 | OGRSpatialReference osr; |
308 | 0 | CPLXMLNode *psSphere = |
309 | 0 | CPLGetXMLNode(psGeorefData, "=geoReference.referenceFrames.sphere"); |
310 | 0 | if (psSphere != nullptr) |
311 | 0 | { |
312 | 0 | const char *pszEllipsoidName = |
313 | 0 | CPLGetXMLValue(psSphere, "ellipsoidID", ""); |
314 | 0 | const double minor_axis = |
315 | 0 | CPLAtof(CPLGetXMLValue(psSphere, "semiMinorAxis", "0.0")); |
316 | 0 | const double major_axis = |
317 | 0 | CPLAtof(CPLGetXMLValue(psSphere, "semiMajorAxis", "0.0")); |
318 | | // save datum parameters to the spatial reference |
319 | 0 | if (EQUAL(pszEllipsoidName, "") || minor_axis == 0.0 || |
320 | 0 | major_axis == 0.0) |
321 | 0 | { |
322 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
323 | 0 | "Warning- incomplete" |
324 | 0 | " ellipsoid information. Using wgs-84 parameters.\n"); |
325 | 0 | osr.SetWellKnownGeogCS("WGS84"); |
326 | 0 | } |
327 | 0 | else if (EQUAL(pszEllipsoidName, "WGS84")) |
328 | 0 | { |
329 | 0 | osr.SetWellKnownGeogCS("WGS84"); |
330 | 0 | } |
331 | 0 | else |
332 | 0 | { |
333 | 0 | const double inv_flattening = |
334 | 0 | major_axis / (major_axis - minor_axis); |
335 | 0 | osr.SetGeogCS("", "", pszEllipsoidName, major_axis, inv_flattening); |
336 | 0 | } |
337 | 0 | } |
338 | | |
339 | | // get gcps |
340 | 0 | CPLXMLNode *psGeolocationGrid = |
341 | 0 | CPLGetXMLNode(psGeorefData, "=geoReference.geolocationGrid"); |
342 | 0 | if (psGeolocationGrid == nullptr) |
343 | 0 | { |
344 | 0 | CPLDestroyXMLNode(psGeorefData); |
345 | 0 | return false; |
346 | 0 | } |
347 | 0 | nGCPCount = atoi( |
348 | 0 | CPLGetXMLValue(psGeolocationGrid, "numberOfGridPoints.total", "0")); |
349 | | // count the gcps if the given count value is invalid |
350 | 0 | CPLXMLNode *psNode = nullptr; |
351 | 0 | if (nGCPCount <= 0) |
352 | 0 | { |
353 | 0 | for (psNode = psGeolocationGrid->psChild; psNode != nullptr; |
354 | 0 | psNode = psNode->psNext) |
355 | 0 | if (EQUAL(psNode->pszValue, "gridPoint")) |
356 | 0 | nGCPCount++; |
357 | 0 | } |
358 | | // if there are no gcps, fail |
359 | 0 | if (nGCPCount <= 0) |
360 | 0 | { |
361 | 0 | CPLDestroyXMLNode(psGeorefData); |
362 | 0 | return false; |
363 | 0 | } |
364 | | |
365 | | // put some reasonable limits of the number of gcps |
366 | 0 | if (nGCPCount > MAX_GCPS) |
367 | 0 | nGCPCount = MAX_GCPS; |
368 | | // allocate memory for the gcps |
369 | 0 | pasGCPList = |
370 | 0 | static_cast<GDAL_GCP *>(CPLCalloc(sizeof(GDAL_GCP), nGCPCount)); |
371 | | |
372 | | // loop through all gcps and set info |
373 | | |
374 | | // save the number allocated to ensure it does not run off the end of the |
375 | | // array |
376 | 0 | const int gcps_allocated = nGCPCount; |
377 | 0 | nGCPCount = 0; // reset to zero and count |
378 | | // do a check on the grid point to make sure it has lat,long row, and column |
379 | | // it seems that only SSC products contain row, col - how to map lat long |
380 | | // otherwise?? for now fail if row and col are not present - just check the |
381 | | // first and assume the rest are the same |
382 | 0 | for (psNode = psGeolocationGrid->psChild; psNode != nullptr; |
383 | 0 | psNode = psNode->psNext) |
384 | 0 | { |
385 | 0 | if (!EQUAL(psNode->pszValue, "gridPoint")) |
386 | 0 | continue; |
387 | | |
388 | 0 | if (!strcmp(CPLGetXMLValue(psNode, "col", "error"), "error") || |
389 | 0 | !strcmp(CPLGetXMLValue(psNode, "row", "error"), "error") || |
390 | 0 | !strcmp(CPLGetXMLValue(psNode, "lon", "error"), "error") || |
391 | 0 | !strcmp(CPLGetXMLValue(psNode, "lat", "error"), "error")) |
392 | 0 | { |
393 | 0 | CPLDestroyXMLNode(psGeorefData); |
394 | 0 | return false; |
395 | 0 | } |
396 | 0 | } |
397 | 0 | for (psNode = psGeolocationGrid->psChild; psNode != nullptr; |
398 | 0 | psNode = psNode->psNext) |
399 | 0 | { |
400 | | // break out if the end of the array has been reached |
401 | 0 | if (nGCPCount >= gcps_allocated) |
402 | 0 | { |
403 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
404 | 0 | "GDAL TSX driver: Truncating the number of GCPs."); |
405 | 0 | break; |
406 | 0 | } |
407 | | |
408 | 0 | GDAL_GCP *psGCP = pasGCPList + nGCPCount; |
409 | |
|
410 | 0 | if (!EQUAL(psNode->pszValue, "gridPoint")) |
411 | 0 | continue; |
412 | | |
413 | 0 | nGCPCount++; |
414 | |
|
415 | 0 | char szID[32]; |
416 | 0 | snprintf(szID, sizeof(szID), "%d", nGCPCount); |
417 | 0 | psGCP->pszId = CPLStrdup(szID); |
418 | 0 | psGCP->pszInfo = CPLStrdup(""); |
419 | 0 | psGCP->dfGCPPixel = CPLAtof(CPLGetXMLValue(psNode, "col", "0")); |
420 | 0 | psGCP->dfGCPLine = CPLAtof(CPLGetXMLValue(psNode, "row", "0")); |
421 | 0 | psGCP->dfGCPX = CPLAtof(CPLGetXMLValue(psNode, "lon", "")); |
422 | 0 | psGCP->dfGCPY = CPLAtof(CPLGetXMLValue(psNode, "lat", "")); |
423 | | // looks like height is in meters - should it be converted so xyz are |
424 | | // all on the same scale?? |
425 | 0 | psGCP->dfGCPZ = 0; |
426 | | // CPLAtof(CPLGetXMLValue(psNode,"height","")); |
427 | 0 | } |
428 | |
|
429 | 0 | m_oGCPSRS = std::move(osr); |
430 | |
|
431 | 0 | CPLDestroyXMLNode(psGeorefData); |
432 | |
|
433 | 0 | return true; |
434 | 0 | } |
435 | | |
436 | | /************************************************************************/ |
437 | | /* Open() */ |
438 | | /************************************************************************/ |
439 | | |
440 | | GDALDataset *TSXDataset::Open(GDALOpenInfo *poOpenInfo) |
441 | 0 | { |
442 | | /* -------------------------------------------------------------------- */ |
443 | | /* Is this a TerraSAR-X product file? */ |
444 | | /* -------------------------------------------------------------------- */ |
445 | 0 | if (!TSXDataset::Identify(poOpenInfo)) |
446 | 0 | { |
447 | 0 | return nullptr; /* nope */ |
448 | 0 | } |
449 | | |
450 | | /* -------------------------------------------------------------------- */ |
451 | | /* Confirm the requested access is supported. */ |
452 | | /* -------------------------------------------------------------------- */ |
453 | 0 | if (poOpenInfo->eAccess == GA_Update) |
454 | 0 | { |
455 | 0 | ReportUpdateNotSupportedByDriver("TSX"); |
456 | 0 | return nullptr; |
457 | 0 | } |
458 | | |
459 | 0 | CPLString osFilename; |
460 | |
|
461 | 0 | if (poOpenInfo->bIsDirectory) |
462 | 0 | { |
463 | 0 | osFilename = CPLFormCIFilenameSafe( |
464 | 0 | poOpenInfo->pszFilename, CPLGetFilename(poOpenInfo->pszFilename), |
465 | 0 | "xml"); |
466 | 0 | } |
467 | 0 | else |
468 | 0 | osFilename = poOpenInfo->pszFilename; |
469 | | |
470 | | /* Ingest the XML */ |
471 | 0 | CPLXMLTreeCloser psData(CPLParseXMLFile(osFilename)); |
472 | 0 | if (psData == nullptr) |
473 | 0 | return nullptr; |
474 | | |
475 | | /* find the product components */ |
476 | 0 | const CPLXMLNode *psComponents = |
477 | 0 | CPLGetXMLNode(psData.get(), "=level1Product.productComponents"); |
478 | 0 | if (psComponents == nullptr) |
479 | 0 | { |
480 | 0 | CPLError(CE_Failure, CPLE_OpenFailed, |
481 | 0 | "Unable to find <productComponents> tag in file.\n"); |
482 | 0 | return nullptr; |
483 | 0 | } |
484 | | |
485 | | /* find the product info tag */ |
486 | 0 | const CPLXMLNode *psProductInfo = |
487 | 0 | CPLGetXMLNode(psData.get(), "=level1Product.productInfo"); |
488 | 0 | if (psProductInfo == nullptr) |
489 | 0 | { |
490 | 0 | CPLError(CE_Failure, CPLE_OpenFailed, |
491 | 0 | "Unable to find <productInfo> tag in file.\n"); |
492 | 0 | return nullptr; |
493 | 0 | } |
494 | | |
495 | | /* -------------------------------------------------------------------- */ |
496 | | /* Create the dataset. */ |
497 | | /* -------------------------------------------------------------------- */ |
498 | | |
499 | 0 | auto poDS = std::make_unique<TSXDataset>(); |
500 | | |
501 | | /* -------------------------------------------------------------------- */ |
502 | | /* Read in product info. */ |
503 | | /* -------------------------------------------------------------------- */ |
504 | |
|
505 | 0 | poDS->SetMetadataItem( |
506 | 0 | "SCENE_CENTRE_TIME", |
507 | 0 | CPLGetXMLValue(psProductInfo, |
508 | 0 | "sceneInfo.sceneCenterCoord.azimuthTimeUTC", "unknown")); |
509 | 0 | poDS->SetMetadataItem("OPERATIONAL_MODE", |
510 | 0 | CPLGetXMLValue(psProductInfo, |
511 | 0 | "generationInfo.groundOperationsType", |
512 | 0 | "unknown")); |
513 | 0 | poDS->SetMetadataItem( |
514 | 0 | "ORBIT_CYCLE", |
515 | 0 | CPLGetXMLValue(psProductInfo, "missionInfo.orbitCycle", "unknown")); |
516 | 0 | poDS->SetMetadataItem( |
517 | 0 | "ABSOLUTE_ORBIT", |
518 | 0 | CPLGetXMLValue(psProductInfo, "missionInfo.absOrbit", "unknown")); |
519 | 0 | poDS->SetMetadataItem( |
520 | 0 | "ORBIT_DIRECTION", |
521 | 0 | CPLGetXMLValue(psProductInfo, "missionInfo.orbitDirection", "unknown")); |
522 | 0 | poDS->SetMetadataItem("IMAGING_MODE", |
523 | 0 | CPLGetXMLValue(psProductInfo, |
524 | 0 | "acquisitionInfo.imagingMode", |
525 | 0 | "unknown")); |
526 | 0 | poDS->SetMetadataItem("PRODUCT_VARIANT", |
527 | 0 | CPLGetXMLValue(psProductInfo, |
528 | 0 | "productVariantInfo.productVariant", |
529 | 0 | "unknown")); |
530 | 0 | std::string osDataType = |
531 | 0 | CPLGetXMLValue(psProductInfo, "imageDataInfo.imageDataType", "unknown"); |
532 | 0 | poDS->SetMetadataItem("IMAGE_TYPE", osDataType.c_str()); |
533 | | |
534 | | /* Get raster information */ |
535 | 0 | int nRows = atoi(CPLGetXMLValue( |
536 | 0 | psProductInfo, "imageDataInfo.imageRaster.numberOfRows", "")); |
537 | 0 | int nCols = atoi(CPLGetXMLValue( |
538 | 0 | psProductInfo, "imageDataInfo.imageRaster.numberOfColumns", "")); |
539 | |
|
540 | 0 | poDS->nRasterXSize = nCols; |
541 | 0 | poDS->nRasterYSize = nRows; |
542 | |
|
543 | 0 | poDS->SetMetadataItem("ROW_SPACING", |
544 | 0 | CPLGetXMLValue(psProductInfo, |
545 | 0 | "imageDataInfo.imageRaster.rowSpacing", |
546 | 0 | "unknown")); |
547 | 0 | poDS->SetMetadataItem( |
548 | 0 | "COL_SPACING", |
549 | 0 | CPLGetXMLValue(psProductInfo, "imageDataInfo.imageRaster.columnSpacing", |
550 | 0 | "unknown")); |
551 | 0 | poDS->SetMetadataItem( |
552 | 0 | "COL_SPACING_UNITS", |
553 | 0 | CPLGetXMLValue(psProductInfo, |
554 | 0 | "imageDataInfo.imageRaster.columnSpacing.units", |
555 | 0 | "unknown")); |
556 | | |
557 | | /* Get equivalent number of looks */ |
558 | 0 | poDS->SetMetadataItem( |
559 | 0 | "AZIMUTH_LOOKS", |
560 | 0 | CPLGetXMLValue(psProductInfo, "imageDataInfo.imageRaster.azimuthLooks", |
561 | 0 | "unknown")); |
562 | 0 | poDS->SetMetadataItem("RANGE_LOOKS", |
563 | 0 | CPLGetXMLValue(psProductInfo, |
564 | 0 | "imageDataInfo.imageRaster.rangeLooks", |
565 | 0 | "unknown")); |
566 | |
|
567 | 0 | const char *pszProductVariant = CPLGetXMLValue( |
568 | 0 | psProductInfo, "productVariantInfo.productVariant", "unknown"); |
569 | |
|
570 | 0 | poDS->SetMetadataItem("PRODUCT_VARIANT", pszProductVariant); |
571 | | |
572 | | /* Determine what product variant this is */ |
573 | 0 | if (STARTS_WITH_CI(pszProductVariant, "SSC")) |
574 | 0 | poDS->nProduct = eSSC; |
575 | 0 | else if (STARTS_WITH_CI(pszProductVariant, "MGD")) |
576 | 0 | poDS->nProduct = eMGD; |
577 | 0 | else if (STARTS_WITH_CI(pszProductVariant, "EEC")) |
578 | 0 | poDS->nProduct = eEEC; |
579 | 0 | else if (STARTS_WITH_CI(pszProductVariant, "GEC")) |
580 | 0 | poDS->nProduct = eGEC; |
581 | 0 | else |
582 | 0 | poDS->nProduct = eUnknown; |
583 | | |
584 | | /* Start reading in the product components */ |
585 | 0 | std::string osGeorefFile; |
586 | 0 | CPLErr geoTransformErr = CE_Failure; |
587 | 0 | for (CPLXMLNode *psComponent = psComponents->psChild; |
588 | 0 | psComponent != nullptr; psComponent = psComponent->psNext) |
589 | 0 | { |
590 | 0 | const char *pszType = nullptr; |
591 | 0 | const std::string osFilePath = GetFilePath(psComponent, &pszType); |
592 | 0 | if (CPLHasPathTraversal(osFilePath.c_str())) |
593 | 0 | { |
594 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
595 | 0 | "Path traversal detected in %s", osFilePath.c_str()); |
596 | 0 | return nullptr; |
597 | 0 | } |
598 | 0 | std::string osPath = CPLFormFilenameSafe( |
599 | 0 | CPLGetDirnameSafe(osFilename).c_str(), osFilePath.c_str(), ""); |
600 | 0 | const char *pszPolLayer = CPLGetXMLValue(psComponent, "polLayer", " "); |
601 | |
|
602 | 0 | if (!STARTS_WITH_CI(pszType, " ")) |
603 | 0 | { |
604 | 0 | if (STARTS_WITH_CI(pszType, "MAPPING_GRID")) |
605 | 0 | { |
606 | | /* the mapping grid... save as a metadata item this path */ |
607 | 0 | poDS->SetMetadataItem("MAPPING_GRID", osPath.c_str()); |
608 | 0 | } |
609 | 0 | else if (STARTS_WITH_CI(pszType, "GEOREF")) |
610 | 0 | { |
611 | | /* save the path to the georef data for later use */ |
612 | 0 | osGeorefFile = std::move(osPath); |
613 | 0 | } |
614 | 0 | } |
615 | 0 | else if (!STARTS_WITH_CI(pszPolLayer, " ") && |
616 | 0 | STARTS_WITH_CI(psComponent->pszValue, "imageData")) |
617 | 0 | { |
618 | | /* determine the polarization of this band */ |
619 | 0 | ePolarization ePol; |
620 | 0 | if (STARTS_WITH_CI(pszPolLayer, "HH")) |
621 | 0 | { |
622 | 0 | ePol = HH; |
623 | 0 | } |
624 | 0 | else if (STARTS_WITH_CI(pszPolLayer, "HV")) |
625 | 0 | { |
626 | 0 | ePol = HV; |
627 | 0 | } |
628 | 0 | else if (STARTS_WITH_CI(pszPolLayer, "VH")) |
629 | 0 | { |
630 | 0 | ePol = VH; |
631 | 0 | } |
632 | 0 | else |
633 | 0 | { |
634 | 0 | ePol = VV; |
635 | 0 | } |
636 | |
|
637 | 0 | GDALDataType eDataType = |
638 | 0 | STARTS_WITH_CI(osDataType.c_str(), "COMPLEX") ? GDT_CInt16 |
639 | 0 | : GDT_UInt16; |
640 | | |
641 | | /* try opening the file that represents that band */ |
642 | 0 | GDALDataset *poBandData = |
643 | 0 | GDALDataset::FromHandle(GDALOpen(osPath.c_str(), GA_ReadOnly)); |
644 | 0 | if (poBandData != nullptr) |
645 | 0 | { |
646 | 0 | TSXRasterBand *poBand = |
647 | 0 | new TSXRasterBand(poDS.get(), eDataType, ePol, poBandData); |
648 | 0 | poDS->SetBand(poDS->GetRasterCount() + 1, poBand); |
649 | | |
650 | | // copy georeferencing info from the band |
651 | | // need error checking?? |
652 | | // it will just save the info from the last band |
653 | 0 | const auto poSrcSRS = poBandData->GetSpatialRef(); |
654 | 0 | if (poSrcSRS) |
655 | 0 | poDS->m_oSRS = *poSrcSRS; |
656 | |
|
657 | 0 | geoTransformErr = poBandData->GetGeoTransform(poDS->m_gt); |
658 | 0 | } |
659 | 0 | } |
660 | 0 | } |
661 | | |
662 | | // now check if there is a geotransform |
663 | 0 | if (!poDS->m_oSRS.IsEmpty() && geoTransformErr == CE_None) |
664 | 0 | { |
665 | 0 | poDS->bHaveGeoTransform = TRUE; |
666 | 0 | } |
667 | 0 | else |
668 | 0 | { |
669 | 0 | poDS->bHaveGeoTransform = FALSE; |
670 | 0 | poDS->m_oSRS.Clear(); |
671 | 0 | poDS->m_gt = GDALGeoTransform(); |
672 | 0 | } |
673 | | |
674 | | /* -------------------------------------------------------------------- */ |
675 | | /* Check and set matrix representation. */ |
676 | | /* -------------------------------------------------------------------- */ |
677 | |
|
678 | 0 | if (poDS->GetRasterCount() == 4) |
679 | 0 | { |
680 | 0 | poDS->SetMetadataItem("MATRIX_REPRESENTATION", "SCATTERING"); |
681 | 0 | } |
682 | | |
683 | | /* -------------------------------------------------------------------- */ |
684 | | /* Read the four corners and centre GCPs in */ |
685 | | /* -------------------------------------------------------------------- */ |
686 | |
|
687 | 0 | const CPLXMLNode *psSceneInfo = |
688 | 0 | CPLGetXMLNode(psData.get(), "=level1Product.productInfo.sceneInfo"); |
689 | 0 | if (psSceneInfo != nullptr) |
690 | 0 | { |
691 | | /* extract the GCPs from the provided file */ |
692 | 0 | bool success = false; |
693 | 0 | if (!osGeorefFile.empty()) |
694 | 0 | success = poDS->getGCPsFromGEOREF_XML(osGeorefFile.c_str()); |
695 | | |
696 | | // if the gcp's cannot be extracted from the georef file, try to get the |
697 | | // corner coordinates for now just SSC because the others don't have |
698 | | // refColumn and refRow |
699 | 0 | if (!success && poDS->nProduct == eSSC) |
700 | 0 | { |
701 | 0 | int nGCP = 0; |
702 | 0 | double dfAvgHeight = CPLAtof( |
703 | 0 | CPLGetXMLValue(psSceneInfo, "sceneAverageHeight", "0.0")); |
704 | | |
705 | | // count and allocate gcps - there should be five - 4 corners and a |
706 | | // centre |
707 | 0 | poDS->nGCPCount = 0; |
708 | 0 | const CPLXMLNode *psNode = psSceneInfo->psChild; |
709 | 0 | for (; psNode != nullptr; psNode = psNode->psNext) |
710 | 0 | { |
711 | 0 | if (!EQUAL(psNode->pszValue, "sceneCenterCoord") && |
712 | 0 | !EQUAL(psNode->pszValue, "sceneCornerCoord")) |
713 | 0 | continue; |
714 | | |
715 | 0 | poDS->nGCPCount++; |
716 | 0 | } |
717 | 0 | if (poDS->nGCPCount > 0) |
718 | 0 | { |
719 | 0 | poDS->pasGCPList = |
720 | 0 | (GDAL_GCP *)CPLCalloc(sizeof(GDAL_GCP), poDS->nGCPCount); |
721 | | |
722 | | /* iterate over GCPs */ |
723 | 0 | for (psNode = psSceneInfo->psChild; psNode != nullptr; |
724 | 0 | psNode = psNode->psNext) |
725 | 0 | { |
726 | 0 | GDAL_GCP *psGCP = poDS->pasGCPList + nGCP; |
727 | |
|
728 | 0 | if (!EQUAL(psNode->pszValue, "sceneCenterCoord") && |
729 | 0 | !EQUAL(psNode->pszValue, "sceneCornerCoord")) |
730 | 0 | continue; |
731 | | |
732 | 0 | psGCP->dfGCPPixel = |
733 | 0 | CPLAtof(CPLGetXMLValue(psNode, "refColumn", "0.0")); |
734 | 0 | psGCP->dfGCPLine = |
735 | 0 | CPLAtof(CPLGetXMLValue(psNode, "refRow", "0.0")); |
736 | 0 | psGCP->dfGCPX = |
737 | 0 | CPLAtof(CPLGetXMLValue(psNode, "lon", "0.0")); |
738 | 0 | psGCP->dfGCPY = |
739 | 0 | CPLAtof(CPLGetXMLValue(psNode, "lat", "0.0")); |
740 | 0 | psGCP->dfGCPZ = dfAvgHeight; |
741 | 0 | psGCP->pszId = CPLStrdup(CPLSPrintf("%d", nGCP)); |
742 | 0 | psGCP->pszInfo = CPLStrdup(""); |
743 | |
|
744 | 0 | nGCP++; |
745 | 0 | } |
746 | | |
747 | | // set the projection string - the fields are lat/long - seems |
748 | | // to be WGS84 datum |
749 | 0 | poDS->m_oGCPSRS.SetWellKnownGeogCS("WGS84"); |
750 | 0 | } |
751 | 0 | } |
752 | | |
753 | | // gcps override geotransform - does it make sense to have both?? |
754 | 0 | if (poDS->nGCPCount > 0) |
755 | 0 | { |
756 | 0 | poDS->bHaveGeoTransform = FALSE; |
757 | 0 | poDS->m_oSRS.Clear(); |
758 | 0 | poDS->m_gt = GDALGeoTransform(); |
759 | 0 | } |
760 | 0 | } |
761 | 0 | else |
762 | 0 | { |
763 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
764 | 0 | "Unable to find sceneInfo tag in XML document. " |
765 | 0 | "Proceeding with caution."); |
766 | 0 | } |
767 | | |
768 | | /* -------------------------------------------------------------------- */ |
769 | | /* Initialize any PAM information. */ |
770 | | /* -------------------------------------------------------------------- */ |
771 | 0 | poDS->SetDescription(poOpenInfo->pszFilename); |
772 | 0 | poDS->TryLoadXML(); |
773 | | |
774 | | /* -------------------------------------------------------------------- */ |
775 | | /* Check for overviews. */ |
776 | | /* -------------------------------------------------------------------- */ |
777 | 0 | poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename); |
778 | |
|
779 | 0 | return poDS.release(); |
780 | 0 | } |
781 | | |
782 | | /************************************************************************/ |
783 | | /* GetGCPCount() */ |
784 | | /************************************************************************/ |
785 | | |
786 | | int TSXDataset::GetGCPCount() |
787 | 0 | { |
788 | 0 | return nGCPCount; |
789 | 0 | } |
790 | | |
791 | | /************************************************************************/ |
792 | | /* GetGCPSpatialRef() */ |
793 | | /************************************************************************/ |
794 | | |
795 | | const OGRSpatialReference *TSXDataset::GetGCPSpatialRef() const |
796 | 0 | { |
797 | 0 | return m_oGCPSRS.IsEmpty() ? nullptr : &m_oGCPSRS; |
798 | 0 | } |
799 | | |
800 | | /************************************************************************/ |
801 | | /* GetGCPs() */ |
802 | | /************************************************************************/ |
803 | | |
804 | | const GDAL_GCP *TSXDataset::GetGCPs() |
805 | 0 | { |
806 | 0 | return pasGCPList; |
807 | 0 | } |
808 | | |
809 | | /************************************************************************/ |
810 | | /* GetSpatialRef() */ |
811 | | /************************************************************************/ |
812 | | |
813 | | const OGRSpatialReference *TSXDataset::GetSpatialRef() const |
814 | | |
815 | 0 | { |
816 | 0 | return m_oSRS.IsEmpty() ? nullptr : &m_oSRS; |
817 | 0 | } |
818 | | |
819 | | /************************************************************************/ |
820 | | /* GetGeotransform() */ |
821 | | /************************************************************************/ |
822 | | CPLErr TSXDataset::GetGeoTransform(GDALGeoTransform >) const |
823 | 0 | { |
824 | 0 | gt = m_gt; |
825 | |
|
826 | 0 | if (bHaveGeoTransform) |
827 | 0 | return CE_None; |
828 | | |
829 | 0 | return CE_Failure; |
830 | 0 | } |
831 | | |
832 | | /************************************************************************/ |
833 | | /* GDALRegister_TSX() */ |
834 | | /************************************************************************/ |
835 | | |
836 | | void GDALRegister_TSX() |
837 | 22 | { |
838 | 22 | if (GDALGetDriverByName("TSX") != nullptr) |
839 | 0 | return; |
840 | | |
841 | 22 | GDALDriver *poDriver = new GDALDriver(); |
842 | | |
843 | 22 | poDriver->SetDescription("TSX"); |
844 | 22 | poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES"); |
845 | 22 | poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "TerraSAR-X Product"); |
846 | 22 | poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/tsx.html"); |
847 | 22 | poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES"); |
848 | | |
849 | 22 | poDriver->pfnOpen = TSXDataset::Open; |
850 | 22 | poDriver->pfnIdentify = TSXDataset::Identify; |
851 | | |
852 | 22 | GetGDALDriverManager()->RegisterDriver(poDriver); |
853 | 22 | } |