/[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 2472 - (show annotations)
Thu Dec 16 15:18:57 2004 UTC (20 years, 2 months ago) by bh
Original Path: trunk/thuban/test/postgissupport.py
File MIME type: text/x-python
File size: 29380 byte(s)
Add support for PostGIS tables with LINESTRING geomentries.
Fixes RT#2299

* Thuban/Model/postgisdb.py (shapetype_map): Add LINESTRING

* test/postgissupport.py
(PostgreSQLServer.get_default_static_data_db): Rename the "roads"
table to "roads-multi" because it now uses MULTILINESTRING
geometries and introduce a new "roads" table that uses LINESTRING
(coords_to_multilinestring): Make the doc string more precise
(coords_to_linestring): New.  Create a LINESTRING WKT
representatin
(wkt_converter): Add coords_to_linestring
(upload_shapefile): Rephrase the doc-string a bit.

* test/test_postgis_db.py (TestPostGISShapestoreArc)
(LineStringTests)
(TestPostGISShapestoreLineString)
(TestPostGISShapestoreMultiLineString): Split
TestPostGISShapestoreArc into a base class LineStringTests and two
derived classes TestPostGISShapestoreLineString for LINESTRING
geometries and TestPostGISShapestoreMultiLineString for
MULTILINESTRING geometries.  Most test methods are in the base
class with the exception of tests that explicitly check the raw
format.

1 # Copyright (C) 2003, 2004 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 postgis.sql file with the
400 postgis 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. For instance:
512
513 postgis 0.7.5 $datadir/contrib/postgis.sql
514 postgis 0.8.1 $datadir/postgis.sql
515
516 To support both versions, we look in both places and return the
517 first one found (looking under contrib first). If the file is not
518 found the return value is None.
519 """
520 bindir = run_config_script("pg_config --bindir").strip()
521 datadir = os.path.join(bindir, "..", "share", "postgresql")
522 for filename in [os.path.join(datadir, "contrib", "postgis.sql"),
523 os.path.join(datadir, "postgis.sql")]:
524 if os.path.exists(filename):
525 return filename
526
527
528 _postgres_server = None
529 def get_test_server():
530 """Return the test database server object.
531
532 If it doesn't exist yet, create it first.
533
534 The server will use the directory postgis under the temp dir (as
535 defined by support.create_temp_dir()) for the database cluster.
536 Sockets will be created in tempdir.
537 """
538 global _postgres_server
539 if _postgres_server is None:
540 tempdir = support.create_temp_dir()
541 dbdir = os.path.join(tempdir, "postgis")
542 socket_dir = tempdir
543
544 _postgres_server = PostgreSQLServer(dbdir, 6543, find_postgis_sql(),
545 socket_dir = socket_dir)
546 _postgres_server.createdb()
547
548 return _postgres_server
549
550 def shutdown_test_server():
551 """Shutdown the test server if it is running"""
552 global _postgres_server
553 if _postgres_server is not None:
554 _postgres_server.shutdown()
555 _postgres_server = None
556
557
558 def reason_for_not_running_tests():
559 """
560 Determine whether postgis tests can be run and return a reason they can't
561
562 There's no fool-proof way to reliably determine this short of
563 actually running the tests but we try the following here:
564
565 - test whether pg_ctl --help can be run successfully
566 - test whether the postgis_sql can be opened
567 The name of the postgis_sql file is determined by find_postgis_sql()
568 - psycopg can be imported successfully.
569 """
570 # run_command currently uses Popen4 which is not available under
571 # Windows, for example.
572 if not hasattr(popen2, "Popen4"):
573 return "Can't run PostGIS test because popen2.Popen4 does not exist"
574
575 try:
576 run_command(["pg_ctl", "--help"], None)
577 except RuntimeError:
578 return "Can't run PostGIS tests because pg_ctl fails"
579
580 try:
581 postgis_sql = find_postgis_sql()
582 except:
583 return "Can't run PostGIS tests because postgis.sql can't be found"
584
585 try:
586 f = open(postgis_sql)
587 f.close()
588 except:
589 return "Can't run PostGIS tests because postgis.sql can't be opened"
590
591 # The test for psycopg was already done when this module was
592 # imported so we only have to check whether it was successful
593 if psycopg is None:
594 return "Can't run PostGIS tests because psycopg can't be imported"
595
596 return ""
597
598
599 _cannot_run_postgis_tests = None
600 def skip_if_no_postgis():
601 global _cannot_run_postgis_tests
602 if _cannot_run_postgis_tests is None:
603 _cannot_run_postgis_tests = reason_for_not_running_tests()
604 if _cannot_run_postgis_tests:
605 raise support.SkipTest(_cannot_run_postgis_tests)
606
607 def skip_if_addgeometrycolumn_does_not_use_quote_ident():
608 """Skip a test if the AddGeometryColumn function doesn't use quote_ident
609
610 If the AddGeometryColumn function doesn't use quote_ident it doesn't
611 support unusual table or column names properly, that is, it will
612 fail with errors for names that contain spaces or double quotes.
613
614 The test performed by this function is a bit simplistic because it
615 only tests whether the string 'quote_ident' occurs anywhere in the
616 postgis.sql file. This will hopefully work because when this was
617 fixed in postgis CVS AddGeometryColumn was the first function to use
618 quote_ident.
619 """
620 f = file(find_postgis_sql())
621 content = f.read()
622 f.close()
623 if content.find("quote_ident") < 0:
624 raise support.SkipTest("AddGeometryColumn doesn't use quote_ident")
625
626 def coords_to_point(coords):
627 """Return string with a WKT representation of the point in coords"""
628 x, y = coords[0]
629 return "POINT(%r %r)" % (x, y)
630
631 def coords_to_polygon(coords):
632 """Return string with a WKT representation of the polygon in coords"""
633 poly = []
634 for ring in coords:
635 poly.append(", ".join(["%r %r" % p for p in ring]))
636 return "POLYGON((%s))" % "), (".join(poly)
637
638 def coords_to_linestring(coords):
639 """Return string with a LINESTRING WKT representation of coords"""
640 if len(coords) > 1:
641 raise ValueError("A LINESTRING can only have one arc")
642 return "LINESTRING(%s)" % ", ".join(["%r %r" % p for p in coords[0]])
643
644 def coords_to_multilinestring(coords):
645 """Return string with a MULTILINESTRING WKT representation of coords"""
646 poly = []
647 for ring in coords:
648 poly.append(", ".join(["%r %r" % p for p in ring]))
649 return "MULTILINESTRING((%s))" % "), (".join(poly)
650
651 def coords_to_multipolygon(coords):
652 """Return string with a WKT representation of the polygon in coords"""
653 poly = []
654 for ring in coords:
655 poly.append(", ".join(["%r %r" % p for p in ring]))
656 return "MULTIPOLYGON(((%s)))" % ")), ((".join(poly)
657
658 wkt_converter = {
659 "POINT": coords_to_point,
660 "LINESTRING": coords_to_linestring,
661 "MULTILINESTRING": coords_to_multilinestring,
662 "POLYGON": coords_to_polygon,
663 "MULTIPOLYGON": coords_to_multipolygon,
664 }
665
666 def upload_shapefile(filename, db, tablename, force_wkt_type = None,
667 gid_offset = 0, gid_column = "gid", srid = -1):
668 """Upload a shapefile into a new database table
669
670 Parameters:
671
672 filename -- The name of the shapefile
673
674 db -- The PostGISDatabase instance representing the database
675
676 tablename -- The name of the table to create and into which the data
677 is to be inserted
678
679 force_wkt_type -- If given and not None, this is used as the WKT
680 geometry type to use instead of the default that would
681 be chosen based on the type of the shapefile
682
683 gid_offset -- A number to add to the shapeid to get the value for
684 the gid column (default 0)
685
686 gid_column -- The name of the column with the shape ids. Default
687 'gid'. If None, no gid column will be created. The
688 name is directly used in SQL statements, so if it
689 contains unusualy characters the caller should provide
690 a suitable quoted string.
691
692 srid -- The srid of the spatial references system used by the table
693 and the data
694 """
695 import dbflib, shapelib
696
697 # We build this map here because we need shapelib which can only be
698 # imported after support.initthuban has been called which we can't
699 # easily do in this module because it's imported by support.
700 shp_to_wkt = {
701 shapelib.SHPT_POINT: "POINT",
702 shapelib.SHPT_ARC: "MULTILINESTRING",
703 shapelib.SHPT_POLYGON: "POLYGON",
704 }
705
706 server = db.server
707 dbname = db.dbname
708 conn = psycopg.connect("dbname=%s " % dbname
709 + db.server.connection_string("admin"))
710 cursor = conn.cursor()
711
712 shp = shapelib.ShapeFile(filename)
713 dbf = dbflib.DBFFile(filename)
714 typemap = {dbflib.FTString: "VARCHAR",
715 dbflib.FTInteger: "INTEGER",
716 dbflib.FTDouble: "DOUBLE PRECISION"}
717
718 insert_formats = []
719 if gid_column:
720 insert_formats.append("%(gid)s")
721
722 fields = []
723 fields_decl = []
724 if gid_column:
725 fields.append(gid_column)
726 fields_decl.append("%s INT" % gid_column)
727 for i in range(dbf.field_count()):
728 ftype, name, width, prec = dbf.field_info(i)
729 fields.append(name)
730 fields_decl.append("%s %s" % (name, typemap[ftype]))
731 insert_formats.append("%%(%s)s" % name)
732 stmt = "CREATE TABLE %s (\n %s\n);" % (tablename,
733 ",\n ".join(fields_decl))
734 cursor.execute(stmt)
735 #print stmt
736
737 numshapes, shapetype, mins, maxs = shp.info()
738 wkttype = shp_to_wkt[shapetype]
739 if force_wkt_type:
740 wkttype = force_wkt_type
741 convert = wkt_converter[wkttype]
742
743 cursor.execute("select AddGeometryColumn('%(dbname)s',"
744 "'%(tablename)s', 'the_geom', %(srid)d, '%(wkttype)s', 2);"
745 % locals())
746 fields.append("the_geom")
747 insert_formats.append("GeometryFromText(%(the_geom)s, %(srid)d)")
748
749 insert = ("INSERT INTO %s (%s) VALUES (%s)"
750 % (tablename, ", ".join(fields), ", ".join(insert_formats)))
751
752 for i in range(numshapes):
753 data = dbf.read_record(i)
754 data["tablename"] = tablename
755 if gid_column:
756 data["gid"] = i + gid_offset
757 data["srid"] = srid
758 data["the_geom"] = convert(shp.read_object(i).vertices())
759 #print insert % data
760 cursor.execute(insert, data)
761
762 cursor.execute("GRANT SELECT ON %s TO PUBLIC;" % tablename)
763
764 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