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

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

integrating Trajectory subsetting into pylons wcs

RevLine 
[2589]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
[2701]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
[2674]22import os, time, string, StringIO
[2589]23
24from ows_server.lib.base import *
25from ows_server.lib.decorators import *
[2600]26import ows_server.lib.validators as V
[2589]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
[2592]33from ows_server.lib.csml_util import get_csml_doc, extractToNetCDF
[2701]34from ows_server.lib.ndgInterface import interface
[2726]35from ows_server.models.wcs_CoverageDescription import CoverageDescription
[2650]36
[2737]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
[2738]42    from email import encoders
[2737]43except:
44    #python 2.4
[2780]45    from email.MIMEText import MIMEText
46    from email.MIMEMultipart import MIMEMultipart
47    from email.MIMEBase import MIMEBase
[2738]48    from email import Encoders as encoders
[2737]49
[2589]50
51class CsmlWcsController(OwsController):
52    _ows_parameters = {
53        'Format': make_domain(['text/xml']),
54        'ExceptionFormat': make_domain(['text/xml']),
55        }
56
[2674]57
58
[2648]59    def _createMultipartMime(self, xml, netcdf):
60        #returns a multipart mime file containing the coverage xml + a netcdf file
[2726]61
[2648]62        # Create the container (outer) email message.
[2780]63        msg=MIMEMultipart()
[2726]64
[2653]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.
[2726]67
68
[2648]69        #add the XML part
[2653]70        submsg=MIMEText(xmlfile.read(), _subtype='xml')
71        submsg.add_header('Content-ID', '<coverage.xml>')
[2667]72        submsg.add_header('Content-Disposition', 'attachment; filename="coverage.xml"')
73        #submsg.set_type('text/xml; name="coverage.xml"')
[2648]74        msg.attach(submsg)
[2589]75
[2726]76
[2648]77        #add the NetCDF part
[2653]78        submsg= MIMEBase('application', 'CF-netcdf') #check in ogc docs
[2648]79        submsg.set_payload(netcdf.read())
[2667]80        submsg.set_type('application/CF-netcdf')
81        submsg.add_header('Content-Disposition', 'attachment; filename="coverage.nc"')
[2653]82        submsg.add_header('Content-ID', '<coverage.nc>')
[2648]83        netcdf.close()
84        # Encode the payload using Base64
85        encoders.encode_base64(submsg)
86        msg.attach(submsg)
[2726]87
[2648]88        #return the message
89        return msg
90
[2726]91
[2589]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.
[2667]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]:
[2726]102                continue
[2667]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=['']))
[2589]107        return dims
108
109    def _loadFeatureSummary(self, feature):
110        dims = self._loadFeatureDimensions(feature)
[2653]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.
[2589]116        return WcsDatasetSummary(identifier=feature.id,
[2653]117                                 titles=cvgTitle,
118                                 boundingBoxes=[BoundingBox([bbox[0],bbox[1]], [bbox[2],bbox[3]],
119                                 crs='CRS:84')], dimensions=dims,  description=cvgDescription,abstracts=cvgAbstract
[2589]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'])
[2726]130        # Add a DatasetSummary for each feature
[2711]131
[2726]132        for f_n in c.dataset.getFeatureList():
[2589]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
[2726]138    def _buildCoverageDescriptions(self):
139        """ builds a collection of CoverageDescription objects - used for DescribeCoverage """
140        CoverageDescriptions=[]
141        for f in self.features:
[2729]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')])
[2726]145          CoverageDescriptions.append(cd)
146        return CoverageDescriptions
147
148
149
150
[2589]151    @operation
152    @parameter('Format', possibleValues=['text/xml'])
153    @parameter('Service', possibleValues=['WCS'], required=True)
154    @parameter('Version', possibleValues=['1.1.0'])
[2780]155    def GetCapabilities(self, uri, service=None, version=None):
[2589]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
[2726]162
[2667]163        #get doc from cache or disk:
[2780]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                #print e
177                #print type(e)
178                #print dir(e)
179                if e.message == '<p> Access Denied </p><p>Not Logged in</p>':
180                    c.msg=e.message
181                    #h.redirect_to('/login') 
182                    return render_response('wcs_accessDenied', mimetype ='text/html')
183                else:
184                    raise OWS_E.NoApplicableCode(e)
185            else:
186               raise OWS_E.NoApplicableCode(e)
[2737]187           
188           
[2589]189    @operation
[2711]190    @parameter('Service', possibleValues=['WCS'], required=True)
191    @parameter('Version', possibleValues=['1.1.0'])
[2726]192    @parameter('Identifiers', required=True)
[2711]193    @parameter('Format', possibleValues=['text/xml'], required=True)  #IS THIS MANDATORY
[2780]194    def DescribeCoverage(self, uri, version, service, identifiers, format='text/xml'):
[2711]195        """
196        WCS DescribeCoverage operation
197        """
[2740]198        try:
199            self.features={} #dictionary to hold requested coverages
[2780]200            rstatus,c.dataset=interface.GetParsedCSML(uri)
[2740]201            if not rstatus: raise ValueError(c.dataset)
202            for ident in identifiers.split(','):
203                feature = c.dataset.getFeature(ident)
204                if feature is None:
205                    raise OWS_E.InvalidParameterValue('Coverage  with id=%s not found'%ident, 'identifiers')
206                self.features[ident]=feature
207            c.covDescs=self._buildCoverageDescriptions()
208            r=render_response('wcs_DescribeCoverageResponse', format='xml')
209            r.headers['content-type'] = 'text/xml'
210            return r
211        except Exception, e:
212            if isinstance(e, OWS_E.OwsError):
213               raise e
214            elif isinstance(e, ValueError):
215                if e.message == '<p> Access Denied </p><p>Not Logged in</p>':
216                    c.msg=e.message
217                    #h.redirect_to('/login') 
218                    return render_response('wcs_accessDenied', mimetype ='text/html')
219                else:
220                    raise OWS_E.NoApplicableCode(e)
221            else:
222               raise OWS_E.NoApplicableCode(e)
[2726]223
[2711]224    @operation
[2592]225    @parameter('Version', possibleValues=['1.1.0'], required=True)
226    @parameter('Identifier', required=True)
[2689]227    @parameter('BoundingBox', required=True, validator=V.bbox_2or3d)
[2644]228    @parameter('TimeSequence',required=True, validator=V.iso8601_time)
[2625]229    @parameter('Format', possibleValues=['application/netcdf'], required=True)
[2650]230    @parameter('Store', validator = V.boolean('Store'))
231    @parameter('Status', validator = V.boolean('Status'))
[2689]232    #TODO some more parameters to add here
[2589]233    # Dimension parameters Time, Elevation, etc. are handled separately
[2780]234    def GetCoverage(self, uri, version, format, identifier, boundingbox, timesequence, store=False, status=False):
235        # Retrieve dataset and selected feature           
[2782]236        #try:
237        rstatus,dataset=interface.GetParsedCSML(uri)
238        if not rstatus: raise ValueError(dataset)
239        feature = dataset.getFeature(identifier)
240        if feature is None:
241            raise OWS_E.InvalidParameterValue('Coverage not found', 'identifier')
[2780]242       
[2782]243                   
244        #set bounding box
245       
246        lon=feature.getLongitudeAxis()
247        lat=feature.getLatitudeAxis()
248        t=feature.getTimeAxis()
249        if None in [lon, lat, time]:
250            #TODO need to return a suitable wcs error.
251            print 'warning, could not get correct axis info'
252       
253        #create selection dictionary:
254        sel={}
255        sel[lat]=(boundingbox[1], boundingbox[3])
256        sel[lon]=(boundingbox[0], boundingbox[2])
257        sel[t]=timesequence
258        #z is the 4th axis (eg height or pressure).
259        #NOTE, need to decide whether bounding box is of form: x1,y1,z1,x2,y2,z2 or x1,y1,x2,y2,z1,z2
260        #currently the latter is implemented.
261       
262        if len(boundingbox)  == 6:
263            for ax in feature.getAxisLabels():
264                if ax not in [lat, lon, t]:
265                    #must be Z 
266                    z=str(ax)
267                    sel[z]=(boundingbox[4], boundingbox[5])
268       
269       
270        axisNames=feature.getAxisLabels()
271   
272                   
273        # Extract via CSML.subsetToGridSeries()
274        if store:
275            #need to farm off to WPS
276            #but for now...
277            filename = extractToNetCDF(feature, sel, publish = True) 
278        else:
279            filename = extractToNetCDF(feature, sel)
280   
[2690]281           
[2782]282        #use the randomly allocated filename as a basis for an identifier           
283        f=os.path.basename(filename)
284        c.fileID=os.path.splitext(f)[0]
[2780]285       
[2782]286        #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.
287                   
288        if store:
289            if status:
290                #STORE = true, STATUS = true:
291                #hand off file "id" to StatusController to determine correct ExectuteResponse type response.
292                status=StatusController()
293                jobID=os.path.splitext(os.path.basename(filename)[9:])[0] #remove the 'csml_wxs_' prefix and file extension to create a jobID
294                return status.getStatus(jobID)                   
[2607]295            else:
[2782]296                #STORE=true, STATUS = false: Return Coverage XML document with link to file.
297                #use the temp file name (minus extension) as an ID
298                c.hyperlink = 'http://'+request.environ['HTTP_HOST']+'/'+os.path.basename(request.environ['paste.config']['app_conf']['publish_dir'])+'/'+os.path.basename(filename)
299                r=render_response('wcs_getCoverageResponse', format='xml')
300                r.headers['content-type'] = 'text/xml'
301                #write ndgSec to text file and store with coverage file:
302                textName=request.environ['paste.config']['app_conf']['publish_dir']+'/'+os.path.splitext(os.path.basename(filename))[0]+'.txt'
303                secText=open(textName, 'w')
304                if 'ndgSec' in session:
305                    securityinfo=str(session['ndgSec'])
[2650]306                else:
[2782]307                    securityinfo='No Security'
308                secText.write(securityinfo)
309                secText.close()                 
310                return r                                 
311        else:               
312            #STORE = FALSE, STATUS therefore irrelevant, return Multipart Mime.
313            netcdfFile=open(filename, 'r')
314            c.hyperlink="cid:coverage.nc"
315            xmlfile=render_response('wcs_getCoverageResponse', format='xml')
316            xmlfile.headers['content-type'] = 'text/xml'
317            multipart=self._createMultipartMime(xmlfile, netcdfFile)       
318            msg=multipart
319            #msg=open(multipart, 'r').readlines()
320            return Response(content=msg, mimetype='multipart') 
[2650]321                 
[2782]322        #comment out for now while testing         
323        #except Exception, e:
324            #if isinstance(e, OWS_E.OwsError):
325               #raise e
326            #elif isinstance(e, ValueError):
327                #if e.message == '<p> Access Denied </p><p>Not Logged in</p>':
328                    #c.msg=e.message
329                    ##h.redirect_to('/login') 
330                    #return render_response('wcs_accessDenied', mimetype ='text/html')
331                #else:
332                    #raise OWS_E.NoApplicableCode(e)
333            #else:
334               #raise OWS_E.NoApplicableCode(e)
[2592]335       
[2726]336       
Note: See TracBrowser for help on using the repository browser.