/[thuban]/trunk/thuban/Thuban/Model/layer.py
ViewVC logotype

Annotation of /trunk/thuban/Thuban/Model/layer.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2710 - (hide annotations)
Tue Oct 10 08:31:44 2006 UTC (18 years, 5 months ago) by dpinte
File MIME type: text/x-python
File size: 18723 byte(s)
2006-10-10 Didrik Pinte <dpinte@itae.be>
    * Thuban/Model/layer.py (Layer.GetLabelPosFromShape): new method 
        extracted from Thuban/UI/viewport.py
    * Thuban/UI/viewport.py (Viewport.LabelShapeAt): uses new method


1 bh 2644 # Copyright (c) 2001, 2002, 2003, 2004, 2005 by Intevation GmbH
2 bh 6 # Authors:
3     # Bernhard Herzog <[email protected]>
4 jonathan 412 # Jonathan Coles <[email protected]>
5 silke 2339 # Silke Reimer <[email protected]>
6 bh 6 #
7     # This program is free software under the GPL (>=v2)
8     # Read the file COPYING coming with Thuban for details.
9    
10     __version__ = "$Revision$"
11    
12 bh 1599 import os
13 bh 1219 import warnings
14 bh 171
15 dpinte 2710 from wxproj import point_in_polygon_shape, shape_centroid
16    
17 jan 374 from Thuban import _
18 bh 6
19 bh 701 from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
20 jonathan 1427 LAYER_CHANGED, LAYER_SHAPESTORE_REPLACED, CLASS_CHANGED
21 bh 6
22 jonathan 437 import classification
23 bh 6
24 jonathan 1338 from color import Transparent, Black
25 bh 6 from base import TitledObject, Modifiable
26 bh 1558 from data import SHAPETYPE_POLYGON, SHAPETYPE_ARC, SHAPETYPE_POINT
27 dpinte 2710 from label import ALIGN_CENTER, ALIGN_TOP, ALIGN_BOTTOM, \
28     ALIGN_LEFT, ALIGN_RIGHT
29 bh 6
30 jonathan 1158 import resource
31 bh 723
32 jonathan 2551 from color import Color
33 jonathan 1158
34 bh 6 shapetype_names = {SHAPETYPE_POINT: "Point",
35     SHAPETYPE_ARC: "Arc",
36     SHAPETYPE_POLYGON: "Polygon"}
37    
38     class BaseLayer(TitledObject, Modifiable):
39    
40     """Base class for the layers."""
41    
42 jonathan 929 def __init__(self, title, visible = True, projection = None):
43 bh 6 """Initialize the layer.
44    
45     title -- the title
46     visible -- boolean. If true the layer is visible.
47     """
48     TitledObject.__init__(self, title)
49     Modifiable.__init__(self)
50     self.visible = visible
51 jonathan 929 self.projection = projection
52 bh 6
53     def Visible(self):
54     """Return true if layer is visible"""
55     return self.visible
56    
57     def SetVisible(self, visible):
58     """Set the layer's visibility."""
59     self.visible = visible
60     self.issue(LAYER_VISIBILITY_CHANGED, self)
61 bh 276
62 jonathan 929 def HasClassification(self):
63 bernhard 2343 """Determine if this layer supports classifications."""
64 jonathan 929 return False
65 bh 276
66 jonathan 1273 def HasShapes(self):
67     """Determine if this layer supports shapes."""
68     return False
69    
70 jonathan 929 def GetProjection(self):
71     """Return the layer's projection."""
72     return self.projection
73    
74     def SetProjection(self, projection):
75 bernhard 2343 """Set the layer's projection."""
76 jonathan 929 self.projection = projection
77     self.changed(LAYER_PROJECTION_CHANGED, self)
78    
79 jonathan 2551 def Type(self):
80     return "Unknown"
81    
82 bh 6 class Layer(BaseLayer):
83    
84     """Represent the information of one geodata file (currently a shapefile)
85    
86     All children of the layer have the same type.
87    
88     A layer has fill and stroke colors. Colors should be instances of
89 jonathan 1338 Color. They can also be Transparent, indicating no fill or no stroke.
90 bh 6
91     The layer objects send the following events, all of which have the
92     layer object as parameter:
93    
94     TITLE_CHANGED -- The title has changed.
95     LAYER_PROJECTION_CHANGED -- the projection has changed.
96     """
97    
98 bh 723 def __init__(self, title, data, projection = None,
99 jonathan 1338 fill = Transparent,
100     stroke = Black,
101 jonathan 464 lineWidth = 1,
102 jonathan 771 visible = True):
103 bh 6 """Initialize the layer.
104    
105     title -- the title
106 bh 723 data -- datastore object for the shape data shown by the layer
107 bh 6 projection -- the projection object. Its Inverse method is
108     assumed to map the layer's coordinates to lat/long
109     coordinates
110 jonathan 1338 fill -- the fill color or Transparent if the shapes are
111 jonathan 610 not filled
112 jonathan 1338 stroke -- the stroke color or Transparent if the shapes
113 jonathan 610 are not stroked
114 bh 6 visible -- boolean. If true the layer is visible.
115    
116     colors are expected to be instances of Color class
117     """
118 jonathan 929 BaseLayer.__init__(self, title,
119     visible = visible,
120     projection = projection)
121 bh 276
122 bh 723 self.__classification = None
123 jonathan 1427 self.store = None
124 jonathan 481
125 bh 723 self.SetShapeStore(data)
126 jonathan 492
127 bh 1452 self.classification_column = None
128     self.SetClassificationColumn(None)
129 jonathan 492 self.SetClassification(None)
130    
131 jonathan 464 self.__classification.SetDefaultLineColor(stroke)
132     self.__classification.SetDefaultLineWidth(lineWidth)
133 jonathan 412 self.__classification.SetDefaultFill(fill)
134 jonathan 364
135 jonathan 389 self.UnsetModified()
136    
137 bh 723 def SetShapeStore(self, store):
138 jonathan 1427 # Set the classification to None if there is a classification
139     # and the new shapestore doesn't have a table with a suitable
140     # column, i.e one with the same name and type as before
141     # FIXME: Maybe we should keep it the same if the type is
142     # compatible enough such as FIELDTYPE_DOUBLE and FIELDTYPE_INT
143     if self.__classification is not None:
144 bh 1452 columnname = self.classification_column
145     columntype = self.GetFieldType(columnname)
146 jonathan 1427 table = store.Table()
147 bh 1452 if (columnname is not None
148     and (not table.HasColumn(columnname)
149     or table.Column(columnname).type != columntype)):
150 jonathan 1427 self.SetClassification(None)
151    
152 bh 723 self.store = store
153 bh 179
154 bh 1142 self.changed(LAYER_SHAPESTORE_REPLACED, self)
155 bh 723
156     def ShapeStore(self):
157     return self.store
158    
159 bh 258 def Destroy(self):
160 bh 260 BaseLayer.Destroy(self)
161 jonathan 1427 if self.__classification is not None:
162 bh 1452 self.__classification.Unsubscribe(CLASS_CHANGED,
163     self._classification_changed)
164 bh 258
165 bh 6 def BoundingBox(self):
166 bh 179 """Return the layer's bounding box in the intrinsic coordinate system.
167    
168     If the layer has no shapes, return None.
169     """
170 bh 1535 return self.store.BoundingBox()
171 bh 6
172     def LatLongBoundingBox(self):
173 bh 179 """Return the layer's bounding box in lat/long coordinates.
174 bh 6
175 bh 179 Return None, if the layer doesn't contain any shapes.
176     """
177     bbox = self.BoundingBox()
178 bh 1983 if bbox is not None and self.projection is not None:
179     bbox = self.projection.InverseBBox(bbox)
180     return bbox
181 bh 179
182 jonathan 2551 def Type(self):
183     return self.ShapeType();
184    
185 jonathan 828 def ShapesBoundingBox(self, shapes):
186     """Return a bounding box in lat/long coordinates for the given
187     list of shape ids.
188    
189     If shapes is None or empty, return None.
190     """
191    
192     if shapes is None or len(shapes) == 0: return None
193    
194 bh 1983 xs = []
195     ys = []
196 jonathan 828
197     for id in shapes:
198 bh 1983 bbox = self.Shape(id).compute_bbox()
199     if self.projection is not None:
200     bbox = self.projection.InverseBBox(bbox)
201     left, bottom, right, top = bbox
202     xs.append(left); xs.append(right)
203     ys.append(bottom); ys.append(top)
204 jonathan 828
205 bh 1983 return (min(xs), min(ys), max(xs), max(ys))
206 jonathan 828
207    
208 jonathan 464 def GetFieldType(self, fieldName):
209 jonathan 1427 if self.store:
210     table = self.store.Table()
211     if table.HasColumn(fieldName):
212     return table.Column(fieldName).type
213 bh 839 return None
214 jonathan 464
215 jonathan 1273 def HasShapes(self):
216     return True
217    
218 bh 6 def NumShapes(self):
219     """Return the number of shapes in the layer"""
220 bh 1535 return self.store.NumShapes()
221 bh 6
222     def ShapeType(self):
223     """Return the type of the shapes in the layer.
224 bh 1535
225     The return value is one of the SHAPETYPE_* constants defined in
226     Thuban.Model.data.
227 bh 6 """
228 bh 1535 return self.store.ShapeType()
229 bh 6
230     def Shape(self, index):
231     """Return the shape with index index"""
232 bh 1535 return self.store.Shape(index)
233 jonathan 364
234 bh 1587 def ShapesInRegion(self, bbox):
235 bh 1593 """Return an iterable over the shapes that overlap the bounding box.
236 bh 143
237 bh 1593 The bbox parameter should be the bounding box as a tuple in the
238     form (minx, miny, maxx, maxy) in unprojected coordinates.
239 bh 143 """
240 jonathan 794 if self.projection is not None:
241 silke 2339 # Ensure that region lies within the layer's bounding box
242     # Otherwise projection of the region would lead to incorrect
243     # values.
244 bh 2644 clipbbox = self.__mangle_bounding_box(bbox)
245 silke 2339 bbox = self.projection.ForwardBBox(clipbbox)
246 bh 1587 return self.store.ShapesInRegion(bbox)
247 bh 143
248 bh 1452 def GetClassificationColumn(self):
249     return self.classification_column
250 jonathan 1427
251 bh 1452 def SetClassificationColumn(self, column):
252     """Set the column to classifiy on, or None. If column is not None
253     and the column does not exist in the table, raise a ValueError.
254 jonathan 1427 """
255 bh 1452 if column:
256     columnType = self.GetFieldType(column)
257     if columnType is None:
258 jonathan 1427 raise ValueError()
259 bh 1452 changed = self.classification_column != column
260     self.classification_column = column
261     if changed:
262     self.changed(LAYER_CHANGED, self)
263 jonathan 1427
264 jonathan 929 def HasClassification(self):
265     return True
266 jonathan 725
267 jonathan 412 def GetClassification(self):
268     return self.__classification
269    
270     def SetClassification(self, clazz):
271 jonathan 1338 """Set the classification used by this layer to 'clazz'
272 jonathan 481
273 jonathan 1338 If 'clazz' is None a default classification is created.
274    
275 jonathan 1427 This issues a LAYER_CHANGED event.
276 jonathan 492 """
277 jonathan 481
278 jonathan 1427 if self.__classification is not None:
279 bh 1452 self.__classification.Unsubscribe(CLASS_CHANGED,
280     self._classification_changed)
281 jonathan 529
282 jonathan 1338 if clazz is None:
283     clazz = classification.Classification()
284 jonathan 529
285 jonathan 1427 self.__classification = clazz
286 bh 1452 self.__classification.Subscribe(CLASS_CHANGED,
287     self._classification_changed)
288 jonathan 1338
289 bh 1452 self._classification_changed()
290 jonathan 492
291 bh 1452 def _classification_changed(self):
292 jonathan 492 """Called from the classification object when it has changed."""
293 jonathan 558 self.changed(LAYER_CHANGED, self)
294 jonathan 492
295 bh 217 def TreeInfo(self):
296     items = []
297    
298 jonathan 1338 items.append(_("Filename: %s") % self.ShapeStore().FileName())
299 jan 1012
300 bh 217 if self.Visible():
301 jan 374 items.append(_("Shown"))
302 bh 217 else:
303 jan 374 items.append(_("Hidden"))
304     items.append(_("Shapes: %d") % self.NumShapes())
305 bh 217
306     bbox = self.LatLongBoundingBox()
307     if bbox is not None:
308 bh 2228 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % tuple(bbox))
309 bh 217 else:
310 jan 374 items.append(_("Extent (lat-lon):"))
311     items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
312 bh 217
313 jonathan 736 if self.projection and len(self.projection.params) > 0:
314     items.append((_("Projection"),
315     [str(param) for param in self.projection.params]))
316    
317 jonathan 412 items.append(self.__classification)
318 bh 217
319 jan 374 return (_("Layer '%s'") % self.Title(), items)
320 jonathan 382
321 bh 2644 def __mangle_bounding_box(self, bbox):
322     # FIXME: This method doesn't make much sense.
323     # See RT #2845 which effectively says:
324     #
325     # If this method, which was originally called ClipBoundingBox,
326     # is supposed to do clipping it shouldn't return the parameter
327     # unchanged when it lies completely outside of the bounding box.
328     # It would be better to return None and return an empty list in
329     # ShapesInRegion (the only caller) in that case.
330     #
331     # This method was introduced to fix a bug that IIRC had
332     # something todo with projections and bounding boxes containing
333     # NaN or INF when the parameter to ShapesInRegion covered the
334     # entire earth or something similarly large).
335 silke 2339 bminx, bminy, bmaxx, bmaxy = bbox
336     lminx, lminy, lmaxx, lmaxy = self.LatLongBoundingBox()
337     if bminx > lmaxx or bmaxx < lminx:
338     left, right = bminx, bmaxx
339     else:
340     left = max(lminx, bminx)
341     right = min(lmaxx, bmaxx)
342     if bminy > lmaxy or bmaxy < lminy:
343     bottom, top = bminy, bmaxy
344     else:
345     bottom = max(lminy, bminy)
346     top = min(lmaxy, bmaxy)
347 bh 2644
348 silke 2339 return (left, bottom, right, top)
349    
350 dpinte 2710 def GetLabelPosFromShape(self, cmap, shape_index):
351     '''
352     Return the label position parameters (x, y, halign, valign) from the
353     shape object
354     '''
355     proj = cmap.projection
356     if proj is not None:
357     map_proj = proj
358     else:
359     map_proj = None
360     proj = self.projection
361     if proj is not None:
362     layer_proj = proj
363     else:
364     layer_proj = None
365 silke 2339
366 dpinte 2710 shapetype = self.ShapeType()
367     if shapetype == SHAPETYPE_POLYGON:
368     shapefile = self.ShapeStore().Shapefile().cobject()
369     x, y = shape_centroid(shapefile, shape_index,
370     map_proj, layer_proj, 1, 1, 0, 0)
371     if map_proj is not None:
372     x, y = map_proj.Inverse(x, y)
373     else:
374     shape = self.Shape(shape_index)
375     if shapetype == SHAPETYPE_POINT:
376     x, y = shape.Points()[0][0]
377     else:
378     # assume SHAPETYPE_ARC
379     points = shape.Points()[0]
380     x, y = points[len(points) / 2]
381     if layer_proj is not None:
382     x, y = layer_proj.Inverse(x, y)
383     if shapetype == SHAPETYPE_POINT:
384     halign = ALIGN_LEFT
385     valign = ALIGN_CENTER
386     elif shapetype == SHAPETYPE_POLYGON:
387     halign = ALIGN_CENTER
388     valign = ALIGN_CENTER
389     elif shapetype == SHAPETYPE_ARC:
390     halign = ALIGN_LEFT
391     valign = ALIGN_CENTER
392    
393     return (x, y, halign, valign)
394    
395    
396    
397 jonathan 1158 if resource.has_gdal_support():
398     import gdal
399     from gdalconst import GA_ReadOnly
400    
401 jonathan 929 class RasterLayer(BaseLayer):
402    
403 jonathan 2562 MASK_NONE = 0
404     MASK_BIT = 1
405     MASK_ALPHA = 2
406    
407 jonathan 2614 def __init__(self, title, filename, projection = None,
408     visible = True, opacity = 1, masktype = MASK_BIT):
409 jonathan 929 """Initialize the Raster Layer.
410    
411     title -- title for the layer.
412    
413     filename -- file name of the source image.
414    
415     projection -- Projection object describing the projection which
416     the source image is in.
417    
418     visible -- True is the layer should initially be visible.
419 jonathan 961
420     Throws IOError if the filename is invalid or points to a file that
421     is not in a format GDAL can use.
422 jonathan 929 """
423    
424     BaseLayer.__init__(self, title, visible = visible)
425    
426     self.projection = projection
427 bh 1599 self.filename = os.path.abspath(filename)
428 jonathan 929
429     self.bbox = -1
430    
431 jonathan 2614 self.mask_type = masktype
432     self.opacity = opacity
433 jonathan 2551
434     self.image_info = None
435    
436 jonathan 1158 if resource.has_gdal_support():
437     #
438     # temporarily open the file so that GDAL can test if it's valid.
439     #
440     dataset = gdal.Open(self.filename, GA_ReadOnly)
441 jonathan 961
442 jonathan 1158 if dataset is None:
443     raise IOError()
444 jonathan 961
445 jonathan 2551 #
446     # while we have the file, extract some basic information
447     # that we can display later
448     #
449     self.image_info = {}
450    
451     self.image_info["nBands"] = dataset.RasterCount
452     self.image_info["Size"] = (dataset.RasterXSize, dataset.RasterYSize)
453     self.image_info["Driver"] = dataset.GetDriver().ShortName
454    
455     # store some information about the individual bands
456     # [min_value, max_value]
457     a = self.image_info["BandData"] = []
458    
459     for i in range(1, dataset.RasterCount+1):
460     band = dataset.GetRasterBand(i)
461     a.append(band.ComputeRasterMinMax())
462    
463 jonathan 929 self.UnsetModified()
464    
465     def BoundingBox(self):
466     """Return the layer's bounding box in the intrinsic coordinate system.
467    
468 jonathan 1338 If the there is no support for images, or the file cannot
469     be read, or there is no geographics information available, return None.
470 jonathan 929 """
471 jonathan 1158 if not resource.has_gdal_support():
472     return None
473    
474 jonathan 929 if self.bbox == -1:
475     dataset = gdal.Open(self.filename, GA_ReadOnly)
476     if dataset is None:
477     self.bbox = None
478     else:
479 jonathan 961 geotransform = dataset.GetGeoTransform()
480     if geotransform is None:
481     return None
482    
483     x = 0
484     y = dataset.RasterYSize
485     left = geotransform[0] + \
486     geotransform[1] * x + \
487     geotransform[2] * y
488    
489     bottom = geotransform[3] + \
490     geotransform[4] * x + \
491     geotransform[5] * y
492    
493     x = dataset.RasterXSize
494     y = 0
495     right = geotransform[0] + \
496     geotransform[1] * x + \
497     geotransform[2] * y
498    
499     top = geotransform[3] + \
500     geotransform[4] * x + \
501     geotransform[5] * y
502    
503 jonathan 929 self.bbox = (left, bottom, right, top)
504    
505     return self.bbox
506    
507     def LatLongBoundingBox(self):
508     bbox = self.BoundingBox()
509 jonathan 961 if bbox is None:
510 jonathan 929 return None
511    
512 jonathan 961 if self.projection is not None:
513 bh 1983 bbox = self.projection.InverseBBox(bbox)
514 jonathan 961
515 bh 1983 return bbox
516 jonathan 961
517 jonathan 2551 def Type(self):
518     return "Image"
519    
520 jonathan 929 def GetImageFilename(self):
521     return self.filename
522    
523 jonathan 2562 def MaskType(self):
524 jonathan 2551 """Return True if the mask should be used when rendering the layer."""
525 jonathan 2562 return self.mask_type
526 jonathan 2551
527 jonathan 2562 def SetMaskType(self, type):
528     """Set the type of mask to use.
529 jonathan 2551
530 jonathan 2562 type can be one of MASK_NONE, MASK_BIT, MASK_ALPHA
531    
532 jonathan 2551 If the state changes, a LAYER_CHANGED message is sent.
533     """
534 jonathan 2562 if type not in (self.MASK_NONE, self.MASK_BIT, self.MASK_ALPHA):
535     raise ValueError("type is invalid")
536    
537     if type != self.mask_type:
538     self.mask_type = type
539 jonathan 2551 self.changed(LAYER_CHANGED, self)
540    
541 jonathan 2587 def Opacity(self):
542     """Return the level of opacity used in alpha blending.
543 jonathan 2562 """
544 jonathan 2587 return self.opacity
545 jonathan 2562
546 jonathan 2587 def SetOpacity(self, op):
547 jonathan 2562 """Set the level of alpha opacity.
548    
549     0 <= op <= 1.
550    
551     The layer is fully opaque when op = 1.
552     """
553     if not (0 <= op <= 1):
554     raise ValueError("op out of range")
555    
556 jonathan 2587 if op != self.opacity:
557     self.opacity = op
558     self.changed(LAYER_CHANGED, self)
559 jonathan 2562
560 jonathan 2551 def ImageInfo(self):
561     return self.image_info
562    
563 jonathan 929 def TreeInfo(self):
564     items = []
565    
566 jonathan 1338 items.append(_("Filename: %s") % self.GetImageFilename())
567    
568 jonathan 929 if self.Visible():
569     items.append(_("Shown"))
570     else:
571     items.append(_("Hidden"))
572    
573     bbox = self.LatLongBoundingBox()
574     if bbox is not None:
575     items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
576     else:
577     items.append(_("Extent (lat-lon):"))
578    
579     if self.projection and len(self.projection.params) > 0:
580     items.append((_("Projection"),
581     [str(param) for param in self.projection.params]))
582    
583     return (_("Layer '%s'") % self.Title(), items)
584    

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26