/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 | } |