EVOLUTION-MANAGER
Edit File: gdaljp2metadata.cpp
/****************************************************************************** * * Project: GDAL * Purpose: GDALJP2Metadata - Read GeoTIFF and/or GML georef info. * Author: Frank Warmerdam, warmerdam@pobox.com * Even Rouault <even dot rouault at spatialys dot com> * ****************************************************************************** * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com> * Copyright (c) 2010-2015, Even Rouault <even dot rouault at spatialys dot com> * Copyright (c) 2015, European Union Satellite Centre * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "cpl_port.h" #include "gdaljp2metadata.h" #include "gdaljp2metadatagenerator.h" #include <cmath> #include <cstddef> #include <cstdlib> #include <cstring> #if HAVE_FCNTL_H # include <fcntl.h> #endif #include <algorithm> #include <memory> #include <set> #include <string> #include <vector> #include "cpl_error.h" #include "cpl_string.h" #include "cpl_minixml.h" #include "gdaljp2metadatagenerator.h" #include "gt_wkt_srs_for_gdal.h" #include "ogr_api.h" #include "ogr_core.h" #include "ogr_geometry.h" #include "ogr_spatialref.h" #include "ogrgeojsonreader.h" /*! @cond Doxygen_Suppress */ CPL_CVSID("$Id: gdaljp2metadata.cpp 37110 2017-01-12 10:36:04Z rouault $"); static const unsigned char msi_uuid2[16] = { 0xb1,0x4b,0xf8,0xbd,0x08,0x3d,0x4b,0x43, 0xa5,0xae,0x8c,0xd7,0xd5,0xa6,0xce,0x03 }; static const unsigned char msig_uuid[16] = { 0x96,0xA9,0xF1,0xF1,0xDC,0x98,0x40,0x2D, 0xA7,0xAE,0xD6,0x8E,0x34,0x45,0x18,0x09 }; static const unsigned char xmp_uuid[16] = { 0xBE,0x7A,0xCF,0xCB,0x97,0xA9,0x42,0xE8, 0x9C,0x71,0x99,0x94,0x91,0xE3,0xAF,0xAC }; struct _GDALJP2GeoTIFFBox { int nGeoTIFFSize; GByte *pabyGeoTIFFData; }; static const int MAX_JP2GEOTIFF_BOXES = 2; /************************************************************************/ /* GDALJP2Metadata() */ /************************************************************************/ GDALJP2Metadata::GDALJP2Metadata() : nGeoTIFFBoxesCount(0), pasGeoTIFFBoxes(NULL), nMSIGSize(0), pabyMSIGData(NULL), papszGMLMetadata(NULL), bHaveGeoTransform(false), #if HAVE_CXX11 adfGeoTransform{0.0, 1.0, 0.0, 0.0, 0.0, 1.0}, #endif bPixelIsPoint(false), pszProjection(NULL), nGCPCount(0), pasGCPList(NULL), papszRPCMD(NULL), papszMetadata(NULL), pszXMPMetadata(NULL), pszGDALMultiDomainMetadata(NULL), pszXMLIPR(NULL) { #if !HAVE_CXX11 adfGeoTransform[0] = 0.0; adfGeoTransform[1] = 1.0; adfGeoTransform[2] = 0.0; adfGeoTransform[3] = 0.0; adfGeoTransform[4] = 0.0; adfGeoTransform[5] = 1.0; #endif } /************************************************************************/ /* ~GDALJP2Metadata() */ /************************************************************************/ GDALJP2Metadata::~GDALJP2Metadata() { CPLFree( pszProjection ); if( nGCPCount > 0 ) { GDALDeinitGCPs( nGCPCount, pasGCPList ); CPLFree( pasGCPList ); } CSLDestroy(papszRPCMD); for( int i = 0; i < nGeoTIFFBoxesCount; ++i ) { CPLFree( pasGeoTIFFBoxes[i].pabyGeoTIFFData ); } CPLFree( pasGeoTIFFBoxes ); CPLFree( pabyMSIGData ); CSLDestroy( papszGMLMetadata ); CSLDestroy( papszMetadata ); CPLFree( pszXMPMetadata ); CPLFree( pszGDALMultiDomainMetadata ); CPLFree( pszXMLIPR ); } /************************************************************************/ /* ReadAndParse() */ /* */ /* Read a JP2 file and try to collect georeferencing */ /* information from the various available forms. Returns TRUE */ /* if anything useful is found. */ /************************************************************************/ int GDALJP2Metadata::ReadAndParse( const char *pszFilename, int nGEOJP2Index, int nGMLJP2Index, int nMSIGIndex, int nWorldFileIndex, int *pnIndexUsed ) { VSILFILE *fpLL = VSIFOpenL( pszFilename, "rb" ); if( fpLL == NULL ) { CPLDebug( "GDALJP2Metadata", "Could not even open %s.", pszFilename ); return FALSE; } int nIndexUsed = -1; bool bRet = CPL_TO_BOOL(ReadAndParse( fpLL, nGEOJP2Index, nGMLJP2Index, nMSIGIndex, &nIndexUsed )); CPL_IGNORE_RET_VAL(VSIFCloseL( fpLL )); /* -------------------------------------------------------------------- */ /* If we still don't have a geotransform, look for a world */ /* file. */ /* -------------------------------------------------------------------- */ if( nWorldFileIndex >= 0 && ((bHaveGeoTransform && nWorldFileIndex < nIndexUsed) || !bHaveGeoTransform) ) { bHaveGeoTransform = CPL_TO_BOOL( GDALReadWorldFile( pszFilename, NULL, adfGeoTransform ) || GDALReadWorldFile( pszFilename, ".wld", adfGeoTransform ) ); bRet |= bHaveGeoTransform; } if( pnIndexUsed ) *pnIndexUsed = nIndexUsed; return bRet; } int GDALJP2Metadata::ReadAndParse( VSILFILE *fpLL, int nGEOJP2Index, int nGMLJP2Index, int nMSIGIndex, int *pnIndexUsed ) { ReadBoxes( fpLL ); /* -------------------------------------------------------------------- */ /* Try JP2GeoTIFF, GML and finally MSIG in specified order. */ /* -------------------------------------------------------------------- */ std::set<int> aoSetPriorities; if( nGEOJP2Index >= 0 ) aoSetPriorities.insert(nGEOJP2Index); if( nGMLJP2Index >= 0 ) aoSetPriorities.insert(nGMLJP2Index); if( nMSIGIndex >= 0 ) aoSetPriorities.insert(nMSIGIndex); std::set<int>::iterator oIter = aoSetPriorities.begin(); for( ; oIter != aoSetPriorities.end(); ++oIter ) { int nIndex = *oIter; if( (nIndex == nGEOJP2Index && ParseJP2GeoTIFF()) || (nIndex == nGMLJP2Index && ParseGMLCoverageDesc()) || (nIndex == nMSIGIndex && ParseMSIG() ) ) { if( pnIndexUsed ) *pnIndexUsed = nIndex; break; } } /* -------------------------------------------------------------------- */ /* Return success either either of projection or geotransform */ /* or gcps. */ /* -------------------------------------------------------------------- */ return bHaveGeoTransform || nGCPCount > 0 || (pszProjection != NULL && strlen(pszProjection) > 0) || papszRPCMD != NULL; } /************************************************************************/ /* CollectGMLData() */ /* */ /* Read all the asoc boxes after this node, and store the */ /* contain xml documents along with the name from the label. */ /************************************************************************/ void GDALJP2Metadata::CollectGMLData( GDALJP2Box *poGMLData ) { GDALJP2Box oChildBox( poGMLData->GetFILE() ); if( !oChildBox.ReadFirstChild( poGMLData ) ) return; while( strlen(oChildBox.GetType()) > 0 ) { if( EQUAL(oChildBox.GetType(),"asoc") ) { GDALJP2Box oSubChildBox( oChildBox.GetFILE() ); if( !oSubChildBox.ReadFirstChild( &oChildBox ) ) break; char *pszLabel = NULL; char *pszXML = NULL; while( strlen(oSubChildBox.GetType()) > 0 ) { if( EQUAL(oSubChildBox.GetType(),"lbl ") ) pszLabel = (char *)oSubChildBox.ReadBoxData(); else if( EQUAL(oSubChildBox.GetType(),"xml ") ) { pszXML = reinterpret_cast<char *>( oSubChildBox.ReadBoxData() ); GIntBig nXMLLength = oSubChildBox.GetDataLength(); // Some GML data contains \0 instead of \n. // See http://trac.osgeo.org/gdal/ticket/5760 // TODO(schwehr): Explain the numbers in the next line. if( pszXML != NULL && nXMLLength < 100 * 1024 * 1024 ) { // coverity[tainted_data]. for( GIntBig i = nXMLLength - 1; i >= 0; --i ) { if( pszXML[i] == '\0' ) --nXMLLength; else break; } // coverity[tainted_data] GIntBig i = 0; // Used after for. for( ; i < nXMLLength; ++i ) { if( pszXML[i] == '\0' ) break; } if( i < nXMLLength ) { CPLPushErrorHandler(CPLQuietErrorHandler); CPLXMLNode* psNode = CPLParseXMLString(pszXML); CPLPopErrorHandler(); if( psNode == NULL ) { CPLDebug( "GMLJP2", "GMLJP2 data contains nul characters " "inside content. Replacing them by \\n"); // coverity[tainted_data] for( GIntBig j = 0; j < nXMLLength; ++j ) { if( pszXML[j] == '\0' ) pszXML[j] = '\n'; } } else { CPLDestroyXMLNode(psNode); } } } } if( !oSubChildBox.ReadNextChild( &oChildBox ) ) break; } if( pszLabel != NULL && pszXML != NULL ) { papszGMLMetadata = CSLSetNameValue( papszGMLMetadata, pszLabel, pszXML ); if( strcmp(pszLabel, "gml.root-instance") == 0 && pszGDALMultiDomainMetadata == NULL && strstr(pszXML, "GDALMultiDomainMetadata") != NULL ) { CPLXMLNode* psTree = CPLParseXMLString(pszXML); if( psTree != NULL ) { CPLXMLNode* psGDALMDMD = CPLSearchXMLNode(psTree, "GDALMultiDomainMetadata"); if( psGDALMDMD ) pszGDALMultiDomainMetadata = CPLSerializeXMLTree(psGDALMDMD); } CPLDestroyXMLNode(psTree); } } CPLFree( pszLabel ); CPLFree( pszXML ); } if( !oChildBox.ReadNextChild( poGMLData ) ) break; } } /************************************************************************/ /* ReadBoxes() */ /************************************************************************/ int GDALJP2Metadata::ReadBoxes( VSILFILE *fpVSIL ) { GDALJP2Box oBox( fpVSIL ); if (!oBox.ReadFirst()) return FALSE; int iBox = 0; while( strlen(oBox.GetType()) > 0 ) { #ifdef DEBUG if (CPLTestBool(CPLGetConfigOption("DUMP_JP2_BOXES", "NO"))) oBox.DumpReadable(stderr); #endif /* -------------------------------------------------------------------- */ /* Collect geotiff box. */ /* -------------------------------------------------------------------- */ if( EQUAL(oBox.GetType(),"uuid") && memcmp( oBox.GetUUID(), msi_uuid2, 16 ) == 0 ) { // Erdas JPEG2000 files sometimes contain 2 GeoTIFF UUID boxes. One // that is correct, another one that does not contain correct // georeferencing. Fetch at most 2 of them for later analysis. if( nGeoTIFFBoxesCount == MAX_JP2GEOTIFF_BOXES ) { CPLDebug( "GDALJP2", "Too many UUID GeoTIFF boxes. Ignoring this one" ); } else { const int nGeoTIFFSize = static_cast<int>( oBox.GetDataLength() ); GByte* pabyGeoTIFFData = oBox.ReadBoxData(); if( pabyGeoTIFFData == NULL ) { CPLDebug( "GDALJP2", "Cannot read data for UUID GeoTIFF box" ); } else { pasGeoTIFFBoxes = static_cast<GDALJP2GeoTIFFBox *>( CPLRealloc( pasGeoTIFFBoxes, sizeof(GDALJP2GeoTIFFBox) * (nGeoTIFFBoxesCount + 1) ) ); pasGeoTIFFBoxes[nGeoTIFFBoxesCount].nGeoTIFFSize = nGeoTIFFSize; pasGeoTIFFBoxes[nGeoTIFFBoxesCount].pabyGeoTIFFData = pabyGeoTIFFData; ++nGeoTIFFBoxesCount; } } } /* -------------------------------------------------------------------- */ /* Collect MSIG box. */ /* -------------------------------------------------------------------- */ if( EQUAL(oBox.GetType(),"uuid") && memcmp( oBox.GetUUID(), msig_uuid, 16 ) == 0 ) { if( nMSIGSize == 0 ) { nMSIGSize = static_cast<int>( oBox.GetDataLength() ); pabyMSIGData = oBox.ReadBoxData(); if( nMSIGSize < 70 || pabyMSIGData == NULL || memcmp( pabyMSIGData, "MSIG/", 5 ) != 0 ) { CPLFree( pabyMSIGData ); pabyMSIGData = NULL; nMSIGSize = 0; } } else { CPLDebug("GDALJP2", "Too many UUID MSIG boxes. Ignoring this one"); } } /* -------------------------------------------------------------------- */ /* Collect XMP box. */ /* -------------------------------------------------------------------- */ if( EQUAL(oBox.GetType(),"uuid") && memcmp( oBox.GetUUID(), xmp_uuid, 16 ) == 0 ) { if( pszXMPMetadata == NULL ) { pszXMPMetadata = (char*) oBox.ReadBoxData(); } else { CPLDebug("GDALJP2", "Too many UUID XMP boxes. Ignoring this one"); } } /* -------------------------------------------------------------------- */ /* Process asoc box looking for Labelled GML data. */ /* -------------------------------------------------------------------- */ if( EQUAL(oBox.GetType(),"asoc") ) { GDALJP2Box oSubBox( fpVSIL ); if( oSubBox.ReadFirstChild( &oBox ) && EQUAL(oSubBox.GetType(),"lbl ") ) { char *pszLabel = (char *) oSubBox.ReadBoxData(); if( pszLabel != NULL && EQUAL(pszLabel,"gml.data") ) { CollectGMLData( &oBox ); } CPLFree( pszLabel ); } } /* -------------------------------------------------------------------- */ /* Process simple xml boxes. */ /* -------------------------------------------------------------------- */ if( EQUAL(oBox.GetType(),"xml ") ) { CPLString osBoxName; char *pszXML = (char *) oBox.ReadBoxData(); if( pszXML != NULL && STARTS_WITH(pszXML, "<GDALMultiDomainMetadata>") ) { if( pszGDALMultiDomainMetadata == NULL ) { pszGDALMultiDomainMetadata = pszXML; pszXML = NULL; } else { CPLDebug( "GDALJP2", "Too many GDAL metadata boxes. Ignoring this one"); } } else if( pszXML != NULL ) { osBoxName.Printf( "BOX_%d", iBox++ ); papszGMLMetadata = CSLSetNameValue( papszGMLMetadata, osBoxName, pszXML ); } CPLFree( pszXML ); } /* -------------------------------------------------------------------- */ /* Check for a resd box in jp2h. */ /* -------------------------------------------------------------------- */ if( EQUAL(oBox.GetType(),"jp2h") ) { GDALJP2Box oSubBox( fpVSIL ); for( oSubBox.ReadFirstChild( &oBox ); strlen(oSubBox.GetType()) > 0; oSubBox.ReadNextChild( &oBox ) ) { if( EQUAL(oSubBox.GetType(),"res ") ) { GDALJP2Box oResBox( fpVSIL ); oResBox.ReadFirstChild( &oSubBox ); // We will use either the resd or resc box, which ever // happens to be first. Should we prefer resd? unsigned char *pabyResData = NULL; if( oResBox.GetDataLength() == 10 && (pabyResData = oResBox.ReadBoxData()) != NULL ) { int nVertNum, nVertDen, nVertExp; int nHorzNum, nHorzDen, nHorzExp; nVertNum = pabyResData[0] * 256 + pabyResData[1]; nVertDen = pabyResData[2] * 256 + pabyResData[3]; nHorzNum = pabyResData[4] * 256 + pabyResData[5]; nHorzDen = pabyResData[6] * 256 + pabyResData[7]; nVertExp = pabyResData[8]; nHorzExp = pabyResData[9]; // compute in pixels/cm const double dfVertRes = (nVertNum / static_cast<double>(nVertDen)) * pow(10.0, nVertExp) / 100; const double dfHorzRes = (nHorzNum / static_cast<double>(nHorzDen)) * pow(10.0,nHorzExp)/100; CPLString osFormatter; papszMetadata = CSLSetNameValue( papszMetadata, "TIFFTAG_XRESOLUTION", osFormatter.Printf("%g",dfHorzRes) ); papszMetadata = CSLSetNameValue( papszMetadata, "TIFFTAG_YRESOLUTION", osFormatter.Printf("%g",dfVertRes) ); papszMetadata = CSLSetNameValue( papszMetadata, "TIFFTAG_RESOLUTIONUNIT", "3 (pixels/cm)" ); CPLFree( pabyResData ); } } } } /* -------------------------------------------------------------------- */ /* Collect IPR box. */ /* -------------------------------------------------------------------- */ if( EQUAL(oBox.GetType(),"jp2i") ) { if( pszXMLIPR == NULL ) { pszXMLIPR = (char*) oBox.ReadBoxData(); CPLXMLNode* psNode = CPLParseXMLString(pszXMLIPR); if( psNode == NULL ) { CPLFree(pszXMLIPR); pszXMLIPR = NULL; } else CPLDestroyXMLNode(psNode); } else { CPLDebug("GDALJP2", "Too many IPR boxes. Ignoring this one"); } } if (!oBox.ReadNext()) break; } return TRUE; } /************************************************************************/ /* ParseJP2GeoTIFF() */ /************************************************************************/ int GDALJP2Metadata::ParseJP2GeoTIFF() { if(! CPLTestBool(CPLGetConfigOption("GDAL_USE_GEOJP2", "TRUE")) ) return FALSE; bool abValidProjInfo[MAX_JP2GEOTIFF_BOXES] = { false }; char* apszProjection[MAX_JP2GEOTIFF_BOXES] = { NULL }; double aadfGeoTransform[MAX_JP2GEOTIFF_BOXES][6]; int anGCPCount[MAX_JP2GEOTIFF_BOXES] = { 0 }; GDAL_GCP *apasGCPList[MAX_JP2GEOTIFF_BOXES] = { NULL }; int abPixelIsPoint[MAX_JP2GEOTIFF_BOXES] = { 0 }; char** apapszRPCMD[MAX_JP2GEOTIFF_BOXES] = { NULL }; const int nMax = std::min(nGeoTIFFBoxesCount, MAX_JP2GEOTIFF_BOXES); for( int i = 0; i < nMax; ++i ) { /* -------------------------------------------------------------------- */ /* Convert raw data into projection and geotransform. */ /* -------------------------------------------------------------------- */ aadfGeoTransform[i][0] = 0; aadfGeoTransform[i][1] = 1; aadfGeoTransform[i][2] = 0; aadfGeoTransform[i][3] = 0; aadfGeoTransform[i][4] = 0; aadfGeoTransform[i][5] = 1; if( GTIFWktFromMemBufEx( pasGeoTIFFBoxes[i].nGeoTIFFSize, pasGeoTIFFBoxes[i].pabyGeoTIFFData, &apszProjection[i], aadfGeoTransform[i], &anGCPCount[i], &apasGCPList[i], &abPixelIsPoint[i], &apapszRPCMD[i] ) == CE_None ) { if( apszProjection[i] != NULL && strlen(apszProjection[i]) != 0 ) abValidProjInfo[i] = true; } } // Detect which box is the better one. int iBestIndex = -1; for( int i = 0; i < nMax; ++i ) { if( abValidProjInfo[i] && iBestIndex < 0 ) { iBestIndex = i; } else if( abValidProjInfo[i] && apszProjection[i] != NULL ) { // Anything else than a LOCAL_CS will probably be better. if( STARTS_WITH_CI(apszProjection[iBestIndex], "LOCAL_CS") ) iBestIndex = i; } } if( iBestIndex < 0 ) { for( int i = 0; i < nMax; ++i ) { if( aadfGeoTransform[i][0] != 0 || aadfGeoTransform[i][1] != 1 || aadfGeoTransform[i][2] != 0 || aadfGeoTransform[i][3] != 0 || aadfGeoTransform[i][4] != 0 || aadfGeoTransform[i][5] != 1 || anGCPCount[i] > 0 || apapszRPCMD[i] != NULL ) { iBestIndex = i; } } } if( iBestIndex >= 0 ) { pszProjection = apszProjection[iBestIndex]; memcpy(adfGeoTransform, aadfGeoTransform[iBestIndex], 6 * sizeof(double)); nGCPCount = anGCPCount[iBestIndex]; pasGCPList = apasGCPList[iBestIndex]; bPixelIsPoint = CPL_TO_BOOL(abPixelIsPoint[iBestIndex]); papszRPCMD = apapszRPCMD[iBestIndex]; if( adfGeoTransform[0] != 0 || adfGeoTransform[1] != 1 || adfGeoTransform[2] != 0 || adfGeoTransform[3] != 0 || adfGeoTransform[4] != 0 || adfGeoTransform[5] != 1 ) bHaveGeoTransform = true; if( pszProjection ) CPLDebug( "GDALJP2Metadata", "Got projection from GeoJP2 (geotiff) box (%d): %s", iBestIndex, pszProjection ); } // Cleanup unused boxes. for( int i = 0; i < nMax; ++i ) { if( i != iBestIndex ) { CPLFree( apszProjection[i] ); if( anGCPCount[i] > 0 ) { GDALDeinitGCPs( anGCPCount[i], apasGCPList[i] ); CPLFree( apasGCPList[i] ); } CSLDestroy( apapszRPCMD[i] ); } } return iBestIndex >= 0; } /************************************************************************/ /* ParseMSIG() */ /************************************************************************/ int GDALJP2Metadata::ParseMSIG() { if( nMSIGSize < 70 ) return FALSE; /* -------------------------------------------------------------------- */ /* Try and extract worldfile parameters and adjust. */ /* -------------------------------------------------------------------- */ memcpy( adfGeoTransform + 0, pabyMSIGData + 22 + 8 * 4, 8 ); memcpy( adfGeoTransform + 1, pabyMSIGData + 22 + 8 * 0, 8 ); memcpy( adfGeoTransform + 2, pabyMSIGData + 22 + 8 * 2, 8 ); memcpy( adfGeoTransform + 3, pabyMSIGData + 22 + 8 * 5, 8 ); memcpy( adfGeoTransform + 4, pabyMSIGData + 22 + 8 * 1, 8 ); memcpy( adfGeoTransform + 5, pabyMSIGData + 22 + 8 * 3, 8 ); // data is in LSB (little endian) order in file. CPL_LSBPTR64( adfGeoTransform + 0 ); CPL_LSBPTR64( adfGeoTransform + 1 ); CPL_LSBPTR64( adfGeoTransform + 2 ); CPL_LSBPTR64( adfGeoTransform + 3 ); CPL_LSBPTR64( adfGeoTransform + 4 ); CPL_LSBPTR64( adfGeoTransform + 5 ); // correct for center of pixel vs. top left of pixel adfGeoTransform[0] -= 0.5 * adfGeoTransform[1]; adfGeoTransform[0] -= 0.5 * adfGeoTransform[2]; adfGeoTransform[3] -= 0.5 * adfGeoTransform[4]; adfGeoTransform[3] -= 0.5 * adfGeoTransform[5]; bHaveGeoTransform = true; return TRUE; } /************************************************************************/ /* GetDictionaryItem() */ /************************************************************************/ static CPLXMLNode * GetDictionaryItem( char **papszGMLMetadata, const char *pszURN ) { char *pszLabel = NULL; if( STARTS_WITH_CI(pszURN, "urn:jp2k:xml:") ) pszLabel = CPLStrdup( pszURN + 13 ); else if( STARTS_WITH_CI(pszURN, "urn:ogc:tc:gmljp2:xml:") ) pszLabel = CPLStrdup( pszURN + 22 ); else if( STARTS_WITH_CI(pszURN, "gmljp2://xml/") ) pszLabel = CPLStrdup( pszURN + 13 ); else pszLabel = CPLStrdup( pszURN ); /* -------------------------------------------------------------------- */ /* Split out label and fragment id. */ /* -------------------------------------------------------------------- */ const char *pszFragmentId = NULL; { int i = 0; // Used after for. for( ; pszLabel[i] != '#'; ++i ) { if( pszLabel[i] == '\0' ) { CPLFree(pszLabel); return NULL; } } pszFragmentId = pszLabel + i + 1; pszLabel[i] = '\0'; } /* -------------------------------------------------------------------- */ /* Can we find an XML box with the desired label? */ /* -------------------------------------------------------------------- */ const char *pszDictionary = CSLFetchNameValue( papszGMLMetadata, pszLabel ); if( pszDictionary == NULL ) { CPLFree(pszLabel); return NULL; } /* -------------------------------------------------------------------- */ /* Try and parse the dictionary. */ /* -------------------------------------------------------------------- */ CPLXMLNode *psDictTree = CPLParseXMLString( pszDictionary ); if( psDictTree == NULL ) { CPLFree(pszLabel); return NULL; } CPLStripXMLNamespace( psDictTree, NULL, TRUE ); CPLXMLNode *psDictRoot = CPLSearchXMLNode( psDictTree, "=Dictionary" ); if( psDictRoot == NULL ) { CPLDestroyXMLNode( psDictTree ); CPLFree(pszLabel); return NULL; } /* -------------------------------------------------------------------- */ /* Search for matching id. */ /* -------------------------------------------------------------------- */ CPLXMLNode *psEntry, *psHit = NULL; for( psEntry = psDictRoot->psChild; psEntry != NULL && psHit == NULL; psEntry = psEntry->psNext ) { const char *pszId; if( psEntry->eType != CXT_Element ) continue; if( !EQUAL(psEntry->pszValue,"dictionaryEntry") ) continue; if( psEntry->psChild == NULL ) continue; pszId = CPLGetXMLValue( psEntry->psChild, "id", "" ); if( EQUAL(pszId, pszFragmentId) ) psHit = CPLCloneXMLTree( psEntry->psChild ); } /* -------------------------------------------------------------------- */ /* Cleanup */ /* -------------------------------------------------------------------- */ CPLFree( pszLabel ); CPLDestroyXMLNode( psDictTree ); return psHit; } /************************************************************************/ /* GMLSRSLookup() */ /* */ /* Lookup an SRS in a dictionary inside this file. We will get */ /* something like: */ /* urn:jp2k:xml:CRSDictionary.xml#crs1112 */ /* */ /* We need to split the filename from the fragment id, and */ /* lookup the fragment in the file if we can find it our */ /* list of labelled xml boxes. */ /************************************************************************/ int GDALJP2Metadata::GMLSRSLookup( const char *pszURN ) { CPLXMLNode *psDictEntry = GetDictionaryItem( papszGMLMetadata, pszURN ); if( psDictEntry == NULL ) return FALSE; /* -------------------------------------------------------------------- */ /* Reserialize this fragment. */ /* -------------------------------------------------------------------- */ char *pszDictEntryXML = CPLSerializeXMLTree( psDictEntry ); CPLDestroyXMLNode( psDictEntry ); /* -------------------------------------------------------------------- */ /* Try to convert into an OGRSpatialReference. */ /* -------------------------------------------------------------------- */ OGRSpatialReference oSRS; bool bSuccess = false; if( oSRS.importFromXML( pszDictEntryXML ) == OGRERR_NONE ) { CPLFree( pszProjection ); pszProjection = NULL; oSRS.exportToWkt( &pszProjection ); bSuccess = true; } CPLFree( pszDictEntryXML ); return bSuccess; } /************************************************************************/ /* ParseGMLCoverageDesc() */ /************************************************************************/ int GDALJP2Metadata::ParseGMLCoverageDesc() { if(! CPLTestBool(CPLGetConfigOption("GDAL_USE_GMLJP2", "TRUE")) ) return FALSE; /* -------------------------------------------------------------------- */ /* Do we have an XML doc that is apparently a coverage */ /* description? */ /* -------------------------------------------------------------------- */ const char *pszCoverage = CSLFetchNameValue( papszGMLMetadata, "gml.root-instance" ); if( pszCoverage == NULL ) return FALSE; CPLDebug( "GDALJP2Metadata", "Found GML Box:\n%s", pszCoverage ); /* -------------------------------------------------------------------- */ /* Try parsing the XML. Wipe any namespace prefixes. */ /* -------------------------------------------------------------------- */ CPLXMLNode *psXML = CPLParseXMLString( pszCoverage ); if( psXML == NULL ) return FALSE; CPLStripXMLNamespace( psXML, NULL, TRUE ); /* -------------------------------------------------------------------- */ /* Isolate RectifiedGrid. Eventually we will need to support */ /* other georeferencing objects. */ /* -------------------------------------------------------------------- */ CPLXMLNode *psRG = CPLSearchXMLNode( psXML, "=RectifiedGrid" ); CPLXMLNode *psOriginPoint = NULL; const char *pszOffset1 = NULL; const char *pszOffset2 = NULL; if( psRG != NULL ) { psOriginPoint = CPLGetXMLNode( psRG, "origin.Point" ); CPLXMLNode *psOffset1 = CPLGetXMLNode( psRG, "offsetVector" ); if( psOffset1 != NULL ) { pszOffset1 = CPLGetXMLValue( psOffset1, "", NULL ); pszOffset2 = CPLGetXMLValue( psOffset1->psNext, "=offsetVector", NULL ); } } /* -------------------------------------------------------------------- */ /* If we are missing any of the origin or 2 offsets then give up. */ /* -------------------------------------------------------------------- */ if( psOriginPoint == NULL || pszOffset1 == NULL || pszOffset2 == NULL ) { CPLDestroyXMLNode( psXML ); return FALSE; } /* -------------------------------------------------------------------- */ /* Extract origin location. */ /* -------------------------------------------------------------------- */ OGRPoint *poOriginGeometry = NULL; const char *pszSRSName = NULL; if( psOriginPoint != NULL ) { poOriginGeometry = (OGRPoint *) OGR_G_CreateFromGMLTree( psOriginPoint ); if( poOriginGeometry != NULL && wkbFlatten(poOriginGeometry->getGeometryType()) != wkbPoint ) { delete poOriginGeometry; poOriginGeometry = NULL; } // SRS? pszSRSName = CPLGetXMLValue( psOriginPoint, "srsName", NULL ); } /* -------------------------------------------------------------------- */ /* Extract offset(s) */ /* -------------------------------------------------------------------- */ char **papszOffset1Tokens = NULL; char **papszOffset2Tokens = NULL; bool bSuccess = false; papszOffset1Tokens = CSLTokenizeStringComplex( pszOffset1, " ,", FALSE, FALSE ); papszOffset2Tokens = CSLTokenizeStringComplex( pszOffset2, " ,", FALSE, FALSE ); if( CSLCount(papszOffset1Tokens) >= 2 && CSLCount(papszOffset2Tokens) >= 2 && poOriginGeometry != NULL ) { adfGeoTransform[0] = poOriginGeometry->getX(); adfGeoTransform[1] = CPLAtof(papszOffset1Tokens[0]); adfGeoTransform[2] = CPLAtof(papszOffset2Tokens[0]); adfGeoTransform[3] = poOriginGeometry->getY(); adfGeoTransform[4] = CPLAtof(papszOffset1Tokens[1]); adfGeoTransform[5] = CPLAtof(papszOffset2Tokens[1]); // offset from center of pixel. adfGeoTransform[0] -= adfGeoTransform[1]*0.5; adfGeoTransform[0] -= adfGeoTransform[2]*0.5; adfGeoTransform[3] -= adfGeoTransform[4]*0.5; adfGeoTransform[3] -= adfGeoTransform[5]*0.5; bSuccess = true; bHaveGeoTransform = true; } CSLDestroy( papszOffset1Tokens ); CSLDestroy( papszOffset2Tokens ); if( poOriginGeometry != NULL ) delete poOriginGeometry; /* -------------------------------------------------------------------- */ /* If we still don't have an srsName, check for it on the */ /* boundedBy Envelope. Some products */ /* (i.e. EuropeRasterTile23.jpx) use this as the only srsName */ /* delivery vehicle. */ /* -------------------------------------------------------------------- */ if( pszSRSName == NULL ) { pszSRSName = CPLGetXMLValue( psXML, "=FeatureCollection.boundedBy.Envelope.srsName", NULL ); } /* -------------------------------------------------------------------- */ /* Examples of DGIWG_Profile_of_JPEG2000_for_Georeference_Imagery.pdf */ /* have srsName only on RectifiedGrid element. */ /* -------------------------------------------------------------------- */ if( psRG != NULL && pszSRSName == NULL ) { pszSRSName = CPLGetXMLValue( psRG, "srsName", NULL ); } /* -------------------------------------------------------------------- */ /* If we have gotten a geotransform, then try to interpret the */ /* srsName. */ /* -------------------------------------------------------------------- */ bool bNeedAxisFlip = false; OGRSpatialReference oSRS; if( bSuccess && pszSRSName != NULL && (pszProjection == NULL || strlen(pszProjection) == 0) ) { if( STARTS_WITH_CI(pszSRSName, "epsg:") ) { if( oSRS.SetFromUserInput( pszSRSName ) == OGRERR_NONE ) oSRS.exportToWkt( &pszProjection ); } else if( (STARTS_WITH_CI(pszSRSName, "urn:") && strstr(pszSRSName,":def:") != NULL && oSRS.importFromURN(pszSRSName) == OGRERR_NONE) || /* GMLJP2 v2.0 uses CRS URL instead of URN */ /* See e.g. http://schemas.opengis.net/gmljp2/2.0/examples/minimalInstance.xml */ (STARTS_WITH_CI(pszSRSName, "http://www.opengis.net/def/crs/") && oSRS.importFromCRSURL(pszSRSName) == OGRERR_NONE) ) { oSRS.exportToWkt( &pszProjection ); // Per #2131 if( oSRS.EPSGTreatsAsLatLong() || oSRS.EPSGTreatsAsNorthingEasting() ) { CPLDebug( "GMLJP2", "Request axis flip for SRS=%s", pszSRSName ); bNeedAxisFlip = true; } } else if( !GMLSRSLookup( pszSRSName ) ) { CPLDebug( "GDALJP2Metadata", "Unable to evaluate SRSName=%s", pszSRSName ); } } if( pszProjection ) CPLDebug( "GDALJP2Metadata", "Got projection from GML box: %s", pszProjection ); /* -------------------------------------------------------------------- */ /* Do we need to flip the axes? */ /* -------------------------------------------------------------------- */ if( bNeedAxisFlip && CPLTestBool( CPLGetConfigOption( "GDAL_IGNORE_AXIS_ORIENTATION", "FALSE" ) ) ) { bNeedAxisFlip = false; CPLDebug( "GMLJP2", "Suppressed axis flipping based on GDAL_IGNORE_AXIS_ORIENTATION." ); } if( pszSRSName && bNeedAxisFlip ) { // Suppress explicit axis order in SRS definition OGR_SRSNode *poGEOGCS = oSRS.GetAttrNode( "GEOGCS" ); if( poGEOGCS != NULL ) poGEOGCS->StripNodes( "AXIS" ); OGR_SRSNode *poPROJCS = oSRS.GetAttrNode( "PROJCS" ); if (poPROJCS != NULL && oSRS.EPSGTreatsAsNorthingEasting()) poPROJCS->StripNodes( "AXIS" ); CPLFree(pszProjection); oSRS.exportToWkt( &pszProjection ); } /* Some Pleiades files have explicit <gml:axisName>Easting</gml:axisName> */ /* <gml:axisName>Northing</gml:axisName> to override default EPSG order */ if( bNeedAxisFlip && psRG != NULL ) { int nAxisCount = 0; bool bFirstAxisIsEastOrLong = false; bool bSecondAxisIsNorthOrLat = false; for(CPLXMLNode* psIter = psRG->psChild; psIter != NULL; psIter = psIter->psNext ) { if( psIter->eType == CXT_Element && strcmp(psIter->pszValue, "axisName") == 0 && psIter->psChild != NULL && psIter->psChild->eType == CXT_Text ) { if( nAxisCount == 0 && (STARTS_WITH_CI(psIter->psChild->pszValue, "EAST") || STARTS_WITH_CI(psIter->psChild->pszValue, "LONG") ) ) { bFirstAxisIsEastOrLong = true; } else if( nAxisCount == 1 && (STARTS_WITH_CI(psIter->psChild->pszValue, "NORTH") || STARTS_WITH_CI(psIter->psChild->pszValue, "LAT")) ) { bSecondAxisIsNorthOrLat = true; } ++nAxisCount; } } if( bFirstAxisIsEastOrLong && bSecondAxisIsNorthOrLat ) { CPLDebug( "GMLJP2", "Disable axis flip because of explicit axisName disabling it" ); bNeedAxisFlip = false; } } CPLDestroyXMLNode( psXML ); psXML = NULL; psRG = NULL; if( bNeedAxisFlip ) { double dfTemp; CPLDebug( "GMLJP2", "Flipping axis orientation in GMLJP2 coverage description." ); dfTemp = adfGeoTransform[0]; adfGeoTransform[0] = adfGeoTransform[3]; adfGeoTransform[3] = dfTemp; int swapWith1Index = 4; int swapWith2Index = 5; /* Look if we have GDAL_JP2K_ALT_OFFSETVECTOR_ORDER=TRUE as a XML comment */ int bHasAltOffsetVectorOrderComment = strstr(pszCoverage, "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER=TRUE") != NULL; if( bHasAltOffsetVectorOrderComment || CPLTestBool( CPLGetConfigOption( "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER", "FALSE" ) ) ) { swapWith1Index = 5; swapWith2Index = 4; CPLDebug( "GMLJP2", "Choosing alternate GML \"<offsetVector>\" order based on " "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER." ); } dfTemp = adfGeoTransform[1]; adfGeoTransform[1] = adfGeoTransform[swapWith1Index]; adfGeoTransform[swapWith1Index] = dfTemp; dfTemp = adfGeoTransform[2]; adfGeoTransform[2] = adfGeoTransform[swapWith2Index]; adfGeoTransform[swapWith2Index] = dfTemp; /* Found in autotest/gdrivers/data/ll.jp2 */ if( adfGeoTransform[1] == 0.0 && adfGeoTransform[2] < 0.0 && adfGeoTransform[4] > 0.0 && adfGeoTransform[5] == 0.0 ) { CPLError(CE_Warning, CPLE_AppDefined, "It is likely that the axis order of the GMLJP2 box is not " "consistent with the EPSG order and that the resulting georeferencing " "will be incorrect. Try setting GDAL_IGNORE_AXIS_ORIENTATION=TRUE if it is the case"); } } return pszProjection != NULL && bSuccess; } /************************************************************************/ /* SetProjection() */ /************************************************************************/ void GDALJP2Metadata::SetProjection( const char *pszWKT ) { CPLFree( pszProjection ); pszProjection = CPLStrdup(pszWKT); } /************************************************************************/ /* SetGCPs() */ /************************************************************************/ void GDALJP2Metadata::SetGCPs( int nCount, const GDAL_GCP *pasGCPsIn ) { if( nGCPCount > 0 ) { GDALDeinitGCPs( nGCPCount, pasGCPList ); CPLFree( pasGCPList ); } nGCPCount = nCount; pasGCPList = GDALDuplicateGCPs(nGCPCount, pasGCPsIn); } /************************************************************************/ /* SetGeoTransform() */ /************************************************************************/ void GDALJP2Metadata::SetGeoTransform( double *padfGT ) { memcpy( adfGeoTransform, padfGT, sizeof(double) * 6 ); } /************************************************************************/ /* SetRPCMD() */ /************************************************************************/ void GDALJP2Metadata::SetRPCMD( char** papszRPCMDIn ) { CSLDestroy( papszRPCMD ); papszRPCMD = CSLDuplicate(papszRPCMDIn); } /************************************************************************/ /* CreateJP2GeoTIFF() */ /************************************************************************/ GDALJP2Box *GDALJP2Metadata::CreateJP2GeoTIFF() { /* -------------------------------------------------------------------- */ /* Prepare the memory buffer containing the degenerate GeoTIFF */ /* file. */ /* -------------------------------------------------------------------- */ int nGTBufSize = 0; unsigned char *pabyGTBuf = NULL; if( GTIFMemBufFromWktEx( pszProjection, adfGeoTransform, nGCPCount, pasGCPList, &nGTBufSize, &pabyGTBuf, bPixelIsPoint, papszRPCMD ) != CE_None ) return NULL; if( nGTBufSize == 0 ) return NULL; /* -------------------------------------------------------------------- */ /* Write to a box on the JP2 file. */ /* -------------------------------------------------------------------- */ GDALJP2Box *poBox; poBox = GDALJP2Box::CreateUUIDBox( msi_uuid2, nGTBufSize, pabyGTBuf ); CPLFree( pabyGTBuf ); return poBox; } /************************************************************************/ /* GetGMLJP2GeoreferencingInfo() */ /************************************************************************/ int GDALJP2Metadata::GetGMLJP2GeoreferencingInfo( int& nEPSGCode, double adfOrigin[2], double adfXVector[2], double adfYVector[2], const char*& pszComment, CPLString& osDictBox, int& bNeedAxisFlip ) { /* -------------------------------------------------------------------- */ /* Try do determine a PCS or GCS code we can use. */ /* -------------------------------------------------------------------- */ OGRSpatialReference oSRS; char *pszWKTCopy = (char *) pszProjection; nEPSGCode = 0; bNeedAxisFlip = FALSE; if( oSRS.importFromWkt( &pszWKTCopy ) != OGRERR_NONE ) return FALSE; if( oSRS.IsProjected() ) { const char *pszAuthName = oSRS.GetAuthorityName( "PROJCS" ); if( pszAuthName != NULL && EQUAL(pszAuthName,"epsg") ) { nEPSGCode = atoi(oSRS.GetAuthorityCode( "PROJCS" )); } } else if( oSRS.IsGeographic() ) { const char *pszAuthName = oSRS.GetAuthorityName( "GEOGCS" ); if( pszAuthName != NULL && EQUAL(pszAuthName,"epsg") ) { nEPSGCode = atoi(oSRS.GetAuthorityCode( "GEOGCS" )); } } // Save error state as importFromEPSGA() will call CPLReset() CPLErrorNum errNo = CPLGetLastErrorNo(); CPLErr eErr = CPLGetLastErrorType(); CPLString osLastErrorMsg = CPLGetLastErrorMsg(); // Determine if we need to flip axis. Reimport from EPSG and make // sure not to strip axis definitions to determine the axis order. if( nEPSGCode != 0 && oSRS.importFromEPSGA(nEPSGCode) == OGRERR_NONE ) { if( oSRS.EPSGTreatsAsLatLong() || oSRS.EPSGTreatsAsNorthingEasting() ) { bNeedAxisFlip = TRUE; } } // Restore error state CPLErrorSetState( eErr, errNo, osLastErrorMsg); /* -------------------------------------------------------------------- */ /* Prepare coverage origin and offset vectors. Take axis */ /* order into account if needed. */ /* -------------------------------------------------------------------- */ adfOrigin[0] = adfGeoTransform[0] + adfGeoTransform[1] * 0.5 + adfGeoTransform[4] * 0.5; adfOrigin[1] = adfGeoTransform[3] + adfGeoTransform[2] * 0.5 + adfGeoTransform[5] * 0.5; adfXVector[0] = adfGeoTransform[1]; adfXVector[1] = adfGeoTransform[2]; adfYVector[0] = adfGeoTransform[4]; adfYVector[1] = adfGeoTransform[5]; if( bNeedAxisFlip && CPLTestBool( CPLGetConfigOption( "GDAL_IGNORE_AXIS_ORIENTATION", "FALSE" ) ) ) { bNeedAxisFlip = FALSE; CPLDebug( "GMLJP2", "Suppressed axis flipping on write based on GDAL_IGNORE_AXIS_ORIENTATION." ); } pszComment = ""; if( bNeedAxisFlip ) { double dfTemp; CPLDebug( "GMLJP2", "Flipping GML coverage axis order." ); dfTemp = adfOrigin[0]; adfOrigin[0] = adfOrigin[1]; adfOrigin[1] = dfTemp; if( CPLTestBool( CPLGetConfigOption( "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER", "FALSE" ) ) ) { CPLDebug( "GMLJP2", "Choosing alternate GML \"<offsetVector>\" order based on " "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER." ); /* In this case the swapping is done in an "X" pattern */ dfTemp = adfXVector[0]; adfXVector[0] = adfYVector[1]; adfYVector[1] = dfTemp; dfTemp = adfYVector[0]; adfYVector[0] = adfXVector[1]; adfXVector[1] = dfTemp; /* We add this as an XML comment so that we know we must do OffsetVector flipping on reading */ pszComment = " <!-- GDAL_JP2K_ALT_OFFSETVECTOR_ORDER=TRUE: First " "value of offset is latitude/northing component of the " "latitude/northing axis. -->\n"; } else { dfTemp = adfXVector[0]; adfXVector[0] = adfXVector[1]; adfXVector[1] = dfTemp; dfTemp = adfYVector[0]; adfYVector[0] = adfYVector[1]; adfYVector[1] = dfTemp; } } /* -------------------------------------------------------------------- */ /* If we need a user defined CRSDictionary entry, prepare it */ /* here. */ /* -------------------------------------------------------------------- */ if( nEPSGCode == 0 ) { char *pszGMLDef = NULL; if( oSRS.exportToXML( &pszGMLDef, NULL ) == OGRERR_NONE ) { char* pszWKT = NULL; oSRS.exportToWkt(&pszWKT); char* pszXMLEscapedWKT = CPLEscapeString(pszWKT, -1, CPLES_XML); CPLFree(pszWKT); osDictBox.Printf( "<gml:Dictionary gml:id=\"CRSU1\" \n" " xmlns:gml=\"http://www.opengis.net/gml\"\n" " xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n" " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" " xsi:schemaLocation=\"http://www.opengis.net/gml " "http://schemas.opengis.net/gml/3.1.1/base/gml.xsd\">\n" " <gml:description>Dictionary for custom SRS %s</gml:description>\n" " <gml:name>Dictionary for custom SRS</gml:name>\n" " <gml:dictionaryEntry>\n" "%s\n" " </gml:dictionaryEntry>\n" "</gml:Dictionary>\n", pszXMLEscapedWKT, pszGMLDef ); CPLFree(pszXMLEscapedWKT); } CPLFree( pszGMLDef ); } return TRUE; } /************************************************************************/ /* CreateGMLJP2() */ /************************************************************************/ GDALJP2Box *GDALJP2Metadata::CreateGMLJP2( int nXSize, int nYSize ) { /* -------------------------------------------------------------------- */ /* This is a backdoor to let us embed a literal gmljp2 chunk */ /* supplied by the user as an external file. This is mostly */ /* for preparing test files with exotic contents. */ /* -------------------------------------------------------------------- */ if( CPLGetConfigOption( "GMLJP2OVERRIDE", NULL ) != NULL ) { VSILFILE *fp = VSIFOpenL( CPLGetConfigOption( "GMLJP2OVERRIDE",""), "r" ); char *pszGML = NULL; if( fp == NULL ) { CPLError( CE_Failure, CPLE_AppDefined, "Unable to open GMLJP2OVERRIDE file." ); return NULL; } CPL_IGNORE_RET_VAL(VSIFSeekL( fp, 0, SEEK_END )); const int nLength = static_cast<int>( VSIFTellL( fp ) ); pszGML = (char *) CPLCalloc(1,nLength+1); CPL_IGNORE_RET_VAL(VSIFSeekL( fp, 0, SEEK_SET )); CPL_IGNORE_RET_VAL(VSIFReadL( pszGML, 1, nLength, fp )); CPL_IGNORE_RET_VAL(VSIFCloseL( fp )); GDALJP2Box *apoGMLBoxes[2]; apoGMLBoxes[0] = GDALJP2Box::CreateLblBox( "gml.data" ); apoGMLBoxes[1] = GDALJP2Box::CreateLabelledXMLAssoc( "gml.root-instance", pszGML ); GDALJP2Box *poGMLData = GDALJP2Box::CreateAsocBox( 2, apoGMLBoxes); delete apoGMLBoxes[0]; delete apoGMLBoxes[1]; CPLFree( pszGML ); return poGMLData; } int nEPSGCode; double adfOrigin[2]; double adfXVector[2]; double adfYVector[2]; const char* pszComment = ""; CPLString osDictBox; int bNeedAxisFlip = FALSE; if( !GetGMLJP2GeoreferencingInfo( nEPSGCode, adfOrigin, adfXVector, adfYVector, pszComment, osDictBox, bNeedAxisFlip ) ) { return NULL; } char szSRSName[100]; if( nEPSGCode != 0 ) snprintf( szSRSName, sizeof(szSRSName), "urn:ogc:def:crs:EPSG::%d", nEPSGCode ); else snprintf( szSRSName, sizeof(szSRSName), "%s", "gmljp2://xml/CRSDictionary.gml#ogrcrs1" ); // Compute bounding box double dfX1 = adfGeoTransform[0]; double dfX2 = adfGeoTransform[0] + nXSize * adfGeoTransform[1]; double dfX3 = adfGeoTransform[0] + nYSize * adfGeoTransform[2]; double dfX4 = adfGeoTransform[0] + nXSize * adfGeoTransform[1] + nYSize * adfGeoTransform[2]; double dfY1 = adfGeoTransform[3]; double dfY2 = adfGeoTransform[3] + nXSize * adfGeoTransform[4]; double dfY3 = adfGeoTransform[3] + nYSize * adfGeoTransform[5]; double dfY4 = adfGeoTransform[3] + nXSize * adfGeoTransform[4] + nYSize * adfGeoTransform[5]; double dfLCX = std::min(std::min(dfX1, dfX2), std::min(dfX3, dfX4)); double dfLCY = std::min(std::min(dfY1, dfY2), std::min(dfY3, dfY4)); double dfUCX = std::max(std::max(dfX1, dfX2), std::max(dfX3, dfX4)); double dfUCY = std::max(std::max(dfY1, dfY2), std::max(dfY3, dfY4)); if( bNeedAxisFlip ) { double dfTmp = dfLCX; dfLCX = dfLCY; dfLCY = dfTmp; dfTmp = dfUCX; dfUCX = dfUCY; dfUCY = dfTmp; } /* -------------------------------------------------------------------- */ /* For now we hardcode for a minimal instance format. */ /* -------------------------------------------------------------------- */ CPLString osDoc; osDoc.Printf( "<gml:FeatureCollection\n" " xmlns:gml=\"http://www.opengis.net/gml\"\n" " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" " xsi:schemaLocation=\"http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlJP2Profile/1.0.0/gmlJP2Profile.xsd\">\n" " <gml:boundedBy>\n" " <gml:Envelope srsName=\"%s\">\n" " <gml:lowerCorner>%.15g %.15g</gml:lowerCorner>\n" " <gml:upperCorner>%.15g %.15g</gml:upperCorner>\n" " </gml:Envelope>\n" " </gml:boundedBy>\n" " <gml:featureMember>\n" " <gml:FeatureCollection>\n" " <gml:featureMember>\n" " <gml:RectifiedGridCoverage dimension=\"2\" gml:id=\"RGC0001\">\n" " <gml:rectifiedGridDomain>\n" " <gml:RectifiedGrid dimension=\"2\">\n" " <gml:limits>\n" " <gml:GridEnvelope>\n" " <gml:low>0 0</gml:low>\n" " <gml:high>%d %d</gml:high>\n" " </gml:GridEnvelope>\n" " </gml:limits>\n" " <gml:axisName>x</gml:axisName>\n" " <gml:axisName>y</gml:axisName>\n" " <gml:origin>\n" " <gml:Point gml:id=\"P0001\" srsName=\"%s\">\n" " <gml:pos>%.15g %.15g</gml:pos>\n" " </gml:Point>\n" " </gml:origin>\n" "%s" " <gml:offsetVector srsName=\"%s\">%.15g %.15g</gml:offsetVector>\n" " <gml:offsetVector srsName=\"%s\">%.15g %.15g</gml:offsetVector>\n" " </gml:RectifiedGrid>\n" " </gml:rectifiedGridDomain>\n" " <gml:rangeSet>\n" " <gml:File>\n" " <gml:rangeParameters/>\n" " <gml:fileName>gmljp2://codestream/0</gml:fileName>\n" " <gml:fileStructure>Record Interleaved</gml:fileStructure>\n" " </gml:File>\n" " </gml:rangeSet>\n" " </gml:RectifiedGridCoverage>\n" " </gml:featureMember>\n" " </gml:FeatureCollection>\n" " </gml:featureMember>\n" "</gml:FeatureCollection>\n", szSRSName, dfLCX, dfLCY, dfUCX, dfUCY, nXSize-1, nYSize-1, szSRSName, adfOrigin[0], adfOrigin[1], pszComment, szSRSName, adfXVector[0], adfXVector[1], szSRSName, adfYVector[0], adfYVector[1] ); /* -------------------------------------------------------------------- */ /* Setup the gml.data label. */ /* -------------------------------------------------------------------- */ GDALJP2Box *apoGMLBoxes[5]; int nGMLBoxes = 0; apoGMLBoxes[nGMLBoxes++] = GDALJP2Box::CreateLblBox( "gml.data" ); /* -------------------------------------------------------------------- */ /* Setup gml.root-instance. */ /* -------------------------------------------------------------------- */ apoGMLBoxes[nGMLBoxes++] = GDALJP2Box::CreateLabelledXMLAssoc( "gml.root-instance", osDoc ); /* -------------------------------------------------------------------- */ /* Add optional dictionary. */ /* -------------------------------------------------------------------- */ if( !osDictBox.empty() ) apoGMLBoxes[nGMLBoxes++] = GDALJP2Box::CreateLabelledXMLAssoc( "CRSDictionary.gml", osDictBox ); /* -------------------------------------------------------------------- */ /* Bundle gml.data boxes into an association. */ /* -------------------------------------------------------------------- */ GDALJP2Box *poGMLData = GDALJP2Box::CreateAsocBox( nGMLBoxes, apoGMLBoxes); /* -------------------------------------------------------------------- */ /* Cleanup working boxes. */ /* -------------------------------------------------------------------- */ while( nGMLBoxes > 0 ) delete apoGMLBoxes[--nGMLBoxes]; return poGMLData; } /************************************************************************/ /* GDALGMLJP2GetXMLRoot() */ /************************************************************************/ static CPLXMLNode* GDALGMLJP2GetXMLRoot(CPLXMLNode* psNode) { for( ; psNode != NULL; psNode = psNode->psNext ) { if( psNode->eType == CXT_Element && psNode->pszValue[0] != '?' ) return psNode; } return NULL; } /************************************************************************/ /* GDALGMLJP2PatchFeatureCollectionSubstitutionGroup() */ /************************************************************************/ static void GDALGMLJP2PatchFeatureCollectionSubstitutionGroup(CPLXMLNode* psRoot) { /* GML 3.2 SF profile recommends the feature collection type to derive */ /* from gml:AbstractGML to prevent it to be included in another feature */ /* collection, but this is what we want to do. So patch that... */ /* <xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:AbstractGML"/> */ /* --> */ /* <xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:AbstractFeature"/> */ if( psRoot->eType == CXT_Element && (strcmp(psRoot->pszValue, "schema") == 0 || strcmp(psRoot->pszValue, "xs:schema") == 0) ) { for(CPLXMLNode* psIter = psRoot->psChild; psIter != NULL; psIter = psIter->psNext) { if( psIter->eType == CXT_Element && (strcmp(psIter->pszValue, "element") == 0 || strcmp(psIter->pszValue, "xs:element") == 0) && strcmp(CPLGetXMLValue(psIter, "name", ""), "FeatureCollection") == 0 && strcmp(CPLGetXMLValue(psIter, "substitutionGroup", ""), "gml:AbstractGML") == 0 ) { CPLDebug("GMLJP2", "Patching substitutionGroup=\"gml:AbstractGML\" to \"gml:AbstractFeature\""); CPLSetXMLValue( psIter, "#substitutionGroup", "gml:AbstractFeature" ); break; } } } } /************************************************************************/ /* CreateGMLJP2V2() */ /************************************************************************/ class GMLJP2V2GMLFileDesc { public: CPLString osFile; CPLString osRemoteResource; CPLString osNamespace; CPLString osNamespacePrefix; CPLString osSchemaLocation; int bInline; int bParentCoverageCollection; GMLJP2V2GMLFileDesc(): bInline(TRUE), bParentCoverageCollection(TRUE) {} }; class GMLJP2V2AnnotationDesc { public: CPLString osFile; }; class GMLJP2V2MetadataDesc { public: CPLString osFile; CPLString osContent; CPLString osTemplateFile; CPLString osSourceFile; int bGDALMetadata; int bParentCoverageCollection; GMLJP2V2MetadataDesc(): bGDALMetadata(FALSE), bParentCoverageCollection(TRUE) {} }; class GMLJP2V2StyleDesc { public: CPLString osFile; int bParentCoverageCollection; GMLJP2V2StyleDesc(): bParentCoverageCollection(TRUE) {} }; class GMLJP2V2ExtensionDesc { public: CPLString osFile; int bParentCoverageCollection; GMLJP2V2ExtensionDesc(): bParentCoverageCollection(TRUE) {} }; class GMLJP2V2BoxDesc { public: CPLString osFile; CPLString osLabel; }; GDALJP2Box *GDALJP2Metadata::CreateGMLJP2V2( int nXSize, int nYSize, const char* pszDefFilename, GDALDataset* poSrcDS ) { CPLString osRootGMLId = "ID_GMLJP2_0"; CPLString osGridCoverage; CPLString osGridCoverageFile; CPLString osCoverageRangeTypeXML; bool bCRSURL = true; std::vector<GMLJP2V2MetadataDesc> aoMetadata; std::vector<GMLJP2V2AnnotationDesc> aoAnnotations; std::vector<GMLJP2V2GMLFileDesc> aoGMLFiles; std::vector<GMLJP2V2StyleDesc> aoStyles; std::vector<GMLJP2V2ExtensionDesc> aoExtensions; std::vector<GMLJP2V2BoxDesc> aoBoxes; /* -------------------------------------------------------------------- */ /* Parse definition file. */ /* -------------------------------------------------------------------- */ if( pszDefFilename && !EQUAL(pszDefFilename, "YES") && !EQUAL(pszDefFilename, "TRUE") ) { GByte* pabyContent = NULL; if( pszDefFilename[0] != '{' ) { if( !VSIIngestFile( NULL, pszDefFilename, &pabyContent, NULL, -1 ) ) return NULL; } /* { "#doc" : "Unless otherwise specified, all elements are optional", "#root_instance_doc": "Describe content of the GMLJP2CoverageCollection", "root_instance": { "#gml_id_doc": "Specify GMLJP2CoverageCollection id here. Default is ID_GMLJP2_0", "gml_id": "some_gml_id", "#grid_coverage_file_doc": [ "External XML file, whose root might be a GMLJP2GridCoverage, ", "GMLJP2RectifiedGridCoverage or a GMLJP2ReferenceableGridCoverage", "If not specified, GDAL will auto-generate a GMLJP2RectifiedGridCoverage" ], "grid_coverage_file": "gmljp2gridcoverage.xml", "#grid_coverage_range_type_field_predefined_name_doc": [ "One of Color, Elevation_meter or Panchromatic ", "to fill gmlcov:rangeType/swe:DataRecord/swe:field", "Only used if grid_coverage_file is not defined.", "Exclusive with grid_coverage_range_type_file" ], "grid_coverage_range_type_field_predefined_name": "Color", "#grid_coverage_range_type_file_doc": [ "File that is XML content to put under gml:RectifiedGrid/gmlcov:rangeType", "Only used if grid_coverage_file is not defined.", "Exclusive with grid_coverage_range_type_field_predefined_name" ], "grid_coverage_range_type_file": "grid_coverage_range_type.xml", "#crs_url_doc": [ "true for http://www.opengis.net/def/crs/EPSG/0/XXXX CRS URL.", "If false, use CRS URN. Default value is true" ], "crs_url": true, "#metadata_doc": [ "An array of metadata items. Can be either strings, with ", "a filename or directly inline XML content, or either ", "a more complete description." ], "metadata": [ "dcmetadata.xml", { "#file_doc": "Can use relative or absolute paths. Exclusive of content, gdal_metadata and generated_metadata.", "file": "dcmetadata.xml", "#gdal_metadata_doc": "Whether to serialize GDAL metadata as GDALMultiDomainMetadata", "gdal_metadata": false, "#dynamic_metadata_doc": [ "The metadata file will be generated from a template and a source file.", "The template is a valid GMLJP2 metadata XML tree with placeholders like", "{{{XPATH(some_xpath_expression)}}}", "that are evaluated from the source XML file. Typical use case", "is to generate a gmljp2:eopMetadata from the XML metadata", "provided by the image provider in their own particular format." ], "dynamic_metadata" : { "template": "my_template.xml", "source": "my_source.xml" }, "#content": "Exclusive of file. Inline XML metadata content", "content": "<gmljp2:metadata>Some simple textual metadata</gmljp2:metadata>", "#parent_node": ["Where to put the metadata.", "Under CoverageCollection (default) or GridCoverage" ], "parent_node": "CoverageCollection" } ], "#annotations_doc": [ "An array of filenames, either directly KML files", "or other vector files recognized by GDAL that ", "will be translated on-the-fly as KML" ], "annotations": [ "my.kml" ], "#gml_filelist_doc" :[ "An array of GML files. Can be either GML filenames, ", "or a more complete description" ], "gml_filelist": [ "my.gml", { "#file_doc": "Can use relative or absolute paths. Exclusive of remote_resource", "file": "converted/test_0.gml", "#remote_resource_doc": "URL of a feature collection that must be referenced through a xlink:href", "remote_resource": "http://svn.osgeo.org/gdal/trunk/autotest/ogr/data/expected_gml_gml32.gml", "#namespace_doc": ["The namespace in schemaLocation for which to substitute", "its original schemaLocation with the one provided below.", "Ignored for a remote_resource"], "namespace": "http://example.com", "#schema_location_doc": ["Value of the substituted schemaLocation. ", "Typically a schema box label (link)", "Ignored for a remote_resource"], "schema_location": "gmljp2://xml/schema_0.xsd", "#inline_doc": [ "Whether to inline the content, or put it in a separate xml box. Default is true", "Ignored for a remote_resource." ], "inline": true, "#parent_node": ["Where to put the FeatureCollection.", "Under CoverageCollection (default) or GridCoverage" ], "parent_node": "CoverageCollection" } ], "#styles_doc": [ "An array of styles. For example SLD files" ], "styles" : [ { "#file_doc": "Can use relative or absolute paths.", "file": "my.sld", "#parent_node": ["Where to put the FeatureCollection.", "Under CoverageCollection (default) or GridCoverage" ], "parent_node": "CoverageCollection" } ], "#extensions_doc": [ "An array of extensions." ], "extensions" : [ { "#file_doc": "Can use relative or absolute paths.", "file": "my.xml", "#parent_node": ["Where to put the FeatureCollection.", "Under CoverageCollection (default) or GridCoverage" ], "parent_node": "CoverageCollection" } ] }, "#boxes_doc": "An array to describe the content of XML asoc boxes", "boxes": [ { "#file_doc": "can use relative or absolute paths. Required", "file": "converted/test_0.xsd", "#label_doc": ["the label of the XML box. If not specified, will be the ", "filename without the directory part." ], "label": "schema_0.xsd" } ] } */ json_tokener* jstok = NULL; json_object* poObj = NULL; jstok = json_tokener_new(); poObj = json_tokener_parse_ex(jstok, pabyContent ? (const char*) pabyContent : pszDefFilename, -1); CPLFree(pabyContent); if( jstok->err != json_tokener_success) { CPLError( CE_Failure, CPLE_AppDefined, "JSON parsing error: %s (at offset %d)", json_tokener_error_desc(jstok->err), jstok->char_offset); json_tokener_free(jstok); return NULL; } json_tokener_free(jstok); json_object* poRootInstance = CPL_json_object_object_get(poObj, "root_instance"); if( poRootInstance && json_object_get_type(poRootInstance) == json_type_object ) { json_object* poGMLId = CPL_json_object_object_get(poRootInstance, "gml_id"); if( poGMLId && json_object_get_type(poGMLId) == json_type_string ) osRootGMLId = json_object_get_string(poGMLId); json_object* poGridCoverageFile = CPL_json_object_object_get(poRootInstance, "grid_coverage_file"); if( poGridCoverageFile && json_object_get_type(poGridCoverageFile) == json_type_string ) osGridCoverageFile = json_object_get_string(poGridCoverageFile); json_object* poGCRTFPN = CPL_json_object_object_get(poRootInstance, "grid_coverage_range_type_field_predefined_name"); if( poGCRTFPN && json_object_get_type(poGCRTFPN) == json_type_string ) { CPLString osPredefinedName( json_object_get_string(poGCRTFPN) ); if( EQUAL(osPredefinedName, "Color") ) { osCoverageRangeTypeXML = "<swe:DataRecord>" "<swe:field name=\"Color\">" "<swe:Quantity definition=\"http://www.opengis.net/def/ogc-eo/opt/SpectralMode/Color\">" "<swe:description>Color image</swe:description>" "<swe:uom code=\"unity\"/>" "</swe:Quantity>" "</swe:field>" "</swe:DataRecord>"; } else if( EQUAL(osPredefinedName, "Elevation_meter") ) { osCoverageRangeTypeXML = "<swe:DataRecord>" "<swe:field name=\"Elevation\">" "<swe:Quantity definition=\"http://inspire.ec.europa.eu/enumeration/ElevationPropertyTypeValue/height\" " "referenceFrame=\"http://www.opengis.net/def/crs/EPSG/0/5714\">" "<swe:description>Elevation above sea level</swe:description>" "<swe:uom code=\"m\"/>" "</swe:Quantity>" "</swe:field>" "</swe:DataRecord>"; } else if( EQUAL(osPredefinedName, "Panchromatic") ) { osCoverageRangeTypeXML = "<swe:DataRecord>" "<swe:field name=\"Panchromatic\">" "<swe:Quantity definition=\"http://www.opengis.net/def/ogc-eo/opt/SpectralMode/Panchromatic\">" "<swe:description>Panchromatic Channel</swe:description>" "<swe:uom code=\"unity\"/>" "</swe:Quantity>" "</swe:field>" "</swe:DataRecord>"; } else { CPLError(CE_Warning, CPLE_AppDefined, "Unrecognized value for grid_coverage_range_type_field_predefined_name"); } } else { json_object* poGCRTFile = CPL_json_object_object_get(poRootInstance, "grid_coverage_range_type_file"); if( poGCRTFile && json_object_get_type(poGCRTFile) == json_type_string ) { CPLXMLNode* psTmp = CPLParseXMLFile(json_object_get_string(poGCRTFile)); if( psTmp != NULL ) { CPLXMLNode* psTmpRoot = GDALGMLJP2GetXMLRoot(psTmp); if( psTmpRoot ) { char* pszTmp = CPLSerializeXMLTree(psTmpRoot); osCoverageRangeTypeXML = pszTmp; CPLFree(pszTmp); } CPLDestroyXMLNode(psTmp); } } } json_object* poCRSURL = CPL_json_object_object_get(poRootInstance, "crs_url"); if( poCRSURL && json_object_get_type(poCRSURL) == json_type_boolean ) bCRSURL = CPL_TO_BOOL(json_object_get_boolean(poCRSURL)); json_object* poMetadatas = CPL_json_object_object_get(poRootInstance, "metadata"); if( poMetadatas && json_object_get_type(poMetadatas) == json_type_array ) { for( int i = 0; i < json_object_array_length(poMetadatas); ++i ) { json_object* poMetadata = json_object_array_get_idx(poMetadatas, i); if( poMetadata && json_object_get_type(poMetadata) == json_type_string ) { GMLJP2V2MetadataDesc oDesc; const char* pszStr = json_object_get_string(poMetadata); if( pszStr[0] == '<' ) oDesc.osContent = pszStr; else oDesc.osFile = pszStr; aoMetadata.push_back(oDesc); } else if ( poMetadata && json_object_get_type(poMetadata) == json_type_object ) { const char* pszFile = NULL; json_object* poFile = CPL_json_object_object_get(poMetadata, "file"); if( poFile && json_object_get_type(poFile) == json_type_string ) pszFile = json_object_get_string(poFile); const char* pszContent = NULL; json_object* poContent = CPL_json_object_object_get(poMetadata, "content"); if( poContent && json_object_get_type(poContent) == json_type_string ) pszContent = json_object_get_string(poContent); const char* pszTemplate = NULL; const char* pszSource = NULL; json_object* poDynamicMetadata = CPL_json_object_object_get(poMetadata, "dynamic_metadata"); if( poDynamicMetadata && json_object_get_type(poDynamicMetadata) == json_type_object ) { #ifdef HAVE_LIBXML2 if( CPLTestBool(CPLGetConfigOption("GDAL_DEBUG_PROCESS_DYNAMIC_METADATA", "YES")) ) { json_object* poTemplate = CPL_json_object_object_get(poDynamicMetadata, "template"); if( poTemplate && json_object_get_type(poTemplate) == json_type_string ) pszTemplate = json_object_get_string(poTemplate); json_object* poSource = CPL_json_object_object_get(poDynamicMetadata, "source"); if( poSource && json_object_get_type(poSource) == json_type_string ) pszSource = json_object_get_string(poSource); } else #endif { CPLError(CE_Warning, CPLE_NotSupported, "dynamic_metadata not supported since libxml2 is not available"); } } bool bGDALMetadata = false; json_object* poGDALMetadata = CPL_json_object_object_get(poMetadata, "gdal_metadata"); if( poGDALMetadata && json_object_get_type(poGDALMetadata) == json_type_boolean ) bGDALMetadata = CPL_TO_BOOL( json_object_get_boolean(poGDALMetadata)); if( pszFile != NULL || pszContent != NULL || (pszTemplate != NULL && pszSource != NULL) || bGDALMetadata ) { GMLJP2V2MetadataDesc oDesc; if( pszFile ) oDesc.osFile = pszFile; if( pszContent ) oDesc.osContent = pszContent; if( pszTemplate ) oDesc.osTemplateFile = pszTemplate; if( pszSource ) oDesc.osSourceFile = pszSource; oDesc.bGDALMetadata = bGDALMetadata; json_object* poLocation = CPL_json_object_object_get(poMetadata, "parent_node"); if( poLocation && json_object_get_type(poLocation) == json_type_string ) { const char* pszLocation = json_object_get_string(poLocation); if( EQUAL(pszLocation, "CoverageCollection") ) oDesc.bParentCoverageCollection = TRUE; else if( EQUAL(pszLocation, "GridCoverage") ) oDesc.bParentCoverageCollection = FALSE; else CPLError(CE_Warning, CPLE_NotSupported, "metadata[].parent_node should be CoverageCollection or GridCoverage"); } aoMetadata.push_back(oDesc); } } } } json_object* poAnnotations = CPL_json_object_object_get(poRootInstance, "annotations"); if( poAnnotations && json_object_get_type(poAnnotations) == json_type_array ) { for( int i = 0; i < json_object_array_length(poAnnotations); ++i ) { json_object* poAnnotation = json_object_array_get_idx(poAnnotations, i); if( poAnnotation && json_object_get_type(poAnnotation) == json_type_string ) { GMLJP2V2AnnotationDesc oDesc; oDesc.osFile = json_object_get_string(poAnnotation); aoAnnotations.push_back(oDesc); } } } json_object* poGMLFileList = CPL_json_object_object_get(poRootInstance, "gml_filelist"); if( poGMLFileList && json_object_get_type(poGMLFileList) == json_type_array ) { for( int i = 0; i < json_object_array_length(poGMLFileList); ++i ) { json_object* poGMLFile = json_object_array_get_idx(poGMLFileList, i); if( poGMLFile && json_object_get_type(poGMLFile) == json_type_object ) { const char* pszFile = NULL; json_object* poFile = CPL_json_object_object_get(poGMLFile, "file"); if( poFile && json_object_get_type(poFile) == json_type_string ) pszFile = json_object_get_string(poFile); const char* pszRemoteResource = NULL; json_object* poRemoteResource = CPL_json_object_object_get(poGMLFile, "remote_resource"); if( poRemoteResource && json_object_get_type(poRemoteResource) == json_type_string ) pszRemoteResource = json_object_get_string(poRemoteResource); if( pszFile || pszRemoteResource ) { GMLJP2V2GMLFileDesc oDesc; if( pszFile ) oDesc.osFile = pszFile; else if( pszRemoteResource ) oDesc.osRemoteResource = pszRemoteResource; json_object* poNamespacePrefix = CPL_json_object_object_get(poGMLFile, "namespace_prefix"); if( poNamespacePrefix && json_object_get_type(poNamespacePrefix) == json_type_string ) oDesc.osNamespacePrefix = json_object_get_string(poNamespacePrefix); json_object* poNamespace = CPL_json_object_object_get(poGMLFile, "namespace"); if( poNamespace && json_object_get_type(poNamespace) == json_type_string ) oDesc.osNamespace = json_object_get_string(poNamespace); json_object* poSchemaLocation = CPL_json_object_object_get(poGMLFile, "schema_location"); if( poSchemaLocation && json_object_get_type(poSchemaLocation) == json_type_string ) oDesc.osSchemaLocation = json_object_get_string(poSchemaLocation); json_object* poInline = CPL_json_object_object_get(poGMLFile, "inline"); if( poInline && json_object_get_type(poInline) == json_type_boolean ) oDesc.bInline = json_object_get_boolean(poInline); json_object* poLocation = CPL_json_object_object_get(poGMLFile, "parent_node"); if( poLocation && json_object_get_type(poLocation) == json_type_string ) { const char* pszLocation = json_object_get_string(poLocation); if( EQUAL(pszLocation, "CoverageCollection") ) oDesc.bParentCoverageCollection = TRUE; else if( EQUAL(pszLocation, "GridCoverage") ) oDesc.bParentCoverageCollection = FALSE; else CPLError(CE_Warning, CPLE_NotSupported, "gml_filelist[].parent_node should be CoverageCollection or GridCoverage"); } aoGMLFiles.push_back(oDesc); } } else if( poGMLFile && json_object_get_type(poGMLFile) == json_type_string ) { GMLJP2V2GMLFileDesc oDesc; oDesc.osFile = json_object_get_string(poGMLFile); aoGMLFiles.push_back(oDesc); } } } json_object* poStyles = CPL_json_object_object_get(poRootInstance, "styles"); if( poStyles && json_object_get_type(poStyles) == json_type_array ) { for( int i = 0; i < json_object_array_length(poStyles); ++i ) { json_object* poStyle = json_object_array_get_idx(poStyles, i); if( poStyle && json_object_get_type(poStyle) == json_type_object ) { const char* pszFile = NULL; json_object* poFile = CPL_json_object_object_get(poStyle, "file"); if( poFile && json_object_get_type(poFile) == json_type_string ) pszFile = json_object_get_string(poFile); if( pszFile ) { GMLJP2V2StyleDesc oDesc; oDesc.osFile = pszFile; json_object* poLocation = CPL_json_object_object_get(poStyle, "parent_node"); if( poLocation && json_object_get_type(poLocation) == json_type_string ) { const char* pszLocation = json_object_get_string(poLocation); if( EQUAL(pszLocation, "CoverageCollection") ) oDesc.bParentCoverageCollection = TRUE; else if( EQUAL(pszLocation, "GridCoverage") ) oDesc.bParentCoverageCollection = FALSE; else CPLError(CE_Warning, CPLE_NotSupported, "styles[].parent_node should be CoverageCollection or GridCoverage"); } aoStyles.push_back(oDesc); } } else if( poStyle && json_object_get_type(poStyle) == json_type_string ) { GMLJP2V2StyleDesc oDesc; oDesc.osFile = json_object_get_string(poStyle); aoStyles.push_back(oDesc); } } } json_object* poExtensions = CPL_json_object_object_get(poRootInstance, "extensions"); if( poExtensions && json_object_get_type(poExtensions) == json_type_array ) { for( int i = 0; i < json_object_array_length(poExtensions); ++i ) { json_object* poExtension = json_object_array_get_idx(poExtensions, i); if( poExtension && json_object_get_type(poExtension) == json_type_object ) { const char* pszFile = NULL; json_object* poFile = CPL_json_object_object_get(poExtension, "file"); if( poFile && json_object_get_type(poFile) == json_type_string ) pszFile = json_object_get_string(poFile); if( pszFile ) { GMLJP2V2ExtensionDesc oDesc; oDesc.osFile = pszFile; json_object* poLocation = CPL_json_object_object_get(poExtension, "parent_node"); if( poLocation && json_object_get_type(poLocation) == json_type_string ) { const char* pszLocation = json_object_get_string(poLocation); if( EQUAL(pszLocation, "CoverageCollection") ) oDesc.bParentCoverageCollection = TRUE; else if( EQUAL(pszLocation, "GridCoverage") ) oDesc.bParentCoverageCollection = FALSE; else CPLError(CE_Warning, CPLE_NotSupported, "extensions[].parent_node should be CoverageCollection or GridCoverage"); } aoExtensions.push_back(oDesc); } } else if( poExtension && json_object_get_type(poExtension) == json_type_string ) { GMLJP2V2ExtensionDesc oDesc; oDesc.osFile = json_object_get_string(poExtension); aoExtensions.push_back(oDesc); } } } } json_object* poBoxes = CPL_json_object_object_get(poObj, "boxes"); if( poBoxes && json_object_get_type(poBoxes) == json_type_array ) { for( int i = 0; i < json_object_array_length(poBoxes); ++i ) { json_object* poBox = json_object_array_get_idx(poBoxes, i); if( poBox && json_object_get_type(poBox) == json_type_object ) { json_object* poFile = CPL_json_object_object_get(poBox, "file"); if( poFile && json_object_get_type(poFile) == json_type_string ) { GMLJP2V2BoxDesc oDesc; oDesc.osFile = json_object_get_string(poFile); json_object* poLabel = CPL_json_object_object_get(poBox, "label"); if( poLabel && json_object_get_type(poLabel) == json_type_string ) oDesc.osLabel = json_object_get_string(poLabel); else oDesc.osLabel = CPLGetFilename(oDesc.osFile); aoBoxes.push_back(oDesc); } } else if( poBox && json_object_get_type(poBox) == json_type_string ) { GMLJP2V2BoxDesc oDesc; oDesc.osFile = json_object_get_string(poBox); oDesc.osLabel = CPLGetFilename(oDesc.osFile); aoBoxes.push_back(oDesc); } } } json_object_put(poObj); // Check that if a GML file points to an internal schemaLocation, // the matching box really exists. for( int i = 0; i < static_cast<int>(aoGMLFiles.size()); ++i ) { if( !aoGMLFiles[i].osSchemaLocation.empty() && STARTS_WITH(aoGMLFiles[i].osSchemaLocation, "gmljp2://xml/") ) { const char* pszLookedLabel = aoGMLFiles[i].osSchemaLocation.c_str() + strlen("gmljp2://xml/"); bool bFound = false; for( int j = 0; !bFound && j < static_cast<int>(aoBoxes.size()); ++j ) bFound = (strcmp(pszLookedLabel, aoBoxes[j].osLabel) == 0); if( !bFound ) { CPLError( CE_Warning, CPLE_AppDefined, "GML file %s has a schema_location=%s, " "but no box with label %s is defined", aoGMLFiles[i].osFile.c_str(), aoGMLFiles[i].osSchemaLocation.c_str(), pszLookedLabel); } } } // Read custom grid coverage file. if( !osGridCoverageFile.empty() ) { CPLXMLNode* psTmp = CPLParseXMLFile(osGridCoverageFile); if( psTmp == NULL ) return NULL; CPLXMLNode* psTmpRoot = GDALGMLJP2GetXMLRoot(psTmp); if( psTmpRoot ) { char* pszTmp = CPLSerializeXMLTree(psTmpRoot); osGridCoverage = pszTmp; CPLFree(pszTmp); } CPLDestroyXMLNode(psTmp); } } CPLString osDictBox; CPLString osDoc; if( osGridCoverage.empty() ) { /* -------------------------------------------------------------------- */ /* Prepare GMLJP2RectifiedGridCoverage */ /* -------------------------------------------------------------------- */ int nEPSGCode = 0; double adfOrigin[2]; double adfXVector[2]; double adfYVector[2]; const char* pszComment = ""; int bNeedAxisFlip = FALSE; if( !GetGMLJP2GeoreferencingInfo( nEPSGCode, adfOrigin, adfXVector, adfYVector, pszComment, osDictBox, bNeedAxisFlip ) ) { return NULL; } char szSRSName[100] = {0}; if( nEPSGCode != 0 ) { if( bCRSURL ) snprintf( szSRSName, sizeof(szSRSName), "http://www.opengis.net/def/crs/EPSG/0/%d", nEPSGCode ); else snprintf( szSRSName, sizeof(szSRSName), "urn:ogc:def:crs:EPSG::%d", nEPSGCode ); } else snprintf( szSRSName, sizeof(szSRSName), "%s", "gmljp2://xml/CRSDictionary.gml#ogrcrs1" ); const double dfLLX = adfGeoTransform[0]; const double dfLLY = adfGeoTransform[3] + adfGeoTransform[5] * nYSize; const double dfURX = adfGeoTransform[0] + adfGeoTransform[1] * nXSize; const double dfURY = adfGeoTransform[3]; osGridCoverage.Printf( " <gmljp2:GMLJP2RectifiedGridCoverage gml:id=\"RGC_1_%s\">\n" " <gml:boundedBy>\n" " <gml:Envelope srsDimension=\"2\" srsName=\"%s\">\n" " <gml:lowerCorner>%.15g %.15g</gml:lowerCorner>\n" " <gml:upperCorner>%.15g %.15g</gml:upperCorner>\n" " </gml:Envelope>\n" " </gml:boundedBy>\n" " <gml:domainSet>\n" " <gml:RectifiedGrid gml:id=\"RGC_1_GRID_%s\" dimension=\"2\" srsName=\"%s\">\n" " <gml:limits>\n" " <gml:GridEnvelope>\n" " <gml:low>0 0</gml:low>\n" " <gml:high>%d %d</gml:high>\n" " </gml:GridEnvelope>\n" " </gml:limits>\n" " <gml:axisName>x</gml:axisName>\n" " <gml:axisName>y</gml:axisName>\n" " <gml:origin>\n" " <gml:Point gml:id=\"P0001\" srsName=\"%s\">\n" " <gml:pos>%.15g %.15g</gml:pos>\n" " </gml:Point>\n" " </gml:origin>\n" "%s" " <gml:offsetVector srsName=\"%s\">%.15g %.15g</gml:offsetVector>\n" " <gml:offsetVector srsName=\"%s\">%.15g %.15g</gml:offsetVector>\n" " </gml:RectifiedGrid>\n" " </gml:domainSet>\n" " <gml:rangeSet>\n" " <gml:File>\n" " <gml:rangeParameters/>\n" " <gml:fileName>gmljp2://codestream/0</gml:fileName>\n" " <gml:fileStructure>inapplicable</gml:fileStructure>\n" " </gml:File>\n" " </gml:rangeSet>\n" " <gmlcov:rangeType>%s</gmlcov:rangeType>\n" " </gmljp2:GMLJP2RectifiedGridCoverage>\n", osRootGMLId.c_str(), szSRSName, dfLLX, dfLLY, dfURX, dfURY, osRootGMLId.c_str(), szSRSName, nXSize-1, nYSize-1, szSRSName, adfOrigin[0], adfOrigin[1], pszComment, szSRSName, adfXVector[0], adfXVector[1], szSRSName, adfYVector[0], adfYVector[1], osCoverageRangeTypeXML.c_str() ); } /* -------------------------------------------------------------------- */ /* Main node. */ /* -------------------------------------------------------------------- */ // Per http://docs.opengeospatial.org/is/08-085r5/08-085r5.html#requirement_11 osDoc.Printf( //"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" "<gmljp2:GMLJP2CoverageCollection gml:id=\"%s\"\n" " xmlns:gml=\"http://www.opengis.net/gml/3.2\"\n" " xmlns:gmlcov=\"http://www.opengis.net/gmlcov/1.0\"\n" " xmlns:gmljp2=\"http://www.opengis.net/gmljp2/2.0\"\n" " xmlns:swe=\"http://www.opengis.net/swe/2.0\"\n" " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" " xsi:schemaLocation=\"http://www.opengis.net/gmljp2/2.0 http://schemas.opengis.net/gmljp2/2.0/gmljp2.xsd\">\n" " <gml:domainSet nilReason=\"inapplicable\"/>\n" " <gml:rangeSet>\n" " <gml:DataBlock>\n" " <gml:rangeParameters nilReason=\"inapplicable\"/>\n" " <gml:doubleOrNilReasonTupleList>inapplicable</gml:doubleOrNilReasonTupleList>\n" " </gml:DataBlock>\n" " </gml:rangeSet>\n" " <gmlcov:rangeType>\n" " <swe:DataRecord>\n" " <swe:field name=\"Collection\"> </swe:field>\n" " </swe:DataRecord>\n" " </gmlcov:rangeType>\n" " <gmljp2:featureMember>\n" "%s" " </gmljp2:featureMember>\n" "</gmljp2:GMLJP2CoverageCollection>\n", osRootGMLId.c_str(), osGridCoverage.c_str() ); /* -------------------------------------------------------------------- */ /* Process metadata, annotations and features collections. */ /* -------------------------------------------------------------------- */ std::vector<CPLString> aosTmpFiles; if( !aoMetadata.empty() || !aoAnnotations.empty() || !aoGMLFiles.empty() || !aoStyles.empty() || !aoExtensions.empty() ) { CPLXMLNode* psRoot = CPLParseXMLString(osDoc); CPLAssert(psRoot); CPLXMLNode* psGMLJP2CoverageCollection = GDALGMLJP2GetXMLRoot(psRoot); CPLAssert(psGMLJP2CoverageCollection); for( int i=0; i < static_cast<int>(aoMetadata.size()); ++i ) { CPLXMLNode* psMetadata = NULL; if( !aoMetadata[i].osFile.empty() ) psMetadata = CPLParseXMLFile(aoMetadata[i].osFile); else if( !aoMetadata[i].osContent.empty() ) psMetadata = CPLParseXMLString(aoMetadata[i].osContent); else if( aoMetadata[i].bGDALMetadata ) { psMetadata = CreateGDALMultiDomainMetadataXML(poSrcDS, TRUE); if( psMetadata ) { CPLSetXMLValue(psMetadata, "#xmlns", "http://gdal.org"); CPLXMLNode* psNewMetadata = CPLCreateXMLNode(NULL, CXT_Element, "gmljp2:metadata"); CPLAddXMLChild(psNewMetadata, psMetadata); psMetadata = psNewMetadata; } } else psMetadata = GDALGMLJP2GenerateMetadata(aoMetadata[i].osTemplateFile, aoMetadata[i].osSourceFile); if( psMetadata == NULL ) continue; CPLXMLNode* psMetadataRoot = GDALGMLJP2GetXMLRoot(psMetadata); if( psMetadataRoot ) { if( strcmp(psMetadataRoot->pszValue, "eop:EarthObservation") == 0 ) { CPLXMLNode* psNewMetadata = CPLCreateXMLNode(NULL, CXT_Element, "gmljp2:eopMetadata"); CPLAddXMLChild(psNewMetadata, CPLCloneXMLTree(psMetadataRoot)); CPLDestroyXMLNode(psMetadata); psMetadata = psMetadataRoot = psNewMetadata; } if( strcmp(psMetadataRoot->pszValue, "gmljp2:isoMetadata") != 0 && strcmp(psMetadataRoot->pszValue, "gmljp2:eopMetadata") != 0 && strcmp(psMetadataRoot->pszValue, "gmljp2:dcMetadata") != 0 && strcmp(psMetadataRoot->pszValue, "gmljp2:metadata") != 0 ) { CPLError(CE_Warning, CPLE_AppDefined, "The metadata root node should be one of gmljp2:isoMetadata, " "gmljp2:eopMetadata, gmljp2:dcMetadata or gmljp2:metadata"); } else if( aoMetadata[i].bParentCoverageCollection ) { /* Insert the gmlcov:metadata link as the next sibling of */ /* GMLJP2CoverageCollection.rangeType */ CPLXMLNode* psRangeType = CPLGetXMLNode(psGMLJP2CoverageCollection, "gmlcov:rangeType"); CPLAssert(psRangeType); CPLXMLNode* psNodeAfterWhichToInsert = psRangeType; CPLXMLNode* psNext = psNodeAfterWhichToInsert->psNext; while( psNext != NULL && psNext->eType == CXT_Element && strcmp(psNext->pszValue, "gmlcov:metadata") == 0 ) { psNodeAfterWhichToInsert = psNext; psNext = psNext->psNext; } psNodeAfterWhichToInsert->psNext = NULL; CPLXMLNode* psGMLCovMetadata = CPLCreateXMLNode( psGMLJP2CoverageCollection, CXT_Element, "gmlcov:metadata" ); psGMLCovMetadata->psNext = psNext; CPLXMLNode* psGMLJP2Metadata = CPLCreateXMLNode( psGMLCovMetadata, CXT_Element, "gmljp2:Metadata" ); CPLAddXMLChild( psGMLJP2Metadata, CPLCloneXMLTree(psMetadataRoot) ); } else { /* Insert the gmlcov:metadata link as the last child of */ /* GMLJP2RectifiedGridCoverage typically */ CPLXMLNode* psFeatureMemberOfGridCoverage = CPLGetXMLNode(psGMLJP2CoverageCollection, "gmljp2:featureMember"); CPLAssert(psFeatureMemberOfGridCoverage); CPLXMLNode* psGridCoverage = psFeatureMemberOfGridCoverage->psChild; CPLAssert(psGridCoverage); CPLXMLNode* psGMLCovMetadata = CPLCreateXMLNode( psGridCoverage, CXT_Element, "gmlcov:metadata" ); CPLXMLNode* psGMLJP2Metadata = CPLCreateXMLNode( psGMLCovMetadata, CXT_Element, "gmljp2:Metadata" ); CPLAddXMLChild( psGMLJP2Metadata, CPLCloneXMLTree(psMetadataRoot) ); } } CPLDestroyXMLNode(psMetadata); } bool bRootHasXLink = false; // Examples of inline or reference feature collections can be found // in http://schemas.opengis.net/gmljp2/2.0/examples/gmljp2.xml for( int i = 0; i < static_cast<int>(aoGMLFiles.size()); ++i ) { // Is the file already a GML file? CPLXMLNode* psGMLFile = NULL; if( !aoGMLFiles[i].osFile.empty() ) { if( EQUAL(CPLGetExtension(aoGMLFiles[i].osFile), "gml") || EQUAL(CPLGetExtension(aoGMLFiles[i].osFile), "xml") ) { psGMLFile = CPLParseXMLFile(aoGMLFiles[i].osFile); } GDALDriverH hDrv = NULL; if( psGMLFile == NULL ) { hDrv = GDALIdentifyDriver(aoGMLFiles[i].osFile, NULL); if( hDrv == NULL ) { CPLError(CE_Failure, CPLE_AppDefined, "%s is no a GDAL recognized file", aoGMLFiles[i].osFile.c_str()); continue; } } GDALDriverH hGMLDrv = GDALGetDriverByName("GML"); if( psGMLFile == NULL && hDrv == hGMLDrv ) { // Yes, parse it psGMLFile = CPLParseXMLFile(aoGMLFiles[i].osFile); } else if( psGMLFile == NULL ) { if( hGMLDrv == NULL ) { CPLError(CE_Failure, CPLE_AppDefined, "Cannot translate %s to GML", aoGMLFiles[i].osFile.c_str()); continue; } // On-the-fly translation to GML 3.2 GDALDatasetH hSrcDS = GDALOpenEx(aoGMLFiles[i].osFile, 0, NULL, NULL, NULL); if( hSrcDS ) { CPLString osTmpFile = CPLSPrintf("/vsimem/gmljp2/%p/%d/%s.gml", this, i, CPLGetBasename(aoGMLFiles[i].osFile)); char ** papszOptions = NULL; papszOptions = CSLSetNameValue(papszOptions, "FORMAT", "GML3.2"); papszOptions = CSLSetNameValue(papszOptions, "SRSNAME_FORMAT", (bCRSURL) ? "OGC_URL" : "OGC_URN"); if( aoGMLFiles.size() > 1 || !aoGMLFiles[i].osNamespace.empty() || !aoGMLFiles[i].osNamespacePrefix.empty() ) { papszOptions = CSLSetNameValue(papszOptions, "PREFIX", aoGMLFiles[i].osNamespacePrefix.empty() ? CPLSPrintf("ogr%d", i) : aoGMLFiles[i].osNamespacePrefix.c_str()); papszOptions = CSLSetNameValue(papszOptions, "TARGET_NAMESPACE", aoGMLFiles[i].osNamespace.empty() ? CPLSPrintf("http://ogr.maptools.org/%d", i) : aoGMLFiles[i].osNamespace.c_str()); } GDALDatasetH hDS = GDALCreateCopy( hGMLDrv, osTmpFile, hSrcDS, FALSE, papszOptions, NULL, NULL); CSLDestroy(papszOptions); if( hDS ) { GDALClose(hDS); psGMLFile = CPLParseXMLFile(osTmpFile); aoGMLFiles[i].osFile = osTmpFile; VSIUnlink(osTmpFile); aosTmpFiles.push_back(CPLResetExtension(osTmpFile, "xsd")); } else { CPLError(CE_Failure, CPLE_AppDefined, "Conversion of %s to GML failed", aoGMLFiles[i].osFile.c_str()); } } GDALClose(hSrcDS); } if( psGMLFile == NULL ) continue; } CPLXMLNode* psGMLFileRoot = psGMLFile ? GDALGMLJP2GetXMLRoot(psGMLFile) : NULL; if( psGMLFileRoot || !aoGMLFiles[i].osRemoteResource.empty() ) { CPLXMLNode *node_f; if( aoGMLFiles[i].bParentCoverageCollection ) { // Insert in gmljp2:featureMember.gmljp2:GMLJP2Features.gmljp2:feature CPLXMLNode *node_fm = CPLCreateXMLNode( psGMLJP2CoverageCollection, CXT_Element, "gmljp2:featureMember" ); CPLXMLNode *node_gf = CPLCreateXMLNode( node_fm, CXT_Element, "gmljp2:GMLJP2Features" ); CPLSetXMLValue(node_gf, "#gml:id", CPLSPrintf("%s_GMLJP2Features_%d", osRootGMLId.c_str(), i)); node_f = CPLCreateXMLNode( node_gf, CXT_Element, "gmljp2:feature" ); } else { CPLXMLNode* psFeatureMemberOfGridCoverage = CPLGetXMLNode(psGMLJP2CoverageCollection, "gmljp2:featureMember"); CPLAssert(psFeatureMemberOfGridCoverage); CPLXMLNode* psGridCoverage = psFeatureMemberOfGridCoverage->psChild; CPLAssert(psGridCoverage); node_f = CPLCreateXMLNode( psGridCoverage, CXT_Element, "gmljp2:feature" ); } if( !aoGMLFiles[i].bInline || !aoGMLFiles[i].osRemoteResource.empty() ) { if( !bRootHasXLink ) { bRootHasXLink = true; CPLSetXMLValue(psGMLJP2CoverageCollection, "#xmlns:xlink", "http://www.w3.org/1999/xlink"); } } if( !aoGMLFiles[i].osRemoteResource.empty() ) { CPLSetXMLValue(node_f, "#xlink:href", aoGMLFiles[i].osRemoteResource.c_str()); continue; } CPLString osTmpFile; if( !aoGMLFiles[i].bInline || !aoGMLFiles[i].osRemoteResource.empty() ) { osTmpFile = CPLSPrintf("/vsimem/gmljp2/%p/%d/%s.gml", this, i, CPLGetBasename(aoGMLFiles[i].osFile)); aosTmpFiles.push_back(osTmpFile); GMLJP2V2BoxDesc oDesc; oDesc.osFile = osTmpFile; oDesc.osLabel = CPLGetFilename(oDesc.osFile); aoBoxes.push_back(oDesc); CPLSetXMLValue(node_f, "#xlink:href", CPLSPrintf("gmljp2://xml/%s", oDesc.osLabel.c_str())); } if( CPLGetXMLNode(psGMLFileRoot, "xmlns") == NULL && CPLGetXMLNode(psGMLFileRoot, "xmlns:gml") == NULL ) { CPLSetXMLValue(psGMLFileRoot, "#xmlns", "http://www.opengis.net/gml/3.2"); } // modify the gml id making it unique for this document CPLXMLNode* psGMLFileGMLId = CPLGetXMLNode(psGMLFileRoot, "gml:id"); if( psGMLFileGMLId && psGMLFileGMLId->eType == CXT_Attribute ) CPLSetXMLValue( psGMLFileGMLId, "", CPLSPrintf("%s_%d_%s", osRootGMLId.c_str(), i, psGMLFileGMLId->psChild->pszValue) ); psGMLFileGMLId = NULL; //PrefixAllGMLIds(psGMLFileRoot, CPLSPrintf("%s_%d_", osRootGMLId.c_str(), i)); // replace schema location CPLXMLNode* psSchemaLocation = CPLGetXMLNode(psGMLFileRoot, "xsi:schemaLocation"); if( psSchemaLocation && psSchemaLocation->eType == CXT_Attribute ) { char **papszTokens = CSLTokenizeString2( psSchemaLocation->psChild->pszValue, " \t\n", CSLT_HONOURSTRINGS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES); CPLString osSchemaLocation; if( CSLCount(papszTokens) == 2 && aoGMLFiles[i].osNamespace.empty() && !aoGMLFiles[i].osSchemaLocation.empty() ) { osSchemaLocation += papszTokens[0]; osSchemaLocation += " "; osSchemaLocation += aoGMLFiles[i].osSchemaLocation; } else if( CSLCount(papszTokens) == 2 && (aoGMLFiles[i].osNamespace.empty() || strcmp(papszTokens[0], aoGMLFiles[i].osNamespace) == 0) && aoGMLFiles[i].osSchemaLocation.empty() ) { VSIStatBufL sStat; CPLString osXSD; if( CSLCount(papszTokens) == 2 && !CPLIsFilenameRelative(papszTokens[1]) && VSIStatL(papszTokens[1], &sStat) == 0 ) { osXSD = papszTokens[1]; } else if( CSLCount(papszTokens) == 2 && CPLIsFilenameRelative(papszTokens[1]) && VSIStatL(CPLFormFilename(CPLGetDirname(aoGMLFiles[i].osFile), papszTokens[1], NULL), &sStat) == 0 ) { osXSD = CPLFormFilename(CPLGetDirname(aoGMLFiles[i].osFile), papszTokens[1], NULL); } if( !osXSD.empty() ) { GMLJP2V2BoxDesc oDesc; oDesc.osFile = osXSD; oDesc.osLabel = CPLGetFilename(oDesc.osFile); osSchemaLocation += papszTokens[0]; osSchemaLocation += " "; osSchemaLocation += "gmljp2://xml/"; osSchemaLocation += oDesc.osLabel; int j = 0; // Used after for. for( ; j < static_cast<int>(aoBoxes.size()); ++j ) { if( aoBoxes[j].osLabel == oDesc.osLabel ) break; } if( j == static_cast<int>(aoBoxes.size()) ) aoBoxes.push_back(oDesc); } } else if( (CSLCount(papszTokens) % 2) == 0 ) { for( char** papszIter = papszTokens; *papszIter; papszIter += 2 ) { if( !osSchemaLocation.empty() ) osSchemaLocation += " "; if( !aoGMLFiles[i].osNamespace.empty() && !aoGMLFiles[i].osSchemaLocation.empty() && strcmp(papszIter[0], aoGMLFiles[i].osNamespace) == 0 ) { osSchemaLocation += papszIter[0]; osSchemaLocation += " "; osSchemaLocation += aoGMLFiles[i].osSchemaLocation; } else { osSchemaLocation += papszIter[0]; osSchemaLocation += " "; osSchemaLocation += papszIter[1]; } } } CSLDestroy(papszTokens); CPLSetXMLValue( psSchemaLocation, "", osSchemaLocation); } if( aoGMLFiles[i].bInline ) CPLAddXMLChild(node_f, CPLCloneXMLTree(psGMLFileRoot)); else CPLSerializeXMLTreeToFile( psGMLFile, osTmpFile ); } CPLDestroyXMLNode(psGMLFile); } // c.f. // http://schemas.opengis.net/gmljp2/2.0/examples/gmljp2_annotation.xml for( int i = 0; i < static_cast<int>(aoAnnotations.size()); ++i ) { // Is the file already a KML file? CPLXMLNode* psKMLFile = NULL; if( EQUAL(CPLGetExtension(aoAnnotations[i].osFile), "kml") ) psKMLFile = CPLParseXMLFile(aoAnnotations[i].osFile); GDALDriverH hDrv = NULL; if( psKMLFile == NULL ) { hDrv = GDALIdentifyDriver(aoAnnotations[i].osFile, NULL); if( hDrv == NULL ) { CPLError(CE_Failure, CPLE_AppDefined, "%s is no a GDAL recognized file", aoAnnotations[i].osFile.c_str()); continue; } } GDALDriverH hKMLDrv = GDALGetDriverByName("KML"); GDALDriverH hLIBKMLDrv = GDALGetDriverByName("LIBKML"); if( psKMLFile == NULL && (hDrv == hKMLDrv || hDrv == hLIBKMLDrv) ) { // Yes, parse it psKMLFile = CPLParseXMLFile(aoAnnotations[i].osFile); } else if( psKMLFile == NULL ) { if( hKMLDrv == NULL && hLIBKMLDrv == NULL ) { CPLError(CE_Failure, CPLE_AppDefined, "Cannot translate %s to KML", aoAnnotations[i].osFile.c_str()); continue; } // On-the-fly translation to KML GDALDatasetH hSrcDS = GDALOpenEx(aoAnnotations[i].osFile, 0, NULL, NULL, NULL); if( hSrcDS ) { CPLString osTmpFile = CPLSPrintf("/vsimem/gmljp2/%p/%d/%s.kml", this, i, CPLGetBasename(aoAnnotations[i].osFile)); char** papszOptions = NULL; if( aoAnnotations.size() > 1 ) { papszOptions = CSLSetNameValue( papszOptions, "DOCUMENT_ID", CPLSPrintf("root_doc_%d", i)); } GDALDatasetH hDS = GDALCreateCopy(hLIBKMLDrv ? hLIBKMLDrv : hKMLDrv, osTmpFile, hSrcDS, FALSE, papszOptions, NULL, NULL); CSLDestroy(papszOptions); if( hDS ) { GDALClose(hDS); psKMLFile = CPLParseXMLFile(osTmpFile); aoAnnotations[i].osFile = osTmpFile; VSIUnlink(osTmpFile); } else { CPLError(CE_Failure, CPLE_AppDefined, "Conversion of %s to KML failed", aoAnnotations[i].osFile.c_str()); } } GDALClose(hSrcDS); } if( psKMLFile == NULL ) continue; CPLXMLNode* psKMLFileRoot = GDALGMLJP2GetXMLRoot(psKMLFile); if( psKMLFileRoot ) { CPLXMLNode* psFeatureMemberOfGridCoverage = CPLGetXMLNode(psGMLJP2CoverageCollection, "gmljp2:featureMember"); CPLAssert(psFeatureMemberOfGridCoverage); CPLXMLNode* psGridCoverage = psFeatureMemberOfGridCoverage->psChild; CPLAssert(psGridCoverage); CPLXMLNode *psAnnotation = CPLCreateXMLNode( psGridCoverage, CXT_Element, "gmljp2:annotation" ); /* Add a xsi:schemaLocation if not already present */ if( psKMLFileRoot->eType == CXT_Element && strcmp(psKMLFileRoot->pszValue, "kml") == 0 && CPLGetXMLNode(psKMLFileRoot, "xsi:schemaLocation") == NULL && strcmp(CPLGetXMLValue(psKMLFileRoot, "xmlns", ""), "http://www.opengis.net/kml/2.2") == 0 ) { CPLSetXMLValue(psKMLFileRoot, "#xsi:schemaLocation", "http://www.opengis.net/kml/2.2 http://schemas.opengis.net/kml/2.2.0/ogckml22.xsd"); } CPLAddXMLChild(psAnnotation, CPLCloneXMLTree(psKMLFileRoot)); } CPLDestroyXMLNode(psKMLFile); } // Add styles. for( int i = 0; i < static_cast<int>(aoStyles.size()); ++i ) { CPLXMLNode* psStyle = CPLParseXMLFile(aoStyles[i].osFile); if( psStyle == NULL ) continue; CPLXMLNode* psStyleRoot = GDALGMLJP2GetXMLRoot(psStyle); if( psStyleRoot ) { CPLXMLNode *psGMLJP2Style = NULL; if( aoStyles[i].bParentCoverageCollection ) { psGMLJP2Style = CPLCreateXMLNode( psGMLJP2CoverageCollection, CXT_Element, "gmljp2:style" ); } else { CPLXMLNode* psFeatureMemberOfGridCoverage = CPLGetXMLNode(psGMLJP2CoverageCollection, "gmljp2:featureMember"); CPLAssert(psFeatureMemberOfGridCoverage); CPLXMLNode* psGridCoverage = psFeatureMemberOfGridCoverage->psChild; CPLAssert(psGridCoverage); psGMLJP2Style = CPLCreateXMLNode( psGridCoverage, CXT_Element, "gmljp2:style" ); } // Add dummy namespace for validation purposes if needed if( strchr(psStyleRoot->pszValue, ':') == NULL && CPLGetXMLValue(psStyleRoot, "xmlns", NULL) == NULL ) { CPLSetXMLValue(psStyleRoot, "#xmlns", "http://undefined_namespace"); } CPLAddXMLChild( psGMLJP2Style, CPLCloneXMLTree(psStyleRoot) ); } CPLDestroyXMLNode(psStyle); } // Add extensions. for( int i = 0; i < static_cast<int>(aoExtensions.size()); ++i ) { CPLXMLNode* psExtension = CPLParseXMLFile(aoExtensions[i].osFile); if( psExtension == NULL ) continue; CPLXMLNode* psExtensionRoot = GDALGMLJP2GetXMLRoot(psExtension); if( psExtensionRoot ) { CPLXMLNode *psGMLJP2Extension; if( aoExtensions[i].bParentCoverageCollection ) { psGMLJP2Extension = CPLCreateXMLNode( psGMLJP2CoverageCollection, CXT_Element, "gmljp2:extension" ); } else { CPLXMLNode* psFeatureMemberOfGridCoverage = CPLGetXMLNode(psGMLJP2CoverageCollection, "gmljp2:featureMember"); CPLAssert(psFeatureMemberOfGridCoverage); CPLXMLNode* psGridCoverage = psFeatureMemberOfGridCoverage->psChild; CPLAssert(psGridCoverage); psGMLJP2Extension = CPLCreateXMLNode( psGridCoverage, CXT_Element, "gmljp2:extension" ); } // Add dummy namespace for validation purposes if needed if( strchr(psExtensionRoot->pszValue, ':') == NULL && CPLGetXMLValue(psExtensionRoot, "xmlns", NULL) == NULL ) { CPLSetXMLValue(psExtensionRoot, "#xmlns", "http://undefined_namespace"); } CPLAddXMLChild( psGMLJP2Extension, CPLCloneXMLTree(psExtensionRoot) ); } CPLDestroyXMLNode(psExtension); } char* pszRoot = CPLSerializeXMLTree(psRoot); CPLDestroyXMLNode(psRoot); psRoot = NULL; osDoc = pszRoot; CPLFree(pszRoot); pszRoot = NULL; } /* -------------------------------------------------------------------- */ /* Setup the gml.data label. */ /* -------------------------------------------------------------------- */ std::vector<GDALJP2Box *> apoGMLBoxes; apoGMLBoxes.push_back(GDALJP2Box::CreateLblBox( "gml.data" )); /* -------------------------------------------------------------------- */ /* Setup gml.root-instance. */ /* -------------------------------------------------------------------- */ apoGMLBoxes.push_back( GDALJP2Box::CreateLabelledXMLAssoc( "gml.root-instance", osDoc )); /* -------------------------------------------------------------------- */ /* Add optional dictionary. */ /* -------------------------------------------------------------------- */ if( !osDictBox.empty() ) apoGMLBoxes.push_back( GDALJP2Box::CreateLabelledXMLAssoc( "CRSDictionary.gml", osDictBox ) ); /* -------------------------------------------------------------------- */ /* Additional user specified boxes. */ /* -------------------------------------------------------------------- */ for( int i = 0; i < static_cast<int>(aoBoxes.size()); ++i ) { GByte* pabyContent = NULL; if( VSIIngestFile( NULL, aoBoxes[i].osFile, &pabyContent, NULL, -1 ) ) { CPLXMLNode* psNode = CPLParseXMLString((const char*)pabyContent); CPLFree(pabyContent); pabyContent = NULL; if( psNode ) { CPLXMLNode* psRoot = GDALGMLJP2GetXMLRoot(psNode); if( psRoot ) { GDALGMLJP2PatchFeatureCollectionSubstitutionGroup(psRoot); pabyContent = (GByte*) CPLSerializeXMLTree(psRoot); apoGMLBoxes.push_back( GDALJP2Box::CreateLabelledXMLAssoc( aoBoxes[i].osLabel, (const char*)pabyContent ) ); } CPLDestroyXMLNode (psNode); } } CPLFree(pabyContent); } /* -------------------------------------------------------------------- */ /* Bundle gml.data boxes into an association. */ /* -------------------------------------------------------------------- */ GDALJP2Box *poGMLData = GDALJP2Box::CreateAsocBox( static_cast<int>(apoGMLBoxes.size()), &apoGMLBoxes[0] ); /* -------------------------------------------------------------------- */ /* Cleanup working boxes. */ /* -------------------------------------------------------------------- */ for( int i = 0; i < static_cast<int>(apoGMLBoxes.size()); ++i ) delete apoGMLBoxes[i]; for( int i = 0; i < static_cast<int>(aosTmpFiles.size()); ++i ) { VSIUnlink(aosTmpFiles[i]); } return poGMLData; } /************************************************************************/ /* CreateGDALMultiDomainMetadataXML() */ /************************************************************************/ CPLXMLNode* GDALJP2Metadata::CreateGDALMultiDomainMetadataXML( GDALDataset* poSrcDS, int bMainMDDomainOnly ) { GDALMultiDomainMetadata oLocalMDMD; char** papszSrcMD = CSLDuplicate(poSrcDS->GetMetadata()); /* Remove useless metadata */ papszSrcMD = CSLSetNameValue(papszSrcMD, GDALMD_AREA_OR_POINT, NULL); papszSrcMD = CSLSetNameValue(papszSrcMD, "TIFFTAG_RESOLUTIONUNIT", NULL); papszSrcMD = CSLSetNameValue(papszSrcMD, "TIFFTAG_XREpsMasterXMLNodeSOLUTION", NULL); papszSrcMD = CSLSetNameValue(papszSrcMD, "TIFFTAG_YRESOLUTION", NULL); papszSrcMD = CSLSetNameValue(papszSrcMD, "Corder", NULL); /* from JP2KAK */ if( poSrcDS->GetDriver() != NULL && EQUAL(poSrcDS->GetDriver()->GetDescription(), "JP2ECW") ) { papszSrcMD = CSLSetNameValue(papszSrcMD, "COMPRESSION_RATE_TARGET", NULL); papszSrcMD = CSLSetNameValue(papszSrcMD, "COLORSPACE", NULL); papszSrcMD = CSLSetNameValue(papszSrcMD, "VERSION", NULL); } bool bHasMD = false; if( papszSrcMD && *papszSrcMD ) { bHasMD = true; oLocalMDMD.SetMetadata(papszSrcMD); } CSLDestroy(papszSrcMD); if( !bMainMDDomainOnly ) { char** papszMDList = poSrcDS->GetMetadataDomainList(); for( char** papszMDListIter = papszMDList; papszMDListIter && *papszMDListIter; ++papszMDListIter ) { if( !EQUAL(*papszMDListIter, "") && !EQUAL(*papszMDListIter, "IMAGE_STRUCTURE") && !EQUAL(*papszMDListIter, "DERIVED_SUBDATASETS") && !EQUAL(*papszMDListIter, "JPEG2000") && !STARTS_WITH_CI(*papszMDListIter, "xml:BOX_") && !EQUAL(*papszMDListIter, "xml:gml.root-instance") && !EQUAL(*papszMDListIter, "xml:XMP") && !EQUAL(*papszMDListIter, "xml:IPR") ) { papszSrcMD = poSrcDS->GetMetadata(*papszMDListIter); if( papszSrcMD && *papszSrcMD ) { bHasMD = true; oLocalMDMD.SetMetadata(papszSrcMD, *papszMDListIter); } } } CSLDestroy(papszMDList); } CPLXMLNode* psMasterXMLNode = NULL; if( bHasMD ) { CPLXMLNode* psXMLNode = oLocalMDMD.Serialize(); psMasterXMLNode = CPLCreateXMLNode( NULL, CXT_Element, "GDALMultiDomainMetadata" ); psMasterXMLNode->psChild = psXMLNode; } return psMasterXMLNode; } /************************************************************************/ /* CreateGDALMultiDomainMetadataXMLBox() */ /************************************************************************/ GDALJP2Box *GDALJP2Metadata::CreateGDALMultiDomainMetadataXMLBox( GDALDataset* poSrcDS, int bMainMDDomainOnly ) { CPLXMLNode* psMasterXMLNode = CreateGDALMultiDomainMetadataXML( poSrcDS, bMainMDDomainOnly ); if( psMasterXMLNode == NULL ) return NULL; char* pszXML = CPLSerializeXMLTree(psMasterXMLNode); CPLDestroyXMLNode(psMasterXMLNode); GDALJP2Box* poBox = new GDALJP2Box(); poBox->SetType("xml "); poBox->SetWritableData(static_cast<int>(strlen(pszXML) + 1), (const GByte*)pszXML); CPLFree(pszXML); return poBox; } /************************************************************************/ /* WriteXMLBoxes() */ /************************************************************************/ GDALJP2Box** GDALJP2Metadata::CreateXMLBoxes( GDALDataset* poSrcDS, int* pnBoxes ) { GDALJP2Box** papoBoxes = NULL; *pnBoxes = 0; char** papszMDList = poSrcDS->GetMetadataDomainList(); for( char** papszMDListIter = papszMDList; papszMDListIter && *papszMDListIter; ++papszMDListIter ) { /* Write metadata that look like originating from JP2 XML boxes */ /* as a standalone JP2 XML box */ if( STARTS_WITH_CI(*papszMDListIter, "xml:BOX_") ) { char** papszSrcMD = poSrcDS->GetMetadata(*papszMDListIter); if( papszSrcMD && *papszSrcMD ) { GDALJP2Box* poBox = new GDALJP2Box(); poBox->SetType("xml "); poBox->SetWritableData(static_cast<int>(strlen(*papszSrcMD) + 1), (const GByte*)*papszSrcMD); papoBoxes = (GDALJP2Box**)CPLRealloc(papoBoxes, sizeof(GDALJP2Box*) * (*pnBoxes + 1)); papoBoxes[(*pnBoxes)++] = poBox; } } } CSLDestroy(papszMDList); return papoBoxes; } /************************************************************************/ /* CreateXMPBox() */ /************************************************************************/ GDALJP2Box *GDALJP2Metadata::CreateXMPBox ( GDALDataset* poSrcDS ) { char** papszSrcMD = poSrcDS->GetMetadata("xml:XMP"); GDALJP2Box* poBox = NULL; if( papszSrcMD && * papszSrcMD ) { poBox = GDALJP2Box::CreateUUIDBox(xmp_uuid, static_cast<int>(strlen(*papszSrcMD) + 1), (const GByte*)*papszSrcMD); } return poBox; } /************************************************************************/ /* CreateIPRBox() */ /************************************************************************/ GDALJP2Box *GDALJP2Metadata::CreateIPRBox ( GDALDataset* poSrcDS ) { char** papszSrcMD = poSrcDS->GetMetadata("xml:IPR"); GDALJP2Box* poBox = NULL; if( papszSrcMD && * papszSrcMD ) { poBox = new GDALJP2Box(); poBox->SetType("jp2i"); poBox->SetWritableData(static_cast<int>(strlen(*papszSrcMD) + 1), (const GByte*)*papszSrcMD); } return poBox; } /************************************************************************/ /* IsUUID_MSI() */ /************************************************************************/ int GDALJP2Metadata::IsUUID_MSI(const GByte *abyUUID) { return memcmp(abyUUID, msi_uuid2, 16) == 0; } /************************************************************************/ /* IsUUID_XMP() */ /************************************************************************/ int GDALJP2Metadata::IsUUID_XMP(const GByte *abyUUID) { return memcmp(abyUUID, xmp_uuid, 16) == 0; } /*! @endcond */