1 |
joey |
2175 |
# Copyright (c) 2003, 2004 by Intevation GmbH |
2 |
|
|
# Authors: |
3 |
|
|
# Jan-Oliver Wagner <[email protected]> |
4 |
|
|
# Martin Schulze <[email protected]> |
5 |
|
|
# |
6 |
|
|
# This program is free software; you can redistribute it and/or modify |
7 |
|
|
# it under the terms of the GNU General Public License as published by |
8 |
|
|
# the Free Software Foundation; either version 2 of the License, or |
9 |
|
|
# (at your option) any later version. |
10 |
|
|
# |
11 |
|
|
# This program is distributed in the hope that it will be useful, |
12 |
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 |
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 |
|
|
# GNU General Public License for more details. |
15 |
|
|
# |
16 |
|
|
# You should have received a copy of the GNU General Public License |
17 |
|
|
# along with this program; if not, write to the Free Software |
18 |
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
19 |
|
|
|
20 |
|
|
""" |
21 |
|
|
Graphic Layer via OGC WMS. |
22 |
|
|
|
23 |
|
|
class WMSLayer: |
24 |
|
|
__init__() |
25 |
|
|
|
26 |
joey |
2177 |
LatLongBoundingBox() |
27 |
|
|
BoundingBox() |
28 |
joey |
2175 |
|
29 |
joey |
2177 |
getFormat(format) |
30 |
|
|
calcFormat(formats) |
31 |
|
|
|
32 |
joey |
2178 |
getFormats() |
33 |
|
|
getLayers() |
34 |
|
|
getLayerTitle() |
35 |
|
|
|
36 |
|
|
getWMSFormat() |
37 |
|
|
setWMSFormat(format) |
38 |
|
|
|
39 |
joey |
2177 |
GetMapImg(width, height, bbox) |
40 |
|
|
|
41 |
joey |
2175 |
Requirements: |
42 |
|
|
- PyOGCLib <http://www.sourceforge.net/projects/pyogclib> |
43 |
|
|
|
44 |
|
|
Requires the ogclib installed regularily on the system or checked out |
45 |
|
|
next to the Thuban checkout. Or set the PYTHONPATH to the PyOGCLib |
46 |
|
|
directory before starting Thuban. |
47 |
|
|
|
48 |
|
|
""" |
49 |
|
|
|
50 |
|
|
__version__ = "$Revision$" |
51 |
|
|
# $Source$ |
52 |
|
|
# $Id$ |
53 |
|
|
|
54 |
|
|
|
55 |
|
|
from Thuban.Model.layer import BaseLayer |
56 |
|
|
from Thuban.Model.resource import get_system_proj_file, EPSG_PROJ_FILE, \ |
57 |
|
|
EPSG_DEPRECATED_PROJ_FILE |
58 |
|
|
from Thuban.UI.common import ThubanBeginBusyCursor, ThubanEndBusyCursor |
59 |
|
|
|
60 |
|
|
from capabilities import WMSCapabilities |
61 |
|
|
|
62 |
|
|
from ogclib.WMSClient import WMSClient |
63 |
|
|
|
64 |
|
|
|
65 |
|
|
def epsg_code_to_projection(epsg): |
66 |
|
|
"""Find the projection for the given epsg code. |
67 |
|
|
|
68 |
|
|
epsg -- EPSG code as string |
69 |
|
|
""" |
70 |
|
|
proj_file, warnings = get_system_proj_file(EPSG_PROJ_FILE) |
71 |
|
|
|
72 |
|
|
for proj in proj_file.GetProjections(): |
73 |
|
|
if proj.EPSGCode() == epsg: |
74 |
|
|
return proj |
75 |
|
|
proj_file, warnings = get_system_proj_file(EPSG_DEPRECATED_PROJ_FILE) |
76 |
|
|
for proj in proj_file.GetProjections(): |
77 |
|
|
if proj.EPSGCode() == epsg: |
78 |
|
|
return proj |
79 |
|
|
return None |
80 |
|
|
|
81 |
|
|
|
82 |
|
|
class WMSLayer(BaseLayer): |
83 |
joey |
2177 |
""" |
84 |
|
|
WMS Layer |
85 |
joey |
2175 |
|
86 |
joey |
2177 |
This layer incorporates all methods from the Thuban BaseLayer and |
87 |
|
|
adds specific methods for operating with a WMS server. |
88 |
|
|
""" |
89 |
|
|
|
90 |
joey |
2175 |
def __init__(self, title, url): |
91 |
|
|
"""Initializes the WMSLayer. |
92 |
|
|
|
93 |
|
|
title -- Title of this layer. |
94 |
|
|
url -- URL of the WMS-Server wich must contain '?' |
95 |
|
|
|
96 |
|
|
If an error occured, self.error_msg is a string describing |
97 |
|
|
the problem(s). Else, self.error_msg is None. |
98 |
|
|
""" |
99 |
|
|
BaseLayer.__init__(self, title, visible = True, projection = None) |
100 |
|
|
self.url = url |
101 |
|
|
self.bbox = None |
102 |
|
|
self.latlonbbox = None |
103 |
|
|
self.error_msg = None |
104 |
joey |
2179 |
self.wms_layers = [] |
105 |
joey |
2175 |
self.capabilities = None |
106 |
|
|
|
107 |
|
|
# Change the cursor to demonstrate that we're busy but working |
108 |
|
|
ThubanBeginBusyCursor() |
109 |
|
|
self.capabilities = WMSCapabilities(url) |
110 |
|
|
ThubanEndBusyCursor() |
111 |
|
|
|
112 |
|
|
# name of the top layer of the remote map |
113 |
|
|
foo = self.capabilities.getLayers() |
114 |
|
|
if len(foo) == 0: |
115 |
|
|
self.error_msg = _('No layers found in remote resource:\n'\ |
116 |
|
|
'%s') % url |
117 |
|
|
return |
118 |
|
|
top_layer = foo[0] |
119 |
joey |
2179 |
self.wms_layers = [top_layer] |
120 |
joey |
2175 |
|
121 |
|
|
# first projection of the top layer |
122 |
|
|
foo = self.capabilities.getLayerSRS(top_layer) |
123 |
|
|
if len(foo) == 0: |
124 |
|
|
self.error_msg = _('No LatLonBoundingBox found for top layer %s')\ |
125 |
|
|
% top_layer |
126 |
|
|
return |
127 |
|
|
top_srs = foo[0] |
128 |
|
|
|
129 |
|
|
# BoundingBox of the top layer |
130 |
|
|
bbox = self.capabilities.getLayerBBox(top_layer, top_srs) |
131 |
|
|
if len(bbox) == 0: |
132 |
|
|
self.error_msg = _('No BoundingBox found for layer %s and EPSG:')\ |
133 |
|
|
% (top_layer, top_srs) |
134 |
|
|
return |
135 |
|
|
self.bbox = (float(bbox['minx']), |
136 |
|
|
float(bbox['miny']), |
137 |
|
|
float(bbox['maxx']), |
138 |
|
|
float(bbox['maxy'])) |
139 |
|
|
|
140 |
|
|
# LatLonBox of the top layer |
141 |
|
|
bbox = self.capabilities.getLayerLatLonBBox(top_layer) |
142 |
|
|
self.latlonbbox = (float(bbox['minx']), |
143 |
|
|
float(bbox['miny']), |
144 |
|
|
float(bbox['maxx']), |
145 |
|
|
float(bbox['maxy'])) |
146 |
|
|
|
147 |
|
|
# get projection |
148 |
|
|
p = epsg_code_to_projection(top_srs) |
149 |
|
|
self.SetProjection(p) |
150 |
|
|
|
151 |
|
|
if p is None: |
152 |
|
|
self.error_msg = _('EPSG projection code %s not found!\n'\ |
153 |
|
|
'Setting projection to "None".\n'\ |
154 |
|
|
'Please set an appropriate projection yourself.'\ |
155 |
|
|
% top_srs) |
156 |
|
|
|
157 |
|
|
# pre-determine the used format |
158 |
|
|
self.wmsformat, self.format = \ |
159 |
|
|
self.calcFormat(self.capabilities.getFormats()) |
160 |
|
|
if self.wmsformat is None: |
161 |
|
|
self.error_msg = \ |
162 |
|
|
_('No supported image format found in remote resource') |
163 |
|
|
return |
164 |
|
|
|
165 |
|
|
# get and set the title |
166 |
|
|
self.SetTitle(self.capabilities.getTitle().encode('latin1', 'replace')) |
167 |
|
|
|
168 |
|
|
|
169 |
|
|
def LatLongBoundingBox(self): |
170 |
|
|
""" |
171 |
joey |
2177 |
Return the layer's bounding box in lat-lon |
172 |
|
|
""" |
173 |
joey |
2175 |
return self.latlonbbox |
174 |
|
|
|
175 |
joey |
2177 |
|
176 |
joey |
2175 |
def BoundingBox(self): |
177 |
|
|
""" |
178 |
joey |
2177 |
Return the layer's bounding box in the intrinsic coordinate system |
179 |
|
|
""" |
180 |
joey |
2175 |
return self.bbox |
181 |
|
|
|
182 |
|
|
|
183 |
|
|
def getFormat(self, format): |
184 |
|
|
""" |
185 |
|
|
Return the image format for the render engine |
186 |
|
|
|
187 |
|
|
format -- format as returned by the WMS server |
188 |
|
|
|
189 |
joey |
2177 |
If no mapping was found, None is returned. |
190 |
joey |
2175 |
|
191 |
joey |
2177 |
This routine uses a simple heuristic in order to find the |
192 |
|
|
broken down image format to be used with the internal render |
193 |
|
|
engine. |
194 |
|
|
|
195 |
joey |
2175 |
An exception rule is implemented in order to not accept |
196 |
|
|
image/wbmp or WBMP which refers to WAP bitmap format and is |
197 |
|
|
not supported by the included render engine. |
198 |
|
|
""" |
199 |
|
|
fmap = {'png' : "PNG", |
200 |
|
|
'jpeg': "JPEG", |
201 |
|
|
'jpg' : "JPEG", |
202 |
|
|
'tif' : "TIFF", |
203 |
|
|
'gif' : "GIF", |
204 |
|
|
'wbmp': None, |
205 |
|
|
'bmp' : "BMP"} |
206 |
|
|
|
207 |
|
|
for f in fmap.keys(): |
208 |
|
|
if format.lower().find(f) > -1: |
209 |
|
|
return fmap[f] |
210 |
|
|
return None |
211 |
|
|
|
212 |
|
|
|
213 |
|
|
def calcFormat(self, formats): |
214 |
|
|
""" |
215 |
|
|
Calculate the preferred image format |
216 |
|
|
|
217 |
|
|
formats -- list of formates as returned by the WMS server |
218 |
|
|
|
219 |
|
|
The following priority is used: |
220 |
|
|
- PNG |
221 |
|
|
- JPEG |
222 |
|
|
- TIFF |
223 |
|
|
- GIF |
224 |
|
|
- BMP |
225 |
|
|
|
226 |
|
|
If no matching format was found, None, None will be returned. |
227 |
|
|
|
228 |
|
|
An exception rule is implemented in order to not accept |
229 |
|
|
image/wbmp or WBMP which refers to WAP bitmap format and is |
230 |
|
|
not supported by the included render engine. |
231 |
|
|
""" |
232 |
joey |
2438 |
prio = ['png', 'gif', 'jpeg', 'bmp'] |
233 |
joey |
2175 |
for p in prio: |
234 |
|
|
for f in formats: |
235 |
|
|
if f.lower().find(p) > -1: |
236 |
|
|
if f.lower().find('wbmp') == -1: |
237 |
|
|
return f, self.getFormat(f) |
238 |
|
|
return None, None |
239 |
|
|
|
240 |
|
|
|
241 |
joey |
2178 |
def getFormats(self): |
242 |
|
|
""" |
243 |
|
|
Return the list of supported image formats by the WMS server |
244 |
|
|
|
245 |
|
|
These formats may be used in the WMS GetMap request. Data is |
246 |
|
|
retrieved from the included WMSCapabilities object. |
247 |
|
|
|
248 |
|
|
The called method from WMSCapabilities will default to |
249 |
|
|
'image/jpeg' if no format is recognised in XML Capabilities, |
250 |
|
|
assuming that JPEG will always be supported on the server side |
251 |
|
|
with this encoding. |
252 |
|
|
""" |
253 |
|
|
return self.capabilities.getFormats() |
254 |
|
|
|
255 |
|
|
|
256 |
|
|
def getLayers(self): |
257 |
|
|
""" |
258 |
|
|
Return the list of layer names supported by the WMS server |
259 |
|
|
|
260 |
|
|
Data is retrieved from the included WMSCapabilities object. |
261 |
|
|
|
262 |
|
|
Only named layers will be returned, since a layer may have a |
263 |
|
|
title but doesn't have to have a name associated to it as |
264 |
|
|
well. If no layers were found, an empty list is returned. |
265 |
|
|
""" |
266 |
|
|
return self.capabilities.getLayers() |
267 |
|
|
|
268 |
|
|
|
269 |
|
|
def getLayerTitle(self, layer): |
270 |
|
|
""" |
271 |
|
|
Return the title of the named layer |
272 |
|
|
|
273 |
|
|
Data is retrieved from the included WMSCapabilities object. |
274 |
|
|
|
275 |
|
|
If no such title or no such layer exists, an empty string is |
276 |
|
|
returned. |
277 |
|
|
""" |
278 |
|
|
return self.capabilities.getLayerTitle(layer) |
279 |
|
|
|
280 |
|
|
|
281 |
|
|
def getWMSFormat(self): |
282 |
|
|
""" |
283 |
|
|
Return the image format that is used for WMS GetMap requests |
284 |
|
|
""" |
285 |
|
|
return self.wmsformat |
286 |
|
|
|
287 |
|
|
|
288 |
|
|
def setWMSFormat(self, format): |
289 |
|
|
""" |
290 |
|
|
Set the image format that is used for WMS GetMap requests |
291 |
|
|
|
292 |
|
|
format -- format, one of getFormats() |
293 |
|
|
""" |
294 |
|
|
self.wmsformat = format |
295 |
joey |
2181 |
self.format = self.getFormat(format) |
296 |
joey |
2178 |
|
297 |
|
|
|
298 |
joey |
2179 |
def getVisibleLayers(self): |
299 |
|
|
""" |
300 |
|
|
Return the list of names for all visible layers |
301 |
|
|
|
302 |
|
|
""" |
303 |
|
|
return self.wms_layers |
304 |
|
|
|
305 |
|
|
|
306 |
|
|
def setVisibleLayers(self, layers): |
307 |
|
|
""" |
308 |
|
|
Set the list of names for all visible layers |
309 |
|
|
|
310 |
|
|
""" |
311 |
|
|
self.wms_layers = layers |
312 |
|
|
|
313 |
|
|
|
314 |
joey |
2175 |
def GetMapImg(self, width, height, bbox): |
315 |
joey |
2177 |
""" |
316 |
|
|
Retrieve a new map from the WMS server and return it |
317 |
|
|
|
318 |
|
|
width -- width in pixel of the desired image |
319 |
|
|
height -- height in pixel of the desired image |
320 |
|
|
bbox -- array of min(x,y) max(x,y) in the given SRS |
321 |
|
|
|
322 |
|
|
SRS and used image format will be retrieved from within the |
323 |
|
|
layer itself. |
324 |
|
|
""" |
325 |
joey |
2175 |
bbox_dict = { 'minx': bbox[0], 'miny': bbox[1], |
326 |
|
|
'maxx': bbox[2], 'maxy': bbox[3] } |
327 |
|
|
|
328 |
|
|
# Change the cursor to demonstrate that we're busy but working |
329 |
|
|
ThubanBeginBusyCursor() |
330 |
|
|
|
331 |
|
|
wmsclient = WMSClient() |
332 |
|
|
|
333 |
|
|
epsg_id = int(self.GetProjection().EPSGCode()) |
334 |
|
|
|
335 |
|
|
wms_response = wmsclient.getMap(self.url, self.wmsformat, width, height, |
336 |
|
|
epsg_id, bbox_dict, |
337 |
joey |
2179 |
self.wms_layers, version = self.capabilities.getVersion()) |
338 |
joey |
2175 |
ThubanEndBusyCursor() |
339 |
|
|
return wms_response, self.format |