mirror of
https://git.osgeo.org/gitea/postgis/postgis
synced 2024-10-23 16:42:35 +00:00
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:
parent
1b0e73ba66
commit
7edd2a57f1
|
@ -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 >= 3.2, improved with GEOS >= 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>
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
43
regress/offsetcurve.sql
Normal 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));
|
15
regress/offsetcurve_expected
Normal file
15
regress/offsetcurve_expected
Normal 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)
|
Loading…
Reference in a new issue