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-11-21 22:18:42 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             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "gdalgeoloc.h"
      15             : #include "gdalgeolocquadtree.h"
      16             : 
      17             : #include "cpl_quad_tree.h"
      18             : 
      19             : #include "ogr_geometry.h"
      20             : 
      21             : #include <algorithm>
      22             : #include <cstddef>
      23             : #include <limits>
      24             : 
      25             : /************************************************************************/
      26             : /*               GDALGeoLocQuadTreeGetFeatureCorners()                  */
      27             : /************************************************************************/
      28             : 
      29             : static bool
      30        3200 : GDALGeoLocQuadTreeGetFeatureCorners(const GDALGeoLocTransformInfo *psTransform,
      31             :                                     size_t nIdx, double &x0, double &y0,
      32             :                                     double &x1, double &y1, double &x2,
      33             :                                     double &y2, double &x3, double &y3)
      34             : {
      35        6400 :     const size_t nExtendedWidth = psTransform->nGeoLocXSize +
      36        3200 :                                   (psTransform->bOriginIsTopLeftCorner ? 0 : 1);
      37        3200 :     int nX = static_cast<int>(nIdx % nExtendedWidth);
      38        3200 :     int nY = static_cast<int>(nIdx / nExtendedWidth);
      39             : 
      40        3200 :     if (!psTransform->bOriginIsTopLeftCorner)
      41             :     {
      42        1681 :         nX--;
      43        1681 :         nY--;
      44             :     }
      45             : 
      46        3200 :     return GDALGeoLocExtractSquare(psTransform, static_cast<int>(nX),
      47             :                                    static_cast<int>(nY), x0, y0, x1, y1, x2, y2,
      48        3200 :                                    x3, y3);
      49             : }
      50             : 
      51             : /************************************************************************/
      52             : /*               GDALGeoLocQuadTreeGetFeatureBounds()                   */
      53             : /************************************************************************/
      54             : 
      55             : constexpr size_t BIT_IDX_RANGE_180 = 8 * sizeof(size_t) - 1;
      56             : constexpr size_t BIT_IDX_RANGE_180_SET = static_cast<size_t>(1)
      57             :                                          << BIT_IDX_RANGE_180;
      58             : 
      59             : // Callback used by quadtree to retrieve the bounding box, in georeferenced
      60             : // space, of a cell of the geolocation array.
      61        2114 : static void GDALGeoLocQuadTreeGetFeatureBounds(const void *hFeature,
      62             :                                                void *pUserData,
      63             :                                                CPLRectObj *pBounds)
      64             : {
      65        2114 :     const GDALGeoLocTransformInfo *psTransform =
      66             :         static_cast<const GDALGeoLocTransformInfo *>(pUserData);
      67        2114 :     size_t nIdx = reinterpret_cast<size_t>(hFeature);
      68             :     // Most significant bit set means that geometries crossing the antimeridian
      69             :     // should have their longitudes lower or greater than 180 deg.
      70        2114 :     const bool bXRefAt180 = (nIdx >> BIT_IDX_RANGE_180) != 0;
      71             :     // Clear that bit.
      72        2114 :     nIdx &= ~BIT_IDX_RANGE_180_SET;
      73             : 
      74        2114 :     double x0 = 0, y0 = 0, x1 = 0, y1 = 0, x2 = 0, y2 = 0, x3 = 0, y3 = 0;
      75        2114 :     GDALGeoLocQuadTreeGetFeatureCorners(psTransform, nIdx, x0, y0, x1, y1, x2,
      76             :                                         y2, x3, y3);
      77             : 
      78        2114 :     if (psTransform->bGeographicSRSWithMinus180Plus180LongRange &&
      79        2114 :         std::fabs(x0) > 170 && std::fabs(x1) > 170 && std::fabs(x2) > 170 &&
      80           0 :         std::fabs(x3) > 170 &&
      81           0 :         (std::fabs(x1 - x0) > 180 || std::fabs(x2 - x0) > 180 ||
      82           0 :          std::fabs(x3 - x0) > 180))
      83             :     {
      84           0 :         const double dfXRef = bXRefAt180 ? 180 : -180;
      85           0 :         x0 = ShiftGeoX(psTransform, dfXRef, x0);
      86           0 :         x1 = ShiftGeoX(psTransform, dfXRef, x1);
      87           0 :         x2 = ShiftGeoX(psTransform, dfXRef, x2);
      88           0 :         x3 = ShiftGeoX(psTransform, dfXRef, x3);
      89             :     }
      90        2114 :     pBounds->minx = std::min(std::min(x0, x1), std::min(x2, x3));
      91        2114 :     pBounds->miny = std::min(std::min(y0, y1), std::min(y2, y3));
      92        2114 :     pBounds->maxx = std::max(std::max(x0, x1), std::max(x2, x3));
      93        2114 :     pBounds->maxy = std::max(std::max(y0, y1), std::max(y2, y3));
      94        2114 : }
      95             : 
      96             : /************************************************************************/
      97             : /*                      GDALGeoLocBuildQuadTree()                       */
      98             : /************************************************************************/
      99             : 
     100           4 : bool GDALGeoLocBuildQuadTree(GDALGeoLocTransformInfo *psTransform)
     101             : {
     102             :     // For the pixel-center convention, insert a "virtual" row and column
     103             :     // at top and left of the geoloc array.
     104           4 :     const int nExtraPixel = psTransform->bOriginIsTopLeftCorner ? 0 : 1;
     105             : 
     106          12 :     if (psTransform->nGeoLocXSize > INT_MAX - nExtraPixel ||
     107           8 :         psTransform->nGeoLocYSize > INT_MAX - nExtraPixel ||
     108             :         // The >> 1 shift is because we need to reserve the most-significant-bit
     109             :         // for the second 'version' of anti-meridian crossing quadrilaterals.
     110             :         // See below
     111           4 :         static_cast<size_t>(psTransform->nGeoLocXSize + nExtraPixel) >
     112           4 :             (std::numeric_limits<size_t>::max() >> 1) /
     113           4 :                 static_cast<size_t>(psTransform->nGeoLocYSize + nExtraPixel))
     114             :     {
     115           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Too big geolocation array");
     116           0 :         return false;
     117             :     }
     118             : 
     119           4 :     const int nExtendedWidth = psTransform->nGeoLocXSize + nExtraPixel;
     120           4 :     const int nExtendedHeight = psTransform->nGeoLocYSize + nExtraPixel;
     121           4 :     const size_t nExtendedXYCount =
     122           4 :         static_cast<size_t>(nExtendedWidth) * nExtendedHeight;
     123             : 
     124           4 :     CPLDebug("GEOLOC", "Start quadtree construction");
     125             : 
     126             :     CPLRectObj globalBounds;
     127           4 :     globalBounds.minx = psTransform->dfMinX;
     128           4 :     globalBounds.miny = psTransform->dfMinY;
     129           4 :     globalBounds.maxx = psTransform->dfMaxX;
     130           4 :     globalBounds.maxy = psTransform->dfMaxY;
     131           4 :     psTransform->hQuadTree = CPLQuadTreeCreateEx(
     132             :         &globalBounds, GDALGeoLocQuadTreeGetFeatureBounds, psTransform);
     133             : 
     134           4 :     CPLQuadTreeForceUseOfSubNodes(psTransform->hQuadTree);
     135             : 
     136        1066 :     for (size_t i = 0; i < nExtendedXYCount; i++)
     137             :     {
     138             :         double x0, y0, x1, y1, x2, y2, x3, y3;
     139        1062 :         if (!GDALGeoLocQuadTreeGetFeatureCorners(psTransform, i, x0, y0, x1, y1,
     140             :                                                  x2, y2, x3, y3))
     141             :         {
     142           0 :             continue;
     143             :         }
     144             : 
     145             :         // Skip too large geometries (typically at very high latitudes)
     146             :         // that would fill too many nodes in the quadtree
     147        1062 :         if (psTransform->bGeographicSRSWithMinus180Plus180LongRange &&
     148        1062 :             (std::fabs(x0) > 170 || std::fabs(x1) > 170 ||
     149        1062 :              std::fabs(x2) > 170 || std::fabs(x3) > 170) &&
     150           0 :             (std::fabs(x1 - x0) > 180 || std::fabs(x2 - x0) > 180 ||
     151           0 :              std::fabs(x3 - x0) > 180) &&
     152           0 :             !(std::fabs(x0) > 170 && std::fabs(x1) > 170 &&
     153           0 :               std::fabs(x2) > 170 && std::fabs(x3) > 170))
     154             :         {
     155           0 :             continue;
     156             :         }
     157             : 
     158        1062 :         CPLQuadTreeInsert(psTransform->hQuadTree,
     159             :                           reinterpret_cast<void *>(static_cast<uintptr_t>(i)));
     160             : 
     161             :         // For a geometry crossing the antimeridian, we've insert before
     162             :         // the "version" around -180 deg. Insert its corresponding version
     163             :         // around +180 deg.
     164        1062 :         if (psTransform->bGeographicSRSWithMinus180Plus180LongRange &&
     165        1062 :             std::fabs(x0) > 170 && std::fabs(x1) > 170 && std::fabs(x2) > 170 &&
     166           0 :             std::fabs(x3) > 170 &&
     167           0 :             (std::fabs(x1 - x0) > 180 || std::fabs(x2 - x0) > 180 ||
     168           0 :              std::fabs(x3 - x0) > 180))
     169             :         {
     170           0 :             CPLQuadTreeInsert(psTransform->hQuadTree,
     171             :                               reinterpret_cast<void *>(static_cast<uintptr_t>(
     172           0 :                                   i | BIT_IDX_RANGE_180_SET)));
     173             :         }
     174             :     }
     175             : 
     176           4 :     CPLDebug("GEOLOC", "End of quadtree construction");
     177             : 
     178             : #ifdef DEBUG_GEOLOC
     179             :     int nFeatureCount = 0;
     180             :     int nNodeCount = 0;
     181             :     int nMaxDepth = 0;
     182             :     int nMaxBucketCapacity = 0;
     183             :     CPLQuadTreeGetStats(psTransform->hQuadTree, &nFeatureCount, &nNodeCount,
     184             :                         &nMaxDepth, &nMaxBucketCapacity);
     185             :     CPLDebug("GEOLOC", "Quadtree stats:");
     186             :     CPLDebug("GEOLOC", "  nFeatureCount = %d", nFeatureCount);
     187             :     CPLDebug("GEOLOC", "  nNodeCount = %d", nNodeCount);
     188             :     CPLDebug("GEOLOC", "  nMaxDepth = %d", nMaxDepth);
     189             :     CPLDebug("GEOLOC", "  nMaxBucketCapacity = %d", nMaxBucketCapacity);
     190             : #endif
     191             : 
     192           4 :     return true;
     193             : }
     194             : 
     195             : /************************************************************************/
     196             : /*                  GDALGeoLocInverseTransformQuadtree()                */
     197             : /************************************************************************/
     198             : 
     199          24 : void GDALGeoLocInverseTransformQuadtree(
     200             :     const GDALGeoLocTransformInfo *psTransform, int nPointCount, double *padfX,
     201             :     double *padfY, int *panSuccess)
     202             : {
     203             :     // Keep those objects in this outer scope, so they are re-used, to
     204             :     // save memory allocations.
     205          48 :     OGRPoint oPoint;
     206          48 :     OGRLinearRing oRing;
     207          24 :     oRing.setNumPoints(5);
     208             : 
     209          24 :     const double dfGeorefConventionOffset =
     210          24 :         psTransform->bOriginIsTopLeftCorner ? 0 : 0.5;
     211             : 
     212          48 :     for (int i = 0; i < nPointCount; i++)
     213             :     {
     214          24 :         if (padfX[i] == HUGE_VAL || padfY[i] == HUGE_VAL)
     215             :         {
     216           0 :             panSuccess[i] = FALSE;
     217           0 :             continue;
     218             :         }
     219             : 
     220          24 :         if (psTransform->bSwapXY)
     221             :         {
     222           0 :             std::swap(padfX[i], padfY[i]);
     223             :         }
     224             : 
     225          24 :         const double dfGeoX = padfX[i];
     226          24 :         const double dfGeoY = padfY[i];
     227             : 
     228          24 :         bool bDone = false;
     229             : 
     230             :         CPLRectObj aoi;
     231          24 :         aoi.minx = dfGeoX;
     232          24 :         aoi.maxx = dfGeoX;
     233          24 :         aoi.miny = dfGeoY;
     234          24 :         aoi.maxy = dfGeoY;
     235          24 :         int nFeatureCount = 0;
     236             :         void **pahFeatures =
     237          24 :             CPLQuadTreeSearch(psTransform->hQuadTree, &aoi, &nFeatureCount);
     238          24 :         if (nFeatureCount != 0)
     239             :         {
     240          24 :             oPoint.setX(dfGeoX);
     241          24 :             oPoint.setY(dfGeoY);
     242          24 :             for (int iFeat = 0; iFeat < nFeatureCount; iFeat++)
     243             :             {
     244          24 :                 size_t nIdx = reinterpret_cast<size_t>(pahFeatures[iFeat]);
     245          24 :                 const bool bXRefAt180 = (nIdx >> BIT_IDX_RANGE_180) != 0;
     246             :                 // Clear that bit.
     247          24 :                 nIdx &= ~BIT_IDX_RANGE_180_SET;
     248             : 
     249          24 :                 double x0 = 0, y0 = 0, x1 = 0, y1 = 0, x2 = 0, y2 = 0, x3 = 0,
     250          24 :                        y3 = 0;
     251          24 :                 GDALGeoLocQuadTreeGetFeatureCorners(psTransform, nIdx, x0, y0,
     252             :                                                     x2, y2, x1, y1, x3, y3);
     253             : 
     254          24 :                 if (psTransform->bGeographicSRSWithMinus180Plus180LongRange &&
     255          24 :                     std::fabs(x0) > 170 && std::fabs(x1) > 170 &&
     256           0 :                     std::fabs(x2) > 170 && std::fabs(x3) > 170 &&
     257           0 :                     (std::fabs(x1 - x0) > 180 || std::fabs(x2 - x0) > 180 ||
     258           0 :                      std::fabs(x3 - x0) > 180))
     259             :                 {
     260           0 :                     const double dfXRef = bXRefAt180 ? 180 : -180;
     261           0 :                     x0 = ShiftGeoX(psTransform, dfXRef, x0);
     262           0 :                     x1 = ShiftGeoX(psTransform, dfXRef, x1);
     263           0 :                     x2 = ShiftGeoX(psTransform, dfXRef, x2);
     264           0 :                     x3 = ShiftGeoX(psTransform, dfXRef, x3);
     265             :                 }
     266             : 
     267          24 :                 oRing.setPoint(0, x0, y0);
     268          24 :                 oRing.setPoint(1, x2, y2);
     269          24 :                 oRing.setPoint(2, x3, y3);
     270          24 :                 oRing.setPoint(3, x1, y1);
     271          24 :                 oRing.setPoint(4, x0, y0);
     272             : 
     273          32 :                 if (oRing.isPointInRing(&oPoint) ||
     274           8 :                     oRing.isPointOnRingBoundary(&oPoint))
     275             :                 {
     276          24 :                     const size_t nExtendedWidth =
     277          48 :                         psTransform->nGeoLocXSize +
     278          24 :                         (psTransform->bOriginIsTopLeftCorner ? 0 : 1);
     279          24 :                     double dfX = static_cast<double>(nIdx % nExtendedWidth);
     280             :                     // store the result as int, and then cast to double, to
     281             :                     // avoid Coverity Scan warning about
     282             :                     // UNINTENDED_INTEGER_DIVISION
     283          24 :                     const size_t nY = nIdx / nExtendedWidth;
     284          24 :                     double dfY = static_cast<double>(nY);
     285          24 :                     if (!psTransform->bOriginIsTopLeftCorner)
     286             :                     {
     287          12 :                         dfX -= 1.0;
     288          12 :                         dfY -= 1.0;
     289             :                     }
     290          24 :                     GDALInverseBilinearInterpolation(dfGeoX, dfGeoY, x0, y0, x1,
     291             :                                                      y1, x2, y2, x3, y3, dfX,
     292             :                                                      dfY);
     293             : 
     294          24 :                     dfX = (dfX + dfGeorefConventionOffset) *
     295          24 :                               psTransform->dfPIXEL_STEP +
     296          24 :                           psTransform->dfPIXEL_OFFSET;
     297          24 :                     dfY = (dfY + dfGeorefConventionOffset) *
     298          24 :                               psTransform->dfLINE_STEP +
     299          24 :                           psTransform->dfLINE_OFFSET;
     300             : 
     301          24 :                     bDone = true;
     302          24 :                     panSuccess[i] = TRUE;
     303          24 :                     padfX[i] = dfX;
     304          24 :                     padfY[i] = dfY;
     305          24 :                     break;
     306             :                 }
     307             :             }
     308             :         }
     309          24 :         CPLFree(pahFeatures);
     310             : 
     311          24 :         if (!bDone)
     312             :         {
     313           0 :             panSuccess[i] = FALSE;
     314           0 :             padfX[i] = HUGE_VAL;
     315           0 :             padfY[i] = HUGE_VAL;
     316             :         }
     317             :     }
     318          24 : }

Generated by: LCOV version 1.14