Coverage Report

Created: 2025-06-13 06:29

/src/gdal/alg/gdalcutline.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  High Performance Image Reprojector
4
 * Purpose:  Implement cutline/blend mask generator.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2008, Frank Warmerdam <warmerdam@pobox.com>
9
 * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_port.h"
15
#include "gdalwarper.h"
16
17
#include <cmath>
18
#include <cstdio>
19
#include <cstring>
20
#include <algorithm>
21
22
#include "cpl_conv.h"
23
#include "cpl_error.h"
24
#include "cpl_string.h"
25
#include "gdal.h"
26
#include "gdal_alg.h"
27
#include "gdal_priv.h"
28
#include "memdataset.h"
29
#include "ogr_api.h"
30
#include "ogr_core.h"
31
#include "ogr_geometry.h"
32
#include "ogr_geos.h"
33
34
/************************************************************************/
35
/*                         BlendMaskGenerator()                         */
36
/************************************************************************/
37
38
#ifndef HAVE_GEOS
39
40
static CPLErr BlendMaskGenerator(int /* nXOff */, int /* nYOff */,
41
                                 int /* nXSize */, int /* nYSize */,
42
                                 GByte * /* pabyPolyMask */,
43
                                 float * /* pafValidityMask */,
44
                                 OGRGeometryH /* hPolygon */,
45
                                 double /* dfBlendDist */)
46
0
{
47
0
    CPLError(CE_Failure, CPLE_AppDefined,
48
0
             "Blend distance support not available without the GEOS library.");
49
0
    return CE_Failure;
50
0
}
51
#else
52
static CPLErr BlendMaskGenerator(int nXOff, int nYOff, int nXSize, int nYSize,
53
                                 GByte *pabyPolyMask, float *pafValidityMask,
54
                                 OGRGeometryH hPolygon, double dfBlendDist)
