Commit 260b853a authored by Paul Ramsey's avatar Paul Ramsey
Browse files

Add in spheroid calculations for ST_Distance and ST_DWithin.


git-svn-id: http://svn.osgeo.org/postgis/trunk@4709 b70326c6-7e19-0410-871a-916f4a2858ee
parent 1a9f9da1
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -376,6 +376,12 @@ extern int lwgeom_calculate_gbox(const LWGEOM *lwgeom, GBOX *gbox);
*/
extern double lwgeom_distance_sphere(LWGEOM *lwgeom1, LWGEOM *lwgeom2, GBOX gbox1, GBOX gbox2, double tolerance);

/**
* Calculate the geodetic distance from lwgeom1 to lwgeom2 on the spheroid. 
* Pass in a major axis, minor axis and tolerance using the same units for each (meters, generally).
*/
extern double lwgeom_distance_spheroid(LWGEOM *lwgeom1, LWGEOM *lwgeom2, GBOX gbox1, GBOX gbox2, double a, double b, double tolerance);

/**
* Calculate the geodetic area of a lwgeom on the unit sphere. The result
* will have to by multiplied by the real radius^2 to get the real area.
+349 −1
Original line number Diff line number Diff line
@@ -100,3 +100,351 @@ double spheroid_distance(GEOGRAPHIC_POINT r, GEOGRAPHIC_POINT s, double a, doubl

	return distance;
}

double ptarray_distance_spheroid(POINTARRAY *pa1, POINTARRAY *pa2, double a, double b, double tolerance, int check_intersection)
{
	GEOGRAPHIC_EDGE e1, e2;
	GEOGRAPHIC_POINT g1, g2;
	GEOGRAPHIC_POINT nearest1, nearest2;
	POINT2D p;
	double radius = (2.0*a + b)/3.0;
	double distance;
	int i, j;
	
	/* Make result really big, so that everything will be smaller than it */
	distance = MAXFLOAT;
	
	/* Empty point arrays? Return negative */
	if ( pa1->npoints == 0 || pa1->npoints == 0 )
		return -1.0;
	
	/* Handle point/point case here */
	if ( pa1->npoints == 1 && pa2->npoints == 1 )
	{
		getPoint2d_p(pa1, 0, &p);
		geographic_point_init(p.x, p.y, &g1);
		getPoint2d_p(pa2, 0, &p);
		geographic_point_init(p.x, p.y, &g2);
		return spheroid_distance(g1, g2, a, b);
	}

	/* Handle point/line case here */
	if ( pa1->npoints == 1 || pa2->npoints == 1 )
	{
		/* Handle one/many case here */
		int i;
		POINTARRAY *pa_one, *pa_many;
		
		if( pa1->npoints == 1 )
		{
			pa_one = pa1;
			pa_many = pa2;
		}
		else
		{
			pa_one = pa2;
			pa_many = pa1;
		}
		
		/* Initialize our point */
		getPoint2d_p(pa_one, 0, &p);
		geographic_point_init(p.x, p.y, &g1);

		/* Initialize start of line */
		getPoint2d_p(pa_many, 0, &p);
		geographic_point_init(p.x, p.y, &(e1.start));

		/* Iterate through the edges in our line */
		for( i = 1; i < pa_many->npoints; i++ )
		{
			double d;
			getPoint2d_p(pa_many, i, &p);
			geographic_point_init(p.x, p.y, &(e1.end));
			d = radius * edge_distance_to_point(e1, g1, &g2);
			if( d < distance ) 
			{
				distance = d;
				nearest2 = g2;
			}
			if( d < tolerance ) 
			{
				double sd = spheroid_distance(g1, nearest2, a, b);
				if( sd < tolerance )
					return sd;
			}
			e1.start = e1.end;
		}
		return spheroid_distance(g1, nearest2, a, b);
	}

	/* Initialize start of line 1 */
	getPoint2d_p(pa1, 0, &p);
	geographic_point_init(p.x, p.y, &(e1.start));

	
	/* Handle line/line case */
	for( i = 1; i < pa1->npoints; i++ )
	{
		getPoint2d_p(pa1, i, &p);
		geographic_point_init(p.x, p.y, &(e1.end));

		/* Initialize start of line 2 */
		getPoint2d_p(pa2, 0, &p);
		geographic_point_init(p.x, p.y, &(e2.start));

		for( j = 1; j < pa2->npoints; j++ )
		{
			double d;
			GEOGRAPHIC_POINT g;

			getPoint2d_p(pa2, j, &p);
			geographic_point_init(p.x, p.y, &(e2.end));

			LWDEBUGF(4, "e1.start == GPOINT(%.6g %.6g) ", e1.start.lat, e1.start.lon);
			LWDEBUGF(4, "e1.end == GPOINT(%.6g %.6g) ", e1.end.lat, e1.end.lon);
			LWDEBUGF(4, "e2.start == GPOINT(%.6g %.6g) ", e2.start.lat, e2.start.lon);
			LWDEBUGF(4, "e2.end == GPOINT(%.6g %.6g) ", e2.end.lat, e2.end.lon);

			if ( check_intersection && edge_intersection(e1, e2, &g) )
			{
				LWDEBUG(4,"edge intersection! returning 0.0");
				return 0.0;
			}
			d = radius * edge_distance_to_edge(e1, e2, &g1, &g2);
			LWDEBUGF(4,"got edge_distance_to_edge %.8g", d);
			
			if( d < distance )
			{
				distance = d;
				nearest1 = g1;
				nearest2 = g2;
			}
			if( d < tolerance )
			{
				double sd = spheroid_distance(nearest1, nearest2, a, b);
				if( sd < tolerance )
					return sd;
			}
				
			/* Copy end to start to allow a new end value in next iteration */
			e2.start = e2.end;
		}
		
		/* Copy end to start to allow a new end value in next iteration */
		e1.start = e1.end;
		
	}
	
	return spheroid_distance(nearest1, nearest2, a, b);
}


