Coverage Report

Created: 2026-02-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/alg/gdalproximity.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  Compute each pixel's proximity to a set of target pixels.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2008, Frank Warmerdam
9
 * Copyright (c) 2009-2010, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_port.h"
15
#include "gdal_alg.h"
16
17
#include <cmath>
18
#include <cstdlib>
19
20
#include <algorithm>
21
22
#include "cpl_conv.h"
23
#include "cpl_error.h"
24
#include "cpl_progress.h"
25
#include "cpl_string.h"
26
#include "cpl_vsi.h"
27
#include "gdal.h"
28
29
static CPLErr ProcessProximityLine(GInt32 *panSrcScanline, int *panNearX,
30
                                   int *panNearY, int bForward, int iLine,
31
                                   int nXSize, double nMaxDist,
32
                                   float *pafProximity,
33
                                   double *pdfSrcNoDataValue, int nTargetValues,
34
                                   int *panTargetValues);
35
36
/************************************************************************/
37
/*                        GDALComputeProximity()                        */
38
/************************************************************************/
39
40
/**
41
Compute the proximity of all pixels in the image to a set of pixels in
42
the source image.
43
44
This function attempts to compute the proximity of all pixels in
45
the image to a set of pixels in the source image.  The following
46
options are used to define the behavior of the function.  By
47
default all non-zero pixels in hSrcBand will be considered the
48
"target", and all proximities will be computed in pixels.  Note
49
that target pixels are set to the value corresponding to a distance
50
of zero.
51
52
The progress function args may be NULL or a valid progress reporting function
53
such as GDALTermProgress/NULL.
54
55
Options:
56
57
  VALUES=n[,n]*
58
59
A list of target pixel values to measure the distance from.  If this
60
option is not provided proximity will be computed from non-zero
61
pixel values.  Currently pixel values are internally processed as
62
integers.
63
64
  DISTUNITS=[PIXEL]/GEO
65
66
Indicates whether distances will be computed in pixel units or
67
in georeferenced units.  The default is pixel units.  This also
68
determines the interpretation of MAXDIST.
69
70
  MAXDIST=n
71
72
The maximum distance to search.  Proximity distances greater than
73
this value will not be computed.  Instead output pixels will be
74
set to a nodata value.
75
76
  NODATA=n
77
78
The NODATA value to use on the output band for pixels that are
79
beyond MAXDIST.  If not provided, the hProximityBand will be
80
queried for a nodata value.  If one is not found, 65535 will be used.
81
82
  USE_INPUT_NODATA=YES/NO
83
84
If this option is set, the input data set no-data value will be
85
respected. Leaving no data pixels in the input as no data pixels in
86
the proximity output.
87
88
  FIXED_BUF_VAL=n
89
90
If this option is set, all pixels within the MAXDIST threshold are
91
set to this fixed value instead of to a proximity distance.
92
*/
93
94
CPLErr CPL_STDCALL GDALComputeProximity(GDALRasterBandH hSrcBand,
95
                                        GDALRasterBandH hProximityBand,
96
                                        char **papszOptions,
97
                                        GDALProgressFunc pfnProgress,
98
                                        void *pProgressArg)
