mirror of
https://git.osgeo.org/gitea/postgis/postgis
synced 2024-10-24 00:52:40 +00:00
4d4cd0ce7b
git-svn-id: http://svn.osgeo.org/postgis/trunk@9021 b70326c6-7e19-0410-871a-916f4a2858ee
2307 lines
60 KiB
C
2307 lines
60 KiB
C
/**********************************************************************
|
|
* $Id: pgsql2shp.c 5450 2010-03-22 19:38:14Z pramsey $
|
|
*
|
|
* PostGIS - Spatial Types for PostgreSQL
|
|
* http://www.postgis.org
|
|
*
|
|
* Copyright (C) 2001-2003 Refractions Research Inc.
|
|
*
|
|
* This is free software; you can redistribute and/or modify it under
|
|
* the terms of the GNU General Public Licence. See the COPYING file.
|
|
*
|
|
**********************************************************************
|
|
*
|
|
* PostGIS to Shapefile converter
|
|
*
|
|
* Original Author: Jeff Lounsbury <jeffloun@refractions.net>
|
|
* Contributions by: Sandro Santilli <strk@keybit.bet>
|
|
* Enhanced by: Mark Cave-Ayland <mark.cave-ayland@siriusit.co.uk>
|
|
*
|
|
**********************************************************************/
|
|
|
|
#include "../postgis_config.h"
|
|
|
|
#include "pgsql2shp-core.h"
|
|
|
|
/* Solaris9 does not provide stdint.h */
|
|
/* #include <stdint.h> */
|
|
#include <inttypes.h>
|
|
|
|
#ifdef HAVE_UNISTD_H /* for getpid() and getopt */
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef __CYGWIN__
|
|
#include <sys/param.h>
|
|
#endif
|
|
|
|
#include "../liblwgeom/liblwgeom.h" /* for LWGEOM struct and funx */
|
|
#include "../liblwgeom/lwgeom_log.h" /* for LWDEBUG macros */
|
|
|
|
/* Maximum DBF field width (according to ARCGIS) */
|
|
#define MAX_DBF_FIELD_SIZE 254
|
|
|
|
|
|
/* Prototypes */
|
|
static int reverse_points(int num_points, double *x, double *y, double *z, double *m);
|
|
static int is_clockwise(int num_points,double *x,double *y,double *z);
|
|
static int is_bigendian(void);
|
|
static SHPObject *create_point(SHPDUMPERSTATE *state, LWPOINT *lwpoint);
|
|
static SHPObject *create_multipoint(SHPDUMPERSTATE *state, LWMPOINT *lwmultipoint);
|
|
static SHPObject *create_polygon(SHPDUMPERSTATE *state, LWPOLY *lwpolygon);
|
|
static SHPObject *create_multipolygon(SHPDUMPERSTATE *state, LWMPOLY *lwmultipolygon);
|
|
static SHPObject *create_linestring(SHPDUMPERSTATE *state, LWLINE *lwlinestring);
|
|
static SHPObject *create_multilinestring(SHPDUMPERSTATE *state, LWMLINE *lwmultilinestring);
|
|
static char *nullDBFValue(char fieldType);
|
|
static int getMaxFieldSize(PGconn *conn, char *schema, char *table, char *fname);
|
|
static int getTableInfo(SHPDUMPERSTATE *state);
|
|
static int projFileCreate(SHPDUMPERSTATE *state);
|
|
|
|
/**
|
|
* @brief Make appropriate formatting of a DBF value based on type.
|
|
* Might return untouched input or pointer to static private
|
|
* buffer: use return value right away.
|
|
*/
|
|
static char * goodDBFValue(char *in, char fieldType);
|
|
|
|
/** @brief Binary to hexewkb conversion function */
|
|
char *convert_bytes_to_hex(uint8_t *ewkb, size_t size);
|
|
|
|
|
|
static SHPObject *
|
|
create_point(SHPDUMPERSTATE *state, LWPOINT *lwpoint)
|
|
{
|
|
SHPObject *obj;
|
|
POINT4D p4d;
|
|
|
|
double *xpts, *ypts, *zpts, *mpts;
|
|
|
|
/* Allocate storage for points */
|
|
xpts = malloc(sizeof(double));
|
|
ypts = malloc(sizeof(double));
|
|
zpts = malloc(sizeof(double));
|
|
mpts = malloc(sizeof(double));
|
|
|
|
/* Grab the point: note getPoint4d will correctly handle
|
|
the case where the POINTs don't contain Z or M coordinates */
|
|
p4d = getPoint4d(lwpoint->point, 0);
|
|
|
|
xpts[0] = p4d.x;
|
|
ypts[0] = p4d.y;
|
|
zpts[0] = p4d.z;
|
|
mpts[0] = p4d.m;
|
|
|
|
LWDEBUGF(4, "Point: %g %g %g %g", xpts[0], ypts[0], zpts[0], mpts[0]);
|
|
|
|
obj = SHPCreateObject(state->outshptype, -1, 0, NULL, NULL, 1, xpts, ypts, zpts, mpts);
|
|
|
|
free(xpts);
|
|
free(ypts);
|
|
free(zpts);
|
|
free(mpts);
|
|
|
|
return obj;
|
|
}
|
|
|
|
static SHPObject *
|
|
create_multipoint(SHPDUMPERSTATE *state, LWMPOINT *lwmultipoint)
|
|
{
|
|
SHPObject *obj;
|
|
POINT4D p4d;
|
|
int i;
|
|
|
|
double *xpts, *ypts, *zpts, *mpts;
|
|
|
|
/* Allocate storage for points */
|
|
xpts = malloc(sizeof(double) * lwmultipoint->ngeoms);
|
|
ypts = malloc(sizeof(double) * lwmultipoint->ngeoms);
|
|
zpts = malloc(sizeof(double) * lwmultipoint->ngeoms);
|
|
mpts = malloc(sizeof(double) * lwmultipoint->ngeoms);
|
|
|
|
/* Grab the points: note getPoint4d will correctly handle
|
|
the case where the POINTs don't contain Z or M coordinates */
|
|
for (i = 0; i < lwmultipoint->ngeoms; i++)
|
|
{
|
|
p4d = getPoint4d(lwmultipoint->geoms[i]->point, 0);
|
|
|
|
xpts[i] = p4d.x;
|
|
ypts[i] = p4d.y;
|
|
zpts[i] = p4d.z;
|
|
mpts[i] = p4d.m;
|
|
|
|
LWDEBUGF(4, "MultiPoint %d - Point: %g %g %g %g", i, xpts[i], ypts[i], zpts[i], mpts[i]);
|
|
}
|
|
|
|
obj = SHPCreateObject(state->outshptype, -1, 0, NULL, NULL, lwmultipoint->ngeoms, xpts, ypts, zpts, mpts);
|
|
|
|
free(xpts);
|
|
free(ypts);
|
|
free(zpts);
|
|
free(mpts);
|
|
|
|
return obj;
|
|
}
|
|
|
|
static SHPObject *
|
|
create_polygon(SHPDUMPERSTATE *state, LWPOLY *lwpolygon)
|
|
{
|
|
SHPObject *obj;
|
|
POINT4D p4d;
|
|
int i, j;
|
|
|
|
double *xpts, *ypts, *zpts, *mpts;
|
|
|
|
int *shpparts, shppointtotal = 0, shppoint = 0;
|
|
|
|
/* Allocate storage for ring pointers */
|
|
shpparts = malloc(sizeof(int) * lwpolygon->nrings);
|
|
|
|
/* First count through all the points in each ring so we now how much memory is required */
|
|
for (i = 0; i < lwpolygon->nrings; i++)
|
|
shppointtotal += lwpolygon->rings[i]->npoints;
|
|
|
|
/* Allocate storage for points */
|
|
xpts = malloc(sizeof(double) * shppointtotal);
|
|
ypts = malloc(sizeof(double) * shppointtotal);
|
|
zpts = malloc(sizeof(double) * shppointtotal);
|
|
mpts = malloc(sizeof(double) * shppointtotal);
|
|
|
|
LWDEBUGF(4, "Total number of points: %d", shppointtotal);
|
|
|
|
/* Iterate through each ring setting up shpparts to point to the beginning of each ring */
|
|
for (i = 0; i < lwpolygon->nrings; i++)
|
|
{
|
|
/* For each ring, store the integer coordinate offset for the start of each ring */
|
|
shpparts[i] = shppoint;
|
|
|
|
for (j = 0; j < lwpolygon->rings[i]->npoints; j++)
|
|
{
|
|
p4d = getPoint4d(lwpolygon->rings[i], j);
|
|
|
|
xpts[shppoint] = p4d.x;
|
|
ypts[shppoint] = p4d.y;
|
|
zpts[shppoint] = p4d.z;
|
|
mpts[shppoint] = p4d.m;
|
|
|
|
LWDEBUGF(4, "Polygon Ring %d - Point: %g %g %g %g", i, xpts[shppoint], ypts[shppoint], zpts[shppoint], mpts[shppoint]);
|
|
|
|
/* Increment the point counter */
|
|
shppoint++;
|
|
}
|
|
|
|
/*
|
|
* First ring should be clockwise,
|
|
* other rings should be counter-clockwise
|
|
*/
|
|
if ( i == 0 )
|
|
{
|
|
if ( ! is_clockwise(lwpolygon->rings[i]->npoints,
|
|
&xpts[shpparts[i]], &ypts[shpparts[i]], NULL) )
|
|
{
|
|
LWDEBUG(4, "Outer ring not clockwise, forcing clockwise\n");
|
|
|
|
reverse_points(lwpolygon->rings[i]->npoints,
|
|
&xpts[shpparts[i]], &ypts[shpparts[i]],
|
|
&zpts[shpparts[i]], &mpts[shpparts[i]]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( is_clockwise(lwpolygon->rings[i]->npoints,
|
|
&xpts[shpparts[i]], &ypts[shpparts[i]], NULL) )
|
|
{
|
|
LWDEBUGF(4, "Inner ring %d not counter-clockwise, forcing counter-clockwise\n", i);
|
|
|
|
reverse_points(lwpolygon->rings[i]->npoints,
|
|
&xpts[shpparts[i]], &ypts[shpparts[i]],
|
|
&zpts[shpparts[i]], &mpts[shpparts[i]]);
|
|
}
|
|
}
|
|
}
|
|
|
|
obj = SHPCreateObject(state->outshptype, -1, lwpolygon->nrings, shpparts, NULL, shppointtotal, xpts, ypts, zpts, mpts);
|
|
|
|
free(xpts);
|
|
free(ypts);
|
|
free(zpts);
|
|
free(mpts);
|
|
free(shpparts);
|
|
|
|
return obj;
|
|
}
|
|
|
|
static SHPObject *
|
|
create_multipolygon(SHPDUMPERSTATE *state, LWMPOLY *lwmultipolygon)
|
|
{
|
|
SHPObject *obj;
|
|
POINT4D p4d;
|
|
int i, j, k;
|
|
|
|
double *xpts, *ypts, *zpts, *mpts;
|
|
|
|
int *shpparts, shppointtotal = 0, shppoint = 0, shpringtotal = 0, shpring = 0;
|
|
|
|
/* NOTE: Multipolygons are stored in shapefiles as Polygon* shapes with multiple outer rings */
|
|
|
|
/* First count through each ring of each polygon so we now know much memory is required */
|
|
for (i = 0; i < lwmultipolygon->ngeoms; i++)
|
|
{
|
|
for (j = 0; j < lwmultipolygon->geoms[i]->nrings; j++)
|
|
{
|
|
shpringtotal++;
|
|
shppointtotal += lwmultipolygon->geoms[i]->rings[j]->npoints;
|
|
}
|
|
}
|
|
|
|
/* Allocate storage for ring pointers */
|
|
shpparts = malloc(sizeof(int) * shpringtotal);
|
|
|
|
/* Allocate storage for points */
|
|
xpts = malloc(sizeof(double) * shppointtotal);
|
|
ypts = malloc(sizeof(double) * shppointtotal);
|
|
zpts = malloc(sizeof(double) * shppointtotal);
|
|
mpts = malloc(sizeof(double) * shppointtotal);
|
|
|
|
LWDEBUGF(4, "Total number of rings: %d Total number of points: %d", shpringtotal, shppointtotal);
|
|
|
|
/* Iterate through each ring of each polygon in turn */
|
|
for (i = 0; i < lwmultipolygon->ngeoms; i++)
|
|
{
|
|
for (j = 0; j < lwmultipolygon->geoms[i]->nrings; j++)
|
|
{
|
|
/* For each ring, store the integer coordinate offset for the start of each ring */
|
|
shpparts[shpring] = shppoint;
|
|
|
|
LWDEBUGF(4, "Ring offset: %d", shpring);
|
|
|
|
for (k = 0; k < lwmultipolygon->geoms[i]->rings[j]->npoints; k++)
|
|
{
|
|
p4d = getPoint4d(lwmultipolygon->geoms[i]->rings[j], k);
|
|
|
|
xpts[shppoint] = p4d.x;
|
|
ypts[shppoint] = p4d.y;
|
|
zpts[shppoint] = p4d.z;
|
|
mpts[shppoint] = p4d.m;
|
|
|
|
LWDEBUGF(4, "MultiPolygon %d Polygon Ring %d - Point: %g %g %g %g", i, j, xpts[shppoint], ypts[shppoint], zpts[shppoint], mpts[shppoint]);
|
|
|
|
/* Increment the point counter */
|
|
shppoint++;
|
|
}
|
|
|
|
/*
|
|
* First ring should be clockwise,
|
|
* other rings should be counter-clockwise
|
|
*/
|
|
if ( j == 0 )
|
|
{
|
|
if ( ! is_clockwise(lwmultipolygon->geoms[i]->rings[j]->npoints,
|
|
&xpts[shpparts[shpring]], &ypts[shpparts[shpring]], NULL) )
|
|
{
|
|
LWDEBUG(4, "Outer ring not clockwise, forcing clockwise\n");
|
|
|
|
reverse_points(lwmultipolygon->geoms[i]->rings[j]->npoints,
|
|
&xpts[shpparts[shpring]], &ypts[shpparts[shpring]],
|
|
&zpts[shpparts[shpring]], &mpts[shpparts[shpring]]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( is_clockwise(lwmultipolygon->geoms[i]->rings[j]->npoints,
|
|
&xpts[shpparts[shpring]], &ypts[shpparts[shpring]], NULL) )
|
|
{
|
|
LWDEBUGF(4, "Inner ring %d not counter-clockwise, forcing counter-clockwise\n", i);
|
|
|
|
reverse_points(lwmultipolygon->geoms[i]->rings[j]->npoints,
|
|
&xpts[shpparts[shpring]], &ypts[shpparts[shpring]],
|
|
&zpts[shpparts[shpring]], &mpts[shpparts[shpring]]);
|
|
}
|
|
}
|
|
|
|
/* Increment the ring counter */
|
|
shpring++;
|
|
}
|
|
}
|
|
|
|
obj = SHPCreateObject(state->outshptype, -1, shpringtotal, shpparts, NULL, shppointtotal, xpts, ypts, zpts, mpts);
|
|
|
|
free(xpts);
|
|
free(ypts);
|
|
free(zpts);
|
|
free(mpts);
|
|
free(shpparts);
|
|
|
|
return obj;
|
|
}
|
|
|
|
static SHPObject *
|
|
create_linestring(SHPDUMPERSTATE *state, LWLINE *lwlinestring)
|
|
{
|
|
SHPObject *obj;
|
|
POINT4D p4d;
|
|
int i;
|
|
|
|
double *xpts, *ypts, *zpts, *mpts;
|
|
|
|
/* Allocate storage for points */
|
|
xpts = malloc(sizeof(double) * lwlinestring->points->npoints);
|
|
ypts = malloc(sizeof(double) * lwlinestring->points->npoints);
|
|
zpts = malloc(sizeof(double) * lwlinestring->points->npoints);
|
|
mpts = malloc(sizeof(double) * lwlinestring->points->npoints);
|
|
|
|
/* Grab the points: note getPoint4d will correctly handle
|
|
the case where the POINTs don't contain Z or M coordinates */
|
|
for (i = 0; i < lwlinestring->points->npoints; i++)
|
|
{
|
|
p4d = getPoint4d(lwlinestring->points, i);
|
|
|
|
xpts[i] = p4d.x;
|
|
ypts[i] = p4d.y;
|
|
zpts[i] = p4d.z;
|
|
mpts[i] = p4d.m;
|
|
|
|
LWDEBUGF(4, "Linestring - Point: %g %g %g %g", i, xpts[i], ypts[i], zpts[i], mpts[i]);
|
|
}
|
|
|
|
obj = SHPCreateObject(state->outshptype, -1, 0, NULL, NULL, lwlinestring->points->npoints, xpts, ypts, zpts, mpts);
|
|
|
|
free(xpts);
|
|
free(ypts);
|
|
free(zpts);
|
|
free(mpts);
|
|
|
|
return obj;
|
|
}
|
|
|
|
static SHPObject *
|
|
create_multilinestring(SHPDUMPERSTATE *state, LWMLINE *lwmultilinestring)
|
|
{
|
|
SHPObject *obj;
|
|
POINT4D p4d;
|
|
int i, j;
|
|
|
|
double *xpts, *ypts, *zpts, *mpts;
|
|
|
|
int *shpparts, shppointtotal = 0, shppoint = 0;
|
|
|
|
/* Allocate storage for ring pointers */
|
|
shpparts = malloc(sizeof(int) * lwmultilinestring->ngeoms);
|
|
|
|
/* First count through all the points in each linestring so we now how much memory is required */
|
|
for (i = 0; i < lwmultilinestring->ngeoms; i++)
|
|
shppointtotal += lwmultilinestring->geoms[i]->points->npoints;
|
|
|
|
LWDEBUGF(3, "Total number of points: %d", shppointtotal);
|
|
|
|
/* Allocate storage for points */
|
|
xpts = malloc(sizeof(double) * shppointtotal);
|
|
ypts = malloc(sizeof(double) * shppointtotal);
|
|
zpts = malloc(sizeof(double) * shppointtotal);
|
|
mpts = malloc(sizeof(double) * shppointtotal);
|
|
|
|
/* Iterate through each linestring setting up shpparts to point to the beginning of each line */
|
|
for (i = 0; i < lwmultilinestring->ngeoms; i++)
|
|
{
|
|
/* For each linestring, store the integer coordinate offset for the start of each line */
|
|
shpparts[i] = shppoint;
|
|
|
|
for (j = 0; j < lwmultilinestring->geoms[i]->points->npoints; j++)
|
|
{
|
|
p4d = getPoint4d(lwmultilinestring->geoms[i]->points, j);
|
|
|
|
xpts[shppoint] = p4d.x;
|
|
ypts[shppoint] = p4d.y;
|
|
zpts[shppoint] = p4d.z;
|
|
mpts[shppoint] = p4d.m;
|
|
|
|
LWDEBUGF(4, "Linestring %d - Point: %g %g %g %g", i, xpts[shppoint], ypts[shppoint], zpts[shppoint], mpts[shppoint]);
|
|
|
|
/* Increment the point counter */
|
|
shppoint++;
|
|
}
|
|
}
|
|
|
|
obj = SHPCreateObject(state->outshptype, -1, lwmultilinestring->ngeoms, shpparts, NULL, shppoint, xpts, ypts, zpts, mpts);
|
|
|
|
free(xpts);
|
|
free(ypts);
|
|
free(zpts);
|
|
free(mpts);
|
|
|
|
return obj;
|
|
}
|
|
|
|
|
|
|
|
/*Reverse the clockwise-ness of the point list... */
|
|
static int
|
|
reverse_points(int num_points, double *x, double *y, double *z, double *m)
|
|
{
|
|
|
|
int i,j;
|
|
double temp;
|
|
j = num_points -1;
|
|
for (i=0; i <num_points; i++)
|
|
{
|
|
if (j <= i)
|
|
{
|
|
break;
|
|
}
|
|
temp = x[j];
|
|
x[j] = x[i];
|
|
x[i] = temp;
|
|
|
|
temp = y[j];
|
|
y[j] = y[i];
|
|
y[i] = temp;
|
|
|
|
if ( z )
|
|
{
|
|
temp = z[j];
|
|
z[j] = z[i];
|
|
z[i] = temp;
|
|
}
|
|
|
|
if ( m )
|
|
{
|
|
temp = m[j];
|
|
m[j] = m[i];
|
|
m[i] = temp;
|
|
}
|
|
|
|
j--;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* Return 1 if the points are in clockwise order */
|
|
static int
|
|
is_clockwise(int num_points, double *x, double *y, double *z)
|
|
{
|
|
int i;
|
|
double x_change,y_change,area;
|
|
double *x_new, *y_new; /* the points, translated to the origin
|
|
* for safer accuracy */
|
|
|
|
x_new = (double *)malloc(sizeof(double) * num_points);
|
|
y_new = (double *)malloc(sizeof(double) * num_points);
|
|
area=0.0;
|
|
x_change = x[0];
|
|
y_change = y[0];
|
|
|
|
for (i=0; i < num_points ; i++)
|
|
{
|
|
x_new[i] = x[i] - x_change;
|
|
y_new[i] = y[i] - y_change;
|
|
}
|
|
|
|
for (i=0; i < num_points - 1; i++)
|
|
{
|
|
/* calculate the area */
|
|
area += (x[i] * y[i+1]) - (y[i] * x[i+1]);
|
|
}
|
|
if (area > 0 )
|
|
{
|
|
free(x_new);
|
|
free(y_new);
|
|
return 0; /*counter-clockwise */
|
|
}
|
|
else
|
|
{
|
|
free(x_new);
|
|
free(y_new);
|
|
return 1; /*clockwise */
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Return the maximum octet_length from given table.
|
|
* Return -1 on error.
|
|
*/
|
|
static int
|
|
getMaxFieldSize(PGconn *conn, char *schema, char *table, char *fname)
|
|
{
|
|
int size;
|
|
char *query;
|
|
PGresult *res;
|
|
|
|
/*( this is ugly: don't forget counting the length */
|
|
/* when changing the fixed query strings ) */
|
|
|
|
if ( schema )
|
|
{
|
|
query = (char *)malloc(strlen(fname)+strlen(table)+
|
|
strlen(schema)+46);
|
|
sprintf(query,
|
|
"select max(octet_length(\"%s\"::text)) from \"%s\".\"%s\"",
|
|
fname, schema, table);
|
|
}
|
|
else
|
|
{
|
|
query = (char *)malloc(strlen(fname)+strlen(table)+46);
|
|
sprintf(query,
|
|
"select max(octet_length(\"%s\"::text)) from \"%s\"",
|
|
fname, table);
|
|
}
|
|
|
|
LWDEBUGF(4, "maxFieldLenQuery: %s\n", query);
|
|
|
|
res = PQexec(conn, query);
|
|
free(query);
|
|
if ( ! res || PQresultStatus(res) != PGRES_TUPLES_OK )
|
|
{
|
|
printf( _("Querying for maximum field length: %s"),
|
|
PQerrorMessage(conn));
|
|
return -1;
|
|
}
|
|
|
|
if (PQntuples(res) <= 0 )
|
|
{
|
|
PQclear(res);
|
|
return -1;
|
|
}
|
|
size = atoi(PQgetvalue(res, 0, 0));
|
|
PQclear(res);
|
|
return size;
|
|
}
|
|
|
|
static int
|
|
is_bigendian(void)
|
|
{
|
|
int test = 1;
|
|
|
|
if ( (((char *)(&test))[0]) == 1)
|
|
{
|
|
return 0; /*NDR (little_endian) */
|
|
}
|
|
else
|
|
{
|
|
return 1; /*XDR (big_endian) */
|
|
}
|
|
}
|
|
|
|
char *
|
|
shapetypename(int num)
|
|
{
|
|
switch (num)
|
|
{
|
|
case SHPT_NULL:
|
|
return "Null Shape";
|
|
case SHPT_POINT:
|
|
return "Point";
|
|
case SHPT_ARC:
|
|
return "PolyLine";
|
|
case SHPT_POLYGON:
|
|
return "Polygon";
|
|
case SHPT_MULTIPOINT:
|
|
return "MultiPoint";
|
|
case SHPT_POINTZ:
|
|
return "PointZ";
|
|
case SHPT_ARCZ:
|
|
return "PolyLineZ";
|
|
case SHPT_POLYGONZ:
|
|
return "PolygonZ";
|
|
case SHPT_MULTIPOINTZ:
|
|
return "MultiPointZ";
|
|
case SHPT_POINTM:
|
|
return "PointM";
|
|
case SHPT_ARCM:
|
|
return "PolyLineM";
|
|
case SHPT_POLYGONM:
|
|
return "PolygonM";
|
|
case SHPT_MULTIPOINTM:
|
|
return "MultiPointM";
|
|
case SHPT_MULTIPATCH:
|
|
return "MultiPatch";
|
|
default:
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
|
|
/* This is taken and adapted from dbfopen.c of shapelib */
|
|
static char *
|
|
nullDBFValue(char fieldType)
|
|
{
|
|
switch (fieldType)
|
|
{
|
|
case FTInteger:
|
|
case FTDouble:
|
|
/* NULL numeric fields have value "****************" */
|
|
return "****************";
|
|
|
|
case FTDate:
|
|
/* NULL date fields have value "00000000" */
|
|
return " ";
|
|
|
|
case FTLogical:
|
|
/* NULL boolean fields have value "?" */
|
|
return "?";
|
|
|
|
default:
|
|
/* empty string fields are considered NULL */
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Make appropriate formatting of a DBF value based on type.
|
|
* Might return untouched input or pointer to static private
|
|
* buffer: use return value right away.
|
|
*/
|
|
static char *
|
|
goodDBFValue(char *in, char fieldType)
|
|
{
|
|
/*
|
|
* We only work on FTLogical and FTDate.
|
|
* FTLogical is 1 byte, FTDate is 8 byte (YYYYMMDD)
|
|
* We allocate space for 9 bytes to take
|
|
* terminating null into account
|
|
*/
|
|
static char buf[9];
|
|
|
|
switch (fieldType)
|
|
{
|
|
case FTLogical:
|
|
buf[0] = toupper(in[0]);
|
|
buf[1]='\0';
|
|
return buf;
|
|
case FTDate:
|
|
buf[0]=in[0]; /* Y */
|
|
buf[1]=in[1]; /* Y */
|
|
buf[2]=in[2]; /* Y */
|
|
buf[3]=in[3]; /* Y */
|
|
buf[4]=in[5]; /* M */
|
|
buf[5]=in[6]; /* M */
|
|
buf[6]=in[8]; /* D */
|
|
buf[7]=in[9]; /* D */
|
|
buf[8]='\0';
|
|
return buf;
|
|
default:
|
|
return in;
|
|
}
|
|
}
|
|
|
|
char *convert_bytes_to_hex(uint8_t *ewkb, size_t size)
|
|
{
|
|
int i;
|
|
char *hexewkb;
|
|
|
|
/* Convert the byte stream to a hex string using liblwgeom's deparse_hex function */
|
|
hexewkb = malloc(size * 2 + 1);
|
|
for (i=0; i<size; ++i) deparse_hex(ewkb[i], &hexewkb[i * 2]);
|
|
hexewkb[size * 2] = '\0';
|
|
|
|
return hexewkb;
|
|
}
|
|
|
|
/**
|
|
* @brief Creates ESRI .prj file for this shp output
|
|
* It looks in the spatial_ref_sys table and outputs the srtext field for this data
|
|
* If data is a table will use geometry_columns, if a query or view will read SRID from query output.
|
|
* @warning Will give warning and not output a .prj file if SRID is -1, Unknown, mixed SRIDS or not found in spatial_ref_sys. The dbf and shp will still be output.
|
|
*/
|
|
static int
|
|
projFileCreate(SHPDUMPERSTATE *state)
|
|
{
|
|
FILE *fp;
|
|
char *pszFullname, *pszBasename;
|
|
int i, result;
|
|
|
|
char *pszFilename = state->shp_file;
|
|
char *schema = state->schema;
|
|
char *table = state->table;
|
|
char *geo_col_name = state->geo_col_name;
|
|
|
|
char *srtext;
|
|
char *query;
|
|
char *esc_schema;
|
|
char *esc_table;
|
|
char *esc_geo_col_name;
|
|
|
|
int error;
|
|
PGresult *res;
|
|
int size;
|
|
|
|
/***********
|
|
*** I'm multiplying by 2 instead of 3 because I am too lazy to figure out how many characters to add
|
|
*** after escaping if any **/
|
|
size = 1000;
|
|
if ( schema )
|
|
{
|
|
size += 3 * strlen(schema);
|
|
}
|
|
size += 1000;
|
|
esc_table = (char *) malloc(3 * strlen(table) + 1);
|
|
esc_geo_col_name = (char *) malloc(3 * strlen(geo_col_name) + 1);
|
|
PQescapeStringConn(state->conn, esc_table, table, strlen(table), &error);
|
|
PQescapeStringConn(state->conn, esc_geo_col_name, geo_col_name, strlen(geo_col_name), &error);
|
|
|
|
/** make our address space large enough to hold query with table/schema **/
|
|
query = (char *) malloc(size);
|
|
if ( ! query ) return 0; /* out of virtual memory */
|
|
|
|
/**************************************************
|
|
* Get what kind of spatial ref is the selected geometry field
|
|
* We first check the geometry_columns table for a match and then if no match do a distinct against the table
|
|
* NOTE: COALESCE does a short-circuit check returning the faster query result and skipping the second if first returns something
|
|
* Escaping quotes in the schema and table in query may not be necessary except to prevent malicious attacks
|
|
* or should someone be crazy enough to have quotes or other weird character in their table, column or schema names
|
|
**************************************************/
|
|
if ( schema )
|
|
{
|
|
esc_schema = (char *) malloc(2 * strlen(schema) + 1);
|
|
PQescapeStringConn(state->conn, esc_schema, schema, strlen(schema), &error);
|
|
sprintf(query, "SELECT COALESCE((SELECT sr.srtext "
|
|
" FROM geometry_columns As gc INNER JOIN spatial_ref_sys sr ON sr.srid = gc.srid "
|
|
" WHERE gc.f_table_schema = '%s' AND gc.f_table_name = '%s' AND gc.f_geometry_column = '%s' LIMIT 1), "
|
|
" (SELECT CASE WHEN COUNT(DISTINCT sr.srid) > 1 THEN 'm' ELSE MAX(sr.srtext) END As srtext "
|
|
" FROM \"%s\".\"%s\" As g INNER JOIN spatial_ref_sys sr ON sr.srid = ST_SRID((g.\"%s\")::geometry)) , ' ') As srtext ",
|
|
esc_schema, esc_table,esc_geo_col_name, schema, table, geo_col_name);
|
|
free(esc_schema);
|
|
}
|
|
else
|
|
{
|
|
sprintf(query, "SELECT COALESCE((SELECT sr.srtext "
|
|
" FROM geometry_columns As gc INNER JOIN spatial_ref_sys sr ON sr.srid = gc.srid "
|
|
" WHERE gc.f_table_name = '%s' AND gc.f_geometry_column = '%s' AND pg_table_is_visible((gc.f_table_schema || '.' || gc.f_table_name)::regclass) LIMIT 1), "
|
|
" (SELECT CASE WHEN COUNT(DISTINCT sr.srid) > 1 THEN 'm' ELSE MAX(sr.srtext) END as srtext "
|
|
" FROM \"%s\" As g INNER JOIN spatial_ref_sys sr ON sr.srid = ST_SRID((g.\"%s\")::geometry)), ' ') As srtext ",
|
|
esc_table, esc_geo_col_name, table, geo_col_name);
|
|
}
|
|
|
|
LWDEBUGF(3,"%s\n",query);
|
|
free(esc_table);
|
|
free(esc_geo_col_name);
|
|
|
|
res = PQexec(state->conn, query);
|
|
|
|
if ( ! res || PQresultStatus(res) != PGRES_TUPLES_OK )
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("WARNING: Could not execute prj query: %s"), PQresultErrorMessage(res));
|
|
PQclear(res);
|
|
free(query);
|
|
return SHPDUMPERWARN;
|
|
}
|
|
|
|
for (i=0; i < PQntuples(res); i++)
|
|
{
|
|
srtext = PQgetvalue(res, i, 0);
|
|
if (strcmp(srtext,"m") == 0)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("WARNING: Mixed set of spatial references. No prj file will be generated"));
|
|
PQclear(res);
|
|
free(query);
|
|
return SHPDUMPERWARN;
|
|
}
|
|
else
|
|
{
|
|
if (srtext[0] == ' ')
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("WARNING: Cannot determine spatial reference (empty table or unknown spatial ref). No prj file will be generated."));
|
|
PQclear(res);
|
|
free(query);
|
|
return SHPDUMPERWARN;
|
|
}
|
|
else
|
|
{
|
|
/* -------------------------------------------------------------------- */
|
|
/* Compute the base (layer) name. If there is any extension */
|
|
/* on the passed in filename we will strip it off. */
|
|
/* -------------------------------------------------------------------- */
|
|
pszBasename = (char *) malloc(strlen(pszFilename)+5);
|
|
strcpy( pszBasename, pszFilename );
|
|
for ( i = strlen(pszBasename)-1;
|
|
i > 0 && pszBasename[i] != '.' && pszBasename[i] != '/'
|
|
&& pszBasename[i] != '\\';
|
|
i-- ) {}
|
|
|
|
if ( pszBasename[i] == '.' )
|
|
pszBasename[i] = '\0';
|
|
|
|
pszFullname = (char *) malloc(strlen(pszBasename) + 5);
|
|
sprintf( pszFullname, "%s.prj", pszBasename );
|
|
free( pszBasename );
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create the file. */
|
|
/* -------------------------------------------------------------------- */
|
|
fp = fopen( pszFullname, "wb" );
|
|
if ( fp == NULL )
|
|
{
|
|
return 0;
|
|
}
|
|
result = fputs (srtext,fp);
|
|
LWDEBUGF(3, "\n result %d proj SRText is %s .\n", result, srtext);
|
|
fclose( fp );
|
|
free( pszFullname );
|
|
}
|
|
}
|
|
}
|
|
PQclear(res);
|
|
free(query);
|
|
return SHPDUMPEROK;
|
|
}
|
|
|
|
|
|
static int
|
|
getTableInfo(SHPDUMPERSTATE *state)
|
|
{
|
|
|
|
/* Get some more information from the table:
|
|
- count = total number of geometries/geographies in the table
|
|
|
|
and if we have found a suitable geometry column:
|
|
|
|
- max = maximum number of dimensions within the geometry/geography column
|
|
- geometrytype = string representing the geometry/geography type, e.g. POINT
|
|
|
|
Since max/geometrytype already require a sequential scan of the table, we may as
|
|
well get the row count too.
|
|
*/
|
|
|
|
PGresult *res;
|
|
char *query;
|
|
int tmpint;
|
|
|
|
|
|
if (state->geo_col_name)
|
|
{
|
|
/* Include geometry information */
|
|
if (state->schema)
|
|
{
|
|
query = malloc(150 + 4 * strlen(state->geo_col_name) + strlen(state->schema) + strlen(state->table));
|
|
|
|
sprintf(query, "SELECT count(\"%s\"), max(ST_zmflag(\"%s\"::geometry)), geometrytype(\"%s\"::geometry) FROM \"%s\".\"%s\" GROUP BY geometrytype(\"%s\"::geometry)",
|
|
state->geo_col_name, state->geo_col_name, state->geo_col_name, state->schema, state->table, state->geo_col_name);
|
|
}
|
|
else
|
|
{
|
|
query = malloc(150 + 4 * strlen(state->geo_col_name) + strlen(state->table));
|
|
|
|
sprintf(query, "SELECT count(\"%s\"), max(ST_zmflag(\"%s\"::geometry)), geometrytype(\"%s\"::geometry) FROM \"%s\" GROUP BY geometrytype(\"%s\"::geometry)",
|
|
state->geo_col_name, state->geo_col_name, state->geo_col_name, state->table, state->geo_col_name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise... just a row count will do */
|
|
if (state->schema)
|
|
{
|
|
query = malloc(40 + strlen(state->schema) + strlen(state->table));
|
|
|
|
sprintf(query, "SELECT count(1) FROM \"%s\".\"%s\"", state->schema, state->table);
|
|
}
|
|
else
|
|
{
|
|
query = malloc(40 + strlen(state->table));
|
|
|
|
sprintf(query, "SELECT count(1) FROM \"%s\"", state->table);
|
|
}
|
|
}
|
|
|
|
LWDEBUGF(3, "Table metadata query: %s\n", query);
|
|
|
|
res = PQexec(state->conn, query);
|
|
free(query);
|
|
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("ERROR: Could not execute table metadata query: %s"), PQresultErrorMessage(res));
|
|
PQclear(res);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
/* Make sure we error if the table is empty */
|
|
if (PQntuples(res) == 0)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("ERROR: Could not determine table metadata (empty table)"));
|
|
PQclear(res);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
/* If we have a geo* column, get the dimension, type and count information */
|
|
if (state->geo_col_name)
|
|
{
|
|
/* If a table has a geometry column containing mixed types then
|
|
the metadata query will return multiple rows. We need to cycle
|
|
through all rows to determine if the type combinations are valid.
|
|
|
|
Note that if we find a combination of a MULTI and non-MULTI geometry
|
|
of the same type, we always choose MULTI to ensure that everything
|
|
gets output correctly. The create_* conversion functions are clever
|
|
enough to up-convert the non-MULTI geometry to a MULTI in this case. */
|
|
|
|
int dummy, i;
|
|
uint8_t type = 0;
|
|
int typefound = 0, typemismatch = 0;
|
|
|
|
state->rowcount = 0;
|
|
|
|
for (i = 0; i < PQntuples(res); i++)
|
|
{
|
|
geometry_type_from_string(PQgetvalue(res, i, 2), &type, &dummy, &dummy);
|
|
|
|
/* We can always set typefound to that of the first column found */
|
|
if (!typefound)
|
|
typefound = type;
|
|
|
|
switch (type)
|
|
{
|
|
case MULTIPOINTTYPE:
|
|
if (typefound != MULTIPOINTTYPE && typefound != POINTTYPE)
|
|
typemismatch = 1;
|
|
else
|
|
typefound = MULTIPOINTTYPE;
|
|
break;
|
|
|
|
case MULTILINETYPE:
|
|
if (typefound != MULTILINETYPE && typefound != LINETYPE)
|
|
typemismatch = 1;
|
|
else
|
|
typefound = MULTILINETYPE;
|
|
break;
|
|
|
|
case MULTIPOLYGONTYPE:
|
|
if (typefound != MULTIPOLYGONTYPE && typefound != POLYGONTYPE)
|
|
typemismatch = 1;
|
|
else
|
|
typefound = MULTIPOLYGONTYPE;
|
|
break;
|
|
|
|
case POINTTYPE:
|
|
if (typefound != POINTTYPE && typefound != MULTIPOINTTYPE)
|
|
typemismatch = 1;
|
|
else if (!lwtype_is_collection(type))
|
|
typefound = POINTTYPE;
|
|
break;
|
|
|
|
case LINETYPE:
|
|
if (typefound != LINETYPE && typefound != MULTILINETYPE)
|
|
typemismatch = 1;
|
|
else if (!lwtype_is_collection(type))
|
|
typefound = LINETYPE;
|
|
break;
|
|
|
|
case POLYGONTYPE:
|
|
if (typefound != POLYGONTYPE && typefound != MULTIPOLYGONTYPE)
|
|
typemismatch = 1;
|
|
else if (!lwtype_is_collection(type))
|
|
typefound = POLYGONTYPE;
|
|
break;
|
|
}
|
|
|
|
/* Update the rowcount for each type */
|
|
state->rowcount += atoi(PQgetvalue(res, i, 0));
|
|
}
|
|
|
|
/* Flag an error if the table contains incompatible geometry combinations */
|
|
if (typemismatch)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("ERROR: Incompatible mixed geometry types in table"));
|
|
PQclear(res);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
/* Set up the dimension output type (note: regardless of how many rows
|
|
the table metadata query returns, this value will be the same. But
|
|
we'll choose to use the first value anyway) */
|
|
tmpint = atoi(PQgetvalue(res, 0, 1));
|
|
switch (tmpint)
|
|
{
|
|
case 0:
|
|
state->outtype = 's';
|
|
break;
|
|
case 1:
|
|
state->outtype = 'm';
|
|
break;
|
|
default:
|
|
state->outtype = 'z';
|
|
break;
|
|
}
|
|
|
|
/* Set up the shapefile output type based upon the dimension information */
|
|
switch (typefound)
|
|
{
|
|
case POINTTYPE:
|
|
switch(state->outtype)
|
|
{
|
|
case 'z':
|
|
state->outshptype = SHPT_POINTZ;
|
|
break;
|
|
|
|
case 'm':
|
|
state->outshptype = SHPT_POINTM;
|
|
break;
|
|
|
|
default:
|
|
state->outshptype = SHPT_POINT;
|
|
}
|
|
break;
|
|
|
|
case MULTIPOINTTYPE:
|
|
switch(state->outtype)
|
|
{
|
|
case 'z':
|
|
state->outshptype = SHPT_MULTIPOINTZ;
|
|
break;
|
|
|
|
case 'm':
|
|
state->outshptype = SHPT_MULTIPOINTM;
|
|
break;
|
|
|
|
default:
|
|
state->outshptype = SHPT_MULTIPOINT;
|
|
}
|
|
break;
|
|
|
|
case LINETYPE:
|
|
case MULTILINETYPE:
|
|
switch(state->outtype)
|
|
{
|
|
case 'z':
|
|
state->outshptype = SHPT_ARCZ;
|
|
break;
|
|
|
|
case 'm':
|
|
state->outshptype = SHPT_ARCM;
|
|
break;
|
|
|
|
default:
|
|
state->outshptype = SHPT_ARC;
|
|
}
|
|
break;
|
|
|
|
case POLYGONTYPE:
|
|
case MULTIPOLYGONTYPE:
|
|
switch(state->outtype)
|
|
{
|
|
case 'z':
|
|
state->outshptype = SHPT_POLYGONZ;
|
|
break;
|
|
|
|
case 'm':
|
|
state->outshptype = SHPT_POLYGONM;
|
|
break;
|
|
|
|
default:
|
|
state->outshptype = SHPT_POLYGON;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Without a geo* column the total is simply the first (COUNT) column */
|
|
state->rowcount = atoi(PQgetvalue(res, 0, 0));
|
|
}
|
|
|
|
/* Dispose of the result set */
|
|
PQclear(res);
|
|
|
|
return SHPDUMPEROK;
|
|
}
|
|
|
|
|
|
/* Default configuration settings */
|
|
void
|
|
set_dumper_config_defaults(SHPDUMPERCONFIG *config)
|
|
{
|
|
config->conn = malloc(sizeof(SHPCONNECTIONCONFIG));
|
|
config->conn->host = NULL;
|
|
config->conn->port = NULL;
|
|
config->conn->database = NULL;
|
|
config->conn->username = NULL;
|
|
config->conn->password = NULL;
|
|
|
|
config->table = NULL;
|
|
config->schema = NULL;
|
|
config->usrquery = NULL;
|
|
config->binary = 0;
|
|
config->shp_file = NULL;
|
|
config->dswitchprovided = 0;
|
|
config->includegid = 0;
|
|
config->unescapedattrs = 0;
|
|
config->geo_col_name = NULL;
|
|
config->keep_fieldname_case = 0;
|
|
config->fetchsize = 100;
|
|
config->column_map_filename = NULL;
|
|
}
|
|
|
|
/**
|
|
* Read the content of filename into a symbol map stored
|
|
* at state->column_map_filename.
|
|
*
|
|
* The content of the file is lines of two names separated by
|
|
* white space and no trailing or leading space:
|
|
*
|
|
* COLUMNNAME DBFFIELD1
|
|
* AVERYLONGCOLUMNNAME DBFFIELD2
|
|
*
|
|
* etc.
|
|
*
|
|
* It is the reponsibility of the caller to reclaim the allocated space
|
|
* as follows:
|
|
*
|
|
* free(state->column_map_pgfieldnames[]) to free the column names
|
|
* free(state->column_map_dbffieldnames[]) to free the dbf field names
|
|
*
|
|
* @param state : container of state->column_map where the malloc'd
|
|
* symbol map will be stored.
|
|
*/
|
|
static int
|
|
read_column_map(SHPDUMPERSTATE *state)
|
|
{
|
|
FILE *fptr;
|
|
char linebuffer[1024];
|
|
char *tmpstr, *tmpptr;
|
|
int curmapsize, fieldnamesize;
|
|
|
|
/* Read column map file and load the column_map_dbffieldnames and column_map_pgfieldnames
|
|
arrays */
|
|
fptr = fopen(state->config->column_map_filename, "r");
|
|
if (!fptr)
|
|
{
|
|
/* Return an error */
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("ERROR: Unable to open column map file %s"), state->config->column_map_filename);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
/* First count how many columns we have... */
|
|
while (fgets(linebuffer, 1024, fptr) != NULL)
|
|
state->column_map_size++;
|
|
|
|
/* Now we know the final size, allocate the arrays and load the data */
|
|
fseek(fptr, 0, SEEK_SET);
|
|
state->column_map_pgfieldnames = (char **)malloc(sizeof(char *) * state->column_map_size);
|
|
state->column_map_dbffieldnames = (char **)malloc(sizeof(char *) * state->column_map_size);
|
|
|
|
/* Read in a line at a time... */
|
|
curmapsize = 0;
|
|
while (fgets(linebuffer, 1024, fptr) != NULL)
|
|
{
|
|
/* Split into two separate strings - pgfieldname followed by dbffieldname */
|
|
|
|
/* First locate end of first column (pgfieldname) */
|
|
for (tmpptr = tmpstr = linebuffer; *tmpptr != '\t' && *tmpptr != '\n' && *tmpptr != ' ' && *tmpptr != '\0'; tmpptr++);
|
|
fieldnamesize = tmpptr - tmpstr;
|
|
|
|
/* Allocate memory and copy the string ensuring it is terminated */
|
|
state->column_map_pgfieldnames[curmapsize] = malloc(fieldnamesize + 1);
|
|
strncpy(state->column_map_pgfieldnames[curmapsize], tmpstr, fieldnamesize);
|
|
state->column_map_pgfieldnames[curmapsize][fieldnamesize] = '\0';
|
|
|
|
/* Now swallow up any whitespace */
|
|
for (tmpstr = tmpptr; *tmpptr == '\t' || *tmpptr == '\n' || *tmpptr == ' '; tmpptr++);
|
|
|
|
/* Finally locate end of second column (dbffieldname) */
|
|
for (tmpstr = tmpptr; *tmpptr != '\t' && *tmpptr != '\n' && *tmpptr != ' ' && *tmpptr != '\0'; tmpptr++);
|
|
fieldnamesize = tmpptr - tmpstr;
|
|
|
|
/* Allocate memory and copy the string ensuring it is terminated */
|
|
state->column_map_dbffieldnames[curmapsize] = malloc(fieldnamesize + 1);
|
|
strncpy(state->column_map_dbffieldnames[curmapsize], tmpstr, fieldnamesize);
|
|
state->column_map_dbffieldnames[curmapsize][fieldnamesize] = '\0';
|
|
|
|
/* Error out if the dbffieldname is > 10 chars */
|
|
if (strlen(state->column_map_dbffieldnames[curmapsize]) > 10)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("ERROR: column map file specifies a DBF field name \"%s\" which is longer than 10 characters"), state->column_map_dbffieldnames[curmapsize]);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
curmapsize++;
|
|
}
|
|
|
|
fclose(fptr);
|
|
|
|
/* Done; return success */
|
|
return SHPDUMPEROK;
|
|
}
|
|
|
|
|
|
/* Create a new shapefile state object */
|
|
SHPDUMPERSTATE *
|
|
ShpDumperCreate(SHPDUMPERCONFIG *config)
|
|
{
|
|
SHPDUMPERSTATE *state;
|
|
|
|
/* Create a new state object and assign the config to it */
|
|
state = malloc(sizeof(SHPDUMPERSTATE));
|
|
state->config = config;
|
|
|
|
/* Set any state defaults */
|
|
state->conn = NULL;
|
|
state->outtype = 's';
|
|
state->geom_oid = 0;
|
|
state->geog_oid = 0;
|
|
state->schema = NULL;
|
|
state->table = NULL;
|
|
state->geo_col_name = NULL;
|
|
state->fetch_query = NULL;
|
|
state->main_scan_query = NULL;
|
|
state->dbffieldnames = NULL;
|
|
state->dbffieldtypes = NULL;
|
|
state->pgfieldnames = NULL;
|
|
state->column_map_pgfieldnames = NULL;
|
|
state->column_map_dbffieldnames = NULL;
|
|
state->column_map_size = 0;
|
|
state->big_endian = is_bigendian();
|
|
|
|
return state;
|
|
}
|
|
|
|
/* Generate the database connection string used by a state */
|
|
char *
|
|
ShpDumperGetConnectionStringFromConn(SHPCONNECTIONCONFIG *conn)
|
|
{
|
|
char *connstring;
|
|
int connlen;
|
|
|
|
connlen = 64 +
|
|
(conn->host ? strlen(conn->host) : 0) + (conn->port ? strlen(conn->port) : 0) +
|
|
(conn->username ? strlen(conn->username) : 0) + (conn->password ? strlen(conn->password) : 0) +
|
|
(conn->database ? strlen(conn->database) : 0);
|
|
|
|
connstring = malloc(connlen);
|
|
memset(connstring, 0, connlen);
|
|
|
|
if (conn->host)
|
|
{
|
|
strcat(connstring, " host=");
|
|
strcat(connstring, conn->host);
|
|
}
|
|
|
|
if (conn->port)
|
|
{
|
|
strcat(connstring, " port=");
|
|
strcat(connstring, conn->port);
|
|
}
|
|
|
|
if (conn->username)
|
|
{
|
|
strcat(connstring, " user=");
|
|
strcat(connstring, conn->username);
|
|
}
|
|
|
|
if (conn->password)
|
|
{
|
|
strcat(connstring, " password='");
|
|
strcat(connstring, conn->password);
|
|
strcat(connstring, "'");
|
|
}
|
|
|
|
if (conn->database)
|
|
{
|
|
strcat(connstring, " dbname=");
|
|
strcat(connstring, conn->database);
|
|
}
|
|
|
|
return connstring;
|
|
}
|
|
|
|
/* Connect to the database and identify the version of PostGIS (and any other
|
|
capabilities required) */
|
|
int
|
|
ShpDumperConnectDatabase(SHPDUMPERSTATE *state)
|
|
{
|
|
PGresult *res;
|
|
|
|
char *connstring, *tmpvalue;
|
|
|
|
/* Generate the PostgreSQL connection string */
|
|
connstring = ShpDumperGetConnectionStringFromConn(state->config->conn);
|
|
|
|
/* Connect to the database */
|
|
state->conn = PQconnectdb(connstring);
|
|
if (PQstatus(state->conn) == CONNECTION_BAD)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, "%s", PQerrorMessage(state->conn));
|
|
free(connstring);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
/* Set datestyle to ISO */
|
|
res = PQexec(state->conn, "SET DATESTYLE='ISO'");
|
|
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, "%s", PQresultErrorMessage(res));
|
|
PQclear(res);
|
|
free(connstring);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
PQclear(res);
|
|
|
|
/* Retrieve PostGIS major version */
|
|
res = PQexec(state->conn, "SELECT postgis_version()");
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, "%s", PQresultErrorMessage(res));
|
|
PQclear(res);
|
|
free(connstring);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
tmpvalue = PQgetvalue(res, 0, 0);
|
|
state->pgis_major_version = atoi(tmpvalue);
|
|
|
|
PQclear(res);
|
|
|
|
/* Find the OID for the geometry type */
|
|
res = PQexec(state->conn, "SELECT oid FROM pg_type WHERE typname = 'geometry'");
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("Error looking up geometry oid: %s"), PQresultErrorMessage(res));
|
|
PQclear(res);
|
|
free(connstring);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
if (PQntuples(res) > 0)
|
|
{
|
|
tmpvalue = PQgetvalue(res, 0, 0);
|
|
state->geom_oid = atoi(tmpvalue);
|
|
}
|
|
else
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("Geometry type unknown (have you enabled postgis?)"));
|
|
PQclear(res);
|
|
free(connstring);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
PQclear(res);
|
|
|
|
/* Find the OID for the geography type */
|
|
res = PQexec(state->conn, "SELECT oid FROM pg_type WHERE typname = 'geography'");
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("Error looking up geography oid: %s"), PQresultErrorMessage(res));
|
|
PQclear(res);
|
|
free(connstring);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
if (PQntuples(res) > 0)
|
|
{
|
|
/* Old databases don't have a geography type, so don't fail if we don't find it */
|
|
tmpvalue = PQgetvalue(res, 0, 0);
|
|
state->geog_oid = atoi(tmpvalue);
|
|
}
|
|
|
|
PQclear(res);
|
|
|
|
free(connstring);
|
|
|
|
return SHPDUMPEROK;
|
|
}
|
|
|
|
|
|
/* Open the specified table in preparation for extracting rows */
|
|
int
|
|
ShpDumperOpenTable(SHPDUMPERSTATE *state)
|
|
{
|
|
PGresult *res;
|
|
|
|
char buf[256];
|
|
char *query;
|
|
int gidfound = 0, i, j, ret, status;
|
|
|
|
|
|
/* Open the column map if one was specified */
|
|
if (state->config->column_map_filename)
|
|
{
|
|
ret = read_column_map(state);
|
|
if (ret != SHPDUMPEROK)
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
/* If a user-defined query has been specified, create and point the state to our new table */
|
|
if (state->config->usrquery)
|
|
{
|
|
state->table = malloc(20 + 20); // string + max long precision
|
|
sprintf(state->table, "__pgsql2shp%lu_tmp_table", (long)getpid());
|
|
|
|
query = malloc(32 + strlen(state->table) + strlen(state->config->usrquery));
|
|
|
|
sprintf(query, "CREATE TEMP TABLE \"%s\" AS %s", state->table, state->config->usrquery);
|
|
res = PQexec(state->conn, query);
|
|
free(query);
|
|
|
|
/* Execute the code to create the table */
|
|
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("Error executing user query: %s"), PQresultErrorMessage(res));
|
|
PQclear(res);
|
|
return SHPDUMPERERR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Simply point the state to copies of the supplied schema and table */
|
|
state->table = strdup(state->config->table);
|
|
if (state->config->schema)
|
|
state->schema = strdup(state->config->schema);
|
|
}
|
|
|
|
|
|
/* Get the list of columns and their types for the selected table */
|
|
if (state->schema)
|
|
{
|
|
query = malloc(250 + strlen(state->schema) + strlen(state->table));
|
|
|
|
sprintf(query, "SELECT a.attname, a.atttypid, "
|
|
"a.atttypmod, a.attlen FROM "
|
|
"pg_attribute a, pg_class c, pg_namespace n WHERE "
|
|
"n.nspname = '%s' AND a.attrelid = c.oid AND "
|
|
"n.oid = c.relnamespace AND "
|
|
"a.atttypid != 0 AND "
|
|
"a.attnum > 0 AND c.relname = '%s'", state->schema, state->table);
|
|
}
|
|
else
|
|
{
|
|
query = malloc(250 + strlen(state->table));
|
|
|
|
sprintf(query, "SELECT a.attname, a.atttypid, "
|
|
"a.atttypmod, a.attlen FROM "
|
|
"pg_attribute a, pg_class c WHERE "
|
|
"a.attrelid = c.oid and a.attnum > 0 AND "
|
|
"a.atttypid != 0 AND "
|
|
"c.relname = '%s'", state->table);
|
|
}
|
|
|
|
LWDEBUGF(3, "query is: %s\n", query);
|
|
|
|
res = PQexec(state->conn, query);
|
|
free(query);
|
|
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("Error querying for attributes: %s"), PQresultErrorMessage(res));
|
|
PQclear(res);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
if (!PQntuples(res))
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("Table %s does not exist"), state->table);
|
|
PQclear(res);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
/* If a shapefile name was specified, use it. Otherwise simply use the table name. */
|
|
if (state->config->shp_file != NULL)
|
|
state->shp_file = state->config->shp_file;
|
|
else
|
|
state->shp_file = state->table;
|
|
|
|
/* Create the dbf file */
|
|
state->dbf = DBFCreate(state->shp_file);
|
|
if (!state->dbf)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("Could not create dbf file %s"), state->shp_file);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
/*
|
|
* Scan the result setting fields to be returned in mainscan
|
|
* query, filling the type_ary, and creating .dbf and .shp files.
|
|
*/
|
|
state->dbffieldnames = malloc(sizeof(char *) * PQntuples(res));
|
|
state->dbffieldtypes = malloc(sizeof(int) * PQntuples(res));
|
|
state->pgfieldnames = malloc(sizeof(char *) * PQntuples(res));
|
|
state->pgfieldlens = malloc(sizeof(int) * PQntuples(res));
|
|
state->pgfieldtypmods = malloc(sizeof(int) * PQntuples(res));
|
|
state->fieldcount = 0;
|
|
int tmpint = 1;
|
|
|
|
for (i = 0; i < PQntuples(res); i++)
|
|
{
|
|
char *ptr;
|
|
|
|
int pgfieldtype, pgtypmod, pgfieldlen;
|
|
char *pgfieldname;
|
|
|
|
int dbffieldtype, dbffieldsize, dbffielddecs;
|
|
char *dbffieldname;
|
|
|
|
pgfieldname = PQgetvalue(res, i, 0);
|
|
pgfieldtype = atoi(PQgetvalue(res, i, 1));
|
|
pgtypmod = atoi(PQgetvalue(res, i, 2));
|
|
pgfieldlen = atoi(PQgetvalue(res, i, 3));
|
|
dbffieldtype = -1;
|
|
dbffieldsize = 0;
|
|
dbffielddecs = 0;
|
|
|
|
/*
|
|
* This is a geometry/geography column
|
|
*/
|
|
if (pgfieldtype == state->geom_oid || pgfieldtype == state->geog_oid)
|
|
{
|
|
/* If no geometry/geography column has been found yet... */
|
|
if (!state->geo_col_name)
|
|
{
|
|
/* If either no geo* column name was provided (in which case this is
|
|
the first match) or we match the provided column name, we have
|
|
found our geo* column */
|
|
if (!state->config->geo_col_name || !strcmp(state->config->geo_col_name, pgfieldname))
|
|
{
|
|
dbffieldtype = 9;
|
|
|
|
state->geo_col_name = strdup(pgfieldname);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Everything else (non geometries) will be
|
|
* a DBF attribute.
|
|
*/
|
|
|
|
/* Skip gid (if not asked to do otherwise */
|
|
if (!strcmp(pgfieldname, "gid") )
|
|
{
|
|
gidfound = 1;
|
|
|
|
if (!state->config->includegid)
|
|
continue;
|
|
}
|
|
|
|
/* Unescape any reserved column names */
|
|
ptr = pgfieldname;
|
|
if (!state->config->unescapedattrs)
|
|
{
|
|
if (*ptr == '_')
|
|
ptr += 2;
|
|
}
|
|
|
|
/*
|
|
* This needs special handling since both xmin and _xmin
|
|
* becomes __xmin when escaped
|
|
*/
|
|
|
|
/* Limit dbf field name to 10-digits */
|
|
dbffieldname = malloc(11);
|
|
strncpy(dbffieldname, ptr, 10);
|
|
dbffieldname[10] = '\0';
|
|
|
|
/* If a column map file has been passed in, use this to create the dbf field name from
|
|
the PostgreSQL column name */
|
|
if (state->column_map_size > 0)
|
|
{
|
|
for (j = 0; j < state->column_map_size; j++)
|
|
{
|
|
if (!strcasecmp(state->column_map_pgfieldnames[j], dbffieldname))
|
|
{
|
|
strncpy(dbffieldname, state->column_map_dbffieldnames[j], 10);
|
|
dbffieldname[10] = '\0';
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* make sure the fields all have unique names,
|
|
*/
|
|
tmpint = 1;
|
|
for (j = 0; j < state->fieldcount; j++)
|
|
{
|
|
if (!strncasecmp(dbffieldname, state->dbffieldnames[j], 10))
|
|
{
|
|
sprintf(dbffieldname, "%.7s_%.2d", ptr, tmpint++);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* make UPPERCASE if keep_fieldname_case = 0 */
|
|
if (!state->config->keep_fieldname_case)
|
|
for (j = 0; j < strlen(dbffieldname); j++)
|
|
dbffieldname[j] = toupper(dbffieldname[j]);
|
|
|
|
/* Issue warning if column has been renamed */
|
|
if (strcasecmp(dbffieldname, pgfieldname))
|
|
{
|
|
/* Note: we concatenate all warnings from the main loop as this is useful information */
|
|
snprintf(buf, 256, _("Warning, field %s renamed to %s\n"), pgfieldname, dbffieldname);
|
|
strncat(state->message, buf, SHPDUMPERMSGLEN - strlen(state->message));
|
|
|
|
ret = SHPDUMPERWARN;
|
|
}
|
|
|
|
|
|
/*
|
|
* Find appropriate type of dbf attributes
|
|
*/
|
|
|
|
/* int2 type */
|
|
if (pgfieldtype == 21)
|
|
{
|
|
/*
|
|
* Longest text representation for
|
|
* an int2 type (16bit) is 6 bytes
|
|
* (-32768)
|
|
*/
|
|
dbffieldtype = FTInteger;
|
|
dbffieldsize = 6;
|
|
dbffielddecs = 0;
|
|
}
|
|
|
|
/* int4 type */
|
|
else if (pgfieldtype == 23)
|
|
{
|
|
/*
|
|
* Longest text representation for
|
|
* an int4 type (32bit) is 11 bytes
|
|
* (-2147483648)
|
|
*/
|
|
dbffieldtype = FTInteger;
|
|
dbffieldsize = 11;
|
|
dbffielddecs = 0;
|
|
}
|
|
|
|
/* int8 type */
|
|
else if (pgfieldtype == 20)
|
|
{
|
|
/*
|
|
* Longest text representation for
|
|
* an int8 type (64bit) is 20 bytes
|
|
* (-9223372036854775808)
|
|
*/
|
|
dbffieldtype = FTInteger;
|
|
dbffieldsize = 19;
|
|
dbffielddecs = 0;
|
|
}
|
|
|
|
/*
|
|
* double or numeric types:
|
|
* 700: float4
|
|
* 701: float8
|
|
* 1700: numeric
|
|
*
|
|
*
|
|
* TODO: stricter handling of sizes
|
|
*/
|
|
else if (pgfieldtype == 700 || pgfieldtype == 701 || pgfieldtype == 1700)
|
|
{
|
|
dbffieldtype = FTDouble;
|
|
dbffieldsize = 32;
|
|
dbffielddecs = 10;
|
|
}
|
|
|
|
/*
|
|
* Boolean field, we use FTLogical
|
|
*/
|
|
else if (pgfieldtype == 16)
|
|
{
|
|
dbffieldtype = FTLogical;
|
|
dbffieldsize = 2;
|
|
dbffielddecs = 0;
|
|
}
|
|
|
|
/*
|
|
* Date field
|
|
*/
|
|
else if (pgfieldtype == 1082)
|
|
{
|
|
dbffieldtype = FTDate;
|
|
dbffieldsize = 8;
|
|
dbffielddecs = 0;
|
|
}
|
|
|
|
/*
|
|
* time, timetz, timestamp, or timestamptz field.
|
|
*/
|
|
else if (pgfieldtype == 1083 || pgfieldtype == 1266 || pgfieldtype == 1114 || pgfieldtype == 1184)
|
|
{
|
|
int secondsize;
|
|
|
|
switch (pgtypmod)
|
|
{
|
|
case -1:
|
|
secondsize = 6 + 1;
|
|
break;
|
|
case 0:
|
|
secondsize = 0;
|
|
break;
|
|
default:
|
|
secondsize = pgtypmod + 1;
|
|
break;
|
|
}
|
|
|
|
/* We assume the worst case scenario for all of these:
|
|
* date = '5874897-12-31' = 13
|
|
* date = '294276-11-20' = 12 (with --enable-interger-datetimes)
|
|
* time = '00:00:00' = 8
|
|
* zone = '+01:39:52' = 9 (see Europe/Helsinki around 1915)
|
|
*/
|
|
|
|
/* time */
|
|
if (pgfieldtype == 1083)
|
|
{
|
|
dbffieldsize = 8 + secondsize;
|
|
}
|
|
/* timetz */
|
|
else if (pgfieldtype == 1266)
|
|
{
|
|
dbffieldsize = 8 + secondsize + 9;
|
|
}
|
|
/* timestamp */
|
|
else if (pgfieldtype == 1114)
|
|
{
|
|
dbffieldsize = 13 + 1 + 8 + secondsize;
|
|
}
|
|
/* timestamptz */
|
|
else if (pgfieldtype == 1184)
|
|
{
|
|
dbffieldsize = 13 + 1 + 8 + secondsize + 9;
|
|
}
|
|
|
|
dbffieldtype = FTString;
|
|
dbffielddecs = 0;
|
|
}
|
|
|
|
/*
|
|
* uuid type 36 bytes (12345678-9012-3456-7890-123456789012)
|
|
*/
|
|
else if (pgfieldtype == 2950)
|
|
{
|
|
dbffieldtype = FTString;
|
|
dbffieldsize = 36;
|
|
dbffielddecs = 0;
|
|
}
|
|
|
|
/*
|
|
* For variable-sized fields we know about, we use
|
|
* the maximum allowed size.
|
|
* 1042 is bpchar, 1043 is varchar
|
|
*/
|
|
else if ((pgfieldtype == 1042 || pgfieldtype == 1043) && pgtypmod != -1)
|
|
{
|
|
/*
|
|
* mod is maximum allowed size, including
|
|
* header which contains *real* size.
|
|
*/
|
|
dbffieldtype = FTString;
|
|
dbffieldsize = pgtypmod - 4; /* 4 is header size */
|
|
dbffielddecs = 0;
|
|
}
|
|
|
|
/* For all other valid non-geometry/geography fields... */
|
|
else if (dbffieldtype == -1)
|
|
{
|
|
/*
|
|
* For types we don't know anything about, all
|
|
* we can do is query the table for the maximum field
|
|
* size.
|
|
*/
|
|
dbffieldsize = getMaxFieldSize(state->conn, state->schema, state->table, pgfieldname);
|
|
if (dbffieldsize == -1)
|
|
return 0;
|
|
|
|
if (!dbffieldsize)
|
|
dbffieldsize = 32;
|
|
|
|
/* might 0 be a good size ? */
|
|
|
|
dbffieldtype = FTString;
|
|
dbffielddecs = 0;
|
|
|
|
/* Check to make sure the final field size isn't too large */
|
|
if (dbffieldsize > MAX_DBF_FIELD_SIZE)
|
|
{
|
|
/* Note: we concatenate all warnings from the main loop as this is useful information */
|
|
snprintf(buf, 256, _("Warning: values of field '%s' exceeding maximum dbf field width (%d) "
|
|
"will be truncated.\n"), dbffieldname, MAX_DBF_FIELD_SIZE);
|
|
strncat(state->message, buf, SHPDUMPERMSGLEN - strlen(state->message));
|
|
dbffieldsize = MAX_DBF_FIELD_SIZE;
|
|
|
|
ret = SHPDUMPERWARN;
|
|
}
|
|
}
|
|
|
|
LWDEBUGF(3, "DBF FIELD_NAME: %s, SIZE: %d\n", dbffieldname, dbffieldsize);
|
|
|
|
if (dbffieldtype != 9)
|
|
{
|
|
/* Add the field to the DBF file */
|
|
if (DBFAddField(state->dbf, dbffieldname, dbffieldtype, dbffieldsize, dbffielddecs) == -1)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("Error: field %s of type %d could not be created."), dbffieldname, dbffieldtype);
|
|
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
/* Add the field information to our field arrays */
|
|
state->dbffieldnames[state->fieldcount] = dbffieldname;
|
|
state->dbffieldtypes[state->fieldcount] = dbffieldtype;
|
|
state->pgfieldnames[state->fieldcount] = pgfieldname;
|
|
state->pgfieldlens[state->fieldcount] = pgfieldlen;
|
|
state->pgfieldtypmods[state->fieldcount] = pgtypmod;
|
|
|
|
state->fieldcount++;
|
|
}
|
|
}
|
|
|
|
/* Now we have generated the field lists, grab some info about the table */
|
|
status = getTableInfo(state);
|
|
if (status == SHPDUMPERERR)
|
|
return SHPDUMPERERR;
|
|
|
|
LWDEBUGF(3, "rows: %d\n", state->rowcount);
|
|
LWDEBUGF(3, "shptype: %c\n", state->outtype);
|
|
LWDEBUGF(3, "shpouttype: %d\n", state->outshptype);
|
|
|
|
/* If we didn't find a geometry/geography column... */
|
|
if (!state->geo_col_name)
|
|
{
|
|
if (state->config->geo_col_name)
|
|
{
|
|
/* A geo* column was specified, but not found */
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("%s: no such attribute in table %s"), state->config->geo_col_name, state->table);
|
|
|
|
return SHPDUMPERERR;
|
|
}
|
|
else
|
|
{
|
|
/* No geo* column specified so we can only create the DBF section -
|
|
but let's issue a warning... */
|
|
snprintf(buf, 256, _("No geometry column found.\nThe DBF file will be created but not the shx or shp files.\n"));
|
|
strncat(state->message, buf, SHPDUMPERMSGLEN - strlen(state->message));
|
|
|
|
state->shp = NULL;
|
|
|
|
ret = SHPDUMPERWARN;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Since we have found a geo* column, open the shapefile */
|
|
state->shp = SHPCreate(state->shp_file, state->outshptype);
|
|
if (!state->shp)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("Could not open shapefile %s!"), state->shp_file);
|
|
|
|
return SHPDUMPERERR;
|
|
}
|
|
}
|
|
|
|
|
|
/* Now we have the complete list of fieldnames, let's generate the SQL query. First let's make sure
|
|
we reserve enough space for tables with lots of columns */
|
|
j = 0;
|
|
for (i = 0; i < state->fieldcount; i++)
|
|
j += strlen(state->pgfieldnames[i] + 2); /* Add 2 for leading and trailing quotes */
|
|
|
|
state->main_scan_query = malloc(1024 + j);
|
|
|
|
sprintf(state->main_scan_query, "DECLARE cur ");
|
|
if (state->config->binary)
|
|
strcat(state->main_scan_query, "BINARY ");
|
|
|
|
strcat(state->main_scan_query, "CURSOR FOR SELECT ");
|
|
|
|
for (i = 0; i < state->fieldcount; i++)
|
|
{
|
|
/* Comma-separated column names */
|
|
if (i > 0)
|
|
strcat(state->main_scan_query, ",");
|
|
|
|
if (state->config->binary)
|
|
sprintf(buf, "\"%s\"::text", state->pgfieldnames[i]);
|
|
else
|
|
sprintf(buf, "\"%s\"", state->pgfieldnames[i]);
|
|
|
|
strcat(state->main_scan_query, buf);
|
|
}
|
|
|
|
/* If we found a valid geometry/geography column then use it */
|
|
if (state->geo_col_name)
|
|
{
|
|
/* If this is the (only) column, no need for the initial comma */
|
|
if (state->fieldcount > 0)
|
|
strcat(state->main_scan_query, ",");
|
|
|
|
if (state->big_endian)
|
|
{
|
|
if (state->pgis_major_version > 0)
|
|
{
|
|
sprintf(buf, "ST_asEWKB(ST_SetSRID(\"%s\"::geometry, 0), 'XDR')", state->geo_col_name);
|
|
}
|
|
else
|
|
{
|
|
sprintf(buf, "asbinary(\"%s\"::geometry, 'XDR')",
|
|
state->geo_col_name);
|
|
}
|
|
}
|
|
else /* little_endian */
|
|
{
|
|
if (state->pgis_major_version > 0)
|
|
{
|
|
sprintf(buf, "ST_AsEWKB(ST_SetSRID(\"%s\"::geometry, 0), 'NDR') AS _geoX", state->geo_col_name);
|
|
}
|
|
else
|
|
{
|
|
sprintf(buf, "asbinary(\"%s\"::geometry, 'NDR') AS _geoX",
|
|
state->geo_col_name);
|
|
}
|
|
}
|
|
|
|
strcat(state->main_scan_query, buf);
|
|
}
|
|
|
|
if (state->schema)
|
|
{
|
|
sprintf(buf, " FROM \"%s\".\"%s\"", state->schema, state->table);
|
|
}
|
|
else
|
|
{
|
|
sprintf(buf, " FROM \"%s\"", state->table);
|
|
}
|
|
|
|
strcat(state->main_scan_query, buf);
|
|
|
|
/* Order by 'gid' (if found) */
|
|
if (gidfound)
|
|
{
|
|
sprintf(buf, " ORDER BY \"gid\"");
|
|
strcat(state->main_scan_query, buf);
|
|
}
|
|
|
|
/* Now we've finished with the result set, we can dispose of it */
|
|
PQclear(res);
|
|
|
|
LWDEBUGF(3, "FINAL QUERY: %s\n", state->main_scan_query);
|
|
|
|
/*
|
|
* Begin the transaction
|
|
* (a cursor can only be defined inside a transaction block)
|
|
*/
|
|
res = PQexec(state->conn, "BEGIN");
|
|
if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("Error starting transaction: %s"), PQresultErrorMessage(res));
|
|
PQclear(res);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
PQclear(res);
|
|
|
|
/* Execute the main scan query */
|
|
res = PQexec(state->conn, state->main_scan_query);
|
|
if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("Error executing main scan query: %s"), PQresultErrorMessage(res));
|
|
PQclear(res);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
PQclear(res);
|
|
|
|
/* Setup initial scan state */
|
|
state->currow = 0;
|
|
state->curresrow = 0;
|
|
state->currescount = 0;
|
|
state->fetchres = NULL;
|
|
|
|
/* Generate the fetch query */
|
|
state->fetch_query = malloc(256);
|
|
sprintf(state->fetch_query, "FETCH %d FROM cur", state->config->fetchsize);
|
|
|
|
return SHPDUMPEROK;
|
|
}
|
|
|
|
|
|
/* Append the next row to the output shapefile */
|
|
int ShpLoaderGenerateShapeRow(SHPDUMPERSTATE *state)
|
|
{
|
|
char *hexewkb = NULL;
|
|
unsigned char *hexewkb_binary = NULL;
|
|
size_t hexewkb_len;
|
|
char *val;
|
|
SHPObject *obj = NULL;
|
|
LWGEOM *lwgeom;
|
|
|
|
int i, geocolnum = 0;
|
|
|
|
/* If we try to go pass the end of the table, fail immediately */
|
|
if (state->currow > state->rowcount)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("Tried to read past end of table!"));
|
|
PQclear(state->fetchres);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
/* If we have reached the end of the current batch, fetch a new one */
|
|
if (state->curresrow == state->currescount && state->currow < state->rowcount)
|
|
{
|
|
/* Clear the previous batch results */
|
|
if (state->fetchres)
|
|
PQclear(state->fetchres);
|
|
|
|
state->fetchres = PQexec(state->conn, state->fetch_query);
|
|
if (PQresultStatus(state->fetchres) != PGRES_TUPLES_OK)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("Error executing fetch query: %s"), PQresultErrorMessage(state->fetchres));
|
|
PQclear(state->fetchres);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
state->curresrow = 0;
|
|
state->currescount = PQntuples(state->fetchres);
|
|
}
|
|
|
|
/* Grab the id of the geo column if we have one */
|
|
if (state->geo_col_name)
|
|
geocolnum = PQfnumber(state->fetchres, "_geoX");
|
|
|
|
/* Process the next record within the batch. First write out all of
|
|
the non-geo fields */
|
|
for (i = 0; i < state->fieldcount; i++)
|
|
{
|
|
/*
|
|
* Transform NULL numbers to '0'
|
|
* This is because the shapelib
|
|
* won't easly take care of setting
|
|
* nulls unless paying the acquisition
|
|
* of a bug in long integer values
|
|
*/
|
|
if (PQgetisnull(state->fetchres, state->curresrow, i))
|
|
{
|
|
val = nullDBFValue(state->dbffieldtypes[i]);
|
|
}
|
|
else
|
|
{
|
|
val = PQgetvalue(state->fetchres, state->curresrow, i);
|
|
val = goodDBFValue(val, state->dbffieldtypes[i]);
|
|
}
|
|
|
|
/* Write it to the DBF file */
|
|
if (!DBFWriteAttributeDirectly(state->dbf, state->currow, i, val))
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("Error: record %d could not be created"), state->currow);
|
|
PQclear(state->fetchres);
|
|
return SHPDUMPERERR;
|
|
}
|
|
}
|
|
|
|
/* Now process the geo field, if present */
|
|
if (state->geo_col_name)
|
|
{
|
|
/* Handle NULL shapes */
|
|
if (PQgetisnull(state->fetchres, state->curresrow, geocolnum))
|
|
{
|
|
obj = SHPCreateSimpleObject(SHPT_NULL, 0, NULL, NULL, NULL);
|
|
if (SHPWriteObject(state->shp, -1, obj) == -1)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("Error writing NULL shape for record %d"), state->currow);
|
|
PQclear(state->fetchres);
|
|
SHPDestroyObject(obj);
|
|
return SHPDUMPERERR;
|
|
}
|
|
SHPDestroyObject(obj);
|
|
}
|
|
else
|
|
{
|
|
/* Get the value from the result set */
|
|
val = PQgetvalue(state->fetchres, state->curresrow, geocolnum);
|
|
|
|
if (!state->config->binary)
|
|
{
|
|
if (state->pgis_major_version > 0)
|
|
{
|
|
LWDEBUG(4, "PostGIS >= 1.0, non-binary cursor");
|
|
|
|
/* Input is bytea encoded text field, so it must be unescaped and
|
|
then converted to hexewkb string */
|
|
hexewkb_binary = PQunescapeBytea((unsigned char *)val, &hexewkb_len);
|
|
hexewkb = convert_bytes_to_hex(hexewkb_binary, hexewkb_len);
|
|
}
|
|
else
|
|
{
|
|
LWDEBUG(4, "PostGIS < 1.0, non-binary cursor");
|
|
|
|
/* Input is already hexewkb string, so we can just
|
|
copy directly to hexewkb */
|
|
hexewkb_len = PQgetlength(state->fetchres, state->curresrow, geocolnum);
|
|
hexewkb = malloc(hexewkb_len + 1);
|
|
strncpy(hexewkb, val, hexewkb_len + 1);
|
|
}
|
|
}
|
|
else /* binary */
|
|
{
|
|
LWDEBUG(4, "PostGIS (any version) using binary cursor");
|
|
|
|
/* Input is binary field - must convert to hexewkb string */
|
|
hexewkb_len = PQgetlength(state->fetchres, state->curresrow, geocolnum);
|
|
hexewkb = convert_bytes_to_hex((unsigned char *)val, hexewkb_len);
|
|
}
|
|
|
|
LWDEBUGF(4, "HexEWKB - length: %d value: %s", strlen(hexewkb), hexewkb);
|
|
|
|
/* Deserialize the LWGEOM */
|
|
lwgeom = lwgeom_from_hexwkb(hexewkb, LW_PARSER_CHECK_NONE);
|
|
if (!lwgeom)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("Error parsing HEXEWKB for record %d"), state->currow);
|
|
PQclear(state->fetchres);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
/* Call the relevant method depending upon the geometry type */
|
|
LWDEBUGF(4, "geomtype: %s\n", lwtype_name(lwgeom->type));
|
|
|
|
switch (lwgeom->type)
|
|
{
|
|
case POINTTYPE:
|
|
obj = create_point(state, lwgeom_as_lwpoint(lwgeom));
|
|
break;
|
|
|
|
case MULTIPOINTTYPE:
|
|
obj = create_multipoint(state, lwgeom_as_lwmpoint(lwgeom));
|
|
break;
|
|
|
|
case POLYGONTYPE:
|
|
obj = create_polygon(state, lwgeom_as_lwpoly(lwgeom));
|
|
break;
|
|
|
|
case MULTIPOLYGONTYPE:
|
|
obj = create_multipolygon(state, lwgeom_as_lwmpoly(lwgeom));
|
|
break;
|
|
|
|
case LINETYPE:
|
|
obj = create_linestring(state, lwgeom_as_lwline(lwgeom));
|
|
break;
|
|
|
|
case MULTILINETYPE:
|
|
obj = create_multilinestring(state, lwgeom_as_lwmline(lwgeom));
|
|
break;
|
|
|
|
default:
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("Unknown WKB type (%d) for record %d"), lwgeom->type, state->currow);
|
|
PQclear(state->fetchres);
|
|
SHPDestroyObject(obj);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
/* Free both the original and geometries */
|
|
lwgeom_free(lwgeom);
|
|
|
|
/* Write the shape out to the file */
|
|
if (SHPWriteObject(state->shp, -1, obj) == -1)
|
|
{
|
|
snprintf(state->message, SHPDUMPERMSGLEN, _("Error writing shape %d"), state->currow);
|
|
PQclear(state->fetchres);
|
|
SHPDestroyObject(obj);
|
|
return SHPDUMPERERR;
|
|
}
|
|
|
|
SHPDestroyObject(obj);
|
|
|
|
/* Free the hexewkb (and temporary bytea unescaped string if used) */
|
|
if (hexewkb) free(hexewkb);
|
|
if (hexewkb_binary) PQfreemem(hexewkb_binary);
|
|
}
|
|
}
|
|
|
|
/* Increment ready for next time */
|
|
state->curresrow++;
|
|
state->currow++;
|
|
|
|
return SHPDUMPEROK;
|
|
}
|
|
|
|
|
|
/* Return a count of the number of rows in the table being dumped */
|
|
int
|
|
ShpDumperGetRecordCount(SHPDUMPERSTATE *state)
|
|
{
|
|
return state->rowcount;
|
|
}
|
|
|
|
|
|
/* Close the specified table and flush all files to disk */
|
|
int
|
|
ShpDumperCloseTable(SHPDUMPERSTATE *state)
|
|
{
|
|
int ret = SHPDUMPEROK;
|
|
|
|
/* Clear the current batch fetch resource */
|
|
PQclear(state->fetchres);
|
|
|
|
/* If a geo column is present, generate the projection file */
|
|
if (state->geo_col_name)
|
|
ret = projFileCreate(state);
|
|
|
|
/* Close the DBF and SHP files */
|
|
if (state->dbf)
|
|
DBFClose(state->dbf);
|
|
if (state->shp)
|
|
SHPClose(state->shp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
void
|
|
ShpDumperDestroy(SHPDUMPERSTATE *state)
|
|
{
|
|
/* Destroy a state object created with ShpDumperConnect */
|
|
int i;
|
|
|
|
if (state != NULL)
|
|
{
|
|
/* Disconnect from the database */
|
|
if (state->conn)
|
|
PQfinish(state->conn);
|
|
|
|
/* Free the query strings */
|
|
if (state->fetch_query)
|
|
free(state->fetch_query);
|
|
if (state->main_scan_query)
|
|
free(state->main_scan_query);
|
|
|
|
/* Free the DBF information fields */
|
|
if (state->dbffieldnames)
|
|
{
|
|
for (i = 0; i < state->fieldcount; i++)
|
|
free(state->dbffieldnames[i]);
|
|
free(state->dbffieldnames);
|
|
}
|
|
|
|
if (state->dbffieldtypes)
|
|
free(state->dbffieldtypes);
|
|
|
|
if (state->pgfieldnames)
|
|
free(state->pgfieldnames);
|
|
|
|
/* Free any column map fieldnames if specified */
|
|
if (state->column_map_size > 0)
|
|
{
|
|
for (i = 0; i < state->column_map_size; i++)
|
|
{
|
|
if (state->column_map_pgfieldnames[i])
|
|
free(state->column_map_pgfieldnames[i]);
|
|
|
|
if (state->column_map_dbffieldnames[i])
|
|
free(state->column_map_dbffieldnames[i]);
|
|
}
|
|
|
|
free(state->column_map_pgfieldnames);
|
|
free(state->column_map_dbffieldnames);
|
|
}
|
|
|
|
/* Free other names */
|
|
if (state->table)
|
|
free(state->table);
|
|
if (state->schema)
|
|
free(state->schema);
|
|
if (state->geo_col_name)
|
|
free(state->geo_col_name);
|
|
|
|
/* Free the state itself */
|
|
free(state);
|
|
}
|
|
}
|