File: data.py

package info (click to toggle)
thuban 1.2.2-14
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 9,176 kB
  • sloc: python: 30,410; ansic: 6,181; xml: 4,234; cpp: 1,595; makefile: 145
file content (385 lines) | stat: -rw-r--r-- 12,321 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# Copyright (C) 2003, 2005 by Intevation GmbH
# Authors:
# Bernhard Herzog <bh@intevation.de>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with the software for details.

"""Data source abstractions"""

from __future__ import generators

__version__ = "$Revision: 2605 $"
# $Source$
# $Id: data.py 2605 2005-04-27 11:04:56Z jan $

import os
import weakref
from math import ceil, log

import shapelib
import shptree
import table
import transientdb

from Thuban import _

# Shape type constants
SHAPETYPE_POLYGON = "polygon"
SHAPETYPE_ARC = "arc"
SHAPETYPE_POINT = "point"

# mapping from shapelib shapetype constants to our constants
shapelib_shapetypes = {shapelib.SHPT_POLYGON: SHAPETYPE_POLYGON,
                       shapelib.SHPT_ARC: SHAPETYPE_ARC,
                       shapelib.SHPT_POINT: SHAPETYPE_POINT}

#
# Raw shape data formats
#

# Raw data is the same as that returned by the points method.
RAW_PYTHON = "RAW_PYTHON"

# Raw data is a shapefile. The Shape object will use the shapeid as the
# raw data.
RAW_SHAPEFILE = "RAW_SHAPEFILE"

# Raw data in well-known text format
RAW_WKT = "RAW_WKT"


class ShapefileShape:

    """Represent one shape of a shapefile"""

    def __init__(self, shapefile, shapeid):
        self.shapefile = shapefile
        self.shapeid = shapeid

    def compute_bbox(self):
        """
        Return the bounding box of the shape as a tuple (minx,miny,maxx,maxy)
        """
        xs = []
        ys = []
        for part in self.Points():
            for x, y in part:
                xs.append(x)
                ys.append(y)
        return (min(xs), min(ys), max(xs), max(ys))

    def ShapeID(self):
        return self.shapeid

    def Points(self):
        """Return the coordinates of the shape as a list of lists of pairs"""
        shape = self.shapefile.read_object(self.shapeid)
        points = shape.vertices()
        if self.shapefile.info()[1] == shapelib.SHPT_POINT:
            points = [points]
        return points

    def RawData(self):
        """Return the shape id to use with the shapefile"""
        return self.shapeid

    def Shapefile(self):
        """Return the shapefile object"""
        return self.shapefile


class ShapeTable(transientdb.AutoTransientTable):

    """A Table that depends on a ShapefileStore

    Intended use is by the ShapefileStore for the table associated with
    the shapefiles.
    """

    def __init__(self, store, db, table):
        """Initialize the ShapeTable.

        Parameters:
            store -- the ShapefileStore the table is to depend on
            db -- The transient database to use
            table -- the table
        """
        transientdb.AutoTransientTable.__init__(self, db, table)
        self.store = weakref.ref(store)

    def Dependencies(self):
        """Return a tuple containing the shapestore"""
        return (self.store(),)

# XXX: (this statement should be kept in mind when re-engeneering)
#
# From a desing POV it was wrong to distinguish between table and
# shapestore.  In hindsight the only reason for doing so was that the
# shapelib has different objects for the shapefile(s) and the dbf file,
# which of course matches the way the data is organized into different
# files.  So the distinction between shapestore and table is an artifact
# of the shapefile format.  When we added the postgis support we should
# have adopted the table interface for the entire shape store, making the
# geometry data an additional column for those shape stores that don't
# store the geometries in columns in the first place.

class FileShapeStore:

    """The base class to derive any file-based ShapeStore from.

    This class contains all information that is needed by a
    loader routine to actually load the shapestore.
    This essentially means that the class contains all required information
    to save the shapestore specification (i.e. in a .thuban file).
    """

    def __init__(self, filename, sublayer_name = None):
        """Initialize the base class with main parameters.

        filename  -- the source filename.
                     This filename will be converted to an absolute filename.
                     The filename will be interpreted relative to the .thuban file anyway,
                     but when saving a session we need to compare absolute paths
                     and it's usually safer to always work with absolute paths.
        sublayer_name -- a string representing a layer within the file shape store.
                     Some file formats support to contain several layers, or
                     at least the ogr library says so.
                     For those filetypes who don't, the sublayer_name can be ignored
                     and by default it is None.
        """
        self._filename = os.path.abspath(filename)
        self._sublayer_name = sublayer_name

    def FileName(self):
        """Return the filename used to open the shapestore.

        The filename can only be set via __init__ method.
        """
        return self._filename

    def FileType(self):
        """Return the filetype.

        The filetype has to be set in all derived classes.
        It must be string.
        Known and used types are: "shapefile"
        """
        raise NotImplementedError

    def SublayerName(self):
        """Return the sublayer_name.

        This could be None if the shapestore type only supports a single
        layer.
        """
        return self._sublayer_name

    # Design/Implementation note:
    # It is not a good idea to have a implementation for a
    # "setBoundingBox" or BoundingBox in this base class.
    # In future this data might change during
    # a Thuban session and thus can not be kept statically here.
    # It is expected that for many derived classes the bbox must
    # be retrieved each time anew.

    def BoundingBox(self):
        """Return the bounding box of the shapes in the shape store.

        The coordinate system used is whatever was used in the shape store.
        If the shape store is empty, return None.
        """
        raise NotImplementedError

