Friday, December 9, 2011

How-To find the closest point of a MultiLineString from a Point in PostGIS

I had the same trouble before with MultiLineStrings, I realized that when a MultiLinestring can't be merged, all functions like ST_ClosestPoint and ST_Line_Locate_Point doesn't work.(you can know if a MultiLineString can't be merged using the ST_LineMerge function) I've made a pl/pgSQL function based in an old maillist but I added some performance tweaks, It only works with MultiLineStrings and LineStrings (but can be easily modified to work with Polygons). First it checks if the geometry only has 1 dimension, if it has, you can use the old ST_Line_Interpolate_Point and ST_Line_Locate_Point combination, if not, then you have to do the same for each LineString in the MultiLineString. Also I've added a ST_LineMerge for pre 1.5 compatibility :


CREATE OR REPLACE FUNCTION ST_MultiLine_Nearest_Point(amultiline geometry,apoint geometry)
  RETURNS geometry AS
$BODY$
DECLARE
    mindistance float8;
    adistance float8;
    nearestlinestring geometry;
    nearestpoint geometry;
    simplifiedline geometry;
    line geometry;
BEGIN
        simplifiedline:=ST_LineMerge(amultiline);
        IF ST_NumGeometries(simplifiedline) <= 1 THEN
            nearestpoint:=ST_Line_Interpolate_Point(simplifiedline, ST_Line_Locate_Point(simplifiedline,apoint) );
            RETURN nearestpoint;
      END IF;
--      *Change your mindistance according to your projection, it should be stupidly big*
        mindistance := 100000;
        FOR line IN SELECT (ST_Dump(simplifiedline)).geom as geom LOOP
                adistance:=ST_Distance(apoint,line);
            IF adistance < mindistance THEN
                mindistance:=adistance;
                nearestlinestring:=line;
            END IF;
        END LOOP;
        RETURN ST_Line_Interpolate_Point(nearestlinestring,ST_Line_Locate_Point(nearestlinestring,apoint));
    END;
    $BODY$
      LANGUAGE 'plpgsql' IMMUTABLE STRICT;