ultimatepp/bazaar/plugin/gdal/gcore/gdaljp2metadata.cpp
cxl 23ff1e7e82 .gdal moved to bazaar
git-svn-id: svn://ultimatepp.org/upp/trunk@9273 f0d560ea-af0d-0410-9eb7-867de7ffcac7
2015-12-07 13:36:24 +00:00

3089 lines
126 KiB
C++

/******************************************************************************
* $Id: gdaljp2metadata.cpp 29330 2015-06-14 12:11:11Z rouault $
*
* 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 "gdaljp2metadata.h"
#include "cpl_string.h"
#include "cpl_minixml.h"
#include "ogr_spatialref.h"
#include "ogr_geometry.h"
#include "ogr_api.h"
#include "gt_wkt_srs_for_gdal.h"
#include "json.h"
#include "gdaljp2metadatagenerator.h"
CPL_CVSID("$Id: gdaljp2metadata.cpp 29330 2015-06-14 12:11:11Z 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;
};
#define MAX_JP2GEOTIFF_BOXES 2
/************************************************************************/
/* GDALJP2Metadata() */
/************************************************************************/
GDALJP2Metadata::GDALJP2Metadata()
{
pszProjection = NULL;
nGCPCount = 0;
pasGCPList = NULL;
papszRPCMD = NULL;
papszGMLMetadata = NULL;
papszMetadata = NULL;
nGeoTIFFBoxesCount = 0;
pasGeoTIFFBoxes = NULL;
nMSIGSize = 0;
pabyMSIGData = NULL;
pszXMPMetadata = NULL;
pszGDALMultiDomainMetadata = NULL;
pszXMLIPR = NULL;
bHaveGeoTransform = FALSE;
adfGeoTransform[0] = 0.0;
adfGeoTransform[1] = 1.0;
adfGeoTransform[2] = 0.0;
adfGeoTransform[3] = 0.0;
adfGeoTransform[4] = 0.0;
adfGeoTransform[5] = 1.0;
bPixelIsPoint = FALSE;
}
/************************************************************************/
/* ~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 )
{
VSILFILE *fpLL;
fpLL = VSIFOpenL( pszFilename, "rb" );
if( fpLL == NULL )
{
CPLDebug( "GDALJP2Metadata", "Could not even open %s.",
pszFilename );
return FALSE;
}
int bRet = ReadAndParse( fpLL );
VSIFCloseL( fpLL );
/* -------------------------------------------------------------------- */
/* If we still don't have a geotransform, look for a world */
/* file. */
/* -------------------------------------------------------------------- */
if( !bHaveGeoTransform )
{
bHaveGeoTransform =
GDALReadWorldFile( pszFilename, NULL, adfGeoTransform )
|| GDALReadWorldFile( pszFilename, ".wld", adfGeoTransform );
bRet |= bHaveGeoTransform;
}
return bRet;
}
int GDALJP2Metadata::ReadAndParse( VSILFILE *fpLL )
{
ReadBoxes( fpLL );
/* -------------------------------------------------------------------- */
/* Try JP2GeoTIFF, GML and finally MSIG to get something. */
/* -------------------------------------------------------------------- */
if( !ParseJP2GeoTIFF() && !ParseGMLCoverageDesc() )
ParseMSIG();
/* -------------------------------------------------------------------- */
/* 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() );
char *pszLabel = NULL;
char *pszXML = NULL;
if( !oSubChildBox.ReadFirstChild( &oChildBox ) )
break;
while( strlen(oSubChildBox.GetType()) > 0 )
{
if( EQUAL(oSubChildBox.GetType(),"lbl ") )
pszLabel = (char *)oSubChildBox.ReadBoxData();
else if( EQUAL(oSubChildBox.GetType(),"xml ") )
{
pszXML = (char *) oSubChildBox.ReadBoxData();
// Some GML data contains \0 instead of \n !
// See http://trac.osgeo.org/gdal/ticket/5760
if( pszXML )
{
int nXMLLength = (int)oSubChildBox.GetDataLength();
int i;
for(i=nXMLLength-1; i >= 0; i--)
{
if( pszXML[i] == '\0' )
nXMLLength --;
else
break;
}
for(i=0;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");
for(int i=0;i<nXMLLength;i++)
{
if( pszXML[i] == '\0' )
pszXML[i] = '\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 );
int iBox = 0;
if (!oBox.ReadFirst())
return FALSE;
while( strlen(oBox.GetType()) > 0 )
{
#ifdef DEBUG
if (CSLTestBoolean(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 can in some conditions contain 2 GeoTIFF */
/* UUID boxes. One that is correct, another one that does not contain */
/* correct georeferencing. So let's 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
{
int nGeoTIFFSize = (int) oBox.GetDataLength();
GByte* pabyGeoTIFFData = oBox.ReadBoxData();
if (pabyGeoTIFFData == NULL)
{
CPLDebug("GDALJP2", "Cannot read data for UUID GeoTIFF box");
}
else
{
pasGeoTIFFBoxes = (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 = (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( strncmp(pszXML, "<GDALMultiDomainMetadata>",
strlen("<GDALMultiDomainMetadata>")) == 0 )
{
if( pszGDALMultiDomainMetadata == NULL )
{
pszGDALMultiDomainMetadata = pszXML;
pszXML = NULL;
}
else
{
CPLDebug("GDALJP2", "Too many GDAL metadata boxes. Ignoring this one");
}
}
else
{
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
double dfVertRes =
(nVertNum/(double)nVertDen) * pow(10.0,nVertExp)/100;
double dfHorzRes =
(nHorzNum/(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(! CSLTestBoolean(CPLGetConfigOption("GDAL_USE_GEOJP2", "TRUE")) )
return FALSE;
int 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 };
int i;
int nMax = MIN(nGeoTIFFBoxesCount, MAX_JP2GEOTIFF_BOXES);
for(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(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( EQUALN(apszProjection[iBestIndex], "LOCAL_CS", strlen("LOCAL_CS")) )
iBestIndex = i;
}
}
if( iBestIndex < 0 )
{
for(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 = 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(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;
const char *pszFragmentId = NULL;
int i;
if( EQUALN(pszURN,"urn:jp2k:xml:", 13) )
pszLabel = CPLStrdup( pszURN + 13 );
else if( EQUALN(pszURN,"urn:ogc:tc:gmljp2:xml:", 22) )
pszLabel = CPLStrdup( pszURN + 22 );
else if( EQUALN(pszURN,"gmljp2://xml/",13) )
pszLabel = CPLStrdup( pszURN + 13 );
else
pszLabel = CPLStrdup( pszURN );
/* -------------------------------------------------------------------- */
/* Split out label and fragment id. */
/* -------------------------------------------------------------------- */
for( i = 0; 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;
int 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(! CSLTestBoolean(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, *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;
int 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 */
/* (ie. 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 interprete the */
/* srsName. */
/* -------------------------------------------------------------------- */
int bNeedAxisFlip = FALSE;
OGRSpatialReference oSRS;
if( bSuccess && pszSRSName != NULL
&& (pszProjection == NULL || strlen(pszProjection) == 0) )
{
if( EQUALN(pszSRSName,"epsg:",5) )
{
if( oSRS.SetFromUserInput( pszSRSName ) == OGRERR_NONE )
oSRS.exportToWkt( &pszProjection );
}
else if( (EQUALN(pszSRSName,"urn:",4)
&& 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 */
(EQUALN(pszSRSName,"http://www.opengis.net/def/crs/",
strlen("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
&& CSLTestBoolean( 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;
int bFirstAxisIsEastOrLong = FALSE, 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 &&
(EQUALN(psIter->psChild->pszValue, "EAST", 4) ||
EQUALN(psIter->psChild->pszValue, "LONG", 4) ) )
{
bFirstAxisIsEastOrLong = TRUE;
}
else if( nAxisCount == 1 &&
(EQUALN(psIter->psChild->pszValue, "NORTH", 5) ||
EQUALN(psIter->psChild->pszValue, "LAT", 3)) )
{
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 ||
CSLTestBoolean( 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()
int errNo = CPLGetLastErrorNo();
CPLErr eErr = CPLGetLastErrorType();
CPLString osLastErrorMsg = CPLGetLastErrorMsg();
// Determinte if we need to flix 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
&& CSLTestBoolean( 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( CSLTestBoolean( 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>Dictionnary for cursom SRS %s</gml:description>\n"
" <gml:name>Dictionnary 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;
}
VSIFSeekL( fp, 0, SEEK_END );
int nLength = (int) VSIFTellL( fp );
pszGML = (char *) CPLCalloc(1,nLength+1);
VSIFSeekL( fp, 0, SEEK_SET );
VSIFReadL( pszGML, 1, nLength, fp );
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 )
sprintf( szSRSName, "urn:ogc:def:crs:EPSG::%d", nEPSGCode );
else
strcpy( szSRSName,
"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 = MIN(MIN(dfX1,dfX2),MIN(dfX3,dfX4));
double dfLCY = MIN(MIN(dfY1,dfY2),MIN(dfY3,dfY4));
double dfUCX = MAX(MAX(dfX1,dfX2),MAX(dfX3,dfX4));
double dfUCY = MAX(MAX(dfY1,dfY2),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.size() > 0 )
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 recommands 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 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;
int 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",
"#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 evalated 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 substitued 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 = json_object_object_get(poObj, "root_instance");
if( poRootInstance && json_object_get_type(poRootInstance) == json_type_object )
{
json_object* poGMLId = 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 = 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* poCRSURL = json_object_object_get(poRootInstance, "crs_url");
if( poCRSURL && json_object_get_type(poCRSURL) == json_type_boolean )
bCRSURL = json_object_get_boolean(poCRSURL);
json_object* poMetadatas = 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 = 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 = 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 = json_object_object_get(poMetadata, "dynamic_metadata");
if( poDynamicMetadata && json_object_get_type(poDynamicMetadata) == json_type_object )
{
#ifdef HAVE_LIBXML2
if( CSLTestBoolean(CPLGetConfigOption("GDAL_DEBUG_PROCESS_DYNAMIC_METADATA", "YES")) )
{
json_object* poTemplate = 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 = 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");
}
}
int bGDALMetadata = FALSE;
json_object* poGDALMetadata = json_object_object_get(poMetadata, "gdal_metadata");
if( poGDALMetadata && json_object_get_type(poGDALMetadata) == json_type_boolean )
bGDALMetadata = 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 = 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 = 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 = 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 = 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 = 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* poNamespace = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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<(int)aoGMLFiles.size();i++)
{
if( aoGMLFiles[i].osSchemaLocation.size() &&
strncmp(aoGMLFiles[i].osSchemaLocation, "gmljp2://xml/",
strlen("gmljp2://xml/")) == 0 )
{
const char* pszLookedLabel =
aoGMLFiles[i].osSchemaLocation.c_str() + strlen("gmljp2://xml/");
int bFound = FALSE;
for(int j=0; !bFound && j<(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.size() > 0 )
{
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.size() == 0 )
{
/* -------------------------------------------------------------------- */
/* 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 )
sprintf( szSRSName, "http://www.opengis.net/def/crs/EPSG/0/%d", nEPSGCode );
else
sprintf( szSRSName, "urn:ogc:def:crs:EPSG::%d", nEPSGCode );
}
else
strcpy( szSRSName,
"gmljp2://xml/CRSDictionary.gml#ogrcrs1" );
osGridCoverage.Printf(
" <gmljp2:GMLJP2RectifiedGridCoverage gml:id=\"RGC_1_%s\">\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/>\n"
" </gmljp2:GMLJP2RectifiedGridCoverage>\n",
osRootGMLId.c_str(),
osRootGMLId.c_str(),
szSRSName,
nXSize-1, nYSize-1, szSRSName, adfOrigin[0], adfOrigin[1],
pszComment,
szSRSName, adfXVector[0], adfXVector[1],
szSRSName, adfYVector[0], adfYVector[1] );
}
/* -------------------------------------------------------------------- */
/* Main node. */
/* -------------------------------------------------------------------- */
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:gridDomain/>\n"
" <gml:rangeSet>\n"
" <gml:File>\n"
" <gml:rangeParameters/>\n"
" <gml:fileName>gmljp2://codestream</gml:fileName>\n"
" <gml:fileStructure>inapplicable</gml:fileStructure>\n"
" </gml:File>\n"
" </gml:rangeSet>\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;
int bRootHasXLink = FALSE;
if( aoMetadata.size() || aoAnnotations.size() || aoGMLFiles.size() ||
aoStyles.size() || aoExtensions.size() )
{
CPLXMLNode* psRoot = CPLParseXMLString(osDoc);
CPLAssert(psRoot);
CPLXMLNode* psGMLJP2CoverageCollection = GDALGMLJP2GetXMLRoot(psRoot);
CPLAssert(psGMLJP2CoverageCollection);
for( int i=0; i < (int)aoMetadata.size(); i++ )
{
CPLXMLNode* psMetadata;
if( aoMetadata[i].osFile.size() )
psMetadata = CPLParseXMLFile(aoMetadata[i].osFile);
else if( aoMetadata[i].osContent.size() )
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 sibbling 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);
}
// 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 < (int)aoGMLFiles.size(); i++ )
{
// Is the file already a GML file ?
CPLXMLNode* psGMLFile = NULL;
if( aoGMLFiles[i].osFile.size() )
{
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* apszOptions[2];
apszOptions[0] = (char*) "FORMAT=GML3.2";
apszOptions[1] = NULL;
GDALDatasetH hDS = GDALCreateCopy(hGMLDrv, osTmpFile, hSrcDS,
FALSE, apszOptions, NULL, NULL);
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.size() )
{
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.size() )
{
if( !bRootHasXLink )
{
bRootHasXLink = TRUE;
CPLSetXMLValue(psGMLJP2CoverageCollection, "#xmlns:xlink",
"http://www.w3.org/1999/xlink");
}
}
if( aoGMLFiles[i].osRemoteResource.size() )
{
CPLSetXMLValue(node_f, "#xlink:href",
aoGMLFiles[i].osRemoteResource.c_str());
continue;
}
CPLString osTmpFile;
if( !aoGMLFiles[i].bInline || aoGMLFiles[i].osRemoteResource.size() )
{
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.size() == 0 &&
aoGMLFiles[i].osSchemaLocation.size() )
{
osSchemaLocation += papszTokens[0];
osSchemaLocation += " ";
osSchemaLocation += aoGMLFiles[i].osSchemaLocation;
}
else if( CSLCount(papszTokens) == 2 &&
(aoGMLFiles[i].osNamespace.size() == 0 ||
strcmp(papszTokens[0], aoGMLFiles[i].osNamespace) == 0) &&
aoGMLFiles[i].osSchemaLocation.size() == 0 )
{
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.size() )
{
GMLJP2V2BoxDesc oDesc;
oDesc.osFile = osXSD;
oDesc.osLabel = CPLGetFilename(oDesc.osFile);
osSchemaLocation += papszTokens[0];
osSchemaLocation += " ";
osSchemaLocation += "gmljp2://xml/";
osSchemaLocation += oDesc.osLabel;
int j;
for( j=0; j<(int)aoBoxes.size(); j++)
{
if( aoBoxes[j].osLabel == oDesc.osLabel )
break;
}
if( j == (int)aoBoxes.size() )
aoBoxes.push_back(oDesc);
}
}
else if( (CSLCount(papszTokens) % 2) == 0 )
{
for(char** papszIter = papszTokens; *papszIter; papszIter += 2 )
{
if( osSchemaLocation.size() )
osSchemaLocation += " ";
if( aoGMLFiles[i].osNamespace.size() &&
aoGMLFiles[i].osSchemaLocation.size() &&
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);
}
// Cf http://schemas.opengis.net/gmljp2/2.0/examples/gmljp2_annotation.xml
for( int i=0; i < (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* apszOptions[2];
apszOptions[0] = NULL;
apszOptions[1] = NULL;
GDALDatasetH hDS = GDALCreateCopy(hLIBKMLDrv ? hLIBKMLDrv : hKMLDrv,
osTmpFile, hSrcDS,
FALSE, apszOptions, NULL, NULL);
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 < (int)aoStyles.size(); i++ )
{
CPLXMLNode* psStyle = CPLParseXMLFile(aoStyles[i].osFile);
if( psStyle == NULL )
continue;
CPLXMLNode* psStyleRoot = GDALGMLJP2GetXMLRoot(psStyle);
if( psStyleRoot )
{
CPLXMLNode *psGMLJP2Style;
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 < (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.size() > 0 )
apoGMLBoxes.push_back(
GDALJP2Box::CreateLabelledXMLAssoc( "CRSDictionary.gml",
osDictBox ) );
/* -------------------------------------------------------------------- */
/* Additional user specified boxes. */
/* -------------------------------------------------------------------- */
for( int i=0; i < (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( (int)apoGMLBoxes.size(),
&apoGMLBoxes[0]);
/* -------------------------------------------------------------------- */
/* Cleanup working boxes. */
/* -------------------------------------------------------------------- */
for( int i=0; i < (int)apoGMLBoxes.size(); i++ )
delete apoGMLBoxes[i];
for( int i=0; i < (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);
}
int 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, "JPEG2000") &&
!EQUALN(*papszMDListIter, "xml:BOX_", strlen("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(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( EQUALN(*papszMDListIter, "xml:BOX_", strlen("xml:BOX_")) )
{
char** papszSrcMD = poSrcDS->GetMetadata(*papszMDListIter);
if( papszSrcMD && *papszSrcMD )
{
GDALJP2Box* poBox = new GDALJP2Box();
poBox->SetType("xml ");
poBox->SetWritableData(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,
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(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;
}