55
{
56
57
    /* -------------------------------------------------------------------- */
58
    /*      Convert the polygon into a collection of lines so that we       */
59
    /*      measure distance from the edge even on the inside.              */
60
    /* -------------------------------------------------------------------- */
61
    const auto poPolygon = OGRGeometry::FromHandle(hPolygon);
62
    auto poLines = std::unique_ptr<OGRGeometry>(
63
        OGRGeometryFactory::forceToMultiLineString(poPolygon->clone()));
64
65
    /* -------------------------------------------------------------------- */
66
    /*      Prepare a clipping polygon a bit bigger than the area of        */
67
    /*      interest in the hopes of simplifying the cutline down to        */
68
    /*      stuff that will be relevant for this area of interest.          */
69
    /* -------------------------------------------------------------------- */
70
    CPLString osClipRectWKT;
71
72
    osClipRectWKT.Printf(
73
        "POLYGON((%g %g,%g %g,%g %g,%g %g,%g %g))", nXOff - (dfBlendDist + 1),
74
        nYOff - (dfBlendDist + 1), nXOff + nXSize + (dfBlendDist + 1),
75
        nYOff - (dfBlendDist + 1), nXOff + nXSize + (dfBlendDist + 1),
76
        nYOff + nYSize + (dfBlendDist + 1), nXOff - (dfBlendDist + 1),
77
        nYOff + nYSize + (dfBlendDist + 1), nXOff - (dfBlendDist + 1),
78
        nYOff - (dfBlendDist + 1));
79
80
    OGRPolygon oClipRect;
81
    {
82
        const char *pszClipRectWKT = osClipRectWKT.c_str();
83
        oClipRect.importFromWkt(&pszClipRectWKT);
84
    }
85
86
    // If it does not intersect the polym, zero the mask and return.
87
    if (!poPolygon->Intersects(&oClipRect))
88
    {
89
        memset(pafValidityMask, 0, sizeof(float) * nXSize * nYSize);
90
91
        return CE_None;
92
    }
93
94
    // If it does not intersect the line at all, just return.
95
    else if (!poLines->Intersects(&oClipRect))
96
    {
97
98
        return CE_None;
99
    }
100
101
    poLines.reset(poLines->Intersection(&oClipRect));
102
103
    /* -------------------------------------------------------------------- */
104
    /*      Convert our polygon into GEOS format, and compute an            */
105
    /*      envelope to accelerate later distance operations.               */
106
    /* -------------------------------------------------------------------- */
107
    OGREnvelope sEnvelope;
108
    GEOSContextHandle_t hGEOSCtxt = OGRGeometry::createGEOSContext();
109
    GEOSGeom poGEOSPoly = poLines->exportToGEOS(hGEOSCtxt);
110
    OGR_G_GetEnvelope(hPolygon, &sEnvelope);
111
112
    // This check was already done in the calling
113
    // function and should never be true.
114
115
    // if( sEnvelope.MinY - dfBlendDist > nYOff+nYSize
116
    //     || sEnvelope.MaxY + dfBlendDist < nYOff
117
    //     || sEnvelope.MinX - dfBlendDist > nXOff+nXSize
118
    //     || sEnvelope.MaxX + dfBlendDist < nXOff )
119
    //     return CE_None;
120
121
    const int iXMin = std::max(
122
        0, static_cast<int>(floor(sEnvelope.MinX - dfBlendDist - nXOff)));
123
    const int iXMax = std::min(
124
        nXSize, static_cast<int>(ceil(sEnvelope.MaxX + dfBlendDist - nXOff)));
125
    const int iYMin = std::max(
126
        0, static_cast<int>(floor(sEnvelope.MinY - dfBlendDist - nYOff)));
127
    const int iYMax = std::min(
128
        nYSize, static_cast<int>(ceil(sEnvelope.MaxY + dfBlendDist - nYOff)));
129
130
    /* -------------------------------------------------------------------- */
131
    /*      Loop over potential area within blend line distance,            */
132
    /*      processing each pixel.                                          */
133
    /* -------------------------------------------------------------------- */
134
    for (int iY = 0; iY < nYSize; iY++)
135
    {
136
        double dfLastDist = 0.0;
137
138
        for (int iX = 0; iX < nXSize; iX++)
139
        {
140
            if (iX < iXMin || iX >= iXMax || iY < iYMin || iY > iYMax ||
141
                dfLastDist > dfBlendDist + 1.5)
142
            {
143
                if (pabyPolyMask[iX + iY * nXSize] == 0)
144
                    pafValidityMask[iX + iY * nXSize] = 0.0;
145
146
                dfLastDist -= 1.0;
147
                continue;
148
            }
149
150
            CPLString osPointWKT;
151
            osPointWKT.Printf("POINT(%d.5 %d.5)", iX + nXOff, iY + nYOff);
152
153
            GEOSGeom poGEOSPoint = GEOSGeomFromWKT_r(hGEOSCtxt, osPointWKT);
154
155
            double dfDist = 0.0;
156
            GEOSDistance_r(hGEOSCtxt, poGEOSPoly, poGEOSPoint, &dfDist);
157
            GEOSGeom_destroy_r(hGEOSCtxt, poGEOSPoint);
158
159
            dfLastDist = dfDist;
160
161
            if (dfDist > dfBlendDist)
162
            {
163
                if (pabyPolyMask[iX + iY * nXSize] == 0)
164
                    pafValidityMask[iX + iY * nXSize] = 0.0;
165
166
                continue;
167
            }
168
169
            const double dfRatio =
170
                pabyPolyMask[iX + iY * nXSize] == 0
171
                    ? 0.5 - (dfDist / dfBlendDist) * 0.5   // Outside.
172
                    : 0.5 + (dfDist / dfBlendDist) * 0.5;  // Inside.
173
174
            pafValidityMask[iX + iY * nXSize] *= static_cast<float>(dfRatio);
175
        }
176
    }
177
178
    /* -------------------------------------------------------------------- */
179
    /*      Cleanup                                                         */
180
    /* -------------------------------------------------------------------- */
181
    GEOSGeom_destroy_r(hGEOSCtxt, poGEOSPoly);
182
    OGRGeometry::freeGEOSContext(hGEOSCtxt);
183
184
    return CE_None;
185
}
186
#endif  // HAVE_GEOS
187
188
/************************************************************************/
189
/*                         CutlineTransformer()                         */
190
/*                                                                      */
191
/*      A simple transformer for the cutline that just offsets          */
192
/*      relative to the current chunk.                                  */
193
/************************************************************************/
194
195
static int CutlineTransformer(void *pTransformArg, int bDstToSrc,
196
                              int nPointCount, double *x, double *y,
197
                              double * /* z */, int * /* panSuccess */)
198
0
{
199
0
    int nXOff = static_cast<int *>(pTransformArg)[0];
200
0
    int nYOff = static_cast<int *>(pTransformArg)[1];
201
202
0
    if (bDstToSrc)
203
0
    {
204
0
        nXOff *= -1;
205
0
        nYOff *= -1;
206
0
    }
207
208
0
    for (int i = 0; i < nPointCount; i++)
209
0
    {
210
0
        x[i] -= nXOff;
211
0
        y[i] -= nYOff;
212
0
    }
213
214
0
    return TRUE;
215
0
}
216
217
/************************************************************************/
218
/*                       GDALWarpCutlineMasker()                        */
219
/*                                                                      */
220
/*      This function will generate a source mask based on a            */
221
/*      provided cutline, and optional blend distance.                  */
222
/************************************************************************/
223
224
CPLErr GDALWarpCutlineMasker(void *pMaskFuncArg, int nBandCount,
225
                             GDALDataType eType, int nXOff, int nYOff,
226
                             int nXSize, int nYSize, GByte **ppImageData,
227
                             int bMaskIsFloat, void *pValidityMask)
