Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL Core
4 : * Purpose: Read metadata from Pleiades imagery.
5 : * Author: Alexander Lisovenko
6 : * Author: Dmitry Baryshnikov, polimax@mail.ru
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2014-2018 NextGIS <info@nextgis.ru>
10 : *
11 : * Permission is hereby granted, free of charge, to any person obtaining a
12 : * copy of this software and associated documentation files (the "Software"),
13 : * to deal in the Software without restriction, including without limitation
14 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 : * and/or sell copies of the Software, and to permit persons to whom the
16 : * Software is furnished to do so, subject to the following conditions:
17 : *
18 : * The above copyright notice and this permission notice shall be included
19 : * in all copies or substantial portions of the Software.
20 : *
21 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 : * DEALINGS IN THE SOFTWARE.
28 : ****************************************************************************/
29 :
30 : #include "cpl_port.h"
31 : #include "reader_pleiades.h"
32 :
33 : #include <cstddef>
34 : #include <cstdio>
35 : #include <cstring>
36 : #include <ctime>
37 :
38 : #include <string>
39 :
40 : #include "cpl_conv.h"
41 : #include "cpl_error.h"
42 : #include "cpl_minixml.h"
43 : #include "cpl_string.h"
44 : #include "cpl_time.h"
45 :
46 : /**
47 : * GDALMDReaderPleiades()
48 : */
49 10076 : GDALMDReaderPleiades::GDALMDReaderPleiades(const char *pszPath,
50 10076 : char **papszSiblingFiles)
51 : : GDALMDReaderBase(pszPath, papszSiblingFiles), m_osBaseFilename(pszPath),
52 10076 : m_osIMDSourceFilename(CPLString()), m_osRPBSourceFilename(CPLString())
53 : {
54 10076 : const CPLString osBaseName = CPLGetBasename(pszPath);
55 10076 : const size_t nBaseNameLen = osBaseName.size();
56 10076 : if (nBaseNameLen < 4 || nBaseNameLen > 511)
57 734 : return;
58 :
59 9342 : const CPLString osDirName = CPLGetDirname(pszPath);
60 :
61 : std::string osIMDSourceFilename = CPLFormFilename(
62 9342 : osDirName, CPLSPrintf("DIM_%s", osBaseName.c_str() + 4), "XML");
63 : std::string osRPBSourceFilename = CPLFormFilename(
64 9342 : osDirName, CPLSPrintf("RPC_%s", osBaseName.c_str() + 4), "XML");
65 :
66 : // find last underline
67 : char sBaseName[512];
68 9342 : size_t nLastUnderline = 0;
69 77929 : for (size_t i = 4; i < nBaseNameLen; i++)
70 : {
71 68587 : sBaseName[i - 4] = osBaseName[i];
72 68587 : if (osBaseName[i] == '_')
73 10759 : nLastUnderline = i - 4U;
74 : }
75 :
76 9342 : sBaseName[nLastUnderline] = 0;
77 :
78 : // Check if last 4 characters are fit in mask RjCj
79 : unsigned int iRow, iCol;
80 9342 : bool bHasRowColPart = nBaseNameLen > nLastUnderline + 5U;
81 9342 : if (!bHasRowColPart || sscanf(osBaseName.c_str() + nLastUnderline + 5U,
82 : "R%uC%u", &iRow, &iCol) != 2)
83 : {
84 9328 : return;
85 : }
86 :
87 : // Strip of suffix from PNEO products
88 14 : char *pszLastUnderScore = strrchr(sBaseName, '_');
89 14 : if (pszLastUnderScore &&
90 3 : (EQUAL(pszLastUnderScore, "_P") || EQUAL(pszLastUnderScore, "_RGB") ||
91 3 : EQUAL(pszLastUnderScore, "_NED")))
92 : {
93 0 : *pszLastUnderScore = 0;
94 : }
95 :
96 14 : if (CPLCheckForFile(&osIMDSourceFilename[0], papszSiblingFiles))
97 : {
98 0 : m_osIMDSourceFilename = std::move(osIMDSourceFilename);
99 : }
100 : else
101 : {
102 : osIMDSourceFilename =
103 14 : CPLFormFilename(osDirName, CPLSPrintf("DIM_%s", sBaseName), "XML");
104 14 : if (CPLCheckForFile(&osIMDSourceFilename[0], papszSiblingFiles))
105 : {
106 14 : m_osIMDSourceFilename = std::move(osIMDSourceFilename);
107 : }
108 : }
109 :
110 14 : if (CPLCheckForFile(&osRPBSourceFilename[0], papszSiblingFiles))
111 : {
112 0 : m_osRPBSourceFilename = std::move(osRPBSourceFilename);
113 : }
114 : else
115 : {
116 : osRPBSourceFilename =
117 14 : CPLFormFilename(osDirName, CPLSPrintf("RPC_%s", sBaseName), "XML");
118 14 : if (CPLCheckForFile(&osRPBSourceFilename[0], papszSiblingFiles))
119 : {
120 4 : m_osRPBSourceFilename = std::move(osRPBSourceFilename);
121 : }
122 : }
123 :
124 14 : if (!m_osIMDSourceFilename.empty())
125 14 : CPLDebug("MDReaderPleiades", "IMD Filename: %s",
126 : m_osIMDSourceFilename.c_str());
127 14 : if (!m_osRPBSourceFilename.empty())
128 4 : CPLDebug("MDReaderPleiades", "RPB Filename: %s",
129 : m_osRPBSourceFilename.c_str());
130 : }
131 :
132 6 : GDALMDReaderPleiades::GDALMDReaderPleiades()
133 6 : : GDALMDReaderBase(nullptr, nullptr)
134 : {
135 6 : }
136 :
137 : GDALMDReaderPleiades *
138 6 : GDALMDReaderPleiades::CreateReaderForRPC(const char *pszRPCSourceFilename)
139 : {
140 6 : GDALMDReaderPleiades *poReader = new GDALMDReaderPleiades();
141 6 : poReader->m_osRPBSourceFilename = pszRPCSourceFilename;
142 6 : return poReader;
143 : }
144 :
145 : /**
146 : * ~GDALMDReaderPleiades()
147 : */
148 15133 : GDALMDReaderPleiades::~GDALMDReaderPleiades()
149 : {
150 15133 : }
151 :
152 : /**
153 : * HasRequiredFiles()
154 : */
155 10076 : bool GDALMDReaderPleiades::HasRequiredFiles() const
156 : {
157 10076 : if (!m_osIMDSourceFilename.empty())
158 15 : return true;
159 10061 : if (!m_osRPBSourceFilename.empty())
160 0 : return true;
161 :
162 10061 : return false;
163 : }
164 :
165 : /**
166 : * GetMetadataFiles()
167 : */
168 15 : char **GDALMDReaderPleiades::GetMetadataFiles() const
169 : {
170 15 : char **papszFileList = nullptr;
171 15 : if (!m_osIMDSourceFilename.empty())
172 15 : papszFileList = CSLAddString(papszFileList, m_osIMDSourceFilename);
173 15 : if (!m_osRPBSourceFilename.empty())
174 4 : papszFileList = CSLAddString(papszFileList, m_osRPBSourceFilename);
175 :
176 15 : return papszFileList;
177 : }
178 :
179 : /**
180 : * LoadMetadata()
181 : */
182 25 : void GDALMDReaderPleiades::LoadMetadata()
183 : {
184 25 : if (m_bIsMetadataLoad)
185 11 : return;
186 :
187 14 : if (!m_osIMDSourceFilename.empty())
188 : {
189 14 : CPLXMLNode *psNode = CPLParseXMLFile(m_osIMDSourceFilename);
190 :
191 14 : if (psNode != nullptr)
192 : {
193 14 : CPLXMLNode *psisdNode = CPLSearchXMLNode(psNode, "=Dimap_Document");
194 :
195 14 : if (psisdNode != nullptr)
196 : {
197 14 : m_papszIMDMD = ReadXMLToList(psisdNode->psChild, m_papszIMDMD);
198 : }
199 14 : CPLDestroyXMLNode(psNode);
200 : }
201 : }
202 :
203 14 : if (!m_osRPBSourceFilename.empty())
204 : {
205 4 : m_papszRPCMD = LoadRPCXmlFile();
206 : }
207 :
208 14 : m_papszDEFAULTMD =
209 14 : CSLAddNameValue(m_papszDEFAULTMD, MD_NAME_MDTYPE, "DIMAP");
210 :
211 14 : m_bIsMetadataLoad = true;
212 :
213 14 : if (nullptr == m_papszIMDMD)
214 : {
215 0 : return;
216 : }
217 :
218 : // extract imagery metadata
219 14 : int nCounter = -1;
220 28 : const char *pszSatId1 = CSLFetchNameValue(
221 14 : m_papszIMDMD,
222 : "Dataset_Sources.Source_Identification.Strip_Source.MISSION");
223 14 : if (nullptr == pszSatId1)
224 : {
225 0 : nCounter = 1;
226 0 : for (int i = 0; i < 5; i++)
227 : {
228 0 : pszSatId1 = CSLFetchNameValue(
229 0 : m_papszIMDMD,
230 : CPLSPrintf("Dataset_Sources.Source_Identification_%d.Strip_"
231 : "Source.MISSION",
232 : nCounter));
233 0 : if (nullptr != pszSatId1)
234 0 : break;
235 0 : nCounter++;
236 : }
237 : }
238 :
239 : const char *pszSatId2;
240 14 : if (nCounter == -1)
241 14 : pszSatId2 = CSLFetchNameValue(
242 14 : m_papszIMDMD,
243 : "Dataset_Sources.Source_Identification.Strip_Source.MISSION_INDEX");
244 : else
245 0 : pszSatId2 = CSLFetchNameValue(
246 0 : m_papszIMDMD, CPLSPrintf("Dataset_Sources.Source_Identification_%d."
247 : "Strip_Source.MISSION_INDEX",
248 : nCounter));
249 :
250 14 : if (nullptr != pszSatId1 && nullptr != pszSatId2)
251 : {
252 28 : m_papszIMAGERYMD = CSLAddNameValue(
253 : m_papszIMAGERYMD, MD_NAME_SATELLITE,
254 28 : CPLSPrintf("%s %s", CPLStripQuotes(pszSatId1).c_str(),
255 28 : CPLStripQuotes(pszSatId2).c_str()));
256 : }
257 0 : else if (nullptr != pszSatId1 && nullptr == pszSatId2)
258 : {
259 0 : m_papszIMAGERYMD = CSLAddNameValue(m_papszIMAGERYMD, MD_NAME_SATELLITE,
260 0 : CPLStripQuotes(pszSatId1));
261 : }
262 0 : else if (nullptr == pszSatId1 && nullptr != pszSatId2)
263 : {
264 0 : m_papszIMAGERYMD = CSLAddNameValue(m_papszIMAGERYMD, MD_NAME_SATELLITE,
265 0 : CPLStripQuotes(pszSatId2));
266 : }
267 :
268 : const char *pszDate;
269 14 : if (nCounter == -1)
270 14 : pszDate = CSLFetchNameValue(
271 14 : m_papszIMDMD,
272 : "Dataset_Sources.Source_Identification.Strip_Source.IMAGING_DATE");
273 : else
274 0 : pszDate = CSLFetchNameValue(
275 0 : m_papszIMDMD, CPLSPrintf("Dataset_Sources.Source_Identification_%d."
276 : "Strip_Source.IMAGING_DATE",
277 : nCounter));
278 :
279 14 : if (nullptr != pszDate)
280 : {
281 : const char *pszTime;
282 14 : if (nCounter == -1)
283 14 : pszTime = CSLFetchNameValue(m_papszIMDMD,
284 : "Dataset_Sources.Source_Identification."
285 : "Strip_Source.IMAGING_TIME");
286 : else
287 0 : pszTime = CSLFetchNameValue(
288 0 : m_papszIMDMD,
289 : CPLSPrintf("Dataset_Sources.Source_Identification_%d.Strip_"
290 : "Source.IMAGING_TIME",
291 : nCounter));
292 :
293 14 : if (nullptr == pszTime)
294 0 : pszTime = "00:00:00.0Z";
295 :
296 : char buffer[80];
297 : GIntBig timeMid =
298 14 : GetAcquisitionTimeFromString(CPLSPrintf("%sT%s", pszDate, pszTime));
299 : struct tm tmBuf;
300 14 : strftime(buffer, 80, MD_DATETIMEFORMAT,
301 14 : CPLUnixTimeToYMDHMS(timeMid, &tmBuf));
302 14 : m_papszIMAGERYMD =
303 14 : CSLAddNameValue(m_papszIMAGERYMD, MD_NAME_ACQDATETIME, buffer);
304 : }
305 :
306 14 : m_papszIMAGERYMD =
307 14 : CSLAddNameValue(m_papszIMAGERYMD, MD_NAME_CLOUDCOVER, MD_CLOUDCOVER_NA);
308 : }
309 :
310 : /**
311 : * LoadRPCXmlFile()
312 : */
313 :
314 : static const char *const apszRPBMapPleiades[] = {
315 : RPC_LINE_OFF, "RFM_Validity.LINE_OFF", // do not change order !
316 : RPC_SAMP_OFF, "RFM_Validity.SAMP_OFF", // do not change order !
317 : RPC_LAT_OFF, "RFM_Validity.LAT_OFF",
318 : RPC_LONG_OFF, "RFM_Validity.LONG_OFF",
319 : RPC_HEIGHT_OFF, "RFM_Validity.HEIGHT_OFF",
320 : RPC_LINE_SCALE, "RFM_Validity.LINE_SCALE",
321 : RPC_SAMP_SCALE, "RFM_Validity.SAMP_SCALE",
322 : RPC_LAT_SCALE, "RFM_Validity.LAT_SCALE",
323 : RPC_LONG_SCALE, "RFM_Validity.LONG_SCALE",
324 : RPC_HEIGHT_SCALE, "RFM_Validity.HEIGHT_SCALE",
325 : nullptr, nullptr};
326 :
327 : static const char *const apszRPCTXT20ValItemsPleiades[] = {
328 : RPC_LINE_NUM_COEFF, RPC_LINE_DEN_COEFF, RPC_SAMP_NUM_COEFF,
329 : RPC_SAMP_DEN_COEFF, nullptr};
330 :
331 10 : char **GDALMDReaderPleiades::LoadRPCXmlFile()
332 : {
333 10 : CPLXMLNode *pNode = CPLParseXMLFile(m_osRPBSourceFilename);
334 :
335 10 : if (nullptr == pNode)
336 0 : return nullptr;
337 :
338 : // search Global_RFM
339 10 : char **papszRawRPCList = nullptr;
340 10 : CPLXMLNode *pGRFMNode = CPLSearchXMLNode(pNode, "=Global_RFM");
341 :
342 10 : if (pGRFMNode != nullptr)
343 : {
344 10 : papszRawRPCList = ReadXMLToList(pGRFMNode->psChild, papszRawRPCList);
345 : }
346 : else
347 : {
348 0 : pGRFMNode = CPLSearchXMLNode(pNode, "=Rational_Function_Model");
349 :
350 0 : if (pGRFMNode != nullptr)
351 : {
352 : papszRawRPCList =
353 0 : ReadXMLToList(pGRFMNode->psChild, papszRawRPCList);
354 : }
355 : }
356 :
357 10 : if (nullptr == papszRawRPCList)
358 : {
359 0 : CPLDestroyXMLNode(pNode);
360 0 : return nullptr;
361 : }
362 :
363 : // If we are not the top-left tile, then we must shift LINE_OFF and SAMP_OFF
364 10 : int nLineOffShift = 0;
365 10 : int nPixelOffShift = 0;
366 10 : for (int i = 1; TRUE; i++)
367 : {
368 11 : CPLString osKey;
369 : osKey.Printf("Raster_Data.Data_Access.Data_Files.Data_File_%d.DATA_"
370 : "FILE_PATH.href",
371 11 : i);
372 11 : const char *pszHref = CSLFetchNameValue(m_papszIMDMD, osKey);
373 11 : if (pszHref == nullptr)
374 9 : break;
375 2 : if (strcmp(CPLGetFilename(pszHref), CPLGetFilename(m_osBaseFilename)) ==
376 : 0)
377 : {
378 : osKey.Printf(
379 1 : "Raster_Data.Data_Access.Data_Files.Data_File_%d.tile_C", i);
380 1 : const char *pszC = CSLFetchNameValue(m_papszIMDMD, osKey);
381 : osKey.Printf(
382 1 : "Raster_Data.Data_Access.Data_Files.Data_File_%d.tile_R", i);
383 1 : const char *pszR = CSLFetchNameValue(m_papszIMDMD, osKey);
384 2 : const char *pszTileWidth = CSLFetchNameValue(
385 1 : m_papszIMDMD, "Raster_Data.Raster_Dimensions.Tile_Set.Regular_"
386 : "Tiling.NTILES_SIZE.ncols");
387 2 : const char *pszTileHeight = CSLFetchNameValue(
388 1 : m_papszIMDMD, "Raster_Data.Raster_Dimensions.Tile_Set.Regular_"
389 : "Tiling.NTILES_SIZE.nrows");
390 : const char *pszOVERLAP_COL =
391 1 : CSLFetchNameValueDef(m_papszIMDMD,
392 : "Raster_Data.Raster_Dimensions.Tile_Set."
393 : "Regular_Tiling.OVERLAP_COL",
394 : "0");
395 : const char *pszOVERLAP_ROW =
396 1 : CSLFetchNameValueDef(m_papszIMDMD,
397 : "Raster_Data.Raster_Dimensions.Tile_Set."
398 : "Regular_Tiling.OVERLAP_ROW",
399 : "0");
400 :
401 1 : if (pszC && pszR && pszTileWidth && pszTileHeight &&
402 1 : atoi(pszOVERLAP_COL) == 0 && atoi(pszOVERLAP_ROW) == 0)
403 : {
404 1 : nLineOffShift = -(atoi(pszR) - 1) * atoi(pszTileHeight);
405 1 : nPixelOffShift = -(atoi(pszC) - 1) * atoi(pszTileWidth);
406 : }
407 1 : break;
408 : }
409 1 : }
410 :
411 : // SPOT and PHR sensors use 1,1 as their upper left corner pixel convention
412 : // for RPCs which is non standard. This was fixed with PNEO which correctly
413 : // assumes 0,0.
414 : // Precompute the offset that will be applied to LINE_OFF and SAMP_OFF
415 : // in order to use the RPCs with the standard 0,0 convention
416 : double topleftOffset;
417 10 : CPLXMLNode *psDoc = CPLGetXMLNode(pNode, "=Dimap_Document");
418 10 : if (!psDoc)
419 0 : psDoc = CPLGetXMLNode(pNode, "=PHR_DIMAP_Document");
420 10 : const char *pszMetadataProfile = CPLGetXMLValue(
421 : psDoc, "Metadata_Identification.METADATA_PROFILE", "PHR_SENSOR");
422 10 : if (EQUAL(pszMetadataProfile, "PHR_SENSOR") ||
423 2 : EQUAL(pszMetadataProfile, "S7_SENSOR") ||
424 2 : EQUAL(pszMetadataProfile, "S6_SENSOR"))
425 : {
426 8 : topleftOffset = 1;
427 : }
428 2 : else if (EQUAL(pszMetadataProfile, "PNEO_SENSOR"))
429 : {
430 2 : topleftOffset = 0;
431 : }
432 : else
433 : {
434 : //CPLError(CE_Warning, CPLE_AppDefined,
435 : // "Unknown RPC Metadata Profile: %s. Assuming PHR_SENSOR",
436 : // pszMetadataProfile);
437 0 : topleftOffset = 1;
438 : }
439 :
440 : // format list
441 10 : char **papszRPB = nullptr;
442 110 : for (int i = 0; apszRPBMapPleiades[i] != nullptr; i += 2)
443 : {
444 : const char *pszValue =
445 100 : CSLFetchNameValue(papszRawRPCList, apszRPBMapPleiades[i + 1]);
446 100 : if ((i == 0 || i == 2) && pszValue) //i.e. LINE_OFF or SAMP_OFF
447 : {
448 20 : CPLString osField;
449 20 : double dfVal = CPLAtofM(pszValue) - topleftOffset;
450 20 : if (i == 0)
451 10 : dfVal += nLineOffShift;
452 : else
453 10 : dfVal += nPixelOffShift;
454 20 : osField.Printf("%.15g", dfVal);
455 : papszRPB =
456 20 : CSLAddNameValue(papszRPB, apszRPBMapPleiades[i], osField);
457 : }
458 : else
459 : {
460 : papszRPB =
461 80 : CSLAddNameValue(papszRPB, apszRPBMapPleiades[i], pszValue);
462 : }
463 : }
464 :
465 : // merge coefficients
466 50 : for (int i = 0; apszRPCTXT20ValItemsPleiades[i] != nullptr; i++)
467 : {
468 40 : CPLString value;
469 840 : for (int j = 1; j < 21; j++)
470 : {
471 : // We want to use the Inverse_Model
472 : // Quoting PleiadesUserGuideV2-1012.pdf:
473 : // """When using the inverse model (ground --> image), the user
474 : // supplies geographic coordinates (lon, lat) and an altitude
475 : // (alt)"""
476 800 : const char *pszValue = CSLFetchNameValue(
477 : papszRawRPCList,
478 : CPLSPrintf("Inverse_Model.%s_%d",
479 800 : apszRPCTXT20ValItemsPleiades[i], j));
480 800 : if (nullptr != pszValue)
481 : {
482 640 : value = value + " " + CPLString(pszValue);
483 : }
484 : else
485 : {
486 160 : pszValue = CSLFetchNameValue(
487 : papszRawRPCList,
488 : CPLSPrintf("GroundtoImage_Values.%s_%d",
489 160 : apszRPCTXT20ValItemsPleiades[i], j));
490 160 : if (nullptr != pszValue)
491 : {
492 160 : value = value + " " + CPLString(pszValue);
493 : }
494 : }
495 : }
496 : papszRPB =
497 40 : CSLAddNameValue(papszRPB, apszRPCTXT20ValItemsPleiades[i], value);
498 : }
499 :
500 10 : CSLDestroy(papszRawRPCList);
501 10 : CPLDestroyXMLNode(pNode);
502 10 : return papszRPB;
503 : }
|