Line data Source code
1 :
2 : /******************************************************************************
3 : *
4 : * Project: GDAL
5 : * Purpose: GDALJP2Metadata - Read GeoTIFF and/or GML georef info.
6 : * Author: Frank Warmerdam, warmerdam@pobox.com
7 : * Even Rouault <even dot rouault at spatialys dot com>
8 : *
9 : ******************************************************************************
10 : * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
11 : * Copyright (c) 2010-2015, Even Rouault <even dot rouault at spatialys dot com>
12 : * Copyright (c) 2015, European Union Satellite Centre
13 : *
14 : * SPDX-License-Identifier: MIT
15 : ****************************************************************************/
16 :
17 : #include "cpl_port.h"
18 : #include "gdaljp2metadata.h"
19 : #include "gdaljp2metadatagenerator.h"
20 :
21 : #include <cmath>
22 : #include <cstddef>
23 : #include <cstdlib>
24 : #include <cstring>
25 : #if HAVE_FCNTL_H
26 : #include <fcntl.h>
27 : #endif
28 :
29 : #include <algorithm>
30 : #include <array>
31 : #include <memory>
32 : #include <set>
33 : #include <string>
34 : #include <vector>
35 :
36 : #include "cpl_error.h"
37 : #include "cpl_string.h"
38 : #include "cpl_minixml.h"
39 : #include "gdaljp2metadatagenerator.h"
40 : #ifdef HAVE_TIFF
41 : #include "gt_wkt_srs_for_gdal.h"
42 : #endif
43 : #include "ogr_api.h"
44 : #include "ogr_core.h"
45 : #include "ogr_geometry.h"
46 : #include "ogr_spatialref.h"
47 : #include "ogrlibjsonutils.h"
48 :
49 : /*! @cond Doxygen_Suppress */
50 :
51 : static const unsigned char msi_uuid2[16] = {0xb1, 0x4b, 0xf8, 0xbd, 0x08, 0x3d,
52 : 0x4b, 0x43, 0xa5, 0xae, 0x8c, 0xd7,
53 : 0xd5, 0xa6, 0xce, 0x03};
54 :
55 : static const unsigned char msig_uuid[16] = {0x96, 0xA9, 0xF1, 0xF1, 0xDC, 0x98,
56 : 0x40, 0x2D, 0xA7, 0xAE, 0xD6, 0x8E,
57 : 0x34, 0x45, 0x18, 0x09};
58 :
59 : static const unsigned char xmp_uuid[16] = {0xBE, 0x7A, 0xCF, 0xCB, 0x97, 0xA9,
60 : 0x42, 0xE8, 0x9C, 0x71, 0x99, 0x94,
61 : 0x91, 0xE3, 0xAF, 0xAC};
62 :
63 : struct _GDALJP2GeoTIFFBox
64 : {
65 : int nGeoTIFFSize;
66 : GByte *pabyGeoTIFFData;
67 : };
68 :
69 : constexpr int MAX_JP2GEOTIFF_BOXES = 2;
70 :
71 : /************************************************************************/
72 : /* GDALJP2Metadata() */
73 : /************************************************************************/
74 :
75 1530 : GDALJP2Metadata::GDALJP2Metadata()
76 : : nGeoTIFFBoxesCount(0), pasGeoTIFFBoxes(nullptr), nMSIGSize(0),
77 : pabyMSIGData(nullptr), papszGMLMetadata(nullptr), bPixelIsPoint(false),
78 : nGCPCount(0), pasGCPList(nullptr), papszRPCMD(nullptr),
79 : papszMetadata(nullptr), pszXMPMetadata(nullptr),
80 1530 : pszGDALMultiDomainMetadata(nullptr), pszXMLIPR(nullptr)
81 : {
82 1530 : }
83 :
84 : /************************************************************************/
85 : /* ~GDALJP2Metadata() */
86 : /************************************************************************/
87 :
88 1531 : GDALJP2Metadata::~GDALJP2Metadata()
89 :
90 : {
91 1531 : if (nGCPCount > 0)
92 : {
93 18 : GDALDeinitGCPs(nGCPCount, pasGCPList);
94 18 : CPLFree(pasGCPList);
95 : }
96 1531 : CSLDestroy(papszRPCMD);
97 :
98 2134 : for (int i = 0; i < nGeoTIFFBoxesCount; ++i)
99 : {
100 603 : CPLFree(pasGeoTIFFBoxes[i].pabyGeoTIFFData);
101 : }
102 1531 : CPLFree(pasGeoTIFFBoxes);
103 1531 : CPLFree(pabyMSIGData);
104 1531 : CSLDestroy(papszGMLMetadata);
105 1531 : CSLDestroy(papszMetadata);
106 1531 : CPLFree(pszXMPMetadata);
107 1531 : CPLFree(pszGDALMultiDomainMetadata);
108 1531 : CPLFree(pszXMLIPR);
109 1531 : }
110 :
111 : /************************************************************************/
112 : /* ReadAndParse() */
113 : /* */
114 : /* Read a JP2 file and try to collect georeferencing */
115 : /* information from the various available forms. Returns TRUE */
116 : /* if anything useful is found. */
117 : /************************************************************************/
118 :
119 89 : int GDALJP2Metadata::ReadAndParse(const char *pszFilename, int nGEOJP2Index,
120 : int nGMLJP2Index, int nMSIGIndex,
121 : int nWorldFileIndex, int *pnIndexUsed)
122 :
123 : {
124 89 : VSILFILE *fpLL = VSIFOpenL(pszFilename, "rb");
125 89 : if (fpLL == nullptr)
126 : {
127 0 : CPLDebug("GDALJP2Metadata", "Could not even open %s.", pszFilename);
128 :
129 0 : return FALSE;
130 : }
131 :
132 89 : int nIndexUsed = -1;
133 89 : bool bRet = CPL_TO_BOOL(ReadAndParse(fpLL, nGEOJP2Index, nGMLJP2Index,
134 : nMSIGIndex, &nIndexUsed));
135 89 : CPL_IGNORE_RET_VAL(VSIFCloseL(fpLL));
136 :
137 : /* -------------------------------------------------------------------- */
138 : /* If we still don't have a geotransform, look for a world */
139 : /* file. */
140 : /* -------------------------------------------------------------------- */
141 89 : if (nWorldFileIndex >= 0 &&
142 89 : ((m_bHaveGeoTransform && nWorldFileIndex < nIndexUsed) ||
143 89 : !m_bHaveGeoTransform))
144 : {
145 54 : m_bHaveGeoTransform =
146 107 : CPL_TO_BOOL(GDALReadWorldFile(pszFilename, nullptr, m_gt.data()) ||
147 53 : GDALReadWorldFile(pszFilename, ".wld", m_gt.data()));
148 54 : bRet |= m_bHaveGeoTransform;
149 : }
150 :
151 89 : if (pnIndexUsed)
152 89 : *pnIndexUsed = nIndexUsed;
153 :
154 89 : return bRet;
155 : }
156 :
157 1184 : int GDALJP2Metadata::ReadAndParse(VSILFILE *fpLL, int nGEOJP2Index,
158 : int nGMLJP2Index, int nMSIGIndex,
159 : int *pnIndexUsed)
160 :
161 : {
162 1184 : ReadBoxes(fpLL);
163 :
164 : /* -------------------------------------------------------------------- */
165 : /* Try JP2GeoTIFF, GML and finally MSIG in specified order. */
166 : /* -------------------------------------------------------------------- */
167 1184 : std::set<int> aoSetPriorities;
168 1183 : if (nGEOJP2Index >= 0)
169 1170 : aoSetPriorities.insert(nGEOJP2Index);
170 1183 : if (nGMLJP2Index >= 0)
171 1169 : aoSetPriorities.insert(nGMLJP2Index);
172 1185 : if (nMSIGIndex >= 0)
173 1169 : aoSetPriorities.insert(nMSIGIndex);
174 2897 : for (const int nIndex : aoSetPriorities)
175 : {
176 1170 : if ((nIndex == nGEOJP2Index && ParseJP2GeoTIFF()) ||
177 5208 : (nIndex == nGMLJP2Index && ParseGMLCoverageDesc()) ||
178 1712 : (nIndex == nMSIGIndex && ParseMSIG()))
179 : {
180 615 : if (pnIndexUsed)
181 615 : *pnIndexUsed = nIndex;
182 615 : break;
183 : }
184 : }
185 :
186 : /* -------------------------------------------------------------------- */
187 : /* Return success either either of projection or geotransform */
188 : /* or gcps. */
189 : /* -------------------------------------------------------------------- */
190 1754 : return m_bHaveGeoTransform || nGCPCount > 0 || !m_oSRS.IsEmpty() ||
191 2938 : papszRPCMD != nullptr;
192 : }
193 :
194 : /************************************************************************/
195 : /* CollectGMLData() */
196 : /* */
197 : /* Read all the asoc boxes after this node, and store the */
198 : /* contain xml documents along with the name from the label. */
199 : /************************************************************************/
200 :
201 225 : void GDALJP2Metadata::CollectGMLData(GDALJP2Box *poGMLData)
202 :
203 : {
204 225 : GDALJP2Box oChildBox(poGMLData->GetFILE());
205 :
206 225 : if (!oChildBox.ReadFirstChild(poGMLData))
207 0 : return;
208 :
209 505 : while (strlen(oChildBox.GetType()) > 0)
210 : {
211 505 : if (EQUAL(oChildBox.GetType(), "asoc"))
212 : {
213 280 : GDALJP2Box oSubChildBox(oChildBox.GetFILE());
214 :
215 280 : if (!oSubChildBox.ReadFirstChild(&oChildBox))
216 0 : break;
217 :
218 280 : char *pszLabel = nullptr;
219 280 : char *pszXML = nullptr;
220 :
221 560 : while (strlen(oSubChildBox.GetType()) > 0)
222 : {
223 560 : if (EQUAL(oSubChildBox.GetType(), "lbl "))
224 : pszLabel =
225 280 : reinterpret_cast<char *>(oSubChildBox.ReadBoxData());
226 280 : else if (EQUAL(oSubChildBox.GetType(), "xml "))
227 : {
228 : pszXML =
229 280 : reinterpret_cast<char *>(oSubChildBox.ReadBoxData());
230 280 : GIntBig nXMLLength = oSubChildBox.GetDataLength();
231 :
232 : // Some GML data contains \0 instead of \n.
233 : // See http://trac.osgeo.org/gdal/ticket/5760
234 : // TODO(schwehr): Explain the numbers in the next line.
235 280 : if (pszXML != nullptr && nXMLLength > 0 &&
236 : nXMLLength < 100 * 1024 * 1024)
237 : {
238 553 : for (GIntBig i = nXMLLength - 1; i >= 0; --i)
239 : {
240 553 : if (pszXML[i] == '\0')
241 273 : --nXMLLength;
242 : else
243 280 : break;
244 : }
245 280 : GIntBig i = 0; // Used after for.
246 714521 : for (; i < nXMLLength; ++i)
247 : {
248 714242 : if (pszXML[i] == '\0')
249 1 : break;
250 : }
251 280 : if (i < nXMLLength)
252 : {
253 1 : CPLPushErrorHandler(CPLQuietErrorHandler);
254 2 : CPLXMLTreeCloser psNode(CPLParseXMLString(pszXML));
255 1 : CPLPopErrorHandler();
256 1 : if (psNode == nullptr)
257 : {
258 1 : CPLDebug(
259 : "GMLJP2",
260 : "GMLJP2 data contains nul characters "
261 : "inside content. Replacing them by \\n");
262 1708 : for (GIntBig j = 0; j < nXMLLength; ++j)
263 : {
264 1707 : if (pszXML[j] == '\0')
265 1 : pszXML[j] = '\n';
266 : }
267 : }
268 : }
269 : }
270 : }
271 :
272 560 : if (!oSubChildBox.ReadNextChild(&oChildBox))
273 280 : break;
274 : }
275 :
276 280 : if (pszLabel != nullptr && pszXML != nullptr)
277 : {
278 280 : papszGMLMetadata =
279 280 : CSLSetNameValue(papszGMLMetadata, pszLabel, pszXML);
280 :
281 280 : if (strcmp(pszLabel, "gml.root-instance") == 0 &&
282 225 : pszGDALMultiDomainMetadata == nullptr &&
283 219 : strstr(pszXML, "GDALMultiDomainMetadata") != nullptr)
284 : {
285 4 : CPLXMLTreeCloser psTree(CPLParseXMLString(pszXML));
286 2 : if (psTree != nullptr)
287 : {
288 2 : CPLXMLNode *psGDALMDMD = CPLSearchXMLNode(
289 : psTree.get(), "GDALMultiDomainMetadata");
290 2 : if (psGDALMDMD)
291 2 : pszGDALMultiDomainMetadata =
292 2 : CPLSerializeXMLTree(psGDALMDMD);
293 : }
294 : }
295 : }
296 :
297 280 : CPLFree(pszLabel);
298 280 : CPLFree(pszXML);
299 : }
300 :
301 505 : if (!oChildBox.ReadNextChild(poGMLData))
302 225 : break;
303 : }
304 : }
305 :
306 : /************************************************************************/
307 : /* ReadBox() */
308 : /************************************************************************/
309 :
310 4086 : void GDALJP2Metadata::ReadBox(VSILFILE *fpVSIL, GDALJP2Box &oBox, int &iBox)
311 : {
312 : #ifdef DEBUG
313 4086 : if (CPLTestBool(CPLGetConfigOption("DUMP_JP2_BOXES", "NO")))
314 0 : oBox.DumpReadable(stderr);
315 : #endif
316 :
317 : /* -------------------------------------------------------------------- */
318 : /* Collect geotiff box. */
319 : /* -------------------------------------------------------------------- */
320 4698 : if (EQUAL(oBox.GetType(), "uuid") &&
321 612 : memcmp(oBox.GetUUID(), msi_uuid2, 16) == 0)
322 : {
323 : // Erdas JPEG2000 files sometimes contain 2 GeoTIFF UUID boxes. One
324 : // that is correct, another one that does not contain correct
325 : // georeferencing. Fetch at most 2 of them for later analysis.
326 603 : if (nGeoTIFFBoxesCount == MAX_JP2GEOTIFF_BOXES)
327 : {
328 0 : CPLDebug("GDALJP2",
329 : "Too many UUID GeoTIFF boxes. Ignoring this one");
330 : }
331 : else
332 : {
333 603 : const int nGeoTIFFSize = static_cast<int>(oBox.GetDataLength());
334 603 : GByte *pabyGeoTIFFData = oBox.ReadBoxData();
335 603 : if (pabyGeoTIFFData == nullptr)
336 : {
337 0 : CPLDebug("GDALJP2", "Cannot read data for UUID GeoTIFF box");
338 : }
339 : else
340 : {
341 603 : pasGeoTIFFBoxes = static_cast<GDALJP2GeoTIFFBox *>(
342 1206 : CPLRealloc(pasGeoTIFFBoxes, sizeof(GDALJP2GeoTIFFBox) *
343 603 : (nGeoTIFFBoxesCount + 1)));
344 603 : pasGeoTIFFBoxes[nGeoTIFFBoxesCount].nGeoTIFFSize = nGeoTIFFSize;
345 603 : pasGeoTIFFBoxes[nGeoTIFFBoxesCount].pabyGeoTIFFData =
346 : pabyGeoTIFFData;
347 603 : ++nGeoTIFFBoxesCount;
348 : }
349 : }
350 : }
351 :
352 : /* -------------------------------------------------------------------- */
353 : /* Collect MSIG box. */
354 : /* -------------------------------------------------------------------- */
355 3492 : else if (EQUAL(oBox.GetType(), "uuid") &&
356 9 : memcmp(oBox.GetUUID(), msig_uuid, 16) == 0)
357 : {
358 0 : if (nMSIGSize == 0)
359 : {
360 0 : nMSIGSize = static_cast<int>(oBox.GetDataLength());
361 0 : pabyMSIGData = oBox.ReadBoxData();
362 :
363 0 : if (nMSIGSize < 70 || pabyMSIGData == nullptr ||
364 0 : memcmp(pabyMSIGData, "MSIG/", 5) != 0)
365 : {
366 0 : CPLFree(pabyMSIGData);
367 0 : pabyMSIGData = nullptr;
368 0 : nMSIGSize = 0;
369 : }
370 : }
371 : else
372 : {
373 0 : CPLDebug("GDALJP2", "Too many UUID MSIG boxes. Ignoring this one");
374 : }
375 : }
376 :
377 : /* -------------------------------------------------------------------- */
378 : /* Collect XMP box. */
379 : /* -------------------------------------------------------------------- */
380 3492 : else if (EQUAL(oBox.GetType(), "uuid") &&
381 9 : memcmp(oBox.GetUUID(), xmp_uuid, 16) == 0)
382 : {
383 9 : if (pszXMPMetadata == nullptr)
384 : {
385 9 : pszXMPMetadata = reinterpret_cast<char *>(oBox.ReadBoxData());
386 : }
387 : else
388 : {
389 0 : CPLDebug("GDALJP2", "Too many UUID XMP boxes. Ignoring this one");
390 : }
391 : }
392 :
393 : /* -------------------------------------------------------------------- */
394 : /* Process asoc box looking for Labelled GML data. */
395 : /* -------------------------------------------------------------------- */
396 3474 : else if (EQUAL(oBox.GetType(), "asoc"))
397 : {
398 450 : GDALJP2Box oSubBox(fpVSIL);
399 :
400 225 : if (oSubBox.ReadFirstChild(&oBox) && EQUAL(oSubBox.GetType(), "lbl "))
401 : {
402 225 : char *pszLabel = reinterpret_cast<char *>(oSubBox.ReadBoxData());
403 225 : if (pszLabel != nullptr && EQUAL(pszLabel, "gml.data"))
404 : {
405 225 : CollectGMLData(&oBox);
406 : }
407 225 : CPLFree(pszLabel);
408 : }
409 : }
410 :
411 : /* -------------------------------------------------------------------- */
412 : /* Process simple xml boxes. */
413 : /* -------------------------------------------------------------------- */
414 3249 : else if (EQUAL(oBox.GetType(), "xml "))
415 : {
416 58 : CPLString osBoxName;
417 :
418 29 : char *pszXML = reinterpret_cast<char *>(oBox.ReadBoxData());
419 29 : if (pszXML != nullptr &&
420 29 : STARTS_WITH(pszXML, "<GDALMultiDomainMetadata>"))
421 : {
422 17 : if (pszGDALMultiDomainMetadata == nullptr)
423 : {
424 17 : pszGDALMultiDomainMetadata = pszXML;
425 17 : pszXML = nullptr;
426 : }
427 : else
428 : {
429 0 : CPLDebug("GDALJP2",
430 : "Too many GDAL metadata boxes. Ignoring this one");
431 : }
432 : }
433 12 : else if (pszXML != nullptr)
434 : {
435 12 : osBoxName.Printf("BOX_%d", iBox++);
436 :
437 12 : papszGMLMetadata =
438 12 : CSLSetNameValue(papszGMLMetadata, osBoxName, pszXML);
439 : }
440 29 : CPLFree(pszXML);
441 : }
442 :
443 : /* -------------------------------------------------------------------- */
444 : /* Check for a resd box in jp2h. */
445 : /* -------------------------------------------------------------------- */
446 3220 : else if (EQUAL(oBox.GetType(), "jp2h"))
447 : {
448 1474 : GDALJP2Box oSubBox(fpVSIL);
449 :
450 2375 : for (oSubBox.ReadFirstChild(&oBox); strlen(oSubBox.GetType()) > 0;
451 1638 : oSubBox.ReadNextChild(&oBox))
452 : {
453 1638 : if (EQUAL(oSubBox.GetType(), "res "))
454 : {
455 32 : GDALJP2Box oResBox(fpVSIL);
456 :
457 16 : oResBox.ReadFirstChild(&oSubBox);
458 :
459 : // We will use either the resd or resc box, which ever
460 : // happens to be first. Should we prefer resd?
461 16 : unsigned char *pabyResData = nullptr;
462 32 : if (oResBox.GetDataLength() == 10 &&
463 16 : (pabyResData = oResBox.ReadBoxData()) != nullptr)
464 : {
465 : int nVertNum, nVertDen, nVertExp;
466 : int nHorzNum, nHorzDen, nHorzExp;
467 :
468 16 : nVertNum = pabyResData[0] * 256 + pabyResData[1];
469 16 : nVertDen = pabyResData[2] * 256 + pabyResData[3];
470 16 : nHorzNum = pabyResData[4] * 256 + pabyResData[5];
471 16 : nHorzDen = pabyResData[6] * 256 + pabyResData[7];
472 16 : nVertExp = pabyResData[8];
473 16 : nHorzExp = pabyResData[9];
474 :
475 : // compute in pixels/cm
476 : const double dfVertRes =
477 32 : (nVertNum / static_cast<double>(nVertDen)) *
478 16 : pow(10.0, nVertExp) / 100;
479 : const double dfHorzRes =
480 32 : (nHorzNum / static_cast<double>(nHorzDen)) *
481 16 : pow(10.0, nHorzExp) / 100;
482 32 : CPLString osFormatter;
483 :
484 16 : papszMetadata =
485 16 : CSLSetNameValue(papszMetadata, "TIFFTAG_XRESOLUTION",
486 16 : osFormatter.Printf("%g", dfHorzRes));
487 :
488 16 : papszMetadata =
489 16 : CSLSetNameValue(papszMetadata, "TIFFTAG_YRESOLUTION",
490 16 : osFormatter.Printf("%g", dfVertRes));
491 16 : papszMetadata =
492 16 : CSLSetNameValue(papszMetadata, "TIFFTAG_RESOLUTIONUNIT",
493 : "3 (pixels/cm)");
494 :
495 16 : CPLFree(pabyResData);
496 : }
497 : }
498 : }
499 : }
500 :
501 : /* -------------------------------------------------------------------- */
502 : /* Collect IPR box. */
503 : /* -------------------------------------------------------------------- */
504 2483 : else if (EQUAL(oBox.GetType(), "jp2i"))
505 : {
506 5 : if (pszXMLIPR == nullptr)
507 : {
508 5 : pszXMLIPR = reinterpret_cast<char *>(oBox.ReadBoxData());
509 10 : CPLXMLTreeCloser psNode(CPLParseXMLString(pszXMLIPR));
510 5 : if (psNode == nullptr)
511 : {
512 0 : CPLFree(pszXMLIPR);
513 0 : pszXMLIPR = nullptr;
514 : }
515 : }
516 : else
517 : {
518 0 : CPLDebug("GDALJP2", "Too many IPR boxes. Ignoring this one");
519 : }
520 : }
521 :
522 : /* -------------------------------------------------------------------- */
523 : /* Process JUMBF super box */
524 : /* -------------------------------------------------------------------- */
525 2478 : else if (EQUAL(oBox.GetType(), "jumb"))
526 : {
527 80 : GDALJP2Box oSubBox(fpVSIL);
528 :
529 120 : for (oSubBox.ReadFirstChild(&oBox); strlen(oSubBox.GetType()) > 0;
530 80 : oSubBox.ReadNextChild(&oBox))
531 : {
532 80 : ReadBox(fpVSIL, oSubBox, iBox);
533 : }
534 : }
535 4086 : }
536 :
537 : /************************************************************************/
538 : /* ReadBoxes() */
539 : /************************************************************************/
540 :
541 1186 : int GDALJP2Metadata::ReadBoxes(VSILFILE *fpVSIL)
542 :
543 : {
544 2370 : GDALJP2Box oBox(fpVSIL);
545 :
546 1184 : if (!oBox.ReadFirst())
547 296 : return FALSE;
548 :
549 888 : int iBox = 0;
550 4155 : while (strlen(oBox.GetType()) > 0)
551 : {
552 4006 : ReadBox(fpVSIL, oBox, iBox);
553 4006 : if (!oBox.ReadNext())
554 739 : break;
555 : }
556 :
557 888 : return TRUE;
558 : }
559 :
560 : /************************************************************************/
561 : /* ParseJP2GeoTIFF() */
562 : /************************************************************************/
563 :
564 1169 : int GDALJP2Metadata::ParseJP2GeoTIFF()
565 :
566 : {
567 : #ifdef HAVE_TIFF
568 1169 : if (!CPLTestBool(CPLGetConfigOption("GDAL_USE_GEOJP2", "TRUE")))
569 11 : return FALSE;
570 :
571 1159 : bool abValidProjInfo[MAX_JP2GEOTIFF_BOXES] = {false};
572 1159 : OGRSpatialReferenceH ahSRS[MAX_JP2GEOTIFF_BOXES] = {nullptr};
573 2318 : std::array<GDALGeoTransform, MAX_JP2GEOTIFF_BOXES> aGT{};
574 1159 : int anGCPCount[MAX_JP2GEOTIFF_BOXES] = {0};
575 1159 : GDAL_GCP *apasGCPList[MAX_JP2GEOTIFF_BOXES] = {nullptr};
576 1159 : int abPixelIsPoint[MAX_JP2GEOTIFF_BOXES] = {0};
577 1159 : char **apapszRPCMD[MAX_JP2GEOTIFF_BOXES] = {nullptr};
578 :
579 1159 : const int nMax = std::min(nGeoTIFFBoxesCount, MAX_JP2GEOTIFF_BOXES);
580 1739 : for (int i = 0; i < nMax; ++i)
581 : {
582 : /* --------------------------------------------------------------------
583 : */
584 : /* Convert raw data into projection and geotransform. */
585 : /* --------------------------------------------------------------------
586 : */
587 580 : if (GTIFWktFromMemBufEx(pasGeoTIFFBoxes[i].nGeoTIFFSize,
588 580 : pasGeoTIFFBoxes[i].pabyGeoTIFFData, &ahSRS[i],
589 580 : aGT[i].data(), &anGCPCount[i], &apasGCPList[i],
590 580 : &abPixelIsPoint[i], &apapszRPCMD[i]) == CE_None)
591 : {
592 579 : if (ahSRS[i] != nullptr)
593 579 : abValidProjInfo[i] = true;
594 : }
595 : }
596 :
597 : // Detect which box is the better one.
598 1159 : int iBestIndex = -1;
599 1739 : for (int i = 0; i < nMax; ++i)
600 : {
601 580 : if (abValidProjInfo[i] && iBestIndex < 0)
602 : {
603 575 : iBestIndex = i;
604 : }
605 5 : else if (abValidProjInfo[i] && ahSRS[i] != nullptr)
606 : {
607 : // Anything else than a LOCAL_CS will probably be better.
608 4 : if (OSRIsLocal(ahSRS[iBestIndex]))
609 0 : iBestIndex = i;
610 : }
611 : }
612 :
613 1159 : if (iBestIndex < 0)
614 : {
615 585 : for (int i = 0; i < nMax; ++i)
616 : {
617 1 : if (aGT[i] != GDALGeoTransform() || anGCPCount[i] > 0 ||
618 0 : apapszRPCMD[i] != nullptr)
619 : {
620 1 : iBestIndex = i;
621 : }
622 : }
623 : }
624 :
625 1159 : if (iBestIndex >= 0)
626 : {
627 576 : m_oSRS.Clear();
628 576 : if (ahSRS[iBestIndex])
629 575 : m_oSRS = *(OGRSpatialReference::FromHandle(ahSRS[iBestIndex]));
630 576 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
631 576 : m_gt = aGT[iBestIndex];
632 576 : nGCPCount = anGCPCount[iBestIndex];
633 576 : pasGCPList = apasGCPList[iBestIndex];
634 576 : bPixelIsPoint = CPL_TO_BOOL(abPixelIsPoint[iBestIndex]);
635 576 : papszRPCMD = apapszRPCMD[iBestIndex];
636 :
637 638 : if (m_gt[0] != 0 || m_gt[1] != 1 || m_gt[2] != 0 || m_gt[3] != 0 ||
638 638 : m_gt[4] != 0 || m_gt[5] != 1)
639 544 : m_bHaveGeoTransform = true;
640 :
641 576 : if (ahSRS[iBestIndex])
642 : {
643 575 : char *pszWKT = nullptr;
644 575 : m_oSRS.exportToWkt(&pszWKT);
645 575 : CPLDebug("GDALJP2Metadata",
646 : "Got projection from GeoJP2 (geotiff) box (%d): %s",
647 575 : iBestIndex, pszWKT ? pszWKT : "(null)");
648 575 : CPLFree(pszWKT);
649 : }
650 : }
651 :
652 : // Cleanup unused boxes.
653 1739 : for (int i = 0; i < nMax; ++i)
654 : {
655 580 : if (i != iBestIndex)
656 : {
657 4 : if (anGCPCount[i] > 0)
658 : {
659 0 : GDALDeinitGCPs(anGCPCount[i], apasGCPList[i]);
660 0 : CPLFree(apasGCPList[i]);
661 : }
662 4 : CSLDestroy(apapszRPCMD[i]);
663 : }
664 580 : OSRDestroySpatialReference(ahSRS[i]);
665 : }
666 :
667 1159 : return iBestIndex >= 0;
668 : #else
669 : return false;
670 : #endif
671 : }
672 :
673 : /************************************************************************/
674 : /* ParseMSIG() */
675 : /************************************************************************/
676 :
677 561 : int GDALJP2Metadata::ParseMSIG()
678 :
679 : {
680 561 : if (nMSIGSize < 70)
681 561 : return FALSE;
682 :
683 : double adfGeoTransform[6];
684 :
685 : /* -------------------------------------------------------------------- */
686 : /* Try and extract worldfile parameters and adjust. */
687 : /* -------------------------------------------------------------------- */
688 0 : memcpy(adfGeoTransform + 0, pabyMSIGData + 22 + 8 * 4, 8);
689 0 : memcpy(adfGeoTransform + 1, pabyMSIGData + 22 + 8 * 0, 8);
690 0 : memcpy(adfGeoTransform + 2, pabyMSIGData + 22 + 8 * 2, 8);
691 0 : memcpy(adfGeoTransform + 3, pabyMSIGData + 22 + 8 * 5, 8);
692 0 : memcpy(adfGeoTransform + 4, pabyMSIGData + 22 + 8 * 1, 8);
693 0 : memcpy(adfGeoTransform + 5, pabyMSIGData + 22 + 8 * 3, 8);
694 :
695 : // data is in LSB (little endian) order in file.
696 0 : CPL_LSBPTR64(adfGeoTransform + 0);
697 0 : CPL_LSBPTR64(adfGeoTransform + 1);
698 0 : CPL_LSBPTR64(adfGeoTransform + 2);
699 0 : CPL_LSBPTR64(adfGeoTransform + 3);
700 0 : CPL_LSBPTR64(adfGeoTransform + 4);
701 0 : CPL_LSBPTR64(adfGeoTransform + 5);
702 :
703 : // correct for center of pixel vs. top left of pixel
704 0 : adfGeoTransform[0] -= 0.5 * adfGeoTransform[1];
705 0 : adfGeoTransform[0] -= 0.5 * adfGeoTransform[2];
706 0 : adfGeoTransform[3] -= 0.5 * adfGeoTransform[4];
707 0 : adfGeoTransform[3] -= 0.5 * adfGeoTransform[5];
708 :
709 0 : m_gt = GDALGeoTransform(adfGeoTransform);
710 0 : m_bHaveGeoTransform = true;
711 :
712 0 : return TRUE;
713 : }
714 :
715 : /************************************************************************/
716 : /* GetDictionaryItem() */
717 : /************************************************************************/
718 :
719 1 : static CPLXMLNode *GetDictionaryItem(char **papszGMLMetadata,
720 : const char *pszURN)
721 :
722 : {
723 1 : char *pszLabel = nullptr;
724 :
725 1 : if (STARTS_WITH_CI(pszURN, "urn:jp2k:xml:"))
726 0 : pszLabel = CPLStrdup(pszURN + 13);
727 1 : else if (STARTS_WITH_CI(pszURN, "urn:ogc:tc:gmljp2:xml:"))
728 0 : pszLabel = CPLStrdup(pszURN + 22);
729 1 : else if (STARTS_WITH_CI(pszURN, "gmljp2://xml/"))
730 1 : pszLabel = CPLStrdup(pszURN + 13);
731 : else
732 0 : pszLabel = CPLStrdup(pszURN);
733 :
734 : /* -------------------------------------------------------------------- */
735 : /* Split out label and fragment id. */
736 : /* -------------------------------------------------------------------- */
737 1 : const char *pszFragmentId = nullptr;
738 :
739 : {
740 1 : int i = 0; // Used after for.
741 18 : for (; pszLabel[i] != '#'; ++i)
742 : {
743 17 : if (pszLabel[i] == '\0')
744 : {
745 0 : CPLFree(pszLabel);
746 0 : return nullptr;
747 : }
748 : }
749 :
750 1 : pszFragmentId = pszLabel + i + 1;
751 1 : pszLabel[i] = '\0';
752 : }
753 :
754 : /* -------------------------------------------------------------------- */
755 : /* Can we find an XML box with the desired label? */
756 : /* -------------------------------------------------------------------- */
757 1 : const char *pszDictionary = CSLFetchNameValue(papszGMLMetadata, pszLabel);
758 :
759 1 : if (pszDictionary == nullptr)
760 : {
761 0 : CPLFree(pszLabel);
762 0 : return nullptr;
763 : }
764 :
765 : /* -------------------------------------------------------------------- */
766 : /* Try and parse the dictionary. */
767 : /* -------------------------------------------------------------------- */
768 2 : CPLXMLTreeCloser psDictTree(CPLParseXMLString(pszDictionary));
769 :
770 1 : if (psDictTree == nullptr)
771 : {
772 0 : CPLFree(pszLabel);
773 0 : return nullptr;
774 : }
775 :
776 1 : CPLStripXMLNamespace(psDictTree.get(), nullptr, TRUE);
777 :
778 1 : CPLXMLNode *psDictRoot = CPLSearchXMLNode(psDictTree.get(), "=Dictionary");
779 :
780 1 : if (psDictRoot == nullptr)
781 : {
782 0 : CPLFree(pszLabel);
783 0 : return nullptr;
784 : }
785 :
786 : /* -------------------------------------------------------------------- */
787 : /* Search for matching id. */
788 : /* -------------------------------------------------------------------- */
789 1 : CPLXMLNode *psEntry, *psHit = nullptr;
790 9 : for (psEntry = psDictRoot->psChild; psEntry != nullptr && psHit == nullptr;
791 8 : psEntry = psEntry->psNext)
792 : {
793 : const char *pszId;
794 :
795 8 : if (psEntry->eType != CXT_Element)
796 5 : continue;
797 :
798 3 : if (!EQUAL(psEntry->pszValue, "dictionaryEntry"))
799 2 : continue;
800 :
801 1 : if (psEntry->psChild == nullptr)
802 0 : continue;
803 :
804 1 : pszId = CPLGetXMLValue(psEntry->psChild, "id", "");
805 :
806 1 : if (EQUAL(pszId, pszFragmentId))
807 0 : psHit = CPLCloneXMLTree(psEntry->psChild);
808 : }
809 :
810 : /* -------------------------------------------------------------------- */
811 : /* Cleanup */
812 : /* -------------------------------------------------------------------- */
813 1 : CPLFree(pszLabel);
814 :
815 1 : return psHit;
816 : }
817 :
818 : /************************************************************************/
819 : /* GMLSRSLookup() */
820 : /* */
821 : /* Lookup an SRS in a dictionary inside this file. We will get */
822 : /* something like: */
823 : /* urn:jp2k:xml:CRSDictionary.xml#crs1112 */
824 : /* */
825 : /* We need to split the filename from the fragment id, and */
826 : /* lookup the fragment in the file if we can find it our */
827 : /* list of labelled xml boxes. */
828 : /************************************************************************/
829 :
830 1 : int GDALJP2Metadata::GMLSRSLookup(const char *pszURN)
831 :
832 : {
833 2 : CPLXMLTreeCloser psDictEntry(GetDictionaryItem(papszGMLMetadata, pszURN));
834 :
835 1 : if (psDictEntry == nullptr)
836 1 : return FALSE;
837 :
838 : /* -------------------------------------------------------------------- */
839 : /* Reserialize this fragment. */
840 : /* -------------------------------------------------------------------- */
841 0 : char *pszDictEntryXML = CPLSerializeXMLTree(psDictEntry.get());
842 0 : psDictEntry.reset();
843 :
844 : /* -------------------------------------------------------------------- */
845 : /* Try to convert into an OGRSpatialReference. */
846 : /* -------------------------------------------------------------------- */
847 0 : OGRSpatialReference oSRS;
848 0 : bool bSuccess = false;
849 :
850 0 : if (oSRS.importFromXML(pszDictEntryXML) == OGRERR_NONE)
851 : {
852 0 : m_oSRS = std::move(oSRS);
853 0 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
854 0 : bSuccess = true;
855 : }
856 :
857 0 : CPLFree(pszDictEntryXML);
858 :
859 0 : return bSuccess;
860 : }
861 :
862 : /************************************************************************/
863 : /* ParseGMLCoverageDesc() */
864 : /************************************************************************/
865 :
866 595 : int GDALJP2Metadata::ParseGMLCoverageDesc()
867 :
868 : {
869 595 : if (!CPLTestBool(CPLGetConfigOption("GDAL_USE_GMLJP2", "TRUE")))
870 0 : return FALSE;
871 :
872 : /* -------------------------------------------------------------------- */
873 : /* Do we have an XML doc that is apparently a coverage */
874 : /* description? */
875 : /* -------------------------------------------------------------------- */
876 : const char *pszCoverage =
877 597 : CSLFetchNameValue(papszGMLMetadata, "gml.root-instance");
878 :
879 597 : if (pszCoverage == nullptr)
880 557 : return FALSE;
881 :
882 40 : CPLDebug("GDALJP2Metadata", "Found GML Box:\n%s", pszCoverage);
883 :
884 : /* -------------------------------------------------------------------- */
885 : /* Try parsing the XML. Wipe any namespace prefixes. */
886 : /* -------------------------------------------------------------------- */
887 80 : CPLXMLTreeCloser psXML(CPLParseXMLString(pszCoverage));
888 :
889 40 : if (psXML == nullptr)
890 0 : return FALSE;
891 :
892 40 : CPLStripXMLNamespace(psXML.get(), nullptr, TRUE);
893 :
894 : /* -------------------------------------------------------------------- */
895 : /* Isolate RectifiedGrid. Eventually we will need to support */
896 : /* other georeferencing objects. */
897 : /* -------------------------------------------------------------------- */
898 40 : CPLXMLNode *psRG = CPLSearchXMLNode(psXML.get(), "=RectifiedGrid");
899 40 : CPLXMLNode *psOriginPoint = nullptr;
900 40 : const char *pszOffset1 = nullptr;
901 40 : const char *pszOffset2 = nullptr;
902 :
903 40 : if (psRG != nullptr)
904 : {
905 40 : psOriginPoint = CPLGetXMLNode(psRG, "origin.Point");
906 :
907 40 : CPLXMLNode *psOffset1 = CPLGetXMLNode(psRG, "offsetVector");
908 40 : if (psOffset1 != nullptr)
909 : {
910 40 : pszOffset1 = CPLGetXMLValue(psOffset1, "", nullptr);
911 : pszOffset2 =
912 40 : CPLGetXMLValue(psOffset1->psNext, "=offsetVector", nullptr);
913 : }
914 : }
915 :
916 : /* -------------------------------------------------------------------- */
917 : /* If we are missing any of the origin or 2 offsets then give up. */
918 : /* -------------------------------------------------------------------- */
919 40 : if (psOriginPoint == nullptr || pszOffset1 == nullptr ||
920 : pszOffset2 == nullptr)
921 : {
922 0 : return FALSE;
923 : }
924 :
925 : /* -------------------------------------------------------------------- */
926 : /* Extract origin location. */
927 : /* -------------------------------------------------------------------- */
928 40 : OGRPoint *poOriginGeometry = nullptr;
929 :
930 : auto poGeom = std::unique_ptr<OGRGeometry>(
931 40 : OGRGeometry::FromHandle(OGR_G_CreateFromGMLTree(psOriginPoint)));
932 :
933 40 : if (poGeom != nullptr && wkbFlatten(poGeom->getGeometryType()) == wkbPoint)
934 : {
935 40 : poOriginGeometry = poGeom->toPoint();
936 : }
937 :
938 : // SRS?
939 40 : const char *pszSRSName = CPLGetXMLValue(psOriginPoint, "srsName", nullptr);
940 :
941 : /* -------------------------------------------------------------------- */
942 : /* Extract offset(s) */
943 : /* -------------------------------------------------------------------- */
944 40 : bool bSuccess = false;
945 :
946 : char **papszOffset1Tokens =
947 40 : CSLTokenizeStringComplex(pszOffset1, " ,", FALSE, FALSE);
948 : char **papszOffset2Tokens =
949 40 : CSLTokenizeStringComplex(pszOffset2, " ,", FALSE, FALSE);
950 :
951 40 : if (CSLCount(papszOffset1Tokens) >= 2 &&
952 40 : CSLCount(papszOffset2Tokens) >= 2 && poOriginGeometry != nullptr)
953 : {
954 40 : m_gt[0] = poOriginGeometry->getX();
955 40 : m_gt[1] = CPLAtof(papszOffset1Tokens[0]);
956 40 : m_gt[2] = CPLAtof(papszOffset2Tokens[0]);
957 40 : m_gt[3] = poOriginGeometry->getY();
958 40 : m_gt[4] = CPLAtof(papszOffset1Tokens[1]);
959 40 : m_gt[5] = CPLAtof(papszOffset2Tokens[1]);
960 :
961 : // offset from center of pixel.
962 40 : m_gt[0] -= m_gt[1] * 0.5;
963 40 : m_gt[0] -= m_gt[2] * 0.5;
964 40 : m_gt[3] -= m_gt[4] * 0.5;
965 40 : m_gt[3] -= m_gt[5] * 0.5;
966 :
967 40 : bSuccess = true;
968 40 : m_bHaveGeoTransform = true;
969 : }
970 :
971 40 : CSLDestroy(papszOffset1Tokens);
972 40 : CSLDestroy(papszOffset2Tokens);
973 :
974 : /* -------------------------------------------------------------------- */
975 : /* If we still don't have an srsName, check for it on the */
976 : /* boundedBy Envelope. Some products */
977 : /* (i.e. EuropeRasterTile23.jpx) use this as the only srsName */
978 : /* delivery vehicle. */
979 : /* -------------------------------------------------------------------- */
980 40 : if (pszSRSName == nullptr)
981 : {
982 6 : pszSRSName = CPLGetXMLValue(
983 6 : psXML.get(), "=FeatureCollection.boundedBy.Envelope.srsName",
984 : nullptr);
985 : }
986 : /* -------------------------------------------------------------------- */
987 : /* Examples of DGIWG_Profile_of_JPEG2000_for_Georeference_Imagery.pdf
988 : */
989 : /* have srsName only on RectifiedGrid element. */
990 : /* -------------------------------------------------------------------- */
991 40 : if (psRG != nullptr && pszSRSName == nullptr)
992 : {
993 2 : pszSRSName = CPLGetXMLValue(psRG, "srsName", nullptr);
994 : }
995 :
996 : /* -------------------------------------------------------------------- */
997 : /* If we have gotten a geotransform, then try to interpret the */
998 : /* srsName. */
999 : /* -------------------------------------------------------------------- */
1000 40 : bool bNeedAxisFlip = false;
1001 :
1002 40 : if (bSuccess && pszSRSName != nullptr && m_oSRS.IsEmpty())
1003 : {
1004 80 : OGRSpatialReference oSRS;
1005 40 : if (STARTS_WITH_CI(pszSRSName, "epsg:"))
1006 : {
1007 0 : if (oSRS.SetFromUserInput(pszSRSName) == OGRERR_NONE)
1008 0 : m_oSRS = std::move(oSRS);
1009 : }
1010 115 : else if ((STARTS_WITH_CI(pszSRSName, "urn:") &&
1011 35 : strstr(pszSRSName, ":def:") != nullptr &&
1012 80 : oSRS.importFromURN(pszSRSName) == OGRERR_NONE) ||
1013 : /* GMLJP2 v2.0 uses CRS URL instead of URN */
1014 : /* See e.g.
1015 : http://schemas.opengis.net/gmljp2/2.0/examples/minimalInstance.xml
1016 : */
1017 5 : (STARTS_WITH_CI(pszSRSName,
1018 4 : "http://www.opengis.net/def/crs/") &&
1019 4 : oSRS.importFromCRSURL(pszSRSName) == OGRERR_NONE))
1020 : {
1021 39 : m_oSRS = std::move(oSRS);
1022 :
1023 : // Per #2131
1024 69 : if (m_oSRS.EPSGTreatsAsLatLong() ||
1025 30 : m_oSRS.EPSGTreatsAsNorthingEasting())
1026 : {
1027 10 : CPLDebug("GMLJP2", "Request axis flip for SRS=%s", pszSRSName);
1028 10 : bNeedAxisFlip = true;
1029 : }
1030 : }
1031 1 : else if (!GMLSRSLookup(pszSRSName))
1032 : {
1033 1 : CPLDebug("GDALJP2Metadata", "Unable to evaluate SRSName=%s",
1034 : pszSRSName);
1035 : }
1036 : }
1037 :
1038 40 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1039 40 : if (!m_oSRS.IsEmpty())
1040 : {
1041 39 : char *pszWKT = nullptr;
1042 39 : m_oSRS.exportToWkt(&pszWKT);
1043 39 : CPLDebug("GDALJP2Metadata", "Got projection from GML box: %s",
1044 39 : pszWKT ? pszWKT : "");
1045 39 : CPLFree(pszWKT);
1046 : }
1047 :
1048 : /* -------------------------------------------------------------------- */
1049 : /* Do we need to flip the axes? */
1050 : /* -------------------------------------------------------------------- */
1051 40 : if (bNeedAxisFlip && CPLTestBool(CPLGetConfigOption(
1052 : "GDAL_IGNORE_AXIS_ORIENTATION", "FALSE")))
1053 : {
1054 1 : bNeedAxisFlip = false;
1055 1 : CPLDebug(
1056 : "GMLJP2",
1057 : "Suppressed axis flipping based on GDAL_IGNORE_AXIS_ORIENTATION.");
1058 : }
1059 :
1060 : /* Some Pleiades files have explicit <gml:axisName>Easting</gml:axisName> */
1061 : /* <gml:axisName>Northing</gml:axisName> to override default EPSG order */
1062 40 : if (bNeedAxisFlip && psRG != nullptr)
1063 : {
1064 9 : int nAxisCount = 0;
1065 9 : bool bFirstAxisIsEastOrLong = false;
1066 9 : bool bSecondAxisIsNorthOrLat = false;
1067 82 : for (CPLXMLNode *psIter = psRG->psChild; psIter != nullptr;
1068 73 : psIter = psIter->psNext)
1069 : {
1070 73 : if (psIter->eType == CXT_Element &&
1071 52 : strcmp(psIter->pszValue, "axisName") == 0 &&
1072 14 : psIter->psChild != nullptr &&
1073 14 : psIter->psChild->eType == CXT_Text)
1074 : {
1075 14 : if (nAxisCount == 0 &&
1076 7 : (STARTS_WITH_CI(psIter->psChild->pszValue, "EAST") ||
1077 6 : STARTS_WITH_CI(psIter->psChild->pszValue, "LONG")))
1078 : {
1079 1 : bFirstAxisIsEastOrLong = true;
1080 : }
1081 13 : else if (nAxisCount == 1 &&
1082 7 : (STARTS_WITH_CI(psIter->psChild->pszValue, "NORTH") ||
1083 6 : STARTS_WITH_CI(psIter->psChild->pszValue, "LAT")))
1084 : {
1085 1 : bSecondAxisIsNorthOrLat = true;
1086 : }
1087 14 : ++nAxisCount;
1088 : }
1089 : }
1090 9 : if (bFirstAxisIsEastOrLong && bSecondAxisIsNorthOrLat)
1091 : {
1092 1 : CPLDebug(
1093 : "GMLJP2",
1094 : "Disable axis flip because of explicit axisName disabling it");
1095 1 : bNeedAxisFlip = false;
1096 : }
1097 : }
1098 :
1099 40 : psXML.reset();
1100 40 : psRG = nullptr;
1101 :
1102 40 : if (bNeedAxisFlip)
1103 : {
1104 8 : CPLDebug("GMLJP2",
1105 : "Flipping axis orientation in GMLJP2 coverage description.");
1106 :
1107 8 : std::swap(m_gt[0], m_gt[3]);
1108 :
1109 8 : int swapWith1Index = 4;
1110 8 : int swapWith2Index = 5;
1111 :
1112 : /* Look if we have GDAL_JP2K_ALT_OFFSETVECTOR_ORDER=TRUE as a XML
1113 : * comment */
1114 8 : int bHasAltOffsetVectorOrderComment =
1115 8 : strstr(pszCoverage, "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER=TRUE") !=
1116 : nullptr;
1117 :
1118 16 : if (bHasAltOffsetVectorOrderComment ||
1119 8 : CPLTestBool(CPLGetConfigOption("GDAL_JP2K_ALT_OFFSETVECTOR_ORDER",
1120 : "FALSE")))
1121 : {
1122 0 : swapWith1Index = 5;
1123 0 : swapWith2Index = 4;
1124 0 : CPLDebug("GMLJP2",
1125 : "Choosing alternate GML \"<offsetVector>\" order based on "
1126 : "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER.");
1127 : }
1128 :
1129 8 : std::swap(m_gt[1], m_gt[swapWith1Index]);
1130 8 : std::swap(m_gt[2], m_gt[swapWith2Index]);
1131 :
1132 : /* Found in autotest/gdrivers/data/ll.jp2 */
1133 8 : if (m_gt[1] == 0.0 && m_gt[2] < 0.0 && m_gt[4] > 0.0 && m_gt[5] == 0.0)
1134 : {
1135 3 : CPLError(
1136 : CE_Warning, CPLE_AppDefined,
1137 : "It is likely that the axis order of the GMLJP2 box is not "
1138 : "consistent with the EPSG order and that the resulting "
1139 : "georeferencing "
1140 : "will be incorrect. Try setting "
1141 : "GDAL_IGNORE_AXIS_ORIENTATION=TRUE if it is the case");
1142 : }
1143 : }
1144 :
1145 40 : return !m_oSRS.IsEmpty() && bSuccess;
1146 : }
1147 :
1148 : /************************************************************************/
1149 : /* SetSpatialRef() */
1150 : /************************************************************************/
1151 :
1152 138 : void GDALJP2Metadata::SetSpatialRef(const OGRSpatialReference *poSRS)
1153 :
1154 : {
1155 138 : m_oSRS.Clear();
1156 138 : if (poSRS)
1157 126 : m_oSRS = *poSRS;
1158 138 : }
1159 :
1160 : /************************************************************************/
1161 : /* SetGCPs() */
1162 : /************************************************************************/
1163 :
1164 41 : void GDALJP2Metadata::SetGCPs(int nCount, const GDAL_GCP *pasGCPsIn)
1165 :
1166 : {
1167 41 : if (nGCPCount > 0)
1168 : {
1169 0 : GDALDeinitGCPs(nGCPCount, pasGCPList);
1170 0 : CPLFree(pasGCPList);
1171 : }
1172 :
1173 41 : nGCPCount = nCount;
1174 41 : pasGCPList = GDALDuplicateGCPs(nGCPCount, pasGCPsIn);
1175 41 : }
1176 :
1177 : /************************************************************************/
1178 : /* SetGeoTransform() */
1179 : /************************************************************************/
1180 :
1181 241 : void GDALJP2Metadata::SetGeoTransform(const GDALGeoTransform >)
1182 :
1183 : {
1184 241 : m_bHaveGeoTransform = true;
1185 241 : m_gt = gt;
1186 241 : }
1187 :
1188 : /************************************************************************/
1189 : /* SetRPCMD() */
1190 : /************************************************************************/
1191 :
1192 38 : void GDALJP2Metadata::SetRPCMD(char **papszRPCMDIn)
1193 :
1194 : {
1195 38 : CSLDestroy(papszRPCMD);
1196 38 : papszRPCMD = CSLDuplicate(papszRPCMDIn);
1197 38 : }
1198 :
1199 : /************************************************************************/
1200 : /* CreateJP2GeoTIFF() */
1201 : /************************************************************************/
1202 :
1203 225 : GDALJP2Box *GDALJP2Metadata::CreateJP2GeoTIFF()
1204 :
1205 : {
1206 : #ifdef HAVE_TIFF
1207 : /* -------------------------------------------------------------------- */
1208 : /* Prepare the memory buffer containing the degenerate GeoTIFF */
1209 : /* file. */
1210 : /* -------------------------------------------------------------------- */
1211 225 : int nGTBufSize = 0;
1212 225 : unsigned char *pabyGTBuf = nullptr;
1213 :
1214 225 : if (GTIFMemBufFromSRS(OGRSpatialReference::ToHandle(&m_oSRS), m_gt.data(),
1215 225 : nGCPCount, pasGCPList, &nGTBufSize, &pabyGTBuf,
1216 450 : bPixelIsPoint, papszRPCMD) != CE_None)
1217 0 : return nullptr;
1218 :
1219 225 : if (nGTBufSize == 0)
1220 0 : return nullptr;
1221 :
1222 : /* -------------------------------------------------------------------- */
1223 : /* Write to a box on the JP2 file. */
1224 : /* -------------------------------------------------------------------- */
1225 : GDALJP2Box *poBox;
1226 :
1227 225 : poBox = GDALJP2Box::CreateUUIDBox(msi_uuid2, nGTBufSize, pabyGTBuf);
1228 :
1229 225 : CPLFree(pabyGTBuf);
1230 :
1231 225 : return poBox;
1232 : #else
1233 : return nullptr;
1234 : #endif
1235 : }
1236 :
1237 : /************************************************************************/
1238 : /* IsSRSCompatible() */
1239 : /************************************************************************/
1240 :
1241 : /* Returns true if the SRS can be references through a EPSG code, or encoded
1242 : * as a GML SRS
1243 : */
1244 76 : bool GDALJP2Metadata::IsSRSCompatible(const OGRSpatialReference *poSRS)
1245 : {
1246 76 : const char *pszAuthName = poSRS->GetAuthorityName(nullptr);
1247 76 : const char *pszAuthCode = poSRS->GetAuthorityCode(nullptr);
1248 :
1249 76 : if (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "epsg"))
1250 : {
1251 61 : if (atoi(pszAuthCode))
1252 61 : return true;
1253 : }
1254 :
1255 15 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
1256 15 : char *pszGMLDef = nullptr;
1257 15 : const bool bRet = (poSRS->exportToXML(&pszGMLDef, nullptr) == OGRERR_NONE);
1258 15 : CPLFree(pszGMLDef);
1259 15 : return bRet;
1260 : }
1261 :
1262 : /************************************************************************/
1263 : /* GetGMLJP2GeoreferencingInfo() */
1264 : /************************************************************************/
1265 :
1266 79 : void GDALJP2Metadata::GetGMLJP2GeoreferencingInfo(
1267 : int &nEPSGCode, double adfOrigin[2], double adfXVector[2],
1268 : double adfYVector[2], const char *&pszComment, CPLString &osDictBox,
1269 : bool &bNeedAxisFlip)
1270 : {
1271 :
1272 : /* -------------------------------------------------------------------- */
1273 : /* Try do determine a PCS or GCS code we can use. */
1274 : /* -------------------------------------------------------------------- */
1275 79 : nEPSGCode = 0;
1276 79 : bNeedAxisFlip = false;
1277 158 : OGRSpatialReference oSRS(m_oSRS);
1278 :
1279 79 : const char *pszAuthName = oSRS.GetAuthorityName(nullptr);
1280 79 : const char *pszAuthCode = oSRS.GetAuthorityCode(nullptr);
1281 :
1282 79 : if (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "epsg"))
1283 : {
1284 56 : nEPSGCode = atoi(pszAuthCode);
1285 : }
1286 :
1287 : {
1288 158 : CPLErrorStateBackuper oErrorStateBackuper;
1289 : // Determine if we need to flip axis. Reimport from EPSG and make
1290 : // sure not to strip axis definitions to determine the axis order.
1291 79 : if (nEPSGCode != 0 && oSRS.importFromEPSG(nEPSGCode) == OGRERR_NONE)
1292 : {
1293 106 : if (oSRS.EPSGTreatsAsLatLong() ||
1294 50 : oSRS.EPSGTreatsAsNorthingEasting())
1295 : {
1296 6 : bNeedAxisFlip = true;
1297 : }
1298 : }
1299 : }
1300 :
1301 : /* -------------------------------------------------------------------- */
1302 : /* Prepare coverage origin and offset vectors. Take axis */
1303 : /* order into account if needed. */
1304 : /* -------------------------------------------------------------------- */
1305 79 : adfOrigin[0] = m_gt[0] + m_gt[1] * 0.5 + m_gt[4] * 0.5;
1306 79 : adfOrigin[1] = m_gt[3] + m_gt[2] * 0.5 + m_gt[5] * 0.5;
1307 79 : adfXVector[0] = m_gt[1];
1308 79 : adfXVector[1] = m_gt[2];
1309 :
1310 79 : adfYVector[0] = m_gt[4];
1311 79 : adfYVector[1] = m_gt[5];
1312 :
1313 79 : if (bNeedAxisFlip && CPLTestBool(CPLGetConfigOption(
1314 : "GDAL_IGNORE_AXIS_ORIENTATION", "FALSE")))
1315 : {
1316 0 : bNeedAxisFlip = false;
1317 0 : CPLDebug("GMLJP2", "Suppressed axis flipping on write based on "
1318 : "GDAL_IGNORE_AXIS_ORIENTATION.");
1319 : }
1320 :
1321 79 : pszComment = "";
1322 79 : if (bNeedAxisFlip)
1323 : {
1324 6 : CPLDebug("GMLJP2", "Flipping GML coverage axis order.");
1325 :
1326 6 : std::swap(adfOrigin[0], adfOrigin[1]);
1327 :
1328 6 : if (CPLTestBool(CPLGetConfigOption("GDAL_JP2K_ALT_OFFSETVECTOR_ORDER",
1329 : "FALSE")))
1330 : {
1331 0 : CPLDebug("GMLJP2",
1332 : "Choosing alternate GML \"<offsetVector>\" order based on "
1333 : "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER.");
1334 :
1335 : /* In this case the swapping is done in an "X" pattern */
1336 0 : std::swap(adfXVector[0], adfYVector[1]);
1337 0 : std::swap(adfYVector[0], adfXVector[1]);
1338 :
1339 : /* We add this as an XML comment so that we know we must do
1340 : * OffsetVector flipping on reading */
1341 0 : pszComment =
1342 : " <!-- GDAL_JP2K_ALT_OFFSETVECTOR_ORDER=TRUE: "
1343 : "First "
1344 : "value of offset is latitude/northing component of the "
1345 : "latitude/northing axis. -->\n";
1346 : }
1347 : else
1348 : {
1349 6 : std::swap(adfXVector[0], adfXVector[1]);
1350 6 : std::swap(adfYVector[0], adfYVector[1]);
1351 : }
1352 : }
1353 :
1354 : /* -------------------------------------------------------------------- */
1355 : /* If we need a user defined CRSDictionary entry, prepare it */
1356 : /* here. */
1357 : /* -------------------------------------------------------------------- */
1358 79 : if (nEPSGCode == 0)
1359 : {
1360 23 : char *pszGMLDef = nullptr;
1361 :
1362 46 : CPLErrorStateBackuper oErrorStateBackuper;
1363 23 : if (oSRS.exportToXML(&pszGMLDef, nullptr) == OGRERR_NONE)
1364 : {
1365 13 : char *pszWKT = nullptr;
1366 13 : oSRS.exportToWkt(&pszWKT);
1367 13 : char *pszXMLEscapedWKT = CPLEscapeString(pszWKT, -1, CPLES_XML);
1368 13 : CPLFree(pszWKT);
1369 13 : osDictBox.Printf(
1370 : "<gml:Dictionary gml:id=\"CRSU1\" \n"
1371 : " xmlns:gml=\"http://www.opengis.net/gml\"\n"
1372 : " xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"
1373 : " "
1374 : "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
1375 : " xsi:schemaLocation=\"http://www.opengis.net/gml "
1376 : "http://schemas.opengis.net/gml/3.1.1/base/gml.xsd\">\n"
1377 : " <gml:description>Dictionary for custom SRS "
1378 : "%s</gml:description>\n"
1379 : " <gml:name>Dictionary for custom SRS</gml:name>\n"
1380 : " <gml:dictionaryEntry>\n"
1381 : "%s\n"
1382 : " </gml:dictionaryEntry>\n"
1383 : "</gml:Dictionary>\n",
1384 13 : pszXMLEscapedWKT, pszGMLDef);
1385 13 : CPLFree(pszXMLEscapedWKT);
1386 : }
1387 23 : CPLFree(pszGMLDef);
1388 : }
1389 79 : }
1390 :
1391 : /************************************************************************/
1392 : /* CreateGMLJP2() */
1393 : /************************************************************************/
1394 :
1395 62 : GDALJP2Box *GDALJP2Metadata::CreateGMLJP2(int nXSize, int nYSize)
1396 :
1397 : {
1398 : /* -------------------------------------------------------------------- */
1399 : /* This is a backdoor to let us embed a literal gmljp2 chunk */
1400 : /* supplied by the user as an external file. This is mostly */
1401 : /* for preparing test files with exotic contents. */
1402 : /* -------------------------------------------------------------------- */
1403 62 : if (CPLGetConfigOption("GMLJP2OVERRIDE", nullptr) != nullptr)
1404 : {
1405 7 : VSILFILE *fp = VSIFOpenL(CPLGetConfigOption("GMLJP2OVERRIDE", ""), "r");
1406 7 : char *pszGML = nullptr;
1407 :
1408 7 : if (fp == nullptr)
1409 : {
1410 0 : CPLError(CE_Failure, CPLE_AppDefined,
1411 : "Unable to open GMLJP2OVERRIDE file.");
1412 0 : return nullptr;
1413 : }
1414 :
1415 7 : CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 0, SEEK_END));
1416 7 : const int nLength = static_cast<int>(VSIFTellL(fp));
1417 7 : pszGML = static_cast<char *>(CPLCalloc(1, nLength + 1));
1418 7 : CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 0, SEEK_SET));
1419 7 : CPL_IGNORE_RET_VAL(VSIFReadL(pszGML, 1, nLength, fp));
1420 7 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
1421 :
1422 : GDALJP2Box *apoGMLBoxes[2];
1423 :
1424 7 : apoGMLBoxes[0] = GDALJP2Box::CreateLblBox("gml.data");
1425 7 : apoGMLBoxes[1] =
1426 7 : GDALJP2Box::CreateLabelledXMLAssoc("gml.root-instance", pszGML);
1427 :
1428 7 : GDALJP2Box *poGMLData = GDALJP2Box::CreateAsocBox(2, apoGMLBoxes);
1429 :
1430 7 : delete apoGMLBoxes[0];
1431 7 : delete apoGMLBoxes[1];
1432 :
1433 7 : CPLFree(pszGML);
1434 :
1435 7 : return poGMLData;
1436 : }
1437 :
1438 : int nEPSGCode;
1439 : double adfOrigin[2];
1440 : double adfXVector[2];
1441 : double adfYVector[2];
1442 55 : const char *pszComment = "";
1443 110 : CPLString osDictBox;
1444 55 : bool bNeedAxisFlip = false;
1445 55 : GetGMLJP2GeoreferencingInfo(nEPSGCode, adfOrigin, adfXVector, adfYVector,
1446 : pszComment, osDictBox, bNeedAxisFlip);
1447 :
1448 : char szSRSName[100];
1449 55 : if (nEPSGCode != 0)
1450 32 : snprintf(szSRSName, sizeof(szSRSName), "urn:ogc:def:crs:EPSG::%d",
1451 : nEPSGCode);
1452 : else
1453 23 : snprintf(szSRSName, sizeof(szSRSName), "%s",
1454 : "gmljp2://xml/CRSDictionary.gml#ogrcrs1");
1455 :
1456 : // Compute bounding box
1457 55 : double dfX1 = m_gt[0];
1458 55 : double dfX2 = m_gt[0] + nXSize * m_gt[1];
1459 55 : double dfX3 = m_gt[0] + nYSize * m_gt[2];
1460 55 : double dfX4 = m_gt[0] + nXSize * m_gt[1] + nYSize * m_gt[2];
1461 55 : double dfY1 = m_gt[3];
1462 55 : double dfY2 = m_gt[3] + nXSize * m_gt[4];
1463 55 : double dfY3 = m_gt[3] + nYSize * m_gt[5];
1464 55 : double dfY4 = m_gt[3] + nXSize * m_gt[4] + nYSize * m_gt[5];
1465 55 : double dfLCX = std::min(std::min(dfX1, dfX2), std::min(dfX3, dfX4));
1466 55 : double dfLCY = std::min(std::min(dfY1, dfY2), std::min(dfY3, dfY4));
1467 55 : double dfUCX = std::max(std::max(dfX1, dfX2), std::max(dfX3, dfX4));
1468 55 : double dfUCY = std::max(std::max(dfY1, dfY2), std::max(dfY3, dfY4));
1469 55 : if (bNeedAxisFlip)
1470 : {
1471 5 : std::swap(dfLCX, dfLCY);
1472 5 : std::swap(dfUCX, dfUCY);
1473 : }
1474 :
1475 : /* -------------------------------------------------------------------- */
1476 : /* For now we hardcode for a minimal instance format. */
1477 : /* -------------------------------------------------------------------- */
1478 55 : CPLString osDoc;
1479 :
1480 55 : osDoc.Printf(
1481 : "<gml:FeatureCollection\n"
1482 : " xmlns:gml=\"http://www.opengis.net/gml\"\n"
1483 : " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
1484 : " xsi:schemaLocation=\"http://www.opengis.net/gml "
1485 : "http://schemas.opengis.net/gml/3.1.1/profiles/gmlJP2Profile/1.0.0/"
1486 : "gmlJP2Profile.xsd\">\n"
1487 : " <gml:boundedBy>\n"
1488 : " <gml:Envelope srsName=\"%s\">\n"
1489 : " <gml:lowerCorner>%.15g %.15g</gml:lowerCorner>\n"
1490 : " <gml:upperCorner>%.15g %.15g</gml:upperCorner>\n"
1491 : " </gml:Envelope>\n"
1492 : " </gml:boundedBy>\n"
1493 : " <gml:featureMember>\n"
1494 : " <gml:FeatureCollection>\n"
1495 : " <gml:featureMember>\n"
1496 : " <gml:RectifiedGridCoverage dimension=\"2\" "
1497 : "gml:id=\"RGC0001\">\n"
1498 : " <gml:rectifiedGridDomain>\n"
1499 : " <gml:RectifiedGrid dimension=\"2\">\n"
1500 : " <gml:limits>\n"
1501 : " <gml:GridEnvelope>\n"
1502 : " <gml:low>0 0</gml:low>\n"
1503 : " <gml:high>%d %d</gml:high>\n"
1504 : " </gml:GridEnvelope>\n"
1505 : " </gml:limits>\n"
1506 : " <gml:axisName>x</gml:axisName>\n"
1507 : " <gml:axisName>y</gml:axisName>\n"
1508 : " <gml:origin>\n"
1509 : " <gml:Point gml:id=\"P0001\" srsName=\"%s\">\n"
1510 : " <gml:pos>%.15g %.15g</gml:pos>\n"
1511 : " </gml:Point>\n"
1512 : " </gml:origin>\n"
1513 : "%s"
1514 : " <gml:offsetVector srsName=\"%s\">%.15g "
1515 : "%.15g</gml:offsetVector>\n"
1516 : " <gml:offsetVector srsName=\"%s\">%.15g "
1517 : "%.15g</gml:offsetVector>\n"
1518 : " </gml:RectifiedGrid>\n"
1519 : " </gml:rectifiedGridDomain>\n"
1520 : " <gml:rangeSet>\n"
1521 : " <gml:File>\n"
1522 : " <gml:rangeParameters/>\n"
1523 : " <gml:fileName>gmljp2://codestream/0</gml:fileName>\n"
1524 : " <gml:fileStructure>Record "
1525 : "Interleaved</gml:fileStructure>\n"
1526 : " </gml:File>\n"
1527 : " </gml:rangeSet>\n"
1528 : " </gml:RectifiedGridCoverage>\n"
1529 : " </gml:featureMember>\n"
1530 : " </gml:FeatureCollection>\n"
1531 : " </gml:featureMember>\n"
1532 : "</gml:FeatureCollection>\n",
1533 : szSRSName, dfLCX, dfLCY, dfUCX, dfUCY, nXSize - 1, nYSize - 1,
1534 : szSRSName, adfOrigin[0], adfOrigin[1], pszComment, szSRSName,
1535 55 : adfXVector[0], adfXVector[1], szSRSName, adfYVector[0], adfYVector[1]);
1536 :
1537 : /* -------------------------------------------------------------------- */
1538 : /* Setup the gml.data label. */
1539 : /* -------------------------------------------------------------------- */
1540 : GDALJP2Box *apoGMLBoxes[5];
1541 55 : int nGMLBoxes = 0;
1542 :
1543 55 : apoGMLBoxes[nGMLBoxes++] = GDALJP2Box::CreateLblBox("gml.data");
1544 :
1545 : /* -------------------------------------------------------------------- */
1546 : /* Setup gml.root-instance. */
1547 : /* -------------------------------------------------------------------- */
1548 55 : apoGMLBoxes[nGMLBoxes++] =
1549 55 : GDALJP2Box::CreateLabelledXMLAssoc("gml.root-instance", osDoc);
1550 :
1551 : /* -------------------------------------------------------------------- */
1552 : /* Add optional dictionary. */
1553 : /* -------------------------------------------------------------------- */
1554 55 : if (!osDictBox.empty())
1555 13 : apoGMLBoxes[nGMLBoxes++] =
1556 13 : GDALJP2Box::CreateLabelledXMLAssoc("CRSDictionary.gml", osDictBox);
1557 :
1558 : /* -------------------------------------------------------------------- */
1559 : /* Bundle gml.data boxes into an association. */
1560 : /* -------------------------------------------------------------------- */
1561 55 : GDALJP2Box *poGMLData = GDALJP2Box::CreateAsocBox(nGMLBoxes, apoGMLBoxes);
1562 :
1563 : /* -------------------------------------------------------------------- */
1564 : /* Cleanup working boxes. */
1565 : /* -------------------------------------------------------------------- */
1566 178 : while (nGMLBoxes > 0)
1567 123 : delete apoGMLBoxes[--nGMLBoxes];
1568 :
1569 55 : return poGMLData;
1570 : }
1571 :
1572 : /************************************************************************/
1573 : /* GDALGMLJP2GetXMLRoot() */
1574 : /************************************************************************/
1575 :
1576 68 : static CPLXMLNode *GDALGMLJP2GetXMLRoot(CPLXMLNode *psNode)
1577 : {
1578 68 : for (; psNode != nullptr; psNode = psNode->psNext)
1579 : {
1580 66 : if (psNode->eType == CXT_Element && psNode->pszValue[0] != '?')
1581 51 : return psNode;
1582 : }
1583 2 : return nullptr;
1584 : }
1585 :
1586 : /************************************************************************/
1587 : /* GDALGMLJP2PatchFeatureCollectionSubstitutionGroup() */
1588 : /************************************************************************/
1589 :
1590 : static void
1591 8 : GDALGMLJP2PatchFeatureCollectionSubstitutionGroup(CPLXMLNode *psRoot)
1592 : {
1593 : /* GML 3.2 SF profile recommends the feature collection type to derive */
1594 : /* from gml:AbstractGML to prevent it to be included in another feature */
1595 : /* collection, but this is what we want to do. So patch that... */
1596 :
1597 : /* <xs:element name="FeatureCollection" type="ogr:FeatureCollectionType"
1598 : * substitutionGroup="gml:AbstractGML"/> */
1599 : /* --> */
1600 : /* <xs:element name="FeatureCollection" type="ogr:FeatureCollectionType"
1601 : * substitutionGroup="gml:AbstractFeature"/> */
1602 8 : if (psRoot->eType == CXT_Element &&
1603 8 : (strcmp(psRoot->pszValue, "schema") == 0 ||
1604 8 : strcmp(psRoot->pszValue, "xs:schema") == 0))
1605 : {
1606 52 : for (CPLXMLNode *psIter = psRoot->psChild; psIter != nullptr;
1607 48 : psIter = psIter->psNext)
1608 : {
1609 122 : if (psIter->eType == CXT_Element &&
1610 22 : (strcmp(psIter->pszValue, "element") == 0 ||
1611 22 : strcmp(psIter->pszValue, "xs:element") == 0) &&
1612 6 : strcmp(CPLGetXMLValue(psIter, "name", ""),
1613 72 : "FeatureCollection") == 0 &&
1614 4 : strcmp(CPLGetXMLValue(psIter, "substitutionGroup", ""),
1615 : "gml:AbstractGML") == 0)
1616 : {
1617 2 : CPLDebug(
1618 : "GMLJP2",
1619 : R"(Patching substitutionGroup="gml:AbstractGML" to "gml:AbstractFeature")");
1620 2 : CPLSetXMLValue(psIter, "#substitutionGroup",
1621 : "gml:AbstractFeature");
1622 2 : break;
1623 : }
1624 : }
1625 : }
1626 8 : }
1627 :
1628 : /************************************************************************/
1629 : /* CreateGMLJP2V2() */
1630 : /************************************************************************/
1631 :
1632 : class GMLJP2V2GMLFileDesc
1633 : {
1634 : public:
1635 : CPLString osFile{};
1636 : CPLString osRemoteResource{};
1637 : CPLString osNamespace{};
1638 : CPLString osNamespacePrefix{};
1639 : CPLString osSchemaLocation{};
1640 : int bInline = true;
1641 : int bParentCoverageCollection = true;
1642 : };
1643 :
1644 : class GMLJP2V2AnnotationDesc
1645 : {
1646 : public:
1647 : CPLString osFile{};
1648 : };
1649 :
1650 : class GMLJP2V2MetadataDesc
1651 : {
1652 : public:
1653 : CPLString osFile{};
1654 : CPLString osContent{};
1655 : CPLString osTemplateFile{};
1656 : CPLString osSourceFile{};
1657 : int bGDALMetadata = false;
1658 : int bParentCoverageCollection = true;
1659 : };
1660 :
1661 : class GMLJP2V2StyleDesc
1662 : {
1663 : public:
1664 : CPLString osFile{};
1665 : int bParentCoverageCollection = true;
1666 : };
1667 :
1668 : class GMLJP2V2ExtensionDesc
1669 : {
1670 : public:
1671 : CPLString osFile{};
1672 : int bParentCoverageCollection = true;
1673 : };
1674 :
1675 : class GMLJP2V2BoxDesc
1676 : {
1677 : public:
1678 : CPLString osFile{};
1679 : CPLString osLabel{};
1680 : };
1681 :
1682 28 : GDALJP2Box *GDALJP2Metadata::CreateGMLJP2V2(int nXSize, int nYSize,
1683 : const char *pszDefFilename,
1684 : GDALDataset *poSrcDS)
1685 :
1686 : {
1687 56 : CPLString osRootGMLId = "ID_GMLJP2_0";
1688 56 : CPLString osGridCoverage;
1689 56 : CPLString osGridCoverageFile;
1690 56 : CPLString osCoverageRangeTypeXML;
1691 28 : bool bCRSURL = true;
1692 56 : std::vector<GMLJP2V2MetadataDesc> aoMetadata;
1693 56 : std::vector<GMLJP2V2AnnotationDesc> aoAnnotations;
1694 56 : std::vector<GMLJP2V2GMLFileDesc> aoGMLFiles;
1695 56 : std::vector<GMLJP2V2StyleDesc> aoStyles;
1696 56 : std::vector<GMLJP2V2ExtensionDesc> aoExtensions;
1697 56 : std::vector<GMLJP2V2BoxDesc> aoBoxes;
1698 :
1699 : /* -------------------------------------------------------------------- */
1700 : /* Parse definition file. */
1701 : /* -------------------------------------------------------------------- */
1702 28 : if (pszDefFilename && !EQUAL(pszDefFilename, "YES") &&
1703 26 : !EQUAL(pszDefFilename, "TRUE"))
1704 : {
1705 26 : GByte *pabyContent = nullptr;
1706 26 : if (pszDefFilename[0] != '{')
1707 : {
1708 7 : if (!VSIIngestFile(nullptr, pszDefFilename, &pabyContent, nullptr,
1709 : -1))
1710 3 : return nullptr;
1711 : }
1712 :
1713 : /*
1714 : {
1715 : "#doc" : "Unless otherwise specified, all elements are optional",
1716 :
1717 : "#root_instance_doc": "Describe content of the
1718 : GMLJP2CoverageCollection", "root_instance": {
1719 : "#gml_id_doc": "Specify GMLJP2CoverageCollection id here.
1720 : Default is ID_GMLJP2_0", "gml_id": "some_gml_id",
1721 :
1722 : "#grid_coverage_file_doc": [
1723 : "External XML file, whose root might be a
1724 : GMLJP2GridCoverage, ", "GMLJP2RectifiedGridCoverage or a
1725 : GMLJP2ReferenceableGridCoverage", "If not specified, GDAL will
1726 : auto-generate a GMLJP2RectifiedGridCoverage" ], "grid_coverage_file":
1727 : "gmljp2gridcoverage.xml",
1728 :
1729 : "#grid_coverage_range_type_field_predefined_name_doc": [
1730 : "One of Color, Elevation_meter or Panchromatic ",
1731 : "to fill gmlcov:rangeType/swe:DataRecord/swe:field",
1732 : "Only used if grid_coverage_file is not defined.",
1733 : "Exclusive with grid_coverage_range_type_file" ],
1734 : "grid_coverage_range_type_field_predefined_name": "Color",
1735 :
1736 : "#grid_coverage_range_type_file_doc": [
1737 : "File that is XML content to put under
1738 : gml:RectifiedGrid/gmlcov:rangeType", "Only used if grid_coverage_file is
1739 : not defined.", "Exclusive with
1740 : grid_coverage_range_type_field_predefined_name" ],
1741 : "grid_coverage_range_type_file": "grid_coverage_range_type.xml",
1742 :
1743 : "#crs_url_doc": [
1744 : "true for http://www.opengis.net/def/crs/EPSG/0/XXXX CRS
1745 : URL.", "If false, use CRS URN. Default value is true" ], "crs_url":
1746 : true,
1747 :
1748 : "#metadata_doc": [ "An array of metadata items. Can be either
1749 : strings, with ", "a filename or directly inline XML content, or either
1750 : ", "a more complete description." ], "metadata": [
1751 :
1752 : "dcmetadata.xml",
1753 :
1754 : {
1755 : "#file_doc": "Can use relative or absolute paths.
1756 : Exclusive of content, gdal_metadata and generated_metadata.", "file":
1757 : "dcmetadata.xml",
1758 :
1759 : "#gdal_metadata_doc": "Whether to serialize GDAL
1760 : metadata as GDALMultiDomainMetadata", "gdal_metadata": false,
1761 :
1762 : "#dynamic_metadata_doc":
1763 : [ "The metadata file will be generated from a
1764 : template and a source file.", "The template is a valid GMLJP2 metadata
1765 : XML tree with placeholders like",
1766 : "{{{XPATH(some_xpath_expression)}}}",
1767 : "that are evaluated from the source XML file.
1768 : Typical use case", "is to generate a gmljp2:eopMetadata from the XML
1769 : metadata", "provided by the image provider in their own particular
1770 : format." ], "dynamic_metadata" :
1771 : {
1772 : "template": "my_template.xml",
1773 : "source": "my_source.xml"
1774 : },
1775 :
1776 : "#content": "Exclusive of file. Inline XML metadata
1777 : content", "content": "<gmljp2:metadata>Some simple textual
1778 : metadata</gmljp2:metadata>",
1779 :
1780 : "#parent_node": ["Where to put the metadata.",
1781 : "Under CoverageCollection (default) or
1782 : GridCoverage" ], "parent_node": "CoverageCollection"
1783 : }
1784 : ],
1785 :
1786 : "#annotations_doc": [ "An array of filenames, either directly
1787 : KML files", "or other vector files recognized by GDAL that ", "will be
1788 : translated on-the-fly as KML" ], "annotations": [ "my.kml"
1789 : ],
1790 :
1791 : "#gml_filelist_doc" :[
1792 : "An array of GML files. Can be either GML filenames, ",
1793 : "or a more complete description" ],
1794 : "gml_filelist": [
1795 :
1796 : "my.gml",
1797 :
1798 : {
1799 : "#file_doc": "Can use relative or absolute paths.
1800 : Exclusive of remote_resource", "file": "converted/test_0.gml",
1801 :
1802 : "#remote_resource_doc": "URL of a feature collection
1803 : that must be referenced through a xlink:href", "remote_resource":
1804 : "http://svn.osgeo.org/gdal/trunk/autotest/ogr/data/expected_gml_gml32.gml",
1805 :
1806 : "#namespace_doc": ["The namespace in schemaLocation for
1807 : which to substitute", "its original schemaLocation with the one provided
1808 : below.", "Ignored for a remote_resource"], "namespace":
1809 : "http://example.com",
1810 :
1811 : "#schema_location_doc": ["Value of the substituted
1812 : schemaLocation. ", "Typically a schema box label (link)", "Ignored for a
1813 : remote_resource"], "schema_location": "gmljp2://xml/schema_0.xsd",
1814 :
1815 : "#inline_doc": [
1816 : "Whether to inline the content, or put it in a
1817 : separate xml box. Default is true", "Ignored for a remote_resource." ],
1818 : "inline": true,
1819 :
1820 : "#parent_node": ["Where to put the FeatureCollection.",
1821 : "Under CoverageCollection (default) or
1822 : GridCoverage" ], "parent_node": "CoverageCollection"
1823 : }
1824 : ],
1825 :
1826 : "#styles_doc": [ "An array of styles. For example SLD files" ],
1827 : "styles" : [
1828 : {
1829 : "#file_doc": "Can use relative or absolute paths.",
1830 : "file": "my.sld",
1831 :
1832 : "#parent_node": ["Where to put the FeatureCollection.",
1833 : "Under CoverageCollection (default) or
1834 : GridCoverage" ], "parent_node": "CoverageCollection"
1835 : }
1836 : ],
1837 :
1838 : "#extensions_doc": [ "An array of extensions." ],
1839 : "extensions" : [
1840 : {
1841 : "#file_doc": "Can use relative or absolute paths.",
1842 : "file": "my.xml",
1843 :
1844 : "#parent_node": ["Where to put the FeatureCollection.",
1845 : "Under CoverageCollection (default) or
1846 : GridCoverage" ], "parent_node": "CoverageCollection"
1847 : }
1848 : ]
1849 : },
1850 :
1851 : "#boxes_doc": "An array to describe the content of XML asoc boxes",
1852 : "boxes": [
1853 : {
1854 : "#file_doc": "can use relative or absolute paths. Required",
1855 : "file": "converted/test_0.xsd",
1856 :
1857 : "#label_doc": ["the label of the XML box. If not specified,
1858 : will be the ", "filename without the directory part." ], "label":
1859 : "schema_0.xsd"
1860 : }
1861 : ]
1862 : }
1863 : */
1864 :
1865 25 : json_tokener *jstok = json_tokener_new();
1866 25 : json_object *poObj = json_tokener_parse_ex(
1867 : jstok,
1868 : pabyContent ? reinterpret_cast<const char *>(pabyContent)
1869 25 : : pszDefFilename,
1870 : -1);
1871 25 : CPLFree(pabyContent);
1872 25 : if (jstok->err != json_tokener_success)
1873 : {
1874 1 : CPLError(CE_Failure, CPLE_AppDefined,
1875 : "JSON parsing error: %s (at offset %d)",
1876 : json_tokener_error_desc(jstok->err), jstok->char_offset);
1877 1 : json_tokener_free(jstok);
1878 1 : return nullptr;
1879 : }
1880 24 : json_tokener_free(jstok);
1881 :
1882 : json_object *poRootInstance =
1883 24 : CPL_json_object_object_get(poObj, "root_instance");
1884 47 : if (poRootInstance &&
1885 23 : json_object_get_type(poRootInstance) == json_type_object)
1886 : {
1887 : json_object *poGMLId =
1888 23 : CPL_json_object_object_get(poRootInstance, "gml_id");
1889 23 : if (poGMLId && json_object_get_type(poGMLId) == json_type_string)
1890 1 : osRootGMLId = json_object_get_string(poGMLId);
1891 :
1892 23 : json_object *poGridCoverageFile = CPL_json_object_object_get(
1893 : poRootInstance, "grid_coverage_file");
1894 25 : if (poGridCoverageFile &&
1895 2 : json_object_get_type(poGridCoverageFile) == json_type_string)
1896 2 : osGridCoverageFile = json_object_get_string(poGridCoverageFile);
1897 :
1898 23 : json_object *poGCRTFPN = CPL_json_object_object_get(
1899 : poRootInstance,
1900 : "grid_coverage_range_type_field_predefined_name");
1901 27 : if (poGCRTFPN &&
1902 4 : json_object_get_type(poGCRTFPN) == json_type_string)
1903 : {
1904 8 : CPLString osPredefinedName(json_object_get_string(poGCRTFPN));
1905 4 : if (EQUAL(osPredefinedName, "Color"))
1906 : {
1907 : osCoverageRangeTypeXML =
1908 : "<swe:DataRecord>"
1909 : "<swe:field name=\"Color\">"
1910 : "<swe:Quantity "
1911 : "definition=\"http://www.opengis.net/def/ogc-eo/opt/"
1912 : "SpectralMode/Color\">"
1913 : "<swe:description>Color image</swe:description>"
1914 : "<swe:uom code=\"unity\"/>"
1915 : "</swe:Quantity>"
1916 : "</swe:field>"
1917 1 : "</swe:DataRecord>";
1918 : }
1919 3 : else if (EQUAL(osPredefinedName, "Elevation_meter"))
1920 : {
1921 : osCoverageRangeTypeXML =
1922 : "<swe:DataRecord>"
1923 : "<swe:field name=\"Elevation\">"
1924 : "<swe:Quantity "
1925 : "definition=\"http://inspire.ec.europa.eu/enumeration/"
1926 : "ElevationPropertyTypeValue/height\" "
1927 : "referenceFrame=\"http://www.opengis.net/def/crs/EPSG/"
1928 : "0/5714\">"
1929 : "<swe:description>Elevation above sea "
1930 : "level</swe:description>"
1931 : "<swe:uom code=\"m\"/>"
1932 : "</swe:Quantity>"
1933 : "</swe:field>"
1934 1 : "</swe:DataRecord>";
1935 : }
1936 2 : else if (EQUAL(osPredefinedName, "Panchromatic"))
1937 : {
1938 : osCoverageRangeTypeXML =
1939 : "<swe:DataRecord>"
1940 : "<swe:field name=\"Panchromatic\">"
1941 : "<swe:Quantity "
1942 : "definition=\"http://www.opengis.net/def/ogc-eo/opt/"
1943 : "SpectralMode/Panchromatic\">"
1944 : "<swe:description>Panchromatic "
1945 : "Channel</swe:description>"
1946 : "<swe:uom code=\"unity\"/>"
1947 : "</swe:Quantity>"
1948 : "</swe:field>"
1949 1 : "</swe:DataRecord>";
1950 : }
1951 : else
1952 : {
1953 1 : CPLError(CE_Warning, CPLE_AppDefined,
1954 : "Unrecognized value for "
1955 : "grid_coverage_range_type_field_predefined_name");
1956 : }
1957 : }
1958 : else
1959 : {
1960 19 : json_object *poGCRTFile = CPL_json_object_object_get(
1961 : poRootInstance, "grid_coverage_range_type_file");
1962 21 : if (poGCRTFile &&
1963 2 : json_object_get_type(poGCRTFile) == json_type_string)
1964 : {
1965 : CPLXMLTreeCloser psTmp(
1966 4 : CPLParseXMLFile(json_object_get_string(poGCRTFile)));
1967 2 : if (psTmp != nullptr)
1968 : {
1969 : CPLXMLNode *psTmpRoot =
1970 1 : GDALGMLJP2GetXMLRoot(psTmp.get());
1971 1 : if (psTmpRoot)
1972 : {
1973 1 : char *pszTmp = CPLSerializeXMLTree(psTmpRoot);
1974 1 : osCoverageRangeTypeXML = pszTmp;
1975 1 : CPLFree(pszTmp);
1976 : }
1977 : }
1978 : }
1979 : }
1980 :
1981 : json_object *poCRSURL =
1982 23 : CPL_json_object_object_get(poRootInstance, "crs_url");
1983 23 : if (poCRSURL && json_object_get_type(poCRSURL) == json_type_boolean)
1984 1 : bCRSURL = CPL_TO_BOOL(json_object_get_boolean(poCRSURL));
1985 :
1986 : json_object *poMetadatas =
1987 23 : CPL_json_object_object_get(poRootInstance, "metadata");
1988 37 : if (poMetadatas &&
1989 14 : json_object_get_type(poMetadatas) == json_type_array)
1990 : {
1991 14 : auto nLength = json_object_array_length(poMetadatas);
1992 34 : for (decltype(nLength) i = 0; i < nLength; ++i)
1993 : {
1994 : json_object *poMetadata =
1995 20 : json_object_array_get_idx(poMetadatas, i);
1996 40 : if (poMetadata &&
1997 20 : json_object_get_type(poMetadata) == json_type_string)
1998 : {
1999 8 : GMLJP2V2MetadataDesc oDesc;
2000 4 : const char *pszStr = json_object_get_string(poMetadata);
2001 4 : if (pszStr[0] == '<')
2002 2 : oDesc.osContent = pszStr;
2003 : else
2004 2 : oDesc.osFile = pszStr;
2005 4 : aoMetadata.push_back(std::move(oDesc));
2006 : }
2007 16 : else if (poMetadata && json_object_get_type(poMetadata) ==
2008 : json_type_object)
2009 : {
2010 16 : const char *pszFile = nullptr;
2011 : json_object *poFile =
2012 16 : CPL_json_object_object_get(poMetadata, "file");
2013 18 : if (poFile &&
2014 2 : json_object_get_type(poFile) == json_type_string)
2015 2 : pszFile = json_object_get_string(poFile);
2016 :
2017 16 : const char *pszContent = nullptr;
2018 : json_object *poContent =
2019 16 : CPL_json_object_object_get(poMetadata, "content");
2020 18 : if (poContent &&
2021 2 : json_object_get_type(poContent) == json_type_string)
2022 2 : pszContent = json_object_get_string(poContent);
2023 :
2024 16 : const char *pszTemplate = nullptr;
2025 16 : const char *pszSource = nullptr;
2026 : json_object *poDynamicMetadata =
2027 16 : CPL_json_object_object_get(poMetadata,
2028 : "dynamic_metadata");
2029 27 : if (poDynamicMetadata &&
2030 11 : json_object_get_type(poDynamicMetadata) ==
2031 : json_type_object)
2032 : {
2033 : #ifdef HAVE_LIBXML2
2034 11 : if (CPLTestBool(CPLGetConfigOption(
2035 : "GDAL_DEBUG_PROCESS_DYNAMIC_METADATA",
2036 : "YES")))
2037 : {
2038 : json_object *poTemplate =
2039 11 : CPL_json_object_object_get(
2040 : poDynamicMetadata, "template");
2041 22 : if (poTemplate &&
2042 11 : json_object_get_type(poTemplate) ==
2043 : json_type_string)
2044 : pszTemplate =
2045 11 : json_object_get_string(poTemplate);
2046 :
2047 : json_object *poSource =
2048 11 : CPL_json_object_object_get(
2049 : poDynamicMetadata, "source");
2050 22 : if (poSource &&
2051 11 : json_object_get_type(poSource) ==
2052 : json_type_string)
2053 : pszSource =
2054 11 : json_object_get_string(poSource);
2055 : }
2056 : else
2057 : #endif
2058 : {
2059 0 : CPLError(CE_Warning, CPLE_NotSupported,
2060 : "dynamic_metadata not supported since "
2061 : "libxml2 is not available");
2062 : }
2063 : }
2064 :
2065 16 : bool bGDALMetadata = false;
2066 : json_object *poGDALMetadata =
2067 16 : CPL_json_object_object_get(poMetadata,
2068 : "gdal_metadata");
2069 17 : if (poGDALMetadata &&
2070 1 : json_object_get_type(poGDALMetadata) ==
2071 : json_type_boolean)
2072 1 : bGDALMetadata = CPL_TO_BOOL(
2073 : json_object_get_boolean(poGDALMetadata));
2074 :
2075 16 : if (pszFile != nullptr || pszContent != nullptr ||
2076 12 : (pszTemplate != nullptr && pszSource != nullptr) ||
2077 : bGDALMetadata)
2078 : {
2079 32 : GMLJP2V2MetadataDesc oDesc;
2080 16 : if (pszFile)
2081 2 : oDesc.osFile = pszFile;
2082 16 : if (pszContent)
2083 2 : oDesc.osContent = pszContent;
2084 16 : if (pszTemplate)
2085 11 : oDesc.osTemplateFile = pszTemplate;
2086 16 : if (pszSource)
2087 11 : oDesc.osSourceFile = pszSource;
2088 16 : oDesc.bGDALMetadata = bGDALMetadata;
2089 :
2090 : json_object *poLocation =
2091 16 : CPL_json_object_object_get(poMetadata,
2092 : "parent_node");
2093 20 : if (poLocation &&
2094 4 : json_object_get_type(poLocation) ==
2095 : json_type_string)
2096 : {
2097 : const char *pszLocation =
2098 4 : json_object_get_string(poLocation);
2099 4 : if (EQUAL(pszLocation, "CoverageCollection"))
2100 2 : oDesc.bParentCoverageCollection = TRUE;
2101 2 : else if (EQUAL(pszLocation, "GridCoverage"))
2102 1 : oDesc.bParentCoverageCollection = FALSE;
2103 : else
2104 1 : CPLError(
2105 : CE_Warning, CPLE_NotSupported,
2106 : "metadata[].parent_node should be "
2107 : "CoverageCollection or GridCoverage");
2108 : }
2109 :
2110 16 : aoMetadata.push_back(std::move(oDesc));
2111 : }
2112 : }
2113 : }
2114 : }
2115 :
2116 : json_object *poAnnotations =
2117 23 : CPL_json_object_object_get(poRootInstance, "annotations");
2118 25 : if (poAnnotations &&
2119 2 : json_object_get_type(poAnnotations) == json_type_array)
2120 : {
2121 2 : auto nLength = json_object_array_length(poAnnotations);
2122 7 : for (decltype(nLength) i = 0; i < nLength; ++i)
2123 : {
2124 : json_object *poAnnotation =
2125 5 : json_object_array_get_idx(poAnnotations, i);
2126 10 : if (poAnnotation &&
2127 5 : json_object_get_type(poAnnotation) == json_type_string)
2128 : {
2129 10 : GMLJP2V2AnnotationDesc oDesc;
2130 5 : oDesc.osFile = json_object_get_string(poAnnotation);
2131 5 : aoAnnotations.push_back(std::move(oDesc));
2132 : }
2133 : }
2134 : }
2135 :
2136 : json_object *poGMLFileList =
2137 23 : CPL_json_object_object_get(poRootInstance, "gml_filelist");
2138 27 : if (poGMLFileList &&
2139 4 : json_object_get_type(poGMLFileList) == json_type_array)
2140 : {
2141 4 : auto nLength = json_object_array_length(poGMLFileList);
2142 14 : for (decltype(nLength) i = 0; i < nLength; ++i)
2143 : {
2144 : json_object *poGMLFile =
2145 10 : json_object_array_get_idx(poGMLFileList, i);
2146 20 : if (poGMLFile &&
2147 10 : json_object_get_type(poGMLFile) == json_type_object)
2148 : {
2149 7 : const char *pszFile = nullptr;
2150 : json_object *poFile =
2151 7 : CPL_json_object_object_get(poGMLFile, "file");
2152 13 : if (poFile &&
2153 6 : json_object_get_type(poFile) == json_type_string)
2154 6 : pszFile = json_object_get_string(poFile);
2155 :
2156 7 : const char *pszRemoteResource = nullptr;
2157 : json_object *poRemoteResource =
2158 7 : CPL_json_object_object_get(poGMLFile,
2159 : "remote_resource");
2160 8 : if (poRemoteResource &&
2161 1 : json_object_get_type(poRemoteResource) ==
2162 : json_type_string)
2163 : pszRemoteResource =
2164 1 : json_object_get_string(poRemoteResource);
2165 :
2166 7 : if (pszFile || pszRemoteResource)
2167 : {
2168 14 : GMLJP2V2GMLFileDesc oDesc;
2169 7 : if (pszFile)
2170 6 : oDesc.osFile = pszFile;
2171 1 : else if (pszRemoteResource)
2172 1 : oDesc.osRemoteResource = pszRemoteResource;
2173 :
2174 : json_object *poNamespacePrefix =
2175 7 : CPL_json_object_object_get(poGMLFile,
2176 : "namespace_prefix");
2177 7 : if (poNamespacePrefix &&
2178 0 : json_object_get_type(poNamespacePrefix) ==
2179 : json_type_string)
2180 : oDesc.osNamespacePrefix =
2181 0 : json_object_get_string(poNamespacePrefix);
2182 :
2183 : json_object *poNamespace =
2184 7 : CPL_json_object_object_get(poGMLFile,
2185 : "namespace");
2186 9 : if (poNamespace &&
2187 2 : json_object_get_type(poNamespace) ==
2188 : json_type_string)
2189 : oDesc.osNamespace =
2190 2 : json_object_get_string(poNamespace);
2191 :
2192 : json_object *poSchemaLocation =
2193 7 : CPL_json_object_object_get(poGMLFile,
2194 : "schema_location");
2195 11 : if (poSchemaLocation &&
2196 4 : json_object_get_type(poSchemaLocation) ==
2197 : json_type_string)
2198 : oDesc.osSchemaLocation =
2199 4 : json_object_get_string(poSchemaLocation);
2200 :
2201 : json_object *poInline =
2202 7 : CPL_json_object_object_get(poGMLFile, "inline");
2203 7 : if (poInline && json_object_get_type(poInline) ==
2204 : json_type_boolean)
2205 4 : oDesc.bInline =
2206 4 : json_object_get_boolean(poInline);
2207 :
2208 : json_object *poLocation =
2209 7 : CPL_json_object_object_get(poGMLFile,
2210 : "parent_node");
2211 11 : if (poLocation &&
2212 4 : json_object_get_type(poLocation) ==
2213 : json_type_string)
2214 : {
2215 : const char *pszLocation =
2216 4 : json_object_get_string(poLocation);
2217 4 : if (EQUAL(pszLocation, "CoverageCollection"))
2218 1 : oDesc.bParentCoverageCollection = TRUE;
2219 3 : else if (EQUAL(pszLocation, "GridCoverage"))
2220 2 : oDesc.bParentCoverageCollection = FALSE;
2221 : else
2222 1 : CPLError(
2223 : CE_Warning, CPLE_NotSupported,
2224 : "gml_filelist[].parent_node should be "
2225 : "CoverageCollection or GridCoverage");
2226 : }
2227 :
2228 7 : aoGMLFiles.push_back(std::move(oDesc));
2229 : }
2230 : }
2231 3 : else if (poGMLFile && json_object_get_type(poGMLFile) ==
2232 : json_type_string)
2233 : {
2234 6 : GMLJP2V2GMLFileDesc oDesc;
2235 3 : oDesc.osFile = json_object_get_string(poGMLFile);
2236 3 : aoGMLFiles.push_back(std::move(oDesc));
2237 : }
2238 : }
2239 : }
2240 :
2241 : json_object *poStyles =
2242 23 : CPL_json_object_object_get(poRootInstance, "styles");
2243 23 : if (poStyles && json_object_get_type(poStyles) == json_type_array)
2244 : {
2245 2 : auto nLength = json_object_array_length(poStyles);
2246 9 : for (decltype(nLength) i = 0; i < nLength; ++i)
2247 : {
2248 : json_object *poStyle =
2249 7 : json_object_array_get_idx(poStyles, i);
2250 14 : if (poStyle &&
2251 7 : json_object_get_type(poStyle) == json_type_object)
2252 : {
2253 4 : const char *pszFile = nullptr;
2254 : json_object *poFile =
2255 4 : CPL_json_object_object_get(poStyle, "file");
2256 8 : if (poFile &&
2257 4 : json_object_get_type(poFile) == json_type_string)
2258 4 : pszFile = json_object_get_string(poFile);
2259 :
2260 4 : if (pszFile)
2261 : {
2262 8 : GMLJP2V2StyleDesc oDesc;
2263 4 : oDesc.osFile = pszFile;
2264 :
2265 : json_object *poLocation =
2266 4 : CPL_json_object_object_get(poStyle,
2267 : "parent_node");
2268 7 : if (poLocation &&
2269 3 : json_object_get_type(poLocation) ==
2270 : json_type_string)
2271 : {
2272 : const char *pszLocation =
2273 3 : json_object_get_string(poLocation);
2274 3 : if (EQUAL(pszLocation, "CoverageCollection"))
2275 1 : oDesc.bParentCoverageCollection = TRUE;
2276 2 : else if (EQUAL(pszLocation, "GridCoverage"))
2277 1 : oDesc.bParentCoverageCollection = FALSE;
2278 : else
2279 1 : CPLError(
2280 : CE_Warning, CPLE_NotSupported,
2281 : "styles[].parent_node should be "
2282 : "CoverageCollection or GridCoverage");
2283 : }
2284 :
2285 4 : aoStyles.push_back(std::move(oDesc));
2286 : }
2287 : }
2288 6 : else if (poStyle &&
2289 3 : json_object_get_type(poStyle) == json_type_string)
2290 : {
2291 6 : GMLJP2V2StyleDesc oDesc;
2292 3 : oDesc.osFile = json_object_get_string(poStyle);
2293 3 : aoStyles.push_back(std::move(oDesc));
2294 : }
2295 : }
2296 : }
2297 :
2298 : json_object *poExtensions =
2299 23 : CPL_json_object_object_get(poRootInstance, "extensions");
2300 25 : if (poExtensions &&
2301 2 : json_object_get_type(poExtensions) == json_type_array)
2302 : {
2303 2 : auto nLength = json_object_array_length(poExtensions);
2304 9 : for (decltype(nLength) i = 0; i < nLength; ++i)
2305 : {
2306 : json_object *poExtension =
2307 7 : json_object_array_get_idx(poExtensions, i);
2308 14 : if (poExtension &&
2309 7 : json_object_get_type(poExtension) == json_type_object)
2310 : {
2311 4 : const char *pszFile = nullptr;
2312 : json_object *poFile =
2313 4 : CPL_json_object_object_get(poExtension, "file");
2314 8 : if (poFile &&
2315 4 : json_object_get_type(poFile) == json_type_string)
2316 4 : pszFile = json_object_get_string(poFile);
2317 :
2318 4 : if (pszFile)
2319 : {
2320 8 : GMLJP2V2ExtensionDesc oDesc;
2321 4 : oDesc.osFile = pszFile;
2322 :
2323 : json_object *poLocation =
2324 4 : CPL_json_object_object_get(poExtension,
2325 : "parent_node");
2326 7 : if (poLocation &&
2327 3 : json_object_get_type(poLocation) ==
2328 : json_type_string)
2329 : {
2330 : const char *pszLocation =
2331 3 : json_object_get_string(poLocation);
2332 3 : if (EQUAL(pszLocation, "CoverageCollection"))
2333 1 : oDesc.bParentCoverageCollection = TRUE;
2334 2 : else if (EQUAL(pszLocation, "GridCoverage"))
2335 1 : oDesc.bParentCoverageCollection = FALSE;
2336 : else
2337 1 : CPLError(
2338 : CE_Warning, CPLE_NotSupported,
2339 : "extensions[].parent_node should be "
2340 : "CoverageCollection or GridCoverage");
2341 : }
2342 :
2343 4 : aoExtensions.push_back(std::move(oDesc));
2344 : }
2345 : }
2346 3 : else if (poExtension && json_object_get_type(poExtension) ==
2347 : json_type_string)
2348 : {
2349 6 : GMLJP2V2ExtensionDesc oDesc;
2350 3 : oDesc.osFile = json_object_get_string(poExtension);
2351 3 : aoExtensions.push_back(std::move(oDesc));
2352 : }
2353 : }
2354 : }
2355 : }
2356 :
2357 24 : json_object *poBoxes = CPL_json_object_object_get(poObj, "boxes");
2358 24 : if (poBoxes && json_object_get_type(poBoxes) == json_type_array)
2359 : {
2360 2 : auto nLength = json_object_array_length(poBoxes);
2361 6 : for (decltype(nLength) i = 0; i < nLength; ++i)
2362 : {
2363 4 : json_object *poBox = json_object_array_get_idx(poBoxes, i);
2364 4 : if (poBox && json_object_get_type(poBox) == json_type_object)
2365 : {
2366 : json_object *poFile =
2367 2 : CPL_json_object_object_get(poBox, "file");
2368 4 : if (poFile &&
2369 2 : json_object_get_type(poFile) == json_type_string)
2370 : {
2371 4 : GMLJP2V2BoxDesc oDesc;
2372 2 : oDesc.osFile = json_object_get_string(poFile);
2373 :
2374 : json_object *poLabel =
2375 2 : CPL_json_object_object_get(poBox, "label");
2376 4 : if (poLabel &&
2377 2 : json_object_get_type(poLabel) == json_type_string)
2378 2 : oDesc.osLabel = json_object_get_string(poLabel);
2379 : else
2380 0 : oDesc.osLabel = CPLGetFilename(oDesc.osFile);
2381 :
2382 2 : aoBoxes.push_back(std::move(oDesc));
2383 : }
2384 : }
2385 4 : else if (poBox &&
2386 2 : json_object_get_type(poBox) == json_type_string)
2387 : {
2388 4 : GMLJP2V2BoxDesc oDesc;
2389 2 : oDesc.osFile = json_object_get_string(poBox);
2390 2 : oDesc.osLabel = CPLGetFilename(oDesc.osFile);
2391 2 : aoBoxes.push_back(std::move(oDesc));
2392 : }
2393 : }
2394 : }
2395 :
2396 24 : json_object_put(poObj);
2397 :
2398 : // Check that if a GML file points to an internal schemaLocation,
2399 : // the matching box really exists.
2400 34 : for (const auto &oGMLFile : aoGMLFiles)
2401 : {
2402 14 : if (!oGMLFile.osSchemaLocation.empty() &&
2403 4 : STARTS_WITH(oGMLFile.osSchemaLocation, "gmljp2://xml/"))
2404 : {
2405 : const char *pszLookedLabel =
2406 4 : oGMLFile.osSchemaLocation.c_str() + strlen("gmljp2://xml/");
2407 4 : bool bFound = false;
2408 9 : for (int j = 0; !bFound && j < static_cast<int>(aoBoxes.size());
2409 : ++j)
2410 5 : bFound = (strcmp(pszLookedLabel, aoBoxes[j].osLabel) == 0);
2411 4 : if (!bFound)
2412 : {
2413 1 : CPLError(CE_Warning, CPLE_AppDefined,
2414 : "GML file %s has a schema_location=%s, "
2415 : "but no box with label %s is defined",
2416 : oGMLFile.osFile.c_str(),
2417 : oGMLFile.osSchemaLocation.c_str(), pszLookedLabel);
2418 : }
2419 : }
2420 : }
2421 :
2422 : // Read custom grid coverage file.
2423 24 : if (!osGridCoverageFile.empty())
2424 : {
2425 2 : CPLXMLTreeCloser psTmp(CPLParseXMLFile(osGridCoverageFile));
2426 2 : if (psTmp == nullptr)
2427 1 : return nullptr;
2428 1 : CPLXMLNode *psTmpRoot = GDALGMLJP2GetXMLRoot(psTmp.get());
2429 1 : if (psTmpRoot)
2430 : {
2431 1 : char *pszTmp = CPLSerializeXMLTree(psTmpRoot);
2432 1 : osGridCoverage = pszTmp;
2433 1 : CPLFree(pszTmp);
2434 : }
2435 : }
2436 : }
2437 :
2438 50 : CPLString osDictBox;
2439 50 : CPLString osDoc;
2440 :
2441 25 : if (osGridCoverage.empty())
2442 : {
2443 : /* --------------------------------------------------------------------
2444 : */
2445 : /* Prepare GMLJP2RectifiedGridCoverage */
2446 : /* --------------------------------------------------------------------
2447 : */
2448 24 : int nEPSGCode = 0;
2449 : double adfOrigin[2];
2450 : double adfXVector[2];
2451 : double adfYVector[2];
2452 24 : const char *pszComment = "";
2453 24 : bool bNeedAxisFlip = false;
2454 24 : GetGMLJP2GeoreferencingInfo(nEPSGCode, adfOrigin, adfXVector,
2455 : adfYVector, pszComment, osDictBox,
2456 : bNeedAxisFlip);
2457 :
2458 24 : char szSRSName[100] = {0};
2459 24 : if (nEPSGCode != 0)
2460 : {
2461 24 : if (bCRSURL)
2462 23 : snprintf(szSRSName, sizeof(szSRSName),
2463 : "http://www.opengis.net/def/crs/EPSG/0/%d", nEPSGCode);
2464 : else
2465 1 : snprintf(szSRSName, sizeof(szSRSName),
2466 : "urn:ogc:def:crs:EPSG::%d", nEPSGCode);
2467 : }
2468 : else
2469 0 : snprintf(szSRSName, sizeof(szSRSName), "%s",
2470 : "gmljp2://xml/CRSDictionary.gml#ogrcrs1");
2471 :
2472 : // Compute bounding box
2473 24 : double dfX1 = m_gt[0];
2474 24 : double dfX2 = m_gt[0] + nXSize * m_gt[1];
2475 24 : double dfX3 = m_gt[0] + nYSize * m_gt[2];
2476 24 : double dfX4 = m_gt[0] + nXSize * m_gt[1] + nYSize * m_gt[2];
2477 24 : double dfY1 = m_gt[3];
2478 24 : double dfY2 = m_gt[3] + nXSize * m_gt[4];
2479 24 : double dfY3 = m_gt[3] + nYSize * m_gt[5];
2480 24 : double dfY4 = m_gt[3] + nXSize * m_gt[4] + nYSize * m_gt[5];
2481 24 : double dfLCX = std::min(std::min(dfX1, dfX2), std::min(dfX3, dfX4));
2482 24 : double dfLCY = std::min(std::min(dfY1, dfY2), std::min(dfY3, dfY4));
2483 24 : double dfUCX = std::max(std::max(dfX1, dfX2), std::max(dfX3, dfX4));
2484 24 : double dfUCY = std::max(std::max(dfY1, dfY2), std::max(dfY3, dfY4));
2485 24 : if (bNeedAxisFlip)
2486 : {
2487 1 : std::swap(dfLCX, dfLCY);
2488 1 : std::swap(dfUCX, dfUCY);
2489 : }
2490 :
2491 48 : osGridCoverage.Printf(
2492 : " <gmljp2:GMLJP2RectifiedGridCoverage gml:id=\"RGC_1_%s\">\n"
2493 : " <gml:boundedBy>\n"
2494 : " <gml:Envelope srsDimension=\"2\" srsName=\"%s\">\n"
2495 : " <gml:lowerCorner>%.15g %.15g</gml:lowerCorner>\n"
2496 : " <gml:upperCorner>%.15g %.15g</gml:upperCorner>\n"
2497 : " </gml:Envelope>\n"
2498 : " </gml:boundedBy>\n"
2499 : " <gml:domainSet>\n"
2500 : " <gml:RectifiedGrid gml:id=\"RGC_1_GRID_%s\" dimension=\"2\" "
2501 : "srsName=\"%s\">\n"
2502 : " <gml:limits>\n"
2503 : " <gml:GridEnvelope>\n"
2504 : " <gml:low>0 0</gml:low>\n"
2505 : " <gml:high>%d %d</gml:high>\n"
2506 : " </gml:GridEnvelope>\n"
2507 : " </gml:limits>\n"
2508 : " <gml:axisName>x</gml:axisName>\n"
2509 : " <gml:axisName>y</gml:axisName>\n"
2510 : " <gml:origin>\n"
2511 : " <gml:Point gml:id=\"P0001\" srsName=\"%s\">\n"
2512 : " <gml:pos>%.15g %.15g</gml:pos>\n"
2513 : " </gml:Point>\n"
2514 : " </gml:origin>\n"
2515 : "%s"
2516 : " <gml:offsetVector srsName=\"%s\">%.15g "
2517 : "%.15g</gml:offsetVector>\n"
2518 : " <gml:offsetVector srsName=\"%s\">%.15g "
2519 : "%.15g</gml:offsetVector>\n"
2520 : " </gml:RectifiedGrid>\n"
2521 : " </gml:domainSet>\n"
2522 : " <gml:rangeSet>\n"
2523 : " <gml:File>\n"
2524 : " <gml:rangeParameters/>\n"
2525 : " <gml:fileName>gmljp2://codestream/0</gml:fileName>\n"
2526 : " <gml:fileStructure>inapplicable</gml:fileStructure>\n"
2527 : " </gml:File>\n"
2528 : " </gml:rangeSet>\n"
2529 : " <gmlcov:rangeType>%s</gmlcov:rangeType>\n"
2530 : " </gmljp2:GMLJP2RectifiedGridCoverage>\n",
2531 : osRootGMLId.c_str(), szSRSName, dfLCX, dfLCY, dfUCX, dfUCY,
2532 : osRootGMLId.c_str(), szSRSName, nXSize - 1, nYSize - 1, szSRSName,
2533 : adfOrigin[0], adfOrigin[1], pszComment, szSRSName, adfXVector[0],
2534 : adfXVector[1], szSRSName, adfYVector[0], adfYVector[1],
2535 24 : osCoverageRangeTypeXML.c_str());
2536 : }
2537 :
2538 : /* -------------------------------------------------------------------- */
2539 : /* Main node. */
2540 : /* -------------------------------------------------------------------- */
2541 :
2542 : // Per
2543 : // http://docs.opengeospatial.org/is/08-085r5/08-085r5.html#requirement_11
2544 : osDoc.Printf(
2545 : //"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
2546 : "<gmljp2:GMLJP2CoverageCollection gml:id=\"%s\"\n"
2547 : " xmlns:gml=\"http://www.opengis.net/gml/3.2\"\n"
2548 : " xmlns:gmlcov=\"http://www.opengis.net/gmlcov/1.0\"\n"
2549 : " xmlns:gmljp2=\"http://www.opengis.net/gmljp2/2.0\"\n"
2550 : " xmlns:swe=\"http://www.opengis.net/swe/2.0\"\n"
2551 : " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
2552 : " xsi:schemaLocation=\"http://www.opengis.net/gmljp2/2.0 "
2553 : "http://schemas.opengis.net/gmljp2/2.0/gmljp2.xsd\">\n"
2554 : " <gml:domainSet nilReason=\"inapplicable\"/>\n"
2555 : " <gml:rangeSet>\n"
2556 : " <gml:DataBlock>\n"
2557 : " <gml:rangeParameters nilReason=\"inapplicable\"/>\n"
2558 : " "
2559 : "<gml:doubleOrNilReasonTupleList>inapplicable</"
2560 : "gml:doubleOrNilReasonTupleList>\n"
2561 : " </gml:DataBlock>\n"
2562 : " </gml:rangeSet>\n"
2563 : " <gmlcov:rangeType>\n"
2564 : " <swe:DataRecord>\n"
2565 : " <swe:field name=\"Collection\"> </swe:field>\n"
2566 : " </swe:DataRecord>\n"
2567 : " </gmlcov:rangeType>\n"
2568 : " <gmljp2:featureMember>\n"
2569 : "%s"
2570 : " </gmljp2:featureMember>\n"
2571 : "</gmljp2:GMLJP2CoverageCollection>\n",
2572 25 : osRootGMLId.c_str(), osGridCoverage.c_str());
2573 :
2574 50 : const std::string osTmpDir = VSIMemGenerateHiddenFilename("gmljp2");
2575 :
2576 : /* -------------------------------------------------------------------- */
2577 : /* Process metadata, annotations and features collections. */
2578 : /* -------------------------------------------------------------------- */
2579 36 : if (!aoMetadata.empty() || !aoAnnotations.empty() || !aoGMLFiles.empty() ||
2580 36 : !aoStyles.empty() || !aoExtensions.empty())
2581 : {
2582 16 : CPLXMLTreeCloser psRoot(CPLParseXMLString(osDoc));
2583 16 : CPLAssert(psRoot);
2584 : CPLXMLNode *psGMLJP2CoverageCollection =
2585 16 : GDALGMLJP2GetXMLRoot(psRoot.get());
2586 16 : CPLAssert(psGMLJP2CoverageCollection);
2587 :
2588 36 : for (const auto &oMetadata : aoMetadata)
2589 : {
2590 20 : CPLXMLTreeCloser psMetadata(nullptr);
2591 20 : if (!oMetadata.osFile.empty())
2592 : psMetadata =
2593 4 : CPLXMLTreeCloser(CPLParseXMLFile(oMetadata.osFile));
2594 16 : else if (!oMetadata.osContent.empty())
2595 : psMetadata =
2596 4 : CPLXMLTreeCloser(CPLParseXMLString(oMetadata.osContent));
2597 12 : else if (oMetadata.bGDALMetadata)
2598 : {
2599 2 : psMetadata = CPLXMLTreeCloser(
2600 1 : CreateGDALMultiDomainMetadataXML(poSrcDS, TRUE));
2601 1 : if (psMetadata)
2602 : {
2603 1 : CPLSetXMLValue(psMetadata.get(), "#xmlns",
2604 : "http://gdal.org");
2605 1 : CPLXMLNode *psNewMetadata = CPLCreateXMLNode(
2606 : nullptr, CXT_Element, "gmljp2:metadata");
2607 1 : CPLAddXMLChild(psNewMetadata, psMetadata.release());
2608 1 : psMetadata = CPLXMLTreeCloser(psNewMetadata);
2609 : }
2610 : }
2611 : else
2612 22 : psMetadata = CPLXMLTreeCloser(GDALGMLJP2GenerateMetadata(
2613 22 : oMetadata.osTemplateFile, oMetadata.osSourceFile));
2614 20 : if (psMetadata == nullptr)
2615 11 : continue;
2616 9 : CPLXMLNode *psMetadataRoot = GDALGMLJP2GetXMLRoot(psMetadata.get());
2617 9 : if (psMetadataRoot)
2618 : {
2619 9 : if (strcmp(psMetadataRoot->pszValue, "eop:EarthObservation") ==
2620 : 0)
2621 : {
2622 0 : CPLXMLNode *psNewMetadata = CPLCreateXMLNode(
2623 : nullptr, CXT_Element, "gmljp2:eopMetadata");
2624 0 : CPLAddXMLChild(psNewMetadata,
2625 : CPLCloneXMLTree(psMetadataRoot));
2626 0 : psMetadataRoot = psNewMetadata;
2627 0 : psMetadata.reset(psNewMetadata);
2628 : }
2629 9 : if (strcmp(psMetadataRoot->pszValue, "gmljp2:isoMetadata") !=
2630 9 : 0 &&
2631 9 : strcmp(psMetadataRoot->pszValue, "gmljp2:eopMetadata") !=
2632 8 : 0 &&
2633 8 : strcmp(psMetadataRoot->pszValue, "gmljp2:dcMetadata") !=
2634 6 : 0 &&
2635 6 : strcmp(psMetadataRoot->pszValue, "gmljp2:metadata") != 0)
2636 : {
2637 1 : CPLError(CE_Warning, CPLE_AppDefined,
2638 : "The metadata root node should be one of "
2639 : "gmljp2:isoMetadata, "
2640 : "gmljp2:eopMetadata, gmljp2:dcMetadata or "
2641 : "gmljp2:metadata");
2642 : }
2643 8 : else if (oMetadata.bParentCoverageCollection)
2644 : {
2645 : /* Insert the gmlcov:metadata link as the next sibling of */
2646 : /* GMLJP2CoverageCollection.rangeType */
2647 7 : CPLXMLNode *psRangeType = CPLGetXMLNode(
2648 : psGMLJP2CoverageCollection, "gmlcov:rangeType");
2649 7 : CPLAssert(psRangeType);
2650 7 : CPLXMLNode *psNodeAfterWhichToInsert = psRangeType;
2651 7 : CPLXMLNode *psNext = psNodeAfterWhichToInsert->psNext;
2652 10 : while (psNext != nullptr && psNext->eType == CXT_Element &&
2653 10 : strcmp(psNext->pszValue, "gmlcov:metadata") == 0)
2654 : {
2655 3 : psNodeAfterWhichToInsert = psNext;
2656 3 : psNext = psNext->psNext;
2657 : }
2658 7 : psNodeAfterWhichToInsert->psNext = nullptr;
2659 : CPLXMLNode *psGMLCovMetadata =
2660 7 : CPLCreateXMLNode(psGMLJP2CoverageCollection,
2661 : CXT_Element, "gmlcov:metadata");
2662 7 : psGMLCovMetadata->psNext = psNext;
2663 7 : CPLXMLNode *psGMLJP2Metadata = CPLCreateXMLNode(
2664 : psGMLCovMetadata, CXT_Element, "gmljp2:Metadata");
2665 7 : CPLAddXMLChild(psGMLJP2Metadata,
2666 : CPLCloneXMLTree(psMetadataRoot));
2667 : }
2668 : else
2669 : {
2670 : /* Insert the gmlcov:metadata link as the last child of */
2671 : /* GMLJP2RectifiedGridCoverage typically */
2672 1 : CPLXMLNode *psFeatureMemberOfGridCoverage = CPLGetXMLNode(
2673 : psGMLJP2CoverageCollection, "gmljp2:featureMember");
2674 1 : CPLAssert(psFeatureMemberOfGridCoverage);
2675 1 : CPLXMLNode *psGridCoverage =
2676 : psFeatureMemberOfGridCoverage->psChild;
2677 1 : CPLAssert(psGridCoverage);
2678 1 : CPLXMLNode *psGMLCovMetadata = CPLCreateXMLNode(
2679 : psGridCoverage, CXT_Element, "gmlcov:metadata");
2680 1 : CPLXMLNode *psGMLJP2Metadata = CPLCreateXMLNode(
2681 : psGMLCovMetadata, CXT_Element, "gmljp2:Metadata");
2682 1 : CPLAddXMLChild(psGMLJP2Metadata,
2683 : CPLCloneXMLTree(psMetadataRoot));
2684 : }
2685 : }
2686 : }
2687 :
2688 16 : bool bRootHasXLink = false;
2689 :
2690 : // Examples of inline or reference feature collections can be found
2691 : // in http://schemas.opengis.net/gmljp2/2.0/examples/gmljp2.xml
2692 26 : for (int i = 0; i < static_cast<int>(aoGMLFiles.size()); ++i)
2693 : {
2694 : // Is the file already a GML file?
2695 10 : CPLXMLTreeCloser psGMLFile(nullptr);
2696 10 : if (!aoGMLFiles[i].osFile.empty())
2697 : {
2698 : const CPLString osExt =
2699 9 : CPLGetExtensionSafe(aoGMLFiles[i].osFile);
2700 9 : if (EQUAL(osExt, "gml") || EQUAL(osExt, "xml"))
2701 : {
2702 : psGMLFile =
2703 4 : CPLXMLTreeCloser(CPLParseXMLFile(aoGMLFiles[i].osFile));
2704 : }
2705 9 : GDALDriverH hDrv = nullptr;
2706 9 : if (psGMLFile == nullptr)
2707 : {
2708 6 : hDrv = GDALIdentifyDriver(aoGMLFiles[i].osFile, nullptr);
2709 6 : if (hDrv == nullptr)
2710 : {
2711 2 : CPLError(CE_Failure, CPLE_AppDefined,
2712 : "%s is no a GDAL recognized file",
2713 2 : aoGMLFiles[i].osFile.c_str());
2714 2 : continue;
2715 : }
2716 : }
2717 7 : GDALDriverH hGMLDrv = GDALGetDriverByName("GML");
2718 7 : if (psGMLFile == nullptr && hDrv == hGMLDrv)
2719 : {
2720 : // Yes, parse it
2721 : psGMLFile =
2722 0 : CPLXMLTreeCloser(CPLParseXMLFile(aoGMLFiles[i].osFile));
2723 : }
2724 7 : else if (psGMLFile == nullptr)
2725 : {
2726 4 : if (hGMLDrv == nullptr)
2727 : {
2728 0 : CPLError(CE_Failure, CPLE_AppDefined,
2729 : "Cannot translate %s to GML",
2730 0 : aoGMLFiles[i].osFile.c_str());
2731 0 : continue;
2732 : }
2733 :
2734 : // On-the-fly translation to GML 3.2
2735 4 : GDALDatasetH hSrcDS = GDALOpenEx(aoGMLFiles[i].osFile, 0,
2736 : nullptr, nullptr, nullptr);
2737 4 : if (hSrcDS)
2738 : {
2739 : const CPLString osTmpFile =
2740 8 : osTmpDir + "/" + std::to_string(i) + "/" +
2741 16 : CPLGetBasenameSafe(aoGMLFiles[i].osFile) + ".gml";
2742 4 : char **papszOptions = nullptr;
2743 : papszOptions =
2744 4 : CSLSetNameValue(papszOptions, "FORMAT", "GML3.2");
2745 : papszOptions =
2746 4 : CSLSetNameValue(papszOptions, "SRSNAME_FORMAT",
2747 : (bCRSURL) ? "OGC_URL" : "OGC_URN");
2748 4 : if (aoGMLFiles.size() > 1 ||
2749 5 : !aoGMLFiles[i].osNamespace.empty() ||
2750 1 : !aoGMLFiles[i].osNamespacePrefix.empty())
2751 : {
2752 6 : papszOptions = CSLSetNameValue(
2753 : papszOptions, "PREFIX",
2754 3 : aoGMLFiles[i].osNamespacePrefix.empty()
2755 3 : ? CPLSPrintf("ogr%d", i)
2756 0 : : aoGMLFiles[i].osNamespacePrefix.c_str());
2757 6 : papszOptions = CSLSetNameValue(
2758 : papszOptions, "TARGET_NAMESPACE",
2759 3 : aoGMLFiles[i].osNamespace.empty()
2760 2 : ? CPLSPrintf("http://ogr.maptools.org/%d",
2761 : i)
2762 1 : : aoGMLFiles[i].osNamespace.c_str());
2763 : }
2764 : GDALDatasetH hDS =
2765 4 : GDALCreateCopy(hGMLDrv, osTmpFile, hSrcDS, FALSE,
2766 : papszOptions, nullptr, nullptr);
2767 4 : CSLDestroy(papszOptions);
2768 4 : if (hDS)
2769 : {
2770 3 : GDALClose(hDS);
2771 : psGMLFile =
2772 3 : CPLXMLTreeCloser(CPLParseXMLFile(osTmpFile));
2773 3 : aoGMLFiles[i].osFile = osTmpFile;
2774 3 : VSIUnlink(osTmpFile);
2775 : }
2776 : else
2777 : {
2778 1 : CPLError(CE_Failure, CPLE_AppDefined,
2779 : "Conversion of %s to GML failed",
2780 1 : aoGMLFiles[i].osFile.c_str());
2781 : }
2782 : }
2783 4 : GDALClose(hSrcDS);
2784 : }
2785 7 : if (psGMLFile == nullptr)
2786 1 : continue;
2787 : }
2788 :
2789 : CPLXMLNode *psGMLFileRoot =
2790 7 : psGMLFile ? GDALGMLJP2GetXMLRoot(psGMLFile.get()) : nullptr;
2791 7 : if (psGMLFileRoot || !aoGMLFiles[i].osRemoteResource.empty())
2792 : {
2793 : CPLXMLNode *node_f;
2794 7 : if (aoGMLFiles[i].bParentCoverageCollection)
2795 : {
2796 : // Insert in
2797 : // gmljp2:featureMember.gmljp2:GMLJP2Features.gmljp2:feature
2798 : CPLXMLNode *node_fm =
2799 5 : CPLCreateXMLNode(psGMLJP2CoverageCollection,
2800 : CXT_Element, "gmljp2:featureMember");
2801 :
2802 5 : CPLXMLNode *node_gf = CPLCreateXMLNode(
2803 : node_fm, CXT_Element, "gmljp2:GMLJP2Features");
2804 :
2805 5 : CPLSetXMLValue(node_gf, "#gml:id",
2806 : CPLSPrintf("%s_GMLJP2Features_%d",
2807 : osRootGMLId.c_str(), i));
2808 :
2809 5 : node_f = CPLCreateXMLNode(node_gf, CXT_Element,
2810 : "gmljp2:feature");
2811 : }
2812 : else
2813 : {
2814 2 : CPLXMLNode *psFeatureMemberOfGridCoverage = CPLGetXMLNode(
2815 : psGMLJP2CoverageCollection, "gmljp2:featureMember");
2816 2 : CPLAssert(psFeatureMemberOfGridCoverage);
2817 2 : CPLXMLNode *psGridCoverage =
2818 : psFeatureMemberOfGridCoverage->psChild;
2819 2 : CPLAssert(psGridCoverage);
2820 2 : node_f = CPLCreateXMLNode(psGridCoverage, CXT_Element,
2821 : "gmljp2:feature");
2822 : }
2823 :
2824 10 : if (!aoGMLFiles[i].bInline ||
2825 3 : !aoGMLFiles[i].osRemoteResource.empty())
2826 : {
2827 5 : if (!bRootHasXLink)
2828 : {
2829 2 : bRootHasXLink = true;
2830 2 : CPLSetXMLValue(psGMLJP2CoverageCollection,
2831 : "#xmlns:xlink",
2832 : "http://www.w3.org/1999/xlink");
2833 : }
2834 : }
2835 :
2836 7 : if (!aoGMLFiles[i].osRemoteResource.empty())
2837 : {
2838 1 : CPLSetXMLValue(node_f, "#xlink:href",
2839 1 : aoGMLFiles[i].osRemoteResource.c_str());
2840 1 : continue;
2841 : }
2842 :
2843 12 : CPLString osTmpFile;
2844 8 : if (!aoGMLFiles[i].bInline ||
2845 2 : !aoGMLFiles[i].osRemoteResource.empty())
2846 : {
2847 8 : osTmpFile = osTmpDir + "/" + std::to_string(i) + "/" +
2848 16 : CPLGetBasenameSafe(aoGMLFiles[i].osFile) +
2849 4 : ".gml";
2850 8 : GMLJP2V2BoxDesc oDesc;
2851 4 : oDesc.osFile = osTmpFile;
2852 4 : oDesc.osLabel = CPLGetFilename(oDesc.osFile);
2853 4 : CPLSetXMLValue(
2854 : node_f, "#xlink:href",
2855 : CPLSPrintf("gmljp2://xml/%s", oDesc.osLabel.c_str()));
2856 4 : aoBoxes.push_back(std::move(oDesc));
2857 : }
2858 :
2859 11 : if (CPLGetXMLNode(psGMLFileRoot, "xmlns") == nullptr &&
2860 5 : CPLGetXMLNode(psGMLFileRoot, "xmlns:gml") == nullptr)
2861 : {
2862 0 : CPLSetXMLValue(psGMLFileRoot, "#xmlns",
2863 : "http://www.opengis.net/gml/3.2");
2864 : }
2865 :
2866 : // modify the gml id making it unique for this document
2867 : CPLXMLNode *psGMLFileGMLId =
2868 6 : CPLGetXMLNode(psGMLFileRoot, "gml:id");
2869 6 : if (psGMLFileGMLId && psGMLFileGMLId->eType == CXT_Attribute)
2870 6 : CPLSetXMLValue(
2871 : psGMLFileGMLId, "",
2872 : CPLSPrintf("%s_%d_%s", osRootGMLId.c_str(), i,
2873 6 : psGMLFileGMLId->psChild->pszValue));
2874 6 : psGMLFileGMLId = nullptr;
2875 : // PrefixAllGMLIds(psGMLFileRoot, CPLSPrintf("%s_%d_",
2876 : // osRootGMLId.c_str(), i));
2877 :
2878 : // replace schema location
2879 : CPLXMLNode *psSchemaLocation =
2880 6 : CPLGetXMLNode(psGMLFileRoot, "xsi:schemaLocation");
2881 6 : if (psSchemaLocation &&
2882 5 : psSchemaLocation->eType == CXT_Attribute)
2883 : {
2884 10 : char **papszTokens = CSLTokenizeString2(
2885 5 : psSchemaLocation->psChild->pszValue, " \t\n",
2886 : CSLT_HONOURSTRINGS | CSLT_STRIPLEADSPACES |
2887 : CSLT_STRIPENDSPACES);
2888 10 : CPLString osSchemaLocation;
2889 :
2890 9 : if (CSLCount(papszTokens) == 2 &&
2891 9 : aoGMLFiles[i].osNamespace.empty() &&
2892 3 : !aoGMLFiles[i].osSchemaLocation.empty())
2893 : {
2894 1 : osSchemaLocation += papszTokens[0];
2895 1 : osSchemaLocation += " ";
2896 1 : osSchemaLocation += aoGMLFiles[i].osSchemaLocation;
2897 : }
2898 :
2899 7 : else if (CSLCount(papszTokens) == 2 &&
2900 3 : (aoGMLFiles[i].osNamespace.empty() ||
2901 1 : strcmp(papszTokens[0],
2902 8 : aoGMLFiles[i].osNamespace) == 0) &&
2903 3 : aoGMLFiles[i].osSchemaLocation.empty())
2904 : {
2905 : VSIStatBufL sStat;
2906 4 : CPLString osXSD;
2907 2 : if (CSLCount(papszTokens) == 2 &&
2908 2 : !CPLIsFilenameRelative(papszTokens[1]) &&
2909 0 : VSIStatL(papszTokens[1], &sStat) == 0)
2910 : {
2911 0 : osXSD = papszTokens[1];
2912 : }
2913 8 : else if (CSLCount(papszTokens) == 2 &&
2914 4 : CPLIsFilenameRelative(papszTokens[1]) &&
2915 2 : VSIStatL(
2916 4 : CPLFormFilenameSafe(
2917 4 : CPLGetDirnameSafe(aoGMLFiles[i].osFile)
2918 : .c_str(),
2919 2 : papszTokens[1], nullptr)
2920 : .c_str(),
2921 : &sStat) == 0)
2922 : {
2923 2 : osXSD = CPLFormFilenameSafe(
2924 4 : CPLGetDirnameSafe(aoGMLFiles[i].osFile).c_str(),
2925 4 : papszTokens[1], nullptr);
2926 : }
2927 2 : if (!osXSD.empty())
2928 : {
2929 4 : GMLJP2V2BoxDesc oDesc;
2930 2 : oDesc.osFile = std::move(osXSD);
2931 2 : oDesc.osLabel = CPLGetFilename(oDesc.osFile);
2932 2 : osSchemaLocation += papszTokens[0];
2933 2 : osSchemaLocation += " ";
2934 2 : osSchemaLocation += "gmljp2://xml/";
2935 2 : osSchemaLocation += oDesc.osLabel;
2936 2 : int j = 0; // Used after for.
2937 5 : for (; j < static_cast<int>(aoBoxes.size()); ++j)
2938 : {
2939 3 : if (aoBoxes[j].osLabel == oDesc.osLabel)
2940 0 : break;
2941 : }
2942 2 : if (j == static_cast<int>(aoBoxes.size()))
2943 2 : aoBoxes.push_back(std::move(oDesc));
2944 : }
2945 : }
2946 :
2947 2 : else if ((CSLCount(papszTokens) % 2) == 0)
2948 : {
2949 5 : for (char **papszIter = papszTokens; *papszIter;
2950 3 : papszIter += 2)
2951 : {
2952 3 : if (!osSchemaLocation.empty())
2953 1 : osSchemaLocation += " ";
2954 3 : if (!aoGMLFiles[i].osNamespace.empty() &&
2955 6 : !aoGMLFiles[i].osSchemaLocation.empty() &&
2956 3 : strcmp(papszIter[0],
2957 3 : aoGMLFiles[i].osNamespace) == 0)
2958 : {
2959 2 : osSchemaLocation += papszIter[0];
2960 2 : osSchemaLocation += " ";
2961 : osSchemaLocation +=
2962 2 : aoGMLFiles[i].osSchemaLocation;
2963 : }
2964 : else
2965 : {
2966 1 : osSchemaLocation += papszIter[0];
2967 1 : osSchemaLocation += " ";
2968 1 : osSchemaLocation += papszIter[1];
2969 : }
2970 : }
2971 : }
2972 5 : CSLDestroy(papszTokens);
2973 5 : CPLSetXMLValue(psSchemaLocation, "", osSchemaLocation);
2974 : }
2975 :
2976 6 : if (aoGMLFiles[i].bInline)
2977 2 : CPLAddXMLChild(node_f, CPLCloneXMLTree(psGMLFileRoot));
2978 : else
2979 4 : CPLSerializeXMLTreeToFile(psGMLFile.get(), osTmpFile);
2980 : }
2981 : }
2982 :
2983 : // c.f.
2984 : // http://schemas.opengis.net/gmljp2/2.0/examples/gmljp2_annotation.xml
2985 21 : for (int i = 0; i < static_cast<int>(aoAnnotations.size()); ++i)
2986 : {
2987 : // Is the file already a KML file?
2988 5 : CPLXMLTreeCloser psKMLFile(nullptr);
2989 5 : if (EQUAL(CPLGetExtensionSafe(aoAnnotations[i].osFile).c_str(),
2990 : "kml"))
2991 : psKMLFile =
2992 2 : CPLXMLTreeCloser(CPLParseXMLFile(aoAnnotations[i].osFile));
2993 5 : GDALDriverH hDrv = nullptr;
2994 5 : if (psKMLFile == nullptr)
2995 : {
2996 4 : hDrv = GDALIdentifyDriver(aoAnnotations[i].osFile, nullptr);
2997 4 : if (hDrv == nullptr)
2998 : {
2999 2 : CPLError(CE_Failure, CPLE_AppDefined,
3000 : "%s is no a GDAL recognized file",
3001 2 : aoAnnotations[i].osFile.c_str());
3002 2 : continue;
3003 : }
3004 : }
3005 3 : GDALDriverH hKMLDrv = GDALGetDriverByName("KML");
3006 3 : GDALDriverH hLIBKMLDrv = GDALGetDriverByName("LIBKML");
3007 3 : if (psKMLFile == nullptr && (hDrv == hKMLDrv || hDrv == hLIBKMLDrv))
3008 : {
3009 : // Yes, parse it
3010 : psKMLFile =
3011 0 : CPLXMLTreeCloser(CPLParseXMLFile(aoAnnotations[i].osFile));
3012 : }
3013 3 : else if (psKMLFile == nullptr)
3014 : {
3015 2 : if (hKMLDrv == nullptr && hLIBKMLDrv == nullptr)
3016 : {
3017 0 : CPLError(CE_Failure, CPLE_AppDefined,
3018 : "Cannot translate %s to KML",
3019 0 : aoAnnotations[i].osFile.c_str());
3020 0 : continue;
3021 : }
3022 :
3023 : // On-the-fly translation to KML
3024 2 : GDALDatasetH hSrcDS = GDALOpenEx(aoAnnotations[i].osFile, 0,
3025 : nullptr, nullptr, nullptr);
3026 2 : if (hSrcDS)
3027 : {
3028 : const CPLString osTmpFile =
3029 4 : osTmpDir + "/" + std::to_string(i) + "/" +
3030 8 : CPLGetBasenameSafe(aoAnnotations[i].osFile) + ".kml";
3031 2 : char **papszOptions = nullptr;
3032 2 : if (aoAnnotations.size() > 1)
3033 : {
3034 : papszOptions =
3035 2 : CSLSetNameValue(papszOptions, "DOCUMENT_ID",
3036 : CPLSPrintf("root_doc_%d", i));
3037 : }
3038 2 : GDALDatasetH hDS = GDALCreateCopy(
3039 : hLIBKMLDrv ? hLIBKMLDrv : hKMLDrv, osTmpFile, hSrcDS,
3040 : FALSE, papszOptions, nullptr, nullptr);
3041 2 : CSLDestroy(papszOptions);
3042 2 : if (hDS)
3043 : {
3044 1 : GDALClose(hDS);
3045 : psKMLFile =
3046 1 : CPLXMLTreeCloser(CPLParseXMLFile(osTmpFile));
3047 1 : aoAnnotations[i].osFile = osTmpFile;
3048 1 : VSIUnlink(osTmpFile);
3049 : }
3050 : else
3051 : {
3052 1 : CPLError(CE_Failure, CPLE_AppDefined,
3053 : "Conversion of %s to KML failed",
3054 1 : aoAnnotations[i].osFile.c_str());
3055 : }
3056 : }
3057 2 : GDALClose(hSrcDS);
3058 : }
3059 3 : if (psKMLFile == nullptr)
3060 1 : continue;
3061 :
3062 2 : CPLXMLNode *psKMLFileRoot = GDALGMLJP2GetXMLRoot(psKMLFile.get());
3063 2 : if (psKMLFileRoot)
3064 : {
3065 2 : CPLXMLNode *psFeatureMemberOfGridCoverage = CPLGetXMLNode(
3066 : psGMLJP2CoverageCollection, "gmljp2:featureMember");
3067 2 : CPLAssert(psFeatureMemberOfGridCoverage);
3068 2 : CPLXMLNode *psGridCoverage =
3069 : psFeatureMemberOfGridCoverage->psChild;
3070 2 : CPLAssert(psGridCoverage);
3071 2 : CPLXMLNode *psAnnotation = CPLCreateXMLNode(
3072 : psGridCoverage, CXT_Element, "gmljp2:annotation");
3073 :
3074 : /* Add a xsi:schemaLocation if not already present */
3075 6 : if (psKMLFileRoot->eType == CXT_Element &&
3076 2 : strcmp(psKMLFileRoot->pszValue, "kml") == 0 &&
3077 2 : CPLGetXMLNode(psKMLFileRoot, "xsi:schemaLocation") ==
3078 4 : nullptr &&
3079 1 : strcmp(CPLGetXMLValue(psKMLFileRoot, "xmlns", ""),
3080 : "http://www.opengis.net/kml/2.2") == 0)
3081 : {
3082 1 : CPLSetXMLValue(
3083 : psKMLFileRoot, "#xsi:schemaLocation",
3084 : "http://www.opengis.net/kml/2.2 "
3085 : "http://schemas.opengis.net/kml/2.2.0/ogckml22.xsd");
3086 : }
3087 :
3088 2 : CPLAddXMLChild(psAnnotation, CPLCloneXMLTree(psKMLFileRoot));
3089 : }
3090 : }
3091 :
3092 : // Add styles.
3093 23 : for (const auto &oStyle : aoStyles)
3094 : {
3095 7 : CPLXMLTreeCloser psStyle(CPLParseXMLFile(oStyle.osFile));
3096 7 : if (psStyle == nullptr)
3097 2 : continue;
3098 :
3099 5 : CPLXMLNode *psStyleRoot = GDALGMLJP2GetXMLRoot(psStyle.get());
3100 5 : if (psStyleRoot)
3101 : {
3102 4 : CPLXMLNode *psGMLJP2Style = nullptr;
3103 4 : if (oStyle.bParentCoverageCollection)
3104 : {
3105 : psGMLJP2Style =
3106 3 : CPLCreateXMLNode(psGMLJP2CoverageCollection,
3107 : CXT_Element, "gmljp2:style");
3108 : }
3109 : else
3110 : {
3111 1 : CPLXMLNode *psFeatureMemberOfGridCoverage = CPLGetXMLNode(
3112 : psGMLJP2CoverageCollection, "gmljp2:featureMember");
3113 1 : CPLAssert(psFeatureMemberOfGridCoverage);
3114 1 : CPLXMLNode *psGridCoverage =
3115 : psFeatureMemberOfGridCoverage->psChild;
3116 1 : CPLAssert(psGridCoverage);
3117 1 : psGMLJP2Style = CPLCreateXMLNode(
3118 : psGridCoverage, CXT_Element, "gmljp2:style");
3119 : }
3120 :
3121 : // Add dummy namespace for validation purposes if needed
3122 7 : if (strchr(psStyleRoot->pszValue, ':') == nullptr &&
3123 3 : CPLGetXMLValue(psStyleRoot, "xmlns", nullptr) == nullptr)
3124 : {
3125 2 : CPLSetXMLValue(psStyleRoot, "#xmlns",
3126 : "http://undefined_namespace");
3127 : }
3128 :
3129 4 : CPLAddXMLChild(psGMLJP2Style, CPLCloneXMLTree(psStyleRoot));
3130 : }
3131 : }
3132 :
3133 : // Add extensions.
3134 23 : for (const auto &oExt : aoExtensions)
3135 : {
3136 7 : CPLXMLTreeCloser psExtension(CPLParseXMLFile(oExt.osFile));
3137 7 : if (psExtension == nullptr)
3138 2 : continue;
3139 :
3140 : CPLXMLNode *psExtensionRoot =
3141 5 : GDALGMLJP2GetXMLRoot(psExtension.get());
3142 5 : if (psExtensionRoot)
3143 : {
3144 : CPLXMLNode *psGMLJP2Extension;
3145 4 : if (oExt.bParentCoverageCollection)
3146 : {
3147 : psGMLJP2Extension =
3148 3 : CPLCreateXMLNode(psGMLJP2CoverageCollection,
3149 : CXT_Element, "gmljp2:extension");
3150 : }
3151 : else
3152 : {
3153 1 : CPLXMLNode *psFeatureMemberOfGridCoverage = CPLGetXMLNode(
3154 : psGMLJP2CoverageCollection, "gmljp2:featureMember");
3155 1 : CPLAssert(psFeatureMemberOfGridCoverage);
3156 1 : CPLXMLNode *psGridCoverage =
3157 : psFeatureMemberOfGridCoverage->psChild;
3158 1 : CPLAssert(psGridCoverage);
3159 1 : psGMLJP2Extension = CPLCreateXMLNode(
3160 : psGridCoverage, CXT_Element, "gmljp2:extension");
3161 : }
3162 :
3163 : // Add dummy namespace for validation purposes if needed
3164 7 : if (strchr(psExtensionRoot->pszValue, ':') == nullptr &&
3165 3 : CPLGetXMLValue(psExtensionRoot, "xmlns", nullptr) ==
3166 : nullptr)
3167 : {
3168 2 : CPLSetXMLValue(psExtensionRoot, "#xmlns",
3169 : "http://undefined_namespace");
3170 : }
3171 :
3172 4 : CPLAddXMLChild(psGMLJP2Extension,
3173 : CPLCloneXMLTree(psExtensionRoot));
3174 : }
3175 : }
3176 :
3177 16 : char *pszRoot = CPLSerializeXMLTree(psRoot.get());
3178 16 : psRoot.reset();
3179 16 : osDoc = pszRoot;
3180 16 : CPLFree(pszRoot);
3181 16 : pszRoot = nullptr;
3182 : }
3183 :
3184 : /* -------------------------------------------------------------------- */
3185 : /* Setup the gml.data label. */
3186 : /* -------------------------------------------------------------------- */
3187 25 : std::vector<GDALJP2Box *> apoGMLBoxes;
3188 :
3189 25 : apoGMLBoxes.push_back(GDALJP2Box::CreateLblBox("gml.data"));
3190 :
3191 : /* -------------------------------------------------------------------- */
3192 : /* Setup gml.root-instance. */
3193 : /* -------------------------------------------------------------------- */
3194 25 : apoGMLBoxes.push_back(
3195 25 : GDALJP2Box::CreateLabelledXMLAssoc("gml.root-instance", osDoc));
3196 :
3197 : /* -------------------------------------------------------------------- */
3198 : /* Add optional dictionary. */
3199 : /* -------------------------------------------------------------------- */
3200 25 : if (!osDictBox.empty())
3201 0 : apoGMLBoxes.push_back(
3202 0 : GDALJP2Box::CreateLabelledXMLAssoc("CRSDictionary.gml", osDictBox));
3203 :
3204 : /* -------------------------------------------------------------------- */
3205 : /* Additional user specified boxes. */
3206 : /* -------------------------------------------------------------------- */
3207 35 : for (const auto &oBox : aoBoxes)
3208 : {
3209 10 : GByte *pabyContent = nullptr;
3210 10 : if (VSIIngestFile(nullptr, oBox.osFile, &pabyContent, nullptr, -1))
3211 : {
3212 : CPLXMLTreeCloser psNode(
3213 16 : CPLParseXMLString(reinterpret_cast<const char *>(pabyContent)));
3214 8 : CPLFree(pabyContent);
3215 8 : pabyContent = nullptr;
3216 8 : if (psNode.get())
3217 : {
3218 8 : CPLXMLNode *psRoot = GDALGMLJP2GetXMLRoot(psNode.get());
3219 8 : if (psRoot)
3220 : {
3221 8 : GDALGMLJP2PatchFeatureCollectionSubstitutionGroup(psRoot);
3222 8 : pabyContent =
3223 8 : reinterpret_cast<GByte *>(CPLSerializeXMLTree(psRoot));
3224 8 : apoGMLBoxes.push_back(GDALJP2Box::CreateLabelledXMLAssoc(
3225 : oBox.osLabel,
3226 : reinterpret_cast<const char *>(pabyContent)));
3227 : }
3228 : }
3229 : }
3230 10 : CPLFree(pabyContent);
3231 : }
3232 :
3233 : /* -------------------------------------------------------------------- */
3234 : /* Bundle gml.data boxes into an association. */
3235 : /* -------------------------------------------------------------------- */
3236 25 : GDALJP2Box *poGMLData = GDALJP2Box::CreateAsocBox(
3237 25 : static_cast<int>(apoGMLBoxes.size()), &apoGMLBoxes[0]);
3238 :
3239 : /* -------------------------------------------------------------------- */
3240 : /* Cleanup working boxes. */
3241 : /* -------------------------------------------------------------------- */
3242 83 : for (auto &poGMLBox : apoGMLBoxes)
3243 58 : delete poGMLBox;
3244 :
3245 25 : VSIRmdirRecursive(osTmpDir.c_str());
3246 :
3247 25 : return poGMLData;
3248 : }
3249 :
3250 : /************************************************************************/
3251 : /* CreateGDALMultiDomainMetadataXML() */
3252 : /************************************************************************/
3253 :
3254 : CPLXMLNode *
3255 26 : GDALJP2Metadata::CreateGDALMultiDomainMetadataXML(GDALDataset *poSrcDS,
3256 : int bMainMDDomainOnly)
3257 : {
3258 26 : GDALMultiDomainMetadata oLocalMDMD;
3259 26 : char **papszSrcMD = CSLDuplicate(poSrcDS->GetMetadata());
3260 : /* Remove useless metadata */
3261 26 : papszSrcMD = CSLSetNameValue(papszSrcMD, GDALMD_AREA_OR_POINT, nullptr);
3262 26 : papszSrcMD = CSLSetNameValue(papszSrcMD, "TIFFTAG_RESOLUTIONUNIT", nullptr);
3263 26 : papszSrcMD = CSLSetNameValue(papszSrcMD,
3264 : "TIFFTAG_XREpsMasterXMLNodeSOLUTION", nullptr);
3265 26 : papszSrcMD = CSLSetNameValue(papszSrcMD, "TIFFTAG_YRESOLUTION", nullptr);
3266 : papszSrcMD =
3267 26 : CSLSetNameValue(papszSrcMD, "Corder", nullptr); /* from JP2KAK */
3268 52 : if (poSrcDS->GetDriver() != nullptr &&
3269 26 : EQUAL(poSrcDS->GetDriver()->GetDescription(), "JP2ECW"))
3270 : {
3271 : papszSrcMD =
3272 0 : CSLSetNameValue(papszSrcMD, "COMPRESSION_RATE_TARGET", nullptr);
3273 0 : papszSrcMD = CSLSetNameValue(papszSrcMD, "COLORSPACE", nullptr);
3274 0 : papszSrcMD = CSLSetNameValue(papszSrcMD, "VERSION", nullptr);
3275 : }
3276 :
3277 26 : bool bHasMD = false;
3278 26 : if (papszSrcMD && *papszSrcMD)
3279 : {
3280 6 : bHasMD = true;
3281 6 : oLocalMDMD.SetMetadata(papszSrcMD);
3282 : }
3283 26 : CSLDestroy(papszSrcMD);
3284 :
3285 26 : if (!bMainMDDomainOnly)
3286 : {
3287 25 : char **papszMDList = poSrcDS->GetMetadataDomainList();
3288 87 : for (char **papszMDListIter = papszMDList;
3289 87 : papszMDListIter && *papszMDListIter; ++papszMDListIter)
3290 : {
3291 62 : if (!EQUAL(*papszMDListIter, "") &&
3292 56 : !EQUAL(*papszMDListIter, "IMAGE_STRUCTURE") &&
3293 40 : !EQUAL(*papszMDListIter, "DERIVED_SUBDATASETS") &&
3294 15 : !EQUAL(*papszMDListIter, "JPEG2000") &&
3295 15 : !STARTS_WITH_CI(*papszMDListIter, "xml:BOX_") &&
3296 12 : !EQUAL(*papszMDListIter, "xml:gml.root-instance") &&
3297 9 : !EQUAL(*papszMDListIter, "xml:XMP") &&
3298 6 : !EQUAL(*papszMDListIter, "xml:IPR"))
3299 : {
3300 4 : papszSrcMD = poSrcDS->GetMetadata(*papszMDListIter);
3301 4 : if (papszSrcMD && *papszSrcMD)
3302 : {
3303 4 : bHasMD = true;
3304 4 : oLocalMDMD.SetMetadata(papszSrcMD, *papszMDListIter);
3305 : }
3306 : }
3307 : }
3308 25 : CSLDestroy(papszMDList);
3309 : }
3310 :
3311 26 : CPLXMLNode *psMasterXMLNode = nullptr;
3312 26 : if (bHasMD)
3313 : {
3314 10 : CPLXMLNode *psXMLNode = oLocalMDMD.Serialize();
3315 : psMasterXMLNode =
3316 10 : CPLCreateXMLNode(nullptr, CXT_Element, "GDALMultiDomainMetadata");
3317 10 : psMasterXMLNode->psChild = psXMLNode;
3318 : }
3319 52 : return psMasterXMLNode;
3320 : }
3321 :
3322 : /************************************************************************/
3323 : /* CreateGDALMultiDomainMetadataXMLBox() */
3324 : /************************************************************************/
3325 :
3326 : GDALJP2Box *
3327 25 : GDALJP2Metadata::CreateGDALMultiDomainMetadataXMLBox(GDALDataset *poSrcDS,
3328 : int bMainMDDomainOnly)
3329 : {
3330 : CPLXMLTreeCloser psMasterXMLNode(
3331 50 : CreateGDALMultiDomainMetadataXML(poSrcDS, bMainMDDomainOnly));
3332 25 : if (psMasterXMLNode == nullptr)
3333 16 : return nullptr;
3334 9 : char *pszXML = CPLSerializeXMLTree(psMasterXMLNode.get());
3335 9 : psMasterXMLNode.reset();
3336 :
3337 9 : GDALJP2Box *poBox = new GDALJP2Box();
3338 9 : poBox->SetType("xml ");
3339 9 : poBox->SetWritableData(static_cast<int>(strlen(pszXML) + 1),
3340 : reinterpret_cast<const GByte *>(pszXML));
3341 9 : CPLFree(pszXML);
3342 :
3343 9 : return poBox;
3344 : }
3345 :
3346 : /************************************************************************/
3347 : /* WriteXMLBoxes() */
3348 : /************************************************************************/
3349 :
3350 25 : GDALJP2Box **GDALJP2Metadata::CreateXMLBoxes(GDALDataset *poSrcDS, int *pnBoxes)
3351 : {
3352 25 : GDALJP2Box **papoBoxes = nullptr;
3353 25 : *pnBoxes = 0;
3354 25 : char **papszMDList = poSrcDS->GetMetadataDomainList();
3355 87 : for (char **papszMDListIter = papszMDList;
3356 87 : papszMDListIter && *papszMDListIter; ++papszMDListIter)
3357 : {
3358 : /* Write metadata that look like originating from JP2 XML boxes */
3359 : /* as a standalone JP2 XML box */
3360 62 : if (STARTS_WITH_CI(*papszMDListIter, "xml:BOX_"))
3361 : {
3362 3 : char **papszSrcMD = poSrcDS->GetMetadata(*papszMDListIter);
3363 3 : if (papszSrcMD && *papszSrcMD)
3364 : {
3365 3 : GDALJP2Box *poBox = new GDALJP2Box();
3366 3 : poBox->SetType("xml ");
3367 3 : poBox->SetWritableData(
3368 3 : static_cast<int>(strlen(*papszSrcMD) + 1),
3369 : reinterpret_cast<const GByte *>(*papszSrcMD));
3370 6 : papoBoxes = static_cast<GDALJP2Box **>(CPLRealloc(
3371 3 : papoBoxes, sizeof(GDALJP2Box *) * (*pnBoxes + 1)));
3372 3 : papoBoxes[(*pnBoxes)++] = poBox;
3373 : }
3374 : }
3375 : }
3376 25 : CSLDestroy(papszMDList);
3377 25 : return papoBoxes;
3378 : }
3379 :
3380 : /************************************************************************/
3381 : /* CreateXMPBox() */
3382 : /************************************************************************/
3383 :
3384 25 : GDALJP2Box *GDALJP2Metadata::CreateXMPBox(GDALDataset *poSrcDS)
3385 : {
3386 25 : char **papszSrcMD = poSrcDS->GetMetadata("xml:XMP");
3387 25 : GDALJP2Box *poBox = nullptr;
3388 25 : if (papszSrcMD && *papszSrcMD)
3389 : {
3390 3 : poBox = GDALJP2Box::CreateUUIDBox(
3391 3 : xmp_uuid, static_cast<int>(strlen(*papszSrcMD) + 1),
3392 : reinterpret_cast<const GByte *>(*papszSrcMD));
3393 : }
3394 25 : return poBox;
3395 : }
3396 :
3397 : /************************************************************************/
3398 : /* CreateIPRBox() */
3399 : /************************************************************************/
3400 :
3401 19 : GDALJP2Box *GDALJP2Metadata::CreateIPRBox(GDALDataset *poSrcDS)
3402 : {
3403 19 : char **papszSrcMD = poSrcDS->GetMetadata("xml:IPR");
3404 19 : GDALJP2Box *poBox = nullptr;
3405 19 : if (papszSrcMD && *papszSrcMD)
3406 : {
3407 2 : poBox = new GDALJP2Box();
3408 2 : poBox->SetType("jp2i");
3409 2 : poBox->SetWritableData(static_cast<int>(strlen(*papszSrcMD) + 1),
3410 : reinterpret_cast<const GByte *>(*papszSrcMD));
3411 : }
3412 19 : return poBox;
3413 : }
3414 :
3415 : /************************************************************************/
3416 : /* IsUUID_MSI() */
3417 : /************************************************************************/
3418 :
3419 38 : int GDALJP2Metadata::IsUUID_MSI(const GByte *abyUUID)
3420 : {
3421 38 : return memcmp(abyUUID, msi_uuid2, 16) == 0;
3422 : }
3423 :
3424 : /************************************************************************/
3425 : /* IsUUID_XMP() */
3426 : /************************************************************************/
3427 :
3428 1 : int GDALJP2Metadata::IsUUID_XMP(const GByte *abyUUID)
3429 : {
3430 1 : return memcmp(abyUUID, xmp_uuid, 16) == 0;
3431 : }
3432 :
3433 : /*! @endcond */
|