/[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 2734 - (hide annotations)
Thu Mar 1 12:42:59 2007 UTC (18 years ago) by bramz
File MIME type: text/x-python
File size: 30463 byte(s)
made a copy
1 bernhard 2714 # Copyright (C) 2003, 2004, 2005, 2006 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 bernhard 2714 _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 bh 1605 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 bh 1634 # host if the name starts with a slash.
127 bh 1605 self.host = os.path.abspath(socket_dir)
128    
129 bh 1634 # 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 bh 1605 # 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 bernhard 2714 run_command([_initdb_command, "-D", self.dbdir, "-U", self.admin_name],
153 bh 1605 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 bernhard 2714 run_command([_pg_ctl_command, "-D", self.dbdir,
159 bh 1605 "-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 bh 1634 self.alter_user(self.admin_name, self.admin_password)
168     self.create_user(self.user_name, self.user_password)
169    
170 bh 1605 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 bh 1634 "-h", self.host, "-U", self.admin_name],
182 bh 1605 os.path.join(self.dbdir, "psql-%d.log" % count))
183 bh 1634 except RuntimeError:
184     pass
185 bh 1605 except:
186 bh 1634 traceback.print_exc()
187 bh 1605 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 bh 2459 """Return whether a postmaster process is running on self.dbdir
196 bh 1605
197 bh 2459 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 bh 1605 """
206 bernhard 2714 return run_boolean_command([_pg_ctl_command, "-D", self.dbdir, "status"])
207 bh 1605
208     def shutdown(self):
209     """Stop the postmaster running for self.dbdir"""
210 bernhard 2714 run_command([_pg_ctl_command, "-m", "fast", "-D", self.dbdir, "stop"],
211 bh 1605 os.path.join(self.dbdir, "pg_ctl-stop.log"))
212    
213 bh 2106 def new_postgis_db(self, dbname, tables = None, reference_systems = None,
214     views = None):
215 bh 1605 """Create and return a new PostGISDatabase object using self as server
216     """
217 bh 2057 db = PostGISDatabase(self, self.postgis_sql, dbname, tables = tables,
218 bh 2106 reference_systems = reference_systems,
219     views = views)
220 bh 1605 db.initdb()
221     self.known_dbs[dbname] = db
222     return db
223    
224 bh 2106 def get_static_data_db(self, dbname, tables, reference_systems, views):
225 bh 1605 """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 bh 2057 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 bh 1605 """
240     db = self.known_dbs.get(dbname)
241     if db is not None:
242 bh 2106 if db.has_data(tables, reference_systems, views):
243 bh 1605 return db
244     raise ValueError("PostGISDatabase named %r doesn't have tables %r"
245     % (dbname, tables))
246 bh 2106 return self.new_postgis_db(dbname, tables, reference_systems, views)
247 bh 1605
248     def get_default_static_data_db(self):
249     dbname = "PostGISStaticTests"
250 bh 2057 srids = [(1, "proj=longlat datum=WGS84")]
251 bh 1662 tables = [
252     # Direct copies of the shapefiles. The shapeids are exactly
253 bh 2057 # the same, except where changed with "gid_offset", of
254 bh 2096 # course. Note that the test implementation requires that
255     # all the landmard tables use an gid_offset of 1000.
256 bh 1662 ("landmarks", os.path.join("..", "Data", "iceland",
257     "cultural_landmark-point.shp"),
258     [("gid_offset", 1000)]),
259     ("political", os.path.join("..", "Data", "iceland",
260 bh 1605 "political.shp")),
261 bh 2472 ("roads_multi", os.path.join("..", "Data", "iceland",
262 bh 1662 "roads-line.shp")),
263 bh 1656
264 bh 2472 # 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 bh 1662 # The polygon data as a MULTIPOLYGON geometry type
271     ("political_multi", os.path.join("..", "Data", "iceland",
272 bh 1656 "political.shp"),
273 bh 1662 [("force_wkt_type", "MULTIPOLYGON")]),
274 bh 2057
275 bh 2096 # Copy of landmarks but using an srid != -1
276 bh 2057 ("landmarks_srid", os.path.join("..", "Data", "iceland",
277     "cultural_landmark-point.shp"),
278     [("gid_offset", 1000),
279     ("srid", 1)]),
280 bh 2096
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 bh 1662 ]
289 bh 2106 views = [("v_landmarks", "SELECT * FROM landmarks_point_id")]
290     return self.get_static_data_db(dbname, tables, srids, views)
291 bh 1605
292 bh 1634 def connection_params(self, user):
293     """Return the connection parameters for the given user
294 bh 1605
295 bh 1634 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 bh 1605
304 bh 1634 def connection_string(self, user):
305     """Return (part of) the connection string to pass to psycopg.connect
306 bh 1605
307 bh 1634 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 bh 2460 """Execute the sql statament and return a result for SELECT statements
321 bh 1634
322     The user parameter us used as in connection_params. The dbname
323 bh 2460 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 bh 1634 """
329     conn = psycopg.connect("dbname=%s " % dbname
330     + self.connection_string(user))
331     cursor = conn.cursor()
332     cursor.execute(sql)
333 bh 2460 if sql.lower().startswith("select"):
334     row = cursor.fetchone()
335     else:
336     row = None
337 bh 1634 conn.commit()
338     conn.close()
339 bh 2460 return row
340 bh 1634
341 bh 2460 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 bh 1634 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 bh 2460 # 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 bh 1634 if required:
372 bh 2460 contents = "local all %s password\n" % user
373 bh 1634 else:
374 bh 2460 contents = "local all %s trust\n" % user
375 bh 1634 f = open(os.path.join(self.dbdir, "pg_hba.conf"), "w")
376     f.write(contents)
377     f.close()
378 bernhard 2714 run_command([_pg_ctl_command, "-D", self.dbdir, "reload"],
379 bh 1634 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 bh 1605 class PostGISDatabase:
394    
395     """A PostGIS database in a PostgreSQLServer"""
396    
397 bh 2057 def __init__(self, server, postgis_sql, dbname, tables = None,
398 bh 2106 reference_systems = (), views = None):
399 bh 1662 """Initialize the PostGISDatabase
400    
401     Parameters:
402    
403     server -- The PostgreSQLServer instance containing the
404     database
405    
406 bh 2543 postgis_sql -- Filename of the sql file with the postgis
407     initialization code
408 bh 1662
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 bh 2057
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 bh 2106
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 bh 1662 """
432 bh 1605 self.server = server
433     self.postgis_sql = postgis_sql
434     self.dbname = dbname
435     self.tables = tables
436 bh 2106 self.views = views
437 bh 2057 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 bh 1605
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 bh 1634 "-h", self.server.host, "-U", self.server.admin_name,
449     self.dbname],
450 bh 1605 os.path.join(self.server.dbdir, "createdb.log"))
451     run_command(["createlang", "-p", str(self.server.port),
452 bh 1634 "-h", self.server.host, "-U", self.server.admin_name,
453     "plpgsql", self.dbname],
454 bh 1605 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 bh 1634 "-p", str(self.server.port), "-h", self.server.host,
463     "-U", self.server.admin_name],
464 bh 1605 os.path.join(self.server.dbdir, "psql.log"))
465    
466 bh 1634 self.server.execute_sql(self.dbname, "admin",
467     "GRANT SELECT ON geometry_columns TO PUBLIC;")
468 bh 2057 self.server.execute_sql(self.dbname, "admin",
469     "GRANT SELECT ON spatial_ref_sys TO PUBLIC;")
470 bh 1634
471 bh 2057 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 bh 1605 if self.tables is not None:
477 bh 1662 def unpack(item):
478 bh 2057 extra = {"force_wkt_type": None, "gid_offset": 0,
479     "srid": -1}
480 bh 1656 if len(info) == 2:
481     tablename, shapefile = info
482     else:
483 bh 1662 tablename, shapefile, kw = info
484     for key, val in kw:
485     extra[key] = val
486     return tablename, shapefile, extra
487 bh 1605
488 bh 1662 for info in self.tables:
489     tablename, shapefile, kw = unpack(info)
490     upload_shapefile(shapefile, self, tablename, **kw)
491    
492 bh 2106 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 bh 2057 return (self.tables == tables
503 bh 2106 and self.reference_systems == reference_systems
504     and self.views == views)
505 bh 1605
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 bh 2471 PostgreSQL's $datadir (i.e. the directory where PostgreSQL keeps
512 bh 1605 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 bh 2471
517     Furthermore, different versions of postgis place the file in
518 bh 2543 slightly different locations or may even use different names. For
519     instance:
520 bh 2471
521     postgis 0.7.5 $datadir/contrib/postgis.sql
522     postgis 0.8.1 $datadir/postgis.sql
523 bh 2543 postgis 1.0.0-rc1 $datadir/lwpostgis.sql
524 bh 2589 postgis 1.0.0-rc4 $datadir/contrib/lwpostgis.sql
525 bh 2471
526     To support both versions, we look in both places and return the
527 bernhard 2714 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 bh 1605 """
535     bindir = run_config_script("pg_config --bindir").strip()
536 bh 2471 datadir = os.path.join(bindir, "..", "share", "postgresql")
537 bernhard 2714 datadir2 = os.path.join("/", "usr", "share", "postgresql-8.1-postgis")
538    
539 bh 2471 for filename in [os.path.join(datadir, "contrib", "postgis.sql"),
540 bh 2543 os.path.join(datadir, "postgis.sql"),
541 bh 2589 os.path.join(datadir, "lwpostgis.sql"),
542 bernhard 2714 os.path.join(datadir, "contrib", "lwpostgis.sql"),
543     os.path.join(datadir2, "lwpostgis.sql") \
544     ]:
545 bh 2471 if os.path.exists(filename):
546     return filename
547 bh 1605
548 bh 2471
549 bh 1605 _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 bh 1679 # 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 bh 1605 try:
597 bernhard 2714 run_command([_pg_ctl_command, "--help"], None)
598 bh 1605 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 bh 1947 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 bh 2096 postgis.sql file. This will hopefully work because when this was
638 bh 1947 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 bh 1656 def coords_to_point(coords):
648 bh 1605 """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 bh 1656 def coords_to_polygon(coords):
653 bh 1605 """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 bh 2472 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 bh 1656 def coords_to_multilinestring(coords):
666 bh 2472 """Return string with a MULTILINESTRING WKT representation of coords"""
667 bh 1605 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 bh 1656 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 bh 2472 "LINESTRING": coords_to_linestring,
682 bh 1656 "MULTILINESTRING": coords_to_multilinestring,
683     "POLYGON": coords_to_polygon,
684     "MULTIPOLYGON": coords_to_multipolygon,
685     }
686    
687 bh 1662 def upload_shapefile(filename, db, tablename, force_wkt_type = None,
688 bh 2096 gid_offset = 0, gid_column = "gid", srid = -1):
689 bh 1662 """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 bh 2472 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 bh 1662
704     gid_offset -- A number to add to the shapeid to get the value for
705     the gid column (default 0)
706 bh 2057
707 bh 2096 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 bh 2057 srid -- The srid of the spatial references system used by the table
714     and the data
715 bernhard 2717
716     The tables will be explicitely created WITH OIDS. This has been
717     default for PostgreSQL <8.0 and some tests relied on it (end of 2006).
718 bh 1662 """
719 bh 1605 import dbflib, shapelib
720    
721 bh 1656 # We build this map here because we need shapelib which can only be
722     # imported after support.initthuban has been called which we can't
723     # easily do in this module because it's imported by support.
724     shp_to_wkt = {
725     shapelib.SHPT_POINT: "POINT",
726     shapelib.SHPT_ARC: "MULTILINESTRING",
727     shapelib.SHPT_POLYGON: "POLYGON",
728     }
729    
730 bh 1605 server = db.server
731     dbname = db.dbname
732 bh 1634 conn = psycopg.connect("dbname=%s " % dbname
733     + db.server.connection_string("admin"))
734 bh 1605 cursor = conn.cursor()
735    
736     shp = shapelib.ShapeFile(filename)
737     dbf = dbflib.DBFFile(filename)
738     typemap = {dbflib.FTString: "VARCHAR",
739     dbflib.FTInteger: "INTEGER",
740     dbflib.FTDouble: "DOUBLE PRECISION"}
741    
742 bh 2096 insert_formats = []
743     if gid_column:
744     insert_formats.append("%(gid)s")
745    
746     fields = []
747     fields_decl = []
748     if gid_column:
749     fields.append(gid_column)
750     fields_decl.append("%s INT" % gid_column)
751 bh 1605 for i in range(dbf.field_count()):
752     ftype, name, width, prec = dbf.field_info(i)
753 bh 2096 fields.append(name)
754     fields_decl.append("%s %s" % (name, typemap[ftype]))
755 bh 1605 insert_formats.append("%%(%s)s" % name)
756 bernhard 2717 stmt = "CREATE TABLE %s (\n %s\n) WITH OIDS ;" % (tablename,
757 bh 2096 ",\n ".join(fields_decl))
758 bh 1605 cursor.execute(stmt)
759     #print stmt
760    
761     numshapes, shapetype, mins, maxs = shp.info()
762 bh 1656 wkttype = shp_to_wkt[shapetype]
763     if force_wkt_type:
764     wkttype = force_wkt_type
765     convert = wkt_converter[wkttype]
766 bh 1605
767     cursor.execute("select AddGeometryColumn('%(dbname)s',"
768 bh 2057 "'%(tablename)s', 'the_geom', %(srid)d, '%(wkttype)s', 2);"
769 bh 1605 % locals())
770 bh 2096 fields.append("the_geom")
771 bh 2057 insert_formats.append("GeometryFromText(%(the_geom)s, %(srid)d)")
772 bh 1605
773 bh 2096 insert = ("INSERT INTO %s (%s) VALUES (%s)"
774     % (tablename, ", ".join(fields), ", ".join(insert_formats)))
775 bh 1605
776     for i in range(numshapes):
777     data = dbf.read_record(i)
778     data["tablename"] = tablename
779 bh 2096 if gid_column:
780     data["gid"] = i + gid_offset
781 bh 2057 data["srid"] = srid
782 bh 1605 data["the_geom"] = convert(shp.read_object(i).vertices())
783     #print insert % data
784     cursor.execute(insert, data)
785    
786 bh 1634 cursor.execute("GRANT SELECT ON %s TO PUBLIC;" % tablename)
787    
788 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