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

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

Added support for storing datetimes in the WMSCapabilities.BottomWMSLayer class. Added a convertor class WMSCapabilities.WmsLayersConvertor? for conversion into 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        '''
127        if self.description:
128            kmlFeatureFolder = KMLFolder(self.description, [], visible = False, opened = False)           
129        else:
130            kmlFeatureFolder = KMLFolder(self.name, [], visible = False, opened = False)           
131
132        if self.splitTimeStepsBy == None:
133
134            for timeStep in self.timeSteps:
135                kmlGroundOverlay = self.buildKmlGroundOverlay(timeStep)
136                kmlFeatureFolder.children.append(kmlGroundOverlay)
137               
138        else:
139           
140            def _getCategory(timeStep):
141                (year, month, day) = self.getYMD(timeStep)
142                if self.splitTimeStepsBy == None:
143                    return timeStep
144                elif self.splitTimeStepsBy == 'year':
145                    return year
146                elif self.splitTimeStepsBy == 'month':
147                    return month
148
149            dict = {}
150
151            for timeStep in self.timeSteps:
152                category = _getCategory(timeStep)
153                if not dict.has_key(category):
154                    dict[category] = []
155                dict[category].append(timeStep)
156            dictKeys = dict.keys()
157            dictKeys.sort()
158
159            for category in dictKeys:           
160
161                if self.categoryNamingPattern == None:
162                    categoryName = 'Category ' + str(category)
163                else:
164                    categoryName = self.categoryNamingPattern.replace('#VERBATIM#', str(category))
165                    if (category >= 1 and category <= 12):
166                        categoryName = categoryName.replace('#MONTH#', getMonthCommonName(category))
167               
168                kmlCategoryFolder = KMLFolder(categoryName, [], visible = False, opened = False)
169                for timeStep in dict[category]:
170                    kmlGroundOverlay = self.buildKmlGroundOverlay(timeStep)
171                    kmlCategoryFolder.children.append(kmlGroundOverlay)
172                kmlFeatureFolder.children.append(kmlCategoryFolder)
173
174        return kmlFeatureFolder
175
176    def buildKmlGroundOverlay(self, timeStep):
177
178        (startDateTime, endDateTime) = self.getLogicalTimeSpan(timeStep)
179
180        return KMLGroundOverlay(
181            self.name + ' ' + timeStep,
182            self.buildWMSRequest(timeStep),
183            startDateTime,
184            endDateTime,
185            self.bBox.west, self.bBox.south, self.bBox.east, self.bBox.north,
186            visible = False
187            )
188
189# ------------------------- Logical date/time transforms (defined as plain functions) --------------------------
190
191def getSameDate(dateTime): return dateTime
192
193def getFirstDayOfMonth(dateTime):
194    replDay = 1
195    return dateTime.replace(day=replDay)
196
197def get20CenturyDecade(dateTime):
198    replYear = (dateTime.year - 1900) / 10 + 1 # get decade as a "logical" year
199    replDay = 1                                # start month on the 1st of month (not 15th)
200    return dateTime.replace(year=replYear, day=replDay)
201
202# --------------------------- Logical date/time deltas (defined as plain functions) ---------------------------
203
204def getMonthHence(dateTime):
205    if dateTime.month+1  <= 12:
206        return dateTime.replace(month=dateTime.month+1)
207    else:
208        return dateTime.replace(year=dateTime.year+1, month=1)
209   
210def getYearHence(dateTime):
211    return dateTime.replace(year=dateTime.year+1)
212
213def getDecadeHence(dateTime):
214    return dateTime.replace(year=dateTime.year+10)
215
216# --------------------------------------------------------------------------------------------------------------
217
218def getMonthCommonName(monthInt):
219    if monthInt < 1 or monthInt > 12:
220        raise ArgumentError('Wrong month name (' + str(monthInt) + ')')
221    names = ['January', 'February', 'March', 'April', 'May', 'June', 
222             'July', 'August', 'September', 'October', 'November', 'December']
223    return names[monthInt-1]
224
225# --------------------------------------------------------------------------------------------------------------
226
227class PointSeriesKML:
228    '''
229    There is no need for this class. This is because there is no way of showing a csml:PointSeriesFeature
230    in Google Earth. Instead, its graph gets loaded from a dynamic web link. The graphing/rendering service
231    is supposed to be independent and not contained in this egg.
232    '''
233    def __init__(self):
234        raise NotImplementedError()
235
236# --------------------------------------------------------------------------------------------------------------
237# (end of kmlfeatures.py)
Note: See TracBrowser for help on using the repository browser.