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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1656 - (show 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 # 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 def has_postgis_support():
24 """Return whether this Thuban instance supports PostGIS connections
25
26 Having PostGIS support means that the psycopg module can be
27 imported.
28 """
29 return psycopg is not None
30
31 def psycopg_version():
32 return psycopg.__version__
33
34 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
40 class ConnectionError(Exception):
41
42 """Class for exceptions occurring when establishing a Databse connection"""
43
44
45 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 self.connect()
68
69 def connect(self):
70 """Internal: Establish the database connection"""
71 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 try:
77 self.connection = psycopg.connect(" ".join(params))
78 except psycopg.OperationalError, val:
79 raise ConnectionError(str(val))
80
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 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 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 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 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 "MULTIPOLYGON": SHAPETYPE_POLYGON,
291 "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