Skip to content

Commit ccbfa7b

Browse files
authored
Merge pull request #13418 from rouault/fix_13416
GTI: accept tiles with south-up orientation (auto-warp them to north-up)
2 parents 04d53c3 + 15d4599 commit ccbfa7b

File tree

3 files changed

+98
-33
lines changed

3 files changed

+98
-33
lines changed

autotest/gdrivers/gti.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ def test_gti_prototype_tile_wrong_gt_5th_value(tmp_vsimem):
329329
gdal.Open(index_filename)
330330

331331

332-
def test_gti_prototype_tile_wrong_gt_6th_value(tmp_vsimem):
332+
def test_gti_prototype_tile_wrong_gt_2nd_value(tmp_vsimem):
333333

334334
index_filename = str(tmp_vsimem / "index.gti.gpkg")
335335
protods_filename = str(tmp_vsimem / "protods_filename.tif")
@@ -346,7 +346,7 @@ def test_gti_prototype_tile_wrong_gt_6th_value(tmp_vsimem):
346346
lyr.CreateFeature(f)
347347
del index_ds
348348

349-
with pytest.raises(Exception, match="6th value of GeoTransform"):
349+
with pytest.raises(Exception, match="2nd value of GeoTransform"):
350350
gdal.Open(index_filename)
351351

352352

@@ -369,6 +369,28 @@ def test_gti_no_extent(tmp_vsimem):
369369
gdal.Open(index_filename)
370370

371371

372+
def test_gti_south_up(tmp_vsimem):
373+
374+
index_filename = str(tmp_vsimem / "index.gti.gpkg")
375+
protods_filename = str(tmp_vsimem / "protods_filename.tif")
376+
with gdal.GetDriverByName("GTiff").Create(protods_filename, 1, 2) as ds:
377+
ds.SetGeoTransform([0, 1, 0, 10, 0, 1])
378+
ds.WriteRaster(0, 0, 1, 2, b"\x01\x02")
379+
380+
index_ds = ogr.GetDriverByName("GPKG").CreateDataSource(index_filename)
381+
lyr = index_ds.CreateLayer("index")
382+
lyr.CreateField(ogr.FieldDefn("location"))
383+
f = ogr.Feature(lyr.GetLayerDefn())
384+
f["location"] = protods_filename
385+
f.SetGeometry(ogr.CreateGeometryFromWkt("POLYGON((0 10,0 12,1 12,1 12,0 12))"))
386+
lyr.CreateFeature(f)
387+
del index_ds
388+
389+
ds = gdal.Open(index_filename)
390+
assert ds.GetGeoTransform() == (0, 1, 0, 12, 0, -1)
391+
assert ds.ReadRaster() == b"\x02\x01"
392+
393+
372394
def test_gti_too_big_x(tmp_vsimem):
373395

374396
index_filename = str(tmp_vsimem / "index.gti.gpkg")

frmts/gti/gdaltileindexdataset.cpp

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1553,16 +1553,23 @@ bool GDALTileIndexDataset::Open(GDALOpenInfo *poOpenInfo)
15531553
"5th value of GeoTransform of %s must be 0", pszTileName);
15541554
return false;
15551555
}
1556-
if (!(gtTile[GT_NS_RES] < 0))
1556+
1557+
const double dfResX = gtTile[GT_WE_RES];
1558+
const double dfResY = gtTile[GT_NS_RES];
1559+
if (!(dfResX > 0))
15571560
{
15581561
CPLError(CE_Failure, CPLE_AppDefined,
1559-
"6th value of GeoTransform of %s must be < 0",
1562+
"2nd value of GeoTransform of %s must be > 0",
1563+
pszTileName);
1564+
return false;
1565+
}
1566+
if (!(dfResY != 0))
1567+
{
1568+
CPLError(CE_Failure, CPLE_AppDefined,
1569+
"6th value of GeoTransform of %s must be != 0",
15601570
pszTileName);
15611571
return false;
15621572
}
1563-
1564-
const double dfResX = gtTile[GT_WE_RES];
1565-
const double dfResY = -gtTile[GT_NS_RES];
15661573

