]> git.openstreetmap.org Git - nominatim.git/blobdiff - nominatim/api/v1/format_json.py
Merge pull request #3384 from mtmail/geocodejson-admin-levels-only-boundaries
[nominatim.git] / nominatim / api / v1 / format_json.py
index 898e621377abebd999b1070d3b457e39ecf294b3..1c17a032c586c6308023da8927dcbb0c8edef407 100644 (file)
@@ -7,16 +7,17 @@
 """
 Helper functions for output of results in json formats.
 """
-from typing import Mapping, Any, Optional, Tuple
+from typing import Mapping, Any, Optional, Tuple, Union
 
 import nominatim.api as napi
-from nominatim.api.v1.constants import OSM_ATTRIBUTION, OSM_TYPE_NAME, bbox_from_result
-from nominatim.api.v1.classtypes import ICONS, get_label_tag
+import nominatim.api.v1.classtypes as cl
 from nominatim.utils.json_writer import JsonWriter
 
+#pylint: disable=too-many-branches
+
 def _write_osm_id(out: JsonWriter, osm_object: Optional[Tuple[str, int]]) -> None:
     if osm_object is not None:
-        out.keyval_not_none('osm_type', OSM_TYPE_NAME.get(osm_object[0], None))\
+        out.keyval_not_none('osm_type', cl.OSM_TYPE_NAME.get(osm_object[0], None))\
            .keyval('osm_id', osm_object[1])
 
 
@@ -24,11 +25,14 @@ def _write_typed_address(out: JsonWriter, address: Optional[napi.AddressLines],
                                country_code: Optional[str]) -> None:
     parts = {}
     for line in (address or []):
-        if line.isaddress and line.local_name:
-            label = get_label_tag(line.category, line.extratags,
-                                  line.rank_address, country_code)
-            if label not in parts:
-                parts[label] = line.local_name
+        if line.isaddress:
+            if line.local_name:
+                label = cl.get_label_tag(line.category, line.extratags,
+                                         line.rank_address, country_code)
+                if label not in parts:
+                    parts[label] = line.local_name
+            if line.names and 'ISO3166-2' in line.names and line.admin_level:
+                parts[f"ISO3166-2-lvl{line.admin_level}"] = line.names['ISO3166-2']
 
     for k, v in parts.items():
         out.keyval(k, v)
