Line data Source code
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 6 : CPLErr CPL_STDCALL GDALComputeProximity(GDALRasterBandH hSrcBand,
95 : GDALRasterBandH hProximityBand,
96 : char **papszOptions,
97 : GDALProgressFunc pfnProgress,
98 : void *pProgressArg)
99 :
100 : {
101 6 : VALIDATE_POINTER1(hSrcBand, "GDALComputeProximity", CE_Failure);
102 6 : VALIDATE_POINTER1(hProximityBand, "GDALComputeProximity", CE_Failure);
103 :
104 6 : if (pfnProgress == nullptr)
105 5 : pfnProgress = GDALDummyProgress;
106 :
107 : /* -------------------------------------------------------------------- */
108 : /* Are we using pixels or georeferenced coordinates for distances? */
109 : /* -------------------------------------------------------------------- */
110 6 : double dfDistMult = 1.0;
111 6 : const char *pszOpt = CSLFetchNameValue(papszOptions, "DISTUNITS");
112 6 : if (pszOpt)
113 : {
114 0 : if (EQUAL(pszOpt, "GEO"))
115 : {
116 0 : GDALDatasetH hSrcDS = GDALGetBandDataset(hSrcBand);
117 0 : if (hSrcDS)
118 : {
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 : CE_Warning, CPLE_AppDefined,
126 : "Pixels not square, distances will be inaccurate.");
127 0 : dfDistMult = std::abs(adfGeoTransform[1]);
128 : }
129 : }
130 0 : else if (!EQUAL(pszOpt, "PIXEL"))
131 : {
132 0 : CPLError(
133 : CE_Failure, CPLE_AppDefined,
134 : "Unrecognized DISTUNITS value '%s', should be GEO or PIXEL.",
135 : pszOpt);
136 0 : return CE_Failure;
137 : }
138 : }
139 :
140 : /* -------------------------------------------------------------------- */
141 : /* What is our maxdist value? */
142 : /* -------------------------------------------------------------------- */
143 6 : pszOpt = CSLFetchNameValue(papszOptions, "MAXDIST");
144 6 : const double dfMaxDist = pszOpt ? CPLAtof(pszOpt) / dfDistMult
145 2 : : GDALGetRasterBandXSize(hSrcBand) +
146 2 : GDALGetRasterBandYSize(hSrcBand);
147 :
148 6 : CPLDebug("GDAL", "MAXDIST=%g, DISTMULT=%g", dfMaxDist, dfDistMult);
149 :
150 : /* -------------------------------------------------------------------- */
151 : /* Verify the source and destination are compatible. */
152 : /* -------------------------------------------------------------------- */
153 6 : const int nXSize = GDALGetRasterBandXSize(hSrcBand);
154 6 : const int nYSize = GDALGetRasterBandYSize(hSrcBand);
155 12 : if (nXSize != GDALGetRasterBandXSize(hProximityBand) ||
156 6 : nYSize != GDALGetRasterBandYSize(hProximityBand))
157 : {
158 0 : CPLError(CE_Failure, CPLE_AppDefined,
159 : "Source and proximity bands are not the same size.");
160 0 : return CE_Failure;
161 : }
162 :
163 : /* -------------------------------------------------------------------- */
164 : /* Get input NODATA value. */
165 : /* -------------------------------------------------------------------- */
166 6 : double dfSrcNoDataValue = 0.0;
167 6 : double *pdfSrcNoData = nullptr;
168 6 : if (CPLFetchBool(papszOptions, "USE_INPUT_NODATA", false))
169 : {
170 2 : int bSrcHasNoData = 0;
171 2 : dfSrcNoDataValue = GDALGetRasterNoDataValue(hSrcBand, &bSrcHasNoData);
172 2 : if (bSrcHasNoData)
173 2 : pdfSrcNoData = &dfSrcNoDataValue;
174 : }
175 :
176 : /* -------------------------------------------------------------------- */
177 : /* Get output NODATA value. */
178 : /* -------------------------------------------------------------------- */
179 6 : float fNoDataValue = 0.0f;
180 6 : pszOpt = CSLFetchNameValue(papszOptions, "NODATA");
181 6 : if (pszOpt != nullptr)
182 : {
183 4 : fNoDataValue = static_cast<float>(CPLAtof(pszOpt));
184 : }
185 : else
186 : {
187 2 : int bSuccess = FALSE;
188 :
189 2 : fNoDataValue = static_cast<float>(
190 2 : GDALGetRasterNoDataValue(hProximityBand, &bSuccess));
191 2 : if (!bSuccess)
192 2 : fNoDataValue = 65535.0;
193 : }
194 :
195 : /* -------------------------------------------------------------------- */
196 : /* Is there a fixed value we wish to force the buffer area to? */
197 : /* -------------------------------------------------------------------- */
198 6 : double dfFixedBufVal = 0.0;
199 6 : bool bFixedBufVal = false;
200 6 : pszOpt = CSLFetchNameValue(papszOptions, "FIXED_BUF_VAL");
201 6 : if (pszOpt)
202 : {
203 2 : dfFixedBufVal = CPLAtof(pszOpt);
204 2 : bFixedBufVal = true;
205 : }
206 :
207 : /* -------------------------------------------------------------------- */
208 : /* Get the target value(s). */
209 : /* -------------------------------------------------------------------- */
210 6 : int *panTargetValues = nullptr;
211 6 : int nTargetValues = 0;
212 :
213 6 : pszOpt = CSLFetchNameValue(papszOptions, "VALUES");
214 6 : if (pszOpt != nullptr)
215 : {
216 : char **papszValuesTokens =
217 4 : CSLTokenizeStringComplex(pszOpt, ",", FALSE, FALSE);
218 :
219 4 : nTargetValues = CSLCount(papszValuesTokens);
220 : panTargetValues =
221 4 : static_cast<int *>(CPLCalloc(sizeof(int), nTargetValues));
222 :
223 12 : for (int i = 0; i < nTargetValues; i++)
224 8 : panTargetValues[i] = atoi(papszValuesTokens[i]);
225 4 : CSLDestroy(papszValuesTokens);
226 : }
227 :
228 : /* -------------------------------------------------------------------- */
229 : /* Initialize progress counter. */
230 : /* -------------------------------------------------------------------- */
231 6 : if (!pfnProgress(0.0, "", pProgressArg))
232 : {
233 0 : CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated");
234 0 : CPLFree(panTargetValues);
235 0 : return CE_Failure;
236 : }
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 6 : GDALRasterBandH hWorkProximityBand = hProximityBand;
244 6 : GDALDatasetH hWorkProximityDS = nullptr;
245 6 : const GDALDataType eProxType = GDALGetRasterDataType(hProximityBand);
246 6 : CPLErr eErr = CE_None;
247 :
248 : // TODO(schwehr): Localize after removing gotos.
249 6 : float *pafProximity = nullptr;
250 6 : int *panNearX = nullptr;
251 6 : int *panNearY = nullptr;
252 6 : GInt32 *panSrcScanline = nullptr;
253 6 : bool bTempFileAlreadyDeleted = false;
254 :
255 6 : if (eProxType == GDT_Byte || eProxType == GDT_UInt16 ||
256 : eProxType == GDT_UInt32)
257 : {
258 3 : GDALDriverH hDriver = GDALGetDriverByName("GTiff");
259 3 : if (hDriver == nullptr)
260 : {
261 0 : CPLError(CE_Failure, CPLE_AppDefined,
262 : "GDALComputeProximity needs GTiff driver");
263 0 : eErr = CE_Failure;
264 0 : goto end;
265 : }
266 3 : CPLString osTmpFile = CPLGenerateTempFilename("proximity");
267 3 : hWorkProximityDS = GDALCreate(hDriver, osTmpFile, nXSize, nYSize, 1,
268 : GDT_Float32, nullptr);
269 3 : if (hWorkProximityDS == nullptr)
270 : {
271 0 : eErr = CE_Failure;
272 0 : goto end;
273 : }
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 3 : bTempFileAlreadyDeleted = VSIUnlink(osTmpFile) == 0;
278 3 : hWorkProximityBand = GDALGetRasterBand(hWorkProximityDS, 1);
279 : }
280 :
281 : /* -------------------------------------------------------------------- */
282 : /* Allocate buffer for two scanlines of distances as floats */
283 : /* (the current and last line). */
284 : /* -------------------------------------------------------------------- */
285 : pafProximity =
286 6 : static_cast<float *>(VSI_MALLOC2_VERBOSE(sizeof(float), nXSize));
287 6 : panNearX = static_cast<int *>(VSI_MALLOC2_VERBOSE(sizeof(int), nXSize));
288 6 : panNearY = static_cast<int *>(VSI_MALLOC2_VERBOSE(sizeof(int), nXSize));
289 : panSrcScanline =
290 6 : static_cast<GInt32 *>(VSI_MALLOC2_VERBOSE(sizeof(GInt32), nXSize));
291 :
292 6 : if (pafProximity == nullptr || panNearX == nullptr || panNearY == nullptr ||
293 : panSrcScanline == nullptr)
294 : {
295 0 : eErr = CE_Failure;
296 0 : goto end;
297 : }
298 :
299 : /* -------------------------------------------------------------------- */
300 : /* Loop from top to bottom of the image. */
301 : /* -------------------------------------------------------------------- */
302 :
303 156 : for (int i = 0; i < nXSize; i++)
304 : {
305 150 : panNearX[i] = -1;
306 150 : panNearY[i] = -1;
307 : }
308 :
309 156 : for (int iLine = 0; eErr == CE_None && iLine < nYSize; iLine++)
310 : {
311 : // Read for target values.
312 150 : eErr = GDALRasterIO(hSrcBand, GF_Read, 0, iLine, nXSize, 1,
313 : panSrcScanline, nXSize, 1, GDT_Int32, 0, 0);
314 150 : if (eErr != CE_None)
315 0 : break;
316 :
317 3900 : for (int i = 0; i < nXSize; i++)
318 3750 : pafProximity[i] = -1.0;
319 :
320 : // Left to right.
321 150 : ProcessProximityLine(panSrcScanline, panNearX, panNearY, TRUE, iLine,
322 : nXSize, dfMaxDist, pafProximity, pdfSrcNoData,
323 : nTargetValues, panTargetValues);
324 :
325 : // Right to Left.
326 150 : ProcessProximityLine(panSrcScanline, panNearX, panNearY, FALSE, iLine,
327 : nXSize, dfMaxDist, pafProximity, pdfSrcNoData,
328 : nTargetValues, panTargetValues);
329 :
330 : // Write out results.
331 150 : eErr = GDALRasterIO(hWorkProximityBand, GF_Write, 0, iLine, nXSize, 1,
332 : pafProximity, nXSize, 1, GDT_Float32, 0, 0);
333 :
334 150 : if (eErr != CE_None)
335 0 : break;
336 :
337 150 : if (!pfnProgress(0.5 * (iLine + 1) / static_cast<double>(nYSize), "",
338 : pProgressArg))
339 : {
340 0 : CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated");
341 0 : eErr = CE_Failure;
342 : }
343 : }
344 :
345 : /* -------------------------------------------------------------------- */
346 : /* Loop from bottom to top of the image. */
347 : /* -------------------------------------------------------------------- */
348 156 : for (int i = 0; i < nXSize; i++)
349 : {
350 150 : panNearX[i] = -1;
351 150 : panNearY[i] = -1;
352 : }
353 :
354 156 : for (int iLine = nYSize - 1; eErr == CE_None && iLine >= 0; iLine--)
355 : {
356 : // Read first pass proximity.
357 150 : eErr = GDALRasterIO(hWorkProximityBand, GF_Read, 0, iLine, nXSize, 1,
358 : pafProximity, nXSize, 1, GDT_Float32, 0, 0);
359 :
360 150 : if (eErr != CE_None)
361 0 : break;
362 :
363 : // Read pixel values.
364 :
365 150 : eErr = GDALRasterIO(hSrcBand, GF_Read, 0, iLine, nXSize, 1,
366 : panSrcScanline, nXSize, 1, GDT_Int32, 0, 0);
367 150 : if (eErr != CE_None)
368 0 : break;
369 :
370 : // Right to left.
371 150 : ProcessProximityLine(panSrcScanline, panNearX, panNearY, FALSE, iLine,
372 : nXSize, dfMaxDist, pafProximity, pdfSrcNoData,
373 : nTargetValues, panTargetValues);
374 :
375 : // Left to right.
376 150 : ProcessProximityLine(panSrcScanline, panNearX, panNearY, TRUE, iLine,
377 : nXSize, dfMaxDist, pafProximity, pdfSrcNoData,
378 : nTargetValues, panTargetValues);
379 :
380 : // Final post processing of distances.
381 3900 : for (int i = 0; i < nXSize; i++)
382 : {
383 3750 : if (pafProximity[i] < 0.0)
384 1428 : pafProximity[i] = fNoDataValue;
385 2322 : else if (pafProximity[i] > 0.0)
386 : {
387 2060 : if (bFixedBufVal)
388 570 : pafProximity[i] = static_cast<float>(dfFixedBufVal);
389 : else
390 1490 : pafProximity[i] =
391 1490 : static_cast<float>(pafProximity[i] * dfDistMult);
392 : }
393 : }
394 :
395 : // Write out results.
396 150 : eErr = GDALRasterIO(hProximityBand, GF_Write, 0, iLine, nXSize, 1,
397 : pafProximity, nXSize, 1, GDT_Float32, 0, 0);
398 :
399 150 : if (eErr != CE_None)
400 0 : break;
401 :
402 150 : if (!pfnProgress(0.5 + 0.5 * (nYSize - iLine) /
403 150 : static_cast<double>(nYSize),
404 : "", pProgressArg))
405 : {
406 0 : CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated");
407 0 : eErr = CE_Failure;
408 : }
409 : }
410 :
411 : /* -------------------------------------------------------------------- */
412 : /* Cleanup */
413 : /* -------------------------------------------------------------------- */
414 6 : end:
415 6 : CPLFree(panNearX);
416 6 : CPLFree(panNearY);
417 6 : CPLFree(panSrcScanline);
418 6 : CPLFree(pafProximity);
419 6 : CPLFree(panTargetValues);
420 :
421 6 : if (hWorkProximityDS != nullptr)
422 : {
423 6 : CPLString osProxFile = GDALGetDescription(hWorkProximityDS);
424 3 : GDALClose(hWorkProximityDS);
425 3 : if (!bTempFileAlreadyDeleted)
426 : {
427 0 : GDALDeleteDataset(GDALGetDriverByName("GTiff"), osProxFile);
428 : }
429 : }
430 :
431 6 : return eErr;
432 : }
433 :
434 : /************************************************************************/
435 : /* SquareDistance() */
436 : /************************************************************************/
437 :
438 26074 : static double SquareDistance(double dfX1, double dfX2, double dfY1, double dfY2)
439 : {
440 26074 : const double dfDX = dfX1 - dfX2;
441 26074 : const double dfDY = dfY1 - dfY2;
442 26074 : return dfDX * dfDX + dfDY * dfDY;
443 : }
444 :
445 : /************************************************************************/
446 : /* ProcessProximityLine() */
447 : /************************************************************************/
448 :
449 600 : 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 : {
457 600 : const int iStart = bForward ? 0 : nXSize - 1;
458 600 : const int iEnd = bForward ? nXSize : -1;
459 600 : const int iStep = bForward ? 1 : -1;
460 :
461 15600 : for (int iPixel = iStart; iPixel != iEnd; iPixel += iStep)
462 : {
463 15000 : bool bIsTarget = false;
464 :
465 : /* --------------------------------------------------------------------
466 : */
467 : /* Is the current pixel a target pixel? */
468 : /* --------------------------------------------------------------------
469 : */
470 15000 : if (nTargetValues == 0)
471 : {
472 5000 : bIsTarget = panSrcScanline[iPixel] != 0;
473 : }
474 : else
475 : {
476 30000 : for (int i = 0; i < nTargetValues; i++)
477 : {
478 20000 : if (panSrcScanline[iPixel] == panTargetValues[i])
479 144 : bIsTarget = TRUE;
480 : }
481 : }
482 :
483 15000 : if (bIsTarget)
484 : {
485 1048 : pafProximity[iPixel] = 0.0;
486 1048 : panNearX[iPixel] = iPixel;
487 1048 : panNearY[iPixel] = iLine;
488 1048 : continue;
489 : }
490 :
491 : /* --------------------------------------------------------------------
492 : */
493 : /* Are we near(er) to the closest target to the above (below) */
494 : /* pixel? */
495 : /* --------------------------------------------------------------------
496 : */
497 13952 : double dfNearDistSq = std::max(dfMaxDist, static_cast<double>(nXSize)) *
498 13952 : std::max(dfMaxDist, static_cast<double>(nXSize)) *
499 13952 : 2.0;
500 :
501 13952 : if (panNearX[iPixel] != -1)
502 : {
503 8864 : const double dfDistSq = SquareDistance(panNearX[iPixel], iPixel,
504 8864 : panNearY[iPixel], iLine);
505 :
506 8864 : if (dfDistSq < dfNearDistSq)
507 : {
508 8864 : dfNearDistSq = dfDistSq;
509 : }
510 : else
511 : {
512 0 : panNearX[iPixel] = -1;
513 0 : panNearY[iPixel] = -1;
514 : }
515 : }
516 :
517 : /* --------------------------------------------------------------------
518 : */
519 : /* Are we near(er) to the closest target to the left (right) */
520 : /* pixel? */
521 : /* --------------------------------------------------------------------
522 : */
523 13952 : const int iLast = iPixel - iStep;
524 :
525 13952 : if (iPixel != iStart && panNearX[iLast] != -1)
526 : {
527 : const double dfDistSq =
528 8732 : SquareDistance(panNearX[iLast], iPixel, panNearY[iLast], iLine);
529 :
530 8732 : if (dfDistSq < dfNearDistSq)
531 : {
532 1500 : dfNearDistSq = dfDistSq;
533 1500 : panNearX[iPixel] = panNearX[iLast];
534 1500 : panNearY[iPixel] = panNearY[iLast];
535 : }
536 : }
537 :
538 : /* --------------------------------------------------------------------
539 : */
540 : /* Are we near(er) to the closest target to the topright */
541 : /* (bottom left) pixel? */
542 : /* --------------------------------------------------------------------
543 : */
544 13952 : const int iTR = iPixel + iStep;
545 :
546 13952 : if (iTR != iEnd && panNearX[iTR] != -1)
547 : {
548 : const double dfDistSq =
549 8478 : SquareDistance(panNearX[iTR], iPixel, panNearY[iTR], iLine);
550 :
551 8478 : if (dfDistSq < dfNearDistSq)
552 : {
553 52 : dfNearDistSq = dfDistSq;
554 52 : panNearX[iPixel] = panNearX[iTR];
555 52 : panNearY[iPixel] = panNearY[iTR];
556 : }
557 : }
558 :
559 : /* --------------------------------------------------------------------
560 : */
561 : /* Update our proximity value. */
562 : /* --------------------------------------------------------------------
563 : */
564 13952 : if (panNearX[iPixel] != -1 &&
565 2684 : (pdfSrcNoDataValue == nullptr ||
566 2684 : panSrcScanline[iPixel] != *pdfSrcNoDataValue) &&
567 8714 : dfNearDistSq <= dfMaxDist * dfMaxDist &&
568 6206 : (pafProximity[iPixel] < 0 ||
569 4146 : dfNearDistSq < static_cast<double>(pafProximity[iPixel]) *
570 4146 : pafProximity[iPixel]))
571 3208 : pafProximity[iPixel] = static_cast<float>(sqrt(dfNearDistSq));
572 : }
573 :
574 600 : return CE_None;
575 : }
|