1 -- SPDX-License-Identifier: GPL-2.0-only
 
   3 -- This file is part of Nominatim. (https://nominatim.org)
 
   5 -- Copyright (C) 2022 by the Nominatim developer community.
 
   6 -- For a full list of authors see the git log.
 
   8 -- Functions for returning address information for a place.
 
  10 DROP TYPE IF EXISTS addressline CASCADE;
 
  11 CREATE TYPE addressline as (
 
  27 CREATE OR REPLACE FUNCTION get_name_by_language(name hstore, languagepref TEXT[])
 
  37   FOR j IN 1..array_upper(languagepref,1) LOOP
 
  38     IF name ? languagepref[j] THEN
 
  39       result := trim(name->languagepref[j]);
 
  46   -- as a fallback - take the last element since it is the default name
 
  47   RETURN trim((avals(name))[array_length(avals(name), 1)]);
 
  50 LANGUAGE plpgsql IMMUTABLE;
 
  53 --housenumber only needed for tiger data
 
  54 CREATE OR REPLACE FUNCTION get_address_by_language(for_place_id BIGINT,
 
  71        CASE WHEN place_id = for_place_id THEN 99 ELSE rank_address END as rank_address
 
  72     FROM get_addressdata(for_place_id, housenumber)
 
  73     WHERE isaddress order by rank_address desc
 
  75     currresult := trim(get_name_by_language(location.name, languagepref));
 
  76     IF currresult != prevresult AND currresult IS NOT NULL
 
  77        AND result[(100 - location.rank_address)] IS NULL
 
  79       result[(100 - location.rank_address)] := currresult;
 
  80       prevresult := currresult;
 
  84   RETURN array_to_string(result,', ');
 
  87 LANGUAGE plpgsql STABLE;
 
  89 DROP TYPE IF EXISTS addressdata_place;
 
  90 CREATE TYPE addressdata_place AS (
 
  92   country_code VARCHAR(2),
 
 102 -- Compute the list of address parts for the given place.
 
 104 -- If in_housenumber is greator or equal 0, look for an interpolation.
 
 105 CREATE OR REPLACE FUNCTION get_addressdata(in_place_id BIGINT, in_housenumber INTEGER)
 
 106   RETURNS setof addressline
 
 109   place addressdata_place;
 
 112   current_rank_address INTEGER;
 
 113   location_isaddress BOOLEAN;
 
 115   -- The place in question might not have a direct entry in place_addressline.
 
 116   -- Look for the parent of such places then and save it in place.
 
 118   -- first query osmline (interpolation lines)
 
 119   IF in_housenumber >= 0 THEN
 
 120     SELECT parent_place_id as place_id, country_code,
 
 121            in_housenumber as housenumber, postcode,
 
 122            'place' as class, 'house' as type,
 
 123            null as name, null as address,
 
 124            ST_Centroid(linegeo) as centroid
 
 126       FROM location_property_osmline
 
 127       WHERE place_id = in_place_id
 
 128             AND in_housenumber between startnumber and endnumber;
 
 131   --then query tiger data
 
 132   {% if config.get_bool('USE_US_TIGER_DATA') %}
 
 133   IF place IS NULL AND in_housenumber >= 0 THEN
 
 134     SELECT parent_place_id as place_id, 'us' as country_code,
 
 135            in_housenumber as housenumber, postcode,
 
 136            'place' as class, 'house' as type,
 
 137            null as name, null as address,
 
 138            ST_Centroid(linegeo) as centroid
 
 140       FROM location_property_tiger
 
 141       WHERE place_id = in_place_id
 
 142             AND in_housenumber between startnumber and endnumber;
 
 147   IF place IS NULL THEN
 
 148     SELECT parent_place_id as place_id, country_code,
 
 149            null::text as housenumber, postcode,
 
 150            'place' as class, 'postcode' as type,
 
 151            null as name, null as address,
 
 154       FROM location_postcode
 
 155       WHERE place_id = in_place_id;
 
 158   -- POI objects in the placex table
 
 159   IF place IS NULL THEN
 
 160     SELECT parent_place_id as place_id, country_code,
 
 161            coalesce(address->'housenumber',
 
 162                     address->'streetnumber',
 
 163                     address->'conscriptionnumber')::text as housenumber,
 
 170       WHERE place_id = in_place_id and rank_search > 27;
 
 173   -- If place is still NULL at this point then the object has its own
 
 174   -- entry in place_address line. However, still check if there is not linked
 
 175   -- place we should be using instead.
 
 176   IF place IS NULL THEN
 
 177     select coalesce(linked_place_id, place_id) as place_id,  country_code,
 
 178            null::text as housenumber, postcode,
 
 180            null as name, address,
 
 183       FROM placex where place_id = in_place_id;
 
 186 --RAISE WARNING '% % % %',searchcountrycode, searchhousenumber, searchpostcode;
 
 188   -- --- Return the record for the base entry.
 
 191     SELECT placex.place_id, osm_type, osm_id, name,
 
 192            coalesce(extratags->'linked_place', extratags->'place') as place_type,
 
 193            class, type, admin_level,
 
 194            CASE WHEN rank_address = 0 THEN 100
 
 195                 WHEN rank_address = 11 THEN 5
 
 196                 ELSE rank_address END as rank_address,
 
 199       WHERE place_id = place.place_id
 
 201 --RAISE WARNING '%',location;
 
 202     -- mix in default names for countries
 
 203     IF location.rank_address = 4 and place.country_code is not NULL THEN
 
 205         SELECT coalesce(name, ''::hstore) as name FROM country_name
 
 206           WHERE country_code = place.country_code LIMIT 1
 
 208         place.name := country.name || place.name;
 
 212     IF location.rank_address < 4 THEN
 
 213       -- no country locations for ranks higher than country
 
 214       place.country_code := NULL::varchar(2);
 
 215     ELSEIF place.country_code IS NULL AND location.country_code IS NOT NULL THEN
 
 216       place.country_code := location.country_code;
 
 219     RETURN NEXT ROW(location.place_id, location.osm_type, location.osm_id,
 
 220                     location.name, location.class, location.type,
 
 222                     location.admin_level, true,
 
 223                     location.type not in ('postcode', 'postal_code'),
 
 224                     location.rank_address, 0)::addressline;
 
 226     current_rank_address := location.rank_address;
 
 229   -- --- Return records for address parts.
 
 232     SELECT placex.place_id, osm_type, osm_id, name, class, type,
 
 233            coalesce(extratags->'linked_place', extratags->'place') as place_type,
 
 234            admin_level, fromarea, isaddress,
 
 235            CASE WHEN rank_address = 11 THEN 5 ELSE rank_address END as rank_address,
 
 236            distance, country_code, postcode
 
 237       FROM place_addressline join placex on (address_place_id = placex.place_id)
 
 238       WHERE place_addressline.place_id IN (place.place_id, in_place_id)
 
 239             AND linked_place_id is null
 
 240             AND (placex.country_code IS NULL OR place.country_code IS NULL
 
 241                  OR placex.country_code = place.country_code)
 
 242       ORDER BY rank_address desc,
 
 243                (place_addressline.place_id = in_place_id) desc,
 
 244                (CASE WHEN coalesce((avals(name) && avals(place.address)), False) THEN 2
 
 245                      WHEN isaddress THEN 0
 
 247                           and place.centroid is not null
 
 248                           and ST_Contains(geometry, place.centroid) THEN 1
 
 250                fromarea desc, distance asc, rank_search desc
 
 252     -- RAISE WARNING '%',location;
 
 253     location_isaddress := location.rank_address != current_rank_address;
 
 255     IF place.country_code IS NULL AND location.country_code IS NOT NULL THEN
 
 256       place.country_code := location.country_code;
 
 258     IF location.type in ('postcode', 'postal_code')
 
 259        AND place.postcode is not null
 
 261       -- If the place had a postcode assigned, take this one only
 
 262       -- into consideration when it is an area and the place does not have
 
 263       -- a postcode itself.
 
 264       IF location.fromarea AND location.isaddress
 
 265          AND (place.address is null or not place.address ? 'postcode')
 
 267         place.postcode := null; -- remove the less exact postcode
 
 269         location_isaddress := false;
 
 272     RETURN NEXT ROW(location.place_id, location.osm_type, location.osm_id,
 
 273                     location.name, location.class, location.type,
 
 275                     location.admin_level, location.fromarea,
 
 277                     location.rank_address,
 
 278                     location.distance)::addressline;
 
 280     current_rank_address := location.rank_address;
 
 283   -- If no country was included yet, add the name information from country_name.
 
 284   IF current_rank_address > 4 THEN
 
 286       SELECT name || coalesce(derived_name, ''::hstore) as name FROM country_name
 
 287         WHERE country_code = place.country_code LIMIT 1
 
 289 --RAISE WARNING '% % %',current_rank_address,searchcountrycode,countryname;
 
 290       RETURN NEXT ROW(null, null, null, location.name, 'place', 'country', NULL,
 
 291                       null, true, true, 4, 0)::addressline;
 
 295   -- Finally add some artificial rows.
 
 296   IF place.country_code IS NOT NULL THEN
 
 297     location := ROW(null, null, null, hstore('ref', place.country_code),
 
 298                     'place', 'country_code', null, null, true, false, 4, 0)::addressline;
 
 299     RETURN NEXT location;
 
 302   IF place.name IS NOT NULL THEN
 
 303     location := ROW(in_place_id, null, null, place.name, place.class,
 
 304                     place.type, null, null, true, true, 29, 0)::addressline;
 
 305     RETURN NEXT location;
 
 308   IF place.housenumber IS NOT NULL THEN
 
 309     location := ROW(null, null, null, hstore('ref', place.housenumber),
 
 310                     'place', 'house_number', null, null, true, true, 28, 0)::addressline;
 
 311     RETURN NEXT location;
 
 314   IF place.address is not null and place.address ? '_unlisted_place' THEN
 
 315     RETURN NEXT ROW(null, null, null, hstore('name', place.address->'_unlisted_place'),
 
 316                     'place', 'locality', null, null, true, true, 25, 0)::addressline;
 
 319   IF place.postcode is not null THEN
 
 320     location := ROW(null, null, null, hstore('ref', place.postcode), 'place',
 
 321                     'postcode', null, null, false, true, 5, 0)::addressline;
 
 322     RETURN NEXT location;
 
 328 LANGUAGE plpgsql STABLE;