From 1893da945ca9ab34c366e58e337986c28d38f13c Mon Sep 17 00:00:00 2001 From: Paul Ramsey Date: Mon, 19 Jan 2009 21:33:14 +0000 Subject: [PATCH] First revision of the GUI. Configure using --with-gui to enable full GUI build. New core/cli will build by default. Old utilities remain in place for now. git-svn-id: http://svn.osgeo.org/postgis/trunk@3538 b70326c6-7e19-0410-871a-916f4a2858ee --- configure.ac | 123 ++- liblwgeom/liblwgeom.h | 3 +- loader/Makefile.in | 52 +- loader/dbfopen.c | 3 - loader/shp2pgsql-cli.c | 224 +++++ loader/shp2pgsql-core.c | 1862 +++++++++++++++++++++++++++++++++++++++ loader/shp2pgsql-core.h | 70 ++ loader/shp2pgsql-gui.c | 905 +++++++++++++++++++ loader/stringbuffer.c | 568 ++++++++++++ loader/stringbuffer.h | 66 ++ 10 files changed, 3819 insertions(+), 57 deletions(-) create mode 100644 loader/shp2pgsql-cli.c create mode 100644 loader/shp2pgsql-core.c create mode 100644 loader/shp2pgsql-core.h create mode 100644 loader/shp2pgsql-gui.c create mode 100644 loader/stringbuffer.c create mode 100644 loader/stringbuffer.h diff --git a/configure.ac b/configure.ac index 8440243e5..634fa5bb5 100644 --- a/configure.ac +++ b/configure.ac @@ -12,6 +12,7 @@ dnl ********************************************************************** AC_INIT() AC_CONFIG_HEADERS([postgis_config.h]) +AC_CONFIG_MACRO_DIR([macros]) dnl Invoke libtool: we do this as it is the easiest way to find the PIC dnl flags required to build liblwgeom @@ -69,6 +70,9 @@ AC_SUBST([LEX]) AC_SUBST([YACC]) +dnl =========================================================================== +dnl Find components needed to build documentation +dnl =========================================================================== dnl dnl Search for xsltproc which is required for building documentation dnl @@ -78,7 +82,6 @@ if test "x$XSLTPROC" = "x"; then AC_MSG_WARN([xsltproc is not installed so documentation cannot be built]) fi - dnl dnl Search for db2pdf which is required for building PDF documentation dnl @@ -88,7 +91,6 @@ if test "x$DB2PDF" = "x"; then AC_MSG_WARN([db2pdf is not installed so PDF documentation cannot be built]) fi - dnl dnl Search for dblatex which is required for building PDF documentation dnl @@ -98,8 +100,6 @@ if test "x$DBLATEX" = "x"; then AC_MSG_WARN([dblatex is not installed so PDF documentation cannot be built]) fi - - dnl dnl Allow the user to specify the location of the html/docbook.xsl stylesheet dnl @@ -110,7 +110,8 @@ AC_ARG_WITH([xsldir], XSLBASE_AUTO="" if test "x$XSLBASE" = "x"; then - dnl If the user did not specify a directory for the docbook stylesheet, choose the first directory + dnl If the user did not specify a directory for the docbook + dnl stylesheet, choose the first directory dnl that matches from the following list SEARCHPATH=" /usr/share/sgml/docbook/xsl-stylesheets @@ -124,12 +125,14 @@ if test "x$XSLBASE" = "x"; then fi done - dnl Check to see if the automatically searched paths above located a valid Docbook stylesheet + dnl Check to see if the automatically searched paths above located a + dnl valid Docbook stylesheet if test "x$XSLBASE_AUTO" = "x"; then AC_MSG_WARN([could not locate Docbook stylesheets required to build the documentation]) fi else - dnl The user specified an alternate directory so make sure everything looks sensible + dnl The user specified an alternate directory so make sure everything + dnl looks sensible if test ! -d "$XSLBASE"; then AC_MSG_ERROR([the docbook stylesheet directory specified using --with-xsldir does not exist]) fi @@ -140,8 +143,10 @@ else fi dnl -dnl If XSLBASE has been set then at this point we know it must be valid and so we can just use it. If XSLBASE_AUTO has been set, and XSLBASE -dnl is empty then a valid stylesheet was found in XSLBASE_AUTO so we should use that. Otherwise just continue silently with a blank XSLBASE +dnl If XSLBASE has been set then at this point we know it must be +dnl valid and so we can just use it. If XSLBASE_AUTO has been set, and XSLBASE +dnl is empty then a valid stylesheet was found in XSLBASE_AUTO so we +dnl should use that. Otherwise just continue silently with a blank XSLBASE dnl variable which will trigger the error message in the documentation Makefile dnl @@ -154,14 +159,14 @@ fi AC_SUBST([XSLBASE]) -dnl +dnl =========================================================================== dnl Detect CUnit if it is installed (used for unit testing) dnl -dnl Note that we pass any specified CPPFLAGS and LDFLAGS into the Makefile as CUnit is -dnl the only compile-time dependency that cannot obtain any specialised flags using -dnl a --with-X parameter, and so we allow this information to be passed in if -dnl required. -dnl +dnl Note that we pass any specified CPPFLAGS and LDFLAGS into the Makefile +dnl as CUnit is the only compile-time dependency that cannot obtain any +dnl specialised flags using a --with-X parameter, and so we allow this +dnl information to be passed in if required. +dnl =========================================================================== CUNIT_LDFLAGS="" AC_CHECK_HEADER([CUnit/CUnit.h], [ @@ -176,9 +181,10 @@ AC_SUBST([CUNIT_CPPFLAGS]) AC_SUBST([CUNIT_LDFLAGS]) -dnl -dnl Detect iconv if it is installed (used for shp2pgsql encoding conversion if available) -dnl +dnl =========================================================================== +dnl Detect iconv if it is installed (used for shp2pgsql encoding conversion +dnl if available) +dnl =========================================================================== HAVE_ICONV_H=0 AC_CHECK_HEADER([iconv.h], [HAVE_ICONV_H=1], []) @@ -213,9 +219,9 @@ fi AC_SUBST([ICONV_LDFLAGS]) -dnl +dnl =========================================================================== dnl Detect the version of PostgreSQL installed on the system -dnl +dnl =========================================================================== AC_ARG_WITH([pgconfig], [AS_HELP_STRING([--with-pgconfig=FILE], [specify an alternative pg_config file])], @@ -243,11 +249,12 @@ else fi -dnl -dnl Ensure that $PG_CONFIG --pgxs points to a valid file. This is because some distributions such as Debian -dnl also include pg_config as part of libpq-dev packages, but don't install the Makefile it points to unless +dnl =========================================================================== +dnl Ensure that $PG_CONFIG --pgxs points to a valid file. This is because some +dnl distributions such as Debian also include pg_config as part of libpq-dev +dnl packages, but don't install the Makefile it points to unless dnl the postgresql-server-dev packages are installed :) -dnl +dnl =========================================================================== PGXS=`$PGCONFIG --pgxs` if test ! -f $PGXS; then @@ -316,9 +323,10 @@ AC_DEFINE_UNQUOTED([POSTGIS_PGSQL_VERSION], [$POSTGIS_PGSQL_VERSION], [PostgreSQ AC_SUBST([POSTGIS_PGSQL_VERSION]) -dnl + +dnl =========================================================================== dnl Detect the version of GEOS installed on the system -dnl +dnl =========================================================================== AC_ARG_WITH([geosconfig], [AS_HELP_STRING([--with-geosconfig=FILE], [specify an alternative geos-config file])], @@ -345,9 +353,10 @@ else fi fi -dnl Extract the version information from pg_config -dnl Note: we extract the major & minor separately, ensure they are numeric, and then combine to give -dnl the final version. This is to guard against user error... +dnl Extract the version information from geos_config +dnl Note: we extract the major & minor separately, ensure they are numeric, +dnl and then combine to give the final version. +dnl This is to guard against user error... GEOS_MAJOR_VERSION=`$GEOSCONFIG --version | cut -d. -f1 | sed 's/[[^0-9]]//g'` GEOS_MINOR_VERSION=`$GEOSCONFIG --version | cut -d. -f2 | sed 's/[[^0-9]]//g'` POSTGIS_GEOS_VERSION="$GEOS_MAJOR_VERSION$GEOS_MINOR_VERSION" @@ -386,9 +395,9 @@ AC_DEFINE_UNQUOTED([POSTGIS_GEOS_VERSION], [$POSTGIS_GEOS_VERSION], [GEOS librar AC_SUBST([POSTGIS_GEOS_VERSION]) -dnl +dnl =========================================================================== dnl Detect the version of PROJ.4 installed -dnl +dnl =========================================================================== AC_ARG_WITH([projdir], [AS_HELP_STRING([--with-projdir=PATH], [specify the PROJ.4 installation directory])], @@ -411,6 +420,7 @@ if test ! "x$PROJDIR" = "x"; then fi fi + dnl Check that we can find the proj_api.h header file CPPFLAGS_SAVE="$CPPFLAGS" CPPFLAGS="$PROJ_CPPFLAGS" @@ -436,8 +446,51 @@ AC_CHECK_LIB([proj], [pj_get_release], []) LIBS="$LIBS_SAVE" +dnl =========================================================================== +dnl Detect GLib GTK for GUI +dnl =========================================================================== + +AC_ARG_WITH([gui], + [AS_HELP_STRING([--with-gui], [compile the data import GUI (requires GTK+2.0)])], + [GUI="yes"], [GUI="no"]) + +if test "x$GUI" = "xyes"; then + AC_MSG_RESULT([GUI: Build requested, checking for dependencies (GKT+2.0)]) + case $host in + *apple*) + for frmwrk [ in Cairo GLib Gtk ]; do + if test -d /Library/Frameworks/${frmwrk}.framework; then + GTK_INCLUDES="$GTK_INCLUDES -I/Library/Frameworks/${frmwrk}.framework/Headers" + GTK_LIBS="$GTK_LIBS -framework $frmwrk" + AC_MSG_RESULT([GUI: Found /Library/Frameworks/${frmwrk}.framework]) + else + AC_MSG_ERROR([GUI: Mac OS/X build requires the GTK+2.0 frameworks available from http://www.gtk-osx.org]) + fi + done + GTK_BUILD="gui" + ;; + *) + dnl Try to find the GTK libs with pkgconfig + if ! test -x `which pkg-config`; then + AC_MSG_ERROR([GUI: Building GTK applications requires 'pkg-config', please install it.]) + else + AC_MSG_RESULT([GUI: Running 'pkg-config gtk+-2.0 --cflags']) + GTK_INCLUDES=`pkg-config gtk+-2.0 --cflags` + AC_MSG_RESULT([GUI: Running 'pkg-config gtk+-2.0 --libs']) + GTK_LIBS=`pkg-config gtk+-2.0 --libs` + if test ! "x$GTK_LIBS" = "x"; then + GTK_BUILD="gui" + fi + fi + esac +fi +AC_SUBST([GTK_INCLUDES]) +AC_SUBST([GTK_LIBS]) +AC_SUBST([GTK_BUILD]) + -dnl + +dnl =========================================================================== dnl Allow the user to enable debugging with --enable-debug dnl dnl Currently we default to debug level 4. See DEBUG for more information. @@ -448,7 +501,7 @@ AC_ARG_ENABLE([debug], AC_HELP_STRING([--enable-debug], [Enable verbose debuggin AC_DEFINE_UNQUOTED([POSTGIS_DEBUG_LEVEL], [$POSTGIS_DEBUG_LEVEL], [PostGIS library debug level (0=disabled)]) -dnl +dnl =========================================================================== dnl Allow the user to enable GEOS profiling with --enable-profile dnl @@ -457,7 +510,7 @@ AC_ARG_ENABLE([profile], AC_HELP_STRING([--enable-profile], [Enable GEOS profili AC_DEFINE_UNQUOTED([POSTGIS_PROFILE], [$POSTGIS_PROFILE], [Enable GEOS profiling (0=disabled)]) -dnl +dnl =========================================================================== dnl Define version macros dnl @@ -477,7 +530,7 @@ AC_SUBST([POSTGIS_BUILD_DATE]) AC_SUBST([POSTGIS_SCRIPTS_VERSION]) -dnl +dnl =========================================================================== dnl Other parameters dnl diff --git a/liblwgeom/liblwgeom.h b/liblwgeom/liblwgeom.h index 9b0322346..1a2930cde 100644 --- a/liblwgeom/liblwgeom.h +++ b/liblwgeom/liblwgeom.h @@ -1274,8 +1274,7 @@ extern LWPOLY *lwpoly_from_lwlines(const LWLINE *shell, unsigned int nholes, con extern const char *lwgeom_typeflags(uchar type); /* Construct an empty pointarray */ -extern POINTARRAY *ptarray_construct(char hasz, char hasm, - unsigned int npoints); +extern POINTARRAY *ptarray_construct(char hasz, char hasm, unsigned int npoints); /* * extern POINTARRAY *ptarray_construct2d(uint32 npoints, const POINT2D *pts); diff --git a/loader/Makefile.in b/loader/Makefile.in index 2a3568a06..a4590fa9a 100644 --- a/loader/Makefile.in +++ b/loader/Makefile.in @@ -16,11 +16,8 @@ CFLAGS=@CFLAGS@ @PICFLAGS@ @WARNFLAGS@ # Filenames with extension as determined by the OS PGSQL2SHP=pgsql2shp@EXESUFFIX@ SHP2PGSQL=shp2pgsql@EXESUFFIX@ - -# Common shapefile library files -OBJS= shpopen.o \ - dbfopen.o \ - getopt.o +SHP2PGSQL-GUI=shp2pgsql-gui@EXESUFFIX@ +SHP2PGSQL-CLI=shp2pgsql-cli@EXESUFFIX@ # PostgreSQL frontend CPPFLAGS and LDFLAGS (for compiling and linking with libpq) PGSQL_FE_CPPFLAGS=@PGSQL_FE_CPPFLAGS@ @@ -32,30 +29,51 @@ PGSQL_BINDIR=@PGSQL_BINDIR@ # iconv flags ICONV_LDFLAGS=@ICONV_LDFLAGS@ +# liblwgeom +LIBLWGEOM=../liblwgeom/liblwgeom.a + +# GTK includes and libraries +GTK_INCLUDES = @GTK_INCLUDES@ +GTK_LIBS = @GTK_LIBS@ -all: $(SHP2PGSQL) $(PGSQL2SHP) +all: $(SHP2PGSQL) $(PGSQL2SHP) $(SHP2PGSQL-CLI) @GTK_BUILD@ -# liblwgeom.a dependency to allow "make install" in the loader/ subdirectory to work -../liblwgeom/liblwgeom.a: +gui: $(SHP2PGSQL-GUI) + +# liblwgeom.a dependency to allow "make install" in +# the loader/ subdirectory to work +$(LIBLWGEOM): make -C ../liblwgeom pgsql2shp.o: pgsql2shp.c $(CC) $(CFLAGS) $(PGSQL_FE_CPPFLAGS) -c $< -$(PGSQL2SHP): ../liblwgeom/liblwgeom.a $(OBJS) pgsql2shp.o - $(CC) $(CFLAGS) $(OBJS) pgsql2shp.o $(ICONV_LDFLAGS) $(PGSQL_FE_LDFLAGS) ../liblwgeom/liblwgeom.a -lm -o $@ +$(PGSQL2SHP): shpopen.o dbfopen.o getopt.o pgsql2shp.o $(LIBLWGEOM) + $(CC) $(CFLAGS) $^ $(ICONV_LDFLAGS) $(PGSQL_FE_LDFLAGS) -lm -o $@ + +$(SHP2PGSQL): shpopen.o dbfopen.o getopt.o shp2pgsql.o $(LIBLWGEOM) + $(CC) $(CFLAGS) $^ $(ICONV_LDFLAGS) -lm -o $@ + +shp2pgsql-core-gui.o: shp2pgsql-core.c + $(CC) $(CFLAGS) -DPGUI -c -o $@ $^ + +shp2pgsql-gui.o: shp2pgsql-gui.c + $(CC) $(PGSQL_FE_CPPFLAGS) $(CFLAGS) $(GTK_INCLUDES) -o $@ -c shp2pgsql-gui.c + +$(SHP2PGSQL-GUI): stringbuffer.o shpopen.o dbfopen.o shp2pgsql-core-gui.o shp2pgsql-gui.o $(LIBLWGEOM) + $(CC) $(CFLAGS) $(GTK_LIBS) $(ICONV_LDFLAGS) $(PGSQL_FE_LDFLAGS) -lm $^ -o $@ -$(SHP2PGSQL): ../liblwgeom/liblwgeom.a $(OBJS) shp2pgsql.o - $(CC) $(CFLAGS) $(OBJS) shp2pgsql.o $(ICONV_LDFLAGS) ../liblwgeom/liblwgeom.a -lm -o $@ +$(SHP2PGSQL-CLI): stringbuffer.o shpopen.o dbfopen.o shp2pgsql-core.o shp2pgsql-cli.o $(LIBLWGEOM) + $(CC) $(CFLAGS) $(ICONV_LDFLAGS) -lm $^ -o $@ install: all - cp $(PGSQL2SHP) $(PGSQL_BINDIR)/$(PGSQL2SHP) - cp $(SHP2PGSQL) $(PGSQL_BINDIR)/$(SHP2PGSQL) + @cp $(PGSQL2SHP) $(PGSQL_BINDIR)/$(PGSQL2SHP) + @cp $(SHP2PGSQL) $(PGSQL_BINDIR)/$(SHP2PGSQL) uninstall: - rm -f $(PGSQL_BINDIR)/$(PGSQL2SHP) - rm -f $(PGSQL_BINDIR)/$(SHP2PGSQL) + @rm -f $(PGSQL_BINDIR)/$(PGSQL2SHP) + @rm -f $(PGSQL_BINDIR)/$(SHP2PGSQL) clean: - rm -f $(OBJS) shp2pgsql.o pgsql2shp.o $(SHP2PGSQL) $(PGSQL2SHP) + @rm -f *.o $(SHP2PGSQL) $(PGSQL2SHP) diff --git a/loader/dbfopen.c b/loader/dbfopen.c index 80fab3544..291f1f4b3 100644 --- a/loader/dbfopen.c +++ b/loader/dbfopen.c @@ -203,9 +203,6 @@ * Added header. */ -static char rcsid[] = - "$Id$"; - #include "shapefil.h" #include diff --git a/loader/shp2pgsql-cli.c b/loader/shp2pgsql-cli.c new file mode 100644 index 000000000..66e3459fd --- /dev/null +++ b/loader/shp2pgsql-cli.c @@ -0,0 +1,224 @@ +/********************************************************************** + * $Id: shp2pgsql-gui.c 3450 2008-12-18 20:42:09Z pramsey $ + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.refractions.net + * Copyright 2008 OpenGeo.org + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + * Maintainer: Paul Ramsey + * + **********************************************************************/ + +#include +#include "shp2pgsql-core.h" + +static void +pcli_usage(char *me, int exitcode, FILE* out) +{ + fprintf(out, "RCSID: %s RELEASE: %s\n", RCSID, POSTGIS_VERSION); + fprintf(out, "USAGE: %s [] [.]\n", me); + fprintf(out, "OPTIONS:\n"); + fprintf(out, " -s Set the SRID field. If not specified it defaults to -1.\n"); + fprintf(out, " (-d|a|c|p) These are mutually exclusive options:\n"); + fprintf(out, " -d Drops the table, then recreates it and populates\n"); + fprintf(out, " it with current shape file data.\n"); + fprintf(out, " -a Appends shape file into current table, must be\n"); + fprintf(out, " exactly the same table schema.\n"); + fprintf(out, " -c Creates a new table and populates it, this is the\n"); + fprintf(out, " default if you do not specify any options.\n"); + fprintf(out, " -p Prepare mode, only creates the table.\n"); + fprintf(out, " -g Specify the name of the geometry column\n"); + fprintf(out, " (mostly useful in append mode).\n"); + fprintf(out, " -D Use postgresql dump format (defaults to sql insert statments.\n"); + fprintf(out, " -k Keep postgresql identifiers case.\n"); + fprintf(out, " -i Use int4 type for all integer dbf fields.\n"); + fprintf(out, " -I Create a GiST index on the geometry column.\n"); + fprintf(out, " -S Generate simple geometries instead of MULTI geometries.\n"); +#ifdef HAVE_ICONV + fprintf(out, " -W Specify the character encoding of Shape's\n"); + fprintf(out, " attribute column. (default : \"ASCII\")\n"); +#endif + fprintf(out, " -N Specify NULL geometries handling policy (insert,skip,abort)\n"); + fprintf(out, " -n Only import DBF file.\n"); + fprintf(out, " -? Display this help screen\n"); + exit (exitcode); +} + + +static int +pcli_cmdline(int ARGC, char **ARGV) +{ + int c; + int curindex=0; + char *ptr; + extern char *optarg; + extern int optind; + + if ( ARGC == 1 ) { + pcli_usage(ARGV[0], 0, stdout); + } + + while ((c = getopt(ARGC, ARGV, "kcdapDs:Sg:iW:wIN:n")) != EOF){ + switch (c) { + case 'c': + if (opt == ' ') + opt ='c'; + else + return 0; + break; + case 'd': + if (opt == ' ') + opt ='d'; + else + return 0; + break; + case 'a': + if (opt == ' ') + opt ='a'; + else + return 0; + break; + case 'p': + if (opt == ' ') + opt ='p'; + else + return 0; + break; + case 'D': + dump_format =1; + break; + case 'S': + simple_geometries =1; + break; + case 's': + if( optarg ) + (void)sscanf(optarg, "%d", &sr_id); + else + pcli_usage(ARGV[0], 0, stdout); + break; + case 'g': + geom = optarg; + break; + case 'k': + quoteidentifiers = 1; + break; + case 'i': + forceint4 = 1; + break; + case 'I': + createindex = 1; + break; + case 'n': + readshape = 0; + break; + case 'W': +#ifdef HAVE_ICONV + encoding = optarg; +#else + fprintf(stderr, "WARNING: the -W switch will have no effect. UTF8 disabled at compile time\n"); +#endif + break; + case 'N': + switch (optarg[0]) + { + case 'a': + null_policy = abort_on_null; + break; + case 'i': + null_policy = insert_null; + break; + case 's': + null_policy = skip_null; + break; + default: + fprintf(stderr, "Unsupported NULL geometry handling policy.\nValid policies: insert, skip, abort\n"); + exit(1); + } + break; + case '?': + pcli_usage(ARGV[0], 0, stdout); + default: + return 0; + } + } + + if ( !sr_id ) sr_id = -1; + + if ( !geom ) geom = "the_geom"; + + if ( opt==' ' ) opt = 'c'; + + for (; optind < ARGC; optind++){ + if(curindex ==0){ + shp_file = ARGV[optind]; + }else if(curindex == 1){ + table = ARGV[optind]; + if ( (ptr=strchr(table, '.')) ) + { + *ptr = '\0'; + schema = table; + table = ptr+1; + } + } + curindex++; + } + + /* + * Third argument (if present) is supported for compatibility + * with old shp2pgsql versions taking also database name. + */ + if(curindex < 2 || curindex > 3){ + return 0; + } + + /* + * Transform table name to lower case unless asked + * to keep original case (we'll quote it later on) + */ + if ( ! quoteidentifiers ) + { + LowerCase(table); + if ( schema ) LowerCase(schema); + } + + return 1; +} + +int +main (int ARGC, char **ARGV) +{ + + int rv = 0; + + /* Emit output to stdout/stderr, not a GUI */ + gui_mode = 0; + + /* Parse command line options and set globals */ + if ( ! pcli_cmdline(ARGC, ARGV) ) pcli_usage(ARGV[0], 2, stderr); + + /* Set record number to beginning of file, and translation stage to first one */ + cur_entity = -1; + translation_stage = 1; + + rv = translation_start(); + if( ! rv ) return 1; + while( translation_stage == 2 ) + { + rv = translation_middle(); + if( ! rv ) return 1; + } + rv = translation_end(); + if( ! rv ) return 1; + + return 0; + +} + + +/********************************************************************** + * $Log$ + * + **********************************************************************/ diff --git a/loader/shp2pgsql-core.c b/loader/shp2pgsql-core.c new file mode 100644 index 000000000..40713bfc6 --- /dev/null +++ b/loader/shp2pgsql-core.c @@ -0,0 +1,1862 @@ +/********************************************************************** + * $Id: shp2pgsql.c 3450 2008-12-18 20:42:09Z pramsey $ + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.refractions.net + * Copyright 2001-2003 Refractions Research Inc. + * Copyright 2008 OpenGeo.org + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + ********************************************************************** + * Using shapelib 1.2.8, this program reads in shape files and + * processes it's contents into a Insert statements which can be + * easily piped into a database frontend. + * Specifically designed to insert type 'geometry' (a custom + * written PostgreSQL type) for the shape files and PostgreSQL + * standard types for all attributes of the entity. + * + * Maintainer: Paul Ramsey + * Original Author: Jeff Lounsbury + * + **********************************************************************/ + +#include "../postgis_config.h" +#include "shp2pgsql-core.h" + +#define POINTTYPE 1 +#define LINETYPE 2 +#define POLYGONTYPE 3 +#define MULTIPOINTTYPE 4 +#define MULTILINETYPE 5 +#define MULTIPOLYGONTYPE 6 +#define COLLECTIONTYPE 7 + +#define WKBZOFFSET 0x80000000 +#define WKBMOFFSET 0x40000000 + +typedef struct +{ + double x, y, z, m; +} +Point; + +typedef struct Ring +{ + Point *list; /* list of points */ + struct Ring *next; + int n; /* number of points in list */ + unsigned int linked; /* number of "next" rings */ +} +Ring; + + +/* Public globals */ +int dump_format = 0; /* 0 = SQL inserts, 1 = dump */ +int simple_geometries = 0; /* 0 = MULTIPOLYGON/MULTILINESTRING, 1 = force to POLYGON/LINESTRING */ +int quoteidentifiers = 0; /* 0 = columnname, 1 = "columnName" */ +int forceint4 = 0; /* 0 = allow int8 fields, 1 = no int8 fields */ +int createindex = 0; /* 0 = no index, 1 = create index after load */ +int readshape = 1; /* 0 = load DBF file only, 1 = load everything */ +char opt = ' '; /* load mode: c = create, d = delete, a = append, p = prepare */ +char *table = NULL; /* table to load into */ +char *schema = NULL; /* schema to load into */ +char *geom = NULL; /* geometry column name to use */ +#ifdef HAVE_ICONV +char *encoding = NULL; /* iconv encoding name */ +#endif +int null_policy = insert_null; /* how to handle nulls */ +int sr_id = 0; /* SRID specified */ +char *shp_file = NULL; /* the shape file (without the .shp extension) */ +int gui_mode = 0; /* 1 = GUI, 0 = commandline */ +int translation_stage = 0; + +/* Private globals */ +stringbuffer_t *sb_row; /* stringbuffer to append results to */ +char *col_names = NULL; +char *pgtype; +int istypeM = 0; +int pgdims; +unsigned int wkbtype; +DBFFieldType *types; /* Fields type, width and precision */ +SHPHandle hSHPHandle; +DBFHandle hDBFHandle; +int shpfiletype; +SHPObject *obj=NULL; +int *widths; +int *precisions; +int num_fields,num_records,num_entities; +int cur_entity = -1; +char **field_names; + + +/* Prototypes */ +int Insert_attributes(DBFHandle hDBFHandle, int row); +char *make_good_string(char *str); +char *protect_quotes_string(char *str); +int PIP( Point P, Point* V, int n ); +int CreateTable(void); +int CreateIndex(void); +void usage(char *me, int exitcode, FILE* out); +int InsertPoint(void); +int InsertPolygon(void); +int InsertLineString(void); +int OutputGeometry(char *geometry); +void SetPgType(void); +#ifdef HAVE_ICONV +char *utf8(const char *fromcode, char *inputbuf); +#endif +int FindPolygons(SHPObject *obj, Ring ***Out); +void ReleasePolygons(Ring **polys, int npolys); +int DropTable(char *schema, char *table, char *geom); +void GetFieldsSpec(void); +int LoadData(void); +int OpenShape(void); +void LowerCase(char *s); +void Cleanup(void); + +static int +pgis_exec(const char *sql) +{ +#ifdef PGUI + return pgui_exec(sql); +#else + printf("%s;\n", sql); + return 1; +#endif +} + +static int +pgis_copy_write(const char *line) +{ +#ifdef PGUI + return pgui_copy_write(line); +#else + printf("%s", line); + return 1; +#endif +} + +static int +pgis_copy_start(const char *sql) +{ +#ifdef PGUI + return pgui_copy_start(sql); +#else + printf("%s;\n", sql); + return 1; +#endif +} + +static int +pgis_copy_end(const int rollback) +{ +#ifdef PGUI + return pgui_copy_end(rollback); +#else + printf("\\.\n"); + return 1; +#endif +} + +static void +pgis_logf(const char *fmt, ... ) +{ +#ifndef PGUI + char *msg; +#endif + va_list ap; + + va_start(ap, fmt); + +#ifdef PGUI + pgui_log_va(fmt, ap); +#else + if (!vasprintf (&msg, fmt, ap)) + { + va_end (ap); + return; + } + fprintf(stderr, "%s\n", msg); + free(msg); +#endif + va_end(ap); +} + +/* liblwgeom allocator callback - install the defaults (malloc/free/stdout/stderr) */ +/* TODO hook lwnotice/lwerr up to the GUI */ +void lwgeom_init_allocators() +{ + lwgeom_install_default_allocators(); +} + +char * +make_good_string(char *str) +{ + /* + * find all the tabs and make them \s + * + * 1. find # of tabs + * 2. make new string + * + * we dont escape already escaped tabs + */ + + char *result; + char *ptr, *optr; + int toescape = 0; + size_t size; +#ifdef HAVE_ICONV + char *utf8str=NULL; + + if ( encoding ) + { + utf8str=utf8(encoding, str); + if ( ! utf8str ) exit(1); + str = utf8str; + } +#endif + + ptr = str; + + while (*ptr) + { + if ( *ptr == '\t' || *ptr == '\\' ) toescape++; + ptr++; + } + + if (toescape == 0) return str; + + size = ptr-str+toescape+1; + + result = calloc(1, size); + + optr=result; + ptr=str; + while (*ptr) + { + if ( *ptr == '\t' || *ptr == '\\' ) *optr++='\\'; + *optr++=*ptr++; + } + *optr='\0'; + +#ifdef HAVE_ICONV + if ( encoding ) free(str); +#endif + + return result; + +} + +char * +protect_quotes_string(char *str) +{ + /* + * find all quotes and make them \quotes + * find all '\' and make them '\\' + * + * 1. find # of characters + * 2. make new string + */ + + char *result; + char *ptr, *optr; + int toescape = 0; + size_t size; +#ifdef HAVE_ICONV + char *utf8str=NULL; + + if ( encoding ) + { + utf8str=utf8(encoding, str); + if ( ! utf8str ) exit(1); + str = utf8str; + } +#endif + + ptr = str; + + while (*ptr) + { + if ( *ptr == '\'' || *ptr == '\\' ) toescape++; + ptr++; + } + + if (toescape == 0) return str; + + size = ptr-str+toescape+1; + + result = calloc(1, size); + + optr=result; + ptr=str; + while (*ptr) + { + if ( *ptr == '\\' ) *optr++='\\'; + if ( *ptr == '\'') *optr++='\''; + *optr++=*ptr++; + } + *optr='\0'; + +#ifdef HAVE_ICONV + if ( encoding ) free(str); +#endif + + return result; +} + + + +/* + * PIP(): crossing number test for a point in a polygon + * input: P = a point, + * V[] = vertex points of a polygon V[n+1] with V[n]=V[0] + * returns: 0 = outside, 1 = inside + */ +int +PIP( Point P, Point* V, int n ) +{ + int cn = 0; /* the crossing number counter */ + int i; + + /* loop through all edges of the polygon */ + for (i=0; i P.y)) /* an upward crossing */ + || ((V[i].y > P.y) && (V[i+1].y <= P.y))) + { /* a downward crossing */ + double vt = (float)(P.y - V[i].y) / (V[i+1].y - V[i].y); + if (P.x < V[i].x + vt * (V[i+1].x - V[i].x)) /* P.x < intersect */ + ++cn; /* a valid crossing of y=P.y right of P.x */ + } + } + return (cn&1); /* 0 if even (out), and 1 if odd (in) */ + +} + + + + + +int +Insert_attributes(DBFHandle hDBFHandle, int row) +{ + int i,num_fields; + char val[1024]; + char *escval; + + num_fields = DBFGetFieldCount( hDBFHandle ); + for ( i = 0; i < num_fields; i++ ) + { + if (DBFIsAttributeNULL( hDBFHandle, row, i)) + { + if (dump_format) + { + stringbuffer_append(sb_row, "\\N"); + } + else + { + stringbuffer_append(sb_row, "NULL"); + } + } + else /* Attribute NOT NULL */ + { + switch (types[i]) + { + case FTInteger: + case FTDouble: + if ( -1 == snprintf(val, 1024, "%s", DBFReadStringAttribute(hDBFHandle, row, i)) ) + { + pgis_logf("Warning: field %d name truncated", i); + val[1023] = '\0'; + } + /* pg_atoi() does not do this */ + if ( val[0] == '\0' ) + { + val[0] = '0'; + val[1] = '\0'; + } + if ( val[strlen(val)-1] == '.' ) val[strlen(val)-1] = '\0'; + break; + + case FTString: + case FTLogical: + case FTDate: + if ( -1 == snprintf(val, 1024, "%s", DBFReadStringAttribute(hDBFHandle, row, i)) ) + { + pgis_logf("Warning: field %d name truncated", i); + val[1023] = '\0'; + } + break; + + default: + pgis_logf( + "Error: field %d has invalid or unknown field type (%d)", + i, types[i]); + return 0; + } + + if (dump_format) + { + escval = make_good_string(val); + stringbuffer_aprintf(sb_row, "%s", escval); + //printf("\t"); + } + else + { + escval = protect_quotes_string(val); + stringbuffer_aprintf(sb_row, "'%s'", escval); + //printf(","); + } + if ( val != escval ) free(escval); + } + //only put in delimeter if not last field or a shape will follow + if (readshape == 1 || i < (num_fields - 1)) + { + if (dump_format) + { + stringbuffer_append_c(sb_row, '\t'); + } + else + { + stringbuffer_append_c(sb_row, ','); + } + } + } + return 1; +} + + + + +/* + * formerly main() + */ +int +translation_start () +{ + + sb_row = stringbuffer_create(); + + /* + * Open shapefile and initialize globals + */ + if ( ! OpenShape() ) + return 0; + + if (readshape == 1) + { + /* + * Compute output geometry type + */ + + SetPgType(); + + pgis_logf("Shapefile type: %s", SHPTypeName(shpfiletype)); + pgis_logf("PostGIS type: %s [%dD]", pgtype, pgdims); + } + +#ifdef HAVE_ICONV + if ( encoding ) + { + if ( ! pgis_exec("SET CLIENT_ENCODING TO UTF8") ) return 0; + } +#endif /* defined HAVE_ICONV */ + + /* + * Drop table if requested + */ + if (opt == 'd') + if ( ! DropTable(schema, table, geom) ) + return 0; + + /* + * Get col names and types for table creation + * and data load. + */ + GetFieldsSpec(); + + if ( ! pgis_exec("BEGIN") ) return 0; + + /* + * If not in 'append' mode create the spatial table + */ + if (opt != 'a') + if ( ! CreateTable() ) + return 0; + + translation_stage = 2; /* done start */ + return 1; +} + +int +translation_middle() +{ + /* + * Generate INSERT or COPY lines + */ + if (opt != 'p') + { + if ( ! LoadData() ) + return 0; + } + else + { + translation_stage = 3; /* done middle */ + } + return 1; +} + +int +translation_end() +{ + /* + * Create GiST index if requested + */ + if (createindex) + if ( ! CreateIndex() ) + return 0; + + if ( ! pgis_exec("END") ) return 0; /* End the last transaction */ + + translation_stage = 4; + + return 1; +} + +void +LowerCase(char *s) +{ + int j; + for (j=0; jnVertices == 0 ) + { + pgis_logf("Empty geometries found, aborted."); + return 0; + } + SHPDestroyObject(obj); + } + } + } + else + { + num_entities = DBFGetRecordCount(hDBFHandle); + } + + return 1; +} + +int +CreateTable(void) +{ + int j; + int field_precision, field_width; + DBFFieldType type = -1; + + /* + * Create a table for inserting the shapes into with appropriate + * columns and types + */ + + stringbuffer_clear(sb_row); + + if ( schema ) + { + stringbuffer_aprintf(sb_row, "CREATE TABLE \"%s\".\"%s\" (gid serial PRIMARY KEY", schema, table); + } + else + { + stringbuffer_aprintf(sb_row, "CREATE TABLE \"%s\" (gid serial PRIMARY KEY", table); + } + + for (j=0;j 18 ) + { + stringbuffer_append (sb_row, "numeric"); + } + else + { + stringbuffer_append (sb_row, "float8"); + } + } + else if (type == FTLogical) + { + stringbuffer_append (sb_row, "boolean"); + } + else + { + pgis_logf ("Invalid type in DBF file"); + } + } + /* Run the CREATE TABLE statement in the buffer and clear. */ + stringbuffer_append_c(sb_row, ')'); + if ( ! pgis_exec(stringbuffer_getstring(sb_row)) ) return 0; + stringbuffer_clear(sb_row); + + /* Create the geometry column with an addgeometry call */ + if ( schema && readshape == 1 ) + { + stringbuffer_aprintf(sb_row, "SELECT AddGeometryColumn('%s','%s','%s','%d',", + schema, table, geom, sr_id); + } + else if (readshape == 1) + { + stringbuffer_aprintf(sb_row, "SELECT AddGeometryColumn('','%s','%s','%d',", + table, geom, sr_id); + } + if (pgtype) + { //pgtype will only be set if we are loading geometries + stringbuffer_aprintf(sb_row, "'%s',%d)", pgtype, pgdims); + } + + /* Run the AddGeometryColumn() statement in the buffer and clear. */ + if ( ! pgis_exec(stringbuffer_getstring(sb_row)) ) return 0; + stringbuffer_clear(sb_row); + + return 1; +} + +int +CreateIndex(void) +{ + + stringbuffer_clear(sb_row); + + if ( schema ) + { + stringbuffer_aprintf(sb_row, "CREATE INDEX \"%s_%s_gist\" ON \"%s\".\"%s\" using gist (\"%s\" gist_geometry_ops)", table, geom, schema, table, geom); + } + else + { + stringbuffer_aprintf(sb_row, "CREATE INDEX \"%s_%s_gist\" ON \"%s\" using gist (\"%s\" gist_geometry_ops)", table, geom, table, geom); + } + + /* Run the CREATE INDEX statement in the buffer and clear. */ + if ( ! pgis_exec(stringbuffer_getstring(sb_row)) ) return 0; + stringbuffer_clear(sb_row); + + return 1; +} + +int +LoadData(void) +{ + int trans=0; + + if (cur_entity == -1 && dump_format) + { + char *copysql; + if ( schema ) + { + asprintf(©sql, "COPY \"%s\".\"%s\" %s FROM stdin", + schema, table, col_names); + } + else + { + asprintf(©sql, "COPY \"%s\" %s FROM stdin", + table, col_names); + } + pgis_copy_start(copysql); + free(copysql); + } + + /************************************************************** + * + * MAIN SHAPE OBJECTS SCAN + * + **************************************************************/ + while (cur_entity < num_entities - 1) + { + + cur_entity++; + + /*wrap a transaction block around each 250 inserts... */ + if ( ! dump_format ) + { + if (trans == 250) + { + trans=0; + if ( ! pgis_exec("END") ) return 0; + if ( ! pgis_exec("BEGIN") ) return 0; + } + } + trans++; + /* transaction stuff done */ + + if ( gui_mode && ( cur_entity % 200 == 0 ) ) + { + pgis_logf("Feature #%d", cur_entity); + } + + /* skip the record if it has been deleted */ + if (readshape != 1 && DBFReadDeleted(hDBFHandle, cur_entity)) + { + continue; + } + + /* open the next object */ + if (readshape == 1) + { + obj = SHPReadObject(hSHPHandle,cur_entity); + if ( ! obj ) + { + pgis_logf("Error reading shape object %d", cur_entity); + return 0; + } + + if ( null_policy == skip_null && obj->nVertices == 0 ) + { + SHPDestroyObject(obj); + continue; + } + } + + /* New row, clear the stringbuffer. */ + stringbuffer_clear(sb_row); + + if (!dump_format) + { + if ( schema ) + { + stringbuffer_aprintf(sb_row, "INSERT INTO \"%s\".\"%s\" %s VALUES (", + schema, table, col_names); + } + else + { + stringbuffer_aprintf(sb_row, "INSERT INTO \"%s\" %s VALUES (", + table, col_names); + } + } + if ( ! Insert_attributes(hDBFHandle,cur_entity) ) return 0; + + if (readshape == 1) + { + /* ---------- NULL SHAPE ----------------- */ + if (obj->nVertices == 0) + { + if (dump_format) + { + stringbuffer_append(sb_row, "\\N\n"); + pgis_copy_write(stringbuffer_getstring(sb_row)); + } + else + { + stringbuffer_append(sb_row, "NULL)"); + if ( ! pgis_exec(stringbuffer_getstring(sb_row)) ) return 0; + } + SHPDestroyObject(obj); + continue; + } + + switch (obj->nSHPType) + { + case SHPT_POLYGON: + case SHPT_POLYGONM: + case SHPT_POLYGONZ: + if ( ! InsertPolygon() ) return 0; + break; + + case SHPT_POINT: + case SHPT_POINTM: + case SHPT_POINTZ: + case SHPT_MULTIPOINT: + case SHPT_MULTIPOINTM: + case SHPT_MULTIPOINTZ: + if ( ! InsertPoint() ) return 0; + break; + + case SHPT_ARC: + case SHPT_ARCM: + case SHPT_ARCZ: + if ( ! InsertLineString() ) return 0; + break; + + default: + pgis_logf ("**** Type is NOT SUPPORTED, type id = %d ****", + obj->nSHPType); + break; + + } + + SHPDestroyObject(obj); + } + else + { + if ( dump_format ) + { /* close for dbf only dump format */ + stringbuffer_append_c(sb_row, '\n'); + pgis_copy_write(stringbuffer_getstring(sb_row)); + } + else + { /* close for dbf only sql insert format */ + if ( ! pgis_exec(stringbuffer_getstring(sb_row)) ) return 0; + } + } + + /* Just do 100 entries at a time, then return to the idle loop. */ + if ( cur_entity % 100 == 0 ) return 1; + + } /* END of MAIN SHAPE OBJECT LOOP */ + + if ( dump_format ) + { + pgis_copy_end(0); + } + + translation_stage = 3; /* done middle */ + + return 1; +} + + +int +InsertLineString() +{ + LWCOLLECTION *lwcollection; + + LWGEOM **lwmultilinestrings; + uchar *serialized_lwgeom; + LWGEOM_UNPARSER_RESULT lwg_unparser_result; + + DYNPTARRAY **dpas; + POINT4D point4d; + + int dims = 0, hasz = 0, hasm = 0; + int result; + int u, v, start_vertex, end_vertex; + + if (wkbtype & WKBZOFFSET) hasz = 1; + if (wkbtype & WKBMOFFSET) hasm = 1; + TYPE_SETZM(dims, hasz, hasm); + + if (simple_geometries == 1 && obj->nParts > 1) + { + pgis_logf("We have a Multilinestring with %d parts, can't use -S switch!", obj->nParts); + return 0; + } + + /* Allocate memory for our array of LWLINEs and our dynptarrays */ + lwmultilinestrings = malloc(sizeof(LWPOINT *) * obj->nParts); + dpas = malloc(sizeof(DYNPTARRAY *) * obj->nParts); + + /* We need an array of pointers to each of our sub-geometries */ + for (u = 0; u < obj->nParts; u++) + { + /* Create a dynptarray containing the line points */ + dpas[u] = dynptarray_create(obj->nParts, dims); + + /* Set the start/end vertices depending upon whether this is + a MULTILINESTRING or not */ + if ( u == obj->nParts-1 ) + end_vertex = obj->nVertices; + else + end_vertex = obj->panPartStart[u + 1]; + + start_vertex = obj->panPartStart[u]; + + for (v = start_vertex; v < end_vertex; v++) + { + /* Generate the point */ + point4d.x = obj->padfX[v]; + point4d.y = obj->padfY[v]; + + if (wkbtype & WKBZOFFSET) + point4d.z = obj->padfZ[v]; + if (wkbtype & WKBMOFFSET) + point4d.m = obj->padfM[v]; + + dynptarray_addPoint4d(dpas[u], &point4d, 0); + } + + /* Generate the LWLINE */ + lwmultilinestrings[u] = lwline_as_lwgeom(lwline_construct(sr_id, NULL, dpas[u]->pa)); + } + + /* If using MULTILINESTRINGs then generate the serialized collection, otherwise just a single LINESTRING */ + if (simple_geometries == 0) + { + lwcollection = lwcollection_construct(MULTILINETYPE, sr_id, NULL, obj->nParts, lwmultilinestrings); + serialized_lwgeom = lwgeom_serialize(lwcollection_as_lwgeom(lwcollection)); + } + else + { + serialized_lwgeom = lwgeom_serialize(lwmultilinestrings[0]); + } + + result = serialized_lwgeom_to_hexwkb(&lwg_unparser_result, serialized_lwgeom, PARSER_CHECK_ALL, -1); + + if (result) + { + pgis_logf("ERROR: %s", lwg_unparser_result.message); + return 0; + } + + if ( ! OutputGeometry(lwg_unparser_result.wkoutput) ) return 0; + + /* Free all of the allocated items */ + lwfree(lwg_unparser_result.wkoutput); + lwfree(serialized_lwgeom); + + for (u = 0; u < obj->nParts; u++) + { + lwline_free(lwgeom_as_lwline(lwmultilinestrings[u])); + lwfree(dpas[u]); + } + + lwfree(dpas); + lwfree(lwmultilinestrings); + + return 1; +} + +int +FindPolygons(SHPObject *obj, Ring ***Out) +{ + Ring **Outer; /* Pointers to Outer rings */ + int out_index=0; /* Count of Outer rings */ + Ring **Inner; /* Pointers to Inner rings */ + int in_index=0; /* Count of Inner rings */ + int pi; /* part index */ + +#if POSTGIS_DEBUG_LEVEL > 0 + static int call = -1; + call++; +#endif + + LWDEBUGF(4, "FindPolygons[%d]: allocated space for %d rings\n", call, obj->nParts); + + /* Allocate initial memory */ + Outer = (Ring**)malloc(sizeof(Ring*)*obj->nParts); + Inner = (Ring**)malloc(sizeof(Ring*)*obj->nParts); + + /* Iterate over rings dividing in Outers and Inners */ + for (pi=0; pinParts; pi++) + { + int vi; /* vertex index */ + int vs; /* start index */ + int ve; /* end index */ + int nv; /* number of vertex */ + double area = 0.0; + Ring *ring; + + /* Set start and end vertexes */ + if ( pi==obj->nParts-1 ) ve = obj->nVertices; + else ve = obj->panPartStart[pi+1]; + vs = obj->panPartStart[pi]; + + /* Compute number of vertexes */ + nv = ve-vs; + + /* Allocate memory for a ring */ + ring = (Ring*)malloc(sizeof(Ring)); + ring->list = (Point*)malloc(sizeof(Point)*nv); + ring->n = nv; + ring->next = NULL; + ring->linked = 0; + + /* Iterate over ring vertexes */ + for ( vi=vs; vilist[vi-vs].x = obj->padfX[vi]; + ring->list[vi-vs].y = obj->padfY[vi]; + ring->list[vi-vs].z = obj->padfZ[vi]; + ring->list[vi-vs].m = obj->padfM[vi]; + + area += (obj->padfX[vi] * obj->padfY[vn]) - + (obj->padfY[vi] * obj->padfX[vn]); + } + + /* Close the ring with first vertex */ + /*ring->list[vi].x = obj->padfX[vs]; */ + /*ring->list[vi].y = obj->padfY[vs]; */ + /*ring->list[vi].z = obj->padfZ[vs]; */ + /*ring->list[vi].m = obj->padfM[vs]; */ + + /* Clockwise (or single-part). It's an Outer Ring ! */ + if (area < 0.0 || obj->nParts ==1) + { + Outer[out_index] = ring; + out_index++; + } + + /* Counterclockwise. It's an Inner Ring ! */ + else + { + Inner[in_index] = ring; + in_index++; + } + } + + LWDEBUGF(4, "FindPolygons[%d]: found %d Outer, %d Inners\n", call, out_index, in_index); + + /* Put the inner rings into the list of the outer rings */ + /* of which they are within */ + for (pi=0; pilist[0].x; + pt.y = inner->list[0].y; + + pt2.x = inner->list[1].x; + pt2.y = inner->list[1].y; + + for (i=0; ilist, Outer[i]->n); + if ( in || PIP(pt2, Outer[i]->list, Outer[i]->n) ) + { + outer = Outer[i]; + break; + } + } + + if ( outer ) + { + outer->linked++; + while (outer->next) outer = outer->next; + outer->next = inner; + } + else + { + /* The ring wasn't within any outer rings, */ + /* assume it is a new outer ring. */ + LWDEBUGF(4, "FindPolygons[%d]: hole %d is orphan\n", call, pi); + + Outer[out_index] = inner; + out_index++; + } + } + + *Out = Outer; + free(Inner); + + return out_index; +} + +void +ReleasePolygons(Ring **polys, int npolys) +{ + int pi; + /* Release all memory */ + for (pi=0; pinext; + free(temp->list); + free(temp); + } + } + free(polys); +} + +/*This function basically deals with the polygon case. */ +/*it sorts the polys in order of outer,inner,inner, so that inners */ +/*always come after outers they are within */ +int +InsertPolygon(void) +{ + Ring **Outer; + int polygon_total, ring_total; + int pi, vi; // part index and vertex index + int u; + + LWCOLLECTION *lwcollection = NULL; + + LWGEOM **lwpolygons; + uchar *serialized_lwgeom; + LWGEOM_UNPARSER_RESULT lwg_unparser_result; + + LWPOLY *lwpoly; + DYNPTARRAY *dpas; + POINTARRAY ***pas; + POINT4D point4d; + + int dims = 0, hasz = 0, hasm = 0; + int result; + + /* Determine the correct dimensions: */ + if (wkbtype & WKBZOFFSET) hasz = 1; + if (wkbtype & WKBMOFFSET) hasm = 1; + TYPE_SETZM(dims, hasz, hasm); + + polygon_total = FindPolygons(obj, &Outer); + + if (simple_geometries == 1 && polygon_total != 1) /* We write Non-MULTI geometries, but have several parts: */ + { + pgis_logf("We have a Multipolygon with %d parts, can't use -S switch!", polygon_total); + return 0; + } + + /* Allocate memory for our array of LWPOLYs */ + lwpolygons = malloc(sizeof(LWPOLY *) * polygon_total); + + /* Allocate memory for our POINTARRAY pointers for each polygon */ + pas = malloc(sizeof(POINTARRAY **) * polygon_total); + + /* Cycle through each individual polygon */ + for (pi = 0; pi < polygon_total; pi++) + { + Ring *polyring; + int ring_index = 0; + + /* Firstly count through the total number of rings in this polygon */ + ring_total = 0; + polyring = Outer[pi]; + while (polyring) + { + ring_total++; + polyring = polyring->next; + } + + /* Reserve memory for the POINTARRAYs representing each ring */ + pas[pi] = malloc(sizeof(POINTARRAY *) * ring_total); + + /* Cycle through each ring within the polygon, starting with the outer */ + polyring = Outer[pi]; + + while (polyring) + { + /* Create a DYNPTARRAY containing the points making up the ring */ + dpas = dynptarray_create(polyring->n, dims); + + for (vi = 0; vi < polyring->n; vi++) + { + /* Build up a point array of all the points in this ring */ + point4d.x = polyring->list[vi].x; + point4d.y = polyring->list[vi].y; + + if (wkbtype & WKBZOFFSET) + point4d.z = polyring->list[vi].z; + if (wkbtype & WKBMOFFSET) + point4d.m = polyring->list[vi].m; + + dynptarray_addPoint4d(dpas, &point4d, 0); + } + + /* Copy the POINTARRAY pointer from the DYNPTARRAY structure so we can + use the LWPOLY constructor */ + pas[pi][ring_index] = dpas->pa; + + /* Free the DYNPTARRAY structure (we don't need this part anymore as we + have the reference to the internal POINTARRAY) */ + lwfree(dpas); + + polyring = polyring->next; + ring_index++; + } + + /* Generate the LWGEOM */ + lwpoly = lwpoly_construct(sr_id, NULL, ring_total, pas[pi]); + lwpolygons[pi] = lwpoly_as_lwgeom(lwpoly); + } + + ReleasePolygons(Outer, polygon_total); + + /* If using MULTIPOLYGONS then generate the serialized collection, otherwise just a single POLYGON */ + if (simple_geometries == 0) + { + lwcollection = lwcollection_construct(MULTIPOLYGONTYPE, sr_id, NULL, polygon_total, lwpolygons); + serialized_lwgeom = lwgeom_serialize(lwcollection_as_lwgeom(lwcollection)); + } + else + { + serialized_lwgeom = lwgeom_serialize(lwpolygons[0]); + } + + result = serialized_lwgeom_to_hexwkb(&lwg_unparser_result, serialized_lwgeom, PARSER_CHECK_ALL, -1); + + if (result) + { + pgis_logf( "ERROR: %s", lwg_unparser_result.message); + return 0; + } + + if ( ! OutputGeometry(lwg_unparser_result.wkoutput) ) return 0; + + /* Free all of the allocated items */ + lwfree(lwg_unparser_result.wkoutput); + lwfree(serialized_lwgeom); + + /* Cycle through each polygon, freeing everything we need... */ + for (u = 0; u < polygon_total; u++) + lwpoly_free(lwgeom_as_lwpoly(lwpolygons[u])); + + /* Free the pointer arrays */ + lwfree(pas); + lwfree(lwpolygons); + if (simple_geometries == 0) + lwfree(lwcollection); + + return 1; +} + +/* + * Insert either a POINT or MULTIPOINT into the output stream + */ +int +InsertPoint(void) +{ + LWCOLLECTION *lwcollection; + + LWGEOM **lwmultipoints; + uchar *serialized_lwgeom; + LWGEOM_UNPARSER_RESULT lwg_unparser_result; + + DYNPTARRAY **dpas; + POINT4D point4d; + + int dims = 0, hasz = 0, hasm = 0; + int result; + int u; + + /* Determine the correct dimensions: */ + if (wkbtype & WKBZOFFSET) hasz = 1; + if (wkbtype & WKBMOFFSET) hasm = 1; + TYPE_SETZM(dims, hasz, hasm); + + /* Allocate memory for our array of LWPOINTs and our dynptarrays */ + lwmultipoints = malloc(sizeof(LWPOINT *) * obj->nVertices); + dpas = malloc(sizeof(DYNPTARRAY *) * obj->nVertices); + + /* We need an array of pointers to each of our sub-geometries */ + for (u = 0; u < obj->nVertices; u++) + { + /* Generate the point */ + point4d.x = obj->padfX[u]; + point4d.y = obj->padfY[u]; + + if (wkbtype & WKBZOFFSET) + point4d.z = obj->padfZ[u]; + if (wkbtype & WKBMOFFSET) + point4d.m = obj->padfM[u]; + + /* Create a dynptarray containing a single point */ + dpas[u] = dynptarray_create(1, dims); + dynptarray_addPoint4d(dpas[u], &point4d, 0); + + /* Generate the LWPOINT */ + lwmultipoints[u] = lwpoint_as_lwgeom(lwpoint_construct(sr_id, NULL, dpas[u]->pa)); + } + + /* If we have more than 1 vertex then we are working on a MULTIPOINT and so generate a MULTIPOINT + rather than a POINT */ + if (obj->nVertices > 1) + { + lwcollection = lwcollection_construct(MULTIPOINTTYPE, sr_id, NULL, obj->nVertices, lwmultipoints); + serialized_lwgeom = lwgeom_serialize(lwcollection_as_lwgeom(lwcollection)); + } + else + { + serialized_lwgeom = lwgeom_serialize(lwmultipoints[0]); + } + + result = serialized_lwgeom_to_hexwkb(&lwg_unparser_result, serialized_lwgeom, PARSER_CHECK_ALL, -1); + + if (result) + { + pgis_logf("ERROR: %s", lwg_unparser_result.message); + return 0; + } + + if ( ! OutputGeometry(lwg_unparser_result.wkoutput) ) return 0; + + /* Free all of the allocated items */ + lwfree(lwg_unparser_result.wkoutput); + lwfree(serialized_lwgeom); + + for (u = 0; u < obj->nVertices; u++) + { + lwpoint_free(lwgeom_as_lwpoint(lwmultipoints[u])); + lwfree(dpas[u]); + } + + lwfree(dpas); + lwfree(lwmultipoints); + + return 1; +} + +int +OutputGeometry(char *geometry) +{ + if (!dump_format) + stringbuffer_append(sb_row, "'"); + + stringbuffer_aprintf(sb_row, "%s", geometry); + + if (!dump_format) + { + stringbuffer_append(sb_row, "')"); + if ( ! pgis_exec(stringbuffer_getstring(sb_row)) ) return 0; + } + else + { + stringbuffer_append_c(sb_row, '\n'); + pgis_copy_write(stringbuffer_getstring(sb_row)); + } + stringbuffer_clear(sb_row); + return 1; + +} + + + +void +SetPgType(void) +{ + switch (shpfiletype) + { + case SHPT_POINT: /* Point */ + pgtype = "POINT"; + wkbtype = POINTTYPE; + pgdims = 2; + break; + case SHPT_ARC: /* PolyLine */ + pgtype = "MULTILINESTRING"; + wkbtype = MULTILINETYPE ; + pgdims = 2; + break; + case SHPT_POLYGON: /* Polygon */ + pgtype = "MULTIPOLYGON"; + wkbtype = MULTIPOLYGONTYPE; + pgdims = 2; + break; + case SHPT_MULTIPOINT: /* MultiPoint */ + pgtype = "MULTIPOINT"; + wkbtype = MULTIPOINTTYPE; + pgdims = 2; + break; + case SHPT_POINTM: /* PointM */ + wkbtype = POINTTYPE | WKBMOFFSET; + pgtype = "POINTM"; + pgdims = 3; + istypeM = 1; + break; + case SHPT_ARCM: /* PolyLineM */ + wkbtype = MULTILINETYPE | WKBMOFFSET; + pgtype = "MULTILINESTRINGM"; + pgdims = 3; + istypeM = 1; + break; + case SHPT_POLYGONM: /* PolygonM */ + wkbtype = MULTIPOLYGONTYPE | WKBMOFFSET; + pgtype = "MULTIPOLYGONM"; + pgdims = 3; + istypeM = 1; + break; + case SHPT_MULTIPOINTM: /* MultiPointM */ + wkbtype = MULTIPOINTTYPE | WKBMOFFSET; + pgtype = "MULTIPOINTM"; + pgdims = 3; + istypeM = 1; + break; + case SHPT_POINTZ: /* PointZ */ + wkbtype = POINTTYPE | WKBMOFFSET | WKBZOFFSET; + pgtype = "POINT"; + pgdims = 4; + break; + case SHPT_ARCZ: /* PolyLineZ */ + pgtype = "MULTILINESTRING"; + wkbtype = MULTILINETYPE | WKBZOFFSET | WKBMOFFSET; + pgdims = 4; + break; + case SHPT_POLYGONZ: /* MultiPolygonZ */ + pgtype = "MULTIPOLYGON"; + wkbtype = MULTIPOLYGONTYPE | WKBZOFFSET | WKBMOFFSET; + pgdims = 4; + break; + case SHPT_MULTIPOINTZ: /* MultiPointZ */ + pgtype = "MULTIPOINT"; + wkbtype = MULTIPOINTTYPE | WKBZOFFSET | WKBMOFFSET; + pgdims = 4; + break; + default: + pgtype = "GEOMETRY"; + wkbtype = COLLECTIONTYPE | WKBZOFFSET | WKBMOFFSET; + pgdims = 4; + pgis_logf("Unknown geometry type: %d", shpfiletype); + break; + } + + if (simple_geometries) + { + // adjust geometry name for CREATE TABLE by skipping MULTI + if ((wkbtype & 0x7) == MULTIPOLYGONTYPE) pgtype += 5; + if ((wkbtype & 0x7) == MULTILINETYPE) pgtype += 5; + } +} + +int +DropTable(char *schema, char *table, char *geom) +{ + /*---------------Drop the table-------------------------- + * TODO: if the table has more then one geometry column + * the DROP TABLE call will leave spurious records in + * geometry_columns. + * + * If the geometry column in the table being dropped + * does not match 'the_geom' or the name specified with + * -g an error is returned by DropGeometryColumn. + * + * The table to be dropped might not exist. + * + */ + char *sql; + + if ( schema ) + { + if (readshape == 1) + { + asprintf(&sql, "SELECT DropGeometryColumn('%s','%s','%s')", schema, table, geom); + if ( ! pgis_exec(sql) ) + { + free(sql); + return 0; + } + free(sql); + } + asprintf(&sql, "DROP TABLE \"%s\".\"%s\"", schema, table); + if ( ! pgis_exec(sql) ) + { + free(sql); + return 0; + } + free(sql); + } + else + { + if (readshape == 1) + { + asprintf(&sql, "SELECT DropGeometryColumn('','%s','%s')", table, geom); + if ( ! pgis_exec(sql) ) + { + free(sql); + return 0; + } + free(sql); + } + asprintf(&sql, "DROP TABLE \"%s\"", table); + if ( ! pgis_exec(sql) ) + { + free(sql); + return 0; + } + free(sql); + } + return 1; +} + +void +GetFieldsSpec(void) +{ + /* + * Shapefile (dbf) field name are at most 10chars + 1 NULL. + * Postgresql field names are at most 63 bytes + 1 NULL. + */ +#define MAXFIELDNAMELEN 64 + int field_precision, field_width; + int j, z; + char name[MAXFIELDNAMELEN]; + char name2[MAXFIELDNAMELEN]; + DBFFieldType type = -1; +#ifdef HAVE_ICONV + char *utf8str; +#endif + + num_fields = DBFGetFieldCount( hDBFHandle ); + num_records = DBFGetRecordCount(hDBFHandle); + field_names = malloc(num_fields*sizeof(char*)); + types = (DBFFieldType *)malloc(num_fields*sizeof(int)); + widths = malloc(num_fields*sizeof(int)); + precisions = malloc(num_fields*sizeof(int)); + if (readshape == 1) + { + col_names = malloc((num_fields+2) * sizeof(char) * MAXFIELDNAMELEN); + } + { //for dbf only, we do not need to allocate slot for the_geom + col_names = malloc((num_fields+1) * sizeof(char) * MAXFIELDNAMELEN); + } + strcpy(col_names, "(" ); + + /*fprintf(stderr, "Number of fields from DBF: %d\n", num_fields); */ + for (j=0;jPGIS mapping + * + * Revision 1.105 2006/01/09 16:40:16 strk + * ISO C90 comments, signedness mismatch fixes + * + * Revision 1.104 2005/11/01 09:25:47 strk + * Reworked NULL geometries handling code letting user specify policy (insert,skip,abort). Insert is the default. + * + * Revision 1.103 2005/10/24 15:54:22 strk + * fixed wrong assumption about maximum size of integer attributes (width is maximum size of text representation) + * + * Revision 1.102 2005/10/24 11:30:59 strk + * + * Fixed a bug in string attributes handling truncating values of maximum + * allowed length, curtesy of Lars Roessiger. + * Reworked integer attributes handling to be stricter in dbf->sql mapping + * and to allow for big int8 values in sql->dbf conversion + * + * Revision 1.101 2005/10/21 11:33:55 strk + * Applied patch by Lars Roessiger handling numerical values with a trailing decima + * l dot + * + * Revision 1.100 2005/10/13 13:40:20 strk + * Fixed return code from shp2pgsql + * + * Revision 1.99 2005/10/03 18:08:55 strk + * Stricter string attributes lenght handling. DBF header will be used + * to set varchar maxlenght, (var)char typmod will be used to set DBF header + * len. + * + * Revision 1.98 2005/10/03 07:45:58 strk + * Issued a warning when -W is specified and no UTF8 support has been compiled in. + * + * Revision 1.97 2005/09/30 08:59:29 strk + * Fixed release of stack memory occurring when shp2pgsql is compiled with USE_ICONV defined, an attribute value needs to be escaped and no -W is used + * + * Revision 1.96 2005/08/29 22:36:25 strk + * Removed premature object destruction in InsertLineString{WKT,} causing segfault + * + * Revision 1.95 2005/08/29 11:48:33 strk + * Fixed sprintf() calls to avoid overlapping memory, + * reworked not-null objects existance check to reduce startup costs. + * + * Revision 1.94 2005/07/27 02:47:14 strk + * Support for multibyte field names in loader + * + * Revision 1.93 2005/07/27 02:35:50 strk + * Minor cleanups in loader + * + * Revision 1.92 2005/07/27 02:07:01 strk + * Fixed handling of POINT types as WKT (-w) in loader + * + * Revision 1.91 2005/07/04 09:47:03 strk + * Added conservative iconv detection code + * + * Revision 1.90 2005/06/16 17:55:58 strk + * Added -I switch for GiST index creation in loader + * + * Revision 1.89 2005/04/21 09:08:34 strk + * Applied patch from Ron Mayer fixing a segfault in string escaper funx + * + * Revision 1.88 2005/04/14 12:58:59 strk + * Applied patch by Gino Lucrezi fixing bug in string escaping code. + * + * Revision 1.87 2005/04/06 14:16:43 strk + * Removed manual update of gid field. + * + * Revision 1.86 2005/04/06 14:02:08 mschaber + * added -p option (prepare mode) that spits out the table schema without + * inserting any data. + * + * Revision 1.85 2005/04/06 10:46:10 strk + * Bugfix in -w (hwgeom) handling of ZM shapefiles. + * Big reorganizzation of code to easy maintainance. + * + * Revision 1.84 2005/04/04 20:51:26 strk + * Added -w flag to output old (WKT/HWGEOM) sql. + * + * Revision 1.83 2005/03/15 12:24:40 strk + * hole-in-ring detector made more readable + * + * Revision 1.82 2005/03/14 22:02:31 strk + * Fixed holes handling. + * + * Revision 1.81 2005/03/08 11:06:33 strk + * modernized old-style parameter declarations + * + * Revision 1.80 2005/03/04 14:48:22 strk + * Applied patch from Jonne Savolainen fixing multilines handling + * + * Revision 1.79 2005/01/31 22:15:22 strk + * Added maintainer notice, to reduce Jeff-strk mail bounces + * + * Revision 1.78 2005/01/17 09:21:13 strk + * Added one more bytes for terminating NULL in utf8 encoder + * + * Revision 1.77 2005/01/16 16:50:01 strk + * String escaping algorithm made simpler and more robust. + * Removed escaped strings leaking. + * Fixed UTF8 encoder to allocate enough space for 3bytes chars strings. + * + * Revision 1.76 2005/01/12 17:03:20 strk + * Added optional UTF8 output support as suggested by IIDA Tetsushi + * + * Revision 1.75 2004/11/15 10:51:35 strk + * Fixed a bug in PIP invocation, added some debugging lines. + * + * Revision 1.74 2004/10/17 13:25:44 strk + * removed USE_WKB partially-used define + * + * Revision 1.73 2004/10/17 13:24:44 strk + * HEXWKB polygon + * + * Revision 1.72 2004/10/17 12:59:12 strk + * HEXWKB multiline output + * + * Revision 1.71 2004/10/17 12:26:02 strk + * Point and MultiPoint loaded using HEXWKB. + * + * Revision 1.70 2004/10/15 22:01:35 strk + * Initial WKB functionalities + * + * Revision 1.69 2004/10/07 21:52:28 strk + * Lots of rewriting/cleanup. TypeM/TypeZ supports. + * + * Revision 1.68 2004/10/07 06:54:24 strk + * cleanups + * + * Revision 1.67 2004/10/06 10:11:16 strk + * Other separator fixes + * + * Revision 1.66 2004/10/06 09:40:27 strk + * Handled 0-DBF-attributes corner case. + * + * Revision 1.65 2004/09/20 17:13:31 strk + * changed comments to better show shape type handling + * + * Revision 1.64 2004/08/20 08:14:37 strk + * Whole output wrapped in transaction blocks. + * Drops are out of transaction, and multiple transactions are used + * for INSERT mode. + * + **********************************************************************/ diff --git a/loader/shp2pgsql-core.h b/loader/shp2pgsql-core.h new file mode 100644 index 000000000..28354d95a --- /dev/null +++ b/loader/shp2pgsql-core.h @@ -0,0 +1,70 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shapefil.h" +#include "getopt.h" + +#ifdef HAVE_ICONV +#include +#endif + +#include "../liblwgeom/liblwgeom.h" +#include "stringbuffer.h" + +#define RCSID "$Id: shp2pgsql.c 3450 2008-12-18 20:42:09Z pramsey $" + +enum { + insert_null, + skip_null, + abort_on_null +}; + +/* +** Global variables for Core +*/ +extern char opt; /* load mode: c = create, d = delete, a = append, p = prepare */ +extern char *table; /* table to load into */ +extern char *schema; /* schema to load into */ +extern char *geom; /* geometry column name to use */ +extern char *shp_file; /* the shape file (without the .shp extension) */ +extern int dump_format; /* 0 = SQL inserts, 1 = dump */ +extern int simple_geometries; /* 0 = MULTIPOLYGON/MULTILINESTRING, 1 = force to POLYGON/LINESTRING */ +extern int quoteidentifiers; /* 0 = columnname, 1 = "columnName" */ +extern int forceint4; /* 0 = allow int8 fields, 1 = no int8 fields */ +extern int createindex; /* 0 = no index, 1 = create index after load */ +extern int readshape; /* 0 = load DBF file only, 1 = load everything */ +#ifdef HAVE_ICONV +extern char *encoding; /* iconv encoding name */ +#endif +extern int null_policy; /* how to handle nulls */ +extern int sr_id; /* SRID specified */ +extern int gui_mode; /* 1 = GUI, 0 = commandline */ +extern int translation_stage; /* 1 = ready, 2 = done start, 3 = done middle, 4 = done end */ +extern int cur_entity; /* what record are we working on? */ + + +/* +** Global variables used only by GUI +*/ + +/* +** Prototypes across modules +*/ +extern int translation_start(void); +extern int translation_middle(void); +extern int translation_end(void); +extern void pgui_log_va(const char *fmt, va_list ap); +extern int pgui_exec(const char *sql); +extern int pgui_copy_write(const char *line); +extern int pgui_copy_start(const char *sql); +extern int pgui_copy_end(const int rollback); +extern void LowerCase(char *s); + diff --git a/loader/shp2pgsql-gui.c b/loader/shp2pgsql-gui.c new file mode 100644 index 000000000..f20f18025 --- /dev/null +++ b/loader/shp2pgsql-gui.c @@ -0,0 +1,905 @@ +/********************************************************************** + * $Id: shp2pgsql-gui.c 3450 2008-12-18 20:42:09Z pramsey $ + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.refractions.net + * Copyright 2008 OpenGeo.org + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + * Maintainer: Paul Ramsey + * + **********************************************************************/ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include "libpq-fe.h" +#include "shp2pgsql-core.h" + +/* +** Global variables for GUI only +*/ + +/* Main window */ +static GtkWidget *window_main; +static GtkWidget *entry_pg_user; +static GtkWidget *entry_pg_pass; +static GtkWidget *entry_pg_host; +static GtkWidget *entry_pg_port; +static GtkWidget *entry_pg_db; +static GtkWidget *entry_config_table; +static GtkWidget *entry_config_schema; +static GtkWidget *entry_config_srid; +static GtkWidget *entry_config_geocolumn; +static GtkWidget *label_pg_connection_test; +static GtkWidget *textview_log; +static GtkWidget *file_chooser_button_shape; +static GtkTextBuffer *textbuffer_log; + +/* Options window */ +static GtkWidget *window_options; +static GtkWidget *entry_options_encoding; +static GtkWidget *entry_options_nullpolicy; +static GtkWidget *checkbutton_options_preservecase; +static GtkWidget *checkbutton_options_forceint; +static GtkWidget *checkbutton_options_autoindex; +static GtkWidget *checkbutton_options_dbfonly; + +/* Other */ +static char *pgui_errmsg = NULL; +static PGconn *pg_connection; + + +/* +** Write a message to the Import Log text area. +*/ +static void +pgui_logf(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + + pgui_log_va(fmt, ap); + + va_end(ap); + return; +} + +/* +** Write a message to the Import Log text area. +*/ +void +pgui_log_va(const char *fmt, va_list ap) +{ + char *msg; + + if (!vasprintf (&msg, fmt, ap)) return; + + gtk_text_buffer_insert_at_cursor(textbuffer_log, msg, -1); + gtk_text_buffer_insert_at_cursor(textbuffer_log, "\n", -1); + gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(textview_log), gtk_text_buffer_get_insert(textbuffer_log) ); + + free(msg); + return; +} + +static void +pgui_seterr(const char *errmsg) +{ + if ( pgui_errmsg ) + { + free(pgui_errmsg); + } + pgui_errmsg = strdup(errmsg); + return; +} + + + +/* +** Run a SQL command against the current connection. +*/ +int +pgui_exec(const char *sql) +{ + PGresult *res = NULL; + ExecStatusType status; + + /* We need a connection to do anything. */ + if ( ! pg_connection ) return 0; + if ( ! sql ) return 0; + + res = PQexec(pg_connection, sql); + status = PQresultStatus(res); + PQclear(res); + + /* Did something unexpected happen? */ + if ( ! ( status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK ) ) + { + /* Log notices and return success. */ + if ( status == PGRES_NONFATAL_ERROR ) + { + pgui_logf("%s", PQerrorMessage(pg_connection)); + return 1; + } + + /* Log errors and return failure. */ + pgui_logf("Failed record number #%d", cur_entity); + pgui_logf("Failed SQL was: %s", sql); + pgui_logf("Failed in pgui_exec(): %s", PQerrorMessage(pg_connection)); + return 0; + } + + return 1; +} + +/* +** Start the COPY process. +*/ +int +pgui_copy_start(const char *sql) +{ + PGresult *res = NULL; + ExecStatusType status; + + /* We need a connection to do anything. */ + if ( ! pg_connection ) return 0; + if ( ! sql ) return 0; + + res = PQexec(pg_connection, sql); + status = PQresultStatus(res); + PQclear(res); + + /* Did something unexpected happen? */ + if ( status != PGRES_COPY_IN ) + { + /* Log errors and return failure. */ + pgui_logf("Failed SQL was: %s", sql); + pgui_logf("Failed in pgui_copy_start(): %s", PQerrorMessage(pg_connection)); + return 0; + } + + return 1; +} + +/* +** Send a line (row) of data into the COPY procedure. +*/ +int +pgui_copy_write(const char *line) +{ + + /* We need a connection to do anything. */ + if ( ! pg_connection ) return 0; + if ( ! line ) return 0; + + /* Did something unexpected happen? */ + if ( PQputCopyData(pg_connection, line, strlen(line)) < 0 ) + { + /* Log errors and return failure. */ + pgui_logf("Failed record number #%d", cur_entity); + pgui_logf("Failed row was: %s", line); + pgui_logf("Failed in pgui_copy_write(): %s", PQerrorMessage(pg_connection)); + return 0; + } + + return 1; + +} + +/* +** Finish the COPY process. +*/ +int +pgui_copy_end(const int rollback) +{ + char *errmsg = NULL; + + /* We need a connection to do anything. */ + if ( ! pg_connection ) return 0; + + if ( rollback ) errmsg = "Roll back the copy."; + + /* Did something unexpected happen? */ + if ( PQputCopyEnd(pg_connection, errmsg) < 0 ) + { + /* Log errors and return failure. */ + pgui_logf("Failed in pgui_copy_end(): %s", PQerrorMessage(pg_connection)); + return 0; + } + + return 1; +} + +static gboolean +check_translation_stage (gpointer data) +{ + int rv = 0; + if ( translation_stage == 0 ) return FALSE; + if ( translation_stage == 4 ) + { + pgui_logf("Import complete."); + return FALSE; + } + if ( translation_stage == 1 ) + { + rv = translation_start(); + if ( ! rv ) + { + pgui_logf("Import failed."); + translation_stage = 0; + } + return TRUE; + } + if ( translation_stage == 2 ) + { + rv = translation_middle(); + if ( ! rv ) + { + pgui_logf("Import failed."); + translation_stage = 0; + } + return TRUE; + } + if ( translation_stage == 3 ) + { + rv = translation_end(); + if ( ! rv ) + { + pgui_logf("Import failed."); + translation_stage = 0; + } + return TRUE; + } + return FALSE; +} + +/* Terminate the main loop and exit the application. */ +static void +pgui_quit (GtkWidget *widget, gpointer data) +{ + if ( pg_connection) PQfinish(pg_connection); + pg_connection = NULL; + gtk_main_quit (); +} + +static char * +pgui_read_connection(void) +{ + const char *pg_host = gtk_entry_get_text(GTK_ENTRY(entry_pg_host)); + const char *pg_port = gtk_entry_get_text(GTK_ENTRY(entry_pg_port)); + const char *pg_user = gtk_entry_get_text(GTK_ENTRY(entry_pg_user)); + const char *pg_pass = gtk_entry_get_text(GTK_ENTRY(entry_pg_pass)); + const char *pg_db = gtk_entry_get_text(GTK_ENTRY(entry_pg_db)); + char *connection_string = NULL; + + if ( ! pg_host || strlen(pg_host) == 0 ) + { + pgui_seterr("Fill in the server host."); + return NULL; + } + if ( ! pg_port || strlen(pg_port) == 0 ) + { + pgui_seterr("Fill in the server port."); + return NULL; + } + if ( ! pg_user || strlen(pg_user) == 0 ) + { + pgui_seterr("Fill in the user name."); + return NULL; + } + if ( ! pg_db || strlen(pg_db) == 0 ) + { + pgui_seterr("Fill in the database name."); + return NULL; + } + if ( ! atoi(pg_port) ) + { + pgui_seterr("Server port must be a number."); + return NULL; + } + if ( ! asprintf(&connection_string, "user=%s password=%s port=%s host=%s dbname=%s", pg_user, pg_pass, pg_port, pg_host, pg_db) ) + { + return NULL; + } + if ( connection_string ) + { + return connection_string; + } + return NULL; +} + +static char * +pgui_read_destination(void) +{ + const char *pg_table = gtk_entry_get_text(GTK_ENTRY(entry_config_table)); + const char *pg_schema = gtk_entry_get_text(GTK_ENTRY(entry_config_schema)); + const char *pg_geom = gtk_entry_get_text(GTK_ENTRY(entry_config_geocolumn)); + + char *dest_string = NULL; + + if ( ! pg_table || strlen(pg_table) == 0 ) + { + pgui_seterr("Fill in the destination table."); + return NULL; + } + if ( ! pg_schema || strlen(pg_schema) == 0 ) + { + pg_schema = "public"; + } + if ( ! pg_geom || strlen(pg_geom) == 0 ) + { + pg_geom = "the_geom"; + } + + if ( ! asprintf(&dest_string, "%s.%s", pg_schema, pg_table) ) + { + return NULL; + } + + if ( dest_string ) + { + /* Set the schema and table into the globals. */ + /* TODO change the core code to use dest_string instead */ + /* and move the global set into the import function. */ + table = strdup(pg_table); + schema = strdup(pg_schema); + geom = strdup(pg_geom); + return dest_string; + } + return NULL; +} + +static void +pgui_raise_error_dialogue(void) +{ + GtkWidget *dialog, *label; + gint result; + + label = gtk_label_new(pgui_errmsg); + dialog = gtk_dialog_new_with_buttons("Error", GTK_WINDOW(window_main), + GTK_DIALOG_MODAL & GTK_DIALOG_NO_SEPARATOR & GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); + gtk_dialog_set_has_separator ( GTK_DIALOG(dialog), FALSE ); + gtk_container_set_border_width (GTK_CONTAINER(dialog), 5); + gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), 15); + gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), label); + gtk_widget_show_all (dialog); + result = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return; +} + +static void +pgui_action_import(GtkWidget *widget, gpointer data) +{ + char *connection_string = NULL; + char *dest_string = NULL; + char *source_file = NULL; + + const char *entry_srid = gtk_entry_get_text(GTK_ENTRY(entry_config_srid)); + const char *entry_encoding = gtk_entry_get_text(GTK_ENTRY(entry_options_encoding)); + + /* Do nothing if we're busy */ + if ( translation_stage > 0 && translation_stage < 4 ) + { + return; + } + + if ( ! (connection_string = pgui_read_connection() ) ) + { + pgui_raise_error_dialogue(); + return; + } + + if ( ! (dest_string = pgui_read_destination() ) ) + { + pgui_raise_error_dialogue(); + return; + } + + if ( ! (source_file = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(file_chooser_button_shape))) ) + { + pgui_seterr("Select a shape file to import."); + pgui_raise_error_dialogue(); + return; + } + + /* Log what we know so far */ + pgui_logf("Connection: %s", connection_string); + pgui_logf("Destination: %s", dest_string); + pgui_logf("Source File: %s", source_file); + + /* Set the shape file into the global. */ + shp_file = strdup(source_file); + g_free(source_file); + + /* Set the mode to "create" in the global. */ + opt = 'c'; + + /* Set the output mode to inserts. */ + dump_format = 0; + + /* + ** Read the options from the options dialogue... + */ + + /* Encoding */ + if( entry_encoding && strlen(entry_encoding) > 0 ) + { + encoding = strdup(entry_encoding); + } + + /* SRID */ + if ( ! ( sr_id = atoi(entry_srid) ) ) + { + sr_id = -1; + } + + /* Preserve case */ + if( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_options_preservecase)) ) + quoteidentifiers = 1; + else + quoteidentifiers = 0; + + /* No long integers in table */ + if( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_options_forceint)) ) + forceint4 = 1; + else + forceint4 = 0; + + /* Create spatial index after load */ + if( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_options_autoindex)) ) + createindex = 1; + else + createindex = 0; + + /* Read the .shp file, don't ignore it */ + if( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_options_dbfonly)) ) + readshape = 0; + else + readshape = 1; + + /* Connect to the database. */ + if ( pg_connection ) PQfinish(pg_connection); + pg_connection = PQconnectdb(connection_string); + + if (PQstatus(pg_connection) == CONNECTION_BAD) + { + pgui_logf( "Connection failed: %s", PQerrorMessage(pg_connection)); + gtk_label_set_text(GTK_LABEL(label_pg_connection_test), "Connection failed."); + free(connection_string); + free(dest_string); + PQfinish(pg_connection); + pg_connection = NULL; + return; + } + + /* add the idle action */ + cur_entity = -1; + translation_stage = 1; + g_idle_add(check_translation_stage, NULL); + + free(connection_string); + free(dest_string); + + return; + +} + +static void +pgui_action_options(GtkWidget *widget, gpointer data) +{ + /* Do nothing if we're busy */ + if ( translation_stage > 0 && translation_stage < 4 ) + { + return; + } + /* TODO Open the options dialog window here... */ + pgui_logf("Open the options dialog..."); + gtk_widget_show_all (window_options); + return; +} + +static void +pgui_action_cancel(GtkWidget *widget, gpointer data) +{ + if ( translation_stage > 0 && translation_stage < 4 ) + { + pgui_logf("Import stopped."); + + translation_stage = 0; /* return to idle if we are running */ + } + else + { + pgui_quit(widget, data); /* quit if we're not running */ + } + return; +} + +static void +pgui_action_connection_test(GtkWidget *widget, gpointer data) +{ + char *connection_string = NULL; + + /* Do nothing if we're busy */ + if ( translation_stage > 0 && translation_stage < 4 ) + { + return; + } + + + if ( ! (connection_string = pgui_read_connection()) ) + { + pgui_raise_error_dialogue(); + return; + } + pgui_logf("Connecting: %s", connection_string); + + if ( pg_connection ) + PQfinish(pg_connection); + + pg_connection = PQconnectdb(connection_string); + if (PQstatus(pg_connection) == CONNECTION_BAD) + { + pgui_logf( "Connection failed: %s", PQerrorMessage(pg_connection)); + gtk_label_set_text(GTK_LABEL(label_pg_connection_test), "Connection failed."); + free(connection_string); + PQfinish(pg_connection); + pg_connection = NULL; + return; + } + gtk_label_set_text(GTK_LABEL(label_pg_connection_test), "Connection succeeded."); + pgui_logf( "Connection succeeded." ); + gtk_widget_show(label_pg_connection_test); + PQfinish(pg_connection); + pg_connection = NULL; + free(connection_string); + + return; +} + +static void +pgui_action_close_options(GtkWidget *widget, gpointer data) +{ + gtk_widget_hide_all (window_options); + return; +} + +static void +pgui_action_shape_file_set(GtkWidget *widget, gpointer data) +{ + char *shp_file; + int shp_file_len; + char *table_start; + char *table_end; + char *table; + + shp_file = strdup(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget))); + shp_file_len = strlen(shp_file); + + /* Roll back from end to first slash character. */ + table_start = shp_file + shp_file_len; + while( *table_start != '/' && *table_start != '\\' && table_start > shp_file) { + table_start--; + } + table_start++; /* Forward one to start of actual characters. */ + + /* Roll back from end to first . character. */ + table_end = shp_file + shp_file_len; + while( *table_end != '.' && table_end > shp_file && table_end > table_start ) { + table_end--; + } + + /* Copy the table name into a fresh memory slot. */ + table = lwalloc(table_end - table_start + 1); + memcpy(table, table_start, table_end - table_start); + table[table_end - table_start + 1] = '\0'; + + /* Set the table name into the entry. */ + gtk_entry_set_text(GTK_ENTRY(entry_config_table), table); + + lwfree(shp_file); +} + +static void +pgui_create_options_dialogue_add_label(GtkWidget *table, const char *str, gfloat alignment, int row) +{ + GtkWidget *align = gtk_alignment_new( alignment, 0.5, 0.0, 1.0 ); + GtkWidget *label = gtk_label_new( str ); + gtk_table_attach_defaults(GTK_TABLE(table), align, 1, 3, row, row+1 ); + gtk_container_add (GTK_CONTAINER (align), label); +} + +static void +pgui_create_options_dialogue() +{ + GtkWidget *table_options; + GtkWidget *button_options_ok; + GtkWidget *vbox_options; + GtkWidget *align_options_center; + static int text_width = 12; + + window_options = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_modal (GTK_WINDOW(window_options), TRUE); + gtk_window_set_keep_above (GTK_WINDOW(window_options), TRUE); + gtk_window_set_title (GTK_WINDOW(window_options), "Import Options"); + gtk_window_set_default_size (GTK_WINDOW(window_options), 180, 200); + + table_options = gtk_table_new(7, 3, TRUE); + gtk_container_set_border_width (GTK_CONTAINER (table_options), 12); + gtk_table_set_row_spacings(GTK_TABLE(table_options), 5); + gtk_table_set_col_spacings(GTK_TABLE(table_options), 10); + + pgui_create_options_dialogue_add_label(table_options, "DBF file character encoding", 0.0, 0); + entry_options_encoding = gtk_entry_new(); + gtk_entry_set_width_chars(GTK_ENTRY(entry_options_encoding), text_width); + gtk_entry_set_text(GTK_ENTRY(entry_options_encoding), "LATIN1"); + gtk_table_attach_defaults(GTK_TABLE(table_options), entry_options_encoding, 0, 1, 0, 1 ); + + pgui_create_options_dialogue_add_label(table_options, "Preserve case of column names", 0.0, 1); + checkbutton_options_preservecase = gtk_check_button_new(); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_options_preservecase), FALSE); + align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 ); + gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 1, 2 ); + gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_preservecase); + + pgui_create_options_dialogue_add_label(table_options, "Do not create 'bigint' columns", 0.0, 2); + checkbutton_options_forceint = gtk_check_button_new(); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_options_forceint), TRUE); + align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 ); + gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 2, 3 ); + gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_forceint); + + pgui_create_options_dialogue_add_label(table_options, "Create spatial index automatically after load", 0.0, 3); + checkbutton_options_autoindex = gtk_check_button_new(); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_options_autoindex), TRUE); + align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 ); + gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 3, 4 ); + gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_autoindex); + + pgui_create_options_dialogue_add_label(table_options, "Load only attribute (dbf) data", 0.0, 4); + checkbutton_options_dbfonly = gtk_check_button_new(); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (checkbutton_options_dbfonly), FALSE); + align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 0.0 ); + gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 4, 5 ); + gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_dbfonly); + + pgui_create_options_dialogue_add_label(table_options, "Policy for records with empty (null) shapes", 0.0, 5); + entry_options_nullpolicy = gtk_entry_new(); + gtk_entry_set_width_chars(GTK_ENTRY(entry_options_nullpolicy), text_width); + gtk_entry_set_text(GTK_ENTRY(entry_options_nullpolicy), "0"); + gtk_table_attach_defaults(GTK_TABLE(table_options), entry_options_nullpolicy, 0, 1, 5, 6 ); + + button_options_ok = gtk_button_new_with_label("OK"); + g_signal_connect (G_OBJECT (button_options_ok), "clicked", G_CALLBACK (pgui_action_close_options), NULL); + gtk_table_attach_defaults(GTK_TABLE(table_options), button_options_ok, 1, 2, 6, 7 ); + + vbox_options = gtk_vbox_new(FALSE, 10); + gtk_box_pack_start(GTK_BOX(vbox_options), table_options, FALSE, FALSE, 0); + gtk_container_add (GTK_CONTAINER (window_options), vbox_options); + + +} + +static void +pgui_create_main_window(void) +{ + static int text_width = 12; + /* Reusable label handle */ + GtkWidget *label; + /* Main widgets */ + GtkWidget *vbox_main; + /* Shape file section */ + GtkWidget *file_chooser_dialog_shape; + GtkFileFilter *file_filter_shape; + /* PgSQL section */ + GtkWidget *frame_pg, *frame_shape, *frame_log, *frame_config; + GtkWidget *table_pg, *table_config; + GtkWidget *button_pg_test; + /* Button section */ + GtkWidget *hbox_buttons, *button_options, *button_import, *button_cancel; + /* Log section */ + GtkWidget *scrolledwindow_log; + + /* create the main, top level, window */ + window_main = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + /* give the window a 10px wide border */ + gtk_container_set_border_width (GTK_CONTAINER (window_main), 10); + + /* give it the title */ + gtk_window_set_title (GTK_WINDOW (window_main), "Shape File to PostGIS Importer"); + + /* open it a bit wider so that both the label and title show up */ + gtk_window_set_default_size (GTK_WINDOW (window_main), 180, 500); + + /* Connect the destroy event of the window with our pgui_quit function + * When the window is about to be destroyed we get a notificaiton and + * stop the main GTK loop + */ + g_signal_connect (G_OBJECT (window_main), "destroy", G_CALLBACK (pgui_quit), NULL); + + /* + ** Shape file selector + */ + frame_shape = gtk_frame_new("Shape File"); + gtk_container_set_border_width (GTK_CONTAINER (frame_shape), 0); + file_chooser_dialog_shape = gtk_file_chooser_dialog_new( "Select a Shape File", GTK_WINDOW (window_main), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); + file_chooser_button_shape = gtk_file_chooser_button_new_with_dialog( file_chooser_dialog_shape ); + gtk_container_set_border_width (GTK_CONTAINER (file_chooser_button_shape), 8); + file_filter_shape = gtk_file_filter_new(); + gtk_file_filter_add_pattern(GTK_FILE_FILTER(file_filter_shape), "*.shp"); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(file_chooser_button_shape), file_filter_shape); + gtk_container_add (GTK_CONTAINER (frame_shape), file_chooser_button_shape); + g_signal_connect (G_OBJECT (file_chooser_button_shape), "file-set", G_CALLBACK (pgui_action_shape_file_set), NULL); + + + /* + ** PostGIS info in a table + */ + frame_pg = gtk_frame_new("PostGIS Connection"); + table_pg = gtk_table_new(5, 3, TRUE); + gtk_container_set_border_width (GTK_CONTAINER (table_pg), 8); + gtk_table_set_col_spacings(GTK_TABLE(table_pg), 7); + gtk_table_set_row_spacings(GTK_TABLE(table_pg), 3); + /* User name row */ + label = gtk_label_new("Username:"); + entry_pg_user = gtk_entry_new(); + gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 0, 1 ); + gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_user, 1, 3, 0, 1 ); + /* Password row */ + label = gtk_label_new("Password:"); + entry_pg_pass = gtk_entry_new(); + gtk_entry_set_visibility( GTK_ENTRY(entry_pg_pass), FALSE); + gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 1, 2 ); + gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_pass, 1, 3, 1, 2 ); + /* Host and port row */ + label = gtk_label_new("Server Host:"); + entry_pg_host = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(entry_pg_host), "localhost"); + gtk_entry_set_width_chars(GTK_ENTRY(entry_pg_host), text_width); + gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 2, 3 ); + gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_host, 1, 2, 2, 3 ); + entry_pg_port = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(entry_pg_port), "5432"); + gtk_entry_set_width_chars(GTK_ENTRY(entry_pg_port), 8); + gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_port, 2, 3, 2, 3 ); + /* Database row */ + label = gtk_label_new("Database:"); + entry_pg_db = gtk_entry_new(); + gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 3, 4 ); + gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_db, 1, 3, 3, 4 ); + /* Test button row */ + button_pg_test = gtk_button_new_with_label("Test Connection..."); + gtk_table_attach_defaults(GTK_TABLE(table_pg), button_pg_test, 1, 2, 4, 5 ); + g_signal_connect (G_OBJECT (button_pg_test), "clicked", G_CALLBACK (pgui_action_connection_test), NULL); + label_pg_connection_test = gtk_label_new(""); + gtk_table_attach_defaults(GTK_TABLE(table_pg), label_pg_connection_test, 2, 3, 4, 5 ); + /* Add table into containing frame */ + gtk_container_add (GTK_CONTAINER (frame_pg), table_pg); + + /* + ** Configuration in a table + */ + frame_config = gtk_frame_new("Configuration"); + table_config = gtk_table_new(2, 4, TRUE); + gtk_table_set_col_spacings(GTK_TABLE(table_config), 7); + gtk_table_set_row_spacings(GTK_TABLE(table_config), 3); + gtk_container_set_border_width (GTK_CONTAINER (table_config), 8); + /* Destination schemea row */ + label = gtk_label_new("Destination Schema:"); + entry_config_schema = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(entry_config_schema), "public"); + gtk_entry_set_width_chars(GTK_ENTRY(entry_config_schema), text_width); + gtk_table_attach_defaults(GTK_TABLE(table_config), label, 0, 1, 0, 1 ); + gtk_table_attach_defaults(GTK_TABLE(table_config), entry_config_schema, 1, 2, 0, 1 ); + /* Destination table row */ + label = gtk_label_new("Destination Table:"); + entry_config_table = gtk_entry_new(); + gtk_entry_set_width_chars(GTK_ENTRY(entry_config_table), text_width); + gtk_table_attach_defaults(GTK_TABLE(table_config), label, 0, 1, 1, 2 ); + gtk_table_attach_defaults(GTK_TABLE(table_config), entry_config_table, 1, 2, 1, 2 ); + /* SRID row */ + label = gtk_label_new("SRID:"); + entry_config_srid = gtk_entry_new(); + gtk_entry_set_width_chars(GTK_ENTRY(entry_config_srid), text_width); + gtk_entry_set_text(GTK_ENTRY(entry_config_srid), "-1"); + gtk_table_attach_defaults(GTK_TABLE(table_config), label, 2, 3, 0, 1 ); + gtk_table_attach_defaults(GTK_TABLE(table_config), entry_config_srid, 3, 4, 0, 1 ); + /* Geom column row */ + label = gtk_label_new("Geometry Column:"); + entry_config_geocolumn = gtk_entry_new(); + gtk_entry_set_width_chars(GTK_ENTRY(entry_config_geocolumn), text_width); + gtk_entry_set_text(GTK_ENTRY(entry_config_geocolumn), "the_geom"); + gtk_table_attach_defaults(GTK_TABLE(table_config), label, 2, 3, 1, 2 ); + gtk_table_attach_defaults(GTK_TABLE(table_config), entry_config_geocolumn, 3, 4, 1, 2 ); + + /* Add table into containing frame */ + gtk_container_add (GTK_CONTAINER (frame_config), table_config); + + + /* + ** Row of action buttons + */ + hbox_buttons = gtk_hbox_new(TRUE, 15); + gtk_container_set_border_width (GTK_CONTAINER (hbox_buttons), 0); + /* Create the buttons themselves */ + button_options = gtk_button_new_with_label("Options..."); + button_import = gtk_button_new_with_label("Import"); + button_cancel = gtk_button_new_with_label("Cancel"); + /* Add actions to the buttons */ + g_signal_connect (G_OBJECT (button_import), "clicked", G_CALLBACK (pgui_action_import), NULL); + g_signal_connect (G_OBJECT (button_options), "clicked", G_CALLBACK (pgui_action_options), NULL); + g_signal_connect (G_OBJECT (button_cancel), "clicked", G_CALLBACK (pgui_action_cancel), NULL); + /* And insert the buttons into the hbox */ + gtk_box_pack_start(GTK_BOX(hbox_buttons), button_options, TRUE, TRUE, 0); + gtk_box_pack_end(GTK_BOX(hbox_buttons), button_cancel, TRUE, TRUE, 0); + gtk_box_pack_end(GTK_BOX(hbox_buttons), button_import, TRUE, TRUE, 0); + + /* + ** Log window + */ + frame_log = gtk_frame_new("Import Log"); + gtk_container_set_border_width (GTK_CONTAINER (frame_log), 0); + textview_log = gtk_text_view_new(); + textbuffer_log = gtk_text_buffer_new(NULL); + scrolledwindow_log = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow_log), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview_log), textbuffer_log); + gtk_container_set_border_width (GTK_CONTAINER (textview_log), 5); + gtk_text_view_set_editable(GTK_TEXT_VIEW(textview_log), FALSE); + gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(textview_log), FALSE); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview_log), GTK_WRAP_WORD); + gtk_container_add (GTK_CONTAINER (scrolledwindow_log), textview_log); + gtk_container_add (GTK_CONTAINER (frame_log), scrolledwindow_log); + + /* + ** Main window + */ + vbox_main = gtk_vbox_new(FALSE, 10); + gtk_container_set_border_width (GTK_CONTAINER (vbox_main), 0); + /* Add the frames into the main vbox */ + gtk_box_pack_start(GTK_BOX(vbox_main), frame_shape, FALSE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(vbox_main), frame_pg, FALSE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(vbox_main), frame_config, FALSE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(vbox_main), hbox_buttons, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox_main), frame_log, TRUE, TRUE, 0); + /* and insert the vbox into the main window */ + gtk_container_add (GTK_CONTAINER (window_main), vbox_main); + /* make sure that everything, window and label, are visible */ + gtk_widget_show_all (window_main); + + return; +} + +int +main(int argc, char *argv[]) +{ + + /* initialize the GTK stack */ + gtk_init(&argc, &argv); + + /* set up the user interface */ + pgui_create_main_window(); + pgui_create_options_dialogue(); + + /* set up and global variables we want before running */ + gui_mode = 1; + + /* start the main loop */ + gtk_main(); + + return 0; +} + +/********************************************************************** + * $Log$ + * + **********************************************************************/ diff --git a/loader/stringbuffer.c b/loader/stringbuffer.c new file mode 100644 index 000000000..4d1d9bcdf --- /dev/null +++ b/loader/stringbuffer.c @@ -0,0 +1,568 @@ +/* + * + * Copyright 2002 Thamer Alharbash + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. The names of the authors may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Sringbuffer object: + * + * (*) allows printfing into a string, + * (*) fast string manipulation by keeping track of string length. + * (*) alignment of string against columns. + * + * Internally stringbuffer does not count the terminating null as the length. + * Therefore the raw string routines will always assume +1 when given length. + */ + +#include "stringbuffer.h" + +/* * * * * * * * * * * * * + * raw string routines. * + * * * * * * * * * * * * */ + +/* just malloc out a string. */ +static char *allocate_string(int len) +{ + char *s; + s = malloc(sizeof(char) * len + 1); /* add for null termination. */ + s[len] = 0; + return s; +} + +/* extend a string. */ +static char *extend_string(char *str, int cur_len, int ex_len) +{ + + str = realloc(str, (cur_len * sizeof(char)) + (ex_len * sizeof(char)) + (1 * sizeof(char))); + str[cur_len] = 0; /* make sure it's null terminated. */ + + return str; +} + +/* get a substring. */ +static char *substring(char *begin, int len) +{ + char *new_string; + + new_string = allocate_string(len); + memcpy(new_string, begin, len); + new_string[len] = 0; + + return new_string; +} + +/* FIXME: get rid of pesky strlen() */ +/* used in aligning -- we try to get words up to end. */ +static char *get_string_align(char *s, int end, int *len) +{ + char *cur_ptr; + + if (s == 0 || *s == 0) /* end of string or no string. */ + return NULL; + + /* if strlen is smaller than len go ahead and just return it. */ + if (strlen(s) < end) + { + *len = strlen(s); + return strdup(s); + } + + /* otherwise we need to hop to len */ + cur_ptr = &s[end - 1]; + + /* now check to see if we have a whitespace behind cur_ptr + * if we do return at that point. */ + + for (; cur_ptr != s; cur_ptr--) + { + if (*cur_ptr == ' ' || *cur_ptr == '\t') + { + /* copy here and return. */ + *len = (cur_ptr - s) + 1; + return (substring(s, *len)); + } + } + + /* keep walking till we find whitspace or end. */ + for (cur_ptr = &s[end - 1]; *cur_ptr != 0 && *cur_ptr != ' ' && *cur_ptr != '\t'; cur_ptr++); + + *len = (cur_ptr - s) + 1; + return (substring(s, *len)); +} + +/* zap newlines by placing spaces in their place. + * we use this before aligning. */ +static void stringbuffer_zap_newline(stringbuffer_t *sb) +{ + + stringbuffer_replace_c(sb, '\n', ' '); + stringbuffer_replace_c(sb, '\r', ' '); +} + +/* * * * * * * * * * * * * * * + * stringbuffer routines. * + * * * * * * * * * * * * * * */ + +/* create a new stringbuffer */ +stringbuffer_t *stringbuffer_create(void) +{ + stringbuffer_t *sb; + + sb = malloc(sizeof(stringbuffer_t)); + sb->len = 0; + sb->capacity = 0; + sb->buf = allocate_string(0); + + return sb; +} + +/* destroy the stringbuffer */ +void stringbuffer_destroy(stringbuffer_t *sb) +{ + free(sb->buf); + free(sb); +} + +/* clear a string. */ +void stringbuffer_clear(stringbuffer_t *sb) +{ + sb->len = 0; + sb->buf[0] = 0; +} + +/* append character to stringbuffer */ +void stringbuffer_append_c(stringbuffer_t *sb, char c) +{ + if (sb->capacity <= (sb->len)) + { + sb->buf = extend_string(sb->buf, sb->len, STRINGBUFFER_CHUNKSIZE); + sb->capacity += STRINGBUFFER_CHUNKSIZE; + } + + sb->buf[sb->len] = c; + sb->len++; + sb->buf[sb->len] = 0; +} + +/* append string to stringbuffer */ +void stringbuffer_append(stringbuffer_t *sb, const char *s) +{ + int len = strlen(s); + + /* increase capacity if needed. */ + if (sb->capacity <= (len + sb->len)) + { + + /* if we're bigger than the chunksize then allocate len. */ + if (len > STRINGBUFFER_CHUNKSIZE) + { + + sb->buf = extend_string(sb->buf, sb->capacity, len); + sb->capacity += len; + + } + else + { + + /* otherwise allocate chunksize. */ + sb->buf = extend_string(sb->buf, sb->capacity, STRINGBUFFER_CHUNKSIZE); + sb->capacity += STRINGBUFFER_CHUNKSIZE; + } + } + + /* copy new string into place: keep in mind we know all + * lengths so strcat() would be less effecient. */ + + memcpy(&sb->buf[sb->len], s, len); + + sb->len += len; + sb->buf[sb->len] = 0; + + return; +} + +/* remove whitespace (including tabs) */ +stringbuffer_t *stringbuffer_trim_whitespace(stringbuffer_t *sb) +{ + char *newbuf; + int new_len; + int i, j; + + if (sb->len == 0) /* empty string. */ + return sb; + + /* find beginning of string after tabs and whitespaces. */ + for (i = 0; i < sb->len && (sb->buf[i] == ' ' || sb->buf[i] == '\t'); i++); + + if (sb->buf[i] != '\0') + { + + /* we do have whitespace in the beginning so find the end. */ + for (j = (sb->len -1);(sb->buf[j] == ' ' || sb->buf[j] == '\t'); j--); + + /* increment j since it's on the non whitespace character. */ + j++; + + /* create a new string. */ + new_len = j - i; + newbuf = allocate_string(new_len); + + /* copy in. */ + memcpy(newbuf, &sb->buf[i], (j - i) * sizeof(char)); + newbuf[new_len] = 0; + + /* free up old. */ + free(sb->buf); + + /* set new. */ + sb->buf = newbuf; + sb->len = new_len; + sb->capacity = new_len; + + + } + else + { + + /* zap beginning of string. since its all whitespace. */ + sb->buf[0] = 0; + sb->len = 0; + } + + return sb; +} + +/* get the last occurance of a specific character. useful for slicing. */ +char *stringbuffer_get_last_occurance(stringbuffer_t *sb, char c) +{ + char *ptr, *ptrend = NULL; + int i; + + ptr = sb->buf; + for (i = 0;i < sb->len;i++) + { + + if (ptr[i] == c) + ptrend = &ptr[i]; + } + + return ptrend; +} + +/* remove the last newline character. */ +void stringbuffer_trim_newline(stringbuffer_t *sb) +{ + char *ptr; + + ptr = stringbuffer_get_last_occurance(sb, '\n'); + if (ptr != NULL) + *ptr = 0; + + + ptr = stringbuffer_get_last_occurance(sb, '\r'); + if (ptr != NULL) + *ptr = 0; + + + sb->len = strlen(sb->buf); + + return; +} + +/* return the C string from the buffer. */ +const char *stringbuffer_getstring(stringbuffer_t *sb) +{ + return sb->buf; +} + +/* set a string into a stringbuffer */ +void stringbuffer_set(stringbuffer_t *dest, const char *s) +{ + stringbuffer_clear(dest); /* zap */ + stringbuffer_append(dest, s); +} + +/* copy a stringbuffer into another stringbuffer */ +void stringbuffer_copy(stringbuffer_t *dest, stringbuffer_t *src) +{ + stringbuffer_set(dest, stringbuffer_getstring(src)); +} + +/* replace in stringbuffer occurances of c with replace */ +void stringbuffer_replace_c(stringbuffer_t *sb, char c, char replace) +{ + int i; + + for (i = 0;i < sb->len;i++) + { + if (sb->buf[i] == c) + sb->buf[i] = replace; + } + + return; +} + +/* replace in stringbuffer occurances of c with replace */ +void stringbuffer_replace(stringbuffer_t *sb, const char *string, const char *replace) +{ + char *ptr; + int i; + int str_len = strlen(string); + stringbuffer_t *sb_replace; + + if (string[0] == 0) + return; /* nothing to replace. */ + + sb_replace = stringbuffer_create(); + ptr = sb->buf; + + for (i = 0; i < sb->len; i++) + { + + if ((sb->len - i) < str_len) + { + + /* copy in. */ + stringbuffer_copy(sb, sb_replace); + + /* append what's left. */ + stringbuffer_append(sb, &ptr[i]); + + /* free up. */ + stringbuffer_destroy(sb_replace); + + /* we're done. */ + return; + } + + if (ptr[i] == string[0]) + { + + /* we know that we have at least enough to complete string. */ + if (!memcmp(&ptr[i], string, str_len)) + { + + /* we have a match, replace. */ + stringbuffer_append(sb_replace, replace); + i += (str_len - 1); + continue; + } + } + + stringbuffer_append_c(sb_replace, ptr[i]); + } + + /* we're done: we should only get here if the last string to + be replaced ended the string itself. */ + + /* copy in. */ + stringbuffer_copy(sb, sb_replace); + + /* free up. */ + stringbuffer_destroy(sb_replace); + + /* we're done. */ + return; + +} + +/* align a stringbuffer on begin and end columns. */ +void stringbuffer_align(stringbuffer_t *sb, int begin, int end) +{ + char *ptr, *word_string; + stringbuffer_t *aligned_string; + int len, i; + + stringbuffer_zap_newline(sb); + + aligned_string = stringbuffer_create(); + ptr = sb->buf; + + while (1) + { + + word_string = get_string_align(ptr, end, &len); + + if (word_string == NULL) + break; + + ptr += len; + + for (i = 0; i < begin; i++) + stringbuffer_append(aligned_string, " "); + + stringbuffer_append(aligned_string, word_string); + stringbuffer_append(aligned_string, "\n"); + free(word_string); + + } + + stringbuffer_copy(sb, aligned_string); + stringbuffer_destroy(aligned_string); + + return; +} + +/* stringbuffer_*printf* these all use snprintf/snvprintf internally */ + +/* append vprintf with alignment. */ +void stringbuffer_avprintf_align(stringbuffer_t *sb, int start, int end, const char *fmt, va_list ap) +{ + stringbuffer_t *tmp_sb; + char *str; + int total, len; + + /* our first malloc is bogus. */ + len = 1; + str = malloc(sizeof(char) * len); + total = vsnprintf(str, len, fmt, ap); + + /* total is the real length needed. */ + free(str); + len = total + 1; + str = malloc(sizeof(char) * len); + vsnprintf(str, len, fmt, ap); + + /* now align if we want to align. */ + if (start != 0 && end != 0) + { + + tmp_sb = stringbuffer_create(); + + stringbuffer_append(tmp_sb, str); + stringbuffer_align(tmp_sb, start, end); + stringbuffer_append(sb, stringbuffer_getstring(tmp_sb)); + + stringbuffer_destroy(tmp_sb); + + } + else + { + stringbuffer_append(sb, str); + } + + free(str); + + return; +} + +/* append printf. */ +void stringbuffer_aprintf(stringbuffer_t *sb, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + + stringbuffer_avprintf(sb, fmt, ap); + + va_end(ap); +} + +/* append printf with alignment. */ +void stringbuffer_aprintf_align(stringbuffer_t *sb, int start, int end, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + + stringbuffer_avprintf_align(sb, start, end, fmt, ap); + + va_end(ap); +} + +/* append vprintf. */ +void stringbuffer_avprintf(stringbuffer_t *sb, const char *fmt, va_list ap) +{ + stringbuffer_avprintf_align(sb, 0, 0, fmt, ap); +} + +/* newline marking and sweeping. this wrecks the string inside + * the stringbuffer. */ + +/* mark newlines to walk through. + * our sentinel is the null terminator. + * two null terminations marks the end of the string. + * we're guaranteed it is a unique sentinel since + * we never accept null terminators from outside sources + * and never build our own strings (obviously!) with + * null terminators inside of them. + */ +int stringbuffer_marknewlines(stringbuffer_t *sb) +{ + char *c; + int newline_count = 0; + + /* first append one null termination to the end + * to act as a proper terminator. */ + stringbuffer_append_c(sb, 0); + + + c = sb->buf; + while (1) + { + + if (*c == '\n') + { + newline_count++; + *c = 0; + } + + c++; + if (*c == 0) + break; + } + + return newline_count; /* return our line count. */ +} + +/* called _after_ newlines are marked. */ +const char *stringbuffer_getnextline(stringbuffer_t *sb, const char *cptr) +{ + const char *ptr; + + if (cptr == NULL) + { + + /* get first line. */ + cptr = sb->buf; + + } + else + { + + for (ptr = cptr; *ptr != 0; ptr++); + if (*ptr == 0 && *(ptr + 1) == 0) + { + return NULL; + + } + else + { + cptr = ptr + 1; + + } + } + + return cptr; +} + +int stringbuffer_getlen(stringbuffer_t *sb) +{ + return sb->len; +} diff --git a/loader/stringbuffer.h b/loader/stringbuffer.h new file mode 100644 index 000000000..f1663394b --- /dev/null +++ b/loader/stringbuffer.h @@ -0,0 +1,66 @@ +/* + * + * Copyright 2002 Thamer Alharbash + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. The names of the authors may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Sringbuffer object: + * + * (*) allows printfing into a string, + * (*) fast string manipulation by keeping track of string length. + * (*) alignment of string against columns. + * + * Internally stringbuffer does not count the terminating null as the length. + * Therefore the raw string routines will always assume +1 when given length. + */ + +#include +#include +#include +#include + +#define STRINGBUFFER_CHUNKSIZE 16 + +typedef struct { + size_t len; + size_t capacity; + char *buf; +} stringbuffer_t; + +extern stringbuffer_t *stringbuffer_create(void); +extern void stringbuffer_destroy(stringbuffer_t *sb); +extern void stringbuffer_clear(stringbuffer_t *sb); +extern void stringbuffer_append_c(stringbuffer_t *sb, char c); +extern void stringbuffer_append(stringbuffer_t *sb, const char *s); +extern stringbuffer_t *stringbuffer_trim_whitespace(stringbuffer_t *sb); +extern char *stringbuffer_get_last_occurance(stringbuffer_t *sb, char c); +extern void stringbuffer_trim_newline(stringbuffer_t *sb); +extern const char *stringbuffer_getstring(stringbuffer_t *sb); +extern void stringbuffer_set(stringbuffer_t *dest, const char *s); +extern void stringbuffer_copy(stringbuffer_t *dest, stringbuffer_t *src); +extern void stringbuffer_replace_c(stringbuffer_t *sb, char c, char replace); +extern void stringbuffer_replace(stringbuffer_t *sb, const char *string, const char *replace); +extern void stringbuffer_align(stringbuffer_t *sb, int begin, int end); +extern void stringbuffer_avprintf_align(stringbuffer_t *sb, int start, int end, const char *fmt, va_list ap); +extern void stringbuffer_aprintf(stringbuffer_t *sb, const char *fmt, ...); +extern void stringbuffer_aprintf_align(stringbuffer_t *sb, int start, int end, const char *fmt, ...); +extern void stringbuffer_avprintf(stringbuffer_t *sb, const char *fmt, va_list ap); +extern int stringbuffer_marknewlines(stringbuffer_t *sb); +extern const char *stringbuffer_getnextline(stringbuffer_t *sb, const char *cptr); +extern int stringbuffer_getlen(stringbuffer_t *sb); -- 2.50.0