From ab36331dce5cf35dabcf6fbfa42d2a7657212ee6 Mon Sep 17 00:00:00 2001 From: Pavel Grochal Date: Wed, 10 Feb 2016 14:05:12 +0100 Subject: [PATCH 01/14] Added Area functionality - no tests --- overpy/__init__.py | 196 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 191 insertions(+), 5 deletions(-) diff --git a/overpy/__init__.py b/overpy/__init__.py index 1d23cf6..5b0b829 100644 --- a/overpy/__init__.py +++ b/overpy/__init__.py @@ -175,11 +175,12 @@ def __init__(self, elements=None, api=None): """ 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} + self._class_collection_map = {Node: self._nodes, Way: self._ways, Relation: self._relations, Area: self._areas} self.api = api def expand(self, other): @@ -195,7 +196,7 @@ def expand(self, other): 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} + 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: @@ -249,6 +250,9 @@ def get_way_ids(self): 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): """ @@ -262,7 +266,7 @@ def from_json(cls, data, api=None): :rtype: overpy.Result """ result = cls(api=api) - for elem_cls in [Node, Way, Relation]: + 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: @@ -289,7 +293,7 @@ def from_xml(cls, data, api=None, parser=XML_PARSER_SAX): import xml.etree.ElementTree as ET root = ET.fromstring(data) - for elem_cls in [Node, Way, Relation]: + 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)) @@ -444,12 +448,59 @@ def get_ways(self, way_id=None, **kwargs): """ return self.get_elements(Way, elem_id=way_id, **kwargs) + def get_area(self, area_id, resolve_missing=False): + """ + Get an area by its ID. + + :param area_id: The way ID + :type area_id: Integer + :param resolve_missing: Query the Overpass API if the way 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_ways(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 way_id: The Id of the area + :type way_id: Integer + :return: List of elements + """ + return self.get_elements(Area, elem_id=area_id, **kwargs) + 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) + area_ids = property(get_area_ids) + areas = property(get_areas) class Element(object): @@ -473,6 +524,106 @@ def __init__(self, attributes=None, result=None, tags=None): self.tags = tags +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 "".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): """ @@ -853,7 +1004,7 @@ def from_xml(cls, child, result=None): tags = {} members = [] - supported_members = [RelationNode, RelationWay, RelationRelation] + supported_members = [RelationNode, RelationWay, RelationRelation, RelationArea] for sub_child in child: if sub_child.tag.lower() == "tag": name = sub_child.attrib.get("k") @@ -983,6 +1134,16 @@ def __repr__(self): return "".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 "".format(self.ref, self.role) + + class OSMSAXHandler(handler.ContentHandler): """ SAX parser for Overpass XML response. @@ -1101,6 +1262,29 @@ def _handle_end_way(self): 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 @@ -1161,5 +1345,7 @@ def _handle_start_member(self, attrs): self._curr['members'].append(RelationWay(**params)) elif attrs['type'] == 'relation': self._curr['members'].append(RelationRelation(**params)) + elif attrs['type'] == 'area': + self._curr['members'].append(RelationArea(**params)) else: raise ValueError("Undefined type for member: '%s'" % attrs['type']) From 50dc75e8670819c06d2e166f710ddcd516bc7e5c Mon Sep 17 00:00:00 2001 From: PhiBo Date: Tue, 22 Nov 2016 21:56:02 +0100 Subject: [PATCH 02/14] src - Restructure code --- overpy/__init__.py | 94 +++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/overpy/__init__.py b/overpy/__init__.py index 3eb9c1b..50f20f8 100644 --- a/overpy/__init__.py +++ b/overpy/__init__.py @@ -320,6 +320,51 @@ def from_xml(cls, data, api=None, parser=XML_PARSER_SAX): 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 way ID + :type area_id: Integer + :param resolve_missing: Query the Overpass API if the way 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_ways(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 way_id: The Id of the area + :type way_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. @@ -455,59 +500,14 @@ def get_ways(self, way_id=None, **kwargs): """ return self.get_elements(Way, elem_id=way_id, **kwargs) - def get_area(self, area_id, resolve_missing=False): - """ - Get an area by its ID. - - :param area_id: The way ID - :type area_id: Integer - :param resolve_missing: Query the Overpass API if the way 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_ways(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 way_id: The Id of the area - :type way_id: Integer - :return: List of elements - """ - return self.get_elements(Area, elem_id=area_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) - area_ids = property(get_area_ids) - areas = property(get_areas) class Element(object): From 4723cb3469324666b7a8859aa0a85eab14c51f4c Mon Sep 17 00:00:00 2001 From: PhiBo Date: Tue, 22 Nov 2016 21:59:47 +0100 Subject: [PATCH 03/14] src - Fix docstring --- overpy/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/overpy/__init__.py b/overpy/__init__.py index 50f20f8..aeb7b4c 100644 --- a/overpy/__init__.py +++ b/overpy/__init__.py @@ -324,9 +324,9 @@ def get_area(self, area_id, resolve_missing=False): """ Get an area by its ID. - :param area_id: The way ID + :param area_id: The area ID :type area_id: Integer - :param resolve_missing: Query the Overpass API if the way is missing in the result set. + :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. @@ -359,8 +359,8 @@ def get_areas(self, area_id=None, **kwargs): """ Alias for get_elements() but filter the result by Area - :param way_id: The Id of the area - :type way_id: Integer + :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) From 86ab3ffaedbc38c3f88b5e37a5823cee1aa4bde3 Mon Sep 17 00:00:00 2001 From: PhiBo Date: Tue, 22 Nov 2016 22:03:17 +0100 Subject: [PATCH 04/14] src - Remove whitespace --- overpy/__init__.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/overpy/__init__.py b/overpy/__init__.py index aeb7b4c..fe7455d 100644 --- a/overpy/__init__.py +++ b/overpy/__init__.py @@ -12,7 +12,6 @@ __uri__, __version__ ) - PY2 = sys.version_info[0] == 2 PY3 = sys.version_info[0] == 3 @@ -40,7 +39,6 @@ def is_valid_type(element, cls): class Overpass(object): - """ Class to access the Overpass API """ @@ -168,7 +166,6 @@ def parse_xml(self, data, encoding="utf-8", parser=None): class Result(object): - """ Class to handle the result. """ @@ -338,10 +335,10 @@ def get_area(self, area_id, resolve_missing=False): raise exception.DataIncomplete("Resolve missing area is disabled") query = ("\n" - "[out:json];\n" - "area({area_id});\n" - "out body;\n" - ) + "[out:json];\n" + "area({area_id});\n" + "out body;\n" + ) query = query.format( area_id=area_id ) @@ -511,7 +508,6 @@ def get_ways(self, way_id=None, **kwargs): class Element(object): - """ Base element """ @@ -544,7 +540,6 @@ def __init__(self, attributes=None, result=None, tags=None): class Area(Element): - """ Class to represent an element of type area """ @@ -644,7 +639,6 @@ def from_xml(cls, child, result=None): class Node(Element): - """ Class to represent an element of type node """ @@ -755,7 +749,6 @@ def from_xml(cls, child, result=None): class Way(Element): - """ Class to represent an element of type way """ @@ -778,7 +771,7 @@ def __init__(self, way_id=None, center_lat=None, center_lon=None, node_ids=None, #: 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 @@ -943,7 +936,6 @@ def from_xml(cls, child, result=None): class Relation(Element): - """ Class to represent an element of type relation """ @@ -1069,7 +1061,6 @@ def from_xml(cls, child, result=None): class RelationMember(object): - """ Base class to represent a member of a relation. """ @@ -1236,7 +1227,7 @@ def _handle_start_center(self, attrs): self._curr['center_lat'] = Decimal(attrs['lat']) if attrs.get('lon', None) is not None: self._curr['center_lon'] = Decimal(attrs['lon']) - + def _handle_start_tag(self, attrs): """ Handle opening tag element From 01c176331cd0ae8014fa1efb359fed92f9ad11d7 Mon Sep 17 00:00:00 2001 From: PhiBo Date: Tue, 22 Nov 2016 22:05:19 +0100 Subject: [PATCH 05/14] example - Get areas by name and show tag names and values --- examples/get_areas.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 examples/get_areas.py diff --git a/examples/get_areas.py b/examples/get_areas.py new file mode 100644 index 0000000..44e7710 --- /dev/null +++ b/examples/get_areas.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +import overpy + +api = overpy.Overpass() + +# fetch all areas +# More info on http://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_API_by_Example +result = api.query(""" +area[name="Troisdorf"]; +out; +""") + +for area in result.areas: + print( + "Name: %s (%i)" % ( + area.tags.get("name", "n/a"), + area.id + ) + ) + for n, v in area.tags.items(): + print(" Tag: %s = %s" % (n, v)) From 8c389cae40414bc7c3e793d2cd5b865fdee8e7f3 Mon Sep 17 00:00:00 2001 From: PhiBo Date: Tue, 22 Nov 2016 22:32:03 +0100 Subject: [PATCH 06/14] test - Add base class for area tests --- tests/base_class.py | 67 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/base_class.py b/tests/base_class.py index ae5f4e2..bedf7dd 100644 --- a/tests/base_class.py +++ b/tests/base_class.py @@ -9,6 +9,73 @@ from tests import read_file +class BaseTestAreas(object): + def _test_area01(self, result): + assert len(result.areas) == 4 + assert len(result.nodes) == 0 + assert len(result.relations) == 0 + assert len(result.ways) == 0 + + area = result.areas[0] + + assert isinstance(area, overpy.Area) + assert isinstance(area.id, int) + assert area.id == 2448756446 + + assert isinstance(area.tags, dict) + assert len(area.tags) == 12 + + area = result.areas[1] + + assert isinstance(area, overpy.Area) + assert isinstance(area.id, int) + assert area.id == 3600055060 + + assert isinstance(area.tags, dict) + assert len(area.tags) == 13 + + area = result.areas[2] + assert isinstance(area, overpy.Area) + assert isinstance(area.id, int) + assert area.id == 3605945175 + + assert isinstance(area.tags, dict) + assert len(area.tags) == 12 + + area = result.areas[3] + assert isinstance(area, overpy.Area) + assert isinstance(area.id, int) + assert area.id == 3605945176 + + assert isinstance(area.tags, dict) + assert len(area.tags) == 14 + + # try to get a single area by id + area = result.get_area(3605945175) + assert area.id == 3605945175 + + # try to get a single area by id not available in the result + with pytest.raises(overpy.exception.DataIncomplete): + result.get_node(123456) + + # area_ids is an alias for get_node_ids() and should return the same data + for area_ids in (result.area_ids, result.get_area_ids()): + assert len(area_ids) == 4 + assert area_ids[0] == 2448756446 + assert area_ids[1] == 3600055060 + assert area_ids[2] == 3605945175 + assert area_ids[3] == 3605945176 + + assert len(result.node_ids) == 0 + assert len(result.get_node_ids()) == 0 + + assert len(result.relation_ids) == 0 + assert len(result.get_relation_ids()) == 0 + + assert len(result.way_ids) == 0 + assert len(result.get_way_ids()) == 0 + + class BaseTestNodes(object): def _test_node01(self, result): assert len(result.nodes) == 3 From a1158545386d1cb83ac8bdf9bb160606a2f5eebf Mon Sep 17 00:00:00 2001 From: PhiBo Date: Tue, 22 Nov 2016 22:32:46 +0100 Subject: [PATCH 07/14] test - Add first test for areas using json --- tests/json/area-01.json | 92 +++++++++++++++++++++++++++++++++++++++++ tests/test_json.py | 9 +++- 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 tests/json/area-01.json diff --git a/tests/json/area-01.json b/tests/json/area-01.json new file mode 100644 index 0000000..183f2d3 --- /dev/null +++ b/tests/json/area-01.json @@ -0,0 +1,92 @@ +{ + "version": 0.6, + "generator": "Overpass API", + "osm3s": { + "timestamp_osm_base": "2016-11-22T21:04:02Z", + "timestamp_areas_base": "2016-11-22T20:25:03Z", + "copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL." + }, + "elements": [ + +{ + "type": "area", + "id": 2448756446, + "tags": { + "addr:city": "Troisdorf", + "addr:postcode": "53840", + "area": "yes", + "description": "Troisdorf Bahnsteig Gleis 9", + "name": "Troisdorf", + "public_transport": "platform", + "railway": "platform", + "ref": "9", + "train": "yes", + "wheelchair": "no", + "wheelchair:description": "Plattformlift ist vorhanden, Betriebsbereitschaft nach 8 Jahren stillstand fraglich.", + "width": "5" + } +} +, +{ + "type": "area", + "id": 3600055060, + "tags": { + "TMC:cid_58:tabcd_1:Class": "Area", + "TMC:cid_58:tabcd_1:LCLversion": "8.00", + "TMC:cid_58:tabcd_1:LocationCode": "2550", + "admin_level": "8", + "boundary": "administrative", + "de:amtlicher_gemeindeschluessel": "05382068", + "de:place": "town", + "de:regionalschluessel": "053820068068", + "name": "Troisdorf", + "name:prefix": "Stadt", + "type": "boundary", + "wikidata": "Q3900", + "wikipedia": "de:Troisdorf" + } +} +, +{ + "type": "area", + "id": 3605945175, + "tags": { + "addr:city": "Troisdorf", + "addr:postcode": "53840", + "description": "Troisdorf Bahnsteig Gleis 1+2", + "local_ref": "1;2", + "name": "Troisdorf", + "public_transport": "platform", + "railway": "platform", + "ref": "1/2", + "tactile_paving": "yes", + "train": "yes", + "type": "multipolygon", + "wheelchair": "yes" + } +} +, +{ + "type": "area", + "id": 3605945176, + "tags": { + "addr:city": "Troisdorf", + "addr:postcode": "53840", + "description": "Troisdorf Bahnsteig Gleis 5+6", + "name": "Troisdorf", + "phone": "+49 221 1411055", + "public_transport": "platform", + "railway": "platform", + "ref": "5/6", + "tactile_paving": "yes", + "train": "yes", + "type": "multipolygon", + "wheelchair": "yes", + "wheelchair:description": "Der Aufzug zu diesem Bahnsteig ist oft defekt, bitte informieren. Betriebszustand kann bei 3S-Zentrale erfragt werden (siehe Telefonnummer, aber Vorsicht, die sind nicht immer informiert!)", + "width": "6" + } +} + + + ] +} diff --git a/tests/test_json.py b/tests/test_json.py index b988eb9..60aab04 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -2,10 +2,17 @@ import overpy -from tests.base_class import BaseTestNodes, BaseTestRelation, BaseTestWay +from tests.base_class import BaseTestAreas, BaseTestNodes, BaseTestRelation, BaseTestWay from tests.base_class import read_file +class TestAreas(BaseTestAreas): + def test_area01(self): + api = overpy.Overpass() + result = api.parse_json(read_file("json/area-01.json")) + self._test_area01(result) + + class TestNodes(BaseTestNodes): def test_node01(self): api = overpy.Overpass() From b5df609777caf3e9c888c57af72e3c587d11e65f Mon Sep 17 00:00:00 2001 From: PhiBo Date: Tue, 22 Nov 2016 22:33:01 +0100 Subject: [PATCH 08/14] test - Add first test for areas using xml --- tests/test_xml.py | 13 ++++++++- tests/xml/area-01.xml | 66 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 tests/xml/area-01.xml diff --git a/tests/test_xml.py b/tests/test_xml.py index 84623f5..81ad359 100644 --- a/tests/test_xml.py +++ b/tests/test_xml.py @@ -2,10 +2,21 @@ import overpy -from tests.base_class import BaseTestNodes, BaseTestRelation, BaseTestWay +from tests.base_class import BaseTestAreas, BaseTestNodes, BaseTestRelation, BaseTestWay from tests.base_class import read_file +class TestAreas(BaseTestAreas): + def test_node01(self): + api = overpy.Overpass() + # DOM + result = api.parse_xml(read_file("xml/area-01.xml"), parser=overpy.XML_PARSER_DOM) + self._test_area01(result) + # SAX + result = api.parse_xml(read_file("xml/area-01.xml"), parser=overpy.XML_PARSER_SAX) + self._test_area01(result) + + class TestNodes(BaseTestNodes): def test_node01(self): api = overpy.Overpass() diff --git a/tests/xml/area-01.xml b/tests/xml/area-01.xml new file mode 100644 index 0000000..e831268 --- /dev/null +++ b/tests/xml/area-01.xml @@ -0,0 +1,66 @@ + + +The data included in this document is from www.openstreetmap.org. The data is made available under ODbL. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 6bfed465ce61b953cfc60199c06d67675818df8f Mon Sep 17 00:00:00 2001 From: PhiBo Date: Tue, 22 Nov 2016 22:33:22 +0100 Subject: [PATCH 09/14] test - Add README with query information --- tests/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/README.md diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..f0a1f6e --- /dev/null +++ b/tests/README.md @@ -0,0 +1,14 @@ +Data +==== + +Queries to get test data. + +Have a look at https://overpass-turbo.eu/ to test the queries. + +area-01 (2016-11-22) +-------------------- + +``` +area[name="Troisdorf"]; +out; +``` \ No newline at end of file From f6a66ed015e0098bf498c9eed6d313226750a439 Mon Sep 17 00:00:00 2001 From: PhiBo Date: Tue, 22 Nov 2016 22:35:29 +0100 Subject: [PATCH 10/14] test - Fix small issue in area test --- tests/base_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/base_class.py b/tests/base_class.py index bedf7dd..a6bdfb0 100644 --- a/tests/base_class.py +++ b/tests/base_class.py @@ -56,7 +56,7 @@ def _test_area01(self, result): # try to get a single area by id not available in the result with pytest.raises(overpy.exception.DataIncomplete): - result.get_node(123456) + result.get_area(123456) # area_ids is an alias for get_node_ids() and should return the same data for area_ids in (result.area_ids, result.get_area_ids()): From c55e61553e818157f6a2f74a67f77dfc359fa59c Mon Sep 17 00:00:00 2001 From: PhiBo Date: Tue, 22 Nov 2016 22:48:02 +0100 Subject: [PATCH 11/14] src - Fix issue in get_area() function --- overpy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overpy/__init__.py b/overpy/__init__.py index fe7455d..52f61f2 100644 --- a/overpy/__init__.py +++ b/overpy/__init__.py @@ -345,7 +345,7 @@ def get_area(self, area_id, resolve_missing=False): tmp_result = self.api.query(query) self.expand(tmp_result) - areas = self.get_ways(area_id=area_id) + areas = self.get_areas(area_id=area_id) if len(areas) == 0: raise exception.DataIncomplete("Unable to resolve requested areas") From 38848f98ff1c774ec5e91a0ee7c9c756a2477c41 Mon Sep 17 00:00:00 2001 From: PhiBo Date: Tue, 22 Nov 2016 22:48:34 +0100 Subject: [PATCH 12/14] test - Add test to resolve area --- tests/json/result-expand-02.json | 4 ++++ tests/test_result.py | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/tests/json/result-expand-02.json b/tests/json/result-expand-02.json index 069d3b0..fa6fc11 100644 --- a/tests/json/result-expand-02.json +++ b/tests/json/result-expand-02.json @@ -6,6 +6,10 @@ "copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL." }, "elements": [ + { + "type": "area", + "id": 3605945176 + }, { "type": "node", "id": 3233854233, diff --git a/tests/test_result.py b/tests/test_result.py index 9831716..0e169af 100644 --- a/tests/test_result.py +++ b/tests/test_result.py @@ -45,6 +45,40 @@ def test_expand_01(self): assert len(result1.ways) == 2 +class TestArea(object): + def test_missing_unresolvable(self): + url, t = new_server_thread(HandleResponseJSON02) + t.start() + + api = overpy.Overpass() + api.url = url + result1 = api.parse_json(read_file("json/result-expand-01.json")) + + with pytest.raises(overpy.exception.DataIncomplete): + result1.get_area(123, resolve_missing=True) + t.join() + + def test_missing_resolvable(self): + url, t = new_server_thread(HandleResponseJSON02) + t.start() + + api = overpy.Overpass() + api.url = url + result1 = api.parse_json(read_file("json/result-expand-01.json")) + + # Node must not be available + with pytest.raises(overpy.exception.DataIncomplete): + result1.get_area(3605945176) + + # Node must be available + area = result1.get_area(3605945176, resolve_missing=True) + + assert isinstance(area, overpy.Area) + assert area.id == 3605945176 + + t.join() + + class TestNode(object): def test_missing_unresolvable(self): url, t = new_server_thread(HandleResponseJSON02) From 8030db619b1f3798080df55147a8a681f86c5705 Mon Sep 17 00:00:00 2001 From: PhiBo Date: Tue, 22 Nov 2016 22:58:50 +0100 Subject: [PATCH 13/14] src - Sort if statement --- overpy/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/overpy/__init__.py b/overpy/__init__.py index 52f61f2..793c9b1 100644 --- a/overpy/__init__.py +++ b/overpy/__init__.py @@ -1375,13 +1375,13 @@ def _handle_start_member(self, attrs): if attrs.get('role', None): params['role'] = attrs['role'] - if attrs['type'] == 'node': + if attrs['type'] == 'area': + self._curr['members'].append(RelationArea(**params)) + elif attrs['type'] == 'node': self._curr['members'].append(RelationNode(**params)) elif attrs['type'] == 'way': self._curr['members'].append(RelationWay(**params)) elif attrs['type'] == 'relation': self._curr['members'].append(RelationRelation(**params)) - elif attrs['type'] == 'area': - self._curr['members'].append(RelationArea(**params)) else: raise ValueError("Undefined type for member: '%s'" % attrs['type']) From 7efdae7fde8f593f0ed57418ed98947b0a7addfb Mon Sep 17 00:00:00 2001 From: PhiBo Date: Tue, 22 Nov 2016 23:01:32 +0100 Subject: [PATCH 14/14] doc - Add area classes to autoclass docu --- docs/source/api.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/source/api.rst b/docs/source/api.rst index c697d3b..a2ffb6d 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -23,6 +23,9 @@ Elements .. autoclass:: Element :members: +.. autoclass:: Area + :members: + .. autoclass:: Node :members: @@ -39,6 +42,9 @@ Relation Members .. autoclass:: RelationMember :members: +.. autoclass:: RelationArea + :members: + .. autoclass:: RelationNode :members: