LCOV - code coverage report
Current view: top level - alg/marching_squares - polygon_ring_appender.h (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 152 164 92.7 %
Date: 2026-09-11 05:09:32 Functions: 28 38 73.7 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  Marching square algorithm
       4             :  * Purpose:  Core algorithm implementation for contour line generation.
       5             :  * Author:   Oslandia <infos at oslandia dot com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2018, Oslandia <infos at oslandia dot com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : #ifndef MARCHING_SQUARE_POLYGON_RING_APPENDER_H
      13             : #define MARCHING_SQUARE_POLYGON_RING_APPENDER_H
      14             : 
      15             : #include <vector>
      16             : #include <list>
      17             : #include <map>
      18             : #include <deque>
      19             : #include <cassert>
      20             : #include <iterator>
      21             : #include <memory>
      22             : #include <algorithm>
      23             : 
      24             : #include "cpl_quad_tree.h"
      25             : 
      26             : #include "point.h"
      27             : #include "ogr_api.h"
      28             : #include "ogr_geometry.h"
      29             : 
      30             : namespace marching_squares
      31             : {
      32             : 
      33             : // Receive rings of different levels and organize them
      34             : // into multi-polygons with possible interior rings when requested.
      35             : template <typename PolygonWriter> class PolygonRingAppender
      36             : {
      37             :   private:
      38           0 :     struct Ring
      39             :     {
      40         847 :         Ring() : points(), bbox(), interiorRings()
      41             :         {
      42         847 :         }
      43             : 
      44        1753 :         Ring(const Ring &other) = default;
      45             :         Ring &operator=(const Ring &other) = default;
      46             :         // Declaring the copy operations above suppresses the
      47             :         // implicit move operations, so vector reshuffles and reallocations
      48             :         // deep-copied entire ring subtrees. Restore them.
      49        1686 :         Ring(Ring &&other) = default;
      50             :         Ring &operator=(Ring &&other) = default;
      51             : 
      52             :         LineString points;
      53             : 
      54             :         // Bounding box, computed once when the ring is complete;
      55             :         // gives isIn() an O(1) reject so parent search stops being
      56             :         // O(rings * vertices) per insertion.
      57             :         OGREnvelope bbox;
      58             : 
      59         847 :         void computeBBox()
      60             :         {
      61         847 :             bbox = OGREnvelope();
      62      489934 :             for (const auto &pt : points)
      63      489087 :                 bbox.Merge(pt.x, pt.y);
      64         847 :         }
      65             : 
      66             :         mutable std::vector<Ring> interiorRings;
      67             : 
      68             :         const Ring *closestExterior = nullptr;
      69             : 
      70         411 :         bool isIn(const Ring &other) const
      71             :         {
      72             :             // Check if this is inside other using the winding number algorithm
      73         411 :             auto checkPoint = this->points.front();
      74             :             // A point outside the candidate ring's bounding box
      75             :             // cannot be inside the ring.
      76         411 :             if (checkPoint.x < other.bbox.MinX ||
      77         411 :                 checkPoint.x > other.bbox.MaxX ||
      78         411 :                 checkPoint.y < other.bbox.MinY ||
      79         411 :                 checkPoint.y > other.bbox.MaxY)
      80             :             {
      81           0 :                 return false;
      82             :             }
      83         411 :             int windingNum = 0;
      84         411 :             auto otherIter = other.points.begin();
      85             :             // p1 and p2 define each segment of the ring other that will be
      86             :             // tested
      87         411 :             auto p1 = *otherIter;
      88     6138680 :             while (true)
      89             :             {
      90     6139093 :                 otherIter++;
      91     6139093 :                 if (otherIter == other.points.end())
      92             :                 {
      93         411 :                     break;
      94             :                 }
      95     6138680 :                 auto p2 = *otherIter;
      96     6138680 :                 if (p1.y <= checkPoint.y)
      97             :                 {
      98     5396957 :                     if (p2.y > checkPoint.y)
      99             :                     {
     100         623 :                         if (isLeft(p1, p2, checkPoint))
     101             :                         {
     102         253 :                             ++windingNum;
     103             :                         }
     104             :                     }
     105             :                 }
     106             :                 else
     107             :                 {
     108      741725 :                     if (p2.y <= checkPoint.y)
     109             :                     {
     110         623 :                         if (!isLeft(p1, p2, checkPoint))
     111             :                         {
     112         434 :                             --windingNum;
     113             :                         }
     114             :                     }
     115             :                 }
     116     6138680 :                 p1 = p2;
     117             :             }
     118         411 :             return windingNum != 0;
     119             :         }
     120             : 
     121             : #ifdef DEBUG
     122             :         size_t id() const
     123             :         {
     124             :             return size_t(static_cast<const void *>(this)) & 0xffff;
     125             :         }
     126             : 
     127             :         void print(std::ostream &ostr) const
     128             :         {
     129             :             ostr << id() << ":";
     130             :             for (const auto &pt : points)
     131             :             {
     132             :                 ostr << pt.x << "," << pt.y << " ";
     133             :             }
     134             :         }
     135             : #endif
     136             :     };
     137             : 
     138         976 :     void processTree(const std::vector<Ring> &tree, int level)
     139             :     {
     140         976 :         if (level % 2 == 0)
     141             :         {
     142         976 :             for (auto &r : tree)
     143             :             {
     144         466 :                 writer_.addPart(r.points);
     145         847 :                 for (auto &innerRing : r.interiorRings)
     146             :                 {
     147         381 :                     writer_.addInteriorRing(innerRing.points);
     148             :                 }
     149             :             }
     150             :         }
     151        1823 :         for (auto &r : tree)
     152             :         {
     153         847 :             processTree(r.interiorRings, level + 1);
     154             :         }
     155         976 :     }
     156             : 
     157             :     // level -> rings
     158             :     std::map<double, std::vector<Ring>> rings_;
     159             : 
     160             :     // Point-in-polygon accelerator for one target ring: an
     161             :     // OGRPreparedGeometry (GEOS indexed point-in-area locator) over the
     162             :     // ring, built lazily when a ring turns out to capture many candidates.
     163             :     // The pathological case is a domain-spanning ring with millions of
     164             :     // vertices capturing tens of thousands of earlier rings; testing each
     165             :     // candidate against the raw ring is O(candidates * vertices). In builds
     166             :     // without GEOS support the capture step falls back to the linear
     167             :     // winding test.
     168             :     struct PreparedRing
     169             :     {
     170         839 :         PreparedRing() : poly(), prep()
     171             :         {
     172         839 :         }
     173             : 
     174             :         OGRPolygon poly;
     175             :         OGRPreparedGeometryUniquePtr prep;
     176             : 
     177          12 :         bool build(const Ring &r)
     178             :         {
     179          12 :             poly.empty();
     180          12 :             prep.reset();
     181          24 :             auto ring = std::make_unique<OGRLinearRing>();
     182          12 :             ring->setNumPoints(static_cast<int>(r.points.size()));
     183          12 :             int i = 0;
     184      371468 :             for (const auto &pt : r.points)
     185      371456 :                 ring->setPoint(i++, pt.x, pt.y);
     186          12 :             poly.addRingDirectly(ring.release());
     187          12 :             poly.closeRings();
     188          12 :             prep.reset(OGRCreatePreparedGeometry(OGRGeometry::ToHandle(&poly)));
     189          24 :             return prep != nullptr;
     190             :         }
     191             : 
     192         252 :         bool contains(const Point &p) const
     193             :         {
     194         504 :             OGRPoint pt(p.x, p.y);
     195         252 :             return CPL_TO_BOOL(OGRPreparedGeometryContains(
     196         504 :                 prep.get(), OGRGeometry::ToHandle(&pt)));
     197             :         }
     198             :     };
     199             : 
     200             :     // Per-level spatial index over TOP-LEVEL rings: a CPLQuadTree over ring
     201             :     // bounding boxes. Each stored feature points at the ring's slot index in
     202             :     // the level's ring vector, held in a std::deque so the pointer survives
     203             :     // growth; vector reallocation of the rings themselves is harmless. Rings
     204             :     // captured as interior rings of a later ring are removed from the tree
     205             :     // and their slot tombstoned (points cleared) rather than erased, keeping
     206             :     // the remaining indices stable.
     207             :     struct QuadTreeDestroyer
     208             :     {
     209         129 :         void operator()(CPLQuadTree *t) const
     210             :         {
     211         129 :             CPLQuadTreeDestroy(t);
     212         129 :         }
     213             :     };
     214             : 
     215             :     std::map<double, std::unique_ptr<CPLQuadTree, QuadTreeDestroyer>> index_;
     216             :     std::map<double, std::deque<std::size_t>> slots_;
     217             :     CPLRectObj domain_;
     218             : 
     219         699 :     static std::size_t featureSlot(const void *f)
     220             :     {
     221         699 :         return *static_cast<const std::size_t *>(f);
     222             :     }
     223             : 
     224        2149 :     static CPLRectObj ringRect(const Ring &r)
     225             :     {
     226        2149 :         return CPLRectObj{r.bbox.MinX, r.bbox.MinY, r.bbox.MaxX, r.bbox.MaxY};
     227             :     }
     228             : 
     229             :     PolygonWriter &writer_;
     230             : 
     231             :   public:
     232             :     const bool polygonize = true;
     233             : 
     234          37 :     PolygonRingAppender(PolygonWriter &writer, double minX, double minY,
     235             :                         double maxX, double maxY)
     236             :         : rings_(), index_(), slots_(), domain_{minX, minY, maxX, maxY},
     237          37 :           writer_(writer)
     238             :     {
     239          37 :     }
     240             : 
     241         879 :     void addLine(double level, LineString &ls, bool)
     242             :     {
     243         879 :         auto &levelRings = rings_[level];
     244         879 :         auto &levelTree = index_[level];
     245         879 :         auto &levelSlots = slots_[level];
     246         879 :         if (!levelTree)
     247         129 :             levelTree.reset(CPLQuadTreeCreate(&domain_, nullptr));
     248         879 :         if (ls.empty())
     249             :         {
     250          32 :             return;
     251             :         }
     252             :         // Create a new ring from the LineString
     253        1694 :         Ring newRing;
     254         847 :         newRing.points.swap(ls);
     255         847 :         newRing.computeBBox();
     256             :         // Find the top-level parent (if any) through the index instead of
     257             :         // scanning every top-level ring, then descend the (short) nested
     258             :         // sibling lists exactly as before.
     259         847 :         Ring *parentRing = nullptr;
     260             :         {
     261         847 :             Ring *top = nullptr;
     262         847 :             const auto &fp0 = newRing.points.front();
     263         847 :             CPLRectObj aoi{fp0.x, fp0.y, fp0.x, fp0.y};
     264         847 :             int nHits = 0;
     265         847 :             void **hits = CPLQuadTreeSearch(levelTree.get(), &aoi, &nHits);
     266         867 :             for (int h = 0; h < nHits && top == nullptr; h++)
     267             :             {
     268          20 :                 Ring &cand = levelRings[featureSlot(hits[h])];
     269          20 :                 if (!cand.points.empty() && newRing.isIn(cand))
     270           8 :                     top = &cand;
     271             :             }
     272         847 :             CPLFree(hits);
     273         847 :             if (top != nullptr)
     274             :             {
     275           8 :                 parentRing = top;
     276             :                 // This queue holds the rings to be checked
     277          16 :                 std::deque<Ring *> queue;
     278           8 :                 std::transform(
     279             :                     top->interiorRings.begin(), top->interiorRings.end(),
     280           0 :                     std::back_inserter(queue), [](Ring &r) { return &r; });
     281           8 :                 while (!queue.empty())
     282             :                 {
     283           0 :                     Ring *curRing = queue.front();
     284           0 :                     queue.pop_front();
     285           0 :                     if (newRing.isIn(*curRing))
     286             :                     {
     287             :                         // We know that there should only be one ring per
     288             :                         // level that we should fit in, so we can discard the
     289             :                         // rest of the queue and try again with the children
     290             :                         // of this ring
     291           0 :                         parentRing = curRing;
     292           0 :                         queue.clear();
     293           0 :                         std::transform(curRing->interiorRings.begin(),
     294             :                                        curRing->interiorRings.end(),
     295             :                                        std::back_inserter(queue),
     296           0 :                                        [](Ring &r) { return &r; });
     297             :                     }
     298             :                 }
     299             :             }
     300             :         }
     301         847 :         if (parentRing == nullptr)
     302             :         {
     303             :             // Top-level insertion: capture existing top-level rings that lie
     304             :             // inside the new ring, via the index. Build a per-target PIP
     305             :             // index lazily so a huge ring capturing many candidates costs
     306             :             // O(V + R * V/B), not O(R * V).
     307        1678 :             std::vector<std::size_t> captured;
     308        1678 :             PreparedRing pip;
     309         839 :             bool pipTried = false;
     310         839 :             bool pipBuilt = false;
     311         839 :             std::size_t nCandidates = 0;
     312             :             {
     313         839 :                 CPLRectObj aoi = ringRect(newRing);
     314         839 :                 int nHits = 0;
     315         839 :                 void **hits = CPLQuadTreeSearch(levelTree.get(), &aoi, &nHits);
     316        1518 :                 for (int h = 0; h < nHits; h++)
     317             :                 {
     318         679 :                     const std::size_t idx = featureSlot(hits[h]);
     319         679 :                     Ring &cand = levelRings[idx];
     320         679 :                     if (cand.points.empty())
     321          36 :                         continue;
     322         679 :                     const auto &fp = cand.points.front();
     323         679 :                     if (fp.x < newRing.bbox.MinX || fp.x > newRing.bbox.MaxX ||
     324         643 :                         fp.y < newRing.bbox.MinY || fp.y > newRing.bbox.MaxY)
     325          36 :                         continue;
     326         655 :                     if (!pipTried && ++nCandidates > 16 &&
     327          12 :                         newRing.points.size() > 512)
     328             :                     {
     329          12 :                         pipTried = true;
     330          12 :                         pipBuilt = pip.build(newRing);
     331             :                     }
     332         643 :                     const bool inside =
     333         643 :                         pipBuilt ? pip.contains(fp) : cand.isIn(newRing);
     334         643 :                     if (inside)
     335         471 :                         captured.push_back(idx);
     336             :                 }
     337         839 :                 CPLFree(hits);
     338             :             }
     339             :             // Sorting by slot restores insertion order, so captured rings
     340             :             // nest in the same order the original linear scan produced.
     341         839 :             std::sort(captured.begin(), captured.end());
     342         839 :             captured.erase(std::unique(captured.begin(), captured.end()),
     343         839 :                            captured.end());
     344        1310 :             for (std::size_t idx : captured)
     345             :             {
     346         471 :                 CPLRectObj rb = ringRect(levelRings[idx]);
     347         471 :                 CPLQuadTreeRemove(levelTree.get(), &levelSlots[idx], &rb);
     348         471 :                 newRing.interiorRings.push_back(std::move(levelRings[idx]));
     349         471 :                 levelRings[idx].points.clear();  // tombstone the slot
     350             :             }
     351         839 :             levelRings.push_back(std::move(newRing));
     352         839 :             levelSlots.push_back(levelRings.size() - 1);
     353         839 :             CPLRectObj nb = ringRect(levelRings.back());
     354         839 :             CPLQuadTreeInsertWithBounds(levelTree.get(), &levelSlots.back(),
     355             :                                         &nb);
     356             :         }
     357             :         else
     358             :         {
     359             :             // Get a pointer to the list we need to check for rings to include
     360             :             // in this ring
     361           8 :             std::vector<Ring> *parentRingList = &(parentRing->interiorRings);
     362             :             // We found a valid parent, so we need to:
     363             :             // 1. Find all the inner rings of the parent that are inside the new
     364             :             // ring
     365           8 :             auto trueGroupIt = std::partition(
     366             :                 parentRingList->begin(), parentRingList->end(),
     367           0 :                 [&newRing](Ring &pRing) { return !pRing.isIn(newRing); });
     368             :             // 2. Move those rings out of the parent and into the new ring's
     369             :             // interior rings
     370           8 :             std::move(trueGroupIt, parentRingList->end(),
     371             :                       std::back_inserter(newRing.interiorRings));
     372             :             // 3. Get rid of the moved-from elements in the parent's interior
     373             :             // rings
     374           8 :             parentRingList->erase(trueGroupIt, parentRingList->end());
     375             :             // 4. Add the new ring to the parent's interior rings
     376           8 :             parentRingList->push_back(std::move(newRing));
     377             :         }
     378             :     }
     379             : 
     380          37 :     ~PolygonRingAppender()
     381             :     {
     382             :         // If there's no rings, nothing to do here
     383          37 :         if (rings_.size() == 0)
     384           0 :             return;
     385             : 
     386             :         // Traverse tree of rings
     387         166 :         for (auto &r : rings_)
     388             :         {
     389             :             // Drop tombstoned slots (rings captured as interior
     390             :             // rings of later-arriving parents) before traversal.
     391         258 :             std::vector<Ring> live;
     392         129 :             live.reserve(r.second.size());
     393         968 :             for (auto &ring : r.second)
     394         839 :                 if (!ring.points.empty())
     395         368 :                     live.push_back(std::move(ring));
     396             :             // For each level, create a multipolygon by traversing the tree of
     397             :             // rings and adding a part for every other level
     398         129 :             writer_.startPolygon(r.first);
     399         129 :             processTree(live, 0);
     400         129 :             writer_.endPolygon();
     401             :         }
     402          37 :     }
     403             : };
     404             : 
     405             : }  // namespace marching_squares
     406             : 
     407             : #endif

Generated by: LCOV version 1.14