/**********************************************************************
*
* 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 (C) 2014 Nicklas Avén
*
**********************************************************************/
#include
#include "liblwgeom_internal.h"
#include "lwgeom_log.h"
#include "varint.h"
#define TWKB_IN_MAXCOORDS 4
/**
* Used for passing the parse state between the parsing functions.
*/
typedef struct
{
/* Pointers to the bytes */
const uint8_t *twkb; /* Points to start of TWKB */
const uint8_t *twkb_end; /* Points to end of TWKB */
const uint8_t *pos; /* Current read position */
uint32_t check; /* Simple validity checks on geometries */
uint32_t lwtype; /* Current type we are handling */
uint8_t has_bbox;
uint8_t has_size;
uint8_t has_idlist;
uint8_t has_z;
uint8_t has_m;
uint8_t is_empty;
/* Precision factors to convert ints to double */
double factor;
double factor_z;
double factor_m;
uint64_t size;
/* Info about current geometry */
uint8_t magic_byte; /* the magic byte contain info about if twkb contain id, size info, bboxes and precision */
int ndims; /* Number of dimensions */
int64_t *coords; /* An array to keep delta values from 4 dimensions */
} twkb_parse_state;
/**
* Internal function declarations.
*/
LWGEOM* lwgeom_from_twkb_state(twkb_parse_state *s);
/**********************************************************************/
/**
* Check that we are not about to read off the end of the WKB
* array.
*/
static inline void twkb_parse_state_advance(twkb_parse_state *s, size_t next)
{
if( (s->pos + next) > s->twkb_end)
{
lwerror("%s: TWKB structure does not match expected size!", __func__);
// lwnotice("TWKB structure does not match expected size!");
}
s->pos += next;
}
static inline int64_t twkb_parse_state_varint(twkb_parse_state *s)
{
size_t size;
int64_t val = varint_s64_decode(s->pos, s->twkb_end, &size);
twkb_parse_state_advance(s, size);
return val;
}
static inline uint64_t twkb_parse_state_uvarint(twkb_parse_state *s)
{
size_t size;
uint64_t val = varint_u64_decode(s->pos, s->twkb_end, &size);
twkb_parse_state_advance(s, size);
return val;
}
static inline double twkb_parse_state_double(twkb_parse_state *s, double factor)
{
size_t size;
int64_t val = varint_s64_decode(s->pos, s->twkb_end, &size);
twkb_parse_state_advance(s, size);
return val / factor;
}
static inline void twkb_parse_state_varint_skip(twkb_parse_state *s)
{
size_t size = varint_size(s->pos, s->twkb_end);
if ( ! size )
lwerror("%s: no varint to skip", __func__);
twkb_parse_state_advance(s, size);
return;
}
static uint32_t lwtype_from_twkb_type(uint8_t twkb_type)
{
switch (twkb_type)
{
case 1:
return POINTTYPE;
case 2:
return LINETYPE;
case 3:
return POLYGONTYPE;
case 4:
return MULTIPOINTTYPE;
case 5:
return MULTILINETYPE;
case 6:
return MULTIPOLYGONTYPE;
case 7:
return COLLECTIONTYPE;
default: /* Error! */
lwerror("Unknown WKB type");
return 0;
}
return 0;
}
/**
* Byte
* Read a byte and advance the parse state forward.
*/
static uint8_t byte_from_twkb_state(twkb_parse_state *s)
{
uint8_t val = *(s->pos);
twkb_parse_state_advance(s, WKB_BYTE_SIZE);
return val;
}
/**
* POINTARRAY
* Read a dynamically sized point array and advance the parse state forward.
*/
static POINTARRAY* ptarray_from_twkb_state(twkb_parse_state *s, uint32_t npoints)
{
POINTARRAY *pa = NULL;
uint32_t ndims = s->ndims;
uint32_t i;
double *dlist;
LWDEBUG(2,"Entering ptarray_from_twkb_state");
LWDEBUGF(4,"Pointarray has %d points", npoints);
/* Empty! */
if( npoints == 0 )
return ptarray_construct_empty(s->has_z, s->has_m, 0);
pa = ptarray_construct(s->has_z, s->has_m, npoints);
dlist = (double*)(pa->serialized_pointlist);
for( i = 0; i < npoints; i++ )
{
int j = 0;
/* X */
s->coords[j] += twkb_parse_state_varint(s);
dlist[ndims*i + j] = s->coords[j] / s->factor;
j++;
/* Y */
s->coords[j] += twkb_parse_state_varint(s);
dlist[ndims*i + j] = s->coords[j] / s->factor;
j++;
/* Z */
if ( s->has_z )
{
s->coords[j] += twkb_parse_state_varint(s);
dlist[ndims*i + j] = s->coords[j] / s->factor_z;
j++;
}
/* M */
if ( s->has_m )
{
s->coords[j] += twkb_parse_state_varint(s);
dlist[ndims*i + j] = s->coords[j] / s->factor_m;
j++;
}
}
return pa;
}
/**
* POINT
*/
static LWPOINT* lwpoint_from_twkb_state(twkb_parse_state *s)
{
static uint32_t npoints = 1;
POINTARRAY *pa;
LWDEBUG(2,"Entering lwpoint_from_twkb_state");
if ( s->is_empty )
return lwpoint_construct_empty(SRID_UNKNOWN, s->has_z, s->has_m);
pa = ptarray_from_twkb_state(s, npoints);
return lwpoint_construct(SRID_UNKNOWN, NULL, pa);
}
/**
* LINESTRING
*/
static LWLINE* lwline_from_twkb_state(twkb_parse_state *s)
{
uint32_t npoints;
POINTARRAY *pa;
LWDEBUG(2,"Entering lwline_from_twkb_state");
if ( s->is_empty )
return lwline_construct_empty(SRID_UNKNOWN, s->has_z, s->has_m);
/* Read number of points */
npoints = twkb_parse_state_uvarint(s);
if ( npoints == 0 )
return lwline_construct_empty(SRID_UNKNOWN, s->has_z, s->has_m);
/* Read coordinates */
pa = ptarray_from_twkb_state(s, npoints);
if( pa == NULL )
return lwline_construct_empty(SRID_UNKNOWN, s->has_z, s->has_m);
if( s->check & LW_PARSER_CHECK_MINPOINTS && pa->npoints < 2 )
{
lwerror("%s must have at least two points", lwtype_name(s->lwtype));
return NULL;
}
return lwline_construct(SRID_UNKNOWN, NULL, pa);
}
/**
* POLYGON
*/
static LWPOLY* lwpoly_from_twkb_state(twkb_parse_state *s)
{
uint32_t nrings;
uint32_t i;
LWPOLY *poly;
LWDEBUG(2,"Entering lwpoly_from_twkb_state");
if ( s->is_empty )
return lwpoly_construct_empty(SRID_UNKNOWN, s->has_z, s->has_m);
/* Read number of rings */
nrings = twkb_parse_state_uvarint(s);
/* Start w/ empty polygon */
poly = lwpoly_construct_empty(SRID_UNKNOWN, s->has_z, s->has_m);
LWDEBUGF(4,"Polygon has %d rings", nrings);
/* Empty polygon? */
if( nrings == 0 )
return poly;
for( i = 0; i < nrings; i++ )
{
/* Ret number of points */
uint32_t npoints = twkb_parse_state_uvarint(s);
POINTARRAY *pa = ptarray_from_twkb_state(s, npoints);
/* Skip empty rings */
if( pa == NULL )
continue;
/* Force first and last points to be the same. */
if( ! ptarray_is_closed_2d(pa) )
{
POINT4D pt;
getPoint4d_p(pa, 0, &pt);
ptarray_append_point(pa, &pt, LW_FALSE);
}
/* Check for at least four points. */
if( s->check & LW_PARSER_CHECK_MINPOINTS && pa->npoints < 4 )
{
LWDEBUGF(2, "%s must have at least four points in each ring", lwtype_name(s->lwtype));
lwerror("%s must have at least four points in each ring", lwtype_name(s->lwtype));
return NULL;
}
/* Add ring to polygon */
if ( lwpoly_add_ring(poly, pa) == LW_FAILURE )
{
LWDEBUG(2, "Unable to add ring to polygon");
lwerror("Unable to add ring to polygon");
}
}
return poly;
}
/**
* MULTIPOINT
*/
static LWCOLLECTION* lwmultipoint_from_twkb_state(twkb_parse_state *s)
{
int ngeoms, i;
LWGEOM *geom = NULL;
LWCOLLECTION *col = lwcollection_construct_empty(s->lwtype, SRID_UNKNOWN, s->has_z, s->has_m);
LWDEBUG(2,"Entering lwmultipoint_from_twkb_state");
if ( s->is_empty )
return col;
/* Read number of geometries */
ngeoms = twkb_parse_state_uvarint(s);
LWDEBUGF(4,"Number of geometries %d", ngeoms);
/* It has an idlist, we need to skip that */
if ( s->has_idlist )
{
for ( i = 0; i < ngeoms; i++ )
twkb_parse_state_varint_skip(s);
}
for ( i = 0; i < ngeoms; i++ )
{
geom = lwpoint_as_lwgeom(lwpoint_from_twkb_state(s));
if ( lwcollection_add_lwgeom(col, geom) == NULL )
{
lwerror("Unable to add geometry (%p) to collection (%p)", geom, col);
return NULL;
}
}
return col;
}
/**
* MULTILINESTRING
*/
static LWCOLLECTION* lwmultiline_from_twkb_state(twkb_parse_state *s)
{
int ngeoms, i;
LWGEOM *geom = NULL;
LWCOLLECTION *col = lwcollection_construct_empty(s->lwtype, SRID_UNKNOWN, s->has_z, s->has_m);
LWDEBUG(2,"Entering lwmultilinestring_from_twkb_state");
if ( s->is_empty )
return col;
/* Read number of geometries */
ngeoms = twkb_parse_state_uvarint(s);
LWDEBUGF(4,"Number of geometries %d",ngeoms);
/* It has an idlist, we need to skip that */
if ( s->has_idlist )
{
for ( i = 0; i < ngeoms; i++ )
twkb_parse_state_varint_skip(s);
}
for ( i = 0; i < ngeoms; i++ )
{
geom = lwline_as_lwgeom(lwline_from_twkb_state(s));
if ( lwcollection_add_lwgeom(col, geom) == NULL )
{
lwerror("Unable to add geometry (%p) to collection (%p)", geom, col);
return NULL;
}
}
return col;
}
/**
* MULTIPOLYGON
*/
static LWCOLLECTION* lwmultipoly_from_twkb_state(twkb_parse_state *s)
{
int ngeoms, i;
LWGEOM *geom = NULL;
LWCOLLECTION *col = lwcollection_construct_empty(s->lwtype, SRID_UNKNOWN, s->has_z, s->has_m);
LWDEBUG(2,"Entering lwmultipolygon_from_twkb_state");
if ( s->is_empty )
return col;
/* Read number of geometries */
ngeoms = twkb_parse_state_uvarint(s);
LWDEBUGF(4,"Number of geometries %d",ngeoms);
/* It has an idlist, we need to skip that */
if ( s->has_idlist )
{
for ( i = 0; i < ngeoms; i++ )
twkb_parse_state_varint_skip(s);
}
for ( i = 0; i < ngeoms; i++ )
{
geom = lwpoly_as_lwgeom(lwpoly_from_twkb_state(s));
if ( lwcollection_add_lwgeom(col, geom) == NULL )
{
lwerror("Unable to add geometry (%p) to collection (%p)", geom, col);
return NULL;
}
}
return col;
}
/**
* COLLECTION, MULTIPOINTTYPE, MULTILINETYPE, MULTIPOLYGONTYPE
**/
static LWCOLLECTION* lwcollection_from_twkb_state(twkb_parse_state *s)
{
int ngeoms, i;
LWGEOM *geom = NULL;
LWCOLLECTION *col = lwcollection_construct_empty(s->lwtype, SRID_UNKNOWN, s->has_z, s->has_m);
LWDEBUG(2,"Entering lwcollection_from_twkb_state");
if ( s->is_empty )
return col;
/* Read number of geometries */
ngeoms = twkb_parse_state_uvarint(s);
LWDEBUGF(4,"Number of geometries %d",ngeoms);
/* It has an idlist, we need to skip that */
if ( s->has_idlist )
{
for ( i = 0; i < ngeoms; i++ )
twkb_parse_state_varint_skip(s);
}
for ( i = 0; i < ngeoms; i++ )
{
geom = lwgeom_from_twkb_state(s);
if ( lwcollection_add_lwgeom(col, geom) == NULL )
{
lwerror("Unable to add geometry (%p) to collection (%p)", geom, col);
return NULL;
}
}
return col;
}
static void header_from_twkb_state(twkb_parse_state *s)
{
LWDEBUG(2,"Entering magicbyte_from_twkb_state");
uint8_t extended_dims;
/* Read the first two bytes */
uint8_t type_precision = byte_from_twkb_state(s);
uint8_t metadata = byte_from_twkb_state(s);
/* Strip type and precision out of first byte */
uint8_t type = type_precision & 0x0F;
int8_t precision = unzigzag8((type_precision & 0xF0) >> 4);
/* Convert TWKB type to internal type */
s->lwtype = lwtype_from_twkb_type(type);
/* Convert the precision into factor */
s->factor = pow(10, (double)precision);
/* Strip metadata flags out of second byte */
s->has_bbox = metadata & 0x01;
s->has_size = (metadata & 0x02) >> 1;
s->has_idlist = (metadata & 0x04) >> 2;
extended_dims = (metadata & 0x08) >> 3;
s->is_empty = (metadata & 0x10) >> 4;
/* Flag for higher dims means read a third byte */
if ( extended_dims )
{
int8_t precision_z, precision_m;
extended_dims = byte_from_twkb_state(s);
/* Strip Z/M presence and precision from ext byte */
s->has_z = (extended_dims & 0x01);
s->has_m = (extended_dims & 0x02) >> 1;
precision_z = (extended_dims & 0x1C) >> 2;
precision_m = (extended_dims & 0xE0) >> 5;
/* Convert the precision into factor */
s->factor_z = pow(10, (double)precision_z);
s->factor_m = pow(10, (double)precision_m);
}
else
{
s->has_z = 0;
s->has_m = 0;
s->factor_z = 0;
s->factor_m = 0;
}
/* Read the size, if there is one */
if ( s->has_size )
{
s->size = twkb_parse_state_uvarint(s);
}
/* Calculate the number of dimensions */
s->ndims = 2 + s->has_z + s->has_m;
return;
}
/**
* Generic handling for TWKB geometries. The front of every TWKB geometry
* (including those embedded in collections) is a type byte and metadata byte,
* then optional size, bbox, etc. Read those, then switch to particular type
* handling code.
*/
LWGEOM* lwgeom_from_twkb_state(twkb_parse_state *s)
{
GBOX bbox;
LWGEOM *geom = NULL;
uint32_t has_bbox = LW_FALSE;
int i;
/* Read the first two bytes, and optional */
/* extended precision info and optional size info */
header_from_twkb_state(s);
/* Just experienced a geometry header, so now we */
/* need to reset our coordinate deltas */
for ( i = 0; i < TWKB_IN_MAXCOORDS; i++ )
{
s->coords[i] = 0.0;
}
/* Read the bounding box, is there is one */
if ( s->has_bbox )
{
/* Initialize */
has_bbox = s->has_bbox;
memset(&bbox, 0, sizeof(GBOX));
bbox.flags = lwflags(s->has_z, s->has_m, 0);
/* X */
bbox.xmin = twkb_parse_state_double(s, s->factor);
bbox.xmax = bbox.xmin + twkb_parse_state_double(s, s->factor);
/* Y */
bbox.ymin = twkb_parse_state_double(s, s->factor);
bbox.ymax = bbox.ymin + twkb_parse_state_double(s, s->factor);
/* Z */
if ( s->has_z )
{
bbox.zmin = twkb_parse_state_double(s, s->factor_z);
bbox.zmax = bbox.zmin + twkb_parse_state_double(s, s->factor_z);
}
/* M */
if ( s->has_m )
{
bbox.mmin = twkb_parse_state_double(s, s->factor_m);
bbox.mmax = bbox.mmin + twkb_parse_state_double(s, s->factor_m);
}
}
/* Switch to code for the particular type we're dealing with */
switch( s->lwtype )
{
case POINTTYPE:
geom = lwpoint_as_lwgeom(lwpoint_from_twkb_state(s));
break;
case LINETYPE:
geom = lwline_as_lwgeom(lwline_from_twkb_state(s));
break;
case POLYGONTYPE:
geom = lwpoly_as_lwgeom(lwpoly_from_twkb_state(s));
break;
case MULTIPOINTTYPE:
geom = lwcollection_as_lwgeom(lwmultipoint_from_twkb_state(s));
break;
case MULTILINETYPE:
geom = lwcollection_as_lwgeom(lwmultiline_from_twkb_state(s));
break;
case MULTIPOLYGONTYPE:
geom = lwcollection_as_lwgeom(lwmultipoly_from_twkb_state(s));
break;
case COLLECTIONTYPE:
geom = lwcollection_as_lwgeom(lwcollection_from_twkb_state(s));
break;
/* Unknown type! */
default:
lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(s->lwtype));
break;
}
if ( has_bbox )
geom->bbox = gbox_clone(&bbox);
return geom;
}
/**
* WKB inputs *must* have a declared size, to prevent malformed WKB from reading
* off the end of the memory segment (this stops a malevolent user from declaring
* a one-ring polygon to have 10 rings, causing the WKB reader to walk off the
* end of the memory).
*
* Check is a bitmask of: LW_PARSER_CHECK_MINPOINTS, LW_PARSER_CHECK_ODD,
* LW_PARSER_CHECK_CLOSURE, LW_PARSER_CHECK_NONE, LW_PARSER_CHECK_ALL
*/
LWGEOM* lwgeom_from_twkb(const uint8_t *twkb, size_t twkb_size, char check)
{
int64_t coords[TWKB_IN_MAXCOORDS] = {0, 0, 0, 0};
twkb_parse_state s;
LWDEBUG(2,"Entering lwgeom_from_twkb");
LWDEBUGF(4,"twkb_size: %d",(int) twkb_size);
/* Zero out the state */
memset(&s, 0, sizeof(twkb_parse_state));
/* Initialize the state appropriately */
s.twkb = s.pos = twkb;
s.twkb_end = twkb + twkb_size;
s.check = check;
s.coords = coords;
/* Read the rest of the geometry */
return lwgeom_from_twkb_state(&s);
}