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

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

Finished CSML-to-KML conversion for the climatology dataset. Changed naming of dataset to use CSML name and naming of meteorological features to use feature description preferentially over feature (short)name.

Line 
1'''KML representatitions of various CSML features'''
2
3import string
4import re
5from datetime import datetime
6from cElementTree import ElementTree, Element, SubElement
7from configuration import Configuration
8
9class GridSeriesKML(object):
10    ''' Represents a CSML GridSeriesFeature in KML (via WMS requests).'''
11   
12    def __init__(self, config, viewConfig, parentFilename, name, description, bBox, timeSteps, timeBounds):
13        ''' Initialise with the key information content'''
14
15        # Conversion configuration
16        self.config = config
17
18        # View-specific conversion configuration (determines how data will be presented)
19        self.splitTimeStepsBy = viewConfig.find('SplitTimeStepsBy').text
20        ldttText = viewConfig.find('LogicalDateTimeTransform').text
21        if ldttText == '20_CENTURY_DECADE':
22            self.logicalDateTimeTransform = get20CenturyDecade
23        elif ldttText == 'SAME_DATE':
24            self.logicalDateTimeTransform = getSameDate
25        elif ldttText == 'FIRST_DAY_OF_MONTH':
26            self.logicalDateTimeTransform = getFirstDayOfMonth
27        else:
28            raise ValueError('Config error: Unsupported date/time transform')
29
30        ldtdText = viewConfig.find('LogicalDateTimeDelta').text
31        if ldtdText == 'MONTH_HENCE':
32            self.logicalDateTimeDelta = getMonthHence
33        elif ldtdText == 'YEAR_HENCE':
34            self.logicalDateTimeDelta = getYearHence
35        elif ldtdText == 'DECADE_HENCE':
36            self.logicalDateTimeDelta = getDecadeHence
37        else:
38            raise ValueError('Config error: Unsupported date/time delta')
39
40        try:
41            self.categoryNamingPattern = viewConfig.find('CategoryNamingPattern').text
42        except AttributeError:
43            self.categoryNamingPattern = None           
44
45        # Name of the CSML file in which feature is contained
46        self.parentFilename = parentFilename
47
48        # A short name for the feature:
49        self.name=name
50
51        # A descriptive name for the feature:
52        self.description=description
53
54        # The latitude/longitude bounding box as a csmlwrappers.BBox object
55        self.bBox=bBox
56
57        # The time steps
58        self.timeSteps=timeSteps.split()
59
60        # Valid bounds of the individual timesteps
61        self.timeBounds=timeBounds.split()
62   
63    def getYMD(self, timeStep):
64        matchObject = re.match("(\d+)\-(\d+)\-(\d+)T", timeStep)
65        (sYear, sMonth, sDay) = matchObject.groups()
66        return (int(sYear), int(sMonth), int(sDay))
67
68    def getLogicalTimeSpan(self, timeStep):
69        ''' Get time span that corresponds to this timeStep by using the appropriate
70            date/time transform and delta (i.e. date/time difference)
71        '''
72        (year, month, day) = self.getYMD(timeStep)
73        dateTime = datetime(year, month, day)
74        startDateTime = self.logicalDateTimeTransform(dateTime)
75        endDateTime = self.logicalDateTimeDelta(startDateTime)
76        return (startDateTime, endDateTime)
77
78    def longitudeWithinMinusPlus180(self, x):
79        ''' Put the x coordinate within bounds of the period (-180, 180)'''
80        if x != 180:
81            x = ((x + 180) % 360) - 180
82        return x
83   
84    def buildWMSRequest(self, timeStep):
85        ''' Build a WMS request '''
86
87        # Set request configuration parameters
88        c = self.config
89        xDir = 'CSMLGridSeriesFeatureWMSRequest/'
90        url = c.get(xDir + 'URL')
91        serviceVersion = c.get(xDir + 'ServiceVersion')
92        imageFormat = c.get(xDir + 'ImageFormat')
93        imageWidth = c.get(xDir + 'ImageWidth')
94        imageHeight = c.get(xDir + 'ImageHeight')
95        crs = c.get(xDir + 'CRS')
96        layerName = c.get(xDir + 'LayerName')
97        filenameWithoutPath = self.parentFilename.split('/')[-1]
98        filenameExcludingSuffix = string.join(filenameWithoutPath.split('.')[:-1], '.')
99        layerName = layerName.replace('#FILENAME_EXCL_SUFFIX#', filenameExcludingSuffix)
100        layerName = layerName.replace('#FEATURE_NAME#', self.name)
101
102        # If timeStep contains a time part, make sure that the "Z" for "Zulu" is appended, as required by WMS
103        if re.search("T\d+\:\d+:\d+(\.\d+)$", timeStep):
104            timeStep = timeStep + 'Z'
105
106        # If required, make sure the longitude part of the bounding box is within (-180, 180)
107        if c.get('LongitudeBounds') == 'MINUS_180_TO_PLUS_180':
108            self.bBox.east = self.longitudeWithinMinusPlus180(self.bBox.east)
109            self.bBox.west = self.longitudeWithinMinusPlus180(self.bBox.west)
110       
111        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)
112
113        return wmsRequest
114
115    def exportFeature(self, viewConfig):
116        ''' method to output KML as ElementTree instance - returns a 'Folder' element which can be put into a KML Document element '''
117
118        featureElement=Element('Folder')
119        if self.description:
120            SubElement(featureElement, 'name').text=self.description  # come up with a feature folder name
121        else:
122            SubElement(featureElement, 'name').text=self.name
123        SubElement(featureElement, 'open').text='0'
124        SubElement(featureElement, 'visibility').text='0'
125
126        if self.splitTimeStepsBy == None:
127
128            for timeStep in self.timeSteps:
129                goElement = self.buildGroundOverlayElement(timeStep)
130                featureElement.append(goElement)
131
132        else:
133           
134            def getCategory(timeStep):
135                (year, month, day) = self.getYMD(timeStep)
136                if self.splitTimeStepsBy == None:
137                    return timeStep
138                elif self.splitTimeStepsBy == 'year':
139                    return year
140                elif self.splitTimeStepsBy == 'month':
141                    return month
142
143            dict = {}
144
145            for timeStep in self.timeSteps:
146                category = getCategory(timeStep)
147                if not dict.has_key(category):
148                    dict[category] = []
149                dict[category].append(timeStep)
150            dictKeys = dict.keys()
151            dictKeys.sort()
152
153            for category in dictKeys:           
154                categoryElement=Element('Folder')
155
156                if self.categoryNamingPattern == None:
157                    categoryName = 'Category ' + str(category)
158                else:
159                    categoryName = self.categoryNamingPattern.replace('#VERBATIM#', str(category))
160                    if (category >= 1 and category <= 12):
161                        categoryName = categoryName.replace('#MONTH#', getMonthCommonName(category))
162               
163                SubElement(categoryElement, 'name').text=categoryName
164                SubElement(categoryElement, 'open').text='0'
165                SubElement(categoryElement, 'visibility').text='0'       
166                for timeStep in dict[category]:
167                    goElement = self.buildGroundOverlayElement(timeStep)
168                    categoryElement.append(goElement)
169                featureElement.append(categoryElement)
170
171        return featureElement
172
173    def buildGroundOverlayElement(self, timeStep):
174
175        goElement=Element('GroundOverlay')     
176        SubElement(goElement, 'name').text=self.name + ' ' + timeStep
177        SubElement(goElement, 'open').text='0'
178        SubElement(goElement, 'visibility').text='0'
179
180        timespanElement=SubElement(goElement, 'TimeSpan')
181        (startDateTime, endDateTime) = self.getLogicalTimeSpan(timeStep)
182        SubElement(timespanElement, 'begin').text = ('%04d-%02d-%02d') % startDateTime.utctimetuple()[0:3]
183        SubElement(timespanElement, 'end').text = ('%04d-%02d-%02d') % endDateTime.utctimetuple()[0:3]
184
185        # Include the WMS service call address
186        iconElement=SubElement(goElement,'icon')
187        SubElement(iconElement, 'href').text=self.buildWMSRequest(timeStep)
188        SubElement(iconElement, 'refreshMode').text='onExpire'
189
190        latlonboxElement=SubElement(goElement, 'LatLonBox')
191        SubElement(latlonboxElement, 'north').text=str(self.bBox.north)
192        SubElement(latlonboxElement, 'south').text=str(self.bBox.south)
193        SubElement(latlonboxElement, 'east' ).text=str(self.bBox.east)
194        SubElement(latlonboxElement, 'west' ).text=str(self.bBox.west)
195
196        return goElement
197
198# ------------------------- Logical date/time transforms (defined as plain functions) --------------------------
199
200def getSameDate(dateTime): return dateTime
201
202def getFirstDayOfMonth(dateTime):
203    replDay = 1
204    return dateTime.replace(day=replDay)
205
206def get20CenturyDecade(dateTime):
207    replYear = (dateTime.year - 1900) / 10 + 1 # get decade as a "logical" year
208    replDay = 1                                # start month on the 1st of month (not 15th)
209    return dateTime.replace(year=replYear, day=replDay)
210
211# --------------------------- Logical date/time deltas (defined as plain functions) ---------------------------
212
213def getMonthHence(dateTime):
214    if dateTime.month+1  <= 12:
215        return dateTime.replace(month=dateTime.month+1)
216    else:
217        return dateTime.replace(year=dateTime.year+1, month=1)
218   
219def getYearHence(dateTime):
220    return dateTime.replace(year=dateTime.year+1)
221
222def getDecadeHence(dateTime):
223    return dateTime.replace(year=dateTime.year+10)
224
225# --------------------------------------------------------------------------------------------------------------
226
227def getMonthCommonName(monthInt):
228    if monthInt < 1 or monthInt > 12:
229        raise ArgumentError('Wrong month name (' + str(monthInt) + ')')
230    names = ['January', 'February', 'March', 'April', 'May', 'June', 
231             'July', 'August', 'September', 'October', 'November', 'December']
232    return names[monthInt-1]
233
234# --------------------------------------------------------------------------------------------------------------
235# (end of kmlfeatures.py)
Note: See TracBrowser for help on using the repository browser.