Spaces:
Running
Running
| # Copyright (c) Meta Platforms, Inc. and affiliates. | |
| import logging | |
| import re | |
| from typing import List | |
| from .reader import OSMData, OSMElement, OSMNode, OSMWay | |
| IGNORE_TAGS = {"source", "phone", "entrance", "inscription", "note", "name"} | |
| def parse_levels(string: str) -> List[float]: | |
| """Parse string representation of level sequence value.""" | |
| try: | |
| cleaned = string.replace(",", ";").replace(" ", "") | |
| return list(map(float, cleaned.split(";"))) | |
| except ValueError: | |
| logging.debug("Cannot parse level description from `%s`.", string) | |
| return [] | |
| def filter_level(elem: OSMElement): | |
| level = elem.tags.get("level") | |
| if level is not None: | |
| levels = parse_levels(level) | |
| # In the US, ground floor levels are sometimes marked as level=1 | |
| # so let's be conservative and include it. | |
| if not (0 in levels or 1 in levels): | |
| return False | |
| layer = elem.tags.get("layer") | |
| if layer is not None: | |
| layer = parse_levels(layer) | |
| if len(layer) > 0 and max(layer) < 0: | |
| return False | |
| return ( | |
| elem.tags.get("location") != "underground" | |
| and elem.tags.get("parking") != "underground" | |
| ) | |
| def filter_node(node: OSMNode): | |
| return len(node.tags.keys() - IGNORE_TAGS) > 0 and filter_level(node) | |
| def is_area(way: OSMWay): | |
| if way.nodes[0] != way.nodes[-1]: | |
| return False | |
| if way.tags.get("area") == "no": | |
| return False | |
| filters = [ | |
| "area", | |
| "building", | |
| "amenity", | |
| "indoor", | |
| "landuse", | |
| "landcover", | |
| "leisure", | |
| "public_transport", | |
| "shop", | |
| ] | |
| for f in filters: | |
| if f in way.tags and way.tags.get(f) != "no": | |
| return True | |
| if way.tags.get("natural") in {"wood", "grassland", "water"}: | |
| return True | |
| return False | |
| def filter_area(way: OSMWay): | |
| return len(way.tags.keys() - IGNORE_TAGS) > 0 and is_area(way) and filter_level(way) | |
| def filter_way(way: OSMWay): | |
| return not filter_area(way) and way.tags != {} and filter_level(way) | |
| def parse_node(tags): | |
| keys = tags.keys() | |
| for key in [ | |
| "amenity", | |
| "natural", | |
| "highway", | |
| "barrier", | |
| "shop", | |
| "tourism", | |
| "public_transport", | |
| "emergency", | |
| "man_made", | |
| ]: | |
| if key in keys: | |
| if "disused" in tags[key]: | |
| continue | |
| return f"{key}:{tags[key]}" | |
| return None | |
| def parse_area(tags): | |
| if "building" in tags: | |
| group = "building" | |
| kind = tags["building"] | |
| if kind == "yes": | |
| for key in ["amenity", "tourism"]: | |
| if key in tags: | |
| kind = tags[key] | |
| break | |
| if kind != "yes": | |
| group += f":{kind}" | |
| return group | |
| if "area:highway" in tags: | |
| return f'highway:{tags["area:highway"]}' | |
| for key in [ | |
| "amenity", | |
| "landcover", | |
| "leisure", | |
| "shop", | |
| "highway", | |
| "tourism", | |
| "natural", | |
| "waterway", | |
| "landuse", | |
| ]: | |
| if key in tags: | |
| return f"{key}:{tags[key]}" | |
| return None | |
| def parse_way(tags): | |
| keys = tags.keys() | |
| for key in ["highway", "barrier", "natural"]: | |
| if key in keys: | |
| return f"{key}:{tags[key]}" | |
| return None | |
| def match_to_group(label, patterns): | |
| for group, pattern in patterns.items(): | |
| if re.match(pattern, label): | |
| return group | |
| return None | |
| class Patterns: | |
| areas = dict( | |
| building="building($|:.*?)*", | |
| parking="amenity:parking", | |
| playground="leisure:(playground|pitch)", | |
| grass="(landuse:grass|landcover:grass|landuse:meadow|landuse:flowerbed|natural:grassland)", # noqa E501 | |
| park="leisure:(park|garden|dog_park)", | |
| forest="(landuse:forest|natural:wood)", | |
| water="(natural:water|waterway:*)", | |
| ) | |
| # + ways: road, path | |
| # + node: fountain, bicycle_parking | |
| ways = dict( | |
| fence="barrier:(fence|yes)", | |
| wall="barrier:(wall|retaining_wall)", | |
| hedge="barrier:hedge", | |
| kerb="barrier:kerb", | |
| building_outline="building($|:.*?)*", | |
| cycleway="highway:cycleway", | |
| path="highway:(pedestrian|footway|steps|path|corridor)", | |
| road="highway:(motorway|trunk|primary|secondary|tertiary|service|construction|track|unclassified|residential|.*_link)", # noqa E501 | |
| busway="highway:busway", | |
| tree_row="natural:tree_row", # maybe merge with node? | |
| ) | |
| # + nodes: bollard | |
| nodes = dict( | |
| tree="natural:tree", | |
| stone="(natural:stone|barrier:block)", | |
| crossing="highway:crossing", | |
| lamp="highway:street_lamp", | |
| traffic_signal="highway:traffic_signals", | |
| bus_stop="highway:bus_stop", | |
| stop_sign="highway:stop", | |
| junction="highway:motorway_junction", | |
| bus_stop_position="public_transport:stop_position", | |
| gate="barrier:(gate|lift_gate|swing_gate|cycle_barrier)", | |
| bollard="barrier:bollard", | |
| shop="(shop.*?|amenity:(bank|post_office))", | |
| restaurant="amenity:(restaurant|fast_food)", | |
| bar="amenity:(cafe|bar|pub|biergarten)", | |
| pharmacy="amenity:pharmacy", | |
| fuel="amenity:fuel", | |
| bicycle_parking="amenity:(bicycle_parking|bicycle_rental)", | |
| charging_station="amenity:charging_station", | |
| parking_entrance="amenity:parking_entrance", | |
| atm="amenity:atm", | |
| toilets="amenity:toilets", | |
| vending_machine="amenity:vending_machine", | |
| fountain="amenity:fountain", | |
| waste_basket="amenity:(waste_basket|waste_disposal)", | |
| bench="amenity:bench", | |
| post_box="amenity:post_box", | |
| artwork="tourism:artwork", | |
| recycling="amenity:recycling", | |
| give_way="highway:give_way", | |
| clock="amenity:clock", | |
| fire_hydrant="emergency:fire_hydrant", | |
| pole="man_made:(flagpole|utility_pole)", | |
| street_cabinet="man_made:street_cabinet", | |
| ) | |
| # + ways: kerb | |
| class Groups: | |
| areas = list(Patterns.areas) | |
| ways = list(Patterns.ways) | |
| nodes = list(Patterns.nodes) | |
| def group_elements(osm: OSMData): | |
| elem2group = { | |
| "area": {}, | |
| "way": {}, | |
| "node": {}, | |
| } | |
| for node in filter(filter_node, osm.nodes.values()): | |
| label = parse_node(node.tags) | |
| if label is None: | |
| continue | |
| group = match_to_group(label, Patterns.nodes) | |
| if group is None: | |
| group = match_to_group(label, Patterns.ways) | |
| if group is None: | |
| continue # missing | |
| elem2group["node"][node.id_] = group | |
| for way in filter(filter_way, osm.ways.values()): | |
| label = parse_way(way.tags) | |
| if label is None: | |
| continue | |
| group = match_to_group(label, Patterns.ways) | |
| if group is None: | |
| group = match_to_group(label, Patterns.nodes) | |
| if group is None: | |
| continue # missing | |
| elem2group["way"][way.id_] = group | |
| for area in filter(filter_area, osm.ways.values()): | |
| label = parse_area(area.tags) | |
| if label is None: | |
| continue | |
| group = match_to_group(label, Patterns.areas) | |
| if group is None: | |
| group = match_to_group(label, Patterns.ways) | |
| if group is None: | |
| group = match_to_group(label, Patterns.nodes) | |
| if group is None: | |
| continue # missing | |
| elem2group["area"][area.id_] = group | |
| return elem2group | |