gdal/frmts/nitf/ecrgtocdataset.cpp

1120 строки
42 KiB
C++

/******************************************************************************
*
* Project: ECRG TOC read Translator
* Purpose: Implementation of ECRGTOCDataset and ECRGTOCSubDataset.
* Author: Even Rouault, even.rouault at spatialys.com
*
******************************************************************************
* Copyright (c) 2011, Even Rouault <even dot rouault at spatialys.com>
*
* 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.
****************************************************************************/
// g++ -g -Wall -fPIC frmts/nitf/ecrgtocdataset.cpp -shared -o gdal_ECRGTOC.so
// -Iport -Igcore -Iogr -Ifrmts/vrt -L. -lgdal
#include "cpl_port.h"
#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <string>
#include <vector>
#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_minixml.h"
#include "cpl_string.h"
#include "gdal.h"
#include "gdal_frmts.h"
#include "gdal_pam.h"
#include "gdal_priv.h"
#include "gdal_proxy.h"
#include "ogr_srs_api.h"
#include "vrtdataset.h"
/** Overview of used classes :
- ECRGTOCDataset : lists the different subdatasets, listed in the .xml,
as subdatasets
- ECRGTOCSubDataset : one of these subdatasets, implemented as a VRT, of
the relevant NITF tiles
*/
namespace
{
typedef struct
{
const char *pszName;
const char *pszPath;
int nScale;
int nZone;
} FrameDesc;
} // namespace
/************************************************************************/
/* ==================================================================== */
/* ECRGTOCDataset */
/* ==================================================================== */
/************************************************************************/
class ECRGTOCDataset final : public GDALPamDataset
{
OGRSpatialReference m_oSRS{};
char **papszSubDatasets;
double adfGeoTransform[6];
char **papszFileList;
public:
ECRGTOCDataset()
{
m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
m_oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
papszSubDatasets = nullptr;
papszFileList = nullptr;
memset(adfGeoTransform, 0, sizeof(adfGeoTransform));
}
virtual ~ECRGTOCDataset()
{
CSLDestroy(papszSubDatasets);
CSLDestroy(papszFileList);
}
virtual char **GetMetadata(const char *pszDomain = "") override;
virtual char **GetFileList() override
{
return CSLDuplicate(papszFileList);
}
void AddSubDataset(const char *pszFilename, const char *pszProductTitle,
const char *pszDiscId, const char *pszScale);
virtual CPLErr GetGeoTransform(double *padfGeoTransform) override
{
memcpy(padfGeoTransform, adfGeoTransform, 6 * sizeof(double));
return CE_None;
}
const OGRSpatialReference *GetSpatialRef() const override
{
return &m_oSRS;
}
static GDALDataset *Build(const char *pszTOCFilename, CPLXMLNode *psXML,
CPLString osProduct, CPLString osDiscId,
CPLString osScale, const char *pszFilename);
static int Identify(GDALOpenInfo *poOpenInfo);
static GDALDataset *Open(GDALOpenInfo *poOpenInfo);
};
/************************************************************************/
/* ==================================================================== */
/* ECRGTOCSubDataset */
/* ==================================================================== */
/************************************************************************/
class ECRGTOCSubDataset final : public VRTDataset
{
char **papszFileList;
public:
ECRGTOCSubDataset(int nXSize, int nYSize) : VRTDataset(nXSize, nYSize)
{
/* Don't try to write a VRT file */
SetWritable(FALSE);
/* The driver is set to VRT in VRTDataset constructor. */
/* We have to set it to the expected value ! */
poDriver =
reinterpret_cast<GDALDriver *>(GDALGetDriverByName("ECRGTOC"));
papszFileList = nullptr;
}
~ECRGTOCSubDataset()
{
CSLDestroy(papszFileList);
}
virtual char **GetFileList() override
{
return CSLDuplicate(papszFileList);
}
static GDALDataset *
Build(const char *pszProductTitle, const char *pszDiscId, int nScale,
int nCountSubDataset, const char *pszTOCFilename,
const std::vector<FrameDesc> &aosFrameDesc, double dfGlobalMinX,
double dfGlobalMinY, double dfGlobalMaxX, double dfGlobalMaxY,
double dfGlobalPixelXSize, double dfGlobalPixelYSize);
};
/************************************************************************/
/* LaunderString() */
/************************************************************************/
static CPLString LaunderString(const char *pszStr)
{
CPLString osRet(pszStr);
for (size_t i = 0; i < osRet.size(); i++)
{
if (osRet[i] == ':' || osRet[i] == ' ')
osRet[i] = '_';
}
return osRet;
}
/************************************************************************/
/* AddSubDataset() */
/************************************************************************/
void ECRGTOCDataset::AddSubDataset(const char *pszFilename,
const char *pszProductTitle,
const char *pszDiscId, const char *pszScale)
{
char szName[80];
const int nCount = CSLCount(papszSubDatasets) / 2;
snprintf(szName, sizeof(szName), "SUBDATASET_%d_NAME", nCount + 1);
papszSubDatasets = CSLSetNameValue(
papszSubDatasets, szName,
CPLSPrintf("ECRG_TOC_ENTRY:%s:%s:%s:%s",
LaunderString(pszProductTitle).c_str(),
LaunderString(pszDiscId).c_str(),
LaunderString(pszScale).c_str(), pszFilename));
snprintf(szName, sizeof(szName), "SUBDATASET_%d_DESC", nCount + 1);
papszSubDatasets =
CSLSetNameValue(papszSubDatasets, szName,
CPLSPrintf("Product %s, disc %s, scale %s",
pszProductTitle, pszDiscId, pszScale));
}
/************************************************************************/
/* GetMetadata() */
/************************************************************************/
char **ECRGTOCDataset::GetMetadata(const char *pszDomain)
{
if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
return papszSubDatasets;
return GDALPamDataset::GetMetadata(pszDomain);
}
/************************************************************************/
/* GetScaleFromString() */
/************************************************************************/
static int GetScaleFromString(const char *pszScale)
{
const char *pszPtr = strstr(pszScale, "1:");
if (pszPtr)
pszPtr = pszPtr + 2;
else
pszPtr = pszScale;
int nScale = 0;
char ch;
while ((ch = *pszPtr) != '\0')
{
if (ch >= '0' && ch <= '9')
nScale = nScale * 10 + ch - '0';
else if (ch == ' ')
;
else if (ch == 'k' || ch == 'K')
return nScale * 1000;
else if (ch == 'm' || ch == 'M')
return nScale * 1000000;
else
return 0;
pszPtr++;
}
return nScale;
}
/************************************************************************/
/* GetFromBase34() */
/************************************************************************/
static GIntBig GetFromBase34(const char *pszVal, int nMaxSize)
{
GIntBig nFrameNumber = 0;
for (int i = 0; i < nMaxSize; i++)
{
char ch = pszVal[i];
if (ch == '\0')
break;
int chVal;
if (ch >= 'A' && ch <= 'Z')
ch += 'a' - 'A';
/* i and o letters are excluded, */
if (ch >= '0' && ch <= '9')
chVal = ch - '0';
else if (ch >= 'a' && ch <= 'h')
chVal = ch - 'a' + 10;
else if (ch >= 'j' && ch <= 'n')
chVal = ch - 'a' + 10 - 1;
else if (ch >= 'p' && ch <= 'z')
chVal = ch - 'a' + 10 - 2;
else
{
CPLDebug("ECRG", "Invalid base34 value : %s", pszVal);
break;
}
nFrameNumber = nFrameNumber * 34 + chVal;
}
return nFrameNumber;
}
/************************************************************************/
/* GetExtent() */
/************************************************************************/
/* MIL-PRF-32283 - Table II. ECRG zone limits. */
/* starting with a fake zone 0 for convenience. */
constexpr int anZoneUpperLat[] = {0, 32, 48, 56, 64, 68, 72, 76, 80};
/* APPENDIX 70, TABLE III of MIL-A-89007 */
constexpr int anACst_ADRG[] = {369664, 302592, 245760, 199168,
163328, 137216, 110080, 82432};
constexpr int nBCst_ADRG = 400384;
// TODO: Why are these two functions done this way?
static int CEIL_ROUND(double a, double b)
{
return static_cast<int>(ceil(a / b) * b);
}
static int NEAR_ROUND(double a, double b)
{
return static_cast<int>(floor((a / b) + 0.5) * b);
}
constexpr int ECRG_PIXELS = 2304;
static void GetExtent(const char *pszFrameName, int nScale, int nZone,
double &dfMinX, double &dfMaxX, double &dfMinY,
double &dfMaxY, double &dfPixelXSize,
double &dfPixelYSize)
{
const int nAbsZone = abs(nZone);
#ifdef DEBUG
assert(nAbsZone > 0 && nAbsZone <= 8);
#endif
/************************************************************************/
/* Compute east-west constant */
/************************************************************************/
/* MIL-PRF-89038 - 60.1.2 - East-west pixel constant. */
const int nEW_ADRG =
CEIL_ROUND(anACst_ADRG[nAbsZone - 1] * (1e6 / nScale), 512);
const int nEW_CADRG = NEAR_ROUND(nEW_ADRG / (150. / 100.), 256);
/* MIL-PRF-32283 - D.2.1.2 - East-west pixel constant. */
const int nEW = nEW_CADRG / 256 * 384;
/************************************************************************/
/* Compute number of longitudinal frames */
/************************************************************************/
/* MIL-PRF-32283 - D.2.1.7 - Longitudinal frames and subframes */
const int nCols =
static_cast<int>(ceil(static_cast<double>(nEW) / ECRG_PIXELS));
/************************************************************************/
/* Compute north-south constant */
/************************************************************************/
/* MIL-PRF-89038 - 60.1.1 - North-south. pixel constant */
const int nNS_ADRG = CEIL_ROUND(nBCst_ADRG * (1e6 / nScale), 512) / 4;
const int nNS_CADRG = NEAR_ROUND(nNS_ADRG / (150. / 100.), 256);
/* MIL-PRF-32283 - D.2.1.1 - North-south. pixel constant and Frame
* Width/Height */
const int nNS = nNS_CADRG / 256 * 384;
/************************************************************************/
/* Compute number of latitudinal frames and latitude of top of zone */
/************************************************************************/
dfPixelYSize = 90.0 / nNS;
const double dfFrameLatHeight = dfPixelYSize * ECRG_PIXELS;
/* MIL-PRF-32283 - D.2.1.5 - Equatorward and poleward zone extents. */
int nUpperZoneFrames =
static_cast<int>(ceil(anZoneUpperLat[nAbsZone] / dfFrameLatHeight));
int nBottomZoneFrames = static_cast<int>(
floor(anZoneUpperLat[nAbsZone - 1] / dfFrameLatHeight));
const int nRows = nUpperZoneFrames - nBottomZoneFrames;
/* Not sure to really understand D.2.1.5.a. Testing needed */
if (nZone < 0)
{
nUpperZoneFrames = -nBottomZoneFrames;
/*nBottomZoneFrames = nUpperZoneFrames - nRows;*/
}
const double dfUpperZoneTopLat = dfFrameLatHeight * nUpperZoneFrames;
/************************************************************************/
/* Compute coordinates of the frame in the zone */
/************************************************************************/
/* Converts the first 10 characters into a number from base 34 */
const GIntBig nFrameNumber = GetFromBase34(pszFrameName, 10);
/* MIL-PRF-32283 - A.2.6.1 */
const GIntBig nY = nFrameNumber / nCols;
const GIntBig nX = nFrameNumber % nCols;
/************************************************************************/
/* Compute extent of the frame */
/************************************************************************/
/* The nY is counted from the bottom of the zone... Pfff */
dfMaxY = dfUpperZoneTopLat - (nRows - 1 - nY) * dfFrameLatHeight;
dfMinY = dfMaxY - dfFrameLatHeight;
dfPixelXSize = 360.0 / nEW;
const double dfFrameLongWidth = dfPixelXSize * ECRG_PIXELS;
dfMinX = -180.0 + nX * dfFrameLongWidth;
dfMaxX = dfMinX + dfFrameLongWidth;
#ifdef DEBUG_VERBOSE
CPLDebug("ECRG",
"Frame %s : minx=%.16g, maxy=%.16g, maxx=%.16g, miny=%.16g",
pszFrameName, dfMinX, dfMaxY, dfMaxX, dfMinY);
#endif
}
/************************************************************************/
/* ECRGTOCSource */
/************************************************************************/
class ECRGTOCSource final : public VRTSimpleSource
{
int m_nRasterXSize = 0;
int m_nRasterYSize = 0;
double m_dfMinX = 0;
double m_dfMaxY = 0;
double m_dfPixelXSize = 0;
double m_dfPixelYSize = 0;
bool ValidateOpenedBand(GDALRasterBand *) const override;
public:
ECRGTOCSource(const char *pszFilename, int nBandIn, int nRasterXSize,
int nRasterYSize, double dfDstXOff, double dfDstYOff,
double dfDstXSize, double dfDstYSize, double dfMinX,
double dfMaxY, double dfPixelXSize, double dfPixelYSize)
: m_nRasterXSize(nRasterXSize), m_nRasterYSize(nRasterYSize),
m_dfMinX(dfMinX), m_dfMaxY(dfMaxY), m_dfPixelXSize(dfPixelXSize),
m_dfPixelYSize(dfPixelYSize)
{
SetSrcBand(pszFilename, nBandIn);
SetSrcWindow(0, 0, nRasterXSize, nRasterYSize);
SetDstWindow(dfDstXOff, dfDstYOff, dfDstXSize, dfDstYSize);
}
};
/************************************************************************/
/* ValidateOpenedBand() */
/************************************************************************/
#define WARN_CHECK_DS(x) \
do \
{ \
if (!(x)) \
{ \
CPLError(CE_Warning, CPLE_AppDefined, \
"For %s, assert '" #x "' failed", \
poSourceDS->GetDescription()); \
checkOK = false; \
} \
} while (false)
bool ECRGTOCSource::ValidateOpenedBand(GDALRasterBand *poBand) const
{
bool checkOK = true;
auto poSourceDS = poBand->GetDataset();
CPLAssert(poSourceDS);
double l_adfGeoTransform[6] = {};
poSourceDS->GetGeoTransform(l_adfGeoTransform);
WARN_CHECK_DS(fabs(l_adfGeoTransform[0] - m_dfMinX) < 1e-10);
WARN_CHECK_DS(fabs(l_adfGeoTransform[3] - m_dfMaxY) < 1e-10);
WARN_CHECK_DS(fabs(l_adfGeoTransform[1] - m_dfPixelXSize) < 1e-10);
WARN_CHECK_DS(fabs(l_adfGeoTransform[5] - (-m_dfPixelYSize)) < 1e-10);
WARN_CHECK_DS(l_adfGeoTransform[2] == 0 &&
l_adfGeoTransform[4] == 0); // No rotation.
WARN_CHECK_DS(poSourceDS->GetRasterCount() == 3);
WARN_CHECK_DS(poSourceDS->GetRasterXSize() == m_nRasterXSize);
WARN_CHECK_DS(poSourceDS->GetRasterYSize() == m_nRasterYSize);
WARN_CHECK_DS(
EQUAL(poSourceDS->GetProjectionRef(), SRS_WKT_WGS84_LAT_LONG));
WARN_CHECK_DS(poSourceDS->GetRasterBand(1)->GetRasterDataType() ==
GDT_Byte);
return checkOK;
}
/************************************************************************/
/* BuildFullName() */
/************************************************************************/
static const char *BuildFullName(const char *pszTOCFilename,
const char *pszFramePath,
const char *pszFrameName)
{
char *pszPath = nullptr;
if (pszFramePath[0] == '.' &&
(pszFramePath[1] == '/' || pszFramePath[1] == '\\'))
pszPath = CPLStrdup(pszFramePath + 2);
else
pszPath = CPLStrdup(pszFramePath);
for (int i = 0; pszPath[i] != '\0'; i++)
{
if (pszPath[i] == '\\')
pszPath[i] = '/';
}
const char *pszName = CPLFormFilename(pszPath, pszFrameName, nullptr);
CPLFree(pszPath);
pszPath = nullptr;
const char *pszTOCPath = CPLGetDirname(pszTOCFilename);
const char *pszFirstSlashInName = strchr(pszName, '/');
if (pszFirstSlashInName != nullptr)
{
int nFirstDirLen = static_cast<int>(pszFirstSlashInName - pszName);
if (static_cast<int>(strlen(pszTOCPath)) >= nFirstDirLen + 1 &&
(pszTOCPath[strlen(pszTOCPath) - (nFirstDirLen + 1)] == '/' ||
pszTOCPath[strlen(pszTOCPath) - (nFirstDirLen + 1)] == '\\') &&
strncmp(pszTOCPath + strlen(pszTOCPath) - nFirstDirLen, pszName,
nFirstDirLen) == 0)
{
pszTOCPath = CPLGetDirname(pszTOCPath);
}
}
return CPLProjectRelativeFilename(pszTOCPath, pszName);
}
/************************************************************************/
/* Build() */
/************************************************************************/
/* Builds a ECRGTOCSubDataset from the set of files of the toc entry */
GDALDataset *ECRGTOCSubDataset::Build(
const char *pszProductTitle, const char *pszDiscId, int nScale,
int nCountSubDataset, const char *pszTOCFilename,
const std::vector<FrameDesc> &aosFrameDesc, double dfGlobalMinX,
double dfGlobalMinY, double dfGlobalMaxX, double dfGlobalMaxY,
double dfGlobalPixelXSize, double dfGlobalPixelYSize)
{
GDALDriver *poDriver = GetGDALDriverManager()->GetDriverByName("VRT");
if (poDriver == nullptr)
return nullptr;
const int nSizeX = static_cast<int>(
(dfGlobalMaxX - dfGlobalMinX) / dfGlobalPixelXSize + 0.5);
const int nSizeY = static_cast<int>(
(dfGlobalMaxY - dfGlobalMinY) / dfGlobalPixelYSize + 0.5);
/* ------------------------------------ */
/* Create the VRT with the overall size */
/* ------------------------------------ */
ECRGTOCSubDataset *poVirtualDS = new ECRGTOCSubDataset(nSizeX, nSizeY);
poVirtualDS->SetProjection(SRS_WKT_WGS84_LAT_LONG);
double adfGeoTransform[6] = {
dfGlobalMinX, dfGlobalPixelXSize, 0, dfGlobalMaxY, 0,
-dfGlobalPixelYSize};
poVirtualDS->SetGeoTransform(adfGeoTransform);
for (int i = 0; i < 3; i++)
{
poVirtualDS->AddBand(GDT_Byte, nullptr);
GDALRasterBand *poBand = poVirtualDS->GetRasterBand(i + 1);
poBand->SetColorInterpretation((GDALColorInterp)(GCI_RedBand + i));
}
poVirtualDS->SetDescription(pszTOCFilename);
poVirtualDS->SetMetadataItem("PRODUCT_TITLE", pszProductTitle);
poVirtualDS->SetMetadataItem("DISC_ID", pszDiscId);
if (nScale != -1)
poVirtualDS->SetMetadataItem("SCALE", CPLString().Printf("%d", nScale));
/* -------------------------------------------------------------------- */
/* Check for overviews. */
/* -------------------------------------------------------------------- */
poVirtualDS->oOvManager.Initialize(
poVirtualDS,
CPLString().Printf("%s.%d", pszTOCFilename, nCountSubDataset));
poVirtualDS->papszFileList = poVirtualDS->GDALDataset::GetFileList();
// Rather hacky... Force GDAL_FORCE_CACHING=NO so that the
// GDALProxyPoolRasterBand do not use the GDALRasterBand::IRasterIO()
// default implementation, which would rely on the block size of
// GDALProxyPoolRasterBand, which we don't know...
CPLConfigOptionSetter oSetter("GDAL_FORCE_CACHING", "NO", false);
for (int i = 0; i < static_cast<int>(aosFrameDesc.size()); i++)
{
const char *pszName = BuildFullName(
pszTOCFilename, aosFrameDesc[i].pszPath, aosFrameDesc[i].pszName);
double dfMinX = 0.0;
double dfMaxX = 0.0;
double dfMinY = 0.0;
double dfMaxY = 0.0;
double dfPixelXSize = 0.0;
double dfPixelYSize = 0.0;
GetExtent(aosFrameDesc[i].pszName, aosFrameDesc[i].nScale,
aosFrameDesc[i].nZone, dfMinX, dfMaxX, dfMinY, dfMaxY,
dfPixelXSize, dfPixelYSize);
const int nFrameXSize =
static_cast<int>((dfMaxX - dfMinX) / dfPixelXSize + 0.5);
const int nFrameYSize =
static_cast<int>((dfMaxY - dfMinY) / dfPixelYSize + 0.5);
poVirtualDS->papszFileList =
CSLAddString(poVirtualDS->papszFileList, pszName);
for (int j = 0; j < 3; j++)
{
VRTSourcedRasterBand *poBand =
cpl::down_cast<VRTSourcedRasterBand *>(
poVirtualDS->GetRasterBand(j + 1));
/* Place the raster band at the right position in the VRT */
auto poSource = new ECRGTOCSource(
pszName, j + 1, nFrameXSize, nFrameYSize,
static_cast<int>((dfMinX - dfGlobalMinX) / dfGlobalPixelXSize +
0.5),
static_cast<int>((dfGlobalMaxY - dfMaxY) / dfGlobalPixelYSize +
0.5),
static_cast<int>((dfMaxX - dfMinX) / dfGlobalPixelXSize + 0.5),
static_cast<int>((dfMaxY - dfMinY) / dfGlobalPixelYSize + 0.5),
dfMinX, dfMaxY, dfPixelXSize, dfPixelYSize);
poBand->AddSource(poSource);
}
}
poVirtualDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
return poVirtualDS;
}
/************************************************************************/
/* Build() */
/************************************************************************/
GDALDataset *ECRGTOCDataset::Build(const char *pszTOCFilename,
CPLXMLNode *psXML, CPLString osProduct,
CPLString osDiscId, CPLString osScale,
const char *pszOpenInfoFilename)
{
CPLXMLNode *psTOC = CPLGetXMLNode(psXML, "=Table_of_Contents");
if (psTOC == nullptr)
{
CPLError(CE_Warning, CPLE_AppDefined,
"Cannot find Table_of_Contents element");
return nullptr;
}
double dfGlobalMinX = 0.0;
double dfGlobalMinY = 0.0;
double dfGlobalMaxX = 0.0;
double dfGlobalMaxY = 0.0;
double dfGlobalPixelXSize = 0.0;
double dfGlobalPixelYSize = 0.0;
bool bGlobalExtentValid = false;
ECRGTOCDataset *poDS = new ECRGTOCDataset();
int nSubDatasets = 0;
int bLookForSubDataset = !osProduct.empty() && !osDiscId.empty();
int nCountSubDataset = 0;
poDS->SetDescription(pszOpenInfoFilename);
poDS->papszFileList = poDS->GDALDataset::GetFileList();
for (CPLXMLNode *psIter1 = psTOC->psChild; psIter1 != nullptr;
psIter1 = psIter1->psNext)
{
if (!(psIter1->eType == CXT_Element && psIter1->pszValue != nullptr &&
strcmp(psIter1->pszValue, "product") == 0))
continue;
const char *pszProductTitle =
CPLGetXMLValue(psIter1, "product_title", nullptr);
if (pszProductTitle == nullptr)
{
CPLError(CE_Warning, CPLE_AppDefined,
"Cannot find product_title attribute");
continue;
}
if (bLookForSubDataset &&
strcmp(LaunderString(pszProductTitle), osProduct.c_str()) != 0)
continue;
for (CPLXMLNode *psIter2 = psIter1->psChild; psIter2 != nullptr;
psIter2 = psIter2->psNext)
{
if (!(psIter2->eType == CXT_Element &&
psIter2->pszValue != nullptr &&
strcmp(psIter2->pszValue, "disc") == 0))
continue;
const char *pszDiscId = CPLGetXMLValue(psIter2, "id", nullptr);
if (pszDiscId == nullptr)
{
CPLError(CE_Warning, CPLE_AppDefined,
"Cannot find id attribute");
continue;
}
if (bLookForSubDataset &&
strcmp(LaunderString(pszDiscId), osDiscId.c_str()) != 0)
continue;
CPLXMLNode *psFrameList = CPLGetXMLNode(psIter2, "frame_list");
if (psFrameList == nullptr)
{
CPLError(CE_Warning, CPLE_AppDefined,
"Cannot find frame_list element");
continue;
}
for (CPLXMLNode *psIter3 = psFrameList->psChild; psIter3 != nullptr;
psIter3 = psIter3->psNext)
{
if (!(psIter3->eType == CXT_Element &&
psIter3->pszValue != nullptr &&
strcmp(psIter3->pszValue, "scale") == 0))
continue;
const char *pszSize = CPLGetXMLValue(psIter3, "size", nullptr);
if (pszSize == nullptr)
{
CPLError(CE_Warning, CPLE_AppDefined,
"Cannot find size attribute");
continue;
}
int nScale = GetScaleFromString(pszSize);
if (nScale <= 0)
{
CPLError(CE_Warning, CPLE_AppDefined, "Invalid scale %s",
pszSize);
continue;
}
if (bLookForSubDataset)
{
if (!osScale.empty())
{
if (strcmp(LaunderString(pszSize), osScale.c_str()) !=
0)
{
continue;
}
}
else
{
int nCountScales = 0;
for (CPLXMLNode *psIter4 = psFrameList->psChild;
psIter4 != nullptr; psIter4 = psIter4->psNext)
{
if (!(psIter4->eType == CXT_Element &&
psIter4->pszValue != nullptr &&
strcmp(psIter4->pszValue, "scale") == 0))
continue;
nCountScales++;
}
if (nCountScales > 1)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Scale should be mentioned in "
"subdatasets syntax since this disk "
"contains several scales");
delete poDS;
return nullptr;
}
}
}
nCountSubDataset++;
std::vector<FrameDesc> aosFrameDesc;
int nValidFrames = 0;
for (CPLXMLNode *psIter4 = psIter3->psChild; psIter4 != nullptr;
psIter4 = psIter4->psNext)
{
if (!(psIter4->eType == CXT_Element &&
psIter4->pszValue != nullptr &&
strcmp(psIter4->pszValue, "frame") == 0))
continue;
const char *pszFrameName =
CPLGetXMLValue(psIter4, "name", nullptr);
if (pszFrameName == nullptr)
{
CPLError(CE_Warning, CPLE_AppDefined,
"Cannot find name element");
continue;
}
if (strlen(pszFrameName) != 18)
{
CPLError(CE_Warning, CPLE_AppDefined,
"Invalid value for name element : %s",
pszFrameName);
continue;
}
const char *pszFramePath =
CPLGetXMLValue(psIter4, "frame_path", nullptr);
if (pszFramePath == nullptr)
{
CPLError(CE_Warning, CPLE_AppDefined,
"Cannot find frame_path element");
continue;
}
const char *pszFrameZone =
CPLGetXMLValue(psIter4, "frame_zone", nullptr);
if (pszFrameZone == nullptr)
{
CPLError(CE_Warning, CPLE_AppDefined,
"Cannot find frame_zone element");
continue;
}
if (strlen(pszFrameZone) != 1)
{
CPLError(CE_Warning, CPLE_AppDefined,
"Invalid value for frame_zone element : %s",
pszFrameZone);
continue;
}
char chZone = pszFrameZone[0];
int nZone = 0;
if (chZone >= '1' && chZone <= '9')
nZone = chZone - '0';
else if (chZone >= 'a' && chZone <= 'h')
nZone = -(chZone - 'a' + 1);
else if (chZone >= 'A' && chZone <= 'H')
nZone = -(chZone - 'A' + 1);
else if (chZone == 'j' || chZone == 'J')
nZone = -9;
else
{
CPLError(CE_Warning, CPLE_AppDefined,
"Invalid value for frame_zone element : %s",
pszFrameZone);
continue;
}
if (nZone == 9 || nZone == -9)
{
CPLError(
CE_Warning, CPLE_AppDefined,
"Polar zones unhandled by current implementation");
continue;
}
double dfMinX = 0.0;
double dfMaxX = 0.0;
double dfMinY = 0.0;
double dfMaxY = 0.0;
double dfPixelXSize = 0.0;
double dfPixelYSize = 0.0;
GetExtent(pszFrameName, nScale, nZone, dfMinX, dfMaxX,
dfMinY, dfMaxY, dfPixelXSize, dfPixelYSize);
nValidFrames++;
const char *pszFullName = BuildFullName(
pszTOCFilename, pszFramePath, pszFrameName);
poDS->papszFileList =
CSLAddString(poDS->papszFileList, pszFullName);
if (!bGlobalExtentValid)
{
dfGlobalMinX = dfMinX;
dfGlobalMinY = dfMinY;
dfGlobalMaxX = dfMaxX;
dfGlobalMaxY = dfMaxY;
dfGlobalPixelXSize = dfPixelXSize;
dfGlobalPixelYSize = dfPixelYSize;
bGlobalExtentValid = true;
}
else
{
if (dfMinX < dfGlobalMinX)
dfGlobalMinX = dfMinX;
if (dfMinY < dfGlobalMinY)
dfGlobalMinY = dfMinY;
if (dfMaxX > dfGlobalMaxX)
dfGlobalMaxX = dfMaxX;
if (dfMaxY > dfGlobalMaxY)
dfGlobalMaxY = dfMaxY;
if (dfPixelXSize < dfGlobalPixelXSize)
dfGlobalPixelXSize = dfPixelXSize;
if (dfPixelYSize < dfGlobalPixelYSize)
dfGlobalPixelYSize = dfPixelYSize;
}
nValidFrames++;
if (bLookForSubDataset)
{
FrameDesc frameDesc;
frameDesc.pszName = pszFrameName;
frameDesc.pszPath = pszFramePath;
frameDesc.nScale = nScale;
frameDesc.nZone = nZone;
aosFrameDesc.push_back(frameDesc);
}
}
if (bLookForSubDataset)
{
delete poDS;
if (nValidFrames == 0)
return nullptr;
return ECRGTOCSubDataset::Build(
pszProductTitle, pszDiscId, nScale, nCountSubDataset,
pszTOCFilename, aosFrameDesc, dfGlobalMinX,
dfGlobalMinY, dfGlobalMaxX, dfGlobalMaxY,
dfGlobalPixelXSize, dfGlobalPixelYSize);
}
if (nValidFrames)
{
poDS->AddSubDataset(pszOpenInfoFilename, pszProductTitle,
pszDiscId, pszSize);
nSubDatasets++;
}
}
}
}
if (!bGlobalExtentValid)
{
delete poDS;
return nullptr;
}
if (nSubDatasets == 1)
{
const char *pszSubDatasetName = CSLFetchNameValue(
poDS->GetMetadata("SUBDATASETS"), "SUBDATASET_1_NAME");
GDALOpenInfo oOpenInfo(pszSubDatasetName, GA_ReadOnly);
delete poDS;
GDALDataset *poRetDS = Open(&oOpenInfo);
if (poRetDS)
poRetDS->SetDescription(pszOpenInfoFilename);
return poRetDS;
}
poDS->adfGeoTransform[0] = dfGlobalMinX;
poDS->adfGeoTransform[1] = dfGlobalPixelXSize;
poDS->adfGeoTransform[2] = 0.0;
poDS->adfGeoTransform[3] = dfGlobalMaxY;
poDS->adfGeoTransform[4] = 0.0;
poDS->adfGeoTransform[5] = -dfGlobalPixelYSize;
poDS->nRasterXSize = static_cast<int>(0.5 + (dfGlobalMaxX - dfGlobalMinX) /
dfGlobalPixelXSize);
poDS->nRasterYSize = static_cast<int>(0.5 + (dfGlobalMaxY - dfGlobalMinY) /
dfGlobalPixelYSize);
/* -------------------------------------------------------------------- */
/* Initialize any PAM information. */
/* -------------------------------------------------------------------- */
poDS->TryLoadXML();
return poDS;
}
/************************************************************************/
/* Identify() */
/************************************************************************/
int ECRGTOCDataset::Identify(GDALOpenInfo *poOpenInfo)
{
const char *pszFilename = poOpenInfo->pszFilename;
/* -------------------------------------------------------------------- */
/* Is this a sub-dataset selector? If so, it is obviously ECRGTOC. */
/* -------------------------------------------------------------------- */
if (STARTS_WITH_CI(pszFilename, "ECRG_TOC_ENTRY:"))
return TRUE;
/* -------------------------------------------------------------------- */
/* First we check to see if the file has the expected header */
/* bytes. */
/* -------------------------------------------------------------------- */
const char *pabyHeader =
reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
if (pabyHeader == nullptr)
return FALSE;
if (strstr(pabyHeader, "<Table_of_Contents") != nullptr &&
strstr(pabyHeader, "<file_header ") != nullptr)
return TRUE;
if (strstr(pabyHeader, "<!DOCTYPE Table_of_Contents [") != nullptr)
return TRUE;
return FALSE;
}
/************************************************************************/
/* Open() */
/************************************************************************/
GDALDataset *ECRGTOCDataset::Open(GDALOpenInfo *poOpenInfo)
{
if (!Identify(poOpenInfo))
return nullptr;
const char *pszFilename = poOpenInfo->pszFilename;
CPLString osFilename;
CPLString osProduct, osDiscId, osScale;
if (STARTS_WITH_CI(pszFilename, "ECRG_TOC_ENTRY:"))
{
pszFilename += strlen("ECRG_TOC_ENTRY:");
/* PRODUCT:DISK:SCALE:FILENAME (or PRODUCT:DISK:FILENAME historically)
*/
/* with FILENAME potentially C:\BLA... */
char **papszTokens = CSLTokenizeString2(pszFilename, ":", 0);
int nTokens = CSLCount(papszTokens);
if (nTokens != 3 && nTokens != 4 && nTokens != 5)
{
CSLDestroy(papszTokens);
return nullptr;
}
osProduct = papszTokens[0];
osDiscId = papszTokens[1];
if (nTokens == 3)
osFilename = papszTokens[2];
else if (nTokens == 4)
{
if (strlen(papszTokens[2]) == 1 &&
(papszTokens[3][0] == '\\' || papszTokens[3][0] == '/'))
{
osFilename = papszTokens[2];
osFilename += ":";
osFilename += papszTokens[3];
}
else
{
osScale = papszTokens[2];
osFilename = papszTokens[3];
}
}
else if (nTokens == 5 && strlen(papszTokens[3]) == 1 &&
(papszTokens[4][0] == '\\' || papszTokens[4][0] == '/'))
{
osScale = papszTokens[2];
osFilename = papszTokens[3];
osFilename += ":";
osFilename += papszTokens[4];
}
else
{
CSLDestroy(papszTokens);
return nullptr;
}
CSLDestroy(papszTokens);
pszFilename = osFilename.c_str();
}
/* -------------------------------------------------------------------- */
/* Parse the XML file */
/* -------------------------------------------------------------------- */
CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
if (psXML == nullptr)
{
return nullptr;
}
GDALDataset *poDS = Build(pszFilename, psXML, osProduct, osDiscId, osScale,
poOpenInfo->pszFilename);
CPLDestroyXMLNode(psXML);
if (poDS && poOpenInfo->eAccess == GA_Update)
{
CPLError(CE_Failure, CPLE_NotSupported,
"ECRGTOC driver does not support update mode");
delete poDS;
return nullptr;
}
return poDS;
}
/************************************************************************/
/* GDALRegister_ECRGTOC() */
/************************************************************************/
void GDALRegister_ECRGTOC()
{
if (GDALGetDriverByName("ECRGTOC") != nullptr)
return;
GDALDriver *poDriver = new GDALDriver();
poDriver->SetDescription("ECRGTOC");
poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "ECRG TOC format");
poDriver->pfnIdentify = ECRGTOCDataset::Identify;
poDriver->pfnOpen = ECRGTOCDataset::Open;
poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
"drivers/raster/ecrgtoc.html");
poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "xml");
poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
GetGDALDriverManager()->RegisterDriver(poDriver);
}