--- /dev/null
+/**********************************************************************
+ * $Id$
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * Copyright 2009 Paul Ramsey <pramsey@cleverelephant.ca>
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU General Public Licence. See the COPYING file.
+ *
+ **********************************************************************/
+
+#include "libgeom.h"
+
+static void lwgeom_to_wkt_sb(const LWGEOM *geom, stringbuffer_t *sb, int precision, uchar variant);
+
+
+/**
+* ISO format uses both Z and M qualifiers.
+* Extended format only uses an M qualifier for 3DM variants, where it is not
+* clear what the third dimension represents.
+* SFSQL format never has more than two dimensions, so no qualifiers.
+*/
+static void dimension_qualifiers_to_wkt_sb(const LWGEOM *geom, stringbuffer_t *sb, uchar variant)
+{
+
+ /* Extended WKT: POINTM(0 0 0) */
+ if( (variant & WKT_EXTENDED) && TYPE_HASM(geom->type) && (!TYPE_HASZ(geom->type)) )
+ {
+ stringbuffer_append(sb, "M"); /* "M" */
+ return;
+ }
+
+ /* ISO WKT: POINT ZM (0 0 0 0) */
+ if( (variant & WKT_ISO) && (TYPE_NDIMS(geom->type) > 2) )
+ {
+ if( TYPE_HASZ(geom->type) )
+ stringbuffer_append(sb, "Z");
+ if( TYPE_HASM(geom->type) )
+ stringbuffer_append(sb, "M");
+ }
+}
+
+/**
+* Point array is a list of coordinates. Depending on output mode,
+* we may suppress some dimensions. ISO and Extended formats include
+* all dimensions. Standard OGC output only includes X/Y coordinates.
+*/
+static void ptarray_to_wkt_sb(const POINTARRAY *ptarray, stringbuffer_t *sb, int precision, uchar variant)
+{
+ /* OGC only includes X/Y */
+ int dimensions = 2;
+ int i, j;
+
+ /* ISO and extended formats include all dimensions */
+ if( variant & ( WKT_ISO | WKT_EXTENDED ) )
+ dimensions = TYPE_NDIMS(ptarray->dims);
+
+ /* Opening paren? */
+ if( ! (variant & WKT_NOPARENS) )
+ stringbuffer_append(sb, "(");
+
+ /* Digits and commas */
+ for(i = 0; i < ptarray->npoints; i++)
+ {
+ uchar *p = getPoint_internal(ptarray, i);
+ double d;
+
+ /* Commas before ever coord but the first */
+ if( i > 0 )
+ stringbuffer_append(sb, ",");
+
+ for(j = 0; j < dimensions; j++)
+ {
+ memcpy(&d, p + j * sizeof(double), sizeof(double));
+ /* Spaces before every ordinate but the first */
+ if( j > 0 )
+ stringbuffer_append(sb, " ");
+ stringbuffer_vasbappend(sb, "%.*g", precision, d);
+ }
+ }
+
+ /* Closing paren? */
+ if( ! (variant & WKT_NOPARENS) )
+ stringbuffer_append(sb, ")");
+}
+
+/**
+* A four-dimensional point will have different outputs depending on variant.
+* ISO: POINT ZM (0 0 0 0)
+* Extended: POINT(0 0 0 0)
+* OGC: POINT(0 0)
+* A three-dimensional m-point will have different outputs too.
+* ISO: POINT M (0 0 0)
+* Extended: POINTM(0 0 0)
+* OGC: POINT(0 0)
+*/
+static void lwpoint_to_wkt_sb(const LWPOINT *pt, stringbuffer_t *sb, int precision, uchar variant)
+{
+ if( ! (variant & WKT_NOTYPE) )
+ {
+ stringbuffer_append(sb, "POINT"); /* "POINT" */
+ dimension_qualifiers_to_wkt_sb((LWGEOM*)pt, sb, variant);
+ }
+
+ if( pt->point->npoints < 1 )
+ {
+ stringbuffer_append(sb, " EMPTY"); /* "EMPTY" */
+ return;
+ }
+
+ ptarray_to_wkt_sb(pt->point, sb, precision, variant);
+}
+
+/**
+* LINESTRING(0 0 0, 1 1 1)
+*/
+static void lwline_to_wkt_sb(const LWLINE *line, stringbuffer_t *sb, int precision, uchar variant)
+{
+ if( ! (variant & WKT_NOTYPE) )
+ {
+ stringbuffer_append(sb, "LINESTRING"); /* "LINESTRING" */
+ dimension_qualifiers_to_wkt_sb((LWGEOM*)line, sb, variant);
+ }
+ if( line->points->npoints < 1 )
+ {
+ stringbuffer_append(sb, " EMPTY"); /* "EMPTY" */
+ return;
+ }
+
+ ptarray_to_wkt_sb(line->points, sb, precision, variant);
+}
+
+/**
+* POLYGON(0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1)
+*/
+static void lwpoly_to_wkt_sb(const LWPOLY *poly, stringbuffer_t *sb, int precision, uchar variant)
+{
+ int i = 0;
+ if( ! (variant & WKT_NOTYPE) )
+ {
+ stringbuffer_append(sb, "POLYGON"); /* "POLYGON" */
+ dimension_qualifiers_to_wkt_sb((LWGEOM*)poly, sb, variant);
+ }
+ if( poly->nrings < 1 )
+ {
+ stringbuffer_append(sb, " EMPTY"); /* "EMPTY" */
+ return;
+ }
+
+ stringbuffer_append(sb, "(");
+ for( i = 0; i < poly->nrings; i++ )
+ {
+ if( i > 0 )
+ stringbuffer_append(sb, ",");
+ ptarray_to_wkt_sb(poly->rings[i], sb, precision, variant);
+ }
+ stringbuffer_append(sb, ")");
+}
+
+/**
+* CIRCULARSTRING
+*/
+static void lwcircstring_to_wkt_sb(const LWCIRCSTRING *circ, stringbuffer_t *sb, int precision, uchar variant)
+{
+ if( ! (variant & WKT_NOTYPE) )
+ {
+ stringbuffer_append(sb, "CIRCULARSTRING"); /* "CIRCULARSTRING" */
+ dimension_qualifiers_to_wkt_sb((LWGEOM*)circ, sb, variant);
+ }
+ if( circ->points->npoints < 1 )
+ {
+ stringbuffer_append(sb, " EMPTY"); /* "EMPTY" */
+ return;
+ }
+ ptarray_to_wkt_sb(circ->points, sb, precision, variant);
+}
+
+
+/**
+* Multi-points do not wrap their sub-members in parens, unlike other multi-geometries.
+* MULTPOINT(0 0, 1 1) instead of MULTIPOINT((0 0),(1 1))
+*/
+static void lwmpoint_to_wkt_sb(const LWMPOINT *mpoint, stringbuffer_t *sb, int precision, uchar variant)
+{
+ int i = 0;
+ if( ! (variant & WKT_NOTYPE) )
+ {
+ stringbuffer_append(sb, "MULTIPOINT"); /* "MULTIPOINT" */
+ dimension_qualifiers_to_wkt_sb((LWGEOM*)mpoint, sb, variant);
+ }
+ if( mpoint->ngeoms < 1 )
+ {
+ stringbuffer_append(sb, " EMPTY"); /* "EMPTY" */
+ return;
+ }
+ stringbuffer_append(sb, "(");
+ for( i = 0; i < mpoint->ngeoms; i++ )
+ {
+ if( i > 0 )
+ stringbuffer_append(sb, ",");
+ /* We don't want type strings or parens on our subgeoms */
+ lwpoint_to_wkt_sb(mpoint->geoms[i], sb, precision, variant | WKT_NOPARENS | WKT_NOTYPE );
+ }
+ stringbuffer_append(sb, ")");
+}
+
+/**
+* MULTILINESTRING
+*/
+static void lwmline_to_wkt_sb(const LWMLINE *mline, stringbuffer_t *sb, int precision, uchar variant)
+{
+ int i = 0;
+
+ if( ! (variant & WKT_NOTYPE) )
+ {
+ stringbuffer_append(sb, "MULTILINESTRING"); /* "MULTILINESTRING" */
+ dimension_qualifiers_to_wkt_sb((LWGEOM*)mline, sb, variant);
+ }
+ if( mline->ngeoms < 1 )
+ {
+ stringbuffer_append(sb, " EMPTY"); /* "EMPTY" */
+ return;
+ }
+
+ stringbuffer_append(sb, "(");
+ for( i = 0; i < mline->ngeoms; i++ )
+ {
+ if( i > 0 )
+ stringbuffer_append(sb, ",");
+ /* We don't want type strings on our subgeoms */
+ lwline_to_wkt_sb(mline->geoms[i], sb, precision, variant | WKT_NOTYPE );
+ }
+ stringbuffer_append(sb, ")");
+}
+
+/**
+* MULTIPOLYGON
+*/
+static void lwmpoly_to_wkt_sb(const LWMPOLY *mpoly, stringbuffer_t *sb, int precision, uchar variant)
+{
+ int i = 0;
+
+ if( ! (variant & WKT_NOTYPE) )
+ {
+ stringbuffer_append(sb, "MULTIPOLYGON"); /* "MULTIPOLYGON" */
+ dimension_qualifiers_to_wkt_sb((LWGEOM*)mpoly, sb, variant);
+ }
+ if( mpoly->ngeoms < 1 )
+ {
+ stringbuffer_append(sb, " EMPTY"); /* "EMPTY" */
+ return;
+ }
+
+ stringbuffer_append(sb, "(");
+ for( i = 0; i < mpoly->ngeoms; i++ )
+ {
+ if( i > 0 )
+ stringbuffer_append(sb, ",");
+ /* We don't want type strings on our subgeoms */
+ lwpoly_to_wkt_sb(mpoly->geoms[i], sb, precision, variant | WKT_NOTYPE );
+ }
+ stringbuffer_append(sb, ")");
+}
+
+/**
+* Compound curves provide type information for their curved sub-geometries
+* but not their linestring sub-geometries.
+* COMPOUNDCURVE((0 0, 1 1), CURVESTRING(1 1, 2 2, 3 3))
+*/
+static void lwcompound_to_wkt_sb(const LWCOMPOUND *comp, stringbuffer_t *sb, int precision, uchar variant)
+{
+ int i = 0;
+
+ if( ! (variant & WKT_NOTYPE) )
+ {
+ stringbuffer_append(sb, "COMPOUNDCURVE"); /* "COMPOUNDCURVE" */
+ dimension_qualifiers_to_wkt_sb((LWGEOM*)comp, sb, variant);
+ }
+ if( comp->ngeoms < 1 )
+ {
+ stringbuffer_append(sb, " EMPTY"); /* "EMPTY" */
+ return;
+ }
+
+ stringbuffer_append(sb, "(");
+ for( i = 0; i < comp->ngeoms; i++ )
+ {
+ int type = TYPE_GETTYPE(comp->geoms[i]->type);
+ if( i > 0 )
+ stringbuffer_append(sb, ",");
+ /* Linestring subgeoms don't get type identifiers */
+ if( type == LINETYPE )
+ {
+ lwline_to_wkt_sb((LWLINE*)comp->geoms[i], sb, precision, variant | WKT_NOTYPE );
+ }
+ /* But circstring subgeoms *do* get type identifiers */
+ else if( type == CIRCSTRINGTYPE )
+ {
+ lwcircstring_to_wkt_sb((LWCIRCSTRING*)comp->geoms[i], sb, precision, variant );
+ }
+ else
+ {
+ lwerror("lwcompound_to_wkt_size: Unknown type recieved %d", type);
+ }
+ }
+ stringbuffer_append(sb, ")");
+}
+
+/**
+* Curve polygons provide type information for their curved rings
+* but not their linestring rings.
+* CURVEPOLYGON((0 0, 1 1, 0 1, 0 0), CURVESTRING(0 0, 1 1, 0 1, 0.5 1, 0 0))
+*/
+static void lwcurvepoly_to_wkt_sb(const LWCURVEPOLY *cpoly, stringbuffer_t *sb, int precision, uchar variant)
+{
+ int i = 0;
+
+ if( ! (variant & WKT_NOTYPE) )
+ {
+ stringbuffer_append(sb, "CURVEPOLYGON"); /* "CURVEPOLYGON" */
+ dimension_qualifiers_to_wkt_sb((LWGEOM*)cpoly, sb, variant);
+ }
+ if( cpoly->nrings < 1 )
+ {
+ stringbuffer_append(sb, " EMPTY"); /* "EMPTY" */
+ return;
+ }
+ stringbuffer_append(sb, "(");
+ for( i = 0; i < cpoly->nrings; i++ )
+ {
+ int type = TYPE_GETTYPE(cpoly->rings[i]->type);
+ if( i > 0 )
+ stringbuffer_append(sb, ",");
+ switch(type)
+ {
+ case LINETYPE:
+ /* Linestring subgeoms don't get type identifiers */
+ lwline_to_wkt_sb((LWLINE*)cpoly->rings[i], sb, precision, variant | WKT_NOTYPE );
+ break;
+ case CIRCSTRINGTYPE:
+ /* But circstring subgeoms *do* get type identifiers */
+ lwcircstring_to_wkt_sb((LWCIRCSTRING*)cpoly->rings[i], sb, precision, variant );
+ break;
+ case COMPOUNDTYPE:
+ /* And compoundcurve subgeoms *do* get type identifiers */
+ lwcompound_to_wkt_sb((LWCOMPOUND*)cpoly->rings[i], sb, precision, variant );
+ break;
+ default:
+ lwerror("lwcurvepoly_to_wkt_size: Unknown type recieved %d", type);
+ }
+ }
+ stringbuffer_append(sb, ")");
+}
+
+
+/**
+* Multi-curves provide type information for their curved sub-geometries
+* but not their linear sub-geometries.
+* MULTICURVE((0 0, 1 1), CURVESTRING(0 0, 1 1, 2 2))
+*/
+static void lwmcurve_to_wkt_sb(const LWMCURVE *mcurv, stringbuffer_t *sb, int precision, uchar variant)
+{
+ int i = 0;
+
+ if( ! (variant & WKT_NOTYPE) )
+ {
+ stringbuffer_append(sb, "MULTICURVE"); /* "MULTICURVE" */
+ dimension_qualifiers_to_wkt_sb((LWGEOM*)mcurv, sb, variant);
+ }
+ if( mcurv->ngeoms < 1 )
+ {
+ stringbuffer_append(sb, " EMPTY"); /* "EMPTY" */
+ return;
+ }
+ stringbuffer_append(sb, "(");
+ for( i = 0; i < mcurv->ngeoms; i++ )
+ {
+ int type = TYPE_GETTYPE(mcurv->geoms[i]->type);
+ if( i > 0 )
+ stringbuffer_append(sb, ",");
+ switch(type)
+ {
+ case LINETYPE:
+ /* Linestring subgeoms don't get type identifiers */
+ lwline_to_wkt_sb((LWLINE*)mcurv->geoms[i], sb, precision, variant | WKT_NOTYPE );
+ break;
+ case CIRCSTRINGTYPE:
+ /* But circstring subgeoms *do* get type identifiers */
+ lwcircstring_to_wkt_sb((LWCIRCSTRING*)mcurv->geoms[i], sb, precision, variant );
+ break;
+ case COMPOUNDTYPE:
+ /* And compoundcurve subgeoms *do* get type identifiers */
+ lwcompound_to_wkt_sb((LWCOMPOUND*)mcurv->geoms[i], sb, precision, variant );
+ break;
+ default:
+ lwerror("lwmcurve_to_wkt_size: Unknown type recieved %d", type);
+ }
+ }
+ stringbuffer_append(sb, ")");
+}
+
+
+/**
+* Multi-surfaces provide type information for their curved sub-geometries
+* but not their linear sub-geometries.
+* MULTISURFACE(((0 0, 1 1, 1 0, 0 0)), CURVEPOLYGON(CURVESTRING(0 0, 1 1, 2 2, 0 1, 0 0)))
+*/
+static void lwmsurface_to_wkt_sb(const LWMSURFACE *msurf, stringbuffer_t *sb, int precision, uchar variant)
+{
+ int i = 0;
+
+ if( ! (variant & WKT_NOTYPE) )
+ {
+ stringbuffer_append(sb, "MULTISURFACE"); /* "MULTISURFACE" */
+ dimension_qualifiers_to_wkt_sb((LWGEOM*)msurf, sb, variant);
+ }
+ if( msurf->ngeoms < 1 )
+ {
+ stringbuffer_append(sb, " EMPTY"); /* "EMPTY" */
+ return;
+ }
+ stringbuffer_append(sb, "(");
+ for( i = 0; i < msurf->ngeoms; i++ )
+ {
+ int type = TYPE_GETTYPE(msurf->geoms[i]->type);
+ if( i > 0 )
+ stringbuffer_append(sb, ",");
+ switch(type)
+ {
+ case POLYGONTYPE:
+ /* Linestring subgeoms don't get type identifiers */
+ lwpoly_to_wkt_sb((LWPOLY*)msurf->geoms[i], sb, precision, variant | WKT_NOTYPE );
+ break;
+ case CURVEPOLYTYPE:
+ /* But circstring subgeoms *do* get type identifiers */
+ lwcurvepoly_to_wkt_sb((LWCURVEPOLY*)msurf->geoms[i], sb, precision, variant );
+ break;
+ default:
+ lwerror("lwmsurface_to_wkt_size: Unknown type recieved %d", type);
+ }
+ }
+ stringbuffer_append(sb, ")");
+}
+
+/**
+* Geometry collections provide type information for all their curved sub-geometries
+* but not their linear sub-geometries.
+* GEOMETRYCOLLECTION(POLYGON((0 0, 1 1, 1 0, 0 0)), CURVEPOLYGON(CURVESTRING(0 0, 1 1, 2 2, 0 1, 0 0)))
+*/
+static void lwcollection_to_wkt_sb(const LWCOLLECTION *collection, stringbuffer_t *sb, int precision, uchar variant)
+{
+ int i = 0;
+
+ if( ! (variant & WKT_NOTYPE) )
+ {
+ stringbuffer_append(sb, "GEOMETRYCOLLECTION"); /* "GEOMETRYCOLLECTION" */
+ dimension_qualifiers_to_wkt_sb((LWGEOM*)collection, sb, variant);
+ }
+ if( collection->ngeoms < 1 )
+ {
+ stringbuffer_append(sb, " EMPTY"); /* "EMPTY" */
+ return;
+ }
+ stringbuffer_append(sb, "(");
+ for( i = 0; i < collection->ngeoms; i++ )
+ {
+ if( i > 0 )
+ stringbuffer_append(sb, ",");
+ lwgeom_to_wkt_sb((LWGEOM*)collection->geoms[i], sb, precision, variant );
+ }
+ stringbuffer_append(sb, ")");
+}
+
+/**
+* Generic GEOMETRY
+*/
+static void lwgeom_to_wkt_sb(const LWGEOM *geom, stringbuffer_t *sb, int precision, uchar variant)
+{
+ switch(TYPE_GETTYPE(geom->type))
+ {
+ case POINTTYPE:
+ lwpoint_to_wkt_sb((LWPOINT*)geom, sb, precision, variant);
+ break;
+ case LINETYPE:
+ lwline_to_wkt_sb((LWLINE*)geom, sb, precision, variant);
+ break;
+ case POLYGONTYPE:
+ lwpoly_to_wkt_sb((LWPOLY*)geom, sb, precision, variant);
+ break;
+ case MULTIPOINTTYPE:
+ lwmpoint_to_wkt_sb((LWMPOINT*)geom, sb, precision, variant);
+ break;
+ case MULTILINETYPE:
+ lwmline_to_wkt_sb((LWMLINE*)geom, sb, precision, variant);
+ break;
+ case MULTIPOLYGONTYPE:
+ lwmpoly_to_wkt_sb((LWMPOLY*)geom, sb, precision, variant);
+ break;
+ case COLLECTIONTYPE:
+ lwcollection_to_wkt_sb((LWCOLLECTION*)geom, sb, precision, variant);
+ break;
+ case CIRCSTRINGTYPE:
+ lwcircstring_to_wkt_sb((LWCIRCSTRING*)geom, sb, precision, variant);
+ break;
+ case COMPOUNDTYPE:
+ lwcompound_to_wkt_sb((LWCOMPOUND*)geom, sb, precision, variant);
+ break;
+ case CURVEPOLYTYPE:
+ lwcurvepoly_to_wkt_sb((LWCURVEPOLY*)geom, sb, precision, variant);
+ break;
+ case MULTICURVETYPE:
+ lwmcurve_to_wkt_sb((LWMCURVE*)geom, sb, precision, variant);
+ break;
+ case MULTISURFACETYPE:
+ lwmsurface_to_wkt_sb((LWMSURFACE*)geom, sb, precision, variant);
+ break;
+ default:
+ lwerror("lwgeom_to_wkt_sb: Type %d unsupported.", TYPE_GETTYPE(geom->type));
+ }
+}
+
+/**
+* Public WKT emitter function
+*/
+char *lwgeom_to_wkt(const LWGEOM *geom, int precision, uchar variant)
+{
+ stringbuffer_t *sb;
+ char *str = NULL;
+ if( geom == NULL )
+ return NULL;
+ sb = stringbuffer_create();
+ lwgeom_to_wkt_sb(geom, sb, precision, variant);
+ if( stringbuffer_getstring(sb) == NULL )
+ {
+ lwerror("Uh oh");
+ return NULL;
+ }
+ str = strdup(stringbuffer_getstring(sb));
+ stringbuffer_destroy(sb);
+ return str;
+}
+