]> git.openstreetmap.org Git - nominatim.git/blob - nominatim/api/v1/format.py
Merge remote-tracking branch 'upstream/master'
[nominatim.git] / nominatim / api / v1 / format.py
1 # SPDX-License-Identifier: GPL-2.0-only
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 Output formatters for API version v1.
9 """
10 from typing import Mapping, Any
11 import collections
12
13 import nominatim.api as napi
14 from nominatim.api.result_formatting import FormatDispatcher
15 from nominatim.api.v1.classtypes import ICONS
16 from nominatim.utils.json_writer import JsonWriter
17
18 dispatch = FormatDispatcher()
19
20 @dispatch.format_func(napi.StatusResult, 'text')
21 def _format_status_text(result: napi.StatusResult, _: Mapping[str, Any]) -> str:
22     if result.status:
23         return f"ERROR: {result.message}"
24
25     return 'OK'
26
27
28 @dispatch.format_func(napi.StatusResult, 'json')
29 def _format_status_json(result: napi.StatusResult, _: Mapping[str, Any]) -> str:
30     out = JsonWriter()
31
32     out.start_object()\
33          .keyval('status', result.status)\
34          .keyval('message', result.message)\
35          .keyval_not_none('data_updated', result.data_updated,
36                           lambda v: v.isoformat())\
37          .keyval('software_version', str(result.software_version))\
38          .keyval_not_none('database_version', result.database_version, str)\
39        .end_object()
40
41     return out()
42
43
44 def _add_address_row(writer: JsonWriter, row: napi.AddressLine,
45                      locales: napi.Locales) -> None:
46     writer.start_object()\
47             .keyval('localname', locales.display_name(row.names))\
48             .keyval_not_none('place_id', row.place_id)
49
50     if row.osm_object is not None:
51         writer.keyval('osm_id', row.osm_object[1])\
52               .keyval('osm_type', row.osm_object[0])
53
54     if row.extratags:
55         writer.keyval_not_none('place_type', row.extratags.get('place_type'))
56
57     writer.keyval('class', row.category[0])\
58           .keyval('type', row.category[1])\
59           .keyval_not_none('admin_level', row.admin_level)\
60           .keyval('rank_address', row.rank_address)\
61           .keyval('distance', row.distance)\
62           .keyval('isaddress', row.isaddress)\
63         .end_object()
64
65
66 def _add_address_rows(writer: JsonWriter, section: str, rows: napi.AddressLines,
67                       locales: napi.Locales) -> None:
68     writer.key(section).start_array()
69     for row in rows:
70         _add_address_row(writer, row, locales)
71         writer.next()
72     writer.end_array().next()
73
74
75 def _add_parent_rows_grouped(writer: JsonWriter, rows: napi.AddressLines,
76                              locales: napi.Locales) -> None:
77     # group by category type
78     data = collections.defaultdict(list)
79     for row in rows:
80         sub = JsonWriter()
81         _add_address_row(sub, row, locales)
82         data[row.category[1]].append(sub())
83
84     writer.key('hierarchy').start_object()
85     for group, grouped in data.items():
86         writer.key(group).start_array()
87         grouped.sort() # sorts alphabetically by local name
88         for line in grouped:
89             writer.raw(line).next()
90         writer.end_array().next()
91
92     writer.end_object().next()
93
94
95 @dispatch.format_func(napi.SearchResult, 'details-json')
96 def _format_search_json(result: napi.SearchResult, options: Mapping[str, Any]) -> str:
97     locales = options.get('locales', napi.Locales())
98     geom = result.geometry.get('geojson')
99     centroid = result.centroid.to_geojson()
100
101     out = JsonWriter()
102     out.start_object()\
103          .keyval_not_none('place_id', result.place_id)\
104          .keyval_not_none('parent_place_id', result.parent_place_id)
105
106     if result.osm_object is not None:
107         out.keyval('osm_type', result.osm_object[0])\
108            .keyval('osm_id', result.osm_object[1])
109
110     out.keyval('category', result.category[0])\
111          .keyval('type', result.category[1])\
112          .keyval('admin_level', result.admin_level)\
113          .keyval('localname', locales.display_name(result.names))\
114          .keyval_not_none('names', result.names or None)\
115          .keyval_not_none('addresstags', result.address or None)\
116          .keyval_not_none('housenumber', result.housenumber)\
117          .keyval_not_none('calculated_postcode', result.postcode)\
118          .keyval_not_none('country_code', result.country_code)\
119          .keyval_not_none('indexed_date', result.indexed_date, lambda v: v.isoformat())\
120          .keyval_not_none('importance', result.importance)\
121          .keyval('calculated_importance', result.calculated_importance())\
122          .keyval_not_none('extratags', result.extratags or None)\
123          .keyval_not_none('calculated_wikipedia', result.wikipedia)\
124          .keyval('rank_address', result.rank_address)\
125          .keyval('rank_search', result.rank_search)\
126          .keyval('isarea', 'Polygon' in (geom or result.geometry.get('type') or ''))\
127          .key('centroid').raw(centroid).next()\
128          .key('geometry').raw(geom or centroid).next()
129
130     if options.get('icon_base_url', None):
131         icon = ICONS.get(result.category)
132         if icon:
133             out.keyval('icon', f"{options['icon_base_url']}/{icon}.p.20.png")
134
135     if result.address_rows is not None:
136         _add_address_rows(out, 'address', result.address_rows, locales)
137
138     if result.linked_rows is not None:
139         _add_address_rows(out, 'linked_places', result.linked_rows, locales)
140
141     if result.name_keywords is not None or result.address_keywords is not None:
142         out.key('keywords').start_object()
143
144         for sec, klist in (('name', result.name_keywords), ('address', result.address_keywords)):
145             out.key(sec).start_array()
146             for word in (klist or []):
147                 out.start_object()\
148                      .keyval('id', word.word_id)\
149                      .keyval('token', word.word_token)\
150                    .end_object().next()
151             out.end_array().next()
152
153         out.end_object().next()
154
155     if result.parented_rows is not None:
156         if options.get('group_hierarchy', False):
157             _add_parent_rows_grouped(out, result.parented_rows, locales)
158         else:
159             _add_address_rows(out, 'hierarchy', result.parented_rows, locales)
160
161     out.end_object()
162
163     return out()