LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/mitab - mitab_geometry.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 104 142 73.2 %
Date: 2024-11-21 22:18:42 Functions: 4 6 66.7 %

          Line data    Source code
       1             : /**********************************************************************
       2             :  *
       3             :  * Name:     mitab_geometry.cpp
       4             :  * Project:  MapInfo TAB Read/Write library
       5             :  * Language: C++
       6             :  * Purpose:  Geometry manipulation functions.
       7             :  * Author:   Daniel Morissette, dmorissette@dmsolutions.ca
       8             :  *           Based on functions from mapprimitive.c/mapsearch.c in the source
       9             :  *           of UMN MapServer by Stephen Lime (http://mapserver.gis.umn.edu/)
      10             :  **********************************************************************
      11             :  * Copyright (c) 1999-2001, Daniel Morissette
      12             :  *
      13             :  * SPDX-License-Identifier: MIT
      14             :  **********************************************************************/
      15             : 
      16             : #include "cpl_port.h"
      17             : #include "mitab_geometry.h"
      18             : 
      19             : #include <cmath>
      20             : #include <cstdlib>
      21             : #include <algorithm>
      22             : #include <utility>
      23             : 
      24             : #include "ogr_core.h"
      25             : 
      26             : #define OGR_NUM_RINGS(poly) (poly->getNumInteriorRings() + 1)
      27             : #define OGR_GET_RING(poly, i)                                                  \
      28             :     (i == 0 ? poly->getExteriorRing() : poly->getInteriorRing(i - 1))
      29             : 
      30             : /**********************************************************************
      31             :  *                   OGRPointInRing()
      32             :  *
      33             :  * Returns TRUE is point is inside ring, FALSE otherwise
      34             :  *
      35             :  * Adapted version of msPointInPolygon() from MapServer's mapsearch.c
      36             :  **********************************************************************/
      37          88 : GBool OGRPointInRing(OGRPoint *poPoint, OGRLineString *poRing)
      38             : {
      39             :     int i, j, numpoints;
      40          88 :     GBool status = FALSE;
      41             :     double x, y;
      42             : 
      43          88 :     numpoints = poRing->getNumPoints();
      44          88 :     x = poPoint->getX();
      45          88 :     y = poPoint->getY();
      46             : 
      47        2515 :     for (i = 0, j = numpoints - 1; i < numpoints; j = i++)
      48             :     {
      49        5991 :         if ((((poRing->getY(i) <= y) && (y < poRing->getY(j))) ||
      50        6089 :              ((poRing->getY(j) <= y) && (y < poRing->getY(i)))) &&
      51         196 :             (x < (poRing->getX(j) - poRing->getX(i)) * (y - poRing->getY(i)) /
      52         392 :                          (poRing->getY(j) - poRing->getY(i)) +
      53         196 :                      poRing->getX(i)))
      54          95 :             status = !status;
      55             :     }
      56             : 
      57          88 :     return status;
      58             : }
      59             : 
      60             : /**********************************************************************
      61             :  *                   OGRIntersectPointPolygon()
      62             :  *
      63             :  * Instead of using ring orientation we count the number of parts the
      64             :  * point falls in. If odd the point is in the polygon, if 0 or even
      65             :  * then the point is in a hole or completely outside.
      66             :  *
      67             :  * Returns TRUE is point is inside polygon, FALSE otherwise
      68             :  *
      69             :  * Adapted version of msIntersectPointPolygon() from MapServer's mapsearch.c
      70             :  **********************************************************************/
      71          88 : GBool OGRIntersectPointPolygon(OGRPoint *poPoint, OGRPolygon *poPoly)
      72             : {
      73          88 :     GBool status = FALSE;
      74             : 
      75         176 :     for (int i = 0; i < OGR_NUM_RINGS(poPoly); i++)
      76             :     {
      77          88 :         if (OGRPointInRing(poPoint, OGR_GET_RING(poPoly, i)))
      78             :         {
      79             :             /* ok, the point is in a line */
      80          75 :             status = !status;
      81             :         }
      82             :     }
      83             : 
      84          88 :     return status;
      85             : }
      86             : 
      87             : /**********************************************************************
      88             :  *                   OGRPolygonLabelPoint()
      89             :  *
      90             :  * Generate a label point on the surface of a polygon.
      91             :  *
      92             :  * The function is based on a scanline conversion routine used for polygon
      93             :  * fills.  Instead of processing each line the as with drawing, the
      94             :  * polygon is sampled. The center of the longest sample is chosen for the
      95             :  * label point. The label point is guaranteed to be in the polygon even if
      96             :  * it has holes assuming the polygon is properly formed.
      97             :  *
      98             :  * Returns OGRERR_NONE if it succeeds or OGRERR_FAILURE otherwise.
      99             :  *
     100             :  * Adapted version of msPolygonLabelPoint() from MapServer's mapprimitive.c
     101             :  **********************************************************************/
     102             : 
     103             : typedef enum
     104             : {
     105             :     CLIP_LEFT,
     106             :     CLIP_MIDDLE,
     107             :     CLIP_RIGHT
     108             : } CLIP_STATE;
     109             : 
     110        1285 : static CLIP_STATE EDGE_CHECK(double x0, double x, double x1)
     111             : {
     112        1285 :     if (x < std::min(x0, x1))
     113         720 :         return CLIP_LEFT;
     114         565 :     if (x > std::max(x0, x1))
     115         425 :         return CLIP_RIGHT;
     116         140 :     return CLIP_MIDDLE;
     117             : }
     118             : 
     119             : constexpr int NUM_SCANLINES = 5;
     120             : 
     121          88 : int OGRPolygonLabelPoint(OGRPolygon *poPoly, OGRPoint *poLabelPoint)
     122             : {
     123          88 :     if (poPoly == nullptr)
     124           0 :         return OGRERR_FAILURE;
     125             : 
     126          88 :     OGREnvelope oEnv;
     127          88 :     poPoly->getEnvelope(&oEnv);
     128             : 
     129          88 :     poLabelPoint->setX((oEnv.MaxX + oEnv.MinX) / 2.0);
     130          88 :     poLabelPoint->setY((oEnv.MaxY + oEnv.MinY) / 2.0);
     131             : 
     132             :     // if( get_centroid(p, lp, &miny, &maxy) == -1 ) return -1;
     133             : 
     134          88 :     if (OGRIntersectPointPolygon(poLabelPoint, poPoly) == TRUE) /* cool, done */
     135          75 :         return OGRERR_NONE;
     136             : 
     137             :     /* do it the hard way - scanline */
     138             : 
     139          13 :     double skip = (oEnv.MaxY - oEnv.MinY) / NUM_SCANLINES;
     140             : 
     141          13 :     int n = 0;
     142          26 :     for (int j = 0; j < OGR_NUM_RINGS(poPoly); j++)
     143             :     {
     144             :         /* count total number of points */
     145          13 :         n += OGR_GET_RING(poPoly, j)->getNumPoints();
     146             :     }
     147          13 :     if (n == 0)
     148           0 :         return OGRERR_FAILURE;
     149             : 
     150          13 :     double *xintersect = static_cast<double *>(calloc(n, sizeof(double)));
     151          13 :     if (xintersect == nullptr)
     152           0 :         return OGRERR_FAILURE;
     153             : 
     154          13 :     double max_len = 0.0;
     155             : 
     156          78 :     for (int k = 1; k <= NUM_SCANLINES; k++)
     157             :     {
     158             :         /* sample the shape in the y direction */
     159             : 
     160          65 :         double y = oEnv.MaxY - k * skip;
     161             : 
     162             :         // Need to find a y that won't intersect any vertices exactly.
     163             :         // First initializing lo_y, hi_y to be any 2 pnts on either side of
     164             :         // lp->y.
     165          65 :         double hi_y = y - 1;
     166          65 :         double lo_y = y + 1;
     167         130 :         for (int j = 0; j < OGR_NUM_RINGS(poPoly); j++)
     168             :         {
     169          65 :             OGRLinearRing *poRing = OGR_GET_RING(poPoly, j);
     170             : 
     171          65 :             if ((lo_y < y) && (hi_y >= y))
     172           0 :                 break; /* already initialized */
     173         691 :             for (int i = 0; i < poRing->getNumPoints(); i++)
     174             :             {
     175         678 :                 if ((lo_y < y) && (hi_y >= y))
     176          52 :                     break; /* already initialized */
     177         626 :                 if (poRing->getY(i) < y)
     178          72 :                     lo_y = poRing->getY(i);
     179         626 :                 if (poRing->getY(i) >= y)
     180         554 :                     hi_y = poRing->getY(i);
     181             :             }
     182             :         }
     183             : 
     184         130 :         for (int j = 0; j < OGR_NUM_RINGS(poPoly); j++)
     185             :         {
     186          65 :             OGRLinearRing *poRing = OGR_GET_RING(poPoly, j);
     187             : 
     188        1350 :             for (int i = 0; i < poRing->getNumPoints(); i++)
     189             :             {
     190        1764 :                 if ((poRing->getY(i) < y) &&
     191         479 :                     ((y - poRing->getY(i)) < (y - lo_y)))
     192          25 :                     lo_y = poRing->getY(i);
     193        2091 :                 if ((poRing->getY(i) >= y) &&
     194         806 :                     ((poRing->getY(i) - y) < (hi_y - y)))
     195         110 :                     hi_y = poRing->getY(i);
     196             :             }
     197             :         }
     198             : 
     199          65 :         if (lo_y == hi_y)
     200             :         {
     201           0 :             free(xintersect);
     202           0 :             return OGRERR_FAILURE;
     203             :         }
     204             : 
     205          65 :         y = (hi_y + lo_y) / 2.0;
     206             : 
     207          65 :         OGRRawPoint point1;
     208          65 :         OGRRawPoint point2;
     209             : 
     210          65 :         int nfound = 0;
     211         130 :         for (int j = 0; j < OGR_NUM_RINGS(poPoly); j++)  // For each line.
     212             :         {
     213          65 :             OGRLinearRing *poRing = OGR_GET_RING(poPoly, j);
     214          65 :             if (poRing->IsEmpty())
     215           0 :                 continue;
     216          65 :             point1.x = poRing->getX(poRing->getNumPoints() - 1);
     217          65 :             point1.y = poRing->getY(poRing->getNumPoints() - 1);
     218             : 
     219        1350 :             for (int i = 0; i < poRing->getNumPoints(); i++)
     220             :             {
     221        1285 :                 point2.x = poRing->getX(i);
     222        1285 :                 point2.y = poRing->getY(i);
     223             : 
     224        1285 :                 if (EDGE_CHECK(point1.y, y, point2.y) == CLIP_MIDDLE)
     225             :                 {
     226         140 :                     if (point1.y == point2.y)
     227           0 :                         continue;  // Ignore horizontal edges.
     228             : 
     229         140 :                     const double slope =
     230         140 :                         (point2.x - point1.x) / (point2.y - point1.y);
     231             : 
     232         140 :                     double x = point1.x + (y - point1.y) * slope;
     233         140 :                     xintersect[nfound++] = x;
     234             :                 } /* End of checking this edge */
     235             : 
     236        1285 :                 point1 = point2; /* Go on to next edge */
     237             :             }
     238             :         } /* Finished the scanline */
     239             : 
     240             :         /* First, sort the intersections */
     241          65 :         bool wrong_order = false;
     242         110 :         do
     243             :         {
     244         110 :             wrong_order = false;
     245         250 :             for (int i = 0; i < nfound - 1; i++)
     246             :             {
     247         140 :                 if (xintersect[i] > xintersect[i + 1])
     248             :                 {
     249          50 :                     wrong_order = true;
     250          50 :                     std::swap(xintersect[i], xintersect[i + 1]);
     251             :                 }
     252             :             }
     253             :         } while (wrong_order);
     254             : 
     255             :         // Great, now find longest span.
     256             :         // point1.y = y;
     257             :         // point2.y = y;
     258         135 :         for (int i = 0; i < nfound - 1; i += 2)
     259             :         {
     260          70 :             point1.x = xintersect[i];
     261          70 :             point2.x = xintersect[i + 1];
     262             :             /* len = length(point1, point2); */
     263          70 :             const double len = std::abs((point2.x - point1.x));
     264          70 :             if (len > max_len)
     265             :             {
     266          23 :                 max_len = len;
     267          23 :                 poLabelPoint->setX((point1.x + point2.x) / 2);
     268          23 :                 poLabelPoint->setY(y);
     269             :             }
     270             :         }
     271             :     }
     272             : 
     273          13 :     free(xintersect);
     274             : 
     275             :     /* __TODO__ Bug 673
     276             :      * There seem to be some polygons for which the label is returned
     277             :      * completely outside of the polygon's MBR and this messes the
     278             :      * file bounds, etc.
     279             :      * Until we find the source of the problem, we'll at least validate
     280             :      * the label point to make sure that it overlaps the polygon MBR.
     281             :      */
     282          26 :     if (poLabelPoint->getX() < oEnv.MinX || poLabelPoint->getY() < oEnv.MinY ||
     283          26 :         poLabelPoint->getX() > oEnv.MaxX || poLabelPoint->getY() > oEnv.MaxY)
     284             :     {
     285             :         // Reset label coordinates to center of MBR, just in case
     286           0 :         poLabelPoint->setX((oEnv.MaxX + oEnv.MinX) / 2.0);
     287           0 :         poLabelPoint->setY((oEnv.MaxY + oEnv.MinY) / 2.0);
     288             : 
     289             :         // And return an error
     290           0 :         return OGRERR_FAILURE;
     291             :     }
     292             : 
     293          13 :     if (max_len > 0)
     294          13 :         return OGRERR_NONE;
     295             :     else
     296           0 :         return OGRERR_FAILURE;
     297             : }
     298             : 
     299             : #ifdef unused
     300             : /**********************************************************************
     301             :  *                   OGRGetCentroid()
     302             :  *
     303             :  * Calculate polygon gravity center.
     304             :  *
     305             :  * Returns OGRERR_NONE if it succeeds or OGRERR_FAILURE otherwise.
     306             :  *
     307             :  * Adapted version of get_centroid() from MapServer's mapprimitive.c
     308             :  **********************************************************************/
     309             : 
     310             : int OGRGetCentroid(OGRPolygon *poPoly, OGRPoint *poCentroid)
     311             : {
     312             :     double cent_weight_x = 0.0;
     313             :     double cent_weight_y = 0.0;
     314             :     double len = 0.0;
     315             :     double total_len = 0.0;
     316             : 
     317             :     for (int i = 0; i < OGR_NUM_RINGS(poPoly); i++)
     318             :     {
     319             :         OGRLinearRing *poRing = OGR_GET_RING(poPoly, i);
     320             : 
     321             :         double x2 = poRing->getX(0);
     322             :         double y2 = poRing->getY(0);
     323             : 
     324             :         for (int j = 1; j < poRing->getNumPoints(); j++)
     325             :         {
     326             :             double x1 = x2;
     327             :             double y1 = y2;
     328             :             x2 = poRing->getX(j);
     329             :             y2 = poRing->getY(j);
     330             : 
     331             :             len = sqrt(pow((x2 - x1), 2) + pow((y2 - y1), 2));
     332             :             cent_weight_x += len * ((x1 + x2) / 2.0);
     333             :             cent_weight_y += len * ((y1 + y2) / 2.0);
     334             :             total_len += len;
     335             :         }
     336             :     }
     337             : 
     338             :     if (total_len == 0)
     339             :         return OGRERR_FAILURE;
     340             : 
     341             :     poCentroid->setX(cent_weight_x / total_len);
     342             :     poCentroid->setY(cent_weight_y / total_len);
     343             : 
     344             :     return OGRERR_NONE;
     345             : }
     346             : #endif
     347             : 
     348             : /**********************************************************************
     349             :  *                   OGRPolylineCenterPoint()
     350             :  *
     351             :  * Return the center point of a polyline.
     352             :  *
     353             :  * In MapInfo, for a simple or multiple polyline (pline), the center point
     354             :  * in the object definition is supposed to be either the center point of
     355             :  * the pline or the first section of a multiple pline (if an odd number of
     356             :  * points in the pline or first section), or the midway point between the
     357             :  * two central points (if an even number of points involved).
     358             :  *
     359             :  * Returns OGRERR_NONE if it succeeds or OGRERR_FAILURE otherwise.
     360             :  **********************************************************************/
     361             : 
     362           0 : int OGRPolylineCenterPoint(OGRLineString *poLine, OGRPoint *poLabelPoint)
     363             : {
     364           0 :     if (poLine == nullptr || poLine->getNumPoints() < 2)
     365           0 :         return OGRERR_FAILURE;
     366             : 
     367           0 :     if (poLine->getNumPoints() % 2 == 0)
     368             :     {
     369             :         // Return the midway between the 2 center points
     370           0 :         int i = poLine->getNumPoints() / 2;
     371           0 :         poLabelPoint->setX((poLine->getX(i - 1) + poLine->getX(i)) / 2.0);
     372           0 :         poLabelPoint->setY((poLine->getY(i - 1) + poLine->getY(i)) / 2.0);
     373             :     }
     374             :     else
     375             :     {
     376             :         // Return the center point
     377           0 :         poLine->getPoint(poLine->getNumPoints() / 2, poLabelPoint);
     378             :     }
     379             : 
     380           0 :     return OGRERR_NONE;
     381             : }
     382             : 
     383             : /**********************************************************************
     384             :  *                   OGRPolylineLabelPoint()
     385             :  *
     386             :  * Generate a label point on a polyline: The center of the longest segment.
     387             :  *
     388             :  * Returns OGRERR_NONE if it succeeds or OGRERR_FAILURE otherwise.
     389             :  **********************************************************************/
     390             : 
     391           0 : int OGRPolylineLabelPoint(OGRLineString *poLine, OGRPoint *poLabelPoint)
     392             : {
     393           0 :     if (poLine == nullptr || poLine->getNumPoints() < 2)
     394           0 :         return OGRERR_FAILURE;
     395             : 
     396           0 :     double max_segment_length = -1.0;
     397             : 
     398           0 :     double x2 = poLine->getX(0);
     399           0 :     double y2 = poLine->getY(0);
     400             : 
     401           0 :     for (int i = 1; i < poLine->getNumPoints(); i++)
     402             :     {
     403           0 :         double x1 = x2;
     404           0 :         double y1 = y2;
     405           0 :         x2 = poLine->getX(i);
     406           0 :         y2 = poLine->getY(i);
     407             : 
     408           0 :         double segment_length = pow((x2 - x1), 2) + pow((y2 - y1), 2);
     409           0 :         if (segment_length > max_segment_length)
     410             :         {
     411           0 :             max_segment_length = segment_length;
     412           0 :             poLabelPoint->setX((x1 + x2) / 2.0);
     413           0 :             poLabelPoint->setY((y1 + y2) / 2.0);
     414             :         }
     415             :     }
     416             : 
     417           0 :     return OGRERR_NONE;
     418             : }

Generated by: LCOV version 1.14