|
|
|
|
|
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) |
|
|
|
|
|
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 = 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", |
|
) |
|
|
|
|
|
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", |
|
) |
|
|
|
|
|
|
|
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 |
|
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 |
|
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 |
|
elem2group["area"][area.id_] = group |
|
|
|
return elem2group |
|
|