LCOV - code coverage report
Current view: top level - alg - gdalgeolocquadtree.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 113 148 76.4 %
Date: 2024-05-04 12:52:34 Functions: 4 4 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Implements Geolocation array based transformer, using a quadtree
       5             :  *           for inverse
       6             :  * Author:   Even Rouault, <even.rouault at spatialys.com>
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 2022, Planet Labs
      10             :  *
      11             :  * Permission is hereby granted, free of charge, to any person obtaining a
      12             :  * copy of this software and associated documentation files (the "Software"),
      13             :  * to deal in the Software without restriction, including without limitation
      14             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      15             :  * and/or sell copies of the Software, and to permit persons to whom the
      16             :  * Software is furnished to do so, subject to the following conditions:
      17             :  *
      18             :  * The above copyright notice and this permission notice shall be included
      19             :  * in all copies or substantial portions of the Software.
      20             :  *
      21             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      22             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      23             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      24             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      25             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      26             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      27             :  * DEALINGS IN THE SOFTWARE.
      28             :  ****************************************************************************/
      29             : 
      30             : #include "gdalgeoloc.h"
      31             : #include "gdalgeolocquadtree.h"
      32             : 
      33             : #include "cpl_quad_tree.h"
      34             : 
      35             : #include "ogr_geometry.h"
      36             : 
      37             : #include <algorithm>
      38             : #include <cstddef>
      39             : #include <limits>
      40             : 
      41             : /************************************************************************/
      42             : /*               GDALGeoLocQuadTreeGetFeatureCorners()                  */
      43             : /************************************************************************/
      44             : 
      45             : static bool
      46        3200 : GDALGeoLocQuadTreeGetFeatureCorners(const GDALGeoLocTransformInfo *psTransform,
      47             :                                     size_t nIdx, double &x0, double &y0,
      48             :                                     double &x1, double &y1, double &x2,
      49             :                                     double &y2, double &x3, double &y3)
      50             : {
      51        6400 :     const size_t nExtendedWidth = psTransform->nGeoLocXSize +
      52        3200 :                                   (psTransform->bOriginIsTopLeftCorner ? 0 : 1);
      53        3200 :     int nX = static_cast<int>(nIdx % nExtendedWidth);
      54        3200 :     int nY = static_cast<int>(nIdx / nExtendedWidth);
      55             : 
      56        3200 :     if (!psTransform->bOriginIsTopLeftCorner)
      57             :     {
      58        1681 :         nX--;
      59        1681 :         nY--;
      60             :     }
      61             : 
      62        3200 :     return GDALGeoLocExtractSquare(psTransform, static_cast<int>(nX),
      63             :                                    static_cast<int>(nY), x0, y0, x1, y1, x2, y2,
      64        3200 :                                    x3, y3);
      65             : }
      66             : 
      67             : /************************************************************************/
      68             : /*               GDALGeoLocQuadTreeGetFeatureBounds()                   */
      69             : /************************************************************************/
      70             : 
      71             : constexpr size_t BIT_IDX_RANGE_180 = 8 * sizeof(size_t) - 1;
      72             : constexpr size_t BIT_IDX_RANGE_180_SET = static_cast<size_t>(1)
      73             :                                          << BIT_IDX_RANGE_180;
      74             : 
      75             : // Callback used by quadtree to retrieve the bounding box, in georeferenced
      76             : // space, of a cell of the geolocation array.
      77        2114 : static void GDALGeoLocQuadTreeGetFeatureBounds(const void *hFeature,
      78             :                                                void *pUserData,
      79             :                                                CPLRectObj *pBounds)
      80             : {
      81        2114 :     const GDALGeoLocTransformInfo *psTransform =
      82             :         static_cast<const GDALGeoLocTransformInfo *>(pUserData);
      83        2114 :     size_t nIdx = reinterpret_cast<size_t>(hFeature);
      84             :     // Most significant bit set means that geometries crossing the antimeridian
      85             :     // should have their longitudes lower or greater than 180 deg.
      86        2114 :     const bool bXRefAt180 = (nIdx >> BIT_IDX_RANGE_180) != 0;
      87             :     // Clear that bit.
      88        2114 :     nIdx &= ~BIT_IDX_RANGE_180_SET;
      89             : 
      90        2114 :     double x0 = 0, y0 = 0, x1 = 0, y1 = 0, x2 = 0, y2 = 0, x3 = 0, y3 = 0;
      91        2114 :     GDALGeoLocQuadTreeGetFeatureCorners(psTransform, nIdx, x0, y0, x1, y1, x2,
      92             :                                         y2, x3, y3);
      93             : 
      94        2114 :     if (psTransform->bGeographicSRSWithMinus180Plus180LongRange &&
      95        2114 :         std::fabs(x0) > 170 && std::fabs(x1) > 170 && std::fabs(x2) > 170 &&
      96           0 :         std::fabs(x3) > 170 &&
      97           0 :         (std::fabs(x1 - x0) > 180 || std::fabs(x2 - x0) > 180 ||
      98           0 :          std::fabs(x3 - x0) > 180))
      99             :     {
     100           0 :         const double dfXRef = bXRefAt180 ? 180 : -180;
     101           0 :         x0 = ShiftGeoX(psTransform, dfXRef, x0);
     102           0 :         x1 = ShiftGeoX(psTransform, dfXRef, x1);
     103           0 :         x2 = ShiftGeoX(psTransform, dfXRef, x2);
     104           0 :         x3 = ShiftGeoX(psTransform, dfXRef, x3);
     105             :     }
     106        2114 :     pBounds->minx = std::min(std::min(x0, x1), std::min(x2, x3));
     107        2114 :     pBounds->miny = std::min(std::min(y0, y1), std::min(y2, y3));
     108        2114 :     pBounds->maxx = std::max(std::max(x0, x1), std::max(x2, x3));
     109        2114 :     pBounds->maxy = std::max(std::max(y0, y1), std::max(y2, y3));
     110        2114 : }
     111             : 
     112             : /************************************************************************/
     113             : /*                      GDALGeoLocBuildQuadTree()                       */
     114             : /************************************************************************/
     115             : 
     116           4 : bool GDALGeoLocBuildQuadTree(GDALGeoLocTransformInfo *psTransform)
     117             : {
     118             :     // For the pixel-center convention, insert a "virtual" row and column
     119             :     // at top and left of the geoloc array.
     120           4 :     const int nExtraPixel = psTransform->bOriginIsTopLeftCorner ? 0 : 1;
     121             : 
     122          12 :     if (psTransform->nGeoLocXSize > INT_MAX - nExtraPixel ||
     123           8 :         psTransform->nGeoLocYSize > INT_MAX - nExtraPixel ||
     124             :         // The >> 1 shift is because we need to reserve the most-significant-bit
     125             :         // for the second 'version' of anti-meridian crossing quadrilaterals.
     126             :         // See below
     127           4 :         static_cast<size_t>(psTransform->nGeoLocXSize + nExtraPixel) >
     128           4 :             (std::numeric_limits<size_t>::max() >> 1) /
     129           4 :                 static_cast<size_t>(psTransform->nGeoLocYSize + nExtraPixel))
     130             :     {
     131           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Too big geolocation array");
     132           0 :         return false;
     133             :     }
     134             : 
     135           4 :     const int nExtendedWidth = psTransform->nGeoLocXSize + nExtraPixel;
     136           4 :     const int nExtendedHeight = psTransform->nGeoLocYSize + nExtraPixel;
     137           4 :     const size_t nExtendedXYCount =
     138           4 :         static_cast<size_t>(nExtendedWidth) * nExtendedHeight;
     139             : 
     140           4 :     CPLDebug("GEOLOC", "Start quadtree construction");
     141             : 
     142             :     CPLRectObj globalBounds;
     143           4 :     globalBounds.minx = psTransform->dfMinX;
     144           4 :     globalBounds.miny = psTransform->dfMinY;
     145           4 :     globalBounds.maxx = psTransform->dfMaxX;
     146           4 :     globalBounds.maxy = psTransform->dfMaxY;
     147           4 :     psTransform->hQuadTree = CPLQuadTreeCreateEx(
     148             :         &globalBounds, GDALGeoLocQuadTreeGetFeatureBounds, psTransform);
     149             : 
     150           4 :     CPLQuadTreeForceUseOfSubNodes(psTransform->hQuadTree);
     151             : 
     152        1066 :     for (size_t i = 0; i < nExtendedXYCount; i++)
     153             :     {
     154             :         double x0, y0, x1, y1, x2, y2, x3, y3;
     155        1062 :         if (!GDALGeoLocQuadTreeGetFeatureCorners(psTransform, i, x0, y0, x1, y1,
     156             :                                                  x2, y2, x3, y3))
     157             :         {
     158           0 :             continue;
     159             :         }
     160             : 
     161             :         // Skip too large geometries (typically at very high latitudes)
     162             :         // that would fill too many nodes in the quadtree
     163        1062 :         if (psTransform->bGeographicSRSWithMinus180Plus180LongRange &&
     164        1062 :             (std::fabs(x0) > 170 || std::fabs(x1) > 170 ||
     165        1062 :              std::fabs(x2) > 170 || std::fabs(x3) > 170) &&
     166           0 :             (std::fabs(x1 - x0) > 180 || std::fabs(x2 - x0) > 180 ||
     167           0 :              std::fabs(x3 - x0) > 180) &&
     168           0 :             !(std::fabs(x0) > 170 && std::fabs(x1) > 170 &&
     169           0 :               std::fabs(x2) > 170 && std::fabs(x3) > 170))
     170             :         {
     171           0 :             continue;
     172             :         }
     173             : 
     174        1062 :         CPLQuadTreeInsert(psTransform->hQuadTree,
     175             :                           reinterpret_cast<void *>(static_cast<uintptr_t>(i)));
     176             : 
     177             :         // For a geometry crossing the antimeridian, we've insert before
     178             :         // the "version" around -180 deg. Insert its corresponding version
     179             :         // around +180 deg.
     180        1062 :         if (psTransform->bGeographicSRSWithMinus180Plus180LongRange &&
     181        1062 :             std::fabs(x0) > 170 && std::fabs(x1) > 170 && std::fabs(x2) > 170 &&
     182           0 :             std::fabs(x3) > 170 &&
     183           0 :             (std::fabs(x1 - x0) > 180 || std::fabs(x2 - x0) > 180 ||
     184           0 :              std::fabs(x3 - x0) > 180))
     185             :         {
     186           0 :             CPLQuadTreeInsert(psTransform->hQuadTree,
     187             :                               reinterpret_cast<void *>(static_cast<uintptr_t>(
     188           0 :                                   i | BIT_IDX_RANGE_180_SET)));
     189             :         }
     190             :     }
     191             : 
     192           4 :     CPLDebug("GEOLOC", "End of quadtree construction");
     193             : 
     194             : #ifdef DEBUG_GEOLOC
     195             :     int nFeatureCount = 0;
     196             :     int nNodeCount = 0;
     197             :     int nMaxDepth = 0;
     198             :     int nMaxBucketCapacity = 0;
     199             :     CPLQuadTreeGetStats(psTransform->hQuadTree, &nFeatureCount, &nNodeCount,
     200             :                         &nMaxDepth, &nMaxBucketCapacity);
     201             :     CPLDebug("GEOLOC", "Quadtree stats:");
     202             :     CPLDebug("GEOLOC", "  nFeatureCount = %d", nFeatureCount);
     203             :     CPLDebug("GEOLOC", "  nNodeCount = %d", nNodeCount);
     204             :     CPLDebug("GEOLOC", "  nMaxDepth = %d", nMaxDepth);
     205             :     CPLDebug("GEOLOC", "  nMaxBucketCapacity = %d", nMaxBucketCapacity);
     206             : #endif
     207             : 
     208           4 :     return true;
     209             : }
     210             : 
     211             : /************************************************************************/
     212             : /*                  GDALGeoLocInverseTransformQuadtree()                */
     213             : /************************************************************************/
     214             : 
     215          24 : void GDALGeoLocInverseTransformQuadtree(
     216             :     const GDALGeoLocTransformInfo *psTransform, int nPointCount, double *padfX,
     217             :     double *padfY, int *panSuccess)
     218             : {
     219             :     // Keep those objects in this outer scope, so they are re-used, to
     220             :     // save memory allocations.
     221          48 :     OGRPoint oPoint;
     222          48 :     OGRLinearRing oRing;
     223          24 :     oRing.setNumPoints(5);
     224             : 
     225          24 :     const double dfGeorefConventionOffset =
     226          24 :         psTransform->bOriginIsTopLeftCorner ? 0 : 0.5;
     227             : 
     228          48 :     for (int i = 0; i < nPointCount; i++)
     229             :     {
     230          24 :         if (padfX[i] == HUGE_VAL || padfY[i] == HUGE_VAL)
     231             :         {
     232           0 :             panSuccess[i] = FALSE;
     233           0 :             continue;
     234             :         }
     235             : 
     236          24 :         if (psTransform->bSwapXY)
     237             :         {
     238           0 :             std::swap(padfX[i], padfY[i]);
     239             :         }
     240             : 
     241          24 :         const double dfGeoX = padfX[i];
     242          24 :         const double dfGeoY = padfY[i];
     243             : 
     244          24 :         bool bDone = false;
     245             : 
     246             :         CPLRectObj aoi;
     247          24 :         aoi.minx = dfGeoX;
     248          24 :         aoi.maxx = dfGeoX;
     249          24 :         aoi.miny = dfGeoY;
     250          24 :         aoi.maxy = dfGeoY;
     251          24 :         int nFeatureCount = 0;
     252             :         void **pahFeatures =
     253          24 :             CPLQuadTreeSearch(psTransform->hQuadTree, &aoi, &nFeatureCount);
     254          24 :         if (nFeatureCount != 0)
     255             :         {
     256          24 :             oPoint.setX(dfGeoX);
     257          24 :             oPoint.setY(dfGeoY);
     258          24 :             for (int iFeat = 0; iFeat < nFeatureCount; iFeat++)
     259             :             {
     260          24 :                 size_t nIdx = reinterpret_cast<size_t>(pahFeatures[iFeat]);
     261          24 :                 const bool bXRefAt180 = (nIdx >> BIT_IDX_RANGE_180) != 0;
     262             :                 // Clear that bit.
     263          24 :                 nIdx &= ~BIT_IDX_RANGE_180_SET;
     264             : 
     265          24 :                 double x0 = 0, y0 = 0, x1 = 0, y1 = 0, x2 = 0, y2 = 0, x3 = 0,
     266          24 :                        y3 = 0;
     267          24 :                 GDALGeoLocQuadTreeGetFeatureCorners(psTransform, nIdx, x0, y0,
     268             :                                                     x2, y2, x1, y1, x3, y3);
     269             : 
     270          24 :                 if (psTransform->bGeographicSRSWithMinus180Plus180LongRange &&
     271          24 :                     std::fabs(x0) > 170 && std::fabs(x1) > 170 &&
     272           0 :                     std::fabs(x2) > 170 && std::fabs(x3) > 170 &&
     273           0 :                     (std::fabs(x1 - x0) > 180 || std::fabs(x2 - x0) > 180 ||
     274           0 :                      std::fabs(x3 - x0) > 180))
     275             :                 {
     276           0 :                     const double dfXRef = bXRefAt180 ? 180 : -180;
     277           0 :                     x0 = ShiftGeoX(psTransform, dfXRef, x0);
     278           0 :                     x1 = ShiftGeoX(psTransform, dfXRef, x1);
     279           0 :                     x2 = ShiftGeoX(psTransform, dfXRef, x2);
     280           0 :                     x3 = ShiftGeoX(psTransform, dfXRef, x3);
     281             :                 }
     282             : 
     283          24 :                 oRing.setPoint(0, x0, y0);
     284          24 :                 oRing.setPoint(1, x2, y2);
     285          24 :                 oRing.setPoint(2, x3, y3);
     286          24 :                 oRing.setPoint(3, x1, y1);
     287          24 :                 oRing.setPoint(4, x0, y0);
     288             : 
     289          32 :                 if (oRing.isPointInRing(&oPoint) ||
     290           8 :                     oRing.isPointOnRingBoundary(&oPoint))
     291             :                 {
     292          24 :                     const size_t nExtendedWidth =
     293          48 :                         psTransform->nGeoLocXSize +
     294          24 :                         (psTransform->bOriginIsTopLeftCorner ? 0 : 1);
     295          24 :                     double dfX = static_cast<double>(nIdx % nExtendedWidth);
     296             :                     // store the result as int, and then cast to double, to
     297             :                     // avoid Coverity Scan warning about
     298             :                     // UNINTENDED_INTEGER_DIVISION
     299          24 :                     const size_t nY = nIdx / nExtendedWidth;
     300          24 :                     double dfY = static_cast<double>(nY);
     301          24 :                     if (!psTransform->bOriginIsTopLeftCorner)
     302             :                     {
     303          12 :                         dfX -= 1.0;
     304          12 :                         dfY -= 1.0;
     305             :                     }
     306          24 :                     GDALInverseBilinearInterpolation(dfGeoX, dfGeoY, x0, y0, x1,
     307             :                                                      y1, x2, y2, x3, y3, dfX,
     308             :                                                      dfY);
     309             : 
     310          24 :                     dfX = (dfX + dfGeorefConventionOffset) *
     311          24 :                               psTransform->dfPIXEL_STEP +
     312          24 :                           psTransform->dfPIXEL_OFFSET;
     313          24 :                     dfY = (dfY + dfGeorefConventionOffset) *
     314          24 :                               psTransform->dfLINE_STEP +
     315          24 :                           psTransform->dfLINE_OFFSET;
     316             : 
     317          24 :                     bDone = true;
     318          24 :                     panSuccess[i] = TRUE;
     319          24 :                     padfX[i] = dfX;
     320          24 :                     padfY[i] = dfY;
     321          24 :                     break;
     322             :                 }
     323             :             }
     324             :         }
     325          24 :         CPLFree(pahFeatures);
     326             : 
     327          24 :         if (!bDone)
     328             :         {
     329           0 :             panSuccess[i] = FALSE;
     330           0 :             padfX[i] = HUGE_VAL;
     331           0 :             padfY[i] = HUGE_VAL;
     332             :         }
     333             :     }
     334          24 : }

Generated by: LCOV version 1.14