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
5 from geojson import FeatureCollection, Feature, Point, LineString
7 from wrpylib.wrgeojson import WrPointFeatureType, WrLineStringFeatureType, sort_features, get_geometry_center
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']/..")
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}
21 class DeRefError(ValueError):
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}']")
33 raise ValueError(f'Unsupported element type: {element.tag}')
35 raise DeRefError(f'Element {element} not found in tree.')
39 def node_coordinates(element: Element) -> Point:
40 lon = float(element.get('lon'))
41 lat = float(element.get('lat'))
42 return Point((lon, lat))
45 def way_coordinates(osm_tree: ElementTree, element: Element) -> LineString:
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)
53 def point_feature(osm_tree: ElementTree, point_type: WrPointFeatureType, name: Optional[str], element: Element) \
55 properties = {'type': point_type.value}
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)
64 raise ValueError(f'Unsupported element: {element.tag}')
65 return Feature(geometry=point, properties=properties)
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')
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)
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)
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)
101 elif element.tag == 'node' and member.get('role') == 'warning':
102 return point_feature(osm_tree, WrPointFeatureType.warning, name, element)
104 elif element.tag == 'way' and (member.get('role') in ['sled', 'walk', 'alternative']):
105 return way_feature(osm_tree, WrLineStringFeatureType[member.get('role')], element)
107 elif element.tag == 'way' and ('sled' in element_tags.get('piste:type', '').split(';')):
108 return way_feature(osm_tree, WrLineStringFeatureType.sled, element)
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)
117 elif element.tag == 'node':
118 return point_feature(osm_tree, WrPointFeatureType.point, name, element)
120 elif element.tag == 'way':
121 return way_feature(osm_tree, WrLineStringFeatureType.line, element)
124 print(f'Unknown Element: {element}')
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."""
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)
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],
144 return feature_collection