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