/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 | } |