File: map.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 (301 lines) | stat: -rw-r--r-- 10,301 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
# Copyright (c) 2001-2003, 2005 by Intevation GmbH
# Authors:
# Bernhard Herzog <bh@intevation.de>
# Jonathan Coles <jonathan@intevation.de>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with Thuban for details.

__version__ = "$Revision: 2569 $"

from messages import MAP_LAYERS_CHANGED, MAP_PROJECTION_CHANGED, \
     CHANGED, LAYER_PROJECTION_CHANGED, LAYER_LEGEND_CHANGED, \
     LAYER_VISIBILITY_CHANGED, LAYER_CHANGED, MAP_STACKING_CHANGED, \
     MAP_LAYERS_ADDED, MAP_LAYERS_REMOVED

from Thuban import _

from base import TitledObject, Modifiable

from label import LabelLayer


class Map(TitledObject, Modifiable):

    """Represent a map.

    A map is a list of layers. Additionally
    there is a special label layer containing all labels that
    are defined for the map.

    Map objects send the following message types:

        TITLE_CHANGED -- The title has changed. Parameter: the map.

        MAP_LAYERS_CHANGED -- Layers were added, removed or rearranged.
                        Parameters: the map

        MAP_PROJECTION_CHANGED -- the map's projection has changed.
                        Parameter: the map
    """

    forwarded_channels = (CHANGED,
                          LAYER_PROJECTION_CHANGED,
                          LAYER_LEGEND_CHANGED,
                          LAYER_CHANGED,
                          LAYER_VISIBILITY_CHANGED)

    def __init__(self, title, projection = None):
        """Initialize the map."""
        TitledObject.__init__(self, title)
        Modifiable.__init__(self)
        self.layers = []
        self.label_layer = LabelLayer(_("Labels"))
        self.label_layer.Subscribe(CHANGED, self.forward, MAP_LAYERS_CHANGED)
        self.projection = projection

    def Destroy(self):
        """Destroys the map object with all layers including the label layer.

        Calls Modifiable. Destroy first since it will call
        Publisher.Destroy which removes all subscriptions. Otherwise
        clearing the layers results in messages to be sent which can
        cause problems.
        """
        Modifiable.Destroy(self)
        self.ClearLayers()
        self.label_layer.Unsubscribe(CHANGED, self.forward, MAP_LAYERS_CHANGED)
        self.label_layer.Destroy()

    def AddLayer(self, layer):
        """Append layer to the map on top of all."""
        self.layers.append(layer)
        self.subscribe_layer_channels(layer)
        self.changed(MAP_LAYERS_CHANGED, self)
        self.changed(MAP_LAYERS_ADDED, self)

    def RemoveLayer(self, layer):
        """Remove layer from the map.

        This can not be applied for the label layer of the map.
        """
        self.unsubscribe_layer_channels(layer)
        self.layers.remove(layer)
        self.changed(MAP_LAYERS_CHANGED, self)
        self.changed(MAP_LAYERS_REMOVED, self)
        layer.Destroy()

    def CanRemoveLayer(self, layer):
        """Return true if the layer can be deleted.

        The default implementation always returns 1. Derived classes
        should override this method if they have e.g. special layers
        that the user should not be able to remove.
        """
        return 1

    def ClearLayers(self):
        """Delete all layers and also remove all labels from the label layer.
        """
        for layer in self.layers:
            self.unsubscribe_layer_channels(layer)
            layer.Destroy()
        del self.layers[:]
        self.label_layer.ClearLabels()
        self.changed(MAP_LAYERS_CHANGED, self)
        self.changed(MAP_LAYERS_REMOVED, self)

    def subscribe_layer_channels(self, layer):
        """Subscribe to some of layer's channels."""
        for channel in self.forwarded_channels:
            layer.Subscribe(channel, self.forward, channel)

    def unsubscribe_layer_channels(self, layer):
        """Unsubscribe to some of layer's channels."""
        for channel in self.forwarded_channels:
            layer.Unsubscribe(channel, self.forward, channel)

    def LabelLayer(self):
        """Return the Map's label layer"""
        return self.label_layer

    def Layers(self):
        """Return the list of layers contained in the map.

        The list does not include the label layer which
        can be retrieved by a separate method."""
        return self.layers

    def HasLayers(self):
        """Information whether this map has layers.

        Returns true if the map has at least one layer other
        than the label layer."""
        return len(self.layers) > 0

    def MoveLayerToTop(self, layer):
        """Put the layer on top of the layer stack.

        This can not be applied to the label layer.

        If the layer is already at the top do nothing. If the stacking
        order has been changed, issue a MAP_LAYERS_CHANGED message.
        """
        index = self.layers.index(layer)
        if index < len(self.layers) - 1:
            del self.layers[index]
            self.layers.append(layer)
            self.changed(MAP_LAYERS_CHANGED, self)
            self.changed(MAP_STACKING_CHANGED, self)

    def RaiseLayer(self, layer):
        """Swap the layer with the one above it.

        This does not apply to the label layer.

        If the layer is already at the top do nothing. If the stacking
        order has been changed, issue a MAP_LAYERS_CHANGED message.
        """
        index = self.layers.index(layer)
        if index < len(self.layers) - 1:
            del self.layers[index]
            self.layers.insert(index + 1, layer)
            self.changed(MAP_LAYERS_CHANGED, self)
            self.changed(MAP_STACKING_CHANGED, self)

    def LowerLayer(self, layer):
        """Swap the layer with the one below it.

        This does not apply to the label layer.

        If the layer is already at the bottom do nothing. If the
        stacking order has been changed, issue a MAP_LAYERS_CHANGED message.
        """
        index = self.layers.index(layer)
        if index > 0:
            del self.layers[index]
            self.layers.insert(index - 1, layer)
            self.changed(MAP_LAYERS_CHANGED, self)
            self.changed(MAP_STACKING_CHANGED, self)

    def MoveLayerToBottom(self, layer):
        """Put the layer at the bottom of the stack.

        This does not apply to the label layer.

        If the layer is already at the bottom do nothing. If the
        stacking order has been changed, issue a MAP_LAYERS_CHANGED message.
        """
        index = self.layers.index(layer)
        if index > 0:
            del self.layers[index]
            self.layers.insert(0, layer)
            self.changed(MAP_LAYERS_CHANGED, self)
            self.changed(MAP_STACKING_CHANGED, self)

    def BoundingBox(self):
        """Return the bounding box of the map in Lat/Lon coordinates.

        The label layer is not considered for the computation of the
        bounding box.

        Return None if there are no layers (except the label layer) or
        no layer contains any shapes.
        """
        if not self.layers:
            return None
        llx = []
        lly = []
        urx = []
        ury = []
        for layer in self.layers:
            # the layer's bbox may be None if it doesn't have any shapes
            bbox = layer.LatLongBoundingBox()
            if bbox is not None:
                left, bottom, right, top = bbox
                llx.append(left)
                lly.append(bottom)
                urx.append(right)
                ury.append(top)

        # check whether there were any empty layers.
        if llx:
            return (min(llx), min(lly), max(urx), max(ury))
        else:
            return None

    def ProjectedBoundingBox(self):
        """Return the bounding box of the map in projected coordinates.

        The label layer is not considered for the computation of the
        bounding box.

        Return None if there are no layers (except the label layer) or
        no layer contains any shapes.
        """
        # This simply returns the rectangle given by the projected
        # corners of the non-projected bbox.
        bbox = self.BoundingBox()
        if bbox is not None and self.projection is not None:
            bbox = self.projection.ForwardBBox(bbox)
        return bbox

    def GetProjection(self):
        """Return the projection of the map."""
        return self.projection

    def SetProjection(self, projection):
        """Set the projection of the map.

        Issue a MAP_PROJECTION_CHANGED message.
        """
        old_proj = self.projection
        self.projection = projection
        self.changed(MAP_PROJECTION_CHANGED, self, old_proj)

    def forward(self, *args):
        """Reissue events"""
        if len(args) > 1:
            args = (args[-1],) + args[:-1]
        apply(self.issue, args)

    def WasModified(self):
        """Return true if the map or one of the layers was modified"""
        if self.modified:
            return 1
        else:
            for layer in self.layers:
                if layer.WasModified():
                    return 1
            return self.label_layer.WasModified()

    def UnsetModified(self):
        """Unset the modified flag of the map and the layers"""
        Modifiable.UnsetModified(self)
        for layer in self.layers:
            layer.UnsetModified()
        self.label_layer.UnsetModified()

    def TreeInfo(self):
        """Return a description of the object.

        A tuple of (title, tupel) describing the contents
        of the object in a tree-structure is returned.
        """
        items = []
        if self.BoundingBox() != None:
            items.append(_("Extent (lat-lon): (%g, %g, %g, %g)")
                         % self.BoundingBox())
            if self.projection and len(self.projection.params) > 0:
                items.append(_("Extent (projected): (%g, %g, %g, %g)")
                             % self.ProjectedBoundingBox())
                items.append((_("Projection"),
                              [str(param)
                               for param in self.projection.params]))

        layers = self.layers[:]
        layers.reverse()
        items.extend(layers)
        items.append(self.label_layer)

        return (_("Map: %s") % self.title, items)