1 : /******************************************************************************
2 : * $Id: gdaljp2metadata.cpp 25727 2013-03-10 14:56:33Z rouault $
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 25727 2013-03-10 14:56:33Z rouault $");
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 142 : int GDALJP2Metadata::ReadAndParse( const char *pszFilename )
114 :
115 : {
116 : VSILFILE *fpLL;
117 :
118 142 : fpLL = VSIFOpenL( pszFilename, "rb" );
119 :
120 142 : if( fpLL == NULL )
121 : {
122 : CPLDebug( "GDALJP2Metadata", "Could not even open %s.",
123 0 : pszFilename );
124 :
125 0 : return FALSE;
126 : }
127 :
128 142 : ReadBoxes( fpLL );
129 142 : VSIFCloseL( fpLL );
130 :
131 : /* -------------------------------------------------------------------- */
132 : /* Try JP2GeoTIFF, GML and finally MSIG to get something. */
133 : /* -------------------------------------------------------------------- */
134 142 : if( !ParseJP2GeoTIFF() && !ParseGMLCoverageDesc() )
135 63 : ParseMSIG();
136 :
137 : /* -------------------------------------------------------------------- */
138 : /* If we still don't have a geotransform, look for a world */
139 : /* file. */
140 : /* -------------------------------------------------------------------- */
141 142 : if( !bHaveGeoTransform )
142 : {
143 : bHaveGeoTransform =
144 : GDALReadWorldFile( pszFilename, NULL, adfGeoTransform )
145 73 : || 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 142 : || (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 28 : void GDALJP2Metadata::CollectGMLData( GDALJP2Box *poGMLData )
165 :
166 : {
167 28 : GDALJP2Box oChildBox( poGMLData->GetFILE() );
168 :
169 28 : oChildBox.ReadFirstChild( poGMLData );
170 :
171 112 : while( strlen(oChildBox.GetType()) > 0 )
172 : {
173 56 : if( EQUAL(oChildBox.GetType(),"asoc") )
174 : {
175 28 : GDALJP2Box oSubChildBox( oChildBox.GetFILE() );
176 :
177 28 : char *pszLabel = NULL;
178 28 : char *pszXML = NULL;
179 :
180 28 : oSubChildBox.ReadFirstChild( &oChildBox );
181 :
182 112 : while( strlen(oSubChildBox.GetType()) > 0 )
183 : {
184 56 : if( EQUAL(oSubChildBox.GetType(),"lbl ") )
185 28 : pszLabel = (char *)oSubChildBox.ReadBoxData();
186 28 : else if( EQUAL(oSubChildBox.GetType(),"xml ") )
187 28 : pszXML = (char *) oSubChildBox.ReadBoxData();
188 :
189 56 : oSubChildBox.ReadNextChild( &oChildBox );
190 : }
191 :
192 28 : if( pszLabel != NULL && pszXML != NULL )
193 : papszGMLMetadata = CSLSetNameValue( papszGMLMetadata,
194 28 : pszLabel, pszXML );
195 28 : CPLFree( pszLabel );
196 28 : CPLFree( pszXML );
197 : }
198 :
199 56 : oChildBox.ReadNextChild( poGMLData );
200 28 : }
201 28 : }
202 :
203 : /************************************************************************/
204 : /* ReadBoxes() */
205 : /************************************************************************/
206 :
207 142 : int GDALJP2Metadata::ReadBoxes( VSILFILE *fpVSIL )
208 :
209 : {
210 142 : GDALJP2Box oBox( fpVSIL );
211 142 : int iBox = 0;
212 :
213 142 : if (!oBox.ReadFirst())
214 0 : return FALSE;
215 :
216 682 : while( strlen(oBox.GetType()) > 0 )
217 : {
218 : #ifdef DEBUG
219 493 : if (CSLTestBoolean(CPLGetConfigOption("DUMP_JP2_BOXES", "NO")))
220 0 : oBox.DumpReadable(stderr);
221 : #endif
222 :
223 : /* -------------------------------------------------------------------- */
224 : /* Collect geotiff box. */
225 : /* -------------------------------------------------------------------- */
226 493 : if( EQUAL(oBox.GetType(),"uuid")
227 : && memcmp( oBox.GetUUID(), msi_uuid2, 16 ) == 0 )
228 : {
229 75 : nGeoTIFFSize = (int) oBox.GetDataLength();
230 75 : pabyGeoTIFFData = oBox.ReadBoxData();
231 75 : 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 493 : 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 493 : 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 493 : if( EQUAL(oBox.GetType(),"asoc") )
271 : {
272 28 : GDALJP2Box oSubBox( fpVSIL );
273 :
274 28 : oSubBox.ReadFirstChild( &oBox );
275 28 : if( EQUAL(oSubBox.GetType(),"lbl ") )
276 : {
277 28 : char *pszLabel = (char *) oSubBox.ReadBoxData();
278 28 : if( pszLabel != NULL && EQUAL(pszLabel,"gml.data") )
279 : {
280 28 : CollectGMLData( &oBox );
281 : }
282 28 : CPLFree( pszLabel );
283 28 : }
284 : }
285 :
286 : /* -------------------------------------------------------------------- */
287 : /* Process simple xml boxes. */
288 : /* -------------------------------------------------------------------- */
289 493 : 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 493 : if( EQUAL(oBox.GetType(),"jp2h") )
305 : {
306 95 : GDALJP2Box oSubBox( fpVSIL );
307 :
308 330 : for( oSubBox.ReadFirstChild( &oBox );
309 : strlen(oSubBox.GetType()) > 0;
310 : oSubBox.ReadNextChild( &oBox ) )
311 : {
312 235 : 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 95 : }
359 : }
360 :
361 493 : if (!oBox.ReadNext())
362 95 : break;
363 : }
364 :
365 142 : return TRUE;
366 : }
367 :
368 : /************************************************************************/
369 : /* ParseJP2GeoTIFF() */
370 : /************************************************************************/
371 :
372 142 : int GDALJP2Metadata::ParseJP2GeoTIFF()
373 :
374 : {
375 142 : if( nGeoTIFFSize < 1 )
376 67 : return FALSE;
377 :
378 : /* -------------------------------------------------------------------- */
379 : /* Convert raw data into projection and geotransform. */
380 : /* -------------------------------------------------------------------- */
381 75 : int bSuccess = TRUE;
382 :
383 75 : if( GTIFWktFromMemBuf( nGeoTIFFSize, pabyGeoTIFFData,
384 : &pszProjection, adfGeoTransform,
385 : &nGCPCount, &pasGCPList ) != CE_None )
386 : {
387 0 : bSuccess = FALSE;
388 : }
389 :
390 75 : if( pszProjection == NULL || strlen(pszProjection) == 0 )
391 0 : bSuccess = FALSE;
392 :
393 75 : if( bSuccess )
394 : CPLDebug( "GDALJP2Metadata",
395 : "Got projection from GeoJP2 (geotiff) box: %s",
396 75 : pszProjection );
397 :
398 125 : 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 65 : bHaveGeoTransform = TRUE;
405 :
406 75 : return bSuccess;;
407 : }
408 :
409 : /************************************************************************/
410 : /* ParseMSIG() */
411 : /************************************************************************/
412 :
413 63 : int GDALJP2Metadata::ParseMSIG()
414 :
415 : {
416 63 : if( nMSIGSize < 70 )
417 63 : 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 67 : 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 67 : "gml.root-instance" );
606 :
607 67 : if( pszCoverage == NULL )
608 63 : 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 : oSRS.exportToWkt( &pszProjection );
749 :
750 : // Per #2131
751 4 : if( oSRS.EPSGTreatsAsLatLong() || oSRS.EPSGTreatsAsNorthingEasting() )
752 : {
753 : CPLDebug( "GMLJP2", "Request axis flip for SRS=%s",
754 4 : pszSRSName );
755 4 : bNeedAxisFlip = TRUE;
756 : }
757 : }
758 0 : else if( !GMLSRSLookup( pszSRSName ) )
759 : {
760 : CPLDebug( "GDALJP2Metadata",
761 : "Unable to evaluate SRSName=%s",
762 0 : pszSRSName );
763 4 : }
764 : }
765 :
766 4 : if( pszProjection )
767 : CPLDebug( "GDALJP2Metadata",
768 : "Got projection from GML box: %s",
769 4 : pszProjection );
770 :
771 4 : CPLDestroyXMLNode( psXML );
772 4 : psXML = NULL;
773 :
774 : /* -------------------------------------------------------------------- */
775 : /* Do we need to flip the axes? */
776 : /* -------------------------------------------------------------------- */
777 4 : if( bNeedAxisFlip
778 : && CSLTestBoolean( CPLGetConfigOption( "GDAL_IGNORE_AXIS_ORIENTATION",
779 : "FALSE" ) ) )
780 : {
781 0 : bNeedAxisFlip = FALSE;
782 0 : CPLDebug( "GMLJP2", "Supressed axis flipping based on GDAL_IGNORE_AXIS_ORIENTATION." );
783 : }
784 :
785 4 : if( bNeedAxisFlip )
786 : {
787 : double dfTemp;
788 :
789 : CPLDebug( "GMLJP2",
790 4 : "Flipping axis orientation in GMLJP2 coverage description." );
791 :
792 4 : dfTemp = adfGeoTransform[0];
793 4 : adfGeoTransform[0] = adfGeoTransform[3];
794 4 : adfGeoTransform[3] = dfTemp;
795 :
796 4 : int swapWith1Index = 4;
797 4 : int swapWith2Index = 5;
798 :
799 4 : if( CSLTestBoolean( CPLGetConfigOption( "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER",
800 : "FALSE" ) ) )
801 : {
802 0 : swapWith1Index = 5;
803 0 : swapWith2Index = 4;
804 : CPLDebug( "GMLJP2", "Choosing alternate GML \"<offsetVector>\" order based on "
805 0 : "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER." );
806 : }
807 :
808 4 : dfTemp = adfGeoTransform[1];
809 4 : adfGeoTransform[1] = adfGeoTransform[swapWith1Index];
810 4 : adfGeoTransform[swapWith1Index] = dfTemp;
811 :
812 4 : dfTemp = adfGeoTransform[2];
813 4 : adfGeoTransform[2] = adfGeoTransform[swapWith2Index];
814 4 : adfGeoTransform[swapWith2Index] = dfTemp;
815 : }
816 :
817 4 : return pszProjection != NULL && bSuccess;
818 : }
819 :
820 : /************************************************************************/
821 : /* SetProjection() */
822 : /************************************************************************/
823 :
824 36 : void GDALJP2Metadata::SetProjection( const char *pszWKT )
825 :
826 : {
827 36 : CPLFree( pszProjection );
828 36 : pszProjection = CPLStrdup(pszWKT);
829 36 : }
830 :
831 : /************************************************************************/
832 : /* SetGCPs() */
833 : /************************************************************************/
834 :
835 24 : void GDALJP2Metadata::SetGCPs( int nCount, const GDAL_GCP *pasGCPsIn )
836 :
837 : {
838 24 : if( nGCPCount > 0 )
839 : {
840 0 : GDALDeinitGCPs( nGCPCount, pasGCPList );
841 0 : CPLFree( pasGCPList );
842 : }
843 :
844 24 : nGCPCount = nCount;
845 24 : pasGCPList = GDALDuplicateGCPs(nGCPCount, pasGCPsIn);
846 24 : }
847 :
848 : /************************************************************************/
849 : /* SetGeoTransform() */
850 : /************************************************************************/
851 :
852 36 : void GDALJP2Metadata::SetGeoTransform( double *padfGT )
853 :
854 : {
855 36 : memcpy( adfGeoTransform, padfGT, sizeof(double) * 6 );
856 36 : }
857 :
858 : /************************************************************************/
859 : /* CreateJP2GeoTIFF() */
860 : /************************************************************************/
861 :
862 30 : GDALJP2Box *GDALJP2Metadata::CreateJP2GeoTIFF()
863 :
864 : {
865 : /* -------------------------------------------------------------------- */
866 : /* Prepare the memory buffer containing the degenerate GeoTIFF */
867 : /* file. */
868 : /* -------------------------------------------------------------------- */
869 30 : int nGTBufSize = 0;
870 30 : unsigned char *pabyGTBuf = NULL;
871 :
872 30 : if( GTIFMemBufFromWkt( pszProjection, adfGeoTransform,
873 : nGCPCount, pasGCPList,
874 : &nGTBufSize, &pabyGTBuf ) != CE_None )
875 0 : return NULL;
876 :
877 30 : if( nGTBufSize == 0 )
878 0 : return NULL;
879 :
880 : /* -------------------------------------------------------------------- */
881 : /* Write to a box on the JP2 file. */
882 : /* -------------------------------------------------------------------- */
883 : GDALJP2Box *poBox;
884 :
885 30 : poBox = GDALJP2Box::CreateUUIDBox( msi_uuid2, nGTBufSize, pabyGTBuf );
886 :
887 30 : CPLFree( pabyGTBuf );
888 :
889 30 : return poBox;
890 : }
891 :
892 : /************************************************************************/
893 : /* PrepareCoverageBox() */
894 : /************************************************************************/
895 :
896 21 : GDALJP2Box *GDALJP2Metadata::CreateGMLJP2( int nXSize, int nYSize )
897 :
898 : {
899 : /* -------------------------------------------------------------------- */
900 : /* This is a backdoor to let us embed a literal gmljp2 chunk */
901 : /* supplied by the user as an external file. This is mostly */
902 : /* for preparing test files with exotic contents. */
903 : /* -------------------------------------------------------------------- */
904 21 : if( CPLGetConfigOption( "GMLJP2OVERRIDE", NULL ) != NULL )
905 : {
906 0 : VSILFILE *fp = VSIFOpenL( CPLGetConfigOption( "GMLJP2OVERRIDE",""), "r" );
907 0 : char *pszGML = NULL;
908 :
909 0 : if( fp == NULL )
910 : {
911 : CPLError( CE_Failure, CPLE_AppDefined,
912 0 : "Unable to open GMLJP2OVERRIDE file." );
913 0 : return NULL;
914 : }
915 :
916 0 : VSIFSeekL( fp, 0, SEEK_END );
917 0 : int nLength = (int) VSIFTellL( fp );
918 0 : pszGML = (char *) CPLCalloc(1,nLength+1);
919 0 : VSIFSeekL( fp, 0, SEEK_SET );
920 0 : VSIFReadL( pszGML, 1, nLength, fp );
921 0 : VSIFCloseL( fp );
922 :
923 : GDALJP2Box *apoGMLBoxes[2];
924 :
925 0 : apoGMLBoxes[0] = GDALJP2Box::CreateLblBox( "gml.data" );
926 : apoGMLBoxes[1] =
927 : GDALJP2Box::CreateLabelledXMLAssoc( "gml.root-instance",
928 0 : pszGML );
929 :
930 0 : GDALJP2Box *poGMLData = GDALJP2Box::CreateAsocBox( 2, apoGMLBoxes);
931 :
932 0 : delete apoGMLBoxes[0];
933 0 : delete apoGMLBoxes[1];
934 :
935 0 : CPLFree( pszGML );
936 :
937 0 : return poGMLData;
938 : }
939 :
940 : /* -------------------------------------------------------------------- */
941 : /* Try do determine a PCS or GCS code we can use. */
942 : /* -------------------------------------------------------------------- */
943 21 : OGRSpatialReference oSRS;
944 21 : char *pszWKTCopy = (char *) pszProjection;
945 21 : int nEPSGCode = 0;
946 : char szSRSName[100];
947 21 : int bNeedAxisFlip = FALSE;
948 :
949 21 : if( oSRS.importFromWkt( &pszWKTCopy ) != OGRERR_NONE )
950 1 : return NULL;
951 :
952 20 : if( oSRS.IsProjected() )
953 : {
954 5 : const char *pszAuthName = oSRS.GetAuthorityName( "PROJCS" );
955 :
956 5 : if( pszAuthName != NULL && EQUAL(pszAuthName,"epsg") )
957 : {
958 5 : nEPSGCode = atoi(oSRS.GetAuthorityCode( "PROJCS" ));
959 : }
960 : }
961 15 : else if( oSRS.IsGeographic() )
962 : {
963 15 : const char *pszAuthName = oSRS.GetAuthorityName( "GEOGCS" );
964 :
965 15 : if( pszAuthName != NULL && EQUAL(pszAuthName,"epsg") )
966 : {
967 13 : nEPSGCode = atoi(oSRS.GetAuthorityCode( "GEOGCS" ));
968 13 : bNeedAxisFlip = TRUE;
969 : }
970 : }
971 :
972 20 : if( nEPSGCode != 0 )
973 18 : sprintf( szSRSName, "urn:ogc:def:crs:EPSG::%d", nEPSGCode );
974 : else
975 : strcpy( szSRSName,
976 2 : "gmljp2://xml/CRSDictionary.gml#ogrcrs1" );
977 :
978 : /* -------------------------------------------------------------------- */
979 : /* Prepare coverage origin and offset vectors. Take axis */
980 : /* order into account if needed. */
981 : /* -------------------------------------------------------------------- */
982 : double adfOrigin[2];
983 : double adfXVector[2];
984 : double adfYVector[2];
985 :
986 20 : adfOrigin[0] = adfGeoTransform[0] + adfGeoTransform[1] * 0.5
987 20 : + adfGeoTransform[4] * 0.5;
988 20 : adfOrigin[1] = adfGeoTransform[3] + adfGeoTransform[2] * 0.5
989 20 : + adfGeoTransform[5] * 0.5;
990 20 : adfXVector[0] = adfGeoTransform[1];
991 20 : adfXVector[1] = adfGeoTransform[2];
992 :
993 20 : adfYVector[0] = adfGeoTransform[4];
994 20 : adfYVector[1] = adfGeoTransform[5];
995 :
996 20 : if( bNeedAxisFlip
997 : && CSLTestBoolean( CPLGetConfigOption( "GDAL_IGNORE_AXIS_ORIENTATION",
998 : "FALSE" ) ) )
999 : {
1000 0 : bNeedAxisFlip = FALSE;
1001 0 : CPLDebug( "GMLJP2", "Supressed axis flipping on write based on GDAL_IGNORE_AXIS_ORIENTATION." );
1002 : }
1003 :
1004 20 : if( bNeedAxisFlip )
1005 : {
1006 : double dfTemp;
1007 :
1008 13 : CPLDebug( "GMLJP2", "Flipping GML coverage axis order." );
1009 :
1010 13 : dfTemp = adfOrigin[0];
1011 13 : adfOrigin[0] = adfOrigin[1];
1012 13 : adfOrigin[1] = dfTemp;
1013 :
1014 13 : if( CSLTestBoolean( CPLGetConfigOption( "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER",
1015 : "FALSE" ) ) )
1016 : {
1017 : CPLDebug( "GMLJP2", "Choosing alternate GML \"<offsetVector>\" order based on "
1018 0 : "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER." );
1019 :
1020 : /* In this case the swapping is done in an "X" pattern */
1021 0 : dfTemp = adfXVector[0];
1022 0 : adfXVector[0] = adfYVector[1];
1023 0 : adfYVector[1] = dfTemp;
1024 :
1025 0 : dfTemp = adfYVector[0];
1026 0 : adfYVector[0] = adfXVector[1];
1027 0 : adfXVector[1] = dfTemp;
1028 : }
1029 : else
1030 : {
1031 13 : dfTemp = adfXVector[0];
1032 13 : adfXVector[0] = adfXVector[1];
1033 13 : adfXVector[1] = dfTemp;
1034 :
1035 13 : dfTemp = adfYVector[0];
1036 13 : adfYVector[0] = adfYVector[1];
1037 13 : adfYVector[1] = dfTemp;
1038 : }
1039 : }
1040 :
1041 : /* -------------------------------------------------------------------- */
1042 : /* For now we hardcode for a minimal instance format. */
1043 : /* -------------------------------------------------------------------- */
1044 20 : CPLString osDoc;
1045 :
1046 : osDoc.Printf(
1047 : "<gml:FeatureCollection\n"
1048 : " xmlns:gml=\"http://www.opengis.net/gml\"\n"
1049 : " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
1050 : " xsi:schemaLocation=\"http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlJP2Profile/1.0.0/gmlJP2Profile.xsd\">\n"
1051 : " <gml:boundedBy>\n"
1052 : " <gml:Null>withheld</gml:Null>\n"
1053 : " </gml:boundedBy>\n"
1054 : " <gml:featureMember>\n"
1055 : " <gml:FeatureCollection>\n"
1056 : " <gml:featureMember>\n"
1057 : " <gml:RectifiedGridCoverage dimension=\"2\" gml:id=\"RGC0001\">\n"
1058 : " <gml:rectifiedGridDomain>\n"
1059 : " <gml:RectifiedGrid dimension=\"2\">\n"
1060 : " <gml:limits>\n"
1061 : " <gml:GridEnvelope>\n"
1062 : " <gml:low>0 0</gml:low>\n"
1063 : " <gml:high>%d %d</gml:high>\n"
1064 : " </gml:GridEnvelope>\n"
1065 : " </gml:limits>\n"
1066 : " <gml:axisName>x</gml:axisName>\n"
1067 : " <gml:axisName>y</gml:axisName>\n"
1068 : " <gml:origin>\n"
1069 : " <gml:Point gml:id=\"P0001\" srsName=\"%s\">\n"
1070 : " <gml:pos>%.15g %.15g</gml:pos>\n"
1071 : " </gml:Point>\n"
1072 : " </gml:origin>\n"
1073 : " <gml:offsetVector srsName=\"%s\">%.15g %.15g</gml:offsetVector>\n"
1074 : " <gml:offsetVector srsName=\"%s\">%.15g %.15g</gml:offsetVector>\n"
1075 : " </gml:RectifiedGrid>\n"
1076 : " </gml:rectifiedGridDomain>\n"
1077 : " <gml:rangeSet>\n"
1078 : " <gml:File>\n"
1079 : " <gml:fileName>gmljp2://codestream/0</gml:fileName>\n"
1080 : " <gml:fileStructure>Record Interleaved</gml:fileStructure>\n"
1081 : " </gml:File>\n"
1082 : " </gml:rangeSet>\n"
1083 : " </gml:RectifiedGridCoverage>\n"
1084 : " </gml:featureMember>\n"
1085 : " </gml:FeatureCollection>\n"
1086 : " </gml:featureMember>\n"
1087 : "</gml:FeatureCollection>\n",
1088 : nXSize-1, nYSize-1, szSRSName, adfOrigin[0], adfOrigin[1],
1089 : szSRSName, adfXVector[0], adfXVector[1],
1090 20 : szSRSName, adfYVector[0], adfYVector[1] );
1091 :
1092 : /* -------------------------------------------------------------------- */
1093 : /* If we need a user defined CRSDictionary entry, prepare it */
1094 : /* here. */
1095 : /* -------------------------------------------------------------------- */
1096 20 : CPLString osDictBox;
1097 :
1098 20 : if( nEPSGCode == 0 )
1099 : {
1100 2 : char *pszGMLDef = NULL;
1101 :
1102 2 : if( oSRS.exportToXML( &pszGMLDef, NULL ) == OGRERR_NONE )
1103 : {
1104 : osDictBox.Printf(
1105 : "<gml:Dictionary gml:id=\"CRSU1\" \n"
1106 : " xmlns:gml=\"http://www.opengis.net/gml\"\n"
1107 : " xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"
1108 : " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
1109 : " <gml:dictionaryEntry>\n"
1110 : "%s\n"
1111 : " </gml:dictionaryEntry>\n"
1112 : "</gml:Dictionary>\n",
1113 2 : pszGMLDef );
1114 : }
1115 2 : CPLFree( pszGMLDef );
1116 : }
1117 :
1118 : /* -------------------------------------------------------------------- */
1119 : /* Setup the gml.data label. */
1120 : /* -------------------------------------------------------------------- */
1121 : GDALJP2Box *apoGMLBoxes[5];
1122 20 : int nGMLBoxes = 0;
1123 :
1124 20 : apoGMLBoxes[nGMLBoxes++] = GDALJP2Box::CreateLblBox( "gml.data" );
1125 :
1126 : /* -------------------------------------------------------------------- */
1127 : /* Setup gml.root-instance. */
1128 : /* -------------------------------------------------------------------- */
1129 40 : apoGMLBoxes[nGMLBoxes++] =
1130 20 : GDALJP2Box::CreateLabelledXMLAssoc( "gml.root-instance", osDoc );
1131 :
1132 : /* -------------------------------------------------------------------- */
1133 : /* Add optional dictionary. */
1134 : /* -------------------------------------------------------------------- */
1135 20 : if( strlen(osDictBox) > 0 )
1136 4 : apoGMLBoxes[nGMLBoxes++] =
1137 : GDALJP2Box::CreateLabelledXMLAssoc( "CRSDictionary.gml",
1138 2 : osDictBox );
1139 :
1140 : /* -------------------------------------------------------------------- */
1141 : /* Bundle gml.data boxes into an association. */
1142 : /* -------------------------------------------------------------------- */
1143 20 : GDALJP2Box *poGMLData = GDALJP2Box::CreateAsocBox( nGMLBoxes, apoGMLBoxes);
1144 :
1145 : /* -------------------------------------------------------------------- */
1146 : /* Cleanup working boxes. */
1147 : /* -------------------------------------------------------------------- */
1148 82 : while( nGMLBoxes > 0 )
1149 42 : delete apoGMLBoxes[--nGMLBoxes];
1150 :
1151 20 : return poGMLData;
1152 : }
1153 :
1154 :
|