MapLocNetGradio / osm /parser.py
wangerniu
maplocnet
629144d
# 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)",
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)",
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