@@ -157,26 +157,27 @@ def _filter_by_layer(self, table: SaFromClause) -> SaColumn:
157157 include .extend (('natural' , 'water' , 'waterway' ))
158158 return table .c .class_ .in_ (tuple (include ))
159159
160- async def _find_closest_street_or_poi (self , distance : float ) -> Optional [SaRow ]:
161- """ Look up the closest rank 26+ place in the database, which
162- is closer than the given distance.
160+ async def _find_closest_street_or_pois (self , distance : float ,
161+ fuzziness : float ) -> list [SaRow ]:
162+ """ Look up the closest rank 26+ place in the database.
163+ The function finds the object that is closest to the reverse
164+ search point as well as all objects within 'fuzziness' distance
165+ to that best result.
163166 """
164167 t = self .conn .t .placex
165168
166169 # PostgreSQL must not get the distance as a parameter because
167170 # there is a danger it won't be able to properly estimate index use
168171 # when used with prepared statements
169- diststr = sa .text (f"{ distance } " )
172+ diststr = sa .text (f"{ distance + fuzziness } " )
170173
171174 sql : SaLambdaSelect = sa .lambda_stmt (
172175 lambda : _select_from_placex (t )
173176 .where (t .c .geometry .within_distance (WKT_PARAM , diststr ))
174177 .where (t .c .indexed_status == 0 )
175178 .where (t .c .linked_place_id == None )
176179 .where (sa .or_ (sa .not_ (t .c .geometry .is_area ()),
177- t .c .centroid .ST_Distance (WKT_PARAM ) < diststr ))
178- .order_by ('distance' )
179- .limit (2 ))
180+ t .c .centroid .ST_Distance (WKT_PARAM ) < diststr )))
180181
181182 if self .has_geometries ():
182183 sql = self ._add_geometry_columns (sql , t .c .geometry )
@@ -198,24 +199,39 @@ async def _find_closest_street_or_poi(self, distance: float) -> Optional[SaRow]:
198199 self ._filter_by_layer (t )))
199200
200201 if not restrict :
201- return None
202-
203- sql = sql .where (sa .or_ (* restrict ))
204-
205- # If the closest object is inside an area, then check if there is a
206- # POI node nearby and return that.
207- prev_row = None
208- for row in await self .conn .execute (sql , self .bind_params ):
209- if prev_row is None :
210- if row .rank_search <= 27 or row .osm_type == 'N' or row .distance > 0 :
211- return row
212- prev_row = row
213- else :
214- if row .rank_search > 27 and row .osm_type == 'N' \
215- and row .distance < 0.0001 :
216- return row
217-
218- return prev_row
202+ return []
203+
204+ inner = sql .where (sa .or_ (* restrict )) \
205+ .add_columns (t .c .geometry .label ('_geometry' )) \
206+ .subquery ()
207+
208+ # Use a window function to get the closest results to the best result.
209+ windowed = sa .select (inner ,
210+ sa .func .first_value (inner .c .distance )
211+ .over (order_by = inner .c .distance )
212+ .label ('_min_distance' ),
213+ sa .func .first_value (inner .c ._geometry .ST_ClosestPoint (WKT_PARAM ))
214+ .over (order_by = inner .c .distance )
215+ .label ('_closest_point' ),
216+ sa .func .first_value (sa .case ((sa .or_ (inner .c .rank_search <= 27 ,
217+ inner .c .osm_type == 'N' ), None ),
218+ else_ = inner .c ._geometry ))
219+ .over (order_by = inner .c .distance )
220+ .label ('_best_geometry' )) \
221+ .subquery ()
222+
223+ outer = sa .select (* (c for c in windowed .c if not c .key .startswith ('_' )),
224+ windowed .c .centroid .ST_Distance (windowed .c ._closest_point )
225+ .label ('best_distance' ),
226+ sa .case ((sa .or_ (windowed .c ._best_geometry == None ,
227+ windowed .c .rank_search <= 27 ,
228+ windowed .c .osm_type != 'N' ), False ),
229+ else_ = windowed .c .centroid .ST_CoveredBy (windowed .c ._best_geometry ))
230+ .label ('best_inside' )) \
231+ .where (windowed .c .distance < windowed .c ._min_distance + fuzziness ) \
232+ .order_by (windowed .c .distance )
233+
234+ return list (await self .conn .execute (outer , self .bind_params ))
219235
220236 async def _find_housenumber_for_street (self , parent_place_id : int ) -> Optional [SaRow ]:
221237 t = self .conn .t .placex
@@ -301,55 +317,69 @@ async def lookup_street_poi(self) -> Tuple[Optional[SaRow], RowFunc]:
301317 """ Find a street or POI/address for the given WKT point.
302318 """
303319 log ().section ('Reverse lookup on street/address level' )
320+ row_func : RowFunc = nres .create_from_placex_row
304321 distance = 0.006
305- parent_place_id = None
306322
307- row = await self ._find_closest_street_or_poi (distance )
308- row_func : RowFunc = nres .create_from_placex_row
309- log ().var_dump ('Result (street/building)' , row )
310-
311- # If the closest result was a street, but an address was requested,
312- # check for a housenumber nearby which is part of the street.
313- if row is not None :
314- if self .max_rank > 27 \
315- and self .layer_enabled (DataLayer .ADDRESS ) \
316- and row .rank_address <= 27 :
317- distance = 0.001
318- parent_place_id = row .place_id
319- log ().comment ('Find housenumber for street' )
320- addr_row = await self ._find_housenumber_for_street (parent_place_id )
321- log ().var_dump ('Result (street housenumber)' , addr_row )
322-
323- if addr_row is not None :
324- row = addr_row
325- row_func = nres .create_from_placex_row
326- distance = addr_row .distance
327- elif row .country_code == 'us' and parent_place_id is not None :
328- log ().comment ('Find TIGER housenumber for street' )
329- addr_row = await self ._find_tiger_number_for_street (parent_place_id )
330- log ().var_dump ('Result (street Tiger housenumber)' , addr_row )
331-
332- if addr_row is not None :
333- row_func = cast (RowFunc ,
334- functools .partial (nres .create_from_tiger_row ,
335- osm_type = row .osm_type ,
336- osm_id = row .osm_id ))
337- row = addr_row
338- else :
323+ result = None
324+ hnr_distance = None
325+ parent_street = None
326+ for row in await self ._find_closest_street_or_pois (distance , 0.001 ):
327+ if result is None :
328+ log ().var_dump ('Closest result' , row )
329+ result = row
330+ if self .max_rank > 27 \
331+ and self .layer_enabled (DataLayer .ADDRESS ) \
332+ and result .rank_address <= 27 :
333+ parent_street = result .place_id
334+ distance = 0.001
335+ else :
336+ distance = row .distance
337+ # If the closest result was a street but an address was requested,
338+ # see if we can refine the result with a housenumber closeby.
339+ elif parent_street is not None \
340+ and row .rank_address > 27 \
341+ and row .best_distance < 0.001 \
342+ and (hnr_distance is None or hnr_distance > row .best_distance ) \
343+ and row .parent_place_id == parent_street :
344+ log ().var_dump ('Housenumber to closest result' , row )
345+ result = row
346+ hnr_distance = row .best_distance
339347 distance = row .distance
348+ # If the closest object is inside an area, then check if there is
349+ # a POI nearby and return that with preference.
350+ elif result .osm_type != 'N' and result .rank_search > 27 \
351+ and result .distance == 0 \
352+ and row .best_inside :
353+ log ().var_dump ('POI near closest result area' , row )
354+ result = row
355+ break # it can't get better than that, everything else is farther away
356+
357+ # For the US also check the TIGER data, when no housenumber/POI was found.
358+ if result is not None and parent_street is not None and hnr_distance is None \
359+ and result .country_code == 'us' :
360+ log ().comment ('Find TIGER housenumber for street' )
361+ addr_row = await self ._find_tiger_number_for_street (parent_street )
362+ log ().var_dump ('Result (street Tiger housenumber)' , addr_row )
363+
364+ if addr_row is not None :
365+ row_func = cast (RowFunc ,
366+ functools .partial (nres .create_from_tiger_row ,
367+ osm_type = row .osm_type ,
368+ osm_id = row .osm_id ))
369+ result = addr_row
340370
341371 # Check for an interpolation that is either closer than our result
342372 # or belongs to a close street found.
343- if self .max_rank > 27 and self .layer_enabled (DataLayer .ADDRESS ):
373+ # No point in doing this when the result is already inside a building,
374+ # i.e. when the distance is already 0.
375+ if self .max_rank > 27 and self .layer_enabled (DataLayer .ADDRESS ) and distance > 0 :
344376 log ().comment ('Find interpolation for street' )
345- addr_row = await self ._find_interpolation_for_street (parent_place_id ,
346- distance )
377+ addr_row = await self ._find_interpolation_for_street (parent_street , distance )
347378 log ().var_dump ('Result (street interpolation)' , addr_row )
348379 if addr_row is not None :
349- row = addr_row
350- row_func = nres .create_from_osmline_row
380+ return addr_row , nres .create_from_osmline_row
351381
352- return row , row_func
382+ return result , row_func
353383
354384 async def _lookup_area_address (self ) -> Optional [SaRow ]:
355385 """ Lookup large addressable areas for the given WKT point.
0 commit comments