PostGIS 3.7.0dev-r@@SVN_REVISION@@
Loading...
Searching...
No Matches
lwgeom_spheroid.c
Go to the documentation of this file.
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 (C) 2001-2003 Refractions Research Inc.
22 *
23 **********************************************************************/
24
25
26#include "postgres.h"
27
28
29#include <math.h>
30#include <float.h>
31#include <string.h>
32#include <stdio.h>
33
34#include "access/gist.h"
35#include "access/itup.h"
36
37#include "fmgr.h"
38#include "utils/elog.h"
39
40#include "../postgis_config.h"
41#include "liblwgeom.h"
42#include "lwgeom_pg.h"
43
44#define SHOW_DIGS_DOUBLE 15
45#define MAX_DIGS_DOUBLE (SHOW_DIGS_DOUBLE + 6 + 1 + 3 +1)
46
47/*
48 * distance from -126 49 to -126 49.011096139863
49 * in 'SPHEROID["GRS_1980",6378137,298.257222101]'
50 * is 1234.000
51 */
52
53
54/* PG-exposed */
55Datum ellipsoid_in(PG_FUNCTION_ARGS);
56Datum ellipsoid_out(PG_FUNCTION_ARGS);
57Datum LWGEOM_length2d_ellipsoid(PG_FUNCTION_ARGS);
58Datum LWGEOM_length_ellipsoid_linestring(PG_FUNCTION_ARGS);
59Datum LWGEOM_distance_ellipsoid(PG_FUNCTION_ARGS);
60Datum LWGEOM_distance_sphere(PG_FUNCTION_ARGS);
61Datum geometry_distance_spheroid(PG_FUNCTION_ARGS);
62
63/* internal */
64double distance_sphere_method(double lat1, double long1,double lat2,double long2, SPHEROID *sphere);
65double distance_ellipse_calculation(double lat1, double long1, double lat2, double long2, SPHEROID *sphere);
66double distance_ellipse(double lat1, double long1, double lat2, double long2, SPHEROID *sphere);
67double deltaLongitude(double azimuth, double sigma, double tsm,SPHEROID *sphere);
68double mu2(double azimuth,SPHEROID *sphere);
69double bigA(double u2);
70double bigB(double u2);
71
72
73/*
74 * Use the WKT definition of an ellipsoid
75 * ie. SPHEROID["name",A,rf] or SPHEROID("name",A,rf)
76 * SPHEROID["GRS_1980",6378137,298.257222101]
77 * wkt says you can use "(" or "["
78 */
80Datum ellipsoid_in(PG_FUNCTION_ARGS)
81{
82 char *str = PG_GETARG_CSTRING(0);
83 SPHEROID *sphere = (SPHEROID *) palloc(sizeof(SPHEROID));
84 int nitems;
85 double rf;
86
87 memset(sphere,0, sizeof(SPHEROID));
88
89 if (strstr(str,"SPHEROID") != str )
90 {
91 elog(ERROR,"SPHEROID parser - doesn't start with SPHEROID");
92 pfree(sphere);
93 PG_RETURN_NULL();
94 }
95
96 nitems = sscanf(str,"SPHEROID[\"%19[^\"]\",%lf,%lf]",
97 sphere->name, &sphere->a, &rf);
98
99 if (nitems != 3)
100 nitems = sscanf(str,"SPHEROID(\"%19[^\"]\",%lf,%lf)",
101 sphere->name, &sphere->a, &rf);
102
103 if (nitems != 3)
104 {
105 elog(ERROR,"SPHEROID parser - couldn't parse the spheroid");
106 pfree(sphere);
107 PG_RETURN_NULL();
108 }
109
110 sphere->f = 1.0/rf;
111 sphere->b = sphere->a - (1.0/rf)*sphere->a;
112 sphere->e_sq = ((sphere->a*sphere->a) - (sphere->b*sphere->b)) /
113 (sphere->a*sphere->a);
114 sphere->e = sqrt(sphere->e_sq);
115
116 PG_RETURN_POINTER(sphere);
117
118}
119
121Datum ellipsoid_out(PG_FUNCTION_ARGS)
122{
123 SPHEROID *sphere = (SPHEROID *) PG_GETARG_POINTER(0);
124 char *result;
125 size_t sz = MAX_DIGS_DOUBLE + MAX_DIGS_DOUBLE + 20 + 9 + 2;
126 result = palloc(sz);
127
128 snprintf(result, sz, "SPHEROID(\"%s\",%.15g,%.15g)",
129 sphere->name, sphere->a, 1.0/sphere->f);
130
131 PG_RETURN_CSTRING(result);
132}
133
134/*
135 * support function for distance calc
136 * code is taken from David Skea
137 * Geographic Data BC, Province of British Columbia, Canada.
138 * Thanks to GDBC and David Skea for allowing this to be
139 * put in PostGIS.
140 */
141double
142deltaLongitude(double azimuth, double sigma, double tsm,SPHEROID *sphere)
143{
144 /* compute the expansion C */
145 double das,C;
146 double ctsm,DL;
147
148 das = cos(azimuth)*cos(azimuth);
149 C = sphere->f/16.0 * das * (4.0 + sphere->f * (4.0 - 3.0 * das));
150
151 /* compute the difference in longitude */
152 ctsm = cos(tsm);
153 DL = ctsm + C * cos(sigma) * (-1.0 + 2.0 * ctsm*ctsm);
154 DL = sigma + C * sin(sigma) * DL;
155 return (1.0 - C) * sphere->f * sin(azimuth) * DL;
156}
157
158
159/*
160 * support function for distance calc
161 * code is taken from David Skea
162 * Geographic Data BC, Province of British Columbia, Canada.
163 * Thanks to GDBC and David Skea for allowing this to be
164 * put in PostGIS.
165 */
166double
167mu2(double azimuth,SPHEROID *sphere)
168{
169 double e2;
170
171 e2 = sqrt(sphere->a*sphere->a-sphere->b*sphere->b)/sphere->b;
172 return cos(azimuth)*cos(azimuth) * e2*e2;
173}
174
175
176/*
177 * Support function for distance calc
178 * code is taken from David Skea
179 * Geographic Data BC, Province of British Columbia, Canada.
180 * Thanks to GDBC and David Skea for allowing this to be
181 * put in PostGIS.
182 */
183double
184bigA(double u2)
185{
186 return 1.0 + u2/256.0 * (64.0 + u2 * (-12.0 + 5.0 * u2));
187}
188
189
190/*
191 * Support function for distance calc
192 * code is taken from David Skea
193 * Geographic Data BC, Province of British Columbia, Canada.
194 * Thanks to GDBC and David Skea for allowing this to be
195 * put in PostGIS.
196 */
197double
198bigB(double u2)
199{
200 return u2/512.0 * (128.0 + u2 * (-64.0 + 37.0 * u2));
201}
202
203
204
205double
206distance_ellipse(double lat1, double long1,
207 double lat2, double long2, SPHEROID *sphere)
208{
209 double result = 0;
210#if POSTGIS_DEBUG_LEVEL >= 4
211 double result2 = 0;
212#endif
213
214 if ( (lat1==lat2) && (long1 == long2) )
215 {
216 return 0.0; /* same point, therefore zero distance */
217 }
218
219 result = distance_ellipse_calculation(lat1,long1,lat2,long2,sphere);
220
221#if POSTGIS_DEBUG_LEVEL >= 4
222 result2 = distance_sphere_method(lat1, long1,lat2,long2, sphere);
223
224 POSTGIS_DEBUGF(4, "delta = %lf, skae says: %.15lf,2 circle says: %.15lf",
225 (result2-result),result,result2);
226 POSTGIS_DEBUGF(4, "2 circle says: %.15lf",result2);
227#endif
228
229 if (result != result) /* NaN check
230 * (x==x for all x except NaN by IEEE definition)
231 */
232 {
233 result = distance_sphere_method(lat1, long1,
234 lat2,long2, sphere);
235 }
236
237 return result;
238}
239
240/*
241 * Given 2 lat/longs and ellipse, find the distance
242 * note original r = 1st, s=2nd location
243 */
244double
245distance_ellipse_calculation(double lat1, double long1,
246 double lat2, double long2, SPHEROID *sphere)
247{
248 /*
249 * Code is taken from David Skea
250 * Geographic Data BC, Province of British Columbia, Canada.
251 * Thanks to GDBC and David Skea for allowing this to be
252 * put in PostGIS.
253 */
254
255 double L1,L2,sinU1,sinU2,cosU1,cosU2;
256 double dl,dl1,dl2,dl3,cosdl1,sindl1;
257 double cosSigma,sigma,azimuthEQ,tsm;
258 double u2,A,B;
259 double dsigma;
260
261 double TEMP;
262
263 int iterations;
264
265
266 L1 = atan((1.0 - sphere->f ) * tan( lat1) );
267 L2 = atan((1.0 - sphere->f ) * tan( lat2) );
268 sinU1 = sin(L1);
269 sinU2 = sin(L2);
270 cosU1 = cos(L1);
271 cosU2 = cos(L2);
272
273 dl = long2- long1;
274 dl1 = dl;
275 cosdl1 = cos(dl);
276 sindl1 = sin(dl);
277 iterations = 0;
278 do
279 {
280 cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosdl1;
281 sigma = acos(cosSigma);
282 azimuthEQ = asin((cosU1 * cosU2 * sindl1)/sin(sigma));
283
284 /*
285 * Patch from Patrica Tozer to handle minor
286 * mathematical stability problem
287 */
288 TEMP = cosSigma - (2.0 * sinU1 * sinU2)/
289 (cos(azimuthEQ)*cos(azimuthEQ));
290 if (TEMP > 1)
291 {
292 TEMP = 1;
293 }
294 else if (TEMP < -1)
295 {
296 TEMP = -1;
297 }
298 tsm = acos(TEMP);
299
300
301 /* (old code?)
302 tsm = acos(cosSigma - (2.0 * sinU1 * sinU2)/(cos(azimuthEQ)*cos(azimuthEQ)));
303 */
304
305 dl2 = deltaLongitude(azimuthEQ, sigma, tsm,sphere);
306 dl3 = dl1 - (dl + dl2);
307 dl1 = dl + dl2;
308 cosdl1 = cos(dl1);
309 sindl1 = sin(dl1);
310 iterations++;
311 }
312 while ( (iterations<999) && (fabs(dl3) > 1.0e-032));
313
314 /* compute expansions A and B */
315 u2 = mu2(azimuthEQ,sphere);
316 A = bigA(u2);
317 B = bigB(u2);
318
319 /* compute length of geodesic */
320 dsigma = B * sin(sigma) * (cos(tsm) +
321 (B*cosSigma*(-1.0 + 2.0 * (cos(tsm)*cos(tsm))))/4.0);
322 return sphere->b * (A * (sigma - dsigma));
323}
324
325
326/*
327 * Find the "length of a geometry"
328 * length2d_spheroid(point, sphere) = 0
329 * length2d_spheroid(line, sphere) = length of line
330 * length2d_spheroid(polygon, sphere) = 0
331 * -- could make sense to return sum(ring perimeter)
332 * uses ellipsoidal math to find the distance
333 * x's are longitude, and y's are latitude - both in decimal degrees
334 */
336Datum LWGEOM_length2d_ellipsoid(PG_FUNCTION_ARGS)
337{
338 GSERIALIZED *geom = PG_GETARG_GSERIALIZED_P(0);
339 SPHEROID *sphere = (SPHEROID *) PG_GETARG_POINTER(1);
340 LWGEOM *lwgeom = lwgeom_from_gserialized(geom);
341 double dist = lwgeom_length_spheroid(lwgeom, sphere);
342 lwgeom_free(lwgeom);
343 PG_FREE_IF_COPY(geom, 0);
344 PG_RETURN_FLOAT8(dist);
345}
346
347
348/*
349 * Find the "length of a geometry"
350 *
351 * length2d_spheroid(point, sphere) = 0
352 * length2d_spheroid(line, sphere) = length of line
353 * length2d_spheroid(polygon, sphere) = 0
354 * -- could make sense to return sum(ring perimeter)
355 * uses ellipsoidal math to find the distance
356 * x's are longitude, and y's are latitude - both in decimal degrees
357 */
360{
361 GSERIALIZED *geom = PG_GETARG_GSERIALIZED_P(0);
362 LWGEOM *lwgeom = lwgeom_from_gserialized(geom);
363 SPHEROID *sphere = (SPHEROID *) PG_GETARG_POINTER(1);
364 double length = 0.0;
365
366 /* EMPTY things have no length */
367 if ( lwgeom_is_empty(lwgeom) )
368 {
369 lwgeom_free(lwgeom);
370 PG_RETURN_FLOAT8(0.0);
371 }
372
373 length = lwgeom_length_spheroid(lwgeom, sphere);
374 lwgeom_free(lwgeom);
375 PG_FREE_IF_COPY(geom, 0);
376
377 /* Something went wrong... */
378 if ( length < 0.0 )
379 {
380 elog(ERROR, "lwgeom_length_spheroid returned length < 0.0");
381 PG_RETURN_NULL();
382 }
383
384 /* Clean up */
385 PG_RETURN_FLOAT8(length);
386}
387
388/*
389 * For some lat/long points, the above method doesn't calculate the distance very well.
390 * Typically this is for two lat/long points that are very very close together (<10cm).
391 * This gets worse closer to the equator.
392 *
393 * This method works very well for very close together points, not so well if they're
394 * far away (>1km).
395 *
396 * METHOD:
397 * We create two circles (with Radius R and Radius S) and use these to calculate the distance.
398 *
399 * The first (R) is basically a (north-south) line of longitude.
400 * Its radius is approximated by looking at the ellipse. Near the equator R = 'a' (earth's major axis)
401 * near the pole R = 'b' (earth's minor axis).
402 *
403 * The second (S) is basically a (east-west) line of latitude.
404 * Its radius runs from 'a' (major axis) at the equator, and near 0 at the poles.
405 *
406 *
407 * North pole
408 * *
409 * *
410 * *\--S--
411 * * R +
412 * * \ +
413 * * A\ +
414 * * ------ \ Equator/centre of earth
415 * *
416 * *
417 * *
418 * *
419 * *
420 * *
421 * South pole
422 * (side view of earth)
423 *
424 * Angle A is lat1
425 * R is the distance from the centre of the earth to the lat1/long1 point on the surface
426 * of the Earth.
427 * S is the circle-of-latitude. Its calculated from the right triangle defined by
428 * the angle (90-A), and the hypotenuse R.
429 *
430 *
431 *
432 * Once R and S have been calculated, the actual distance between the two points can be
433 * calculated.
434 *
435 * We dissolve the vector from lat1,long1 to lat2,long2 into its X and Y components (called DeltaX,DeltaY).
436 * The actual distance that these angle-based measurements represent is taken from the two
437 * circles we just calculated; R (for deltaY) and S (for deltaX).
438 *
439 * (if deltaX is 1 degrees, then that distance represents 1/360 of a circle of radius S.)
440 *
441 *
442 * Parts taken from PROJ - geodetic_to_geocentric() (for calculating Rn)
443 *
444 * remember that lat1/long1/lat2/long2 are coming in a *RADIANS* not degrees.
445 *
446 * By Patricia Tozer and Dave Blasby
447 *
448 * This is also called the "curvature method".
449 */
450
451double distance_sphere_method(double lat1, double long1,double lat2,double long2, SPHEROID *sphere)
452{
453 double R,S,X,Y,deltaX,deltaY;
454
455 double distance = 0.0;
456 double sin_lat = sin(lat1);
457 double sin2_lat = sin_lat * sin_lat;
458
459 double Geocent_a = sphere->a;
460 double Geocent_e2 = sphere->e_sq;
461
462 R = Geocent_a / (sqrt(1.0e0 - Geocent_e2 * sin2_lat));
463 /* 90 - lat1, but in radians */
464 S = R * sin(M_PI_2 - lat1) ;
465
466 deltaX = long2 - long1; /* in rads */
467 deltaY = lat2 - lat1; /* in rads */
468
469 /* think: a % of 2*pi*S */
470 X = deltaX/(2.0*M_PI) * 2 * M_PI * S;
471 Y = deltaY/(2.0*M_PI) * 2 * M_PI * R;
472
473 distance = sqrt((X * X + Y * Y));
474
475 return distance;
476}
477
478/*
479 * distance (geometry,geometry, sphere)
480 */
482Datum geometry_distance_spheroid(PG_FUNCTION_ARGS)
483{
484 GSERIALIZED *geom1 = PG_GETARG_GSERIALIZED_P(0);
485 GSERIALIZED *geom2 = PG_GETARG_GSERIALIZED_P(1);
486 SPHEROID *sphere = (SPHEROID *)PG_GETARG_POINTER(2);
487 int type1 = gserialized_get_type(geom1);
488 int type2 = gserialized_get_type(geom2);
489 bool use_spheroid = PG_GETARG_BOOL(3);
490 LWGEOM *lwgeom1, *lwgeom2;
491 double distance;
492 gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
493
494 /* Calculate some other parameters on the spheroid */
495 spheroid_init(sphere, sphere->a, sphere->b);
496
497 /* Catch sphere special case and re-jig spheroid appropriately */
498 if ( ! use_spheroid )
499 {
500 sphere->a = sphere->b = sphere->radius;
501 }
502
503 if ( ! ( type1 == POINTTYPE || type1 == LINETYPE || type1 == POLYGONTYPE ||
504 type1 == MULTIPOINTTYPE || type1 == MULTILINETYPE || type1 == MULTIPOLYGONTYPE ))
505 {
506 elog(ERROR, "geometry_distance_spheroid: Only point/line/polygon supported.\n");
507 PG_RETURN_NULL();
508 }
509
510 if ( ! ( type2 == POINTTYPE || type2 == LINETYPE || type2 == POLYGONTYPE ||
511 type2 == MULTIPOINTTYPE || type2 == MULTILINETYPE || type2 == MULTIPOLYGONTYPE ))
512 {
513 elog(ERROR, "geometry_distance_spheroid: Only point/line/polygon supported.\n");
514 PG_RETURN_NULL();
515 }
516
517 /* Get #LWGEOM structures */
518 lwgeom1 = lwgeom_from_gserialized(geom1);
519 lwgeom2 = lwgeom_from_gserialized(geom2);
520
521 /* We are going to be calculating geodetic distances */
524 lwgeom_refresh_bbox(lwgeom1);
525 lwgeom_refresh_bbox(lwgeom2);
526
527 distance = lwgeom_distance_spheroid(lwgeom1, lwgeom2, sphere, 0.0);
528
529 PG_RETURN_FLOAT8(distance);
530
531}
532
534Datum LWGEOM_distance_ellipsoid(PG_FUNCTION_ARGS)
535{
536 SPHEROID s;
537
538 /* No spheroid provided */
539 if (PG_NARGS() == 2) {
540 /* Init to WGS84 */
541 spheroid_init(&s, 6378137.0, 6356752.314245179498);
542 PG_RETURN_DATUM(DirectFunctionCall4(geometry_distance_spheroid,
543 PG_GETARG_DATUM(0),
544 PG_GETARG_DATUM(1),
545 PointerGetDatum(&s),
546 BoolGetDatum(true)));
547 }
548
549 PG_RETURN_DATUM(DirectFunctionCall4(geometry_distance_spheroid,
550 PG_GETARG_DATUM(0),
551 PG_GETARG_DATUM(1),
552 PG_GETARG_DATUM(2),
553 BoolGetDatum(true)));
554}
555
557Datum LWGEOM_distance_sphere(PG_FUNCTION_ARGS)
558{
559 SPHEROID s;
560 /* Init to WGS84 */
561 spheroid_init(&s, 6378137.0, 6356752.314245179498);
562
563 if (PG_NARGS() == 3) {
564 s.radius = PG_GETARG_FLOAT8(2);
565 }
566 s.a = s.b = s.radius;
567
568 PG_RETURN_DATUM(DirectFunctionCall4(geometry_distance_spheroid,
569 PG_GETARG_DATUM(0), PG_GETARG_DATUM(1), PointerGetDatum(&s), BoolGetDatum(false)));
570}
571
char * s
Definition cu_in_wkt.c:23
char result[OUT_DOUBLE_BUFFER_SIZE]
Definition cu_print.c:267
void gserialized_error_if_srid_mismatch(const GSERIALIZED *g1, const GSERIALIZED *g2, const char *funcname)
LWGEOM * lwgeom_from_gserialized(const GSERIALIZED *g)
Allocate a new LWGEOM from a GSERIALIZED.
uint32_t gserialized_get_type(const GSERIALIZED *g)
Extract the geometry type from the serialized form (it hides in the anonymous data area,...
void lwgeom_refresh_bbox(LWGEOM *lwgeom)
Drop current bbox and calculate a fresh one.
Definition lwgeom.c:735
void lwgeom_set_geodetic(LWGEOM *geom, int value)
Set the FLAGS geodetic bit on geometry an all sub-geometries and pointlists.
Definition lwgeom.c:992
double lwgeom_distance_spheroid(const LWGEOM *lwgeom1, const LWGEOM *lwgeom2, const SPHEROID *spheroid, double tolerance)
Calculate the geodetic distance from lwgeom1 to lwgeom2 on the spheroid.
void lwgeom_free(LWGEOM *geom)
Definition lwgeom.c:1246
#define MULTILINETYPE
Definition liblwgeom.h:106
#define LINETYPE
Definition liblwgeom.h:103
#define MULTIPOINTTYPE
Definition liblwgeom.h:105
#define POINTTYPE
LWTYPE numbers, used internally by PostGIS.
Definition liblwgeom.h:102
#define MULTIPOLYGONTYPE
Definition liblwgeom.h:107
#define POLYGONTYPE
Definition liblwgeom.h:104
void spheroid_init(SPHEROID *s, double a, double b)
Initialize a spheroid object for use in geodetic functions.
Definition lwspheroid.c:39
double lwgeom_length_spheroid(const LWGEOM *geom, const SPHEROID *s)
Calculate the geodetic length of a lwgeom on the unit sphere.
#define LW_TRUE
Return types for functions with status returns.
Definition liblwgeom.h:93
This library is the generic geometry handling section of PostGIS.
#define str(s)
double bigB(double u2)
#define MAX_DIGS_DOUBLE
Datum LWGEOM_distance_ellipsoid(PG_FUNCTION_ARGS)
double distance_ellipse_calculation(double lat1, double long1, double lat2, double long2, SPHEROID *sphere)
double mu2(double azimuth, SPHEROID *sphere)
double distance_sphere_method(double lat1, double long1, double lat2, double long2, SPHEROID *sphere)
double bigA(double u2)
Datum LWGEOM_distance_sphere(PG_FUNCTION_ARGS)
Datum LWGEOM_length_ellipsoid_linestring(PG_FUNCTION_ARGS)
PG_FUNCTION_INFO_V1(ellipsoid_in)
double distance_ellipse(double lat1, double long1, double lat2, double long2, SPHEROID *sphere)
Datum ellipsoid_in(PG_FUNCTION_ARGS)
Datum ellipsoid_out(PG_FUNCTION_ARGS)
Datum geometry_distance_spheroid(PG_FUNCTION_ARGS)
Datum LWGEOM_length2d_ellipsoid(PG_FUNCTION_ARGS)
double deltaLongitude(double azimuth, double sigma, double tsm, SPHEROID *sphere)
static int lwgeom_is_empty(const LWGEOM *geom)
Return true or false depending on whether a geometry is an "empty" geometry (no vertices members)
Definition lwinline.h:199
static double distance(double x1, double y1, double x2, double y2)
Definition lwtree.c:1032
double e_sq
Definition liblwgeom.h:379
double e
Definition liblwgeom.h:378
double radius
Definition liblwgeom.h:380
double a
Definition liblwgeom.h:375
double b
Definition liblwgeom.h:376
double f
Definition liblwgeom.h:377
char name[20]
Definition liblwgeom.h:381