/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/Model/postgisdb.py
ViewVC logotype

Annotation of /branches/WIP-pyshapelib-bramz/Thuban/Model/postgisdb.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1656 - (hide annotations)
Mon Aug 25 18:26:54 2003 UTC (21 years, 6 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/postgisdb.py
File MIME type: text/x-python
File size: 12313 byte(s)
* Thuban/Model/postgisdb.py (shapetype_map): Add MUTLIPOLYGON.

* test/test_postgis_db.py (PolygonTests): New class containing
those tests from TestPostGISShapestorePolygon that can also be
used to test MUTLIPOLYGON tables
(TestPostGISShapestorePolygon): Most tests are now in PolygonTests
so derive from that
(TestPostGISShapestoreMultiPolygon): New class with tests for
MUTLIPOLYGON tables

* test/postgissupport.py (PostGISDatabase.initdb): Allow the
tables argument to have tuples with three items to override the
WKT type used.
(PostgreSQLServer.get_default_static_data_db): Use the above to
create a polygon table with MUTLIPOLYGONs
(point_to_wkt, coords_to_point, polygon_to_wkt, coords_to_polygon)
(arc_to_wkt, coords_to_multilinestring): Rename from *_to_wkt to
coords_to*
(coords_to_multipolygon): New. Convert to MUTLIPOLYGON
(wkt_converter): New. Map WKT types to converters
(upload_shapefile): New parameter force_wkt_type to use a
different WKT type than the default

1 bh 1605 # Copyright (C) 2003 by Intevation GmbH
2     # Authors:
3     # Martin Mueller <[email protected]>
4     # Bernhard Herzog <[email protected]>
5     #
6     # This program is free software under the GPL (>=v2)
7     # Read the file COPYING coming with the software for details.
8    
9     """Basic interface to a PostGIS database"""
10    
11     from __future__ import generators
12    
13     try:
14     import psycopg
15     except ImportError:
16     psycopg = None
17    
18     import table
19     import wellknowntext
20    
21     from data import SHAPETYPE_POLYGON, SHAPETYPE_ARC, SHAPETYPE_POINT, RAW_WKT
22    
23 bh 1625 def has_postgis_support():
24     """Return whether this Thuban instance supports PostGIS connections
25 bh 1605
26 bh 1625 Having PostGIS support means that the psycopg module can be
27     imported.
28     """
29     return psycopg is not None
30    
31 bh 1631 def psycopg_version():
32     return psycopg.__version__
33    
34 bh 1605 if psycopg is not None:
35     type_map = [(psycopg.STRING, table.FIELDTYPE_STRING),
36     (psycopg.INTEGER, table.FIELDTYPE_INT),
37     (psycopg.FLOAT, table.FIELDTYPE_DOUBLE)]
38    
39 bh 1636
40     class ConnectionError(Exception):
41    
42     """Class for exceptions occurring when establishing a Databse connection"""
43    
44    
45 bh 1605 class PostGISConnection:
46    
47     """Represent a PostGIS database
48    
49     A PostGISConnection instance has the following public attributes:
50    
51     dbname -- The name of the database
52     host, port -- Host and port to connect to
53     user -- The user name to connect as.
54    
55     All of these attributes are strings and may be empty strings to
56     indicate default values.
57     """
58    
59     def __init__(self, dbname, host="", user="", password="", dbtype="",
60     port=""):
61     self.dbname = dbname
62     self.host = host
63     self.port = port
64     self.user = user
65     self.password = password
66     self.dbtype = dbtype
67 bh 1620 self.connect()
68    
69     def connect(self):
70     """Internal: Establish the database connection"""
71 bh 1605 params = []
72     for name in ("host", "port", "dbname", "user", "password"):
73     val = getattr(self, name)
74     if val:
75     params.append("%s=%s" % (name, val))
76 bh 1636 try:
77     self.connection = psycopg.connect(" ".join(params))
78     except psycopg.OperationalError, val:
79     raise ConnectionError(str(val))
80 bh 1605
81     # determine the OID for the geometry type. This is PostGIS
82     # specific.
83     cursor = self.connection.cursor()
84     cursor.execute("SELECT OID, typname FROM pg_type WHERE"
85     +" typname = 'geometry'")
86     row = cursor.fetchone()
87     self.connection.commit()
88     if row is not None:
89     self.geometry_type = row[0]
90     else:
91     raise ValueError("Can't determine postgres type of geometries")
92    
93 bh 1620 def BriefDescription(self):
94     """Return a brief, one-line description of the connection
95    
96     The return value is suitable for a list box of all database
97     connections.
98     """
99     return ("postgis://%(user)s@%(host)s:%(port)s/%(dbname)s"
100     % self.__dict__)
101    
102 bh 1605 def Close(self):
103     """Close the database connection"""
104     self.connection.close()
105    
106     def GeometryTables(self):
107     """Return a list with the names of all tables with a geometry column"""
108     cursor = self.connection.cursor()
109     cursor.execute("SELECT f_table_name FROM geometry_columns;")
110     result = [row[0] for row in cursor.fetchall()]
111     self.connection.commit()
112     return result
113    
114     def cursor(self):
115     """Return a DB API 2.0 cursor for the database"""
116     return self.connection.cursor()
117    
118    
119    
120     class PostGISColumn:
121    
122     """Column description for a PostGISTable"""
123    
124     def __init__(self, name, type, index):
125     self.name = name
126     self.type = type
127     self.index = index
128    
129    
130     class PostGISTable:
131    
132     """A Table in a PostGIS database"""
133    
134     def __init__(self, db, tablename):
135     """Initialize the PostGISTable.
136    
137     The db parameter should be an instance of PostGISConnection and
138     tablename the name of a table in the database represented by db.
139     """
140     self.db = db
141     self.tablename = tablename
142     self.column_map = {}
143     self._fetch_table_information()
144    
145     def _fetch_table_information(self):
146     """Internal: Update information about the table"""
147     self.columns = []
148     cursor = self.db.cursor()
149     cursor.execute("SELECT * FROM %s WHERE 0=1" % self.tablename)
150     description = cursor.description
151    
152     for i in range(len(description)):
153     for pgtyp, tabletyp in type_map:
154     if pgtyp == description[i][1]:
155     col = PostGISColumn(description[i][0], tabletyp, i)
156     break
157     else:
158     if description[i][1] == self.db.geometry_type:
159     self.geometry_column = description[i][0]
160     # No matching table type. Ignore the column.
161     # FIXME: We should at least print a warning about
162     # ignored columns
163     continue
164     self.columns.append(col)
165    
166     for col in self.columns:
167     self.column_map[col.name] = col
168     self.column_map[col.index] = col
169    
170     # Build query string for ReadRowAsDict
171     self.query_stmt = ("SELECT %s from %s"
172     % (", ".join([col.name for col in self.columns]),
173     self.tablename))
174    
175 bh 1638 def DBConnection(self):
176     """Return the dbconnection used by the table"""
177     return self.db
178    
179     def TableName(self):
180     """Return the name of the table in the database"""
181     return self.tablename
182    
183 bh 1605 def Dependencies(self):
184     """Return an empty tuple because a PostGISTable depends on nothing else
185     """
186     return ()
187    
188     def NumColumns(self):
189     return len(self.columns)
190    
191     def Columns(self):
192     return self.columns
193    
194     def Column(self, col):
195     return self.column_map[col]
196    
197     def HasColumn(self, col):
198     return self.column_map.has_key(col)
199    
200     def NumRows(self):
201     cursor = self.db.cursor()
202     cursor.execute("SELECT count(*) FROM %s" % self.tablename)
203     return cursor.fetchone()[0]
204    
205     def ReadRowAsDict(self, row):
206     cursor = self.db.cursor()
207     cursor.execute(self.query_stmt + " LIMIT 1 OFFSET %d" % row)
208     result = {}
209     for col, value in zip(self.columns, cursor.fetchone()):
210     result[col.name] = value
211     return result
212    
213     def ReadValue(self, row, col):
214     cursor = self.db.cursor()
215     cursor.execute("SELECT %s FROM %s LIMIT 1 OFFSET %d" %
216     (self.column_map[col].name, self.tablename, row))
217     return cursor.fetchone()[0]
218    
219     def ValueRange(self, col):
220     cursor = self.db.cursor()
221     name = self.column_map[col].name
222     cursor.execute("SELECT min(%s), max(%s) FROM %s" %
223     (name, name, self.tablename))
224     return tuple(cursor.fetchone())
225    
226     def UniqueValues(self, col):
227     cursor = self.db.cursor()
228     name = self.column_map[col].name
229     cursor.execute("SELECT %s FROM %s GROUP BY %s" %
230     (name, self.tablename, name))
231     return [row[0] for row in cursor.fetchall()]
232    
233     def SimpleQuery(self, left, comparison, right):
234     if comparison not in ("==", "!=", "<", "<=", ">=", ">"):
235     raise ValueError("Comparison operator %r not allowed" % comparison)
236    
237     if comparison == "==":
238     comparison = "="
239    
240     if isinstance(right, PostGISColumn):
241     right_template = right.name
242     params = ()
243     else:
244     right_template = "%s"
245     params = (right,)
246    
247     query = "SELECT gid FROM %s WHERE %s %s %s ORDER BY gid;" \
248     % (self.tablename, left.name, comparison, right_template)
249    
250     cursor = self.db.cursor()
251     cursor.execute(query, params)
252     result = []
253     while 1:
254     row = cursor.fetchone()
255     if row is None:
256     break
257     result.append(row[0])
258     return result
259    
260    
261     class PostGISShape:
262    
263     def __init__(self, shapeid, data):
264     self.shapeid = shapeid
265     self.data = data
266    
267     def compute_bbox(self):
268     """
269     Return the bounding box of the shape as a tuple (minx,miny,maxx,maxy)
270     """
271     xs = []
272     ys = []
273     for part in self.Points():
274     for x, y in part:
275     xs.append(x)
276     ys.append(y)
277     return (min(xs), min(ys), max(xs), max(ys))
278    
279     def ShapeID(self):
280     return self.shapeid
281    
282     def Points(self):
283     return wellknowntext.parse_wkt_thuban(self.data)
284    
285     def RawData(self):
286     return self.data
287    
288    
289     shapetype_map = {"POLYGON": SHAPETYPE_POLYGON,
290 bh 1656 "MULTIPOLYGON": SHAPETYPE_POLYGON,
291 bh 1605 "MULTILINESTRING": SHAPETYPE_ARC,
292     "POINT": SHAPETYPE_POINT}
293    
294    
295     class PostGISShapeStore(PostGISTable):
296    
297     """Shapestore interface to a table in a PostGIS database"""
298    
299     def Table(self):
300     """Return self since a PostGISShapeStore is its own table."""
301     return self
302    
303     def OrigShapeStore(self):
304     """Return None since the PostGISShapeStore is not derived from another
305     """
306     return None
307    
308     def ShapeType(self):
309     """Return the type of the shapes in the shapestore."""
310     cursor = self.db.cursor()
311     cursor.execute("SELECT type FROM geometry_columns WHERE"
312     " f_table_name=%s", (self.tablename,))
313     result = cursor.fetchone()[0]
314     cursor.close()
315     return shapetype_map[result]
316    
317     def RawShapeFormat(self):
318     """Return the raw data format of the shape data.
319    
320     For the PostGISShapeStore this is RAW_WKT.
321     """
322     return RAW_WKT
323    
324     def NumShapes(self):
325     # The number of shapes is the same as the number of rows,
326     # assuming that the geometry can't be NULL.
327     return self.NumRows()
328    
329     def BoundingBox(self):
330     """Return the bounding box of all shapes in the postgis table"""
331     minx = miny = maxx = maxy = None
332     x=[]
333     y=[]
334     cursor = self.db.cursor()
335     try:
336     stmt = ("SELECT AsText(Envelope(%s)) FROM %s;"
337     % (self.geometry_column, self.tablename))
338     cursor.execute(stmt)
339     result = cursor.fetchone()
340     while result:
341     result = result[0]
342     # Here we must do some parsing through the result string
343     # to get the points out of the polygon which representes
344     # the bounding box The first and the last point of a
345     # polygon are identical
346     result = result.split("(")[2]
347     result = result.split(")")[0]
348     points = result.split(",")
349     del points[4] # Remove the last point
350     for point in points:
351     px, py = point.split()
352     x.append(float(px))
353     y.append(float(py))
354     result = cursor.fetchone()
355     finally:
356     cursor.close()
357     if not x:
358     # Empty table
359     return None
360     return (min(x), min(y), max(x), max(y))
361    
362     def Shape(self, shapeid):
363     cursor = self.db.cursor()
364     cursor.execute("SELECT AsText(%s) FROM %s WHERE gid=%d"
365     % (self.geometry_column, self.tablename, shapeid))
366     wkt = cursor.fetchone()[0]
367     cursor.close()
368     return PostGISShape(shapeid, wkt)
369    
370     def ShapesInRegion(self, bbox):
371     """Generate all shapes overlapping the region given by bbox."""
372     # IMPORTANT:This will work for PostGIS < 0.8
373     left, bottom, right, top = bbox
374     geom = (("POLYGON((" + ", ".join(["%f %f"] * 5) + "))")
375     % (left, bottom, left, top, right, top, right, bottom,
376     left, bottom))
377     cursor = self.db.cursor()
378     cursor.execute("SELECT gid, AsText(%s) FROM %s"
379     " WHERE %s && GeometryFromText('%s', -1) ORDER BY gid"
380     % (self.geometry_column, self.tablename,
381     self.geometry_column, geom))
382     while 1:
383     result = cursor.fetchone()
384     if result is None:
385     raise StopIteration
386     yield PostGISShape(result[0], result[1])

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26