15671574
if (!sEnvelope.IsInit() &&
15681575
m_poLayer->GetExtent(&sEnvelope, /* bForce = */ false) ==
@@ -1587,7 +1594,8 @@ bool GDALTileIndexDataset::Open(GDALOpenInfo *poOpenInfo)
15871594
return false;
15881595
}
15891596

1590-
const double dfYSize = (sEnvelope.MaxY - sEnvelope.MinY) / dfResY;
1597+
const double dfYSize =
1598+
(sEnvelope.MaxY - sEnvelope.MinY) / std::fabs(dfResY);
15911599
if (!(dfYSize >= 0 && dfYSize < INT_MAX))
15921600
{
15931601
CPLError(CE_Failure, CPLE_AppDefined,
@@ -1600,7 +1608,8 @@ bool GDALTileIndexDataset::Open(GDALOpenInfo *poOpenInfo)
16001608
m_gt[GT_ROTATION_PARAM1] = 0;
16011609
m_gt[GT_TOPLEFT_Y] = sEnvelope.MaxY;
16021610
m_gt[GT_ROTATION_PARAM2] = 0;
1603-
m_gt[GT_NS_RES] = -dfResY;
1611+
m_gt[GT_NS_RES] = -std::fabs(dfResY);
1612+
16041613
nRasterXSize = static_cast<int>(std::ceil(dfXSize));
16051614
nRasterYSize = static_cast<int>(std::ceil(dfYSize));
16061615
}
@@ -1637,6 +1646,12 @@ bool GDALTileIndexDataset::Open(GDALOpenInfo *poOpenInfo)
16371646
{
16381647
m_gt[i] = CPLAtof(aosTokens[i]);
16391648
}
1649+
if (!(m_gt[GT_WE_RES] > 0))
1650+
{
1651+
CPLError(CE_Failure, CPLE_AppDefined, "2nd value of %s must be > 0",
1652+
MD_GEOTRANSFORM);
1653+
return false;
1654+
}
16401655
if (!(m_gt[GT_ROTATION_PARAM1] == 0))
16411656
{
16421657
CPLError(CE_Failure, CPLE_AppDefined, "3rd value of %s must be 0",
@@ -1655,7 +1670,6 @@ bool GDALTileIndexDataset::Open(GDALOpenInfo *poOpenInfo)
16551670
MD_GEOTRANSFORM);
16561671
return false;
16571672
}
1658-
16591673
nRasterXSize = nXSize;
16601674
nRasterYSize = nYSize;
16611675
}
@@ -3492,16 +3506,37 @@ bool GDALTileIndexDataset::GetSourceDesc(const std::string &osTileName,
34923506
if (!GTIDoPaletteExpansionIfNeeded(poTileDS, nBands))
34933507
return false;
34943508

3495-
const OGRSpatialReference *poTileSRS;
3496-
if (!m_oSRS.IsEmpty() &&
3497-
(poTileSRS = poTileDS->GetSpatialRef()) != nullptr &&
3509+
bool bWarpVRT = false;
3510+
bool bExportSRS = false;
3511+
bool bAddAlphaToVRT = false;
3512+
const OGRSpatialReference *poTileSRS = poTileDS->GetSpatialRef();
3513+
GDALGeoTransform tileGT;
3514+
if (!m_oSRS.IsEmpty() && poTileSRS != nullptr &&
34983515
!m_oSRS.IsSame(poTileSRS))
34993516
{
35003517
CPLDebug("VRT",
35013518
"Tile %s has not the same SRS as the VRT. "
35023519
"Proceed to on-the-fly warping",
35033520
osTileName.c_str());
3521+
bWarpVRT = true;
3522+
bExportSRS = true;
3523+
bAddAlphaToVRT = true;
3524+
}
3525+
else if (poTileDS->GetGeoTransform(tileGT) == CE_None &&
3526+
tileGT[GT_NS_RES] > 0 &&
3527+
((m_oSRS.IsEmpty() && poTileSRS == nullptr) ||
3528+
(!m_oSRS.IsEmpty() && poTileSRS && m_oSRS.IsSame(poTileSRS))))
35043529

3530+
{
3531+
CPLDebug("VRT",
3532+
"Tile %s is south-up oriented. "
3533+
"Proceed to on-the-fly warping",
3534+
osTileName.c_str());
3535+
bWarpVRT = true;
3536+
}
3537+
3538+
if (bWarpVRT)
3539+
{
35053540
CPLStringList aosOptions;
35063541
aosOptions.AddString("-of");
35073542
aosOptions.AddString("VRT");
@@ -3514,25 +3549,29 @@ bool GDALTileIndexDataset::GetSourceDesc(const std::string &osTileName,
35143549
aosOptions.AddString(m_osResampling.c_str());
35153550
}
35163551

3517-
if (m_osWKT.empty())
3552+
if (bExportSRS)
35183553
{
3519-
char *pszWKT = nullptr;
3520-
const char *const apszWKTOptions[] = {"FORMAT=WKT2_2019",
3521-
nullptr};
3522-
m_oSRS.exportToWkt(&pszWKT, apszWKTOptions);
3523-
if (pszWKT)
3524-
m_osWKT = pszWKT;
3525-
CPLFree(pszWKT);
3526-
}
3527-
if (m_osWKT.empty())
3528-
{
3529-
CPLError(CE_Failure, CPLE_AppDefined,
3530-
"Cannot export VRT SRS to WKT2");
3531-
return false;
3532-
}
3554+
if (m_osWKT.empty())
3555+
{
3556+
char *pszWKT = nullptr;
3557+
const char *const apszWKTOptions[] = {"FORMAT=WKT2_2019",
3558+
nullptr};
3559+
m_oSRS.exportToWkt(&pszWKT, apszWKTOptions);
3560+
if (pszWKT)
3561+
m_osWKT = pszWKT;
3562+
CPLFree(pszWKT);
3563+
3564+
if (m_osWKT.empty())
3565+
{
3566+
CPLError(CE_Failure, CPLE_AppDefined,
3567+
"Cannot export VRT SRS to WKT2");
3568+
return false;
3569+
}
3570+
}
35333571

3534-
aosOptions.AddString("-t_srs");
3535-
aosOptions.AddString(m_osWKT.c_str());
3572+
aosOptions.AddString("-t_srs");
3573+
aosOptions.AddString(m_osWKT.c_str());
3574+
}
35363575

35373576
// First pass to get the extent of the tile in the
35383577
// target VRT SRS
@@ -3592,7 +3631,8 @@ bool GDALTileIndexDataset::GetSourceDesc(const std::string &osTileName,
35923631
aosOptions.AddString(CPLSPrintf("%.17g", dfVRTResX));
35933632
aosOptions.AddString(CPLSPrintf("%.17g", dfVRTResYAbs));
35943633

3595-
aosOptions.AddString("-dstalpha");
3634+
if (bAddAlphaToVRT)
3635+
aosOptions.AddString("-dstalpha");
35963636

35973637
psWarpOptions = GDALWarpAppOptionsNew(aosOptions.List(), nullptr);
35983638
poWarpDS.reset(GDALDataset::FromHandle(GDALWarp(

gcore/gdalproxypool.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,8 +1110,11 @@ const OGRSpatialReference *GDALProxyPoolDataset::GetGCPSpatialRef() const
11101110
if (poUnderlyingDataset == nullptr)
11111111
return nullptr;
11121112

1113-
m_poGCPSRS->Release();
1114-
m_poGCPSRS = nullptr;
1113+
if (m_poGCPSRS)
1114+
{
1115+
m_poGCPSRS->Release();
1116+
m_poGCPSRS = nullptr;
1117+
}
11151118

11161119
const auto poUnderlyingGCPSRS = poUnderlyingDataset->GetGCPSpatialRef();
11171120
if (poUnderlyingGCPSRS)

0 commit comments

Comments
 (0)