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

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

Moved *.py back to csml2kml

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.