double lwgeom_distance_spheroid(LWGEOM *lwgeom1, LWGEOM *lwgeom2, GBOX gbox1, GBOX gbox2, double a, double b, double tolerance)
{
	int type1, type2;
	int check_intersection = LW_FALSE;
	
	assert(lwgeom1);
	assert(lwgeom2);
	
	LWDEBUGF(4, "entered function, tolerance %.8g", tolerance);

	/* What's the distance to an empty geometry? We don't know. */
	if( lwgeom_is_empty(lwgeom1) || lwgeom_is_empty(lwgeom2) )
	{
		return 0.0;
	}
		
	type1 = TYPE_GETTYPE(lwgeom1->type);
	type2 = TYPE_GETTYPE(lwgeom2->type);
	
	
	/* If the boxes aren't disjoint, we have to check for edge intersections */
	if( gbox_overlaps(gbox1, gbox2) )
		check_intersection = LW_TRUE;
	
	/* Point/line combinations can all be handled with simple point array iterations */
	if( ( type1 == POINTTYPE || type1 == LINETYPE ) && 
	    ( type2 == POINTTYPE || type2 == LINETYPE ) )
	{
		POINTARRAY *pa1, *pa2;
		
		if( type1 == POINTTYPE ) 
			pa1 = ((LWPOINT*)lwgeom1)->point;
		else 
			pa1 = ((LWLINE*)lwgeom1)->points;
		
		if( type2 == POINTTYPE )
			pa2 = ((LWPOINT*)lwgeom2)->point;
		else
			pa2 = ((LWLINE*)lwgeom2)->points;
		
		return ptarray_distance_spheroid(pa1, pa2, a, b, tolerance, check_intersection);
	}
	
	/* Point/Polygon cases, if point-in-poly, return zero, else return distance. */
	if( ( type1 == POLYGONTYPE && type2 == POINTTYPE ) || 
	    ( type2 == POLYGONTYPE && type1 == POINTTYPE ) )
	{
		POINT2D p;
		LWPOLY *lwpoly;
		LWPOINT *lwpt;
		GBOX gbox;
		double distance = MAXFLOAT;
		int i;
		
		if( type1 == POINTTYPE )
		{
			lwpt = (LWPOINT*)lwgeom1;
			lwpoly = (LWPOLY*)lwgeom2;
			gbox = gbox2;
		}
		else
		{
			lwpt = (LWPOINT*)lwgeom2;
			lwpoly = (LWPOLY*)lwgeom1;
			gbox = gbox1;
		}
		getPoint2d_p(lwpt->point, 0, &p);

		/* Point in polygon implies zero distance */
		if( lwpoly_covers_point2d(lwpoly, gbox, p) )
			return 0.0;
		
		/* Not inside, so what's the actual distance? */
		for( i = 0; i < lwpoly->nrings; i++ )
		{
			double ring_distance = ptarray_distance_spheroid(lwpoly->rings[i], lwpt->point, a, b, tolerance, check_intersection);
			if( ring_distance < distance )
				distance = ring_distance;
			if( distance < tolerance )
				return distance;
		}
		return distance;
	}

	/* Line/polygon case, if start point-in-poly, return zero, else return distance. */
	if( ( type1 == POLYGONTYPE && type2 == LINETYPE ) || 
	    ( type2 == POLYGONTYPE && type1 == LINETYPE ) )
	{
		POINT2D p;
		LWPOLY *lwpoly;
		LWLINE *lwline;
		GBOX gbox;
		double distance = MAXFLOAT;
		int i;
		
		if( type1 == LINETYPE )
		{
			lwline = (LWLINE*)lwgeom1;
			lwpoly = (LWPOLY*)lwgeom2;
			gbox = gbox2;
		}
		else
		{
			lwline = (LWLINE*)lwgeom2;
			lwpoly = (LWPOLY*)lwgeom1;
			gbox = gbox1;
		}
		getPoint2d_p(lwline->points, 0, &p);

		LWDEBUG(4, "checking if a point of line is in polygon");

		/* Point in polygon implies zero distance */
		if( lwpoly_covers_point2d(lwpoly, gbox, p) )
			return 0.0;

		LWDEBUG(4, "checking ring distances");

		/* Not contained, so what's the actual distance? */
		for( i = 0; i < lwpoly->nrings; i++ )
		{
			double ring_distance = ptarray_distance_spheroid(lwpoly->rings[i], lwline->points, a, b, tolerance, check_intersection);
			LWDEBUGF(4, "ring[%d] ring_distance = %.8g", i, ring_distance);
			if( ring_distance < distance )
				distance = ring_distance;
			if( distance < tolerance )
				return distance;
		}
		LWDEBUGF(4, "all rings checked, returning distance = %.8g", distance);
		return distance;

	}

	/* Polygon/polygon case, if start point-in-poly, return zero, else return distance. */
	if( ( type1 == POLYGONTYPE && type2 == POLYGONTYPE ) || 
	    ( type2 == POLYGONTYPE && type1 == POLYGONTYPE ) )
	{
		POINT2D p;
		LWPOLY *lwpoly1 = (LWPOLY*)lwgeom1;
		LWPOLY *lwpoly2 = (LWPOLY*)lwgeom2;
		double distance = MAXFLOAT;
		int i, j;
		
		/* Point of 2 in polygon 1 implies zero distance */
		getPoint2d_p(lwpoly1->rings[0], 0, &p);
		if( lwpoly_covers_point2d(lwpoly2, gbox2, p) )
			return 0.0;

		/* Point of 1 in polygon 2 implies zero distance */
		getPoint2d_p(lwpoly2->rings[0], 0, &p);
		if( lwpoly_covers_point2d(lwpoly1, gbox1, p) )
			return 0.0;
		
		/* Not contained, so what's the actual distance? */
		for( i = 0; i < lwpoly1->nrings; i++ )
		{
			for( j = 0; j < lwpoly2->nrings; j++ )
			{
				double ring_distance = ptarray_distance_spheroid(lwpoly1->rings[i], lwpoly2->rings[j], a, b, tolerance, check_intersection);
				if( ring_distance < distance )
					distance = ring_distance;
				if( distance < tolerance )
					return distance;
			}
		}
		return distance;		
	}

	/* Recurse into collections */
	if( lwgeom_is_collection(type1) )
	{
		int i;
		double distance = MAXFLOAT;
		LWCOLLECTION *col = (LWCOLLECTION*)lwgeom1;

		for( i = 0; i < col->ngeoms; i++ )
		{
			double geom_distance = lwgeom_distance_spheroid(col->geoms[i], lwgeom2, gbox1, gbox2, a, b, tolerance);
			if( geom_distance < distance )
				distance = geom_distance;
			if( distance < tolerance )
				return distance;
		}
		return distance;
	}

	/* Recurse into collections */
	if( lwgeom_is_collection(type2) )
	{
		int i;
		double distance = MAXFLOAT;
		LWCOLLECTION *col = (LWCOLLECTION*)lwgeom2;

		for( i = 0; i < col->ngeoms; i++ )
		{
			double geom_distance = lwgeom_distance_spheroid(lwgeom1, col->geoms[i], gbox1, gbox2, a, b, tolerance);
			if( geom_distance < distance )
				distance = geom_distance;
			if( distance < tolerance )
				return distance;
		}
		return distance;
	}


	lwerror("arguments include unsupported geometry type (%s, %s)", lwgeom_typename(type1), lwgeom_typename(type1));
	return -1.0;
	
}
+1 −0
Original line number Diff line number Diff line
@@ -12,5 +12,6 @@
*/

