You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							1619 lines
						
					
					
						
							52 KiB
						
					
					
				
			
		
		
	
	
							1619 lines
						
					
					
						
							52 KiB
						
					
					
				from collections import OrderedDict
 | 
						|
from datetime import datetime
 | 
						|
from decimal import Decimal
 | 
						|
from xml.sax import handler, make_parser
 | 
						|
import json
 | 
						|
import re
 | 
						|
import sys
 | 
						|
import time
 | 
						|
import requests
 | 
						|
 | 
						|
from overpy import exception
 | 
						|
from overpy.__about__ import (
 | 
						|
    __author__, __copyright__, __email__, __license__, __summary__, __title__,
 | 
						|
    __uri__, __version__
 | 
						|
)
 | 
						|
 | 
						|
PY2 = sys.version_info[0] == 2
 | 
						|
PY3 = sys.version_info[0] == 3
 | 
						|
 | 
						|
XML_PARSER_DOM = 1
 | 
						|
XML_PARSER_SAX = 2
 | 
						|
 | 
						|
# Try to convert some common attributes
 | 
						|
# http://wiki.openstreetmap.org/wiki/Elements#Common_attributes
 | 
						|
GLOBAL_ATTRIBUTE_MODIFIERS = {
 | 
						|
    "changeset": int,
 | 
						|
    "timestamp": lambda ts: datetime.strptime(ts, "%Y-%m-%dT%H:%M:%SZ"),
 | 
						|
    "uid": int,
 | 
						|
    "version": int,
 | 
						|
    "visible": lambda v: v.lower() == "true"
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
def is_valid_type(element, cls):
 | 
						|
    """
 | 
						|
    Test if an element is of a given type.
 | 
						|
 | 
						|
    :param Element() element: The element instance to test
 | 
						|
    :param Element cls: The element class to test
 | 
						|
    :return: False or True
 | 
						|
    :rtype: Boolean
 | 
						|
    """
 | 
						|
    return isinstance(element, cls) and element.id is not None
 | 
						|
 | 
						|
 | 
						|
class Overpass(object):
 | 
						|
    """
 | 
						|
    Class to access the Overpass API
 | 
						|
 | 
						|
    :cvar default_max_retry_count: Global max number of retries (Default: 0)
 | 
						|
    :cvar default_retry_timeout: Global time to wait between tries (Default: 1.0s)
 | 
						|
    """
 | 
						|
    default_max_retry_count = 0
 | 
						|
    default_read_chunk_size = 4096
 | 
						|
    default_retry_timeout = 1.0
 | 
						|
    default_url = "http://overpass-api.de/api/interpreter"
 | 
						|
 | 
						|
    def __init__(self, read_chunk_size=None, url=None, xml_parser=XML_PARSER_SAX, max_retry_count=None, retry_timeout=None, timeout=5.0, headers=None):
 | 
						|
        """
 | 
						|
        :param read_chunk_size: Max size of each chunk read from the server response
 | 
						|
        :type read_chunk_size: Integer
 | 
						|
        :param url: Optional URL of the Overpass server. Defaults to http://overpass-api.de/api/interpreter
 | 
						|
        :type url: str
 | 
						|
        :param xml_parser: The xml parser to use
 | 
						|
        :type xml_parser: Integer
 | 
						|
        :param max_retry_count: Max number of retries (Default: default_max_retry_count)
 | 
						|
        :type max_retry_count: Integer
 | 
						|
        :param retry_timeout: Time to wait between tries (Default: default_retry_timeout)
 | 
						|
        :type retry_timeout: float
 | 
						|
        :param timeout: HTTP request timeout
 | 
						|
        :type timeout: float
 | 
						|
        :param headers: HTTP request headers
 | 
						|
        :type headers: dict
 | 
						|
        """
 | 
						|
        self.url = self.default_url
 | 
						|
        if url is not None:
 | 
						|
            self.url = url
 | 
						|
 | 
						|
        self._regex_extract_error_msg = re.compile(b"\<p\>(?P<msg>\<strong\s.*?)\</p\>")
 | 
						|
        self._regex_remove_tag = re.compile(b"<[^>]*?>")
 | 
						|
        if read_chunk_size is None:
 | 
						|
            read_chunk_size = self.default_read_chunk_size
 | 
						|
        self.read_chunk_size = read_chunk_size
 | 
						|
 | 
						|
        if max_retry_count is None:
 | 
						|
            max_retry_count = self.default_max_retry_count
 | 
						|
        self.max_retry_count = max_retry_count
 | 
						|
 | 
						|
        if retry_timeout is None:
 | 
						|
            retry_timeout = self.default_retry_timeout
 | 
						|
        self.retry_timeout = retry_timeout
 | 
						|
 | 
						|
        self.xml_parser = xml_parser
 | 
						|
        self.timeout = timeout
 | 
						|
        self.headers = headers
 | 
						|
 | 
						|
    def _handle_remark_msg(self, msg):
 | 
						|
        """
 | 
						|
        Try to parse the message provided with the remark tag or element.
 | 
						|
 | 
						|
        :param str msg: The message
 | 
						|
        :raises overpy.exception.OverpassRuntimeError: If message starts with 'runtime error:'
 | 
						|
        :raises overpy.exception.OverpassRuntimeRemark: If message starts with 'runtime remark:'
 | 
						|
        :raises overpy.exception.OverpassUnknownError: If we are unable to identify the error
 | 
						|
        """
 | 
						|
        msg = msg.strip()
 | 
						|
        if msg.startswith("runtime error:"):
 | 
						|
            raise exception.OverpassRuntimeError(msg=msg)
 | 
						|
        elif msg.startswith("runtime remark:"):
 | 
						|
            raise exception.OverpassRuntimeRemark(msg=msg)
 | 
						|
        raise exception.OverpassUnknownError(msg=msg)
 | 
						|
 | 
						|
    def query(self, query):
 | 
						|
        """
 | 
						|
        Query the Overpass API
 | 
						|
 | 
						|
        :param String|Bytes query: The query string in Overpass QL
 | 
						|
        :return: The parsed result
 | 
						|
        :rtype: overpy.Result
 | 
						|
        """
 | 
						|
        if not isinstance(query, bytes):
 | 
						|
            query = query.encode("utf-8")
 | 
						|
 | 
						|
        retry_num = 0
 | 
						|
        retry_exceptions = []
 | 
						|
        do_retry = True if self.max_retry_count > 0 else False
 | 
						|
        while retry_num <= self.max_retry_count:
 | 
						|
            if retry_num > 0:
 | 
						|
                time.sleep(self.retry_timeout)
 | 
						|
            retry_num += 1
 | 
						|
 | 
						|
            try:
 | 
						|
                if self.headers is not None:
 | 
						|
                    r = requests.post(self.url, query, timeout=self.timeout, headers=self.headers)
 | 
						|
                else:
 | 
						|
                    r = requests.post(self.url, query, timeout=self.timeout)
 | 
						|
                response = r.content
 | 
						|
            except (requests.exceptions.BaseHTTPError, requests.exceptions.RequestException) as e:
 | 
						|
                if not do_retry:
 | 
						|
                    raise e
 | 
						|
                retry_exceptions.append(e)
 | 
						|
                continue
 | 
						|
 | 
						|
            if r.status_code == 200:
 | 
						|
                content_type = r.headers["Content-Type"]
 | 
						|
 | 
						|
                if content_type == "application/json":
 | 
						|
                    return self.parse_json(response)
 | 
						|
 | 
						|
                if content_type == "application/osm3s+xml":
 | 
						|
                    return self.parse_xml(response)
 | 
						|
 | 
						|
                e = exception.OverpassUnknownContentType(content_type)
 | 
						|
                if not do_retry:
 | 
						|
                    raise e
 | 
						|
                retry_exceptions.append(e)
 | 
						|
                continue
 | 
						|
            elif r.status_code == 400:
 | 
						|
                msgs = []
 | 
						|
                for msg in self._regex_extract_error_msg.finditer(response):
 | 
						|
                    tmp = self._regex_remove_tag.sub(b"", msg.group("msg"))
 | 
						|
                    try:
 | 
						|
                        tmp = tmp.decode("utf-8")
 | 
						|
                    except UnicodeDecodeError:
 | 
						|
                        tmp = repr(tmp)
 | 
						|
                    msgs.append(tmp)
 | 
						|
 | 
						|
                e = exception.OverpassBadRequest(
 | 
						|
                    query,
 | 
						|
                    msgs=msgs
 | 
						|
                )
 | 
						|
                if not do_retry:
 | 
						|
                    raise e
 | 
						|
                retry_exceptions.append(e)
 | 
						|
                continue
 | 
						|
            elif r.status_code == 429:
 | 
						|
                e = exception.OverpassTooManyRequests
 | 
						|
                if not do_retry:
 | 
						|
                    raise e
 | 
						|
                retry_exceptions.append(e)
 | 
						|
                continue
 | 
						|
            elif r.status_code == 504:
 | 
						|
                e = exception.OverpassGatewayTimeout
 | 
						|
                if not do_retry:
 | 
						|
                    raise e
 | 
						|
                retry_exceptions.append(e)
 | 
						|
                continue
 | 
						|
 | 
						|
            # No valid response code
 | 
						|
            e = exception.OverpassUnknownHTTPStatusCode(r.status_code)
 | 
						|
            if not do_retry:
 | 
						|
                raise e
 | 
						|
            retry_exceptions.append(e)
 | 
						|
            continue
 | 
						|
 | 
						|
        raise exception.MaxRetriesReached(retry_count=retry_num, exceptions=retry_exceptions)
 | 
						|
 | 
						|
    def parse_json(self, data, encoding="utf-8"):
 | 
						|
        """
 | 
						|
        Parse raw response from Overpass service.
 | 
						|
 | 
						|
        :param data: Raw JSON Data
 | 
						|
        :type data: String or Bytes
 | 
						|
        :param encoding: Encoding to decode byte string
 | 
						|
        :type encoding: String
 | 
						|
        :return: Result object
 | 
						|
        :rtype: overpy.Result
 | 
						|
        """
 | 
						|
        if isinstance(data, bytes):
 | 
						|
          data = data.decode(encoding)
 | 
						|
 | 
						|
        data = json.loads(data, parse_float=Decimal)
 | 
						|
        if "remark" in data:
 | 
						|
            self._handle_remark_msg(msg=data.get("remark"))
 | 
						|
        return Result.from_json(data, api=self)
 | 
						|
 | 
						|
    def parse_xml(self, data, encoding="utf-8", parser=None):
 | 
						|
        """
 | 
						|
 | 
						|
        :param data: Raw XML Data
 | 
						|
        :type data: String or Bytes
 | 
						|
        :param encoding: Encoding to decode byte string
 | 
						|
        :type encoding: String
 | 
						|
        :return: Result object
 | 
						|
        :rtype: overpy.Result
 | 
						|
        """
 | 
						|
        if parser is None:
 | 
						|
            parser = self.xml_parser
 | 
						|
        if isinstance(data, bytes):
 | 
						|
            data = data.decode(encoding)
 | 
						|
        if PY2 and not isinstance(data, str):
 | 
						|
            # Python 2.x: Convert unicode strings
 | 
						|
            data = data.encode(encoding)
 | 
						|
 | 
						|
        m = re.compile("<remark>(?P<msg>[^<>]*)</remark>").search(data)
 | 
						|
        if m:
 | 
						|
            self._handle_remark_msg(m.group("msg"))
 | 
						|
 | 
						|
        return Result.from_xml(data, api=self, parser=parser)
 | 
						|
 | 
						|
 | 
						|
class Result(object):
 | 
						|
    """
 | 
						|
    Class to handle the result.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, elements=None, api=None):
 | 
						|
        """
 | 
						|
 | 
						|
        :param List elements:
 | 
						|
        :param api:
 | 
						|
        :type api: overpy.Overpass
 | 
						|
        """
 | 
						|
        if elements is None:
 | 
						|
            elements = []
 | 
						|
        self._areas = OrderedDict((element.id, element) for element in elements if is_valid_type(element, Area))
 | 
						|
        self._nodes = OrderedDict((element.id, element) for element in elements if is_valid_type(element, Node))
 | 
						|
        self._ways = OrderedDict((element.id, element) for element in elements if is_valid_type(element, Way))
 | 
						|
        self._relations = OrderedDict((element.id, element)
 | 
						|
                                      for element in elements if is_valid_type(element, Relation))
 | 
						|
        self._class_collection_map = {Node: self._nodes, Way: self._ways, Relation: self._relations, Area: self._areas}
 | 
						|
        self.api = api
 | 
						|
 | 
						|
    def expand(self, other):
 | 
						|
        """
 | 
						|
        Add all elements from an other result to the list of elements of this result object.
 | 
						|
 | 
						|
        It is used by the auto resolve feature.
 | 
						|
 | 
						|
        :param other: Expand the result with the elements from this result.
 | 
						|
        :type other: overpy.Result
 | 
						|
        :raises ValueError: If provided parameter is not instance of :class:`overpy.Result`
 | 
						|
        """
 | 
						|
        if not isinstance(other, Result):
 | 
						|
            raise ValueError("Provided argument has to be instance of overpy:Result()")
 | 
						|
 | 
						|
        other_collection_map = {Node: other.nodes, Way: other.ways, Relation: other.relations, Area: other.areas}
 | 
						|
        for element_type, own_collection in self._class_collection_map.items():
 | 
						|
            for element in other_collection_map[element_type]:
 | 
						|
                if is_valid_type(element, element_type) and element.id not in own_collection:
 | 
						|
                    own_collection[element.id] = element
 | 
						|
 | 
						|
    def append(self, element):
 | 
						|
        """
 | 
						|
        Append a new element to the result.
 | 
						|
 | 
						|
        :param element: The element to append
 | 
						|
        :type element: overpy.Element
 | 
						|
        """
 | 
						|
        if is_valid_type(element, Element):
 | 
						|
            self._class_collection_map[element.__class__].setdefault(element.id, element)
 | 
						|
 | 
						|
    def get_elements(self, filter_cls, elem_id=None):
 | 
						|
        """
 | 
						|
        Get a list of elements from the result and filter the element type by a class.
 | 
						|
 | 
						|
        :param filter_cls:
 | 
						|
        :param elem_id: ID of the object
 | 
						|
        :type elem_id: Integer
 | 
						|
        :return: List of available elements
 | 
						|
        :rtype: List
 | 
						|
        """
 | 
						|
        result = []
 | 
						|
        if elem_id is not None:
 | 
						|
            try:
 | 
						|
                result = [self._class_collection_map[filter_cls][elem_id]]
 | 
						|
            except KeyError:
 | 
						|
                result = []
 | 
						|
        else:
 | 
						|
            for e in self._class_collection_map[filter_cls].values():
 | 
						|
                result.append(e)
 | 
						|
        return result
 | 
						|
 | 
						|
    def get_ids(self, filter_cls):
 | 
						|
        """
 | 
						|
 | 
						|
        :param filter_cls:
 | 
						|
        :return:
 | 
						|
        """
 | 
						|
        return list(self._class_collection_map[filter_cls].keys())
 | 
						|
 | 
						|
    def get_node_ids(self):
 | 
						|
        return self.get_ids(filter_cls=Node)
 | 
						|
 | 
						|
    def get_way_ids(self):
 | 
						|
        return self.get_ids(filter_cls=Way)
 | 
						|
 | 
						|
    def get_relation_ids(self):
 | 
						|
        return self.get_ids(filter_cls=Relation)
 | 
						|
 | 
						|
    def get_area_ids(self):
 | 
						|
        return self.get_ids(filter_cls=Area)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def from_json(cls, data, api=None):
 | 
						|
        """
 | 
						|
        Create a new instance and load data from json object.
 | 
						|
 | 
						|
        :param data: JSON data returned by the Overpass API
 | 
						|
        :type data: Dict
 | 
						|
        :param api:
 | 
						|
        :type api: overpy.Overpass
 | 
						|
        :return: New instance of Result object
 | 
						|
        :rtype: overpy.Result
 | 
						|
        """
 | 
						|
        result = cls(api=api)
 | 
						|
        for elem_cls in [Node, Way, Relation, Area]:
 | 
						|
            for element in data.get("elements", []):
 | 
						|
                e_type = element.get("type")
 | 
						|
                if hasattr(e_type, "lower") and e_type.lower() == elem_cls._type_value:
 | 
						|
                    result.append(elem_cls.from_json(element, result=result))
 | 
						|
 | 
						|
        return result
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def from_xml(cls, data, api=None, parser=None):
 | 
						|
        """
 | 
						|
        Create a new instance and load data from xml data or object.
 | 
						|
 | 
						|
        .. note::
 | 
						|
            If parser is set to None, the functions tries to find the best parse.
 | 
						|
            By default the SAX parser is chosen if a string is provided as data.
 | 
						|
            The parser is set to DOM if an xml.etree.ElementTree.Element is provided as data value.
 | 
						|
 | 
						|
        :param data: Root element
 | 
						|
        :type data: str | xml.etree.ElementTree.Element
 | 
						|
        :param api: The instance to query additional information if required.
 | 
						|
        :type api: Overpass
 | 
						|
        :param parser: Specify the parser to use(DOM or SAX)(Default: None = autodetect, defaults to SAX)
 | 
						|
        :type parser: Integer | None
 | 
						|
        :return: New instance of Result object
 | 
						|
        :rtype: Result
 | 
						|
        """
 | 
						|
        if parser is None:
 | 
						|
            if isinstance(data, str):
 | 
						|
                parser = XML_PARSER_SAX
 | 
						|
            else:
 | 
						|
                parser = XML_PARSER_DOM
 | 
						|
 | 
						|
        result = cls(api=api)
 | 
						|
        if parser == XML_PARSER_DOM:
 | 
						|
            import xml.etree.ElementTree as ET
 | 
						|
            if isinstance(data, str):
 | 
						|
                root = ET.fromstring(data)
 | 
						|
            elif isinstance(data, ET.Element):
 | 
						|
                root = data
 | 
						|
            else:
 | 
						|
                raise exception.OverPyException("Unable to detect data type.")
 | 
						|
 | 
						|
            for elem_cls in [Node, Way, Relation, Area]:
 | 
						|
                for child in root:
 | 
						|
                    if child.tag.lower() == elem_cls._type_value:
 | 
						|
                        result.append(elem_cls.from_xml(child, result=result))
 | 
						|
 | 
						|
        elif parser == XML_PARSER_SAX:
 | 
						|
            if PY2:
 | 
						|
                from StringIO import StringIO
 | 
						|
            else:
 | 
						|
                from io import StringIO
 | 
						|
            source = StringIO(data)
 | 
						|
            sax_handler = OSMSAXHandler(result)
 | 
						|
            parser = make_parser()
 | 
						|
            parser.setContentHandler(sax_handler)
 | 
						|
            parser.parse(source)
 | 
						|
        else:
 | 
						|
            # ToDo: better exception
 | 
						|
            raise Exception("Unknown XML parser")
 | 
						|
        return result
 | 
						|
 | 
						|
    def get_area(self, area_id, resolve_missing=False):
 | 
						|
        """
 | 
						|
        Get an area by its ID.
 | 
						|
 | 
						|
        :param area_id: The area ID
 | 
						|
        :type area_id: Integer
 | 
						|
        :param resolve_missing: Query the Overpass API if the area is missing in the result set.
 | 
						|
        :return: The area
 | 
						|
        :rtype: overpy.Area
 | 
						|
        :raises overpy.exception.DataIncomplete: The requested way is not available in the result cache.
 | 
						|
        :raises overpy.exception.DataIncomplete: If resolve_missing is True and the area can't be resolved.
 | 
						|
        """
 | 
						|
        areas = self.get_areas(area_id=area_id)
 | 
						|
        if len(areas) == 0:
 | 
						|
            if resolve_missing is False:
 | 
						|
                raise exception.DataIncomplete("Resolve missing area is disabled")
 | 
						|
 | 
						|
            query = ("\n"
 | 
						|
                     "[out:json];\n"
 | 
						|
                     "area({area_id});\n"
 | 
						|
                     "out body;\n"
 | 
						|
                     )
 | 
						|
            query = query.format(
 | 
						|
                area_id=area_id
 | 
						|
            )
 | 
						|
            tmp_result = self.api.query(query)
 | 
						|
            self.expand(tmp_result)
 | 
						|
 | 
						|
            areas = self.get_areas(area_id=area_id)
 | 
						|
 | 
						|
        if len(areas) == 0:
 | 
						|
            raise exception.DataIncomplete("Unable to resolve requested areas")
 | 
						|
 | 
						|
        return areas[0]
 | 
						|
 | 
						|
    def get_areas(self, area_id=None, **kwargs):
 | 
						|
        """
 | 
						|
        Alias for get_elements() but filter the result by Area
 | 
						|
 | 
						|
        :param area_id: The Id of the area
 | 
						|
        :type area_id: Integer
 | 
						|
        :return: List of elements
 | 
						|
        """
 | 
						|
        return self.get_elements(Area, elem_id=area_id, **kwargs)
 | 
						|
 | 
						|
    def get_node(self, node_id, resolve_missing=False):
 | 
						|
        """
 | 
						|
        Get a node by its ID.
 | 
						|
 | 
						|
        :param node_id: The node ID
 | 
						|
        :type node_id: Integer
 | 
						|
        :param resolve_missing: Query the Overpass API if the node is missing in the result set.
 | 
						|
        :return: The node
 | 
						|
        :rtype: overpy.Node
 | 
						|
        :raises overpy.exception.DataIncomplete: At least one referenced node is not available in the result cache.
 | 
						|
        :raises overpy.exception.DataIncomplete: If resolve_missing is True and at least one node can't be resolved.
 | 
						|
        """
 | 
						|
        nodes = self.get_nodes(node_id=node_id)
 | 
						|
        if len(nodes) == 0:
 | 
						|
            if not resolve_missing:
 | 
						|
                raise exception.DataIncomplete("Resolve missing nodes is disabled")
 | 
						|
 | 
						|
            query = ("\n"
 | 
						|
                     "[out:json];\n"
 | 
						|
                     "node({node_id});\n"
 | 
						|
                     "out body;\n"
 | 
						|
                     )
 | 
						|
            query = query.format(
 | 
						|
                node_id=node_id
 | 
						|
            )
 | 
						|
            tmp_result = self.api.query(query)
 | 
						|
            self.expand(tmp_result)
 | 
						|
 | 
						|
            nodes = self.get_nodes(node_id=node_id)
 | 
						|
 | 
						|
        if len(nodes) == 0:
 | 
						|
            raise exception.DataIncomplete("Unable to resolve all nodes")
 | 
						|
 | 
						|
        return nodes[0]
 | 
						|
 | 
						|
    def get_nodes(self, node_id=None, **kwargs):
 | 
						|
        """
 | 
						|
        Alias for get_elements() but filter the result by Node()
 | 
						|
 | 
						|
        :param node_id: The Id of the node
 | 
						|
        :type node_id: Integer
 | 
						|
        :return: List of elements
 | 
						|
        """
 | 
						|
        return self.get_elements(Node, elem_id=node_id, **kwargs)
 | 
						|
 | 
						|
    def get_relation(self, rel_id, resolve_missing=False):
 | 
						|
        """
 | 
						|
        Get a relation by its ID.
 | 
						|
 | 
						|
        :param rel_id: The relation ID
 | 
						|
        :type rel_id: Integer
 | 
						|
        :param resolve_missing: Query the Overpass API if the relation is missing in the result set.
 | 
						|
        :return: The relation
 | 
						|
        :rtype: overpy.Relation
 | 
						|
        :raises overpy.exception.DataIncomplete: The requested relation is not available in the result cache.
 | 
						|
        :raises overpy.exception.DataIncomplete: If resolve_missing is True and the relation can't be resolved.
 | 
						|
        """
 | 
						|
        relations = self.get_relations(rel_id=rel_id)
 | 
						|
        if len(relations) == 0:
 | 
						|
            if resolve_missing is False:
 | 
						|
                raise exception.DataIncomplete("Resolve missing relations is disabled")
 | 
						|
 | 
						|
            query = ("\n"
 | 
						|
                     "[out:json];\n"
 | 
						|
                     "relation({relation_id});\n"
 | 
						|
                     "out body;\n"
 | 
						|
                     )
 | 
						|
            query = query.format(
 | 
						|
                relation_id=rel_id
 | 
						|
            )
 | 
						|
            tmp_result = self.api.query(query)
 | 
						|
            self.expand(tmp_result)
 | 
						|
 | 
						|
            relations = self.get_relations(rel_id=rel_id)
 | 
						|
 | 
						|
        if len(relations) == 0:
 | 
						|
            raise exception.DataIncomplete("Unable to resolve requested reference")
 | 
						|
 | 
						|
        return relations[0]
 | 
						|
 | 
						|
    def get_relations(self, rel_id=None, **kwargs):
 | 
						|
        """
 | 
						|
        Alias for get_elements() but filter the result by Relation
 | 
						|
 | 
						|
        :param rel_id: Id of the relation
 | 
						|
        :type rel_id: Integer
 | 
						|
        :return: List of elements
 | 
						|
        """
 | 
						|
        return self.get_elements(Relation, elem_id=rel_id, **kwargs)
 | 
						|
 | 
						|
    def get_way(self, way_id, resolve_missing=False):
 | 
						|
        """
 | 
						|
        Get a way by its ID.
 | 
						|
 | 
						|
        :param way_id: The way ID
 | 
						|
        :type way_id: Integer
 | 
						|
        :param resolve_missing: Query the Overpass API if the way is missing in the result set.
 | 
						|
        :return: The way
 | 
						|
        :rtype: overpy.Way
 | 
						|
        :raises overpy.exception.DataIncomplete: The requested way is not available in the result cache.
 | 
						|
        :raises overpy.exception.DataIncomplete: If resolve_missing is True and the way can't be resolved.
 | 
						|
        """
 | 
						|
        ways = self.get_ways(way_id=way_id)
 | 
						|
        if len(ways) == 0:
 | 
						|
            if resolve_missing is False:
 | 
						|
                raise exception.DataIncomplete("Resolve missing way is disabled")
 | 
						|
 | 
						|
            query = ("\n"
 | 
						|
                     "[out:json];\n"
 | 
						|
                     "way({way_id});\n"
 | 
						|
                     "out body;\n"
 | 
						|
                     )
 | 
						|
            query = query.format(
 | 
						|
                way_id=way_id
 | 
						|
            )
 | 
						|
            tmp_result = self.api.query(query)
 | 
						|
            self.expand(tmp_result)
 | 
						|
 | 
						|
            ways = self.get_ways(way_id=way_id)
 | 
						|
 | 
						|
        if len(ways) == 0:
 | 
						|
            raise exception.DataIncomplete("Unable to resolve requested way")
 | 
						|
 | 
						|
        return ways[0]
 | 
						|
 | 
						|
    def get_ways(self, way_id=None, **kwargs):
 | 
						|
        """
 | 
						|
        Alias for get_elements() but filter the result by Way
 | 
						|
 | 
						|
        :param way_id: The Id of the way
 | 
						|
        :type way_id: Integer
 | 
						|
        :return: List of elements
 | 
						|
        """
 | 
						|
        return self.get_elements(Way, elem_id=way_id, **kwargs)
 | 
						|
 | 
						|
    area_ids = property(get_area_ids)
 | 
						|
    areas = property(get_areas)
 | 
						|
    node_ids = property(get_node_ids)
 | 
						|
    nodes = property(get_nodes)
 | 
						|
    relation_ids = property(get_relation_ids)
 | 
						|
    relations = property(get_relations)
 | 
						|
    way_ids = property(get_way_ids)
 | 
						|
    ways = property(get_ways)
 | 
						|
 | 
						|
 | 
						|
class Element(object):
 | 
						|
    """
 | 
						|
    Base element
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, attributes=None, result=None, tags=None):
 | 
						|
        """
 | 
						|
        :param attributes: Additional attributes
 | 
						|
        :type attributes: Dict
 | 
						|
        :param result: The result object this element belongs to
 | 
						|
        :param tags: List of tags
 | 
						|
        :type tags: Dict
 | 
						|
        """
 | 
						|
 | 
						|
        self._result = result
 | 
						|
        self.attributes = attributes
 | 
						|
        # ToDo: Add option to modify attribute modifiers
 | 
						|
        attribute_modifiers = dict(GLOBAL_ATTRIBUTE_MODIFIERS.items())
 | 
						|
        for n, m in attribute_modifiers.items():
 | 
						|
            if n in self.attributes:
 | 
						|
                self.attributes[n] = m(self.attributes[n])
 | 
						|
        self.id = None
 | 
						|
        self.tags = tags
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def get_center_from_json(cls, data):
 | 
						|
        """
 | 
						|
        Get center information from json data
 | 
						|
 | 
						|
        :param data: json data
 | 
						|
        :return: tuple with two elements: lat and lon
 | 
						|
        :rtype: tuple
 | 
						|
        """
 | 
						|
        center_lat = None
 | 
						|
        center_lon = None
 | 
						|
        center = data.get("center")
 | 
						|
        if isinstance(center, dict):
 | 
						|
            center_lat = center.get("lat")
 | 
						|
            center_lon = center.get("lon")
 | 
						|
            if center_lat is None or center_lon is None:
 | 
						|
                raise ValueError("Unable to get lat or lon of way center.")
 | 
						|
            center_lat = Decimal(center_lat)
 | 
						|
            center_lon = Decimal(center_lon)
 | 
						|
        return (center_lat, center_lon)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def get_center_from_xml_dom(cls, sub_child):
 | 
						|
        center_lat = sub_child.attrib.get("lat")
 | 
						|
        center_lon = sub_child.attrib.get("lon")
 | 
						|
        if center_lat is None or center_lon is None:
 | 
						|
            raise ValueError("Unable to get lat or lon of way center.")
 | 
						|
        center_lat = Decimal(center_lat)
 | 
						|
        center_lon = Decimal(center_lon)
 | 
						|
        return center_lat, center_lon
 | 
						|
 | 
						|
 | 
						|
