LCOV - code coverage report
Current view: top level - alg - gdalcutline.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 125 144 86.8 %
Date: 2024-11-21 22:18:42 Functions: 3 4 75.0 %

          Line data    Source code
       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             : {
      47             :     CPLError(CE_Failure, CPLE_AppDefined,
      48             :              "Blend distance support not available without the GEOS library.");
      49             :     return CE_Failure;
      50             : }
      51             : #else
      52           1 : 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           1 :     const auto poPolygon = OGRGeometry::FromHandle(hPolygon);
      62             :     auto poLines = std::unique_ptr<OGRGeometry>(
      63           2 :         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           2 :     CPLString osClipRectWKT;
      71             : 
      72             :     osClipRectWKT.Printf(
      73           1 :         "POLYGON((%g %g,%g %g,%g %g,%g %g,%g %g))", nXOff - (dfBlendDist + 1),
      74           1 :         nYOff - (dfBlendDist + 1), nXOff + nXSize + (dfBlendDist + 1),
      75           1 :         nYOff - (dfBlendDist + 1), nXOff + nXSize + (dfBlendDist + 1),
      76           1 :         nYOff + nYSize + (dfBlendDist + 1), nXOff - (dfBlendDist + 1),
      77           1 :         nYOff + nYSize + (dfBlendDist + 1), nXOff - (dfBlendDist + 1),
      78           1 :         nYOff - (dfBlendDist + 1));
      79             : 
      80           2 :     OGRPolygon oClipRect;
      81             :     {
      82           1 :         const char *pszClipRectWKT = osClipRectWKT.c_str();
      83           1 :         oClipRect.importFromWkt(&pszClipRectWKT);
      84             :     }
      85             : 
      86             :     // If it does not intersect the polym, zero the mask and return.
      87           1 :     if (!poPolygon->Intersects(&oClipRect))
      88             :     {
      89           0 :         memset(pafValidityMask, 0, sizeof(float) * nXSize * nYSize);
      90             : 
      91           0 :         return CE_None;
      92             :     }
      93             : 
      94             :     // If it does not intersect the line at all, just return.
      95           1 :     else if (!poLines->Intersects(&oClipRect))
      96             :     {
      97             : 
      98           0 :         return CE_None;
      99             :     }
     100             : 
     101           1 :     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           1 :     OGREnvelope sEnvelope;
     108           1 :     GEOSContextHandle_t hGEOSCtxt = OGRGeometry::createGEOSContext();
     109           1 :     GEOSGeom poGEOSPoly = poLines->exportToGEOS(hGEOSCtxt);
     110           1 :     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           1 :         0, static_cast<int>(floor(sEnvelope.MinX - dfBlendDist - nXOff)));
     123             :     const int iXMax = std::min(
     124           1 :         nXSize, static_cast<int>(ceil(sEnvelope.MaxX + dfBlendDist - nXOff)));
     125             :     const int iYMin = std::max(
     126           1 :         0, static_cast<int>(floor(sEnvelope.MinY - dfBlendDist - nYOff)));
     127             :     const int iYMax = std::min(
     128           1 :         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         101 :     for (int iY = 0; iY < nYSize; iY++)
     135             :     {
     136         100 :         double dfLastDist = 0.0;
     137             : 
     138       10100 :         for (int iX = 0; iX < nXSize; iX++)
     139             :         {
     140       10000 :             if (iX < iXMin || iX >= iXMax || iY < iYMin || iY > iYMax ||
     141        3060 :                 dfLastDist > dfBlendDist + 1.5)
     142             :             {
     143        7944 :                 if (pabyPolyMask[iX + iY * nXSize] == 0)
     144        7784 :                     pafValidityMask[iX + iY * nXSize] = 0.0;
     145             : 
     146        7944 :                 dfLastDist -= 1.0;
     147        8528 :                 continue;
     148             :             }
     149             : 
     150        2056 :             CPLString osPointWKT;
     151        2056 :             osPointWKT.Printf("POINT(%d.5 %d.5)", iX + nXOff, iY + nYOff);
     152             : 
     153        2056 :             GEOSGeom poGEOSPoint = GEOSGeomFromWKT_r(hGEOSCtxt, osPointWKT);
     154             : 
     155        2056 :             double dfDist = 0.0;
     156        2056 :             GEOSDistance_r(hGEOSCtxt, poGEOSPoly, poGEOSPoint, &dfDist);
     157        2056 :             GEOSGeom_destroy_r(hGEOSCtxt, poGEOSPoint);
     158             : 
     159        2056 :             dfLastDist = dfDist;
     160             : 
     161        2056 :             if (dfDist > dfBlendDist)
     162             :             {
     163         584 :                 if (pabyPolyMask[iX + iY * nXSize] == 0)
     164         366 :                     pafValidityMask[iX + iY * nXSize] = 0.0;
     165             : 
     166         584 :                 continue;
     167             :             }
     168             : 
     169        1472 :             const double dfRatio =
     170        1472 :                 pabyPolyMask[iX + iY * nXSize] == 0
     171        1472 :                     ? 0.5 - (dfDist / dfBlendDist) * 0.5   // Outside.
     172         622 :                     : 0.5 + (dfDist / dfBlendDist) * 0.5;  // Inside.
     173             : 
     174        1472 :             pafValidityMask[iX + iY * nXSize] *= static_cast<float>(dfRatio);
     175             :         }
     176             :     }
     177             : 
     178             :     /* -------------------------------------------------------------------- */
     179             :     /*      Cleanup                                                         */
     180             :     /* -------------------------------------------------------------------- */
     181           1 :     GEOSGeom_destroy_r(hGEOSCtxt, poGEOSPoly);
     182           1 :     OGRGeometry::freeGEOSContext(hGEOSCtxt);
     183             : 
     184           1 :     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          29 : static int CutlineTransformer(void *pTransformArg, int bDstToSrc,
     196             :                               int nPointCount, double *x, double *y,
     197             :                               double * /* z */, int * /* panSuccess */)
     198             : {
     199          29 :     int nXOff = static_cast<int *>(pTransformArg)[0];
     200          29 :     int nYOff = static_cast<int *>(pTransformArg)[1];
     201             : 
     202          29 :     if (bDstToSrc)
     203             :     {
     204           0 :         nXOff *= -1;
     205           0 :         nYOff *= -1;
     206             :     }
     207             : 
     208        5005 :     for (int i = 0; i < nPointCount; i++)
     209             :     {
     210        4976 :         x[i] -= nXOff;
     211        4976 :         y[i] -= nYOff;
     212             :     }
     213             : 
     214          29 :     return TRUE;
     215             : }
     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           0 : 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             : {
     230           0 :     return GDALWarpCutlineMaskerEx(pMaskFuncArg, nBandCount, eType, nXOff,
     231             :                                    nYOff, nXSize, nYSize, ppImageData,
     232           0 :                                    bMaskIsFloat, pValidityMask, nullptr);
     233             : }
     234             : 
     235          33 : 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             : {
     242          33 :     if (pnValidityFlag)
     243          33 :         *pnValidityFlag = GCMVF_PARTIAL_INTERSECTION;
     244             : 
     245          33 :     if (nXSize < 1 || nYSize < 1)
     246           0 :         return CE_None;
     247             : 
     248             :     /* -------------------------------------------------------------------- */
     249             :     /*      Do some minimal checking.                                       */
     250             :     /* -------------------------------------------------------------------- */
     251          33 :     if (!bMaskIsFloat)
     252             :     {
     253           0 :         CPLAssert(false);
     254             :         return CE_Failure;
     255             :     }
     256             : 
     257          33 :     GDALWarpOptions *psWO = static_cast<GDALWarpOptions *>(pMaskFuncArg);
     258             : 
     259          33 :     if (psWO == nullptr || psWO->hCutline == nullptr)
     260             :     {
     261           0 :         CPLAssert(false);
     262             :         return CE_Failure;
     263             :     }
     264             : 
     265          33 :     GDALDriverH hMemDriver = GDALGetDriverByName("MEM");
     266          33 :     if (hMemDriver == nullptr)
     267             :     {
     268           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     269             :                  "GDALWarpCutlineMasker needs MEM driver");
     270           0 :         return CE_Failure;
     271             :     }
     272             : 
     273             :     /* -------------------------------------------------------------------- */
     274             :     /*      Check the polygon.                                              */
     275             :     /* -------------------------------------------------------------------- */
     276          33 :     OGRGeometryH hPolygon = static_cast<OGRGeometryH>(psWO->hCutline);
     277             : 
     278          63 :     if (wkbFlatten(OGR_G_GetGeometryType(hPolygon)) != wkbPolygon &&
     279          30 :         wkbFlatten(OGR_G_GetGeometryType(hPolygon)) != wkbMultiPolygon)
     280             :     {
     281           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     282             :                  "Cutline should be a polygon or a multipolygon");
     283           0 :         return CE_Failure;
     284             :     }
     285             : 
     286          33 :     OGREnvelope sEnvelope;
     287          33 :     OGR_G_GetEnvelope(hPolygon, &sEnvelope);
     288             : 
     289          33 :     float *pafMask = static_cast<float *>(pValidityMask);
     290             : 
     291          33 :     if (sEnvelope.MaxX + psWO->dfCutlineBlendDist < nXOff ||
     292          33 :         sEnvelope.MinX - psWO->dfCutlineBlendDist > nXOff + nXSize ||
     293          33 :         sEnvelope.MaxY + psWO->dfCutlineBlendDist < nYOff ||
     294          33 :         sEnvelope.MinY - psWO->dfCutlineBlendDist > nYOff + nYSize)
     295             :     {
     296           0 :         if (pnValidityFlag)
     297           0 :             *pnValidityFlag = GCMVF_NO_INTERSECTION;
     298             : 
     299             :         // We are far from the blend line - everything is masked to zero.
     300             :         // It would be nice to realize no work is required for this whole
     301             :         // chunk!
     302           0 :         memset(pafMask, 0, sizeof(float) * nXSize * nYSize);
     303           0 :         return CE_None;
     304             :     }
     305             : 
     306             :     // And now check if the chunk to warp is fully contained within the cutline
     307             :     // to save rasterization.
     308          33 :     if (OGRGeometryFactory::haveGEOS()
     309             : #ifdef DEBUG
     310             :         // Env var just for debugging purposes
     311          33 :         && !CPLTestBool(
     312             :                CPLGetConfigOption("GDALCUTLINE_SKIP_CONTAINMENT_TEST", "NO"))
     313             : #endif
     314             :     )
     315             :     {
     316          33 :         OGRLinearRing *poRing = new OGRLinearRing();
     317          33 :         poRing->addPoint(-psWO->dfCutlineBlendDist + nXOff,
     318          33 :                          -psWO->dfCutlineBlendDist + nYOff);
     319          33 :         poRing->addPoint(-psWO->dfCutlineBlendDist + nXOff,
     320          33 :                          psWO->dfCutlineBlendDist + nYOff + nYSize);
     321          33 :         poRing->addPoint(psWO->dfCutlineBlendDist + nXOff + nXSize,
     322          33 :                          psWO->dfCutlineBlendDist + nYOff + nYSize);
     323          33 :         poRing->addPoint(psWO->dfCutlineBlendDist + nXOff + nXSize,
     324          33 :                          -psWO->dfCutlineBlendDist + nYOff);
     325          33 :         poRing->addPoint(-psWO->dfCutlineBlendDist + nXOff,
     326          33 :                          -psWO->dfCutlineBlendDist + nYOff);
     327          33 :         OGRPolygon oChunkFootprint;
     328          33 :         oChunkFootprint.addRingDirectly(poRing);
     329          33 :         OGREnvelope sChunkEnvelope;
     330          33 :         oChunkFootprint.getEnvelope(&sChunkEnvelope);
     331          42 :         if (sEnvelope.Contains(sChunkEnvelope) &&
     332           9 :             OGRGeometry::FromHandle(hPolygon)->Contains(&oChunkFootprint))
     333             :         {
     334           8 :             if (pnValidityFlag)
     335           8 :                 *pnValidityFlag = GCMVF_CHUNK_FULLY_WITHIN_CUTLINE;
     336             : 
     337           8 :             CPLDebug("WARP", "Source chunk fully contained within cutline.");
     338           8 :             return CE_None;
     339             :         }
     340             :     }
     341             : 
     342             :     /* -------------------------------------------------------------------- */
     343             :     /*      Create a byte buffer into which we can burn the                 */
     344             :     /*      mask polygon and wrap it up as a memory dataset.                */
     345             :     /* -------------------------------------------------------------------- */
     346          25 :     GByte *pabyPolyMask = static_cast<GByte *>(CPLCalloc(nXSize, nYSize));
     347             : 
     348             :     auto poMEMDS =
     349          25 :         MEMDataset::Create("warp_temp", nXSize, nYSize, 0, GDT_Byte, nullptr);
     350             :     GDALRasterBandH hMEMBand =
     351          25 :         MEMCreateRasterBandEx(poMEMDS, 1, pabyPolyMask, GDT_Byte, 0, 0, false);
     352          25 :     poMEMDS->AddMEMBand(hMEMBand);
     353             : 
     354          25 :     GDALDatasetH hMemDS = GDALDataset::ToHandle(poMEMDS);
     355          25 :     double adfGeoTransform[6] = {0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
     356          25 :     GDALSetGeoTransform(hMemDS, adfGeoTransform);
     357             : 
     358             :     /* -------------------------------------------------------------------- */
     359             :     /*      Burn the polygon into the mask with 1.0 values.                 */
     360             :     /* -------------------------------------------------------------------- */
     361          25 :     int nTargetBand = 1;
     362          25 :     double dfBurnValue = 255.0;
     363          25 :     char **papszRasterizeOptions = nullptr;
     364             : 
     365          25 :     if (CPLFetchBool(psWO->papszWarpOptions, "CUTLINE_ALL_TOUCHED", false))
     366             :         papszRasterizeOptions =
     367           5 :             CSLSetNameValue(papszRasterizeOptions, "ALL_TOUCHED", "TRUE");
     368             : 
     369          25 :     int anXYOff[2] = {nXOff, nYOff};
     370             : 
     371          25 :     CPLErr eErr = GDALRasterizeGeometries(
     372             :         hMemDS, 1, &nTargetBand, 1, &hPolygon, CutlineTransformer, anXYOff,
     373             :         &dfBurnValue, papszRasterizeOptions, nullptr, nullptr);
     374             : 
     375          25 :     CSLDestroy(papszRasterizeOptions);
     376             : 
     377             :     // Close and ensure data flushed to underlying array.
     378          25 :     GDALClose(hMemDS);
     379             : 
     380             :     /* -------------------------------------------------------------------- */
     381             :     /*      In the case with no blend distance, we just apply this as a     */
     382             :     /*      mask, zeroing out everything outside the polygon.               */
     383             :     /* -------------------------------------------------------------------- */
     384          25 :     if (psWO->dfCutlineBlendDist == 0.0)
     385             :     {
     386     2861310 :         for (int i = nXSize * nYSize - 1; i >= 0; i--)
     387             :         {
     388     2861290 :             if (pabyPolyMask[i] == 0)
     389     2602350 :                 static_cast<float *>(pValidityMask)[i] = 0.0;
     390             :         }
     391             :     }
     392             :     else
     393             :     {
     394           1 :         eErr = BlendMaskGenerator(nXOff, nYOff, nXSize, nYSize, pabyPolyMask,
     395             :                                   static_cast<float *>(pValidityMask), hPolygon,
     396             :                                   psWO->dfCutlineBlendDist);
     397             :     }
     398             : 
     399             :     /* -------------------------------------------------------------------- */
     400             :     /*      Clean up.                                                       */
     401             :     /* -------------------------------------------------------------------- */
     402          25 :     CPLFree(pabyPolyMask);
     403             : 
     404          25 :     return eErr;
     405             : }

Generated by: LCOV version 1.14