double spheroid_distance(GEOGRAPHIC_POINT r, GEOGRAPHIC_POINT s, double a, double b);
double ptarray_distance_spheroid(POINTARRAY *pa1, POINTARRAY *pa2, double a, double b, double tolerance, int check_intersection);

+1 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@
#define WGS84_MINOR_AXIS 6356752.314245179498
#define WGS84_RADIUS ((2.0 * WGS84_MAJOR_AXIS + WGS84_MINOR_AXIS ) / 3.0)


/**********************************************************************
**  Useful functions for all GEOGRAPHY handlers. 
*/
+11 −3
Original line number Diff line number Diff line
@@ -435,15 +435,22 @@ CREATE OR REPLACE FUNCTION ST_AsGeoJson(int4, geography, int4, int4)

-- Stop calculation and return immediately once distance is less than tolerance
-- Availability: 1.5.0
CREATE OR REPLACE FUNCTION _ST_Distance(geography, geography, float8)
CREATE OR REPLACE FUNCTION _ST_Distance(geography, geography, float8, boolean)
	RETURNS float8
	AS 'MODULE_PATHNAME','geography_distance_sphere'
	LANGUAGE 'C' IMMUTABLE STRICT;

-- Availability: 1.5.0
CREATE OR REPLACE FUNCTION ST_Distance(geography, geography, boolean)
	RETURNS float8
	AS 'SELECT _ST_Distance($1, $2, 0.0, $3)'
	LANGUAGE 'SQL' IMMUTABLE STRICT;

