| 1 | /**********************************************************************
|
|---|
| 2 | *
|
|---|
| 3 | * PostGIS - Spatial Types for PostgreSQL
|
|---|
| 4 | * http://postgis.net
|
|---|
| 5 | *
|
|---|
| 6 | * PostGIS is free software: you can redistribute it and/or modify
|
|---|
| 7 | * it under the terms of the GNU General Public License as published by
|
|---|
| 8 | * the Free Software Foundation, either version 2 of the License, or
|
|---|
| 9 | * (at your option) any later version.
|
|---|
| 10 | *
|
|---|
| 11 | * PostGIS is distributed in the hope that it will be useful,
|
|---|
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|---|
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|---|
| 14 | * GNU General Public License for more details.
|
|---|
| 15 | *
|
|---|
| 16 | * You should have received a copy of the GNU General Public License
|
|---|
| 17 | * along with PostGIS. If not, see <http://www.gnu.org/licenses/>.
|
|---|
| 18 | *
|
|---|
| 19 | **********************************************************************
|
|---|
| 20 | *
|
|---|
| 21 | * Copyright 2009 Paul Ramsey <pramsey@cleverelephant.ca>
|
|---|
| 22 | * Copyright 2009 David Skea <David.Skea@gov.bc.ca>
|
|---|
| 23 | *
|
|---|
| 24 | **********************************************************************/
|
|---|
| 25 |
|
|---|
| 26 |
|
|---|
| 27 | #include "liblwgeom_internal.h"
|
|---|
| 28 | #include "lwgeodetic.h"
|
|---|
| 29 | #include "lwgeom_log.h"
|
|---|
| 30 |
|
|---|
| 31 | /**
|
|---|
| 32 | * For testing geodetic bounding box, we have a magic global variable.
|
|---|
| 33 | * When this is true (when the cunit tests set it), use the slow, but
|
|---|
| 34 | * guaranteed correct, algorithm. Otherwise use the regular one.
|
|---|
| 35 | */
|
|---|
| 36 | int gbox_geocentric_slow = LW_FALSE;
|
|---|
| 37 |
|
|---|
| 38 | /**
|
|---|
| 39 | * Utility function for ptarray_contains_point_sphere()
|
|---|
| 40 | */
|
|---|
| 41 | static int
|
|---|
| 42 | point3d_equals(const POINT3D *p1, const POINT3D *p2)
|
|---|
| 43 | {
|
|---|
| 44 | return FP_EQUALS(p1->x, p2->x) && FP_EQUALS(p1->y, p2->y) && FP_EQUALS(p1->z, p2->z);
|
|---|
| 45 | }
|
|---|
| 46 |
|
|---|
| 47 | /**
|
|---|
| 48 | * Convert a longitude to the range of -PI,PI
|
|---|
| 49 | */
|
|---|
| 50 | double longitude_radians_normalize(double lon)
|
|---|
| 51 | {
|
|---|
| 52 | if ( lon == -1.0 * M_PI )
|
|---|
| 53 | return M_PI;
|
|---|
| 54 | if ( lon == -2.0 * M_PI )
|
|---|
| 55 | return 0.0;
|
|---|
| 56 |
|
|---|
| 57 | if ( lon > 2.0 * M_PI )
|
|---|
| 58 | lon = remainder(lon, 2.0 * M_PI);
|
|---|
| 59 |
|
|---|
| 60 | if ( lon < -2.0 * M_PI )
|
|---|
| 61 | lon = remainder(lon, -2.0 * M_PI);
|
|---|
| 62 |
|
|---|
| 63 | if ( lon > M_PI )
|
|---|
| 64 | lon = -2.0 * M_PI + lon;
|
|---|
| 65 |
|
|---|
| 66 | if ( lon < -1.0 * M_PI )
|
|---|
| 67 | lon = 2.0 * M_PI + lon;
|
|---|
| 68 |
|
|---|
| 69 | if ( lon == -2.0 * M_PI )
|
|---|
| 70 | lon *= -1.0;
|
|---|
| 71 |
|
|---|
| 72 | return lon;
|
|---|
| 73 | }
|
|---|
| 74 |
|
|---|
| 75 | /**
|
|---|
| 76 | * Convert a latitude to the range of -PI/2,PI/2
|
|---|
| 77 | */
|
|---|
| 78 | double latitude_radians_normalize(double lat)
|
|---|
| 79 | {
|
|---|
| 80 |
|
|---|
| 81 | if ( lat > 2.0 * M_PI )
|
|---|
| 82 | lat = remainder(lat, 2.0 * M_PI);
|
|---|
| 83 |
|
|---|
| 84 | if ( lat < -2.0 * M_PI )
|
|---|
| 85 | lat = remainder(lat, -2.0 * M_PI);
|
|---|
| 86 |
|
|---|
| 87 | if ( lat > M_PI )
|
|---|
| 88 | lat = M_PI - lat;
|
|---|
| 89 |
|
|---|
| 90 | if ( lat < -1.0 * M_PI )
|
|---|
| 91 | lat = -1.0 * M_PI - lat;
|
|---|
| 92 |
|
|---|
| 93 | if ( lat > M_PI_2 )
|
|---|
| 94 | lat = M_PI - lat;
|
|---|
| 95 |
|
|---|
| 96 | if ( lat < -1.0 * M_PI_2 )
|
|---|
| 97 | lat = -1.0 * M_PI - lat;
|
|---|
| 98 |
|
|---|
| 99 | return lat;
|
|---|
| 100 | }
|
|---|
| 101 |
|
|---|
| 102 | /**
|
|---|
| 103 | * Convert a longitude to the range of -180,180
|
|---|
| 104 | * @param lon longitude in degrees
|
|---|
| 105 | */
|
|---|
| 106 | double longitude_degrees_normalize(double lon)
|
|---|
| 107 | {
|
|---|
| 108 | if ( lon > 360.0 )
|
|---|
| 109 | lon = remainder(lon, 360.0);
|
|---|
| 110 |
|
|---|
| 111 | if ( lon < -360.0 )
|
|---|
| 112 | lon = remainder(lon, -360.0);
|
|---|
| 113 |
|
|---|
| 114 | if ( lon > 180.0 )
|
|---|
| 115 | lon = -360.0 + lon;
|
|---|
| 116 |
|
|---|
| 117 | if ( lon < -180.0 )
|
|---|
| 118 | lon = 360 + lon;
|
|---|
| 119 |
|
|---|
| 120 | if ( lon == -180.0 )
|
|---|
| 121 | return 180.0;
|
|---|
| 122 |
|
|---|
| 123 | if ( lon == -360.0 )
|
|---|
| 124 | return 0.0;
|
|---|
| 125 |
|
|---|
| 126 | return lon;
|
|---|
| 127 | }
|
|---|
| 128 |
|
|---|
| 129 | /**
|
|---|
| 130 | * Convert a latitude to the range of -90,90
|
|---|
| 131 | * @param lat latitude in degrees
|
|---|
| 132 | */
|
|---|
| 133 | double latitude_degrees_normalize(double lat)
|
|---|
| 134 | {
|
|---|
| 135 |
|
|---|
| 136 | if ( lat > 360.0 )
|
|---|
| 137 | lat = remainder(lat, 360.0);
|
|---|
| 138 |
|
|---|
| 139 | if ( lat < -360.0 )
|
|---|
| 140 | lat = remainder(lat, -360.0);
|
|---|
| 141 |
|
|---|
| 142 | if ( lat > 180.0 )
|
|---|
| 143 | lat = 180.0 - lat;
|
|---|
| 144 |
|
|---|
| 145 | if ( lat < -180.0 )
|
|---|
| 146 | lat = -180.0 - lat;
|
|---|
| 147 |
|
|---|
| 148 | if ( lat > 90.0 )
|
|---|
| 149 | lat = 180.0 - lat;
|
|---|
| 150 |
|
|---|
| 151 | if ( lat < -90.0 )
|
|---|
| 152 | lat = -180.0 - lat;
|
|---|
| 153 |
|
|---|
| 154 | return lat;
|
|---|
| 155 | }
|
|---|
| 156 |
|
|---|
| 157 | /**
|
|---|
| 158 | * Shift a point around by a number of radians
|
|---|
| 159 | */
|
|---|
| 160 | void point_shift(GEOGRAPHIC_POINT *p, double shift)
|
|---|
| 161 | {
|
|---|
| 162 | double lon = p->lon + shift;
|
|---|
| 163 | if ( lon > M_PI )
|
|---|
| 164 | p->lon = -1.0 * M_PI + (lon - M_PI);
|
|---|
| 165 | else
|
|---|
| 166 | p->lon = lon;
|
|---|
| 167 | return;
|
|---|
| 168 | }
|
|---|
| 169 |
|
|---|
| 170 | int geographic_point_equals(const GEOGRAPHIC_POINT *g1, const GEOGRAPHIC_POINT *g2)
|
|---|
| 171 | {
|
|---|
| 172 | return FP_EQUALS(g1->lat, g2->lat) && FP_EQUALS(g1->lon, g2->lon);
|
|---|
| 173 | }
|
|---|
| 174 |
|
|---|
| 175 | /**
|
|---|
| 176 | * Initialize a geographic point
|
|---|
| 177 | * @param lon longitude in degrees
|
|---|
| 178 | * @param lat latitude in degrees
|
|---|
| 179 | */
|
|---|
| 180 | void geographic_point_init(double lon, double lat, GEOGRAPHIC_POINT *g)
|
|---|
| 181 | {
|
|---|
| 182 | g->lat = latitude_radians_normalize(deg2rad(lat));
|
|---|
| 183 | g->lon = longitude_radians_normalize(deg2rad(lon));
|
|---|
| 184 | }
|
|---|
| 185 |
|
|---|
| 186 | /** Returns the angular height (latitudinal span) of the box in radians */
|
|---|
| 187 | double
|
|---|
| 188 | gbox_angular_height(const GBOX* gbox)
|
|---|
| 189 | {
|
|---|
| 190 | double d[6];
|
|---|
| 191 | int i;
|
|---|
| 192 | double zmin = FLT_MAX;
|
|---|
| 193 | double zmax = -1 * FLT_MAX;
|
|---|
| 194 | POINT3D pt;
|
|---|
| 195 |
|
|---|
| 196 | /* Take a copy of the box corners so we can treat them as a list */
|
|---|
| 197 | /* Elements are xmin, xmax, ymin, ymax, zmin, zmax */
|
|---|
| 198 | memcpy(d, &(gbox->xmin), 6*sizeof(double));
|
|---|
| 199 |
|
|---|
| 200 | /* Generate all 8 corner vectors of the box */
|
|---|
| 201 | for ( i = 0; i < 8; i++ )
|
|---|
| 202 | {
|
|---|
| 203 | pt.x = d[i / 4];
|
|---|
| 204 | pt.y = d[2 + (i % 4) / 2];
|
|---|
| 205 | pt.z = d[4 + (i % 2)];
|
|---|
| 206 | normalize(&pt);
|
|---|
| 207 | if ( pt.z < zmin ) zmin = pt.z;
|
|---|
| 208 | if ( pt.z > zmax ) zmax = pt.z;
|
|---|
| 209 | }
|
|---|
| 210 | return asin(zmax) - asin(zmin);
|
|---|
| 211 | }
|
|---|
| 212 |
|
|---|
| 213 | /** Returns the angular width (longitudinal span) of the box in radians */
|
|---|
| 214 | double
|
|---|
| 215 | gbox_angular_width(const GBOX* gbox)
|
|---|
| 216 | {
|
|---|
| 217 | double d[6];
|
|---|
| 218 | int i, j;
|
|---|
| 219 | POINT3D pt[3];
|
|---|
| 220 | double maxangle;
|
|---|
| 221 | double magnitude;
|
|---|
| 222 |
|
|---|
| 223 | /* Take a copy of the box corners so we can treat them as a list */
|
|---|
| 224 | /* Elements are xmin, xmax, ymin, ymax, zmin, zmax */
|
|---|
| 225 | memcpy(d, &(gbox->xmin), 6*sizeof(double));
|
|---|
| 226 |
|
|---|
| 227 | /* Start with the bottom corner */
|
|---|
| 228 | pt[0].x = gbox->xmin;
|
|---|
| 229 | pt[0].y = gbox->ymin;
|
|---|
| 230 | magnitude = sqrt(pt[0].x*pt[0].x + pt[0].y*pt[0].y);
|
|---|
| 231 | pt[0].x /= magnitude;
|
|---|
| 232 | pt[0].y /= magnitude;
|
|---|
| 233 |
|
|---|
| 234 | /* Generate all 8 corner vectors of the box */
|
|---|
| 235 | /* Find the vector furthest from our seed vector */
|
|---|
| 236 | for ( j = 0; j < 2; j++ )
|
|---|
| 237 | {
|
|---|
| 238 | maxangle = -1 * FLT_MAX;
|
|---|
| 239 | for ( i = 0; i < 4; i++ )
|
|---|
| 240 | {
|
|---|
| 241 | double angle, dotprod;
|
|---|
| 242 | POINT3D pt_n;
|
|---|
| 243 |
|
|---|
| 244 | pt_n.x = d[i / 2];
|
|---|
| 245 | pt_n.y = d[2 + (i % 2)];
|
|---|
| 246 | magnitude = sqrt(pt_n.x*pt_n.x + pt_n.y*pt_n.y);
|
|---|
| 247 | pt_n.x /= magnitude;
|
|---|
| 248 | pt_n.y /= magnitude;
|
|---|
| 249 | pt_n.z = 0.0;
|
|---|
| 250 |
|
|---|
| 251 | dotprod = pt_n.x*pt[j].x + pt_n.y*pt[j].y;
|
|---|
| 252 | angle = acos(dotprod > 1.0 ? 1.0 : dotprod);
|
|---|
| 253 | if ( angle > maxangle )
|
|---|
| 254 | {
|
|---|
| 255 | pt[j+1] = pt_n;
|
|---|
| 256 | maxangle = angle;
|
|---|
| 257 | }
|
|---|
| 258 | }
|
|---|
| 259 | }
|
|---|
| 260 |
|
|---|
| 261 | /* Return the distance between the two furthest vectors */
|
|---|
| 262 | return maxangle;
|
|---|
| 263 | }
|
|---|
| 264 |
|
|---|
| 265 | /** Computes the average(ish) center of the box and returns success. */
|
|---|
| 266 | int
|
|---|
| 267 | gbox_centroid(const GBOX* gbox, POINT2D* out)
|
|---|
| 268 | {
|
|---|
| 269 | double d[6];
|
|---|
| 270 | GEOGRAPHIC_POINT g;
|
|---|
| 271 | POINT3D pt;
|
|---|
| 272 | int i;
|
|---|
| 273 |
|
|---|
| 274 | /* Take a copy of the box corners so we can treat them as a list */
|
|---|
| 275 | /* Elements are xmin, xmax, ymin, ymax, zmin, zmax */
|
|---|
| 276 | memcpy(d, &(gbox->xmin), 6*sizeof(double));
|
|---|
| 277 |
|
|---|
| 278 | /* Zero out our return vector */
|
|---|
| 279 | pt.x = pt.y = pt.z = 0.0;
|
|---|
| 280 |
|
|---|
| 281 | for ( i = 0; i < 8; i++ )
|
|---|
| 282 | {
|
|---|
| 283 | POINT3D pt_n;
|
|---|
| 284 |
|
|---|
| 285 | pt_n.x = d[i / 4];
|
|---|
| 286 | pt_n.y = d[2 + ((i % 4) / 2)];
|
|---|
| 287 | pt_n.z = d[4 + (i % 2)];
|
|---|
| 288 | normalize(&pt_n);
|
|---|
| 289 |
|
|---|
| 290 | pt.x += pt_n.x;
|
|---|
| 291 | pt.y += pt_n.y;
|
|---|
| 292 | pt.z += pt_n.z;
|
|---|
| 293 | }
|
|---|
| 294 |
|
|---|
| 295 | pt.x /= 8.0;
|
|---|
| 296 | pt.y /= 8.0;
|
|---|
| 297 | pt.z /= 8.0;
|
|---|
| 298 | normalize(&pt);
|
|---|
| 299 |
|
|---|
| 300 | cart2geog(&pt, &g);
|
|---|
| 301 | out->x = longitude_degrees_normalize(rad2deg(g.lon));
|
|---|
| 302 | out->y = latitude_degrees_normalize(rad2deg(g.lat));
|
|---|
| 303 |
|
|---|
| 304 | return LW_SUCCESS;
|
|---|
| 305 | }
|
|---|
| 306 |
|
|---|
| 307 | /**
|
|---|
| 308 | * Check to see if this geocentric gbox is wrapped around a pole.
|
|---|
| 309 | * Only makes sense if this gbox originated from a polygon, as it's assuming
|
|---|
| 310 | * the box is generated from external edges and there's an "interior" which
|
|---|
| 311 | * contains the pole.
|
|---|
| 312 | *
|
|---|
| 313 | * This function is overdetermined, for very large polygons it might add an
|
|---|
| 314 | * unwarranted pole. STILL NEEDS WORK!
|
|---|
| 315 | */
|
|---|
| 316 | static int gbox_check_poles(GBOX *gbox)
|
|---|
| 317 | {
|
|---|
| 318 | int rv = LW_FALSE;
|
|---|
| 319 | #if POSTGIS_DEBUG_LEVEL >= 4
|
|---|
| 320 | char *gbox_str = gbox_to_string(gbox);
|
|---|
| 321 | LWDEBUG(4, "checking poles");
|
|---|
| 322 | LWDEBUGF(4, "gbox %s", gbox_str);
|
|---|
| 323 | lwfree(gbox_str);
|
|---|
| 324 | #endif
|
|---|
| 325 | /* Z axis */
|
|---|
| 326 | if (gbox->xmin < 0.0 && gbox->xmax > 0.0 &&
|
|---|
| 327 | gbox->ymin < 0.0 && gbox->ymax > 0.0)
|
|---|
| 328 | {
|
|---|
| 329 | /* Extrema lean positive */
|
|---|
| 330 | if ((gbox->zmin > 0.0) && (gbox->zmax > 0.0))
|
|---|
| 331 | {
|
|---|
| 332 | LWDEBUG(4, "enclosed positive z axis");
|
|---|
| 333 | gbox->zmax = 1.0;
|
|---|
| 334 | }
|
|---|
| 335 | /* Extrema lean negative */
|
|---|
| 336 | else if ((gbox->zmin < 0.0) && (gbox->zmax < 0.0))
|
|---|
| 337 | {
|
|---|
| 338 | LWDEBUG(4, "enclosed negative z axis");
|
|---|
| 339 | gbox->zmin = -1.0;
|
|---|
| 340 | }
|
|---|
| 341 | /* Extrema both sides! */
|
|---|
| 342 | else
|
|---|
| 343 | {
|
|---|
| 344 | LWDEBUG(4, "enclosed both z axes");
|
|---|
| 345 | gbox->zmin = -1.0;
|
|---|
| 346 | gbox->zmax = 1.0;
|
|---|
| 347 | }
|
|---|
| 348 | rv = LW_TRUE;
|
|---|
| 349 | }
|
|---|
| 350 |
|
|---|
| 351 | /* Y axis */
|
|---|
| 352 | if (gbox->xmin < 0.0 && gbox->xmax > 0.0 &&
|
|---|
| 353 | gbox->zmin < 0.0 && gbox->zmax > 0.0)
|
|---|
| 354 | {
|
|---|
| 355 | if ((gbox->ymin > 0.0) && (gbox->ymax > 0.0))
|
|---|
| 356 | {
|
|---|
| 357 | LWDEBUG(4, "enclosed positive y axis");
|
|---|
| 358 | gbox->ymax = 1.0;
|
|---|
| 359 | }
|
|---|
| 360 | else if ((gbox->ymin < 0.0) && (gbox->ymax < 0.0))
|
|---|
| 361 | {
|
|---|
| 362 | LWDEBUG(4, "enclosed negative y axis");
|
|---|
| 363 | gbox->ymax = -1.0;
|
|---|
| 364 | }
|
|---|
| 365 | else
|
|---|
| 366 | {
|
|---|
| 367 | LWDEBUG(4, "enclosed both y axes");
|
|---|
| 368 | gbox->ymax = 1.0;
|
|---|
| 369 | gbox->ymin = -1.0;
|
|---|
| 370 | }
|
|---|
| 371 | rv = LW_TRUE;
|
|---|
| 372 | }
|
|---|
| 373 |
|
|---|
| 374 | /* X axis */
|
|---|
| 375 | if (gbox->ymin < 0.0 && gbox->ymax > 0.0 &&
|
|---|
| 376 | gbox->zmin < 0.0 && gbox->zmax > 0.0)
|
|---|
| 377 | {
|
|---|
| 378 | if ((gbox->xmin > 0.0) && (gbox->xmax > 0.0))
|
|---|
| 379 | {
|
|---|
| 380 | LWDEBUG(4, "enclosed positive x axis");
|
|---|
| 381 | gbox->xmax = 1.0;
|
|---|
| 382 | }
|
|---|
| 383 | else if ((gbox->xmin < 0.0) && (gbox->xmax < 0.0))
|
|---|
| 384 | {
|
|---|
| 385 | LWDEBUG(4, "enclosed negative x axis");
|
|---|
| 386 | gbox->xmin = -1.0;
|
|---|
| 387 | }
|
|---|
| 388 | else
|
|---|
| 389 | {
|
|---|
| 390 | LWDEBUG(4, "enclosed both x axes");
|
|---|
| 391 | gbox->xmax = 1.0;
|
|---|
| 392 | gbox->xmin = -1.0;
|
|---|
| 393 | }
|
|---|
| 394 |
|
|---|
| 395 | rv = LW_TRUE;
|
|---|
| 396 | }
|
|---|
| 397 |
|
|---|
| 398 | return rv;
|
|---|
| 399 | }
|
|---|
| 400 |
|
|---|
| 401 | /**
|
|---|
| 402 | * Convert spherical coordinates to cartesian coordinates on unit sphere
|
|---|
| 403 | */
|
|---|
| 404 | void geog2cart(const GEOGRAPHIC_POINT *g, POINT3D *p)
|
|---|
| 405 | {
|
|---|
| 406 | p->x = cos(g->lat) * cos(g->lon);
|
|---|
| 407 | p->y = cos(g->lat) * sin(g->lon);
|
|---|
| 408 | p->z = sin(g->lat);
|
|---|
| 409 | }
|
|---|
| 410 |
|
|---|
| 411 | /**
|
|---|
| 412 | * Convert cartesian coordinates on unit sphere to spherical coordinates
|
|---|
| 413 | */
|
|---|
| 414 | void cart2geog(const POINT3D *p, GEOGRAPHIC_POINT *g)
|
|---|
| 415 | {
|
|---|
| 416 | g->lon = atan2(p->y, p->x);
|
|---|
| 417 | g->lat = asin(p->z);
|
|---|
| 418 | }
|
|---|
| 419 |
|
|---|
| 420 | /**
|
|---|
| 421 | * Convert lon/lat coordinates to cartesian coordinates on unit sphere
|
|---|
| 422 | */
|
|---|
| 423 | void ll2cart(const POINT2D *g, POINT3D *p)
|
|---|
| 424 | {
|
|---|
| 425 | double x_rad = M_PI * g->x / 180.0;
|
|---|
| 426 | double y_rad = M_PI * g->y / 180.0;
|
|---|
| 427 | double cos_y_rad = cos(y_rad);
|
|---|
| 428 | p->x = cos_y_rad * cos(x_rad);
|
|---|
| 429 | p->y = cos_y_rad * sin(x_rad);
|
|---|
| 430 | p->z = sin(y_rad);
|
|---|
| 431 | }
|
|---|
| 432 |
|
|---|
| 433 | /**
|
|---|
| 434 | * Convert cartesian coordinates on unit sphere to lon/lat coordinates
|
|---|
| 435 | static void cart2ll(const POINT3D *p, POINT2D *g)
|
|---|
| 436 | {
|
|---|
| 437 | g->x = longitude_degrees_normalize(180.0 * atan2(p->y, p->x) / M_PI);
|
|---|
| 438 | g->y = latitude_degrees_normalize(180.0 * asin(p->z) / M_PI);
|
|---|
| 439 | }
|
|---|
| 440 | */
|
|---|
| 441 |
|
|---|
| 442 | /**
|
|---|
| 443 | * Calculate the dot product of two unit vectors
|
|---|
| 444 | * (-1 == opposite, 0 == orthogonal, 1 == identical)
|
|---|
| 445 | */
|
|---|
| 446 | static double dot_product(const POINT3D *p1, const POINT3D *p2)
|
|---|
| 447 | {
|
|---|
| 448 | return (p1->x*p2->x) + (p1->y*p2->y) + (p1->z*p2->z);
|
|---|
| 449 | }
|
|---|
| 450 |
|
|---|
| 451 | /**
|
|---|
| 452 | * Calculate the cross product of two vectors
|
|---|
| 453 | */
|
|---|
| 454 | static void cross_product(const POINT3D *a, const POINT3D *b, POINT3D *n)
|
|---|
| 455 | {
|
|---|
| 456 | n->x = a->y * b->z - a->z * b->y;
|
|---|
| 457 | n->y = a->z * b->x - a->x * b->z;
|
|---|
| 458 | n->z = a->x * b->y - a->y * b->x;
|
|---|
| 459 | return;
|
|---|
| 460 | }
|
|---|
| 461 |
|
|---|
| 462 | /**
|
|---|
| 463 | * Calculate the sum of two vectors
|
|---|
| 464 | */
|
|---|
| 465 | void vector_sum(const POINT3D *a, const POINT3D *b, POINT3D *n)
|
|---|
| 466 | {
|
|---|
| 467 | n->x = a->x + b->x;
|
|---|
| 468 | n->y = a->y + b->y;
|
|---|
| 469 | n->z = a->z + b->z;
|
|---|
| 470 | return;
|
|---|
| 471 | }
|
|---|
| 472 |
|
|---|
| 473 | /**
|
|---|
| 474 | * Calculate the difference of two vectors
|
|---|
| 475 | */
|
|---|
| 476 | static void vector_difference(const POINT3D *a, const POINT3D *b, POINT3D *n)
|
|---|
| 477 | {
|
|---|
| 478 | n->x = a->x - b->x;
|
|---|
| 479 | n->y = a->y - b->y;
|
|---|
| 480 | n->z = a->z - b->z;
|
|---|
| 481 | return;
|
|---|
| 482 | }
|
|---|
| 483 |
|
|---|
| 484 | /**
|
|---|
| 485 | * Scale a vector out by a factor
|
|---|
| 486 | */
|
|---|
| 487 | void vector_scale(POINT3D *n, double scale)
|
|---|
| 488 | {
|
|---|
| 489 | n->x *= scale;
|
|---|
| 490 | n->y *= scale;
|
|---|
| 491 | n->z *= scale;
|
|---|
| 492 | return;
|
|---|
| 493 | }
|
|---|
| 494 |
|
|---|
| 495 | /*
|
|---|
| 496 | * static inline double vector_magnitude(const POINT3D* v)
|
|---|
| 497 | * {
|
|---|
| 498 | * return sqrt(v->x*v->x + v->y*v->y + v->z*v->z);
|
|---|
| 499 | * }
|
|---|
| 500 | */
|
|---|
| 501 |
|
|---|
| 502 | /**
|
|---|
| 503 | * Angle between two unit vectors
|
|---|
| 504 | */
|
|---|
| 505 | double vector_angle(const POINT3D* v1, const POINT3D* v2)
|
|---|
| 506 | {
|
|---|
| 507 | POINT3D v3, normal;
|
|---|
| 508 | double angle, x, y;
|
|---|
| 509 |
|
|---|
| 510 | cross_product(v1, v2, &normal);
|
|---|
| 511 | normalize(&normal);
|
|---|
| 512 | cross_product(&normal, v1, &v3);
|
|---|
| 513 |
|
|---|
| 514 | x = dot_product(v1, v2);
|
|---|
| 515 | y = dot_product(v2, &v3);
|
|---|
| 516 |
|
|---|
| 517 | angle = atan2(y, x);
|
|---|
| 518 | return angle;
|
|---|
| 519 | }
|
|---|
| 520 |
|
|---|
| 521 | /**
|
|---|
| 522 | * Normalize to a unit vector.
|
|---|
| 523 | */
|
|---|
| 524 | static void normalize2d(POINT2D *p)
|
|---|
| 525 | {
|
|---|
| 526 | double d = sqrt(p->x*p->x + p->y*p->y);
|
|---|
| 527 | if (FP_IS_ZERO(d))
|
|---|
| 528 | {
|
|---|
| 529 | p->x = p->y = 0.0;
|
|---|
| 530 | return;
|
|---|
| 531 | }
|
|---|
| 532 | p->x = p->x / d;
|
|---|
| 533 | p->y = p->y / d;
|
|---|
| 534 | return;
|
|---|
| 535 | }
|
|---|
| 536 |
|
|---|
| 537 | /**
|
|---|
| 538 | * Calculates the unit normal to two vectors, trying to avoid
|
|---|
| 539 | * problems with over-narrow or over-wide cases.
|
|---|
| 540 | */
|
|---|
| 541 | void unit_normal(const POINT3D *P1, const POINT3D *P2, POINT3D *normal)
|
|---|
| 542 | {
|
|---|
| 543 | double p_dot = dot_product(P1, P2);
|
|---|
| 544 | POINT3D P3;
|
|---|
| 545 |
|
|---|
| 546 | /* If edge is really large, calculate a narrower equivalent angle A1/A3. */
|
|---|
| 547 | if ( p_dot < 0 )
|
|---|
| 548 | {
|
|---|
| 549 | vector_sum(P1, P2, &P3);
|
|---|
| 550 | normalize(&P3);
|
|---|
| 551 | }
|
|---|
| 552 | /* If edge is narrow, calculate a wider equivalent angle A1/A3. */
|
|---|
| 553 | else if ( p_dot > 0.95 )
|
|---|
| 554 | {
|
|---|
| 555 | vector_difference(P2, P1, &P3);
|
|---|
| 556 | normalize(&P3);
|
|---|
| 557 | }
|
|---|
| 558 | /* Just keep the current angle in A1/A3. */
|
|---|
| 559 | else
|
|---|
| 560 | {
|
|---|
| 561 | P3 = *P2;
|
|---|
| 562 | }
|
|---|
| 563 |
|
|---|
| 564 | /* Normals to the A-plane and B-plane */
|
|---|
| 565 | cross_product(P1, &P3, normal);
|
|---|
| 566 | normalize(normal);
|
|---|
| 567 | }
|
|---|
| 568 |
|
|---|
| 569 | /**
|
|---|
| 570 | * Rotates v1 through an angle (in radians) within the plane defined by v1/v2, returns
|
|---|
| 571 | * the rotated vector in n.
|
|---|
| 572 | */
|
|---|
| 573 | void vector_rotate(const POINT3D* v1, const POINT3D* v2, double angle, POINT3D* n)
|
|---|
| 574 | {
|
|---|
| 575 | POINT3D u;
|
|---|
| 576 | double cos_a = cos(angle);
|
|---|
| 577 | double sin_a = sin(angle);
|
|---|
| 578 | double uxuy, uyuz, uxuz;
|
|---|
| 579 | double ux2, uy2, uz2;
|
|---|
| 580 | double rxx, rxy, rxz, ryx, ryy, ryz, rzx, rzy, rzz;
|
|---|
| 581 |
|
|---|
| 582 | /* Need a unit vector normal to rotate around */
|
|---|
| 583 | unit_normal(v1, v2, &u);
|
|---|
| 584 |
|
|---|
| 585 | uxuy = u.x * u.y;
|
|---|
| 586 | uxuz = u.x * u.z;
|
|---|
| 587 | uyuz = u.y * u.z;
|
|---|
| 588 |
|
|---|
| 589 | ux2 = u.x * u.x;
|
|---|
| 590 | uy2 = u.y * u.y;
|
|---|
| 591 | uz2 = u.z * u.z;
|
|---|
| 592 |
|
|---|
| 593 | rxx = cos_a + ux2 * (1 - cos_a);
|
|---|
| 594 | rxy = uxuy * (1 - cos_a) - u.z * sin_a;
|
|---|
| 595 | rxz = uxuz * (1 - cos_a) + u.y * sin_a;
|
|---|
| 596 |
|
|---|
| 597 | ryx = uxuy * (1 - cos_a) + u.z * sin_a;
|
|---|
| 598 | ryy = cos_a + uy2 * (1 - cos_a);
|
|---|
| 599 | ryz = uyuz * (1 - cos_a) - u.x * sin_a;
|
|---|
| 600 |
|
|---|
| 601 | rzx = uxuz * (1 - cos_a) - u.y * sin_a;
|
|---|
| 602 | rzy = uyuz * (1 - cos_a) + u.x * sin_a;
|
|---|
| 603 | rzz = cos_a + uz2 * (1 - cos_a);
|
|---|
| 604 |
|
|---|
| 605 | n->x = rxx * v1->x + rxy * v1->y + rxz * v1->z;
|
|---|
| 606 | n->y = ryx * v1->x + ryy * v1->y + ryz * v1->z;
|
|---|
| 607 | n->z = rzx * v1->x + rzy * v1->y + rzz * v1->z;
|
|---|
| 608 |
|
|---|
| 609 | normalize(n);
|
|---|
| 610 | }
|
|---|
| 611 |
|
|---|
| 612 | /**
|
|---|
| 613 | * Normalize to a unit vector.
|
|---|
| 614 | */
|
|---|
| 615 | void normalize(POINT3D *p)
|
|---|
| 616 | {
|
|---|
| 617 | double d = sqrt(p->x*p->x + p->y*p->y + p->z*p->z);
|
|---|
| 618 | if (FP_IS_ZERO(d))
|
|---|
| 619 | {
|
|---|
| 620 | p->x = p->y = p->z = 0.0;
|
|---|
| 621 | return;
|
|---|
| 622 | }
|
|---|
| 623 | p->x = p->x / d;
|
|---|
| 624 | p->y = p->y / d;
|
|---|
| 625 | p->z = p->z / d;
|
|---|
| 626 | return;
|
|---|
| 627 | }
|
|---|
| 628 |
|
|---|
| 629 |
|
|---|
| 630 | /**
|
|---|
| 631 | * Computes the cross product of two vectors using their lat, lng representations.
|
|---|
| 632 | * Good even for small distances between p and q.
|
|---|
| 633 | */
|
|---|
| 634 | void robust_cross_product(const GEOGRAPHIC_POINT *p, const GEOGRAPHIC_POINT *q, POINT3D *a)
|
|---|
| 635 | {
|
|---|
| 636 | double lon_qpp = (q->lon + p->lon) / -2.0;
|
|---|
| 637 | double lon_qmp = (q->lon - p->lon) / 2.0;
|
|---|
| 638 | double sin_p_lat_minus_q_lat = sin(p->lat-q->lat);
|
|---|
| 639 | double sin_p_lat_plus_q_lat = sin(p->lat+q->lat);
|
|---|
| 640 | double sin_lon_qpp = sin(lon_qpp);
|
|---|
| 641 | double sin_lon_qmp = sin(lon_qmp);
|
|---|
| 642 | double cos_lon_qpp = cos(lon_qpp);
|
|---|
| 643 | double cos_lon_qmp = cos(lon_qmp);
|
|---|
| 644 | a->x = sin_p_lat_minus_q_lat * sin_lon_qpp * cos_lon_qmp -
|
|---|
| 645 | sin_p_lat_plus_q_lat * cos_lon_qpp * sin_lon_qmp;
|
|---|
| 646 | a->y = sin_p_lat_minus_q_lat * cos_lon_qpp * cos_lon_qmp +
|
|---|
| 647 | sin_p_lat_plus_q_lat * sin_lon_qpp * sin_lon_qmp;
|
|---|
| 648 | a->z = cos(p->lat) * cos(q->lat) * sin(q->lon-p->lon);
|
|---|
| 649 | }
|
|---|
| 650 |
|
|---|
| 651 | void x_to_z(POINT3D *p)
|
|---|
| 652 | {
|
|---|
| 653 | double tmp = p->z;
|
|---|
| 654 | p->z = p->x;
|
|---|
| 655 | p->x = tmp;
|
|---|
| 656 | }
|
|---|
| 657 |
|
|---|
| 658 | void y_to_z(POINT3D *p)
|
|---|
| 659 | {
|
|---|
| 660 | double tmp = p->z;
|
|---|
| 661 | p->z = p->y;
|
|---|
| 662 | p->y = tmp;
|
|---|
| 663 | }
|
|---|
| 664 |
|
|---|
| 665 |
|
|---|
| 666 | int crosses_dateline(const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e)
|
|---|
| 667 | {
|
|---|
| 668 | double sign_s = SIGNUM(s->lon);
|
|---|
| 669 | double sign_e = SIGNUM(e->lon);
|
|---|
| 670 | double ss = fabs(s->lon);
|
|---|
| 671 | double ee = fabs(e->lon);
|
|---|
| 672 | if ( sign_s == sign_e )
|
|---|
| 673 | {
|
|---|
| 674 | return LW_FALSE;
|
|---|
| 675 | }
|
|---|
| 676 | else
|
|---|
| 677 | {
|
|---|
| 678 | double dl = ss + ee;
|
|---|
| 679 | if ( dl < M_PI )
|
|---|
| 680 | return LW_FALSE;
|
|---|
| 681 | else if ( FP_EQUALS(dl, M_PI) )
|
|---|
| 682 | return LW_FALSE;
|
|---|
| 683 | else
|
|---|
| 684 | return LW_TRUE;
|
|---|
| 685 | }
|
|---|
| 686 | }
|
|---|
| 687 |
|
|---|
| 688 | /**
|
|---|
| 689 | * Returns -1 if the point is to the left of the plane formed
|
|---|
| 690 | * by the edge, 1 if the point is to the right, and 0 if the
|
|---|
| 691 | * point is on the plane.
|
|---|
| 692 | */
|
|---|
| 693 | static int
|
|---|
| 694 | edge_point_side(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p)
|
|---|
| 695 | {
|
|---|
| 696 | POINT3D normal, pt;
|
|---|
| 697 | double w;
|
|---|
| 698 | /* Normal to the plane defined by e */
|
|---|
| 699 | robust_cross_product(&(e->start), &(e->end), &normal);
|
|---|
| 700 | normalize(&normal);
|
|---|
| 701 | geog2cart(p, &pt);
|
|---|
| 702 | /* We expect the dot product of with normal with any vector in the plane to be zero */
|
|---|
| 703 | w = dot_product(&normal, &pt);
|
|---|
| 704 | LWDEBUGF(4,"dot product %.9g",w);
|
|---|
| 705 | if ( FP_IS_ZERO(w) )
|
|---|
| 706 | {
|
|---|
| 707 | LWDEBUG(4, "point is on plane (dot product is zero)");
|
|---|
| 708 | return 0;
|
|---|
| 709 | }
|
|---|
| 710 |
|
|---|
| 711 | if ( w < 0 )
|
|---|
| 712 | return -1;
|
|---|
| 713 | else
|
|---|
| 714 | return 1;
|
|---|
| 715 | }
|
|---|
| 716 |
|
|---|
| 717 | /**
|
|---|
| 718 | * Returns the angle in radians at point B of the triangle formed by A-B-C
|
|---|
| 719 | */
|
|---|
| 720 | static double
|
|---|
| 721 | sphere_angle(const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const GEOGRAPHIC_POINT *c)
|
|---|
| 722 | {
|
|---|
| 723 | POINT3D normal1, normal2;
|
|---|
| 724 | robust_cross_product(b, a, &normal1);
|
|---|
| 725 | robust_cross_product(b, c, &normal2);
|
|---|
| 726 | normalize(&normal1);
|
|---|
| 727 | normalize(&normal2);
|
|---|
| 728 | return sphere_distance_cartesian(&normal1, &normal2);
|
|---|
| 729 | }
|
|---|
| 730 |
|
|---|
| 731 | /**
|
|---|
| 732 | * Computes the spherical area of a triangle. If C is to the left of A/B,
|
|---|
| 733 | * the area is negative. If C is to the right of A/B, the area is positive.
|
|---|
| 734 | *
|
|---|
| 735 | * @param a The first triangle vertex.
|
|---|
| 736 | * @param b The second triangle vertex.
|
|---|
| 737 | * @param c The last triangle vertex.
|
|---|
| 738 | * @return the signed area in radians.
|
|---|
| 739 | */
|
|---|
| 740 | static double
|
|---|
| 741 | sphere_signed_area(const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const GEOGRAPHIC_POINT *c)
|
|---|
| 742 | {
|
|---|
| 743 | double angle_a, angle_b, angle_c;
|
|---|
| 744 | double area_radians = 0.0;
|
|---|
| 745 | int side;
|
|---|
| 746 | GEOGRAPHIC_EDGE e;
|
|---|
| 747 |
|
|---|
| 748 | angle_a = sphere_angle(b,a,c);
|
|---|
| 749 | angle_b = sphere_angle(a,b,c);
|
|---|
| 750 | angle_c = sphere_angle(b,c,a);
|
|---|
| 751 |
|
|---|
| 752 | area_radians = angle_a + angle_b + angle_c - M_PI;
|
|---|
| 753 |
|
|---|
| 754 | /* What's the direction of the B/C edge? */
|
|---|
| 755 | e.start = *a;
|
|---|
| 756 | e.end = *b;
|
|---|
| 757 | side = edge_point_side(&e, c);
|
|---|
| 758 |
|
|---|
| 759 | /* Co-linear points implies no area */
|
|---|
| 760 | if ( side == 0 )
|
|---|
| 761 | return 0.0;
|
|---|
| 762 |
|
|---|
| 763 | /* Add the sign to the area */
|
|---|
| 764 | return side * area_radians;
|
|---|
| 765 | }
|
|---|
| 766 |
|
|---|
| 767 |
|
|---|
| 768 |
|
|---|
| 769 | /**
|
|---|
| 770 | * Returns true if the point p is on the great circle plane.
|
|---|
| 771 | * Forms the scalar triple product of A,B,p and if the volume of the
|
|---|
| 772 | * resulting parallelepiped is near zero the point p is on the
|
|---|
| 773 | * great circle plane.
|
|---|
| 774 | */
|
|---|
| 775 | int edge_point_on_plane(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p)
|
|---|
| 776 | {
|
|---|
| 777 | int side = edge_point_side(e, p);
|
|---|
| 778 | if ( side == 0 )
|
|---|
| 779 | return LW_TRUE;
|
|---|
| 780 |
|
|---|
| 781 | return LW_FALSE;
|
|---|
| 782 | }
|
|---|
| 783 |
|
|---|
| 784 | /**
|
|---|
| 785 | * Returns true if the point p is inside the cone defined by the
|
|---|
| 786 | * two ends of the edge e.
|
|---|
| 787 | */
|
|---|
| 788 | int edge_point_in_cone(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p)
|
|---|
| 789 | {
|
|---|
| 790 | POINT3D vcp, vs, ve, vp;
|
|---|
| 791 | double vs_dot_vcp, vp_dot_vcp;
|
|---|
| 792 | geog2cart(&(e->start), &vs);
|
|---|
| 793 | geog2cart(&(e->end), &ve);
|
|---|
| 794 | /* Antipodal case, everything is inside. */
|
|---|
| 795 | if ( vs.x == -1.0 * ve.x && vs.y == -1.0 * ve.y && vs.z == -1.0 * ve.z )
|
|---|
| 796 | return LW_TRUE;
|
|---|
| 797 | geog2cart(p, &vp);
|
|---|
| 798 | /* The normalized sum bisects the angle between start and end. */
|
|---|
| 799 | vector_sum(&vs, &ve, &vcp);
|
|---|
| 800 | normalize(&vcp);
|
|---|
| 801 | /* The projection of start onto the center defines the minimum similarity */
|
|---|
| 802 | vs_dot_vcp = dot_product(&vs, &vcp);
|
|---|
| 803 | LWDEBUGF(4,"vs_dot_vcp %.19g",vs_dot_vcp);
|
|---|
| 804 | /* The projection of candidate p onto the center */
|
|---|
| 805 | vp_dot_vcp = dot_product(&vp, &vcp);
|
|---|
| 806 | LWDEBUGF(4,"vp_dot_vcp %.19g",vp_dot_vcp);
|
|---|
| 807 | /* If p is more similar than start then p is inside the cone */
|
|---|
| 808 | LWDEBUGF(4,"fabs(vp_dot_vcp - vs_dot_vcp) %.39g",fabs(vp_dot_vcp - vs_dot_vcp));
|
|---|
| 809 |
|
|---|
| 810 | /*
|
|---|
| 811 | ** We want to test that vp_dot_vcp is >= vs_dot_vcp but there are
|
|---|
| 812 | ** numerical stability issues for values that are very very nearly
|
|---|
| 813 | ** equal. Unfortunately there are also values of vp_dot_vcp that are legitimately
|
|---|
| 814 | ** very close to but still less than vs_dot_vcp which we also need to catch.
|
|---|
| 815 | ** The tolerance of 10-17 seems to do the trick on 32-bit and 64-bit architectures,
|
|---|
| 816 | ** for the test cases here.
|
|---|
| 817 | ** However, tuning the tolerance value feels like a dangerous hack.
|
|---|
| 818 | ** Fundamentally, the problem is that this test is so sensitive.
|
|---|
| 819 | */
|
|---|
| 820 |
|
|---|
| 821 | /* 1.1102230246251565404236316680908203125e-16 */
|
|---|
| 822 |
|
|---|
| 823 | if ( vp_dot_vcp > vs_dot_vcp || fabs(vp_dot_vcp - vs_dot_vcp) < 2e-16 )
|
|---|
| 824 | {
|
|---|
| 825 | LWDEBUG(4, "point is in cone");
|
|---|
| 826 | return LW_TRUE;
|
|---|
| 827 | }
|
|---|
| 828 | LWDEBUG(4, "point is not in cone");
|
|---|
| 829 | return LW_FALSE;
|
|---|
| 830 | }
|
|---|
| 831 |
|
|---|
| 832 | /**
|
|---|
| 833 | * True if the longitude of p is within the range of the longitude of the ends of e
|
|---|
| 834 | */
|
|---|
| 835 | int edge_contains_coplanar_point(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p)
|
|---|
| 836 | {
|
|---|
| 837 | GEOGRAPHIC_EDGE g;
|
|---|
| 838 | GEOGRAPHIC_POINT q;
|
|---|
| 839 | double slon = fabs((e->start).lon) + fabs((e->end).lon);
|
|---|
| 840 | double dlon = fabs(fabs((e->start).lon) - fabs((e->end).lon));
|
|---|
| 841 | double slat = (e->start).lat + (e->end).lat;
|
|---|
| 842 |
|
|---|
| 843 | LWDEBUGF(4, "e.start == GPOINT(%.6g %.6g) ", (e->start).lat, (e->start).lon);
|
|---|
| 844 | LWDEBUGF(4, "e.end == GPOINT(%.6g %.6g) ", (e->end).lat, (e->end).lon);
|
|---|
| 845 | LWDEBUGF(4, "p == GPOINT(%.6g %.6g) ", p->lat, p->lon);
|
|---|
| 846 |
|
|---|
| 847 | /* Copy values into working registers */
|
|---|
| 848 | g = *e;
|
|---|
| 849 | q = *p;
|
|---|
| 850 |
|
|---|
| 851 | /* Vertical plane, we need to do this calculation in latitude */
|
|---|
| 852 | if ( FP_EQUALS( g.start.lon, g.end.lon ) )
|
|---|
| 853 | {
|
|---|
| 854 | LWDEBUG(4, "vertical plane, we need to do this calculation in latitude");
|
|---|
| 855 | /* Supposed to be co-planar... */
|
|---|
| 856 | if ( ! FP_EQUALS( q.lon, g.start.lon ) )
|
|---|
| 857 | return LW_FALSE;
|
|---|
| 858 |
|
|---|
| 859 | if ( ( g.start.lat <= q.lat && q.lat <= g.end.lat ) ||
|
|---|
| 860 | ( g.end.lat <= q.lat && q.lat <= g.start.lat ) )
|
|---|
| 861 | {
|
|---|
| 862 | return LW_TRUE;
|
|---|
| 863 | }
|
|---|
| 864 | else
|
|---|
| 865 | {
|
|---|
| 866 | return LW_FALSE;
|
|---|
| 867 | }
|
|---|
| 868 | }
|
|---|
| 869 |
|
|---|
| 870 | /* Over the pole, we need normalize latitude and do this calculation in latitude */
|
|---|
| 871 | if ( FP_EQUALS( slon, M_PI ) && ( SIGNUM(g.start.lon) != SIGNUM(g.end.lon) || FP_EQUALS(dlon, M_PI) ) )
|
|---|
| 872 | {
|
|---|
| 873 | LWDEBUG(4, "over the pole...");
|
|---|
| 874 | /* Antipodal, everything (or nothing?) is inside */
|
|---|
| 875 | if ( FP_EQUALS( slat, 0.0 ) )
|
|---|
| 876 | return LW_TRUE;
|
|---|
| 877 |
|
|---|
| 878 | /* Point *is* the north pole */
|
|---|
| 879 | if ( slat > 0.0 && FP_EQUALS(q.lat, M_PI_2 ) )
|
|---|
| 880 | return LW_TRUE;
|
|---|
| 881 |
|
|---|
| 882 | /* Point *is* the south pole */
|
|---|
| 883 | if ( slat < 0.0 && FP_EQUALS(q.lat, -1.0 * M_PI_2) )
|
|---|
| 884 | return LW_TRUE;
|
|---|
| 885 |
|
|---|
| 886 | LWDEBUG(4, "coplanar?...");
|
|---|
| 887 |
|
|---|
| 888 | /* Supposed to be co-planar... */
|
|---|
| 889 | if ( ! FP_EQUALS( q.lon, g.start.lon ) )
|
|---|
| 890 | return LW_FALSE;
|
|---|
| 891 |
|
|---|
| 892 | LWDEBUG(4, "north or south?...");
|
|---|
| 893 |
|
|---|
| 894 | /* Over north pole, test based on south pole */
|
|---|
| 895 | if ( slat > 0.0 )
|
|---|
| 896 | {
|
|---|
| 897 | LWDEBUG(4, "over the north pole...");
|
|---|
| 898 | if ( q.lat > FP_MIN(g.start.lat, g.end.lat) )
|
|---|
| 899 | return LW_TRUE;
|
|---|
| 900 | else
|
|---|
| 901 | return LW_FALSE;
|
|---|
| 902 | }
|
|---|
| 903 | else
|
|---|
| 904 | /* Over south pole, test based on north pole */
|
|---|
| 905 | {
|
|---|
| 906 | LWDEBUG(4, "over the south pole...");
|
|---|
| 907 | if ( q.lat < FP_MAX(g.start.lat, g.end.lat) )
|
|---|
| 908 | return LW_TRUE;
|
|---|
| 909 | else
|
|---|
| 910 | return LW_FALSE;
|
|---|
| 911 | }
|
|---|
| 912 | }
|
|---|
| 913 |
|
|---|
| 914 | /* Dateline crossing, flip everything to the opposite hemisphere */
|
|---|
| 915 | else if ( slon > M_PI && ( SIGNUM(g.start.lon) != SIGNUM(g.end.lon) ) )
|
|---|
| 916 | {
|
|---|
| 917 | LWDEBUG(4, "crosses dateline, flip longitudes...");
|
|---|
| 918 | if ( g.start.lon > 0.0 )
|
|---|
| 919 | g.start.lon -= M_PI;
|
|---|
| 920 | else
|
|---|
| 921 | g.start.lon += M_PI;
|
|---|
| 922 | if ( g.end.lon > 0.0 )
|
|---|
| 923 | g.end.lon -= M_PI;
|
|---|
| 924 | else
|
|---|
| 925 | g.end.lon += M_PI;
|
|---|
| 926 |
|
|---|
| 927 | if ( q.lon > 0.0 )
|
|---|
| 928 | q.lon -= M_PI;
|
|---|
| 929 | else
|
|---|
| 930 | q.lon += M_PI;
|
|---|
| 931 | }
|
|---|
| 932 |
|
|---|
| 933 | if ( ( g.start.lon <= q.lon && q.lon <= g.end.lon ) ||
|
|---|
| 934 | ( g.end.lon <= q.lon && q.lon <= g.start.lon ) )
|
|---|
| 935 | {
|
|---|
| 936 | LWDEBUG(4, "true, this edge contains point");
|
|---|
| 937 | return LW_TRUE;
|
|---|
| 938 | }
|
|---|
| 939 |
|
|---|
| 940 | LWDEBUG(4, "false, this edge does not contain point");
|
|---|
| 941 | return LW_FALSE;
|
|---|
| 942 | }
|
|---|
| 943 |
|
|---|
| 944 |
|
|---|
| 945 | /**
|
|---|
| 946 | * Given two points on a unit sphere, calculate their distance apart in radians.
|
|---|
| 947 | */
|
|---|
| 948 | double sphere_distance(const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e)
|
|---|
| 949 | {
|
|---|
| 950 | double d_lon = e->lon - s->lon;
|
|---|
| 951 | double cos_d_lon = cos(d_lon);
|
|---|
| 952 | double cos_lat_e = cos(e->lat);
|
|---|
| 953 | double sin_lat_e = sin(e->lat);
|
|---|
| 954 | double cos_lat_s = cos(s->lat);
|
|---|
| 955 | double sin_lat_s = sin(s->lat);
|
|---|
| 956 |
|
|---|
| 957 | double a1 = POW2(cos_lat_e * sin(d_lon));
|
|---|
| 958 | double a2 = POW2(cos_lat_s * sin_lat_e - sin_lat_s * cos_lat_e * cos_d_lon);
|
|---|
| 959 | double a = sqrt(a1 + a2);
|
|---|
| 960 | double b = sin_lat_s * sin_lat_e + cos_lat_s * cos_lat_e * cos_d_lon;
|
|---|
| 961 | return atan2(a, b);
|
|---|
| 962 | }
|
|---|
| 963 |
|
|---|
| 964 | /**
|
|---|
| 965 | * Given two unit vectors, calculate their distance apart in radians.
|
|---|
| 966 | */
|
|---|
| 967 | double sphere_distance_cartesian(const POINT3D *s, const POINT3D *e)
|
|---|
| 968 | {
|
|---|
| 969 | return acos(FP_MIN(1.0, dot_product(s, e)));
|
|---|
| 970 | }
|
|---|
| 971 |
|
|---|
| 972 | /**
|
|---|
| 973 | * Given two points on a unit sphere, calculate the direction from s to e.
|
|---|
| 974 | */
|
|---|
| 975 | double sphere_direction(const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e, double d)
|
|---|
| 976 | {
|
|---|
| 977 | double heading = 0.0;
|
|---|
| 978 | double f;
|
|---|
| 979 |
|
|---|
| 980 | /* Starting from the poles? Special case. */
|
|---|
| 981 | if ( FP_IS_ZERO(cos(s->lat)) )
|
|---|
| 982 | return (s->lat > 0.0) ? M_PI : 0.0;
|
|---|
| 983 |
|
|---|
| 984 | f = (sin(e->lat) - sin(s->lat) * cos(d)) / (sin(d) * cos(s->lat));
|
|---|
| 985 | if ( FP_EQUALS(f, 1.0) )
|
|---|
| 986 | heading = 0.0;
|
|---|
| 987 | else if ( FP_EQUALS(f, -1.0) )
|
|---|
| 988 | heading = M_PI;
|
|---|
| 989 | else if ( fabs(f) > 1.0 )
|
|---|
| 990 | {
|
|---|
| 991 | LWDEBUGF(4, "f = %g", f);
|
|---|
| 992 | heading = acos(f);
|
|---|
| 993 | }
|
|---|
| 994 | else
|
|---|
| 995 | heading = acos(f);
|
|---|
| 996 |
|
|---|
| 997 | if ( sin(e->lon - s->lon) < 0.0 )
|
|---|
| 998 | heading = -1 * heading;
|
|---|
| 999 |
|
|---|
| 1000 | return heading;
|
|---|
| 1001 | }
|
|---|
| 1002 |
|
|---|
| 1003 | #if 0 /* unused */
|
|---|
| 1004 | /**
|
|---|
| 1005 | * Computes the spherical excess of a spherical triangle defined by
|
|---|
| 1006 | * the three vertices A, B, C. Computes on the unit sphere (i.e., divides
|
|---|
| 1007 | * edge lengths by the radius, even if the radius is 1.0). The excess is
|
|---|
| 1008 | * signed based on the sign of the delta longitude of A and B.
|
|---|
| 1009 | *
|
|---|
| 1010 | * @param a The first triangle vertex.
|
|---|
| 1011 | * @param b The second triangle vertex.
|
|---|
| 1012 | * @param c The last triangle vertex.
|
|---|
| 1013 | * @return the signed spherical excess.
|
|---|
| 1014 | */
|
|---|
| 1015 | static double sphere_excess(const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const GEOGRAPHIC_POINT *c)
|
|---|
| 1016 | {
|
|---|
| 1017 | double a_dist = sphere_distance(b, c);
|
|---|
| 1018 | double b_dist = sphere_distance(c, a);
|
|---|
| 1019 | double c_dist = sphere_distance(a, b);
|
|---|
| 1020 | double hca = sphere_direction(c, a, b_dist);
|
|---|
| 1021 | double hcb = sphere_direction(c, b, a_dist);
|
|---|
| 1022 | double sign = SIGNUM(hcb-hca);
|
|---|
| 1023 | double ss = (a_dist + b_dist + c_dist) / 2.0;
|
|---|
| 1024 | double E = tan(ss/2.0)*tan((ss-a_dist)/2.0)*tan((ss-b_dist)/2.0)*tan((ss-c_dist)/2.0);
|
|---|
| 1025 | return 4.0 * atan(sqrt(fabs(E))) * sign;
|
|---|
| 1026 | }
|
|---|
| 1027 | #endif
|
|---|
| 1028 |
|
|---|
| 1029 |
|
|---|
| 1030 | /**
|
|---|
| 1031 | * Returns true if the point p is on the minor edge defined by the
|
|---|
| 1032 | * end points of e.
|
|---|
| 1033 | */
|
|---|
| 1034 | int edge_contains_point(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p)
|
|---|
| 1035 | {
|
|---|
| 1036 | if ( edge_point_in_cone(e, p) && edge_point_on_plane(e, p) )
|
|---|
| 1037 | /* if ( edge_contains_coplanar_point(e, p) && edge_point_on_plane(e, p) ) */
|
|---|
| 1038 | {
|
|---|
| 1039 | LWDEBUG(4, "point is on edge");
|
|---|
| 1040 | return LW_TRUE;
|
|---|
| 1041 | }
|
|---|
| 1042 | LWDEBUG(4, "point is not on edge");
|
|---|
| 1043 | return LW_FALSE;
|
|---|
| 1044 | }
|
|---|
| 1045 |
|
|---|
| 1046 | /**
|
|---|
| 1047 | * Used in great circle to compute the pole of the great circle.
|
|---|
| 1048 | */
|
|---|
| 1049 | double z_to_latitude(double z, int top)
|
|---|
| 1050 | {
|
|---|
| 1051 | double sign = SIGNUM(z);
|
|---|
| 1052 | double tlat = acos(z);
|
|---|
| 1053 | LWDEBUGF(4, "inputs: z(%.8g) sign(%.8g) tlat(%.8g)", z, sign, tlat);
|
|---|
| 1054 | if (FP_IS_ZERO(z))
|
|---|
| 1055 | {
|
|---|
| 1056 | if (top) return M_PI_2;
|
|---|
| 1057 | else return -1.0 * M_PI_2;
|
|---|
| 1058 | }
|
|---|
| 1059 | if (fabs(tlat) > M_PI_2 )
|
|---|
| 1060 | {
|
|---|
| 1061 | tlat = sign * (M_PI - fabs(tlat));
|
|---|
| 1062 | }
|
|---|
| 1063 | else
|
|---|
| 1064 | {
|
|---|
| 1065 | tlat = sign * tlat;
|
|---|
| 1066 | }
|
|---|
| 1067 | LWDEBUGF(4, "output: tlat(%.8g)", tlat);
|
|---|
| 1068 | return tlat;
|
|---|
| 1069 | }
|
|---|
| 1070 |
|
|---|
| 1071 | /**
|
|---|
| 1072 | * Computes the pole of the great circle disk which is the intersection of
|
|---|
| 1073 | * the great circle with the line of maximum/minimum gradient that lies on
|
|---|
| 1074 | * the great circle plane.
|
|---|
| 1075 | */
|
|---|
| 1076 | int clairaut_cartesian(const POINT3D *start, const POINT3D *end, GEOGRAPHIC_POINT *g_top, GEOGRAPHIC_POINT *g_bottom)
|
|---|
| 1077 | {
|
|---|
| 1078 | POINT3D t1, t2;
|
|---|
| 1079 | GEOGRAPHIC_POINT vN1, vN2;
|
|---|
| 1080 | LWDEBUG(4,"entering function");
|
|---|
| 1081 | unit_normal(start, end, &t1);
|
|---|
| 1082 | unit_normal(end, start, &t2);
|
|---|
| 1083 | LWDEBUGF(4, "unit normal t1 == POINT(%.8g %.8g %.8g)", t1.x, t1.y, t1.z);
|
|---|
| 1084 | LWDEBUGF(4, "unit normal t2 == POINT(%.8g %.8g %.8g)", t2.x, t2.y, t2.z);
|
|---|
| 1085 | cart2geog(&t1, &vN1);
|
|---|
| 1086 | cart2geog(&t2, &vN2);
|
|---|
| 1087 | g_top->lat = z_to_latitude(t1.z,LW_TRUE);
|
|---|
| 1088 | g_top->lon = vN2.lon;
|
|---|
| 1089 | g_bottom->lat = z_to_latitude(t2.z,LW_FALSE);
|
|---|
| 1090 | g_bottom->lon = vN1.lon;
|
|---|
| 1091 | LWDEBUGF(4, "clairaut top == GPOINT(%.6g %.6g)", g_top->lat, g_top->lon);
|
|---|
| 1092 | LWDEBUGF(4, "clairaut bottom == GPOINT(%.6g %.6g)", g_bottom->lat, g_bottom->lon);
|
|---|
| 1093 | return LW_SUCCESS;
|
|---|
| 1094 | }
|
|---|
| 1095 |
|
|---|
| 1096 | /**
|
|---|
| 1097 | * Computes the pole of the great circle disk which is the intersection of
|
|---|
| 1098 | * the great circle with the line of maximum/minimum gradient that lies on
|
|---|
| 1099 | * the great circle plane.
|
|---|
| 1100 | */
|
|---|
| 1101 | int clairaut_geographic(const GEOGRAPHIC_POINT *start, const GEOGRAPHIC_POINT *end, GEOGRAPHIC_POINT *g_top, GEOGRAPHIC_POINT *g_bottom)
|
|---|
| 1102 | {
|
|---|
| 1103 | POINT3D t1, t2;
|
|---|
| 1104 | GEOGRAPHIC_POINT vN1, vN2;
|
|---|
| 1105 | LWDEBUG(4,"entering function");
|
|---|
| 1106 | robust_cross_product(start, end, &t1);
|
|---|
| 1107 | normalize(&t1);
|
|---|
| 1108 | robust_cross_product(end, start, &t2);
|
|---|
| 1109 | normalize(&t2);
|
|---|
| 1110 | LWDEBUGF(4, "unit normal t1 == POINT(%.8g %.8g %.8g)", t1.x, t1.y, t1.z);
|
|---|
| 1111 | LWDEBUGF(4, "unit normal t2 == POINT(%.8g %.8g %.8g)", t2.x, t2.y, t2.z);
|
|---|
| 1112 | cart2geog(&t1, &vN1);
|
|---|
| 1113 | cart2geog(&t2, &vN2);
|
|---|
| 1114 | g_top->lat = z_to_latitude(t1.z,LW_TRUE);
|
|---|
| 1115 | g_top->lon = vN2.lon;
|
|---|
| 1116 | g_bottom->lat = z_to_latitude(t2.z,LW_FALSE);
|
|---|
| 1117 | g_bottom->lon = vN1.lon;
|
|---|
| 1118 | LWDEBUGF(4, "clairaut top == GPOINT(%.6g %.6g)", g_top->lat, g_top->lon);
|
|---|
| 1119 | LWDEBUGF(4, "clairaut bottom == GPOINT(%.6g %.6g)", g_bottom->lat, g_bottom->lon);
|
|---|
| 1120 | return LW_SUCCESS;
|
|---|
| 1121 | }
|
|---|
| 1122 |
|
|---|
| 1123 | /**
|
|---|
| 1124 | * Returns true if an intersection can be calculated, and places it in *g.
|
|---|
| 1125 | * Returns false otherwise.
|
|---|
| 1126 | */
|
|---|
| 1127 | int edge_intersection(const GEOGRAPHIC_EDGE *e1, const GEOGRAPHIC_EDGE *e2, GEOGRAPHIC_POINT *g)
|
|---|
| 1128 | {
|
|---|
| 1129 | POINT3D ea, eb, v;
|
|---|
| 1130 | LWDEBUGF(4, "e1 start(%.20g %.20g) end(%.20g %.20g)", e1->start.lat, e1->start.lon, e1->end.lat, e1->end.lon);
|
|---|
| 1131 | LWDEBUGF(4, "e2 start(%.20g %.20g) end(%.20g %.20g)", e2->start.lat, e2->start.lon, e2->end.lat, e2->end.lon);
|
|---|
| 1132 |
|
|---|
| 1133 | LWDEBUGF(4, "e1 start(%.20g %.20g) end(%.20g %.20g)", rad2deg(e1->start.lon), rad2deg(e1->start.lat), rad2deg(e1->end.lon), rad2deg(e1->end.lat));
|
|---|
| 1134 | LWDEBUGF(4, "e2 start(%.20g %.20g) end(%.20g %.20g)", rad2deg(e2->start.lon), rad2deg(e2->start.lat), rad2deg(e2->end.lon), rad2deg(e2->end.lat));
|
|---|
| 1135 |
|
|---|
| 1136 | if ( geographic_point_equals(&(e1->start), &(e2->start)) )
|
|---|
| 1137 | {
|
|---|
| 1138 | *g = e1->start;
|
|---|
| 1139 | return LW_TRUE;
|
|---|
| 1140 | }
|
|---|
| 1141 | if ( geographic_point_equals(&(e1->end), &(e2->end)) )
|
|---|
| 1142 | {
|
|---|
| 1143 | *g = e1->end;
|
|---|
| 1144 | return LW_TRUE;
|
|---|
| 1145 | }
|
|---|
| 1146 | if ( geographic_point_equals(&(e1->end), &(e2->start)) )
|
|---|
| 1147 | {
|
|---|
| 1148 | *g = e1->end;
|
|---|
| 1149 | return LW_TRUE;
|
|---|
| 1150 | }
|
|---|
| 1151 | if ( geographic_point_equals(&(e1->start), &(e2->end)) )
|
|---|
| 1152 | {
|
|---|
| 1153 | *g = e1->start;
|
|---|
| 1154 | return LW_TRUE;
|
|---|
| 1155 | }
|
|---|
| 1156 |
|
|---|
| 1157 | robust_cross_product(&(e1->start), &(e1->end), &ea);
|
|---|
| 1158 | normalize(&ea);
|
|---|
| 1159 | robust_cross_product(&(e2->start), &(e2->end), &eb);
|
|---|
| 1160 | normalize(&eb);
|
|---|
| 1161 | LWDEBUGF(4, "e1 cross product == POINT(%.12g %.12g %.12g)", ea.x, ea.y, ea.z);
|
|---|
| 1162 | LWDEBUGF(4, "e2 cross product == POINT(%.12g %.12g %.12g)", eb.x, eb.y, eb.z);
|
|---|
| 1163 | LWDEBUGF(4, "fabs(dot_product(ea, eb)) == %.14g", fabs(dot_product(&ea, &eb)));
|
|---|
| 1164 | if ( FP_EQUALS(fabs(dot_product(&ea, &eb)), 1.0) )
|
|---|
| 1165 | {
|
|---|
| 1166 | LWDEBUGF(4, "parallel edges found! dot_product = %.12g", dot_product(&ea, &eb));
|
|---|
| 1167 | /* Parallel (maybe equal) edges! */
|
|---|
| 1168 | /* Hack alert, only returning ONE end of the edge right now, most do better later. */
|
|---|
| 1169 | /* Hack alert #2, returning a value of 2 to indicate a co-linear crossing event. */
|
|---|
| 1170 | if ( edge_contains_point(e1, &(e2->start)) )
|
|---|
| 1171 | {
|
|---|
| 1172 | *g = e2->start;
|
|---|
| 1173 | return 2;
|
|---|
| 1174 | }
|
|---|
| 1175 | if ( edge_contains_point(e1, &(e2->end)) )
|
|---|
| 1176 | {
|
|---|
| 1177 | *g = e2->end;
|
|---|
| 1178 | return 2;
|
|---|
| 1179 | }
|
|---|
| 1180 | if ( edge_contains_point(e2, &(e1->start)) )
|
|---|
| 1181 | {
|
|---|
| 1182 | *g = e1->start;
|
|---|
| 1183 | return 2;
|
|---|
| 1184 | }
|
|---|
| 1185 | if ( edge_contains_point(e2, &(e1->end)) )
|
|---|
| 1186 | {
|
|---|
| 1187 | *g = e1->end;
|
|---|
| 1188 | return 2;
|
|---|
| 1189 | }
|
|---|
| 1190 | }
|
|---|
| 1191 | unit_normal(&ea, &eb, &v);
|
|---|
| 1192 | LWDEBUGF(4, "v == POINT(%.12g %.12g %.12g)", v.x, v.y, v.z);
|
|---|
| 1193 | g->lat = atan2(v.z, sqrt(v.x * v.x + v.y * v.y));
|
|---|
| 1194 | g->lon = atan2(v.y, v.x);
|
|---|
| 1195 | LWDEBUGF(4, "g == GPOINT(%.12g %.12g)", g->lat, g->lon);
|
|---|
| 1196 | LWDEBUGF(4, "g == POINT(%.12g %.12g)", rad2deg(g->lon), rad2deg(g->lat));
|
|---|
| 1197 | if ( edge_contains_point(e1, g) && edge_contains_point(e2, g) )
|
|---|
| 1198 | {
|
|---|
| 1199 | return LW_TRUE;
|
|---|
| 1200 | }
|
|---|
| 1201 | else
|
|---|
| 1202 | {
|
|---|
| 1203 | LWDEBUG(4, "flipping point to other side of sphere");
|
|---|
| 1204 | g->lat = -1.0 * g->lat;
|
|---|
| 1205 | g->lon = g->lon + M_PI;
|
|---|
| 1206 | if ( g->lon > M_PI )
|
|---|
| 1207 | {
|
|---|
| 1208 | g->lon = -1.0 * (2.0 * M_PI - g->lon);
|
|---|
| 1209 | }
|
|---|
| 1210 | if ( edge_contains_point(e1, g) && edge_contains_point(e2, g) )
|
|---|
| 1211 | {
|
|---|
| 1212 | return LW_TRUE;
|
|---|
| 1213 | }
|
|---|
| 1214 | }
|
|---|
| 1215 | return LW_FALSE;
|
|---|
| 1216 | }
|
|---|
| 1217 |
|
|---|
| 1218 | double edge_distance_to_point(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *gp, GEOGRAPHIC_POINT *closest)
|
|---|
| 1219 | {
|
|---|
| 1220 | double d1 = 1000000000.0, d2, d3, d_nearest;
|
|---|
| 1221 | POINT3D n, p, k;
|
|---|
| 1222 | GEOGRAPHIC_POINT gk, g_nearest;
|
|---|
| 1223 |
|
|---|
| 1224 | /* Zero length edge, */
|
|---|
| 1225 | if ( geographic_point_equals(&(e->start), &(e->end)) )
|
|---|
| 1226 | {
|
|---|
| 1227 | *closest = e->start;
|
|---|
| 1228 | return sphere_distance(&(e->start), gp);
|
|---|
| 1229 | }
|
|---|
| 1230 |
|
|---|
| 1231 | robust_cross_product(&(e->start), &(e->end), &n);
|
|---|
| 1232 | normalize(&n);
|
|---|
| 1233 | geog2cart(gp, &p);
|
|---|
| 1234 | vector_scale(&n, dot_product(&p, &n));
|
|---|
| 1235 | vector_difference(&p, &n, &k);
|
|---|
| 1236 | normalize(&k);
|
|---|
| 1237 | cart2geog(&k, &gk);
|
|---|
| 1238 | if ( edge_contains_point(e, &gk) )
|
|---|
| 1239 | {
|
|---|
| 1240 | d1 = sphere_distance(gp, &gk);
|
|---|
| 1241 | }
|
|---|
| 1242 | d2 = sphere_distance(gp, &(e->start));
|
|---|
| 1243 | d3 = sphere_distance(gp, &(e->end));
|
|---|
| 1244 |
|
|---|
| 1245 | d_nearest = d1;
|
|---|
| 1246 | g_nearest = gk;
|
|---|
| 1247 |
|
|---|
| 1248 | if ( d2 < d_nearest )
|
|---|
| 1249 | {
|
|---|
| 1250 | d_nearest = d2;
|
|---|
| 1251 | g_nearest = e->start;
|
|---|
| 1252 | }
|
|---|
| 1253 | if ( d3 < d_nearest )
|
|---|
| 1254 | {
|
|---|
| 1255 | d_nearest = d3;
|
|---|
| 1256 | g_nearest = e->end;
|
|---|
| 1257 | }
|
|---|
| 1258 | if (closest)
|
|---|
| 1259 | *closest = g_nearest;
|
|---|
| 1260 |
|
|---|
| 1261 | return d_nearest;
|
|---|
| 1262 | }
|
|---|
| 1263 |
|
|---|
| 1264 | /**
|
|---|
| 1265 | * Calculate the distance between two edges.
|
|---|
| 1266 | * IMPORTANT: this test does not check for edge intersection!!! (distance == 0)
|
|---|
| 1267 | * You have to check for intersection before calling this function.
|
|---|
| 1268 | */
|
|---|
| 1269 | double edge_distance_to_edge(const GEOGRAPHIC_EDGE *e1, const GEOGRAPHIC_EDGE *e2, GEOGRAPHIC_POINT *closest1, GEOGRAPHIC_POINT *closest2)
|
|---|
| 1270 | {
|
|---|
| 1271 | double d;
|
|---|
| 1272 | GEOGRAPHIC_POINT gcp1s, gcp1e, gcp2s, gcp2e, c1, c2;
|
|---|
| 1273 | double d1s = edge_distance_to_point(e1, &(e2->start), &gcp1s);
|
|---|
| 1274 | double d1e = edge_distance_to_point(e1, &(e2->end), &gcp1e);
|
|---|
| 1275 | double d2s = edge_distance_to_point(e2, &(e1->start), &gcp2s);
|
|---|
| 1276 | double d2e = edge_distance_to_point(e2, &(e1->end), &gcp2e);
|
|---|
| 1277 |
|
|---|
| 1278 | d = d1s;
|
|---|
| 1279 | c1 = gcp1s;
|
|---|
| 1280 | c2 = e2->start;
|
|---|
| 1281 |
|
|---|
| 1282 | if ( d1e < d )
|
|---|
| 1283 | {
|
|---|
| 1284 | d = d1e;
|
|---|
| 1285 | c1 = gcp1e;
|
|---|
| 1286 | c2 = e2->end;
|
|---|
| 1287 | }
|
|---|
| 1288 |
|
|---|
| 1289 | if ( d2s < d )
|
|---|
| 1290 | {
|
|---|
| 1291 | d = d2s;
|
|---|
| 1292 | c1 = e1->start;
|
|---|
| 1293 | c2 = gcp2s;
|
|---|
| 1294 | }
|
|---|
| 1295 |
|
|---|
| 1296 | if ( d2e < d )
|
|---|
| 1297 | {
|
|---|
| 1298 | d = d2e;
|
|---|
| 1299 | c1 = e1->end;
|
|---|
| 1300 | c2 = gcp2e;
|
|---|
| 1301 | }
|
|---|
| 1302 |
|
|---|
| 1303 | if ( closest1 ) *closest1 = c1;
|
|---|
| 1304 | if ( closest2 ) *closest2 = c2;
|
|---|
| 1305 |
|
|---|
| 1306 | return d;
|
|---|
| 1307 | }
|
|---|
| 1308 |
|
|---|
| 1309 |
|
|---|
| 1310 | /**
|
|---|
| 1311 | * Given a starting location r, a distance and an azimuth
|
|---|
| 1312 | * to the new point, compute the location of the projected point on the unit sphere.
|
|---|
| 1313 | */
|
|---|
| 1314 | int sphere_project(const GEOGRAPHIC_POINT *r, double distance, double azimuth, GEOGRAPHIC_POINT *n)
|
|---|
| 1315 | {
|
|---|
| 1316 | double d = distance;
|
|---|
| 1317 | double lat1 = r->lat;
|
|---|
| 1318 | double lon1 = r->lon;
|
|---|
| 1319 | double lat2, lon2;
|
|---|
| 1320 |
|
|---|
| 1321 | lat2 = asin(sin(lat1)*cos(d) + cos(lat1)*sin(d)*cos(azimuth));
|
|---|
| 1322 |
|
|---|
| 1323 | /* If we're going straight up or straight down, we don't need to calculate the longitude */
|
|---|
| 1324 | /* TODO: this isn't quite true, what if we're going over the pole? */
|
|---|
| 1325 | if ( FP_EQUALS(azimuth, M_PI) || FP_EQUALS(azimuth, 0.0) )
|
|---|
| 1326 | {
|
|---|
| 1327 | lon2 = r->lon;
|
|---|
| 1328 | }
|
|---|
| 1329 | else
|
|---|
| 1330 | {
|
|---|
| 1331 | lon2 = lon1 + atan2(sin(azimuth)*sin(d)*cos(lat1), cos(d)-sin(lat1)*sin(lat2));
|
|---|
| 1332 | }
|
|---|
| 1333 |
|
|---|
| 1334 | if ( isnan(lat2) || isnan(lon2) )
|
|---|
| 1335 | return LW_FAILURE;
|
|---|
| 1336 |
|
|---|
| 1337 | n->lat = lat2;
|
|---|
| 1338 | n->lon = lon2;
|
|---|
| 1339 |
|
|---|
| 1340 | return LW_SUCCESS;
|
|---|
| 1341 | }
|
|---|
| 1342 |
|
|---|
| 1343 |
|
|---|
| 1344 | int edge_calculate_gbox_slow(const GEOGRAPHIC_EDGE *e, GBOX *gbox)
|
|---|
| 1345 | {
|
|---|
| 1346 | int steps = 1000000;
|
|---|
| 1347 | int i;
|
|---|
| 1348 | double dx, dy, dz;
|
|---|
| 1349 | double distance = sphere_distance(&(e->start), &(e->end));
|
|---|
| 1350 | POINT3D pn, p, start, end;
|
|---|
| 1351 |
|
|---|
| 1352 | /* Edge is zero length, just return the naive box */
|
|---|
| 1353 | if ( FP_IS_ZERO(distance) )
|
|---|
| 1354 | {
|
|---|
| 1355 | LWDEBUG(4, "edge is zero length. returning");
|
|---|
| 1356 | geog2cart(&(e->start), &start);
|
|---|
| 1357 | geog2cart(&(e->end), &end);
|
|---|
| 1358 | gbox_init_point3d(&start, gbox);
|
|---|
| 1359 | gbox_merge_point3d(&end, gbox);
|
|---|
| 1360 | return LW_SUCCESS;
|
|---|
| 1361 | }
|
|---|
| 1362 |
|
|---|
| 1363 | /* Edge is antipodal (one point on each side of the globe),
|
|---|
| 1364 | set the box to contain the whole world and return */
|
|---|
| 1365 | if ( FP_EQUALS(distance, M_PI) )
|
|---|
| 1366 | {
|
|---|
| 1367 | LWDEBUG(4, "edge is antipodal. setting to maximum size box, and returning");
|
|---|
| 1368 | gbox->xmin = gbox->ymin = gbox->zmin = -1.0;
|
|---|
| 1369 | gbox->xmax = gbox->ymax = gbox->zmax = 1.0;
|
|---|
| 1370 | return LW_SUCCESS;
|
|---|
| 1371 | }
|
|---|
| 1372 |
|
|---|
| 1373 | /* Walk along the chord between start and end incrementally,
|
|---|
| 1374 | normalizing at each step. */
|
|---|
| 1375 | geog2cart(&(e->start), &start);
|
|---|
| 1376 | geog2cart(&(e->end), &end);
|
|---|
| 1377 | dx = (end.x - start.x)/steps;
|
|---|
| 1378 | dy = (end.y - start.y)/steps;
|
|---|
| 1379 | dz = (end.z - start.z)/steps;
|
|---|
| 1380 | p = start;
|
|---|
| 1381 | gbox->xmin = gbox->xmax = p.x;
|
|---|
| 1382 | gbox->ymin = gbox->ymax = p.y;
|
|---|
| 1383 | gbox->zmin = gbox->zmax = p.z;
|
|---|
| 1384 | for ( i = 0; i < steps; i++ )
|
|---|
| 1385 | {
|
|---|
| 1386 | p.x += dx;
|
|---|
| 1387 | p.y += dy;
|
|---|
| 1388 | p.z += dz;
|
|---|
| 1389 | pn = p;
|
|---|
| 1390 | normalize(&pn);
|
|---|
| 1391 | gbox_merge_point3d(&pn, gbox);
|
|---|
| 1392 | }
|
|---|
| 1393 | return LW_SUCCESS;
|
|---|
| 1394 | }
|
|---|
| 1395 |
|
|---|
| 1396 | /**
|
|---|
| 1397 | * The magic function, given an edge in spherical coordinates, calculate a
|
|---|
| 1398 | * 3D bounding box that fully contains it, taking into account the curvature
|
|---|
| 1399 | * of the sphere on which it is inscribed.
|
|---|
| 1400 | *
|
|---|
| 1401 | * Any arc on the sphere defines a plane that bisects the sphere. In this plane,
|
|---|
| 1402 | * the arc is a portion of a unit circle.
|
|---|
| 1403 | * Projecting the end points of the axes (1,0,0), (-1,0,0) etc, into the plane
|
|---|
| 1404 | * and normalizing yields potential extrema points. Those points on the
|
|---|
| 1405 | * side of the plane-dividing line formed by the end points that is opposite
|
|---|
| 1406 | * the origin of the plane are extrema and should be added to the bounding box.
|
|---|
| 1407 | */
|
|---|
| 1408 | int edge_calculate_gbox(const POINT3D *A1, const POINT3D *A2, GBOX *gbox)
|
|---|
| 1409 | {
|
|---|
| 1410 | POINT2D R1, R2, RX, O;
|
|---|
| 1411 | POINT3D AN, A3;
|
|---|
| 1412 | POINT3D X[6];
|
|---|
| 1413 | int i, o_side;
|
|---|
| 1414 |
|
|---|
| 1415 | /* Initialize the box with the edge end points */
|
|---|
| 1416 | gbox_init_point3d(A1, gbox);
|
|---|
| 1417 | gbox_merge_point3d(A2, gbox);
|
|---|
| 1418 |
|
|---|
| 1419 | /* Zero length edge, just return! */
|
|---|
| 1420 | if ( p3d_same(A1, A2) )
|
|---|
| 1421 | return LW_SUCCESS;
|
|---|
| 1422 |
|
|---|
| 1423 | /* Error out on antipodal edge */
|
|---|
| 1424 | if ( FP_EQUALS(A1->x, -1*A2->x) && FP_EQUALS(A1->y, -1*A2->y) && FP_EQUALS(A1->z, -1*A2->z) )
|
|---|
| 1425 | {
|
|---|
| 1426 | lwerror("Antipodal (180 degrees long) edge detected!");
|
|---|
| 1427 | return LW_FAILURE;
|
|---|
| 1428 | }
|
|---|
| 1429 |
|
|---|
| 1430 | /* Create A3, a vector in the plane of A1/A2, orthogonal to A1 */
|
|---|
| 1431 | unit_normal(A1, A2, &AN);
|
|---|
| 1432 | unit_normal(&AN, A1, &A3);
|
|---|
| 1433 |
|
|---|
| 1434 | /* Project A1 and A2 into the 2-space formed by the plane A1/A3 */
|
|---|
| 1435 | R1.x = 1.0;
|
|---|
| 1436 | R1.y = 0.0;
|
|---|
| 1437 | R2.x = dot_product(A2, A1);
|
|---|
| 1438 | R2.y = dot_product(A2, &A3);
|
|---|
| 1439 |
|
|---|
| 1440 | /* Initialize our 3-space axis points (x+, x-, y+, y-, z+, z-) */
|
|---|
| 1441 | memset(X, 0, sizeof(POINT3D) * 6);
|
|---|
| 1442 | X[0].x = X[2].y = X[4].z = 1.0;
|
|---|
| 1443 | X[1].x = X[3].y = X[5].z = -1.0;
|
|---|
| 1444 |
|
|---|
| 1445 | /* Initialize a 2-space origin point. */
|
|---|
| 1446 | O.x = O.y = 0.0;
|
|---|
| 1447 | /* What side of the line joining R1/R2 is O? */
|
|---|
| 1448 | o_side = lw_segment_side(&R1, &R2, &O);
|
|---|
| 1449 |
|
|---|
| 1450 | /* Add any extrema! */
|
|---|
| 1451 | for ( i = 0; i < 6; i++ )
|
|---|
| 1452 | {
|
|---|
| 1453 | /* Convert 3-space axis points to 2-space unit vectors */
|
|---|
| 1454 | RX.x = dot_product(&(X[i]), A1);
|
|---|
| 1455 | RX.y = dot_product(&(X[i]), &A3);
|
|---|
| 1456 | normalize2d(&RX);
|
|---|
| 1457 |
|
|---|
| 1458 | /* Any axis end on the side of R1/R2 opposite the origin */
|
|---|
| 1459 | /* is an extreme point in the arc, so we add the 3-space */
|
|---|
| 1460 | /* version of the point on R1/R2 to the gbox */
|
|---|
| 1461 | if ( lw_segment_side(&R1, &R2, &RX) != o_side )
|
|---|
| 1462 | {
|
|---|
| 1463 | POINT3D Xn;
|
|---|
| 1464 | Xn.x = RX.x * A1->x + RX.y * A3.x;
|
|---|
| 1465 | Xn.y = RX.x * A1->y + RX.y * A3.y;
|
|---|
| 1466 | Xn.z = RX.x * A1->z + RX.y * A3.z;
|
|---|
| 1467 |
|
|---|
| 1468 | gbox_merge_point3d(&Xn, gbox);
|
|---|
| 1469 | }
|
|---|
| 1470 | }
|
|---|
| 1471 |
|
|---|
| 1472 | return LW_SUCCESS;
|
|---|
| 1473 | }
|
|---|
| 1474 |
|
|---|
| 1475 | /*
|
|---|
| 1476 | * When we have a globe-covering gbox but we still want an outside
|
|---|
| 1477 | * point, we do this Very Bad Hack, which is look at the first two points
|
|---|
| 1478 | * in the ring and then nudge a point to the left of that arc.
|
|---|
| 1479 | * There is an assumption of convexity built in there, as well as that
|
|---|
| 1480 | * the shape doesn't have a sharp reversal in it. It's ugly, but
|
|---|
| 1481 | * it fixes some common cases (large selection polygons) that users
|
|---|
| 1482 | * are generating. At some point all of geodetic needs a clean-room
|
|---|
| 1483 | * rewrite.
|
|---|
| 1484 | * There is also an assumption of CCW exterior ring, which is how the
|
|---|
| 1485 | * GeoJSON spec defined geographic ring orientation.
|
|---|
| 1486 | */
|
|---|
| 1487 | static int lwpoly_pt_outside_hack(const LWPOLY *poly, POINT2D *pt_outside)
|
|---|
| 1488 | {
|
|---|
| 1489 | GEOGRAPHIC_POINT g1, g2, gSum;
|
|---|
| 1490 | POINT4D p1, p2;
|
|---|
| 1491 | POINT3D q1, q2, qMid, qCross, qSum;
|
|---|
| 1492 | POINTARRAY *pa;
|
|---|
| 1493 | if (lwgeom_is_empty((LWGEOM*)poly))
|
|---|
| 1494 | return LW_FAILURE;
|
|---|
| 1495 | if (poly->nrings < 1)
|
|---|
| 1496 | return LW_FAILURE;
|
|---|
| 1497 | pa = poly->rings[0];
|
|---|
| 1498 | if (pa->npoints < 2)
|
|---|
| 1499 | return LW_FAILURE;
|
|---|
| 1500 |
|
|---|
| 1501 | /* First two points of ring */
|
|---|
| 1502 | getPoint4d_p(pa, 0, &p1);
|
|---|
| 1503 | getPoint4d_p(pa, 1, &p2);
|
|---|
| 1504 | /* Convert to XYZ unit vectors */
|
|---|
| 1505 | geographic_point_init(p1.x, p1.y, &g1);
|
|---|
| 1506 | geographic_point_init(p2.x, p2.y, &g2);
|
|---|
| 1507 | geog2cart(&g1, &q1);
|
|---|
| 1508 | geog2cart(&g2, &q2);
|
|---|
| 1509 | /* Mid-point of first two points */
|
|---|
| 1510 | vector_sum(&q1, &q2, &qMid);
|
|---|
| 1511 | normalize(&qMid);
|
|---|
| 1512 | /* Cross product of first two points (perpendicular) */
|
|---|
| 1513 | cross_product(&q1, &q2, &qCross);
|
|---|
| 1514 | normalize(&qCross);
|
|---|
| 1515 | /* Invert it to put it outside, and scale down */
|
|---|
| 1516 | vector_scale(&qCross, -0.2);
|
|---|
| 1517 | /* Project midpoint to the right */
|
|---|
| 1518 | vector_sum(&qMid, &qCross, &qSum);
|
|---|
| 1519 | normalize(&qSum);
|
|---|
| 1520 | /* Convert back to lon/lat */
|
|---|
| 1521 | cart2geog(&qSum, &gSum);
|
|---|
| 1522 | pt_outside->x = rad2deg(gSum.lon);
|
|---|
| 1523 | pt_outside->y = rad2deg(gSum.lat);
|
|---|
| 1524 | return LW_SUCCESS;
|
|---|
| 1525 | }
|
|---|
| 1526 |
|
|---|
| 1527 | int lwpoly_pt_outside(const LWPOLY *poly, POINT2D *pt_outside)
|
|---|
| 1528 | {
|
|---|
| 1529 | int rv;
|
|---|
| 1530 | /* Make sure we have boxes */
|
|---|
| 1531 | if ( poly->bbox )
|
|---|
| 1532 | {
|
|---|
| 1533 | rv = gbox_pt_outside(poly->bbox, pt_outside);
|
|---|
| 1534 | }
|
|---|
| 1535 | else
|
|---|
| 1536 | {
|
|---|
| 1537 | GBOX gbox;
|
|---|
| 1538 | lwgeom_calculate_gbox_geodetic((LWGEOM*)poly, &gbox);
|
|---|
| 1539 | rv = gbox_pt_outside(&gbox, pt_outside);
|
|---|
| 1540 | }
|
|---|
| 1541 |
|
|---|
| 1542 | if (rv == LW_FALSE)
|
|---|
| 1543 | return lwpoly_pt_outside_hack(poly, pt_outside);
|
|---|
| 1544 |
|
|---|
| 1545 | return rv;
|
|---|
| 1546 | }
|
|---|
| 1547 |
|
|---|
| 1548 | /**
|
|---|
| 1549 | * Given a unit geocentric gbox, return a lon/lat (degrees) coordinate point point that is
|
|---|
| 1550 | * guaranteed to be outside the box (and therefore anything it contains).
|
|---|
| 1551 | */
|
|---|
| 1552 | int gbox_pt_outside(const GBOX *gbox, POINT2D *pt_outside)
|
|---|
| 1553 | {
|
|---|
| 1554 | double grow = M_PI / 180.0 / 60.0; /* one arc-minute */
|
|---|
| 1555 | int i;
|
|---|
| 1556 | GBOX ge;
|
|---|
| 1557 | POINT3D corners[8];
|
|---|
| 1558 | POINT3D pt;
|
|---|
| 1559 | GEOGRAPHIC_POINT g;
|
|---|
| 1560 |
|
|---|
| 1561 | while ( grow < M_PI )
|
|---|
| 1562 | {
|
|---|
| 1563 | /* Assign our box and expand it slightly. */
|
|---|
| 1564 | ge = *gbox;
|
|---|
| 1565 | if ( ge.xmin > -1 ) ge.xmin -= grow;
|
|---|
| 1566 | if ( ge.ymin > -1 ) ge.ymin -= grow;
|
|---|
| 1567 | if ( ge.zmin > -1 ) ge.zmin -= grow;
|
|---|
| 1568 | if ( ge.xmax < 1 ) ge.xmax += grow;
|
|---|
| 1569 | if ( ge.ymax < 1 ) ge.ymax += grow;
|
|---|
| 1570 | if ( ge.zmax < 1 ) ge.zmax += grow;
|
|---|
| 1571 |
|
|---|
| 1572 | /* Build our eight corner points */
|
|---|
| 1573 | corners[0].x = ge.xmin;
|
|---|
| 1574 | corners[0].y = ge.ymin;
|
|---|
| 1575 | corners[0].z = ge.zmin;
|
|---|
| 1576 |
|
|---|
| 1577 | corners[1].x = ge.xmin;
|
|---|
| 1578 | corners[1].y = ge.ymax;
|
|---|
| 1579 | corners[1].z = ge.zmin;
|
|---|
| 1580 |
|
|---|
| 1581 | corners[2].x = ge.xmin;
|
|---|
| 1582 | corners[2].y = ge.ymin;
|
|---|
| 1583 | corners[2].z = ge.zmax;
|
|---|
| 1584 |
|
|---|
| 1585 | corners[3].x = ge.xmax;
|
|---|
| 1586 | corners[3].y = ge.ymin;
|
|---|
| 1587 | corners[3].z = ge.zmin;
|
|---|
| 1588 |
|
|---|
| 1589 | corners[4].x = ge.xmax;
|
|---|
| 1590 | corners[4].y = ge.ymax;
|
|---|
| 1591 | corners[4].z = ge.zmin;
|
|---|
| 1592 |
|
|---|
| 1593 | corners[5].x = ge.xmax;
|
|---|
| 1594 | corners[5].y = ge.ymin;
|
|---|
| 1595 | corners[5].z = ge.zmax;
|
|---|
| 1596 |
|
|---|
| 1597 | corners[6].x = ge.xmin;
|
|---|
| 1598 | corners[6].y = ge.ymax;
|
|---|
| 1599 | corners[6].z = ge.zmax;
|
|---|
| 1600 |
|
|---|
| 1601 | corners[7].x = ge.xmax;
|
|---|
| 1602 | corners[7].y = ge.ymax;
|
|---|
| 1603 | corners[7].z = ge.zmax;
|
|---|
| 1604 |
|
|---|
| 1605 | LWDEBUG(4, "trying to use a box corner point...");
|
|---|
| 1606 | for ( i = 0; i < 8; i++ )
|
|---|
| 1607 | {
|
|---|
| 1608 | normalize(&(corners[i]));
|
|---|
| 1609 | LWDEBUGF(4, "testing corner %d: POINT(%.8g %.8g %.8g)", i, corners[i].x, corners[i].y, corners[i].z);
|
|---|
| 1610 | if ( ! gbox_contains_point3d(gbox, &(corners[i])) )
|
|---|
| 1611 | {
|
|---|
| 1612 | LWDEBUGF(4, "corner %d is outside our gbox", i);
|
|---|
| 1613 | pt = corners[i];
|
|---|
| 1614 | normalize(&pt);
|
|---|
| 1615 | cart2geog(&pt, &g);
|
|---|
| 1616 | pt_outside->x = rad2deg(g.lon);
|
|---|
| 1617 | pt_outside->y = rad2deg(g.lat);
|
|---|
| 1618 | LWDEBUGF(4, "returning POINT(%.8g %.8g) as outside point", pt_outside->x, pt_outside->y);
|
|---|
| 1619 | return LW_SUCCESS;
|
|---|
| 1620 | }
|
|---|
| 1621 | }
|
|---|
| 1622 |
|
|---|
| 1623 | /* Try a wider growth to push the corners outside the original box. */
|
|---|
| 1624 | grow *= 2.0;
|
|---|
| 1625 | }
|
|---|
| 1626 |
|
|---|
| 1627 | /* This should never happen! */
|
|---|
| 1628 | // lwerror("BOOM! Could not generate outside point!");
|
|---|
| 1629 | return LW_FAILURE;
|
|---|
| 1630 | }
|
|---|
| 1631 |
|
|---|
| 1632 |
|
|---|
| 1633 | static int ptarray_segmentize_sphere_edge_recursive (
|
|---|
| 1634 | const POINT3D *p1, const POINT3D *p2, /* 3-space points we are interpolating between */
|
|---|
| 1635 | const POINT4D *v1, const POINT4D *v2, /* real values and z/m values */
|
|---|
| 1636 | double d, double max_seg_length, /* current segment length and segment limit */
|
|---|
| 1637 | POINTARRAY *pa) /* write out results here */
|
|---|
| 1638 | {
|
|---|
| 1639 | GEOGRAPHIC_POINT g;
|
|---|
| 1640 | /* Reached the terminal leaf in recursion. Add */
|
|---|
| 1641 | /* the left-most point to the pointarray here */
|
|---|
| 1642 | /* We recurse down the left side first, so outputs should */
|
|---|
| 1643 | /* end up added to the array in order this way */
|
|---|
| 1644 | if (d <= max_seg_length)
|
|---|
| 1645 | {
|
|---|
| 1646 | POINT4D p;
|
|---|
| 1647 | cart2geog(p1, &g);
|
|---|
| 1648 | p.x = v1->x;
|
|---|
| 1649 | p.y = v1->y;
|
|---|
| 1650 | p.z = v1->z;
|
|---|
| 1651 | p.m = v1->m;
|
|---|
| 1652 | return ptarray_append_point(pa, &p, LW_FALSE);
|
|---|
| 1653 | }
|
|---|
| 1654 | /* Find the mid-point and recurse on the left and then the right */
|
|---|
| 1655 | else
|
|---|
| 1656 | {
|
|---|
| 1657 | /* Calculate mid-point */
|
|---|
| 1658 | POINT3D mid;
|
|---|
| 1659 | mid.x = (p1->x + p2->x) / 2.0;
|
|---|
| 1660 | mid.y = (p1->y + p2->y) / 2.0;
|
|---|
| 1661 | mid.z = (p1->z + p2->z) / 2.0;
|
|---|
| 1662 | normalize(&mid);
|
|---|
| 1663 |
|
|---|
| 1664 | /* Calculate z/m mid-values */
|
|---|
| 1665 | POINT4D midv;
|
|---|
| 1666 | cart2geog(&mid, &g);
|
|---|
| 1667 | midv.x = rad2deg(g.lon);
|
|---|
| 1668 | midv.y = rad2deg(g.lat);
|
|---|
| 1669 | midv.z = (v1->z + v2->z) / 2.0;
|
|---|
| 1670 | midv.m = (v1->m + v2->m) / 2.0;
|
|---|
| 1671 | /* Recurse on the left first */
|
|---|
| 1672 | ptarray_segmentize_sphere_edge_recursive(p1, &mid, v1, &midv, d/2.0, max_seg_length, pa);
|
|---|
| 1673 | ptarray_segmentize_sphere_edge_recursive(&mid, p2, &midv, v2, d/2.0, max_seg_length, pa);
|
|---|
| 1674 | return LW_SUCCESS;
|
|---|
| 1675 | }
|
|---|
| 1676 | }
|
|---|
| 1677 |
|
|---|
| 1678 | /**
|
|---|
| 1679 | * Create a new point array with no segment longer than the input segment length (expressed in radians!)
|
|---|
| 1680 | * @param pa_in - input point array pointer
|
|---|
| 1681 | * @param max_seg_length - maximum output segment length in radians
|
|---|
| 1682 | */
|
|---|
| 1683 | static POINTARRAY*
|
|---|
| 1684 | ptarray_segmentize_sphere(const POINTARRAY *pa_in, double max_seg_length)
|
|---|
| 1685 | {
|
|---|
| 1686 | POINTARRAY *pa_out;
|
|---|
| 1687 | int hasz = ptarray_has_z(pa_in);
|
|---|
| 1688 | int hasm = ptarray_has_m(pa_in);
|
|---|
| 1689 | POINT4D p1, p2;
|
|---|
| 1690 | POINT3D q1, q2;
|
|---|
| 1691 | GEOGRAPHIC_POINT g1, g2;
|
|---|
| 1692 | uint32_t i;
|
|---|
| 1693 |
|
|---|
| 1694 | /* Just crap out on crazy input */
|
|---|
| 1695 | if ( ! pa_in )
|
|---|
| 1696 | lwerror("%s: null input pointarray", __func__);
|
|---|
| 1697 | if ( max_seg_length <= 0.0 )
|
|---|
| 1698 | lwerror("%s: maximum segment length must be positive", __func__);
|
|---|
| 1699 |
|
|---|
| 1700 | /* Empty starting array */
|
|---|
| 1701 | pa_out = ptarray_construct_empty(hasz, hasm, pa_in->npoints);
|
|---|
| 1702 |
|
|---|
| 1703 | /* Simple loop per edge */
|
|---|
| 1704 | for (i = 1; i < pa_in->npoints; i++)
|
|---|
| 1705 | {
|
|---|
| 1706 | getPoint4d_p(pa_in, i-1, &p1);
|
|---|
| 1707 | getPoint4d_p(pa_in, i, &p2);
|
|---|
| 1708 | geographic_point_init(p1.x, p1.y, &g1);
|
|---|
| 1709 | geographic_point_init(p2.x, p2.y, &g2);
|
|---|
| 1710 |
|
|---|
| 1711 | /* Skip duplicate points (except in case of 2-point lines!) */
|
|---|
| 1712 | if ((pa_in->npoints > 2) && p4d_same(&p1, &p2))
|
|---|
| 1713 | continue;
|
|---|
| 1714 |
|
|---|
| 1715 | /* How long is this edge? */
|
|---|
| 1716 | double d = sphere_distance(&g1, &g2);
|
|---|
| 1717 |
|
|---|
| 1718 | if (d > max_seg_length)
|
|---|
| 1719 | {
|
|---|
| 1720 | geog2cart(&g1, &q1);
|
|---|
| 1721 | geog2cart(&g2, &q2);
|
|---|
| 1722 | /* 3-d end points, XYZM end point, current edge size, min edge size */
|
|---|
| 1723 | ptarray_segmentize_sphere_edge_recursive(&q1, &q2, &p1, &p2, d, max_seg_length, pa_out);
|
|---|
| 1724 | }
|
|---|
| 1725 | /* If we don't segmentize, we need to add first point manually */
|
|---|
| 1726 | else
|
|---|
| 1727 | {
|
|---|
| 1728 | ptarray_append_point(pa_out, &p1, LW_TRUE);
|
|---|
| 1729 | }
|
|---|
| 1730 | }
|
|---|
| 1731 | /* Always add the last point */
|
|---|
| 1732 | ptarray_append_point(pa_out, &p2, LW_TRUE);
|
|---|
| 1733 | return pa_out;
|
|---|
| 1734 | }
|
|---|
| 1735 |
|
|---|
| 1736 | /**
|
|---|
| 1737 | * Create a new, densified geometry where no segment is longer than max_seg_length.
|
|---|
| 1738 | * Input geometry is not altered, output geometry must be freed by caller.
|
|---|
| 1739 | * @param lwg_in = input geometry
|
|---|
| 1740 | * @param max_seg_length = maximum segment length in radians
|
|---|
| 1741 | */
|
|---|
| 1742 | LWGEOM*
|
|---|
| 1743 | lwgeom_segmentize_sphere(const LWGEOM *lwg_in, double max_seg_length)
|
|---|
| 1744 | {
|
|---|
| 1745 | POINTARRAY *pa_out;
|
|---|
| 1746 | LWLINE *lwline;
|
|---|
| 1747 | LWPOLY *lwpoly_in, *lwpoly_out;
|
|---|
| 1748 | LWCOLLECTION *lwcol_in, *lwcol_out;
|
|---|
| 1749 | uint32_t i;
|
|---|
| 1750 |
|
|---|
| 1751 | /* Reflect NULL */
|
|---|
| 1752 | if ( ! lwg_in )
|
|---|
| 1753 | return NULL;
|
|---|
| 1754 |
|
|---|
| 1755 | /* Clone empty */
|
|---|
| 1756 | if ( lwgeom_is_empty(lwg_in) )
|
|---|
| 1757 | return lwgeom_clone(lwg_in);
|
|---|
| 1758 |
|
|---|
| 1759 | switch (lwg_in->type)
|
|---|
| 1760 | {
|
|---|
| 1761 | case MULTIPOINTTYPE:
|
|---|
| 1762 | case POINTTYPE:
|
|---|
| 1763 | return lwgeom_clone_deep(lwg_in);
|
|---|
| 1764 | break;
|
|---|
| 1765 | case LINETYPE:
|
|---|
| 1766 | lwline = lwgeom_as_lwline(lwg_in);
|
|---|
| 1767 | pa_out = ptarray_segmentize_sphere(lwline->points, max_seg_length);
|
|---|
| 1768 | return lwline_as_lwgeom(lwline_construct(lwg_in->srid, NULL, pa_out));
|
|---|
| 1769 | break;
|
|---|
| 1770 | case POLYGONTYPE:
|
|---|
| 1771 | lwpoly_in = lwgeom_as_lwpoly(lwg_in);
|
|---|
| 1772 | lwpoly_out = lwpoly_construct_empty(lwg_in->srid, lwgeom_has_z(lwg_in), lwgeom_has_m(lwg_in));
|
|---|
| 1773 | for ( i = 0; i < lwpoly_in->nrings; i++ )
|
|---|
| 1774 | {
|
|---|
| 1775 | pa_out = ptarray_segmentize_sphere(lwpoly_in->rings[i], max_seg_length);
|
|---|
| 1776 | lwpoly_add_ring(lwpoly_out, pa_out);
|
|---|
| 1777 | }
|
|---|
| 1778 | return lwpoly_as_lwgeom(lwpoly_out);
|
|---|
| 1779 | break;
|
|---|
| 1780 | case MULTILINETYPE:
|
|---|
| 1781 | case MULTIPOLYGONTYPE:
|
|---|
| 1782 | case COLLECTIONTYPE:
|
|---|
| 1783 | lwcol_in = lwgeom_as_lwcollection(lwg_in);
|
|---|
| 1784 | lwcol_out = lwcollection_construct_empty(lwg_in->type, lwg_in->srid, lwgeom_has_z(lwg_in), lwgeom_has_m(lwg_in));
|
|---|
| 1785 | for ( i = 0; i < lwcol_in->ngeoms; i++ )
|
|---|
| 1786 | {
|
|---|
| 1787 | lwcollection_add_lwgeom(lwcol_out, lwgeom_segmentize_sphere(lwcol_in->geoms[i], max_seg_length));
|
|---|
| 1788 | }
|
|---|
| 1789 | return lwcollection_as_lwgeom(lwcol_out);
|
|---|
| 1790 | break;
|
|---|
| 1791 | default:
|
|---|
| 1792 | lwerror("lwgeom_segmentize_sphere: unsupported input geometry type: %d - %s",
|
|---|
| 1793 | lwg_in->type, lwtype_name(lwg_in->type));
|
|---|
| 1794 | break;
|
|---|
| 1795 | }
|
|---|
| 1796 |
|
|---|
| 1797 | lwerror("lwgeom_segmentize_sphere got to the end of the function, should not happen");
|
|---|
| 1798 | return NULL;
|
|---|
| 1799 | }
|
|---|
| 1800 |
|
|---|
| 1801 |
|
|---|
| 1802 | /**
|
|---|
| 1803 | * Returns the area of the ring (ring must be closed) in square radians (surface of
|
|---|
| 1804 | * the sphere is 4*PI).
|
|---|
| 1805 | */
|
|---|
| 1806 | double
|
|---|
| 1807 | ptarray_area_sphere(const POINTARRAY *pa)
|
|---|
| 1808 | {
|
|---|
| 1809 | uint32_t i;
|
|---|
| 1810 | const POINT2D *p;
|
|---|
| 1811 | GEOGRAPHIC_POINT a, b, c;
|
|---|
| 1812 | double area = 0.0;
|
|---|
| 1813 |
|
|---|
| 1814 | /* Return zero on nonsensical inputs */
|
|---|
| 1815 | if ( ! pa || pa->npoints < 4 )
|
|---|
| 1816 | return 0.0;
|
|---|
| 1817 |
|
|---|
| 1818 | p = getPoint2d_cp(pa, 0);
|
|---|
| 1819 | geographic_point_init(p->x, p->y, &a);
|
|---|
| 1820 | p = getPoint2d_cp(pa, 1);
|
|---|
| 1821 | geographic_point_init(p->x, p->y, &b);
|
|---|
| 1822 |
|
|---|
| 1823 | for ( i = 2; i < pa->npoints-1; i++ )
|
|---|
| 1824 | {
|
|---|
| 1825 | p = getPoint2d_cp(pa, i);
|
|---|
| 1826 | geographic_point_init(p->x, p->y, &c);
|
|---|
| 1827 | area += sphere_signed_area(&a, &b, &c);
|
|---|
| 1828 | b = c;
|
|---|
| 1829 | }
|
|---|
| 1830 |
|
|---|
| 1831 | return fabs(area);
|
|---|
| 1832 | }
|
|---|
| 1833 |
|
|---|
| 1834 |
|
|---|
| 1835 | static double ptarray_distance_spheroid(const POINTARRAY *pa1, const POINTARRAY *pa2, const SPHEROID *s, double tolerance, int check_intersection)
|
|---|
| 1836 | {
|
|---|
| 1837 | GEOGRAPHIC_EDGE e1, e2;
|
|---|
| 1838 | GEOGRAPHIC_POINT g1, g2;
|
|---|
| 1839 | GEOGRAPHIC_POINT nearest1, nearest2;
|
|---|
| 1840 | POINT3D A1, A2, B1, B2;
|
|---|
| 1841 | const POINT2D *p;
|
|---|
| 1842 | double distance;
|
|---|
| 1843 | uint32_t i, j;
|
|---|
| 1844 | int use_sphere = (s->a == s->b ? 1 : 0);
|
|---|
| 1845 |
|
|---|
| 1846 | /* Make result really big, so that everything will be smaller than it */
|
|---|
| 1847 | distance = FLT_MAX;
|
|---|
| 1848 |
|
|---|
| 1849 | /* Empty point arrays? Return negative */
|
|---|
| 1850 | if ( pa1->npoints == 0 || pa2->npoints == 0 )
|
|---|
| 1851 | return -1.0;
|
|---|
| 1852 |
|
|---|
| 1853 | /* Handle point/point case here */
|
|---|
| 1854 | if ( pa1->npoints == 1 && pa2->npoints == 1 )
|
|---|
| 1855 | {
|
|---|
| 1856 | p = getPoint2d_cp(pa1, 0);
|
|---|
| 1857 | geographic_point_init(p->x, p->y, &g1);
|
|---|
| 1858 | p = getPoint2d_cp(pa2, 0);
|
|---|
| 1859 | geographic_point_init(p->x, p->y, &g2);
|
|---|
| 1860 | /* Sphere special case, axes equal */
|
|---|
| 1861 | distance = s->radius * sphere_distance(&g1, &g2);
|
|---|
| 1862 | if ( use_sphere )
|
|---|
| 1863 | return distance;
|
|---|
| 1864 | /* Below tolerance, actual distance isn't of interest */
|
|---|
| 1865 | else if ( distance < 0.95 * tolerance )
|
|---|
| 1866 | return distance;
|
|---|
| 1867 | /* Close or greater than tolerance, get the real answer to be sure */
|
|---|
| 1868 | else
|
|---|
| 1869 | return spheroid_distance(&g1, &g2, s);
|
|---|
| 1870 | }
|
|---|
| 1871 |
|
|---|
| 1872 | /* Handle point/line case here */
|
|---|
| 1873 | if ( pa1->npoints == 1 || pa2->npoints == 1 )
|
|---|
| 1874 | {
|
|---|
| 1875 | /* Handle one/many case here */
|
|---|
| 1876 | uint32_t i;
|
|---|
| 1877 | const POINTARRAY *pa_one;
|
|---|
| 1878 | const POINTARRAY *pa_many;
|
|---|
| 1879 |
|
|---|
| 1880 | if ( pa1->npoints == 1 )
|
|---|
| 1881 | {
|
|---|
| 1882 | pa_one = pa1;
|
|---|
| 1883 | pa_many = pa2;
|
|---|
| 1884 | }
|
|---|
| 1885 | else
|
|---|
| 1886 | {
|
|---|
| 1887 | pa_one = pa2;
|
|---|
| 1888 | pa_many = pa1;
|
|---|
| 1889 | }
|
|---|
| 1890 |
|
|---|
| 1891 | /* Initialize our point */
|
|---|
| 1892 | p = getPoint2d_cp(pa_one, 0);
|
|---|
| 1893 | geographic_point_init(p->x, p->y, &g1);
|
|---|
| 1894 |
|
|---|
| 1895 | /* Initialize start of line */
|
|---|
| 1896 | p = getPoint2d_cp(pa_many, 0);
|
|---|
| 1897 | geographic_point_init(p->x, p->y, &(e1.start));
|
|---|
| 1898 |
|
|---|
| 1899 | /* Iterate through the edges in our line */
|
|---|
| 1900 | for ( i = 1; i < pa_many->npoints; i++ )
|
|---|
| 1901 | {
|
|---|
| 1902 | double d;
|
|---|
| 1903 | p = getPoint2d_cp(pa_many, i);
|
|---|
| 1904 | geographic_point_init(p->x, p->y, &(e1.end));
|
|---|
| 1905 | /* Get the spherical distance between point and edge */
|
|---|
| 1906 | d = s->radius * edge_distance_to_point(&e1, &g1, &g2);
|
|---|
| 1907 | /* New shortest distance! Record this distance / location */
|
|---|
| 1908 | if ( d < distance )
|
|---|
| 1909 | {
|
|---|
| 1910 | distance = d;
|
|---|
| 1911 | nearest2 = g2;
|
|---|
| 1912 | }
|
|---|
| 1913 | /* We've gotten closer than the tolerance... */
|
|---|
| 1914 | if ( d < tolerance )
|
|---|
| 1915 | {
|
|---|
| 1916 | /* Working on a sphere? The answer is correct, return */
|
|---|
| 1917 | if ( use_sphere )
|
|---|
| 1918 | {
|
|---|
| 1919 | return d;
|
|---|
| 1920 | }
|
|---|
| 1921 | /* Far enough past the tolerance that the spheroid calculation won't change things */
|
|---|
| 1922 | else if ( d < tolerance * 0.95 )
|
|---|
| 1923 | {
|
|---|
| 1924 | return d;
|
|---|
| 1925 | }
|
|---|
| 1926 | /* On a spheroid and near the tolerance? Confirm that we are *actually* closer than tolerance */
|
|---|
| 1927 | else
|
|---|
| 1928 | {
|
|---|
| 1929 | d = spheroid_distance(&g1, &nearest2, s);
|
|---|
| 1930 | /* Yes, closer than tolerance, return! */
|
|---|
| 1931 | if ( d < tolerance )
|
|---|
| 1932 | return d;
|
|---|
| 1933 | }
|
|---|
| 1934 | }
|
|---|
| 1935 | e1.start = e1.end;
|
|---|
| 1936 | }
|
|---|
| 1937 |
|
|---|
| 1938 | /* On sphere, return answer */
|
|---|
| 1939 | if ( use_sphere )
|
|---|
| 1940 | return distance;
|
|---|
| 1941 | /* On spheroid, calculate final answer based on closest approach */
|
|---|
| 1942 | else
|
|---|
| 1943 | return spheroid_distance(&g1, &nearest2, s);
|
|---|
| 1944 |
|
|---|
| 1945 | }
|
|---|
| 1946 |
|
|---|
| 1947 | /* Initialize start of line 1 */
|
|---|
| 1948 | p = getPoint2d_cp(pa1, 0);
|
|---|
| 1949 | geographic_point_init(p->x, p->y, &(e1.start));
|
|---|
| 1950 | geog2cart(&(e1.start), &A1);
|
|---|
| 1951 |
|
|---|
| 1952 |
|
|---|
| 1953 | /* Handle line/line case */
|
|---|
| 1954 | for ( i = 1; i < pa1->npoints; i++ )
|
|---|
| 1955 | {
|
|---|
| 1956 | p = getPoint2d_cp(pa1, i);
|
|---|
| 1957 | geographic_point_init(p->x, p->y, &(e1.end));
|
|---|
| 1958 | geog2cart(&(e1.end), &A2);
|
|---|
| 1959 |
|
|---|
| 1960 | /* Initialize start of line 2 */
|
|---|
| 1961 | p = getPoint2d_cp(pa2, 0);
|
|---|
| 1962 | geographic_point_init(p->x, p->y, &(e2.start));
|
|---|
| 1963 | geog2cart(&(e2.start), &B1);
|
|---|
| 1964 |
|
|---|
| 1965 | for ( j = 1; j < pa2->npoints; j++ )
|
|---|
| 1966 | {
|
|---|
| 1967 | double d;
|
|---|
| 1968 |
|
|---|
| 1969 | p = getPoint2d_cp(pa2, j);
|
|---|
| 1970 | geographic_point_init(p->x, p->y, &(e2.end));
|
|---|
| 1971 | geog2cart(&(e2.end), &B2);
|
|---|
| 1972 |
|
|---|
| 1973 | LWDEBUGF(4, "e1.start == GPOINT(%.6g %.6g) ", e1.start.lat, e1.start.lon);
|
|---|
| 1974 | LWDEBUGF(4, "e1.end == GPOINT(%.6g %.6g) ", e1.end.lat, e1.end.lon);
|
|---|
| 1975 | LWDEBUGF(4, "e2.start == GPOINT(%.6g %.6g) ", e2.start.lat, e2.start.lon);
|
|---|
| 1976 | LWDEBUGF(4, "e2.end == GPOINT(%.6g %.6g) ", e2.end.lat, e2.end.lon);
|
|---|
| 1977 |
|
|---|
| 1978 | if ( check_intersection && edge_intersects(&A1, &A2, &B1, &B2) )
|
|---|
| 1979 | {
|
|---|
| 1980 | LWDEBUG(4,"edge intersection! returning 0.0");
|
|---|
| 1981 | return 0.0;
|
|---|
| 1982 | }
|
|---|
| 1983 | d = s->radius * edge_distance_to_edge(&e1, &e2, &g1, &g2);
|
|---|
| 1984 | LWDEBUGF(4,"got edge_distance_to_edge %.8g", d);
|
|---|
| 1985 |
|
|---|
| 1986 | if ( d < distance )
|
|---|
| 1987 | {
|
|---|
| 1988 | distance = d;
|
|---|
| 1989 | nearest1 = g1;
|
|---|
| 1990 | nearest2 = g2;
|
|---|
| 1991 | }
|
|---|
| 1992 | if ( d < tolerance )
|
|---|
| 1993 | {
|
|---|
| 1994 | if ( use_sphere )
|
|---|
| 1995 | {
|
|---|
| 1996 | return d;
|
|---|
| 1997 | }
|
|---|
| 1998 | else
|
|---|
| 1999 | {
|
|---|
| 2000 | d = spheroid_distance(&nearest1, &nearest2, s);
|
|---|
| 2001 | if ( d < tolerance )
|
|---|
| 2002 | return d;
|
|---|
| 2003 | }
|
|---|
| 2004 | }
|
|---|
| 2005 |
|
|---|
| 2006 | /* Copy end to start to allow a new end value in next iteration */
|
|---|
| 2007 | e2.start = e2.end;
|
|---|
| 2008 | B1 = B2;
|
|---|
| 2009 | }
|
|---|
| 2010 |
|
|---|
| 2011 | /* Copy end to start to allow a new end value in next iteration */
|
|---|
| 2012 | e1.start = e1.end;
|
|---|
| 2013 | A1 = A2;
|
|---|
| 2014 | LW_ON_INTERRUPT(return -1.0);
|
|---|
| 2015 | }
|
|---|
| 2016 | LWDEBUGF(4,"finished all loops, returning %.8g", distance);
|
|---|
| 2017 |
|
|---|
| 2018 | if ( use_sphere )
|
|---|
| 2019 | return distance;
|
|---|
| 2020 | else
|
|---|
| 2021 | return spheroid_distance(&nearest1, &nearest2, s);
|
|---|
| 2022 | }
|
|---|
| 2023 |
|
|---|
| 2024 |
|
|---|
| 2025 | /**
|
|---|
| 2026 | * Calculate the area of an LWGEOM. Anything except POLYGON, MULTIPOLYGON
|
|---|
| 2027 | * and GEOMETRYCOLLECTION return zero immediately. Multi's recurse, polygons
|
|---|
| 2028 | * calculate external ring area and subtract internal ring area. A GBOX is
|
|---|
| 2029 | * required to calculate an outside point.
|
|---|
| 2030 | */
|
|---|
| 2031 | double lwgeom_area_sphere(const LWGEOM *lwgeom, const SPHEROID *spheroid)
|
|---|
| 2032 | {
|
|---|
| 2033 | int type;
|
|---|
| 2034 | double radius2 = spheroid->radius * spheroid->radius;
|
|---|
| 2035 |
|
|---|
| 2036 | assert(lwgeom);
|
|---|
| 2037 |
|
|---|
| 2038 | /* No area in nothing */
|
|---|
| 2039 | if ( lwgeom_is_empty(lwgeom) )
|
|---|
| 2040 | return 0.0;
|
|---|
| 2041 |
|
|---|
| 2042 | /* Read the geometry type number */
|
|---|
| 2043 | type = lwgeom->type;
|
|---|
| 2044 |
|
|---|
| 2045 | /* Anything but polygons and collections returns zero */
|
|---|
| 2046 | if ( ! ( type == POLYGONTYPE || type == MULTIPOLYGONTYPE || type == COLLECTIONTYPE ) )
|
|---|
| 2047 | return 0.0;
|
|---|
| 2048 |
|
|---|
| 2049 | /* Actually calculate area */
|
|---|
| 2050 | if ( type == POLYGONTYPE )
|
|---|
| 2051 | {
|
|---|
| 2052 | LWPOLY *poly = (LWPOLY*)lwgeom;
|
|---|
| 2053 | uint32_t i;
|
|---|
| 2054 | double area = 0.0;
|
|---|
| 2055 |
|
|---|
| 2056 | /* Just in case there's no rings */
|
|---|
| 2057 | if ( poly->nrings < 1 )
|
|---|
| 2058 | return 0.0;
|
|---|
| 2059 |
|
|---|
| 2060 | /* First, the area of the outer ring */
|
|---|
| 2061 | area += radius2 * ptarray_area_sphere(poly->rings[0]);
|
|---|
| 2062 |
|
|---|
| 2063 | /* Subtract areas of inner rings */
|
|---|
| 2064 | for ( i = 1; i < poly->nrings; i++ )
|
|---|
| 2065 | {
|
|---|
| 2066 | area -= radius2 * ptarray_area_sphere(poly->rings[i]);
|
|---|
| 2067 | }
|
|---|
| 2068 | return area;
|
|---|
| 2069 | }
|
|---|
| 2070 |
|
|---|
| 2071 | /* Recurse into sub-geometries to get area */
|
|---|
| 2072 | if ( type == MULTIPOLYGONTYPE || type == COLLECTIONTYPE )
|
|---|
| 2073 | {
|
|---|
| 2074 | LWCOLLECTION *col = (LWCOLLECTION*)lwgeom;
|
|---|
| 2075 | uint32_t i;
|
|---|
| 2076 | double area = 0.0;
|
|---|
| 2077 |
|
|---|
| 2078 | for ( i = 0; i < col->ngeoms; i++ )
|
|---|
| 2079 | {
|
|---|
| 2080 | area += lwgeom_area_sphere(col->geoms[i], spheroid);
|
|---|
| 2081 | }
|
|---|
| 2082 | return area;
|
|---|
| 2083 | }
|
|---|
| 2084 |
|
|---|
| 2085 | /* Shouldn't get here. */
|
|---|
| 2086 | return 0.0;
|
|---|
| 2087 | }
|
|---|
| 2088 |
|
|---|
| 2089 |
|
|---|
| 2090 | /**
|
|---|
| 2091 | * Calculate a projected point given a source point, a distance and a bearing.
|
|---|
| 2092 | * @param r - location of first point.
|
|---|
| 2093 | * @param spheroid - spheroid definition.
|
|---|
| 2094 | * @param distance - distance, in units of the spheroid def'n.
|
|---|
| 2095 | * @param azimuth - azimuth in radians.
|
|---|
| 2096 | * @return s - location of projected point.
|
|---|
| 2097 | *
|
|---|
| 2098 | */
|
|---|
| 2099 | LWPOINT* lwgeom_project_spheroid(const LWPOINT *r, const SPHEROID *spheroid, double distance, double azimuth)
|
|---|
| 2100 | {
|
|---|
| 2101 | GEOGRAPHIC_POINT geo_source, geo_dest;
|
|---|
| 2102 | POINT4D pt_dest;
|
|---|
| 2103 | double x, y;
|
|---|
| 2104 | POINTARRAY *pa;
|
|---|
| 2105 | LWPOINT *lwp;
|
|---|
| 2106 |
|
|---|
| 2107 | /* Normalize distance to be positive*/
|
|---|
| 2108 | if ( distance < 0.0 ) {
|
|---|
| 2109 | distance = -distance;
|
|---|
| 2110 | azimuth += M_PI;
|
|---|
| 2111 | }
|
|---|
| 2112 |
|
|---|
| 2113 | /* Normalize azimuth */
|
|---|
| 2114 | azimuth -= 2.0 * M_PI * floor(azimuth / (2.0 * M_PI));
|
|---|
| 2115 |
|
|---|
| 2116 | /* Check the distance validity */
|
|---|
| 2117 | if ( distance > (M_PI * spheroid->radius) )
|
|---|
| 2118 | {
|
|---|
| 2119 | lwerror("Distance must not be greater than %g", M_PI * spheroid->radius);
|
|---|
| 2120 | return NULL;
|
|---|
| 2121 | }
|
|---|
| 2122 |
|
|---|
| 2123 | /* Convert to ta geodetic point */
|
|---|
| 2124 | x = lwpoint_get_x(r);
|
|---|
| 2125 | y = lwpoint_get_y(r);
|
|---|
| 2126 | geographic_point_init(x, y, &geo_source);
|
|---|
| 2127 |
|
|---|
| 2128 | /* Try the projection */
|
|---|
| 2129 | if( spheroid_project(&geo_source, spheroid, distance, azimuth, &geo_dest) == LW_FAILURE )
|
|---|
| 2130 | {
|
|---|
| 2131 | LWDEBUGF(3, "Unable to project from (%g %g) with azimuth %g and distance %g", x, y, azimuth, distance);
|
|---|
| 2132 | lwerror("Unable to project from (%g %g) with azimuth %g and distance %g", x, y, azimuth, distance);
|
|---|
| 2133 | return NULL;
|
|---|
| 2134 | }
|
|---|
| 2135 |
|
|---|
| 2136 | /* Build the output LWPOINT */
|
|---|
| 2137 | pa = ptarray_construct(0, 0, 1);
|
|---|
| 2138 | pt_dest.x = rad2deg(longitude_radians_normalize(geo_dest.lon));
|
|---|
| 2139 | pt_dest.y = rad2deg(latitude_radians_normalize(geo_dest.lat));
|
|---|
| 2140 | pt_dest.z = pt_dest.m = 0.0;
|
|---|
| 2141 | ptarray_set_point4d(pa, 0, &pt_dest);
|
|---|
| 2142 | lwp = lwpoint_construct(r->srid, NULL, pa);
|
|---|
| 2143 | lwgeom_set_geodetic(lwpoint_as_lwgeom(lwp), LW_TRUE);
|
|---|
| 2144 | return lwp;
|
|---|
| 2145 | }
|
|---|
| 2146 |
|
|---|
| 2147 |
|
|---|
| 2148 | /**
|
|---|
| 2149 | * Calculate a bearing (azimuth) given a source and destination point.
|
|---|
| 2150 | * @param r - location of first point.
|
|---|
| 2151 | * @param s - location of second point.
|
|---|
| 2152 | * @param spheroid - spheroid definition.
|
|---|
| 2153 | * @return azimuth - azimuth in radians.
|
|---|
| 2154 | *
|
|---|
| 2155 | */
|
|---|
| 2156 | double lwgeom_azumith_spheroid(const LWPOINT *r, const LWPOINT *s, const SPHEROID *spheroid)
|
|---|
| 2157 | {
|
|---|
| 2158 | GEOGRAPHIC_POINT g1, g2;
|
|---|
| 2159 | double x1, y1, x2, y2;
|
|---|
| 2160 |
|
|---|
| 2161 | /* Convert r to a geodetic point */
|
|---|
| 2162 | x1 = lwpoint_get_x(r);
|
|---|
| 2163 | y1 = lwpoint_get_y(r);
|
|---|
| 2164 | geographic_point_init(x1, y1, &g1);
|
|---|
| 2165 |
|
|---|
| 2166 | /* Convert s to a geodetic point */
|
|---|
| 2167 | x2 = lwpoint_get_x(s);
|
|---|
| 2168 | y2 = lwpoint_get_y(s);
|
|---|
| 2169 | geographic_point_init(x2, y2, &g2);
|
|---|
| 2170 |
|
|---|
| 2171 | /* Same point, return NaN */
|
|---|
| 2172 | if ( FP_EQUALS(x1, x2) && FP_EQUALS(y1, y2) )
|
|---|
| 2173 | {
|
|---|
| 2174 | return NAN;
|
|---|
| 2175 | }
|
|---|
| 2176 |
|
|---|
| 2177 | /* Do the direction calculation */
|
|---|
| 2178 | return spheroid_direction(&g1, &g2, spheroid);
|
|---|
| 2179 | }
|
|---|
| 2180 |
|
|---|
| 2181 | /**
|
|---|
| 2182 | * Calculate the distance between two LWGEOMs, using the coordinates are
|
|---|
| 2183 | * longitude and latitude. Return immediately when the calculated distance drops
|
|---|
| 2184 | * below the tolerance (useful for dwithin calculations).
|
|---|
| 2185 | * Return a negative distance for incalculable cases.
|
|---|
| 2186 | */
|
|---|
| 2187 | double lwgeom_distance_spheroid(const LWGEOM *lwgeom1, const LWGEOM *lwgeom2, const SPHEROID *spheroid, double tolerance)
|
|---|
| 2188 | {
|
|---|
| 2189 | uint8_t type1, type2;
|
|---|
| 2190 | int check_intersection = LW_FALSE;
|
|---|
| 2191 | GBOX gbox1, gbox2;
|
|---|
| 2192 |
|
|---|
| 2193 | gbox_init(&gbox1);
|
|---|
| 2194 | gbox_init(&gbox2);
|
|---|
| 2195 |
|
|---|
| 2196 | assert(lwgeom1);
|
|---|
| 2197 | assert(lwgeom2);
|
|---|
| 2198 |
|
|---|
| 2199 | LWDEBUGF(4, "entered function, tolerance %.8g", tolerance);
|
|---|
| 2200 |
|
|---|
| 2201 | /* What's the distance to an empty geometry? We don't know.
|
|---|
| 2202 | Return a negative number so the caller can catch this case. */
|
|---|
| 2203 | if ( lwgeom_is_empty(lwgeom1) || lwgeom_is_empty(lwgeom2) )
|
|---|
| 2204 | {
|
|---|
| 2205 | return -1.0;
|
|---|
| 2206 | }
|
|---|
| 2207 |
|
|---|
| 2208 | type1 = lwgeom1->type;
|
|---|
| 2209 | type2 = lwgeom2->type;
|
|---|
| 2210 |
|
|---|
| 2211 | /* Make sure we have boxes */
|
|---|
| 2212 | if ( lwgeom1->bbox )
|
|---|
| 2213 | gbox1 = *(lwgeom1->bbox);
|
|---|
| 2214 | else
|
|---|
| 2215 | lwgeom_calculate_gbox_geodetic(lwgeom1, &gbox1);
|
|---|
| 2216 |
|
|---|
| 2217 | /* Make sure we have boxes */
|
|---|
| 2218 | if ( lwgeom2->bbox )
|
|---|
| 2219 | gbox2 = *(lwgeom2->bbox);
|
|---|
| 2220 | else
|
|---|
| 2221 | lwgeom_calculate_gbox_geodetic(lwgeom2, &gbox2);
|
|---|
| 2222 |
|
|---|
| 2223 | /* If the boxes aren't disjoint, we have to check for edge intersections */
|
|---|
| 2224 | if ( gbox_overlaps(&gbox1, &gbox2) )
|
|---|
| 2225 | check_intersection = LW_TRUE;
|
|---|
| 2226 |
|
|---|
| 2227 | /* Point/line combinations can all be handled with simple point array iterations */
|
|---|
| 2228 | if ( ( type1 == POINTTYPE || type1 == LINETYPE ) &&
|
|---|
| 2229 | ( type2 == POINTTYPE || type2 == LINETYPE ) )
|
|---|
| 2230 | {
|
|---|
| 2231 | POINTARRAY *pa1, *pa2;
|
|---|
| 2232 |
|
|---|
| 2233 | if ( type1 == POINTTYPE )
|
|---|
| 2234 | pa1 = ((LWPOINT*)lwgeom1)->point;
|
|---|
| 2235 | else
|
|---|
| 2236 | pa1 = ((LWLINE*)lwgeom1)->points;
|
|---|
| 2237 |
|
|---|
| 2238 | if ( type2 == POINTTYPE )
|
|---|
| 2239 | pa2 = ((LWPOINT*)lwgeom2)->point;
|
|---|
| 2240 | else
|
|---|
| 2241 | pa2 = ((LWLINE*)lwgeom2)->points;
|
|---|
| 2242 |
|
|---|
| 2243 | return ptarray_distance_spheroid(pa1, pa2, spheroid, tolerance, check_intersection);
|
|---|
| 2244 | }
|
|---|
| 2245 |
|
|---|
| 2246 | /* Point/Polygon cases, if point-in-poly, return zero, else return distance. */
|
|---|
| 2247 | if ( ( type1 == POLYGONTYPE && type2 == POINTTYPE ) ||
|
|---|
| 2248 | ( type2 == POLYGONTYPE && type1 == POINTTYPE ) )
|
|---|
| 2249 | {
|
|---|
| 2250 | const POINT2D *p;
|
|---|
| 2251 | LWPOLY *lwpoly;
|
|---|
| 2252 | LWPOINT *lwpt;
|
|---|
| 2253 | double distance = FLT_MAX;
|
|---|
| 2254 | uint32_t i;
|
|---|
| 2255 |
|
|---|
| 2256 | if ( type1 == POINTTYPE )
|
|---|
| 2257 | {
|
|---|
| 2258 | lwpt = (LWPOINT*)lwgeom1;
|
|---|
| 2259 | lwpoly = (LWPOLY*)lwgeom2;
|
|---|
| 2260 | }
|
|---|
| 2261 | else
|
|---|
| 2262 | {
|
|---|
| 2263 | lwpt = (LWPOINT*)lwgeom2;
|
|---|
| 2264 | lwpoly = (LWPOLY*)lwgeom1;
|
|---|
| 2265 | }
|
|---|
| 2266 | p = getPoint2d_cp(lwpt->point, 0);
|
|---|
| 2267 |
|
|---|
| 2268 | /* Point in polygon implies zero distance */
|
|---|
| 2269 | if ( lwpoly_covers_point2d(lwpoly, p) )
|
|---|
| 2270 | {
|
|---|
| 2271 | return 0.0;
|
|---|
| 2272 | }
|
|---|
| 2273 |
|
|---|
| 2274 | /* Not inside, so what's the actual distance? */
|
|---|
| 2275 | for ( i = 0; i < lwpoly->nrings; i++ )
|
|---|
| 2276 | {
|
|---|
| 2277 | double ring_distance = ptarray_distance_spheroid(lwpoly->rings[i], lwpt->point, spheroid, tolerance, check_intersection);
|
|---|
| 2278 | if ( ring_distance < distance )
|
|---|
| 2279 | distance = ring_distance;
|
|---|
| 2280 | if ( distance < tolerance )
|
|---|
| 2281 | return distance;
|
|---|
| 2282 | }
|
|---|
| 2283 | return distance;
|
|---|
| 2284 | }
|
|---|
| 2285 |
|
|---|
| 2286 | /* Line/polygon case, if start point-in-poly, return zero, else return distance. */
|
|---|
| 2287 | if ( ( type1 == POLYGONTYPE && type2 == LINETYPE ) ||
|
|---|
| 2288 | ( type2 == POLYGONTYPE && type1 == LINETYPE ) )
|
|---|
| 2289 | {
|
|---|
| 2290 | const POINT2D *p;
|
|---|
| 2291 | LWPOLY *lwpoly;
|
|---|
| 2292 | LWLINE *lwline;
|
|---|
| 2293 | double distance = FLT_MAX;
|
|---|
| 2294 | uint32_t i;
|
|---|
| 2295 |
|
|---|
| 2296 | if ( type1 == LINETYPE )
|
|---|
| 2297 | {
|
|---|
| 2298 | lwline = (LWLINE*)lwgeom1;
|
|---|
| 2299 | lwpoly = (LWPOLY*)lwgeom2;
|
|---|
| 2300 | }
|
|---|
| 2301 | else
|
|---|
| 2302 | {
|
|---|
| 2303 | lwline = (LWLINE*)lwgeom2;
|
|---|
| 2304 | lwpoly = (LWPOLY*)lwgeom1;
|
|---|
| 2305 | }
|
|---|
| 2306 | p = getPoint2d_cp(lwline->points, 0);
|
|---|
| 2307 |
|
|---|
| 2308 | LWDEBUG(4, "checking if a point of line is in polygon");
|
|---|
| 2309 |
|
|---|
| 2310 | /* Point in polygon implies zero distance */
|
|---|
| 2311 | if ( lwpoly_covers_point2d(lwpoly, p) )
|
|---|
| 2312 | return 0.0;
|
|---|
| 2313 |
|
|---|
| 2314 | LWDEBUG(4, "checking ring distances");
|
|---|
| 2315 |
|
|---|
| 2316 | /* Not contained, so what's the actual distance? */
|
|---|
| 2317 | for ( i = 0; i < lwpoly->nrings; i++ )
|
|---|
| 2318 | {
|
|---|
| 2319 | double ring_distance = ptarray_distance_spheroid(lwpoly->rings[i], lwline->points, spheroid, tolerance, check_intersection);
|
|---|
| 2320 | LWDEBUGF(4, "ring[%d] ring_distance = %.8g", i, ring_distance);
|
|---|
| 2321 | if ( ring_distance < distance )
|
|---|
| 2322 | distance = ring_distance;
|
|---|
| 2323 | if ( distance < tolerance )
|
|---|
| 2324 | return distance;
|
|---|
| 2325 | }
|
|---|
| 2326 | LWDEBUGF(4, "all rings checked, returning distance = %.8g", distance);
|
|---|
| 2327 | return distance;
|
|---|
| 2328 |
|
|---|
| 2329 | }
|
|---|
| 2330 |
|
|---|
| 2331 | /* Polygon/polygon case, if start point-in-poly, return zero, else
|
|---|
| 2332 | * return distance. */
|
|---|
| 2333 | if (type1 == POLYGONTYPE && type2 == POLYGONTYPE)
|
|---|
| 2334 | {
|
|---|
| 2335 | const POINT2D* p;
|
|---|
| 2336 | LWPOLY* lwpoly1 = (LWPOLY*)lwgeom1;
|
|---|
| 2337 | LWPOLY* lwpoly2 = (LWPOLY*)lwgeom2;
|
|---|
| 2338 | double distance = FLT_MAX;
|
|---|
| 2339 | uint32_t i, j;
|
|---|
| 2340 |
|
|---|
| 2341 | /* Point of 2 in polygon 1 implies zero distance */
|
|---|
| 2342 | p = getPoint2d_cp(lwpoly1->rings[0], 0);
|
|---|
| 2343 | if (lwpoly_covers_point2d(lwpoly2, p)) return 0.0;
|
|---|
| 2344 |
|
|---|
| 2345 | /* Point of 1 in polygon 2 implies zero distance */
|
|---|
| 2346 | p = getPoint2d_cp(lwpoly2->rings[0], 0);
|
|---|
| 2347 | if (lwpoly_covers_point2d(lwpoly1, p)) return 0.0;
|
|---|
| 2348 |
|
|---|
| 2349 | /* Not contained, so what's the actual distance? */
|
|---|
| 2350 | for (i = 0; i < lwpoly1->nrings; i++)
|
|---|
| 2351 | {
|
|---|
| 2352 | for (j = 0; j < lwpoly2->nrings; j++)
|
|---|
| 2353 | {
|
|---|
| 2354 | double ring_distance =
|
|---|
| 2355 | ptarray_distance_spheroid(
|
|---|
| 2356 | lwpoly1->rings[i],
|
|---|
| 2357 | lwpoly2->rings[j],
|
|---|
| 2358 | spheroid,
|
|---|
| 2359 | tolerance,
|
|---|
| 2360 | check_intersection);
|
|---|
| 2361 | if (ring_distance < distance)
|
|---|
| 2362 | distance = ring_distance;
|
|---|
| 2363 | if (distance < tolerance) return distance;
|
|---|
| 2364 | }
|
|---|
| 2365 | }
|
|---|
| 2366 | return distance;
|
|---|
| 2367 | }
|
|---|
| 2368 |
|
|---|
| 2369 | /* Recurse into collections */
|
|---|
| 2370 | if ( lwtype_is_collection(type1) )
|
|---|
| 2371 | {
|
|---|
| 2372 | uint32_t i;
|
|---|
| 2373 | double distance = FLT_MAX;
|
|---|
| 2374 | LWCOLLECTION *col = (LWCOLLECTION*)lwgeom1;
|
|---|
| 2375 |
|
|---|
| 2376 | for ( i = 0; i < col->ngeoms; i++ )
|
|---|
| 2377 | {
|
|---|
| 2378 | double geom_distance = lwgeom_distance_spheroid(
|
|---|
| 2379 | col->geoms[i], lwgeom2, spheroid, tolerance);
|
|---|
| 2380 | if ( geom_distance < distance )
|
|---|
| 2381 | distance = geom_distance;
|
|---|
| 2382 | if ( distance < tolerance )
|
|---|
| 2383 | return distance;
|
|---|
| 2384 | }
|
|---|
| 2385 | return distance;
|
|---|
| 2386 | }
|
|---|
| 2387 |
|
|---|
| 2388 | /* Recurse into collections */
|
|---|
| 2389 | if ( lwtype_is_collection(type2) )
|
|---|
| 2390 | {
|
|---|
| 2391 | uint32_t i;
|
|---|
| 2392 | double distance = FLT_MAX;
|
|---|
| 2393 | LWCOLLECTION *col = (LWCOLLECTION*)lwgeom2;
|
|---|
| 2394 |
|
|---|
| 2395 | for ( i = 0; i < col->ngeoms; i++ )
|
|---|
| 2396 | {
|
|---|
| 2397 | double geom_distance = lwgeom_distance_spheroid(lwgeom1, col->geoms[i], spheroid, tolerance);
|
|---|
| 2398 | if ( geom_distance < distance )
|
|---|
| 2399 | distance = geom_distance;
|
|---|
| 2400 | if ( distance < tolerance )
|
|---|
| 2401 | return distance;
|
|---|
| 2402 | }
|
|---|
| 2403 | return distance;
|
|---|
| 2404 | }
|
|---|
| 2405 |
|
|---|
| 2406 |
|
|---|
| 2407 | lwerror("arguments include unsupported geometry type (%s, %s)", lwtype_name(type1), lwtype_name(type1));
|
|---|
| 2408 | return -1.0;
|
|---|
| 2409 |
|
|---|
| 2410 | }
|
|---|
| 2411 |
|
|---|
| 2412 |
|
|---|
| 2413 | int lwgeom_covers_lwgeom_sphere(const LWGEOM *lwgeom1, const LWGEOM *lwgeom2)
|
|---|
| 2414 | {
|
|---|
| 2415 | int type1, type2;
|
|---|
| 2416 | GBOX gbox1, gbox2;
|
|---|
| 2417 | gbox1.flags = gbox2.flags = 0;
|
|---|
| 2418 |
|
|---|
| 2419 | assert(lwgeom1);
|
|---|
| 2420 | assert(lwgeom2);
|
|---|
| 2421 |
|
|---|
| 2422 | type1 = lwgeom1->type;
|
|---|
| 2423 | type2 = lwgeom2->type;
|
|---|
| 2424 |
|
|---|
| 2425 | /* dim(geom2) > dim(geom1) always returns false (because geom2 is bigger) */
|
|---|
| 2426 | if ( (type1 == POINTTYPE && type2 == LINETYPE)
|
|---|
| 2427 | || (type1 == POINTTYPE && type2 == POLYGONTYPE)
|
|---|
| 2428 | || (type1 == LINETYPE && type2 == POLYGONTYPE) )
|
|---|
| 2429 | {
|
|---|
| 2430 | LWDEBUG(4, "dimension of geom2 is bigger than geom1");
|
|---|
| 2431 | return LW_FALSE;
|
|---|
| 2432 | }
|
|---|
| 2433 |
|
|---|
| 2434 | /* Make sure we have boxes */
|
|---|
| 2435 | if ( lwgeom1->bbox )
|
|---|
| 2436 | gbox1 = *(lwgeom1->bbox);
|
|---|
| 2437 | else
|
|---|
| 2438 | lwgeom_calculate_gbox_geodetic(lwgeom1, &gbox1);
|
|---|
| 2439 |
|
|---|
| 2440 | /* Make sure we have boxes */
|
|---|
| 2441 | if ( lwgeom2->bbox )
|
|---|
| 2442 | gbox2 = *(lwgeom2->bbox);
|
|---|
| 2443 | else
|
|---|
| 2444 | lwgeom_calculate_gbox_geodetic(lwgeom2, &gbox2);
|
|---|
| 2445 |
|
|---|
| 2446 |
|
|---|
| 2447 | /* Handle the polygon/point case */
|
|---|
| 2448 | if ( type1 == POLYGONTYPE && type2 == POINTTYPE )
|
|---|
| 2449 | {
|
|---|
| 2450 | POINT2D pt_to_test;
|
|---|
| 2451 | getPoint2d_p(((LWPOINT*)lwgeom2)->point, 0, &pt_to_test);
|
|---|
| 2452 | return lwpoly_covers_point2d((LWPOLY*)lwgeom1, &pt_to_test);
|
|---|
| 2453 | }
|
|---|
| 2454 | else if ( type1 == POLYGONTYPE && type2 == LINETYPE)
|
|---|
| 2455 | {
|
|---|
| 2456 | return lwpoly_covers_lwline((LWPOLY*)lwgeom1, (LWLINE*)lwgeom2);
|
|---|
| 2457 | }
|
|---|
| 2458 | else if ( type1 == POLYGONTYPE && type2 == POLYGONTYPE)
|
|---|
| 2459 | {
|
|---|
| 2460 | return lwpoly_covers_lwpoly((LWPOLY*)lwgeom1, (LWPOLY*)lwgeom2);
|
|---|
| 2461 | }
|
|---|
| 2462 | else if ( type1 == LINETYPE && type2 == POINTTYPE)
|
|---|
| 2463 | {
|
|---|
| 2464 | return lwline_covers_lwpoint((LWLINE*)lwgeom1, (LWPOINT*)lwgeom2);
|
|---|
| 2465 | }
|
|---|
| 2466 | else if ( type1 == LINETYPE && type2 == LINETYPE)
|
|---|
| 2467 | {
|
|---|
| 2468 | return lwline_covers_lwline((LWLINE*)lwgeom1, (LWLINE*)lwgeom2);
|
|---|
| 2469 | }
|
|---|
| 2470 | else if ( type1 == POINTTYPE && type2 == POINTTYPE)
|
|---|
| 2471 | {
|
|---|
| 2472 | return lwpoint_same((LWPOINT*)lwgeom1, (LWPOINT*)lwgeom2);
|
|---|
| 2473 | }
|
|---|
| 2474 |
|
|---|
| 2475 | /* If any of the first argument parts covers the second argument, it's true */
|
|---|
| 2476 | if ( lwtype_is_collection( type1 ) )
|
|---|
| 2477 | {
|
|---|
| 2478 | uint32_t i;
|
|---|
| 2479 | LWCOLLECTION *col = (LWCOLLECTION*)lwgeom1;
|
|---|
| 2480 |
|
|---|
| 2481 | for ( i = 0; i < col->ngeoms; i++ )
|
|---|
| 2482 | {
|
|---|
| 2483 | if ( lwgeom_covers_lwgeom_sphere(col->geoms[i], lwgeom2) )
|
|---|
| 2484 | {
|
|---|
| 2485 | return LW_TRUE;
|
|---|
| 2486 | }
|
|---|
| 2487 | }
|
|---|
| 2488 | return LW_FALSE;
|
|---|
| 2489 | }
|
|---|
| 2490 |
|
|---|
| 2491 | /* Only if all of the second arguments are covered by the first argument is the condition true */
|
|---|
| 2492 | if ( lwtype_is_collection( type2 ) )
|
|---|
| 2493 | {
|
|---|
| 2494 | uint32_t i;
|
|---|
| 2495 | LWCOLLECTION *col = (LWCOLLECTION*)lwgeom2;
|
|---|
| 2496 |
|
|---|
| 2497 | for ( i = 0; i < col->ngeoms; i++ )
|
|---|
| 2498 | {
|
|---|
| 2499 | if ( ! lwgeom_covers_lwgeom_sphere(lwgeom1, col->geoms[i]) )
|
|---|
| 2500 | {
|
|---|
| 2501 | return LW_FALSE;
|
|---|
| 2502 | }
|
|---|
| 2503 | }
|
|---|
| 2504 | return LW_TRUE;
|
|---|
| 2505 | }
|
|---|
| 2506 |
|
|---|
| 2507 | /* Don't get here */
|
|---|
| 2508 | lwerror("lwgeom_covers_lwgeom_sphere: reached end of function without resolution");
|
|---|
| 2509 | return LW_FALSE;
|
|---|
| 2510 |
|
|---|
| 2511 | }
|
|---|
| 2512 |
|
|---|
| 2513 | /**
|
|---|
| 2514 | * Given a polygon (lon/lat decimal degrees) and point (lon/lat decimal degrees) and
|
|---|
| 2515 | * a guaranteed outside point (lon/lat decimal degrees) (calculate with gbox_pt_outside())
|
|---|
| 2516 | * return LW_TRUE if point is inside or on edge of polygon.
|
|---|
| 2517 | */
|
|---|
| 2518 | int lwpoly_covers_point2d(const LWPOLY *poly, const POINT2D *pt_to_test)
|
|---|
| 2519 | {
|
|---|
| 2520 | uint32_t i;
|
|---|
| 2521 | int in_hole_count = 0;
|
|---|
| 2522 | POINT3D p;
|
|---|
| 2523 | GEOGRAPHIC_POINT gpt_to_test;
|
|---|
| 2524 | POINT2D pt_outside;
|
|---|
| 2525 | GBOX gbox;
|
|---|
| 2526 | #if POSTGIS_DEBUG_LEVEL >= 4
|
|---|
| 2527 | char *geom_ewkt;
|
|---|
| 2528 | #endif
|
|---|
| 2529 | gbox.flags = 0;
|
|---|
| 2530 |
|
|---|
| 2531 | /* Nulls and empties don't contain anything! */
|
|---|
| 2532 | if ( ! poly || lwgeom_is_empty((LWGEOM*)poly) )
|
|---|
| 2533 | {
|
|---|
| 2534 | LWDEBUG(4,"returning false, geometry is empty or null");
|
|---|
| 2535 | return LW_FALSE;
|
|---|
| 2536 | }
|
|---|
| 2537 |
|
|---|
| 2538 | /* Make sure we have boxes */
|
|---|
| 2539 | if ( poly->bbox )
|
|---|
| 2540 | gbox = *(poly->bbox);
|
|---|
| 2541 | else
|
|---|
| 2542 | lwgeom_calculate_gbox_geodetic((LWGEOM*)poly, &gbox);
|
|---|
| 2543 |
|
|---|
| 2544 | /* Point not in box? Done! */
|
|---|
| 2545 | geographic_point_init(pt_to_test->x, pt_to_test->y, &gpt_to_test);
|
|---|
| 2546 | geog2cart(&gpt_to_test, &p);
|
|---|
| 2547 | if ( ! gbox_contains_point3d(&gbox, &p) )
|
|---|
| 2548 | {
|
|---|
| 2549 | LWDEBUG(4, "the point is not in the box!");
|
|---|
| 2550 | return LW_FALSE;
|
|---|
| 2551 | }
|
|---|
| 2552 |
|
|---|
| 2553 | /* Calculate our outside point from the gbox */
|
|---|
| 2554 | lwpoly_pt_outside(poly, &pt_outside);
|
|---|
| 2555 |
|
|---|
| 2556 | LWDEBUGF(4, "pt_outside POINT(%.18g %.18g)", pt_outside.x, pt_outside.y);
|
|---|
| 2557 | LWDEBUGF(4, "pt_to_test POINT(%.18g %.18g)", pt_to_test->x, pt_to_test->y);
|
|---|
| 2558 | #if POSTGIS_DEBUG_LEVEL >= 4
|
|---|
| 2559 | geom_ewkt = lwgeom_to_ewkt((LWGEOM*)poly);
|
|---|
| 2560 | LWDEBUGF(4, "polygon %s", geom_ewkt);
|
|---|
| 2561 | lwfree(geom_ewkt);
|
|---|
| 2562 | geom_ewkt = gbox_to_string(&gbox);
|
|---|
| 2563 | LWDEBUGF(4, "gbox %s", geom_ewkt);
|
|---|
| 2564 | lwfree(geom_ewkt);
|
|---|
| 2565 | #endif
|
|---|
| 2566 |
|
|---|
| 2567 | /* Not in outer ring? We're done! */
|
|---|
| 2568 | if ( ! ptarray_contains_point_sphere(poly->rings[0], &pt_outside, pt_to_test) )
|
|---|
| 2569 | {
|
|---|
| 2570 | LWDEBUG(4,"returning false, point is outside ring");
|
|---|
| 2571 | return LW_FALSE;
|
|---|
| 2572 | }
|
|---|
| 2573 |
|
|---|
| 2574 | LWDEBUGF(4, "testing %d rings", poly->nrings);
|
|---|
| 2575 |
|
|---|
| 2576 | /* But maybe point is in a hole... */
|
|---|
| 2577 | for ( i = 1; i < poly->nrings; i++ )
|
|---|
| 2578 | {
|
|---|
| 2579 | LWDEBUGF(4, "ring test loop %d", i);
|
|---|
| 2580 | /* Count up hole containment. Odd => outside boundary. */
|
|---|
| 2581 | if ( ptarray_contains_point_sphere(poly->rings[i], &pt_outside, pt_to_test) )
|
|---|
| 2582 | in_hole_count++;
|
|---|
| 2583 | }
|
|---|
| 2584 |
|
|---|
| 2585 | LWDEBUGF(4, "in_hole_count == %d", in_hole_count);
|
|---|
| 2586 |
|
|---|
| 2587 | if ( in_hole_count % 2 )
|
|---|
| 2588 | {
|
|---|
| 2589 | LWDEBUG(4,"returning false, inner ring containment count is odd");
|
|---|
| 2590 | return LW_FALSE;
|
|---|
| 2591 | }
|
|---|
| 2592 |
|
|---|
| 2593 | LWDEBUG(4,"returning true, inner ring containment count is even");
|
|---|
| 2594 | return LW_TRUE;
|
|---|
| 2595 | }
|
|---|
| 2596 |
|
|---|
| 2597 | /**
|
|---|
| 2598 | * Given a polygon1 check if all points of polygon2 are inside polygon1 and no
|
|---|
| 2599 | * intersections of the polygon edges occur.
|
|---|
| 2600 | * return LW_TRUE if polygon is inside or on edge of polygon.
|
|---|
| 2601 | */
|
|---|
| 2602 | int lwpoly_covers_lwpoly(const LWPOLY *poly1, const LWPOLY *poly2)
|
|---|
| 2603 | {
|
|---|
| 2604 | uint32_t i;
|
|---|
| 2605 |
|
|---|
| 2606 | /* Nulls and empties don't contain anything! */
|
|---|
| 2607 | if ( ! poly1 || lwgeom_is_empty((LWGEOM*)poly1) )
|
|---|
| 2608 | {
|
|---|
| 2609 | LWDEBUG(4,"returning false, geometry1 is empty or null");
|
|---|
| 2610 | return LW_FALSE;
|
|---|
| 2611 | }
|
|---|
| 2612 |
|
|---|
| 2613 | /* Nulls and empties don't contain anything! */
|
|---|
| 2614 | if ( ! poly2 || lwgeom_is_empty((LWGEOM*)poly2) )
|
|---|
| 2615 | {
|
|---|
| 2616 | LWDEBUG(4,"returning false, geometry2 is empty or null");
|
|---|
| 2617 | return LW_FALSE;
|
|---|
| 2618 | }
|
|---|
| 2619 |
|
|---|
| 2620 | /* check if all vertices of poly2 are inside poly1 */
|
|---|
| 2621 | for (i = 0; i < poly2->nrings; i++)
|
|---|
| 2622 | {
|
|---|
| 2623 |
|
|---|
| 2624 | /* every other ring is a hole, check if point is inside the actual polygon */
|
|---|
| 2625 | if ( i % 2 == 0)
|
|---|
| 2626 | {
|
|---|
| 2627 | if (LW_FALSE == lwpoly_covers_pointarray(poly1, poly2->rings[i]))
|
|---|
| 2628 | {
|
|---|
| 2629 | LWDEBUG(4,"returning false, geometry2 has point outside of geometry1");
|
|---|
| 2630 | return LW_FALSE;
|
|---|
| 2631 | }
|
|---|
| 2632 | }
|
|---|
| 2633 | else
|
|---|
| 2634 | {
|
|---|
| 2635 | if (LW_TRUE == lwpoly_covers_pointarray(poly1, poly2->rings[i]))
|
|---|
| 2636 | {
|
|---|
| 2637 | LWDEBUG(4,"returning false, geometry2 has point inside a hole of geometry1");
|
|---|
| 2638 | return LW_FALSE;
|
|---|
| 2639 | }
|
|---|
| 2640 | }
|
|---|
| 2641 | }
|
|---|
| 2642 |
|
|---|
| 2643 | /* check for any edge intersections, so nothing is partially outside of poly1 */
|
|---|
| 2644 | for (i = 0; i < poly2->nrings; i++)
|
|---|
| 2645 | {
|
|---|
| 2646 | if (LW_TRUE == lwpoly_intersects_line(poly1, poly2->rings[i]))
|
|---|
| 2647 | {
|
|---|
| 2648 | LWDEBUG(4,"returning false, geometry2 is partially outside of geometry1");
|
|---|
| 2649 | return LW_FALSE;
|
|---|
| 2650 | }
|
|---|
| 2651 | }
|
|---|
| 2652 |
|
|---|
| 2653 | /* no abort condition found, so the poly2 should be completly inside poly1 */
|
|---|
| 2654 | return LW_TRUE;
|
|---|
| 2655 | }
|
|---|
| 2656 |
|
|---|
| 2657 | /**
|
|---|
| 2658 | *
|
|---|
| 2659 | */
|
|---|
| 2660 | int lwpoly_covers_lwline(const LWPOLY *poly, const LWLINE *line)
|
|---|
| 2661 | {
|
|---|
| 2662 | /* Nulls and empties don't contain anything! */
|
|---|
| 2663 | if ( ! poly || lwgeom_is_empty((LWGEOM*)poly) )
|
|---|
| 2664 | {
|
|---|
| 2665 | LWDEBUG(4,"returning false, geometry1 is empty or null");
|
|---|
| 2666 | return LW_FALSE;
|
|---|
| 2667 | }
|
|---|
| 2668 |
|
|---|
| 2669 | /* Nulls and empties don't contain anything! */
|
|---|
| 2670 | if ( ! line || lwgeom_is_empty((LWGEOM*)line) )
|
|---|
| 2671 | {
|
|---|
| 2672 | LWDEBUG(4,"returning false, geometry2 is empty or null");
|
|---|
| 2673 | return LW_FALSE;
|
|---|
| 2674 | }
|
|---|
| 2675 |
|
|---|
| 2676 | if (LW_FALSE == lwpoly_covers_pointarray(poly, line->points))
|
|---|
| 2677 | {
|
|---|
| 2678 | LWDEBUG(4,"returning false, geometry2 has point outside of geometry1");
|
|---|
| 2679 | return LW_FALSE;
|
|---|
| 2680 | }
|
|---|
| 2681 |
|
|---|
| 2682 | /* check for any edge intersections, so nothing is partially outside of poly1 */
|
|---|
| 2683 | if (LW_TRUE == lwpoly_intersects_line(poly, line->points))
|
|---|
| 2684 | {
|
|---|
| 2685 | LWDEBUG(4,"returning false, geometry2 is partially outside of geometry1");
|
|---|
| 2686 | return LW_FALSE;
|
|---|
| 2687 | }
|
|---|
| 2688 |
|
|---|
| 2689 | /* no abort condition found, so the poly2 should be completely inside poly1 */
|
|---|
| 2690 | return LW_TRUE;
|
|---|
| 2691 | }
|
|---|
| 2692 |
|
|---|
| 2693 | /**
|
|---|
| 2694 | * return LW_TRUE if all points are inside the polygon
|
|---|
| 2695 | */
|
|---|
| 2696 | int lwpoly_covers_pointarray(const LWPOLY* lwpoly, const POINTARRAY* pta)
|
|---|
| 2697 | {
|
|---|
| 2698 | uint32_t i;
|
|---|
| 2699 | for (i = 0; i < pta->npoints; i++) {
|
|---|
| 2700 | const POINT2D* pt_to_test = getPoint2d_cp(pta, i);
|
|---|
| 2701 |
|
|---|
| 2702 | if ( LW_FALSE == lwpoly_covers_point2d(lwpoly, pt_to_test) ) {
|
|---|
| 2703 | LWDEBUG(4,"returning false, geometry2 has point outside of geometry1");
|
|---|
| 2704 | return LW_FALSE;
|
|---|
| 2705 | }
|
|---|
| 2706 | }
|
|---|
| 2707 |
|
|---|
| 2708 | return LW_TRUE;
|
|---|
| 2709 | }
|
|---|
| 2710 |
|
|---|
| 2711 | /**
|
|---|
| 2712 | * Checks if any edges of lwpoly intersect with the line formed by the pointarray
|
|---|
| 2713 | * return LW_TRUE if any intersection between the given polygon and the line
|
|---|
| 2714 | */
|
|---|
| 2715 | int lwpoly_intersects_line(const LWPOLY* lwpoly, const POINTARRAY* line)
|
|---|
| 2716 | {
|
|---|
| 2717 | uint32_t i, j, k;
|
|---|
| 2718 | POINT3D pa1, pa2, pb1, pb2;
|
|---|
| 2719 | for (i = 0; i < lwpoly->nrings; i++)
|
|---|
| 2720 | {
|
|---|
| 2721 | for (j = 0; j < lwpoly->rings[i]->npoints - 1; j++)
|
|---|
| 2722 | {
|
|---|
| 2723 | const POINT2D* a1 = getPoint2d_cp(lwpoly->rings[i], j);
|
|---|
| 2724 | const POINT2D* a2 = getPoint2d_cp(lwpoly->rings[i], j+1);
|
|---|
| 2725 |
|
|---|
| 2726 | /* Set up our stab line */
|
|---|
| 2727 | ll2cart(a1, &pa1);
|
|---|
| 2728 | ll2cart(a2, &pa2);
|
|---|
| 2729 |
|
|---|
| 2730 | for (k = 0; k < line->npoints - 1; k++)
|
|---|
| 2731 | {
|
|---|
| 2732 | const POINT2D* b1 = getPoint2d_cp(line, k);
|
|---|
| 2733 | const POINT2D* b2 = getPoint2d_cp(line, k+1);
|
|---|
| 2734 |
|
|---|
| 2735 | /* Set up our stab line */
|
|---|
| 2736 | ll2cart(b1, &pb1);
|
|---|
| 2737 | ll2cart(b2, &pb2);
|
|---|
| 2738 |
|
|---|
| 2739 | int inter = edge_intersects(&pa1, &pa2, &pb1, &pb2);
|
|---|
| 2740 |
|
|---|
| 2741 | /* ignore same edges */
|
|---|
| 2742 | if (inter & PIR_INTERSECTS
|
|---|
| 2743 | && !(inter & PIR_B_TOUCH_RIGHT || inter & PIR_COLINEAR) )
|
|---|
| 2744 | {
|
|---|
| 2745 | return LW_TRUE;
|
|---|
| 2746 | }
|
|---|
| 2747 | }
|
|---|
| 2748 | }
|
|---|
| 2749 | }
|
|---|
| 2750 |
|
|---|
| 2751 | return LW_FALSE;
|
|---|
| 2752 | }
|
|---|
| 2753 |
|
|---|
| 2754 | /**
|
|---|
| 2755 | * return LW_TRUE if any of the line segments covers the point
|
|---|
| 2756 | */
|
|---|
| 2757 | int lwline_covers_lwpoint(const LWLINE* lwline, const LWPOINT* lwpoint)
|
|---|
| 2758 | {
|
|---|
| 2759 | uint32_t i;
|
|---|
| 2760 | GEOGRAPHIC_POINT p;
|
|---|
| 2761 | GEOGRAPHIC_EDGE e;
|
|---|
| 2762 |
|
|---|
| 2763 | for ( i = 0; i < lwline->points->npoints - 1; i++)
|
|---|
| 2764 | {
|
|---|
| 2765 | const POINT2D* a1 = getPoint2d_cp(lwline->points, i);
|
|---|
| 2766 | const POINT2D* a2 = getPoint2d_cp(lwline->points, i+1);
|
|---|
| 2767 |
|
|---|
| 2768 | geographic_point_init(a1->x, a1->y, &(e.start));
|
|---|
| 2769 | geographic_point_init(a2->x, a2->y, &(e.end));
|
|---|
| 2770 |
|
|---|
| 2771 | geographic_point_init(lwpoint_get_x(lwpoint), lwpoint_get_y(lwpoint), &p);
|
|---|
| 2772 |
|
|---|
| 2773 | if ( edge_contains_point(&e, &p) ) {
|
|---|
| 2774 | return LW_TRUE;
|
|---|
| 2775 | }
|
|---|
| 2776 | }
|
|---|
| 2777 |
|
|---|
| 2778 | return LW_FALSE;
|
|---|
| 2779 | }
|
|---|
| 2780 |
|
|---|
| 2781 | /**
|
|---|
| 2782 | * Check if first and last point of line2 are covered by line1 and then each
|
|---|
| 2783 | * point in between has to be one line1 in the exact same order
|
|---|
| 2784 | * return LW_TRUE if all edge points of line2 are on line1
|
|---|
| 2785 | */
|
|---|
| 2786 | int lwline_covers_lwline(const LWLINE* lwline1, const LWLINE* lwline2)
|
|---|
| 2787 | {
|
|---|
| 2788 | uint32_t i, j;
|
|---|
| 2789 | GEOGRAPHIC_EDGE e1, e2;
|
|---|
| 2790 | GEOGRAPHIC_POINT p1, p2;
|
|---|
| 2791 | int start = LW_FALSE;
|
|---|
| 2792 | int changed = LW_FALSE;
|
|---|
| 2793 |
|
|---|
| 2794 | /* first point on line */
|
|---|
| 2795 | if ( ! lwline_covers_lwpoint(lwline1, lwline_get_lwpoint(lwline2, 0)))
|
|---|
| 2796 | {
|
|---|
| 2797 | LWDEBUG(4,"returning false, first point of line2 is not covered by line1");
|
|---|
| 2798 | return LW_FALSE;
|
|---|
| 2799 | }
|
|---|
| 2800 |
|
|---|
| 2801 | /* last point on line */
|
|---|
| 2802 | if ( ! lwline_covers_lwpoint(lwline1, lwline_get_lwpoint(lwline2, lwline2->points->npoints - 1)))
|
|---|
| 2803 | {
|
|---|
| 2804 | LWDEBUG(4,"returning false, last point of line2 is not covered by line1");
|
|---|
| 2805 | return LW_FALSE;
|
|---|
| 2806 | }
|
|---|
| 2807 |
|
|---|
| 2808 | j = 0;
|
|---|
| 2809 | i = 0;
|
|---|
| 2810 | while (i < lwline1->points->npoints - 1 && j < lwline2->points->npoints - 1)
|
|---|
| 2811 | {
|
|---|
| 2812 | changed = LW_FALSE;
|
|---|
| 2813 | const POINT2D* a1 = getPoint2d_cp(lwline1->points, i);
|
|---|
| 2814 | const POINT2D* a2 = getPoint2d_cp(lwline1->points, i+1);
|
|---|
| 2815 | const POINT2D* b1 = getPoint2d_cp(lwline2->points, j);
|
|---|
| 2816 | const POINT2D* b2 = getPoint2d_cp(lwline2->points, j+1);
|
|---|
| 2817 |
|
|---|
| 2818 | geographic_point_init(a1->x, a1->y, &(e1.start));
|
|---|
| 2819 | geographic_point_init(a2->x, a2->y, &(e1.end));
|
|---|
| 2820 | geographic_point_init(b1->x, b1->y, &p2);
|
|---|
| 2821 |
|
|---|
| 2822 | /* we already know, that the last point is on line1, so we're done */
|
|---|
| 2823 | if ( j == lwline2->points->npoints - 1)
|
|---|
| 2824 | {
|
|---|
| 2825 | return LW_TRUE;
|
|---|
| 2826 | }
|
|---|
| 2827 | else if (start == LW_TRUE)
|
|---|
| 2828 | {
|
|---|
| 2829 | /* point is on current line1 edge, check next point in line2 */
|
|---|
| 2830 | if ( edge_contains_point(&e1, &p2)) {
|
|---|
| 2831 | j++;
|
|---|
| 2832 | changed = LW_TRUE;
|
|---|
| 2833 | }
|
|---|
| 2834 |
|
|---|
| 2835 | geographic_point_init(a1->x, a1->y, &(e2.start));
|
|---|
| 2836 | geographic_point_init(a2->x, b2->y, &(e2.end));
|
|---|
| 2837 | geographic_point_init(a1->x, a1->y, &p1);
|
|---|
| 2838 |
|
|---|
| 2839 | /* point is on current line2 edge, check next point in line1 */
|
|---|
| 2840 | if ( edge_contains_point(&e2, &p1)) {
|
|---|
| 2841 | i++;
|
|---|
| 2842 | changed = LW_TRUE;
|
|---|
| 2843 | }
|
|---|
| 2844 |
|
|---|
| 2845 | /* no edge progressed -> point left one line */
|
|---|
| 2846 | if ( changed == LW_FALSE )
|
|---|
| 2847 | {
|
|---|
| 2848 | LWDEBUG(4,"returning false, found point not covered by both lines");
|
|---|
| 2849 | return LW_FALSE;
|
|---|
| 2850 | }
|
|---|
| 2851 | else
|
|---|
| 2852 | {
|
|---|
| 2853 | continue;
|
|---|
| 2854 | }
|
|---|
| 2855 | }
|
|---|
| 2856 |
|
|---|
| 2857 | /* find first edge to cover line2 */
|
|---|
| 2858 | if (edge_contains_point(&e1, &p2))
|
|---|
| 2859 | {
|
|---|
| 2860 | start = LW_TRUE;
|
|---|
| 2861 | }
|
|---|
| 2862 |
|
|---|
| 2863 | /* next line1 edge */
|
|---|
| 2864 | i++;
|
|---|
| 2865 | }
|
|---|
| 2866 |
|
|---|
| 2867 | /* no uncovered point found */
|
|---|
| 2868 | return LW_TRUE;
|
|---|
| 2869 | }
|
|---|
| 2870 |
|
|---|
| 2871 | /**
|
|---|
| 2872 | * This function can only be used on LWGEOM that is built on top of
|
|---|
| 2873 | * GSERIALIZED, otherwise alignment errors will ensue.
|
|---|
| 2874 | */
|
|---|
| 2875 | int getPoint2d_p_ro(const POINTARRAY *pa, uint32_t n, POINT2D **point)
|
|---|
| 2876 | {
|
|---|
| 2877 | uint8_t *pa_ptr = NULL;
|
|---|
| 2878 | assert(pa);
|
|---|
| 2879 | assert(n < pa->npoints);
|
|---|
| 2880 |
|
|---|
| 2881 | pa_ptr = getPoint_internal(pa, n);
|
|---|
| 2882 | /* printf( "pa_ptr[0]: %g\n", *((double*)pa_ptr)); */
|
|---|
| 2883 | *point = (POINT2D*)pa_ptr;
|
|---|
| 2884 |
|
|---|
| 2885 | return LW_SUCCESS;
|
|---|
| 2886 | }
|
|---|
| 2887 |
|
|---|
| 2888 |
|
|---|
| 2889 | int ptarray_calculate_gbox_geodetic(const POINTARRAY *pa, GBOX *gbox)
|
|---|
| 2890 | {
|
|---|
| 2891 | uint32_t i;
|
|---|
| 2892 | int first = LW_TRUE;
|
|---|
| 2893 | const POINT2D *p;
|
|---|
| 2894 | POINT3D A1, A2;
|
|---|
| 2895 | GBOX edge_gbox;
|
|---|
| 2896 |
|
|---|
| 2897 | assert(gbox);
|
|---|
| 2898 | assert(pa);
|
|---|
| 2899 |
|
|---|
| 2900 | gbox_init(&edge_gbox);
|
|---|
| 2901 | edge_gbox.flags = gbox->flags;
|
|---|
| 2902 |
|
|---|
| 2903 | if ( pa->npoints == 0 ) return LW_FAILURE;
|
|---|
| 2904 |
|
|---|
| 2905 | if ( pa->npoints == 1 )
|
|---|
| 2906 | {
|
|---|
| 2907 | p = getPoint2d_cp(pa, 0);
|
|---|
| 2908 | ll2cart(p, &A1);
|
|---|
| 2909 | gbox->xmin = gbox->xmax = A1.x;
|
|---|
| 2910 | gbox->ymin = gbox->ymax = A1.y;
|
|---|
| 2911 | gbox->zmin = gbox->zmax = A1.z;
|
|---|
| 2912 | return LW_SUCCESS;
|
|---|
| 2913 | }
|
|---|
| 2914 |
|
|---|
| 2915 | p = getPoint2d_cp(pa, 0);
|
|---|
| 2916 | ll2cart(p, &A1);
|
|---|
| 2917 |
|
|---|
| 2918 | for ( i = 1; i < pa->npoints; i++ )
|
|---|
| 2919 | {
|
|---|
| 2920 |
|
|---|
| 2921 | p = getPoint2d_cp(pa, i);
|
|---|
| 2922 | ll2cart(p, &A2);
|
|---|
| 2923 |
|
|---|
| 2924 | edge_calculate_gbox(&A1, &A2, &edge_gbox);
|
|---|
| 2925 |
|
|---|
| 2926 | /* Initialize the box */
|
|---|
| 2927 | if ( first )
|
|---|
| 2928 | {
|
|---|
| 2929 | gbox_duplicate(&edge_gbox, gbox);
|
|---|
| 2930 | first = LW_FALSE;
|
|---|
| 2931 | }
|
|---|
| 2932 | /* Expand the box where necessary */
|
|---|
| 2933 | else
|
|---|
| 2934 | {
|
|---|
| 2935 | gbox_merge(&edge_gbox, gbox);
|
|---|
| 2936 | }
|
|---|
| 2937 |
|
|---|
| 2938 | A1 = A2;
|
|---|
| 2939 | }
|
|---|
| 2940 |
|
|---|
| 2941 | return LW_SUCCESS;
|
|---|
| 2942 | }
|
|---|
| 2943 |
|
|---|
| 2944 | static int lwpoint_calculate_gbox_geodetic(const LWPOINT *point, GBOX *gbox)
|
|---|
| 2945 | {
|
|---|
| 2946 | assert(point);
|
|---|
| 2947 | return ptarray_calculate_gbox_geodetic(point->point, gbox);
|
|---|
| 2948 | }
|
|---|
| 2949 |
|
|---|
| 2950 | static int lwline_calculate_gbox_geodetic(const LWLINE *line, GBOX *gbox)
|
|---|
| 2951 | {
|
|---|
| 2952 | assert(line);
|
|---|
| 2953 | return ptarray_calculate_gbox_geodetic(line->points, gbox);
|
|---|
| 2954 | }
|
|---|
| 2955 |
|
|---|
| 2956 | static int lwpolygon_calculate_gbox_geodetic(const LWPOLY *poly, GBOX *gbox)
|
|---|
| 2957 | {
|
|---|
| 2958 | GBOX ringbox;
|
|---|
| 2959 | uint32_t i;
|
|---|
| 2960 | int first = LW_TRUE;
|
|---|
| 2961 | assert(poly);
|
|---|
| 2962 | if ( poly->nrings == 0 )
|
|---|
| 2963 | return LW_FAILURE;
|
|---|
| 2964 | ringbox.flags = gbox->flags;
|
|---|
| 2965 | for ( i = 0; i < poly->nrings; i++ )
|
|---|
| 2966 | {
|
|---|
| 2967 | if ( ptarray_calculate_gbox_geodetic(poly->rings[i], &ringbox) == LW_FAILURE )
|
|---|
| 2968 | return LW_FAILURE;
|
|---|
| 2969 | if ( first )
|
|---|
| 2970 | {
|
|---|
| 2971 | gbox_duplicate(&ringbox, gbox);
|
|---|
| 2972 | first = LW_FALSE;
|
|---|
| 2973 | }
|
|---|
| 2974 | else
|
|---|
| 2975 | {
|
|---|
| 2976 | gbox_merge(&ringbox, gbox);
|
|---|
| 2977 | }
|
|---|
| 2978 | }
|
|---|
| 2979 |
|
|---|
| 2980 | /* If the box wraps a poly, push that axis to the absolute min/max as appropriate */
|
|---|
| 2981 | gbox_check_poles(gbox);
|
|---|
| 2982 |
|
|---|
| 2983 | return LW_SUCCESS;
|
|---|
| 2984 | }
|
|---|
| 2985 |
|
|---|
| 2986 | static int lwtriangle_calculate_gbox_geodetic(const LWTRIANGLE *triangle, GBOX *gbox)
|
|---|
| 2987 | {
|
|---|
| 2988 | assert(triangle);
|
|---|
| 2989 | return ptarray_calculate_gbox_geodetic(triangle->points, gbox);
|
|---|
| 2990 | }
|
|---|
| 2991 |
|
|---|
| 2992 |
|
|---|
| 2993 | static int lwcollection_calculate_gbox_geodetic(const LWCOLLECTION *coll, GBOX *gbox)
|
|---|
| 2994 | {
|
|---|
| 2995 | GBOX subbox;
|
|---|
| 2996 | uint32_t i;
|
|---|
| 2997 | int result = LW_FAILURE;
|
|---|
| 2998 | int first = LW_TRUE;
|
|---|
| 2999 | assert(coll);
|
|---|
| 3000 | if ( coll->ngeoms == 0 )
|
|---|
| 3001 | return LW_FAILURE;
|
|---|
| 3002 |
|
|---|
| 3003 | subbox.flags = gbox->flags;
|
|---|
| 3004 |
|
|---|
| 3005 | for ( i = 0; i < coll->ngeoms; i++ )
|
|---|
| 3006 | {
|
|---|
| 3007 | if ( lwgeom_calculate_gbox_geodetic((LWGEOM*)(coll->geoms[i]), &subbox) == LW_SUCCESS )
|
|---|
| 3008 | {
|
|---|
| 3009 | /* Keep a copy of the sub-bounding box for later */
|
|---|
| 3010 | if ( coll->geoms[i]->bbox )
|
|---|
| 3011 | lwfree(coll->geoms[i]->bbox);
|
|---|
| 3012 | coll->geoms[i]->bbox = gbox_copy(&subbox);
|
|---|
| 3013 | if ( first )
|
|---|
| 3014 | {
|
|---|
| 3015 | gbox_duplicate(&subbox, gbox);
|
|---|
| 3016 | first = LW_FALSE;
|
|---|
| 3017 | }
|
|---|
| 3018 | else
|
|---|
| 3019 | {
|
|---|
| 3020 | gbox_merge(&subbox, gbox);
|
|---|
| 3021 | }
|
|---|
| 3022 | result = LW_SUCCESS;
|
|---|
| 3023 | }
|
|---|
| 3024 | }
|
|---|
| 3025 | return result;
|
|---|
| 3026 | }
|
|---|
| 3027 |
|
|---|
| 3028 | int lwgeom_calculate_gbox_geodetic(const LWGEOM *geom, GBOX *gbox)
|
|---|
| 3029 | {
|
|---|
| 3030 | int result = LW_FAILURE;
|
|---|
| 3031 | LWDEBUGF(4, "got type %d", geom->type);
|
|---|
| 3032 |
|
|---|
| 3033 | /* Add a geodetic flag to the incoming gbox */
|
|---|
| 3034 | gbox->flags = lwflags(FLAGS_GET_Z(geom->flags),FLAGS_GET_M(geom->flags),1);
|
|---|
| 3035 |
|
|---|
| 3036 | switch (geom->type)
|
|---|
| 3037 | {
|
|---|
| 3038 | case POINTTYPE:
|
|---|
| 3039 | result = lwpoint_calculate_gbox_geodetic((LWPOINT*)geom, gbox);
|
|---|
| 3040 | break;
|
|---|
| 3041 | case LINETYPE:
|
|---|
| 3042 | result = lwline_calculate_gbox_geodetic((LWLINE *)geom, gbox);
|
|---|
| 3043 | break;
|
|---|
| 3044 | case POLYGONTYPE:
|
|---|
| 3045 | result = lwpolygon_calculate_gbox_geodetic((LWPOLY *)geom, gbox);
|
|---|
| 3046 | break;
|
|---|
| 3047 | case TRIANGLETYPE:
|
|---|
| 3048 | result = lwtriangle_calculate_gbox_geodetic((LWTRIANGLE *)geom, gbox);
|
|---|
| 3049 | break;
|
|---|
| 3050 | case MULTIPOINTTYPE:
|
|---|
| 3051 | case MULTILINETYPE:
|
|---|
| 3052 | case MULTIPOLYGONTYPE:
|
|---|
| 3053 | case POLYHEDRALSURFACETYPE:
|
|---|
| 3054 | case TINTYPE:
|
|---|
| 3055 | case COLLECTIONTYPE:
|
|---|
| 3056 | result = lwcollection_calculate_gbox_geodetic((LWCOLLECTION *)geom, gbox);
|
|---|
| 3057 | break;
|
|---|
| 3058 | default:
|
|---|
| 3059 | lwerror("lwgeom_calculate_gbox_geodetic: unsupported input geometry type: %d - %s",
|
|---|
| 3060 | geom->type, lwtype_name(geom->type));
|
|---|
| 3061 | break;
|
|---|
| 3062 | }
|
|---|
| 3063 | return result;
|
|---|
| 3064 | }
|
|---|
| 3065 |
|
|---|
| 3066 |
|
|---|
| 3067 |
|
|---|
| 3068 | static int ptarray_check_geodetic(const POINTARRAY *pa)
|
|---|
| 3069 | {
|
|---|
| 3070 | uint32_t t;
|
|---|
| 3071 | POINT2D pt;
|
|---|
| 3072 |
|
|---|
| 3073 | assert(pa);
|
|---|
| 3074 |
|
|---|
| 3075 | for (t=0; t<pa->npoints; t++)
|
|---|
| 3076 | {
|
|---|
| 3077 | getPoint2d_p(pa, t, &pt);
|
|---|
| 3078 | /* printf( "%d (%g, %g)\n", t, pt.x, pt.y); */
|
|---|
| 3079 | if ( pt.x < -180.0 || pt.y < -90.0 || pt.x > 180.0 || pt.y > 90.0 )
|
|---|
| 3080 | return LW_FALSE;
|
|---|
| 3081 | }
|
|---|
| 3082 |
|
|---|
| 3083 | return LW_TRUE;
|
|---|
| 3084 | }
|
|---|
| 3085 |
|
|---|
| 3086 | static int lwpoint_check_geodetic(const LWPOINT *point)
|
|---|
| 3087 | {
|
|---|
| 3088 | assert(point);
|
|---|
| 3089 | return ptarray_check_geodetic(point->point);
|
|---|
| 3090 | }
|
|---|
| 3091 |
|
|---|
| 3092 | static int lwline_check_geodetic(const LWLINE *line)
|
|---|
| 3093 | {
|
|---|
| 3094 | assert(line);
|
|---|
| 3095 | return ptarray_check_geodetic(line->points);
|
|---|
| 3096 | }
|
|---|
| 3097 |
|
|---|
| 3098 | static int lwpoly_check_geodetic(const LWPOLY *poly)
|
|---|
| 3099 | {
|
|---|
| 3100 | uint32_t i = 0;
|
|---|
| 3101 | assert(poly);
|
|---|
| 3102 |
|
|---|
| 3103 | for ( i = 0; i < poly->nrings; i++ )
|
|---|
| 3104 | {
|
|---|
| 3105 | if ( ptarray_check_geodetic(poly->rings[i]) == LW_FALSE )
|
|---|
| 3106 | return LW_FALSE;
|
|---|
| 3107 | }
|
|---|
| 3108 | return LW_TRUE;
|
|---|
| 3109 | }
|
|---|
| 3110 |
|
|---|
| 3111 | static int lwtriangle_check_geodetic(const LWTRIANGLE *triangle)
|
|---|
| 3112 | {
|
|---|
| 3113 | assert(triangle);
|
|---|
| 3114 | return ptarray_check_geodetic(triangle->points);
|
|---|
| 3115 | }
|
|---|
| 3116 |
|
|---|
| 3117 |
|
|---|
| 3118 | static int lwcollection_check_geodetic(const LWCOLLECTION *col)
|
|---|
| 3119 | {
|
|---|
| 3120 | uint32_t i = 0;
|
|---|
| 3121 | assert(col);
|
|---|
| 3122 |
|
|---|
| 3123 | for ( i = 0; i < col->ngeoms; i++ )
|
|---|
| 3124 | {
|
|---|
| 3125 | if ( lwgeom_check_geodetic(col->geoms[i]) == LW_FALSE )
|
|---|
| 3126 | return LW_FALSE;
|
|---|
| 3127 | }
|
|---|
| 3128 | return LW_TRUE;
|
|---|
| 3129 | }
|
|---|
| 3130 |
|
|---|
| 3131 | int lwgeom_check_geodetic(const LWGEOM *geom)
|
|---|
| 3132 | {
|
|---|
| 3133 | if ( lwgeom_is_empty(geom) )
|
|---|
| 3134 | return LW_TRUE;
|
|---|
| 3135 |
|
|---|
| 3136 | switch (geom->type)
|
|---|
| 3137 | {
|
|---|
| 3138 | case POINTTYPE:
|
|---|
| 3139 | return lwpoint_check_geodetic((LWPOINT *)geom);
|
|---|
| 3140 | case LINETYPE:
|
|---|
| 3141 | return lwline_check_geodetic((LWLINE *)geom);
|
|---|
| 3142 | case POLYGONTYPE:
|
|---|
| 3143 | return lwpoly_check_geodetic((LWPOLY *)geom);
|
|---|
| 3144 | case TRIANGLETYPE:
|
|---|
| 3145 | return lwtriangle_check_geodetic((LWTRIANGLE *)geom);
|
|---|
| 3146 | case MULTIPOINTTYPE:
|
|---|
| 3147 | case MULTILINETYPE:
|
|---|
| 3148 | case MULTIPOLYGONTYPE:
|
|---|
| 3149 | case POLYHEDRALSURFACETYPE:
|
|---|
| 3150 | case TINTYPE:
|
|---|
| 3151 | case COLLECTIONTYPE:
|
|---|
| 3152 | return lwcollection_check_geodetic((LWCOLLECTION *)geom);
|
|---|
| 3153 | default:
|
|---|
| 3154 | lwerror("lwgeom_check_geodetic: unsupported input geometry type: %d - %s",
|
|---|
| 3155 | geom->type, lwtype_name(geom->type));
|
|---|
| 3156 | }
|
|---|
| 3157 | return LW_FALSE;
|
|---|
| 3158 | }
|
|---|
| 3159 |
|
|---|
| 3160 | static int ptarray_force_geodetic(POINTARRAY *pa)
|
|---|
| 3161 | {
|
|---|
| 3162 | uint32_t t;
|
|---|
| 3163 | int changed = LW_FALSE;
|
|---|
| 3164 | POINT4D pt;
|
|---|
| 3165 |
|
|---|
| 3166 | assert(pa);
|
|---|
| 3167 |
|
|---|
| 3168 | for ( t=0; t < pa->npoints; t++ )
|
|---|
| 3169 | {
|
|---|
| 3170 | getPoint4d_p(pa, t, &pt);
|
|---|
| 3171 | if ( pt.x < -180.0 || pt.x > 180.0 || pt.y < -90.0 || pt.y > 90.0 )
|
|---|
| 3172 | {
|
|---|
| 3173 | pt.x = longitude_degrees_normalize(pt.x);
|
|---|
| 3174 | pt.y = latitude_degrees_normalize(pt.y);
|
|---|
| 3175 | ptarray_set_point4d(pa, t, &pt);
|
|---|
| 3176 | changed = LW_TRUE;
|
|---|
| 3177 | }
|
|---|
| 3178 | }
|
|---|
| 3179 | return changed;
|
|---|
| 3180 | }
|
|---|
| 3181 |
|
|---|
| 3182 | static int lwpoint_force_geodetic(LWPOINT *point)
|
|---|
| 3183 | {
|
|---|
| 3184 | assert(point);
|
|---|
| 3185 | return ptarray_force_geodetic(point->point);
|
|---|
| 3186 | }
|
|---|
| 3187 |
|
|---|
| 3188 | static int lwline_force_geodetic(LWLINE *line)
|
|---|
| 3189 | {
|
|---|
| 3190 | assert(line);
|
|---|
| 3191 | return ptarray_force_geodetic(line->points);
|
|---|
| 3192 | }
|
|---|
| 3193 |
|
|---|
| 3194 | static int lwpoly_force_geodetic(LWPOLY *poly)
|
|---|
| 3195 | {
|
|---|
| 3196 | uint32_t i = 0;
|
|---|
| 3197 | int changed = LW_FALSE;
|
|---|
| 3198 | assert(poly);
|
|---|
| 3199 |
|
|---|
| 3200 | for ( i = 0; i < poly->nrings; i++ )
|
|---|
| 3201 | {
|
|---|
| 3202 | if ( ptarray_force_geodetic(poly->rings[i]) == LW_TRUE )
|
|---|
| 3203 | changed = LW_TRUE;
|
|---|
| 3204 | }
|
|---|
| 3205 | return changed;
|
|---|
| 3206 | }
|
|---|
| 3207 |
|
|---|
| 3208 | static int lwcollection_force_geodetic(LWCOLLECTION *col)
|
|---|
| 3209 | {
|
|---|
| 3210 | uint32_t i = 0;
|
|---|
| 3211 | int changed = LW_FALSE;
|
|---|
| 3212 | assert(col);
|
|---|
| 3213 |
|
|---|
| 3214 | for ( i = 0; i < col->ngeoms; i++ )
|
|---|
| 3215 | {
|
|---|
| 3216 | if ( lwgeom_force_geodetic(col->geoms[i]) == LW_TRUE )
|
|---|
| 3217 | changed = LW_TRUE;
|
|---|
| 3218 | }
|
|---|
| 3219 | return changed;
|
|---|
| 3220 | }
|
|---|
| 3221 |
|
|---|
| 3222 | int lwgeom_force_geodetic(LWGEOM *geom)
|
|---|
| 3223 | {
|
|---|
| 3224 | switch ( lwgeom_get_type(geom) )
|
|---|
| 3225 | {
|
|---|
| 3226 | case POINTTYPE:
|
|---|
| 3227 | return lwpoint_force_geodetic((LWPOINT *)geom);
|
|---|
| 3228 | case LINETYPE:
|
|---|
| 3229 | return lwline_force_geodetic((LWLINE *)geom);
|
|---|
| 3230 | case POLYGONTYPE:
|
|---|
| 3231 | return lwpoly_force_geodetic((LWPOLY *)geom);
|
|---|
| 3232 | case MULTIPOINTTYPE:
|
|---|
| 3233 | case MULTILINETYPE:
|
|---|
| 3234 | case MULTIPOLYGONTYPE:
|
|---|
| 3235 | case COLLECTIONTYPE:
|
|---|
| 3236 | return lwcollection_force_geodetic((LWCOLLECTION *)geom);
|
|---|
| 3237 | default:
|
|---|
| 3238 | lwerror("unsupported input geometry type: %d", lwgeom_get_type(geom));
|
|---|
| 3239 | }
|
|---|
| 3240 | return LW_FALSE;
|
|---|
| 3241 | }
|
|---|
| 3242 |
|
|---|
| 3243 |
|
|---|
| 3244 | double ptarray_length_spheroid(const POINTARRAY *pa, const SPHEROID *s)
|
|---|
| 3245 | {
|
|---|
| 3246 | GEOGRAPHIC_POINT a, b;
|
|---|
| 3247 | double za = 0.0, zb = 0.0;
|
|---|
| 3248 | POINT4D p;
|
|---|
| 3249 | uint32_t i;
|
|---|
| 3250 | int hasz = LW_FALSE;
|
|---|
| 3251 | double length = 0.0;
|
|---|
| 3252 | double seglength = 0.0;
|
|---|
| 3253 |
|
|---|
| 3254 | /* Return zero on non-sensical inputs */
|
|---|
| 3255 | if ( ! pa || pa->npoints < 2 )
|
|---|
| 3256 | return 0.0;
|
|---|
| 3257 |
|
|---|
| 3258 | /* See if we have a third dimension */
|
|---|
| 3259 | hasz = FLAGS_GET_Z(pa->flags);
|
|---|
| 3260 |
|
|---|
| 3261 | /* Initialize first point */
|
|---|
| 3262 | getPoint4d_p(pa, 0, &p);
|
|---|
| 3263 | geographic_point_init(p.x, p.y, &a);
|
|---|
| 3264 | if ( hasz )
|
|---|
| 3265 | za = p.z;
|
|---|
| 3266 |
|
|---|
| 3267 | /* Loop and sum the length for each segment */
|
|---|
| 3268 | for ( i = 1; i < pa->npoints; i++ )
|
|---|
| 3269 | {
|
|---|
| 3270 | seglength = 0.0;
|
|---|
| 3271 | getPoint4d_p(pa, i, &p);
|
|---|
| 3272 | geographic_point_init(p.x, p.y, &b);
|
|---|
| 3273 | if ( hasz )
|
|---|
| 3274 | zb = p.z;
|
|---|
| 3275 |
|
|---|
| 3276 | /* Special sphere case */
|
|---|
| 3277 | if ( s->a == s->b )
|
|---|
| 3278 | seglength = s->radius * sphere_distance(&a, &b);
|
|---|
| 3279 | /* Spheroid case */
|
|---|
| 3280 | else
|
|---|
| 3281 | seglength = spheroid_distance(&a, &b, s);
|
|---|
| 3282 |
|
|---|
| 3283 | /* Add in the vertical displacement if we're in 3D */
|
|---|
| 3284 | if ( hasz )
|
|---|
| 3285 | seglength = sqrt( (zb-za)*(zb-za) + seglength*seglength );
|
|---|
| 3286 |
|
|---|
| 3287 | /* Add this segment length to the total */
|
|---|
| 3288 | length += seglength;
|
|---|
| 3289 |
|
|---|
| 3290 | /* B gets incremented in the next loop, so we save the value here */
|
|---|
| 3291 | a = b;
|
|---|
| 3292 | za = zb;
|
|---|
| 3293 | }
|
|---|
| 3294 | return length;
|
|---|
| 3295 | }
|
|---|
| 3296 |
|
|---|
| 3297 | double lwgeom_length_spheroid(const LWGEOM *geom, const SPHEROID *s)
|
|---|
| 3298 | {
|
|---|
| 3299 | int type;
|
|---|
| 3300 | uint32_t i = 0;
|
|---|
| 3301 | double length = 0.0;
|
|---|
| 3302 |
|
|---|
| 3303 | assert(geom);
|
|---|
| 3304 |
|
|---|
| 3305 | /* No area in nothing */
|
|---|
| 3306 | if ( lwgeom_is_empty(geom) )
|
|---|
| 3307 | return 0.0;
|
|---|
| 3308 |
|
|---|
| 3309 | type = geom->type;
|
|---|
| 3310 |
|
|---|
| 3311 | if ( type == POINTTYPE || type == MULTIPOINTTYPE )
|
|---|
| 3312 | return 0.0;
|
|---|
| 3313 |
|
|---|
| 3314 | if ( type == LINETYPE )
|
|---|
| 3315 | return ptarray_length_spheroid(((LWLINE*)geom)->points, s);
|
|---|
| 3316 |
|
|---|
| 3317 | if ( type == POLYGONTYPE )
|
|---|
| 3318 | {
|
|---|
| 3319 | LWPOLY *poly = (LWPOLY*)geom;
|
|---|
| 3320 | for ( i = 0; i < poly->nrings; i++ )
|
|---|
| 3321 | {
|
|---|
| 3322 | length += ptarray_length_spheroid(poly->rings[i], s);
|
|---|
| 3323 | }
|
|---|
| 3324 | return length;
|
|---|
| 3325 | }
|
|---|
| 3326 |
|
|---|
| 3327 | if ( type == TRIANGLETYPE )
|
|---|
| 3328 | return ptarray_length_spheroid(((LWTRIANGLE*)geom)->points, s);
|
|---|
| 3329 |
|
|---|
| 3330 | if ( lwtype_is_collection( type ) )
|
|---|
| 3331 | {
|
|---|
| 3332 | LWCOLLECTION *col = (LWCOLLECTION*)geom;
|
|---|
| 3333 |
|
|---|
| 3334 | for ( i = 0; i < col->ngeoms; i++ )
|
|---|
| 3335 | {
|
|---|
| 3336 | length += lwgeom_length_spheroid(col->geoms[i], s);
|
|---|
| 3337 | }
|
|---|
| 3338 | return length;
|
|---|
| 3339 | }
|
|---|
| 3340 |
|
|---|
| 3341 | lwerror("unsupported type passed to lwgeom_length_sphere");
|
|---|
| 3342 | return 0.0;
|
|---|
| 3343 | }
|
|---|
| 3344 |
|
|---|
| 3345 | /**
|
|---|
| 3346 | * When features are snapped or sometimes they are just this way, they are very close to
|
|---|
| 3347 | * the geodetic bounds but slightly over. This routine nudges those points, and only
|
|---|
| 3348 | * those points, back over to the bounds.
|
|---|
| 3349 | * http://trac.osgeo.org/postgis/ticket/1292
|
|---|
| 3350 | */
|
|---|
| 3351 | static int
|
|---|
| 3352 | ptarray_nudge_geodetic(POINTARRAY *pa)
|
|---|
| 3353 | {
|
|---|
| 3354 |
|
|---|
| 3355 | uint32_t i;
|
|---|
| 3356 | POINT4D p;
|
|---|
| 3357 | int altered = LW_FALSE;
|
|---|
| 3358 | int rv = LW_FALSE;
|
|---|
| 3359 | static double tolerance = 1e-10;
|
|---|
| 3360 |
|
|---|
| 3361 | if ( ! pa )
|
|---|
| 3362 | lwerror("ptarray_nudge_geodetic called with null input");
|
|---|
| 3363 |
|
|---|
| 3364 | for(i = 0; i < pa->npoints; i++ )
|
|---|
| 3365 | {
|
|---|
| 3366 | getPoint4d_p(pa, i, &p);
|
|---|
| 3367 | if ( p.x < -180.0 && (-180.0 - p.x < tolerance) )
|
|---|
| 3368 | {
|
|---|
| 3369 | p.x = -180.0;
|
|---|
| 3370 | altered = LW_TRUE;
|
|---|
| 3371 | }
|
|---|
| 3372 | if ( p.x > 180.0 && (p.x - 180.0 < tolerance) )
|
|---|
| 3373 | {
|
|---|
| 3374 | p.x = 180.0;
|
|---|
| 3375 | altered = LW_TRUE;
|
|---|
| 3376 | }
|
|---|
| 3377 | if ( p.y < -90.0 && (-90.0 - p.y < tolerance) )
|
|---|
| 3378 | {
|
|---|
| 3379 | p.y = -90.0;
|
|---|
| 3380 | altered = LW_TRUE;
|
|---|
| 3381 | }
|
|---|
| 3382 | if ( p.y > 90.0 && (p.y - 90.0 < tolerance) )
|
|---|
| 3383 | {
|
|---|
| 3384 | p.y = 90.0;
|
|---|
| 3385 | altered = LW_TRUE;
|
|---|
| 3386 | }
|
|---|
| 3387 | if ( altered == LW_TRUE )
|
|---|
| 3388 | {
|
|---|
| 3389 | ptarray_set_point4d(pa, i, &p);
|
|---|
| 3390 | altered = LW_FALSE;
|
|---|
| 3391 | rv = LW_TRUE;
|
|---|
| 3392 | }
|
|---|
| 3393 | }
|
|---|
| 3394 | return rv;
|
|---|
| 3395 | }
|
|---|
| 3396 |
|
|---|
| 3397 | /**
|
|---|
| 3398 | * When features are snapped or sometimes they are just this way, they are very close to
|
|---|
| 3399 | * the geodetic bounds but slightly over. This routine nudges those points, and only
|
|---|
| 3400 | * those points, back over to the bounds.
|
|---|
| 3401 | * http://trac.osgeo.org/postgis/ticket/1292
|
|---|
| 3402 | */
|
|---|
| 3403 | int
|
|---|
| 3404 | lwgeom_nudge_geodetic(LWGEOM *geom)
|
|---|
| 3405 | {
|
|---|
| 3406 | int type;
|
|---|
| 3407 | uint32_t i = 0;
|
|---|
| 3408 | int rv = LW_FALSE;
|
|---|
| 3409 |
|
|---|
| 3410 | assert(geom);
|
|---|
| 3411 |
|
|---|
| 3412 | /* No points in nothing */
|
|---|
| 3413 | if ( lwgeom_is_empty(geom) )
|
|---|
| 3414 | return LW_FALSE;
|
|---|
| 3415 |
|
|---|
| 3416 | type = geom->type;
|
|---|
| 3417 |
|
|---|
| 3418 | if ( type == POINTTYPE )
|
|---|
| 3419 | return ptarray_nudge_geodetic(((LWPOINT*)geom)->point);
|
|---|
| 3420 |
|
|---|
| 3421 | if ( type == LINETYPE )
|
|---|
| 3422 | return ptarray_nudge_geodetic(((LWLINE*)geom)->points);
|
|---|
| 3423 |
|
|---|
| 3424 | if ( type == POLYGONTYPE )
|
|---|
| 3425 | {
|
|---|
| 3426 | LWPOLY *poly = (LWPOLY*)geom;
|
|---|
| 3427 | for ( i = 0; i < poly->nrings; i++ )
|
|---|
| 3428 | {
|
|---|
| 3429 | int n = ptarray_nudge_geodetic(poly->rings[i]);
|
|---|
| 3430 | rv = (rv == LW_TRUE ? rv : n);
|
|---|
| 3431 | }
|
|---|
| 3432 | return rv;
|
|---|
| 3433 | }
|
|---|
| 3434 |
|
|---|
| 3435 | if ( type == TRIANGLETYPE )
|
|---|
| 3436 | return ptarray_nudge_geodetic(((LWTRIANGLE*)geom)->points);
|
|---|
| 3437 |
|
|---|
| 3438 | if ( lwtype_is_collection( type ) )
|
|---|
| 3439 | {
|
|---|
| 3440 | LWCOLLECTION *col = (LWCOLLECTION*)geom;
|
|---|
| 3441 |
|
|---|
| 3442 | for ( i = 0; i < col->ngeoms; i++ )
|
|---|
| 3443 | {
|
|---|
| 3444 | int n = lwgeom_nudge_geodetic(col->geoms[i]);
|
|---|
| 3445 | rv = (rv == LW_TRUE ? rv : n);
|
|---|
| 3446 | }
|
|---|
| 3447 | return rv;
|
|---|
| 3448 | }
|
|---|
| 3449 |
|
|---|
| 3450 | lwerror("unsupported type (%s) passed to lwgeom_nudge_geodetic", lwtype_name(type));
|
|---|
| 3451 | return rv;
|
|---|
| 3452 | }
|
|---|
| 3453 |
|
|---|
| 3454 |
|
|---|
| 3455 | /**
|
|---|
| 3456 | * Utility function for checking if P is within the cone defined by A1/A2.
|
|---|
| 3457 | */
|
|---|
| 3458 | static int
|
|---|
| 3459 | point_in_cone(const POINT3D *A1, const POINT3D *A2, const POINT3D *P)
|
|---|
| 3460 | {
|
|---|
| 3461 | POINT3D AC; /* Center point of A1/A2 */
|
|---|
| 3462 | double min_similarity, similarity;
|
|---|
| 3463 |
|
|---|
| 3464 | /* Boundary case */
|
|---|
| 3465 | if (point3d_equals(A1, P) || point3d_equals(A2, P))
|
|---|
| 3466 | return LW_TRUE;
|
|---|
| 3467 |
|
|---|
| 3468 | /* The normalized sum bisects the angle between start and end. */
|
|---|
| 3469 | vector_sum(A1, A2, &AC);
|
|---|
| 3470 | normalize(&AC);
|
|---|
| 3471 |
|
|---|
| 3472 | /* The projection of start onto the center defines the minimum similarity */
|
|---|
| 3473 | min_similarity = dot_product(A1, &AC);
|
|---|
| 3474 |
|
|---|
| 3475 | /* If the edge is sufficiently curved, use the dot product test */
|
|---|
| 3476 | if (fabs(1.0 - min_similarity) > 1e-10)
|
|---|
| 3477 | {
|
|---|
| 3478 | /* The projection of candidate p onto the center */
|
|---|
| 3479 | similarity = dot_product(P, &AC);
|
|---|
| 3480 |
|
|---|
| 3481 | /* If the projection of the candidate is larger than */
|
|---|
| 3482 | /* the projection of the start point, the candidate */
|
|---|
| 3483 | /* must be closer to the center than the start, so */
|
|---|
| 3484 | /* therefor inside the cone */
|
|---|
| 3485 | if (similarity > min_similarity)
|
|---|
| 3486 | {
|
|---|
| 3487 | return LW_TRUE;
|
|---|
| 3488 | }
|
|---|
| 3489 | else
|
|---|
| 3490 | {
|
|---|
| 3491 | return LW_FALSE;
|
|---|
| 3492 | }
|
|---|
| 3493 | }
|
|---|
| 3494 | else
|
|---|
| 3495 | {
|
|---|
| 3496 | /* Where the edge is very narrow, the dot product test */
|
|---|
| 3497 | /* fails, but we can use the almost-planar nature of the */
|
|---|
| 3498 | /* problem space then to test if the vector from the */
|
|---|
| 3499 | /* candidate to the start point in a different direction */
|
|---|
| 3500 | /* to the vector from candidate to end point */
|
|---|
| 3501 | /* If so, then candidate is between start and end */
|
|---|
| 3502 | POINT3D PA1, PA2;
|
|---|
| 3503 | vector_difference(P, A1, &PA1);
|
|---|
| 3504 | vector_difference(P, A2, &PA2);
|
|---|
| 3505 | normalize(&PA1);
|
|---|
| 3506 | normalize(&PA2);
|
|---|
| 3507 | if (dot_product(&PA1, &PA2) < 0.0)
|
|---|
| 3508 | {
|
|---|
| 3509 | return LW_TRUE;
|
|---|
| 3510 | }
|
|---|
| 3511 | else
|
|---|
| 3512 | {
|
|---|
| 3513 | return LW_FALSE;
|
|---|
| 3514 | }
|
|---|
| 3515 | }
|
|---|
| 3516 | return LW_FALSE;
|
|---|
| 3517 | }
|
|---|
| 3518 |
|
|---|
| 3519 |
|
|---|
| 3520 |
|
|---|
| 3521 | /**
|
|---|
| 3522 | * Utility function for edge_intersects(), signum with a tolerance
|
|---|
| 3523 | * in determining if the value is zero.
|
|---|
| 3524 | */
|
|---|
| 3525 | static int
|
|---|
| 3526 | dot_product_side(const POINT3D *p, const POINT3D *q)
|
|---|
| 3527 | {
|
|---|
| 3528 | double dp = dot_product(p, q);
|
|---|
| 3529 |
|
|---|
| 3530 | if ( FP_IS_ZERO(dp) )
|
|---|
| 3531 | return 0;
|
|---|
| 3532 |
|
|---|
| 3533 | return dp < 0.0 ? -1 : 1;
|
|---|
| 3534 | }
|
|---|
| 3535 |
|
|---|
| 3536 | /**
|
|---|
| 3537 | * Returns non-zero if edges A and B interact. The type of interaction is given in the
|
|---|
| 3538 | * return value with the bitmask elements defined above.
|
|---|
| 3539 | */
|
|---|
| 3540 | uint32_t
|
|---|
| 3541 | edge_intersects(const POINT3D *A1, const POINT3D *A2, const POINT3D *B1, const POINT3D *B2)
|
|---|
| 3542 | {
|
|---|
| 3543 | POINT3D AN, BN, VN; /* Normals to plane A and plane B */
|
|---|
| 3544 | double ab_dot;
|
|---|
| 3545 | int a1_side, a2_side, b1_side, b2_side;
|
|---|
| 3546 | int rv = PIR_NO_INTERACT;
|
|---|
| 3547 |
|
|---|
| 3548 | /* Normals to the A-plane and B-plane */
|
|---|
| 3549 | unit_normal(A1, A2, &AN);
|
|---|
| 3550 | unit_normal(B1, B2, &BN);
|
|---|
| 3551 |
|
|---|
| 3552 | /* Are A-plane and B-plane basically the same? */
|
|---|
| 3553 | ab_dot = dot_product(&AN, &BN);
|
|---|
| 3554 |
|
|---|
| 3555 | if ( FP_EQUALS(fabs(ab_dot), 1.0) )
|
|---|
| 3556 | {
|
|---|
| 3557 | /* Co-linear case */
|
|---|
| 3558 | if ( point_in_cone(A1, A2, B1) || point_in_cone(A1, A2, B2) ||
|
|---|
| 3559 | point_in_cone(B1, B2, A1) || point_in_cone(B1, B2, A2) )
|
|---|
| 3560 | {
|
|---|
| 3561 | rv |= PIR_INTERSECTS;
|
|---|
| 3562 | rv |= PIR_COLINEAR;
|
|---|
| 3563 | }
|
|---|
| 3564 | return rv;
|
|---|
| 3565 | }
|
|---|
| 3566 |
|
|---|
| 3567 | /* What side of plane-A and plane-B do the end points */
|
|---|
| 3568 | /* of A and B fall? */
|
|---|
| 3569 | a1_side = dot_product_side(&BN, A1);
|
|---|
| 3570 | a2_side = dot_product_side(&BN, A2);
|
|---|
| 3571 | b1_side = dot_product_side(&AN, B1);
|
|---|
| 3572 | b2_side = dot_product_side(&AN, B2);
|
|---|
| 3573 |
|
|---|
| 3574 | /* Both ends of A on the same side of plane B. */
|
|---|
| 3575 | if ( a1_side == a2_side && a1_side != 0 )
|
|---|
| 3576 | {
|
|---|
| 3577 | /* No intersection. */
|
|---|
| 3578 | return PIR_NO_INTERACT;
|
|---|
| 3579 | }
|
|---|
| 3580 |
|
|---|
| 3581 | /* Both ends of B on the same side of plane A. */
|
|---|
| 3582 | if ( b1_side == b2_side && b1_side != 0 )
|
|---|
| 3583 | {
|
|---|
| 3584 | /* No intersection. */
|
|---|
| 3585 | return PIR_NO_INTERACT;
|
|---|
| 3586 | }
|
|---|
| 3587 |
|
|---|
| 3588 | /* A straddles B and B straddles A, so... */
|
|---|
| 3589 | if ( a1_side != a2_side && (a1_side + a2_side) == 0 &&
|
|---|
| 3590 | b1_side != b2_side && (b1_side + b2_side) == 0 )
|
|---|
| 3591 | {
|
|---|
| 3592 | /* Have to check if intersection point is inside both arcs */
|
|---|
| 3593 | unit_normal(&AN, &BN, &VN);
|
|---|
| 3594 | if ( point_in_cone(A1, A2, &VN) && point_in_cone(B1, B2, &VN) )
|
|---|
| 3595 | {
|
|---|
| 3596 | return PIR_INTERSECTS;
|
|---|
| 3597 | }
|
|---|
| 3598 |
|
|---|
| 3599 | /* Have to check if intersection point is inside both arcs */
|
|---|
| 3600 | vector_scale(&VN, -1);
|
|---|
| 3601 | if ( point_in_cone(A1, A2, &VN) && point_in_cone(B1, B2, &VN) )
|
|---|
| 3602 | {
|
|---|
| 3603 | return PIR_INTERSECTS;
|
|---|
| 3604 | }
|
|---|
| 3605 |
|
|---|
| 3606 | return PIR_NO_INTERACT;
|
|---|
| 3607 | }
|
|---|
| 3608 |
|
|---|
| 3609 | /* The rest are all intersects variants... */
|
|---|
| 3610 | rv |= PIR_INTERSECTS;
|
|---|
| 3611 |
|
|---|
| 3612 | /* A touches B */
|
|---|
| 3613 | if ( a1_side == 0 )
|
|---|
| 3614 | {
|
|---|
| 3615 | /* Touches at A1, A2 is on what side? */
|
|---|
| 3616 | rv |= (a2_side < 0 ? PIR_A_TOUCH_RIGHT : PIR_A_TOUCH_LEFT);
|
|---|
| 3617 | }
|
|---|
| 3618 | else if ( a2_side == 0 )
|
|---|
| 3619 | {
|
|---|
| 3620 | /* Touches at A2, A1 is on what side? */
|
|---|
| 3621 | rv |= (a1_side < 0 ? PIR_A_TOUCH_RIGHT : PIR_A_TOUCH_LEFT);
|
|---|
| 3622 | }
|
|---|
| 3623 |
|
|---|
| 3624 | /* B touches A */
|
|---|
| 3625 | if ( b1_side == 0 )
|
|---|
| 3626 | {
|
|---|
| 3627 | /* Touches at B1, B2 is on what side? */
|
|---|
| 3628 | rv |= (b2_side < 0 ? PIR_B_TOUCH_RIGHT : PIR_B_TOUCH_LEFT);
|
|---|
| 3629 | }
|
|---|
| 3630 | else if ( b2_side == 0 )
|
|---|
| 3631 | {
|
|---|
| 3632 | /* Touches at B2, B1 is on what side? */
|
|---|
| 3633 | rv |= (b1_side < 0 ? PIR_B_TOUCH_RIGHT : PIR_B_TOUCH_LEFT);
|
|---|
| 3634 | }
|
|---|
| 3635 |
|
|---|
| 3636 | return rv;
|
|---|
| 3637 | }
|
|---|
| 3638 |
|
|---|
| 3639 | /**
|
|---|
| 3640 | * This routine returns LW_TRUE if the stabline joining the pt_outside and pt_to_test
|
|---|
| 3641 | * crosses the ring an odd number of times, or if the pt_to_test is on the ring boundary itself,
|
|---|
| 3642 | * returning LW_FALSE otherwise.
|
|---|
| 3643 | * The pt_outside *must* be guaranteed to be outside the ring (use the geography_pt_outside() function
|
|---|
| 3644 | * to derive one in postgis, or the gbox_pt_outside() function if you don't mind burning CPU cycles
|
|---|
| 3645 | * building a gbox first).
|
|---|
| 3646 | */
|
|---|
| 3647 | int ptarray_contains_point_sphere(const POINTARRAY *pa, const POINT2D *pt_outside, const POINT2D *pt_to_test)
|
|---|
| 3648 | {
|
|---|
| 3649 | POINT3D S1, S2; /* Stab line end points */
|
|---|
| 3650 | POINT3D E1, E2; /* Edge end points (3-space) */
|
|---|
| 3651 | POINT2D p; /* Edge end points (lon/lat) */
|
|---|
| 3652 | uint32_t count = 0, i, inter;
|
|---|
| 3653 |
|
|---|
| 3654 | /* Null input, not enough points for a ring? You ain't closed! */
|
|---|
| 3655 | if ( ! pa || pa->npoints < 4 )
|
|---|
| 3656 | return LW_FALSE;
|
|---|
| 3657 |
|
|---|
| 3658 | /* Set up our stab line */
|
|---|
| 3659 | ll2cart(pt_to_test, &S1);
|
|---|
| 3660 | ll2cart(pt_outside, &S2);
|
|---|
| 3661 |
|
|---|
| 3662 | /* Initialize first point */
|
|---|
| 3663 | getPoint2d_p(pa, 0, &p);
|
|---|
| 3664 | ll2cart(&p, &E1);
|
|---|
| 3665 |
|
|---|
| 3666 | /* Walk every edge and see if the stab line hits it */
|
|---|
| 3667 | for ( i = 1; i < pa->npoints; i++ )
|
|---|
| 3668 | {
|
|---|
| 3669 | LWDEBUGF(4, "testing edge (%d)", i);
|
|---|
| 3670 | LWDEBUGF(4, " start point == POINT(%.12g %.12g)", p.x, p.y);
|
|---|
| 3671 |
|
|---|
| 3672 | /* Read next point. */
|
|---|
| 3673 | getPoint2d_p(pa, i, &p);
|
|---|
| 3674 | ll2cart(&p, &E2);
|
|---|
| 3675 |
|
|---|
| 3676 | /* Skip over too-short edges. */
|
|---|
| 3677 | if ( point3d_equals(&E1, &E2) )
|
|---|
| 3678 | {
|
|---|
| 3679 | continue;
|
|---|
| 3680 | }
|
|---|
| 3681 |
|
|---|
| 3682 | /* Our test point is on an edge end! Point is "in ring" by our definition */
|
|---|
| 3683 | if ( point3d_equals(&S1, &E1) )
|
|---|
| 3684 | {
|
|---|
| 3685 | return LW_TRUE;
|
|---|
| 3686 | }
|
|---|
| 3687 |
|
|---|
| 3688 | /* Calculate relationship between stab line and edge */
|
|---|
| 3689 | inter = edge_intersects(&S1, &S2, &E1, &E2);
|
|---|
| 3690 |
|
|---|
| 3691 | /* We have some kind of interaction... */
|
|---|
| 3692 | if ( inter & PIR_INTERSECTS )
|
|---|
| 3693 | {
|
|---|
| 3694 | /* If the stabline is touching the edge, that implies the test point */
|
|---|
| 3695 | /* is on the edge, so we're done, the point is in (on) the ring. */
|
|---|
| 3696 | if ( (inter & PIR_A_TOUCH_RIGHT) || (inter & PIR_A_TOUCH_LEFT) )
|
|---|
| 3697 | {
|
|---|
| 3698 | return LW_TRUE;
|
|---|
| 3699 | }
|
|---|
| 3700 |
|
|---|
| 3701 | /* It's a touching interaction, disregard all the left-side ones. */
|
|---|
| 3702 | /* It's a co-linear intersection, ignore those. */
|
|---|
| 3703 | if ( inter & PIR_B_TOUCH_RIGHT || inter & PIR_COLINEAR )
|
|---|
| 3704 | {
|
|---|
| 3705 | /* Do nothing, to avoid double counts. */
|
|---|
| 3706 | LWDEBUGF(4," edge (%d) crossed, disregarding to avoid double count", i, count);
|
|---|
| 3707 | }
|
|---|
| 3708 | else
|
|---|
| 3709 | {
|
|---|
| 3710 | /* Increment crossingn count. */
|
|---|
| 3711 | count++;
|
|---|
| 3712 | LWDEBUGF(4," edge (%d) crossed, count == %d", i, count);
|
|---|
| 3713 | }
|
|---|
| 3714 | }
|
|---|
| 3715 | else
|
|---|
| 3716 | {
|
|---|
| 3717 | LWDEBUGF(4," edge (%d) did not cross", i);
|
|---|
| 3718 | }
|
|---|
| 3719 |
|
|---|
| 3720 | /* Increment to next edge */
|
|---|
| 3721 | E1 = E2;
|
|---|
| 3722 | }
|
|---|
| 3723 |
|
|---|
| 3724 | LWDEBUGF(4,"final count == %d", count);
|
|---|
| 3725 |
|
|---|
| 3726 | /* An odd number of crossings implies containment! */
|
|---|
| 3727 | if ( count % 2 )
|
|---|
| 3728 | {
|
|---|
| 3729 | return LW_TRUE;
|
|---|
| 3730 | }
|
|---|
| 3731 |
|
|---|
| 3732 | return LW_FALSE;
|
|---|
| 3733 | }
|
|---|