-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathkml.py
More file actions
executable file
·151 lines (123 loc) · 6.11 KB
/
kml.py
File metadata and controls
executable file
·151 lines (123 loc) · 6.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# Copyright (C)2014,2015 Philipp Naumann
# Copyright (C)2014,2015 Marcus Soll
#
# This file is part of SPtP.
#
# SPtP is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# SPtP is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with SPtP. If not, see <http://www.gnu.org/licenses/>.
import shapely.geometry
from osm import *
from geometry import *
class KML:
def __init__(self, root):
"""
This class represents a KML data structure.
:return: None
"""
self.nodes = {}
self.ways = {}
ns = root.tag.replace('}kml', '}')
def node_text(node, default):
"""
Returns a node's text if it is not None and node.text is not
None, default otherwise.
:param node: Node
:type node: xml.etree.ElementTree.Element
:return: Node text if set, default otherwise
:rtype: string
"""
return default if node is None else node.text if node.text else default
def text_to_float_pairs(text):
"""
Translates coordinates from KML format to float tuple.
:param text: Coordinate string to split
:type text: string
:return: List of float tuples
:rtype: [(float,float),...]
"""
c_text_pairs = [c.split(',') for c in text.split(' ') if len(c) > 0]
return [(float(c[0]), float(c[1])) for c in c_text_pairs]
for placemark in root.iter(ns + 'Placemark'):
name = node_text(placemark.find(ns + 'name'), '?')
description = node_text(placemark.find(ns + 'description'), '?')
node = placemark.find(ns + 'Polygon')
if node is not None:
coordinates_node = node.find('.//' + ns + 'coordinates')
c_float_pairs_text = coordinates_node.text.strip().replace('\n', ' ')
c_float_pairs = text_to_float_pairs(c_float_pairs_text)
# Polygon type is expected to be "LinearRing"
if len(c_float_pairs) < 3:
continue
polygon = shapely.geometry.Polygon(c_float_pairs)
tags = {'description': description}
self.ways[name] = Way(name, tags, polygon)
node = placemark.find(ns + 'Point')
if node is not None:
c_float_pairs = text_to_float_pairs(node.find('.//' + ns + 'coordinates').text)
assert len(c_float_pairs) == 1
point = shapely.geometry.Point(c_float_pairs[0])
tags = {'description': description}
self.nodes[name] = Node(name, tags, point)
def __repr__(self):
return '%s[%s]' % (self.__class__.__name__, ', '.join(['%s = %s' % (str(k), str(v)) for (k, v) in self.__dict__.items()]))
class KMLBuilder:
def __init__(self):
"""
This class builds a string containing the contents of a KML file.
All placemarks which should be saved in the KML file must be added using add_placemark().
:return: None
"""
self._placemarks = []
def add_placemark(self, item):
"""
Adds a placemark to the builder.
:param item: an instance of geometry.Node or geometry.Way
:type item: node or way to be added
:raise ValueError: if item is not an instance of geometry.Node or geometry.Way
:return: None
"""
if not isinstance(item, Node) and not isinstance(item, Way):
raise ValueError()
self._placemarks.append(item)
def run(self):
"""
Builds a KML string from objects stored in "_placemarks".
:return: KML String
:rtype: string
"""
styles = '<Style id="poly1">\n<LineStyle>\n<width>1.5</width>\n</LineStyle>\n<PolyStyle>\n<color>7dff0000</color>\n</PolyStyle>\n</Style>\n'
header = '%s' % styles
kml = '<?xml version="1.0" encoding="UTF-8"?>\n<kml xmlns="http://www.opengis.net/kml/2.2">\n<Document>\n%s' % header
kml += '<!--\nContains information from http://www.openstreetmap.org/, which is made available\nhere under the Open Database License (ODbL) [http://opendatacommons.org/licenses/odbl/1.0/].\n-->\n'
for item in self._placemarks:
node_kml = None
description = item.tags['description'] if 'description' in item.tags else ""
# KML point
if isinstance(item, Node):
extended_data = '\n'
node_kml = '<Placemark>\n<name>%s</name>\n<description>%s</description>\n' % (item.name, description)
node_kml += '<Point>\n<coordinates>\n%f,%f\n</coordinates>\n</Point>\n' % (item.point.x, item.point.y)
node_kml += '<ExtendedData>%s</ExtendedData>\n</Placemark>\n' % extended_data
# KML polygon
if isinstance(item, Way):
coordinates = '\n'.join('%f,%f' % (p[0], p[1]) for p in item.polygon.exterior.coords)
style_url = '#poly1'
extended_tags = '<altitudeMode>clampToGround</altitudeMode>\n<extrude>1</extrude>\n<tessellate>1</tessellate>'
extended_data = '\n'
node_kml = '<Placemark>\n<name>%s</name>\n<styleUrl>%s</styleUrl>\n%s\n<description>%s</description>\n' % (item.name, style_url, extended_tags, description)
node_kml += '<Polygon>\n<outerBoundaryIs>\n<%s>\n<coordinates>\n%s\n</coordinates>\n</%s>\n</outerBoundaryIs>\n</Polygon>\n' % ('LinearRing', coordinates, 'LinearRing')
node_kml += '<ExtendedData>%s</ExtendedData>\n</Placemark>\n' % extended_data
assert node_kml is not None
kml += node_kml
kml += '</Document>\n</kml>'
return kml