/[thuban]/branches/WIP-pyshapelib-bramz/test/postgissupport.py
ViewVC logotype

Contents of /branches/WIP-pyshapelib-bramz/test/postgissupport.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2714 - (show annotations)
Mon Nov 6 10:09:14 2006 UTC (18 years, 4 months ago) by bernhard
Original Path: trunk/thuban/test/postgissupport.py
File MIME type: text/x-python
File size: 30308 byte(s)
Fixing the possibility to run postgis tests on Debian Etch (80%).

* test/postgissupport.py: Taking pg_ctl and initdb commands
from internal variables now. Adding deactivated example values
for Debian Etch.  
find_postgis_sql(): Added special line for Debian Etch postgresql-8.1.
Added copyright year 2006.


1 # Copyright (C) 2003, 2004, 2005, 2006 by Intevation GmbH
2 # Authors:
3 # Bernhard Herzog <[email protected]>
4 #
5 # This program is free software under the GPL (>=v2)
6 # Read the file COPYING coming with the software for details.
7
8 """Support module for tests that use a live PostGIS database"""
9
10 __version__ = "$Revision$"
11 # $Source$
12 # $Id$
13
14 import sys
15 import os
16 import time
17 import popen2
18 import shutil
19 import traceback
20 import re
21
22 import support
23
24 try:
25 import psycopg
26 except ImportError:
27 psycopg = None
28
29 #
30 # Helper code
31 #
32
33 def run_config_script(cmdline):
34 """Run command cmdline and return its stdout or none in case of errors"""
35 pipe = os.popen(cmdline)
36 result = pipe.read()
37 if pipe.close() is not None:
38 raise RuntimeError('Command %r failed' % cmdline)
39 return result
40
41 def run_command(command, outfilename = None):
42 """Run command as a subprocess and send its stdout and stderr to outfile
43
44 The subprocess is run synchroneously so the function returns once
45 the subprocess has termninated. If the process' exit code is not
46 zero raise a RuntimeError.
47
48 If outfilename is None stdout and stderr are still captured but they
49 are ignored and not written to any file.
50 """
51 proc = popen2.Popen4(command)
52 proc.tochild.close()
53 output = proc.fromchild.read()
54 status = proc.wait()
55 if outfilename is not None:
56 outfile = open(outfilename, "w")
57 outfile.write(output)
58 outfile.close()
59 if not os.WIFEXITED(status) or os.WEXITSTATUS(status) != 0:
60 if outfilename:
61 message = "see %s" % outfilename
62 else:
63 message = output
64 raise RuntimeError("command %r exited with code %d.\n%s"
65 % (command, status, message))
66
67
68 def run_boolean_command(command):
69 """
70 Run command as a subprocess silently and return whether it ran successfully
71
72 Silently means that all output is captured and ignored. The exit
73 status is true if the command ran successfull, i.e. it terminated by
74 exiting and returned as zero exit code and false other wise
75 """
76 try:
77 run_command(command, None)
78 return 1
79 except RuntimeError:
80 pass
81 return 0
82
83
84 #
85 # PostgreSQL and database
86 #
87
88 _pg_ctl_command = "pg_ctl"
89 _initdb_command = "initdb"
90 # Example Values for Debian (upcomind Etch) postgresql-8.1
91 # TODO: detect which values to use here.
92 #_pg_ctl_command = "/usr/lib/postgresql/8.1/bin/pg_ctl"
93 #_initdb_command = "/usr/lib/postgresql/8.1/bin/initdb"
94
95 class PostgreSQLServer:
96
97 """A PostgreSQL server
98
99 Instances of this class represent a PostgreSQL server with postgis
100 extensions run explicitly for the test cases. Such a server has its
101 own database directory and its own directory for the unix sockets so
102 that it doesn't interfere with any other PostgreSQL server already
103 running on the system.
104 """
105
106 def __init__(self, dbdir, port, postgis_sql, socket_dir):
107 """Initialize the PostgreSQLServer object
108
109 Parameters:
110
111 dbdir -- The directory for the databases
112 port -- The port to use
113 postgis_sql -- The name of the file with the SQL statements to
114 initialize a database for postgis.
115 socket_dir -- The directory for the socket files.
116
117 When connecting to the database server use the port and host
118 instance variables.
119 """
120 self.dbdir = dbdir
121 self.port = port
122 self.postgis_sql = postgis_sql
123 self.socket_dir = socket_dir
124
125 # For the client side the socket directory can be used as the
126 # host if the name starts with a slash.
127 self.host = os.path.abspath(socket_dir)
128
129 # name and password for the admin and an unprivileged user
130 self.admin_name = "postgres"
131 self.admin_password = "postgres"
132 self.user_name = "observer"
133 self.user_password = "telescope"
134
135 # Map db names to db objects
136 self.known_dbs = {}
137
138 def createdb(self):
139 """Create the database in dbdir and start the server.
140
141 First check whether the dbdir already exists and if necessary
142 stop an already running postmaster and remove the dbdir
143 directory completely. Then create a new database cluster in the
144 dbdir and start a postmaster.
145 """
146 if os.path.isdir(self.dbdir):
147 if self.is_running():
148 self.shutdown()
149 shutil.rmtree(self.dbdir)
150 os.mkdir(self.dbdir)
151
152 run_command([_initdb_command, "-D", self.dbdir, "-U", self.admin_name],
153 os.path.join(self.dbdir, "initdb.log"))
154
155 extra_opts = "-p %d" % self.port
156 if self.socket_dir is not None:
157 extra_opts += " -k %s" % self.socket_dir
158 run_command([_pg_ctl_command, "-D", self.dbdir,
159 "-l", os.path.join(self.dbdir, "logfile"),
160 "-o", extra_opts, "start"],
161 os.path.join(self.dbdir, "pg_ctl-start.log"))
162 # the -w option of pg_ctl doesn't work properly when the port is
163 # not the default port, so we have to implement waiting for the
164 # server ourselves
165 self.wait_for_postmaster()
166
167 self.alter_user(self.admin_name, self.admin_password)
168 self.create_user(self.user_name, self.user_password)
169
170 def wait_for_postmaster(self):
171 """Return when the database server is running
172
173 Internal method to wait until the postmaster process has been
174 started and is ready for client connections.
175 """
176 max_count = 60
177 count = 0
178 while count < max_count:
179 try:
180 run_command(["psql", "-l", "-p", str(self.port),
181 "-h", self.host, "-U", self.admin_name],
182 os.path.join(self.dbdir, "psql-%d.log" % count))
183 except RuntimeError:
184 pass
185 except:
186 traceback.print_exc()
187 else:
188 break
189 time.sleep(0.5)
190 count += 1
191 else:
192 raise RuntimeError("postmaster didn't start")
193
194 def is_running(self):
195 """Return whether a postmaster process is running on self.dbdir
196
197 This method runs pg_ctl status on the dbdir and returns True if
198 that command succeeds and False otherwise.
199
200 Note that it is possible that this method returns true even if
201 the PostgreSQLServer instance has just been created and
202 createdb() has not been called yet. This can happen, for
203 instance, if the server has been started manually for debugging
204 purposes after a test suite run.
205 """
206 return run_boolean_command([_pg_ctl_command, "-D", self.dbdir, "status"])
207
208 def shutdown(self):
209 """Stop the postmaster running for self.dbdir"""
210 run_command([_pg_ctl_command, "-m", "fast", "-D", self.dbdir, "stop"],
211 os.path.join(self.dbdir, "pg_ctl-stop.log"))
212
213 def new_postgis_db(self, dbname, tables = None, reference_systems = None,
214 views = None):
215 """Create and return a new PostGISDatabase object using self as server
216 """
217 db = PostGISDatabase(self, self.postgis_sql, dbname, tables = tables,
218 reference_systems = reference_systems,
219 views = views)
220 db.initdb()
221 self.known_dbs[dbname] = db
222 return db
223
224 def get_static_data_db(self, dbname, tables, reference_systems, views):
225 """Return a PostGISDatabase for a database with the given static data
226
227 If no databasse of the name dbname exists, create a new one via
228 new_postgis_db and upload the data.
229
230 If a database of the name dbname already exists and uses the
231 indicated data, return that. If the already existing db uses
232 different data raise a value error.
233
234 If the database doesn't exist, create a new one via
235 self.new_postgis_db.
236
237 The parameters tables and reference_systems have the same
238 meaning as for new_postgis_db.
239 """
240 db = self.known_dbs.get(dbname)
241 if db is not None:
242 if db.has_data(tables, reference_systems, views):
243 return db
244 raise ValueError("PostGISDatabase named %r doesn't have tables %r"
245 % (dbname, tables))
246 return self.new_postgis_db(dbname, tables, reference_systems, views)
247
248 def get_default_static_data_db(self):
249 dbname = "PostGISStaticTests"
250 srids = [(1, "proj=longlat datum=WGS84")]
251 tables = [
252 # Direct copies of the shapefiles. The shapeids are exactly
253 # the same, except where changed with "gid_offset", of
254 # course. Note that the test implementation requires that
255 # all the landmard tables use an gid_offset of 1000.
256 ("landmarks", os.path.join("..", "Data", "iceland",
257 "cultural_landmark-point.shp"),
258 [("gid_offset", 1000)]),
259 ("political", os.path.join("..", "Data", "iceland",
260 "political.shp")),
261 ("roads_multi", os.path.join("..", "Data", "iceland",
262 "roads-line.shp")),
263
264 # same as roads-multi but using LINESTRING instead of
265 # MULTILINESTRING
266 ("roads", os.path.join("..", "Data", "iceland",
267 "roads-line.shp"),
268 [("force_wkt_type", "LINESTRING")]),
269
270 # The polygon data as a MULTIPOLYGON geometry type
271 ("political_multi", os.path.join("..", "Data", "iceland",
272 "political.shp"),
273 [("force_wkt_type", "MULTIPOLYGON")]),
274
275 # Copy of landmarks but using an srid != -1
276 ("landmarks_srid", os.path.join("..", "Data", "iceland",
277 "cultural_landmark-point.shp"),
278 [("gid_offset", 1000),
279 ("srid", 1)]),
280
281 # Copy of landmarks with a gid column called "point_id" instead
282 # of "gid" and using an srid != -1.
283 ("landmarks_point_id", os.path.join("..", "Data", "iceland",
284 "cultural_landmark-point.shp"),
285 [("gid_offset", 1000),
286 ("srid", 1),
287 ("gid_column", "point_id")]),
288 ]
289 views = [("v_landmarks", "SELECT * FROM landmarks_point_id")]
290 return self.get_static_data_db(dbname, tables, srids, views)
291
292 def connection_params(self, user):
293 """Return the connection parameters for the given user
294
295 The return value is a dictionary suitable as keyword argument
296 list to PostGISConnection. The user parameter may be either
297 'admin' to connect as admin or 'user' to connect as an
298 unprivileged user.
299 """
300 return {"host": self.host, "port": self.port,
301 "user": getattr(self, user + "_name"),
302 "password": getattr(self, user + "_password")}
303
304 def connection_string(self, user):
305 """Return (part of) the connection string to pass to psycopg.connect
306
307 The string contains host, port, user and password. The user
308 parameter must be either 'admin' or 'user', as for
309 connection_params.
310 """
311 params = []
312 for key, value in self.connection_params(user).items():
313 # FIXME: this doesn't do quiting correctly but that
314 # shouldn't be much of a problem (people shouldn't be using
315 # single quotes in filenames anyway :) )
316 params.append("%s='%s'" % (key, value))
317 return " ".join(params)
318
319 def execute_sql(self, dbname, user, sql):
320 """Execute the sql statament and return a result for SELECT statements
321
322 The user parameter us used as in connection_params. The dbname
323 parameter must be the name of a database in the cluster. The
324 sql parameter is the SQL statement to execute as a string. If
325 the string starts with 'select' (matched case insensitively) the
326 first row of the result will be returned. Otherwise the return
327 value is None.
328 """
329 conn = psycopg.connect("dbname=%s " % dbname
330 + self.connection_string(user))
331 cursor = conn.cursor()
332 cursor.execute(sql)
333 if sql.lower().startswith("select"):
334 row = cursor.fetchone()
335 else:
336 row = None
337 conn.commit()
338 conn.close()
339 return row
340
341 def server_version(self):
342 """Return the server version as a tuple (major, minor, patch)
343
344 Each item in the tuple is an int.
345 """
346 result = self.execute_sql("template1", "admin", "SELECT version();")[0]
347 match = re.match(r"PostgreSQL (\d+\.\d+\.\d+)", result)
348 if match:
349 return tuple(map(int, match.group(1).split(".")))
350 else:
351 raise RutimeError("Cannot determine PostgreSQL server version"
352 " from %r" % result)
353
354 def require_authentication(self, required):
355 """Switch authentication requirements on or off
356
357 When started for the first time no passwords are required. Some
358 tests want to explicitly test whether Thuban's password
359 infrastructure works and switch password authentication on
360 explicitly. When switching it on, there should be a
361 corresponding call to switch it off again in the test case'
362 tearDown method or in a finally: block.
363 """
364 # Starting with PostgreSQL 7.3 the pg_hba.conf file has an
365 # additional column with a username. Query the server version
366 # and generate a file in the correct format.
367 if self.server_version() >= (7, 3):
368 user = "all"
369 else:
370 user = ""
371 if required:
372 contents = "local all %s password\n" % user
373 else:
374 contents = "local all %s trust\n" % user
375 f = open(os.path.join(self.dbdir, "pg_hba.conf"), "w")
376 f.write(contents)
377 f.close()
378 run_command([_pg_ctl_command, "-D", self.dbdir, "reload"],
379 os.path.join(self.dbdir, "pg_ctl-reload.log"))
380
381
382 def create_user(self, username, password):
383 """Create user username with password in the database"""
384 self.execute_sql("template1", "admin",
385 "CREATE USER %s PASSWORD '%s';" % (username,password))
386
387 def alter_user(self, username, password):
388 """Change the user username's password in the database"""
389 self.execute_sql("template1", "admin",
390 "ALTER USER %s PASSWORD '%s';" % (username,password))
391
392
393 class PostGISDatabase:
394
395 """A PostGIS database in a PostgreSQLServer"""
396
397 def __init__(self, server, postgis_sql, dbname, tables = None,
398 reference_systems = (), views = None):
399 """Initialize the PostGISDatabase
400
401 Parameters:
402
403 server -- The PostgreSQLServer instance containing the
404 database
405
406 postgis_sql -- Filename of the sql file with the postgis
407 initialization code
408
409 dbname -- The name of the database
410
411 tables -- Optional description of tables to create in the
412 new database. If given it should be a list of
413 (tablename, shapefilename) pairs meaning that a table
414 tablename will be created with the contents of the given
415 shapefile or (tablename, shapefilename, extraargs)
416 triples. The extraargs should be a list of key, value
417 pairs to use as keyword arguments to upload_shapefile.
418
419 reference_systems -- Optional description of spatial
420 reference systems. If given, it should be a sequence of
421 (srid, params) pairs where srid is the srid defined by
422 the proj4 paramter string params. The srid can be given
423 as an extra parameter in the tables list.
424
425 views -- Optional description of views. If given it should
426 be a list of (viewname, select_stmt) pairs where
427 viewname is the name of the view to be created and
428 select_stmt is the select statement to use as the basis.
429 The views will be created after the tables and may refer
430 to them in the select_stmt.
431 """
432 self.server = server
433 self.postgis_sql = postgis_sql
434 self.dbname = dbname
435 self.tables = tables
436 self.views = views
437 if reference_systems:
438 self.reference_systems = reference_systems
439 else:
440 # Make sure that it's a sequence we can iterate over even if
441 # the parameter's None
442 self.reference_systems = ()
443
444 def initdb(self):
445 """Remove the old db directory and create and initialize a new database
446 """
447 run_command(["createdb", "-p", str(self.server.port),
448 "-h", self.server.host, "-U", self.server.admin_name,
449 self.dbname],
450 os.path.join(self.server.dbdir, "createdb.log"))
451 run_command(["createlang", "-p", str(self.server.port),
452 "-h", self.server.host, "-U", self.server.admin_name,
453 "plpgsql", self.dbname],
454 os.path.join(self.server.dbdir, "createlang.log"))
455 # for some reason psql doesn't exit with an error code if the
456 # file given as -f doesn't exist, so we check manually by trying
457 # to open it before we run psql
458 f = open(self.postgis_sql)
459 f.close()
460 del f
461 run_command(["psql", "-f", self.postgis_sql, "-d", self.dbname,
462 "-p", str(self.server.port), "-h", self.server.host,
463 "-U", self.server.admin_name],
464 os.path.join(self.server.dbdir, "psql.log"))
465
466 self.server.execute_sql(self.dbname, "admin",
467 "GRANT SELECT ON geometry_columns TO PUBLIC;")
468 self.server.execute_sql(self.dbname, "admin",
469 "GRANT SELECT ON spatial_ref_sys TO PUBLIC;")
470
471 for srid, params in self.reference_systems:
472 self.server.execute_sql(self.dbname, "admin",
473 "INSERT INTO spatial_ref_sys VALUES"
474 " (%d, '', %d, '', '%s');"
475 % (srid, srid, params))
476 if self.tables is not None:
477 def unpack(item):
478 extra = {"force_wkt_type": None, "gid_offset": 0,
479 "srid": -1}
480 if len(info) == 2:
481 tablename, shapefile = info
482 else:
483 tablename, shapefile, kw = info
484 for key, val in kw:
485 extra[key] = val
486 return tablename, shapefile, extra
487
488 for info in self.tables:
489 tablename, shapefile, kw = unpack(info)
490 upload_shapefile(shapefile, self, tablename, **kw)
491
492 if self.views is not None:
493 for viewname, select_stmt in self.views:
494 self.server.execute_sql(self.dbname, "admin",
495 "CREATE VIEW %s AS %s" % (viewname,
496 select_stmt))
497 self.server.execute_sql(self.dbname, "admin",
498 "GRANT SELECT ON %s TO PUBLIC;"
499 % viewname)
500
501 def has_data(self, tables, reference_systems, views):
502 return (self.tables == tables
503 and self.reference_systems == reference_systems
504 and self.views == views)
505
506
507 def find_postgis_sql():
508 """Return the name of the postgis_sql file
509
510 A postgis installation usually has the postgis_sql file in
511 PostgreSQL's $datadir (i.e. the directory where PostgreSQL keeps
512 static files, not the directory containing the databases).
513 Unfortunately there's no way to determine the name of this directory
514 with pg_config so we assume here that it's
515 $bindir/../share/postgresql/.
516
517 Furthermore, different versions of postgis place the file in
518 slightly different locations or may even use different names. For
519 instance:
520
521 postgis 0.7.5 $datadir/contrib/postgis.sql
522 postgis 0.8.1 $datadir/postgis.sql
523 postgis 1.0.0-rc1 $datadir/lwpostgis.sql
524 postgis 1.0.0-rc4 $datadir/contrib/lwpostgis.sql
525
526 To support both versions, we look in both places and return the
527 first one found (looking under contrib first).
528
529 Debian (umcoming Etch) can do several version of postgresql
530 and thus has changed the paths. We try one location
531 in datadir2 only for Debian Etch postgresql-8.1.
532
533 If the file is not found the return value is None.
534 """
535 bindir = run_config_script("pg_config --bindir").strip()
536 datadir = os.path.join(bindir, "..", "share", "postgresql")
537 datadir2 = os.path.join("/", "usr", "share", "postgresql-8.1-postgis")
538
539 for filename in [os.path.join(datadir, "contrib", "postgis.sql"),
540 os.path.join(datadir, "postgis.sql"),
541 os.path.join(datadir, "lwpostgis.sql"),
542 os.path.join(datadir, "contrib", "lwpostgis.sql"),
543 os.path.join(datadir2, "lwpostgis.sql") \
544 ]:
545 if os.path.exists(filename):
546 return filename
547
548
549 _postgres_server = None
550 def get_test_server():
551 """Return the test database server object.
552
553 If it doesn't exist yet, create it first.
554
555 The server will use the directory postgis under the temp dir (as
556 defined by support.create_temp_dir()) for the database cluster.
557 Sockets will be created in tempdir.
558 """
559 global _postgres_server
560 if _postgres_server is None:
561 tempdir = support.create_temp_dir()
562 dbdir = os.path.join(tempdir, "postgis")
563 socket_dir = tempdir
564
565 _postgres_server = PostgreSQLServer(dbdir, 6543, find_postgis_sql(),
566 socket_dir = socket_dir)
567 _postgres_server.createdb()
568
569 return _postgres_server
570
571 def shutdown_test_server():
572 """Shutdown the test server if it is running"""
573 global _postgres_server
574 if _postgres_server is not None:
575 _postgres_server.shutdown()
576 _postgres_server = None
577
578
579 def reason_for_not_running_tests():
580 """
581 Determine whether postgis tests can be run and return a reason they can't
582
583 There's no fool-proof way to reliably determine this short of
584 actually running the tests but we try the following here:
585
586 - test whether pg_ctl --help can be run successfully
587 - test whether the postgis_sql can be opened
588 The name of the postgis_sql file is determined by find_postgis_sql()
589 - psycopg can be imported successfully.
590 """
591 # run_command currently uses Popen4 which is not available under
592 # Windows, for example.
593 if not hasattr(popen2, "Popen4"):
594 return "Can't run PostGIS test because popen2.Popen4 does not exist"
595
596 try:
597 run_command([_pg_ctl_command, "--help"], None)
598 except RuntimeError:
599 return "Can't run PostGIS tests because pg_ctl fails"
600
601 try:
602 postgis_sql = find_postgis_sql()
603 except:
604 return "Can't run PostGIS tests because postgis.sql can't be found"
605
606 try:
607 f = open(postgis_sql)
608 f.close()
609 except:
610 return "Can't run PostGIS tests because postgis.sql can't be opened"
611
612 # The test for psycopg was already done when this module was
613 # imported so we only have to check whether it was successful
614 if psycopg is None:
615 return "Can't run PostGIS tests because psycopg can't be imported"
616
617 return ""
618
619
620 _cannot_run_postgis_tests = None
621 def skip_if_no_postgis():
622 global _cannot_run_postgis_tests
623 if _cannot_run_postgis_tests is None:
624 _cannot_run_postgis_tests = reason_for_not_running_tests()
625 if _cannot_run_postgis_tests:
626 raise support.SkipTest(_cannot_run_postgis_tests)
627
628 def skip_if_addgeometrycolumn_does_not_use_quote_ident():
629 """Skip a test if the AddGeometryColumn function doesn't use quote_ident
630
631 If the AddGeometryColumn function doesn't use quote_ident it doesn't
632 support unusual table or column names properly, that is, it will
633 fail with errors for names that contain spaces or double quotes.
634
635 The test performed by this function is a bit simplistic because it
636 only tests whether the string 'quote_ident' occurs anywhere in the
637 postgis.sql file. This will hopefully work because when this was
638 fixed in postgis CVS AddGeometryColumn was the first function to use
639 quote_ident.
640 """
641 f = file(find_postgis_sql())
642 content = f.read()
643 f.close()
644 if content.find("quote_ident") < 0:
645 raise support.SkipTest("AddGeometryColumn doesn't use quote_ident")
646
647 def coords_to_point(coords):
648 """Return string with a WKT representation of the point in coords"""
649 x, y = coords[0]
650 return "POINT(%r %r)" % (x, y)
651
652 def coords_to_polygon(coords):
653 """Return string with a WKT representation of the polygon in coords"""
654 poly = []
655 for ring in coords:
656 poly.append(", ".join(["%r %r" % p for p in ring]))
657 return "POLYGON((%s))" % "), (".join(poly)
658
659 def coords_to_linestring(coords):
660 """Return string with a LINESTRING WKT representation of coords"""
661 if len(coords) > 1:
662 raise ValueError("A LINESTRING can only have one arc")
663 return "LINESTRING(%s)" % ", ".join(["%r %r" % p for p in coords[0]])
664
665 def coords_to_multilinestring(coords):
666 """Return string with a MULTILINESTRING WKT representation of coords"""
667 poly = []
668 for ring in coords:
669 poly.append(", ".join(["%r %r" % p for p in ring]))
670 return "MULTILINESTRING((%s))" % "), (".join(poly)
671
672 def coords_to_multipolygon(coords):
673 """Return string with a WKT representation of the polygon in coords"""
674 poly = []
675 for ring in coords:
676 poly.append(", ".join(["%r %r" % p for p in ring]))
677 return "MULTIPOLYGON(((%s)))" % ")), ((".join(poly)
678
679 wkt_converter = {
680 "POINT": coords_to_point,
681 "LINESTRING": coords_to_linestring,
682 "MULTILINESTRING": coords_to_multilinestring,
683 "POLYGON": coords_to_polygon,
684 "MULTIPOLYGON": coords_to_multipolygon,
685 }
686
687 def upload_shapefile(filename, db, tablename, force_wkt_type = None,
688 gid_offset = 0, gid_column = "gid", srid = -1):
689 """Upload a shapefile into a new database table
690
691 Parameters:
692
693 filename -- The name of the shapefile
694
695 db -- The PostGISDatabase instance representing the database
696
697 tablename -- The name of the table to create and into which the data
698 is to be inserted
699
700 force_wkt_type -- If given and not None, this is used as the WKT
701 geometry type to use instead of the default that would
702 be chosen based on the type of the shapefile
703
704 gid_offset -- A number to add to the shapeid to get the value for
705 the gid column (default 0)
706
707 gid_column -- The name of the column with the shape ids. Default
708 'gid'. If None, no gid column will be created. The
709 name is directly used in SQL statements, so if it
710 contains unusualy characters the caller should provide
711 a suitable quoted string.
712
713 srid -- The srid of the spatial references system used by the table
714 and the data
715 """
716 import dbflib, shapelib
717
718 # We build this map here because we need shapelib which can only be
719 # imported after support.initthuban has been called which we can't
720 # easily do in this module because it's imported by support.
721 shp_to_wkt = {
722 shapelib.SHPT_POINT: "POINT",
723 shapelib.SHPT_ARC: "MULTILINESTRING",
724 shapelib.SHPT_POLYGON: "POLYGON",
725 }
726
727 server = db.server
728 dbname = db.dbname
729 conn = psycopg.connect("dbname=%s " % dbname
730 + db.server.connection_string("admin"))
731 cursor = conn.cursor()
732
733 shp = shapelib.ShapeFile(filename)
734 dbf = dbflib.DBFFile(filename)
735 typemap = {dbflib.FTString: "VARCHAR",
736 dbflib.FTInteger: "INTEGER",
737 dbflib.FTDouble: "DOUBLE PRECISION"}
738
739 insert_formats = []
740 if gid_column:
741 insert_formats.append("%(gid)s")
742
743 fields = []
744 fields_decl = []
745 if gid_column:
746 fields.append(gid_column)
747 fields_decl.append("%s INT" % gid_column)
748 for i in range(dbf.field_count()):
749 ftype, name, width, prec = dbf.field_info(i)
750 fields.append(name)
751 fields_decl.append("%s %s" % (name, typemap[ftype]))
752 insert_formats.append("%%(%s)s" % name)
753 stmt = "CREATE TABLE %s (\n %s\n);" % (tablename,
754 ",\n ".join(fields_decl))
755 cursor.execute(stmt)
756 #print stmt
757
758 numshapes, shapetype, mins, maxs = shp.info()
759 wkttype = shp_to_wkt[shapetype]
760 if force_wkt_type:
761 wkttype = force_wkt_type
762 convert = wkt_converter[wkttype]
763
764 cursor.execute("select AddGeometryColumn('%(dbname)s',"
765 "'%(tablename)s', 'the_geom', %(srid)d, '%(wkttype)s', 2);"
766 % locals())
767 fields.append("the_geom")
768 insert_formats.append("GeometryFromText(%(the_geom)s, %(srid)d)")
769
770 insert = ("INSERT INTO %s (%s) VALUES (%s)"
771 % (tablename, ", ".join(fields), ", ".join(insert_formats)))
772
773 for i in range(numshapes):
774 data = dbf.read_record(i)
775 data["tablename"] = tablename
776 if gid_column:
777 data["gid"] = i + gid_offset
778 data["srid"] = srid
779 data["the_geom"] = convert(shp.read_object(i).vertices())
780 #print insert % data
781 cursor.execute(insert, data)
782
783 cursor.execute("GRANT SELECT ON %s TO PUBLIC;" % tablename)
784
785 conn.commit()

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26