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

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

capabilities doc now validates, although some optional stuff still missing

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