"""
Reading OpenStreetMap data from XML file.
Author: Sergey Vartanov
"""
from typing import Any, Dict, List, Optional
from roentgen import ui
class OSMNode:
"""
OpenStreetMap node.
See https://wiki.openstreetmap.org/wiki/Node
"""
def __init__(self, id_: int = 0, lat: float = 0, lon: float = 0):
self.id_: int = id_
self.lat: float = lat
self.lon: float = lon
self.tags: Dict[str, str] = {}
self.visible: Optional[str] = None
self.changeset: Optional[str] = None
self.timestamp: Optional[str] = None
self.user: Optional[str] = None
self.uid: Optional[str] = None
def parse_from_xml(self, text: str, is_full: bool = False) -> "OSMNode":
"""
Parse from XML node representation.
:param text: XML node representation
:param is_full: if false, parse only ID, latitude and longitude
"""
self.id_ = int(get_value("id", text))
self.lat = float(get_value("lat", text))
self.lon = float(get_value("lon", text))
if is_full:
self.visible = get_value("visible", text)
self.changeset = get_value("changeset", text)
self.timestamp = get_value("timestamp", text)
self.user = get_value("user", text)
self.uid = get_value("uid", text)
return self
class OSMWay:
"""
OpenStreetMap way.
See https://wiki.openstreetmap.org/wiki/Way
"""
def __init__(self, id_: int = 0, nodes=None):
self.id_: int = id_
self.nodes: List[int] = [] if nodes is None else nodes
self.tags: Dict[str, str] = {}
self.visible: Optional[str] = None
self.changeset: Optional[str] = None
self.user: Optional[str] = None
self.timestamp: Optional[str] = None
self.uid: Optional[str] = None
def parse_from_xml(self, text: str, is_full: bool = False) -> "OSMWay":
"""
Parse from XML way representation.
:param text: XML way representation
:param is_full: if false, parse only ID
"""
self.id_ = int(get_value("id", text))
if is_full:
self.visible = get_value("visible", text)
self.changeset = get_value("changeset", text)
self.timestamp = get_value("timestamp", text)
self.user = get_value("user", text)
self.uid = get_value("uid", text)
return self
def is_cycle(self) -> bool:
return self.nodes[0] == self.nodes[-1]
def try_to_glue(self, other: "OSMWay"):
if self.nodes[0] == other.nodes[0]:
return OSMWay(nodes=list(reversed(other.nodes[1:])) + self.nodes)
elif self.nodes[0] == other.nodes[-1]:
return OSMWay(nodes=other.nodes[:-1] + self.nodes)
elif self.nodes[-1] == other.nodes[-1]:
return OSMWay(nodes=self.nodes + list(reversed(other.nodes[:-1])))
elif self.nodes[-1] == other.nodes[0]:
return OSMWay(nodes=self.nodes + other.nodes[1:])
def __repr__(self):
return f"Way <{self.id_}> {self.nodes}"
class OSMRelation:
"""
OpenStreetMap relation.
See https://wiki.openstreetmap.org/wiki/Relation
"""
def __init__(self, id_: int = 0):
self.id_: int = id_
self.tags: Dict[str, str] = {}
self.members: List["OSMMember"] = []
def parse_from_xml(self, text: str) -> "OSMRelation":
"""
Parse from XML relation representation.
:param text: XML way representation
"""
self.id_ = int(get_value("id", text))
return self
class OSMMember:
"""
Member of OpenStreetMap relation.
"""
def __init__(self, text: str):
self.type_ = get_value("type", text)
self.ref = int(get_value("ref", text))
self.role = get_value("role", text)
def get_value(key: str, text: str):
"""
Parse xml value from the tag in the format of key="value".
"""
if key + '="' in text:
index: int = text.find(key + '="')
value = text[index + len(key) + 2:text.find('"', index + len(key) + 4)]
return value
class Map:
def __init__(self, node_map, way_map, relation_map):
self.node_map = node_map
self.way_map = way_map
self.relation_map = relation_map
class OSMReader:
"""
OSM XML representation reader.
"""
def __init__(self, file_name: str):
self.file_name = file_name
def parse_osm_file(
self, parse_nodes: bool = True, parse_ways: bool = True,
parse_relations: bool = True, full: bool = False) -> Map:
"""
Parse OSM XML representation.
"""
node_map: Dict[int, OSMNode] = {}
way_map: Dict[int, OSMWay] = {}
relation_map: Dict[int, OSMRelation] = {}
print(f"Line number counting for {self.file_name}...")
with open(self.file_name) as f:
for lines_number, _ in enumerate(f):
pass
print("Done.")
print(f"Parsing OSM file {self.file_name}...")
input_file = open(self.file_name)
line = input_file.readline()
line_number = 0
while line != "":
line_number += 1
ui.write_line(line_number, lines_number)
# Node parsing.
if line[:6] in [" \n", "\t\n", " \n"]:
node_map[element.id_] = element
# Way parsing.
elif line[:5] in [' \n', '\t\n'] or line == " \n":
way_map[element.id_] = element
# Relation parsing.
elif line[:10] in [" \n", "\t\n"] or \
line == " \n":
relation_map[element.id_] = element
# Elements parsing.
elif line[:6] in ["