/[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 2543 - (show 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 # 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
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 """
522 bindir = run_config_script("pg_config --bindir").strip()
523 datadir = os.path.join(bindir, "..", "share", "postgresql")
524 for filename in [os.path.join(datadir, "contrib", "postgis.sql"),
525 os.path.join(datadir, "postgis.sql"),
526 os.path.join(datadir, "lwpostgis.sql")]:
527 if os.path.exists(filename):
528 return filename
529
530
531 _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 # 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 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 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 postgis.sql file. This will hopefully work because when this was
620 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 def coords_to_point(coords):
630 """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 def coords_to_polygon(coords):
635 """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 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 def coords_to_multilinestring(coords):
648 """Return string with a MULTILINESTRING WKT representation of coords"""
649 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 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 "LINESTRING": coords_to_linestring,
664 "MULTILINESTRING": coords_to_multilinestring,
665 "POLYGON": coords_to_polygon,
666 "MULTIPOLYGON": coords_to_multipolygon,
667 }
668
669 def upload_shapefile(filename, db, tablename, force_wkt_type = None,
670 gid_offset = 0, gid_column = "gid", srid = -1):
671 """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 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
686 gid_offset -- A number to add to the shapeid to get the value for
687 the gid column (default 0)
688
689 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 srid -- The srid of the spatial references system used by the table
696 and the data
697 """
698 import dbflib, shapelib
699
700 # 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 server = db.server
710 dbname = db.dbname
711 conn = psycopg.connect("dbname=%s " % dbname
712 + db.server.connection_string("admin"))
713 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 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 for i in range(dbf.field_count()):
731 ftype, name, width, prec = dbf.field_info(i)
732 fields.append(name)
733 fields_decl.append("%s %s" % (name, typemap[ftype]))
734 insert_formats.append("%%(%s)s" % name)
735 stmt = "CREATE TABLE %s (\n %s\n);" % (tablename,
736 ",\n ".join(fields_decl))
737 cursor.execute(stmt)
738 #print stmt
739
740 numshapes, shapetype, mins, maxs = shp.info()
741 wkttype = shp_to_wkt[shapetype]
742 if force_wkt_type:
743 wkttype = force_wkt_type
744 convert = wkt_converter[wkttype]
745
746 cursor.execute("select AddGeometryColumn('%(dbname)s',"
747 "'%(tablename)s', 'the_geom', %(srid)d, '%(wkttype)s', 2);"
748 % locals())
749 fields.append("the_geom")
750 insert_formats.append("GeometryFromText(%(the_geom)s, %(srid)d)")
751
752 insert = ("INSERT INTO %s (%s) VALUES (%s)"
753 % (tablename, ", ".join(fields), ", ".join(insert_formats)))
754
755 for i in range(numshapes):
756 data = dbf.read_record(i)
757 data["tablename"] = tablename
758 if gid_column:
759 data["gid"] = i + gid_offset
760 data["srid"] = srid
761 data["the_geom"] = convert(shp.read_object(i).vertices())
762 #print insert % data
763 cursor.execute(insert, data)
764
765 cursor.execute("GRANT SELECT ON %s TO PUBLIC;" % tablename)
766
767 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