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

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

changed colormapping to work at the slab level rather than the getimage level

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        #set colour map for ALL images from this slab
231        v=self._netcdf(layer.title)
232        tvar=v(squeeze=1)
233        #array of data
234        value=tvar.getValue()
235        self.minval=min(min(l) for l in value)
236        self.maxval=max(max(l) for l in value)
237       
238       
239    def getImage(self, bbox, width, height):
240        """
241        Create an image of a sub-bbox of a given size.
242
243        @ivar bbox: A bbox 4-tuple.
244        @ivar width: width in pixels.` 
245        @ivar height: height in pixels.
246        @return: A PIL Image object.
247
248        """
249        cmap=eval(config['colourmap']) # renderOpts is hook for colourmap, for now use config
250        grid=Grid(self.layer, self._netcdf, bbox, width, height)
251        #how to handle varmin,varmax? ..read array?
252        #minval, maxval=genutil.minmax(grid.value)
253        #minval=min(min(l) for l in grid.value)
254        #maxval=max(max(l) for l in grid.value)
255        minval=self.minval
256        maxval=self.maxval
257        renderer=RGBARenderer(minval, maxval)         
258        return renderer.renderGrid(grid, bbox, width, height, cmap)
259   
260class Grid(object):
261    """A class encapsulating a simple regularly spaced, rectilinear
262    grid.  This is the only type of grid pywms is expected to
263    understand and adaptors should be provided to connect to
264    underlying implementations such as cdms or csml.
265
266    @cvar crs: Coordinate reference system
267
268    @ivar x0: coordinate of the lower left corner.
269    @ivar y0: coordinate of the lower left corner.
270    @ivar dx: The x grid spacing.
271    @ivar dy: The y grid spacing.
272    @ivar nx: The number of x grid points.
273    @ivar ny: The number of y grid points.
274    @ivar value: A masked array of the grid values.
275    @ivar ix: The dimension index of longidude in value
276    @ivar iy: The dimension index of latitude in value
277    @ivar long_name: The name of the field.
278    @ivar units: The units of the field.
279    """
280    def __init__(self, layer, netcdf, bbox, width, height):
281        #we know the axes are called latitude and longitude as the CSML code has written it:
282
283        v=netcdf(layer.title)
284        tvar=v(latitude=(bbox[1], bbox[3]), longitude=(bbox[0],bbox[2]),squeeze=1)
285        order=tvar.getOrder()
286        #array of data
287        self.value=tvar.getValue()
288        #order of axes
289        if order == 'xy':
290            self.ix=0
291            self.iy=1
292        else:
293            self.ix=1
294            self.iy=0
295        lat = tvar.getLatitude()
296        lon = tvar.getLongitude()
297        self.x0=lon[0]
298        self.y0=lat[0]
299        self.dx=abs(lon[0]-lon[1])
300        self.dy=abs(lat[0]-lat[1])
301        self.nx=len(lon)
302        self.ny=len(lat)
303        self.long_name=tvar.id  #TODO, get long name from feature
304        self.units=tvar.units
Note: See TracBrowser for help on using the repository browser.