/**********************************************************************
*
* PostGIS - Spatial Types for PostgreSQL
* http://postgis.net
*
* PostGIS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PostGIS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PostGIS. If not, see .
*
**********************************************************************
*
* Copyright 2011-2017 Arrival 3D, Regina Obe
*
**********************************************************************/
/**
* @file X3D output routines.
*
**********************************************************************/
#include "lwout_x3d.h"
/*
* VERSION X3D 3.0.2 http://www.web3d.org/specifications/x3d-3.0.dtd
*/
/* takes a GEOMETRY and returns a X3D representation */
lwvarlena_t *
lwgeom_to_x3d3(const LWGEOM *geom, int precision, int opts, const char *defid)
{
stringbuffer_t *sb;
int rv;
/* Empty varlena for empties */
if( lwgeom_is_empty(geom) )
{
lwvarlena_t *v = lwalloc(LWVARHDRSZ);
LWSIZE_SET(v->size, LWVARHDRSZ);
return v;
}
sb = stringbuffer_create();
rv = lwgeom_to_x3d3_sb(geom, precision, opts, defid, sb);
if ( rv == LW_FAILURE )
{
stringbuffer_destroy(sb);
return NULL;
}
lwvarlena_t *v = stringbuffer_getvarlenacopy(sb);
stringbuffer_destroy(sb);
return v;
}
/* takes a GEOMETRY and appends to string buffer the x3d output */
static int
lwgeom_to_x3d3_sb(const LWGEOM *geom, int precision, int opts, const char *defid, stringbuffer_t *sb)
{
int type = geom->type;
switch (type)
{
case POINTTYPE:
return asx3d3_point_sb((LWPOINT *)geom, precision, opts, defid, sb);
case LINETYPE:
return asx3d3_line_sb((LWLINE *)geom, precision, opts, defid, sb);
case POLYGONTYPE:
{
/** We might change this later, but putting a polygon in an indexed face set
* seems like the simplest way to go so treat just like a mulitpolygon
*/
LWCOLLECTION *tmp = (LWCOLLECTION*)lwgeom_as_multi(geom);
asx3d3_multi_sb(tmp, precision, opts, defid, sb);
lwcollection_free(tmp);
return LW_SUCCESS;
}
case TRIANGLETYPE:
return asx3d3_triangle_sb((LWTRIANGLE *)geom, precision, opts, defid, sb);
case MULTIPOINTTYPE:
case MULTILINETYPE:
case MULTIPOLYGONTYPE:
return asx3d3_multi_sb((LWCOLLECTION *)geom, precision, opts, defid, sb);
case POLYHEDRALSURFACETYPE:
return asx3d3_psurface_sb((LWPSURFACE *)geom, precision, opts, defid, sb);
case TINTYPE:
return asx3d3_tin_sb((LWTIN *)geom, precision, opts, defid, sb);
case COLLECTIONTYPE:
return asx3d3_collection_sb((LWCOLLECTION *)geom, precision, opts, defid, sb);
default:
lwerror("lwgeom_to_x3d3: '%s' geometry type not supported", lwtype_name(type));
return LW_FAILURE;
}
}
static int
asx3d3_point_sb(const LWPOINT *point,
int precision,
int opts,
__attribute__((__unused__)) const char *defid,
stringbuffer_t *sb)
{
/** for point we just output the coordinates **/
return ptarray_to_x3d3_sb(point->point, precision, opts, 0, sb);
}
static int
asx3d3_line_coords_sb(const LWLINE *line, int precision, int opts, stringbuffer_t *sb)
{
return ptarray_to_x3d3_sb(line->points, precision, opts, lwline_is_closed(line), sb);
}
/* Calculate the coordIndex property of the IndexedLineSet for the multilinestring
and add to string buffer */
static int
asx3d3_mline_coordindex_sb(const LWMLINE *mgeom, stringbuffer_t *sb)
{
LWLINE *geom;
uint32_t i, j, k, si;
POINTARRAY *pa;
uint32_t np;
j = 0;
for (i=0; i < mgeom->ngeoms; i++)
{
geom = (LWLINE *) mgeom->geoms[i];
pa = geom->points;
np = pa->npoints;
si = j; /* start index of first point of linestring */
for (k=0; k < np ; k++)
{
if (k)
{
stringbuffer_aprintf(sb, " ");
}
/** if the linestring is closed, we put the start point index
* for the last vertex to denote use first point
* and don't increment the index **/
if (!lwline_is_closed(geom) || k < (np -1) )
{
stringbuffer_aprintf(sb, "%u", j);
j += 1;
}
else
{
stringbuffer_aprintf(sb,"%u", si);
}
}
if (i < (mgeom->ngeoms - 1) )
{
stringbuffer_aprintf(sb, " -1 "); /* separator for each linestring */
}
}
return LW_SUCCESS;
}
/* Calculate the coordIndex property of the IndexedLineSet for a multipolygon
This is not ideal -- would be really nice to just share this function with psurf,
but I'm not smart enough to do that yet*/
static int
asx3d3_mpoly_coordindex_sb(const LWMPOLY *psur, stringbuffer_t *sb)
{
LWPOLY *patch;
uint32_t i, j, k, l;
uint32_t np;
j = 0;
for (i=0; ingeoms; i++)
{
patch = (LWPOLY *) psur->geoms[i];
for (l=0; l < patch->nrings; l++)
{
np = patch->rings[l]->npoints - 1;
for (k=0; k < np ; k++)
{
if (k)
{
stringbuffer_aprintf(sb, " ");
}
stringbuffer_aprintf(sb, "%d", (j + k));
}
j += k;
if (l < (patch->nrings - 1) )
{
/** @todo TODO: Decide the best way to render holes
* Evidently according to my X3D expert the X3D consortium doesn't really
* support holes and it's an issue of argument among many that feel it should. He thinks CAD x3d extensions to spec might.
* What he has done and others developing X3D exports to simulate a hole is to cut around it.
* So if you have a donut, you would cut it into half and have 2 solid polygons. Not really sure the best way to handle this.
* For now will leave it as polygons stacked on top of each other -- which is what we are doing here and perhaps an option
* to color differently. It's not ideal but the alternative sounds complicated.
**/
stringbuffer_aprintf(sb, " -1 "); /* separator for each inner ring. Ideally we should probably triangulate and cut around as others do */
}
}
if (i < (psur->ngeoms - 1) )
{
stringbuffer_aprintf(sb, " -1 "); /* separator for each subgeom */
}
}
return LW_SUCCESS;
}
/** Return the linestring as an X3D LineSet */
static int
asx3d3_line_sb(const LWLINE *line,
int precision,
int opts,
__attribute__((__unused__)) const char *defid,
stringbuffer_t *sb)
{
/* int dimension=2; */
POINTARRAY *pa;
/* if (FLAGS_GET_Z(line->flags)) dimension = 3; */
pa = line->points;
stringbuffer_aprintf(sb, "", defid, pa->npoints);
if ( X3D_USE_GEOCOORDS(opts) ) stringbuffer_aprintf(sb, "points, precision, opts, lwline_is_closed((LWLINE *) line), sb);
stringbuffer_aprintf(sb, "' />");
return stringbuffer_aprintf(sb, "");
}
/** Compute the X3D coordinates of the polygon and add to string buffer **/
static int
asx3d3_poly_sb(const LWPOLY *poly,
int precision,
int opts,
__attribute__((__unused__)) int is_patch,
__attribute__((__unused__)) const char *defid,
stringbuffer_t *sb)
{
uint32_t i;
for (i=0; inrings; i++)
{
if (i) stringbuffer_aprintf(sb, " "); /* inner ring points start */
ptarray_to_x3d3_sb(poly->rings[i], precision, opts, 1, sb);
}
return LW_SUCCESS;
}
static int
asx3d3_triangle_sb(const LWTRIANGLE *triangle,
int precision,
int opts,
__attribute__((__unused__)) const char *defid,
stringbuffer_t *sb)
{
return ptarray_to_x3d3_sb(triangle->points, precision, opts, 1, sb);
}
/*
* Don't call this with single-geoms inspected!
*/
static int
asx3d3_multi_sb(const LWCOLLECTION *col, int precision, int opts, const char *defid, stringbuffer_t *sb)
{
char *x3dtype;
uint32_t i;
int dimension=2;
if (FLAGS_GET_Z(col->flags)) dimension = 3;
LWGEOM *subgeom;
x3dtype="";
switch (col->type)
{
case MULTIPOINTTYPE:
x3dtype = "PointSet";
if ( dimension == 2 ){ /** Use Polypoint2D instead **/
x3dtype = "Polypoint2D";
stringbuffer_aprintf(sb, "<%s %s point='", x3dtype, defid);
}
else {
stringbuffer_aprintf(sb, "<%s %s>", x3dtype, defid);
}
break;
case MULTILINETYPE:
x3dtype = "IndexedLineSet";
stringbuffer_aprintf(sb, "<%s %s coordIndex='", x3dtype, defid);
asx3d3_mline_coordindex_sb((const LWMLINE *)col, sb);
stringbuffer_aprintf(sb, "'>");
break;
case MULTIPOLYGONTYPE:
x3dtype = "IndexedFaceSet";
stringbuffer_aprintf(sb, "<%s %s convex='false' coordIndex='", x3dtype, defid);
asx3d3_mpoly_coordindex_sb((const LWMPOLY *)col, sb);
stringbuffer_aprintf(sb, "'>");
break;
default:
lwerror("asx3d3_multi_buf: '%s' geometry type not supported", lwtype_name(col->type));
return 0;
}
if (dimension == 3){
if ( X3D_USE_GEOCOORDS(opts) )
stringbuffer_aprintf(sb, "ngeoms; i++)
{
subgeom = col->geoms[i];
if (subgeom->type == POINTTYPE)
{
asx3d3_point_sb((LWPOINT *)subgeom, precision, opts, defid, sb);
stringbuffer_aprintf(sb, " ");
}
else if (subgeom->type == LINETYPE)
{
asx3d3_line_coords_sb((LWLINE*)subgeom, precision, opts, sb);
stringbuffer_aprintf(sb, " ");
}
else if (subgeom->type == POLYGONTYPE)
{
asx3d3_poly_sb((LWPOLY *)subgeom, precision, opts, 0, defid, sb);
stringbuffer_aprintf(sb, " ");
}
}
/* Close outmost tag */
if (dimension == 3){
stringbuffer_aprintf(sb, "' />%s>", x3dtype);
}
else { stringbuffer_aprintf(sb, "' />"); }
return LW_SUCCESS;
}
/*
* Don't call this with single-geoms inspected!
*/
static int
asx3d3_psurface_sb(const LWPSURFACE *psur, int precision, int opts, const char *defid, stringbuffer_t *sb)
{
uint32_t i;
uint32_t j;
uint32_t k;
uint32_t np;
LWPOLY *patch;
/* Open outmost tag */
stringbuffer_aprintf(sb, "");
}
/*
* Computes X3D representation of TIN (as IndexedTriangleSet and adds to string buffer)
*/
static int
asx3d3_tin_sb(const LWTIN *tin, int precision, int opts, const char *defid, stringbuffer_t *sb)
{
uint32_t i;
uint32_t k;
/* int dimension=2; */
stringbuffer_aprintf(sb,"");
}
static int
asx3d3_collection_sb(const LWCOLLECTION *col, int precision, int opts, const char *defid, stringbuffer_t *sb)
{
uint32_t i;
LWGEOM *subgeom;
/* Open outmost tag */
/** @TODO: if collection should be grouped, we'll wrap in a group tag. Still needs cleanup
* like the shapes should really be in a transform **/
#ifdef PGIS_X3D_OUTERMOST_TAGS
stringbuffer_aprintf(sb, "<%sGroup>", defid);
#endif
for (i=0; ingeoms; i++)
{
subgeom = col->geoms[i];
stringbuffer_aprintf(sb, "", defid);
if ( subgeom->type == POINTTYPE )
{
asx3d3_point_sb((LWPOINT *)subgeom, precision, opts, defid, sb);
}
else if ( subgeom->type == LINETYPE )
{
asx3d3_line_sb((LWLINE *)subgeom, precision, opts, defid, sb);
}
else if ( subgeom->type == POLYGONTYPE )
{
asx3d3_poly_sb((LWPOLY *)subgeom, precision, opts, 0, defid, sb);
}
else if ( subgeom->type == TINTYPE )
{
asx3d3_tin_sb((LWTIN *)subgeom, precision, opts, defid, sb);
}
else if ( subgeom->type == POLYHEDRALSURFACETYPE )
{
asx3d3_psurface_sb((LWPSURFACE *)subgeom, precision, opts, defid, sb);
}
else if ( lwgeom_is_collection(subgeom) )
{
if ( subgeom->type == COLLECTIONTYPE )
asx3d3_collection_sb((LWCOLLECTION *)subgeom, precision, opts, defid, sb);
else
asx3d3_multi_sb((LWCOLLECTION *)subgeom, precision, opts, defid, sb);
}
else
lwerror("asx3d3_collection_buf: unknown geometry type");
stringbuffer_aprintf(sb, "");
}
/* Close outmost tag */
#ifdef PGIS_X3D_OUTERMOST_TAGS
stringbuffer_aprintf(sb, "%sGroup>", defid);
#endif
return LW_SUCCESS;
}
/** In X3D3, coordinates are separated by a space separator
*/
static int
ptarray_to_x3d3_sb(POINTARRAY *pa, int precision, int opts, int is_closed, stringbuffer_t *sb )
{
uint32_t i;
char x[OUT_DOUBLE_BUFFER_SIZE];
char y[OUT_DOUBLE_BUFFER_SIZE];
char z[OUT_DOUBLE_BUFFER_SIZE];
if ( ! FLAGS_GET_Z(pa->flags) )
{
for (i=0; inpoints; i++)
{
/** Only output the point if it is not the last point of a closed object or it is a non-closed type **/
if ( !is_closed || i < (pa->npoints - 1) )
{
POINT2D pt;
getPoint2d_p(pa, i, &pt);
lwprint_double(pt.x, precision, x);
lwprint_double(pt.y, precision, y);
if ( i ) stringbuffer_append_len(sb," ",1);
if ( ( opts & LW_X3D_FLIP_XY) )
stringbuffer_aprintf(sb, "%s %s", y, x);
else
stringbuffer_aprintf(sb, "%s %s", x, y);
}
}
}
else
{
for (i=0; inpoints; i++)
{
/** Only output the point if it is not the last point of a closed object or it is a non-closed type **/
if ( !is_closed || i < (pa->npoints - 1) )
{
POINT4D pt;
getPoint4d_p(pa, i, &pt);
lwprint_double(pt.x, precision, x);
lwprint_double(pt.y, precision, y);
lwprint_double(pt.z, precision, z);
if ( i ) stringbuffer_append_len(sb," ",1);
if ( ( opts & LW_X3D_FLIP_XY) )
stringbuffer_aprintf(sb, "%s %s %s", y, x, z);
else
stringbuffer_aprintf(sb, "%s %s %s", x, y, z);
}
}
}
return LW_SUCCESS;
}