class Area(Element):
 | 
						|
    """
 | 
						|
    Class to represent an element of type area
 | 
						|
    """
 | 
						|
 | 
						|
    _type_value = "area"
 | 
						|
 | 
						|
    def __init__(self, area_id=None, **kwargs):
 | 
						|
        """
 | 
						|
        :param area_id: Id of the area element
 | 
						|
        :type area_id: Integer
 | 
						|
        :param kwargs: Additional arguments are passed directly to the parent class
 | 
						|
 | 
						|
        """
 | 
						|
 | 
						|
        Element.__init__(self, **kwargs)
 | 
						|
        #: The id of the way
 | 
						|
        self.id = area_id
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<overpy.Area id={}>".format(self.id)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def from_json(cls, data, result=None):
 | 
						|
        """
 | 
						|
        Create new Area element from JSON data
 | 
						|
 | 
						|
        :param data: Element data from JSON
 | 
						|
        :type data: Dict
 | 
						|
        :param result: The result this element belongs to
 | 
						|
        :type result: overpy.Result
 | 
						|
        :return: New instance of Way
 | 
						|
        :rtype: overpy.Area
 | 
						|
        :raises overpy.exception.ElementDataWrongType: If type value of the passed JSON data does not match.
 | 
						|
        """
 | 
						|
        if data.get("type") != cls._type_value:
 | 
						|
            raise exception.ElementDataWrongType(
 | 
						|
                type_expected=cls._type_value,
 | 
						|
                type_provided=data.get("type")
 | 
						|
            )
 | 
						|
 | 
						|
        tags = data.get("tags", {})
 | 
						|
 | 
						|
        area_id = data.get("id")
 | 
						|
 | 
						|
        attributes = {}
 | 
						|
        ignore = ["id", "tags", "type"]
 | 
						|
        for n, v in data.items():
 | 
						|
            if n in ignore:
 | 
						|
                continue
 | 
						|
            attributes[n] = v
 | 
						|
 | 
						|
        return cls(area_id=area_id, attributes=attributes, tags=tags, result=result)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def from_xml(cls, child, result=None):
 | 
						|
        """
 | 
						|
        Create new way element from XML data
 | 
						|
 | 
						|
        :param child: XML node to be parsed
 | 
						|
        :type child: xml.etree.ElementTree.Element
 | 
						|
        :param result: The result this node belongs to
 | 
						|
        :type result: overpy.Result
 | 
						|
        :return: New Way oject
 | 
						|
        :rtype: overpy.Way
 | 
						|
        :raises overpy.exception.ElementDataWrongType: If name of the xml child node doesn't match
 | 
						|
        :raises ValueError: If the ref attribute of the xml node is not provided
 | 
						|
        :raises ValueError: If a tag doesn't have a name
 | 
						|
        """
 | 
						|
        if child.tag.lower() != cls._type_value:
 | 
						|
            raise exception.ElementDataWrongType(
 | 
						|
                type_expected=cls._type_value,
 | 
						|
                type_provided=child.tag.lower()
 | 
						|
            )
 | 
						|
 | 
						|
        tags = {}
 | 
						|
 | 
						|
        for sub_child in child:
 | 
						|
            if sub_child.tag.lower() == "tag":
 | 
						|
                name = sub_child.attrib.get("k")
 | 
						|
                if name is None:
 | 
						|
                    raise ValueError("Tag without name/key.")
 | 
						|
                value = sub_child.attrib.get("v")
 | 
						|
                tags[name] = value
 | 
						|
 | 
						|
        area_id = child.attrib.get("id")
 | 
						|
        if area_id is not None:
 | 
						|
            area_id = int(area_id)
 | 
						|
 | 
						|
        attributes = {}
 | 
						|
        ignore = ["id"]
 | 
						|
        for n, v in child.attrib.items():
 | 
						|
            if n in ignore:
 | 
						|
                continue
 | 
						|
            attributes[n] = v
 | 
						|
 | 
						|
        return cls(area_id=area_id, attributes=attributes, tags=tags, result=result)
 | 
						|
 | 
						|
 | 
						|
