"""Converts OpenStreetMap XML containing one or more sledrun relations to geojson.""" from typing import Optional, Dict, Iterable from xml.etree.ElementTree import ElementTree, Element from geojson import FeatureCollection, Feature, Point, LineString from wrpylib.wrgeojson import WrPointFeatureType, WrLineStringFeatureType, sort_features, get_geometry_center def find_sledrun_relations(osm_tree: ElementTree) -> Iterable[Element]: """Returns all sledrun relations found in the OSM file.""" return osm_tree.findall("relation/tag[@k='type'][@v='sled']/..") def tags(element: Element) -> Dict: """Returns the OMS tags of a relation, a way or a node as dict.""" tag_list = element.findall('tag') return {tag.attrib['k']: tag.attrib['v'] for tag in tag_list} class DeRefError(ValueError): pass def de_ref(osm_tree: ElementTree, element: Element) -> Element: """De-references an element (relation member or nd).""" ref = element.get('ref') if element.tag == 'member': target = osm_tree.find(f"./{element.get('type')}[@id='{ref}']") elif element.tag == 'nd': target = osm_tree.find(f"./node[@id='{ref}']") else: raise ValueError(f'Unsupported element type: {element.tag}') if target is None: raise DeRefError(f'Element {element} not found in tree.') return target def node_coordinates(element: Element) -> Point: lon = float(element.get('lon')) lat = float(element.get('lat')) return Point((lon, lat)) def way_coordinates(osm_tree: ElementTree, element: Element) -> LineString: lon_lat_list = [] for element in element.findall('nd'): node = de_ref(osm_tree, element) lon_lat_list.append((float(node.get('lon')), float(node.get('lat')))) return LineString(lon_lat_list) def point_feature(osm_tree: ElementTree, point_type: WrPointFeatureType, name: Optional[str], element: Element) \ -> Feature: properties = {'type': point_type.value} if name is not None: properties['name'] = name if element.tag == 'node': point = node_coordinates(element) elif element.tag == 'way': line_string = way_coordinates(osm_tree, element) point = get_geometry_center(line_string) else: raise ValueError(f'Unsupported element: {element.tag}') return Feature(geometry=point, properties=properties) def way_feature(osm_tree: ElementTree, line_string_type: WrLineStringFeatureType, element: Element) -> Feature: properties = {'type': line_string_type.value} if element.tag == 'way': line_string = way_coordinates(osm_tree, element) return Feature(geometry=line_string, properties=properties) elif element.tag == 'relation': for member in element.findall('member'): subelement = de_ref(osm_tree, member) if subelement.tag == 'way': if member.get('role') == '': line_string = way_coordinates(osm_tree, subelement) return Feature(geometry=line_string) raise ValueError('Unsupported element type') def element_to_feature(osm_tree: ElementTree, member: Element) -> Optional[Feature]: element = de_ref(osm_tree, member) element_tags = tags(element) name = element_tags.get('name') if element_tags.get('amenity') == 'parking': return point_feature(osm_tree, WrPointFeatureType.parking, name, element) elif element_tags.get('amenity') in ['restaurant', 'bar', 'fast_food', 'pub'] \ or element_tags.get('tourism') in ['alpine_hut', 'hotel', 'guest_house']: return point_feature(osm_tree, WrPointFeatureType.gastronomy, name, element) elif element.tag == 'node' and ( element_tags.get('highway') == 'bus_stop' or element_tags.get('railway') in ['halt', 'station'] or element_tags.get('public_transport') in ['stop_position', 'platform'] or element_tags.get('amenity') == 'bus_station'): return point_feature(osm_tree, WrPointFeatureType.bus_stop, name, element) elif element.tag == 'node' and member.get('role') == 'warning': return point_feature(osm_tree, WrPointFeatureType.warning, name, element) elif element.tag == 'way' and (member.get('role') in ['sled', 'walk', 'alternative']): return way_feature(osm_tree, WrLineStringFeatureType[member.get('role')], element) elif element.tag == 'way' and ('sled' in element_tags.get('piste:type', '').split(';')): return way_feature(osm_tree, WrLineStringFeatureType.sled, element) elif (element.tag == 'way' and ( element_tags.get('aerialway') in ['gondola', 'cable_car', 'chair_lift', 't-bar', 'mixed_lift'] or element_tags.get('railway') in ['funicular', 'rail', 'narrow_gauge'])) \ or (element.tag == 'relation' and element_tags.get('route') == 'train'): return way_feature(osm_tree, WrLineStringFeatureType.lift, element) elif element.tag == 'node': return point_feature(osm_tree, WrPointFeatureType.point, name, element) elif element.tag == 'way': return way_feature(osm_tree, WrLineStringFeatureType.line, element) else: print(f'Unknown Element: {element}') def convert_osm_to_geojson(osm_tree: ElementTree, sledrun_relation: Element) -> FeatureCollection: """Converts an OSM XML file parsed with ElementTree to GeoJSON. sledrun is the relation representing the sledrun.""" feature_list = [] for member in sledrun_relation.findall('member'): feature = element_to_feature(osm_tree, member) if feature is not None: feature_list.append(feature) # Calculate center of the map feature_collection = FeatureCollection(feature_list) center = get_geometry_center(feature_collection) feature_collection = sort_features(feature_collection) feature_collection['wr_properties'] = { 'lon': center['coordinates'][0], 'lat': center['coordinates'][1], 'zoom': 14 } return feature_collection