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

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

Added proxyname to development.ini. Needed for wcs retrieval and demo today, cannot see another way of getting at it. Also some changes to 3D bounding box validator to allow CRS atttribute

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                c.xml='<div class="error">%s</div>'%e
199                return render_response('error')
200            else:
201                raise OWS_E.NoApplicableCode(e)
202           
203    @operation
204    @parameter('Service', possibleValues=['WCS'], required=True)
205    @parameter('Version', possibleValues=['1.1.0'])
206    @parameter('Identifiers', required=True)
207    @parameter('Format', possibleValues=['text/xml'], required=True)  #IS THIS MANDATORY
208    def DescribeCoverage(self, uri, version, service, identifiers, format='text/xml'):
209        """
210        WCS DescribeCoverage operation
211        """
212        try:
213            self.features={} #dictionary to hold requested coverages
214            rstatus,c.dataset=interface.GetParsedCSML(uri)
215            if not rstatus: raise ValueError(c.dataset)
216            for ident in identifiers.split(','):
217                feature = c.dataset.getFeature(ident)
218                if feature is None:
219                    raise OWS_E.InvalidParameterValue('Coverage  with id=%s not found'%ident, 'identifiers')
220                self.features[ident]=feature
221            c.covDescs=self._buildCoverageDescriptions()
222            r=render_response('wcs_DescribeCoverageResponse', format='xml')
223            r.headers['content-type'] = 'text/xml'
224            return r
225        except Exception, e:
226            if isinstance(e, OWS_E.OwsError):
227               raise e
228            elif isinstance(e, ValueError):
229                c.xml='<div class="error">%s</div>'%e
230                return render_response('error')
231            else:
232               raise OWS_E.NoApplicableCode(e)
233
234    @operation
235    @parameter('Version', possibleValues=['1.1.0'], required=True)
236    @parameter('Identifier', required=True)
237    @parameter('BoundingBox', required=True, validator=V.bbox_2or3d)
238    @parameter('TimeSequence',required=True, validator=V.iso8601_time)
239    @parameter('Format', possibleValues=['application/netcdf'], required=True)
240    @parameter('Store', validator = V.boolean('Store'))
241    @parameter('Status', validator = V.boolean('Status'))
242    #TODO some more parameters to add here
243    # Dimension parameters Time, Elevation, etc. are handled separately
244    def GetCoverage(self, uri, version, format, identifier, boundingbox, timesequence, store=False, status=False):
245        # Retrieve dataset and selected feature           
246        try:
247            rstatus,dataset=interface.GetParsedCSML(uri)               
248            if not rstatus: 
249                c.xml='<div class="error">%s</div>'%dataset
250                resp=render_response('error')
251                return resp
252            feature = dataset.getFeature(identifier)
253            if feature is None:
254                raise OWS_E.InvalidParameterValue('Coverage not found', 'identifier')
255           
256                       
257            #set bounding box
258           
259            lon=feature.getLongitudeAxis()
260            lat=feature.getLatitudeAxis()
261            t=feature.getTimeAxis()
262            if None in [lon, lat, time]:
263                #TODO need to return a suitable wcs error.
264                print 'warning, could not get correct axis info'
265           
266            #create selection dictionary:
267            sel={}
268            sel[lat]=(boundingbox[1], boundingbox[3])
269            sel[lon]=(boundingbox[0], boundingbox[2])
270            if  type(timesequence) is unicode:
271                sel[t]=str(timesequence)
272            else:
273                sel[t]=timesequence
274           
275            #z is the 4th axis (eg height or pressure).
276            #NOTE, need to decide whether bounding box is of form: x1,y1,z1,x2,y2,z2 or x1,y1,x2,y2,z1,z2
277            #currently the latter is implemented.
278           
279            if len(boundingbox)  == 6:
280                for ax in feature.getAxisLabels():
281                    if ax not in [lat, lon, t]:
282                        #must be Z 
283                        z=str(ax)
284                        sel[z]=(boundingbox[4], boundingbox[5])
285           
286           
287            axisNames=feature.getAxisLabels()
288   
289            # Extract via CSML.subsetToGridSeries()
290            if store:
291                #need to farm off to WPS
292                #but for now...
293                filename = extractToNetCDF(feature, sel, publish = True) 
294            else:
295                filename = extractToNetCDF(feature, sel)
296       
297               
298            #use the randomly allocated filename as a basis for an identifier           
299            f=os.path.basename(filename)
300            c.fileID=os.path.splitext(f)[0]
301           
302            #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.
303                       
304            if store:
305                if status:
306                    #STORE = true, STATUS = true:
307                    #hand off file "id" to StatusController to determine correct ExectuteResponse type response.
308                    status=StatusController()
309                    jobID=os.path.splitext(os.path.basename(filename)[9:])[0] #remove the 'csml_wxs_' prefix and file extension to create a jobID
310                    return status.getStatus(jobID)                   
311                else:
312                    #STORE=true, STATUS = false: Return Coverage XML document with link to file.
313                    #use the temp file name (minus extension) as an ID
314
315                    try:
316                        hostname=request.environ['paste.config']['app_conf']['proxyname']
317                    except:
318                        hostname=hostname=request.environ['HTTP_HOST']                   
319                    c.hyperlink ='http://'+hostname+'/'+os.path.basename(request.environ['paste.config']['app_conf']['publish_dir'])+'/'+os.path.basename(filename)
320                    r=render_response('wcs_getCoverageResponse', format='xml')
321                    r.headers['content-type'] = 'text/xml'
322                    #write ndgSec to text file and store with coverage file:
323                    textName=request.environ['paste.config']['app_conf']['publish_dir']+'/'+os.path.splitext(os.path.basename(filename))[0]+'.txt'
324                    secText=open(textName, 'w')
325                    if 'ndgSec' in session:
326                        username=str(session['ndgSec']['u'])
327                        securityinfo=username
328                    else:
329                        securityinfo='No Security'
330                    secText.write(securityinfo)
331                    secText.close()                 
332                    return r                                 
333            else:               
334                #STORE = FALSE, STATUS therefore irrelevant, return Multipart Mime.
335                netcdfFile=open(filename, 'r')
336                c.hyperlink="cid:coverage.nc"
337                xmlfile=render_response('wcs_getCoverageResponse', format='xml')
338                xmlfile.headers['content-type'] = 'text/xml'
339                multipart=self._createMultipartMime(xmlfile, netcdfFile)       
340                msg=multipart
341                try:
342                    #0.9.6
343                    pylons.response.headers['Content-Type']='multipart'
344                    return pylons.response(content=msg)
345                except:
346                    #0.9.5
347                    return Response(content=msg, mimetype='multipart')                     
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_response('error')
354            else:
355               raise OWS_E.NoApplicableCode(e)
356       
357       
Note: See TracBrowser for help on using the repository browser.