class Node(Element):
 | 
						|
    """
 | 
						|
    Class to represent an element of type node
 | 
						|
    """
 | 
						|
 | 
						|
    _type_value = "node"
 | 
						|
 | 
						|
    def __init__(self, node_id=None, lat=None, lon=None, **kwargs):
 | 
						|
        """
 | 
						|
        :param lat: Latitude
 | 
						|
        :type lat: Decimal or Float
 | 
						|
        :param lon: Longitude
 | 
						|
        :type long: Decimal or Float
 | 
						|
        :param node_id: Id of the node element
 | 
						|
        :type node_id: Integer
 | 
						|
        :param kwargs: Additional arguments are passed directly to the parent class
 | 
						|
        """
 | 
						|
 | 
						|
        Element.__init__(self, **kwargs)
 | 
						|
        self.id = node_id
 | 
						|
        self.lat = lat
 | 
						|
        self.lon = lon
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<overpy.Node id={} lat={} lon={}>".format(self.id, self.lat, self.lon)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def from_json(cls, data, result=None):
 | 
						|
        """
 | 
						|
        Create new Node element from JSON data
 | 
						|
 | 
						|
        :param data: Element data from JSON
 | 
						|
        :type data: Dict
 | 
						|
        :param result: The result this element belongs to
 | 
						|
        :type result: overpy.Result
 | 
						|
        :return: New instance of Node
 | 
						|
        :rtype: overpy.Node
 | 
						|
        :raises overpy.exception.ElementDataWrongType: If type value of the passed JSON data does not match.
 | 
						|
        """
 | 
						|
        if data.get("type") != cls._type_value:
 | 
						|
            raise exception.ElementDataWrongType(
 | 
						|
                type_expected=cls._type_value,
 | 
						|
                type_provided=data.get("type")
 | 
						|
            )
 | 
						|
 | 
						|
        tags = data.get("tags", {})
 | 
						|
 | 
						|
        node_id = data.get("id")
 | 
						|
        lat = data.get("lat")
 | 
						|
        lon = data.get("lon")
 | 
						|
 | 
						|
        attributes = {}
 | 
						|
        ignore = ["type", "id", "lat", "lon", "tags"]
 | 
						|
        for n, v in data.items():
 | 
						|
            if n in ignore:
 | 
						|
                continue
 | 
						|
            attributes[n] = v
 | 
						|
 | 
						|
        return cls(node_id=node_id, lat=lat, lon=lon, tags=tags, attributes=attributes, result=result)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def from_xml(cls, child, result=None):
 | 
						|
        """
 | 
						|
        Create new way element from XML data
 | 
						|
 | 
						|
        :param child: XML node to be parsed
 | 
						|
        :type child: xml.etree.ElementTree.Element
 | 
						|
        :param result: The result this node belongs to
 | 
						|
        :type result: overpy.Result
 | 
						|
        :return: New Way oject
 | 
						|
        :rtype: overpy.Node
 | 
						|
        :raises overpy.exception.ElementDataWrongType: If name of the xml child node doesn't match
 | 
						|
        :raises ValueError: If a tag doesn't have a name
 | 
						|
        """
 | 
						|
        if child.tag.lower() != cls._type_value:
 | 
						|
            raise exception.ElementDataWrongType(
 | 
						|
                type_expected=cls._type_value,
 | 
						|
                type_provided=child.tag.lower()
 | 
						|
            )
 | 
						|
 | 
						|
        tags = {}
 | 
						|
 | 
						|
        for sub_child in child:
 | 
						|
            if sub_child.tag.lower() == "tag":
 | 
						|
                name = sub_child.attrib.get("k")
 | 
						|
                if name is None:
 | 
						|
                    raise ValueError("Tag without name/key.")
 | 
						|
                value = sub_child.attrib.get("v")
 | 
						|
                tags[name] = value
 | 
						|
 | 
						|
        node_id = child.attrib.get("id")
 | 
						|
        if node_id is not None:
 | 
						|
            node_id = int(node_id)
 | 
						|
        lat = child.attrib.get("lat")
 | 
						|
        if lat is not None:
 | 
						|
            lat = Decimal(lat)
 | 
						|
        lon = child.attrib.get("lon")
 | 
						|
        if lon is not None:
 | 
						|
            lon = Decimal(lon)
 | 
						|
 | 
						|
        attributes = {}
 | 
						|
        ignore = ["id", "lat", "lon"]
 | 
						|
        for n, v in child.attrib.items():
 | 
						|
            if n in ignore:
 | 
						|
                continue
 | 
						|
            attributes[n] = v
 | 
						|
 | 
						|
        return cls(node_id=node_id, lat=lat, lon=lon, tags=tags, attributes=attributes, result=result)
 | 
						|
 | 
						|
 | 
						|
