source: TI05-delivery/ows_framework/trunk/ows_server/ows_server/controllers/csml_wcs.py @ 2806

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

changes to WCS to allow Trajectory subsetting. Also change in way mimetypes are handled differs between pylons 0.9.5 and 0.9.6

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
37try:
38    #python 2.5
39    from email.mime.text import MIMEText
40    from email.mime.multipart import MIMEMultipart
41    from email.MIMEBase import MIMEBase
42    from email import encoders
43except:
44    #python 2.4
45    from email.MIMEText import MIMEText
46    from email.MIMEMultipart import MIMEMultipart
47    from email.MIMEBase import MIMEBase
48    from email import Encoders as encoders
49
50
51class CsmlWcsController(OwsController):
52    _ows_parameters = {
53        'Format': make_domain(['text/xml']),
54        'ExceptionFormat': make_domain(['text/xml']),
55        }
56
57
58
59    def _createMultipartMime(self, xml, netcdf):
60        #returns a multipart mime file containing the coverage xml + a netcdf file
61
62        # Create the container (outer) email message.
63        msg=MIMEMultipart()
64
65        xmlfile =StringIO.StringIO(xml)
66        xmlfile.readline(), xmlfile.readline() #don't read in first 2 lines (the content type) as MIMEBase also provides it.
67
68
69        #add the XML part
70        submsg=MIMEText(xmlfile.read(), _subtype='xml')
71        submsg.add_header('Content-ID', '<coverage.xml>')
72        submsg.add_header('Content-Disposition', 'attachment; filename="coverage.xml"')
73        #submsg.set_type('text/xml; name="coverage.xml"')
74        msg.attach(submsg)
75
76
77        #add the NetCDF part
78        submsg= MIMEBase('application', 'CF-netcdf') #check in ogc docs
79        submsg.set_payload(netcdf.read())
80        submsg.set_type('application/CF-netcdf')
81        submsg.add_header('Content-Disposition', 'attachment; filename="coverage.nc"')
82        submsg.add_header('Content-ID', '<coverage.nc>')
83        netcdf.close()
84        # Encode the payload using Base64
85        encoders.encode_base64(submsg)
86        msg.attach(submsg)
87
88        #return the message
89        return  msg
90
91
92    def _loadFeatureDimensions(self, feature):
93        dims = {}
94        #!WARNING
95        # This bit is a hack until the CSML API implements a mechanism
96        # to determine which elements of a domain are longitude and latitude.
97        lon=feature.getLongitudeAxis()
98        lat=feature.getLongitudeAxis()
99        domain=feature.getDomain()
100        for axis_name, axis in domain.iteritems():
101            if axis_name in [lon,lat]:
102                continue
103            dims[axis_name] =            Domain(possibleValues=PossibleValues.fromAllowedValues(axis),
104                                 #!TODO: this is a fudge until we can deduce UOM.
105                                 valuesUnit=ValuesUnit(uoms=[''],
106                                                       referenceSystems=['']))
107        return dims
108
109    def _loadFeatureSummary(self, feature):
110        dims = self._loadFeatureDimensions(feature)
111        #TODO - need to ensure proper values for Name. ID and Description are populated.
112        cvgTitle=[feature.description.CONTENT]
113        cvgDescription=feature.description.CONTENT
114        cvgAbstract=[feature.description.CONTENT]
115        bbox=c.dataset.getBoundingBox()  #TODO, use the bounding box of the feature not the dataset.
116        return WcsDatasetSummary(identifier=feature.id,
117                                 titles=cvgTitle,
118                                 boundingBoxes=[BoundingBox([bbox[0],bbox[1]], [bbox[2],bbox[3]],
119                                 crs='CRS:84')], dimensions=dims,  description=cvgDescription,abstracts=cvgAbstract
120                                 )
121
122    def _loadCapabilities(self):
123        """
124        Overriding subclass to add layer capabilities
125
126        """
127        # Get default capabilities from superclass
128        sm = super(CsmlWcsController, self)._loadCapabilities()
129        ds = WcsDatasetSummary(titles=['Root Dataset'], datasetSummaries=[], CRSs=['CRS:84'])
130        # Add a DatasetSummary for each feature
131
132        for f_n in c.dataset.getFeatureList():
133            feature_ds = self._loadFeatureSummary(c.dataset.getFeature(f_n))
134            ds.datasetSummaries.append(feature_ds)
135        sm.contents = Contents(datasetSummaries=[ds])
136        return sm
137
138    def _buildCoverageDescriptions(self):
139        """ builds a collection of CoverageDescription objects - used for DescribeCoverage """
140        CoverageDescriptions=[]
141        for f in self.features:
142          feature = self.features[f]
143          bbox=c.dataset.getBoundingBox()  #TODO, use the bounding box of the feature not the dataset.
144          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='CRS:84')])
145          CoverageDescriptions.append(cd)
146        return CoverageDescriptions
147
148
149
150
151    @operation
152    @parameter('Format', possibleValues=['text/xml'])
153    @parameter('Service', possibleValues=['WCS'], required=True)
154    @parameter('Version', possibleValues=['1.1.0'])
155    def GetCapabilities(self, uri, service=None, version=None):
156        """
157        @note: format and updatesequence parameters are not supported
158            by this WMS.
159
160        """
161        # Populate the context object with information required by the template
162
163        #get doc from cache or disk:
164        try:
165            rstatus,c.dataset=interface.GetParsedCSML(uri)
166            if not rstatus: raise ValueError(c.dataset)
167   
168            if type(c.dataset) is str:
169                #If not a csml datset is some message from exist such as 'access denied'
170                return Response(c.dataset)
171            return self._renderCapabilities('ows/wcs_capabilities')
172        except Exception, e:
173            if isinstance(e, OWS_E.OwsError):
174               raise e
175            elif isinstance(e, ValueError):
176                if e == '<p> Access Denied </p><p>Not Logged in</p>':
177                    c.msg=e
178                    #h.redirect_to('/login') 
179                    return render_response('wcs_accessDenied', mimetype ='text/html')                   
180                else:
181                    raise OWS_E.NoApplicableCode(e)
182            else:
183               raise OWS_E.NoApplicableCode(e)
184           
185           
186    @operation
187    @parameter('Service', possibleValues=['WCS'], required=True)
188    @parameter('Version', possibleValues=['1.1.0'])
189    @parameter('Identifiers', required=True)
190    @parameter('Format', possibleValues=['text/xml'], required=True)  #IS THIS MANDATORY
191    def DescribeCoverage(self, uri, version, service, identifiers, format='text/xml'):
192        """
193        WCS DescribeCoverage operation
194        """
195        try:
196            self.features={} #dictionary to hold requested coverages
197            rstatus,c.dataset=interface.GetParsedCSML(uri)
198            if not rstatus: raise ValueError(c.dataset)
199            for ident in identifiers.split(','):
200                feature = c.dataset.getFeature(ident)
201                if feature is None:
202                    raise OWS_E.InvalidParameterValue('Coverage  with id=%s not found'%ident, 'identifiers')
203                self.features[ident]=feature
204            c.covDescs=self._buildCoverageDescriptions()
205            r=render_response('wcs_DescribeCoverageResponse', format='xml')
206            r.headers['content-type'] = 'text/xml'
207            return r
208        except Exception, e:
209            if isinstance(e, OWS_E.OwsError):
210               raise e
211            elif isinstance(e, ValueError):
212                if e.message == '<p> Access Denied </p><p>Not Logged in</p>':
213                    c.msg=e.message
214                    #h.redirect_to('/login') 
215                    return render_response('wcs_accessDenied', mimetype ='text/html')
216                else:
217                    raise OWS_E.NoApplicableCode(e)
218            else:
219               raise OWS_E.NoApplicableCode(e)
220
221    @operation
222    @parameter('Version', possibleValues=['1.1.0'], required=True)
223    @parameter('Identifier', required=True)
224    @parameter('BoundingBox', required=True, validator=V.bbox_2or3d)
225    @parameter('TimeSequence',required=True, validator=V.iso8601_time)
226    @parameter('Format', possibleValues=['application/netcdf'], required=True)
227    @parameter('Store', validator = V.boolean('Store'))
228    @parameter('Status', validator = V.boolean('Status'))
229    #TODO some more parameters to add here
230    # Dimension parameters Time, Elevation, etc. are handled separately
231    def GetCoverage(self, uri, version, format, identifier, boundingbox, timesequence, store=False, status=False):
232        # Retrieve dataset and selected feature           
233        #try:
234        rstatus,dataset=interface.GetParsedCSML(uri)               
235        if not rstatus: raise ValueError(dataset)
236        feature = dataset.getFeature(identifier)
237        if feature is None:
238            raise OWS_E.InvalidParameterValue('Coverage not found', 'identifier')
239       
240                   
241        #set bounding box
242       
243        lon=feature.getLongitudeAxis()
244        lat=feature.getLatitudeAxis()
245        t=feature.getTimeAxis()
246        if None in [lon, lat, time]:
247            #TODO need to return a suitable wcs error.
248            print 'warning, could not get correct axis info'
249       
250        #create selection dictionary:
251        sel={}
252        sel[lat]=(boundingbox[1], boundingbox[3])
253        sel[lon]=(boundingbox[0], boundingbox[2])
254        sel[t]=timesequence
255        #z is the 4th axis (eg height or pressure).
256        #NOTE, need to decide whether bounding box is of form: x1,y1,z1,x2,y2,z2 or x1,y1,x2,y2,z1,z2
257        #currently the latter is implemented.
258       
259        if len(boundingbox)  == 6:
260            for ax in feature.getAxisLabels():
261                if ax not in [lat, lon, t]:
262                    #must be Z 
263                    z=str(ax)
264                    sel[z]=(boundingbox[4], boundingbox[5])
265       
266       
267        axisNames=feature.getAxisLabels()
268   
269                   
270        # Extract via CSML.subsetToGridSeries()
271        if store:
272            #need to farm off to WPS
273            #but for now...
274            filename = extractToNetCDF(feature, sel, publish = True) 
275        else:
276            filename = extractToNetCDF(feature, sel)
277   
278           
279        #use the randomly allocated filename as a basis for an identifier           
280        f=os.path.basename(filename)
281        c.fileID=os.path.splitext(f)[0]
282       
283        #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.
284                   
285        if store:
286            if status:
287                #STORE = true, STATUS = true:
288                #hand off file "id" to StatusController to determine correct ExectuteResponse type response.
289                status=StatusController()
290                jobID=os.path.splitext(os.path.basename(filename)[9:])[0] #remove the 'csml_wxs_' prefix and file extension to create a jobID
291                return status.getStatus(jobID)                   
292            else:
293                #STORE=true, STATUS = false: Return Coverage XML document with link to file.
294                #use the temp file name (minus extension) as an ID
295                c.hyperlink = 'http://'+request.environ['HTTP_HOST']+'/'+os.path.basename(request.environ['paste.config']['app_conf']['publish_dir'])+'/'+os.path.basename(filename)
296                r=render_response('wcs_getCoverageResponse', format='xml')
297                r.headers['content-type'] = 'text/xml'
298                #write ndgSec to text file and store with coverage file:
299                textName=request.environ['paste.config']['app_conf']['publish_dir']+'/'+os.path.splitext(os.path.basename(filename))[0]+'.txt'
300                secText=open(textName, 'w')
301                if 'ndgSec' in session:
302                    securityinfo=str(session['ndgSec'])
303                else:
304                    securityinfo='No Security'
305                secText.write(securityinfo)
306                secText.close()                 
307                return r                                 
308        else:               
309            #STORE = FALSE, STATUS therefore irrelevant, return Multipart Mime.
310            netcdfFile=open(filename, 'r')
311            c.hyperlink="cid:coverage.nc"
312            xmlfile=render_response('wcs_getCoverageResponse', format='xml')
313            xmlfile.headers['content-type'] = 'text/xml'
314            multipart=self._createMultipartMime(xmlfile, netcdfFile)       
315            msg=multipart
316            #msg=open(multipart, 'r').readlines()
317            return Response(content=msg, mimetype='multipart') 
318                 
319        #comment out for now while testing         
320        #except Exception, e:
321            #if isinstance(e, OWS_E.OwsError):
322               #raise e
323            #elif isinstance(e, ValueError):
324                #if e.message == '<p> Access Denied </p><p>Not Logged in</p>':
325                    #c.msg=e.message
326                    ##h.redirect_to('/login') 
327                    #return render_response('wcs_accessDenied', mimetype ='text/html')
328                #else:
329                    #raise OWS_E.NoApplicableCode(e)
330            #else:
331               #raise OWS_E.NoApplicableCode(e)
332       
333       
Note: See TracBrowser for help on using the repository browser.