99
100
0
{
101
0
    VALIDATE_POINTER1(hSrcBand, "GDALComputeProximity", CE_Failure);
102
0
    VALIDATE_POINTER1(hProximityBand, "GDALComputeProximity", CE_Failure);
103
104
0
    if (pfnProgress == nullptr)
105
0
        pfnProgress = GDALDummyProgress;
106
107
    /* -------------------------------------------------------------------- */
108
    /*      Are we using pixels or georeferenced coordinates for distances? */
109
    /* -------------------------------------------------------------------- */
110
0
    double dfDistMult = 1.0;
111
0
    const char *pszOpt = CSLFetchNameValue(papszOptions, "DISTUNITS");
112
0
    if (pszOpt)
113
0
    {
114
0
        if (EQUAL(pszOpt, "GEO"))
115
0
        {
116
0
            GDALDatasetH hSrcDS = GDALGetBandDataset(hSrcBand);
117
0
            if (hSrcDS)
118
0
            {
119
0
                double adfGeoTransform[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
120
121
0
                GDALGetGeoTransform(hSrcDS, adfGeoTransform);
122
0
                if (std::abs(adfGeoTransform[1]) !=
123
0
                    std::abs(adfGeoTransform[5]))
124
0
                    CPLError(
125
0
                        CE_Warning, CPLE_AppDefined,
126
0
                        "Pixels not square, distances will be inaccurate.");
127
0
                dfDistMult = std::abs(adfGeoTransform[1]);
128
0
            }
129
0
        }
130
0
        else if (!EQUAL(pszOpt, "PIXEL"))
131
0
        {
132
0
            CPLError(
133
0
                CE_Failure, CPLE_AppDefined,
134
0
                "Unrecognized DISTUNITS value '%s', should be GEO or PIXEL.",
135
0
                pszOpt);
136
0
            return CE_Failure;
137
0
        }
138
0
    }
139
140
    /* -------------------------------------------------------------------- */
141
    /*      What is our maxdist value?                                      */
142
    /* -------------------------------------------------------------------- */
143
0
    pszOpt = CSLFetchNameValue(papszOptions, "MAXDIST");
144
0
    const double dfMaxDist = pszOpt ? CPLAtof(pszOpt) / dfDistMult
145
0
                                    : GDALGetRasterBandXSize(hSrcBand) +
146
0
                                          GDALGetRasterBandYSize(hSrcBand);
147
148
0
    CPLDebug("GDAL", "MAXDIST=%g, DISTMULT=%g", dfMaxDist, dfDistMult);
149
150
    /* -------------------------------------------------------------------- */
151
    /*      Verify the source and destination are compatible.               */
152
    /* -------------------------------------------------------------------- */
153
0
    const int nXSize = GDALGetRasterBandXSize(hSrcBand);
154
0
    const int nYSize = GDALGetRasterBandYSize(hSrcBand);
155
0
    if (nXSize != GDALGetRasterBandXSize(hProximityBand) ||
156
0
        nYSize != GDALGetRasterBandYSize(hProximityBand))
157
0
    {
158
0
        CPLError(CE_Failure, CPLE_AppDefined,
159
0
                 "Source and proximity bands are not the same size.");
160
0
        return CE_Failure;
161
0
    }
162
163
    /* -------------------------------------------------------------------- */
164
    /*      Get input NODATA value.                                         */
165
    /* -------------------------------------------------------------------- */
166
0
    double dfSrcNoDataValue = 0.0;
167
0
    double *pdfSrcNoData = nullptr;
168
0
    if (CPLFetchBool(papszOptions, "USE_INPUT_NODATA", false))
169
0
    {
170
0
        int bSrcHasNoData = 0;
171
0
        dfSrcNoDataValue = GDALGetRasterNoDataValue(hSrcBand, &bSrcHasNoData);
172
0
        if (bSrcHasNoData)
173
0
            pdfSrcNoData = &dfSrcNoDataValue;
174
0
    }
175
176
    /* -------------------------------------------------------------------- */
177
    /*      Get output NODATA value.                                        */
178
    /* -------------------------------------------------------------------- */
179
0
    float fNoDataValue = 0.0f;
180
0
    pszOpt = CSLFetchNameValue(papszOptions, "NODATA");
181
0
    if (pszOpt != nullptr)
182
0
    {
183
0
        fNoDataValue = static_cast<float>(CPLAtof(pszOpt));
184
0
    }
185
0
    else
186
0
    {
187
0
        int bSuccess = FALSE;
188
189
0
        fNoDataValue = static_cast<float>(
190
0
            GDALGetRasterNoDataValue(hProximityBand, &bSuccess));
191
0
        if (!bSuccess)
192
0
            fNoDataValue = 65535.0;
193
0
    }
194
195
    /* -------------------------------------------------------------------- */
196
    /*      Is there a fixed value we wish to force the buffer area to?     */
197
    /* -------------------------------------------------------------------- */
198
0
    double dfFixedBufVal = 0.0;
199
0
    bool bFixedBufVal = false;
200
0
    pszOpt = CSLFetchNameValue(papszOptions, "FIXED_BUF_VAL");
201
0
    if (pszOpt)
202
0
    {
203
0
        dfFixedBufVal = CPLAtof(pszOpt);
204
0
        bFixedBufVal = true;
205
0
    }
206
207
    /* -------------------------------------------------------------------- */
208
    /*      Get the target value(s).                                        */
209
    /* -------------------------------------------------------------------- */
210
0
    int *panTargetValues = nullptr;
211
0
    int nTargetValues = 0;
212
213
0
    pszOpt = CSLFetchNameValue(papszOptions, "VALUES");
214
0
    if (pszOpt != nullptr)
215
0
    {
216
0
        char **papszValuesTokens =
217
0
            CSLTokenizeStringComplex(pszOpt, ",", FALSE, FALSE);
218
219
0
        nTargetValues = CSLCount(papszValuesTokens);
220
0
        panTargetValues =
221
0
            static_cast<int *>(CPLCalloc(sizeof(int), nTargetValues));
222
223
0
        for (int i = 0; i < nTargetValues; i++)
224
0
            panTargetValues[i] = atoi(papszValuesTokens[i]);
225
0
        CSLDestroy(papszValuesTokens);
226
0
    }
227
228
    /* -------------------------------------------------------------------- */
229
    /*      Initialize progress counter.                                    */
230
    /* -------------------------------------------------------------------- */
231
0
    if (!pfnProgress(0.0, "", pProgressArg))
232
0
    {
233
0
        CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated");
234
0
        CPLFree(panTargetValues);
235
0
        return CE_Failure;
236
0
    }
237
238
    /* -------------------------------------------------------------------- */
239
    /*      We need a signed type for the working proximity values kept     */
240
    /*      on disk.  If our proximity band is not signed, then create a    */
241
    /*      temporary file for this purpose.                                */
242
    /* -------------------------------------------------------------------- */
243
0
    GDALRasterBandH hWorkProximityBand = hProximityBand;
244
0
    GDALDatasetH hWorkProximityDS = nullptr;
245
0
    const GDALDataType eProxType = GDALGetRasterDataType(hProximityBand);
246
0
    CPLErr eErr = CE_None;
247
248
    // TODO(schwehr): Localize after removing gotos.
249
0
    float *pafProximity = nullptr;
250
0
    int *panNearX = nullptr;
251
0
    int *panNearY = nullptr;
252
0
    GInt32 *panSrcScanline = nullptr;
253
0
    bool bTempFileAlreadyDeleted = false;
254
255
0
    if (eProxType == GDT_UInt8 || eProxType == GDT_UInt16 ||
256
0
        eProxType == GDT_UInt32)
257
0
    {
258
0
        GDALDriverH hDriver = GDALGetDriverByName("GTiff");
259
0
        if (hDriver == nullptr)
260
0
        {
261
0
            CPLError(CE_Failure, CPLE_AppDefined,
262
0
                     "GDALComputeProximity needs GTiff driver");
263
0
            eErr = CE_Failure;
264
0
            goto end;
265
0
        }
266
0
        CPLString osTmpFile = CPLGenerateTempFilenameSafe("proximity");
267
0
        hWorkProximityDS = GDALCreate(hDriver, osTmpFile, nXSize, nYSize, 1,
268
0
                                      GDT_Float32, nullptr);
269
0
        if (hWorkProximityDS == nullptr)
270
0
        {
271
0
            eErr = CE_Failure;
272
0
            goto end;
273
0
        }
274
        // On Unix, attempt at deleting the temporary file now, so that
275
        // if the process gets interrupted, it is automatically destroyed
276
        // by the operating system.
277
0
        bTempFileAlreadyDeleted = VSIUnlink(osTmpFile) == 0;
278
0
        hWorkProximityBand = GDALGetRasterBand(hWorkProximityDS, 1);
279
0
    }
280
281
    /* -------------------------------------------------------------------- */
282
    /*      Allocate buffer for two scanlines of distances as floats        */
283
    /*      (the current and last line).                                    */
284
    /* -------------------------------------------------------------------- */
285
0
    pafProximity =
286
0
        static_cast<float *>(VSI_MALLOC2_VERBOSE(sizeof(float), nXSize));
287
0
    panNearX = static_cast<int *>(VSI_MALLOC2_VERBOSE(sizeof(int), nXSize));
288
0
    panNearY = static_cast<int *>(VSI_MALLOC2_VERBOSE(sizeof(int), nXSize));
289
0
    panSrcScanline =
290
0
        static_cast<GInt32 *>(VSI_MALLOC2_VERBOSE(sizeof(GInt32), nXSize));
291
292
0
    if (pafProximity == nullptr || panNearX == nullptr || panNearY == nullptr ||
293
0
        panSrcScanline == nullptr)
294
0
    {
295
0
        eErr = CE_Failure;
296
0
        goto end;
297
0
    }
298
299
    /* -------------------------------------------------------------------- */
300
    /*      Loop from top to bottom of the image.                           */
301
    /* -------------------------------------------------------------------- */
302
303
0
    for (int i = 0; i < nXSize; i++)
304
0
    {
305
0
        panNearX[i] = -1;
306
0
        panNearY[i] = -1;
307
0
    }
308
309
0
    for (int iLine = 0; eErr == CE_None && iLine < nYSize; iLine++)
310
0
    {
311
        // Read for target values.
312
0
        eErr = GDALRasterIO(hSrcBand, GF_Read, 0, iLine, nXSize, 1,
313
0
                            panSrcScanline, nXSize, 1, GDT_Int32, 0, 0);
314
0
        if (eErr != CE_None)
315
0
            break;
316
317
0
        for (int i = 0; i < nXSize; i++)
318
0
            pafProximity[i] = -1.0;
319
320
        // Left to right.
321
0
        ProcessProximityLine(panSrcScanline, panNearX, panNearY, TRUE, iLine,
322
0
                             nXSize, dfMaxDist, pafProximity, pdfSrcNoData,
323
0
                             nTargetValues, panTargetValues);
324
325
        // Right to Left.
326
0
        ProcessProximityLine(panSrcScanline, panNearX, panNearY, FALSE, iLine,
327
0
                             nXSize, dfMaxDist, pafProximity, pdfSrcNoData,
328
0
                             nTargetValues, panTargetValues);
329
330
        // Write out results.
331
0
        eErr = GDALRasterIO(hWorkProximityBand, GF_Write, 0, iLine, nXSize, 1,
332
0
                            pafProximity, nXSize, 1, GDT_Float32, 0, 0);
333
334
0
        if (eErr != CE_None)
335
0
            break;
336
337
0
        if (!pfnProgress(0.5 * (iLine + 1) / static_cast<double>(nYSize), "",
338
0
                         pProgressArg))
339
0
        {
340
0
            CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated");
341
0
            eErr = CE_Failure;
342
0
        }
343
0
    }
344
345
    /* -------------------------------------------------------------------- */
346
    /*      Loop from bottom to top of the image.                           */
347
    /* -------------------------------------------------------------------- */
348
0
    for (int i = 0; i < nXSize; i++)
349
0
    {
350
0
        panNearX[i] = -1;
351
0
        panNearY[i] = -1;
352
0
    }
353
354
0
    for (int iLine = nYSize - 1; eErr == CE_None && iLine >= 0; iLine--)
355
0
    {
356
        // Read first pass proximity.
357
0
        eErr = GDALRasterIO(hWorkProximityBand, GF_Read, 0, iLine, nXSize, 1,
358
0
                            pafProximity, nXSize, 1, GDT_Float32, 0, 0);
359
360
0
        if (eErr != CE_None)
361
0
            break;
362
363
        // Read pixel values.
364
365
0
        eErr = GDALRasterIO(hSrcBand, GF_Read, 0, iLine, nXSize, 1,
366
0
                            panSrcScanline, nXSize, 1, GDT_Int32, 0, 0);
367
0
        if (eErr != CE_None)
368
0
            break;
369
370
        // Right to left.
371
0
        ProcessProximityLine(panSrcScanline, panNearX, panNearY, FALSE, iLine,
372
0
                             nXSize, dfMaxDist, pafProximity, pdfSrcNoData,
373
0
                             nTargetValues, panTargetValues);
374
375
        // Left to right.
376
0
        ProcessProximityLine(panSrcScanline, panNearX, panNearY, TRUE, iLine,
377
0
                             nXSize, dfMaxDist, pafProximity, pdfSrcNoData,
378
0
                             nTargetValues, panTargetValues);
379
380
        // Final post processing of distances.
381
0
        for (int i = 0; i < nXSize; i++)
382
0
        {
383
0
            if (pafProximity[i] < 0.0f)
384
0
                pafProximity[i] = fNoDataValue;
385
0
            else if (pafProximity[i] > 0.0f)
386
0
            {
387
0
                if (bFixedBufVal)
388
0
                    pafProximity[i] = static_cast<float>(dfFixedBufVal);
389
0
                else
390
0
                    pafProximity[i] =
391
0
                        pafProximity[i] * static_cast<float>(dfDistMult);
392
0
            }
393
0
        }
394
395
        // Write out results.
396
0
        eErr = GDALRasterIO(hProximityBand, GF_Write, 0, iLine, nXSize, 1,
397
0
                            pafProximity, nXSize, 1, GDT_Float32, 0, 0);
398
399
0
        if (eErr != CE_None)
400
0
            break;
401
402
0
        if (!pfnProgress(0.5 + 0.5 * (nYSize - iLine) /
403
0
                                   static_cast<double>(nYSize),
404
0
                         "", pProgressArg))
405
0
        {
406
0
            CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated");
407
0
            eErr = CE_Failure;
408
0
        }
409
0
    }
410
411
/* -------------------------------------------------------------------- */
412
/*      Cleanup                                                         */
413
/* -------------------------------------------------------------------- */
414
0
end:
415
0
    CPLFree(panNearX);
416
0
    CPLFree(panNearY);
417
0
    CPLFree(panSrcScanline);
418
0
    CPLFree(pafProximity);
419
0
    CPLFree(panTargetValues);
420
421
0
    if (hWorkProximityDS != nullptr)
422
0
    {
423
0
        CPLString osProxFile = GDALGetDescription(hWorkProximityDS);
424
0
        GDALClose(hWorkProximityDS);
425
0
        if (!bTempFileAlreadyDeleted)
426
0
        {
427
0
            GDALDeleteDataset(GDALGetDriverByName("GTiff"), osProxFile);
428
0
        }
429
0
    }
430
431
0
    return eErr;
432
0
}
433
434
/************************************************************************/
435
/*                           SquareDistance()                           */
436
/************************************************************************/
437
438
static double SquareDistance(double dfX1, double dfX2, double dfY1, double dfY2)
439
0
{
440
0
    const double dfDX = dfX1 - dfX2;
441
0
    const double dfDY = dfY1 - dfY2;
442
0
    return dfDX * dfDX + dfDY * dfDY;
443
0
}
444
445
/************************************************************************/
446
/*                        ProcessProximityLine()                        */
447
/************************************************************************/
448
449
static CPLErr ProcessProximityLine(GInt32 *panSrcScanline, int *panNearX,
450
                                   int *panNearY, int bForward, int iLine,
451
                                   int nXSize, double dfMaxDist,
452
                                   float *pafProximity,
453
                                   double *pdfSrcNoDataValue, int nTargetValues,
454
                                   int *panTargetValues)
455
456
0
{
457
0
    const int iStart = bForward ? 0 : nXSize - 1;
458
0
    const int iEnd = bForward ? nXSize : -1;
459
0
    const int iStep = bForward ? 1 : -1;
460
461
0
    for (int iPixel = iStart; iPixel != iEnd; iPixel += iStep)
462
0
    {
463
0
        bool bIsTarget = false;
464
465
        /* --------------------------------------------------------------------
466
         */
467
        /*      Is the current pixel a target pixel? */
468
        /* --------------------------------------------------------------------
469
         */
470
0
        if (nTargetValues == 0)
471
0
        {
472
0
            bIsTarget = panSrcScanline[iPixel] != 0;
473
0
        }
474
0
        else
475
0
        {
476
0
            for (int i = 0; i < nTargetValues; i++)
477
0
            {
478
0
                if (panSrcScanline[iPixel] == panTargetValues[i])
479
0
                    bIsTarget = TRUE;
480
0
            }
481
0
        }
482
483
0
        if (bIsTarget)
484
0
        {
485
0
            pafProximity[iPixel] = 0.0;
486
0
            panNearX[iPixel] = iPixel;
487
0
            panNearY[iPixel] = iLine;
488
0
            continue;
489
0
        }
490
491
        /* --------------------------------------------------------------------
492
         */
493
        /*      Are we near(er) to the closest target to the above (below) */
494
        /*      pixel? */
495
        /* --------------------------------------------------------------------
496
         */
497
0
        double dfNearDistSq = std::max(dfMaxDist, static_cast<double>(nXSize)) *
498
0
                              std::max(dfMaxDist, static_cast<double>(nXSize)) *
499
0
                              2.0;
500
501
0
        if (panNearX[iPixel] != -1)
502
0
        {
503
0
            const double dfDistSq = SquareDistance(panNearX[iPixel], iPixel,
504
0
                                                   panNearY[iPixel], iLine);
505
506
0
            if (dfDistSq < dfNearDistSq)
507
0
            {
508
0
                dfNearDistSq = dfDistSq;
509
0
            }
510
0
            else
511
0
            {
512
0
                panNearX[iPixel] = -1;
513
0
                panNearY[iPixel] = -1;
514
0
            }
515
0
        }
516
517
        /* --------------------------------------------------------------------
518
         */
519
        /*      Are we near(er) to the closest target to the left (right) */
520
        /*      pixel? */
521
        /* --------------------------------------------------------------------
522
         */
523
0
        const int iLast = iPixel - iStep;
524
525
0
        if (iPixel != iStart && panNearX[iLast] != -1)
526
0
        {
527
0
            const double dfDistSq =
528
0
                SquareDistance(panNearX[iLast], iPixel, panNearY[iLast], iLine);
529
530
0
            if (dfDistSq < dfNearDistSq)
531
0
            {
532
0
                dfNearDistSq = dfDistSq;
533
0
                panNearX[iPixel] = panNearX[iLast];
534
0
                panNearY[iPixel] = panNearY[iLast];
535
0
            }
536
0
        }
537
538
        /* --------------------------------------------------------------------
539
         */
540
        /*      Are we near(er) to the closest target to the topright */
541
        /*      (bottom left) pixel? */
542
        /* --------------------------------------------------------------------
543
         */
544
0
        const int iTR = iPixel + iStep;
545
546
0
        if (iTR != iEnd && panNearX[iTR] != -1)
547
0
        {
548
0
            const double dfDistSq =
549
0
                SquareDistance(panNearX[iTR], iPixel, panNearY[iTR], iLine);
550
551
0
            if (dfDistSq < dfNearDistSq)
552
0
            {
553
0
                dfNearDistSq = dfDistSq;
554
0
                panNearX[iPixel] = panNearX[iTR];
555
0
                panNearY[iPixel] = panNearY[iTR];
556
0
            }
557
0
        }
558
559
        /* --------------------------------------------------------------------
560
         */
561
        /*      Update our proximity value. */
562
        /* --------------------------------------------------------------------
563
         */
564
0
        if (panNearX[iPixel] != -1 &&
565
0
            (pdfSrcNoDataValue == nullptr ||
566
0
             panSrcScanline[iPixel] != *pdfSrcNoDataValue) &&
567
0
            dfNearDistSq <= dfMaxDist * dfMaxDist &&
568
0
            (pafProximity[iPixel] < 0 ||
569
0
             dfNearDistSq < static_cast<double>(pafProximity[iPixel]) *
570
0
                                static_cast<double>(pafProximity[iPixel])))
571
0
        {
572
0
            pafProximity[iPixel] = static_cast<float>(sqrt(dfNearDistSq));
573
0
        }
574
0
    }
575
576
0
    return CE_None;
577
0
}