source: DPPP/kml/csml2kml/python/csml2kml/csml2kml/kmlfeatures.py @ 3459

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

Changed GridSeriesConvertor? and kmlfeatures to use KML.

Line 
1'''
2KML representatitions of those CSML features which can be represented directly in KML.
3Not all CSML features satisfy this requirement -- e.g. csml:GridSeriesFeature does, while
4csml:PointSeriesFeature does not (it has to be represented by a graph-plotting web service).
5'''
6
7import string
8import re
9from datetime import datetime
10from cElementTree import ElementTree, Element, SubElement
11
12from KML import *
13
14class GridSeriesKML(object):
15    ''' Represents a CSML GridSeriesFeature in KML (via WMS requests).'''
16   
17    def __init__(self, config, viewConfig, parentFilename, name, description, bBox, timeSteps, timeBounds):
18        ''' Initialise with the key information content'''
19
20        # Conversion configuration
21        self.config = config
22
23        # View-specific conversion configuration (determines how data will be presented)
24        self.splitTimeStepsBy = viewConfig.find('SplitTimeStepsBy').text
25        ldttText = viewConfig.find('LogicalDateTimeTransform').text
26        if ldttText == '20_CENTURY_DECADE':
27            self.logicalDateTimeTransform = get20CenturyDecade
28        elif ldttText == 'SAME_DATE':
29            self.logicalDateTimeTransform = getSameDate
30        elif ldttText == 'FIRST_DAY_OF_MONTH':
31            self.logicalDateTimeTransform = getFirstDayOfMonth
32        else:
33            raise ValueError('Config error: Unsupported date/time transform')
34
35        ldtdText = viewConfig.find('LogicalDateTimeDelta').text
36        if ldtdText == 'MONTH_HENCE':
37            self.logicalDateTimeDelta = getMonthHence
38        elif ldtdText == 'YEAR_HENCE':
39            self.logicalDateTimeDelta = getYearHence
40        elif ldtdText == 'DECADE_HENCE':
41            self.logicalDateTimeDelta = getDecadeHence
42        else:
43            raise ValueError('Config error: Unsupported date/time delta')
44
45        try:
46            self.categoryNamingPattern = viewConfig.find('CategoryNamingPattern').text
47        except AttributeError:
48            self.categoryNamingPattern = None           
49
50        # Name of the CSML file in which feature is contained
51        self.parentFilename = parentFilename
52
53        # A short name for the feature:
54        self.name=name
55
56        # A descriptive name for the feature:
57        self.description=description
58
59        # The latitude/longitude bounding box as a csmlwrappers.BBox object
60        self.bBox=bBox
61
62        # The time steps
63        self.timeSteps=timeSteps.split()
64
65        # Valid bounds of the individual timesteps
66        self.timeBounds=timeBounds.split()
67   
68    def getYMD(self, timeStep):
69        matchObject = re.match("(\d+)\-(\d+)\-(\d+)T", timeStep)
70        (sYear, sMonth, sDay) = matchObject.groups()
71        return (int(sYear), int(sMonth), int(sDay))
72
73    def getLogicalTimeSpan(self, timeStep):
74        ''' Get time span that corresponds to this timeStep by using the appropriate
75            date/time transform and delta (i.e. date/time difference)
76        '''
77        (year, month, day) = self.getYMD(timeStep)
78        dateTime = datetime(year, month, day)
79        startDateTime = self.logicalDateTimeTransform(dateTime)
80        endDateTime = self.logicalDateTimeDelta(startDateTime)
81        return (startDateTime, endDateTime)
82
83    def longitudeWithinMinusPlus180(self, x):
84        ''' Put the x coordinate within bounds of the period (-180, 180)'''
85        if x != 180:
86            x = ((x + 180) % 360) - 180
87        return x
88   
89    def buildWMSRequest(self, timeStep):
90        ''' Build a WMS request '''
91
92        # We will be using configuration for WMS request
93        c = self.config.find('CSMLGridSeriesFeatureWMSRequest')
94
95        # Set request configuration parameters
96        url = c.find('URL').text
97        serviceVersion = c.find('ServiceVersion').text
98        imageFormat = c.find('ImageFormat').text
99        imageWidth = c.find('ImageWidth').text
100        imageHeight = c.find('ImageHeight').text
101        crs = c.find('CRS').text
102        layerName = c.find('LayerName').text
103        filenameWithoutPath = self.parentFilename.split('/')[-1]
104        filenameExcludingSuffix = string.join(filenameWithoutPath.split('.')[:-1], '.')
105        layerName = layerName.replace('#FILENAME_EXCL_SUFFIX#', filenameExcludingSuffix)
106        layerName = layerName.replace('#FEATURE_NAME#', self.name)
107
108        # If timeStep contains a time part, make sure that the "Z" for "Zulu" is appended, as required by WMS
109        if re.search("T\d+\:\d+:\d+(\.\d+)$", timeStep):
110            timeStep = timeStep + 'Z'
111
112        # If required, make sure the longitude part of the bounding box is within (-180, 180)
113        if c.find('LongitudeBounds').text == 'MINUS_180_TO_PLUS_180':
114            self.bBox.east = self.longitudeWithinMinusPlus180(self.bBox.east)
115            self.bBox.west = self.longitudeWithinMinusPlus180(self.bBox.west)
116       
117        wmsRequest = '%s?request=GetMap&SERVICE=%s&FORMAT=%s&LAYERS=%s&BBOX=%s&WIDTH=%s&HEIGHT=%s&CRS=%s&TIME=%s' % (url, serviceVersion, imageFormat, layerName, self.bBox.str(), imageWidth, imageHeight, crs, timeStep)
118
119        return wmsRequest
120
121    def toKML(self, viewConfig):
122        '''
123        Returns the KML representation of this CSML feature.
124        It will be a KMLFolder object, containing KMLGroundOverlay objects -- each of them stands for
125        a grid for a single time step.
126        [MORE EXPLANATION]
127        [RESOLVE VISIBILITY]
128        '''
129        if self.description:
130            kmlFeatureFolder = KMLFolder(self.description, [], visible = False, opened = False)           
131        else:
132            kmlFeatureFolder = KMLFolder(self.name, [], visible = False, opened = False)           
133
134        if self.splitTimeStepsBy == None:
135
136            for timeStep in self.timeSteps:
137                kmlGroundOverlay = self.buildKmlGroundOverlay(timeStep)
138                kmlFeatureFolder.children.append(kmlGroundOverlay)
139               
140        else:
141           
142            def _getCategory(timeStep):
143                (year, month, day) = self.getYMD(timeStep)
144                if self.splitTimeStepsBy == None:
145                    return timeStep
146                elif self.splitTimeStepsBy == 'year':
147                    return year
148                elif self.splitTimeStepsBy == 'month':
149                    return month
150
151            dict = {}
152
153            for timeStep in self.timeSteps:
154                category = _getCategory(timeStep)
155                if not dict.has_key(category):
156                    dict[category] = []
157                dict[category].append(timeStep)
158            dictKeys = dict.keys()
159            dictKeys.sort()
160
161            for category in dictKeys:           
162
163                if self.categoryNamingPattern == None:
164                    categoryName = 'Category ' + str(category)
165                else:
166                    categoryName = self.categoryNamingPattern.replace('#VERBATIM#', str(category))
167                    if (category >= 1 and category <= 12):
168                        categoryName = categoryName.replace('#MONTH#', getMonthCommonName(category))
169               
170                kmlCategoryFolder = KMLFolder(categoryName, [], visible = False, opened = False)
171                for timeStep in dict[category]:
172                    kmlGroundOverlay = self.buildKmlGroundOverlay(timeStep)
173                    kmlCategoryFolder.children.append(kmlGroundOverlay)
174                kmlFeatureFolder.children.append(kmlCategoryFolder)
175
176        return kmlFeatureFolder
177
178    def exportFeature(self, viewConfig):
179        ''' method to output KML as ElementTree instance - returns a 'Folder' element which can be put into a KML Document element '''
180
181        featureElement=Element('Folder')
182        if self.description:
183            SubElement(featureElement, 'name').text=self.description  # come up with a feature folder name
184        else:
185            SubElement(featureElement, 'name').text=self.name
186        SubElement(featureElement, 'open').text='0'
187        SubElement(featureElement, 'visibility').text='0'
188
189        if self.splitTimeStepsBy == None:
190
191            for timeStep in self.timeSteps:
192                goElement = self.buildGroundOverlayElement(timeStep)
193                featureElement.append(goElement)
194
195        else:
196           
197            def getCategory(timeStep):
198                (year, month, day) = self.getYMD(timeStep)
199                if self.splitTimeStepsBy == None:
200                    return timeStep
201                elif self.splitTimeStepsBy == 'year':
202                    return year
203                elif self.splitTimeStepsBy == 'month':
204                    return month
205
206            dict = {}
207
208            for timeStep in self.timeSteps:
209                category = getCategory(timeStep)
210                if not dict.has_key(category):
211                    dict[category] = []
212                dict[category].append(timeStep)
213            dictKeys = dict.keys()
214            dictKeys.sort()
215
216            for category in dictKeys:           
217                categoryElement=Element('Folder')
218
219                if self.categoryNamingPattern == None:
220                    categoryName = 'Category ' + str(category)
221                else:
222                    categoryName = self.categoryNamingPattern.replace('#VERBATIM#', str(category))
223                    if (category >= 1 and category <= 12):
224                        categoryName = categoryName.replace('#MONTH#', getMonthCommonName(category))
225               
226                SubElement(categoryElement, 'name').text=categoryName
227                SubElement(categoryElement, 'open').text='0'
228                SubElement(categoryElement, 'visibility').text='0'       
229                for timeStep in dict[category]:
230                    goElement = self.buildGroundOverlayElement(timeStep)
231                    categoryElement.append(goElement)
232                featureElement.append(categoryElement)
233
234        return featureElement
235
236    def buildKmlGroundOverlay(self, timeStep):
237        ''' [VISIBILITY?]'''
238
239        (startDateTime, endDateTime) = self.getLogicalTimeSpan(timeStep)
240
241        return KMLGroundOverlay(
242            self.name + ' ' + timeStep,
243            self.buildWMSRequest(timeStep),
244            startDateTime,
245            endDateTime,
246            self.bBox.west, self.bBox.south, self.bBox.east, self.bBox.north,
247            visible = False
248            )
249
250    def buildGroundOverlayElement(self, timeStep):
251
252        goElement=Element('GroundOverlay')     
253        SubElement(goElement, 'name').text=self.name + ' ' + timeStep
254        SubElement(goElement, 'open').text='0'
255        SubElement(goElement, 'visibility').text='0'
256
257        timespanElement=SubElement(goElement, 'TimeSpan')
258        (startDateTime, endDateTime) = self.getLogicalTimeSpan(timeStep)
259        SubElement(timespanElement, 'begin').text = ('%04d-%02d-%02d') % (startDateTime.utctimetuple()[0:3])
260        SubElement(timespanElement, 'end').text = ('%04d-%02d-%02d') % (endDateTime.utctimetuple()[0:3])
261
262        # Include the WMS service call address
263        iconElement=SubElement(goElement,'icon')
264        SubElement(iconElement, 'href').text=self.buildWMSRequest(timeStep)
265        SubElement(iconElement, 'refreshMode').text='onExpire'
266
267        latlonboxElement=SubElement(goElement, 'LatLonBox')
268        SubElement(latlonboxElement, 'north').text=str(self.bBox.north)
269        SubElement(latlonboxElement, 'south').text=str(self.bBox.south)
270        SubElement(latlonboxElement, 'east' ).text=str(self.bBox.east)
271        SubElement(latlonboxElement, 'west' ).text=str(self.bBox.west)
272
273        return goElement
274
275# ------------------------- Logical date/time transforms (defined as plain functions) --------------------------
276
277def getSameDate(dateTime): return dateTime
278
279def getFirstDayOfMonth(dateTime):
280    replDay = 1
281    return dateTime.replace(day=replDay)
282
283def get20CenturyDecade(dateTime):
284    replYear = (dateTime.year - 1900) / 10 + 1 # get decade as a "logical" year
285    replDay = 1                                # start month on the 1st of month (not 15th)
286    return dateTime.replace(year=replYear, day=replDay)
287
288# --------------------------- Logical date/time deltas (defined as plain functions) ---------------------------
289
290def getMonthHence(dateTime):
291    if dateTime.month+1  <= 12:
292        return dateTime.replace(month=dateTime.month+1)
293    else:
294        return dateTime.replace(year=dateTime.year+1, month=1)
295   
296def getYearHence(dateTime):
297    return dateTime.replace(year=dateTime.year+1)
298
299def getDecadeHence(dateTime):
300    return dateTime.replace(year=dateTime.year+10)
301
302# --------------------------------------------------------------------------------------------------------------
303
304def getMonthCommonName(monthInt):
305    if monthInt < 1 or monthInt > 12:
306        raise ArgumentError('Wrong month name (' + str(monthInt) + ')')
307    names = ['January', 'February', 'March', 'April', 'May', 'June', 
308             'July', 'August', 'September', 'October', 'November', 'December']
309    return names[monthInt-1]
310
311# --------------------------------------------------------------------------------------------------------------
312
313class PointSeriesKML:
314    '''
315    There is no need for this class. This is because there is no way of showing a csml:PointSeriesFeature
316    in Google Earth. Instead, its graph gets loaded from a dynamic web link. The graphing/rendering service
317    is supposed to be independent and not contained in this egg.
318    '''
319    def __init__(self):
320        raise NotImplementedError()
321
322# --------------------------------------------------------------------------------------------------------------
323# (end of kmlfeatures.py)
Note: See TracBrowser for help on using the repository browser.