source: DPPP/kml/csml2kml/python/csml2kmlpylon/csml2kmlpylon/controllers/csmlGrapher.py @ 3592

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/DPPP/kml/csml2kml/python/csml2kmlpylon/csml2kmlpylon/controllers/csmlGrapher.py@3592
Revision 3592, 12.4 KB checked in by mkochan, 14 years ago (diff)

Turned the debug mode OFF in csml2kmlpylon.

Line 
1# Pylons-specific imports
2import logging
3from csml2kmlpylon.lib.base import *
4
5# Other imports
6import Image
7import pylab
8from pylab import *
9from datetime import datetime, timedelta
10import re
11import urllib
12from cStringIO import StringIO
13from tempfile import NamedTemporaryFile
14from cElementTree import Element, SubElement, ElementTree, XML
15from urllib import quote
16
17import csml
18import csml2kml.Station
19from csml2kml.utils import wget
20
21log = logging.getLogger(__name__)
22
23class CsmlgrapherController(BaseController):
24
25    def __call__(self, environ, start_response):
26        '''
27        Initialise the web service by overriding a method that always get called upon controller construction.
28        More specifically, read in the config file.
29        '''
30
31        # The name of the config file is set in the server's "development.ini" file.
32        configFileName = config['app_conf']['csmlGrapher.configfile']
33       
34        # Load the configuration XML element (but only the part pertaining to the web service)
35        grapherConfig = (ElementTree().parse(configFileName)).find('GrapherWebService')
36
37        # Set the configurable variables
38        self.displayIntervalStart = grapherConfig.find('DisplayIntervalStart').text
39        self.displayIntervalEnd = grapherConfig.find('DisplayIntervalEnd').text
40        self.geoServerUrl = grapherConfig.find('GeoServerURL').text
41
42        # Call the __call__ method of the parent class
43        return BaseController.__call__(self, environ, start_response)
44
45    def _retrieveCsmlPointSeriesFeature(self, station_name, feature_id):
46        '''
47        @return: A PointSeriesFeature object representing the single CSML feature.
48        @throws: A LookupError exception on retrieval error.
49        '''
50
51        # Prepare the request for the GeoServer.
52        geoServerRequestUrl = urllib.quote(self.geoServerUrl + '?request=getfeature&service=wfs&version=1.1.0&typename=csml:PointSeriesFeature&filter=<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml"><ogc:And><ogc:PropertyIsEqualTo><ogc:PropertyName>gml:description</ogc:PropertyName><ogc:Literal>'+ station_name +'</ogc:Literal></ogc:PropertyIsEqualTo><ogc:PropertyIsEqualTo><ogc:PropertyName>csml:parameter/swe:Phenomenon/gml:name</ogc:PropertyName><ogc:Literal>'+ feature_id +'</ogc:Literal></ogc:PropertyIsEqualTo><ogc:PropertyIsBetween><ogc:PropertyName>csml:value/csml:PointSeriesCoverage/csml:pointSeriesDomain/csml:TimeSeries/csml:timePositionList</ogc:PropertyName><ogc:LowerBoundary><ogc:Literal>'+ self.displayIntervalStart +'</ogc:Literal></ogc:LowerBoundary><ogc:UpperBoundary><ogc:Literal>'+ self.displayIntervalEnd +'</ogc:Literal></ogc:UpperBoundary></ogc:PropertyIsBetween></ogc:And></ogc:Filter>', '/._:?&=')
53
54        # Acquire the GeoServer response and parse it into a CSMLFeatureCollection object
55        geoServerResponse = wget(geoServerRequestUrl)
56        if not geoServerResponse:
57            raise LookupError('Cannot acquire response from server (wrong URL or server down)')
58        csmlFeatureCollection = csml.parser.CSMLFeatureCollection()
59        csmlFeatureCollection.fromXML( XML(geoServerResponse) )
60
61        # Now, csmlFeatureCollection should only contain a single CSML feature (if the feature was found).
62        # Isolate that CSML feature.
63        try:
64            csmlFeature = csmlFeatureCollection.featureMembers
65        except AttributeError:
66            raise LookupError('No PointSeriesFeature with ID "' + feature_id + '" found in the collection.')
67
68        # Make sure that the feature is a PointSeriesFeature
69        if not isinstance(csmlFeature, csml.parser.PointSeriesFeature):
70            raise TypeError('CSML feature not a PointSeriesFeature')
71        csmlPointSeriesFeature = csmlFeature
72
73        return csmlPointSeriesFeature
74
75    def plot(self):
76        '''
77        Handler for plotting a specific PointSeries feature given by URL parameter "feature_id".
78        Retrieves the data from a web service, which should preferably run on the same machine.
79
80        Request parameters:
81                               feature_id      Unique identifier of the requested CSML PointSeries feature.
82                               station_name    Name of the requested station.
83                               
84        Response:              An image/png of the time series for the CSML PointSeries feature.
85        '''
86        def _plot_feature(feature):
87            '''
88            Plot a PointSeries feature using matplotlib, into a temporary file
89            '''
90           
91            # Make sure that time is considered to be UTC time. This is because all CSML data is supposed to be in UTC,
92            # but actually pylab is "time zone aware" and will interpret the time as local time zone time.
93            # Not quite sure if this would be a problem, but rather enforce it is UTC.
94            def _enforce_UTC_timezone(datestr):
95                if datestr[-1] == 'Z':
96                    return datestr
97                else:
98                    return datestr + 'Z'
99           
100            # Isolate time points   
101            times=feature.value.pointSeriesDomain.timePositionList.CONTENT.split()
102            times = map(_enforce_UTC_timezone, times)
103           
104            # Note the start and end date (they are actually date+time), and time span between them
105            start_date = dates.dateutil.parser.parse(times[0])
106            end_date = dates.dateutil.parser.parse(times[-1])
107            span = datestr2num(times[-1]) - datestr2num(times[0])
108
109            # Make a list of times as float numbers being days passed since start of epoch (here 01-01-0001)
110            elapsed_times = map(datestr2num, times)
111
112            # Generate tick locator and formatter; these determine the time axis: locator determines tick positions
113            # and formatter format of time
114            (tickLocator, tickFormatter) = dates.date_ticker_factory(span, numticks=8)
115
116            # We may need to do this for bodc data (i.e. not inline data)
117            # vals=feature.value.rangeSet.valueArray.valueComponent.insertedExtract.components.getDataFromChunks(0,19)
118
119            # Get values of the measured quantity
120            ql = feature.value.rangeSet.quantityList
121            vals = map(float, ql.CONTENT.split())
122
123            # Determine the units of measurement
124            uom=ql.uom.title()
125            if ql.uom.islower():
126                uom = uom.lower()
127            if ql.uom.isupper():
128                uom = uom.upper()
129
130            # Make sure the times are sorted in ascending order (and rearrange the values accordingly)
131            sortTimesExplicitly = True
132            if sortTimesExplicitly:
133                valuesAtTimes = {}
134                for i in range(len(elapsed_times)):
135                    valuesAtTimes[elapsed_times[i]] = vals[i]
136                    elapsed_times.sort()
137                for i in range(len(elapsed_times)):
138                    vals[i] = valuesAtTimes[elapsed_times[i]]
139
140            # Prepare the plot the figure (actual plotting actions are postponed until save)
141            fig = figure()
142            plot_date(elapsed_times, vals, 'b-', xdate=True, lw=1)
143            plot_date(elapsed_times, vals, 'go', markeredgecolor = 'g', xdate=True, lw=2)
144            ax = gca()
145            ax.xaxis.set_major_locator(tickLocator)
146            ax.xaxis.set_major_formatter(tickFormatter)
147            fig.autofmt_xdate()  # show times without overlaps
148            time_format = '%d-%b-%Y %H:%M:%S UTC'
149            xlabel('Times between %s and %s ' % (start_date.strftime(time_format), end_date.strftime(time_format)))
150            ylabel('Values [%s]' % uom)
151            title('"%s" (%s)' % (feature.id, feature.description.CONTENT))
152            grid(True)
153
154            # Save the figure to a temporary file
155            tempFile = NamedTemporaryFile(suffix='.png')
156            savefig(tempFile.name)
157           
158            return tempFile
159
160        def _set_response(tempFile):
161            '''
162            Set the WSGI response to an image, containing image read from a temporary location.
163            '''
164            img = Image.open(tempFile.name)
165            buf = StringIO()
166            img.save(buf, 'PNG')
167            response.content_type = 'image/png'
168            response.content = buf.getvalue()
169           
170        #----------------------
171
172        # Get parameters from the request object
173        try:
174            feature_id = str(request.params['feature_id'])     # convert back from Unicode
175            station_name = str(request.params['station_name']) # convert back from Unicode
176        except KeyError:
177            raise HTTPBadRequest('Parameters "feature_id" and "station_name" must be supplied.')
178
179        # Retrieve the csmlPointSeriesFeature object
180        csmlPointSeriesFeature = self._retrieveCsmlPointSeriesFeature(station_name, feature_id)
181
182        # Try to plot the feature into a temporary file, and put the contents of that file into the response
183        try:
184            tempFile = _plot_feature(csmlPointSeriesFeature)    # plot the feature into a temporary file
185            _set_response(tempFile)                             # set the response as an image containing the plot
186        finally:
187            try:
188                tempFile.close()
189            except NameError:
190                pass  # tempFile undefined -- no need to close
191
192    def list(self):
193        '''
194        Handler for generating a list of CSML features per station, with dynamic links to the plot service.
195        Request parameters:
196                               station_name    Name of the requested station.
197                               
198        Response:              A text/html with the page containing links to the plot service on the same server.
199        '''
200
201        # Get parameters from the request object
202        try:
203            station_name = str(request.params['station_name']) # convert back from Unicode
204        except KeyError:
205            raise HTTPBadRequest('Parameter "station_name" must be supplied.')
206
207        # Prepare the request for the GeoServer -- to return a collection of np:Station, containing a single np:Station,
208        # which contains a list of CSML features.
209        geoServerRequestUrl = urllib.quote(self.geoServerUrl + '?request=getFeature&service=wfs&version=1.1.0&typename=np:Station&filter=<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml"><ogc:PropertyIsEqualTo><ogc:PropertyName>stationName</ogc:PropertyName><ogc:Literal>' + station_name + '</ogc:Literal></ogc:PropertyIsEqualTo></ogc:Filter>', '/._:?&=')
210
211        # Acquire the GeoServer response and parse it into a WFSStationCollection object
212        geoServerResponse = wget(geoServerRequestUrl)
213        if not geoServerResponse:
214            raise LookupError('Cannot acquire response from server (wrong URL or server down)')
215        wfsStationCollection = csml2kml.Station.WFSStationCollection()
216        wfsStationCollection.parseString(geoServerResponse)
217        if len(wfsStationCollection.stations) != 1:
218            raise ValueError('Multiple stations match OGC selection filter (only one must match)')
219        wfsStation = wfsStationCollection.stations[0]
220
221        # Generate HTML with a list of CSML features contained in the station.
222        htmlElement = Element('html')
223        SubElement(htmlElement, 'title').text = 'List of CSML features for station ' + station_name
224        bodyElement = SubElement(htmlElement, 'body')
225        SubElement(htmlElement, 'h2').text = 'List of CSML features for station ' + station_name
226        tableElement = SubElement(htmlElement, 'table')
227        tableElement.set('border', '1')
228        for csmlFeatureId in wfsStation.csmlFeatureIds:
229            try:
230                # Test whether the feature exists. Remove this if such test is no more necessary.
231                self._retrieveCsmlPointSeriesFeature(station_name, csmlFeatureId)
232
233                # If no LookupError exception has been caught, proceed with inluding the feature in the list.
234                rowElement = SubElement(tableElement, 'tr')
235                dataElement = SubElement(rowElement, 'td')
236                anchorElement = SubElement(dataElement, 'a')
237                linkToGrapher = 'http://bond.badc.rl.ac.uk:8089/csmlGrapher/plot?station_name=' + station_name + '&feature_id=' + csmlFeatureId
238                anchorElement.set('href', linkToGrapher)
239                anchorElement.text = csmlFeatureId
240            except LookupError:
241                pass
242
243        htmlStringIO = StringIO()
244        ElementTree(htmlElement).write(htmlStringIO)
245
246        response.content_type = 'text/html'
247        response.content = htmlStringIO.getvalue()
Note: See TracBrowser for help on using the repository browser.