mirror of
https://github.com/pgpointcloud/pointcloud.git
synced 2025-12-08 20:36:04 +00:00
951 lines
22 KiB
C
951 lines
22 KiB
C
/***********************************************************************
|
|
* pc_pgsql.c
|
|
*
|
|
* Utility functions to bind pc_api.h functions to PgSQL, including
|
|
* memory management and serialization/deserializations.
|
|
*
|
|
* PgSQL Pointcloud is free and open source software provided
|
|
* by the Government of Canada
|
|
* Copyright (c) 2013 Natural Resources Canada
|
|
*
|
|
***********************************************************************/
|
|
|
|
#include <assert.h>
|
|
#include "pc_pgsql.h"
|
|
#include "executor/spi.h"
|
|
#include "access/hash.h"
|
|
#include "utils/hsearch.h"
|
|
|
|
PG_MODULE_MAGIC;
|
|
|
|
/**********************************************************************************
|
|
* POSTGRESQL MEMORY MANAGEMENT HOOKS
|
|
*/
|
|
|
|
static void *
|
|
pgsql_alloc(size_t size)
|
|
{
|
|
void * result;
|
|
result = palloc(size);
|
|
|
|
if ( ! result )
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_OUT_OF_MEMORY),
|
|
errmsg("Out of virtual memory")));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void *
|
|
pgsql_realloc(void *mem, size_t size)
|
|
{
|
|
void * result;
|
|
result = repalloc(mem, size);
|
|
if ( ! result )
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_OUT_OF_MEMORY),
|
|
errmsg("Out of virtual memory")));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
pgsql_free(void *ptr)
|
|
{
|
|
pfree(ptr);
|
|
}
|
|
|
|
static void
|
|
pgsql_msg_handler(int sig, const char *fmt, va_list ap)
|
|
__attribute__ (( format (printf, 2, 0) ));
|
|
|
|
static void
|
|
pgsql_msg_handler(int sig, const char *fmt, va_list ap)
|
|
{
|
|
#define MSG_MAXLEN 1024
|
|
char msg[MSG_MAXLEN] = {0};
|
|
vsnprintf(msg, MSG_MAXLEN, fmt, ap);
|
|
msg[MSG_MAXLEN-1] = '\0';
|
|
ereport(sig, (errmsg_internal("%s", msg)));
|
|
}
|
|
|
|
static void
|
|
pgsql_error(const char *fmt, va_list ap) __attribute__ (( format (printf, 1, 0) ));
|
|
|
|
static void
|
|
pgsql_error(const char *fmt, va_list ap)
|
|
{
|
|
pgsql_msg_handler(ERROR, fmt, ap);
|
|
}
|
|
|
|
static void
|
|
pgsql_warn(const char *fmt, va_list ap) __attribute__ (( format (printf, 1, 0) ));
|
|
|
|
static void
|
|
pgsql_warn(const char *fmt, va_list ap)
|
|
{
|
|
pgsql_msg_handler(WARNING, fmt, ap);
|
|
}
|
|
|
|
static void
|
|
pgsql_info(const char *fmt, va_list ap) __attribute__ (( format (printf, 1, 0) ));
|
|
|
|
static void
|
|
pgsql_info(const char *fmt, va_list ap)
|
|
{
|
|
pgsql_msg_handler(NOTICE, fmt, ap);
|
|
}
|
|
|
|
/**********************************************************************************
|
|
* POINTCLOUD START-UP/SHUT-DOWN CALLBACKS
|
|
*/
|
|
|
|
/**
|
|
* On module load we want to hook the message writing and memory allocation
|
|
* functions of libpc to the PostgreSQL ones.
|
|
* TODO: also hook the libxml2 hooks into PostgreSQL.
|
|
*/
|
|
void _PG_init(void);
|
|
void
|
|
_PG_init(void)
|
|
{
|
|
elog(LOG, "Pointcloud (%s) module loaded", POINTCLOUD_VERSION);
|
|
pc_set_handlers(
|
|
pgsql_alloc, pgsql_realloc,
|
|
pgsql_free, pgsql_error,
|
|
pgsql_info, pgsql_warn
|
|
);
|
|
|
|
}
|
|
|
|
/* Module unload callback */
|
|
void _PG_fini(void);
|
|
void
|
|
_PG_fini(void)
|
|
{
|
|
elog(LOG, "Pointcloud (%s) module unloaded", POINTCLOUD_VERSION);
|
|
}
|
|
|
|
/* Mask pcid from bottom of typmod */
|
|
uint32 pcid_from_typmod(const int32 typmod)
|
|
{
|
|
if ( typmod == -1 )
|
|
return 0;
|
|
else
|
|
return (typmod & 0x0000FFFF);
|
|
}
|
|
|
|
/**********************************************************************************
|
|
* PCPOINT WKB Handling
|
|
*/
|
|
|
|
PCPOINT *
|
|
pc_point_from_hexwkb(const char *hexwkb, size_t hexlen, FunctionCallInfoData *fcinfo)
|
|
{
|
|
PCPOINT *pt;
|
|
PCSCHEMA *schema;
|
|
uint32 pcid;
|
|
uint8 *wkb = pc_bytes_from_hexbytes(hexwkb, hexlen);
|
|
size_t wkblen = hexlen/2;
|
|
pcid = pc_wkb_get_pcid(wkb);
|
|
schema = pc_schema_from_pcid(pcid, fcinfo);
|
|
pt = pc_point_from_wkb(schema, wkb, wkblen);
|
|
pfree(wkb);
|
|
return pt;
|
|
}
|
|
|
|
char *
|
|
pc_point_to_hexwkb(const PCPOINT *pt)
|
|
{
|
|
uint8 *wkb;
|
|
size_t wkb_size;
|
|
char *hexwkb;
|
|
|
|
wkb = pc_point_to_wkb(pt, &wkb_size);
|
|
hexwkb = pc_hexbytes_from_bytes(wkb, wkb_size);
|
|
pfree(wkb);
|
|
return hexwkb;
|
|
}
|
|
|
|
|
|
/**********************************************************************************
|
|
* PCPATCH WKB Handling
|
|
*/
|
|
|
|
PCPATCH *
|
|
pc_patch_from_hexwkb(const char *hexwkb, size_t hexlen, FunctionCallInfoData *fcinfo)
|
|
{
|
|
PCPATCH *patch;
|
|
PCSCHEMA *schema;
|
|
uint32 pcid;
|
|
uint8 *wkb = pc_bytes_from_hexbytes(hexwkb, hexlen);
|
|
size_t wkblen = hexlen/2;
|
|
pcid = pc_wkb_get_pcid(wkb);
|
|
if ( ! pcid )
|
|
elog(ERROR, "%s: pcid is zero", __func__);
|
|
|
|
schema = pc_schema_from_pcid(pcid, fcinfo);
|
|
if ( ! schema )
|
|
elog(ERROR, "%s: unable to look up schema entry", __func__);
|
|
|
|
patch = pc_patch_from_wkb(schema, wkb, wkblen);
|
|
pfree(wkb);
|
|
return patch;
|
|
}
|
|
|
|
char *
|
|
pc_patch_to_hexwkb(const PCPATCH *patch)
|
|
{
|
|
uint8 *wkb;
|
|
size_t wkb_size;
|
|
char *hexwkb;
|
|
|
|
wkb = pc_patch_to_wkb(patch, &wkb_size);
|
|
hexwkb = pc_hexbytes_from_bytes(wkb, wkb_size);
|
|
pfree(wkb);
|
|
return hexwkb;
|
|
}
|
|
|
|
|
|
/**********************************************************************************
|
|
* PCID <=> PCSCHEMA translation via POINTCLOUD_FORMATS
|
|
*/
|
|
|
|
uint32 pcid_from_datum(Datum d)
|
|
{
|
|
SERIALIZED_POINT *serpart;
|
|
if ( ! d )
|
|
return 0;
|
|
/* Serializations are int32_t <size> uint32_t <pcid> == 8 bytes */
|
|
/* Cast to SERIALIZED_POINT for convenience, SERIALIZED_PATCH shares same header */
|
|
serpart = (SERIALIZED_POINT*)PG_DETOAST_DATUM_SLICE(d, 0, 8);
|
|
return serpart->pcid;
|
|
}
|
|
|
|
PCSCHEMA *
|
|
pc_schema_from_pcid_uncached(uint32 pcid)
|
|
{
|
|
char sql[256];
|
|
char *xml, *xml_spi, *srid_spi;
|
|
int err, srid;
|
|
size_t size;
|
|
PCSCHEMA *schema;
|
|
|
|
if (SPI_OK_CONNECT != SPI_connect ())
|
|
{
|
|
SPI_finish();
|
|
elog(ERROR, "%s: could not connect to SPI manager", __func__);
|
|
return NULL;
|
|
}
|
|
|
|
sprintf(sql, "select %s, %s from %s where pcid = %d",
|
|
POINTCLOUD_FORMATS_XML, POINTCLOUD_FORMATS_SRID, POINTCLOUD_FORMATS, pcid);
|
|
err = SPI_exec(sql, 1);
|
|
|
|
if ( err < 0 )
|
|
{
|
|
SPI_finish();
|
|
elog(ERROR, "%s: error (%d) executing query: %s", __func__, err, sql);
|
|
return NULL;
|
|
}
|
|
|
|
/* No entry in POINTCLOUD_FORMATS */
|
|
if (SPI_processed <= 0)
|
|
{
|
|
SPI_finish();
|
|
elog(ERROR, "no entry in \"%s\" for pcid = %d", POINTCLOUD_FORMATS, pcid);
|
|
return NULL;
|
|
}
|
|
|
|
/* Result */
|
|
xml_spi = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1);
|
|
srid_spi = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 2);
|
|
|
|
/* NULL result */
|
|
if ( ! ( xml_spi && srid_spi ) )
|
|
{
|
|
SPI_finish();
|
|
elog(ERROR, "unable to read row from \"%s\" for pcid = %d", POINTCLOUD_FORMATS, pcid);
|
|
return NULL;
|
|
}
|
|
|
|
/* Copy result to upper executor context */
|
|
size = strlen(xml_spi) + 1;
|
|
xml = SPI_palloc(size);
|
|
memcpy(xml, xml_spi, size);
|
|
|
|
/* Parse the SRID string into the function stack */
|
|
srid = atoi(srid_spi);
|
|
|
|
/* Disconnect from SPI, losing all our SPI-allocated memory now... */
|
|
SPI_finish();
|
|
|
|
/* Build the schema object */
|
|
schema = pc_schema_from_xml(xml);
|
|
|
|
if ( !schema )
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NOT_AN_XML_DOCUMENT),
|
|
errmsg("unable to parse XML for pcid = %d in \"%s\"", pcid, POINTCLOUD_FORMATS)));
|
|
}
|
|
|
|
schema->pcid = pcid;
|
|
schema->srid = srid;
|
|
|
|
return schema;
|
|
}
|
|
|
|
|
|
/**
|
|
* Hold the schema references in a list.
|
|
* We'll just search them linearly, because
|
|
* usually we'll have only one per statement
|
|
*/
|
|
#define SchemaCacheSize 16
|
|
|
|
typedef struct
|
|
{
|
|
int next_slot;
|
|
int pcids[SchemaCacheSize];
|
|
PCSCHEMA* schemas[SchemaCacheSize];
|
|
} SchemaCache;
|
|
|
|
|
|
/**
|
|
* Get the schema entry from the schema cache if one exists.
|
|
* If it doesn't exist, make a new empty one, cache it, and
|
|
* return it.
|
|
*/
|
|
static SchemaCache *
|
|
GetSchemaCache(FunctionCallInfoData* fcinfo)
|
|
{
|
|
SchemaCache *cache = fcinfo->flinfo->fn_extra;
|
|
if ( ! cache )
|
|
{
|
|
cache = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(SchemaCache));
|
|
memset(cache, 0, sizeof(SchemaCache));
|
|
fcinfo->flinfo->fn_extra = cache;
|
|
}
|
|
return cache;
|
|
}
|
|
|
|
|
|
PCSCHEMA *
|
|
pc_schema_from_pcid(uint32 pcid, FunctionCallInfoData *fcinfo)
|
|
{
|
|
SchemaCache *schema_cache = GetSchemaCache(fcinfo);
|
|
int i;
|
|
PCSCHEMA *schema;
|
|
MemoryContext oldcontext;
|
|
|
|
/* Unable to find/make a schema cache? Odd. */
|
|
if ( ! schema_cache )
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INTERNAL_ERROR),
|
|
errmsg("unable to create/load statement level schema cache")));
|
|
}
|
|
|
|
/* Find our PCID if it's in there (usually it will be first) */
|
|
for ( i = 0; i < SchemaCacheSize; i++ )
|
|
{
|
|
if ( schema_cache->pcids[i] == pcid )
|
|
{
|
|
return schema_cache->schemas[i];
|
|
}
|
|
}
|
|
|
|
elog(DEBUG1, "schema cache miss, use pc_schema_from_pcid_uncached");
|
|
|
|
/* Not in there, load one the old-fashioned way. */
|
|
oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
|
|
schema = pc_schema_from_pcid_uncached(pcid);
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
/* Failed to load the XML? Odd. */
|
|
if ( ! schema )
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INTERNAL_ERROR),
|
|
errmsg("unable to load schema for pcid %u", pcid)));
|
|
}
|
|
|
|
/* Save the XML in the next unused slot */
|
|
schema_cache->schemas[schema_cache->next_slot] = schema;
|
|
schema_cache->pcids[schema_cache->next_slot] = pcid;
|
|
schema_cache->next_slot = (schema_cache->next_slot + 1) % SchemaCacheSize;
|
|
return schema;
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************************************
|
|
* SERIALIZATION/DESERIALIZATION UTILITIES
|
|
*/
|
|
|
|
SERIALIZED_POINT *
|
|
pc_point_serialize(const PCPOINT *pcpt)
|
|
{
|
|
size_t serpt_size = sizeof(SERIALIZED_POINT) - 1 + pcpt->schema->size;
|
|
SERIALIZED_POINT *serpt = palloc(serpt_size);
|
|
serpt->pcid = pcpt->schema->pcid;
|
|
memcpy(serpt->data, pcpt->data, pcpt->schema->size);
|
|
SET_VARSIZE(serpt, serpt_size);
|
|
return serpt;
|
|
}
|
|
|
|
PCPOINT *
|
|
pc_point_deserialize(const SERIALIZED_POINT *serpt, const PCSCHEMA *schema)
|
|
{
|
|
PCPOINT *pcpt;
|
|
size_t pgsize = VARSIZE(serpt) + 1 - sizeof(SERIALIZED_POINT);
|
|
/*
|
|
* Big problem, the size on disk doesn't match what we expect,
|
|
* so we cannot reliably interpret the contents.
|
|
*/
|
|
if ( schema->size != pgsize )
|
|
{
|
|
elog(ERROR, "schema size and disk size mismatch, repair the schema");
|
|
return NULL;
|
|
}
|
|
pcpt = pc_point_from_data(schema, serpt->data);
|
|
return pcpt;
|
|
}
|
|
|
|
|
|
size_t
|
|
pc_patch_serialized_size(const PCPATCH *patch)
|
|
{
|
|
size_t stats_size = pc_stats_size(patch->schema);
|
|
size_t common_size = sizeof(SERIALIZED_PATCH) - 1;
|
|
switch( patch->type )
|
|
{
|
|
case PC_NONE:
|
|
{
|
|
PCPATCH_UNCOMPRESSED *pu = (PCPATCH_UNCOMPRESSED*)patch;
|
|
return common_size + stats_size + pu->datasize;
|
|
}
|
|
case PC_DIMENSIONAL:
|
|
{
|
|
return common_size + stats_size + pc_patch_dimensional_serialized_size((PCPATCH_DIMENSIONAL*)patch);
|
|
}
|
|
case PC_LAZPERF:
|
|
{
|
|
static size_t lazsize_size = 4;
|
|
PCPATCH_LAZPERF *pg = (PCPATCH_LAZPERF*)patch;
|
|
return common_size + stats_size + lazsize_size + pg->lazperfsize;
|
|
}
|
|
default:
|
|
{
|
|
pcerror("%s: unknown compresed %d", __func__, patch->type);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static size_t
|
|
pc_patch_stats_serialize(uint8_t *buf, const PCSCHEMA *schema, const PCSTATS *stats)
|
|
{
|
|
size_t sz = schema->size;
|
|
/* Copy min */
|
|
memcpy(buf, stats->min.data, sz);
|
|
/* Copy max */
|
|
memcpy(buf + sz, stats->max.data, sz);
|
|
/* Copy avg */
|
|
memcpy(buf + 2*sz, stats->avg.data, sz);
|
|
|
|
return sz*3;
|
|
}
|
|
|
|
/**
|
|
* Stats are always three PCPOINT serializations in a row,
|
|
* min, max, avg. Their size is the uncompressed buffer size for
|
|
* a point, the schema->size.
|
|
*/
|
|
PCSTATS *
|
|
pc_patch_stats_deserialize(const PCSCHEMA *schema, const uint8_t *buf)
|
|
{
|
|
size_t sz = schema->size;
|
|
const uint8_t *buf_min = buf;
|
|
const uint8_t *buf_max = buf + sz;
|
|
const uint8_t *buf_avg = buf + 2*sz;
|
|
|
|
return pc_stats_new_from_data(schema, buf_min, buf_max, buf_avg);
|
|
}
|
|
|
|
static SERIALIZED_PATCH *
|
|
pc_patch_dimensional_serialize(const PCPATCH *patch_in)
|
|
{
|
|
// uint32_t size;
|
|
// uint32_t pcid;
|
|
// uint32_t compression;
|
|
// uint32_t npoints;
|
|
// double xmin, xmax, ymin, ymax;
|
|
// data:
|
|
// pcpoint[3] stats;
|
|
// serialized_pcbytes[ndims] dimensions;
|
|
|
|
int i;
|
|
uint8_t *buf;
|
|
size_t serpch_size = pc_patch_serialized_size(patch_in);
|
|
SERIALIZED_PATCH *serpch = pcalloc(serpch_size);
|
|
const PCPATCH_DIMENSIONAL *patch = (PCPATCH_DIMENSIONAL*)patch_in;
|
|
|
|
assert(patch_in);
|
|
assert(patch_in->type == PC_DIMENSIONAL);
|
|
|
|
/* Copy basics */
|
|
serpch->pcid = patch->schema->pcid;
|
|
serpch->npoints = patch->npoints;
|
|
serpch->bounds = patch->bounds;
|
|
serpch->compression = patch->type;
|
|
|
|
/* Get a pointer to the data area */
|
|
buf = serpch->data;
|
|
|
|
/* Write stats into the buffer */
|
|
if ( patch->stats )
|
|
{
|
|
buf += pc_patch_stats_serialize(buf, patch->schema, patch->stats);
|
|
}
|
|
else
|
|
{
|
|
pcerror("%s: stats missing!", __func__);
|
|
}
|
|
|
|
/* Write each dimension in after the stats */
|
|
for ( i = 0; i < patch->schema->ndims; i++ )
|
|
{
|
|
size_t bsize = 0;
|
|
PCBYTES *pcb = &(patch->bytes[i]);
|
|
pc_bytes_serialize(pcb, buf, &bsize);
|
|
buf += bsize;
|
|
}
|
|
|
|
SET_VARSIZE(serpch, serpch_size);
|
|
return serpch;
|
|
}
|
|
|
|
|
|
static SERIALIZED_PATCH *
|
|
pc_patch_lazperf_serialize(const PCPATCH *patch_in)
|
|
{
|
|
size_t serpch_size = pc_patch_serialized_size(patch_in);
|
|
SERIALIZED_PATCH *serpch = pcalloc(serpch_size);
|
|
const PCPATCH_LAZPERF *patch = (PCPATCH_LAZPERF*)patch_in;
|
|
uint32_t lazsize = patch->lazperfsize;
|
|
uint8_t *buf = serpch->data;
|
|
|
|
assert(patch);
|
|
assert(patch->type == PC_LAZPERF);
|
|
|
|
/* Copy basics */
|
|
serpch->pcid = patch->schema->pcid;
|
|
serpch->npoints = patch->npoints;
|
|
serpch->bounds = patch->bounds;
|
|
serpch->compression = patch->type;
|
|
|
|
/* Write stats into the buffer first */
|
|
if ( patch->stats )
|
|
{
|
|
buf += pc_patch_stats_serialize(buf, patch->schema, patch->stats);
|
|
}
|
|
else
|
|
{
|
|
pcerror("%s: stats missing!", __func__);
|
|
}
|
|
|
|
/* Write buffer size */
|
|
memcpy(buf, &(lazsize), 4);
|
|
buf += 4;
|
|
|
|
/* Write buffer */
|
|
memcpy(buf, patch->lazperf, patch->lazperfsize);
|
|
SET_VARSIZE(serpch, serpch_size);
|
|
|
|
return serpch;
|
|
}
|
|
|
|
static SERIALIZED_PATCH *
|
|
pc_patch_uncompressed_serialize(const PCPATCH *patch_in)
|
|
{
|
|
// uint32_t size;
|
|
// uint32_t pcid;
|
|
// uint32_t compression;
|
|
// uint32_t npoints;
|
|
// double xmin, xmax, ymin, ymax;
|
|
// data:
|
|
// pcpoint [];
|
|
|
|
uint8_t *buf;
|
|
size_t serpch_size;
|
|
SERIALIZED_PATCH *serpch;
|
|
const PCPATCH_UNCOMPRESSED *patch = (PCPATCH_UNCOMPRESSED *)patch_in;
|
|
|
|
serpch_size = pc_patch_serialized_size(patch_in);
|
|
serpch = pcalloc(serpch_size);
|
|
|
|
/* Copy basic */
|
|
serpch->compression = patch->type;
|
|
serpch->pcid = patch->schema->pcid;
|
|
serpch->npoints = patch->npoints;
|
|
serpch->bounds = patch->bounds;
|
|
|
|
/* Write stats into the buffer first */
|
|
buf = serpch->data;
|
|
if ( patch->stats )
|
|
{
|
|
buf += pc_patch_stats_serialize(buf, patch->schema, patch->stats);
|
|
}
|
|
else
|
|
{
|
|
pcerror("%s: stats missing!", __func__);
|
|
}
|
|
|
|
/* Copy point list into data buffer */
|
|
memcpy(buf, patch->data, patch->datasize);
|
|
SET_VARSIZE(serpch, serpch_size);
|
|
return serpch;
|
|
}
|
|
|
|
|
|
/**
|
|
* Convert struct to byte array.
|
|
* Userdata is currently only PCDIMSTATS, hopefully updated across
|
|
* a number of iterations and saved.
|
|
*/
|
|
SERIALIZED_PATCH *
|
|
pc_patch_serialize(const PCPATCH *patch_in, void *userdata)
|
|
{
|
|
PCPATCH *patch = (PCPATCH*)patch_in;
|
|
SERIALIZED_PATCH *serpatch = NULL;
|
|
/*
|
|
* Ensure the patch has stats calculated before going on
|
|
*/
|
|
if ( ! patch->stats )
|
|
{
|
|
pcerror("%s: patch is missing stats", __func__);
|
|
return NULL;
|
|
}
|
|
/*
|
|
* Convert the patch to the final target compression,
|
|
* which is the one in the schema.
|
|
*/
|
|
if ( patch->type != patch->schema->compression )
|
|
{
|
|
patch = pc_patch_compress(patch_in, userdata);
|
|
}
|
|
|
|
switch( patch->type )
|
|
{
|
|
case PC_NONE:
|
|
{
|
|
serpatch = pc_patch_uncompressed_serialize(patch);
|
|
break;
|
|
}
|
|
case PC_DIMENSIONAL:
|
|
{
|
|
serpatch = pc_patch_dimensional_serialize(patch);
|
|
break;
|
|
}
|
|
case PC_LAZPERF:
|
|
{
|
|
serpatch = pc_patch_lazperf_serialize(patch);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
pcerror("%s: unsupported compression type %d", __func__, patch->type);
|
|
}
|
|
}
|
|
|
|
if ( patch != patch_in )
|
|
pc_patch_free(patch);
|
|
|
|
return serpatch;
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Convert struct to byte array.
|
|
* Userdata is currently only PCDIMSTATS, hopefully updated across
|
|
* a number of iterations and saved.
|
|
*/
|
|
SERIALIZED_PATCH *
|
|
pc_patch_serialize_to_uncompressed(const PCPATCH *patch_in)
|
|
{
|
|
PCPATCH *patch = (PCPATCH*)patch_in;
|
|
SERIALIZED_PATCH *serpatch;
|
|
|
|
/* Convert the patch to uncompressed, if necessary */
|
|
if ( patch->type != PC_NONE )
|
|
{
|
|
patch = pc_patch_uncompress(patch_in);
|
|
}
|
|
|
|
serpatch = pc_patch_uncompressed_serialize(patch);
|
|
|
|
/* An uncompressed input won't result in a copy */
|
|
if ( patch != patch_in )
|
|
pc_patch_free(patch);
|
|
|
|
return serpatch;
|
|
}
|
|
|
|
static PCPATCH *
|
|
pc_patch_uncompressed_deserialize(const SERIALIZED_PATCH *serpatch, const PCSCHEMA *schema)
|
|
{
|
|
// typedef struct
|
|
// {
|
|
// uint32_t size;
|
|
// uint32_t pcid;
|
|
// uint32_t compression;
|
|
// uint32_t npoints;
|
|
// double xmin, xmax, ymin, ymax;
|
|
// data:
|
|
// pcpoint[3] pcstats(min, max, avg)
|
|
// pcpoint[npoints]
|
|
// }
|
|
// SERIALIZED_PATCH;
|
|
|
|
uint8_t *buf;
|
|
size_t stats_size = pc_stats_size(schema); // 3 pcpoints worth of stats
|
|
PCPATCH_UNCOMPRESSED *patch = pcalloc(sizeof(PCPATCH_UNCOMPRESSED));
|
|
|
|
/* Set up basic info */
|
|
patch->type = serpatch->compression;
|
|
patch->schema = schema;
|
|
patch->readonly = true;
|
|
patch->npoints = serpatch->npoints;
|
|
patch->maxpoints = 0;
|
|
patch->bounds = serpatch->bounds;
|
|
|
|
buf = (uint8_t*)serpatch->data;
|
|
|
|
/* Point into the stats area */
|
|
patch->stats = pc_patch_stats_deserialize(schema, buf);
|
|
|
|
/* Advance data pointer past the stats serialization */
|
|
patch->data = buf + stats_size;
|
|
|
|
/* Calculate the point data buffer size */
|
|
patch->datasize = VARSIZE(serpatch) - sizeof(SERIALIZED_PATCH) + 1 - stats_size;
|
|
if ( patch->datasize != patch->npoints * schema->size )
|
|
pcerror("%s: calculated patch data sizes don't match (%d != %d)", __func__, patch->datasize, patch->npoints * schema->size);
|
|
|
|
return (PCPATCH*)patch;
|
|
}
|
|
|
|
static PCPATCH *
|
|
pc_patch_dimensional_deserialize(const SERIALIZED_PATCH *serpatch, const PCSCHEMA *schema)
|
|
{
|
|
// typedef struct
|
|
// {
|
|
// uint32_t size;
|
|
// uint32_t pcid;
|
|
// uint32_t compression;
|
|
// uint32_t npoints;
|
|
// double xmin, xmax, ymin, ymax;
|
|
// data:
|
|
// pcpoint[3] pcstats(min, max, avg)
|
|
// pcbytes[ndims];
|
|
// }
|
|
// SERIALIZED_PATCH;
|
|
|
|
PCPATCH_DIMENSIONAL *patch;
|
|
int i;
|
|
const uint8_t *buf;
|
|
int ndims = schema->ndims;
|
|
int npoints = serpatch->npoints;
|
|
size_t stats_size = pc_stats_size(schema); // 3 pcpoints worth of stats
|
|
|
|
/* Reference the external data */
|
|
patch = pcalloc(sizeof(PCPATCH_DIMENSIONAL));
|
|
|
|
/* Set up basic info */
|
|
patch->type = serpatch->compression;
|
|
patch->schema = schema;
|
|
patch->readonly = true;
|
|
patch->npoints = npoints;
|
|
patch->bounds = serpatch->bounds;
|
|
|
|
/* Point into the stats area */
|
|
patch->stats = pc_patch_stats_deserialize(schema, serpatch->data);
|
|
|
|
/* Set up dimensions */
|
|
patch->bytes = pcalloc(ndims * sizeof(PCBYTES));
|
|
buf = serpatch->data + stats_size;
|
|
|
|
for ( i = 0; i < ndims; i++ )
|
|
{
|
|
PCBYTES *pcb = &(patch->bytes[i]);
|
|
PCDIMENSION *dim = schema->dims[i];
|
|
pc_bytes_deserialize(buf, dim, pcb, true /*readonly*/, false /*flipendian*/);
|
|
pcb->npoints = npoints;
|
|
buf += pc_bytes_serialized_size(pcb);
|
|
}
|
|
|
|
return (PCPATCH*)patch;
|
|
}
|
|
|
|
/*
|
|
* We don't do any radical deserialization here. Don't build out the tree, just
|
|
* set up pointers to the start of the buffer, so we can build it out later
|
|
* if necessary.
|
|
*/
|
|
static PCPATCH *
|
|
pc_patch_lazperf_deserialize(const SERIALIZED_PATCH *serpatch, const PCSCHEMA *schema)
|
|
{
|
|
PCPATCH_LAZPERF *patch;
|
|
uint32_t lazperfsize;
|
|
int npoints = serpatch->npoints;
|
|
size_t stats_size = pc_stats_size(schema);
|
|
uint8_t *buf = (uint8_t*)serpatch->data + stats_size;
|
|
|
|
/* Reference the external data */
|
|
patch = pcalloc(sizeof(PCPATCH_LAZPERF));
|
|
|
|
/* Set up basic info */
|
|
patch->type = serpatch->compression;
|
|
patch->schema = schema;
|
|
patch->readonly = true;
|
|
patch->npoints = npoints;
|
|
patch->bounds = serpatch->bounds;
|
|
|
|
/* Point into the stats area */
|
|
patch->stats = pc_patch_stats_deserialize(schema, serpatch->data);
|
|
|
|
/* Set up buffer */
|
|
memcpy(&lazperfsize, buf, 4);
|
|
patch->lazperfsize = lazperfsize;
|
|
buf += 4;
|
|
|
|
patch->lazperf = pcalloc( patch->lazperfsize );
|
|
memcpy(patch->lazperf, buf, patch->lazperfsize);
|
|
|
|
return (PCPATCH*)patch;
|
|
}
|
|
|
|
PCPATCH *
|
|
pc_patch_deserialize(const SERIALIZED_PATCH *serpatch, const PCSCHEMA *schema)
|
|
{
|
|
switch(serpatch->compression)
|
|
{
|
|
case PC_NONE:
|
|
return pc_patch_uncompressed_deserialize(serpatch, schema);
|
|
case PC_DIMENSIONAL:
|
|
return pc_patch_dimensional_deserialize(serpatch, schema);
|
|
case PC_LAZPERF:
|
|
return pc_patch_lazperf_deserialize(serpatch, schema);
|
|
}
|
|
pcerror("%s: unsupported compression type", __func__);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static uint8_t *
|
|
pc_patch_wkb_set_double(uint8_t *wkb, double d)
|
|
{
|
|
memcpy(wkb, &d, 8);
|
|
wkb += 8;
|
|
return wkb;
|
|
}
|
|
|
|
static uint8_t *
|
|
pc_patch_wkb_set_int32(uint8_t *wkb, uint32_t i)
|
|
{
|
|
memcpy(wkb, &i, 8);
|
|
wkb += 4;
|
|
return wkb;
|
|
}
|
|
|
|
static uint8_t *
|
|
pc_patch_wkb_set_char(uint8_t *wkb, char c)
|
|
{
|
|
memcpy(wkb, &c, 1);
|
|
wkb += 1;
|
|
return wkb;
|
|
}
|
|
|
|
/* 0 = xdr | big endian */
|
|
/* 1 = ndr | little endian */
|
|
static char
|
|
machine_endian(void)
|
|
{
|
|
static int check_int = 1; /* dont modify this!!! */
|
|
return *((char *) &check_int);
|
|
}
|
|
|
|
uint8_t *
|
|
pc_patch_to_geometry_wkb_envelope(const SERIALIZED_PATCH *pa, const PCSCHEMA *schema, size_t *wkbsize)
|
|
{
|
|
static uint32_t srid_mask = 0x20000000;
|
|
static uint32_t nrings = 1;
|
|
static uint32_t npoints = 5;
|
|
uint32_t wkbtype = 3; /* WKB POLYGON */
|
|
uint8_t *wkb, *ptr;
|
|
int has_srid = false;
|
|
size_t size = 1 + 4 + 4 + 4 + 2*npoints*8; /* endian + type + nrings + npoints + 5 dbl pts */
|
|
|
|
/* Bounds! */
|
|
double xmin = pa->bounds.xmin;
|
|
double ymin = pa->bounds.ymin;
|
|
double xmax = pa->bounds.xmax;
|
|
double ymax = pa->bounds.ymax;
|
|
|
|
/* Make sure they're slightly bigger than a point */
|
|
if ( xmin == xmax ) xmax += xmax * 0.0000001;
|
|
if ( ymin == ymax ) ymax += ymax * 0.0000001;
|
|
|
|
if ( schema->srid > 0 )
|
|
{
|
|
has_srid = true;
|
|
wkbtype |= srid_mask;
|
|
size += 4;
|
|
}
|
|
|
|
wkb = palloc(size);
|
|
ptr = wkb;
|
|
|
|
ptr = pc_patch_wkb_set_char(ptr, machine_endian()); /* Endian flag */
|
|
|
|
ptr = pc_patch_wkb_set_int32(ptr, wkbtype); /* TYPE = Polygon */
|
|
|
|
if ( has_srid )
|
|
{
|
|
ptr = pc_patch_wkb_set_int32(ptr, schema->srid); /* SRID */
|
|
}
|
|
|
|
ptr = pc_patch_wkb_set_int32(ptr, nrings); /* NRINGS = 1 */
|
|
ptr = pc_patch_wkb_set_int32(ptr, npoints); /* NPOINTS = 5 */
|
|
|
|
/* Point 0 */
|
|
ptr = pc_patch_wkb_set_double(ptr, pa->bounds.xmin);
|
|
ptr = pc_patch_wkb_set_double(ptr, pa->bounds.ymin);
|
|
|
|
/* Point 1 */
|
|
ptr = pc_patch_wkb_set_double(ptr, pa->bounds.xmin);
|
|
ptr = pc_patch_wkb_set_double(ptr, pa->bounds.ymax);
|
|
|
|
/* Point 2 */
|
|
ptr = pc_patch_wkb_set_double(ptr, pa->bounds.xmax);
|
|
ptr = pc_patch_wkb_set_double(ptr, pa->bounds.ymax);
|
|
|
|
/* Point 3 */
|
|
ptr = pc_patch_wkb_set_double(ptr, pa->bounds.xmax);
|
|
ptr = pc_patch_wkb_set_double(ptr, pa->bounds.ymin);
|
|
|
|
/* Point 4 */
|
|
ptr = pc_patch_wkb_set_double(ptr, pa->bounds.xmin);
|
|
ptr = pc_patch_wkb_set_double(ptr, pa->bounds.ymin);
|
|
|
|
if ( wkbsize ) *wkbsize = size;
|
|
return wkb;
|
|
}
|