File: layer.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 (359 lines) | stat: -rw-r--r-- 11,451 bytes parent folder | download | duplicates (2)
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
# Copyright (c) 2003, 2004, 2007, 2008 by Intevation GmbH
# Authors:
# Didrik Pinte <dpinte@dipole-consulting.com>
# Jan-Oliver Wagner <jan@intevation.de>
# Martin Schulze <joey@infodrom.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

"""
Graphic Layer via OGC WMS.

class WMSLayer:
    __init__()

    LatLongBoundingBox()
    BoundingBox()

    getFormat(format)
    calcFormat(formats)

    getFormats()
    getLayers()
    getLayerTitle()

    getWMSFormat()
    setWMSFormat(format)

    GetMapImg(width, height, bbox)

Requirements:
    - OWSLib <http://trac.gispython.org/projects/PCL/wiki/OwsLib>

Requires the owslib installed regularily on the system or checked out
next to the Thuban checkout.  Or set the PYTHONPATH to the OWSLib
directory before starting Thuban.

"""

__version__ = "$Revision: 2868 $"
# $Source$
# $Id: layer.py 2868 2008-12-08 08:50:53Z dpinte $


from Thuban import internal_from_unicode, _
from Thuban.Model.layer import BaseLayer
from Thuban.Model.resource import get_system_proj_file, EPSG_PROJ_FILE, \
     EPSG_DEPRECATED_PROJ_FILE
from Thuban.UI.common import ThubanBeginBusyCursor, ThubanEndBusyCursor

from owslib.wms import WebMapService

def epsg_code_to_projection(epsg):
    """Find the projection for the given epsg code.

    epsg -- EPSG code as string
    """
    proj_file, warnings = get_system_proj_file(EPSG_PROJ_FILE)

    for proj in proj_file.GetProjections():
        if proj.EPSGCode() == epsg:
            return proj
    proj_file, warnings = get_system_proj_file(EPSG_DEPRECATED_PROJ_FILE)
    for proj in proj_file.GetProjections():
        if proj.EPSGCode() == epsg:
            return proj
    return None


