]> ToastFreeware Gitweb - philipp/winterrodeln/wrpylib.git/blob - wrpylib/wrosm.py
VAO is missing important streets in Switzerland.
[philipp/winterrodeln/wrpylib.git] / wrpylib / wrosm.py
1 """Converts OpenStreetMap XML containing one or more sledrun relations to geojson."""
2 from typing import Optional, Dict, Iterable
3 from xml.etree.ElementTree import ElementTree, Element
4
5 from geojson import FeatureCollection, Feature, Point, LineString
6
7 from wrpylib.wrgeojson import WrPointFeatureType, WrLineStringFeatureType, sort_features, get_geometry_center
8
9
10 def find_sledrun_relations(osm_tree: ElementTree) -> Iterable[Element]:
11     """Returns all sledrun relations found in the OSM file."""
12     return osm_tree.findall("relation/tag[@k='type'][@v='sled']/..")
13
14
15 def tags(element: Element) -> Dict:
16     """Returns the OMS tags of a relation, a way or a node as dict."""
17     tag_list = element.findall('tag')
18     return {tag.attrib['k']: tag.attrib['v'] for tag in tag_list}
19
20
21 class DeRefError(ValueError):
22     pass
23
24
25 def de_ref(osm_tree: ElementTree, element: Element) -> Element:
26     """De-references an element (relation member or nd)."""
27     ref = element.get('ref')
28     if element.tag == 'member':
29         target = osm_tree.find(f"./{element.get('type')}[@id='{ref}']")
30     elif element.tag == 'nd':
31         target = osm_tree.find(f"./node[@id='{ref}']")
32     else:
33         raise ValueError(f'Unsupported element type: {element.tag}')
34     if target is None:
35         raise DeRefError(f'Element {element} not found in tree.')
36     return target
37
38
39 def node_coordinates(element: Element) -> Point:
40     lon = float(element.get('lon'))
41     lat = float(element.get('lat'))
42     return Point((lon, lat))
43
44
45 def way_coordinates(osm_tree: ElementTree, element: Element) -> LineString:
46     lon_lat_list = []
47     for element in element.findall('nd'):
48         node = de_ref(osm_tree, element)
49         lon_lat_list.append((float(node.get('lon')), float(node.get('lat'))))
50     return LineString(lon_lat_list)
51
52
53 def point_feature(osm_tree: ElementTree, point_type: WrPointFeatureType, name: Optional[str], element: Element) \
54         -> Feature:
55     properties = {'type': point_type.value}
56     if name is not None:
57         properties['name'] = name
58     if element.tag == 'node':
59         point = node_coordinates(element)
60     elif element.tag == 'way':
61         line_string = way_coordinates(osm_tree, element)
62         point = get_geometry_center(line_string)
63     else:
64         raise ValueError(f'Unsupported element: {element.tag}')
65     return Feature(geometry=point, properties=properties)
66
67
68 def way_feature(osm_tree: ElementTree, line_string_type: WrLineStringFeatureType, element: Element) -> Feature:
69     properties = {'type': line_string_type.value}
70     if element.tag == 'way':
71         line_string = way_coordinates(osm_tree, element)
72         return Feature(geometry=line_string, properties=properties)
73     elif element.tag == 'relation':
74         for member in element.findall('member'):
75             subelement = de_ref(osm_tree, member)
76             if subelement.tag == 'way':
77                 if member.get('role') == '':
78                     line_string = way_coordinates(osm_tree, subelement)
79                     return Feature(geometry=line_string)
80     raise ValueError('Unsupported element type')
81
82
83 def element_to_feature(osm_tree: ElementTree, member: Element) -> Optional[Feature]:
84     element = de_ref(osm_tree, member)
85     element_tags = tags(element)
86     name = element_tags.get('name')
87     if element_tags.get('amenity') == 'parking':
88         return point_feature(osm_tree, WrPointFeatureType.parking, name, element)
89
90     elif element_tags.get('amenity') in ['restaurant', 'bar', 'fast_food', 'pub'] \
91             or element_tags.get('tourism') in ['alpine_hut', 'hotel', 'guest_house']:
92         return point_feature(osm_tree, WrPointFeatureType.gastronomy, name, element)
93
94     elif element.tag == 'node' and (
95             element_tags.get('highway') == 'bus_stop'
96             or element_tags.get('railway') in ['halt', 'station']
97             or element_tags.get('public_transport') in ['stop_position', 'platform']
98             or element_tags.get('amenity') == 'bus_station'):
99         return point_feature(osm_tree, WrPointFeatureType.bus_stop, name, element)
100
101     elif element.tag == 'node' and member.get('role') == 'warning':
102         return point_feature(osm_tree, WrPointFeatureType.warning, name, element)
103
104     elif element.tag == 'way' and (member.get('role') in ['sled', 'walk', 'alternative']):
105         return way_feature(osm_tree, WrLineStringFeatureType[member.get('role')], element)
106
107     elif element.tag == 'way' and ('sled' in element_tags.get('piste:type', '').split(';')):
108         return way_feature(osm_tree, WrLineStringFeatureType.sled, element)
109
110     elif (element.tag == 'way' and (
111                 element_tags.get('aerialway')
112                 in ['gondola', 'cable_car', 'chair_lift', 't-bar', 'mixed_lift']
113                 or element_tags.get('railway') in ['funicular', 'rail', 'narrow_gauge'])) \
114             or (element.tag == 'relation' and element_tags.get('route') == 'train'):
115         return way_feature(osm_tree, WrLineStringFeatureType.lift, element)
116
117     elif element.tag == 'node':
118         return point_feature(osm_tree, WrPointFeatureType.point, name, element)
119
120     elif element.tag == 'way':
121         return way_feature(osm_tree, WrLineStringFeatureType.line, element)
122
123     else:
124         print(f'Unknown Element: {element}')
125
126
127 def convert_osm_to_geojson(osm_tree: ElementTree, sledrun_relation: Element) -> FeatureCollection:
128     """Converts an OSM XML file parsed with ElementTree to GeoJSON. sledrun is the relation representing the sledrun."""
129     feature_list = []
130     for member in sledrun_relation.findall('member'):
131         feature = element_to_feature(osm_tree, member)
132         if feature is not None:
133             feature_list.append(feature)
134
135     # Calculate center of the map
136     feature_collection = FeatureCollection(feature_list)
137     center = get_geometry_center(feature_collection)
138     feature_collection = sort_features(feature_collection)
139     feature_collection['wr_properties'] = {
140         'lon': center['coordinates'][0],
141         'lat': center['coordinates'][1],
142         'zoom': 14
143     }
144     return feature_collection