@@ -50,7 +54,10 @@ def _write_geocodejson_address(out: JsonWriter,
                 out.keyval('housenumber', line.local_name)
             elif (obj_place_id is None or obj_place_id != line.place_id) \
                  and line.rank_address >= 4 and line.rank_address < 28:
-                extra[GEOCODEJSON_RANKS[line.rank_address]] = line.local_name
+                rank_name = GEOCODEJSON_RANKS[line.rank_address]
+                if rank_name not in extra:
+                    extra[rank_name] = line.local_name
+
 
     for k, v in extra.items():
         out.keyval(k, v)
@@ -59,13 +66,11 @@ def _write_geocodejson_address(out: JsonWriter,
         out.keyval('country_code', country_code)
 
 
-def format_base_json(results: napi.ReverseResults, #pylint: disable=too-many-branches
+def format_base_json(results: Union[napi.ReverseResults, napi.SearchResults],
                      options: Mapping[str, Any], simple: bool,
                      class_label: str) -> str:
     """ Return the result list as a simple json string in custom Nominatim format.
     """
-    locales = options.get('locales', napi.Locales())
-
     out = JsonWriter()
 
     if simple:
@@ -75,29 +80,27 @@ def format_base_json(results: napi.ReverseResults, #pylint: disable=too-many-bra
         out.start_array()
 
     for result in results:
-        label_parts = result.address_rows.localize(locales) if result.address_rows else []
-
         out.start_object()\
              .keyval_not_none('place_id', result.place_id)\
-             .keyval('licence', OSM_ATTRIBUTION)\
+             .keyval('licence', cl.OSM_ATTRIBUTION)\
 
         _write_osm_id(out, result.osm_object)
 
-        out.keyval('lat', result.centroid.lat)\
-             .keyval('lon', result.centroid.lon)\
+        out.keyval('lat', f"{result.centroid.lat}")\
+             .keyval('lon', f"{result.centroid.lon}")\
              .keyval(class_label, result.category[0])\
              .keyval('type', result.category[1])\
              .keyval('place_rank', result.rank_search)\
              .keyval('importance', result.calculated_importance())\
-             .keyval('addresstype', get_label_tag(result.category, result.extratags,
-                                                  result.rank_address,
-                                                  result.country_code))\
-             .keyval('name', locales.display_name(result.names))\
-             .keyval('display_name', ', '.join(label_parts))
+             .keyval('addresstype', cl.get_label_tag(result.category, result.extratags,
+                                                     result.rank_address,
+                                                     result.country_code))\
+             .keyval('name', result.locale_name or '')\
+             .keyval('display_name', result.display_name or '')
 
 
         if options.get('icon_base_url', None):
-            icon = ICONS.get(result.category)
+            icon = cl.ICONS.get(result.category)
             if icon:
                 out.keyval('icon', f"{options['icon_base_url']}/{icon}.p.20.png")
 
@@ -112,12 +115,12 @@ def format_base_json(results: napi.ReverseResults, #pylint: disable=too-many-bra
         if options.get('namedetails', False):
             out.keyval('namedetails', result.names)
 
-        bbox = bbox_from_result(result)
+        bbox = cl.bbox_from_result(result)
         out.key('boundingbox').start_array()\
-             .value(bbox.minlat).next()\
-             .value(bbox.maxlat).next()\
-             .value(bbox.minlon).next()\
-             .value(bbox.maxlon).next()\
+             .value(f"{bbox.minlat:0.7f}").next()\
+             .value(f"{bbox.maxlat:0.7f}").next()\
+             .value(f"{bbox.minlon:0.7f}").next()\
+             .value(f"{bbox.maxlon:0.7f}").next()\
            .end_array().next()
 
         if result.geometry:
@@ -139,7 +142,7 @@ def format_base_json(results: napi.ReverseResults, #pylint: disable=too-many-bra
     return out()
 
 
-def format_base_geojson(results: napi.ReverseResults,
+def format_base_geojson(results: Union[napi.ReverseResults, napi.SearchResults],
                         options: Mapping[str, Any],
                         simple: bool) -> str:
     """ Return the result list as a geojson string.
@@ -147,21 +150,14 @@ def format_base_geojson(results: napi.ReverseResults,
     if not results and simple:
         return '{"error":"Unable to geocode"}'
 
-    locales = options.get('locales', napi.Locales())
-
     out = JsonWriter()
 
     out.start_object()\
          .keyval('type', 'FeatureCollection')\
-         .keyval('licence', OSM_ATTRIBUTION)\
+         .keyval('licence', cl.OSM_ATTRIBUTION)\
          .key('features').start_array()
 
     for result in results:
-        if result.address_rows:
-            label_parts = result.address_rows.localize(locales)
-        else:
-            label_parts = []
-
         out.start_object()\
              .keyval('type', 'Feature')\
              .key('properties').start_object()
@@ -174,11 +170,11 @@ def format_base_geojson(results: napi.ReverseResults,
            .keyval('category', result.category[0])\
            .keyval('type', result.category[1])\
            .keyval('importance', result.calculated_importance())\
-           .keyval('addresstype', get_label_tag(result.category, result.extratags,
-                                                result.rank_address,
-                                                result.country_code))\
-           .keyval('name', locales.display_name(result.names))\
-           .keyval('display_name', ', '.join(label_parts))
+           .keyval('addresstype', cl.get_label_tag(result.category, result.extratags,
+                                                   result.rank_address,
+                                                   result.country_code))\
+           .keyval('name', result.locale_name or '')\
+           .keyval('display_name', result.display_name or '')
 
         if options.get('addressdetails', False):
             out.key('address').start_object()
@@ -193,8 +189,10 @@ def format_base_geojson(results: napi.ReverseResults,
 
         out.end_object().next() # properties
 
-        bbox = bbox_from_result(result)
-        out.keyval('bbox', bbox.coords)
+        out.key('bbox').start_array()
+        for coord in cl.bbox_from_result(result).coords:
+            out.float(coord, 7).next()
+        out.end_array().next()
 
         out.key('geometry').raw(result.geometry.get('geojson')
                                 or result.centroid.to_geojson()).next()
@@ -206,33 +204,26 @@ def format_base_geojson(results: napi.ReverseResults,
     return out()
 
 
-def format_base_geocodejson(results: napi.ReverseResults,
+def format_base_geocodejson(results: Union[napi.ReverseResults, napi.SearchResults],
                             options: Mapping[str, Any], simple: bool) -> str:
     """ Return the result list as a geocodejson string.
     """
     if not results and simple:
         return '{"error":"Unable to geocode"}'
 
-    locales = options.get('locales', napi.Locales())
-
     out = JsonWriter()
 
     out.start_object()\
          .keyval('type', 'FeatureCollection')\
          .key('geocoding').start_object()\
            .keyval('version', '0.1.0')\
-           .keyval('attribution', OSM_ATTRIBUTION)\
+           .keyval('attribution', cl.OSM_ATTRIBUTION)\
            .keyval('licence', 'ODbL')\
            .keyval_not_none('query', options.get('query'))\
            .end_object().next()\
          .key('features').start_array()
 
     for result in results:
-        if result.address_rows:
-            label_parts = result.address_rows.localize(locales)
-        else:
-            label_parts = []
-
         out.start_object()\
              .keyval('type', 'Feature')\
              .key('properties').start_object()\
@@ -245,9 +236,9 @@ def format_base_geocodejson(results: napi.ReverseResults,
         out.keyval('osm_key', result.category[0])\
            .keyval('osm_value', result.category[1])\
            .keyval('type', GEOCODEJSON_RANKS[max(3, min(28, result.rank_address))])\
-           .keyval_not_none('accuracy', result.distance)\
-           .keyval('label', ', '.join(label_parts))\
-           .keyval_not_none('name', locales.display_name(result.names))\
+           .keyval_not_none('accuracy', getattr(result, 'distance', None), transform=int)\
+           .keyval('label', result.display_name or '')\
+           .keyval_not_none('name', result.locale_name or None)\
 
         if options.get('addressdetails', False):
             _write_geocodejson_address(out, result.address_rows, result.place_id,
@@ -256,7 +247,8 @@ def format_base_geocodejson(results: napi.ReverseResults,
             out.key('admin').start_object()
             if result.address_rows:
                 for line in result.address_rows:
-                    if line.isaddress and (line.admin_level or 15) < 15 and line.local_name:
+                    if line.isaddress and (line.admin_level or 15) < 15 and line.local_name \
+                       and line.category[0] == 'boundary' and line.category[1] == 'administrative':
                         out.keyval(f"level{line.admin_level}", line.local_name)
             out.end_object().next()