Coverage Report

Created: 2026-06-30 08:33

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
33.6k
#define OGR_NUM_RINGS(poly) (poly->getNumInteriorRings() + 1)
27
#define OGR_GET_RING(poly, i)                                                  \
28
19.2k
    (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
3.31k
{
39
3.31k
    int i, j, numpoints;
40
3.31k
    GBool status = FALSE;
41
3.31k
    double x, y;
42
43
3.31k
    numpoints = poRing->getNumPoints();
44
3.31k
    x = poPoint->getX();
45
3.31k
    y = poPoint->getY();
46
47
4.98k
    for (i = 0, j = numpoints - 1; i < numpoints; j = i++)
48
1.67k
    {
49
1.67k
        if ((((poRing->getY(i) <= y) && (y < poRing->getY(j))) ||
50
1.33k
             ((poRing->getY(j) <= y) && (y < poRing->getY(i)))) &&
51
684
            (x < (poRing->getX(j) - poRing->getX(i)) * (y - poRing->getY(i)) /
52
684
                         (poRing->getY(j) - poRing->getY(i)) +
53
684
                     poRing->getX(i)))
54
93
            status = !status;
55
1.67k
    }
56
57
3.31k
    return status;
58
3.31k
}
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
2.78k
{
73
2.78k
    GBool status = FALSE;
74
75
6.09k
    for (int i = 0; i < OGR_NUM_RINGS(poPoly); i++)
76
3.31k
    {
77
3.31k
        if (OGRPointInRing(poPoint, OGR_GET_RING(poPoly, i)))
78
67
        {
79
            /* ok, the point is in a line */
80
67
            status = !status;
81
67
        }
82
3.31k
    }
83
84
2.78k
    return status;
85
2.78k
}
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
6.65k
{
112
6.65k
    if (x < std::min(x0, x1))
113
1.99k
        return CLIP_LEFT;
114
4.65k
    if (x > std::max(x0, x1))
115
2.44k
        return CLIP_RIGHT;
116
2.20k
    return CLIP_MIDDLE;
117
4.65k
}
118
119
constexpr int NUM_SCANLINES = 5;
120
121
int OGRPolygonLabelPoint(OGRPolygon *poPoly, OGRPoint *poLabelPoint)
122
2.78k
{
123
2.78k
    if (poPoly == nullptr)
124
0
        return OGRERR_FAILURE;
125
126
2.78k
    OGREnvelope oEnv;
127
2.78k
    poPoly->getEnvelope(&oEnv);
128
129
2.78k
    poLabelPoint->setX((oEnv.MaxX + oEnv.MinX) / 2.0);
130
2.78k
    poLabelPoint->setY((oEnv.MaxY + oEnv.MinY) / 2.0);
131
132
    // if( get_centroid(p, lp, &miny, &maxy) == -1 ) return -1;
133
134
2.78k
    if (OGRIntersectPointPolygon(poLabelPoint, poPoly) == TRUE) /* cool, done */
135
67
        return OGRERR_NONE;
136
137
    /* do it the hard way - scanline */
138
139
2.71k
    double skip = (oEnv.MaxY - oEnv.MinY) / NUM_SCANLINES;
140
141
2.71k
    int n = 0;
142
5.90k
    for (int j = 0; j < OGR_NUM_RINGS(poPoly); j++)
143
3.19k
    {
144
        /* count total number of points */
145
3.19k
        n += OGR_GET_RING(poPoly, j)->getNumPoints();
146
3.19k
    }
147
2.71k
    if (n == 0)
148
2.07k
        return OGRERR_FAILURE;
149
150
642
    double *xintersect = static_cast<double *>(calloc(n, sizeof(double)));
151
642
    if (xintersect == nullptr)
152
0
        return OGRERR_FAILURE;
153
154
642
    double max_len = 0.0;
155
156
3.63k
    for (int k = 1; k <= NUM_SCANLINES; k++)
157
3.17k
    {
158
        /* sample the shape in the y direction */
159
160
3.17k
        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
3.17k
        double hi_y = y - 1;
166
3.17k
        double lo_y = y + 1;
167
7.00k
        for (int j = 0; j < OGR_NUM_RINGS(poPoly); j++)
168
4.33k
        {
169
4.33k
            OGRLinearRing *poRing = OGR_GET_RING(poPoly, j);
170
171
4.33k
            if ((lo_y < y) && (hi_y >= y))
172
500
                break; /* already initialized */
173
9.76k
            for (int i = 0; i < poRing->getNumPoints(); i++)
174
6.38k
            {
175
6.38k
                if ((lo_y < y) && (hi_y >= y))
176
453
                    break; /* already initialized */
177
5.92k
                if (poRing->getY(i) < y)
178
1.44k
                    lo_y = poRing->getY(i);
179
5.92k
                if (poRing->getY(i) >= y)
180
4.48k
                    hi_y = poRing->getY(i);
181
5.92k
            }
182
3.83k
        }
183
184
7.50k
        for (int j = 0; j < OGR_NUM_RINGS(poPoly); j++)
185
4.33k
        {
186
4.33k
            OGRLinearRing *poRing = OGR_GET_RING(poPoly, j);
187
188
11.8k
            for (int i = 0; i < poRing->getNumPoints(); i++)
189
7.48k
            {
190
7.48k
                if ((poRing->getY(i) < y) &&
191
1.71k
                    ((y - poRing->getY(i)) < (y - lo_y)))
192
0
                    lo_y = poRing->getY(i);
193
7.48k
                if ((poRing->getY(i) >= y) &&
194
5.77k
                    ((poRing->getY(i) - y) < (hi_y - y)))
195
280
                    hi_y = poRing->getY(i);
196
7.48k
            }
197
4.33k
        }
198
199
3.17k
        if (lo_y == hi_y)
200
174
        {
201
174
            free(xintersect);
202
174
            return OGRERR_FAILURE;
203
174
        }
204
205
2.99k
        y = (hi_y + lo_y) / 2.0;
206
207
2.99k
        OGRRawPoint point1;
208
2.99k
        OGRRawPoint point2;
209
210
2.99k
        int nfound = 0;
211
7.09k
        for (int j = 0; j < OGR_NUM_RINGS(poPoly); j++)  // For each line.
212
4.10k
        {
213
4.10k
            OGRLinearRing *poRing = OGR_GET_RING(poPoly, j);
214
4.10k
            if (poRing->IsEmpty())
215
790
                continue;
216
3.31k
            point1.x = poRing->getX(poRing->getNumPoints() - 1);
217
3.31k
            point1.y = poRing->getY(poRing->getNumPoints() - 1);
218
219
9.96k
            for (int i = 0; i < poRing->getNumPoints(); i++)
220
6.65k
            {
221
6.65k
                point2.x = poRing->getX(i);
222
6.65k
                point2.y = poRing->getY(i);
223
224
6.65k
                if (EDGE_CHECK(point1.y, y, point2.y) == CLIP_MIDDLE)
225
2.20k
                {
226
2.20k
                    if (point1.y == point2.y)
227
0
                        continue;  // Ignore horizontal edges.
228
229
2.20k
                    const double slope =
230
2.20k
                        (point2.x - point1.x) / (point2.y - point1.y);
231
232
2.20k
                    double x = point1.x + (y - point1.y) * slope;
233
2.20k
                    xintersect[nfound++] = x;
234
2.20k
                } /* End of checking this edge */
235
236
6.65k
                point1 = point2; /* Go on to next edge */
237
6.65k
            }
238
3.31k
        } /* Finished the scanline */
239
240
        /* First, sort the intersections */
241
2.99k
        bool wrong_order = false;
242
2.99k
        do
243
3.83k
        {
244
3.83k
            wrong_order = false;
245
5.99k
            for (int i = 0; i < nfound - 1; i++)
246
2.15k
            {
247
2.15k
                if (xintersect[i] > xintersect[i + 1])
248
886
                {
249
886
                    wrong_order = true;
250
886
                    std::swap(xintersect[i], xintersect[i + 1]);
251
886
                }
252
2.15k
            }
253
3.83k
        } while (wrong_order);
254
255
        // Great, now find longest span.
256
        // point1.y = y;
257
        // point2.y = y;
258
4.09k
        for (int i = 0; i < nfound - 1; i += 2)
259
1.10k
        {
260
1.10k
            point1.x = xintersect[i];
261
1.10k
            point2.x = xintersect[i + 1];
262
            /* len = length(point1, point2); */
263
1.10k
            const double len = std::abs((point2.x - point1.x));
264
1.10k
            if (len > max_len)
265
363
            {
266
363
                max_len = len;
267
363
                poLabelPoint->setX((point1.x + point2.x) / 2);
268
363
                poLabelPoint->setY(y);
269
363
            }
270
1.10k
        }
271
2.99k
    }
272
273
468
    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
468
    if (poLabelPoint->getX() < oEnv.MinX || poLabelPoint->getY() < oEnv.MinY ||
283
468
        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
468
    if (max_len > 0)
294
147
        return OGRERR_NONE;
295
321
    else
296
321
        return OGRERR_FAILURE;
297
468
}
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
}