source: TI05-delivery/ows_framework/branches/ows_framework-refactor/ows_common/ows_common/service/imps/wms_csmllayer.py @ 3689

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI05-delivery/ows_framework/branches/ows_framework-refactor/ows_common/ows_common/service/imps/wms_csmllayer.py@3689
Revision 3689, 10.0 KB checked in by spascoe, 11 years ago (diff)

A quick fix to csml wms support to provide EX_GeographicBoundingBox. Almost certainly not how we want to do it in the end.

Line 
1"""
2implementation of ILayerMapper, ILayer, IDimension, ILayerSlab interfaces, as defined in wms_iface.py
3
4"""
5import os, string
6import csml
7try:
8    import cdms2 as cdms
9except:
10    import cdms
11import Image
12from copy import copy
13from pywms.render_imp import RGBARenderer
14from matplotlib import cm
15import genutil
16from pylons import config  #config must have tmpfilebuffer and csmlstore values
17import cdtime
18
19
20class CSMLLayerMapper(object):
21    """
22    Map keyword arguments to a collection of layers.
23    Supports the retrieval of sets of layers according to arbitary
24    keyword/value pairs.
25    Implements  ILayerMapper
26   
27    """
28   
29    def _getInfo(self, feature):
30        ''' given a csml feature, return info about the layer/feature
31        @return:   title, abstract, dimensions, units, crss '''
32
33        try:
34            title=feature.name.CONTENT
35        except:
36            title=''
37        try:
38            abstract=feature.description.CONTENT
39        except:
40            abstract=title
41       
42        units=feature.getDomainUnits()
43        dimensions={}
44        tmpunits=copy(units)
45        tmpunits.reverse()
46        domain = feature.getDomain()
47        for dim in feature.getAxisLabels():
48            nextdim=CSMLDimension(domain, dim, tmpunits.pop())
49            if dim not in ['latitude', 'longitude']:
50                dimensions[dim]=nextdim
51        crs=feature.getNativeCRS()
52        crss=[self._crscat.getCRS(crs).twoD]
53        return title, abstract, dimensions, units, crss
54   
55    def map(self, **kwargs):
56        """
57        Given csml.parser.Dataset object list the names of
58        all layers available.
59       
60        @return: A mapping of layer names to ILayer implementations.
61        @raise ValueError: If no layers are available for these keywords.
62        """
63        fileoruri=kwargs['fileoruri']
64       
65        #TODO - handle file paths/directories URIs in config
66        #.xml or .csml extensions are supported:
67        filename='%s/%s.csml'%(config['csmlstore'],fileoruri)
68        if not os.path.exists(filename):
69           filename='%s/%s.xml'%(config['csmlstore'],fileoruri)
70        if not os.path.exists(filename):
71            raise Exception(str('CSML File could not be found: %s')%filename)
72           
73        ds=csml.parser.Dataset(filename)
74           
75        layermap={}
76        self._crscat=csml.csmllibs.csmlcrs.CRSCatalogue()
77        for feature in csml.csmllibs.csmlextra.listify(ds.featureCollection.featureMembers):
78            title, abstract, dimensions, units, crss=self._getInfo(feature)
79            layermap[feature.id]=CSMLLayer(title,abstract, dimensions, units, crss, feature)
80        if len(layermap) > 0:
81            return layermap
82        else:
83            raise ValueError
84
85
86class CSMLLayer(object):
87    """
88     representing a WMS layer.    Implements ILayer
89
90    @ivar title: The layer title.  As seen in the Capabilities document.
91    @ivar abstract:  Abstract as seen in the Capabilities document.
92    @ivar dimensions: A dictionary of IDimension objects.
93    @ivar units: A string describing the units.
94    @ivar crss: A sequence of SRS/CRSs supported by this layer.
95
96    @todo: Do we need minValue/maxValue?
97
98    """
99
100    def __init__(self, title, abstract, dimensions, units, crss, feature):
101        self.title=title
102        self.abstract=abstract
103        self.dimensions=dimensions
104        self.units=units
105        self.crss=crss
106        self._feature=feature
107        self.legendSize=(30,100)
108        try:
109            self.wgs84BBox = self.getBBox('EPSG:4326')
110        except:
111            raise ValueError("Layer must provide a bounding box in EPSG:4326 "
112                             "coordinates for compatibility with WMS-1.3.0")
113
114    def getBBox(self, crs):
115        """
116        @return: A 4-typle of the bounding box in the given coordinate
117            reference system.
118        """
119        bb= self._feature.getCSMLBoundingBox().getBox()
120        #convert 0 - 360 to -180, 180 as per common WMS convention
121        if abs(bb[2]-bb[0]) >= 359 and abs(bb[2]-bb[0]) < 361:
122            bb[0], bb[2]=-180, 180
123        return bb
124        #raise NotImplementedError
125       
126    def getSlab(self, crs, dimValues=None, renderOpts={}):
127        """
128        Creates a slab of the layer in a particular CRS and set of
129        dimensions.
130
131        @param crs: The coordinate reference system.
132        @param dimValues: A mapping of dimension names to dimension values
133            as specified in the IDimension.extent
134        @param renderOpts: A generic mapping object for passing rendering
135            options
136        @return: An object implementing ILayerSlab
137        #create netcdf for whole lat/lon for given dimValues, use to init slab
138        """
139        if type(self._feature) == csml.parser.GridSeriesFeature:
140            randomname= csml.csmllibs.csmlextra.getRandomID() + '.nc'
141            result= self._feature.subsetToGridSeries(config['tmpfilebuffer'], ncname=randomname, **dimValues)
142            #for now have to read netcdf back from disk (limitiation of CSML api)
143            netcdf=cdms.open(result[1])
144            #and then delete the temporary file
145            os.system('rm %s'%result[1])
146            bbox=self.getBBox(crs)
147            return CSMLLayerSlab(netcdf, self, crs, dimValues, renderOpts, bbox)
148        else:
149            raise NotImplementedError
150       
151    def getCacheKey(self, crs, dimValues=None, renderOpts={}):
152        """
153        Create a unique key for use in caching a slab.
154
155        The intention here is that most of the work should be done when
156        instantiating an ILayerSlab object.  These can be cached by the
157        server for future use.  The server will first call getCacheKey()
158        for the slab creation arguments and if the key is in it's cache
159        it will use a pre-generated ILayerSlab object.
160
161        """
162        return None
163        #raise NotImplementedError
164
165
166
167class CSMLDimension(object):
168    """
169    implements IDimension
170    @ivar units: The units string.
171    @ivar extent: Sequence of extent values.
172
173    """
174   
175    def __init__(self, domain, dimname, unit):
176        self.units = unit
177        self.extent = []
178        #patch to handle current limitations of multiple time dimension scanning in csml.
179        if string.lower(self.units)[:10] in ['days_since', 'seconds_si', 'minutes_si', 'hours_sinc','months _sin', 'years_sinc']:
180            if type(domain[dimname][0]) is not str   :
181                tunits=self.units.replace('_', ' ')
182                for val in domain[dimname]:
183                    csmltime= csml.csmllibs.csmltime.UDtimeToCSMLtime(cdtime.reltime(float(val), tunits).tocomp())
184                    self.extent.append(csmltime)
185            else:
186                for val in domain[dimname]:
187                    self.extent.append(str(val))
188        else:
189            for val in domain[dimname]:
190                self.extent.append(str(val))
191        #for time axis replace units with iso string
192        if dimname == 'time':
193            self.units='ISO8601'
194       
195class CSMLLayerSlab(object):
196    """
197    Implements LayerSlab
198    Represents a particular horizontal slice of a WMS layer.
199
200    ILayerSlab objects are designed to be convenient to cache.
201    They should be pickleable to enable memcached support in the future.
202
203    @ivar layer: The source ILayer instance.
204    @ivar crs: The coordinate reference system.
205    @ivar dimValues: A mapping of dimension values of this view.
206    @ivar renderOpts: The renderOpts used to create this view.
207    @ivar bbox: The bounding box as a 4-tuple.
208    """
209   
210    def __init__(self, netcdf, layer, crs, dimValues, renderOpts, bbox):
211        self._netcdf=netcdf
212        self.layer = layer
213        self.crs = crs
214        self.dimValues = dimValues
215        self.renderOpts=renderOpts
216        self.bbox=bbox
217       
218       
219    def getImage(self, bbox, width, height):
220        """
221        Create an image of a sub-bbox of a given size.
222
223        @ivar bbox: A bbox 4-tuple.
224        @ivar width: width in pixels.` 
225        @ivar height: height in pixels.
226        @return: A PIL Image object.
227
228        """
229        cmap=eval(config['colourmap']) # renderOpts is hook for colourmap, for now use config
230        grid=Grid(self.layer, self._netcdf, bbox, width, height)
231        #how to handle varmin,varmax? ..read array?
232        #minval, maxval=genutil.minmax(grid.value)
233        minval=min(min(l) for l in grid.value)
234        maxval=max(max(l) for l in grid.value)
235        renderer=RGBARenderer(minval, maxval)         
236        return renderer.renderGrid(grid, bbox, width, height, cmap)
237   
238class Grid(object):
239    """A class encapsulating a simple regularly spaced, rectilinear
240    grid.  This is the only type of grid pywms is expected to
241    understand and adaptors should be provided to connect to
242    underlying implementations such as cdms or csml.
243
244    @cvar crs: Coordinate reference system
245
246    @ivar x0: coordinate of the lower left corner.
247    @ivar y0: coordinate of the lower left corner.
248    @ivar dx: The x grid spacing.
249    @ivar dy: The y grid spacing.
250    @ivar nx: The number of x grid points.
251    @ivar ny: The number of y grid points.
252    @ivar value: A masked array of the grid values.
253    @ivar ix: The dimension index of longidude in value
254    @ivar iy: The dimension index of latitude in value
255    @ivar long_name: The name of the field.
256    @ivar units: The units of the field.
257    """
258    def __init__(self, layer, netcdf, bbox, width, height):
259        #we know the axes are called latitude and longitude as the CSML code has written it:
260
261        v=netcdf(layer.title)
262        tvar=v(latitude=(bbox[1], bbox[3]), longitude=(bbox[0],bbox[2]),squeeze=1)
263        order=tvar.getOrder()
264        #array of data
265        self.value=tvar.getValue()
266        #order of axes
267        if order == 'xy':
268            self.ix=0
269            self.iy=1
270        else:
271            self.ix=1
272            self.iy=0
273        lat = tvar.getLatitude()
274        lon = tvar.getLongitude()
275        self.x0=lon[0]
276        self.y0=lat[0]
277        self.dx=abs(lon[0]-lon[1])
278        self.dy=abs(lat[0]-lat[1])
279        self.nx=len(lon)
280        self.ny=len(lat)
281        self.long_name=tvar.id  #TODO, get long name from feature
282        self.units=tvar.units
Note: See TracBrowser for help on using the repository browser.