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

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

Changed WMSCapabilities.py so that it generates "obs" dataset. Still need to change ViewWholeTimecourse?.

Line 
1import os
2import re
3from pylab import dates     # a very good date/time module from matplotviz -- allows years < 1970
4from KML import *
5
6# [TODO]
7#        * Resolve maxDirDepth
8
9wmsXmlNamespace = 'http://www.opengis.net/wms'
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, viewTypes):
50        # ignore wmsRequestConfigElement, viewTypess
51        kmlLayerFolder = KMLFolder(self.name, [], opened = False, visible = False)
52        for childWmsLayer in self.children:
53            kmlLayerFolder.children.append( childWmsLayer.toKML(wmsRequestConfigElement, viewTypes) )
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        mo = re.match('(.+)\:(.+)\:(.+)', self.name)
70        if mo:
71            (modelName, scenarioName, rest) = mo.groups()
72        else:
73            (modelName, scenarioName, rest) = (None, None, self.name)
74
75        mo2 = re.match('(clim|change)\_(\d+)\/(.+)', rest)
76        if mo2:
77            (type, periodText, description) = mo2.groups()
78            period = int(periodText)
79        else:
80            raise ValueError('Cannot parse in layer name')
81
82        return (type, period, description, modelName, scenarioName)
83
84    def getType(self):
85        return self._parseName()[0]
86
87    def getPeriod(self):
88        '''@return The period length (integer)'''
89        return self._parseName()[1]
90   
91    def getDescription(self):
92        return self._parseName()[2]
93
94    def getModelName(self):
95        return self._parseName()[3]
96
97    def getScenarioName(self):
98        return self._parseName()[4]
99
100    def toKML(self, wmsRequestConfigElement, viewTypes):
101        '''
102        @param viewTyps: A list of View classes (but not instances), which define how we are going
103        to look at the data.
104        @return: a KML.KMLFolder object representing a <kml:Folder> element with lots of <kml:GroundOverlay>
105        elements, each standing for a different time segment.
106        '''
107
108        def buildWMSRequest(layerName, timestep):
109            ''' Build a WMS request '''
110
111            # We will be using configuration for WMS request
112            c = wmsRequestConfigElement
113
114            # Set request configuration parameters
115            url = c.find('URL').text
116            serviceVersion = c.find('ServiceVersion').text
117            imageFormat = c.find('ImageFormat').text
118            imageWidth = c.find('ImageWidth').text
119            imageHeight = c.find('ImageHeight').text
120            crs = c.find('CRS').text
121
122            bBox = '-180,-90,180,90'
123
124            # If the timezone is UTC (which in ISO form would look like 'yyyy-mm-ddThh:mm:ss+00:00'),
125            # then replace it with 'Z'.
126            timestepString = timestep.isoformat()
127            timestepString = timestepString.replace('+00:00', 'Z')
128
129            wmsRequest = '%s?request=GetMap&SERVICE=%s&FORMAT=%s&LAYERS=%s&BBOX=%s&WIDTH=%s&HEIGHT=%s&CRS=%s&TIME=%s' % (url, serviceVersion, imageFormat, layerName, bBox, imageWidth, imageHeight, crs, timestepString)
130
131            return wmsRequest
132
133        def buildKmlGroundOverlay(view, timestep):
134            (timespanStart, timespanEnd) = view.getLogicalTimespan(timestep)
135            return KMLGroundOverlay(
136                self.name + ' ' + timestep.isoformat(),
137                buildWMSRequest(self.name, timestep),
138                timespanStart, timespanEnd,
139                -180, -90, 180, 90,
140                visible = False
141                )
142
143        def buildLayerViewFolder(viewType):
144
145            # Create a view that is informed in advance of the timesteps involved in the following categorisation
146            # (most views actually no not need to see the timesteps in advance).
147            view = viewFactory(viewType, self)
148
149            # Create a KML folder that represents the view of the layer
150            kmlLayerViewFolder = KMLFolder(view.name, [], visible = False, opened = False)
151
152            # Create a categorisation dictionary, dict, which will contain categories (as returned by
153            # view.getCategory()) as keys, and timesteps belonging into those categories as values.
154            dict = {}
155            for timestep in self.timesteps:
156                category = view.getCategory(timestep)
157                if not dict.has_key(category):
158                    dict[category] = []
159                dict[category].append(timestep)
160            categories = dict.keys()
161            categories.sort()
162
163            for category in categories:
164                categoryDescription = view.getCategoryDescription(category)
165                categoryTimesteps = dict[category]
166                kmlCategoryFolder = KMLFolder(categoryDescription, [], visible = False, opened = False)
167                for timestep in categoryTimesteps:
168                    kmlGroundOverlay = buildKmlGroundOverlay(view, timestep)
169                    kmlCategoryFolder.children.append(kmlGroundOverlay)
170                kmlLayerViewFolder.children.append(kmlCategoryFolder)
171
172            return kmlLayerViewFolder
173
174        # ------------
175
176        kmlLayerFolder = KMLFolder(self.name, [], visible = False, opened = False)
177        for viewType in viewTypes:
178            kmlLayerFolder.children.append( buildLayerViewFolder(viewType) )
179        return kmlLayerFolder
180
181class View:
182    '''
183    Determines how BottomWMSLayer data can be viewed, i.e. how it can be converted into KML so it can be viewed
184    in Google Earth. In particular, it defines logical transforms of time-points into time-spans.
185    '''
186
187    def __init__(self, layer):
188        '''
189        Initialize the view.
190        @param layer: Some views (not all) may need to "see" the layer data (although some ignore it).
191        '''
192        self.layer = layer
193
194    def getLogicalTimespan(self, timestep):
195        '''
196        Abstract method, defined in derived classes.
197        Translates a single time step into a time span.
198        @param timestep: The date step (a datetime object)
199        @return: The (timespanStart, timespanEnd) tuple (both are datetime objects)
200        '''
201        pass
202
203    def getCategory(self, timestep):
204        pass
205
206    def getCategoryDescription(self, category):
207        '''Abstract method, defined in derived classes. Get a human-readable description of the category.'''
208        pass
209
210    def _getSameDate(self, timestep):
211        return timestep
212
213    def _getFirstDayOfMonth(self, timestep):
214        return timestep.replace(day=1)
215
216    ##def _get20thCenturyDecade(self, timestep):
217    ##    replYear = (timestep.year - 1900) / 10 + 1       # get decade as a "logical" year
218    ##    return timestep.replace(year=replYear, day=1)    # start a month on the 1st of the month
219
220    def _getMonthHence(self, timestep):
221        if timestep.month+1 <= 12:
222            return timestep.replace(month=timestep.month+1)
223        else:
224            return timestep.replace(year=timestep.year+1, month=1)
225   
226    def _getYearHence(self, timestep):
227        return timestep.replace(year=timestep.year+1)
228
229    ##def _getDecadeHence(self, timestep):
230    ##    return timestep.replace(year=timestep.year+10)
231
232    def _getHalfPeriodEarlier(self, timestep):
233        return timestep.replace(year = timestep.year-self.layer.getPeriod()/2)
234
235    def _getHalfPeriodLater(self, timestep):
236        return timestep.replace(year = timestep.year+self.layer.getPeriod()/2)
237
238def viewFactory(viewType, layer):
239    newView = viewType(layer)
240    return newView
241
242class ViewWholeTimecourse(View):
243
244    def __init__(self, layer):
245        self.name = 'Whole timecourse'
246        self.layer = layer
247        yearSet = set()
248        for timestep in self.layer.timesteps:
249            yearSet.add(timestep.year)
250        self.sortedYears = list(yearSet); self.sortedYears.sort()
251
252    def getLogicalTimespan(self, timestep):
253        category = self.getCategory(timestep)
254        timespanStart = self._getFirstDayOfMonth( timestep.replace(year = category) )
255        timespanEnd = self._getMonthHence(timespanStart)
256        return (timespanStart, timespanEnd)
257
258    def getCategory(self, timestep):
259        try:
260            return self.sortedYears.index(timestep.year) + 1
261        except ValueError:
262            raise ValueError("Timestep's year is not among years that define the categories.")
263
264    def getCategoryDescription(self, category):
265        '''Get a human-readable description of the category (here, return category verbatim).'''
266        return str(category)
267
268class ViewSplittedByMonth(View):
269
270    def __init__(self, layer):
271        self.name = 'Compare months'
272        self.layer = layer
273
274    def getLogicalTimespan(self, timestep):
275        timespanStart = self._getHalfPeriodEarlier(timestep)
276        timespanEnd = self._getHalfPeriodLater(timestep)
277        return (timespanStart, timespanEnd)
278
279    def getCategory(self, timestep):
280        return timestep.month
281
282    def getCategoryDescription(self, category):
283        '''
284        Get a human-readable description of the category.
285        For instance, for category being 2, the result is 'February'.
286        '''
287        if not ( isinstance(category, int) and category >= 1 and category <= 12 ):
288            raise ValueError('Category not an integer between 1 and 12.')
289        month = category
290        monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 
291                      'July', 'August', 'September', 'October', 'November', 'December']
292        return monthNames[month-1]
293
294class ViewSplittedByPeriod(View):
295
296    def __init__(self, layer):
297        self.name = 'Compare period'
298        self.layer = layer
299
300    def getLogicalTimespan(self, timestep):
301        timespanStart = self._getFirstDayOfMonth(timestep)
302        timespanEnd = self._getMonthHence(timespanStart)
303        return (timespanStart, timespanEnd)
304
305    def getCategory(self, timestep):
306        return timestep.year
307
308    def getCategoryDescription(self, category):
309        '''
310        Get a human-readable description of the category that timestep belongs to.
311        For instance, for 1990, the result would be 'Period of 1990'.
312        '''
313        if not isinstance(category, int):
314            raise ValueError('Category not an integer (a year)')
315        year = category
316        return 'Period of ' + str(year)
317
318class WMSCapabilities:
319
320    '''[DOC]'''
321
322    def __init__(self):
323        self.topWmsLayer = None
324
325    def parseXML(self, wmsCapabilitiesElement):
326        topLayerElement = wmsCapabilitiesElement.find('{%s}Capability/{%s}Layer' % (wmsXmlNamespace, wmsXmlNamespace))
327        self.topWmsLayer = wmsLayerFactory(topLayerElement)
328
329    def __repr__(self):
330        if self.topWmsLayer:
331            return '--- WMSCapabilities object with top layer as follows): ' + repr(self.topWmsLayer) + ' ---'
332        else:
333            return '--- WMSCapabilities object with no top layer ---'
334
335class WMSLayersConvertor:
336   
337    def __init__(self, topWmsLayer, wmsRequestConfigElement, baseKmlOutputDirectory, maxDirDepth):
338        self.topWmsLayer = topWmsLayer
339        self.wmsRequestConfigElement = wmsRequestConfigElement
340        self.baseKmlOutputDirectory = baseKmlOutputDirectory
341        self.maxDirDepth = maxDirDepth
342       
343    def convert(self):
344
345        def _convertToKML(wmsLayer):
346            viewTypes = [ViewWholeTimecourse, ViewSplittedByMonth, ViewSplittedByPeriod]
347            return wmsLayer.toKML(self.wmsRequestConfigElement, viewTypes)
348       
349        def _convertToDirectory(wmsLayer, parentDir, currentLevel):
350            '''recursive'''
351            if currentLevel < self.maxDirDepth:
352                currentDir = parentDir + '/' + wmsLayer.title
353                os.mkdir(currentDir)
354                print 'Created directory "%s"' % currentDir
355                if not isinstance(wmsLayer, BottomWMSLayer):
356                    for childWmsLayer in wmsLayer.children:
357                        _convertToDirectory(childWmsLayer, currentDir, currentLevel+1)
358            elif currentLevel == self.maxDirDepth:
359                # Create a KML document with no styles
360                kmlDocument = KMLDocument(wmsLayer.name, []) 
361                kmlDocument.elements.append( _convertToKML(wmsLayer) )
362                filename = parentDir + '/' + wmsLayer.title + '.kml'
363                kmlDocument.save(filename)
364                print 'Saved file "%s"' % filename
365            else:
366                pass
367
368        _convertToDirectory(self.topWmsLayer, self.baseKmlOutputDirectory, 0)
Note: See TracBrowser for help on using the repository browser.