source: DPPP/kml/csml2kml/python/csml2kml/csml2kml/KML.py @ 3549

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

Wrote code for generating a hierarchy of KML files (with kml:NetworkLinks) and directories.

RevLine 
[3549]1import os
2import sys
[3287]3from cElementTree import ElementTree, Element, SubElement, XML
4
[3362]5class KMLElement:
6    '''
7    Abstract class, representing any element of a KML document -- i.e. anything contained *within*
8    the <kml:Document> element.
9    '''
10
11    def build(self):
12        '''
13        It is required that each KMLElement instance is able to build() itself into an ElementTree.Element
14        instance of KML, which it represents.
15        '''
16        raise NotImplementedError("Abstract method, to be overriden by child classes")
17
[3287]18class KMLDocument:
[3292]19    '''
20    Wraps around a whole KML document and makes it possible to save it to a file directly.
[3362]21    Represent the <kml:Document>, so that the method build() builds the kml:Document element,
22    but also contains the method save() that allows saving that to a file, wrapped within the <kml> header.
[3292]23    '''
[3287]24
[3291]25    def __init__(self, name, styles):
[3287]26
27        self.name = name
[3291]28        self.styles = styles
29        self.elements = []
[3287]30
[3362]31    def build(self):
32
33        # Create the <Document> an element to hold the document
34        documentElement = Element('Document')
35        SubElement(documentElement, 'name').text = self.name
[3459]36        SubElement(documentElement, 'open').text = '1'
[3362]37
38        # Build the associated styles
39        for style in self.styles:
40            documentElement.append( style.build() )
41
42        # Build the sub-elements
43        for element in self.elements:
44            documentElement.append( element.build() )
45       
46        return documentElement
47       
[3287]48    def save(self, outputFilename):
[3292]49        '''
[3549]50        Save the document to file <code>outputFilename</code> (full path).
[3292]51        '''
[3287]52
53        def _indentXML(elem, level=0):
[3549]54            '''Auxiliary function, indents XML'''
[3287]55            i = "\n" + level * "  "
56            if len(elem):
57                if not elem.text or not elem.text.strip():
58                    elem.text = i + "  "
59                for child in elem:
60                    _indentXML(child, level+1)
61                if not child.tail or not child.tail.strip():
62                    child.tail = i
63                if not elem.tail or not elem.tail.strip():
64                    elem.tail = i
65            else:
66                if level and (not elem.tail or not elem.tail.strip()):
67                    elem.tail = i
68
[3549]69        def _save(filename):
70            '''Builds and saves the document in the KML format into the file filename.'''
[3291]71
[3549]72            # Build the Document element
73            documentElement = self.build()
[3287]74
[3549]75            # Attach the Document element as a subelement of a root 'kml' element
76            rootElement=Element('kml', xmlns='http://earth.google.com/kml/2.2')
77            rootElement.append(documentElement)
78            _indentXML(rootElement)
[3287]79
[3549]80            # Write the KML document to a file
81            elementTree = ElementTree(rootElement)       
82            kmlFile = open(filename, 'w')
83            elementTree.write(kmlFile)
84            kmlFile.close()
85
86        # Check the suffix of outputFilename, and depending on suffix, either save the document
87        # directly to a KML file, or save it packed in a KMZ file.
88        suffix = outputFilename[-3:].lower()
89        if suffix == 'kml':
90            _save(outputFilename)
91        elif suffix == 'kmz':
92            raise NotImplementError('Needs re-testing')
93            ##shortOutputFilename = outputFilename.split('/')[-1]          # separate out the short filename
94            ##outputDir = outputFilename[:-(len(shortOutputFilename)+1)]   # separate out the directory name
95            ##_save(outputDir + '/doc.kml')
96            ##os.system('zip -q %s %s/doc.kml' % (outputFilename, outputDir))
97            ##os.remove(outputDir + '/doc.kml')
98        else:
99            raise ValueError('Wrong file suffix, only "kml" and "kmz" allowed.')
100
[3290]101class KMLStyle(KMLElement):
[3292]102    '''
103    Represents the <kml:Style> tag.
104    '''
[3290]105
[3291]106    def __init__(self, id, iconURL = None, balloonTemplate = None, listItemType = None):
[3287]107        self.id = id
108        self.iconURL = iconURL
109        self.balloonTemplate = balloonTemplate
[3291]110        if listItemType:
111            allowedValues =  ['check', 'checkOffOnly', 'checkHideChildren', 'radioFolder']
112            if not listItemType in allowedValues:
113                raise ValueError('listItemType not among allowed values: ' + str(allowedValues))
114        self.listItemType = listItemType
[3287]115
116    def build(self):
117
118        styleElement = Element('Style')
119        styleElement.set('id', self.id)
120
[3291]121        # If specified, build the IconStyle element -- for assigning an icon to station placemarks
122        if self.iconURL:
123            iconStyleElement = SubElement(styleElement, 'IconStyle')
124            SubElement(iconStyleElement, 'scale').text = '1.2'
125            iconElement = SubElement(iconStyleElement, 'Icon')
126            SubElement(iconElement, 'href').text = self.iconURL
[3287]127
[3291]128        # If specified, build the BalloonStyle sub-element -- an HTML template for the balloons
129        if self.balloonTemplate:
130            balloonStyleElement = SubElement(styleElement, 'BalloonStyle')
131            SubElement(balloonStyleElement, 'text').text = self.balloonTemplate
[3287]132
[3291]133        # If specified, build the ListStyle sub-element -- which determines how the associated lists
134        # in the left-hand side of the Google Earth screen are going to display/behave
135        if self.listItemType:
136            listStyleElement = SubElement(styleElement, 'ListStyle')
137            SubElement(listStyleElement, 'listItemType').text = self.listItemType
138       
[3287]139        return styleElement
140
[3291]141def createDefaultPlacemarKMLStyle(id = 'default_placemark_style',
142                                  iconURL = 'http://maps.google.com/mapfiles/kml/shapes/target.png',
143                                  balloonTemplate = ''):
[3292]144    '''
145    A factory method defined for convenience. Creates a style readily usable for adding styles to KML placemarks.
146    '''
[3291]147    return KMLStyle(id, iconURL, balloonTemplate)
[3289]148
[3290]149class KMLPlacemark(KMLElement):
[3292]150    '''
151    Represents the <kml:Placemark> tag.
152    '''
[3289]153   
[3459]154    def __init__(self, id, name, lon, lat, featuresHtml, styleID = None, data=None, visible = True):
[3289]155        self.id = id
156        self.name = name
157        self.lon = lon
158        self.lat = lat
[3434]159        self.featuresHtml = featuresHtml
[3291]160        self.styleID = styleID
[3289]161        self.data = data
[3459]162        self.visible = visible
[3289]163
164    def build(self):
165        placemarkElement = Element('Placemark')
166        SubElement(placemarkElement, 'name').text = self.name
[3459]167
168        if self.visible:
169            SubElement(placemarkElement, 'visibility').text = '1'
170        else:
171            SubElement(placemarkElement, 'visibility').text = '0'
172
[3291]173        if self.styleID:
174            SubElement(placemarkElement, 'styleUrl').text = '#' + self.styleID
[3289]175
176        lookAtElement = SubElement(placemarkElement, 'LookAt')
177        SubElement(lookAtElement, 'longitude').text = str(self.lon)
178        SubElement(lookAtElement, 'latitude').text = str(self.lat)
179
180        pointElement = SubElement(placemarkElement, 'Point')
181        SubElement(pointElement, 'coordinates').text = '%f,%f,%f' % (self.lon, self.lat, 0.)
182
183        # If the "data" dictionary is provided, create the additional <ExtendedData> element,
184        # which contains specific data items, which will be automatically substituted
185        # for placemarks in the balloon template when the user views the document in Google Earth.
186        if self.data:
187            extendedDataElement = SubElement(placemarkElement, 'ExtendedData')
188            for key in self.data:
189                dataElement = SubElement(extendedDataElement, 'Data')
190                dataElement.set('name', key)
191                value = self.data[key]
192                value = value.replace('#ID#', self.id)
193                value = value.replace('#NAME#', self.name)
194                value = value.replace('#LON#', str(self.lon))
195                value = value.replace('#LAT#', str(self.lat))
[3436]196                value = value.replace('#FEATURES#', self.featuresHtml)
[3289]197                SubElement(dataElement, 'value').text = value
[3434]198               
[3289]199        return placemarkElement
[3290]200
201class KMLFolder(KMLElement):
[3292]202    '''
203    Represents the <kml:Folder> tag.
204    '''
[3290]205
[3534]206    def __init__(self, name, children, styleID = None, region = None, opened = True, visible = True, description = None):
[3290]207        self.name = name
208        self.children = children
[3291]209        self.styleID = styleID
210        self.region = region
[3459]211        self.opened = opened
212        self.visible = visible
[3534]213        self.description = description
[3291]214
[3290]215    def build(self):
216        folderElement = Element('Folder')
[3291]217        if self.styleID:
218            SubElement(folderElement, 'styleUrl').text = '#' + self.styleID
219        SubElement(folderElement, 'name').text = self.name
[3534]220        if self.description:
221            SubElement(folderElement, 'description').text = self.description
[3459]222        if self.visible:
223            SubElement(folderElement, 'visibility').text = '1'
224        else:
225            SubElement(folderElement, 'visibility').text = '0'
226        if self.opened:
227            SubElement(folderElement, 'open').text = '1'
228        else:
229            SubElement(folderElement, 'open').text = '0'
[3291]230        if self.region:
231            if not isinstance(self.region, KMLRegion):
232                raise TypeError('Region not a KMLRegion')
233            folderElement.append( self.region.build() )
234        for child in self.children:
[3290]235            if not isinstance(child, KMLElement):
[3291]236                raise TypeError('Child does not have a KMLElement base class')
[3290]237            folderElement.append( child.build() )
238        return folderElement
239
240class KMLRegion(KMLElement):
[3292]241    '''
242    Represents the <kml:Region> tag.
243    '''
[3290]244   
245    def __init__(self, west, south, east, north, minLodPixels = 64, maxLodPixels = -1):
246        self.west = west
247        self.south = south
248        self.east = east
249        self.north = north
[3291]250        self.minLodPixels = minLodPixels
251        self.maxLodPixels = maxLodPixels
[3290]252
253    def build(self):
254        llabElement = Element('LatLonAltBox')
[3291]255        SubElement(llabElement, 'west').text = str(self.west)
256        SubElement(llabElement, 'south').text = str(self.south)
257        SubElement(llabElement, 'east').text = str(self.east)
258        SubElement(llabElement, 'north').text = str(self.north)
[3290]259
260        lodElement = Element('Lod')
[3291]261        SubElement(lodElement, 'minLodPixels').text = str(self.minLodPixels)
262        SubElement(lodElement, 'maxLodPixels').text = str(self.maxLodPixels)
[3290]263
264        regionElement = Element('Region')
265        regionElement.append(llabElement)
266        regionElement.append(lodElement)
267        return regionElement
[3451]268
269class KMLGroundOverlay(KMLElement):
270    '''Represents the <kml:GroundOverlay> tag.'''
271
[3459]272    def __init__(self, name, sourceUrl, timespanStart, timespanEnd, west, south, east, north, visible = True):
[3451]273        self.name = name
[3459]274        self.sourceUrl = sourceUrl
[3451]275        self.timespanStart = timespanStart
276        self.timespanEnd = timespanEnd
277        self.west = west
278        self.south = south
279        self.east = east
280        self.north = north
[3459]281        self.visible = visible
[3451]282
283    def build(self):
284
285        groundOverlayElement = Element('GroundOverlay')
286
287        SubElement(groundOverlayElement, 'name').text = self.name
288
[3459]289        if self.visible:
290            SubElement(groundOverlayElement, 'visibility').text = '1'
291        else:
292            SubElement(groundOverlayElement, 'visibility').text = '0'
293
[3451]294        timespanElement = SubElement(groundOverlayElement, 'TimeSpan')
[3459]295        SubElement(timespanElement, 'begin').text = ('%04d-%02d-%02d') % self.timespanStart.utctimetuple()[0:3]
296        SubElement(timespanElement, 'end').text = ('%04d-%02d-%02d') % self.timespanEnd.utctimetuple()[0:3]
[3451]297
298        # Include the WMS service call address
299        iconElement = SubElement(groundOverlayElement, 'icon')
[3459]300        SubElement(iconElement, 'href').text = self.sourceUrl
[3451]301        SubElement(iconElement, 'refreshMode').text = 'onExpire'
302
303        latlonboxElement=SubElement(groundOverlayElement, 'LatLonBox')
304        SubElement(latlonboxElement, 'north').text = str(self.north)
305        SubElement(latlonboxElement, 'south').text = str(self.south)
306        SubElement(latlonboxElement, 'east' ).text = str(self.east)
307        SubElement(latlonboxElement, 'west' ).text = str(self.west)
308
309        return groundOverlayElement
[3545]310
311class KMLNetworkLink(KMLElement):
312
[3549]313    def __init__(self, name, url, visible = True):
314        self.name = name
315        self.url = url
316        self.visible = visible
317
[3545]318    def build(self):
[3549]319        networkLinkElement = Element('NetworkLink')
320
321        SubElement(networkLinkElement, 'name').text = self.name
322
323        if self.visible:
324            SubElement(networkLinkElement, 'visibility').text = '1'
325        else:
326            SubElement(networkLinkElement, 'visibility').text = '0'
327
328        linkElement = SubElement(networkLinkElement, 'Link')
329        SubElement(linkElement, 'href').text = self.url
330
331        return networkLinkElement
Note: See TracBrowser for help on using the repository browser.