source: TI05-delivery/ows_framework/trunk/ows_server/ows_server/controllers/csml_wcs1_0_0.py @ 3248

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

reverting last commit

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
36import paste
37
38import logging
39logger = logging.getLogger('ows_server.controllers.csml_wcs_1_0')
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 CsmlWcs100Controller(OwsController):
57    _ows_parameters = {
58        'Format': make_domain(['text/xml']),
59        'ExceptionFormat': make_domain(['text/xml']),
60        }
61
62       
63    def _createMultipartMime(self, xml, netcdf):
64        #returns a multipart mime file containing the coverage xml + a netcdf file
65
66        # Create the container (outer) email message.
67        msg=MIMEMultipart()
68
69        xmlfile =StringIO.StringIO(xml)
70        xmlfile.readline(), xmlfile.readline() #don't read in first 2 lines (the content type) as MIMEBase also provides it.
71
72
73        #add the XML part
74        submsg=MIMEText(xmlfile.read(), _subtype='xml')
75        submsg.add_header('Content-ID', '<coverage.xml>')
76        submsg.add_header('Content-Disposition', 'attachment; filename="coverage.xml"')
77        #submsg.set_type('text/xml; name="coverage.xml"')
78        msg.attach(submsg)
79
80
81        #add the NetCDF part
82        submsg= MIMEBase('application', 'CF-netcdf') #check in ogc docs
83        submsg.set_payload(netcdf.read())
84        submsg.set_type('application/CF-netcdf')
85        submsg.add_header('Content-Disposition', 'attachment; filename="coverage.nc"')
86        submsg.add_header('Content-ID', '<coverage.nc>')
87        netcdf.close()
88        # Encode the payload using Base64
89        encoders.encode_base64(submsg)
90        msg.attach(submsg)
91
92        #return the message
93        return  msg
94
95
96    def _loadFeatureDimensions(self, feature):
97        dims = {}
98        lon=feature.getLongitudeAxis()
99        lat=feature.getLongitudeAxis()
100        domain=feature.getDomain()
101        for axis_name, axis in domain.iteritems():
102            if axis_name in [lon,lat]:
103                continue
104            dims[axis_name] =            Domain(possibleValues=PossibleValues.fromAllowedValues(axis),
105                                 #!TODO: this is a fudge until we can deduce UOM.
106                                 valuesUnit=ValuesUnit(uoms=[''],
107                                                       referenceSystems=['']))
108        return dims
109
110    def _loadFeatureSummary(self, feature):
111        dims = self._loadFeatureDimensions(feature)
112        #TODO - need to ensure proper values for Name. ID and Description are populated.
113        cvgTitle=[feature.description.CONTENT]
114        cvgDescription=feature.description.CONTENT
115        cvgAbstract=[feature.description.CONTENT]
116        csmlbbox=feature.getCSMLBoundingBox()
117        if csmlbbox is not None:
118            bbox=csmlbbox.getBox()                   
119            timeLimits=csmlbbox.getTimeLimits()
120        else:
121            csmlbbox=c.dataset.getCSMLBoundingBox()
122            bbox=csmlbbox.getBox()         
123            timeLimits=csmlbbox.getTimeLimits()
124        #crs= csmlbbox.getCRSName()
125        crs=feature.getNativeCRS()
126        crslist=[crs] #TODO, get these crs from the csml features
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, timelimits=timeLimits
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(CsmlWcs100Controller, 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        sm.sectionrequested = self.sectionrequested
148        return sm
149
150    def _buildCoverageDescriptions(self):
151        """ builds a collection of CoverageDescription objects - used for DescribeCoverage """
152        CoverageDescriptions=[]
153        csmlbbox=c.dataset.getCSMLBoundingBox()
154        dsbbox=csmlbbox.getBox()
155        dstimes=csmlbbox.getTimeLimits()
156        dscrs=csmlbbox.getCRSName()
157        for f in self.features:
158            feature = self.features[f]
159            csmlbbox=feature.getCSMLBoundingBox()
160            if csmlbbox is None: #use the bounding box of the  dataset.
161                bbox=dsbbox 
162                timeLimits=dstimes
163                crsname=dscrs
164            else: #use the feature's bounding box info
165                bbox=csmlbbox.getBox()
166                timeLimits=csmlbbox.getTimeLimits()
167                crsname=csmlbbox.getCRSName()                         
168            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)
169            CoverageDescriptions.append(cd)
170        return CoverageDescriptions
171
172    @operation
173    @parameter('Format', possibleValues=['text/xml'])
174    @parameter('Service', possibleValues=['WCS'], required=True)
175    @parameter('Version', possibleValues=['','1.0.0'])
176    @parameter('Section', possibleValues=['/','/Service', '/Capability', '/ContentMetadata'])
177    def GetCapabilities(self, uri, service=None, version='1.0.0', section=None):
178        """
179        @note: format and updatesequence parameters are not supported
180            by this WMS.
181
182        """
183        # Populate the context object with information required by the template
184
185        #get doc from cache or disk:
186        self.sectionrequested=section
187        try:
188            rstatus,c.dataset=interface.GetParsedCSML(uri)               
189            if not rstatus: 
190                c.xml='<div class="error">%s</div>'%c.dataset
191                resp=render_response('error')
192                return resp
193   
194            if type(c.dataset) is str:
195                #If not a csml datset is some message from exist such as 'access denied'
196                return Response(c.dataset)
197            return self._renderCapabilities('wcs1_0_0_Capabilities')
198        except Exception, e:
199            if isinstance(e, OWS_E.OwsError):
200               c.ex=e.report               
201               r=render_response('ogc_se', format='xml')
202               r.headers['content-type'] = 'text/xml'
203               return r
204            elif isinstance(e, ValueError):
205                c.xml='<div class="error">%s</div>'%e
206                return render_response('error')
207            else:
208                raise OWS_E.NoApplicableCode(e)
209           
210    @operation
211    @parameter('Service', possibleValues=['WCS'], required=True)
212    @parameter('Version', possibleValues=['','1.0.0'], required=True)
213    @parameter('Coverage', required=True)
214    @parameter('Format', possibleValues=['text/xml'])  #IS THIS MANDATORY
215    def DescribeCoverage(self, uri=None, version=None, service=None, coverage=None, format='text/xml'):
216        """
217        WCS DescribeCoverage operation
218        """
219        identifiers=coverage
220        try:
221            self.features={} #dictionary to hold requested coverages
222            rstatus,c.dataset=interface.GetParsedCSML(uri)
223            if not rstatus: raise ValueError(c.dataset)
224            for ident in identifiers.split(','):
225                feature = c.dataset.getFeature(ident)
226                if feature is None:
227                    raise OWS_E.InvalidParameterValue('Coverage  with name=%s not found'%ident, 'identifiers')
228                self.features[ident]=feature
229            if not hasattr(self,'covDescs'):
230                self.covDescs=self._buildCoverageDescriptions()
231            c.covDescs=self.covDescs
232            r=render_response('wcs1_0_0_DescribeCoverageResponse', format='xml')
233            r.headers['content-type'] = 'text/xml'
234            return r
235        except Exception, e:
236            if isinstance(e, OWS_E.OwsError):
237                c.ex=e.report               
238                r=render_response('ogc_se', format='xml')
239                r.headers['content-type'] = 'application/vnd.ogc.se_xml'
240                return r
241            elif isinstance(e, ValueError):
242                c.xml='<div class="error">%s</div>'%e
243                return render_response('error')
244            else:
245               raise OWS_E.NoApplicableCode(e)
246
247    @operation
248    @parameter('Version', possibleValues=['','1.0.0'], required=True)
249    @parameter('Coverage', required=True)
250    @parameter('CRS')
251    @parameter('Response_CRS')
252    @parameter('BBox', required=True, validator=V.bbox_2or3d)
253    @parameter('Time', validator=V.iso8601_time)
254    @parameter('Format', possibleValues=['cf-netcdf'], required=True)
255    @parameter('Store', validator = V.boolean('Store'))
256    @parameter('Status', validator = V.boolean('Status'))
257    #TODO some more parameters to add here
258    # Dimension parameters Time, Elevation, etc. are handled separately
259    def GetCoverage(self, uri, version, format, coverage, bbox, time =None, crs=None, response_crs=None, exceptions='application/vnd.ogc.se_xml',store=False, status=False):
260        # Retrieve dataset and selected feature         
261        identifier=coverage
262        boundingbox=bbox
263        try:
264            rstatus,dataset=interface.GetParsedCSML(uri)               
265            if not rstatus: 
266                c.xml='<div class="error">%s</div>'%dataset
267                resp=render_response('error')
268                return resp
269            feature = dataset.getFeature(identifier)
270            if feature is None:
271                raise OWS_E.InvalidParameterValue('Coverage not found', 'identifier')
272           
273                       
274            #set bounding box
275           
276            lon=feature.getLongitudeAxis()
277            lat=feature.getLatitudeAxis()
278            t=feature.getTimeAxis()
279            if None in [lon, lat, t]:
280                #TODO need to return a suitable wcs error.
281                print 'warning, could not get correct axis info'
282                #best guess!
283                if t is None:
284                    t='time'
285                if lon is None:
286                    lon = 'longitude'
287                if lat is None:
288                    lat = 'latitude'
289            #create selection dictionary:
290            sel={}
291            sel[lat]=(boundingbox[1], boundingbox[3])
292            sel[lon]=(boundingbox[0], boundingbox[2])
293            if time is not None:
294                if  type(time) is unicode:
295                    sel[t]=str(time)
296                else:
297                    sel[t]=time
298            #z is the 4th axis (eg height or pressure).
299            #NOTE, need to decide whether bounding box is of form: x1,y1,z1,x2,y2,z2 or x1,y1,x2,y2,z1,z2
300            #currently the latter is implemented.
301           
302            if len(boundingbox)  == 6:
303                for ax in feature.getAxisLabels():
304                    if ax not in [lat, lon, t]:
305                        #must be Z 
306                        z=str(ax)
307                        sel[z]=(boundingbox[4], boundingbox[5])
308           
309           
310            axisNames=feature.getAxisLabels()
311   
312                # Extract via CSML.subsetToGridSeries()
313            if store:
314                #need to farm off to WPS
315                #but for now...
316                filename = extractToNetCDF(feature, sel, publish = True) 
317            else:
318                filename = extractToNetCDF(feature, sel)
319       
320               
321            #use the randomly allocated filename as a basis for an identifier           
322            f=os.path.basename(filename)
323            c.fileID=os.path.splitext(f)[0]
324           
325            #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.
326                       
327            if store:
328                if status:
329                    #STORE = true, STATUS = true:
330                    #hand off file "id" to StatusController to determine correct ExectuteResponse type response.
331                    status=StatusController()
332                    jobID=os.path.splitext(os.path.basename(filename)[9:])[0] #remove the 'csml_wxs_' prefix and file extension to create a jobID
333                    return status.getStatus(jobID)                   
334                else:
335                    #STORE=true, STATUS = false: Return Coverage XML document with link to file.
336                    #use the temp file name (minus extension) as an ID
337   
338                    try:
339                        hostname=request.environ['paste.config']['app_conf']['proxyname']
340                    except:
341                        hostname=hostname=request.environ['HTTP_HOST']                   
342                    c.hyperlink ='http://'+hostname+'/'+os.path.basename(request.environ['paste.config']['app_conf']['publish_dir'])+'/'+os.path.basename(filename)
343                    r=render_response('wcs_getCoverageResponse', format='xml')
344                    r.headers['content-type'] = 'text/xml'
345                    #write ndgSec to text file and store with coverage file:
346                    textName=request.environ['paste.config']['app_conf']['publish_dir']+'/'+os.path.splitext(os.path.basename(filename))[0]+'.txt'
347                    secText=open(textName, 'w')
348                    if 'ndgSec' in session:
349                        username=str(session['ndgSec']['u'])
350                        securityinfo=username
351                    else:
352                        securityinfo='No Security'
353                    secText.write(securityinfo)
354                    secText.close()                 
355                    return r                                 
356            else:               
357                #STORE = FALSE, STATUS therefore irrelevant, return file
358                fileToReturn=open(filename, 'r')
359                if os.path.splitext(filename)[1]=='.nc':
360                    mType='application/cf-netcdf'
361                else:
362                    mType='application/unknown'
363                    #Differnce in how Content-Types are handled between pylons 0.9.5 and 0.9.6
364            try:
365                #0.9.6
366                pylons.response.headers['Content-Type']=mType               
367                pylons.response.headers['Content-Disposition'] = paste.httpheaders.CONTENT_DISPOSITION(attachment=True, filename=f)
368                return pylons.response(content=fileToReturn)
369            except:
370                #0.9.5               
371                r=Response(content=fileToReturn, mimetype=mType)     
372                r.headers['Content-Disposition'] = paste.httpheaders.CONTENT_DISPOSITION(attachment=True, filename=f)               
373                return r
374        except Exception, e:
375            if isinstance(e, OWS_E.OwsError):
376                c.ex=e.report               
377                r=render_response('ogc_se', format='xml')
378                #r.headers['content-type'] = 'text/xml'
379                r.headers['content-type'] = 'application/vnd.ogc.se_xml'
380                return r
381            elif isinstance(e, ValueError):
382                c.xml='<div class="error">%s</div>'%e
383                return render_response('error')
384            else:
385               raise OWS_E.NoApplicableCode(e)
386       
387       
Note: See TracBrowser for help on using the repository browser.