/***********************************************************************
* --
strk@keybit.net;
***********************************************************************/
/***********************************************************************
* Interpolate a point along a line, useful for Geocoding applications
* SELECT line_interpolate_point( 'LINESTRING( 0 0, 2 2'::geometry, .5 )
* returns POINT( 1 1 ).
* Works in 2d space only.
*
* Initially written by:
jsunday@rochgrp.com
* Ported to LWGEOM by:
strk@refractions.net
***********************************************************************/
Datum LWGEOM_line_interpolate_point(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(LWGEOM_line_interpolate_point);
Datum LWGEOM_line_interpolate_point(PG_FUNCTION_ARGS)
{
PG_LWGEOM *geom = (PG_LWGEOM *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
double distance = PG_GETARG_FLOAT8(1);
LWLINE *line;
LWPOINT *point;
POINTARRAY *ipa, *opa;
POINT4D pt;
int nsegs, i;
double length, slength, tlength;
if ( distance < 0 || distance > 1 )
{
elog(ERROR,"line_interpolate_point: 2nd arg isnt within [0,1]");
PG_RETURN_NULL();
}
if ( pglwgeom_get_type(geom) != LINETYPE )
{
elog(ERROR,"line_interpolate_point: 1st arg isnt a line");
PG_RETURN_NULL();
}
line = lwgeom_as_lwline(pglwgeom_deserialize(geom));
ipa = line->points;
/* If distance is one of the two extremes, return the point on that
* end rather than doing any expensive computations
*/
if ( distance == 0.0 || distance == 1.0 )
{
if ( distance == 0.0 )
getPoint4d_p(ipa, 0, &pt);
else
getPoint4d_p(ipa, ipa->npoints-1, &pt);
opa = ptarray_construct_reference_data(FLAGS_GET_Z(line->flags), FLAGS_GET_M(line->flags), 1, (uchar*)&pt);
point = lwpoint_construct(line->srid, NULL, opa);
PG_RETURN_POINTER(pglwgeom_serialize(lwpoint_as_lwgeom(point)));
}
/* Interpolate a point on the line */
nsegs = ipa->npoints - 1;
length = ptarray_length_2d(ipa);
tlength = 0;
for ( i = 0; i < nsegs; i++ )
{
POINT4D p1, p2;
POINT4D *p1ptr=&p1, *p2ptr=&p2; /* don't break
* strict-aliasing rules
*/
getPoint4d_p(ipa, i, &p1);
getPoint4d_p(ipa, i+1, &p2);
/* Find the relative length of this segment */
slength = distance2d_pt_pt((POINT2D*)p1ptr, (POINT2D*)p2ptr)/length;
/* If our target distance is before the total length we've seen
* so far. create a new point some distance down the current
* segment.
*/
if ( distance < tlength + slength )
{
double dseg = (distance - tlength) / slength;
interpolate_point4d(&p1, &p2, &pt, dseg);
opa = ptarray_construct_reference_data(FLAGS_GET_Z(line->flags), FLAGS_GET_M(line->flags), 1, (uchar*)&pt);
point = lwpoint_construct(line->srid, NULL, opa);
PG_RETURN_POINTER(pglwgeom_serialize(lwpoint_as_lwgeom(point)));
}
tlength += slength;
}
/* Return the last point on the line. This shouldn't happen, but
* could if there's some floating point rounding errors. */
getPoint4d_p(ipa, ipa->npoints-1, &pt);
opa = ptarray_construct_reference_data(FLAGS_GET_Z(line->flags), FLAGS_GET_M(line->flags), 1, (uchar*)&pt);
point = lwpoint_construct(line->srid, NULL, opa);
PG_RETURN_POINTER(pglwgeom_serialize(lwpoint_as_lwgeom(point)));
}
/***********************************************************************
* --
jsunday@rochgrp.com;
***********************************************************************/