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

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

Removed dependency on configuration.py from the GridSeriesConvertor? module, in order to make it more like the PointSeriesConvertor? module.

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        # We will be using configuration for WMS request
88        c = self.config.find('CSMLGridSeriesFeatureWMSRequest')
89
90        # Set request configuration parameters
91        url = c.find('URL').text
92        serviceVersion = c.find('ServiceVersion').text
93        imageFormat = c.find('ImageFormat').text
94        imageWidth = c.find('ImageWidth').text
95        imageHeight = c.find('ImageHeight').text
96        crs = c.find('CRS').text
97        layerName = c.find('LayerName').text
98        filenameWithoutPath = self.parentFilename.split('/')[-1]
99        filenameExcludingSuffix = string.join(filenameWithoutPath.split('.')[:-1], '.')
100        layerName = layerName.replace('#FILENAME_EXCL_SUFFIX#', filenameExcludingSuffix)
101        layerName = layerName.replace('#FEATURE_NAME#', self.name)
102
103        # If timeStep contains a time part, make sure that the "Z" for "Zulu" is appended, as required by WMS
104        if re.search("T\d+\:\d+:\d+(\.\d+)$", timeStep):
105            timeStep = timeStep + 'Z'
106
107        # If required, make sure the longitude part of the bounding box is within (-180, 180)
108        if c.find('LongitudeBounds').text == 'MINUS_180_TO_PLUS_180':
109            self.bBox.east = self.longitudeWithinMinusPlus180(self.bBox.east)
110            self.bBox.west = self.longitudeWithinMinusPlus180(self.bBox.west)
111       
112        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)
113
114        return wmsRequest
115
116    def exportFeature(self, viewConfig):
117        ''' method to output KML as ElementTree instance - returns a 'Folder' element which can be put into a KML Document element '''
118
119        featureElement=Element('Folder')
120        if self.description:
121            SubElement(featureElement, 'name').text=self.description  # come up with a feature folder name
122        else:
123            SubElement(featureElement, 'name').text=self.name
124        SubElement(featureElement, 'open').text='0'
125        SubElement(featureElement, 'visibility').text='0'
126
127        if self.splitTimeStepsBy == None:
128
129            for timeStep in self.timeSteps:
130                goElement = self.buildGroundOverlayElement(timeStep)
131                featureElement.append(goElement)
132
133        else:
134           
135            def getCategory(timeStep):
136                (year, month, day) = self.getYMD(timeStep)
137                if self.splitTimeStepsBy == None:
138                    return timeStep
139                elif self.splitTimeStepsBy == 'year':
140                    return year
141                elif self.splitTimeStepsBy == 'month':
142                    return month
143
144            dict = {}
145
146            for timeStep in self.timeSteps:
147                category = getCategory(timeStep)
148                if not dict.has_key(category):
149                    dict[category] = []
150                dict[category].append(timeStep)
151            dictKeys = dict.keys()
152            dictKeys.sort()
153
154            for category in dictKeys:           
155                categoryElement=Element('Folder')
156
157                if self.categoryNamingPattern == None:
158                    categoryName = 'Category ' + str(category)
159                else:
160                    categoryName = self.categoryNamingPattern.replace('#VERBATIM#', str(category))
161                    if (category >= 1 and category <= 12):
162                        categoryName = categoryName.replace('#MONTH#', getMonthCommonName(category))
163               
164                SubElement(categoryElement, 'name').text=categoryName
165                SubElement(categoryElement, 'open').text='0'
166                SubElement(categoryElement, 'visibility').text='0'       
167                for timeStep in dict[category]:
168                    goElement = self.buildGroundOverlayElement(timeStep)
169                    categoryElement.append(goElement)
170                featureElement.append(categoryElement)
171
172        return featureElement
173
174    def buildGroundOverlayElement(self, timeStep):
175
176        goElement=Element('GroundOverlay')     
177        SubElement(goElement, 'name').text=self.name + ' ' + timeStep
178        SubElement(goElement, 'open').text='0'
179        SubElement(goElement, 'visibility').text='0'
180
181        timespanElement=SubElement(goElement, 'TimeSpan')
182        (startDateTime, endDateTime) = self.getLogicalTimeSpan(timeStep)
183        SubElement(timespanElement, 'begin').text = ('%04d-%02d-%02d') % startDateTime.utctimetuple()[0:3]
184        SubElement(timespanElement, 'end').text = ('%04d-%02d-%02d') % endDateTime.utctimetuple()[0:3]
185
186        # Include the WMS service call address
187        iconElement=SubElement(goElement,'icon')
188        SubElement(iconElement, 'href').text=self.buildWMSRequest(timeStep)
189        SubElement(iconElement, 'refreshMode').text='onExpire'
190
191        latlonboxElement=SubElement(goElement, 'LatLonBox')
192        SubElement(latlonboxElement, 'north').text=str(self.bBox.north)
193        SubElement(latlonboxElement, 'south').text=str(self.bBox.south)
194        SubElement(latlonboxElement, 'east' ).text=str(self.bBox.east)
195        SubElement(latlonboxElement, 'west' ).text=str(self.bBox.west)
196
197        return goElement
198
199# ------------------------- Logical date/time transforms (defined as plain functions) --------------------------
200
201def getSameDate(dateTime): return dateTime
202
203def getFirstDayOfMonth(dateTime):
204    replDay = 1
205    return dateTime.replace(day=replDay)
206
207def get20CenturyDecade(dateTime):
208    replYear = (dateTime.year - 1900) / 10 + 1 # get decade as a "logical" year
209    replDay = 1                                # start month on the 1st of month (not 15th)
210    return dateTime.replace(year=replYear, day=replDay)
211
212# --------------------------- Logical date/time deltas (defined as plain functions) ---------------------------
213
214def getMonthHence(dateTime):
215    if dateTime.month+1  <= 12:
216        return dateTime.replace(month=dateTime.month+1)
217    else:
218        return dateTime.replace(year=dateTime.year+1, month=1)
219   
220def getYearHence(dateTime):
221    return dateTime.replace(year=dateTime.year+1)
222
223def getDecadeHence(dateTime):
224    return dateTime.replace(year=dateTime.year+10)
225
226# --------------------------------------------------------------------------------------------------------------
227
228def getMonthCommonName(monthInt):
229    if monthInt < 1 or monthInt > 12:
230        raise ArgumentError('Wrong month name (' + str(monthInt) + ')')
231    names = ['January', 'February', 'March', 'April', 'May', 'June', 
232             'July', 'August', 'September', 'October', 'November', 'December']
233    return names[monthInt-1]
234
235# --------------------------------------------------------------------------------------------------------------
236# (end of kmlfeatures.py)
Note: See TracBrowser for help on using the repository browser.