class ShapefileStore(FileShapeStore):

    """Combine a shapefile and the corresponding DBF file into one object"""

    def __init__(self, session, filename):
        FileShapeStore.__init__(self, filename)

        self.dbftable = table.DBFTable(filename)
        self._table = ShapeTable(self, session.TransientDB(), self.dbftable)
        self._bbox = None
        self._open_shapefile()

    def _open_shapefile(self):
        self.shapefile = shapelib.ShapeFile(self.FileName())
        self.numshapes, shapetype, mins, maxs = self.shapefile.info()
        if self.numshapes:
            self._bbox = mins[:2] + maxs[:2]
        else:
            self._bbox = None
        self.shapetype = shapelib_shapetypes[shapetype]

        # estimate a good depth for the quad tree. Each depth multiplies
        # the number of nodes by four, therefore we basically take the
        # base 4 logarithm of the number of shapes.
        if self.numshapes < 4:
            maxdepth = 1
        else:
            maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))

        self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2,
                                         maxdepth)

    def Table(self):
        """Return the table containing the attribute data."""
        return self._table

    def Shapefile(self):
        """Return the shapefile object"""
        return self.shapefile

    def FileType(self):
        """Return the filetype. This is always the string 'shapefile'"""
        return "shapefile"

    def ShapeType(self):
        """Return the type of the shapes in the shapestore.

        This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.
        """
        return self.shapetype

    def RawShapeFormat(self):
        """Return the raw data format of the shape data, i.e. RAW_SHAPEFILE"""
        return RAW_SHAPEFILE

    def NumShapes(self):
        """Return the number of shapes in the shapefile"""
        return self.numshapes

    def Dependencies(self):
        """Return the empty tuple.

        The ShapefileStore doesn't depend on anything else.
        """
        return ()

    def OrigShapeStore(self):
        """Return None.

        The ShapefileStore was not derived from another shapestore.
        """
        return None

    def BoundingBox(self):
        """Return the bounding box of the shapes in the shapefile.

        The coordinate system used is whatever was used in the shapefile.
        If the shapefileis empty, return None.
        """
        return self._bbox

    def ShapesInRegion(self, bbox):
        """Return an iterable over the shapes that overlap the bounding box.

        The bbox parameter should be the bounding box as a tuple in the
        form (minx, miny, maxx, maxy) in the coordinate system of the
        shapefile.
        """
        # Bind a few globals to locals to make it a bit faster
        cls = ShapefileShape
        shapefile = self.shapefile

        left, bottom, right, top = bbox
        for i in self.shapetree.find_shapes((left, bottom), (right, top)):
            yield cls(shapefile, i)

    def AllShapes(self):
        """Return an iterable over the shapes in the shapefile."""
        for i in xrange(self.NumShapes()):
            yield ShapefileShape(self.shapefile, i)

    def Shape(self, index):
        """Return the shape with index index"""
        return ShapefileShape(self.shapefile, index)


class DerivedShapeStore:

    """A ShapeStore derived from other shapestores or tables"""

    def __init__(self, shapestore, table):
        """Initialize the derived shapestore.

        The arguments are a shapestore for the shapedata and a table for
        the tabular data.

        Raises ValueError if the number of shapes in the shapestore
        is different from the number of rows in the table.
        """

        numShapes = shapestore.Shapefile().info()[0]
        if numShapes != table.NumRows():
            raise ValueError(_("Table not compatible with shapestore."))

        self.shapestore = shapestore
        self.table = table

    def Table(self):
        """Return the table"""
        return self.table

    def Shapefile(self):
        """Return the shapefile of the underlying shapestore"""
        return self.shapestore.Shapefile()

    def Dependencies(self):
        """Return a tuple containing the shapestore and the table"""
        return (self.shapestore, self.table)

    def OrigShapeStore(self):
        """
        Return the original shapestore the derived store was instantiated with
        """
        return self.shapestore

    def Shape(self, index):
        """Return the shape with index index"""
        return self.shapestore.Shape(index)

    def ShapesInRegion(self, bbox):
        """Return the ids of the shapes that overlap the box.

        This method is simply delegated to the shapestore the
        DerivedShapeStore was instantiated with.
        """
        return self.shapestore.ShapesInRegion(bbox)

    def AllShapes(self):
        """Return an iterable over the shapes in the shapefile.

        This method is simply delegated to the shapestore the
        DerivedShapeStore was instantiated with.
        """
        return self.shapestore.AllShapes()

    def ShapeType(self):
        """Return the type of the shapes in the layer.

        This method is simply delegated to the shapestore the
        DerivedShapeStore was instantiated with.
        """
        return self.shapestore.ShapeType()

    def RawShapeFormat(self):
        """Return the raw data format of the shapes.

        This method is simply delegated to the shapestore the
        DerivedShapeStore was instantiated with.
        """
        return self.shapestore.RawShapeFormat()

    def NumShapes(self):
        """Return the number of shapes in the shapestore."""
        return self.shapestore.NumShapes()

    def BoundingBox(self):
        """Return the bounding box of the shapes in the shapestore.

        This method is simply delegated to the shapestore the
        DerivedShapeStore was instantiated with.
        """
        return self.shapestore.BoundingBox()