PostGIS  3.0.6dev-r@@SVN_REVISION@@
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 */
55 Datum ellipsoid_in(PG_FUNCTION_ARGS);
56 Datum ellipsoid_out(PG_FUNCTION_ARGS);
57 Datum LWGEOM_length2d_ellipsoid(PG_FUNCTION_ARGS);
58 Datum LWGEOM_length_ellipsoid_linestring(PG_FUNCTION_ARGS);
59 Datum LWGEOM_distance_ellipsoid(PG_FUNCTION_ARGS);
60 Datum LWGEOM_distance_sphere(PG_FUNCTION_ARGS);
61 Datum geometry_distance_spheroid(PG_FUNCTION_ARGS);
62 
63 /* internal */
64 double distance_sphere_method(double lat1, double long1,double lat2,double long2, SPHEROID *sphere);
65 double distance_ellipse_calculation(double lat1, double long1, double lat2, double long2, SPHEROID *sphere);
66 double distance_ellipse(double lat1, double long1, double lat2, double long2, SPHEROID *sphere);
67 double deltaLongitude(double azimuth, double sigma, double tsm,SPHEROID *sphere);
68 double mu2(double azimuth,SPHEROID *sphere);
69 double bigA(double u2);
70 double 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  */
80 Datum 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==0)
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 - couldnt 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 
121 Datum 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  */
141 double
142 deltaLongitude(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  */
166 double
167 mu2(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  */
183 double
184 bigA(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  */
197 double
198 bigB(double u2)
199 {
200  return u2/512.0 * (128.0 + u2 * (-64.0 + 37.0 * u2));
201 }
202 
203 
204 
205 double
206 distance_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  */
244 double
245 distance_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  */
336 Datum 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  */
359 Datum LWGEOM_length_ellipsoid_linestring(PG_FUNCTION_ARGS)
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 comming in a *RADIANS* not degrees.
445  *
446  * By Patricia Tozer and Dave Blasby
447  *
448  * This is also called the "curvature method".
449  */
450 
451 double 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  */
482 Datum 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 */
522  lwgeom_set_geodetic(lwgeom1, LW_TRUE);
523  lwgeom_set_geodetic(lwgeom2, LW_TRUE);
524 
525  distance = lwgeom_distance_spheroid(lwgeom1, lwgeom2, sphere, 0.0);
526 
527  PG_RETURN_FLOAT8(distance);
528 
529 }
530 
532 Datum LWGEOM_distance_ellipsoid(PG_FUNCTION_ARGS)
533 {
534  PG_RETURN_DATUM(DirectFunctionCall4(geometry_distance_spheroid,
535  PG_GETARG_DATUM(0), PG_GETARG_DATUM(1), PG_GETARG_DATUM(2), BoolGetDatum(true)));
536 }
537 
539 Datum LWGEOM_distance_sphere(PG_FUNCTION_ARGS)
540 {
541  SPHEROID s;
542 
543  /* Init to WGS84 */
544  spheroid_init(&s, 6378137.0, 6356752.314245179498);
545  s.a = s.b = s.radius;
546 
547  PG_RETURN_DATUM(DirectFunctionCall4(geometry_distance_spheroid,
548  PG_GETARG_DATUM(0), PG_GETARG_DATUM(1), PointerGetDatum(&s), BoolGetDatum(false)));
549 }
550 
char * s
Definition: cu_in_wkt.c:23
void gserialized_error_if_srid_mismatch(const GSERIALIZED *g1, const GSERIALIZED *g2, const char *funcname)
Definition: gserialized.c:404
LWGEOM * lwgeom_from_gserialized(const GSERIALIZED *g)
Allocate a new LWGEOM from a GSERIALIZED.
Definition: gserialized.c:239
uint32_t gserialized_get_type(const GSERIALIZED *g)
Extract the geometry type from the serialized form (it hides in the anonymous data area,...
Definition: gserialized.c:89
void lwgeom_set_geodetic(LWGEOM *geom, int value)
Set the FLAGS geodetic bit on geometry an all sub-geometries and pointlists.
Definition: lwgeom.c:946
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.
Definition: lwgeodetic.c:2187
void lwgeom_free(LWGEOM *geom)
Definition: lwgeom.c:1138
#define MULTILINETYPE
Definition: liblwgeom.h:120
#define LINETYPE
Definition: liblwgeom.h:117
#define MULTIPOINTTYPE
Definition: liblwgeom.h:119
#define POINTTYPE
LWTYPE numbers, used internally by PostGIS.
Definition: liblwgeom.h:116
#define MULTIPOLYGONTYPE
Definition: liblwgeom.h:121
#define POLYGONTYPE
Definition: liblwgeom.h:118
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.
Definition: lwgeodetic.c:3297
#define LW_TRUE
Return types for functions with status returns.
Definition: liblwgeom.h:107
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:193
static double distance(double x1, double y1, double x2, double y2)
Definition: lwtree.c:1032
double e_sq
Definition: liblwgeom.h:365
double e
Definition: liblwgeom.h:364
double radius
Definition: liblwgeom.h:366
double a
Definition: liblwgeom.h:361
double b
Definition: liblwgeom.h:362
double f
Definition: liblwgeom.h:363
char name[20]
Definition: liblwgeom.h:367