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

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

Re-tested GridSeriesConvertor?

Line 
1'''KML representatitions of various CSML features'''
2
3import string
4import re
5from datetime import datetime
6from cElementTree import ElementTree, Element, SubElement
7
8class GridSeriesKML(object):
9    ''' Represents a CSML GridSeriesFeature in KML (via WMS requests).'''
10   
11    def __init__(self, config, viewConfig, parentFilename, name, description, bBox, timeSteps, timeBounds):
12        ''' Initialise with the key information content'''
13
14        # Conversion configuration
15        self.config = config
16
17        # View-specific conversion configuration (determines how data will be presented)
18        self.splitTimeStepsBy = viewConfig.find('SplitTimeStepsBy').text
19        ldttText = viewConfig.find('LogicalDateTimeTransform').text
20        if ldttText == '20_CENTURY_DECADE':
21            self.logicalDateTimeTransform = get20CenturyDecade
22        elif ldttText == 'SAME_DATE':
23            self.logicalDateTimeTransform = getSameDate
24        elif ldttText == 'FIRST_DAY_OF_MONTH':
25            self.logicalDateTimeTransform = getFirstDayOfMonth
26        else:
27            raise ValueError('Config error: Unsupported date/time transform')
28
29        ldtdText = viewConfig.find('LogicalDateTimeDelta').text
30        if ldtdText == 'MONTH_HENCE':
31            self.logicalDateTimeDelta = getMonthHence
32        elif ldtdText == 'YEAR_HENCE':
33            self.logicalDateTimeDelta = getYearHence
34        elif ldtdText == 'DECADE_HENCE':
35            self.logicalDateTimeDelta = getDecadeHence
36        else:
37            raise ValueError('Config error: Unsupported date/time delta')
38
39        try:
40            self.categoryNamingPattern = viewConfig.find('CategoryNamingPattern').text
41        except AttributeError:
42            self.categoryNamingPattern = None           
43
44        # Name of the CSML file in which feature is contained
45        self.parentFilename = parentFilename
46
47        # A short name for the feature:
48        self.name=name
49
50        # A descriptive name for the feature:
51        self.description=description
52
53        # The latitude/longitude bounding box as a csmlwrappers.BBox object
54        self.bBox=bBox
55
56        # The time steps
57        self.timeSteps=timeSteps.split()
58
59        # Valid bounds of the individual timesteps
60        self.timeBounds=timeBounds.split()
61   
62    def getYMD(self, timeStep):
63        matchObject = re.match("(\d+)\-(\d+)\-(\d+)T", timeStep)
64        (sYear, sMonth, sDay) = matchObject.groups()
65        return (int(sYear), int(sMonth), int(sDay))
66
67    def getLogicalTimeSpan(self, timeStep):
68        ''' Get time span that corresponds to this timeStep by using the appropriate
69            date/time transform and delta (i.e. date/time difference)
70        '''
71        (year, month, day) = self.getYMD(timeStep)
72        dateTime = datetime(year, month, day)
73        startDateTime = self.logicalDateTimeTransform(dateTime)
74        endDateTime = self.logicalDateTimeDelta(startDateTime)
75        return (startDateTime, endDateTime)
76
77    def longitudeWithinMinusPlus180(self, x):
78        ''' Put the x coordinate within bounds of the period (-180, 180)'''
79        if x != 180:
80            x = ((x + 180) % 360) - 180
81        return x
82   
83    def buildWMSRequest(self, timeStep):
84        ''' Build a WMS request '''
85
86        # We will be using configuration for WMS request
87        c = self.config.find('CSMLGridSeriesFeatureWMSRequest')
88
89        # Set request configuration parameters
90        url = c.find('URL').text
91        serviceVersion = c.find('ServiceVersion').text
92        imageFormat = c.find('ImageFormat').text
93        imageWidth = c.find('ImageWidth').text
94        imageHeight = c.find('ImageHeight').text
95        crs = c.find('CRS').text
96        layerName = c.find('LayerName').text
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.find('LongitudeBounds').text == '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
234class PointSeriesKML:
235    '''
236    There is no need for this class. This is because there is no way of showing a csml:PointSeriesFeature
237    in Google Earth. Instead, it's graph gets loaded from a dynamic web link. The graphing/rendering service
238    is supposed to be independent and not contained in this egg.
239    '''
240    def __init__(self):
241        raise NotImplementedError()
242
243# --------------------------------------------------------------------------------------------------------------
244# (end of kmlfeatures.py)
Note: See TracBrowser for help on using the repository browser.