Using PostGIS 2.0.0 I'd like to find the point on a LINESTRING that is closest to a given point. The LINESTRING represents a great circle line (ie. geography type). ST_ClosestPoint appears to do exactly what I want, however, I find that the returned point does not lie on the great circle line:
WITH points AS (
SELECT ST_GeomFromEWKT('SRID=4326;POINT(41 12)') AS point,
ST_GeomFromEWKT('SRID=4326;LINESTRING(40 5,41 15)') AS border
)
SELECT ST_AsText(ST_ClosestPoint(border, point)), -- POINT(40.7029702970297 12.029702970297)
ST_DWithin(ST_ClosestPoint(border, point)::geography, border::geography, 700) -- false
FROM points;
It's actually over 700 metres from the line. I believe this happens because ST_ClosestPoint operates on a plane. How would I do the same thing on the spheroid (or at least on the sphere)?
I tried @MerseyViking's function (ported to plpgsql) and it mostly works very well, but in some cases returns a point that is not on the line. A simple example:
WITH points AS (
SELECT ST_GeomFromEWKT('SRID=4326;POINT(10 3)') AS point,
ST_GeomFromEWKT('SRID=4326;LINESTRING(10 5,10 15)') AS border
),
calc AS
(
SELECT ST_ClosestPoint(border, point)::geography AS plane_closest,
closest_point_to_line(border, point) AS sphere_closest
FROM points
)
SELECT ST_AsText(plane_closest) AS plane_closest, -- POINT(10 5)
ST_DWithin(plane_closest, border::geography, 1), -- true
ST_AsText(sphere_closest) AS sphere_closest, -- POINT(10 3)
ST_DWithin(sphere_closest, border::geography, 1), -- false
FROM points, calc;
Best Answer
Sadly there isn't a geographic version of
ST_ClosestPoint
, so you will have to write your own function. There are two ways of calculating the nearest point of a great circle: spherical trigonometry, or 3D vector algebra. Luckily for you I have just written such a function for the latter method; I've not attempted the former because my spherical trig is pretty poor.I've written a PL/Python function that you can add to your database, although you will need to install the Python libraries and PL/Python wrappers, which if you're using Linux should be simply to install the
postgresql-python-x.x
package (wherex.x
is the version of PostgreSQL you're using).This function isn't necessarily the most efficient, it includes no bounds or error checking, and it only uses a sphere, but it certainly works well enough with the data I've tried it with. Feel free to modify accordingly.
First I define my own simple long/lat type:
The function looks like this:
And giving it the data you provided gives me this:
If anyone has a spherical trig method, which I think will be slightly more efficient, then I'd love to see it.