1120 строки
42 KiB
C++
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);
|
|
}
|