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

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@3902
Revision 3902, 13.7 KB checked in by domlowe, 11 years ago (diff)

correcting featureinfoformats in csml wms

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
19import logging
20log = logging.getLogger(__name__)
21
22class CSMLLayerMapper(object):
23    """
24    Map keyword arguments to a collection of layers.
25    Supports the retrieval of sets of layers according to arbitary
26    keyword/value pairs.
27    Implements  ILayerMapper
28   
29    """
30   
31    def _getInfo(self, feature):
32        ''' given a csml feature, return info about the layer/feature
33        @return:   title, abstract, dimensions, units, crss '''
34
35        try:
36            title=feature.name.CONTENT
37        except:
38            title=''
39        try:
40            abstract=feature.description.CONTENT
41        except:
42            abstract=title
43       
44        units=feature.getDomainUnits()
45        dimensions={}
46        tmpunits=copy(units)
47        tmpunits.reverse()
48        domain = feature.getDomain()
49        for dim in feature.getAxisLabels():
50            nextdim=CSMLDimension(domain, dim, tmpunits.pop())
51            if dim not in ['latitude', 'longitude']:
52                dimensions[dim]=nextdim
53        crs=feature.getNativeCRS()
54        crss=[self._crscat.getCRS(crs).twoD]
55        if 'EPSG:4326' in crss:
56            crss.append('CRS:84')
57            crss.append('WGS84')
58        return title, abstract, dimensions, units, crss
59   
60    def map(self, **kwargs):
61        """
62        Given csml.parser.Dataset object list the names of
63        all layers available.
64       
65        @return: A mapping of layer names to ILayer implementations.
66        @raise ValueError: If no layers are available for these keywords.
67        """
68        print "hello"
69        #print kwargs
70        fileoruri=kwargs['fileoruri']
71         
72        #TODO - handle file paths/directories URIs in config
73        #.xml or .csml extensions are supported:
74        filename='%s/%s.csml'%(config['csmlstore'],fileoruri)
75        if not os.path.exists(filename):
76           filename='%s/%s.xml'%(config['csmlstore'],fileoruri)
77        if not os.path.exists(filename):
78            raise Exception(str('CSML File could not be found: %s')%filename)
79        print filename   
80        ds=csml.parser.Dataset(filename)
81           
82        layermap={}
83        self._crscat=csml.csmllibs.csmlcrs.CRSCatalogue()
84        for feature in csml.csmllibs.csmlextra.listify(ds.featureCollection.featureMembers):
85            title, abstract, dimensions, units, crss=self._getInfo(feature)
86            layermap[feature.id]=CSMLLayer(title,abstract, dimensions, units, crss, feature)
87        if len(layermap) > 0:
88            return layermap
89        else:
90            raise ValueError
91
92
93class CSMLLayer(object):
94    """
95     representing a WMS layer.    Implements ILayer
96
97    @ivar title: The layer title.  As seen in the Capabilities document.
98    @ivar abstract:  Abstract as seen in the Capabilities document.
99    @ivar dimensions: A dictionary of IDimension objects.
100    @ivar units: A string describing the units.
101    @ivar crss: A sequence of SRS/CRSs supported by this layer.
102
103    @todo: Do we need minValue/maxValue?
104
105    """
106   
107    def __init__(self, title, abstract, dimensions, units, crss, feature):
108        self.featureInfoFormats=None #NotImplemented
109        self.title=title
110        self.abstract=abstract
111        self.dimensions=dimensions
112        self.units=units
113        self.crss=crss
114        self._feature=feature
115        self.legendSize=(30,100)
116        bb= self._feature.getCSMLBoundingBox().getBox()
117        #convert 0 - 360 to -180, 180 as per common WMS convention
118        if abs(bb[2]-bb[0]) >= 359 and abs(bb[2]-bb[0]) < 361:
119            bb[0], bb[2]=-180, 180
120        self.wgs84BBox = bb
121        self.featureInfoFormats = ['text/html']
122        try:
123            self.wgs84BBox = self.getBBox('EPSG:4326')
124        except:
125            raise ValueError("Layer must provide a bounding box in EPSG:4326 "
126                             "coordinates for compatibility with WMS-1.3.0")
127
128    def getBBox(self, crs):
129        """
130        @return: A 4-typle of the bounding box in the given coordinate
131            reference system.
132        """
133        #bb= self._feature.getCSMLBoundingBox().getBox()
134        #convert 0 - 360 to -180, 180 as per common WMS convention
135        #if abs(bb[2]-bb[0]) >= 359 and abs(bb[2]-bb[0]) < 361:
136        #    bb[0], bb[2]=-180, 180
137        #self.wgs84BBox = bb
138        return self.wgs84BBox
139        #raise NotImplementedError
140       
141    def getSlab(self, crs, dimValues=None, renderOpts={}):
142        """
143        Creates a slab of the layer in a particular CRS and set of
144        dimensions.
145
146        @param crs: The coordinate reference system.
147        @param dimValues: A mapping of dimension names to dimension values
148            as specified in the IDimension.extent
149        @param renderOpts: A generic mapping object for passing rendering
150            options
151        @return: An object implementing ILayerSlab
152        #create netcdf for whole lat/lon for given dimValues, use to init slab
153        """
154
155        log.debug('getSlab(%s, %s)' % (crs, dimValues))
156       
157        #unicode conversion
158        for dimval in dimValues:
159            if dimval != 'time':
160                dimValues[dimval]=float(dimValues[dimval])
161            else:
162                #remove any trailing Zs from time string
163                if dimValues[dimval] [-1:] in ['Z', 'z']:
164                    dimValues[dimval]=dimValues[dimval][:-1]
165        if type(self._feature) == csml.parser.GridSeriesFeature:
166            randomname= csml.csmllibs.csmlextra.getRandomID() + '.nc'
167            result= self._feature.subsetToGridSeries(config['tmpfilebuffer'], ncname=randomname, **dimValues)
168            #for now have to read netcdf back from
169            #disk (limitiation of CSML api)
170            print ['h', result[1]]
171            netcdf=cdms.open(result[1])
172            #and then delete the temporary file
173            os.system('rm %s'%result[1])
174            bbox=self.getBBox(crs)
175            return CSMLLayerSlab(netcdf, self, crs, dimValues, renderOpts, bbox)
176        else:
177            raise NotImplementedError
178       
179    def getCacheKey(self, crs, dimValues=None, renderOpts={}):
180        """
181        Create a unique key for use in caching a slab.
182
183        The intention here is that most of the work should be done when
184        instantiating an ILayerSlab object.  These can be cached by the
185        server for future use.  The server will first call getCacheKey()
186        for the slab creation arguments and if the key is in it's cache
187        it will use a pre-generated ILayerSlab object.
188
189        """
190        dimList = list(dimValues.items())
191        dimList.sort()
192        return '%s:%s' % (crs, dimList)
193
194    def getFeatureInfo(self, format, crs, point, dimValues):
195        """
196        Return a response string descibing the feature at a given
197        point in a given CRS.
198
199        Currently only "html" is supported as output format
200
201        @param format: One of self.featureInfoFormats.  Defines which
202            format the response will be in.
203        @param crs: One of self.crss
204        @param point: a tuple (x, y) in the supplied crs of the point
205            being selected.
206        @param dimValues: A mapping of dimension names to dimansion values.
207        @return: A string containing the response.
208
209        """
210        print point
211        randomname= csml.csmllibs.csmlextra.getRandomID() + '.nc'
212        result= self._feature.subsetToGridSeries(config['tmpfilebuffer'], ncname=randomname, **dimValues)
213        #for now have to read netcdf back from disk (limitiation of CSML api)
214       
215        f=cdms.open(result[1])
216        #and then delete the temporary file
217        os.system('rm %s'%result[1])
218        netcdf = f(self.title)
219        #Now grab the netCDF object for the point specified.
220        #The reason for the 'cob' option is so that if the grid the data
221        #is defined on does not have a grid point at the point specified,
222        #we should  still get the nearest location
223       
224        t_point = netcdf(latitude=(point[1], point[1], 'cob'), longitude=(point[0], point[0], 'cob'))
225        #now get the value recorded at this location
226        value = t_point.getValue().tolist()
227        print value
228        print t_point.fill_value()
229        #and the fill_value too
230        fill_value = t_point.fill_value()
231        #value is actually embedded in a multi dimensional list,
232        #so we need to extract the actual value from the list
233        while type(value) is list:
234                value = value[0]
235
236        #now check if the value is actually the fill_value rather than
237        #a value recorded at the point specified
238        print [value, fill_value]
239        if (2*fill_value) == value:
240                value = "No value found at position: "+str(point[1])+", "+str(point[0])
241        else:
242                value = "Value found at position: "+str(point[1])+", "+str(point[0])+" is: "+str(value)
243        # finally return the value
244        return value
245
246
247class CSMLDimension(object):
248    """
249    implements IDimension
250    @ivar units: The units string.
251    @ivar extent: Sequence of extent values.
252
253    """
254   
255    def __init__(self, domain, dimname, unit):
256        self.units = unit
257        self.extent = []
258        #patch to handle current limitations of multiple time dimension scanning in csml.
259        if string.lower(self.units)[:10] in ['days_since', 'seconds_si', 'minutes_si', 'hours_sinc','months _sin', 'years_sinc']:
260            if type(domain[dimname][0]) is not str   :
261                tunits=self.units.replace('_', ' ')
262                for val in domain[dimname]:
263                    csmltime= csml.csmllibs.csmltime.UDtimeToCSMLtime(cdtime.reltime(float(val), tunits).tocomp())
264                    self.extent.append(csmltime)
265            else:
266                for val in domain[dimname]:
267                    self.extent.append(str(val))
268        else:
269            for val in domain[dimname]:
270                self.extent.append(str(val))
271        #for time axis replace units with iso string
272        if dimname == 'time':
273            self.units='ISO8601'
274       
275class CSMLLayerSlab(object):
276    """
277    Implements LayerSlab
278    Represents a particular horizontal slice of a WMS layer.
279
280    ILayerSlab objects are designed to be convenient to cache.
281    They should be pickleable to enable memcached support in the future.
282
283    @ivar layer: The source ILayer instance.
284    @ivar crs: The coordinate reference system.
285    @ivar dimValues: A mapping of dimension values of this view.
286    @ivar renderOpts: The renderOpts used to create this view.
287    @ivar bbox: The bounding box as a 4-tuple.
288    """
289   
290    def __init__(self, netcdf, layer, crs, dimValues, renderOpts, bbox):
291        self._netcdf=netcdf
292        self.layer = layer
293        self.crs = crs
294        self.dimValues = dimValues
295        self.renderOpts=renderOpts
296        self.bbox=bbox
297       
298        #set colour map for ALL images from this slab
299        v=self._netcdf(layer.title)
300        tvar=v(squeeze=1)
301        #array of data
302        value=tvar.getValue()
303        self.minval=min(min(l) for l in value)
304        self.maxval=max(max(l) for l in value)
305       
306       
307    def getImage(self, bbox, width, height):
308        """
309        Create an image of a sub-bbox of a given size.
310
311        @ivar bbox: A bbox 4-tuple.
312        @ivar width: width in pixels.` 
313        @ivar height: height in pixels.
314        @return: A PIL Image object.
315
316        """
317
318        log.debug('getImage(%s, %s, %s)' % (bbox, width, height))
319       
320
321        cmap=eval(config['colourmap']) # renderOpts is hook for colourmap, for now use config
322        grid=Grid(self.layer, self._netcdf, bbox, width, height)
323        #how to handle varmin,varmax? ..read array?
324        #minval, maxval=genutil.minmax(grid.value)
325        #minval=min(min(l) for l in grid.value)
326        #maxval=max(max(l) for l in grid.value)
327        minval=self.minval
328        maxval=self.maxval
329        renderer=RGBARenderer(minval, maxval)         
330        return renderer.renderGrid(grid, bbox, width, height, cmap)
331   
332class Grid(object):
333    """A class encapsulating a simple regularly spaced, rectilinear
334    grid.  This is the only type of grid pywms is expected to
335    understand and adaptors should be provided to connect to
336    underlying implementations such as cdms or csml.
337
338    @cvar crs: Coordinate reference system
339
340    @ivar x0: coordinate of the lower left corner.
341    @ivar y0: coordinate of the lower left corner.
342    @ivar dx: The x grid spacing.
343    @ivar dy: The y grid spacing.
344    @ivar nx: The number of x grid points.
345    @ivar ny: The number of y grid points.
346    @ivar value: A masked array of the grid values.
347    @ivar ix: The dimension index of longidude in value
348    @ivar iy: The dimension index of latitude in value
349    @ivar long_name: The name of the field.
350    @ivar units: The units of the field.
351    """
352    def __init__(self, layer, netcdf, bbox, width, height):
353        #we know the axes are called latitude and longitude as the CSML code has written it:
354        #print netcdf
355        v=netcdf(layer.title)
356        #print v
357        tvar=v(latitude=(bbox[1], bbox[3]), longitude=(bbox[0],bbox[2]),squeeze=1)
358        order=tvar.getOrder()
359        #array of data
360        self.value=tvar.getValue()
361        #print self.value
362        #order of axes
363        if order == 'xy':
364            self.ix=0
365            self.iy=1
366        else:
367            self.ix=1
368            self.iy=0
369        lat = tvar.getLatitude()
370        lon = tvar.getLongitude()
371        self.x0=lon[0]
372        self.y0=lat[0]
373        self.dx=abs(lon[0]-lon[1])
374        self.dy=abs(lat[0]-lat[1])
375        #print [lon, len(lon)]
376        #print len(self.value)
377        print len(lon)
378        print len(lat)
379
380   
381
382       
383
384        self.nx=len(lon)
385        self.ny=len(lat)
386        self.long_name=tvar.id  #TODO, get long name from feature
387        self.units=tvar.units
Note: See TracBrowser for help on using the repository browser.