[postgis-users] Spherical expand

Rob Young bobbotron at gmail.com
Fri Mar 2 00:54:39 PST 2007


Hey guys,

A while ago I mentioned that I had written a postgres function that
acted as a expand function that worked with lat/long data.  I thought
I'd pass it on to the list, in case other people might find it useful.

Included are two functions: expand_sphere and distance_from.
distance_from is a helper function for expand_sphere.  expand_sphere
creates a polygon around the lat and long coordinates passed to it,
the size of which is determined by the radius value you give it (in
metres.)  It uses a spherical earth approximation for the underlying
math.  Currently it doesn't handle wraparound.  So, it could in theory
create a box that bounds outside of the normal lat/long bounds.  (I'm
on vacation, I'll get around to writing the boundery cases when I get
back home.  Also, more commenting then...)  :)

Cheers!

Rob Young

Ps: hopefully the attached code is preserved in some useable form..  :)


---- See => http://williams.best.vwh.net/avform.htm#LL
----      lat=asin(sin(lat1)*cos(d)+cos(lat1)*sin(d)*cos(tc))
----      IF (cos(lat)=0)
----         lon=lon1      // endpoint a pole
----      ELSE
----         lon=mod(lon1-asin(sin(tc)*sin(d)/cos(lat))+pi,2*pi)-pi
----      ENDIF

CREATE OR REPLACE FUNCTION distance_from( IN lat real, -- Latitude in degrees
					IN long real, -- Longitude in degrees
					IN course real, -- course in degrees
					IN dist real ) -- distance in metres
RETURNS geometry AS $$
DECLARE
	radius_km real; -- CONSTANT radius of the earth
	latRad real;
	longRad real;
	courseRad real;
	distRad real;

	newLatRad real;
	newLongRad real;

	newLat real;
	newLong real;
BEGIN
	radius_km = 6378.137; -- radius of the earth (ROUGHLY, CHECK!)

	latRad := ( pi() / 180.0 ) * lat;
	longRad := ( pi() / 180.0 ) * long;
	courseRad := ( pi() / 180.0 ) * course;
	distRad := ( dist / 1000.0 ) / radius_km;

	newLatRad := asin(sin(latRad)*cos(distRad)+cos(latRad)*sin(distRad)*cos(courseRad));
	newLongRad := 0.0;


	IF cos( newLatRad ) = 0 THEN
		newLongRad = longRad;
	ELSE
		--newLongRad =
mod(longRad-asin(sin(courseRad)*sin(distRad)/cos(lat))+pi(), 2 * pi())
- pi();
		newLongRad = cast(longRad-asin(sin(courseRad)*sin(distRad)/cos(latRad))+pi()
as numeric ) % cast( 2 * pi() as numeric ) - pi();
	END IF;

	newLat := ( 180.0 / pi() ) * newLatRad;
	newLong := ( 180.0 / pi() ) * newLongRad;

	RETURN SetSRID( MakePoint( newLong, newLat ), 4326 );
END;
$$ LANGUAGE plpgsql IMMUTABLE;

CREATE OR REPLACE FUNCTION expand_sphere( IN lat real, -- Latitude in degrees
					IN long real, -- Longitude in degrees
					IN dist real ) -- distance in metres
RETURNS geometry AS $$
DECLARE
	a geometry;
	b geometry;
	c geometry;
	d geometry;

	llPoint geometry;
	urPoint geometry;

	sphereBox box2D;
BEGIN
	a := distance_from( lat, long, 0.0, dist );
	b := distance_from( lat, long, 90.0, dist );
	c := distance_from( lat, long, 180.0, dist );
	d := distance_from( lat, long, 270.0, dist );

	llPoint := MakePoint( X( d ), Y( c ) );
	urPoint := MakePoint( X( b ), Y( a ) );

	sphereBox := MakeBox2D( llPoint, urPoint );

	RETURN SetSRID( sphereBox, 4326 );
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-------------- next part --------------
---- See => http://williams.best.vwh.net/avform.htm#LL
----      lat=asin(sin(lat1)*cos(d)+cos(lat1)*sin(d)*cos(tc))
----      IF (cos(lat)=0)
----         lon=lon1      // endpoint a pole
----      ELSE
----         lon=mod(lon1-asin(sin(tc)*sin(d)/cos(lat))+pi,2*pi)-pi
----      ENDIF

CREATE OR REPLACE FUNCTION distance_from( IN lat real, -- Latitude in degrees
					IN long real, -- Longitude in degrees
					IN course real, -- course in degrees
					IN dist real ) -- distance in metres
RETURNS geometry AS $$
DECLARE 
	radius_km real; -- CONSTANT radius of the earth
	latRad real;
	longRad real;
	courseRad real;
	distRad real;

	newLatRad real;
	newLongRad real;

	newLat real;
	newLong real;
BEGIN 
	radius_km = 6378.137; -- radius of the earth (ROUGHLY, CHECK!)

	latRad := ( pi() / 180.0 ) * lat;
	longRad := ( pi() / 180.0 ) * long;
	courseRad := ( pi() / 180.0 ) * course;
	distRad := ( dist / 1000.0 ) / radius_km;

	----      lat=asin(sin(lat1)*cos(d)+cos(lat1)*sin(d)*cos(tc))
	newLatRad := asin(sin(latRad)*cos(distRad)+cos(latRad)*sin(distRad)*cos(courseRad));
	newLongRad := 0.0;

----      IF (cos(lat)=0)
----         lon=lon1      // endpoint a pole
----      ELSE
----         lon=mod(lon1-asin(sin(tc)*sin(d)/cos(lat))+pi,2*pi)-pi
----      ENDIF

	IF cos( newLatRad ) = 0 THEN
		newLongRad = longRad;
	ELSE
		--newLongRad = mod(longRad-asin(sin(courseRad)*sin(distRad)/cos(lat))+pi(), 2 * pi()) - pi();
		newLongRad = cast(longRad-asin(sin(courseRad)*sin(distRad)/cos(latRad))+pi() as numeric ) % cast( 2 * pi() as numeric ) - pi();
	END IF;

	newLat := ( 180.0 / pi() ) * newLatRad;
	newLong := ( 180.0 / pi() ) * newLongRad;

	RETURN SetSRID( MakePoint( newLong, newLat ), 4326 );
END;
$$ LANGUAGE plpgsql IMMUTABLE;

CREATE OR REPLACE FUNCTION expand_sphere( IN lat real, -- Latitude in degrees
					IN long real, -- Longitude in degrees
					IN dist real ) -- distance in metres
RETURNS geometry AS $$
DECLARE 
	a geometry;
	b geometry;
	c geometry;
	d geometry;

	llPoint geometry;
	urPoint geometry;

	sphereBox box2D;
BEGIN 
	a := distance_from( lat, long, 0.0, dist );
	b := distance_from( lat, long, 90.0, dist );
	c := distance_from( lat, long, 180.0, dist );
	d := distance_from( lat, long, 270.0, dist );

	llPoint := MakePoint( X( d ), Y( c ) );
	urPoint := MakePoint( X( b ), Y( a ) );

	sphereBox := MakeBox2D( llPoint, urPoint );

	RETURN SetSRID( sphereBox, 4326 );
END;
$$ LANGUAGE plpgsql IMMUTABLE;


More information about the postgis-users mailing list