mirror of
https://github.com/osm-search/Nominatim.git
synced 2026-02-26 11:08:13 +00:00
adapt search frontend to new postcode table
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# This file is part of Nominatim. (https://nominatim.org)
|
# This file is part of Nominatim. (https://nominatim.org)
|
||||||
#
|
#
|
||||||
# Copyright (C) 2024 by the Nominatim developer community.
|
# Copyright (C) 2025 by the Nominatim developer community.
|
||||||
# For a full list of authors see the git log.
|
# For a full list of authors see the git log.
|
||||||
"""
|
"""
|
||||||
Implementation of place lookup by ID (doing many places at once).
|
Implementation of place lookup by ID (doing many places at once).
|
||||||
@@ -291,12 +291,30 @@ async def find_in_postcode(conn: SearchConnection, collector: Collector) -> bool
|
|||||||
.table_valued(sa.column('value', type_=sa.JSON))
|
.table_valued(sa.column('value', type_=sa.JSON))
|
||||||
t = conn.t.postcode
|
t = conn.t.postcode
|
||||||
sql = sa.select(pid_tab.c.value['i'].as_integer().label('_idx'),
|
sql = sa.select(pid_tab.c.value['i'].as_integer().label('_idx'),
|
||||||
t.c.place_id, t.c.parent_place_id,
|
t.c.osm_id, t.c.place_id, t.c.parent_place_id,
|
||||||
t.c.rank_search, t.c.rank_address,
|
t.c.rank_search,
|
||||||
t.c.indexed_date, t.c.postcode, t.c.country_code,
|
t.c.indexed_date, t.c.postcode, t.c.country_code,
|
||||||
t.c.geometry.label('centroid'))\
|
t.c.centroid)\
|
||||||
.where(t.c.place_id == pid_tab.c.value['id'].as_string().cast(sa.BigInteger))
|
.where(t.c.place_id == pid_tab.c.value['id'].as_string().cast(sa.BigInteger))
|
||||||
|
|
||||||
|
if await collector.add_rows_from_sql(conn, sql, t.c.geometry,
|
||||||
|
nres.create_from_postcode_row):
|
||||||
|
return True
|
||||||
|
|
||||||
|
osm_ids = [{'i': i, 'oi': p.osm_id}
|
||||||
|
for i, p in collector.enumerate_free_osm_ids() if p.osm_type == 'R']
|
||||||
|
|
||||||
|
if osm_ids:
|
||||||
|
pid_tab = sa.func.JsonArrayEach(sa.type_coerce(osm_ids, sa.JSON))\
|
||||||
|
.table_valued(sa.column('value', type_=sa.JSON))
|
||||||
|
t = conn.t.postcode
|
||||||
|
sql = sa.select(pid_tab.c.value['i'].as_integer().label('_idx'),
|
||||||
|
t.c.osm_id, t.c.place_id, t.c.parent_place_id,
|
||||||
|
t.c.rank_search,
|
||||||
|
t.c.indexed_date, t.c.postcode, t.c.country_code,
|
||||||
|
t.c.centroid)\
|
||||||
|
.where(t.c.osm_id == pid_tab.c.value['oi'].as_string().cast(sa.BigInteger))
|
||||||
|
|
||||||
return await collector.add_rows_from_sql(conn, sql, t.c.geometry,
|
return await collector.add_rows_from_sql(conn, sql, t.c.geometry,
|
||||||
nres.create_from_postcode_row)
|
nres.create_from_postcode_row)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# This file is part of Nominatim. (https://nominatim.org)
|
# This file is part of Nominatim. (https://nominatim.org)
|
||||||
#
|
#
|
||||||
# Copyright (C) 2024 by the Nominatim developer community.
|
# Copyright (C) 2025 by the Nominatim developer community.
|
||||||
# For a full list of authors see the git log.
|
# For a full list of authors see the git log.
|
||||||
"""
|
"""
|
||||||
Dataclasses for search results and helper functions to fill them.
|
Dataclasses for search results and helper functions to fill them.
|
||||||
@@ -407,11 +407,13 @@ def create_from_postcode_row(row: SaRow, class_type: Type[BaseResultT]) -> BaseR
|
|||||||
"""
|
"""
|
||||||
return class_type(source_table=SourceTable.POSTCODE,
|
return class_type(source_table=SourceTable.POSTCODE,
|
||||||
place_id=row.place_id,
|
place_id=row.place_id,
|
||||||
|
osm_object=None if row.osm_id is None else ('R', row.osm_id),
|
||||||
parent_place_id=row.parent_place_id,
|
parent_place_id=row.parent_place_id,
|
||||||
category=('place', 'postcode'),
|
category=(('place', 'postcode') if row.osm_id is None
|
||||||
|
else ('boundary', 'postal_code')),
|
||||||
names={'ref': row.postcode},
|
names={'ref': row.postcode},
|
||||||
rank_search=row.rank_search,
|
rank_search=row.rank_search,
|
||||||
rank_address=row.rank_address,
|
rank_address=5,
|
||||||
country_code=row.country_code,
|
country_code=row.country_code,
|
||||||
centroid=Point.from_wkb(row.centroid),
|
centroid=Point.from_wkb(row.centroid),
|
||||||
geometry=_filter_geometries(row))
|
geometry=_filter_geometries(row))
|
||||||
@@ -494,17 +496,15 @@ def _get_address_lookup_id(result: BaseResultT) -> int:
|
|||||||
|
|
||||||
async def _finalize_entry(conn: SearchConnection, result: BaseResultT) -> None:
|
async def _finalize_entry(conn: SearchConnection, result: BaseResultT) -> None:
|
||||||
assert result.address_rows is not None
|
assert result.address_rows is not None
|
||||||
if result.category[0] not in ('boundary', 'place')\
|
|
||||||
or result.category[1] not in ('postal_code', 'postcode'):
|
postcode = result.postcode or (result.address and result.address.get('postcode'))
|
||||||
postcode = result.postcode
|
if postcode and ',' not in postcode and ';' not in postcode:
|
||||||
if not postcode and result.address:
|
result.address_rows.append(AddressLine(
|
||||||
postcode = result.address.get('postcode')
|
category=('place', 'postcode'),
|
||||||
if postcode and ',' not in postcode and ';' not in postcode:
|
names={'ref': postcode},
|
||||||
result.address_rows.append(AddressLine(
|
fromarea=False, isaddress=True, rank_address=5,
|
||||||
category=('place', 'postcode'),
|
distance=0.0))
|
||||||
names={'ref': postcode},
|
|
||||||
fromarea=False, isaddress=True, rank_address=5,
|
|
||||||
distance=0.0))
|
|
||||||
if result.country_code:
|
if result.country_code:
|
||||||
async def _get_country_names() -> Optional[Dict[str, str]]:
|
async def _get_country_names() -> Optional[Dict[str, str]]:
|
||||||
t = conn.t.country_name
|
t = conn.t.country_name
|
||||||
@@ -627,13 +627,6 @@ async def complete_address_details(conn: SearchConnection, results: List[BaseRes
|
|||||||
if current_result.country_code is None and row.country_code:
|
if current_result.country_code is None and row.country_code:
|
||||||
current_result.country_code = row.country_code
|
current_result.country_code = row.country_code
|
||||||
|
|
||||||
if row.type in ('postcode', 'postal_code') and location_isaddress:
|
|
||||||
if not row.fromarea or \
|
|
||||||
(current_result.address and 'postcode' in current_result.address):
|
|
||||||
location_isaddress = False
|
|
||||||
else:
|
|
||||||
current_result.postcode = None
|
|
||||||
|
|
||||||
assert current_result.address_rows is not None
|
assert current_result.address_rows is not None
|
||||||
current_result.address_rows.append(_result_row_to_address_row(row, location_isaddress))
|
current_result.address_rows.append(_result_row_to_address_row(row, location_isaddress))
|
||||||
current_rank_address = row.rank_address
|
current_rank_address = row.rank_address
|
||||||
|
|||||||
@@ -175,7 +175,8 @@ class AddressSearch(base.AbstractSearch):
|
|||||||
sql = sql.where(sa.select(tpc.c.postcode)
|
sql = sql.where(sa.select(tpc.c.postcode)
|
||||||
.where(tpc.c.postcode.in_(self.postcodes.values))
|
.where(tpc.c.postcode.in_(self.postcodes.values))
|
||||||
.where(tpc.c.country_code == t.c.country_code)
|
.where(tpc.c.country_code == t.c.country_code)
|
||||||
.where(t.c.centroid.within_distance(tpc.c.geometry, 0.4))
|
.where(t.c.centroid.intersects(tpc.c.geometry,
|
||||||
|
use_index=False))
|
||||||
.exists())
|
.exists())
|
||||||
|
|
||||||
if details.viewbox is not None:
|
if details.viewbox is not None:
|
||||||
@@ -225,7 +226,7 @@ class AddressSearch(base.AbstractSearch):
|
|||||||
tpc = conn.t.postcode
|
tpc = conn.t.postcode
|
||||||
pcs = self.postcodes.values
|
pcs = self.postcodes.values
|
||||||
|
|
||||||
pc_near = sa.select(sa.func.min(tpc.c.geometry.ST_Distance(t.c.centroid)
|
pc_near = sa.select(sa.func.min(tpc.c.centroid.ST_Distance(t.c.centroid)
|
||||||
* (tpc.c.rank_search - 19)))\
|
* (tpc.c.rank_search - 19)))\
|
||||||
.where(tpc.c.postcode.in_(pcs))\
|
.where(tpc.c.postcode.in_(pcs))\
|
||||||
.where(tpc.c.country_code == t.c.country_code)\
|
.where(tpc.c.country_code == t.c.country_code)\
|
||||||
|
|||||||
@@ -79,7 +79,8 @@ class PlaceSearch(base.AbstractSearch):
|
|||||||
tpc = conn.t.postcode
|
tpc = conn.t.postcode
|
||||||
sql = sql.where(sa.select(tpc.c.postcode)
|
sql = sql.where(sa.select(tpc.c.postcode)
|
||||||
.where(tpc.c.postcode.in_(self.postcodes.values))
|
.where(tpc.c.postcode.in_(self.postcodes.values))
|
||||||
.where(t.c.centroid.within_distance(tpc.c.geometry, 0.4))
|
.where(t.c.centroid.intersects(tpc.c.geometry,
|
||||||
|
use_index=False))
|
||||||
.exists())
|
.exists())
|
||||||
|
|
||||||
if details.viewbox is not None:
|
if details.viewbox is not None:
|
||||||
@@ -157,7 +158,7 @@ class PlaceSearch(base.AbstractSearch):
|
|||||||
tpc = conn.t.postcode
|
tpc = conn.t.postcode
|
||||||
pcs = self.postcodes.values
|
pcs = self.postcodes.values
|
||||||
|
|
||||||
pc_near = sa.select(sa.func.min(tpc.c.geometry.ST_Distance(t.c.centroid)))\
|
pc_near = sa.select(sa.func.min(tpc.c.centroid.ST_Distance(t.c.centroid)))\
|
||||||
.where(tpc.c.postcode.in_(pcs))\
|
.where(tpc.c.postcode.in_(pcs))\
|
||||||
.scalar_subquery()
|
.scalar_subquery()
|
||||||
penalty += sa.case((t.c.postcode.in_(pcs), 0.0),
|
penalty += sa.case((t.c.postcode.in_(pcs), 0.0),
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from . import base
|
|||||||
from ...typing import SaBind, SaExpression
|
from ...typing import SaBind, SaExpression
|
||||||
from ...sql.sqlalchemy_types import Geometry, IntArray
|
from ...sql.sqlalchemy_types import Geometry, IntArray
|
||||||
from ...connection import SearchConnection
|
from ...connection import SearchConnection
|
||||||
from ...types import SearchDetails, Bbox
|
from ...types import SearchDetails
|
||||||
from ... import results as nres
|
from ... import results as nres
|
||||||
from ..db_search_fields import SearchData
|
from ..db_search_fields import SearchData
|
||||||
|
|
||||||
@@ -42,10 +42,9 @@ class PostcodeSearch(base.AbstractSearch):
|
|||||||
t = conn.t.postcode
|
t = conn.t.postcode
|
||||||
pcs = self.postcodes.values
|
pcs = self.postcodes.values
|
||||||
|
|
||||||
sql = sa.select(t.c.place_id, t.c.parent_place_id,
|
sql = sa.select(t.c.place_id, t.c.parent_place_id, t.c.osm_id,
|
||||||
t.c.rank_search, t.c.rank_address,
|
t.c.rank_search, t.c.postcode, t.c.country_code,
|
||||||
t.c.postcode, t.c.country_code,
|
t.c.centroid)\
|
||||||
t.c.geometry.label('centroid'))\
|
|
||||||
.where(t.c.postcode.in_(pcs))
|
.where(t.c.postcode.in_(pcs))
|
||||||
|
|
||||||
if details.geometry_output:
|
if details.geometry_output:
|
||||||
@@ -59,7 +58,7 @@ class PostcodeSearch(base.AbstractSearch):
|
|||||||
else_=1.0)
|
else_=1.0)
|
||||||
|
|
||||||
if details.near is not None:
|
if details.near is not None:
|
||||||
sql = sql.order_by(t.c.geometry.ST_Distance(NEAR_PARAM))
|
sql = sql.order_by(t.c.centroid.ST_Distance(NEAR_PARAM))
|
||||||
|
|
||||||
sql = base.filter_by_area(sql, t, details)
|
sql = base.filter_by_area(sql, t, details)
|
||||||
|
|
||||||
@@ -100,29 +99,9 @@ class PostcodeSearch(base.AbstractSearch):
|
|||||||
|
|
||||||
results = nres.SearchResults()
|
results = nres.SearchResults()
|
||||||
for row in await conn.execute(sql, bind_params):
|
for row in await conn.execute(sql, bind_params):
|
||||||
p = conn.t.placex
|
result = nres.create_from_postcode_row(row, nres.SearchResult)
|
||||||
placex_sql = base.select_placex(p)\
|
|
||||||
.add_columns(p.c.importance)\
|
|
||||||
.where(sa.text("""class = 'boundary'
|
|
||||||
AND type = 'postal_code'
|
|
||||||
AND osm_type = 'R'"""))\
|
|
||||||
.where(p.c.country_code == row.country_code)\
|
|
||||||
.where(p.c.postcode == row.postcode)\
|
|
||||||
.limit(1)
|
|
||||||
|
|
||||||
if details.geometry_output:
|
result.accuracy = row.accuracy
|
||||||
placex_sql = base.add_geometry_columns(placex_sql, p.c.geometry, details)
|
results.append(result)
|
||||||
|
|
||||||
for prow in await conn.execute(placex_sql, bind_params):
|
|
||||||
result = nres.create_from_placex_row(prow, nres.SearchResult)
|
|
||||||
if result is not None:
|
|
||||||
result.bbox = Bbox.from_wkb(prow.bbox)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
result = nres.create_from_postcode_row(row, nres.SearchResult)
|
|
||||||
|
|
||||||
if result.place_id not in details.excluded:
|
|
||||||
result.accuracy = row.accuracy
|
|
||||||
results.append(result)
|
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# This file is part of Nominatim. (https://nominatim.org)
|
# This file is part of Nominatim. (https://nominatim.org)
|
||||||
#
|
#
|
||||||
# Copyright (C) 2024 by the Nominatim developer community.
|
# Copyright (C) 2025 by the Nominatim developer community.
|
||||||
# For a full list of authors see the git log.
|
# For a full list of authors see the git log.
|
||||||
"""
|
"""
|
||||||
Custom types for SQLAlchemy.
|
Custom types for SQLAlchemy.
|
||||||
@@ -178,6 +178,8 @@ class Geometry(types.UserDefinedType): # type: ignore[type-arg]
|
|||||||
|
|
||||||
def bind_processor(self, dialect: 'sa.Dialect') -> Callable[[Any], str]:
|
def bind_processor(self, dialect: 'sa.Dialect') -> Callable[[Any], str]:
|
||||||
def process(value: Any) -> str:
|
def process(value: Any) -> str:
|
||||||
|
if value is None:
|
||||||
|
return 'null'
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# This file is part of Nominatim. (https://nominatim.org)
|
# This file is part of Nominatim. (https://nominatim.org)
|
||||||
#
|
#
|
||||||
# Copyright (C) 2024 by the Nominatim developer community.
|
# Copyright (C) 2025 by the Nominatim developer community.
|
||||||
# For a full list of authors see the git log.
|
# For a full list of authors see the git log.
|
||||||
"""
|
"""
|
||||||
Hard-coded information about tag categories.
|
Hard-coded information about tag categories.
|
||||||
@@ -20,7 +20,9 @@ def get_label_tag(category: Tuple[str, str], extratags: Optional[Mapping[str, st
|
|||||||
rank: int, country: Optional[str]) -> str:
|
rank: int, country: Optional[str]) -> str:
|
||||||
""" Create a label tag for the given place that can be used as an XML name.
|
""" Create a label tag for the given place that can be used as an XML name.
|
||||||
"""
|
"""
|
||||||
if rank < 26 and extratags and 'place' in extratags:
|
if category in (('place', 'postcode'), ('boundary', 'postal_code')):
|
||||||
|
label = 'postcode'
|
||||||
|
elif rank < 26 and extratags and 'place' in extratags:
|
||||||
label = extratags['place']
|
label = extratags['place']
|
||||||
elif rank < 26 and extratags and 'linked_place' in extratags:
|
elif rank < 26 and extratags and 'linked_place' in extratags:
|
||||||
label = extratags['linked_place']
|
label = extratags['linked_place']
|
||||||
@@ -28,8 +30,6 @@ def get_label_tag(category: Tuple[str, str], extratags: Optional[Mapping[str, st
|
|||||||
label = ADMIN_LABELS.get((country or '', rank // 2))\
|
label = ADMIN_LABELS.get((country or '', rank // 2))\
|
||||||
or ADMIN_LABELS.get(('', rank // 2))\
|
or ADMIN_LABELS.get(('', rank // 2))\
|
||||||
or 'Administrative'
|
or 'Administrative'
|
||||||
elif category[1] == 'postal_code':
|
|
||||||
label = 'postcode'
|
|
||||||
elif rank < 26:
|
elif rank < 26:
|
||||||
label = category[1] if category[1] != 'yes' else category[0]
|
label = category[1] if category[1] != 'yes' else category[0]
|
||||||
elif rank < 28:
|
elif rank < 28:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# This file is part of Nominatim. (https://nominatim.org)
|
# This file is part of Nominatim. (https://nominatim.org)
|
||||||
#
|
#
|
||||||
# Copyright (C) 2024 by the Nominatim developer community.
|
# Copyright (C) 2025 by the Nominatim developer community.
|
||||||
# For a full list of authors see the git log.
|
# For a full list of authors see the git log.
|
||||||
"""
|
"""
|
||||||
Helper function for parsing parameters and and outputting data
|
Helper function for parsing parameters and and outputting data
|
||||||
@@ -12,7 +12,7 @@ from typing import Tuple, Optional, Any, Dict, Iterable
|
|||||||
from itertools import chain
|
from itertools import chain
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from ..results import SearchResult, SearchResults, SourceTable
|
from ..results import SearchResults, SourceTable
|
||||||
from ..types import SearchDetails, GeometryFormat
|
from ..types import SearchDetails, GeometryFormat
|
||||||
|
|
||||||
|
|
||||||
@@ -106,10 +106,6 @@ def deduplicate_results(results: SearchResults, max_results: int) -> SearchResul
|
|||||||
classification_done = set()
|
classification_done = set()
|
||||||
deduped = SearchResults()
|
deduped = SearchResults()
|
||||||
for result in results:
|
for result in results:
|
||||||
if result.source_table == SourceTable.POSTCODE:
|
|
||||||
assert result.names and 'ref' in result.names
|
|
||||||
if any(_is_postcode_relation_for(r, result.names['ref']) for r in results):
|
|
||||||
continue
|
|
||||||
if result.source_table == SourceTable.PLACEX:
|
if result.source_table == SourceTable.PLACEX:
|
||||||
classification = (result.osm_object[0] if result.osm_object else None,
|
classification = (result.osm_object[0] if result.osm_object else None,
|
||||||
result.category,
|
result.category,
|
||||||
@@ -128,15 +124,6 @@ def deduplicate_results(results: SearchResults, max_results: int) -> SearchResul
|
|||||||
return deduped
|
return deduped
|
||||||
|
|
||||||
|
|
||||||
def _is_postcode_relation_for(result: SearchResult, postcode: str) -> bool:
|
|
||||||
return result.source_table == SourceTable.PLACEX \
|
|
||||||
and result.osm_object is not None \
|
|
||||||
and result.osm_object[0] == 'R' \
|
|
||||||
and result.category == ('boundary', 'postal_code') \
|
|
||||||
and result.names is not None \
|
|
||||||
and result.names.get('ref') == postcode
|
|
||||||
|
|
||||||
|
|
||||||
def _deg(axis: str) -> str:
|
def _deg(axis: str) -> str:
|
||||||
return f"(?P<{axis}_deg>\\d+\\.\\d+)°?"
|
return f"(?P<{axis}_deg>\\d+\\.\\d+)°?"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user