source: TI05-delivery/ows_framework/trunk/ows_server/ows_server/controllers/csml_wcs1_1_0.py @ 3536

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI05-delivery/ows_framework/trunk/ows_server/ows_server/controllers/csml_wcs1_1_0.py@3536
Revision 3536, 14.8 KB checked in by cbyrom, 12 years ago (diff)

Upgrade the server code to make consistent with newer pylons codebase - v0.9.6.1.
This mainly involves the replacement of the Response object, and associated methods,
with the inbuild, default response object. Typical changes include:

render_response -> render - with required settings on the response object
made before the render call against the response object

Response(...) -> response.write() - for content + response.headers.. = .. for headers info

  • also included the replacement of depricated functions, as highlighted by

the server logging

Line 
1 #Copyright (C) 2007 STFC & NERC (Science and Technology Facilities Council).
2# This software may be distributed under the terms of the
3# Q Public License, version 1.0 or later.
4# http://ndg.nerc.ac.uk/public_docs/QPublic_license.txt
5"""
6WCS controller driven by CSML.
7
8@author: DominicLowe, Stephen Pascoe
9"""
10
11
12try: #python 2.5
13    from xml.etree import ElementTree as ET
14except ImportError:
15    try:
16        # if you've installed it yourself it comes this way
17        import ElementTree as ET
18    except ImportError:
19        # if you've egged it this is the way it comes
20        from elementtree import ElementTree as ET
21
22import os, time, string, StringIO
23
24from ows_server.lib.base import *
25from ows_server.lib.decorators import *
26import ows_server.lib.validators as V
27
28from ows_common import exceptions as OWS_E
29from ows_common.wcs import *
30from ows_common.common import BoundingBox
31from ows_common.domain import ValuesUnit, PossibleValues
32
33from ows_server.lib.csml_util import get_csml_doc, extractToNetCDF
34from ows_server.lib.ndgInterface import interface
35from ows_server.models.wcs_CoverageDescription import CoverageDescription
36
37
38import logging
39logger = logging.getLogger('ows_server.controllers.csml_wcs')
40
41
42try:
43    #python 2.5
44    from email.mime.text import MIMEText
45    from email.mime.multipart import MIMEMultipart
46    from email.MIMEBase import MIMEBase
47    from email import encoders
48except:
49    #python 2.4
50    from email.MIMEText import MIMEText
51    from email.MIMEMultipart import MIMEMultipart
52    from email.MIMEBase import MIMEBase
53    from email import Encoders as encoders
54
55
56class CsmlWcs110Controller(OwsController):
57    _ows_parameters = {
58        'Format': make_domain(['text/xml']),
59        'ExceptionFormat': make_domain(['text/xml']),
60        }
61
62    #def __call__(self, environ, start_response):
63            #return super(CsmlWcsController, self).__call__(environ, start_response)
64
65
66    def _createMultipartMime(self, xml, netcdf):
67        #returns a multipart mime file containing the coverage xml + a netcdf file
68
69        # Create the container (outer) email message.
70        msg=MIMEMultipart()
71
72        xmlfile =StringIO.StringIO(xml)
73        xmlfile.readline(), xmlfile.readline() #don't read in first 2 lines (the content type) as MIMEBase also provides it.
74
75
76        #add the XML part
77        submsg=MIMEText(xmlfile.read(), _subtype='xml')
78        submsg.add_header('Content-ID', '<coverage.xml>')
79        submsg.add_header('Content-Disposition', 'attachment; filename="coverage.xml"')
80        #submsg.set_type('text/xml; name="coverage.xml"')
81        msg.attach(submsg)
82
83
84        #add the NetCDF part
85        submsg= MIMEBase('application', 'CF-netcdf') #check in ogc docs
86        submsg.set_payload(netcdf.read())
87        submsg.set_type('application/CF-netcdf')
88        submsg.add_header('Content-Disposition', 'attachment; filename="coverage.nc"')
89        submsg.add_header('Content-ID', '<coverage.nc>')
90        netcdf.close()
91        # Encode the payload using Base64
92        encoders.encode_base64(submsg)
93        msg.attach(submsg)
94
95        #return the message
96        return  msg
97
98
99    def _loadFeatureDimensions(self, feature):
100        dims = {}
101        lon=feature.getLongitudeAxis()
102        lat=feature.getLongitudeAxis()
103        domain=feature.getDomain()
104        for axis_name, axis in domain.iteritems():
105            if axis_name in [lon,lat]:
106                continue
107            dims[axis_name] =            Domain(possibleValues=PossibleValues.fromAllowedValues(axis),
108                                 #!TODO: this is a fudge until we can deduce UOM.
109                                 valuesUnit=ValuesUnit(uoms=[''],
110                                                       referenceSystems=['']))
111        return dims
112
113    def _loadFeatureSummary(self, feature):
114        dims = self._loadFeatureDimensions(feature)
115        #TODO - need to ensure proper values for Name. ID and Description are populated.
116        cvgTitle=[feature.description.CONTENT]
117        cvgDescription=feature.description.CONTENT
118        cvgAbstract=[feature.description.CONTENT]
119        csmlbbox=feature.getCSMLBoundingBox()
120        if csmlbbox is not None:
121            bbox=csmlbbox.getBox()       
122        else:
123            csmlbbox=c.dataset.getCSMLBoundingBox()
124            bbox=csmlbbox.getBox()
125        crs=feature.getNativeCRS()
126        crslist=[crs] 
127        return WcsDatasetSummary(identifier=feature.id,
128                                 titles=cvgTitle,
129                                 boundingBoxes=[BoundingBox([bbox[0],bbox[1]], [bbox[2],bbox[3]],
130                                 crs='CRS:84')], description=cvgDescription,abstracts=cvgAbstract, formats=['application/cf-netcdf'],
131                                 supportedCRSs=crslist
132                                 )
133
134    def _loadCapabilities(self):
135        """
136        Overriding subclass to add layer capabilities
137
138        """
139        # Get default capabilities from superclass
140        sm = super(CsmlWcs110Controller, self)._loadCapabilities()
141        ds = WcsDatasetSummary(titles=['Root Dataset'], datasetSummaries=[], CRSs=['CRS:84'])
142        # Add a DatasetSummary for each feature
143        for f_n in c.dataset.getFeatureList():
144            feature_ds = self._loadFeatureSummary(c.dataset.getFeature(f_n))
145            ds.datasetSummaries.append(feature_ds)
146        sm.contents = Contents(datasetSummaries=[ds])
147        return sm
148
149    def _buildCoverageDescriptions(self):
150        """ builds a collection of CoverageDescription objects - used for DescribeCoverage """
151        CoverageDescriptions=[]
152        csmlbbox=c.dataset.getCSMLBoundingBox()
153        dsbbox=csmlbbox.getBox()
154        dstimes=csmlbbox.getTimeLimits()
155        dscrs=csmlbbox.getCRSName()
156        for f in self.features:
157            feature = self.features[f]
158            csmlbbox=feature.getCSMLBoundingBox()
159            if csmlbbox is None: #use the bounding box of the  dataset.
160                bbox=dsbbox 
161                timeLimits=dstimes
162                crsname=dscrs
163            else: #use the feature's bounding box info
164                bbox=csmlbbox.getBox()
165                timeLimits=csmlbbox.getTimeLimits()
166                crsname=csmlbbox.getCRSName()               
167            cd=CoverageDescription(identifier=f,titles=feature.name.CONTENT, keywords=None, abstracts=feature.description.CONTENT, boundingBoxes=[BoundingBox([bbox[0],bbox[1]], [bbox[2],bbox[3]], crs=crsname)], crs=crsname, timeDomain=timeLimits)
168            CoverageDescriptions.append(cd)
169        return CoverageDescriptions
170
171    @operation
172    @parameter('Format', possibleValues=['text/xml'])
173    @parameter('Service', possibleValues=['WCS'], required=True)
174    @parameter('Version', possibleValues=['1.1.0'])
175    def GetCapabilities(self, uri, service=None, version='1.1.0'):
176        """
177        @note: format and updatesequence parameters are not supported
178            by this WMS.
179
180        """
181        # Populate the context object with information required by the template
182
183        #get doc from cache or disk:
184        try:
185            rstatus,c.dataset=interface.GetParsedCSML(uri)               
186            if not rstatus: 
187                c.xml='<div class="error">%s</div>'%c.dataset
188                resp=render('error')
189                return resp
190   
191            if type(c.dataset) is str:
192                #If not a csml datset is some message from exist such as 'access denied'
193                response.write(c.dataset)
194                return response
195            return self._renderCapabilities('ows/wcs_capabilities')
196        except Exception, e:
197            if isinstance(e, OWS_E.OwsError):
198                raise e               
199            elif isinstance(e, ValueError):
200                c.xml='<div class="error">%s</div>'%e
201                return render('error')
202            else:
203                raise OWS_E.NoApplicableCode(e)
204           
205    @operation
206    @parameter('Service', possibleValues=['WCS'], required=True)
207    @parameter('Version', possibleValues=['1.1.0'])
208    @parameter('Identifiers', required=True)
209    @parameter('Format', possibleValues=['text/xml']) 
210    def DescribeCoverage(self, uri, version, service, identifiers, format='text/xml'):
211        """
212        WCS DescribeCoverage operation
213        """
214        try:
215            self.features={} #dictionary to hold requested coverages
216            rstatus,c.dataset=interface.GetParsedCSML(uri)
217            if not rstatus: raise ValueError(c.dataset)
218            for ident in identifiers.split(','):
219                feature = c.dataset.getFeature(ident)
220                if feature is None:
221                    raise OWS_E.InvalidParameterValue('Coverage  with id=%s not found'%ident, 'identifiers')
222                self.features[ident]=feature
223            if not hasattr(self,'covDescs'):
224                self.covDescs=self._buildCoverageDescriptions()
225            c.covDescs=self.covDescs
226            r=render('wcs_DescribeCoverageResponse', format='xml')
227            r.headers['content-type'] = 'text/xml'
228            return r
229        except Exception, e:
230            if isinstance(e, OWS_E.OwsError):
231                raise e
232            elif isinstance(e, ValueError):
233                c.xml='<div class="error">%s</div>'%e
234                return render('error')
235            else:
236                raise OWS_E.NoApplicableCode(e)
237
238    @operation
239    @parameter('Version', possibleValues=['1.1.0'], required=True)
240    @parameter('Identifier', required=True)
241    @parameter('BoundingBox', required=True, validator=V.bbox_2or3d)
242    @parameter('TimeSequence',required=True, validator=V.iso8601_time)
243    @parameter('Format', possibleValues=['cf-netcdf'], required=True)
244    @parameter('Store', validator = V.boolean('Store'))
245    @parameter('Status', validator = V.boolean('Status'))
246    #TODO some more parameters to add here
247    # Dimension parameters Time, Elevation, etc. are handled separately
248    def GetCoverage(self, uri, version, format, identifier, boundingbox, timesequence, store=False, status=False):
249        # Retrieve dataset and selected feature         
250        try:
251            rstatus,dataset=interface.GetParsedCSML(uri)               
252            if not rstatus: 
253                c.xml='<div class="error">%s</div>'%dataset
254                resp=render('error')
255                return resp
256            feature = dataset.getFeature(identifier)
257            if feature is None:
258                raise OWS_E.InvalidParameterValue('Coverage not found', 'identifier')
259           
260                       
261            #set bounding box
262           
263            lon=feature.getLongitudeAxis()
264            lat=feature.getLatitudeAxis()
265            t=feature.getTimeAxis()
266            print '%s %s %s'%(lon,lat,t)
267            if None in [lon, lat, time]:
268                #TODO need to return a suitable wcs error.
269                print 'warning, could not get correct axis info'
270           
271            #create selection dictionary:
272            sel={}
273            sel[lat]=(boundingbox[1], boundingbox[3])
274            sel[lon]=(boundingbox[0], boundingbox[2])
275            if  type(timesequence) is unicode:
276                sel[t]=str(timesequence)
277            else:
278                sel[t]=timesequence
279           
280            #z is the 4th axis (eg height or pressure).
281            #NOTE, need to decide whether bounding box is of form: x1,y1,z1,x2,y2,z2 or x1,y1,x2,y2,z1,z2
282            #currently the latter is implemented.
283           
284            if len(boundingbox)  == 6:
285                for ax in feature.getAxisLabels():
286                    if ax not in [lat, lon, t]:
287                        #must be Z 
288                        z=str(ax)
289                        sel[z]=(boundingbox[4], boundingbox[5])
290           
291           
292            axisNames=feature.getAxisLabels()
293   
294            # Extract via CSML.subsetToGridSeries()
295           
296            if store:
297                #need to farm off to WPS
298                #but for now...
299                filename = extractToNetCDF(feature, sel, publish = True) 
300            else:
301                filename = extractToNetCDF(feature, sel)
302       
303               
304            #use the randomly allocated filename as a basis for an identifier           
305            f=os.path.basename(filename)
306            c.fileID=os.path.splitext(f)[0]
307           
308            #Depending on if the 'store' parameter is set, either return the netcdf file + coverage xml as a multipart mime or return a coverage document containing a link.
309                       
310            if store:
311                if status:
312                    #STORE = true, STATUS = true:
313                    #hand off file "id" to StatusController to determine correct ExectuteResponse type response.
314                    status=StatusController()
315                    jobID=os.path.splitext(os.path.basename(filename)[9:])[0] #remove the 'csml_wxs_' prefix and file extension to create a jobID
316                    return status.getStatus(jobID)                   
317                else:
318                    #STORE=true, STATUS = false: Return Coverage XML document with link to file.
319                    #use the temp file name (minus extension) as an ID
320
321                    hostname=g.server 
322                    c.hyperlink =hostname+'/'+os.path.basename(request.environ['paste.config']['app_conf']['publish_dir'])+'/'+os.path.basename(filename)
323                    r=render('wcs_getCoverageResponse', format='xml')
324                    r.headers['content-type'] = 'text/xml'
325                    #write ndgSec to text file and store with coverage file:
326                    textName=request.environ['paste.config']['app_conf']['publish_dir']+'/'+os.path.splitext(os.path.basename(filename))[0]+'.txt'
327                    secText=open(textName, 'w')
328                    if 'ndgSec' in session:
329                        username=str(session['ndgSec']['u'])
330                        securityinfo=username
331                    else:
332                        securityinfo='No Security'
333                    secText.write(securityinfo)
334                    secText.close()                 
335                    return r                                 
336            else:               
337                #STORE = FALSE, STATUS therefore irrelevant, return Multipart Mime.
338                netcdfFile=open(filename, 'r')
339                c.hyperlink="cid:coverage.nc"
340                xmlfile=render('wcs_getCoverageResponse', format='xml')
341                xmlfile.headers['content-type'] = 'text/xml'
342                multipart=self._createMultipartMime(xmlfile, netcdfFile)       
343                msg=multipart
344
345                response.headers['Content-Type']='multipart'
346                response.write(msg)
347                return response
348        except Exception, e:
349            if isinstance(e, OWS_E.OwsError):
350               raise e
351            elif isinstance(e, ValueError):
352                c.xml='<div class="error">%s</div>'%e
353                return render('error')
354            else:
355               raise OWS_E.NoApplicableCode(e)
356       
357       
Note: See TracBrowser for help on using the repository browser.