LCOV - code coverage report
Current view: top level - ogr - ograssemblepolygon.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 112 124 90.3 %
Date: 2024-11-21 22:18:42 Functions: 3 3 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  S-57 Reader
       4             :  * Purpose:  Implements polygon assembly from a bunch of arcs.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 1999, Frank Warmerdam
       9             :  * Copyright (c) 2009-2011, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "cpl_port.h"
      15             : #include "ogr_api.h"
      16             : 
      17             : #include <cmath>
      18             : #include <cstddef>
      19             : #include <list>
      20             : #include <vector>
      21             : 
      22             : #include "ogr_core.h"
      23             : #include "ogr_geometry.h"
      24             : #include "cpl_conv.h"
      25             : #include "cpl_error.h"
      26             : 
      27             : /************************************************************************/
      28             : /*                            CheckPoints()                             */
      29             : /*                                                                      */
      30             : /*      Check if two points are closer than the current best            */
      31             : /*      distance.  Update the current best distance if they are.        */
      32             : /************************************************************************/
      33             : 
      34       28692 : static bool CheckPoints(OGRLineString *poLine1, int iPoint1,
      35             :                         OGRLineString *poLine2, int iPoint2,
      36             :                         double *pdfDistance)
      37             : 
      38             : {
      39       28692 :     if (pdfDistance == nullptr || *pdfDistance == 0)
      40             :     {
      41       35781 :         if (poLine1->getX(iPoint1) == poLine2->getX(iPoint2) &&
      42        7291 :             poLine1->getY(iPoint1) == poLine2->getY(iPoint2))
      43             :         {
      44        5332 :             if (pdfDistance)
      45        4954 :                 *pdfDistance = 0.0;
      46        5332 :             return true;
      47             :         }
      48       23158 :         return false;
      49             :     }
      50             : 
      51             :     const double dfDeltaX =
      52         202 :         std::abs(poLine1->getX(iPoint1) - poLine2->getX(iPoint2));
      53             : 
      54         202 :     if (dfDeltaX > *pdfDistance)
      55          30 :         return false;
      56             : 
      57             :     const double dfDeltaY =
      58         172 :         std::abs(poLine1->getY(iPoint1) - poLine2->getY(iPoint2));
      59             : 
      60         172 :     if (dfDeltaY > *pdfDistance)
      61           6 :         return false;
      62             : 
      63         166 :     const double dfDistance = sqrt(dfDeltaX * dfDeltaX + dfDeltaY * dfDeltaY);
      64             : 
      65         166 :     if (dfDistance < *pdfDistance)
      66             :     {
      67         166 :         *pdfDistance = dfDistance;
      68         166 :         return true;
      69             :     }
      70             : 
      71           0 :     return false;
      72             : }
      73             : 
      74             : /************************************************************************/
      75             : /*                           AddEdgeToRing()                            */
      76             : /************************************************************************/
      77             : 
      78        2717 : static void AddEdgeToRing(OGRLinearRing *poRing, OGRLineString *poLine,
      79             :                           bool bReverse, double dfTolerance)
      80             : 
      81             : {
      82             :     /* -------------------------------------------------------------------- */
      83             :     /*      Establish order and range of traverse.                          */
      84             :     /* -------------------------------------------------------------------- */
      85        2717 :     const int nVertToAdd = poLine->getNumPoints();
      86             : 
      87        2717 :     int iStart = bReverse ? nVertToAdd - 1 : 0;
      88        2717 :     const int iEnd = bReverse ? 0 : nVertToAdd - 1;
      89        2717 :     const int iStep = bReverse ? -1 : 1;
      90             : 
      91             :     /* -------------------------------------------------------------------- */
      92             :     /*      Should we skip a repeating vertex?                              */
      93             :     /* -------------------------------------------------------------------- */
      94        5083 :     if (poRing->getNumPoints() > 0 &&
      95        2366 :         CheckPoints(poRing, poRing->getNumPoints() - 1, poLine, iStart,
      96             :                     &dfTolerance))
      97             :     {
      98        2366 :         iStart += iStep;
      99             :     }
     100             : 
     101        2717 :     poRing->addSubLineString(poLine, iStart, iEnd);
     102        2717 : }
     103             : 
     104             : /************************************************************************/
     105             : /*                      OGRBuildPolygonFromEdges()                      */
     106             : /************************************************************************/
     107             : 
     108             : /**
     109             :  * Build a ring from a bunch of arcs.
     110             :  *
     111             :  * @param hLines handle to an OGRGeometryCollection (or OGRMultiLineString)
     112             :  * containing the line string geometries to be built into rings.
     113             :  * @param bBestEffort not yet implemented???.
     114             :  * @param bAutoClose indicates if the ring should be close when first and
     115             :  * last points of the ring are the same.
     116             :  * @param dfTolerance tolerance into which two arcs are considered
     117             :  * close enough to be joined.
     118             :  * @param peErr OGRERR_NONE on success, or OGRERR_FAILURE on failure.
     119             :  * @return a handle to the new geometry, a polygon.
     120             :  *
     121             :  */
     122             : 
     123         343 : OGRGeometryH OGRBuildPolygonFromEdges(OGRGeometryH hLines,
     124             :                                       CPL_UNUSED int bBestEffort,
     125             :                                       int bAutoClose, double dfTolerance,
     126             :                                       OGRErr *peErr)
     127             : 
     128             : {
     129         343 :     if (hLines == nullptr)
     130             :     {
     131           0 :         if (peErr != nullptr)
     132           0 :             *peErr = OGRERR_NONE;
     133           0 :         return nullptr;
     134             :     }
     135             : 
     136             :     /* -------------------------------------------------------------------- */
     137             :     /*      Check for the case of a geometrycollection that can be          */
     138             :     /*      promoted to MultiLineString.                                    */
     139             :     /* -------------------------------------------------------------------- */
     140         343 :     OGRGeometry *poGeom = OGRGeometry::FromHandle(hLines);
     141         343 :     if (wkbFlatten(poGeom->getGeometryType()) == wkbGeometryCollection)
     142             :     {
     143        3055 :         for (auto &&poMember : poGeom->toGeometryCollection())
     144             :         {
     145        2715 :             if (wkbFlatten(poMember->getGeometryType()) != wkbLineString)
     146             :             {
     147           1 :                 if (peErr != nullptr)
     148           1 :                     *peErr = OGRERR_FAILURE;
     149           1 :                 CPLError(CE_Failure, CPLE_NotSupported,
     150             :                          "The geometry collection contains "
     151             :                          "non-line string geometries");
     152           1 :                 return nullptr;
     153             :             }
     154             :         }
     155             :     }
     156           2 :     else if (wkbFlatten(poGeom->getGeometryType()) != wkbMultiLineString)
     157             :     {
     158           1 :         if (peErr != nullptr)
     159           1 :             *peErr = OGRERR_FAILURE;
     160           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     161             :                  "The passed geometry is not an OGRGeometryCollection "
     162             :                  "(or OGRMultiLineString) "
     163             :                  "containing line string geometries");
     164           1 :         return nullptr;
     165             :     }
     166             : 
     167         341 :     bool bSuccess = true;
     168         341 :     OGRGeometryCollection *poLines = poGeom->toGeometryCollection();
     169         682 :     std::vector<OGRLinearRing *> apoRings;
     170             : 
     171             :     /* -------------------------------------------------------------------- */
     172             :     /*      Setup array of line markers indicating if they have been        */
     173             :     /*      added to a ring yet.                                            */
     174             :     /* -------------------------------------------------------------------- */
     175         341 :     const int nEdges = poLines->getNumGeometries();
     176         682 :     std::list<OGRLineString *> oListEdges;
     177        3060 :     for (int i = 0; i < nEdges; i++)
     178             :     {
     179        2719 :         OGRLineString *poLine = poLines->getGeometryRef(i)->toLineString();
     180        2719 :         if (poLine->getNumPoints() >= 2)
     181             :         {
     182        2717 :             oListEdges.push_back(poLine);
     183             :         }
     184             :     }
     185             : 
     186             :     /* ==================================================================== */
     187             :     /*      Loop generating rings.                                          */
     188             :     /* ==================================================================== */
     189         692 :     while (!oListEdges.empty())
     190             :     {
     191             :         /* --------------------------------------------------------------------
     192             :          */
     193             :         /*      Find the first unconsumed edge. */
     194             :         /* --------------------------------------------------------------------
     195             :          */
     196         351 :         OGRLineString *poLine = oListEdges.front();
     197         351 :         oListEdges.erase(oListEdges.begin());
     198             : 
     199             :         /* --------------------------------------------------------------------
     200             :          */
     201             :         /*      Start a new ring, copying in the current line directly */
     202             :         /* --------------------------------------------------------------------
     203             :          */
     204         351 :         OGRLinearRing *poRing = new OGRLinearRing();
     205             : 
     206         351 :         AddEdgeToRing(poRing, poLine, FALSE, 0);
     207             : 
     208             :         /* ====================================================================
     209             :          */
     210             :         /*      Loop adding edges to this ring until we make a whole pass */
     211             :         /*      within finding anything to add. */
     212             :         /* ====================================================================
     213             :          */
     214         351 :         bool bWorkDone = true;
     215         351 :         double dfBestDist = dfTolerance;
     216             : 
     217        2717 :         while (!CheckPoints(poRing, 0, poRing, poRing->getNumPoints() - 1,
     218        2371 :                             nullptr) &&
     219        2717 :                !oListEdges.empty() && bWorkDone)
     220             :         {
     221        2366 :             bool bReverse = false;
     222             : 
     223        2366 :             bWorkDone = false;
     224        2366 :             dfBestDist = dfTolerance;
     225             : 
     226             :             // We consider linking the end to the beginning.  If this is
     227             :             // closer than any other option we will just close the loop.
     228             : 
     229             :             // CheckPoints(poRing, 0, poRing, poRing->getNumPoints()-1,
     230             :             //             &dfBestDist);
     231             : 
     232             :             // Find unused edge with end point closest to our loose end.
     233        2366 :             OGRLineString *poBestEdge = nullptr;
     234        2366 :             std::list<OGRLineString *>::iterator oBestIter;
     235       11604 :             for (auto oIter = oListEdges.begin(); oIter != oListEdges.end();
     236        9238 :                  ++oIter)
     237             :             {
     238       11592 :                 poLine = *oIter;
     239             : 
     240       11592 :                 if (CheckPoints(poLine, 0, poRing, poRing->getNumPoints() - 1,
     241             :                                 &dfBestDist))
     242             :                 {
     243        2031 :                     poBestEdge = poLine;
     244        2031 :                     oBestIter = oIter;
     245        2031 :                     bReverse = false;
     246             :                 }
     247       11592 :                 if (CheckPoints(poLine, poLine->getNumPoints() - 1, poRing,
     248       11592 :                                 poRing->getNumPoints() - 1, &dfBestDist))
     249             :                 {
     250         335 :                     poBestEdge = poLine;
     251         335 :                     oBestIter = oIter;
     252         335 :                     bReverse = true;
     253             :                 }
     254             : 
     255             :                 // If we found an exact match, jump now.
     256       11592 :                 if (dfBestDist == 0.0 && poBestEdge != nullptr)
     257        2354 :                     break;
     258             :             }
     259             : 
     260             :             // We found one within tolerance - add it.
     261        2366 :             if (poBestEdge)
     262             :             {
     263        2366 :                 AddEdgeToRing(poRing, poBestEdge, bReverse, dfTolerance);
     264             : 
     265        2366 :                 oListEdges.erase(oBestIter);
     266        2366 :                 bWorkDone = true;
     267             :             }
     268             :         }
     269             : 
     270             :         /* --------------------------------------------------------------------
     271             :          */
     272             :         /*      Did we fail to complete the ring? */
     273             :         /* --------------------------------------------------------------------
     274             :          */
     275         351 :         dfBestDist = dfTolerance;
     276             : 
     277         351 :         if (!CheckPoints(poRing, 0, poRing, poRing->getNumPoints() - 1,
     278             :                          &dfBestDist))
     279             :         {
     280           0 :             CPLDebug("OGR",
     281             :                      "Failed to close ring %d.\n"
     282             :                      "End Points are: (%.8f,%.7f) and (%.7f,%.7f)",
     283           0 :                      static_cast<int>(apoRings.size()), poRing->getX(0),
     284           0 :                      poRing->getY(0), poRing->getX(poRing->getNumPoints() - 1),
     285           0 :                      poRing->getY(poRing->getNumPoints() - 1));
     286             : 
     287           0 :             bSuccess = false;
     288             :         }
     289             : 
     290             :         /* --------------------------------------------------------------------
     291             :          */
     292             :         /*      Do we need to auto-close this ring? */
     293             :         /* --------------------------------------------------------------------
     294             :          */
     295         351 :         dfBestDist = dfTolerance;
     296             : 
     297         351 :         if (bAutoClose)
     298             :         {
     299          37 :             if (!CheckPoints(poRing, 0, poRing, poRing->getNumPoints() - 1,
     300             :                              &dfBestDist))
     301             :             {
     302           0 :                 poRing->addPoint(poRing->getX(0), poRing->getY(0),
     303             :                                  poRing->getZ(0));
     304             :             }
     305          37 :             else if (!CheckPoints(poRing, 0, poRing, poRing->getNumPoints() - 1,
     306             :                                   nullptr))
     307             :             {
     308             :                 // The endpoints are very close but do not exactly match.
     309             :                 // Alter the last one so it is equal to the first, to prevent
     310             :                 // invalid self-intersecting rings.
     311           5 :                 poRing->setPoint(poRing->getNumPoints() - 1, poRing->getX(0),
     312             :                                  poRing->getY(0), poRing->getZ(0));
     313             :             }
     314             :         }
     315             : 
     316         351 :         apoRings.push_back(poRing);
     317             :     }  // Next ring.
     318             : 
     319             :     /* -------------------------------------------------------------------- */
     320             :     /*      Identify exterior ring - it will be the largest.  #3610         */
     321             :     /* -------------------------------------------------------------------- */
     322         341 :     double maxarea = 0.0;
     323         341 :     int maxring = -1;
     324         341 :     OGREnvelope tenv;
     325             : 
     326         692 :     for (int rn = 0; rn < static_cast<int>(apoRings.size()); ++rn)
     327             :     {
     328         351 :         apoRings[rn]->getEnvelope(&tenv);
     329         351 :         const double tarea = (tenv.MaxX - tenv.MinX) * (tenv.MaxY - tenv.MinY);
     330         351 :         if (tarea > maxarea)
     331             :         {
     332         342 :             maxarea = tarea;
     333         342 :             maxring = rn;
     334             :         }
     335             :     }
     336             : 
     337         341 :     OGRPolygon *poPolygon = new OGRPolygon();
     338             : 
     339         341 :     if (maxring != -1)
     340             :     {
     341         341 :         poPolygon->addRingDirectly(apoRings[maxring]);
     342         692 :         for (int rn = 0; rn < static_cast<int>(apoRings.size()); ++rn)
     343             :         {
     344         351 :             if (rn == maxring)
     345         341 :                 continue;
     346          10 :             poPolygon->addRingDirectly(apoRings[rn]);
     347             :         }
     348             :     }
     349             :     else
     350             :     {
     351           0 :         for (auto &poRing : apoRings)
     352           0 :             delete poRing;
     353             :     }
     354             : 
     355         341 :     if (peErr != nullptr)
     356             :     {
     357         341 :         *peErr = bSuccess ? OGRERR_NONE : OGRERR_FAILURE;
     358             :     }
     359             : 
     360         341 :     return OGRGeometry::ToHandle(poPolygon);
     361             : }

Generated by: LCOV version 1.14