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