/[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 2543 - (hide annotations)
Fri Jan 21 16:58:31 2005 UTC (20 years, 1 month ago) by bh
Original Path: trunk/thuban/test/postgissupport.py
File MIME type: text/x-python
File size: 29525 byte(s)
(PostGISDatabase.__init__): Tweak
doc-string
(find_postgis_sql): Update for postgis-1.0.0-rc1, which uses a
different name for the initialization SQL file.

1 bh 2543 # Copyright (C) 2003, 2004, 2005 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 2472 ("roads_multi", os.path.join("..", "Data", "iceland",
255 bh 1662 "roads-line.shp")),
256 bh 1656
257 bh 2472 # same as roads-multi but using LINESTRING instead of
258     # MULTILINESTRING
259     ("roads", os.path.join("..", "Data", "iceland",
260     "roads-line.shp"),
261     [("force_wkt_type", "LINESTRING")]),
262    
263 bh 1662 # The polygon data as a MULTIPOLYGON geometry type
264     ("political_multi", os.path.join("..", "Data", "iceland",
265 bh 1656 "political.shp"),
266 bh 1662 [("force_wkt_type", "MULTIPOLYGON")]),
267 bh 2057
268 bh 2096 # Copy of landmarks but using an srid != -1
269 bh 2057 ("landmarks_srid", os.path.join("..", "Data", "iceland",
270     "cultural_landmark-point.shp"),
271     [("gid_offset", 1000),
272     ("srid", 1)]),
273 bh 2096
274     # Copy of landmarks with a gid column called "point_id" instead
275     # of "gid" and using an srid != -1.
276     ("landmarks_point_id", os.path.join("..", "Data", "iceland",
277     "cultural_landmark-point.shp"),
278     [("gid_offset", 1000),
279     ("srid", 1),
280     ("gid_column", "point_id")]),
281 bh 1662 ]
282 bh 2106 views = [("v_landmarks", "SELECT * FROM landmarks_point_id")]
283     return self.get_static_data_db(dbname, tables, srids, views)
284 bh 1605
285 bh 1634 def connection_params(self, user):
286     """Return the connection parameters for the given user
287 bh 1605
288 bh 1634 The return value is a dictionary suitable as keyword argument
289     list to PostGISConnection. The user parameter may be either
290     'admin' to connect as admin or 'user' to connect as an
291     unprivileged user.
292     """
293     return {"host": self.host, "port": self.port,
294     "user": getattr(self, user + "_name"),
295     "password": getattr(self, user + "_password")}
296 bh 1605
297 bh 1634 def connection_string(self, user):
298     """Return (part of) the connection string to pass to psycopg.connect
299 bh 1605
300 bh 1634 The string contains host, port, user and password. The user
301     parameter must be either 'admin' or 'user', as for
302     connection_params.
303     """
304     params = []
305     for key, value in self.connection_params(user).items():
306     # FIXME: this doesn't do quiting correctly but that
307     # shouldn't be much of a problem (people shouldn't be using
308     # single quotes in filenames anyway :) )
309     params.append("%s='%s'" % (key, value))
310     return " ".join(params)
311    
312     def execute_sql(self, dbname, user, sql):
313 bh 2460 """Execute the sql statament and return a result for SELECT statements
314 bh 1634
315     The user parameter us used as in connection_params. The dbname
316 bh 2460 parameter must be the name of a database in the cluster. The
317     sql parameter is the SQL statement to execute as a string. If
318     the string starts with 'select' (matched case insensitively) the
319     first row of the result will be returned. Otherwise the return
320     value is None.
321 bh 1634 """
322     conn = psycopg.connect("dbname=%s " % dbname
323     + self.connection_string(user))
324     cursor = conn.cursor()
325     cursor.execute(sql)
326 bh 2460 if sql.lower().startswith("select"):
327     row = cursor.fetchone()
328     else:
329     row = None
330 bh 1634 conn.commit()
331     conn.close()
332 bh 2460 return row
333 bh 1634
334 bh 2460 def server_version(self):
335     """Return the server version as a tuple (major, minor, patch)
336    
337     Each item in the tuple is an int.
338     """
339     result = self.execute_sql("template1", "admin", "SELECT version();")[0]
340     match = re.match(r"PostgreSQL (\d+\.\d+\.\d+)", result)
341     if match:
342     return tuple(map(int, match.group(1).split(".")))
343     else:
344     raise RutimeError("Cannot determine PostgreSQL server version"
345     " from %r" % result)
346    
347 bh 1634 def require_authentication(self, required):
348     """Switch authentication requirements on or off
349    
350     When started for the first time no passwords are required. Some
351     tests want to explicitly test whether Thuban's password
352     infrastructure works and switch password authentication on
353     explicitly. When switching it on, there should be a
354     corresponding call to switch it off again in the test case'
355     tearDown method or in a finally: block.
356     """
357 bh 2460 # Starting with PostgreSQL 7.3 the pg_hba.conf file has an
358     # additional column with a username. Query the server version
359     # and generate a file in the correct format.
360     if self.server_version() >= (7, 3):
361     user = "all"
362     else:
363     user = ""
364 bh 1634 if required:
365 bh 2460 contents = "local all %s password\n" % user
366 bh 1634 else:
367 bh 2460 contents = "local all %s trust\n" % user
368 bh 1634 f = open(os.path.join(self.dbdir, "pg_hba.conf"), "w")
369     f.write(contents)
370     f.close()
371     run_command(["pg_ctl", "-D", self.dbdir, "reload"],
372     os.path.join(self.dbdir, "pg_ctl-reload.log"))
373    
374    
375     def create_user(self, username, password):
376     """Create user username with password in the database"""
377     self.execute_sql("template1", "admin",
378     "CREATE USER %s PASSWORD '%s';" % (username,password))
379    
380     def alter_user(self, username, password):
381     """Change the user username's password in the database"""
382     self.execute_sql("template1", "admin",
383     "ALTER USER %s PASSWORD '%s';" % (username,password))
384    
385    
386 bh 1605 class PostGISDatabase:
387    
388     """A PostGIS database in a PostgreSQLServer"""
389    
390 bh 2057 def __init__(self, server, postgis_sql, dbname, tables = None,
391 bh 2106 reference_systems = (), views = None):
392 bh 1662 """Initialize the PostGISDatabase
393    
394     Parameters:
395    
396     server -- The PostgreSQLServer instance containing the
397     database
398    
399 bh 2543 postgis_sql -- Filename of the sql file with the postgis
400     initialization code
401 bh 1662
402     dbname -- The name of the database
403    
404     tables -- Optional description of tables to create in the
405     new database. If given it should be a list of
406     (tablename, shapefilename) pairs meaning that a table
407     tablename will be created with the contents of the given
408     shapefile or (tablename, shapefilename, extraargs)
409     triples. The extraargs should be a list of key, value
410     pairs to use as keyword arguments to upload_shapefile.
411 bh 2057
412     reference_systems -- Optional description of spatial
413     reference systems. If given, it should be a sequence of
414     (srid, params) pairs where srid is the srid defined by
415     the proj4 paramter string params. The srid can be given
416     as an extra parameter in the tables list.
417 bh 2106
418     views -- Optional description of views. If given it should
419     be a list of (viewname, select_stmt) pairs where
420     viewname is the name of the view to be created and
421     select_stmt is the select statement to use as the basis.
422     The views will be created after the tables and may refer
423     to them in the select_stmt.
424 bh 1662 """
425 bh 1605 self.server = server
426     self.postgis_sql = postgis_sql
427     self.dbname = dbname
428     self.tables = tables
429 bh 2106 self.views = views
430 bh 2057 if reference_systems:
431     self.reference_systems = reference_systems
432     else:
433     # Make sure that it's a sequence we can iterate over even if
434     # the parameter's None
435     self.reference_systems = ()
436 bh 1605
437     def initdb(self):
438     """Remove the old db directory and create and initialize a new database
439     """
440     run_command(["createdb", "-p", str(self.server.port),
441 bh 1634 "-h", self.server.host, "-U", self.server.admin_name,
442     self.dbname],
443 bh 1605 os.path.join(self.server.dbdir, "createdb.log"))
444     run_command(["createlang", "-p", str(self.server.port),
445 bh 1634 "-h", self.server.host, "-U", self.server.admin_name,
446     "plpgsql", self.dbname],
447 bh 1605 os.path.join(self.server.dbdir, "createlang.log"))
448     # for some reason psql doesn't exit with an error code if the
449     # file given as -f doesn't exist, so we check manually by trying
450     # to open it before we run psql
451     f = open(self.postgis_sql)
452     f.close()
453     del f
454     run_command(["psql", "-f", self.postgis_sql, "-d", self.dbname,
455 bh 1634 "-p", str(self.server.port), "-h", self.server.host,
456     "-U", self.server.admin_name],
457 bh 1605 os.path.join(self.server.dbdir, "psql.log"))
458    
459 bh 1634 self.server.execute_sql(self.dbname, "admin",
460     "GRANT SELECT ON geometry_columns TO PUBLIC;")
461 bh 2057 self.server.execute_sql(self.dbname, "admin",
462     "GRANT SELECT ON spatial_ref_sys TO PUBLIC;")
463 bh 1634
464 bh 2057 for srid, params in self.reference_systems:
465     self.server.execute_sql(self.dbname, "admin",
466     "INSERT INTO spatial_ref_sys VALUES"
467     " (%d, '', %d, '', '%s');"
468     % (srid, srid, params))
469 bh 1605 if self.tables is not None:
470 bh 1662 def unpack(item):
471 bh 2057 extra = {"force_wkt_type": None, "gid_offset": 0,
472     "srid": -1}
473 bh 1656 if len(info) == 2:
474     tablename, shapefile = info
475     else:
476 bh 1662 tablename, shapefile, kw = info
477     for key, val in kw:
478     extra[key] = val
479     return tablename, shapefile, extra
480 bh 1605
481 bh 1662 for info in self.tables:
482     tablename, shapefile, kw = unpack(info)
483     upload_shapefile(shapefile, self, tablename, **kw)
484    
485 bh 2106 if self.views is not None:
486     for viewname, select_stmt in self.views:
487     self.server.execute_sql(self.dbname, "admin",
488     "CREATE VIEW %s AS %s" % (viewname,
489     select_stmt))
490     self.server.execute_sql(self.dbname, "admin",
491     "GRANT SELECT ON %s TO PUBLIC;"
492     % viewname)
493    
494     def has_data(self, tables, reference_systems, views):
495 bh 2057 return (self.tables == tables
496 bh 2106 and self.reference_systems == reference_systems
497     and self.views == views)
498 bh 1605
499    
500     def find_postgis_sql():
501     """Return the name of the postgis_sql file
502    
503     A postgis installation usually has the postgis_sql file in
504 bh 2471 PostgreSQL's $datadir (i.e. the directory where PostgreSQL keeps
505 bh 1605 static files, not the directory containing the databases).
506     Unfortunately there's no way to determine the name of this directory
507     with pg_config so we assume here that it's
508     $bindir/../share/postgresql/.
509 bh 2471
510     Furthermore, different versions of postgis place the file in
511 bh 2543 slightly different locations or may even use different names. For
512     instance:
513 bh 2471
514     postgis 0.7.5 $datadir/contrib/postgis.sql
515     postgis 0.8.1 $datadir/postgis.sql
516 bh 2543 postgis 1.0.0-rc1 $datadir/lwpostgis.sql
517 bh 2471
518     To support both versions, we look in both places and return the
519     first one found (looking under contrib first). If the file is not
520     found the return value is None.
521 bh 1605 """
522     bindir = run_config_script("pg_config --bindir").strip()
523 bh 2471 datadir = os.path.join(bindir, "..", "share", "postgresql")
524     for filename in [os.path.join(datadir, "contrib", "postgis.sql"),
525 bh 2543 os.path.join(datadir, "postgis.sql"),
526     os.path.join(datadir, "lwpostgis.sql")]:
527 bh 2471 if os.path.exists(filename):
528     return filename
529 bh 1605
530 bh 2471
531 bh 1605 _postgres_server = None
532     def get_test_server():
533     """Return the test database server object.
534    
535     If it doesn't exist yet, create it first.
536    
537     The server will use the directory postgis under the temp dir (as
538     defined by support.create_temp_dir()) for the database cluster.
539     Sockets will be created in tempdir.
540     """
541     global _postgres_server
542     if _postgres_server is None:
543     tempdir = support.create_temp_dir()
544     dbdir = os.path.join(tempdir, "postgis")
545     socket_dir = tempdir
546    
547     _postgres_server = PostgreSQLServer(dbdir, 6543, find_postgis_sql(),
548     socket_dir = socket_dir)
549     _postgres_server.createdb()
550    
551     return _postgres_server
552    
553     def shutdown_test_server():
554     """Shutdown the test server if it is running"""
555     global _postgres_server
556     if _postgres_server is not None:
557     _postgres_server.shutdown()
558     _postgres_server = None
559    
560    
561     def reason_for_not_running_tests():
562     """
563     Determine whether postgis tests can be run and return a reason they can't
564    
565     There's no fool-proof way to reliably determine this short of
566     actually running the tests but we try the following here:
567    
568     - test whether pg_ctl --help can be run successfully
569     - test whether the postgis_sql can be opened
570     The name of the postgis_sql file is determined by find_postgis_sql()
571     - psycopg can be imported successfully.
572     """
573 bh 1679 # run_command currently uses Popen4 which is not available under
574     # Windows, for example.
575     if not hasattr(popen2, "Popen4"):
576     return "Can't run PostGIS test because popen2.Popen4 does not exist"
577    
578 bh 1605 try:
579     run_command(["pg_ctl", "--help"], None)
580     except RuntimeError:
581     return "Can't run PostGIS tests because pg_ctl fails"
582    
583     try:
584     postgis_sql = find_postgis_sql()
585     except:
586     return "Can't run PostGIS tests because postgis.sql can't be found"
587    
588     try:
589     f = open(postgis_sql)
590     f.close()
591     except:
592     return "Can't run PostGIS tests because postgis.sql can't be opened"
593    
594     # The test for psycopg was already done when this module was
595     # imported so we only have to check whether it was successful
596     if psycopg is None:
597     return "Can't run PostGIS tests because psycopg can't be imported"
598    
599     return ""
600    
601    
602     _cannot_run_postgis_tests = None
603     def skip_if_no_postgis():
604     global _cannot_run_postgis_tests
605     if _cannot_run_postgis_tests is None:
606     _cannot_run_postgis_tests = reason_for_not_running_tests()
607     if _cannot_run_postgis_tests:
608     raise support.SkipTest(_cannot_run_postgis_tests)
609    
610 bh 1947 def skip_if_addgeometrycolumn_does_not_use_quote_ident():
611     """Skip a test if the AddGeometryColumn function doesn't use quote_ident
612    
613     If the AddGeometryColumn function doesn't use quote_ident it doesn't
614     support unusual table or column names properly, that is, it will
615     fail with errors for names that contain spaces or double quotes.
616    
617     The test performed by this function is a bit simplistic because it
618     only tests whether the string 'quote_ident' occurs anywhere in the
619 bh 2096 postgis.sql file. This will hopefully work because when this was
620 bh 1947 fixed in postgis CVS AddGeometryColumn was the first function to use
621     quote_ident.
622     """
623     f = file(find_postgis_sql())
624     content = f.read()
625     f.close()
626     if content.find("quote_ident") < 0:
627     raise support.SkipTest("AddGeometryColumn doesn't use quote_ident")
628    
629 bh 1656 def coords_to_point(coords):
630 bh 1605 """Return string with a WKT representation of the point in coords"""
631     x, y = coords[0]
632     return "POINT(%r %r)" % (x, y)
633    
634 bh 1656 def coords_to_polygon(coords):
635 bh 1605 """Return string with a WKT representation of the polygon in coords"""
636     poly = []
637     for ring in coords:
638     poly.append(", ".join(["%r %r" % p for p in ring]))
639     return "POLYGON((%s))" % "), (".join(poly)
640    
641 bh 2472 def coords_to_linestring(coords):
642     """Return string with a LINESTRING WKT representation of coords"""
643     if len(coords) > 1:
644     raise ValueError("A LINESTRING can only have one arc")
645     return "LINESTRING(%s)" % ", ".join(["%r %r" % p for p in coords[0]])
646    
647 bh 1656 def coords_to_multilinestring(coords):
648 bh 2472 """Return string with a MULTILINESTRING WKT representation of coords"""
649 bh 1605 poly = []
650     for ring in coords:
651     poly.append(", ".join(["%r %r" % p for p in ring]))
652     return "MULTILINESTRING((%s))" % "), (".join(poly)
653    
654 bh 1656 def coords_to_multipolygon(coords):
655     """Return string with a WKT representation of the polygon in coords"""
656     poly = []
657     for ring in coords:
658     poly.append(", ".join(["%r %r" % p for p in ring]))
659     return "MULTIPOLYGON(((%s)))" % ")), ((".join(poly)
660    
661     wkt_converter = {
662     "POINT": coords_to_point,
663 bh 2472 "LINESTRING": coords_to_linestring,
664 bh 1656 "MULTILINESTRING": coords_to_multilinestring,
665     "POLYGON": coords_to_polygon,
666     "MULTIPOLYGON": coords_to_multipolygon,
667     }
668    
669 bh 1662 def upload_shapefile(filename, db, tablename, force_wkt_type = None,
670 bh 2096 gid_offset = 0, gid_column = "gid", srid = -1):
671 bh 1662 """Upload a shapefile into a new database table
672    
673     Parameters:
674    
675     filename -- The name of the shapefile
676    
677     db -- The PostGISDatabase instance representing the database
678    
679     tablename -- The name of the table to create and into which the data
680     is to be inserted
681    
682 bh 2472 force_wkt_type -- If given and not None, this is used as the WKT
683     geometry type to use instead of the default that would
684     be chosen based on the type of the shapefile
685 bh 1662
686     gid_offset -- A number to add to the shapeid to get the value for
687     the gid column (default 0)
688 bh 2057
689 bh 2096 gid_column -- The name of the column with the shape ids. Default
690     'gid'. If None, no gid column will be created. The
691     name is directly used in SQL statements, so if it
692     contains unusualy characters the caller should provide
693     a suitable quoted string.
694    
695 bh 2057 srid -- The srid of the spatial references system used by the table
696     and the data
697 bh 1662 """
698 bh 1605 import dbflib, shapelib
699    
700 bh 1656 # We build this map here because we need shapelib which can only be
701     # imported after support.initthuban has been called which we can't
702     # easily do in this module because it's imported by support.
703     shp_to_wkt = {
704     shapelib.SHPT_POINT: "POINT",
705     shapelib.SHPT_ARC: "MULTILINESTRING",
706     shapelib.SHPT_POLYGON: "POLYGON",
707     }
708    
709 bh 1605 server = db.server
710     dbname = db.dbname
711 bh 1634 conn = psycopg.connect("dbname=%s " % dbname
712     + db.server.connection_string("admin"))
713 bh 1605 cursor = conn.cursor()
714    
715     shp = shapelib.ShapeFile(filename)
716     dbf = dbflib.DBFFile(filename)
717     typemap = {dbflib.FTString: "VARCHAR",
718     dbflib.FTInteger: "INTEGER",
719     dbflib.FTDouble: "DOUBLE PRECISION"}
720    
721 bh 2096 insert_formats = []
722     if gid_column:
723     insert_formats.append("%(gid)s")
724    
725     fields = []
726     fields_decl = []
727     if gid_column:
728     fields.append(gid_column)
729     fields_decl.append("%s INT" % gid_column)
730 bh 1605 for i in range(dbf.field_count()):
731     ftype, name, width, prec = dbf.field_info(i)
732 bh 2096 fields.append(name)
733     fields_decl.append("%s %s" % (name, typemap[ftype]))
734 bh 1605 insert_formats.append("%%(%s)s" % name)
735     stmt = "CREATE TABLE %s (\n %s\n);" % (tablename,
736 bh 2096 ",\n ".join(fields_decl))
737 bh 1605 cursor.execute(stmt)
738     #print stmt
739    
740     numshapes, shapetype, mins, maxs = shp.info()
741 bh 1656 wkttype = shp_to_wkt[shapetype]
742     if force_wkt_type:
743     wkttype = force_wkt_type
744     convert = wkt_converter[wkttype]
745 bh 1605
746     cursor.execute("select AddGeometryColumn('%(dbname)s',"
747 bh 2057 "'%(tablename)s', 'the_geom', %(srid)d, '%(wkttype)s', 2);"
748 bh 1605 % locals())
749 bh 2096 fields.append("the_geom")
750 bh 2057 insert_formats.append("GeometryFromText(%(the_geom)s, %(srid)d)")
751 bh 1605
752 bh 2096 insert = ("INSERT INTO %s (%s) VALUES (%s)"
753     % (tablename, ", ".join(fields), ", ".join(insert_formats)))
754 bh 1605
755     for i in range(numshapes):
756     data = dbf.read_record(i)
757     data["tablename"] = tablename
758 bh 2096 if gid_column:
759     data["gid"] = i + gid_offset
760 bh 2057 data["srid"] = srid
761 bh 1605 data["the_geom"] = convert(shp.read_object(i).vertices())
762     #print insert % data
763     cursor.execute(insert, data)
764    
765 bh 1634 cursor.execute("GRANT SELECT ON %s TO PUBLIC;" % tablename)
766    
767 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