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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2734 - (show annotations)
Thu Mar 1 12:42:59 2007 UTC (18 years ago) by bramz
File MIME type: text/x-python
File size: 12274 byte(s)
made a copy
1 # Copyright (C) 2003, 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 """Data source abstractions"""
9
10 from __future__ import generators
11
12 __version__ = "$Revision$"
13 # $Source$
14 # $Id$
15
16 import os
17 import weakref
18 from math import ceil, log
19
20 import shapelib
21 import shptree
22 import table
23 import transientdb
24
25 from Thuban import _
26
27 # Shape type constants
28 SHAPETYPE_POLYGON = "polygon"
29 SHAPETYPE_ARC = "arc"
30 SHAPETYPE_POINT = "point"
31
32 # mapping from shapelib shapetype constants to our constants
33 shapelib_shapetypes = {shapelib.SHPT_POLYGON: SHAPETYPE_POLYGON,
34 shapelib.SHPT_ARC: SHAPETYPE_ARC,
35 shapelib.SHPT_POINT: SHAPETYPE_POINT}
36
37 #
38 # Raw shape data formats
39 #
40
41 # Raw data is the same as that returned by the points method.
42 RAW_PYTHON = "RAW_PYTHON"
43
44 # Raw data is a shapefile. The Shape object will use the shapeid as the
45 # raw data.
46 RAW_SHAPEFILE = "RAW_SHAPEFILE"
47
48 # Raw data in well-known text format
49 RAW_WKT = "RAW_WKT"
50
51
52 class ShapefileShape:
53
54 """Represent one shape of a shapefile"""
55
56 def __init__(self, shapefile, shapeid):
57 self.shapefile = shapefile
58 self.shapeid = shapeid
59
60 def compute_bbox(self):
61 """
62 Return the bounding box of the shape as a tuple (minx,miny,maxx,maxy)
63 """
64 xs = []
65 ys = []
66 for part in self.Points():
67 for x, y in part:
68 xs.append(x)
69 ys.append(y)
70 return (min(xs), min(ys), max(xs), max(ys))
71
72 def ShapeID(self):
73 return self.shapeid
74
75 def Points(self):
76 """Return the coordinates of the shape as a list of lists of pairs"""
77 shape = self.shapefile.read_object(self.shapeid)
78 points = shape.vertices()
79 if self.shapefile.info()[1] == shapelib.SHPT_POINT:
80 points = [points]
81 return points
82
83 def RawData(self):
84 """Return the shape id to use with the shapefile"""
85 return self.shapeid
86
87 def Shapefile(self):
88 """Return the shapefile object"""
89 return self.shapefile
90
91
92 class ShapeTable(transientdb.AutoTransientTable):
93
94 """A Table that depends on a ShapefileStore
95
96 Intended use is by the ShapefileStore for the table associated with
97 the shapefiles.
98 """
99
100 def __init__(self, store, db, table):
101 """Initialize the ShapeTable.
102
103 Parameters:
104 store -- the ShapefileStore the table is to depend on
105 db -- The transient database to use
106 table -- the table
107 """
108 transientdb.AutoTransientTable.__init__(self, db, table)
109 self.store = weakref.ref(store)
110
111 def Dependencies(self):
112 """Return a tuple containing the shapestore"""
113 return (self.store(),)
114
115 # XXX: (this statement should be kept in mind when re-engeneering)
116 #
117 # From a desing POV it was wrong to distinguish between table and
118 # shapestore. In hindsight the only reason for doing so was that the
119 # shapelib has different objects for the shapefile(s) and the dbf file,
120 # which of course matches the way the data is organized into different
121 # files. So the distinction between shapestore and table is an artifact
122 # of the shapefile format. When we added the postgis support we should
123 # have adopted the table interface for the entire shape store, making the
124 # geometry data an additional column for those shape stores that don't
125 # store the geometries in columns in the first place.
126
127 class FileShapeStore:
128
129 """The base class to derive any file-based ShapeStore from.
130
131 This class contains all information that is needed by a
132 loader routine to actually load the shapestore.
133 This essentially means that the class contains all required information
134 to save the shapestore specification (i.e. in a .thuban file).
135 """
136
137 def __init__(self, filename, sublayer_name = None):
138 """Initialize the base class with main parameters.
139
140 filename -- the source filename.
141 This filename will be converted to an absolute filename.
142 The filename will be interpreted relative to the .thuban file anyway,
143 but when saving a session we need to compare absolute paths
144 and it's usually safer to always work with absolute paths.
145 sublayer_name -- a string representing a layer within the file shape store.
146 Some file formats support to contain several layers, or
147 at least the ogr library says so.
148 For those filetypes who don't, the sublayer_name can be ignored
149 and by default it is None.
150 """
151 self._filename = os.path.abspath(filename)
152 self._sublayer_name = sublayer_name
153
154 def FileName(self):
155 """Return the filename used to open the shapestore.
156
157 The filename can only be set via __init__ method.
158 """
159 return self._filename
160
161 def FileType(self):
162 """Return the filetype.
163
164 The filetype has to be set in all derived classes.
165 It must be string.
166 Known and used types are: "shapefile"
167 """
168 raise NotImplementedError
169
170 def SublayerName(self):
171 """Return the sublayer_name.
172
173 This could be None if the shapestore type only supports a single
174 layer.
175 """
176 return self._sublayer_name
177
178 # Design/Implementation note:
179 # It is not a good idea to have a implementation for a
180 # "setBoundingBox" or BoundingBox in this base class.
181 # In future this data might change during
182 # a Thuban session and thus can not be kept statically here.
183 # It is expected that for many derived classes the bbox must
184 # be retrieved each time anew.
185
186 def BoundingBox(self):
187 """Return the bounding box of the shapes in the shape store.
188
189 The coordinate system used is whatever was used in the shape store.
190 If the shape store is empty, return None.
191 """
192 raise NotImplementedError
193
194 class ShapefileStore(FileShapeStore):
195
196 """Combine a shapefile and the corresponding DBF file into one object"""
197
198 def __init__(self, session, filename):
199 FileShapeStore.__init__(self, filename)
200
201 self.dbftable = table.DBFTable(filename)
202 self._table = ShapeTable(self, session.TransientDB(), self.dbftable)
203 self._bbox = None
204 self._open_shapefile()
205
206 def _open_shapefile(self):
207 self.shapefile = shapelib.ShapeFile(self.FileName())
208 self.numshapes, shapetype, mins, maxs = self.shapefile.info()
209 if self.numshapes:
210 self._bbox = mins[:2] + maxs[:2]
211 else:
212 self._bbox = None
213 self.shapetype = shapelib_shapetypes[shapetype]
214
215 # estimate a good depth for the quad tree. Each depth multiplies
216 # the number of nodes by four, therefore we basically take the
217 # base 4 logarithm of the number of shapes.
218 if self.numshapes < 4:
219 maxdepth = 1
220 else:
221 maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))
222
223 self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2,
224 maxdepth)
225
226 def Table(self):
227 """Return the table containing the attribute data."""
228 return self._table
229
230 def Shapefile(self):
231 """Return the shapefile object"""
232 return self.shapefile
233
234 def FileType(self):
235 """Return the filetype. This is always the string 'shapefile'"""
236 return "shapefile"
237
238 def ShapeType(self):
239 """Return the type of the shapes in the shapestore.
240
241 This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.
242 """
243 return self.shapetype
244
245 def RawShapeFormat(self):
246 """Return the raw data format of the shape data, i.e. RAW_SHAPEFILE"""
247 return RAW_SHAPEFILE
248
249 def NumShapes(self):
250 """Return the number of shapes in the shapefile"""
251 return self.numshapes
252
253 def Dependencies(self):
254 """Return the empty tuple.
255
256 The ShapefileStore doesn't depend on anything else.
257 """
258 return ()
259
260 def OrigShapeStore(self):
261 """Return None.
262
263 The ShapefileStore was not derived from another shapestore.
264 """
265 return None
266
267 def BoundingBox(self):
268 """Return the bounding box of the shapes in the shapefile.
269
270 The coordinate system used is whatever was used in the shapefile.
271 If the shapefileis empty, return None.
272 """
273 return self._bbox
274
275 def ShapesInRegion(self, bbox):
276 """Return an iterable over the shapes that overlap the bounding box.
277
278 The bbox parameter should be the bounding box as a tuple in the
279 form (minx, miny, maxx, maxy) in the coordinate system of the
280 shapefile.
281 """
282 # Bind a few globals to locals to make it a bit faster
283 cls = ShapefileShape
284 shapefile = self.shapefile
285
286 left, bottom, right, top = bbox
287 for i in self.shapetree.find_shapes((left, bottom), (right, top)):
288 yield cls(shapefile, i)
289
290 def AllShapes(self):
291 """Return an iterable over the shapes in the shapefile."""
292 for i in xrange(self.NumShapes()):
293 yield ShapefileShape(self.shapefile, i)
294
295 def Shape(self, index):
296 """Return the shape with index index"""
297 return ShapefileShape(self.shapefile, index)
298
299
300 class DerivedShapeStore:
301
302 """A ShapeStore derived from other shapestores or tables"""
303
304 def __init__(self, shapestore, table):
305 """Initialize the derived shapestore.
306
307 The arguments are a shapestore for the shapedata and a table for
308 the tabular data.
309
310 Raises ValueError if the number of shapes in the shapestore
311 is different from the number of rows in the table.
312 """
313
314 numShapes = shapestore.Shapefile().info()[0]
315 if numShapes != table.NumRows():
316 raise ValueError(_("Table not compatible with shapestore."))
317
318 self.shapestore = shapestore
319 self.table = table
320
321 def Table(self):
322 """Return the table"""
323 return self.table
324
325 def Shapefile(self):
326 """Return the shapefile of the underlying shapestore"""
327 return self.shapestore.Shapefile()
328
329 def Dependencies(self):
330 """Return a tuple containing the shapestore and the table"""
331 return (self.shapestore, self.table)
332
333 def OrigShapeStore(self):
334 """
335 Return the original shapestore the derived store was instantiated with
336 """
337 return self.shapestore
338
339 def Shape(self, index):
340 """Return the shape with index index"""
341 return self.shapestore.Shape(index)
342
343 def ShapesInRegion(self, bbox):
344 """Return the ids of the shapes that overlap the box.
345
346 This method is simply delegated to the shapestore the
347 DerivedShapeStore was instantiated with.
348 """
349 return self.shapestore.ShapesInRegion(bbox)
350
351 def AllShapes(self):
352 """Return an iterable over the shapes in the shapefile.
353
354 This method is simply delegated to the shapestore the
355 DerivedShapeStore was instantiated with.
356 """
357 return self.shapestore.AllShapes()
358
359 def ShapeType(self):
360 """Return the type of the shapes in the layer.
361
362 This method is simply delegated to the shapestore the
363 DerivedShapeStore was instantiated with.
364 """
365 return self.shapestore.ShapeType()
366
367 def RawShapeFormat(self):
368 """Return the raw data format of the shapes.
369
370 This method is simply delegated to the shapestore the
371 DerivedShapeStore was instantiated with.
372 """
373 return self.shapestore.RawShapeFormat()
374
375 def NumShapes(self):
376 """Return the number of shapes in the shapestore."""
377 return self.shapestore.NumShapes()
378
379 def BoundingBox(self):
380 """Return the bounding box of the shapes in the shapestore.
381
382 This method is simply delegated to the shapestore the
383 DerivedShapeStore was instantiated with.
384 """
385 return self.shapestore.BoundingBox()

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26