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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2589 - (show annotations)
Tue Mar 29 18:36:53 2005 UTC (19 years, 11 months ago) by bh
Original Path: trunk/thuban/test/postgissupport.py
File MIME type: text/x-python
File size: 29655 byte(s)
(find_postgis_sql): Added yet another
potential location for (lw)postgis.sql because the file has moved
again in postgis 1.0.0 rc4.

1 # Copyright (C) 2003, 2004, 2005 by Intevation GmbH
2 # Authors:
3 # Bernhard Herzog <[email protected]>
4 #
5 # This program is free software under the GPL (>=v2)
6 # Read the file COPYING coming with the software for details.
7
8 """Support module for tests that use a live PostGIS database"""
9
10 __version__ = "$Revision$"
11 # $Source$
12 # $Id$
13
14 import sys
15 import os
16 import time
17 import popen2
18 import shutil
19 import traceback
20 import re
21
22 import support
23
24 try:
25 import psycopg
26 except ImportError:
27 psycopg = None
28
29 #
30 # Helper code
31 #
32
33 def run_config_script(cmdline):
34 """Run command cmdline and return its stdout or none in case of errors"""
35 pipe = os.popen(cmdline)
36 result = pipe.read()
37 if pipe.close() is not None:
38 raise RuntimeError('Command %r failed' % cmdline)
39 return result
40
41 def run_command(command, outfilename = None):
42 """Run command as a subprocess and send its stdout and stderr to outfile
43
44 The subprocess is run synchroneously so the function returns once
45 the subprocess has termninated. If the process' exit code is not
46 zero raise a RuntimeError.
47
48 If outfilename is None stdout and stderr are still captured but they
49 are ignored and not written to any file.
50 """
51 proc = popen2.Popen4(command)
52 proc.tochild.close()
53 output = proc.fromchild.read()
54 status = proc.wait()
55 if outfilename is not None:
56 outfile = open(outfilename, "w")
57 outfile.write(output)
58 outfile.close()
59 if not os.WIFEXITED(status) or os.WEXITSTATUS(status) != 0:
60 if outfilename:
61 message = "see %s" % outfilename
62 else:
63 message = output
64 raise RuntimeError("command %r exited with code %d.\n%s"
65 % (command, status, message))
66
67
68 def run_boolean_command(command):
69 """
70 Run command as a subprocess silently and return whether it ran successfully
71
72 Silently means that all output is captured and ignored. The exit
73 status is true if the command ran successfull, i.e. it terminated by
74 exiting and returned as zero exit code and false other wise
75 """
76 try:
77 run_command(command, None)
78 return 1
79 except RuntimeError:
80 pass
81 return 0
82
83
84 #
85 # PostgreSQL and database
86 #
87
88 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 # host if the name starts with a slash.
120 self.host = os.path.abspath(socket_dir)
121
122 # 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 # 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 run_command(["initdb", "-D", self.dbdir, "-U", self.admin_name],
146 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 self.alter_user(self.admin_name, self.admin_password)
161 self.create_user(self.user_name, self.user_password)
162
163 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 "-h", self.host, "-U", self.admin_name],
175 os.path.join(self.dbdir, "psql-%d.log" % count))
176 except RuntimeError:
177 pass
178 except:
179 traceback.print_exc()
180 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 """Return whether a postmaster process is running on self.dbdir
189
190 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 """
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 def new_postgis_db(self, dbname, tables = None, reference_systems = None,
207 views = None):
208 """Create and return a new PostGISDatabase object using self as server
209 """
210 db = PostGISDatabase(self, self.postgis_sql, dbname, tables = tables,
211 reference_systems = reference_systems,
212 views = views)
213 db.initdb()
214 self.known_dbs[dbname] = db
215 return db
216
217 def get_static_data_db(self, dbname, tables, reference_systems, views):
218 """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 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 """
233 db = self.known_dbs.get(dbname)
234 if db is not None:
235 if db.has_data(tables, reference_systems, views):
236 return db
237 raise ValueError("PostGISDatabase named %r doesn't have tables %r"
238 % (dbname, tables))
239 return self.new_postgis_db(dbname, tables, reference_systems, views)
240
241 def get_default_static_data_db(self):
242 dbname = "PostGISStaticTests"
243 srids = [(1, "proj=longlat datum=WGS84")]
244 tables = [
245 # Direct copies of the shapefiles. The shapeids are exactly
246 # the same, except where changed with "gid_offset", of
247 # course. Note that the test implementation requires that
248 # all the landmard tables use an gid_offset of 1000.
249 ("landmarks", os.path.join("..", "Data", "iceland",
250 "cultural_landmark-point.shp"),
251 [("gid_offset", 1000)]),
252 ("political", os.path.join("..", "Data", "iceland",
253 "political.shp")),
254 ("roads_multi", os.path.join("..", "Data", "iceland",
255 "roads-line.shp")),
256
257 # 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 # The polygon data as a MULTIPOLYGON geometry type
264 ("political_multi", os.path.join("..", "Data", "iceland",
265 "political.shp"),
266 [("force_wkt_type", "MULTIPOLYGON")]),
267
268 # Copy of landmarks but using an srid != -1
269 ("landmarks_srid", os.path.join("..", "Data", "iceland",
270 "cultural_landmark-point.shp"),
271 [("gid_offset", 1000),
272 ("srid", 1)]),
273
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 ]
282 views = [("v_landmarks", "SELECT * FROM landmarks_point_id")]
283 return self.get_static_data_db(dbname, tables, srids, views)
284
285 def connection_params(self, user):
286 """Return the connection parameters for the given user
287
288 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
297 def connection_string(self, user):
298 """Return (part of) the connection string to pass to psycopg.connect
299
300 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 """Execute the sql statament and return a result for SELECT statements
314
315 The user parameter us used as in connection_params. The dbname
316 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 """
322 conn = psycopg.connect("dbname=%s " % dbname
323 + self.connection_string(user))
324 cursor = conn.cursor()
325 cursor.execute(sql)
326 if sql.lower().startswith("select"):
327 row = cursor.fetchone()
328 else:
329 row = None
330 conn.commit()
331 conn.close()
332 return row
333
334 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 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 # 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 if required:
365 contents = "local all %s password\n" % user
366 else:
367 contents = "local all %s trust\n" % user
368 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 class PostGISDatabase:
387
388 """A PostGIS database in a PostgreSQLServer"""
389
390 def __init__(self, server, postgis_sql, dbname, tables = None,
391 reference_systems = (), views = None):
392 """Initialize the PostGISDatabase
393
394 Parameters:
395
396 server -- The PostgreSQLServer instance containing the
397 database
398
399 postgis_sql -- Filename of the sql file with the postgis
400 initialization code
401
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
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
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 """
425 self.server = server
426 self.postgis_sql = postgis_sql
427 self.dbname = dbname
428 self.tables = tables
429 self.views = views
430 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
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 "-h", self.server.host, "-U", self.server.admin_name,
442 self.dbname],
443 os.path.join(self.server.dbdir, "createdb.log"))
444 run_command(["createlang", "-p", str(self.server.port),
445 "-h", self.server.host, "-U", self.server.admin_name,
446 "plpgsql", self.dbname],
447 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 "-p", str(self.server.port), "-h", self.server.host,
456 "-U", self.server.admin_name],
457 os.path.join(self.server.dbdir, "psql.log"))
458
459 self.server.execute_sql(self.dbname, "admin",
460 "GRANT SELECT ON geometry_columns TO PUBLIC;")
461 self.server.execute_sql(self.dbname, "admin",
462 "GRANT SELECT ON spatial_ref_sys TO PUBLIC;")
463
464 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 if self.tables is not None:
470 def unpack(item):
471 extra = {"force_wkt_type": None, "gid_offset": 0,
472 "srid": -1}
473 if len(info) == 2:
474 tablename, shapefile = info
475 else:
476 tablename, shapefile, kw = info
477 for key, val in kw:
478 extra[key] = val
479 return tablename, shapefile, extra
480
481 for info in self.tables:
482 tablename, shapefile, kw = unpack(info)
483 upload_shapefile(shapefile, self, tablename, **kw)
484
485 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 return (self.tables == tables
496 and self.reference_systems == reference_systems
497 and self.views == views)
498
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 PostgreSQL's $datadir (i.e. the directory where PostgreSQL keeps
505 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
510 Furthermore, different versions of postgis place the file in
511 slightly different locations or may even use different names. For
512 instance:
513
514 postgis 0.7.5 $datadir/contrib/postgis.sql
515 postgis 0.8.1 $datadir/postgis.sql
516 postgis 1.0.0-rc1 $datadir/lwpostgis.sql
517 postgis 1.0.0-rc4 $datadir/contrib/lwpostgis.sql
518
519 To support both versions, we look in both places and return the
520 first one found (looking under contrib first). If the file is not
521 found the return value is None.
522 """
523 bindir = run_config_script("pg_config --bindir").strip()
524 datadir = os.path.join(bindir, "..", "share", "postgresql")
525 for filename in [os.path.join(datadir, "contrib", "postgis.sql"),
526 os.path.join(datadir, "postgis.sql"),
527 os.path.join(datadir, "lwpostgis.sql"),
528 os.path.join(datadir, "contrib", "lwpostgis.sql")]:
529 if os.path.exists(filename):
530 return filename
531
532
533 _postgres_server = None
534 def get_test_server():
535 """Return the test database server object.
536
537 If it doesn't exist yet, create it first.
538
539 The server will use the directory postgis under the temp dir (as
540 defined by support.create_temp_dir()) for the database cluster.
541 Sockets will be created in tempdir.
542 """
543 global _postgres_server
544 if _postgres_server is None:
545 tempdir = support.create_temp_dir()
546 dbdir = os.path.join(tempdir, "postgis")
547 socket_dir = tempdir
548
549 _postgres_server = PostgreSQLServer(dbdir, 6543, find_postgis_sql(),
550 socket_dir = socket_dir)
551 _postgres_server.createdb()
552
553 return _postgres_server
554
555 def shutdown_test_server():
556 """Shutdown the test server if it is running"""
557 global _postgres_server
558 if _postgres_server is not None:
559 _postgres_server.shutdown()
560 _postgres_server = None
561
562
563 def reason_for_not_running_tests():
564 """
565 Determine whether postgis tests can be run and return a reason they can't
566
567 There's no fool-proof way to reliably determine this short of
568 actually running the tests but we try the following here:
569
570 - test whether pg_ctl --help can be run successfully
571 - test whether the postgis_sql can be opened
572 The name of the postgis_sql file is determined by find_postgis_sql()
573 - psycopg can be imported successfully.
574 """
575 # run_command currently uses Popen4 which is not available under
576 # Windows, for example.
577 if not hasattr(popen2, "Popen4"):
578 return "Can't run PostGIS test because popen2.Popen4 does not exist"
579
580 try:
581 run_command(["pg_ctl", "--help"], None)
582 except RuntimeError:
583 return "Can't run PostGIS tests because pg_ctl fails"
584
585 try:
586 postgis_sql = find_postgis_sql()
587 except:
588 return "Can't run PostGIS tests because postgis.sql can't be found"
589
590 try:
591 f = open(postgis_sql)
592 f.close()
593 except:
594 return "Can't run PostGIS tests because postgis.sql can't be opened"
595
596 # The test for psycopg was already done when this module was
597 # imported so we only have to check whether it was successful
598 if psycopg is None:
599 return "Can't run PostGIS tests because psycopg can't be imported"
600
601 return ""
602
603
604 _cannot_run_postgis_tests = None
605 def skip_if_no_postgis():
606 global _cannot_run_postgis_tests
607 if _cannot_run_postgis_tests is None:
608 _cannot_run_postgis_tests = reason_for_not_running_tests()
609 if _cannot_run_postgis_tests:
610 raise support.SkipTest(_cannot_run_postgis_tests)
611
612 def skip_if_addgeometrycolumn_does_not_use_quote_ident():
613 """Skip a test if the AddGeometryColumn function doesn't use quote_ident
614
615 If the AddGeometryColumn function doesn't use quote_ident it doesn't
616 support unusual table or column names properly, that is, it will
617 fail with errors for names that contain spaces or double quotes.
618
619 The test performed by this function is a bit simplistic because it
620 only tests whether the string 'quote_ident' occurs anywhere in the
621 postgis.sql file. This will hopefully work because when this was
622 fixed in postgis CVS AddGeometryColumn was the first function to use
623 quote_ident.
624 """
625 f = file(find_postgis_sql())
626 content = f.read()
627 f.close()
628 if content.find("quote_ident") < 0:
629 raise support.SkipTest("AddGeometryColumn doesn't use quote_ident")
630
631 def coords_to_point(coords):
632 """Return string with a WKT representation of the point in coords"""
633 x, y = coords[0]
634 return "POINT(%r %r)" % (x, y)
635
636 def coords_to_polygon(coords):
637 """Return string with a WKT representation of the polygon in coords"""
638 poly = []
639 for ring in coords:
640 poly.append(", ".join(["%r %r" % p for p in ring]))
641 return "POLYGON((%s))" % "), (".join(poly)
642
643 def coords_to_linestring(coords):
644 """Return string with a LINESTRING WKT representation of coords"""
645 if len(coords) > 1:
646 raise ValueError("A LINESTRING can only have one arc")
647 return "LINESTRING(%s)" % ", ".join(["%r %r" % p for p in coords[0]])
648
649 def coords_to_multilinestring(coords):
650 """Return string with a MULTILINESTRING WKT representation of coords"""
651 poly = []
652 for ring in coords:
653 poly.append(", ".join(["%r %r" % p for p in ring]))
654 return "MULTILINESTRING((%s))" % "), (".join(poly)
655
656 def coords_to_multipolygon(coords):
657 """Return string with a WKT representation of the polygon in coords"""
658 poly = []
659 for ring in coords:
660 poly.append(", ".join(["%r %r" % p for p in ring]))
661 return "MULTIPOLYGON(((%s)))" % ")), ((".join(poly)
662
663 wkt_converter = {
664 "POINT": coords_to_point,
665 "LINESTRING": coords_to_linestring,
666 "MULTILINESTRING": coords_to_multilinestring,
667 "POLYGON": coords_to_polygon,
668 "MULTIPOLYGON": coords_to_multipolygon,
669 }
670
671 def upload_shapefile(filename, db, tablename, force_wkt_type = None,
672 gid_offset = 0, gid_column = "gid", srid = -1):
673 """Upload a shapefile into a new database table
674
675 Parameters:
676
677 filename -- The name of the shapefile
678
679 db -- The PostGISDatabase instance representing the database
680
681 tablename -- The name of the table to create and into which the data
682 is to be inserted
683
684 force_wkt_type -- If given and not None, this is used as the WKT
685 geometry type to use instead of the default that would
686 be chosen based on the type of the shapefile
687
688 gid_offset -- A number to add to the shapeid to get the value for
689 the gid column (default 0)
690
691 gid_column -- The name of the column with the shape ids. Default
692 'gid'. If None, no gid column will be created. The
693 name is directly used in SQL statements, so if it
694 contains unusualy characters the caller should provide
695 a suitable quoted string.
696
697 srid -- The srid of the spatial references system used by the table
698 and the data
699 """
700 import dbflib, shapelib
701
702 # We build this map here because we need shapelib which can only be
703 # imported after support.initthuban has been called which we can't
704 # easily do in this module because it's imported by support.
705 shp_to_wkt = {
706 shapelib.SHPT_POINT: "POINT",
707 shapelib.SHPT_ARC: "MULTILINESTRING",
708 shapelib.SHPT_POLYGON: "POLYGON",
709 }
710
711 server = db.server
712 dbname = db.dbname
713 conn = psycopg.connect("dbname=%s " % dbname
714 + db.server.connection_string("admin"))
715 cursor = conn.cursor()
716
717 shp = shapelib.ShapeFile(filename)
718 dbf = dbflib.DBFFile(filename)
719 typemap = {dbflib.FTString: "VARCHAR",
720 dbflib.FTInteger: "INTEGER",
721 dbflib.FTDouble: "DOUBLE PRECISION"}
722
723 insert_formats = []
724 if gid_column:
725 insert_formats.append("%(gid)s")
726
727 fields = []
728 fields_decl = []
729 if gid_column:
730 fields.append(gid_column)
731 fields_decl.append("%s INT" % gid_column)
732 for i in range(dbf.field_count()):
733 ftype, name, width, prec = dbf.field_info(i)
734 fields.append(name)
735 fields_decl.append("%s %s" % (name, typemap[ftype]))
736 insert_formats.append("%%(%s)s" % name)
737 stmt = "CREATE TABLE %s (\n %s\n);" % (tablename,
738 ",\n ".join(fields_decl))
739 cursor.execute(stmt)
740 #print stmt
741
742 numshapes, shapetype, mins, maxs = shp.info()
743 wkttype = shp_to_wkt[shapetype]
744 if force_wkt_type:
745 wkttype = force_wkt_type
746 convert = wkt_converter[wkttype]
747
748 cursor.execute("select AddGeometryColumn('%(dbname)s',"
749 "'%(tablename)s', 'the_geom', %(srid)d, '%(wkttype)s', 2);"
750 % locals())
751 fields.append("the_geom")
752 insert_formats.append("GeometryFromText(%(the_geom)s, %(srid)d)")
753
754 insert = ("INSERT INTO %s (%s) VALUES (%s)"
755 % (tablename, ", ".join(fields), ", ".join(insert_formats)))
756
757 for i in range(numshapes):
758 data = dbf.read_record(i)
759 data["tablename"] = tablename
760 if gid_column:
761 data["gid"] = i + gid_offset
762 data["srid"] = srid
763 data["the_geom"] = convert(shp.read_object(i).vertices())
764 #print insert % data
765 cursor.execute(insert, data)
766
767 cursor.execute("GRANT SELECT ON %s TO PUBLIC;" % tablename)
768
769 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