class WMSLayer(BaseLayer):
    """
    WMS Layer

    This layer incorporates all methods from the Thuban BaseLayer and
    adds specific methods for operating with a WMS server.
    """

    def __init__(self, title, url):
        """Initializes the WMSLayer.

        title -- Title of this layer.
        url -- URL of the WMS-Server wich must contain '?'

        If an error occurred, self.error_msg is a string describing
        the problem(s). Else, self.error_msg is None.
        """
        BaseLayer.__init__(self, title, visible = True, projection = None)
        self.url = url
        self.bbox = None
        self.latlonbbox = None
        self.error_msg = None
        self.wms_layers = []
        self.capabilities = None
        self.cached_bbox = None

        # Change the cursor to demonstrate that we're busy but working
        ThubanBeginBusyCursor()
        self.wmsserver = WebMapService(url, version="1.1.1")
        self.capabilities = [op.name for op in self.wmsserver.operations]
        ThubanEndBusyCursor()

        # name of the top layer of the remote map
        layers = list(self.wmsserver.contents)                   
        if len(layers) == 0:
            self.error_msg = _('No layers found in remote resource:\n'\
                               '%s') % url
            return
        top_layer = layers[1]
        self.wms_layers = [top_layer]

        # first projection of the top layer
        crs = self.wmsserver[top_layer].crsOptions
        if len(crs) == 0:
            self.error_msg = _('No LatLonBoundingBox found for top layer %s')\
                             % top_layer
            return
        # extract only the EPSG code from the EPSG string 
        # received 'EPSG:4326' but keep only '4326'
        top_srs = crs[0].split(':')[1]

        # LatLonBox of the top layer
        bbox = self.wmsserver.contents[top_layer].boundingBoxWGS84
        self.latlonbbox = (float(bbox[0]),
                     float(bbox[1]),
                     float(bbox[2]),
                     float(bbox[3])) 
        
        # BoundingBox of the top layer
        # Do we really need to know the bbox not in WGS84 ? 
        bbox = self.wmsserver.contents[top_layer].boundingBox
        if bbox is None or len(bbox) == 0:
        #    self.error_msg = _('No BoundingBox found for layer %s and EPSG %s')\
        #                     % (top_layer, top_srs)
            self.bbox = None
        else :
            self.bbox = (float(bbox[0]),
                     float(bbox[1]),
                     float(bbox[2]),
                     float(bbox[3]))
        if self.bbox is None:
            self.bbox =self.latlonbbox

        # get projection
        p = epsg_code_to_projection(top_srs)
        self.SetProjection(p)

        if p is None:
            self.error_msg = _('EPSG projection code %s not found!\n'\
                               'Setting projection to "None".\n'\
                               'Please set an appropriate projection yourself.'\
                               % top_srs)

        # pre-determine the used format
        self.wmsformat, self.format = \
        self.calcFormat(self.wmsserver.getOperationByName('GetMap').formatOptions)
        if self.wmsformat is None:
            self.error_msg = \
                _('No supported image format found in remote resource')
            return

        # get and set the title
        self.SetTitle(internal_from_unicode(self.wmsserver.identification.title))


    def LatLongBoundingBox(self):
        """
        Return the layer's bounding box in lat-lon
        """
        return self.latlonbbox


    def BoundingBox(self):
        """
        Return the layer's bounding box in the intrinsic coordinate system
        """
        return self.bbox


    def getFormat(self, format):
        """
        Return the image format for the render engine

        format -- format as returned by the WMS server

        If no mapping was found, None is returned.

        This routine uses a simple heuristic in order to find the
        broken down image format to be used with the internal render
        engine.

        An exception rule is implemented in order to not accept
        image/wbmp or WBMP which refers to WAP bitmap format and is
        not supported by the included render engine.
        """
        fmap = {'png' : "PNG",
                'jpeg': "JPEG",
                'jpg' : "JPEG",
                'tif' : "TIFF",
                'gif' : "GIF",
                'wbmp': None,
                'bmp' : "BMP"}

        for f in fmap.keys():
            if format.lower().find(f) > -1:
                    return fmap[f]
        return None

        
    def calcFormat(self, formats):
        """
        Calculate the preferred image format

        formats -- list of formates as returned by the WMS server

        The following priority is used:
        - PNG
        - JPEG
        - TIFF
        - GIF
        - BMP

        If no matching format was found, None, None will be returned.

        An exception rule is implemented in order to not accept
        image/wbmp or WBMP which refers to WAP bitmap format and is
        not supported by the included render engine.
        """
        prio = ['png', 'gif', 'jpeg', 'bmp']
        for p in prio:
            for f in formats:
                if f.lower().find(p) > -1:
                    if f.lower().find('wbmp') == -1:
                        return f, self.getFormat(f)
        return None, None
        

    def getFormats(self):
        """
        Return the list of supported image formats by the WMS server

        These formats may be used in the WMS GetMap request.  Data is
        retrieved from the included WMSCapabilities object.

        The called method from WMSCapabilities will default to
        'image/jpeg' if no format is recognised in XML Capabilities,
        assuming that JPEG will always be supported on the server side
        with this encoding.
        """
        return self.wmsserver.getOperationByName('GetMap').formatOptions


    def getLayers(self):
        """
        Return the list of layer names supported by the WMS server

        Data is retrieved from the included WMSCapabilities object.

        Only named layers will be returned, since a layer may have a
        title but doesn't have to have a name associated to it as
        well.  If no layers were found, an empty list is returned.
        """
        return list(self.wmsserver.contents)


    def getLayerTitle(self, layer):
        """
        Return the title of the named layer

        Data is retrieved from the included WMSCapabilities object.

        If no such title or no such layer exists, an empty string is
        returned.
        """
        return self.wmsserver[layer].title


    def getWMSFormat(self):
        """
        Return the image format that is used for WMS GetMap requests
        """
        return self.wmsformat


    def setWMSFormat(self, format):
        """
        Set the image format that is used for WMS GetMap requests

        format -- format, one of getFormats()
        """
        self.wmsformat = format
        self.format = self.getFormat(format)


    def getVisibleLayers(self):
        """
        Return the list of names for all visible layers

        """
        return self.wms_layers


    def setVisibleLayers(self, layers):
        """
        Set the list of names for all visible layers

        """
        self.wms_layers = layers


    def GetMapImg(self, width, height, bbox):
        """
        Retrieve a new map from the WMS server and return it

        width -- width in pixel of the desired image
        height -- height in pixel of the desired image
        bbox -- array of min(x,y) max(x,y) in the given SRS

        SRS and used image format will be retrieved from within the
        layer itself.
        """
        bbox_dict = { 'minx': bbox[0], 'miny': bbox[1],
                      'maxx': bbox[2], 'maxy': bbox[3] }

        if self.cached_bbox is not None:
            for i in xrange(4):
                if self.cached_bbox[i] != bbox[i]:
                    break
            else:
                if self.cached_response is not None:
                    return self.cached_response, self.format
        self.cached_bbox = bbox
        # Change the cursor to demonstrate that we're busy but working
        ThubanBeginBusyCursor()

        epsg_id = int(self.GetProjection().EPSGCode())

        wms_response = self.wmsserver.getmap(layers=self.wms_layers, 
                                             styles=None, 
                                             srs="EPSG:%s" % epsg_id, 
                                             bbox=bbox, 
                                             size=(width, height), 
                                             format=self.wmsformat, 
                                             transparent=False)
        self.cached_response = wms_response.read()
        
        ThubanEndBusyCursor()
        
        return self.cached_response , self.format