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

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

Mostly corrected the layer views code. Switched to using "non-naive" View objects (they know the timesteps, although they may not use them).

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
10def wmsLayerFactory(layerElement):
11    '''
12    [DOC]
13    '''
14    name = layerElement.find('{%s}Name' % wmsXmlNamespace).text
15    title = layerElement.find('{%s}Title' % wmsXmlNamespace).text
16    abstract = layerElement.find('{%s}Abstract' % wmsXmlNamespace).text
17    childElements = layerElement.findall('{%s}Layer' % wmsXmlNamespace)
18    childWmsLayers = []
19    for childElement in childElements:
20        childWmsLayer = wmsLayerFactory(childElement)
21        childWmsLayers.append(childWmsLayer)
22    if childElements != []:
23        return WMSLayer(name, title, abstract, childWmsLayers)       
24    else:
25        dimensionElements = layerElement.findall('{%s}Dimension' % wmsXmlNamespace)
26        for dimensionElement in dimensionElements:
27            if dimensionElement.get('name') == 'time':
28                timesteps = map( dates.dateutil.parser.parse, dimensionElement.text.split(',') )
29        return BottomWMSLayer(name, title, abstract, timesteps)
30
31class WMSLayer:
32    '''
33    [DOC]
34    '''
35
36    def __init__(self, name, title, abstract, children):
37        self.name = name
38        self.title = title
39        self.abstract = abstract
40        self.children = children
41
42    def __repr__(self):
43        return str(vars(self))
44
45    def parseXML(self, layerElement):
46        raise NotImplementedError('Use the wmsLayerFactory() function instead.')
47
48    def toKML(self, wmsRequestConfigElement, viewTypes):
49        # ignore wmsRequestConfigElement, viewTypess
50        kmlLayerFolder = KMLFolder(self.name, [], opened = False, visible = False)
51        for childWmsLayer in self.children:
52            kmlLayerFolder.children.append( childWmsLayer.toKML(wmsRequestConfigElement, viewTypes) )
53        return kmlLayerFolder
54
55class BottomWMSLayer(WMSLayer):
56
57    '''[DOC]'''
58   
59    def __init__(self, name, title, abstract, timesteps):
60
61        self.name = name
62        self.title = title
63        self.abstract = abstract
64        # but no self.children
65        self.timesteps = timesteps
66
67    def _parseName(self):
68        (modelName, scenarioName, rest) = self.name.split(':')
69        timespanDesignator = rest.split('/')[0]
70
71        if timespanDesignator == 'clim_20' or timespanDesignator == 'change_20':
72            sampleTimespan = 20
73        elif timespanDesignator == 'clim_30' or timespanDesignator == 'change_30':
74            sampleTimespan = 30
75        else:
76            raise ValueError('Sample timespan designation is incorrect')
77
78        return (modelName, scenarioName, sampleTimespan)
79
80    def getModelName(self):
81        return self._parseName()[0]
82
83    def getScenarioName(self):
84        return self._parseName()[1]
85
86    def getSampleTimespan(self):
87        return self._parseName()[2]
88
89    def toKML(self, wmsRequestConfigElement, viewTypes):
90        '''
91        @param viewTyps: A list of View classes (but not instances), which define how we are going
92        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&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)
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(viewType):
133
134            # Create a view that is informed in advance of the timesteps involved in the following categorisation
135            # (most views actually no not need to see the timesteps in advance).
136            view = viewFactory(viewType, self.timesteps)
137
138            # Create a KML folder that represents the view of the layer
139            kmlLayerViewFolder = KMLFolder(view.name, [], visible = False, opened = False)           
140
141            # Create a categorisation dictionary, dict, which will contain categories (as returned by
142            # view.getCategory()) as keys, and timesteps belonging into those categories as values.
143            dict = {}
144            for timestep in self.timesteps:
145                category = view.getCategory(timestep)
146                if not dict.has_key(category):
147                    dict[category] = []
148                dict[category].append(timestep)
149            categories = dict.keys()
150            categories.sort()
151
152            for category in categories:
153                categoryDescription = view.getCategoryDescription(category)
154                categoryTimesteps = dict[category]
155                kmlCategoryFolder = KMLFolder(categoryDescription, [], visible = False, opened = False)
156                for timestep in categoryTimesteps:
157                    kmlGroundOverlay = buildKmlGroundOverlay(view, timestep)
158                    kmlCategoryFolder.children.append(kmlGroundOverlay)
159                kmlLayerViewFolder.children.append(kmlCategoryFolder)
160
161            return kmlLayerViewFolder
162       
163        # ------------
164
165        kmlLayerFolder = KMLFolder(self.name, [], visible = False, opened = False)
166        for viewType in viewTypes:
167            kmlLayerFolder.children.append( buildLayerViewFolder(viewType) )
168        return kmlLayerFolder
169
170class View:
171    '''
172    Determines how data can be viewed. That is, in practice, how it can be converted into KML so it can be viewed
173    in Google Earth. In particular, it defines logical transforms of time-points into time-spans.
174    '''
175
176    def __init__(self, timesteps):
177        '''
178        Initialize the view.
179        @param timesteps: Some views (not all) may need to "see" all the timesteps before they
180        are asked to provide a category for each; they are passed in here. Most views actually
181        ignore the timesteps.
182        '''
183        pass
184
185    def getLogicalTimespan(self, timestep):
186        '''
187        Abstract method, defined in derived classes.
188        Translates a single time step into a time span.
189        @param timestep: The date step (a datetime object)
190        @return: The (timespanStart, timespanEnd) tuple (both are datetime objects)
191        '''
192        pass
193
194    def getCategory(self, timestep):
195        pass
196
197    def getCategoryDescription(self, category):
198        '''Abstract method, defined in derived classes. Get a human-readable description of the category.'''
199        pass
200
201    def _getSameDate(self, timestep):
202        return timestep
203
204    def _getFirstDayOfMonth(self, timestep):
205        return timestep.replace(day=1)
206
207    def _get20thCenturyDecade(self, timestep):
208        replYear = (timestep.year - 1900) / 10 + 1       # get decade as a "logical" year
209        return timestep.replace(year=replYear, day=1)    # start a month on the 1st of the month
210
211    def _getMonthHence(self, timestep):
212        if timestep.month+1 <= 12:
213            return timestep.replace(month=timestep.month+1)
214        else:
215            return timestep.replace(year=timestep.year+1, month=1)
216   
217    def _getYearHence(self, timestep):
218        return timestep.replace(year=timestep.year+1)
219
220    def _getDecadeHence(self, timestep):
221        return timestep.replace(year=timestep.year+10)
222
223def viewFactory(viewType, timesteps):
224    newView = viewType(timesteps)
225    return newView
226
227class ViewWholeTimecourse(View):
228
229    def __init__(self, timesteps):
230        self.name = 'Whole timecourse'
231        yearSet = set()
232        for timestep in timesteps:
233            yearSet.add(timestep.year)
234        self.sortedYears = list(yearSet); self.sortedYears.sort()
235
236    def getLogicalTimespan(self, timestep):
237        category = self.getCategory(timestep)
238        timespanStart = self._getFirstDayOfMonth( timestep.replace(year = category) )
239        timespanEnd = self._getMonthHence(timespanStart)
240        return (timespanStart, timespanEnd)
241
242    def getCategory(self, timestep):
243        try:
244            return self.sortedYears.index(timestep.year) + 1
245        except ValueError:
246            raise ValueError("Timestep's year is not among years that define the categories.")
247
248    def getCategoryDescription(self, category):
249        '''Get a human-readable description of the category (here, return category verbatim).'''
250        return str(category)
251
252class ViewSplittedByMonth(View):
253
254    def __init__(self, timesteps):
255        self.name = 'Compare months'
256
257    def getLogicalTimespan(self, timestep):
258        timespanStart = self._getFirstDayOfMonth(timestep)
259        timespanEnd = self._getDecadeHence(timespanStart)
260        return (timespanStart, timespanEnd)
261
262    def getCategory(self, timestep):
263        return timestep.month
264
265    def getCategoryDescription(self, category):
266        '''
267        Get a human-readable description of the category.
268        For instance, for category being 2, the result is 'February'.
269        '''
270        if not ( isinstance(category, int) and category >= 1 and category <= 12 ):
271            raise ValueError('Category not an integer between 1 and 12.')
272        month = category
273        monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 
274                      'July', 'August', 'September', 'October', 'November', 'December']
275        return monthNames[month-1]
276
277class ViewSplittedByYear(View):
278
279    def __init__(self, timesteps):
280        self.name = 'Compare period'
281
282    def getLogicalTimespan(self, timestep):
283        timespanStart = self._getFirstDayOfMonth(timestep)
284        timespanEnd = self._getMonthHence(timespanStart)
285        return (timespanStart, timespanEnd)
286
287    def getCategory(self, timestep):
288        return timestep.year
289
290    def getCategoryDescription(self, category):
291        '''
292        Get a human-readable description of the category that timestep belongs to.
293        For instance, for 1990, the result would be 'Period of 1990'.
294        '''
295        if not isinstance(category, int):
296            raise ValueError('Category not an integer (a year)')
297        year = category
298        return 'Period of ' + str(year)
299
300class WMSCapabilities:
301
302    '''[DOC]'''
303
304    def __init__(self):
305        self.topWmsLayer = None
306
307    def parseXML(self, wmsCapabilitiesElement):
308        topLayerElement = wmsCapabilitiesElement.find('{%s}Capability/{%s}Layer' % (wmsXmlNamespace, wmsXmlNamespace))
309        self.topWmsLayer = wmsLayerFactory(topLayerElement)
310
311    def __repr__(self):
312        if self.topWmsLayer:
313            return '--- WMSCapabilities object with top layer as follows): ' + repr(self.topWmsLayer) + ' ---'
314        else:
315            return '--- WMSCapabilities object with no top layer ---'
316
317class WMSLayersConvertor:
318   
319    def __init__(self, topWmsLayer, wmsRequestConfigElement, baseKmlOutputDirectory, maxDirDepth):
320        self.topWmsLayer = topWmsLayer
321        self.wmsRequestConfigElement = wmsRequestConfigElement
322        self.baseKmlOutputDirectory = baseKmlOutputDirectory
323        self.maxDirDepth = maxDirDepth
324       
325    def convert(self):
326
327        def _convertToKML(wmsLayer):
328            viewTypes = [ViewWholeTimecourse, ViewSplittedByMonth, ViewSplittedByYear]
329            return wmsLayer.toKML(self.wmsRequestConfigElement, viewTypes)
330       
331        def _convertToDirectory(wmsLayer, parentDir, currentLevel):
332            '''recursive'''
333            if currentLevel < self.maxDirDepth:
334                currentDir = parentDir + '/' + wmsLayer.name
335                os.mkdir(currentDir)
336                print 'Created directory "%s"' % currentDir
337                if not isinstance(wmsLayer, BottomWMSLayer):
338                    for childWmsLayer in wmsLayer.children:
339                        _convertToDirectory(childWmsLayer, currentDir, currentLevel+1)
340            elif currentLevel == self.maxDirDepth:
341                # Create a KML document with no styles
342                kmlDocument = KMLDocument(wmsLayer.name, []) 
343                kmlDocument.elements.append( _convertToKML(wmsLayer) )
344                filename = parentDir + '/' + wmsLayer.name + '.kml'
345                kmlDocument.save(filename)
346                print 'Saved file "%s"' % filename
347            else:
348                pass
349
350        #### topWmsLayer = wmsLayerFactory(topLayerElement)
351        _convertToDirectory(self.topWmsLayer, self.baseKmlOutputDirectory, 0)
Note: See TracBrowser for help on using the repository browser.