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

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@3760
Revision 3760, 10.5 KB checked in by domlowe, 12 years ago (diff)

Fix to remove any Zs from time request, and fix to add CRS:84 and WGS84 as equivalent CRSs to EPSG:4326

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