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