LCOV - code coverage report
Current view: top level - ogr - ograssemblepolygon.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 104 115 90.4 %
Date: 2026-02-01 11:59:10 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       30186 : static bool CheckPoints(const OGRLineString *poLine1, int iPoint1,
      35             :                         const OGRLineString *poLine2, int iPoint2,
      36             :                         double *pdfDistance)
      37             : 
      38             : {
      39       30186 :     if (pdfDistance == nullptr || *pdfDistance == 0)
      40             :     {
      41       37633 :         if (poLine1->getX(iPoint1) == poLine2->getX(iPoint2) &&
      42        7691 :             poLine1->getY(iPoint1) == poLine2->getY(iPoint2))
      43             :         {
      44        5634 :             if (pdfDistance)
      45        5198 :                 *pdfDistance = 0.0;
      46        5634 :             return true;
      47             :         }
      48       24308 :         return false;
      49             :     }
      50             : 
      51             :     const double dfDeltaX =
      52         244 :         std::abs(poLine1->getX(iPoint1) - poLine2->getX(iPoint2));
      53             : 
      54         244 :     if (dfDeltaX > *pdfDistance)
      55          30 :         return false;
      56             : 
      57             :     const double dfDeltaY =
      58         214 :         std::abs(poLine1->getY(iPoint1) - poLine2->getY(iPoint2));
      59             : 
      60         214 :     if (dfDeltaY > *pdfDistance)
      61           6 :         return false;
      62             : 
      63         208 :     const double dfDistance = sqrt(dfDeltaX * dfDeltaX + dfDeltaY * dfDeltaY);
      64             : 
      65         208 :     if (dfDistance < *pdfDistance)
      66             :     {
      67         208 :         *pdfDistance = dfDistance;
      68         208 :         return true;
      69             :     }
      70             : 
      71           0 :     return false;
      72             : }
      73             : 
      74             : /************************************************************************/
      75             : /*                           AddEdgeToRing()                            */
      76             : /************************************************************************/
      77             : 
      78        2868 : static void AddEdgeToRing(OGRLinearRing *poRing, const OGRLineString *poLine,
      79             :                           bool bReverse, double dfTolerance)
      80             : 
      81             : {
      82             :     /* -------------------------------------------------------------------- */
      83             :     /*      Establish order and range of traverse.                          */
      84             :     /* -------------------------------------------------------------------- */
      85        2868 :     const int nVertToAdd = poLine->getNumPoints();
      86             : 
      87        2868 :     int iStart = bReverse ? nVertToAdd - 1 : 0;
      88        2868 :     const int iEnd = bReverse ? 0 : nVertToAdd - 1;
      89        2868 :     const int iStep = bReverse ? -1 : 1;
      90             : 
      91             :     /* -------------------------------------------------------------------- */
      92             :     /*      Should we skip a repeating vertex?                              */
      93             :     /* -------------------------------------------------------------------- */
      94        5348 :     if (poRing->getNumPoints() > 0 &&
      95        2480 :         CheckPoints(poRing, poRing->getNumPoints() - 1, poLine, iStart,
      96             :                     &dfTolerance))
      97             :     {
      98        2480 :         iStart += iStep;
      99             :     }
     100             : 
     101        2868 :     poRing->addSubLineString(poLine, iStart, iEnd);
     102        2868 : }
     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 or multipolygon.
     120             :  *
     121             :  */
     122             : 
     123         375 : OGRGeometryH OGRBuildPolygonFromEdges(OGRGeometryH hLines,
     124             :                                       CPL_UNUSED int bBestEffort,
     125             :                                       int bAutoClose, double dfTolerance,
     126             :                                       OGRErr *peErr)
     127             : 
     128             : {
     129         375 :     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         375 :     OGRGeometry *poGeom = OGRGeometry::FromHandle(hLines);
     141         375 :     if (wkbFlatten(poGeom->getGeometryType()) == wkbGeometryCollection)
     142             :     {
     143        3233 :         for (auto &&poMember : poGeom->toGeometryCollection())
     144             :         {
     145        2862 :             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           3 :     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         373 :     bool bSuccess = true;
     168         373 :     const OGRGeometryCollection *poLines = poGeom->toGeometryCollection();
     169         746 :     std::vector<std::unique_ptr<OGRGeometry>> apoPolys;
     170             : 
     171             :     /* -------------------------------------------------------------------- */
     172             :     /*      Setup array of line markers indicating if they have been        */
     173             :     /*      added to a ring yet.                                            */
     174             :     /* -------------------------------------------------------------------- */
     175         373 :     const int nEdges = poLines->getNumGeometries();
     176         373 :     std::list<const OGRLineString *> oListEdges;
     177        3243 :     for (int i = 0; i < nEdges; i++)
     178             :     {
     179             :         const OGRLineString *poLine =
     180        2870 :             poLines->getGeometryRef(i)->toLineString();
     181        2870 :         if (poLine->getNumPoints() >= 2)
     182             :         {
     183        2868 :             oListEdges.push_back(poLine);
     184             :         }
     185             :     }
     186             : 
     187             :     /* ==================================================================== */
     188             :     /*      Loop generating rings.                                          */
     189             :     /* ==================================================================== */
     190         761 :     while (!oListEdges.empty())
     191             :     {
     192             :         /* --------------------------------------------------------------------
     193             :          */
     194             :         /*      Find the first unconsumed edge. */
     195             :         /* --------------------------------------------------------------------
     196             :          */
     197         388 :         const OGRLineString *poLine = oListEdges.front();
     198         388 :         oListEdges.erase(oListEdges.begin());
     199             : 
     200             :         /* --------------------------------------------------------------------
     201             :          */
     202             :         /*      Start a new ring, copying in the current line directly */
     203             :         /* --------------------------------------------------------------------
     204             :          */
     205         776 :         auto poRing = std::make_unique<OGRLinearRing>();
     206             : 
     207         388 :         AddEdgeToRing(poRing.get(), poLine, FALSE, 0);
     208             : 
     209             :         /* ====================================================================
     210             :          */
     211             :         /*      Loop adding edges to this ring until we make a whole pass */
     212             :         /*      within finding anything to add. */
     213             :         /* ====================================================================
     214             :          */
     215         388 :         bool bWorkDone = true;
     216         388 :         double dfBestDist = dfTolerance;
     217             : 
     218        5348 :         while (!CheckPoints(poRing.get(), 0, poRing.get(),
     219        2868 :                             poRing->getNumPoints() - 1, nullptr) &&
     220        2868 :                !oListEdges.empty() && bWorkDone)
     221             :         {
     222        2480 :             bool bReverse = false;
     223             : 
     224        2480 :             bWorkDone = false;
     225        2480 :             dfBestDist = dfTolerance;
     226             : 
     227             :             // We consider linking the end to the beginning.  If this is
     228             :             // closer than any other option we will just close the loop.
     229             : 
     230             :             // CheckPoints(poRing, 0, poRing, poRing->getNumPoints()-1,
     231             :             //             &dfBestDist);
     232             : 
     233             :             // Find unused edge with end point closest to our loose end.
     234        2480 :             const OGRLineString *poBestEdge = nullptr;
     235        2480 :             std::list<const OGRLineString *>::iterator oBestIter;
     236       12179 :             for (auto oIter = oListEdges.begin(); oIter != oListEdges.end();
     237        9699 :                  ++oIter)
     238             :             {
     239       12167 :                 poLine = *oIter;
     240             : 
     241       12167 :                 if (CheckPoints(poLine, 0, poRing.get(),
     242       12167 :                                 poRing->getNumPoints() - 1, &dfBestDist))
     243             :                 {
     244        2130 :                     poBestEdge = poLine;
     245        2130 :                     oBestIter = oIter;
     246        2130 :                     bReverse = false;
     247             :                 }
     248       12167 :                 if (CheckPoints(poLine, poLine->getNumPoints() - 1,
     249       12167 :                                 poRing.get(), poRing->getNumPoints() - 1,
     250             :                                 &dfBestDist))
     251             :                 {
     252         350 :                     poBestEdge = poLine;
     253         350 :                     oBestIter = oIter;
     254         350 :                     bReverse = true;
     255             :                 }
     256             : 
     257             :                 // If we found an exact match, jump now.
     258       12167 :                 if (dfBestDist == 0.0 && poBestEdge != nullptr)
     259        2468 :                     break;
     260             :             }
     261             : 
     262             :             // We found one within tolerance - add it.
     263        2480 :             if (poBestEdge)
     264             :             {
     265        2480 :                 AddEdgeToRing(poRing.get(), poBestEdge, bReverse, dfTolerance);
     266             : 
     267        2480 :                 oListEdges.erase(oBestIter);
     268        2480 :                 bWorkDone = true;
     269             :             }
     270             :         }
     271             : 
     272             :         /* --------------------------------------------------------------------
     273             :          */
     274             :         /*      Did we fail to complete the ring? */
     275             :         /* --------------------------------------------------------------------
     276             :          */
     277         388 :         dfBestDist = dfTolerance;
     278             : 
     279         388 :         if (!CheckPoints(poRing.get(), 0, poRing.get(),
     280         388 :                          poRing->getNumPoints() - 1, &dfBestDist))
     281             :         {
     282           0 :             CPLDebug("OGR",
     283             :                      "Failed to close ring %d.\n"
     284             :                      "End Points are: (%.8f,%.7f) and (%.7f,%.7f)",
     285           0 :                      static_cast<int>(apoPolys.size()), poRing->getX(0),
     286           0 :                      poRing->getY(0), poRing->getX(poRing->getNumPoints() - 1),
     287           0 :                      poRing->getY(poRing->getNumPoints() - 1));
     288             : 
     289           0 :             bSuccess = false;
     290             :         }
     291             : 
     292             :         /* --------------------------------------------------------------------
     293             :          */
     294             :         /*      Do we need to auto-close this ring? */
     295             :         /* --------------------------------------------------------------------
     296             :          */
     297         388 :         dfBestDist = dfTolerance;
     298             : 
     299         388 :         if (bAutoClose)
     300             :         {
     301          58 :             if (!CheckPoints(poRing.get(), 0, poRing.get(),
     302          58 :                              poRing->getNumPoints() - 1, &dfBestDist))
     303             :             {
     304           0 :                 poRing->addPoint(poRing->getX(0), poRing->getY(0),
     305           0 :                                  poRing->getZ(0));
     306             :             }
     307          58 :             else if (!CheckPoints(poRing.get(), 0, poRing.get(),
     308          58 :                                   poRing->getNumPoints() - 1, nullptr))
     309             :             {
     310             :                 // The endpoints are very close but do not exactly match.
     311             :                 // Alter the last one so it is equal to the first, to prevent
     312             :                 // invalid self-intersecting rings.
     313          15 :                 poRing->setPoint(poRing->getNumPoints() - 1, poRing->getX(0),
     314          10 :                                  poRing->getY(0), poRing->getZ(0));
     315             :             }
     316             :         }
     317             : 
     318         388 :         auto poPoly = std::make_unique<OGRPolygon>();
     319         388 :         poPoly->addRing(std::move(poRing));
     320         388 :         apoPolys.push_back(std::move(poPoly));
     321             :     }  // Next ring.
     322             : 
     323         373 :     if (peErr != nullptr)
     324             :     {
     325         372 :         *peErr = bSuccess ? OGRERR_NONE : OGRERR_FAILURE;
     326             :     }
     327             : 
     328         373 :     return OGRGeometry::ToHandle(
     329         746 :         OGRGeometryFactory::organizePolygons(apoPolys).release());
     330             : }

Generated by: LCOV version 1.14