Add ST_OffsetCurve function supporting both GEOS-3.2 and GEOS-3.3+. Uses distance parameter sign to derive left/right side. Includes regress testing and documentation. Based on patch by Rafal Magda.

git-svn-id: http://svn.osgeo.org/postgis/trunk@7534 b70326c6-7e19-0410-871a-916f4a2858ee
This commit is contained in:
Sandro Santilli 2011-07-01 09:56:11 +00:00
parent 1b0e73ba66
commit 7edd2a57f1
6 changed files with 356 additions and 0 deletions

View file

@ -283,6 +283,101 @@ MULTIPOLYGON(((-1 2,3 4,5 6,-1 2)),((-1 2,2 3,5 6,-1 2))) | POLYGON((-1 2,5 6,3
</refsection>
</refentry>
<refentry id="ST_OffsetCurve">
<refnamediv>
<refname>ST_OffsetCurve</refname>
<refpurpose>
Return an offset line at a given distance and side from an input line.
</refpurpose>
</refnamediv>
<refsynopsisdiv>
<funcsynopsis>
<funcprototype>
<funcdef>geometry <function>ST_OffsetCurve</function></funcdef>
<paramdef><type>geometry </type> <parameter>line</parameter></paramdef>
<paramdef><type>float </type> <parameter>signed_distance</parameter></paramdef>
</funcprototype>
<funcprototype>
<funcdef>geometry <function>ST_OffsetCurve</function></funcdef>
<paramdef><type>geometry </type> <parameter>line</parameter></paramdef>
<paramdef><type>float </type> <parameter>signed_distance</parameter></paramdef>
<paramdef><type>text </type> <parameter>style_parameters</parameter></paramdef>
</funcprototype>
</funcsynopsis>
</refsynopsisdiv>
<refsection>
<title>Description</title>
<para>
Return an offset line at a given distance and side from an input line.
All points of the returned geometries are not further than the given
distance from the input geometry.
</para>
<para>
For positive distance the offset will be at the left side of the input line
and retain the same direction. For a negative distance it'll be at the right
side and in the opposite direction.
</para>
<para>
Availability: 2.0 - requires GEOS &gt;= 3.2, improved with GEOS &gt;= 3.3
</para>
<para>
The optional third parameter allows specifying a list of blank-separated
key=value pairs to tweak operations as follows:
<itemizedlist>
<listitem>
<para>'quad_segs=#' : number of segments used to approximate a quarter circle (defaults to 8).</para>
</listitem>
<listitem>
<para>'join=round|mitre|bevel' : join style (defaults to "round"). 'miter' is also accepted as a synonym for 'mitre'.</para>
</listitem>
<listitem>
<para>'mitre_limit=#.#' : mitre ratio limit (only affects mitred join style). 'miter_limit' is also accepted as a synonym for 'mitre_limit'.</para>
</listitem>
</itemizedlist>
</para>
<para>
Units of distance are measured in units of the spatial reference system.
</para>
<para>The inputs can only be LINESTRINGS.</para>
<para>Performed by the GEOS module.</para>
<note><para>
This function ignores the third dimension (z) and will always give a
2-d result even when presented with a 3d-geometry.</para></note>
</refsection>
<refsection>
<title>Examples</title>
<para>Compute an open buffer around roads</para>
<programlisting>
SELECT ST_Union(
ST_OffsetCurve(f.the_geom, f.width/2, "quad_segs=4 join=round"),
ST_OffsetCurve(f.the_geom, -f.width/2, "quad_segs=4 join=round")
) as track
FROM someroadstable;
</programlisting>
</refsection>
<refsection>
<title>See Also</title>
<para><xref linkend="ST_Buffer" /></para>
</refsection>
</refentry>
<refentry id="ST_BuildArea">
<refnamediv>
<refname>ST_BuildArea</refname>

View file

@ -46,6 +46,7 @@ Datum isvalid(PG_FUNCTION_ARGS);
Datum isvalidreason(PG_FUNCTION_ARGS);
Datum isvaliddetail(PG_FUNCTION_ARGS);
Datum buffer(PG_FUNCTION_ARGS);
Datum offsetcurve(PG_FUNCTION_ARGS);
Datum intersection(PG_FUNCTION_ARGS);
Datum convexhull(PG_FUNCTION_ARGS);
Datum topologypreservesimplify(PG_FUNCTION_ARGS);
@ -1376,6 +1377,200 @@ Datum buffer(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(result);
}
PG_FUNCTION_INFO_V1(offsetcurve);
Datum offsetcurve(PG_FUNCTION_ARGS)
{
#if POSTGIS_GEOS_VERSION >= 32
PG_LWGEOM *geom1;
double size;
GEOSGeometry *g1, *g3;
PG_LWGEOM *result;
int quadsegs = 8; /* the default */
int nargs;
enum
{
JOIN_ROUND = 1,
JOIN_MITRE = 2,
JOIN_BEVEL = 3
};
static const double DEFAULT_MITRE_LIMIT = 5.0;
static const int DEFAULT_JOIN_STYLE = JOIN_ROUND;
double mitreLimit = DEFAULT_MITRE_LIMIT;
int joinStyle = DEFAULT_JOIN_STYLE;
char *param;
char *params = NULL;
PROFSTART(PROF_QRUN);
// geom arg
geom1 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
// distance/size/direction arg
size = PG_GETARG_FLOAT8(1);
/*
* For distance = 0 we just return the input.
* Note that due to a bug, GEOS 3.3.0 would return EMPTY.
* See http://trac.osgeo.org/geos/ticket/454
*/
if ( size == 0 ) {
PG_RETURN_POINTER(geom1);
}
nargs = PG_NARGS();
initGEOS(lwnotice, lwnotice);
initGEOS(lwnotice, lwgeom_geos_error);
PROFSTART(PROF_P2G1);
g1 = (GEOSGeometry *)POSTGIS2GEOS(geom1);
if ( ! g1 ) {
lwerror("Geometry could not be converted to GEOS: %s",
lwgeom_geos_errmsg);
PG_RETURN_NULL();
}
PROFSTOP(PROF_P2G1);
// options arg (optional)
if (nargs > 2)
{
/* We strdup `cause we're going to modify it */
params = pstrdup(PG_GETARG_CSTRING(2));
POSTGIS_DEBUGF(3, "Params: %s", params);
for (param=params; ; param=NULL)
{
char *key, *val;
param = strtok(param, " ");
if ( param == NULL ) break;
POSTGIS_DEBUGF(3, "Param: %s", param);
key = param;
val = strchr(key, '=');
if ( val == NULL || *(val+1) == '\0' )
{
lwerror("Missing value for buffer "
"parameter %s", key);
break;
}
*val = '\0';
++val;
POSTGIS_DEBUGF(3, "Param: %s : %s", key, val);
if ( !strcmp(key, "join") )
{
if ( !strcmp(val, "round") )
{
joinStyle = JOIN_ROUND;
}
else if ( !strcmp(val, "mitre") ||
!strcmp(val, "miter") )
{
joinStyle = JOIN_MITRE;
}
else if ( !strcmp(val, "bevel") )
{
joinStyle = JOIN_BEVEL;
}
else
{
lwerror("Invalid buffer end cap "
"style: %s (accept: "
"'round', 'mitre', 'miter' "
" or 'bevel'"
")", val);
break;
}
}
else if ( !strcmp(key, "mitre_limit") ||
!strcmp(key, "miter_limit") )
{
/* mitreLimit is a float */
mitreLimit = atof(val);
}
else if ( !strcmp(key, "quad_segs") )
{
/* quadrant segments is an int */
quadsegs = atoi(val);
}
else
{
lwerror("Invalid buffer parameter: %s (accept: "
"'join', 'mitre_limit', "
"'miter_limit and "
"'quad_segs')", key);
break;
}
}
pfree(params); /* was pstrduped */
POSTGIS_DEBUGF(3, "joinStyle:%d mitreLimit:%g",
joinStyle, mitreLimit);
}
PROFSTART(PROF_GRUN);
#if POSTGIS_GEOS_VERSION < 33
g3 = GEOSSingleSidedBuffer(g1, size < 0 ? -size : size,
quadsegs, joinStyle, mitreLimit,
size < 0 ? 0 : 1);
#else
g3 = GEOSOffsetCurve(g1, size, quadsegs, joinStyle, mitreLimit);
#endif
PROFSTOP(PROF_GRUN);
if (g3 == NULL)
{
lwerror("GEOSOffsetCurve: %s", lwgeom_geos_errmsg);
GEOSGeom_destroy(g1);
PG_RETURN_NULL(); /* never get here */
}
POSTGIS_DEBUGF(3, "result: %s", GEOSGeomToWKT(g3));
GEOSSetSRID(g3, pglwgeom_get_srid(geom1));
PROFSTART(PROF_G2P);
result = GEOS2POSTGIS(g3, pglwgeom_has_z(geom1));
PROFSTOP(PROF_G2P);
if (result == NULL)
{
GEOSGeom_destroy(g1);
GEOSGeom_destroy(g3);
lwerror("ST_OffsetCurve() threw an error (result postgis geometry formation)!");
PG_RETURN_NULL(); /* never get here */
}
GEOSGeom_destroy(g1);
GEOSGeom_destroy(g3);
/* compressType(result); */
PROFSTOP(PROF_QRUN);
PROFREPORT("geos",geom1, NULL, result);
PG_FREE_IF_COPY(geom1, 0);
PG_RETURN_POINTER(result);
#else /* POSTGIS_GEOS_VERSION < 32 */
lwerror("The GEOS version this postgis binary "
"was compiled against (%d) doesn't support "
"ST_OffsetCurve function "
"(needs 3.2 or higher)",
POSTGIS_GEOS_VERSION);
PG_RETURN_NULL(); /* never get here */
#endif /* POSTGIS_GEOS_VERSION < 32 */
}
PG_FUNCTION_INFO_V1(intersection);
Datum intersection(PG_FUNCTION_ARGS)
{

View file

@ -3086,6 +3086,13 @@ CREATE OR REPLACE FUNCTION ST_Buffer(geometry,float8,text)
$$
LANGUAGE 'SQL' IMMUTABLE STRICT;
-- Availability: 2.0.0 - requires GEOS-3.2 or higher
CREATE OR REPLACE FUNCTION ST_OffsetCurve(line geometry, distance float8, params cstring DEFAULT '')
RETURNS geometry
AS 'MODULE_PATHNAME','offsetcurve'
LANGUAGE 'C' IMMUTABLE STRICT
COST 100;
-- PostGIS equivalent function: convexhull(geometry)
CREATE OR REPLACE FUNCTION ST_ConvexHull(geometry)
RETURNS geometry

View file

@ -96,6 +96,7 @@ ifeq ($(shell expr $(POSTGIS_GEOS_VERSION) ">=" 32),1)
# ST_HausdorffDistance, ST_Buffer(params)
TESTS += \
hausdorff \
offsetcurve \
regress_buffer_params
endif

43
regress/offsetcurve.sql Normal file
View file

@ -0,0 +1,43 @@
\set VERBOSITY terse
set client_min_messages to NOTICE;
SELECT 't0', ST_OffsetCurve('POINT(0 0)', 10);
SELECT 't0', ST_AsEWKT(ST_OffsetCurve('SRID=42;LINESTRING(0 0, 10 0)', 0));
SELECT 't1', ST_AsEWKT(ST_OffsetCurve('SRID=42;LINESTRING(0 0, 10 0)', 10));
SELECT 't2', ST_AsEWKT(ST_OffsetCurve('SRID=42;LINESTRING(0 0, 10 0)', -10));
SELECT 't3', ST_AsEWKT(ST_OffsetCurve('SRID=42;LINESTRING(10 0, 0 0)', 10));
SELECT 't4', ST_AsEWKT(ST_OffsetCurve('SRID=42;LINESTRING(10 0, 0 0)', -10));
SELECT 't5', ST_AsEWKT(ST_SnapToGrid(ST_OffsetCurve(
'SRID=42;LINESTRING(0 0, 10 0, 10 10)', -10),
1));
SELECT 't6', ST_AsEWKT(ST_SnapToGrid(ST_OffsetCurve(
'SRID=42;LINESTRING(0 0, 10 0, 10 10)', -10,
'quad_segs=2'),
1));
SELECT 't7', ST_AsEWKT(ST_OffsetCurve(
'SRID=42;LINESTRING(0 0, 10 0, 10 10)', -10,
'join=bevel')
);
SELECT 't8', ST_AsEWKT(ST_SnapToGrid(ST_OffsetCurve(
'SRID=42;LINESTRING(0 0, 10 0, 10 10)', -10,
'quad_segs=2 join=mitre'),
1));
SELECT 't9', ST_AsEWKT(ST_SnapToGrid(ST_OffsetCurve(
'SRID=42;LINESTRING(0 0, 10 0, 5 10)', -10,
'quad_segs=2 join=mitre mitre_limit=1'),
1));
SELECT 't10', ST_AsEWKT(ST_SnapToGrid(ST_OffsetCurve(
'SRID=42;LINESTRING(0 0, 10 0, 5 10)', 2,
'quad_segs=2 join=mitre mitre_limit=1'),
1));
SELECT 't10b', ST_AsEWKT(ST_SnapToGrid(ST_OffsetCurve(
'SRID=42;LINESTRING(0 0, 10 0, 5 10)', 2,
'quad_segs=2 join=miter miter_limit=1'),
1));
SELECT 't11', ST_AsEWKT(ST_SnapToGrid(ST_OffsetCurve(
'LINESTRING(36 38,38 35,41 34,42 33,45 32,47 28,50 28,52 32,57 33)', 2,
'join=mitre'),
0.2));
SELECT 't12', ST_AsEWKT(ST_SnapToGrid(ST_OffsetCurve(
'LINESTRING(36 38,38 35,41 34,42 33,45 32,47 28,50 28,52 32,57 33)', -2,
'join=mitre'),
0.2));

View file

@ -0,0 +1,15 @@
ERROR: GEOSOffsetCurve: IllegalArgumentException: BufferBuilder::bufferLineSingleSided only accept linestrings
t0|SRID=42;LINESTRING(0 0,10 0)
t1|SRID=42;LINESTRING(0 10,10 10)
t2|SRID=42;LINESTRING(10 -10,0 -10)
t3|SRID=42;LINESTRING(10 -10,0 -10)
t4|SRID=42;LINESTRING(0 10,10 10)
t5|SRID=42;LINESTRING(20 10,20 0,20 -2,19 -4,18 -6,17 -7,16 -8,14 -9,12 -10,10 -10,0 -10)
t6|SRID=42;LINESTRING(20 10,20 0,17 -7,10 -10,0 -10)
t7|SRID=42;LINESTRING(20 10,20 0,10 -10,0 -10)
t8|SRID=42;LINESTRING(20 10,20 -10,0 -10)
t9|SRID=42;LINESTRING(14 14,21 -1,16 -9,0 -10)
t10|SRID=42;LINESTRING(0 2,7 2,3 9)
t10b|SRID=42;LINESTRING(0 2,7 2,3 9)
t11|LINESTRING(37.6 39.2,39.2 36.6,42 35.8,43 34.8,46.4 33.6,48.2 30,48.8 30,50.6 33.8,56.6 35)
t12|LINESTRING(57.4 31,53.4 30.2,51.2 26,45.8 26,43.6 30.4,41 31.2,40 32.2,36.8 33.4,34.4 36.8)