-- Currently defaulting to spheroid calculations
-- Availability: 1.5.0
CREATE OR REPLACE FUNCTION ST_Distance(geography, geography)
	RETURNS float8
	AS 'SELECT _ST_Distance($1, $2, 0.0)'
	AS 'SELECT _ST_Distance($1, $2, 0.0, true)'
	LANGUAGE 'SQL' IMMUTABLE STRICT;

-- Only expands the bounding box, the actual geometry will remain unchanged, use with care.
@@ -453,10 +460,11 @@ CREATE OR REPLACE FUNCTION _ST_Expand(geography, float8)
	AS 'MODULE_PATHNAME','geography_expand'
	LANGUAGE 'C' IMMUTABLE STRICT;

-- Currently defaulting to spheroid calculations
-- Availability: 1.5.0
CREATE OR REPLACE FUNCTION ST_DWithin(geography, geography, float8)
	RETURNS boolean
	AS 'SELECT $1 && _ST_Expand($2,$3) AND $2 && _ST_Expand($1,$3) AND _ST_Distance($1, $2, $3) < $3'
	AS 'SELECT $1 && _ST_Expand($2,$3) AND $2 && _ST_Expand($1,$3) AND _ST_Distance($1, $2, $3, true) < $3'
	LANGUAGE 'SQL' IMMUTABLE;

-- Availability: 1.5.0
Loading