source: DPPP/kml/csml2kml/python/pylonsstack/pylonsstack/controllers/csmlGrapher.py @ 3576

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

Updated csmlGrapher and StationConvertor? to allow dynamic display of a list of CSML features per station, and per-feature graphing.

Line 
1# Pylons-specific imports
2import logging
3from pylonsstack.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
23# [TODO] . Add security (especially checking of input parameters).
24#        . Add configurable URL for the data source web service (preferably explicitly on localhost)
25class CsmlgrapherController(BaseController):
26
27    def plot(self):
28        '''
29        Handler for plotting a specific PointSeries feature given by URL parameter "feature_id".
30        Retrieves the data from a web service, which should preferably run on the same machine.
31
32        Request parameters:    EITHER "feature_id" OR "station_id"
33        where:
34                               feature_id    Unique identifier of a requested CSML PointSeries feature.
35                               station_id    Unique identifier of a requested station.
36
37        Response:              If feature_id is supplied, an image/png of the time series for the CSML PointSeries feature;
38                               if station_id is supplied, nothing.
39        '''
40        def _plot_feature(feature):
41            '''
42            Plot a PointSeries feature using matplotlib, into a temporary file
43            '''
44           
45            # Make sure that time is considered to be UTC time. This is because all CSML data is supposed to be in UTC,
46            # but actually pylab is "time zone aware" and will interpret the time as local time zone time.
47            # Not quite sure if this would be a problem, but rather enforce it is UTC.
48            def _enforce_UTC_timezone(datestr):
49                if datestr[-1] == 'Z':
50                    return datestr
51                else:
52                    return datestr + 'Z'
53           
54            # Isolate time points   
55            times=feature.value.pointSeriesDomain.timePositionList.CONTENT.split()
56            times = map(_enforce_UTC_timezone, times)
57           
58            # Note the start and end date (they are actually date+time), and time span between them
59            start_date = dates.dateutil.parser.parse(times[0])
60            end_date = dates.dateutil.parser.parse(times[-1])
61            span = datestr2num(times[-1]) - datestr2num(times[0])
62
63            # Make a list of times as float numbers being days passed since start of epoch (here 01-01-0001)
64            elapsed_times = map(datestr2num, times)
65
66            # Generate tick locator and formatter; these determine the time axis: locator determines tick positions
67            # and formatter format of time
68            (tickLocator, tickFormatter) = dates.date_ticker_factory(span, numticks=8)
69
70            # We may need to do this for bodc data (i.e. not inline data)
71            # vals=feature.value.rangeSet.valueArray.valueComponent.insertedExtract.components.getDataFromChunks(0,19)
72
73            # Get values of the measured quantity
74            ql = feature.value.rangeSet.quantityList
75            vals = map(float, ql.CONTENT.split())
76
77            # Determine the units of measurement
78            uom=ql.uom.title()
79            if ql.uom.islower():
80                uom = uom.lower()
81            if ql.uom.isupper():
82                uom = uom.upper()
83
84            # Prepare the plot the figure (actual plotting actions are postponed until save)
85            fig = figure()
86            plot_date(elapsed_times, vals, 'b-', xdate=True, lw=1)
87            plot_date(elapsed_times, vals, 'go', markeredgecolor = 'g', xdate=True, lw=2)
88            ax = gca()
89            ax.xaxis.set_major_locator(tickLocator)
90            ax.xaxis.set_major_formatter(tickFormatter)
91            fig.autofmt_xdate()  # show times without overlaps
92            time_format = '%d-%b-%Y %H:%M:%S UTC'
93            xlabel('Times between %s and %s ' % (start_date.strftime(time_format), end_date.strftime(time_format)))
94            ylabel('Values [%s]' % uom)
95            title('"%s" (%s)' % (feature.id, feature.description.CONTENT))
96            grid(True)
97
98            # Save the figure to a temporary file
99            tempFile = NamedTemporaryFile(suffix='.png')
100            savefig(tempFile.name)
101           
102            return tempFile
103
104        def _set_response(tempFile):
105            '''
106            Set the WSGI response to an image, containing image read from a temporary location.
107            '''
108            img = Image.open(tempFile.name)
109            buf = StringIO()
110            img.save(buf, 'PNG')
111            response.content_type = 'image/png'
112            response.content = buf.getvalue()
113           
114        #----------------------
115
116        # Get parameters from the request object
117        try:
118            feature_id = str(request.params['feature_id'])     # convert back from Unicode
119            station_name = str(request.params['station_name']) # convert back from Unicode
120        except KeyError:
121            raise HTTPBadRequest('Parameters "feature_id" and "station_name" must be supplied.')
122
123        # Prepare the request for the GeoServer
124        lowerTimeBoundary = '1-JAN-2003'   # hard-coded for now
125        upperTimeBoundary = '31-JAN-2003'  # hard-coded for now
126        geoServerRequestUrl = urllib.quote('http://130.246.76.98:8084/geoserver/wfs?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>'+ lowerTimeBoundary +'</ogc:Literal></ogc:LowerBoundary><ogc:UpperBoundary><ogc:Literal>'+ upperTimeBoundary +'</ogc:Literal></ogc:UpperBoundary></ogc:PropertyIsBetween></ogc:And></ogc:Filter>', '/._:?&=')
127
128        # Acquire the GeoServer response and parse it into a CSMLFeatureCollection object
129        geoServerResponse = wget(geoServerRequestUrl)
130        if not geoServerResponse:
131            raise LookupError('Cannot acquire response from server (wrong URL or server down)')
132        csmlFeatureCollection = csml.parser.CSMLFeatureCollection()
133        csmlFeatureCollection.fromXML( XML(geoServerResponse) )
134
135        # Now, csmlFeatureCollection should only contain a single CSML feature (if the feature was found).
136        # Isolate that CSML feature.
137        try:
138            csmlFeature = csmlFeatureCollection.featureMembers
139        except AttributeError:
140            raise LookupError('No PointSeriesFeature with ID "' + feature_id + '" found in the collection.')
141
142        # Make sure that the feature is a PointSeriesFeature
143        if not isinstance(csmlFeature, csml.parser.PointSeriesFeature):
144            raise TypeError('CSML feature not a PointSeriesFeature')
145        csmlPointSeriesFeature = csmlFeature
146
147        # Try to plot the feature into a temporary file, and put the contents of that file into the response
148        try:
149            tempFile = _plot_feature(csmlPointSeriesFeature)    # plot the feature into a temporary file
150            _set_response(tempFile)                             # set the response as an image containing the plot
151        finally:
152            try:
153                tempFile.close()
154            except NameError:
155                pass  # tempFile undefined -- no need to close
156
157    def list(self):
158        '''Handler for generating a list of CSML features per station, with dynamic links to the plot service.'''
159
160        # Get parameters from the request object
161        try:
162            station_name = str(request.params['station_name']) # convert back from Unicode
163        except KeyError:
164            raise HTTPBadRequest('Parameter "station_name" must be supplied.')
165
166        # Prepare the request for the GeoServer
167        geoServerRequestUrl = urllib.quote('http://130.246.76.98:8084/geoserver/wfs?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>', '/._:?&=')
168
169        # Acquire the GeoServer response and parse it into a WFSStationCollection object
170        geoServerResponse = wget(geoServerRequestUrl)
171        if not geoServerResponse:
172            raise LookupError('Cannot acquire response from server (wrong URL or server down)')
173        wfsStationCollection = csml2kml.Station.WFSStationCollection()
174        wfsStationCollection.parseString(geoServerResponse)
175        if len(wfsStationCollection.stations) != 1:
176            raise ValueError('Multiple stations match OGC selection filter (only one must match)')
177        wfsStation = wfsStationCollection.stations[0]
178
179        htmlElement = Element('html')
180        SubElement(htmlElement, 'title').text = 'List of CSML features for station ' + station_name
181        bodyElement = SubElement(htmlElement, 'body')
182        for csmlFeatureId in wfsStation.csmlFeatureIds:
183            anchorElement = SubElement(bodyElement, 'a')
184            linkToGrapher = 'http://bond.badc.rl.ac.uk:8089/csmlGrapher/plot?station_name=' + station_name + '&feature_id=' + csmlFeatureId
185            anchorElement.set('href', linkToGrapher)
186            anchorElement.text = csmlFeatureId
187            SubElement(bodyElement, 'br')
188        htmlStringIO = StringIO()
189        ElementTree(htmlElement).write(htmlStringIO)
190
191        response.content_type = 'text/html'
192        response.content = htmlStringIO.getvalue()
193       
Note: See TracBrowser for help on using the repository browser.