20 |
|
|
21 |
from data import SHAPETYPE_POLYGON, SHAPETYPE_ARC, SHAPETYPE_POINT, RAW_WKT |
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: |
if psycopg is not None: |
35 |
type_map = [(psycopg.STRING, table.FIELDTYPE_STRING), |
type_map = [(psycopg.STRING, table.FIELDTYPE_STRING), |
36 |
(psycopg.INTEGER, table.FIELDTYPE_INT), |
(psycopg.INTEGER, table.FIELDTYPE_INT), |
37 |
(psycopg.FLOAT, table.FIELDTYPE_DOUBLE)] |
(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: |
class PostGISConnection: |
46 |
|
|
47 |
"""Represent a PostGIS database |
"""Represent a PostGIS database |
64 |
self.user = user |
self.user = user |
65 |
self.password = password |
self.password = password |
66 |
self.dbtype = dbtype |
self.dbtype = dbtype |
67 |
|
self.connect() |
68 |
|
|
69 |
|
def connect(self): |
70 |
|
"""Internal: Establish the database connection""" |
71 |
params = [] |
params = [] |
72 |
for name in ("host", "port", "dbname", "user", "password"): |
for name in ("host", "port", "dbname", "user", "password"): |
73 |
val = getattr(self, name) |
val = getattr(self, name) |
74 |
if val: |
if val: |
75 |
params.append("%s=%s" % (name, val)) |
params.append("%s=%s" % (name, val)) |
76 |
self.connection = psycopg.connect(" ".join(params)) |
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 |
# determine the OID for the geometry type. This is PostGIS |
82 |
# specific. |
# specific. |
90 |
else: |
else: |
91 |
raise ValueError("Can't determine postgres type of geometries") |
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): |
def Close(self): |
103 |
"""Close the database connection""" |
"""Close the database connection""" |
104 |
self.connection.close() |
self.connection.close() |
172 |
% (", ".join([col.name for col in self.columns]), |
% (", ".join([col.name for col in self.columns]), |
173 |
self.tablename)) |
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 Title(self): |
184 |
|
"""Return the title of the table. |
185 |
|
|
186 |
|
The title is currently fixed and equal to the tablename |
187 |
|
""" |
188 |
|
return self.tablename |
189 |
|
|
190 |
def Dependencies(self): |
def Dependencies(self): |
191 |
"""Return an empty tuple because a PostGISTable depends on nothing else |
"""Return an empty tuple because a PostGISTable depends on nothing else |
192 |
""" |
""" |
294 |
|
|
295 |
|
|
296 |
shapetype_map = {"POLYGON": SHAPETYPE_POLYGON, |
shapetype_map = {"POLYGON": SHAPETYPE_POLYGON, |
297 |
|
"MULTIPOLYGON": SHAPETYPE_POLYGON, |
298 |
"MULTILINESTRING": SHAPETYPE_ARC, |
"MULTILINESTRING": SHAPETYPE_ARC, |
299 |
"POINT": SHAPETYPE_POINT} |
"POINT": SHAPETYPE_POINT} |
300 |
|
|
340 |
y=[] |
y=[] |
341 |
cursor = self.db.cursor() |
cursor = self.db.cursor() |
342 |
try: |
try: |
343 |
stmt = ("SELECT AsText(Envelope(%s)) FROM %s;" |
# Using the extent function is postgis specific. An OGC |
344 |
% (self.geometry_column, self.tablename)) |
# Simple Features compliant solution would be to use a query |
345 |
cursor.execute(stmt) |
# like "SELECT AsText(Envelope(the_geom)) FROM mytable;" and |
346 |
|
# calculate the bounding box by hand from that |
347 |
|
cursor.execute("SELECT extent(%s) FROM %s;" |
348 |
|
% (self.geometry_column, self.tablename)) |
349 |
result = cursor.fetchone() |
result = cursor.fetchone() |
350 |
while result: |
if result: |
351 |
result = result[0] |
(minx, miny), (maxx, maxy) \ |
352 |
# Here we must do some parsing through the result string |
= wellknowntext.parse_wkt_thuban(result[0])[0] |
353 |
# to get the points out of the polygon which representes |
return (minx, miny, maxx, maxy) |
|
# the bounding box The first and the last point of a |
|
|
# polygon are identical |
|
|
result = result.split("(")[2] |
|
|
result = result.split(")")[0] |
|
|
points = result.split(",") |
|
|
del points[4] # Remove the last point |
|
|
for point in points: |
|
|
px, py = point.split() |
|
|
x.append(float(px)) |
|
|
y.append(float(py)) |
|
|
result = cursor.fetchone() |
|
354 |
finally: |
finally: |
355 |
cursor.close() |
cursor.close() |
|
if not x: |
|
|
# Empty table |
|
|
return None |
|
|
return (min(x), min(y), max(x), max(y)) |
|
356 |
|
|
357 |
def Shape(self, shapeid): |
def Shape(self, shapeid): |
358 |
cursor = self.db.cursor() |
cursor = self.db.cursor() |
362 |
cursor.close() |
cursor.close() |
363 |
return PostGISShape(shapeid, wkt) |
return PostGISShape(shapeid, wkt) |
364 |
|
|
365 |
|
def AllShapes(self): |
366 |
|
cursor = self.db.cursor() |
367 |
|
cursor.execute("SELECT gid, AsText(%s) FROM %s ORDER BY gid" |
368 |
|
% (self.geometry_column, self.tablename)) |
369 |
|
while 1: |
370 |
|
result = cursor.fetchone() |
371 |
|
if result is None: |
372 |
|
return |
373 |
|
yield PostGISShape(result[0], result[1]) |
374 |
|
|
375 |
|
|
376 |
def ShapesInRegion(self, bbox): |
def ShapesInRegion(self, bbox): |
377 |
"""Generate all shapes overlapping the region given by bbox.""" |
"""Generate all shapes overlapping the region given by bbox.""" |
378 |
# IMPORTANT:This will work for PostGIS < 0.8 |
# IMPORTANT:This will work for PostGIS < 0.8 |
388 |
while 1: |
while 1: |
389 |
result = cursor.fetchone() |
result = cursor.fetchone() |
390 |
if result is None: |
if result is None: |
391 |
raise StopIteration |
return |
392 |
yield PostGISShape(result[0], result[1]) |
yield PostGISShape(result[0], result[1]) |