class Way(Element):
 | 
						|
    """
 | 
						|
    Class to represent an element of type way
 | 
						|
    """
 | 
						|
 | 
						|
    _type_value = "way"
 | 
						|
 | 
						|
    def __init__(self, way_id=None, center_lat=None, center_lon=None, node_ids=None, **kwargs):
 | 
						|
        """
 | 
						|
        :param node_ids: List of node IDs
 | 
						|
        :type node_ids: List or Tuple
 | 
						|
        :param way_id: Id of the way element
 | 
						|
        :type way_id: Integer
 | 
						|
        :param kwargs: Additional arguments are passed directly to the parent class
 | 
						|
 | 
						|
        """
 | 
						|
 | 
						|
        Element.__init__(self, **kwargs)
 | 
						|
        #: The id of the way
 | 
						|
        self.id = way_id
 | 
						|
 | 
						|
        #: List of Ids of the associated nodes
 | 
						|
        self._node_ids = node_ids
 | 
						|
 | 
						|
        #: The lat/lon of the center of the way (optional depending on query)
 | 
						|
        self.center_lat = center_lat
 | 
						|
        self.center_lon = center_lon
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<overpy.Way id={} nodes={}>".format(self.id, self._node_ids)
 | 
						|
 | 
						|
    @property
 | 
						|
    def nodes(self):
 | 
						|
        """
 | 
						|
        List of nodes associated with the way.
 | 
						|
        """
 | 
						|
        return self.get_nodes()
 | 
						|
 | 
						|
    def get_nodes(self, resolve_missing=False):
 | 
						|
        """
 | 
						|
        Get the nodes defining the geometry of the way
 | 
						|
 | 
						|
        :param resolve_missing: Try to resolve missing nodes.
 | 
						|
        :type resolve_missing: Boolean
 | 
						|
        :return: List of nodes
 | 
						|
        :rtype: List of overpy.Node
 | 
						|
        :raises overpy.exception.DataIncomplete: At least one referenced node is not available in the result cache.
 | 
						|
        :raises overpy.exception.DataIncomplete: If resolve_missing is True and at least one node can't be resolved.
 | 
						|
        """
 | 
						|
        result = []
 | 
						|
        resolved = False
 | 
						|
 | 
						|
        for node_id in self._node_ids:
 | 
						|
            try:
 | 
						|
                node = self._result.get_node(node_id)
 | 
						|
            except exception.DataIncomplete:
 | 
						|
                node = None
 | 
						|
 | 
						|
            if node is not None:
 | 
						|
                result.append(node)
 | 
						|
                continue
 | 
						|
 | 
						|
            if not resolve_missing:
 | 
						|
                raise exception.DataIncomplete("Resolve missing nodes is disabled")
 | 
						|
 | 
						|
            # We tried to resolve the data but some nodes are still missing
 | 
						|
            if resolved:
 | 
						|
                raise exception.DataIncomplete("Unable to resolve all nodes")
 | 
						|
 | 
						|
            query = ("\n"
 | 
						|
                     "[out:json];\n"
 | 
						|
                     "way({way_id});\n"
 | 
						|
                     "node(w);\n"
 | 
						|
                     "out body;\n"
 | 
						|
                     )
 | 
						|
            query = query.format(
 | 
						|
                way_id=self.id
 | 
						|
            )
 | 
						|
            tmp_result = self._result.api.query(query)
 | 
						|
            self._result.expand(tmp_result)
 | 
						|
            resolved = True
 | 
						|
 | 
						|
            try:
 | 
						|
                node = self._result.get_node(node_id)
 | 
						|
            except exception.DataIncomplete:
 | 
						|
                node = None
 | 
						|
 | 
						|
            if node is None:
 | 
						|
                raise exception.DataIncomplete("Unable to resolve all nodes")
 | 
						|
 | 
						|
            result.append(node)
 | 
						|
 | 
						|
        return result
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def from_json(cls, data, result=None):
 | 
						|
        """
 | 
						|
        Create new Way element from JSON data
 | 
						|
 | 
						|
        :param data: Element data from JSON
 | 
						|
        :type data: Dict
 | 
						|
        :param result: The result this element belongs to
 | 
						|
        :type result: overpy.Result
 | 
						|
        :return: New instance of Way
 | 
						|
        :rtype: overpy.Way
 | 
						|
        :raises overpy.exception.ElementDataWrongType: If type value of the passed JSON data does not match.
 | 
						|
        """
 | 
						|
        if data.get("type") != cls._type_value:
 | 
						|
            raise exception.ElementDataWrongType(
 | 
						|
                type_expected=cls._type_value,
 | 
						|
                type_provided=data.get("type")
 | 
						|
            )
 | 
						|
 | 
						|
        tags = data.get("tags", {})
 | 
						|
 | 
						|
        way_id = data.get("id")
 | 
						|
        node_ids = data.get("nodes")
 | 
						|
        (center_lat, center_lon) = cls.get_center_from_json(data=data)
 | 
						|
 | 
						|
        attributes = {}
 | 
						|
        ignore = ["center", "id", "nodes", "tags", "type"]
 | 
						|
        for n, v in data.items():
 | 
						|
            if n in ignore:
 | 
						|
                continue
 | 
						|
            attributes[n] = v
 | 
						|
 | 
						|
        return cls(
 | 
						|
            attributes=attributes,
 | 
						|
            center_lat=center_lat,
 | 
						|
            center_lon=center_lon,
 | 
						|
            node_ids=node_ids,
 | 
						|
            tags=tags,
 | 
						|
            result=result,
 | 
						|
            way_id=way_id
 | 
						|
        )
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def from_xml(cls, child, result=None):
 | 
						|
        """
 | 
						|
        Create new way element from XML data
 | 
						|
 | 
						|
        :param child: XML node to be parsed
 | 
						|
        :type child: xml.etree.ElementTree.Element
 | 
						|
        :param result: The result this node belongs to
 | 
						|
        :type result: overpy.Result
 | 
						|
        :return: New Way oject
 | 
						|
        :rtype: overpy.Way
 | 
						|
        :raises overpy.exception.ElementDataWrongType: If name of the xml child node doesn't match
 | 
						|
        :raises ValueError: If the ref attribute of the xml node is not provided
 | 
						|
        :raises ValueError: If a tag doesn't have a name
 | 
						|
        """
 | 
						|
        if child.tag.lower() != cls._type_value:
 | 
						|
            raise exception.ElementDataWrongType(
 | 
						|
                type_expected=cls._type_value,
 | 
						|
                type_provided=child.tag.lower()
 | 
						|
            )
 | 
						|
 | 
						|
        tags = {}
 | 
						|
        node_ids = []
 | 
						|
        center_lat = None
 | 
						|
        center_lon = None
 | 
						|
 | 
						|
        for sub_child in child:
 | 
						|
            if sub_child.tag.lower() == "tag":
 | 
						|
                name = sub_child.attrib.get("k")
 | 
						|
                if name is None:
 | 
						|
                    raise ValueError("Tag without name/key.")
 | 
						|
                value = sub_child.attrib.get("v")
 | 
						|
                tags[name] = value
 | 
						|
            if sub_child.tag.lower() == "nd":
 | 
						|
                ref_id = sub_child.attrib.get("ref")
 | 
						|
                if ref_id is None:
 | 
						|
                    raise ValueError("Unable to find required ref value.")
 | 
						|
                ref_id = int(ref_id)
 | 
						|
                node_ids.append(ref_id)
 | 
						|
            if sub_child.tag.lower() == "center":
 | 
						|
                (center_lat, center_lon) = cls.get_center_from_xml_dom(sub_child=sub_child)
 | 
						|
 | 
						|
        way_id = child.attrib.get("id")
 | 
						|
        if way_id is not None:
 | 
						|
            way_id = int(way_id)
 | 
						|
 | 
						|
        attributes = {}
 | 
						|
        ignore = ["id"]
 | 
						|
        for n, v in child.attrib.items():
 | 
						|
            if n in ignore:
 | 
						|
                continue
 | 
						|
            attributes[n] = v
 | 
						|
 | 
						|
        return cls(way_id=way_id, center_lat=center_lat, center_lon=center_lon,
 | 
						|
                   attributes=attributes, node_ids=node_ids, tags=tags, result=result)
 | 
						|
 | 
						|
 | 
						|
