]> git.openstreetmap.org Git - nominatim.git/blob - nominatim/api/v1/format_xml.py
add server glue for reverse API call
[nominatim.git] / nominatim / api / v1 / format_xml.py
1 # SPDX-License-Identifier: GPL-3.0-or-later
2 #
3 # This file is part of Nominatim. (https://nominatim.org)
4 #
5 # Copyright (C) 2023 by the Nominatim developer community.
6 # For a full list of authors see the git log.
7 """
8 Helper functions for output of results in XML format.
9 """
10 from typing import Mapping, Any, Optional
11 import datetime as dt
12 import xml.etree.ElementTree as ET
13
14 import nominatim.api as napi
15 from nominatim.api.v1.constants import OSM_ATTRIBUTION, OSM_TYPE_NAME, bbox_from_result
16 from nominatim.api.v1.classtypes import ICONS, get_label_tag
17
18 def _write_xml_address(root: ET.Element, address: napi.AddressLines,
19                        country_code: Optional[str]) -> None:
20     parts = {}
21     for line in address:
22         if line.isaddress and line.local_name:
23             label = get_label_tag(line.category, line.extratags,
24                                   line.rank_address, country_code)
25             if label not in parts:
26                 parts[label] = line.local_name
27
28     for k,v in parts.items():
29         ET.SubElement(root, k).text = v
30
31     if country_code:
32         ET.SubElement(root, 'country_code').text = country_code
33
34
35 def _create_base_entry(result: napi.ReverseResult, #pylint: disable=too-many-branches
36                        root: ET.Element, simple: bool,
37                        locales: napi.Locales) -> ET.Element:
38     if result.address_rows:
39         label_parts = result.address_rows.localize(locales)
40     else:
41         label_parts = []
42
43     place = ET.SubElement(root, 'result' if simple else 'place')
44     if result.place_id is not None:
45         place.set('place_id', str(result.place_id))
46     if result.osm_object:
47         osm_type = OSM_TYPE_NAME.get(result.osm_object[0], None)
48         if osm_type is not None:
49             place.set('osm_type', osm_type)
50         place.set('osm_id', str(result.osm_object[1]))
51     if result.names and 'ref' in result.names:
52         place.set('place_id', result.names['ref'])
53     place.set('lat', str(result.centroid.lat))
54     place.set('lon', str(result.centroid.lon))
55
56     bbox = bbox_from_result(result)
57     place.set('boundingbox', ','.join(map(str, [bbox.minlat, bbox.maxlat,
58                                                 bbox.minlon, bbox.maxlon])))
59
60     place.set('place_rank', str(result.rank_search))
61     place.set('address_rank', str(result.rank_address))
62
63     if result.geometry:
64         for key in ('text', 'svg'):
65             if key in result.geometry:
66                 place.set('geo' + key, result.geometry[key])
67         if 'kml' in result.geometry:
68             ET.SubElement(root if simple else place, 'geokml')\
69               .append(ET.fromstring(result.geometry['kml']))
70         if 'geojson' in result.geometry:
71             place.set('geojson', result.geometry['geojson'])
72
73     if simple:
74         place.text = ', '.join(label_parts)
75     else:
76         place.set('display_name', ', '.join(label_parts))
77         place.set('class', result.category[0])
78         place.set('type', result.category[1])
79         place.set('importance', str(result.calculated_importance()))
80
81     return place
82
83
84 def format_base_xml(results: napi.ReverseResults,
85                     options: Mapping[str, Any],
86                     simple: bool, xml_root_tag: str,
87                     xml_extra_info: Mapping[str, str]) -> str:
88     """ Format the result into an XML response. With 'simple' exactly one
89         result will be output, otherwise a list.
90     """
91     locales = options.get('locales', napi.Locales())
92
93     root = ET.Element(xml_root_tag)
94     root.set('timestamp', dt.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S +00:00'))
95     root.set('attribution', OSM_ATTRIBUTION)
96     for k, v in xml_extra_info.items():
97         root.set(k, v)
98
99     if simple and not results:
100         ET.SubElement(root, 'error').text = 'Unable to geocode'
101
102     for result in results:
103         place = _create_base_entry(result, root, simple, locales)
104
105         if not simple and options.get('icon_base_url', None):
106             icon = ICONS.get(result.category)
107             if icon:
108                 place.set('icon', icon)
109
110         if options.get('addressdetails', False) and result.address_rows:
111             _write_xml_address(ET.SubElement(root, 'addressparts') if simple else place,
112                                result.address_rows, result.country_code)
113
114         if options.get('extratags', False):
115             eroot = ET.SubElement(root if simple else place, 'extratags')
116             if result.extratags:
117                 for k, v in result.extratags.items():
118                     ET.SubElement(eroot, 'tag', attrib={'key': k, 'value': v})
119
120         if options.get('namedetails', False):
121             eroot = ET.SubElement(root if simple else place, 'namedetails')
122             if result.names:
123                 for k,v in result.names.items():
124                     ET.SubElement(eroot, 'name', attrib={'desc': k}).text = v
125
126     return '<?xml version="1.0" encoding="UTF-8" ?>\n' + ET.tostring(root, encoding='unicode')