1 : /******************************************************************************
2 : * $Id: gdaljp2metadata.cpp 24404 2012-05-11 19:44:08Z aboudreault $
3 : *
4 : * Project: GDAL
5 : * Purpose: GDALJP2Metadata - Read GeoTIFF and/or GML georef info.
6 : * Author: Frank Warmerdam, warmerdam@pobox.com
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
10 : *
11 : * Permission is hereby granted, free of charge, to any person obtaining a
12 : * copy of this software and associated documentation files (the "Software"),
13 : * to deal in the Software without restriction, including without limitation
14 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 : * and/or sell copies of the Software, and to permit persons to whom the
16 : * Software is furnished to do so, subject to the following conditions:
17 : *
18 : * The above copyright notice and this permission notice shall be included
19 : * in all copies or substantial portions of the Software.
20 : *
21 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 : * DEALINGS IN THE SOFTWARE.
28 : ****************************************************************************/
29 :
30 : #include "gdaljp2metadata.h"
31 : #include "cpl_string.h"
32 : #include "cpl_minixml.h"
33 : #include "ogr_spatialref.h"
34 : #include "ogr_geometry.h"
35 : #include "ogr_api.h"
36 : #include "gt_wkt_srs_for_gdal.h"
37 :
38 : CPL_CVSID("$Id: gdaljp2metadata.cpp 24404 2012-05-11 19:44:08Z aboudreault $");
39 :
40 : static const unsigned char msi_uuid2[16] =
41 : {0xb1,0x4b,0xf8,0xbd,0x08,0x3d,0x4b,0x43,
42 : 0xa5,0xae,0x8c,0xd7,0xd5,0xa6,0xce,0x03};
43 :
44 : static const unsigned char msig_uuid[16] =
45 : { 0x96,0xA9,0xF1,0xF1,0xDC,0x98,0x40,0x2D,
46 : 0xA7,0xAE,0xD6,0x8E,0x34,0x45,0x18,0x09 };
47 :
48 : static const unsigned char xmp_uuid[16] =
49 : { 0xBE,0x7A,0xCF,0xCB,0x97,0xA9,0x42,0xE8,
50 : 0x9C,0x71,0x99,0x94,0x91,0xE3,0xAF,0xAC};
51 :
52 : /************************************************************************/
53 : /* GDALJP2Metadata() */
54 : /************************************************************************/
55 :
56 180 : GDALJP2Metadata::GDALJP2Metadata()
57 :
58 : {
59 180 : pszProjection = NULL;
60 :
61 180 : nGCPCount = 0;
62 180 : pasGCPList = NULL;
63 :
64 180 : papszGMLMetadata = NULL;
65 180 : papszMetadata = NULL;
66 :
67 180 : nGeoTIFFSize = 0;
68 180 : pabyGeoTIFFData = NULL;
69 :
70 180 : nMSIGSize = 0;
71 180 : pabyMSIGData = NULL;
72 :
73 180 : pszXMPMetadata = NULL;
74 :
75 180 : bHaveGeoTransform = FALSE;
76 180 : adfGeoTransform[0] = 0.0;
77 180 : adfGeoTransform[1] = 1.0;
78 180 : adfGeoTransform[2] = 0.0;
79 180 : adfGeoTransform[3] = 0.0;
80 180 : adfGeoTransform[4] = 0.0;
81 180 : adfGeoTransform[5] = 1.0;
82 180 : }
83 :
84 : /************************************************************************/
85 : /* ~GDALJP2Metadata() */
86 : /************************************************************************/
87 :
88 180 : GDALJP2Metadata::~GDALJP2Metadata()
89 :
90 : {
91 180 : CPLFree( pszProjection );
92 180 : if( nGCPCount > 0 )
93 : {
94 7 : GDALDeinitGCPs( nGCPCount, pasGCPList );
95 7 : CPLFree( pasGCPList );
96 : }
97 :
98 180 : CPLFree( pabyGeoTIFFData );
99 180 : CPLFree( pabyMSIGData );
100 180 : CSLDestroy( papszGMLMetadata );
101 180 : CSLDestroy( papszMetadata );
102 180 : CPLFree( pszXMPMetadata );
103 180 : }
104 :
105 : /************************************************************************/
106 : /* ReadAndParse() */
107 : /* */
108 : /* Read a JP2 file and try to collect georeferencing */
109 : /* information from the various available forms. Returns TRUE */
110 : /* if anything useful is found. */
111 : /************************************************************************/
112 :
113 143 : int GDALJP2Metadata::ReadAndParse( const char *pszFilename )
114 :
115 : {
116 : VSILFILE *fpLL;
117 :
118 143 : fpLL = VSIFOpenL( pszFilename, "rb" );
119 :
120 143 : if( fpLL == NULL )
121 : {
122 : CPLDebug( "GDALJP2Metadata", "Could not even open %s.",
123 0 : pszFilename );
124 :
125 0 : return FALSE;
126 : }
127 :
128 143 : ReadBoxes( fpLL );
129 143 : VSIFCloseL( fpLL );
130 :
131 : /* -------------------------------------------------------------------- */
132 : /* Try JP2GeoTIFF, GML and finally MSIG to get something. */
133 : /* -------------------------------------------------------------------- */
134 143 : if( !ParseJP2GeoTIFF() && !ParseGMLCoverageDesc() )
135 65 : ParseMSIG();
136 :
137 : /* -------------------------------------------------------------------- */
138 : /* If we still don't have a geotransform, look for a world */
139 : /* file. */
140 : /* -------------------------------------------------------------------- */
141 143 : if( !bHaveGeoTransform )
142 : {
143 : bHaveGeoTransform =
144 : GDALReadWorldFile( pszFilename, NULL, adfGeoTransform )
145 75 : || GDALReadWorldFile( pszFilename, ".wld", adfGeoTransform );
146 : }
147 :
148 : /* -------------------------------------------------------------------- */
149 : /* Return success either either of projection or geotransform */
150 : /* or gcps. */
151 : /* -------------------------------------------------------------------- */
152 : return bHaveGeoTransform
153 : || nGCPCount > 0
154 143 : || (pszProjection != NULL && strlen(pszProjection) > 0);
155 : }
156 :
157 : /************************************************************************/
158 : /* CollectGMLData() */
159 : /* */
160 : /* Read all the asoc boxes after this node, and store the */
161 : /* contain xml documents along with the name from the label. */
162 : /************************************************************************/
163 :
164 27 : void GDALJP2Metadata::CollectGMLData( GDALJP2Box *poGMLData )
165 :
166 : {
167 27 : GDALJP2Box oChildBox( poGMLData->GetFILE() );
168 :
169 27 : oChildBox.ReadFirstChild( poGMLData );
170 :
171 108 : while( strlen(oChildBox.GetType()) > 0 )
172 : {
173 54 : if( EQUAL(oChildBox.GetType(),"asoc") )
174 : {
175 27 : GDALJP2Box oSubChildBox( oChildBox.GetFILE() );
176 :
177 27 : char *pszLabel = NULL;
178 27 : char *pszXML = NULL;
179 :
180 27 : oSubChildBox.ReadFirstChild( &oChildBox );
181 :
182 108 : while( strlen(oSubChildBox.GetType()) > 0 )
183 : {
184 54 : if( EQUAL(oSubChildBox.GetType(),"lbl ") )
185 27 : pszLabel = (char *)oSubChildBox.ReadBoxData();
186 27 : else if( EQUAL(oSubChildBox.GetType(),"xml ") )
187 27 : pszXML = (char *) oSubChildBox.ReadBoxData();
188 :
189 54 : oSubChildBox.ReadNextChild( &oChildBox );
190 : }
191 :
192 27 : if( pszLabel != NULL && pszXML != NULL )
193 : papszGMLMetadata = CSLSetNameValue( papszGMLMetadata,
194 27 : pszLabel, pszXML );
195 27 : CPLFree( pszLabel );
196 27 : CPLFree( pszXML );
197 : }
198 :
199 54 : oChildBox.ReadNextChild( poGMLData );
200 27 : }
201 27 : }
202 :
203 : /************************************************************************/
204 : /* ReadBoxes() */
205 : /************************************************************************/
206 :
207 143 : int GDALJP2Metadata::ReadBoxes( VSILFILE *fpVSIL )
208 :
209 : {
210 143 : GDALJP2Box oBox( fpVSIL );
211 143 : int iBox = 0;
212 :
213 143 : if (!oBox.ReadFirst())
214 0 : return FALSE;
215 :
216 679 : while( strlen(oBox.GetType()) > 0 )
217 : {
218 : #ifdef DEBUG
219 487 : if (CSLTestBoolean(CPLGetConfigOption("DUMP_JP2_BOXES", "NO")))
220 0 : oBox.DumpReadable(stderr);
221 : #endif
222 :
223 : /* -------------------------------------------------------------------- */
224 : /* Collect geotiff box. */
225 : /* -------------------------------------------------------------------- */
226 487 : if( EQUAL(oBox.GetType(),"uuid")
227 : && memcmp( oBox.GetUUID(), msi_uuid2, 16 ) == 0 )
228 : {
229 74 : nGeoTIFFSize = (int) oBox.GetDataLength();
230 74 : pabyGeoTIFFData = oBox.ReadBoxData();
231 74 : if (pabyGeoTIFFData == NULL)
232 : {
233 0 : CPLDebug("GDALJP2", "Cannot read data for UUID GeoTIFF box");
234 0 : nGeoTIFFSize = 0;
235 : }
236 : }
237 :
238 : /* -------------------------------------------------------------------- */
239 : /* Collect MSIG box. */
240 : /* -------------------------------------------------------------------- */
241 487 : if( EQUAL(oBox.GetType(),"uuid")
242 : && memcmp( oBox.GetUUID(), msig_uuid, 16 ) == 0 )
243 : {
244 0 : nMSIGSize = (int) oBox.GetDataLength();
245 0 : pabyMSIGData = oBox.ReadBoxData();
246 :
247 0 : if( nMSIGSize < 70
248 : || pabyMSIGData == NULL
249 : || memcmp( pabyMSIGData, "MSIG/", 5 ) != 0 )
250 : {
251 0 : CPLFree( pabyMSIGData );
252 0 : pabyMSIGData = NULL;
253 0 : nMSIGSize = 0;
254 : }
255 : }
256 :
257 : /* -------------------------------------------------------------------- */
258 : /* Collect XMP box. */
259 : /* -------------------------------------------------------------------- */
260 487 : if( EQUAL(oBox.GetType(),"uuid")
261 : && memcmp( oBox.GetUUID(), xmp_uuid, 16 ) == 0 &&
262 : pszXMPMetadata == NULL )
263 : {
264 4 : pszXMPMetadata = (char*) oBox.ReadBoxData();
265 : }
266 :
267 : /* -------------------------------------------------------------------- */
268 : /* Process asoc box looking for Labelled GML data. */
269 : /* -------------------------------------------------------------------- */
270 487 : if( EQUAL(oBox.GetType(),"asoc") )
271 : {
272 27 : GDALJP2Box oSubBox( fpVSIL );
273 :
274 27 : oSubBox.ReadFirstChild( &oBox );
275 27 : if( EQUAL(oSubBox.GetType(),"lbl ") )
276 : {
277 27 : char *pszLabel = (char *) oSubBox.ReadBoxData();
278 27 : if( pszLabel != NULL && EQUAL(pszLabel,"gml.data") )
279 : {
280 27 : CollectGMLData( &oBox );
281 : }
282 27 : CPLFree( pszLabel );
283 27 : }
284 : }
285 :
286 : /* -------------------------------------------------------------------- */
287 : /* Process simple xml boxes. */
288 : /* -------------------------------------------------------------------- */
289 487 : if( EQUAL(oBox.GetType(),"xml ") )
290 : {
291 6 : CPLString osBoxName;
292 6 : char *pszXML = (char *) oBox.ReadBoxData();
293 :
294 6 : osBoxName.Printf( "BOX_%d", iBox++ );
295 :
296 : papszGMLMetadata = CSLSetNameValue( papszGMLMetadata,
297 6 : osBoxName, pszXML );
298 6 : CPLFree( pszXML );
299 : }
300 :
301 : /* -------------------------------------------------------------------- */
302 : /* Check for a resd box in jp2h. */
303 : /* -------------------------------------------------------------------- */
304 487 : if( EQUAL(oBox.GetType(),"jp2h") )
305 : {
306 94 : GDALJP2Box oSubBox( fpVSIL );
307 :
308 327 : for( oSubBox.ReadFirstChild( &oBox );
309 : strlen(oSubBox.GetType()) > 0;
310 : oSubBox.ReadNextChild( &oBox ) )
311 : {
312 233 : if( EQUAL(oSubBox.GetType(),"res ") )
313 : {
314 4 : GDALJP2Box oResBox( fpVSIL );
315 :
316 4 : oResBox.ReadFirstChild( &oSubBox );
317 :
318 : // we will use either the resd or resc box, which ever
319 : // happens to be first. Should we prefer resd?
320 4 : unsigned char *pabyResData = NULL;
321 4 : if( oResBox.GetDataLength() == 10 &&
322 : (pabyResData = oResBox.ReadBoxData()) != NULL )
323 : {
324 : int nVertNum, nVertDen, nVertExp;
325 : int nHorzNum, nHorzDen, nHorzExp;
326 :
327 4 : nVertNum = pabyResData[0] * 256 + pabyResData[1];
328 4 : nVertDen = pabyResData[2] * 256 + pabyResData[3];
329 4 : nHorzNum = pabyResData[4] * 256 + pabyResData[5];
330 4 : nHorzDen = pabyResData[6] * 256 + pabyResData[7];
331 4 : nVertExp = pabyResData[8];
332 4 : nHorzExp = pabyResData[9];
333 :
334 : // compute in pixels/cm
335 : double dfVertRes =
336 4 : (nVertNum/(double)nVertDen) * pow(10.0,nVertExp)/100;
337 : double dfHorzRes =
338 4 : (nHorzNum/(double)nHorzDen) * pow(10.0,nHorzExp)/100;
339 4 : CPLString osFormatter;
340 :
341 : papszMetadata = CSLSetNameValue(
342 : papszMetadata,
343 : "TIFFTAG_XRESOLUTION",
344 4 : osFormatter.Printf("%g",dfHorzRes) );
345 :
346 : papszMetadata = CSLSetNameValue(
347 : papszMetadata,
348 : "TIFFTAG_YRESOLUTION",
349 4 : osFormatter.Printf("%g",dfVertRes) );
350 : papszMetadata = CSLSetNameValue(
351 : papszMetadata,
352 : "TIFFTAG_RESOLUTIONUNIT",
353 4 : "3 (pixels/cm)" );
354 :
355 4 : CPLFree( pabyResData );
356 4 : }
357 : }
358 94 : }
359 : }
360 :
361 487 : if (!oBox.ReadNext())
362 94 : break;
363 : }
364 :
365 143 : return TRUE;
366 : }
367 :
368 : /************************************************************************/
369 : /* ParseJP2GeoTIFF() */
370 : /************************************************************************/
371 :
372 143 : int GDALJP2Metadata::ParseJP2GeoTIFF()
373 :
374 : {
375 143 : if( nGeoTIFFSize < 1 )
376 69 : return FALSE;
377 :
378 : /* -------------------------------------------------------------------- */
379 : /* Convert raw data into projection and geotransform. */
380 : /* -------------------------------------------------------------------- */
381 74 : int bSuccess = TRUE;
382 :
383 74 : if( GTIFWktFromMemBuf( nGeoTIFFSize, pabyGeoTIFFData,
384 : &pszProjection, adfGeoTransform,
385 : &nGCPCount, &pasGCPList ) != CE_None )
386 : {
387 0 : bSuccess = FALSE;
388 : }
389 :
390 74 : if( pszProjection == NULL || strlen(pszProjection) == 0 )
391 0 : bSuccess = FALSE;
392 :
393 74 : if( bSuccess )
394 : CPLDebug( "GDALJP2Metadata",
395 : "Got projection from GeoJP2 (geotiff) box: %s",
396 74 : pszProjection );
397 :
398 124 : if( adfGeoTransform[0] != 0
399 10 : || adfGeoTransform[1] != 1
400 10 : || adfGeoTransform[2] != 0
401 10 : || adfGeoTransform[3] != 0
402 10 : || adfGeoTransform[4] != 0
403 10 : || adfGeoTransform[5] != 1 )
404 64 : bHaveGeoTransform = TRUE;
405 :
406 74 : return bSuccess;;
407 : }
408 :
409 : /************************************************************************/
410 : /* ParseMSIG() */
411 : /************************************************************************/
412 :
413 65 : int GDALJP2Metadata::ParseMSIG()
414 :
415 : {
416 65 : if( nMSIGSize < 70 )
417 65 : return FALSE;
418 :
419 : /* -------------------------------------------------------------------- */
420 : /* Try and extract worldfile parameters and adjust. */
421 : /* -------------------------------------------------------------------- */
422 0 : memcpy( adfGeoTransform + 0, pabyMSIGData + 22 + 8 * 4, 8 );
423 0 : memcpy( adfGeoTransform + 1, pabyMSIGData + 22 + 8 * 0, 8 );
424 0 : memcpy( adfGeoTransform + 2, pabyMSIGData + 22 + 8 * 2, 8 );
425 0 : memcpy( adfGeoTransform + 3, pabyMSIGData + 22 + 8 * 5, 8 );
426 0 : memcpy( adfGeoTransform + 4, pabyMSIGData + 22 + 8 * 1, 8 );
427 0 : memcpy( adfGeoTransform + 5, pabyMSIGData + 22 + 8 * 3, 8 );
428 :
429 : // data is in LSB (little endian) order in file.
430 : CPL_LSBPTR64( adfGeoTransform + 0 );
431 : CPL_LSBPTR64( adfGeoTransform + 1 );
432 : CPL_LSBPTR64( adfGeoTransform + 2 );
433 : CPL_LSBPTR64( adfGeoTransform + 3 );
434 : CPL_LSBPTR64( adfGeoTransform + 4 );
435 : CPL_LSBPTR64( adfGeoTransform + 5 );
436 :
437 : // correct for center of pixel vs. top left of pixel
438 0 : adfGeoTransform[0] -= 0.5 * adfGeoTransform[1];
439 0 : adfGeoTransform[0] -= 0.5 * adfGeoTransform[2];
440 0 : adfGeoTransform[3] -= 0.5 * adfGeoTransform[4];
441 0 : adfGeoTransform[3] -= 0.5 * adfGeoTransform[5];
442 :
443 0 : bHaveGeoTransform = TRUE;
444 :
445 0 : return TRUE;
446 : }
447 :
448 : /************************************************************************/
449 : /* GetDictionaryItem() */
450 : /************************************************************************/
451 :
452 : static CPLXMLNode *
453 0 : GetDictionaryItem( char **papszGMLMetadata, const char *pszURN )
454 :
455 : {
456 : char *pszLabel;
457 0 : const char *pszFragmentId = NULL;
458 : int i;
459 :
460 :
461 0 : if( EQUALN(pszURN,"urn:jp2k:xml:", 13) )
462 0 : pszLabel = CPLStrdup( pszURN + 13 );
463 0 : else if( EQUALN(pszURN,"urn:ogc:tc:gmljp2:xml:", 22) )
464 0 : pszLabel = CPLStrdup( pszURN + 22 );
465 0 : else if( EQUALN(pszURN,"gmljp2://xml/",13) )
466 0 : pszLabel = CPLStrdup( pszURN + 13 );
467 : else
468 0 : pszLabel = CPLStrdup( pszURN );
469 :
470 : /* -------------------------------------------------------------------- */
471 : /* Split out label and fragment id. */
472 : /* -------------------------------------------------------------------- */
473 0 : for( i = 0; pszLabel[i] != '#'; i++ )
474 : {
475 0 : if( pszLabel[i] == '\0' )
476 0 : return NULL;
477 : }
478 :
479 0 : pszFragmentId = pszLabel + i + 1;
480 0 : pszLabel[i] = '\0';
481 :
482 : /* -------------------------------------------------------------------- */
483 : /* Can we find an XML box with the desired label? */
484 : /* -------------------------------------------------------------------- */
485 : const char *pszDictionary =
486 0 : CSLFetchNameValue( papszGMLMetadata, pszLabel );
487 :
488 0 : if( pszDictionary == NULL )
489 0 : return NULL;
490 :
491 : /* -------------------------------------------------------------------- */
492 : /* Try and parse the dictionary. */
493 : /* -------------------------------------------------------------------- */
494 0 : CPLXMLNode *psDictTree = CPLParseXMLString( pszDictionary );
495 :
496 0 : if( psDictTree == NULL )
497 : {
498 0 : CPLDestroyXMLNode( psDictTree );
499 0 : return NULL;
500 : }
501 :
502 0 : CPLStripXMLNamespace( psDictTree, NULL, TRUE );
503 :
504 0 : CPLXMLNode *psDictRoot = CPLSearchXMLNode( psDictTree, "=Dictionary" );
505 :
506 0 : if( psDictRoot == NULL )
507 : {
508 0 : CPLDestroyXMLNode( psDictTree );
509 0 : return NULL;
510 : }
511 :
512 : /* -------------------------------------------------------------------- */
513 : /* Search for matching id. */
514 : /* -------------------------------------------------------------------- */
515 0 : CPLXMLNode *psEntry, *psHit = NULL;
516 0 : for( psEntry = psDictRoot->psChild;
517 : psEntry != NULL && psHit == NULL;
518 : psEntry = psEntry->psNext )
519 : {
520 : const char *pszId;
521 :
522 0 : if( psEntry->eType != CXT_Element )
523 0 : continue;
524 :
525 0 : if( !EQUAL(psEntry->pszValue,"dictionaryEntry") )
526 0 : continue;
527 :
528 0 : if( psEntry->psChild == NULL )
529 0 : continue;
530 :
531 0 : pszId = CPLGetXMLValue( psEntry->psChild, "id", "" );
532 :
533 0 : if( EQUAL(pszId, pszFragmentId) )
534 0 : psHit = CPLCloneXMLTree( psEntry->psChild );
535 : }
536 :
537 : /* -------------------------------------------------------------------- */
538 : /* Cleanup */
539 : /* -------------------------------------------------------------------- */
540 0 : CPLFree( pszLabel );
541 0 : CPLDestroyXMLNode( psDictTree );
542 :
543 0 : return psHit;
544 : }
545 :
546 :
547 : /************************************************************************/
548 : /* GMLSRSLookup() */
549 : /* */
550 : /* Lookup an SRS in a dictionary inside this file. We will get */
551 : /* something like: */
552 : /* urn:jp2k:xml:CRSDictionary.xml#crs1112 */
553 : /* */
554 : /* We need to split the filename from the fragment id, and */
555 : /* lookup the fragment in the file if we can find it our */
556 : /* list of labelled xml boxes. */
557 : /************************************************************************/
558 :
559 0 : int GDALJP2Metadata::GMLSRSLookup( const char *pszURN )
560 :
561 : {
562 0 : CPLXMLNode *psDictEntry = GetDictionaryItem( papszGMLMetadata, pszURN );
563 :
564 0 : if( psDictEntry == NULL )
565 0 : return FALSE;
566 :
567 : /* -------------------------------------------------------------------- */
568 : /* Reserialize this fragment. */
569 : /* -------------------------------------------------------------------- */
570 0 : char *pszDictEntryXML = CPLSerializeXMLTree( psDictEntry );
571 0 : CPLDestroyXMLNode( psDictEntry );
572 :
573 : /* -------------------------------------------------------------------- */
574 : /* Try to convert into an OGRSpatialReference. */
575 : /* -------------------------------------------------------------------- */
576 0 : OGRSpatialReference oSRS;
577 0 : int bSuccess = FALSE;
578 :
579 0 : if( oSRS.importFromXML( pszDictEntryXML ) == OGRERR_NONE )
580 : {
581 0 : CPLFree( pszProjection );
582 0 : pszProjection = NULL;
583 :
584 0 : oSRS.exportToWkt( &pszProjection );
585 0 : bSuccess = TRUE;
586 : }
587 :
588 0 : CPLFree( pszDictEntryXML );
589 :
590 0 : return bSuccess;
591 : }
592 :
593 : /************************************************************************/
594 : /* ParseGMLCoverageDesc() */
595 : /************************************************************************/
596 :
597 69 : int GDALJP2Metadata::ParseGMLCoverageDesc()
598 :
599 : {
600 : /* -------------------------------------------------------------------- */
601 : /* Do we have an XML doc that is apparently a coverage */
602 : /* description? */
603 : /* -------------------------------------------------------------------- */
604 : const char *pszCoverage = CSLFetchNameValue( papszGMLMetadata,
605 69 : "gml.root-instance" );
606 :
607 69 : if( pszCoverage == NULL )
608 65 : return FALSE;
609 :
610 4 : CPLDebug( "GDALJP2Metadata", "Found GML Box:\n%s", pszCoverage );
611 :
612 : /* -------------------------------------------------------------------- */
613 : /* Try parsing the XML. Wipe any namespace prefixes. */
614 : /* -------------------------------------------------------------------- */
615 4 : CPLXMLNode *psXML = CPLParseXMLString( pszCoverage );
616 :
617 4 : if( psXML == NULL )
618 0 : return FALSE;
619 :
620 4 : CPLStripXMLNamespace( psXML, NULL, TRUE );
621 :
622 : /* -------------------------------------------------------------------- */
623 : /* Isolate RectifiedGrid. Eventually we will need to support */
624 : /* other georeferencing objects. */
625 : /* -------------------------------------------------------------------- */
626 4 : CPLXMLNode *psRG = CPLSearchXMLNode( psXML, "=RectifiedGrid" );
627 4 : CPLXMLNode *psOriginPoint = NULL;
628 4 : const char *pszOffset1=NULL, *pszOffset2=NULL;
629 :
630 4 : if( psRG != NULL )
631 : {
632 4 : psOriginPoint = CPLGetXMLNode( psRG, "origin.Point" );
633 :
634 :
635 4 : CPLXMLNode *psOffset1 = CPLGetXMLNode( psRG, "offsetVector" );
636 4 : if( psOffset1 != NULL )
637 : {
638 4 : pszOffset1 = CPLGetXMLValue( psOffset1, "", NULL );
639 : pszOffset2 = CPLGetXMLValue( psOffset1->psNext, "=offsetVector",
640 4 : NULL );
641 : }
642 : }
643 :
644 : /* -------------------------------------------------------------------- */
645 : /* If we are missing any of the origin or 2 offsets then give up. */
646 : /* -------------------------------------------------------------------- */
647 4 : if( psOriginPoint == NULL || pszOffset1 == NULL || pszOffset2 == NULL )
648 : {
649 0 : CPLDestroyXMLNode( psXML );
650 0 : return FALSE;
651 : }
652 :
653 : /* -------------------------------------------------------------------- */
654 : /* Extract origin location. */
655 : /* -------------------------------------------------------------------- */
656 4 : OGRPoint *poOriginGeometry = NULL;
657 4 : const char *pszSRSName = NULL;
658 :
659 4 : if( psOriginPoint != NULL )
660 : {
661 : poOriginGeometry = (OGRPoint *)
662 4 : OGR_G_CreateFromGMLTree( psOriginPoint );
663 :
664 8 : if( poOriginGeometry != NULL
665 4 : && wkbFlatten(poOriginGeometry->getGeometryType()) != wkbPoint )
666 : {
667 0 : delete poOriginGeometry;
668 0 : poOriginGeometry = NULL;
669 : }
670 :
671 : // SRS?
672 4 : pszSRSName = CPLGetXMLValue( psOriginPoint, "srsName", NULL );
673 : }
674 :
675 : /* -------------------------------------------------------------------- */
676 : /* Extract offset(s) */
677 : /* -------------------------------------------------------------------- */
678 4 : char **papszOffset1Tokens = NULL;
679 4 : char **papszOffset2Tokens = NULL;
680 4 : int bSuccess = FALSE;
681 :
682 : papszOffset1Tokens =
683 4 : CSLTokenizeStringComplex( pszOffset1, " ,", FALSE, FALSE );
684 : papszOffset2Tokens =
685 4 : CSLTokenizeStringComplex( pszOffset2, " ,", FALSE, FALSE );
686 :
687 4 : if( CSLCount(papszOffset1Tokens) >= 2
688 : && CSLCount(papszOffset2Tokens) >= 2
689 : && poOriginGeometry != NULL )
690 : {
691 4 : adfGeoTransform[0] = poOriginGeometry->getX();
692 4 : adfGeoTransform[1] = atof(papszOffset1Tokens[0]);
693 4 : adfGeoTransform[2] = atof(papszOffset2Tokens[0]);
694 4 : adfGeoTransform[3] = poOriginGeometry->getY();
695 4 : adfGeoTransform[4] = atof(papszOffset1Tokens[1]);
696 4 : adfGeoTransform[5] = atof(papszOffset2Tokens[1]);
697 :
698 : // offset from center of pixel.
699 4 : adfGeoTransform[0] -= adfGeoTransform[1]*0.5;
700 4 : adfGeoTransform[0] -= adfGeoTransform[2]*0.5;
701 4 : adfGeoTransform[3] -= adfGeoTransform[4]*0.5;
702 4 : adfGeoTransform[3] -= adfGeoTransform[5]*0.5;
703 :
704 4 : bSuccess = TRUE;
705 4 : bHaveGeoTransform = TRUE;
706 : }
707 :
708 4 : CSLDestroy( papszOffset1Tokens );
709 4 : CSLDestroy( papszOffset2Tokens );
710 :
711 4 : if( poOriginGeometry != NULL )
712 4 : delete poOriginGeometry;
713 :
714 : /* -------------------------------------------------------------------- */
715 : /* If we still don't have an srsName, check for it on the */
716 : /* boundedBy Envelope. Some products */
717 : /* (ie. EuropeRasterTile23.jpx) use this as the only srsName */
718 : /* delivery vehicle. */
719 : /* -------------------------------------------------------------------- */
720 4 : if( pszSRSName == NULL )
721 : {
722 : pszSRSName =
723 : CPLGetXMLValue( psXML,
724 : "=FeatureCollection.boundedBy.Envelope.srsName",
725 4 : NULL );
726 : }
727 :
728 : /* -------------------------------------------------------------------- */
729 : /* If we have gotten a geotransform, then try to interprete the */
730 : /* srsName. */
731 : /* -------------------------------------------------------------------- */
732 4 : int bNeedAxisFlip = FALSE;
733 :
734 4 : if( bSuccess && pszSRSName != NULL
735 : && (pszProjection == NULL || strlen(pszProjection) == 0) )
736 : {
737 4 : OGRSpatialReference oSRS;
738 :
739 4 : if( EQUALN(pszSRSName,"epsg:",5) )
740 : {
741 0 : if( oSRS.SetFromUserInput( pszSRSName ) == OGRERR_NONE )
742 0 : oSRS.exportToWkt( &pszProjection );
743 : }
744 4 : else if( EQUALN(pszSRSName,"urn:",4)
745 : && strstr(pszSRSName,":def:") != NULL
746 : && oSRS.importFromURN(pszSRSName) == OGRERR_NONE )
747 : {
748 4 : const char *pszCode = strrchr(pszSRSName,':') + 1;
749 :
750 4 : oSRS.exportToWkt( &pszProjection );
751 :
752 : // Per #2131
753 4 : if( atoi(pszCode) >= 4000 && atoi(pszCode) <= 4999 )
754 : {
755 : CPLDebug( "GMLJP2", "Request axis flip for SRS=%s",
756 4 : pszSRSName );
757 4 : bNeedAxisFlip = TRUE;
758 : }
759 : }
760 0 : else if( !GMLSRSLookup( pszSRSName ) )
761 : {
762 : CPLDebug( "GDALJP2Metadata",
763 : "Unable to evaluate SRSName=%s",
764 0 : pszSRSName );
765 4 : }
766 : }
767 :
768 4 : if( pszProjection )
769 : CPLDebug( "GDALJP2Metadata",
770 : "Got projection from GML box: %s",
771 4 : pszProjection );
772 :
773 4 : CPLDestroyXMLNode( psXML );
774 4 : psXML = NULL;
775 :
776 : /* -------------------------------------------------------------------- */
777 : /* Do we need to flip the axes? */
778 : /* -------------------------------------------------------------------- */
779 4 : if( bNeedAxisFlip
780 : && CSLTestBoolean( CPLGetConfigOption( "GDAL_IGNORE_AXIS_ORIENTATION",
781 : "FALSE" ) ) )
782 : {
783 0 : bNeedAxisFlip = FALSE;
784 0 : CPLDebug( "GMLJP2", "Supressed axis flipping based on GDAL_IGNORE_AXIS_ORIENTATION." );
785 : }
786 :
787 4 : if( bNeedAxisFlip )
788 : {
789 : double dfTemp;
790 :
791 : CPLDebug( "GMLJP2",
792 4 : "Flipping axis orientation in GMLJP2 coverage description." );
793 :
794 4 : dfTemp = adfGeoTransform[0];
795 4 : adfGeoTransform[0] = adfGeoTransform[3];
796 4 : adfGeoTransform[3] = dfTemp;
797 :
798 4 : int swapWith1Index = 4;
799 4 : int swapWith2Index = 5;
800 :
801 4 : if( CSLTestBoolean( CPLGetConfigOption( "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER",
802 : "FALSE" ) ) )
803 : {
804 0 : swapWith1Index = 5;
805 0 : swapWith2Index = 4;
806 : CPLDebug( "GMLJP2", "Choosing alternate GML \"<offsetVector>\" order based on "
807 0 : "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER." );
808 : }
809 :
810 4 : dfTemp = adfGeoTransform[1];
811 4 : adfGeoTransform[1] = adfGeoTransform[swapWith1Index];
812 4 : adfGeoTransform[swapWith1Index] = dfTemp;
813 :
814 4 : dfTemp = adfGeoTransform[2];
815 4 : adfGeoTransform[2] = adfGeoTransform[swapWith2Index];
816 4 : adfGeoTransform[swapWith2Index] = dfTemp;
817 : }
818 :
819 4 : return pszProjection != NULL && bSuccess;
820 : }
821 :
822 : /************************************************************************/
823 : /* SetProjection() */
824 : /************************************************************************/
825 :
826 35 : void GDALJP2Metadata::SetProjection( const char *pszWKT )
827 :
828 : {
829 35 : CPLFree( pszProjection );
830 35 : pszProjection = CPLStrdup(pszWKT);
831 35 : }
832 :
833 : /************************************************************************/
834 : /* SetGCPs() */
835 : /************************************************************************/
836 :
837 24 : void GDALJP2Metadata::SetGCPs( int nCount, const GDAL_GCP *pasGCPsIn )
838 :
839 : {
840 24 : if( nGCPCount > 0 )
841 : {
842 0 : GDALDeinitGCPs( nGCPCount, pasGCPList );
843 0 : CPLFree( pasGCPList );
844 : }
845 :
846 24 : nGCPCount = nCount;
847 24 : pasGCPList = GDALDuplicateGCPs(nGCPCount, pasGCPsIn);
848 24 : }
849 :
850 : /************************************************************************/
851 : /* SetGeoTransform() */
852 : /************************************************************************/
853 :
854 35 : void GDALJP2Metadata::SetGeoTransform( double *padfGT )
855 :
856 : {
857 35 : memcpy( adfGeoTransform, padfGT, sizeof(double) * 6 );
858 35 : }
859 :
860 : /************************************************************************/
861 : /* CreateJP2GeoTIFF() */
862 : /************************************************************************/
863 :
864 32 : GDALJP2Box *GDALJP2Metadata::CreateJP2GeoTIFF()
865 :
866 : {
867 : /* -------------------------------------------------------------------- */
868 : /* Prepare the memory buffer containing the degenerate GeoTIFF */
869 : /* file. */
870 : /* -------------------------------------------------------------------- */
871 32 : int nGTBufSize = 0;
872 32 : unsigned char *pabyGTBuf = NULL;
873 :
874 32 : if( GTIFMemBufFromWkt( pszProjection, adfGeoTransform,
875 : nGCPCount, pasGCPList,
876 : &nGTBufSize, &pabyGTBuf ) != CE_None )
877 0 : return NULL;
878 :
879 32 : if( nGTBufSize == 0 )
880 0 : return NULL;
881 :
882 : /* -------------------------------------------------------------------- */
883 : /* Write to a box on the JP2 file. */
884 : /* -------------------------------------------------------------------- */
885 : GDALJP2Box *poBox;
886 :
887 32 : poBox = GDALJP2Box::CreateUUIDBox( msi_uuid2, nGTBufSize, pabyGTBuf );
888 :
889 32 : CPLFree( pabyGTBuf );
890 :
891 32 : return poBox;
892 : }
893 :
894 : /************************************************************************/
895 : /* PrepareCoverageBox() */
896 : /************************************************************************/
897 :
898 23 : GDALJP2Box *GDALJP2Metadata::CreateGMLJP2( int nXSize, int nYSize )
899 :
900 : {
901 : /* -------------------------------------------------------------------- */
902 : /* This is a backdoor to let us embed a literal gmljp2 chunk */
903 : /* supplied by the user as an external file. This is mostly */
904 : /* for preparing test files with exotic contents. */
905 : /* -------------------------------------------------------------------- */
906 23 : if( CPLGetConfigOption( "GMLJP2OVERRIDE", NULL ) != NULL )
907 : {
908 0 : VSILFILE *fp = VSIFOpenL( CPLGetConfigOption( "GMLJP2OVERRIDE",""), "r" );
909 0 : char *pszGML = NULL;
910 :
911 0 : if( fp == NULL )
912 : {
913 : CPLError( CE_Failure, CPLE_AppDefined,
914 0 : "Unable to open GMLJP2OVERRIDE file." );
915 0 : return NULL;
916 : }
917 :
918 0 : VSIFSeekL( fp, 0, SEEK_END );
919 0 : int nLength = (int) VSIFTellL( fp );
920 0 : pszGML = (char *) CPLCalloc(1,nLength+1);
921 0 : VSIFSeekL( fp, 0, SEEK_SET );
922 0 : VSIFReadL( pszGML, 1, nLength, fp );
923 0 : VSIFCloseL( fp );
924 :
925 : GDALJP2Box *apoGMLBoxes[2];
926 :
927 0 : apoGMLBoxes[0] = GDALJP2Box::CreateLblBox( "gml.data" );
928 : apoGMLBoxes[1] =
929 : GDALJP2Box::CreateLabelledXMLAssoc( "gml.root-instance",
930 0 : pszGML );
931 :
932 0 : GDALJP2Box *poGMLData = GDALJP2Box::CreateAsocBox( 2, apoGMLBoxes);
933 :
934 0 : delete apoGMLBoxes[0];
935 0 : delete apoGMLBoxes[1];
936 :
937 0 : CPLFree( pszGML );
938 :
939 0 : return poGMLData;
940 : }
941 :
942 : /* -------------------------------------------------------------------- */
943 : /* Try do determine a PCS or GCS code we can use. */
944 : /* -------------------------------------------------------------------- */
945 23 : OGRSpatialReference oSRS;
946 23 : char *pszWKTCopy = (char *) pszProjection;
947 23 : int nEPSGCode = 0;
948 : char szSRSName[100];
949 23 : int bNeedAxisFlip = FALSE;
950 :
951 23 : if( oSRS.importFromWkt( &pszWKTCopy ) != OGRERR_NONE )
952 2 : return NULL;
953 :
954 21 : if( oSRS.IsProjected() )
955 : {
956 5 : const char *pszAuthName = oSRS.GetAuthorityName( "PROJCS" );
957 :
958 5 : if( pszAuthName != NULL && EQUAL(pszAuthName,"epsg") )
959 : {
960 5 : nEPSGCode = atoi(oSRS.GetAuthorityCode( "PROJCS" ));
961 : }
962 : }
963 16 : else if( oSRS.IsGeographic() )
964 : {
965 16 : const char *pszAuthName = oSRS.GetAuthorityName( "GEOGCS" );
966 :
967 16 : if( pszAuthName != NULL && EQUAL(pszAuthName,"epsg") )
968 : {
969 12 : nEPSGCode = atoi(oSRS.GetAuthorityCode( "GEOGCS" ));
970 12 : bNeedAxisFlip = TRUE;
971 : }
972 : }
973 :
974 21 : if( nEPSGCode != 0 )
975 17 : sprintf( szSRSName, "urn:ogc:def:crs:EPSG::%d", nEPSGCode );
976 : else
977 : strcpy( szSRSName,
978 4 : "gmljp2://xml/CRSDictionary.gml#ogrcrs1" );
979 :
980 : /* -------------------------------------------------------------------- */
981 : /* Prepare coverage origin and offset vectors. Take axis */
982 : /* order into account if needed. */
983 : /* -------------------------------------------------------------------- */
984 : double adfOrigin[2];
985 : double adfXVector[2];
986 : double adfYVector[2];
987 :
988 21 : adfOrigin[0] = adfGeoTransform[0] + adfGeoTransform[1] * 0.5
989 21 : + adfGeoTransform[4] * 0.5;
990 21 : adfOrigin[1] = adfGeoTransform[3] + adfGeoTransform[2] * 0.5
991 21 : + adfGeoTransform[5] * 0.5;
992 21 : adfXVector[0] = adfGeoTransform[1];
993 21 : adfXVector[1] = adfGeoTransform[2];
994 :
995 21 : adfYVector[0] = adfGeoTransform[4];
996 21 : adfYVector[1] = adfGeoTransform[5];
997 :
998 21 : if( bNeedAxisFlip
999 : && CSLTestBoolean( CPLGetConfigOption( "GDAL_IGNORE_AXIS_ORIENTATION",
1000 : "FALSE" ) ) )
1001 : {
1002 0 : bNeedAxisFlip = FALSE;
1003 0 : CPLDebug( "GMLJP2", "Supressed axis flipping on write based on GDAL_IGNORE_AXIS_ORIENTATION." );
1004 : }
1005 :
1006 21 : if( bNeedAxisFlip )
1007 : {
1008 : double dfTemp;
1009 :
1010 12 : CPLDebug( "GMLJP2", "Flipping GML coverage axis order." );
1011 :
1012 12 : dfTemp = adfOrigin[0];
1013 12 : adfOrigin[0] = adfOrigin[1];
1014 12 : adfOrigin[1] = dfTemp;
1015 :
1016 12 : if( CSLTestBoolean( CPLGetConfigOption( "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER",
1017 : "FALSE" ) ) )
1018 : {
1019 : CPLDebug( "GMLJP2", "Choosing alternate GML \"<offsetVector>\" order based on "
1020 0 : "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER." );
1021 :
1022 : /* In this case the swapping is done in an "X" pattern */
1023 0 : dfTemp = adfXVector[0];
1024 0 : adfXVector[0] = adfYVector[1];
1025 0 : adfYVector[1] = dfTemp;
1026 :
1027 0 : dfTemp = adfYVector[0];
1028 0 : adfYVector[0] = adfXVector[1];
1029 0 : adfXVector[1] = dfTemp;
1030 : }
1031 : else
1032 : {
1033 12 : dfTemp = adfXVector[0];
1034 12 : adfXVector[0] = adfXVector[1];
1035 12 : adfXVector[1] = dfTemp;
1036 :
1037 12 : dfTemp = adfYVector[0];
1038 12 : adfYVector[0] = adfYVector[1];
1039 12 : adfYVector[1] = dfTemp;
1040 : }
1041 : }
1042 :
1043 : /* -------------------------------------------------------------------- */
1044 : /* For now we hardcode for a minimal instance format. */
1045 : /* -------------------------------------------------------------------- */
1046 21 : CPLString osDoc;
1047 :
1048 : osDoc.Printf(
1049 : "<gml:FeatureCollection\n"
1050 : " xmlns:gml=\"http://www.opengis.net/gml\"\n"
1051 : " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
1052 : " xsi:schemaLocation=\"http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlJP2Profile/1.0.0/gmlJP2Profile.xsd\">\n"
1053 : " <gml:boundedBy>\n"
1054 : " <gml:Null>withheld</gml:Null>\n"
1055 : " </gml:boundedBy>\n"
1056 : " <gml:featureMember>\n"
1057 : " <gml:FeatureCollection>\n"
1058 : " <gml:featureMember>\n"
1059 : " <gml:RectifiedGridCoverage dimension=\"2\" gml:id=\"RGC0001\">\n"
1060 : " <gml:rectifiedGridDomain>\n"
1061 : " <gml:RectifiedGrid dimension=\"2\">\n"
1062 : " <gml:limits>\n"
1063 : " <gml:GridEnvelope>\n"
1064 : " <gml:low>0 0</gml:low>\n"
1065 : " <gml:high>%d %d</gml:high>\n"
1066 : " </gml:GridEnvelope>\n"
1067 : " </gml:limits>\n"
1068 : " <gml:axisName>x</gml:axisName>\n"
1069 : " <gml:axisName>y</gml:axisName>\n"
1070 : " <gml:origin>\n"
1071 : " <gml:Point gml:id=\"P0001\" srsName=\"%s\">\n"
1072 : " <gml:pos>%.15g %.15g</gml:pos>\n"
1073 : " </gml:Point>\n"
1074 : " </gml:origin>\n"
1075 : " <gml:offsetVector srsName=\"%s\">%.15g %.15g</gml:offsetVector>\n"
1076 : " <gml:offsetVector srsName=\"%s\">%.15g %.15g</gml:offsetVector>\n"
1077 : " </gml:RectifiedGrid>\n"
1078 : " </gml:rectifiedGridDomain>\n"
1079 : " <gml:rangeSet>\n"
1080 : " <gml:File>\n"
1081 : " <gml:fileName>gmljp2://codestream/0</gml:fileName>\n"
1082 : " <gml:fileStructure>Record Interleaved</gml:fileStructure>\n"
1083 : " </gml:File>\n"
1084 : " </gml:rangeSet>\n"
1085 : " </gml:RectifiedGridCoverage>\n"
1086 : " </gml:featureMember>\n"
1087 : " </gml:FeatureCollection>\n"
1088 : " </gml:featureMember>\n"
1089 : "</gml:FeatureCollection>\n",
1090 : nXSize-1, nYSize-1, szSRSName, adfOrigin[0], adfOrigin[1],
1091 : szSRSName, adfXVector[0], adfXVector[1],
1092 21 : szSRSName, adfYVector[0], adfYVector[1] );
1093 :
1094 : /* -------------------------------------------------------------------- */
1095 : /* If we need a user defined CRSDictionary entry, prepare it */
1096 : /* here. */
1097 : /* -------------------------------------------------------------------- */
1098 21 : CPLString osDictBox;
1099 :
1100 21 : if( nEPSGCode == 0 )
1101 : {
1102 4 : char *pszGMLDef = NULL;
1103 :
1104 4 : if( oSRS.exportToXML( &pszGMLDef, NULL ) == OGRERR_NONE )
1105 : {
1106 : osDictBox.Printf(
1107 : "<gml:Dictionary gml:id=\"CRSU1\" \n"
1108 : " xmlns:gml=\"http://www.opengis.net/gml\"\n"
1109 : " xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"
1110 : " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
1111 : " <gml:dictionaryEntry>\n"
1112 : "%s\n"
1113 : " </gml:dictionaryEntry>\n"
1114 : "</gml:Dictionary>\n",
1115 4 : pszGMLDef );
1116 : }
1117 4 : CPLFree( pszGMLDef );
1118 : }
1119 :
1120 : /* -------------------------------------------------------------------- */
1121 : /* Setup the gml.data label. */
1122 : /* -------------------------------------------------------------------- */
1123 : GDALJP2Box *apoGMLBoxes[5];
1124 21 : int nGMLBoxes = 0;
1125 :
1126 21 : apoGMLBoxes[nGMLBoxes++] = GDALJP2Box::CreateLblBox( "gml.data" );
1127 :
1128 : /* -------------------------------------------------------------------- */
1129 : /* Setup gml.root-instance. */
1130 : /* -------------------------------------------------------------------- */
1131 42 : apoGMLBoxes[nGMLBoxes++] =
1132 21 : GDALJP2Box::CreateLabelledXMLAssoc( "gml.root-instance", osDoc );
1133 :
1134 : /* -------------------------------------------------------------------- */
1135 : /* Add optional dictionary. */
1136 : /* -------------------------------------------------------------------- */
1137 21 : if( strlen(osDictBox) > 0 )
1138 8 : apoGMLBoxes[nGMLBoxes++] =
1139 : GDALJP2Box::CreateLabelledXMLAssoc( "CRSDictionary.gml",
1140 4 : osDictBox );
1141 :
1142 : /* -------------------------------------------------------------------- */
1143 : /* Bundle gml.data boxes into an association. */
1144 : /* -------------------------------------------------------------------- */
1145 21 : GDALJP2Box *poGMLData = GDALJP2Box::CreateAsocBox( nGMLBoxes, apoGMLBoxes);
1146 :
1147 : /* -------------------------------------------------------------------- */
1148 : /* Cleanup working boxes. */
1149 : /* -------------------------------------------------------------------- */
1150 88 : while( nGMLBoxes > 0 )
1151 46 : delete apoGMLBoxes[--nGMLBoxes];
1152 :
1153 21 : return poGMLData;
1154 : }
1155 :
1156 :
|