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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2460 - (hide annotations)
Wed Dec 15 14:01:04 2004 UTC (20 years, 2 months ago) by bh
Original Path: trunk/thuban/test/postgissupport.py
File MIME type: text/x-python
File size: 28224 byte(s)
(PostgreSQLServer.execute_sql): Extend to
so that it returns a result for select statements.
(PostgreSQLServer.server_version): New.  Return the version of the
server software.
(PostgreSQLServer.require_authentication): The format of
pg_hba.conf has changed between PostgrSQL 7.2 and 7.3.  Check the
server version and generate the file in the correct format

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