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