1 |
# Copyright (c) 2001, 2002, 2003 by Intevation GmbH |
# Copyright (c) 2001, 2002, 2003, 2004, 2005 by Intevation GmbH |
2 |
# Authors: |
# Authors: |
3 |
# Bernhard Herzog <[email protected]> |
# Bernhard Herzog <[email protected]> |
4 |
# Jonathan Coles <[email protected]> |
# Jonathan Coles <[email protected]> |
5 |
|
# Silke Reimer <[email protected]> |
6 |
# |
# |
7 |
# This program is free software under the GPL (>=v2) |
# This program is free software under the GPL (>=v2) |
8 |
# Read the file COPYING coming with Thuban for details. |
# Read the file COPYING coming with Thuban for details. |
9 |
|
|
10 |
__version__ = "$Revision$" |
__version__ = "$Revision$" |
11 |
|
|
12 |
|
import os |
13 |
import warnings |
import warnings |
14 |
|
|
15 |
from Thuban import _ |
from Thuban import _ |
25 |
|
|
26 |
import resource |
import resource |
27 |
|
|
28 |
|
from color import Color |
29 |
|
|
30 |
shapetype_names = {SHAPETYPE_POINT: "Point", |
shapetype_names = {SHAPETYPE_POINT: "Point", |
31 |
SHAPETYPE_ARC: "Arc", |
SHAPETYPE_ARC: "Arc", |
56 |
self.issue(LAYER_VISIBILITY_CHANGED, self) |
self.issue(LAYER_VISIBILITY_CHANGED, self) |
57 |
|
|
58 |
def HasClassification(self): |
def HasClassification(self): |
59 |
"""Determine if this layer support classifications.""" |
"""Determine if this layer supports classifications.""" |
60 |
return False |
return False |
61 |
|
|
62 |
def HasShapes(self): |
def HasShapes(self): |
68 |
return self.projection |
return self.projection |
69 |
|
|
70 |
def SetProjection(self, projection): |
def SetProjection(self, projection): |
71 |
"""Set the layer's projection""" |
"""Set the layer's projection.""" |
72 |
self.projection = projection |
self.projection = projection |
73 |
self.changed(LAYER_PROJECTION_CHANGED, self) |
self.changed(LAYER_PROJECTION_CHANGED, self) |
74 |
|
|
75 |
|
def Type(self): |
76 |
|
return "Unknown" |
77 |
|
|
78 |
class Layer(BaseLayer): |
class Layer(BaseLayer): |
79 |
|
|
80 |
"""Represent the information of one geodata file (currently a shapefile) |
"""Represent the information of one geodata file (currently a shapefile) |
130 |
|
|
131 |
self.UnsetModified() |
self.UnsetModified() |
132 |
|
|
|
def __getattr__(self, attr): |
|
|
"""Access to some attributes for backwards compatibility |
|
|
|
|
|
The attributes implemented here are now held by the shapestore |
|
|
if at all. For backwards compatibility pretend that they are |
|
|
still there but issue a DeprecationWarning when they are |
|
|
accessed. |
|
|
""" |
|
|
if attr in ("table", "shapetable"): |
|
|
value = self.store.Table() |
|
|
elif attr == "shapefile": |
|
|
value = self.store.Shapefile() |
|
|
elif attr == "filename": |
|
|
value = self.store.FileName() |
|
|
else: |
|
|
raise AttributeError, attr |
|
|
warnings.warn("The Layer attribute %r is deprecated." |
|
|
" It's value can be accessed through the shapestore" |
|
|
% attr, DeprecationWarning, stacklevel = 2) |
|
|
return value |
|
|
|
|
133 |
def SetShapeStore(self, store): |
def SetShapeStore(self, store): |
134 |
# Set the classification to None if there is a classification |
# Set the classification to None if there is a classification |
135 |
# and the new shapestore doesn't have a table with a suitable |
# and the new shapestore doesn't have a table with a suitable |
171 |
Return None, if the layer doesn't contain any shapes. |
Return None, if the layer doesn't contain any shapes. |
172 |
""" |
""" |
173 |
bbox = self.BoundingBox() |
bbox = self.BoundingBox() |
174 |
if bbox is not None: |
if bbox is not None and self.projection is not None: |
175 |
llx, lly, urx, ury = bbox |
bbox = self.projection.InverseBBox(bbox) |
176 |
if self.projection is not None: |
return bbox |
177 |
llx, lly = self.projection.Inverse(llx, lly) |
|
178 |
urx, ury = self.projection.Inverse(urx, ury) |
def Type(self): |
179 |
return llx, lly, urx, ury |
return self.ShapeType(); |
|
else: |
|
|
return None |
|
180 |
|
|
181 |
def ShapesBoundingBox(self, shapes): |
def ShapesBoundingBox(self, shapes): |
182 |
"""Return a bounding box in lat/long coordinates for the given |
"""Return a bounding box in lat/long coordinates for the given |
187 |
|
|
188 |
if shapes is None or len(shapes) == 0: return None |
if shapes is None or len(shapes) == 0: return None |
189 |
|
|
190 |
llx = [] |
xs = [] |
191 |
lly = [] |
ys = [] |
|
urx = [] |
|
|
ury = [] |
|
|
|
|
|
if self.projection is not None: |
|
|
inverse = lambda x, y: self.projection.Inverse(x, y) |
|
|
else: |
|
|
inverse = lambda x, y: (x, y) |
|
192 |
|
|
193 |
for id in shapes: |
for id in shapes: |
194 |
left, bottom, right, top = self.Shape(id).compute_bbox() |
bbox = self.Shape(id).compute_bbox() |
195 |
|
if self.projection is not None: |
196 |
left, bottom = inverse(left, bottom) |
bbox = self.projection.InverseBBox(bbox) |
197 |
right, top = inverse(right, top) |
left, bottom, right, top = bbox |
198 |
|
xs.append(left); xs.append(right) |
199 |
|
ys.append(bottom); ys.append(top) |
200 |
|
|
201 |
llx.append(left) |
return (min(xs), min(ys), max(xs), max(ys)) |
|
lly.append(bottom) |
|
|
urx.append(right) |
|
|
ury.append(top) |
|
202 |
|
|
|
return (min(llx), min(lly), max(urx), max(ury)) |
|
203 |
|
|
204 |
def GetFieldType(self, fieldName): |
def GetFieldType(self, fieldName): |
205 |
if self.store: |
if self.store: |
227 |
"""Return the shape with index index""" |
"""Return the shape with index index""" |
228 |
return self.store.Shape(index) |
return self.store.Shape(index) |
229 |
|
|
230 |
def ShapesInRegion(self, box): |
def ShapesInRegion(self, bbox): |
231 |
"""Return the ids of the shapes that overlap the box. |
"""Return an iterable over the shapes that overlap the bounding box. |
232 |
|
|
233 |
Box is a tuple (left, bottom, right, top) in unprojected coordinates. |
The bbox parameter should be the bounding box as a tuple in the |
234 |
|
form (minx, miny, maxx, maxy) in unprojected coordinates. |
235 |
""" |
""" |
|
left, bottom, right, top = box |
|
|
|
|
236 |
if self.projection is not None: |
if self.projection is not None: |
237 |
left, bottom = self.projection.Forward(left, bottom) |
# Ensure that region lies within the layer's bounding box |
238 |
right, top = self.projection.Forward(right, top) |
# Otherwise projection of the region would lead to incorrect |
239 |
|
# values. |
240 |
return self.store.ShapesInRegion((left, bottom, right, top)) |
clipbbox = self.__mangle_bounding_box(bbox) |
241 |
|
bbox = self.projection.ForwardBBox(clipbbox) |
242 |
|
return self.store.ShapesInRegion(bbox) |
243 |
|
|
244 |
def GetClassificationColumn(self): |
def GetClassificationColumn(self): |
245 |
return self.classification_column |
return self.classification_column |
301 |
|
|
302 |
bbox = self.LatLongBoundingBox() |
bbox = self.LatLongBoundingBox() |
303 |
if bbox is not None: |
if bbox is not None: |
304 |
items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox) |
items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % tuple(bbox)) |
305 |
else: |
else: |
306 |
items.append(_("Extent (lat-lon):")) |
items.append(_("Extent (lat-lon):")) |
307 |
items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()]) |
items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()]) |
314 |
|
|
315 |
return (_("Layer '%s'") % self.Title(), items) |
return (_("Layer '%s'") % self.Title(), items) |
316 |
|
|
317 |
|
def __mangle_bounding_box(self, bbox): |
318 |
|
# FIXME: This method doesn't make much sense. |
319 |
|
# See RT #2845 which effectively says: |
320 |
|
# |
321 |
|
# If this method, which was originally called ClipBoundingBox, |
322 |
|
# is supposed to do clipping it shouldn't return the parameter |
323 |
|
# unchanged when it lies completely outside of the bounding box. |
324 |
|
# It would be better to return None and return an empty list in |
325 |
|
# ShapesInRegion (the only caller) in that case. |
326 |
|
# |
327 |
|
# This method was introduced to fix a bug that IIRC had |
328 |
|
# something todo with projections and bounding boxes containing |
329 |
|
# NaN or INF when the parameter to ShapesInRegion covered the |
330 |
|
# entire earth or something similarly large). |
331 |
|
bminx, bminy, bmaxx, bmaxy = bbox |
332 |
|
lminx, lminy, lmaxx, lmaxy = self.LatLongBoundingBox() |
333 |
|
if bminx > lmaxx or bmaxx < lminx: |
334 |
|
left, right = bminx, bmaxx |
335 |
|
else: |
336 |
|
left = max(lminx, bminx) |
337 |
|
right = min(lmaxx, bmaxx) |
338 |
|
if bminy > lmaxy or bmaxy < lminy: |
339 |
|
bottom, top = bminy, bmaxy |
340 |
|
else: |
341 |
|
bottom = max(lminy, bminy) |
342 |
|
top = min(lmaxy, bmaxy) |
343 |
|
|
344 |
|
return (left, bottom, right, top) |
345 |
|
|
346 |
|
|
347 |
if resource.has_gdal_support(): |
if resource.has_gdal_support(): |
348 |
import gdal |
import gdal |
350 |
|
|
351 |
class RasterLayer(BaseLayer): |
class RasterLayer(BaseLayer): |
352 |
|
|
353 |
def __init__(self, title, filename, projection = None, visible = True): |
MASK_NONE = 0 |
354 |
|
MASK_BIT = 1 |
355 |
|
MASK_ALPHA = 2 |
356 |
|
|
357 |
|
def __init__(self, title, filename, projection = None, |
358 |
|
visible = True, opacity = 1, masktype = MASK_BIT): |
359 |
"""Initialize the Raster Layer. |
"""Initialize the Raster Layer. |
360 |
|
|
361 |
title -- title for the layer. |
title -- title for the layer. |
374 |
BaseLayer.__init__(self, title, visible = visible) |
BaseLayer.__init__(self, title, visible = visible) |
375 |
|
|
376 |
self.projection = projection |
self.projection = projection |
377 |
self.filename = filename |
self.filename = os.path.abspath(filename) |
378 |
|
|
379 |
self.bbox = -1 |
self.bbox = -1 |
380 |
|
|
381 |
|
self.mask_type = masktype |
382 |
|
self.opacity = opacity |
383 |
|
|
384 |
|
self.image_info = None |
385 |
|
|
386 |
if resource.has_gdal_support(): |
if resource.has_gdal_support(): |
387 |
# |
# |
388 |
# temporarily open the file so that GDAL can test if it's valid. |
# temporarily open the file so that GDAL can test if it's valid. |
392 |
if dataset is None: |
if dataset is None: |
393 |
raise IOError() |
raise IOError() |
394 |
|
|
395 |
|
# |
396 |
|
# while we have the file, extract some basic information |
397 |
|
# that we can display later |
398 |
|
# |
399 |
|
self.image_info = {} |
400 |
|
|
401 |
|
self.image_info["nBands"] = dataset.RasterCount |
402 |
|
self.image_info["Size"] = (dataset.RasterXSize, dataset.RasterYSize) |
403 |
|
self.image_info["Driver"] = dataset.GetDriver().ShortName |
404 |
|
|
405 |
|
# store some information about the individual bands |
406 |
|
# [min_value, max_value] |
407 |
|
a = self.image_info["BandData"] = [] |
408 |
|
|
409 |
|
for i in range(1, dataset.RasterCount+1): |
410 |
|
band = dataset.GetRasterBand(i) |
411 |
|
a.append(band.ComputeRasterMinMax()) |
412 |
|
|
413 |
self.UnsetModified() |
self.UnsetModified() |
414 |
|
|
415 |
def BoundingBox(self): |
def BoundingBox(self): |
459 |
if bbox is None: |
if bbox is None: |
460 |
return None |
return None |
461 |
|
|
|
llx, lly, urx, ury = bbox |
|
462 |
if self.projection is not None: |
if self.projection is not None: |
463 |
llx, lly = self.projection.Inverse(llx, lly) |
bbox = self.projection.InverseBBox(bbox) |
464 |
urx, ury = self.projection.Inverse(urx, ury) |
|
465 |
|
return bbox |
466 |
|
|
467 |
return llx, lly, urx, ury |
def Type(self): |
468 |
|
return "Image" |
469 |
|
|
470 |
def GetImageFilename(self): |
def GetImageFilename(self): |
471 |
return self.filename |
return self.filename |
472 |
|
|
473 |
|
def MaskType(self): |
474 |
|
"""Return True if the mask should be used when rendering the layer.""" |
475 |
|
return self.mask_type |
476 |
|
|
477 |
|
def SetMaskType(self, type): |
478 |
|
"""Set the type of mask to use. |
479 |
|
|
480 |
|
type can be one of MASK_NONE, MASK_BIT, MASK_ALPHA |
481 |
|
|
482 |
|
If the state changes, a LAYER_CHANGED message is sent. |
483 |
|
""" |
484 |
|
if type not in (self.MASK_NONE, self.MASK_BIT, self.MASK_ALPHA): |
485 |
|
raise ValueError("type is invalid") |
486 |
|
|
487 |
|
if type != self.mask_type: |
488 |
|
self.mask_type = type |
489 |
|
self.changed(LAYER_CHANGED, self) |
490 |
|
|
491 |
|
def Opacity(self): |
492 |
|
"""Return the level of opacity used in alpha blending. |
493 |
|
""" |
494 |
|
return self.opacity |
495 |
|
|
496 |
|
def SetOpacity(self, op): |
497 |
|
"""Set the level of alpha opacity. |
498 |
|
|
499 |
|
0 <= op <= 1. |
500 |
|
|
501 |
|
The layer is fully opaque when op = 1. |
502 |
|
""" |
503 |
|
if not (0 <= op <= 1): |
504 |
|
raise ValueError("op out of range") |
505 |
|
|
506 |
|
if op != self.opacity: |
507 |
|
self.opacity = op |
508 |
|
self.changed(LAYER_CHANGED, self) |
509 |
|
|
510 |
|
def ImageInfo(self): |
511 |
|
return self.image_info |
512 |
|
|
513 |
def TreeInfo(self): |
def TreeInfo(self): |
514 |
items = [] |
items = [] |
515 |
|
|