228
229
0
{
230
0
    return GDALWarpCutlineMaskerEx(pMaskFuncArg, nBandCount, eType, nXOff,
231
0
                                   nYOff, nXSize, nYSize, ppImageData,
232
0
                                   bMaskIsFloat, pValidityMask, nullptr);
233
0
}
234
235
CPLErr GDALWarpCutlineMaskerEx(void *pMaskFuncArg, int /* nBandCount */,
236
                               GDALDataType /* eType */, int nXOff, int nYOff,
237
                               int nXSize, int nYSize,
238
                               GByte ** /*ppImageData */, int bMaskIsFloat,
239
                               void *pValidityMask, int *pnValidityFlag)
240
241
0
{
242
0
    if (pnValidityFlag)
243
0
        *pnValidityFlag = GCMVF_PARTIAL_INTERSECTION;
244
245
0
    if (nXSize < 1 || nYSize < 1)
246
0
        return CE_None;
247
248
    /* -------------------------------------------------------------------- */
249
    /*      Do some minimal checking.                                       */
250
    /* -------------------------------------------------------------------- */
251
0
    if (!bMaskIsFloat)
252
0
    {
253
0
        CPLAssert(false);
254
0
        return CE_Failure;
255
0
    }
256
257
0
    GDALWarpOptions *psWO = static_cast<GDALWarpOptions *>(pMaskFuncArg);
258
259
0
    if (psWO == nullptr || psWO->hCutline == nullptr)
260
0
    {
261
0
        CPLAssert(false);
262
0
        return CE_Failure;
263
0
    }
264
265
    /* -------------------------------------------------------------------- */
266
    /*      Check the polygon.                                              */
267
    /* -------------------------------------------------------------------- */
268
0
    OGRGeometryH hPolygon = static_cast<OGRGeometryH>(psWO->hCutline);
269
270
0
    if (wkbFlatten(OGR_G_GetGeometryType(hPolygon)) != wkbPolygon &&
271
0
        wkbFlatten(OGR_G_GetGeometryType(hPolygon)) != wkbMultiPolygon)
272
0
    {
273
0
        CPLError(CE_Failure, CPLE_NotSupported,
274
0
                 "Cutline should be a polygon or a multipolygon");
275
0
        return CE_Failure;
276
0
    }
277
278
0
    OGREnvelope sEnvelope;
279
0
    OGR_G_GetEnvelope(hPolygon, &sEnvelope);
280
281
0
    float *pafMask = static_cast<float *>(pValidityMask);
282
283
0
    if (sEnvelope.MaxX + psWO->dfCutlineBlendDist < nXOff ||
284
0
        sEnvelope.MinX - psWO->dfCutlineBlendDist > nXOff + nXSize ||
285
0
        sEnvelope.MaxY + psWO->dfCutlineBlendDist < nYOff ||
286
0
        sEnvelope.MinY - psWO->dfCutlineBlendDist > nYOff + nYSize)
287
0
    {
288
0
        if (pnValidityFlag)
289
0
            *pnValidityFlag = GCMVF_NO_INTERSECTION;
290
291
        // We are far from the blend line - everything is masked to zero.
292
        // It would be nice to realize no work is required for this whole
293
        // chunk!
294
0
        memset(pafMask, 0, sizeof(float) * nXSize * nYSize);
295
0
        return CE_None;
296
0
    }
297
298
    // And now check if the chunk to warp is fully contained within the cutline
299
    // to save rasterization.
300
0
    if (OGRGeometryFactory::haveGEOS()
301
0
#ifdef DEBUG
302
        // Env var just for debugging purposes
303
0
        && !CPLTestBool(
304
0
               CPLGetConfigOption("GDALCUTLINE_SKIP_CONTAINMENT_TEST", "NO"))
305
0
#endif
306
0
    )
307
0
    {
308
0
        OGRLinearRing *poRing = new OGRLinearRing();
309
0
        poRing->addPoint(-psWO->dfCutlineBlendDist + nXOff,
310
0
                         -psWO->dfCutlineBlendDist + nYOff);
311
0
        poRing->addPoint(-psWO->dfCutlineBlendDist + nXOff,
312
0
                         psWO->dfCutlineBlendDist + nYOff + nYSize);
313
0
        poRing->addPoint(psWO->dfCutlineBlendDist + nXOff + nXSize,
314
0
                         psWO->dfCutlineBlendDist + nYOff + nYSize);
315
0
        poRing->addPoint(psWO->dfCutlineBlendDist + nXOff + nXSize,
316
0
                         -psWO->dfCutlineBlendDist + nYOff);
317
0
        poRing->addPoint(-psWO->dfCutlineBlendDist + nXOff,
318
0
                         -psWO->dfCutlineBlendDist + nYOff);
319
0
        OGRPolygon oChunkFootprint;
320
0
        oChunkFootprint.addRingDirectly(poRing);
321
0
        OGREnvelope sChunkEnvelope;
322
0
        oChunkFootprint.getEnvelope(&sChunkEnvelope);
323
0
        if (sEnvelope.Contains(sChunkEnvelope) &&
324
0
            OGRGeometry::FromHandle(hPolygon)->Contains(&oChunkFootprint))
325
0
        {
326
0
            if (pnValidityFlag)
327
0
                *pnValidityFlag = GCMVF_CHUNK_FULLY_WITHIN_CUTLINE;
328
329
0
            CPLDebug("WARP", "Source chunk fully contained within cutline.");
330
0
            return CE_None;
331
0
        }
332
0
    }
333
334
    /* -------------------------------------------------------------------- */
335
    /*      Create a byte buffer into which we can burn the                 */
336
    /*      mask polygon and wrap it up as a memory dataset.                */
337
    /* -------------------------------------------------------------------- */
338
0
    GByte *pabyPolyMask = static_cast<GByte *>(CPLCalloc(nXSize, nYSize));
339
340
0
    auto poMEMDS =
341
0
        MEMDataset::Create("warp_temp", nXSize, nYSize, 0, GDT_Byte, nullptr);
342
0
    GDALRasterBandH hMEMBand =
343
0
        MEMCreateRasterBandEx(poMEMDS, 1, pabyPolyMask, GDT_Byte, 0, 0, false);
344
0
    poMEMDS->AddMEMBand(hMEMBand);
345
346
0
    GDALDatasetH hMemDS = GDALDataset::ToHandle(poMEMDS);
347
0
    double adfGeoTransform[6] = {0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
348
0
    GDALSetGeoTransform(hMemDS, adfGeoTransform);
349
350
    /* -------------------------------------------------------------------- */
351
    /*      Burn the polygon into the mask with 1.0 values.                 */
352
    /* -------------------------------------------------------------------- */
353
0
    int nTargetBand = 1;
354
0
    double dfBurnValue = 255.0;
355
0
    char **papszRasterizeOptions = nullptr;
356
357
0
    if (CPLFetchBool(psWO->papszWarpOptions, "CUTLINE_ALL_TOUCHED", false))
358
0
        papszRasterizeOptions =
359
0
            CSLSetNameValue(papszRasterizeOptions, "ALL_TOUCHED", "TRUE");
360
361
0
    int anXYOff[2] = {nXOff, nYOff};
362
363
0
    CPLErr eErr = GDALRasterizeGeometries(
364
0
        hMemDS, 1, &nTargetBand, 1, &hPolygon, CutlineTransformer, anXYOff,
365
0
        &dfBurnValue, papszRasterizeOptions, nullptr, nullptr);
366
367
0
    CSLDestroy(papszRasterizeOptions);
368
369
    // Close and ensure data flushed to underlying array.
370
0
    GDALClose(hMemDS);
371
372
    /* -------------------------------------------------------------------- */
373
    /*      In the case with no blend distance, we just apply this as a     */
374
    /*      mask, zeroing out everything outside the polygon.               */
375
    /* -------------------------------------------------------------------- */
376
0
    if (psWO->dfCutlineBlendDist == 0.0)
377
0
    {
378
0
        for (int i = nXSize * nYSize - 1; i >= 0; i--)
379
0
        {
380
0
            if (pabyPolyMask[i] == 0)
381
0
                static_cast<float *>(pValidityMask)[i] = 0.0;
382
0
        }
383
0
    }
384
0
    else
385
0
    {
386
0
        eErr = BlendMaskGenerator(nXOff, nYOff, nXSize, nYSize, pabyPolyMask,
387
0
                                  static_cast<float *>(pValidityMask), hPolygon,
388
0
                                  psWO->dfCutlineBlendDist);
389
0
    }
390
391
    /* -------------------------------------------------------------------- */
392
    /*      Clean up.                                                       */
393
    /* -------------------------------------------------------------------- */
394
0
    CPLFree(pabyPolyMask);
395
396
0
    return eErr;
397
0
}