source: DPPP/kml/csml2kml/python/csml2kml/csml2kml/WMSCapabilities.py @ 3509

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/DPPP/kml/csml2kml/python/csml2kml/csml2kml/WMSCapabilities.py@3509
Revision 3509, 12.1 KB checked in by mkochan, 12 years ago (diff)

Corrected problem with wrong Zulu format in WMS and wrong category list order.

Line 
1import os
2from pylab import dates     # a very good date/time module from matplotviz -- allows years < 1970
3from KML import *
4
5# [TODO]
6#        * Resolve maxDirDepth
7
8wmsXmlNamespace = 'http://www.opengis.net/wms'
9
10
11def wmsLayerFactory(layerElement):
12    '''
13    [DOC]
14    '''
15    name = layerElement.find('{%s}Name' % wmsXmlNamespace).text
16    title = layerElement.find('{%s}Title' % wmsXmlNamespace).text
17    abstract = layerElement.find('{%s}Abstract' % wmsXmlNamespace).text
18    childElements = layerElement.findall('{%s}Layer' % wmsXmlNamespace)
19    childWmsLayers = []
20    for childElement in childElements:
21        childWmsLayer = wmsLayerFactory(childElement)
22        childWmsLayers.append(childWmsLayer)
23    if childElements != []:
24        return WMSLayer(name, title, abstract, childWmsLayers)       
25    else:
26        dimensionElements = layerElement.findall('{%s}Dimension' % wmsXmlNamespace)
27        for dimensionElement in dimensionElements:
28            if dimensionElement.get('name') == 'time':
29                timesteps = map( dates.dateutil.parser.parse, dimensionElement.text.split(',') )
30        return BottomWMSLayer(name, title, abstract, timesteps)
31
32class WMSLayer:
33    '''
34    [DOC]
35    '''
36
37    def __init__(self, name, title, abstract, children):
38        self.name = name
39        self.title = title
40        self.abstract = abstract
41        self.children = children
42
43    def __repr__(self):
44        return str(vars(self))
45
46    def parseXML(self, layerElement):
47        raise NotImplementedError('Use the wmsLayerFactory() function instead.')
48
49    def toKML(self, wmsRequestConfigElement, views):
50        # ignore wmsRequestConfigElement, views
51        kmlLayerFolder = KMLFolder(self.name, [], opened = False, visible = False)
52        for childWmsLayer in self.children:
53            kmlLayerFolder.children.append( childWmsLayer.toKML(wmsRequestConfigElement, views) )
54        return kmlLayerFolder
55
56class BottomWMSLayer(WMSLayer):
57
58    '''[DOC]'''
59   
60    def __init__(self, name, title, abstract, timesteps):
61
62        self.name = name
63        self.title = title
64        self.abstract = abstract
65        # but no self.children
66        self.timesteps = timesteps
67
68    def _parseName(self):
69        (modelName, scenarioName, rest) = self.name.split(':')
70        timespanDesignator = rest.split('/')[0]
71
72        if timespanDesignator == 'clim_20' or timespanDesignator == 'change_20':
73            sampleTimespan = 20
74        elif timespanDesignator == 'clim_30' or timespanDesignator == 'change_30':
75            sampleTimespan = 30
76        else:
77            raise ValueError('Sample timespan designation is incorrect')
78
79        return (modelName, scenarioName, sampleTimespan)
80
81    def getModelName(self):
82        return self._parseName()[0]
83
84    def getScenarioName(self):
85        return self._parseName()[1]
86
87    def getSampleTimespan(self):
88        return self._parseName()[2]
89
90    def toKML(self, wmsRequestConfigElement, views):
91        '''
92        @param views: A list of View objects, which define how we are going to look at the data.
93        @return: a KML.KMLFolder object representing a <kml:Folder> element with lots of <kml:GroundOverlay>
94        elements, each standing for a different time segment.
95        '''
96
97        def buildWMSRequest(layerName, timestep):
98            ''' Build a WMS request '''
99
100            # We will be using configuration for WMS request
101            c = wmsRequestConfigElement
102
103            # Set request configuration parameters
104            url = c.find('URL').text
105            serviceVersion = c.find('ServiceVersion').text
106            imageFormat = c.find('ImageFormat').text
107            imageWidth = c.find('ImageWidth').text
108            imageHeight = c.find('ImageHeight').text
109            crs = c.find('CRS').text
110
111            bBox = '-180,-90,180,90'
112
113            # If the timezone is UTC (which in ISO form would look like 'yyyy-mm-ddThh:mm:ss+00:00'),
114            # then replace it with 'Z'.
115            timestepString = timestep.isoformat()
116            timestepString = timestepString.replace('+00:00', 'Z')
117
118            wmsRequest = '%s?request=GetMap&amp;SERVICE=%s&amp;FORMAT=%s&amp;LAYERS=%s&amp;BBOX=%s&amp;WIDTH=%s&amp;HEIGHT=%s&amp;CRS=%s&TIME=%s' % (url, serviceVersion, imageFormat, layerName, bBox, imageWidth, imageHeight, crs, timestepString)
119
120            return wmsRequest
121
122        def buildKmlGroundOverlay(view, timestep):
123            (timespanStart, timespanEnd) = view.getLogicalTimespan(timestep)
124            return KMLGroundOverlay(
125                self.name + ' ' + timestep.isoformat(),
126                buildWMSRequest(self.name, timestep),
127                timespanStart, timespanEnd,
128                -180, -90, 180, 90,
129                visible = False
130                )
131
132        def buildLayerViewFolder(view):
133
134            kmlLayerViewFolder = KMLFolder(view.name, [], visible = False, opened = False)           
135
136            # Create a categorisation dictionary, dict, which will contain categories (as returned by
137            # view.getCategory()) as keys, and timesteps belonging into those categories as values.
138            dict = {}
139            for timestep in self.timesteps:
140                category = view.getCategory(timestep)
141                if not dict.has_key(category):
142                    dict[category] = []
143                dict[category].append(timestep)
144            categories = dict.keys()
145            categories.sort()
146
147            for category in categories:
148                categoryDescription = view.getCategoryDescription(category)
149                categoryTimesteps = dict[category]
150                kmlCategoryFolder = KMLFolder(categoryDescription, [], visible = False, opened = False)
151                for timestep in categoryTimesteps:
152                    kmlGroundOverlay = buildKmlGroundOverlay(view, timestep)
153                    kmlCategoryFolder.children.append(kmlGroundOverlay)
154                kmlLayerViewFolder.children.append(kmlCategoryFolder)
155
156            return kmlLayerViewFolder
157       
158        # ------------
159
160        kmlLayerFolder = KMLFolder(self.name, [], visible = False, opened = False)
161        for view in views:
162            kmlLayerFolder.children.append( buildLayerViewFolder(view) )
163        return kmlLayerFolder
164   
165class View:
166    '''
167    Determines how data can be viewed. That is, in practice, how it can be converted into KML so it can be viewed
168    in Google Earth. In particular, it defines logical transforms of time-points into time-spans.
169    '''
170
171    def __init__(self):
172        pass
173
174    def getLogicalTimespan(self, timestep):
175        '''
176        Abstract method, defined in derived classes.
177        Translates a single time step into a time span.
178        @param timestep: The date step (a datetime object)
179        @return: The (timespanStart, timespanEnd) tuple (both are datetime objects)
180        '''
181        pass
182
183    def getCategory(self, timestep):
184        pass
185
186    def getCategoryDescription(self, category):
187        '''Abstract method, defined in derived classes. Get a human-readable description of the category.'''
188        pass
189
190    def _getSameDate(self, timestep):
191        return timestep
192
193    def _getFirstDayOfMonth(self, timestep):
194        return timestep.replace(day=1)
195
196    def _get20thCenturyDecade(self, timestep):
197        replYear = (timestep.year - 1900) / 10 + 1       # get decade as a "logical" year
198        return timestep.replace(year=replYear, day=1)    # start a month on the 1st of the month
199
200    def _getMonthHence(self, timestep):
201        if timestep.month+1 <= 12:
202            return timestep.replace(month=timestep.month+1)
203        else:
204            return timestep.replace(year=timestep.year+1, month=1)
205   
206    def _getYearHence(self, timestep):
207        return timestep.replace(year=timestep.year+1)
208
209    def _getDecadeHence(self, timestep):
210        return timestep.replace(year=timestep.year+10)
211
212class ViewWholeCentury(View):
213
214    def __init__(self):
215        self.name = 'Whole century'
216
217    def getLogicalTimespan(self, timestep):
218        timespanStart = self._get20thCenturyDecade(timestep)
219        timespanEnd = self._getMonthHence(timespanStart)
220        return (timespanStart, timespanEnd)
221
222    def getCategory(self, timestep):
223        return timestep.isoformat()
224
225    def getCategoryDescription(self, category):
226        '''Get a human-readable description of the category (here, return category verbatim).'''
227        return category
228
229class ViewSplittedByMonth(View):
230
231    def __init__(self):
232        self.name = 'Compare months'
233
234    def getLogicalTimespan(self, timestep):
235        timespanStart = self._getFirstDayOfMonth(timestep)
236        timespanEnd = self._getDecadeHence(timespanStart)
237        return (timespanStart, timespanEnd)
238
239    def getCategory(self, timestep):
240        return timestep.month
241
242    def getCategoryDescription(self, category):
243        '''
244        Get a human-readable description of the category.
245        For instance, for category being 2, the result is 'February'.
246        '''
247        if not ( isinstance(category, int) and category >= 1 and category <= 12 ):
248            raise ValueError('Category not an integer between 1 and 12.')
249        month = category
250        monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 
251                      'July', 'August', 'September', 'October', 'November', 'December']
252        return monthNames[month-1]
253
254class ViewSplittedByDecade(View):
255
256    def __init__(self):
257        self.name = 'Compare decades'
258
259    def getLogicalTimespan(self, timestep):
260        timespanStart = self._getFirstDayOfMonth(timestep)
261        timespanEnd = self._getMonthHence(timespanStart)
262        return (timespanStart, timespanEnd)
263
264    def getCategory(self, timestep):
265        return timestep.year
266
267    def getCategoryDescription(self, category):
268        '''
269        Get a human-readable description of the category that timestep belongs to.
270        For instance, for 1990, the result would be 'Decade of 1990'.
271        '''
272        if not isinstance(category, int):
273            raise ValueError('Category not an integer (a year)')
274        year = category
275        return 'Decade of ' + str(year)
276
277class WMSCapabilities:
278
279    '''[DOC]'''
280
281    def __init__(self):
282        self.topWmsLayer = None
283
284    def parseXML(self, wmsCapabilitiesElement):
285        topLayerElement = wmsCapabilitiesElement.find('{%s}Capability/{%s}Layer' % (wmsXmlNamespace, wmsXmlNamespace))
286        self.topWmsLayer = wmsLayerFactory(topLayerElement)
287
288    def __repr__(self):
289        if self.topWmsLayer:
290            return '--- WMSCapabilities object with top layer as follows): ' + repr(self.topWmsLayer) + ' ---'
291        else:
292            return '--- WMSCapabilities object with no top layer ---'
293
294class WMSLayersConvertor:
295   
296    def __init__(self, topWmsLayer, wmsRequestConfigElement, baseKmlOutputDirectory):
297        self.topWmsLayer = topWmsLayer
298        self.wmsRequestConfigElement = wmsRequestConfigElement
299        self.baseKmlOutputDirectory = baseKmlOutputDirectory
300        self.maxDirDepth = 1
301        # [a:D,r:DEBUG] self.maxDirDepth = self.config(...)
302       
303    def convert(self):
304
305        def _convertToKML(wmsLayer):
306            views = [ViewWholeCentury(), ViewSplittedByMonth(), ViewSplittedByDecade()]
307            return wmsLayer.toKML(self.wmsRequestConfigElement, views)
308       
309        def _convertToDirectory(wmsLayer, parentDir, currentLevel):
310            '''recursive'''
311            if currentLevel < self.maxDirDepth:
312                currentDir = parentDir + '/' + wmsLayer.name
313                os.mkdir(currentDir)
314                print 'Created directory "%s"' % currentDir
315                if not isinstance(wmsLayer, BottomWMSLayer):
316                    for childWmsLayer in wmsLayer.children:
317                        _convertToDirectory(childWmsLayer, currentDir, currentLevel+1)
318            elif currentLevel == self.maxDirDepth:
319                # Create a KML document with no styles
320                kmlDocument = KMLDocument(wmsLayer.name, []) 
321                kmlDocument.elements.append( _convertToKML(wmsLayer) )
322                filename = parentDir + '/' + wmsLayer.name + '.kml'
323                kmlDocument.save(filename)
324                print 'Saved file "%s"' % filename
325            else:
326                pass
327
328        #### topWmsLayer = wmsLayerFactory(topLayerElement)
329        _convertToDirectory(self.topWmsLayer, self.baseKmlOutputDirectory, 0)
Note: See TracBrowser for help on using the repository browser.