class Relation(Element):
 | 
						|
    """
 | 
						|
    Class to represent an element of type relation
 | 
						|
    """
 | 
						|
 | 
						|
    _type_value = "relation"
 | 
						|
 | 
						|
    def __init__(self, rel_id=None, center_lat=None, center_lon=None, members=None, **kwargs):
 | 
						|
        """
 | 
						|
        :param members:
 | 
						|
        :param rel_id: Id of the relation element
 | 
						|
        :type rel_id: Integer
 | 
						|
        :param kwargs:
 | 
						|
        :return:
 | 
						|
        """
 | 
						|
 | 
						|
        Element.__init__(self, **kwargs)
 | 
						|
        self.id = rel_id
 | 
						|
        self.members = members
 | 
						|
 | 
						|
        #: The lat/lon of the center of the way (optional depending on query)
 | 
						|
        self.center_lat = center_lat
 | 
						|
        self.center_lon = center_lon
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<overpy.Relation id={}>".format(self.id)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def from_json(cls, data, result=None):
 | 
						|
        """
 | 
						|
        Create new Relation element from JSON data
 | 
						|
 | 
						|
        :param data: Element data from JSON
 | 
						|
        :type data: Dict
 | 
						|
        :param result: The result this element belongs to
 | 
						|
        :type result: overpy.Result
 | 
						|
        :return: New instance of Relation
 | 
						|
        :rtype: overpy.Relation
 | 
						|
        :raises overpy.exception.ElementDataWrongType: If type value of the passed JSON data does not match.
 | 
						|
        """
 | 
						|
        if data.get("type") != cls._type_value:
 | 
						|
            raise exception.ElementDataWrongType(
 | 
						|
                type_expected=cls._type_value,
 | 
						|
                type_provided=data.get("type")
 | 
						|
            )
 | 
						|
 | 
						|
        tags = data.get("tags", {})
 | 
						|
 | 
						|
        rel_id = data.get("id")
 | 
						|
        (center_lat, center_lon) = cls.get_center_from_json(data=data)
 | 
						|
 | 
						|
        members = []
 | 
						|
 | 
						|
        supported_members = [RelationNode, RelationWay, RelationRelation]
 | 
						|
        for member in data.get("members", []):
 | 
						|
            type_value = member.get("type")
 | 
						|
            for member_cls in supported_members:
 | 
						|
                if member_cls._type_value == type_value:
 | 
						|
                    members.append(
 | 
						|
                        member_cls.from_json(
 | 
						|
                            member,
 | 
						|
                            result=result
 | 
						|
                        )
 | 
						|
                    )
 | 
						|
 | 
						|
        attributes = {}
 | 
						|
        ignore = ["id", "members", "tags", "type"]
 | 
						|
        for n, v in data.items():
 | 
						|
            if n in ignore:
 | 
						|
                continue
 | 
						|
            attributes[n] = v
 | 
						|
 | 
						|
        return cls(
 | 
						|
            rel_id=rel_id,
 | 
						|
            attributes=attributes,
 | 
						|
            center_lat=center_lat,
 | 
						|
            center_lon=center_lon,
 | 
						|
            members=members,
 | 
						|
            tags=tags,
 | 
						|
            result=result
 | 
						|
        )
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def from_xml(cls, child, result=None):
 | 
						|
        """
 | 
						|
        Create new way element from XML data
 | 
						|
 | 
						|
        :param child: XML node to be parsed
 | 
						|
        :type child: xml.etree.ElementTree.Element
 | 
						|
        :param result: The result this node belongs to
 | 
						|
        :type result: overpy.Result
 | 
						|
        :return: New Way oject
 | 
						|
        :rtype: overpy.Relation
 | 
						|
        :raises overpy.exception.ElementDataWrongType: If name of the xml child node doesn't match
 | 
						|
        :raises ValueError: If a tag doesn't have a name
 | 
						|
        """
 | 
						|
        if child.tag.lower() != cls._type_value:
 | 
						|
            raise exception.ElementDataWrongType(
 | 
						|
                type_expected=cls._type_value,
 | 
						|
                type_provided=child.tag.lower()
 | 
						|
            )
 | 
						|
 | 
						|
        tags = {}
 | 
						|
        members = []
 | 
						|
        center_lat = None
 | 
						|
        center_lon = None
 | 
						|
 | 
						|
        supported_members = [RelationNode, RelationWay, RelationRelation, RelationArea]
 | 
						|
        for sub_child in child:
 | 
						|
            if sub_child.tag.lower() == "tag":
 | 
						|
                name = sub_child.attrib.get("k")
 | 
						|
                if name is None:
 | 
						|
                    raise ValueError("Tag without name/key.")
 | 
						|
                value = sub_child.attrib.get("v")
 | 
						|
                tags[name] = value
 | 
						|
            if sub_child.tag.lower() == "member":
 | 
						|
                type_value = sub_child.attrib.get("type")
 | 
						|
                for member_cls in supported_members:
 | 
						|
                    if member_cls._type_value == type_value:
 | 
						|
                        members.append(
 | 
						|
                            member_cls.from_xml(
 | 
						|
                                sub_child,
 | 
						|
                                result=result
 | 
						|
                            )
 | 
						|
                        )
 | 
						|
            if sub_child.tag.lower() == "center":
 | 
						|
                (center_lat, center_lon) = cls.get_center_from_xml_dom(sub_child=sub_child)
 | 
						|
 | 
						|
        rel_id = child.attrib.get("id")
 | 
						|
        if rel_id is not None:
 | 
						|
            rel_id = int(rel_id)
 | 
						|
 | 
						|
        attributes = {}
 | 
						|
        ignore = ["id"]
 | 
						|
        for n, v in child.attrib.items():
 | 
						|
            if n in ignore:
 | 
						|
                continue
 | 
						|
            attributes[n] = v
 | 
						|
 | 
						|
        return cls(
 | 
						|
            rel_id=rel_id,
 | 
						|
            attributes=attributes,
 | 
						|
            center_lat=center_lat,
 | 
						|
            center_lon=center_lon,
 | 
						|
            members=members,
 | 
						|
            tags=tags,
 | 
						|
            result=result
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class RelationMember(object):
 | 
						|
    """
 | 
						|
    Base class to represent a member of a relation.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, attributes=None, geometry=None, ref=None, role=None, result=None):
 | 
						|
        """
 | 
						|
        :param ref: Reference Id
 | 
						|
        :type ref: Integer
 | 
						|
        :param role: The role of the relation member
 | 
						|
        :type role: String
 | 
						|
        :param result:
 | 
						|
        """
 | 
						|
        self.ref = ref
 | 
						|
        self._result = result
 | 
						|
        self.role = role
 | 
						|
        self.attributes = attributes
 | 
						|
        self.geometry = geometry
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def from_json(cls, data, result=None):
 | 
						|
        """
 | 
						|
        Create new RelationMember element from JSON data
 | 
						|
 | 
						|
        :param child: Element data from JSON
 | 
						|
        :type child: Dict
 | 
						|
        :param result: The result this element belongs to
 | 
						|
        :type result: overpy.Result
 | 
						|
        :return: New instance of RelationMember
 | 
						|
        :rtype: overpy.RelationMember
 | 
						|
        :raises overpy.exception.ElementDataWrongType: If type value of the passed JSON data does not match.
 | 
						|
        """
 | 
						|
        if data.get("type") != cls._type_value:
 | 
						|
            raise exception.ElementDataWrongType(
 | 
						|
                type_expected=cls._type_value,
 | 
						|
                type_provided=data.get("type")
 | 
						|
            )
 | 
						|
 | 
						|
        ref = data.get("ref")
 | 
						|
        role = data.get("role")
 | 
						|
 | 
						|
        attributes = {}
 | 
						|
        ignore = ["geometry", "type", "ref", "role"]
 | 
						|
        for n, v in data.items():
 | 
						|
            if n in ignore:
 | 
						|
                continue
 | 
						|
            attributes[n] = v
 | 
						|
 | 
						|
        geometry = data.get("geometry")
 | 
						|
        if isinstance(geometry, list):
 | 
						|
            geometry_orig = geometry
 | 
						|
            geometry = []
 | 
						|
            for v in geometry_orig:
 | 
						|
                geometry.append(
 | 
						|
                    RelationWayGeometryValue(
 | 
						|
                        lat=v.get("lat"),
 | 
						|
                        lon=v.get("lon")
 | 
						|
                    )
 | 
						|
                )
 | 
						|
        else:
 | 
						|
            geometry = None
 | 
						|
 | 
						|
        return cls(
 | 
						|
            attributes=attributes,
 | 
						|
            geometry=geometry,
 | 
						|
            ref=ref,
 | 
						|
            role=role,
 | 
						|
            result=result
 | 
						|
        )
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def from_xml(cls, child, result=None):
 | 
						|
        """
 | 
						|
        Create new RelationMember from XML data
 | 
						|
 | 
						|
        :param child: XML node to be parsed
 | 
						|
        :type child: xml.etree.ElementTree.Element
 | 
						|
        :param result: The result this element belongs to
 | 
						|
        :type result: overpy.Result
 | 
						|
        :return: New relation member oject
 | 
						|
        :rtype: overpy.RelationMember
 | 
						|
        :raises overpy.exception.ElementDataWrongType: If name of the xml child node doesn't match
 | 
						|
        """
 | 
						|
        if child.attrib.get("type") != cls._type_value:
 | 
						|
            raise exception.ElementDataWrongType(
 | 
						|
                type_expected=cls._type_value,
 | 
						|
                type_provided=child.tag.lower()
 | 
						|
            )
 | 
						|
 | 
						|
        ref = child.attrib.get("ref")
 | 
						|
        if ref is not None:
 | 
						|
            ref = int(ref)
 | 
						|
        role = child.attrib.get("role")
 | 
						|
 | 
						|
        attributes = {}
 | 
						|
        ignore = ["geometry", "ref", "role", "type"]
 | 
						|
        for n, v in child.attrib.items():
 | 
						|
            if n in ignore:
 | 
						|
                continue
 | 
						|
            attributes[n] = v
 | 
						|
 | 
						|
        geometry = None
 | 
						|
        for sub_child in child:
 | 
						|
            if sub_child.tag.lower() == "nd":
 | 
						|
                if geometry is None:
 | 
						|
                    geometry = []
 | 
						|
                geometry.append(
 | 
						|
                    RelationWayGeometryValue(
 | 
						|
                        lat=Decimal(sub_child.attrib["lat"]),
 | 
						|
                        lon=Decimal(sub_child.attrib["lon"])
 | 
						|
                    )
 | 
						|
                )
 | 
						|
 | 
						|
        return cls(
 | 
						|
            attributes=attributes,
 | 
						|
            geometry=geometry,
 | 
						|
            ref=ref,
 | 
						|
            role=role,
 | 
						|
            result=result
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class RelationNode(RelationMember):
 | 
						|
    _type_value = "node"
 | 
						|
 | 
						|
    def resolve(self, resolve_missing=False):
 | 
						|
        return self._result.get_node(self.ref, resolve_missing=resolve_missing)
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<overpy.RelationNode ref={} role={}>".format(self.ref, self.role)
 | 
						|
 | 
						|
 | 
						|
class RelationWay(RelationMember):
 | 
						|
    _type_value = "way"
 | 
						|
 | 
						|
    def resolve(self, resolve_missing=False):
 | 
						|
        return self._result.get_way(self.ref, resolve_missing=resolve_missing)
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<overpy.RelationWay ref={} role={}>".format(self.ref, self.role)
 | 
						|
 | 
						|
 | 
						|
class RelationWayGeometryValue(object):
 | 
						|
    def __init__(self, lat, lon):
 | 
						|
        self.lat = lat
 | 
						|
        self.lon = lon
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<overpy.RelationWayGeometryValue lat={} lon={}>".format(self.lat, self.lon)
 | 
						|
 | 
						|
 | 
						|
class RelationRelation(RelationMember):
 | 
						|
    _type_value = "relation"
 | 
						|
 | 
						|
    def resolve(self, resolve_missing=False):
 | 
						|
        return self._result.get_relation(self.ref, resolve_missing=resolve_missing)
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<overpy.RelationRelation ref={} role={}>".format(self.ref, self.role)
 | 
						|
 | 
						|
 | 
						|
class RelationArea(RelationMember):
 | 
						|
    _type_value = "area"
 | 
						|
 | 
						|
    def resolve(self, resolve_missing=False):
 | 
						|
        return self._result.get_area(self.ref, resolve_missing=resolve_missing)
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<overpy.RelationArea ref={} role={}>".format(self.ref, self.role)
 | 
						|
 | 
						|
 | 
						|
class OSMSAXHandler(handler.ContentHandler):
 | 
						|
    """
 | 
						|
    SAX parser for Overpass XML response.
 | 
						|
    """
 | 
						|
    #: Tuple of opening elements to ignore
 | 
						|
    ignore_start = ('osm', 'meta', 'note', 'bounds', 'remark')
 | 
						|
    #: Tuple of closing elements to ignore
 | 
						|
    ignore_end = ('osm', 'meta', 'note', 'bounds', 'remark', 'tag', 'nd', 'center')
 | 
						|
 | 
						|
    def __init__(self, result):
 | 
						|
        """
 | 
						|
        :param result: Append results to this result set.
 | 
						|
        :type result: overpy.Result
 | 
						|
        """
 | 
						|
        handler.ContentHandler.__init__(self)
 | 
						|
        self._result = result
 | 
						|
        self._curr = {}
 | 
						|
        #: Current relation member object
 | 
						|
        self.cur_relation_member = None
 | 
						|
 | 
						|
    def startElement(self, name, attrs):
 | 
						|
        """
 | 
						|
        Handle opening elements.
 | 
						|
 | 
						|
        :param name: Name of the element
 | 
						|
        :type name: String
 | 
						|
        :param attrs: Attributes of the element
 | 
						|
        :type attrs: Dict
 | 
						|
        """
 | 
						|
        if name in self.ignore_start:
 | 
						|
            return
 | 
						|
        try:
 | 
						|
            handler = getattr(self, '_handle_start_%s' % name)
 | 
						|
        except AttributeError:
 | 
						|
            raise KeyError("Unknown element start '%s'" % name)
 | 
						|
        handler(attrs)
 | 
						|
 | 
						|
    def endElement(self, name):
 | 
						|
        """
 | 
						|
        Handle closing elements
 | 
						|
 | 
						|
        :param name: Name of the element
 | 
						|
        :type name: String
 | 
						|
        """
 | 
						|
        if name in self.ignore_end:
 | 
						|
            return
 | 
						|
        try:
 | 
						|
            handler = getattr(self, '_handle_end_%s' % name)
 | 
						|
        except AttributeError:
 | 
						|
            raise KeyError("Unknown element end '%s'" % name)
 | 
						|
        handler()
 | 
						|
 | 
						|
    def _handle_start_center(self, attrs):
 | 
						|
        """
 | 
						|
        Handle opening center element
 | 
						|
 | 
						|
        :param attrs: Attributes of the element
 | 
						|
        :type attrs: Dict
 | 
						|
        """
 | 
						|
        center_lat = attrs.get("lat")
 | 
						|
        center_lon = attrs.get("lon")
 | 
						|
        if center_lat is None or center_lon is None:
 | 
						|
            raise ValueError("Unable to get lat or lon of way center.")
 | 
						|
        self._curr["center_lat"] = Decimal(center_lat)
 | 
						|
        self._curr["center_lon"] = Decimal(center_lon)
 | 
						|
 | 
						|
    def _handle_start_tag(self, attrs):
 | 
						|
        """
 | 
						|
        Handle opening tag element
 | 
						|
 | 
						|
        :param attrs: Attributes of the element
 | 
						|
        :type attrs: Dict
 | 
						|
        """
 | 
						|
        try:
 | 
						|
            tag_key = attrs['k']
 | 
						|
        except KeyError:
 | 
						|
            raise ValueError("Tag without name/key.")
 | 
						|
        self._curr['tags'][tag_key] = attrs.get('v')
 | 
						|
 | 
						|
    def _handle_start_node(self, attrs):
 | 
						|
        """
 | 
						|
        Handle opening node element
 | 
						|
 | 
						|
        :param attrs: Attributes of the element
 | 
						|
        :type attrs: Dict
 | 
						|
        """
 | 
						|
        self._curr = {
 | 
						|
            'attributes': dict(attrs),
 | 
						|
            'lat': None,
 | 
						|
            'lon': None,
 | 
						|
            'node_id': None,
 | 
						|
            'tags': {}
 | 
						|
        }
 | 
						|
        if attrs.get('id', None) is not None:
 | 
						|
            self._curr['node_id'] = int(attrs['id'])
 | 
						|
            del self._curr['attributes']['id']
 | 
						|
        if attrs.get('lat', None) is not None:
 | 
						|
            self._curr['lat'] = Decimal(attrs['lat'])
 | 
						|
            del self._curr['attributes']['lat']
 | 
						|
        if attrs.get('lon', None) is not None:
 | 
						|
            self._curr['lon'] = Decimal(attrs['lon'])
 | 
						|
            del self._curr['attributes']['lon']
 | 
						|
 | 
						|
    def _handle_end_node(self):
 | 
						|
        """
 | 
						|
        Handle closing node element
 | 
						|
        """
 | 
						|
        self._result.append(Node(result=self._result, **self._curr))
 | 
						|
        self._curr = {}
 | 
						|
 | 
						|
    def _handle_start_way(self, attrs):
 | 
						|
        """
 | 
						|
        Handle opening way element
 | 
						|
 | 
						|
        :param attrs: Attributes of the element
 | 
						|
        :type attrs: Dict
 | 
						|
        """
 | 
						|
        self._curr = {
 | 
						|
            'center_lat': None,
 | 
						|
            'center_lon': None,
 | 
						|
            'attributes': dict(attrs),
 | 
						|
            'node_ids': [],
 | 
						|
            'tags': {},
 | 
						|
            'way_id': None
 | 
						|
        }
 | 
						|
        if attrs.get('id', None) is not None:
 | 
						|
            self._curr['way_id'] = int(attrs['id'])
 | 
						|
            del self._curr['attributes']['id']
 | 
						|
 | 
						|
    def _handle_end_way(self):
 | 
						|
        """
 | 
						|
        Handle closing way element
 | 
						|
        """
 | 
						|
        self._result.append(Way(result=self._result, **self._curr))
 | 
						|
        self._curr = {}
 | 
						|
 | 
						|
    def _handle_start_area(self, attrs):
 | 
						|
        """
 | 
						|
        Handle opening area element
 | 
						|
 | 
						|
        :param attrs: Attributes of the element
 | 
						|
        :type attrs: Dict
 | 
						|
        """
 | 
						|
        self._curr = {
 | 
						|
            'attributes': dict(attrs),
 | 
						|
            'tags': {},
 | 
						|
            'area_id': None
 | 
						|
        }
 | 
						|
        if attrs.get('id', None) is not None:
 | 
						|
            self._curr['area_id'] = int(attrs['id'])
 | 
						|
            del self._curr['attributes']['id']
 | 
						|
 | 
						|
    def _handle_end_area(self):
 | 
						|
        """
 | 
						|
        Handle closing area element
 | 
						|
        """
 | 
						|
        self._result.append(Area(result=self._result, **self._curr))
 | 
						|
        self._curr = {}
 | 
						|
 | 
						|
    def _handle_start_nd(self, attrs):
 | 
						|
        """
 | 
						|
        Handle opening nd element
 | 
						|
 | 
						|
        :param attrs: Attributes of the element
 | 
						|
        :type attrs: Dict
 | 
						|
        """
 | 
						|
        if isinstance(self.cur_relation_member, RelationWay):
 | 
						|
            if self.cur_relation_member.geometry is None:
 | 
						|
                self.cur_relation_member.geometry = []
 | 
						|
            self.cur_relation_member.geometry.append(
 | 
						|
                RelationWayGeometryValue(
 | 
						|
                    lat=Decimal(attrs["lat"]),
 | 
						|
                    lon=Decimal(attrs["lon"])
 | 
						|
                )
 | 
						|
            )
 | 
						|
        else:
 | 
						|
            try:
 | 
						|
                node_ref = attrs['ref']
 | 
						|
            except KeyError:
 | 
						|
                raise ValueError("Unable to find required ref value.")
 | 
						|
            self._curr['node_ids'].append(int(node_ref))
 | 
						|
 | 
						|
    def _handle_start_relation(self, attrs):
 | 
						|
        """
 | 
						|
        Handle opening relation element
 | 
						|
 | 
						|
        :param attrs: Attributes of the element
 | 
						|
        :type attrs: Dict
 | 
						|
        """
 | 
						|
        self._curr = {
 | 
						|
            'attributes': dict(attrs),
 | 
						|
            'members': [],
 | 
						|
            'rel_id': None,
 | 
						|
            'tags': {}
 | 
						|
        }
 | 
						|
        if attrs.get('id', None) is not None:
 | 
						|
            self._curr['rel_id'] = int(attrs['id'])
 | 
						|
            del self._curr['attributes']['id']
 | 
						|
 | 
						|
    def _handle_end_relation(self):
 | 
						|
        """
 | 
						|
        Handle closing relation element
 | 
						|
        """
 | 
						|
        self._result.append(Relation(result=self._result, **self._curr))
 | 
						|
        self._curr = {}
 | 
						|
 | 
						|
    def _handle_start_member(self, attrs):
 | 
						|
        """
 | 
						|
        Handle opening member element
 | 
						|
 | 
						|
        :param attrs: Attributes of the element
 | 
						|
        :type attrs: Dict
 | 
						|
        """
 | 
						|
 | 
						|
        params = {
 | 
						|
            # ToDo: Parse attributes
 | 
						|
            'attributes': {},
 | 
						|
            'ref': None,
 | 
						|
            'result': self._result,
 | 
						|
            'role': None
 | 
						|
        }
 | 
						|
        if attrs.get('ref', None):
 | 
						|
            params['ref'] = int(attrs['ref'])
 | 
						|
        if attrs.get('role', None):
 | 
						|
            params['role'] = attrs['role']
 | 
						|
 | 
						|
        cls_map = {
 | 
						|
            "area": RelationArea,
 | 
						|
            "node": RelationNode,
 | 
						|
            "relation": RelationRelation,
 | 
						|
            "way": RelationWay
 | 
						|
        }
 | 
						|
        cls = cls_map.get(attrs["type"])
 | 
						|
        if cls is None:
 | 
						|
            raise ValueError("Undefined type for member: '%s'" % attrs['type'])
 | 
						|
 | 
						|
        self.cur_relation_member = cls(**params)
 | 
						|
        self._curr['members'].append(self.cur_relation_member)
 | 
						|
 | 
						|
    def _handle_end_member(self):
 | 
						|
        self.cur_relation_member = None
 | 
						|
 |