From c634e9fc5f35d50b3cf52a5bf08078adb97dcdec Mon Sep 17 00:00:00 2001 From: Sarah Hoffmann Date: Mon, 7 Jul 2025 11:17:01 +0200 Subject: [PATCH] differentiate between place searches with and without address --- src/nominatim_api/search/db_search_builder.py | 6 ++--- .../search/db_searches/address_search.py | 22 ++++++++++------ .../search/db_searches/place_search.py | 25 ++++++++++++------- test/python/api/search/test_search_address.py | 4 +-- test/python/api/search/test_search_near.py | 2 +- test/python/api/search/test_search_places.py | 4 +-- 6 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/nominatim_api/search/db_search_builder.py b/src/nominatim_api/search/db_search_builder.py index 7120fce1..9cb263fd 100644 --- a/src/nominatim_api/search/db_search_builder.py +++ b/src/nominatim_api/search/db_search_builder.py @@ -180,7 +180,7 @@ class SearchBuilder: dbf.FieldLookup('nameaddress_vector', addr_fulls, lookups.LookupAny)) sdata.housenumbers = dbf.WeightedStrings([], []) - yield dbs.PlaceSearch(0.05, sdata, expected_count) + yield dbs.PlaceSearch(0.05, sdata, expected_count, True) def build_name_search(self, sdata: dbf.SearchData, name: qmod.TokenRange, address: List[qmod.TokenRange], @@ -195,9 +195,9 @@ class SearchBuilder: for penalty, count, lookup in self.yield_lookups(name, address): sdata.lookups = lookup if sdata.housenumbers: - yield dbs.AddressSearch(penalty + name_penalty, sdata, count) + yield dbs.AddressSearch(penalty + name_penalty, sdata, count, bool(address)) else: - yield dbs.PlaceSearch(penalty + name_penalty, sdata, count) + yield dbs.PlaceSearch(penalty + name_penalty, sdata, count, bool(address)) def yield_lookups(self, name: qmod.TokenRange, address: List[qmod.TokenRange] ) -> Iterator[Tuple[float, int, List[dbf.FieldLookup]]]: diff --git a/src/nominatim_api/search/db_searches/address_search.py b/src/nominatim_api/search/db_searches/address_search.py index a6d2e1ab..90688339 100644 --- a/src/nominatim_api/search/db_searches/address_search.py +++ b/src/nominatim_api/search/db_searches/address_search.py @@ -136,7 +136,8 @@ class AddressSearch(base.AbstractSearch): """ SEARCH_PRIO = 1 - def __init__(self, extra_penalty: float, sdata: SearchData, expected_count: int) -> None: + def __init__(self, extra_penalty: float, sdata: SearchData, + expected_count: int, has_address_terms: bool) -> None: assert sdata.housenumbers super().__init__(sdata.penalty + extra_penalty) self.countries = sdata.countries @@ -146,6 +147,7 @@ class AddressSearch(base.AbstractSearch): self.lookups = sdata.lookups self.rankings = sdata.rankings self.expected_count = expected_count + self.has_address_terms = has_address_terms def _inner_search_name_cte(self, conn: SearchConnection, details: SearchDetails) -> 'sa.CTE': @@ -173,8 +175,6 @@ class AddressSearch(base.AbstractSearch): sql = sql.where(t.c.country_code.in_(self.countries.values)) if self.postcodes: - # if a postcode is given, don't search for state or country level objects - sql = sql.where(t.c.address_rank > 9) if self.expected_count > 10000: # Many results expected. Restrict by postcode. tpc = conn.t.postcode @@ -197,7 +197,12 @@ class AddressSearch(base.AbstractSearch): sql = sql.where(t.c.centroid .ST_Distance(NEAR_PARAM) < NEAR_RADIUS_PARAM) - sql = sql.where(t.c.address_rank.between(16, 30)) + if self.has_address_terms: + sql = sql.where(t.c.address_rank.between(16, 30)) + else: + # If no further address terms are given, then the base street must + # be in the name. No search for named POIs with the given house number. + sql = sql.where(t.c.address_rank.between(16, 27)) inner = sql.limit(10000).order_by(sa.desc(sa.text('importance'))).subquery() @@ -248,9 +253,12 @@ class AddressSearch(base.AbstractSearch): .order_by(sa.text('accuracy')) hnr_list = '|'.join(self.housenumbers.values) - inner = sql.where(sa.or_(tsearch.c.address_rank < 30, - sa.func.RegexpWord(hnr_list, t.c.housenumber)))\ - .subquery() + + if self.has_address_terms: + sql = sql.where(sa.or_(tsearch.c.address_rank < 30, + sa.func.RegexpWord(hnr_list, t.c.housenumber))) + + inner = sql.subquery() # Housenumbers from placex thnr = conn.t.placex.alias('hnr') diff --git a/src/nominatim_api/search/db_searches/place_search.py b/src/nominatim_api/search/db_searches/place_search.py index f745a259..ffe561aa 100644 --- a/src/nominatim_api/search/db_searches/place_search.py +++ b/src/nominatim_api/search/db_searches/place_search.py @@ -35,7 +35,8 @@ class PlaceSearch(base.AbstractSearch): """ SEARCH_PRIO = 1 - def __init__(self, extra_penalty: float, sdata: SearchData, expected_count: int) -> None: + def __init__(self, extra_penalty: float, sdata: SearchData, + expected_count: int, has_address_terms: bool) -> None: assert not sdata.housenumbers super().__init__(sdata.penalty + extra_penalty) self.countries = sdata.countries @@ -44,6 +45,7 @@ class PlaceSearch(base.AbstractSearch): self.lookups = sdata.lookups self.rankings = sdata.rankings self.expected_count = expected_count + self.has_address_terms = has_address_terms def _inner_search_name_cte(self, conn: SearchConnection, details: SearchDetails) -> 'sa.CTE': @@ -148,14 +150,19 @@ class PlaceSearch(base.AbstractSearch): penalty: SaExpression = tsearch.c.penalty if self.postcodes: - tpc = conn.t.postcode - pcs = self.postcodes.values - - pc_near = sa.select(sa.func.min(tpc.c.geometry.ST_Distance(t.c.centroid)))\ - .where(tpc.c.postcode.in_(pcs))\ - .scalar_subquery() - penalty += sa.case((t.c.postcode.in_(pcs), 0.0), - else_=sa.func.coalesce(pc_near, cast(SaColumn, 2.0))) + if self.has_address_terms: + tpc = conn.t.postcode + pcs = self.postcodes.values + + pc_near = sa.select(sa.func.min(tpc.c.geometry.ST_Distance(t.c.centroid)))\ + .where(tpc.c.postcode.in_(pcs))\ + .scalar_subquery() + penalty += sa.case((t.c.postcode.in_(pcs), 0.0), + else_=sa.func.coalesce(pc_near, cast(SaColumn, 2.0))) + else: + # High penalty if the postcode is not an exact match. + # The postcode search needs to get priority here. + penalty += sa.case((t.c.postcode.in_(self.postcodes.values), 0.0), else_=1.0) if details.viewbox is not None and not details.bounded_viewbox: penalty += sa.case((t.c.geometry.intersects(VIEWBOX_PARAM, use_index=False), 0.0), diff --git a/test/python/api/search/test_search_address.py b/test/python/api/search/test_search_address.py index fba590bc..025459af 100644 --- a/test/python/api/search/test_search_address.py +++ b/test/python/api/search/test_search_address.py @@ -20,7 +20,7 @@ APIOPTIONS = ['search'] def run_search(apiobj, frontend, global_penalty, lookup, ranking, count=2, - hnrs=[], pcs=[], ccodes=[], quals=[], + hnrs=[], pcs=[], ccodes=[], quals=[], has_address=False, details=SearchDetails()): class MySearchData: penalty = global_penalty @@ -31,7 +31,7 @@ def run_search(apiobj, frontend, global_penalty, lookup, ranking, count=2, lookups = lookup rankings = ranking - search = AddressSearch(0.0, MySearchData(), count) + search = AddressSearch(0.0, MySearchData(), count, has_address) if frontend is None: api = apiobj diff --git a/test/python/api/search/test_search_near.py b/test/python/api/search/test_search_near.py index e9650168..76592c89 100644 --- a/test/python/api/search/test_search_near.py +++ b/test/python/api/search/test_search_near.py @@ -32,7 +32,7 @@ def run_search(apiobj, frontend, global_penalty, cat, cat_penalty=None, ccodes=[ if ccodes is not None: details.countries = ccodes - place_search = PlaceSearch(0.0, PlaceSearchData(), 2) + place_search = PlaceSearch(0.0, PlaceSearchData(), 2, False) if cat_penalty is None: cat_penalty = [0.0] * len(cat) diff --git a/test/python/api/search/test_search_places.py b/test/python/api/search/test_search_places.py index 80a63773..31d1a778 100644 --- a/test/python/api/search/test_search_places.py +++ b/test/python/api/search/test_search_places.py @@ -22,7 +22,7 @@ APIOPTIONS = ['search'] def run_search(apiobj, frontend, global_penalty, lookup, ranking, count=2, - pcs=[], ccodes=[], quals=[], + pcs=[], ccodes=[], quals=[], has_address=False, details=SearchDetails()): class MySearchData: penalty = global_penalty @@ -33,7 +33,7 @@ def run_search(apiobj, frontend, global_penalty, lookup, ranking, count=2, rankings = ranking housenumbers = None - search = PlaceSearch(0.0, MySearchData(), count) + search = PlaceSearch(0.0, MySearchData(), count, has_address) if frontend is None: api = apiobj -- 2.39.5