]> granicus.if.org Git - postgis/commitdiff
Add ST_OffsetCurve function supporting both GEOS-3.2 and GEOS-3.3+. Uses distance...
authorSandro Santilli <strk@keybit.net>
Fri, 1 Jul 2011 09:56:11 +0000 (09:56 +0000)
committerSandro Santilli <strk@keybit.net>
Fri, 1 Jul 2011 09:56:11 +0000 (09:56 +0000)
git-svn-id: http://svn.osgeo.org/postgis/trunk@7534 b70326c6-7e19-0410-871a-916f4a2858ee

doc/reference_processing.xml
postgis/lwgeom_geos.c
postgis/postgis.sql.in.c
regress/Makefile.in
regress/offsetcurve.sql [new file with mode: 0644]
regress/offsetcurve_expected [new file with mode: 0644]

index b48d56577ef03cb48b52a90eac863c7f9d4a0d81..f0cfd4022be75746db9d52bc834e16345aadb922 100644 (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>
index ca8a46468e44d0ede51f49314d2ee3ddde5b64b0..7afcd4407890b712cc0eeb776225f47f7dc71d2c 100644 (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)
 {
index d0df5c4e20fc3ced61d1744688e4016fe28750a4..f820bddd553a22fe36a256ba64d1b2fbc5aab5c1 100644 (file)
@@ -3086,6 +3086,13 @@ CREATE OR REPLACE FUNCTION ST_Buffer(geometry,float8,text)
           $$\r
        LANGUAGE 'SQL' IMMUTABLE STRICT;\r
 \r
+-- Availability: 2.0.0 - requires GEOS-3.2 or higher\r
+CREATE OR REPLACE FUNCTION ST_OffsetCurve(line geometry, distance float8, params cstring DEFAULT '')\r
+       RETURNS geometry\r
+       AS 'MODULE_PATHNAME','offsetcurve'\r
+       LANGUAGE 'C' IMMUTABLE STRICT\r
+       COST 100;\r
+\r
 -- PostGIS equivalent function: convexhull(geometry)\r
 CREATE OR REPLACE FUNCTION ST_ConvexHull(geometry)\r
        RETURNS geometry\r
index aaf055a95f09c5f14ba3644f5e841b83394ceb26..bb32890aac23512c77b4594b3c00071de12f2167 100644 (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
 
diff --git a/regress/offsetcurve.sql b/regress/offsetcurve.sql
new file mode 100644 (file)
index 0000000..d9df12a
--- /dev/null
@@ -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));
diff --git a/regress/offsetcurve_expected b/regress/offsetcurve_expected
new file mode 100644 (file)
index 0000000..878bc3c
--- /dev/null
@@ -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)