Point as Point,
Bbox as Bbox,
GeometryFormat as GeometryFormat,
- LookupDetails as LookupDetails,
DataLayer as DataLayer)
from .results import (SourceTable as SourceTable,
AddressLine as AddressLine,
from nominatim.api.status import get_status, StatusResult
from nominatim.api.lookup import get_detailed_place, get_simple_place
from nominatim.api.reverse import ReverseGeocoder
-from nominatim.api.types import PlaceRef, LookupDetails, AnyPoint, DataLayer
+import nominatim.api.types as ntyp
from nominatim.api.results import DetailedResult, ReverseResult, SearchResults
return status
- async def details(self, place: PlaceRef,
- details: Optional[LookupDetails] = None) -> Optional[DetailedResult]:
+ async def details(self, place: ntyp.PlaceRef, **params: Any) -> Optional[DetailedResult]:
""" Get detailed information about a place in the database.
Returns None if there is no entry under the given ID.
"""
async with self.begin() as conn:
- return await get_detailed_place(conn, place, details or LookupDetails())
+ return await get_detailed_place(conn, place,
+ ntyp.LookupDetails.from_kwargs(params))
- async def lookup(self, places: Sequence[PlaceRef],
- details: Optional[LookupDetails] = None) -> SearchResults:
+ async def lookup(self, places: Sequence[ntyp.PlaceRef], **params: Any) -> SearchResults:
""" Get simple information about a list of places.
Returns a list of place information for all IDs that were found.
"""
- if details is None:
- details = LookupDetails()
+ details = ntyp.LookupDetails.from_kwargs(params)
async with self.begin() as conn:
return SearchResults(filter(None,
[await get_simple_place(conn, p, details) for p in places]))
- async def reverse(self, coord: AnyPoint, max_rank: Optional[int] = None,
- layer: Optional[DataLayer] = None,
- details: Optional[LookupDetails] = None) -> Optional[ReverseResult]:
+ async def reverse(self, coord: ntyp.AnyPoint, **params: Any) -> Optional[ReverseResult]:
""" Find a place by its coordinates. Also known as reverse geocoding.
Returns the closest result that can be found or None if
# There are no results to be expected outside valid coordinates.
return None
- if layer is None:
- layer = DataLayer.ADDRESS | DataLayer.POI
-
- max_rank = max(0, min(max_rank or 30, 30))
-
async with self.begin() as conn:
- geocoder = ReverseGeocoder(conn, max_rank, layer,
- details or LookupDetails())
+ geocoder = ReverseGeocoder(conn, ntyp.ReverseDetails.from_kwargs(params))
return await geocoder.lookup(coord)
return self._loop.run_until_complete(self._async_api.status())
- def details(self, place: PlaceRef,
- details: Optional[LookupDetails] = None) -> Optional[DetailedResult]:
+ def details(self, place: ntyp.PlaceRef, **params: Any) -> Optional[DetailedResult]:
""" Get detailed information about a place in the database.
"""
- return self._loop.run_until_complete(self._async_api.details(place, details))
+ return self._loop.run_until_complete(self._async_api.details(place, **params))
- def lookup(self, places: Sequence[PlaceRef],
- details: Optional[LookupDetails] = None) -> SearchResults:
+ def lookup(self, places: Sequence[ntyp.PlaceRef], **params: Any) -> SearchResults:
""" Get simple information about a list of places.
Returns a list of place information for all IDs that were found.
"""
- return self._loop.run_until_complete(self._async_api.lookup(places, details))
+ return self._loop.run_until_complete(self._async_api.lookup(places, **params))
- def reverse(self, coord: AnyPoint, max_rank: Optional[int] = None,
- layer: Optional[DataLayer] = None,
- details: Optional[LookupDetails] = None) -> Optional[ReverseResult]:
+ def reverse(self, coord: ntyp.AnyPoint, **params: Any) -> Optional[ReverseResult]:
""" Find a place by its coordinates. Also known as reverse geocoding.
Returns the closest result that can be found or None if
no place matches the given criteria.
"""
- return self._loop.run_until_complete(
- self._async_api.reverse(coord, max_rank, layer, details))
+ return self._loop.run_until_complete(self._async_api.reverse(coord, **params))
from nominatim.api.connection import SearchConnection
import nominatim.api.results as nres
from nominatim.api.logging import log
-from nominatim.api.types import AnyPoint, DataLayer, LookupDetails, GeometryFormat, Bbox
+from nominatim.api.types import AnyPoint, DataLayer, ReverseDetails, GeometryFormat, Bbox
# In SQLAlchemy expression which compare with NULL need to be expressed with
# the equal sign.
coordinate.
"""
- def __init__(self, conn: SearchConnection, max_rank: int, layer: DataLayer,
- details: LookupDetails) -> None:
+ def __init__(self, conn: SearchConnection, params: ReverseDetails) -> None:
self.conn = conn
- self.max_rank = max_rank
- self.layer = layer
- self.details = details
+ self.params = params
+
+
+ @property
+ def max_rank(self) -> int:
+ """ Return the maximum configured rank.
+ """
+ return self.params.max_rank
+
+
+ def has_geometries(self) -> bool:
+ """ Check if any geometries are requested.
+ """
+ return bool(self.params.geometry_output)
+
def layer_enabled(self, *layer: DataLayer) -> bool:
""" Return true when any of the given layer types are requested.
"""
- return any(self.layer & l for l in layer)
+ return any(self.params.layers & l for l in layer)
def layer_disabled(self, *layer: DataLayer) -> bool:
""" Return true when none of the given layer types is requested.
"""
- return not any(self.layer & l for l in layer)
+ return not any(self.params.layers & l for l in layer)
def has_feature_layers(self) -> bool:
return self.layer_enabled(DataLayer.RAILWAY, DataLayer.MANMADE, DataLayer.NATURAL)
def _add_geometry_columns(self, sql: SaSelect, col: SaColumn) -> SaSelect:
- if not self.details.geometry_output:
+ if not self.has_geometries():
return sql
out = []
- if self.details.geometry_simplification > 0.0:
- col = col.ST_SimplifyPreserveTopology(self.details.geometry_simplification)
+ if self.params.geometry_simplification > 0.0:
+ col = col.ST_SimplifyPreserveTopology(self.params.geometry_simplification)
- if self.details.geometry_output & GeometryFormat.GEOJSON:
+ if self.params.geometry_output & GeometryFormat.GEOJSON:
out.append(col.ST_AsGeoJSON().label('geometry_geojson'))
- if self.details.geometry_output & GeometryFormat.TEXT:
+ if self.params.geometry_output & GeometryFormat.TEXT:
out.append(col.ST_AsText().label('geometry_text'))
- if self.details.geometry_output & GeometryFormat.KML:
+ if self.params.geometry_output & GeometryFormat.KML:
out.append(col.ST_AsKML().label('geometry_kml'))
- if self.details.geometry_output & GeometryFormat.SVG:
+ if self.params.geometry_output & GeometryFormat.SVG:
out.append(col.ST_AsSVG().label('geometry_svg'))
return sql.add_columns(*out)
if parent_place_id is not None:
sql = sql.where(t.c.parent_place_id == parent_place_id)
- inner = sql.subquery()
+ inner = sql.subquery('ipol')
sql = sa.select(inner.c.place_id, inner.c.osm_id,
inner.c.parent_place_id, inner.c.address,
inner.c.postcode, inner.c.country_code,
inner.c.distance)
- if self.details.geometry_output:
- sub = sql.subquery()
- sql = self._add_geometry_columns(sql, sub.c.centroid)
+ if self.has_geometries():
+ sub = sql.subquery('geom')
+ sql = self._add_geometry_columns(sa.select(sub), sub.c.centroid)
return (await self.conn.execute(sql)).one_or_none()
.where(t.c.parent_place_id == parent_place_id)\
.order_by('distance')\
.limit(1)\
- .subquery()
+ .subquery('tiger')
sql = sa.select(inner.c.place_id,
inner.c.parent_place_id,
inner.c.postcode,
inner.c.distance)
- if self.details.geometry_output:
- sub = sql.subquery()
- sql = self._add_geometry_columns(sql, sub.c.centroid)
+ if self.has_geometries():
+ sub = sql.subquery('geom')
+ sql = self._add_geometry_columns(sa.select(sub), sub.c.centroid)
return (await self.conn.execute(sql)).one_or_none()
.where(t.c.type != 'postcode')\
.order_by(sa.desc(t.c.rank_search))\
.limit(50)\
- .subquery()
+ .subquery('area')
sql = _select_from_placex(inner)\
.where(inner.c.geometry.ST_Contains(wkt))\
.intersects(wkt))\
.order_by(sa.desc(t.c.rank_search))\
.limit(50)\
- .subquery()
+ .subquery('places')
touter = self.conn.t.placex.alias('outer')
sql = _select_from_placex(inner)\
""" Look up a single coordinate. Returns the place information,
if a place was found near the coordinates or None otherwise.
"""
- log().function('reverse_lookup',
- coord=coord, max_rank=self.max_rank,
- layer=self.layer, details=self.details)
+ log().function('reverse_lookup', coord=coord, params=self.params)
wkt = WKTElement(f'POINT({coord[0]} {coord[1]})', srid=4326)
result.distance = row.distance
if hasattr(row, 'bbox'):
result.bbox = Bbox.from_wkb(row.bbox.data)
- await nres.add_result_details(self.conn, result, self.details)
+ await nres.add_result_details(self.conn, result, self.params)
return result
"""
Complex datatypes used by the Nominatim API.
"""
-from typing import Optional, Union, Tuple, NamedTuple
+from typing import Optional, Union, Tuple, NamedTuple, TypeVar, Type, Dict, Any
import dataclasses
import enum
from struct import unpack
+from nominatim.errors import UsageError
+
@dataclasses.dataclass
class PlaceID:
""" Reference an object by Nominatim's internal ID.
TEXT = enum.auto()
+class DataLayer(enum.Flag):
+ """ Layer types that can be selected for reverse and forward search.
+ """
+ POI = enum.auto()
+ ADDRESS = enum.auto()
+ RAILWAY = enum.auto()
+ MANMADE = enum.auto()
+ NATURAL = enum.auto()
+
+
+TParam = TypeVar('TParam', bound='LookupDetails') # pylint: disable=invalid-name
+
@dataclasses.dataclass
class LookupDetails:
""" Collection of parameters that define the amount of details
- returned with a search result.
+ returned with a lookup or details result.
"""
geometry_output: GeometryFormat = GeometryFormat.NONE
""" Add the full geometry of the place to the result. Multiple
more the geometry gets simplified.
"""
+ @classmethod
+ def from_kwargs(cls: Type[TParam], kwargs: Dict[str, Any]) -> TParam:
+ """ Load the data fields of the class from a dictionary.
+ Unknown entries in the dictionary are ignored, missing ones
+ get the default setting.
-class DataLayer(enum.Flag):
- """ Layer types that can be selected for reverse and forward search.
+ The function supports type checking and throws a UsageError
+ when the value does not fit.
+ """
+ def _check_field(v: Any, field: 'dataclasses.Field[Any]') -> Any:
+ if v is None:
+ return field.default_factory() \
+ if field.default_factory != dataclasses.MISSING \
+ else field.default
+ if field.metadata and 'transform' in field.metadata:
+ return field.metadata['transform'](v)
+ if not isinstance(v, field.type):
+ raise UsageError(f"Parameter '{field.name}' needs to be of {field.type!s}.")
+ return v
+
+ return cls(**{f.name: _check_field(kwargs[f.name], f)
+ for f in dataclasses.fields(cls) if f.name in kwargs})
+
+
+@dataclasses.dataclass
+class ReverseDetails(LookupDetails):
+ """ Collection of parameters for the reverse call.
+ """
+ max_rank: int = dataclasses.field(default=30,
+ metadata={'transform': lambda v: max(0, min(v, 30))}
+ )
+ """ Highest address rank to return.
+ """
+ layers: DataLayer = DataLayer.ADDRESS | DataLayer.POI
+ """ Filter which kind of data to include.
"""
- POI = enum.auto()
- ADDRESS = enum.auto()
- RAILWAY = enum.auto()
- MANMADE = enum.auto()
- NATURAL = enum.auto()
--- /dev/null
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of Nominatim. (https://nominatim.org)
+#
+# Copyright (C) 2023 by the Nominatim developer community.
+# For a full list of authors see the git log.
+"""
+Helper function for parsing parameters and and outputting data
+specifically for the v1 version of the API.
+"""
+
+REVERSE_MAX_RANKS = [2, 2, 2, # 0-2 Continent/Sea
+ 4, 4, # 3-4 Country
+ 8, # 5 State
+ 10, 10, # 6-7 Region
+ 12, 12, # 8-9 County
+ 16, 17, # 10-11 City
+ 18, # 12 Town
+ 19, # 13 Village/Suburb
+ 22, # 14 Hamlet/Neighbourhood
+ 25, # 15 Localities
+ 26, # 16 Major Streets
+ 27, # 17 Minor Streets
+ 30 # 18 Building
+ ]
+
+
+def zoom_to_rank(zoom: int) -> int:
+ """ Convert a zoom parameter into a rank according to the v1 API spec.
+ """
+ return REVERSE_MAX_RANKS[max(0, min(18, zoom))]
Generic part of the server implementation of the v1 API.
Combine with the scaffolding provided for the various Python ASGI frameworks.
"""
-from typing import Optional, Any, Type, Callable, NoReturn, cast
+from typing import Optional, Any, Type, Callable, NoReturn, Dict, cast
from functools import reduce
import abc
import math
import nominatim.api as napi
import nominatim.api.logging as loglib
from nominatim.api.v1.format import dispatch as formatting
+from nominatim.api.v1 import helpers
CONTENT_TYPE = {
'text': 'text/plain; charset=utf-8',
return fmt
- def parse_geometry_details(self, fmt: str) -> napi.LookupDetails:
+ def parse_geometry_details(self, fmt: str) -> Dict[str, Any]:
""" Create details strucutre from the supplied geometry parameters.
"""
- details = napi.LookupDetails(address_details=True,
- geometry_simplification=
- self.get_float('polygon_threshold', 0.0))
numgeoms = 0
+ output = napi.GeometryFormat.NONE
if self.get_bool('polygon_geojson', False):
- details.geometry_output |= napi.GeometryFormat.GEOJSON
+ output |= napi.GeometryFormat.GEOJSON
numgeoms += 1
if fmt not in ('geojson', 'geocodejson'):
if self.get_bool('polygon_text', False):
- details.geometry_output |= napi.GeometryFormat.TEXT
+ output |= napi.GeometryFormat.TEXT
numgeoms += 1
if self.get_bool('polygon_kml', False):
- details.geometry_output |= napi.GeometryFormat.KML
+ output |= napi.GeometryFormat.KML
numgeoms += 1
if self.get_bool('polygon_svg', False):
- details.geometry_output |= napi.GeometryFormat.SVG
+ output |= napi.GeometryFormat.SVG
numgeoms += 1
if numgeoms > self.config().get_int('POLYGON_OUTPUT_MAX_TYPES'):
self.raise_error('Too many polgyon output options selected.')
- return details
+ return {'address_details': True,
+ 'geometry_simplification': self.get_float('polygon_threshold', 0.0),
+ 'geometry_output': output
+ }
async def status_endpoint(api: napi.NominatimAPIAsync, params: ASGIAdaptor) -> Any:
debug = params.setup_debugging()
- details = napi.LookupDetails(address_details=params.get_bool('addressdetails', False),
- linked_places=params.get_bool('linkedplaces', False),
- parented_places=params.get_bool('hierarchy', False),
- keywords=params.get_bool('keywords', False))
-
- if params.get_bool('polygon_geojson', False):
- details.geometry_output = napi.GeometryFormat.GEOJSON
-
locales = napi.Locales.from_accept_languages(params.get_accepted_languages())
- result = await api.details(place, details)
+ result = await api.details(place,
+ address_details=params.get_bool('addressdetails', False),
+ linked_places=params.get_bool('linkedplaces', False),
+ parented_places=params.get_bool('hierarchy', False),
+ keywords=params.get_bool('keywords', False),
+ geometry_output = napi.GeometryFormat.GEOJSON
+ if params.get_bool('polygon_geojson', False)
+ else napi.GeometryFormat.NONE
+ )
if debug:
return params.build_response(loglib.get_and_disable())
debug = params.setup_debugging()
coord = napi.Point(params.get_float('lon'), params.get_float('lat'))
locales = napi.Locales.from_accept_languages(params.get_accepted_languages())
- details = params.parse_geometry_details(fmt)
-
- zoom = max(0, min(18, params.get_int('zoom', 18)))
+ details = params.parse_geometry_details(fmt)
+ details['max_rank'] = helpers.zoom_to_rank(params.get_int('zoom', 18))
+ details['layers'] = params.get_layers()
- result = await api.reverse(coord, REVERSE_MAX_RANKS[zoom],
- params.get_layers() or
- napi.DataLayer.ADDRESS | napi.DataLayer.POI,
- details)
+ result = await api.reverse(coord, **details)
if debug:
return params.build_response(loglib.get_and_disable())
places.append(napi.OsmID(oid[0], int(oid[1:])))
if places:
- results = await api.lookup(places, details)
+ results = await api.lookup(places, **details)
else:
results = napi.SearchResults()
EndpointFunc = Callable[[napi.NominatimAPIAsync, ASGIAdaptor], Any]
-REVERSE_MAX_RANKS = [2, 2, 2, # 0-2 Continent/Sea
- 4, 4, # 3-4 Country
- 8, # 5 State
- 10, 10, # 6-7 Region
- 12, 12, # 8-9 County
- 16, 17, # 10-11 City
- 18, # 12 Town
- 19, # 13 Village/Suburb
- 22, # 14 Hamlet/Neighbourhood
- 25, # 15 Localities
- 26, # 16 Major Streets
- 27, # 17 Minor Streets
- 30 # 18 Building
- ]
-
-
ROUTES = [
('status', status_endpoint),
('details', details_endpoint),
from nominatim.clicmd.args import NominatimArgs
import nominatim.api as napi
import nominatim.api.v1 as api_output
-from nominatim.api.v1.server_glue import REVERSE_MAX_RANKS
+from nominatim.api.v1.helpers import zoom_to_rank
# Do not repeat documentation of subcommand classes.
# pylint: disable=C0111
def run(self, args: NominatimArgs) -> int:
api = napi.NominatimAPI(args.project_dir)
- details = napi.LookupDetails(address_details=True, # needed for display name
- geometry_output=args.get_geometry_output(),
- geometry_simplification=args.polygon_threshold or 0.0)
-
result = api.reverse(napi.Point(args.lon, args.lat),
- REVERSE_MAX_RANKS[max(0, min(18, args.zoom or 18))],
- args.get_layers(napi.DataLayer.ADDRESS | napi.DataLayer.POI),
- details)
+ max_rank=zoom_to_rank(args.zoom or 18),
+ layers=args.get_layers(napi.DataLayer.ADDRESS | napi.DataLayer.POI),
+ address_details=True, # needed for display name
+ geometry_output=args.get_geometry_output(),
+ geometry_simplification=args.polygon_threshold)
if result:
output = api_output.format_result(
def run(self, args: NominatimArgs) -> int:
api = napi.NominatimAPI(args.project_dir)
- details = napi.LookupDetails(address_details=True, # needed for display name
- geometry_output=args.get_geometry_output(),
- geometry_simplification=args.polygon_threshold or 0.0)
-
places = [napi.OsmID(o[0], int(o[1:])) for o in args.ids]
- results = api.lookup(places, details)
+ results = api.lookup(places,
+ address_details=True, # needed for display name
+ geometry_output=args.get_geometry_output(),
+ geometry_simplification=args.polygon_threshold or 0.0)
output = api_output.format_result(
results,
api = napi.NominatimAPI(args.project_dir)
- details = napi.LookupDetails(address_details=args.addressdetails,
- linked_places=args.linkedplaces,
- parented_places=args.hierarchy,
- keywords=args.keywords)
- if args.polygon_geojson:
- details.geometry_output = napi.GeometryFormat.GEOJSON
+ result = api.details(place,
+ address_details=args.addressdetails,
+ linked_places=args.linkedplaces,
+ parented_places=args.hierarchy,
+ keywords=args.keywords,
+ geometry_output=napi.GeometryFormat.GEOJSON
+ if args.polygon_geojson
+ else napi.GeometryFormat.NONE)
- result = api.details(place, details)
if result:
output = api_output.format_result(
indexed_date=import_date,
geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
- result = apiobj.api.details(idobj, napi.LookupDetails())
+ result = apiobj.api.details(idobj)
assert result is not None
indexed_date=import_date,
geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)')
- result = apiobj.api.details(napi.PlaceID(332), napi.LookupDetails())
+ result = apiobj.api.details(napi.PlaceID(332))
assert result is not None
apiobj.add_placex(place_id=332,
geometry='LINESTRING(23 34, 23.1 34)')
- result = apiobj.api.details(napi.PlaceID(332),
- napi.LookupDetails(geometry_output=napi.GeometryFormat.GEOJSON))
+ result = apiobj.api.details(napi.PlaceID(332), geometry_output=napi.GeometryFormat.GEOJSON)
assert result.geometry == {'geojson': '{"type":"LineString","coordinates":[[23,34],[23.1,34]]}'}
country_code='pl',
rank_search=17, rank_address=16)
- result = apiobj.api.details(napi.PlaceID(332),
- napi.LookupDetails(address_details=True))
+ result = apiobj.api.details(napi.PlaceID(332), address_details=True)
assert result.address_rows == [
napi.AddressLine(place_id=332, osm_object=('W', 4),
country_code='pl', linked_place_id=45,
rank_search=27, rank_address=26)
- result = apiobj.api.details(napi.PlaceID(332),
- napi.LookupDetails(linked_places=True))
+ result = apiobj.api.details(napi.PlaceID(332), linked_places=True)
assert result.linked_rows == []
country_code='pl', linked_place_id=332,
rank_search=27, rank_address=26)
- result = apiobj.api.details(napi.PlaceID(332),
- napi.LookupDetails(linked_places=True))
+ result = apiobj.api.details(napi.PlaceID(332), linked_places=True)
assert result.linked_rows == [
napi.AddressLine(place_id=1001, osm_object=('W', 5),
country_code='pl', parent_place_id=45,
rank_search=27, rank_address=26)
- result = apiobj.api.details(napi.PlaceID(332),
- napi.LookupDetails(parented_places=True))
+ result = apiobj.api.details(napi.PlaceID(332), parented_places=True)
assert result.parented_rows == []
country_code='pl', parent_place_id=332,
rank_search=27, rank_address=26)
- result = apiobj.api.details(napi.PlaceID(332),
- napi.LookupDetails(parented_places=True))
+ result = apiobj.api.details(napi.PlaceID(332), parented_places=True)
assert result.parented_rows == [
napi.AddressLine(place_id=1001, osm_object=('N', 5),
indexed_date=import_date,
geometry='LINESTRING(23 34, 23 35)')
- result = apiobj.api.details(idobj, napi.LookupDetails())
+ result = apiobj.api.details(idobj)
assert result is not None
startnumber=11, endnumber=20, step=1)
for i in range(1, 6):
- result = apiobj.api.details(napi.OsmID('W', 9, str(i)), napi.LookupDetails())
+ result = apiobj.api.details(napi.OsmID('W', 9, str(i)))
assert result.place_id == 1000
for i in range(7, 11):
- result = apiobj.api.details(napi.OsmID('W', 9, str(i)), napi.LookupDetails())
+ result = apiobj.api.details(napi.OsmID('W', 9, str(i)))
assert result.place_id == 1001
for i in range(12, 22):
- result = apiobj.api.details(napi.OsmID('W', 9, str(i)), napi.LookupDetails())
+ result = apiobj.api.details(napi.OsmID('W', 9, str(i)))
assert result.place_id == 1002
country_code='pl',
rank_search=17, rank_address=16)
- result = apiobj.api.details(napi.PlaceID(9000),
- napi.LookupDetails(address_details=True))
+ result = apiobj.api.details(napi.PlaceID(9000), address_details=True)
assert result.address_rows == [
napi.AddressLine(place_id=None, osm_object=None,
osm_type='W', osm_id=6601223,
geometry='LINESTRING(23 34, 23 35)')
- result = apiobj.api.details(napi.PlaceID(4924), napi.LookupDetails())
+ result = apiobj.api.details(napi.PlaceID(4924))
assert result is not None
country_code='us',
rank_search=17, rank_address=16)
- result = apiobj.api.details(napi.PlaceID(9000),
- napi.LookupDetails(address_details=True))
+ result = apiobj.api.details(napi.PlaceID(9000), address_details=True)
assert result.address_rows == [
napi.AddressLine(place_id=None, osm_object=None,
indexed_date=import_date,
geometry='POINT(-9.45 5.6)')
- result = apiobj.api.details(napi.PlaceID(554), napi.LookupDetails())
+ result = apiobj.api.details(napi.PlaceID(554))
assert result is not None
country_code='gb',
rank_search=17, rank_address=16)
- result = apiobj.api.details(napi.PlaceID(9000),
- napi.LookupDetails(address_details=True))
+ result = apiobj.api.details(napi.PlaceID(9000), address_details=True)
assert result.address_rows == [
napi.AddressLine(place_id=332, osm_object=('N', 3333),
apiobj.add_placex(place_id=1, osm_type='N', osm_id=55,
class_='place', type='suburb')
- assert apiobj.api.details(objid, napi.LookupDetails()) is None
+ assert apiobj.api.details(objid) is None
@pytest.mark.parametrize('gtype', (napi.GeometryFormat.KML,
apiobj.add_placex(place_id=332)
with pytest.raises(ValueError):
- apiobj.api.details(napi.PlaceID(332),
- napi.LookupDetails(geometry_output=gtype))
+ apiobj.api.details(napi.PlaceID(332), geometry_output=gtype)
result = apiobj.api.lookup((napi.OsmID('W', 1),
napi.OsmID('W', 4),
- napi.OsmID('W', 9928)), napi.LookupDetails())
+ napi.OsmID('W', 9928)))
assert len(result) == 2
rank_search=30,
centroid=(1.3, 0.70005))
- assert apiobj.api.reverse((1.3, y), layer=layer).place_id == place_id
+ assert apiobj.api.reverse((1.3, y), layers=layer).place_id == place_id
def test_reverse_poi_layer_with_no_pois(apiobj):
centroid=(1.3, 0.70001))
assert apiobj.api.reverse((1.3, 0.70001), max_rank=29,
- layer=napi.DataLayer.POI) is None
+ layers=napi.DataLayer.POI) is None
def test_reverse_housenumber_on_street(apiobj):
rank_search=16,
centroid=(1.3, 0.70005))
- assert apiobj.api.reverse((1.3, 0.7), layer=layer).place_id == place_id
+ assert apiobj.api.reverse((1.3, 0.7), layers=layer).place_id == place_id
def test_reverse_country_lookup_no_objects(apiobj):
country_code='xx',
centroid=(0.5, 0.5))
- details = napi.LookupDetails(geometry_output=gtype)
-
- assert apiobj.api.reverse((59.3, 80.70001), details=details).place_id == 1001
- assert apiobj.api.reverse((0.5, 0.5), details=details).place_id == 1003
+ assert apiobj.api.reverse((59.3, 80.70001), geometry_output=gtype).place_id == 1001
+ assert apiobj.api.reverse((0.5, 0.5), geometry_output=gtype).place_id == 1003
def test_reverse_simplified_geometry(apiobj):
rank_search=30,
centroid=(59.3, 80.70001))
- details = napi.LookupDetails(geometry_output=napi.GeometryFormat.GEOJSON,
- geometry_simplification=0.1)
- assert apiobj.api.reverse((59.3, 80.70001), details=details).place_id == 1001
+ details = dict(geometry_output=napi.GeometryFormat.GEOJSON,
+ geometry_simplification=0.1)
+ assert apiobj.api.reverse((59.3, 80.70001), **details).place_id == 1001
def test_reverse_interpolation_geometry(apiobj):
centroid=(10.0, 10.00001),
geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
- details = napi.LookupDetails(geometry_output=napi.GeometryFormat.TEXT)
- assert apiobj.api.reverse((10.0, 10.0), details=details)\
+ assert apiobj.api.reverse((10.0, 10.0), geometry_output=napi.GeometryFormat.TEXT)\
.geometry['text'] == 'POINT(10 10.00001)'
centroid=(10.0, 10.00001),
geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)')
- details = napi.LookupDetails(geometry_output=napi.GeometryFormat.GEOJSON)
- output = apiobj.api.reverse((10.0, 10.0), details=details).geometry['geojson']
+ output = apiobj.api.reverse((10.0, 10.0),
+ geometry_output=napi.GeometryFormat.GEOJSON).geometry['geojson']
assert json.loads(output) == {'coordinates': [10, 10.00001], 'type': 'Point'}
--- /dev/null
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of Nominatim. (https://nominatim.org)
+#
+# Copyright (C) 2023 by the Nominatim developer community.
+# For a full list of authors see the git log.
+"""
+Tests for loading of parameter dataclasses.
+"""
+import pytest
+
+from nominatim.errors import UsageError
+import nominatim.api.types as typ
+
+def test_no_params_defaults():
+ params = typ.LookupDetails.from_kwargs({})
+
+ assert not params.parented_places
+ assert params.geometry_simplification == 0.0
+
+
+@pytest.mark.parametrize('k,v', [('geometry_output', 'a'),
+ ('linked_places', 0),
+ ('geometry_simplification', 'NaN')])
+def test_bad_format_reverse(k, v):
+ with pytest.raises(UsageError):
+ params = typ.ReverseDetails.from_kwargs({k: v})
+
+
+@pytest.mark.parametrize('rin,rout', [(-23, 0), (0, 0), (1, 1),
+ (15, 15), (30, 30), (31, 30)])
+def test_rank_params(rin, rout):
+ params = typ.ReverseDetails.from_kwargs({'max_rank': rin})
+
+ assert params.max_rank == rout
napi.Point(1.0, -3.0))
monkeypatch.setattr(napi.NominatimAPI, 'details',
- lambda *args: result)
+ lambda *args, **kwargs: result)
@pytest.mark.parametrize("params", [('--node', '1'),
('--way', '1'),
extratags={'extra':'Extra'})
monkeypatch.setattr(napi.NominatimAPI, 'reverse',
- lambda *args: result)
+ lambda *args, **kwargs: result)
def test_reverse_simple(self, cli_call, tmp_path, capsys):
extratags={'extra':'Extra'})
monkeypatch.setattr(napi.NominatimAPI, 'lookup',
- lambda *args: napi.SearchResults([result]))
+ lambda *args, **kwargs: napi.SearchResults([result]))
def test_lookup_simple(self, cli_call, tmp_path, capsys):
result = cli_call('lookup', '--project-dir', str(tmp_path),