Coverage Report

Created: 2025-11-15 08:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/mitab/mitab_geometry.cpp
Line
Count
Source
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
8.68k
#define OGR_NUM_RINGS(poly) (poly->getNumInteriorRings() + 1)
27
#define OGR_GET_RING(poly, i)                                                  \
28
4.79k
    (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
GBool OGRPointInRing(OGRPoint *poPoint, OGRLineString *poRing)
38
986
{
39
986
    int i, j, numpoints;
40
986
    GBool status = FALSE;
41
986
    double x, y;
42
43
986
    numpoints = poRing->getNumPoints();
44
986
    x = poPoint->getX();
45
986
    y = poPoint->getY();
46
47
1.42k
    for (i = 0, j = numpoints - 1; i < numpoints; j = i++)
48
434
    {
49
434
        if ((((poRing->getY(i) <= y) && (y < poRing->getY(j))) ||
50
353
             ((poRing->getY(j) <= y) && (y < poRing->getY(i)))) &&
51
162
            (x < (poRing->getX(j) - poRing->getX(i)) * (y - poRing->getY(i)) /
52
162
                         (poRing->getY(j) - poRing->getY(i)) +
53
162
                     poRing->getX(i)))
54
22
            status = !status;
55
434
    }
56
57
986
    return status;
58
986
}
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
GBool OGRIntersectPointPolygon(OGRPoint *poPoint, OGRPolygon *poPoly)
72
846
{
73
846
    GBool status = FALSE;
74
75
1.83k
    for (int i = 0; i < OGR_NUM_RINGS(poPoly); i++)
76
986
    {
77
986
        if (OGRPointInRing(poPoint, OGR_GET_RING(poPoly, i)))
78
22
        {
79
            /* ok, the point is in a line */
80
22
            status = !status;
81
22
        }
82
986
    }
83
84
846
    return status;
85
846
}
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
static CLIP_STATE EDGE_CHECK(double x0, double x, double x1)
111
1.50k
{
112
1.50k
    if (x < std::min(x0, x1))
113
406
        return CLIP_LEFT;
114
1.10k
    if (x > std::max(x0, x1))
115
631
        return CLIP_RIGHT;
116
472
    return CLIP_MIDDLE;
117
1.10k
}
118
119
constexpr int NUM_SCANLINES = 5;
120
121
int OGRPolygonLabelPoint(OGRPolygon *poPoly, OGRPoint *poLabelPoint)
122
846
{
123
846
    if (poPoly == nullptr)
124
0
        return OGRERR_FAILURE;
125
126
846
    OGREnvelope oEnv;
127
846
    poPoly->getEnvelope(&oEnv);
128
129
846
    poLabelPoint->setX((oEnv.MaxX + oEnv.MinX) / 2.0);
130
846
    poLabelPoint->setY((oEnv.MaxY + oEnv.MinY) / 2.0);
131
132
    // if( get_centroid(p, lp, &miny, &maxy) == -1 ) return -1;
133
134
846
    if (OGRIntersectPointPolygon(poLabelPoint, poPoly) == TRUE) /* cool, done */
135
22
        return OGRERR_NONE;
136
137
    /* do it the hard way - scanline */
138
139
824
    double skip = (oEnv.MaxY - oEnv.MinY) / NUM_SCANLINES;
140
141
824
    int n = 0;
142
1.77k
    for (int j = 0; j < OGR_NUM_RINGS(poPoly); j++)
143
951
    {
144
        /* count total number of points */
145
951
        n += OGR_GET_RING(poPoly, j)->getNumPoints();
146
951
    }
147
824
    if (n == 0)
148
657
        return OGRERR_FAILURE;
149
150
167
    double *xintersect = static_cast<double *>(calloc(n, sizeof(double)));
151
167
    if (xintersect == nullptr)
152
0
        return OGRERR_FAILURE;
153
154
167
    double max_len = 0.0;
155
156
912
    for (int k = 1; k <= NUM_SCANLINES; k++)
157
787
    {
158
        /* sample the shape in the y direction */
159
160
787
        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
787
        double hi_y = y - 1;
166
787
        double lo_y = y + 1;
167
1.65k
        for (int j = 0; j < OGR_NUM_RINGS(poPoly); j++)
168
967
        {
169
967
            OGRLinearRing *poRing = OGR_GET_RING(poPoly, j);
170
171
967
            if ((lo_y < y) && (hi_y >= y))
172
100
                break; /* already initialized */
173
2.17k
            for (int i = 0; i < poRing->getNumPoints(); i++)
174
1.42k
            {
175
1.42k
                if ((lo_y < y) && (hi_y >= y))
176
116
                    break; /* already initialized */
177
1.30k
                if (poRing->getY(i) < y)
178
272
                    lo_y = poRing->getY(i);
179
1.30k
                if (poRing->getY(i) >= y)
180
1.03k
                    hi_y = poRing->getY(i);
181
1.30k
            }
182
867
        }
183
184
1.75k
        for (int j = 0; j < OGR_NUM_RINGS(poPoly); j++)
185
967
        {
186
967
            OGRLinearRing *poRing = OGR_GET_RING(poPoly, j);
187
188
2.65k
            for (int i = 0; i < poRing->getNumPoints(); i++)
189
1.69k
            {
190
1.69k
                if ((poRing->getY(i) < y) &&
191
334
                    ((y - poRing->getY(i)) < (y - lo_y)))
192
0
                    lo_y = poRing->getY(i);
193
1.69k
                if ((poRing->getY(i) >= y) &&
194
1.35k
                    ((poRing->getY(i) - y) < (hi_y - y)))
195
64
                    hi_y = poRing->getY(i);
196
1.69k
            }
197
967
        }
198
199
787
        if (lo_y == hi_y)
200
42
        {
201
42
            free(xintersect);
202
42
            return OGRERR_FAILURE;
203
42
        }
204
205
745
        y = (hi_y + lo_y) / 2.0;
206
207
745
        OGRRawPoint point1;
208
745
        OGRRawPoint point2;
209
210
745
        int nfound = 0;
211
1.66k
        for (int j = 0; j < OGR_NUM_RINGS(poPoly); j++)  // For each line.
212
922
        {
213
922
            OGRLinearRing *poRing = OGR_GET_RING(poPoly, j);
214
922
            if (poRing->IsEmpty())
215
160
                continue;
216
762
            point1.x = poRing->getX(poRing->getNumPoints() - 1);
217
762
            point1.y = poRing->getY(poRing->getNumPoints() - 1);
218
219
2.27k
            for (int i = 0; i < poRing->getNumPoints(); i++)
220
1.50k
            {
221
1.50k
                point2.x = poRing->getX(i);
222
1.50k
                point2.y = poRing->getY(i);
223
224
1.50k
                if (EDGE_CHECK(point1.y, y, point2.y) == CLIP_MIDDLE)
225
472
                {
226
472
                    if (point1.y == point2.y)
227
0
                        continue;  // Ignore horizontal edges.
228
229
472
                    const double slope =
230
472
                        (point2.x - point1.x) / (point2.y - point1.y);
231
232
472
                    double x = point1.x + (y - point1.y) * slope;
233
472
                    xintersect[nfound++] = x;
234
472
                } /* End of checking this edge */
235
236
1.50k
                point1 = point2; /* Go on to next edge */
237
1.50k
            }
238
762
        } /* Finished the scanline */
239
240
        /* First, sort the intersections */
241
745
        bool wrong_order = false;
242
745
        do
243
923
        {
244
923
            wrong_order = false;
245
1.33k
            for (int i = 0; i < nfound - 1; i++)
246
414
            {
247
414
                if (xintersect[i] > xintersect[i + 1])
248
178
                {
249
178
                    wrong_order = true;
250
178
                    std::swap(xintersect[i], xintersect[i + 1]);
251
178
                }
252
414
            }
253
923
        } while (wrong_order);
254
255
        // Great, now find longest span.
256
        // point1.y = y;
257
        // point2.y = y;
258
981
        for (int i = 0; i < nfound - 1; i += 2)
259
236
        {
260
236
            point1.x = xintersect[i];
261
236
            point2.x = xintersect[i + 1];
262
            /* len = length(point1, point2); */
263
236
            const double len = std::abs((point2.x - point1.x));
264
236
            if (len > max_len)
265
86
            {
266
86
                max_len = len;
267
86
                poLabelPoint->setX((point1.x + point2.x) / 2);
268
86
                poLabelPoint->setY(y);
269
86
            }
270
236
        }
271
745
    }
272
273
125
    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
125
    if (poLabelPoint->getX() < oEnv.MinX || poLabelPoint->getY() < oEnv.MinY ||
283
125
        poLabelPoint->getX() > oEnv.MaxX || poLabelPoint->getY() > oEnv.MaxY)
284
0
    {
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
0
    }
292
293
125
    if (max_len > 0)
294
32
        return OGRERR_NONE;
295
93
    else
296
93
        return OGRERR_FAILURE;
297
125
}
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
int OGRPolylineCenterPoint(OGRLineString *poLine, OGRPoint *poLabelPoint)
363
0
{
364
0
    if (poLine == nullptr || poLine->getNumPoints() < 2)
365
0
        return OGRERR_FAILURE;
366
367
0
    if (poLine->getNumPoints() % 2 == 0)
368
0
    {
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
0
    }
374
0
    else
375
0
    {
376
        // Return the center point
377
0
        poLine->getPoint(poLine->getNumPoints() / 2, poLabelPoint);
378
0
    }
379
380
0
    return OGRERR_NONE;
381
0
}
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
int OGRPolylineLabelPoint(OGRLineString *poLine, OGRPoint *poLabelPoint)
392
0
{
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
0
    {
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
0
        {
411
0
            max_segment_length = segment_length;
412
0
            poLabelPoint->setX((x1 + x2) / 2.0);
413
0
            poLabelPoint->setY((y1 + y2) / 2.0);
414
0
        }
415
0
    }
416
417
0
    return OGRERR_NONE;
418
0
}