Coverage Report

Created: 2026-09-14 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_raster_shift_longitude.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  gdal "raster shift-longitude" subcommand
5
 * Author:   Dan Baston
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2026, ISciences LLC
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdalalg_raster_shift_longitude.h"
14
15
#include "cpl_conv.h"
16
#include "gdal_priv.h"
17
#include "gdal_priv_templates.hpp"
18
#include "../frmts/vrt/gdal_vrt.h"
19
#include "../frmts/vrt/vrtdataset.h"
20
21
#include <algorithm>
22
23
//! @cond Doxygen_Suppress
24
25
#ifndef _
26
0
#define _(x) (x)
27
#endif
28
29
/************************************************************************/
30
/*GDALRasterShiftLongitudeAlgorithm::GDALRasterShiftLongitudeAlgorithm()*/
31
/************************************************************************/
32
33
GDALRasterShiftLongitudeAlgorithm::GDALRasterShiftLongitudeAlgorithm(
34
    bool bStandalone)
35
0
    : GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL, bStandalone)
36
0
{
37
0
    AddArg("min-x", 0, _("Sets the minimum longitude value"), &m_minX)
38
0
        .SetRequired();
39
0
    AddArg("max-x", 0, _("Sets the maximum longitude value"), &m_maxX)
40
0
        .SetRequired();
41
0
    AddNodataArg(&m_nodata, /* noneAllowed = */ true, "output-nodata");
42
0
}
43
44
/************************************************************************/
45
/*             GDALRasterShiftLongitudeAlgorithm::RunStep()             */
46
/************************************************************************/
47
48
static double NormalizeLongitude(double lon)
49
0
{
50
0
    while (lon < -180.0)
51
0
        lon += 360.0;
52
0
    while (lon > 180.0)
53
0
        lon -= 360.0;
54
0
    return lon;
55
0
}
56
57
bool GDALRasterShiftLongitudeAlgorithm::RunStep(GDALPipelineStepRunContext &)
58
0
{
59
0
    CPLAssert(!m_outputDataset.GetDatasetRef());
60
61
0
    const auto poSrcDS = m_inputDataset[0].GetDatasetRef();
62
63
0
    GDALGeoTransform srcGT;
64
0
    if (poSrcDS->GetGeoTransform(srcGT) != CE_None)
65
0
    {
66
0
        ReportError(CE_Failure, CPLE_AppDefined,
67
0
                    "Input dataset does not have a geotransform");
68
0
        return false;
69
0
    }
70
71
0
    if (!srcGT.IsAxisAligned())
72
0
    {
73
0
        ReportError(CE_Failure, CPLE_AppDefined,
74
0
                    "Input dataset geotransform cannot have a rotation term");
75
0
        return false;
76
0
    }
77
78
0
    const auto nSrcXSize = poSrcDS->GetRasterXSize();
79
80
0
    const double dfSrcMinX = srcGT.xorig;
81
0
    const double dfSrcMaxX = srcGT.xorig + srcGT.xscale * nSrcXSize;
82
83
    // Snap m_minX and m_maxX to pixel boundaries of the input raster
84
0
    {
85
0
        const double dfXOff =
86
0
            std::ceil(std::abs(dfSrcMinX - m_minX) / srcGT.xscale) *
87
0
            (m_minX < dfSrcMinX ? -1 : 1);
88
0
        if (!GDALIsValueInRange<int>(dfXOff))
89
0
        {
90
0
            CPLError(CE_Failure, CPLE_AppDefined,
91
0
                     "Failed to compute pixel offset between minimum x "
92
0
                     "coordinate of input and output.");
93
0
            return false;
94
0
        }
95
0
        const int nXOff = static_cast<int>(dfXOff);
96
0
        m_minX = dfSrcMinX + nXOff * srcGT.xscale;
97
0
    }
98
0
    const double dfDstXSize = std::ceil((m_maxX - m_minX) / srcGT.xscale);
99
0
    if (!GDALIsValueInRange<int>(dfDstXSize))
100
0
    {
101
0
        CPLError(CE_Failure, CPLE_AppDefined,
102
0
                 "Size of output raster exceeds maximum allowable size.");
103
0
        return false;
104
0
    }
105
0
    const int nDstXSize = static_cast<int>(dfDstXSize);
106
107
0
    if (nDstXSize <= 0)
108
0
    {
109
0
        ReportError(CE_Failure, CPLE_AppDefined,
110
0
                    "--max-x must be greater than --min-x");
111
0
        return false;
112
0
    }
113
114
0
    m_maxX = m_minX + nDstXSize * srcGT.xscale;
115
0
    const int nYSize = poSrcDS->GetRasterYSize();
116
117
0
    auto poDstDS = std::make_unique<VRTDataset>(nDstXSize, nYSize);
118
0
    {
119
0
        GDALGeoTransform dstGT = srcGT;
120
0
        dstGT.xorig = m_minX;
121
0
        poDstDS->SetGeoTransform(dstGT);
122
0
    }
123
124
0
    std::vector<GDALRasterWindow> aosSrcWindows;
125
0
    for (int nDstXOff = 0; nDstXOff < nDstXSize;)
126
0
    {
127
0
        double dfDstChunkMinX = m_minX + nDstXOff * srcGT.xscale;
128
129
0
        while (dfDstChunkMinX < dfSrcMinX)
130
0
        {
131
0
            dfDstChunkMinX += 360;
132
0
        }
133
0
        while (dfDstChunkMinX >= dfSrcMaxX)
134
0
        {
135
0
            dfDstChunkMinX -= 360;
136
0
        }
137
138
0
        const double dfSrcXOff =
139
0
            std::round((dfDstChunkMinX - dfSrcMinX) / srcGT.xscale);
140
0
        if (!GDALIsValueInRange<int>(dfSrcXOff))
141
0
        {
142
0
            CPLError(CE_Failure, CPLE_AppDefined,
143
0
                     "Failed to calculate source pixels for longitude range "
144
0
                     "beginning at %g",
145
0
                     dfDstChunkMinX);
146
0
            return false;
147
0
        }
148
0
        const int nSrcXOff = static_cast<int>(dfSrcXOff);
149
150
0
        if (nSrcXOff < 0)
151
0
        {
152
0
            const double dfMissingRangeMinX =
153
0
                NormalizeLongitude(dfDstChunkMinX);
154
0
            const int nMissingPx =
155
0
                std::min(std::abs(nSrcXOff), nDstXSize - nDstXOff);
156
0
            double dfMissingRangeMaxX =
157
0
                NormalizeLongitude(dfDstChunkMinX + srcGT.xscale * nMissingPx);
158
0
            while (dfMissingRangeMaxX < dfMissingRangeMinX)
159
0
            {
160
0
                dfMissingRangeMaxX += 360;
161
0
            }
162
163
0
            CPLError(
164
0
                CE_Warning, CPLE_AppDefined,
165
0
                "No source data available for output longitude range %g to %g",
166
0
                dfMissingRangeMinX, dfMissingRangeMaxX);
167
0
            nDstXOff += nMissingPx;
168
169
0
            const GDALRasterWindow chunk{-1, -1, nMissingPx, nYSize};
170
0
            aosSrcWindows.push_back(chunk);
171
0
            continue;
172
0
        }
173
174
0
        const int nColumnsWanted = nSrcXSize - nSrcXOff;
175
0
        const int nColumnsAvailable = nDstXSize - nDstXOff;
176
177
0
        const GDALRasterWindow chunk{
178
0
            nSrcXOff, 0, std::min(nColumnsWanted, nColumnsAvailable), nYSize};
179
180
0
        CPLAssert(chunk.nXSize > 0);
181
182
0
        const double dstChunkMaxX = dfDstChunkMinX + chunk.nXSize;
183
0
        CPLDebug("ShiftLongitude", "Src %g - %g, Dst %g - %g", dfDstChunkMinX,
184
0
                 dstChunkMaxX, m_minX + chunk.nXOff * srcGT.xscale,
185
0
                 m_minX + (chunk.nXOff + chunk.nXSize) * srcGT.xscale);
186
187
0
        aosSrcWindows.push_back(chunk);
188
0
        nDstXOff += chunk.nXSize;
189
0
    }
190
191
0
    for (int iBand = 1; iBand <= poSrcDS->GetRasterCount(); ++iBand)
192
0
    {
193
0
        GDALRasterBand *poSrcBand = poSrcDS->GetRasterBand(iBand);
194
0
        poDstDS->AddBand(poSrcBand->GetRasterDataType());
195
0
        VRTSourcedRasterBand *poDstBand =
196
0
            cpl::down_cast<VRTSourcedRasterBand *>(
197
0
                poDstDS->GetRasterBand(iBand));
198
0
        poDstBand->CopyCommonInfoFrom(poSrcBand);
199
200
0
        bool bHasNoData;
201
0
        double dfSrcNoData{0};
202
203
0
        {
204
0
            int bSuccess;
205
0
            dfSrcNoData = poSrcBand->GetNoDataValue(&bSuccess);
206
0
            if (!bSuccess)
207
0
            {
208
0
                dfSrcNoData = VRT_NODATA_UNSET;
209
0
            }
210
0
        }
211
212
0
        if (!m_nodata.empty())
213
0
        {
214
0
            bool inexact = false;
215
0
            if (poDstBand->SetNoDataValueAsString(m_nodata.c_str(), &inexact) !=
216
0
                CE_None)
217
0
            {
218
0
                CPLError(CE_Failure, CPLE_AppDefined,
219
0
                         "Invalid NoData value: %s", m_nodata.c_str());
220
0
                return false;
221
0
            }
222
0
            if (inexact)
223
0
            {
224
0
                CPLError(CE_Failure, CPLE_AppDefined,
225
0
                         "Specified NoData value cannot be represented in the "
226
0
                         "output data type.");
227
0
                return false;
228
0
            }
229
0
            bHasNoData = true;
230
0
        }
231
0
        else
232
0
        {
233
0
            bHasNoData = GDALCopyNoDataValue(poDstBand, poSrcBand, nullptr);
234
0
        }
235
236
0
        int nDstXOff = 0;
237
0
        for (const auto &chunk : aosSrcWindows)
238
0
        {
239
0
            const bool bDataAvailable = chunk.nXOff >= 0;
240
241
0
            if (bDataAvailable)
242
0
            {
243
0
                CPLDebug("ShiftLongitude", "Adding source chunk %d,%d %d %d",
244
0
                         chunk.nXOff, chunk.nYOff, chunk.nXSize, chunk.nYSize);
245
246
                // Scale and offset are propagated to the output band, not handled by the ComplexSource
247
0
                constexpr double dfSourceScale = 1.0;
248
0
                constexpr double dfSourceOffset = 0.0;
249
0
                constexpr int nSrcYOff = 0;
250
0
                constexpr int nDstYOff = 0;
251
0
                if (poDstBand->AddComplexSource(
252
0
                        poSrcBand, chunk.nXOff, nSrcYOff, chunk.nXSize,
253
0
                        chunk.nYSize, nDstXOff, nDstYOff, chunk.nXSize,
254
0
                        chunk.nYSize, dfSourceOffset, dfSourceScale,
255
0
                        dfSrcNoData) != CE_None)
256
0
                {
257
0
                    return false;
258
0
                }
259
0
            }
260
0
            else if (!bHasNoData)
261
0
            {
262
0
                CPLErrorOnce(
263
0
                    CE_Warning, CPLE_AppDefined,
264
0
                    "Some regions of the output dataset have no source data "
265
0
                    "available, but a NoData value has not been defined. You "
266
0
                    "can set a value using --output-nodata");
267
0
            }
268
0
            nDstXOff += chunk.nXSize;
269
0
        }
270
0
    }
271
272
0
    m_outputDataset.Set(std::move(poDstDS));
273
274
0
    return true;
275
0
}
276
277
GDALRasterShiftLongitudeAlgorithmStandalone::
278
0
    ~GDALRasterShiftLongitudeAlgorithmStandalone() = default;
279
280
//! @endcond