1 # SPDX-License-Identifier: GPL-3.0-or-later
3 # This file is part of Nominatim. (https://nominatim.org)
5 # Copyright (C) 2025 by the Nominatim developer community.
6 # For a full list of authors see the git log.
8 Wrapper for results from the API
11 import xml.etree.ElementTree as ET
16 def __init__(self, fmt, endpoint, body):
17 getattr(self, '_parse_' + fmt)(endpoint, body)
20 return not isinstance(self.result, list)
23 return 1 if self.is_simple() else len(self.result)
26 return json.dumps({'meta': self.meta, 'result': self.result}, indent=2)
28 def _parse_json(self, _, body):
30 self.result = json.loads(body)
32 def _parse_xml(self, endpoint, body):
33 xml_tree = ET.fromstring(body)
35 self.meta = dict(xml_tree.attrib)
37 if xml_tree.tag == 'reversegeocode':
38 self._parse_xml_simple(xml_tree)
39 elif xml_tree.tag == 'searchresults':
40 self._parse_xml_multi(xml_tree)
41 elif xml_tree.tag == 'error':
42 self.result = {'error': {sub.tag: sub.text for sub in xml_tree}}
44 def _parse_xml_simple(self, xml):
48 if child.tag == 'result':
49 assert not self.result, "More than one result in reverse result"
50 self.result.update(child.attrib)
51 assert 'display_name' not in self.result
52 self.result['display_name'] = child.text
53 elif child.tag == 'addressparts':
54 assert 'address' not in self.result
55 self.result['address'] = {sub.tag: sub.text for sub in child}
56 elif child.tag == 'extratags':
57 assert 'extratags' not in self.result
58 self.result['extratags'] = {tag.attrib['key']: tag.attrib['value'] for tag in child}
59 elif child.tag == 'namedetails':
60 assert 'namedetails' not in self.result
61 self.result['namedetails'] = {tag.attrib['desc']: tag.text for tag in child}
62 elif child.tag == 'geokml':
63 assert 'geokml' not in self.result
64 self.result['geokml'] = ET.tostring(child, encoding='unicode')
65 elif child.tag == 'error':
66 assert not self.result
67 self.result['error'] = child.text
69 assert False, f"Unknown XML tag {child.tag} on page: {self.page}"
71 def _parse_xml_multi(self, xml):
75 assert child.tag == "place"
76 res = dict(child.attrib)
80 if sub.tag == 'extratags':
81 assert 'extratags' not in res
82 res['extratags'] = {tag.attrib['key']: tag.attrib['value'] for tag in sub}
83 elif sub.tag == 'namedetails':
84 assert 'namedetails' not in res
85 res['namedetails'] = {tag.attrib['desc']: tag.text for tag in sub}
86 elif sub.tag == 'geokml':
87 res['geokml'] = ET.tostring(sub, encoding='utf-8')
89 address[sub.tag] = sub.text
92 res['address'] = address
94 self.result.append(res)
96 def _parse_geojson(self, _, body):
97 geojson = json.loads(body)
99 assert geojson.get('type') == 'FeatureCollection'
100 assert isinstance(geojson.get('features'), list)
102 self.meta = {k: v for k, v in geojson.items() if k not in ('type', 'features')}
105 for obj in geojson['features']:
106 assert isinstance(obj, dict)
107 assert obj.get('type') == 'Feature'
109 assert isinstance(obj.get('properties'), dict)
110 result = obj['properties']
111 assert 'geojson' not in result
112 result['geojson'] = obj['geometry']
114 assert 'boundingbox' not in result
115 # bbox is minlon, minlat, maxlon, maxlat
116 # boundingbox is minlat, maxlat, minlon, maxlon
117 result['boundingbox'] = [obj['bbox'][1], obj['bbox'][3],
118 obj['bbox'][0], obj['bbox'][2]]
119 self.result.append(result)
121 def _parse_geocodejson(self, endpoint, body):
122 self._parse_geojson(endpoint, body)
124 assert set(self.meta.keys()) == {'geocoding'}
125 assert isinstance(self.meta['geocoding'], dict)
126 self.meta = self.meta['geocoding']
128 for r in self.result:
129 assert set(r.keys()) == {'geocoding', 'geojson'}
130 inner = r.pop('geocoding')
131 assert isinstance(inner, dict)
132 assert 'geojson' not in inner