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.
 
 190   current_rank_address := 1000;
 
 192     SELECT placex.place_id, osm_type, osm_id, name,
 
 193            coalesce(extratags->'linked_place', extratags->'place') as place_type,
 
 194            class, type, admin_level,
 
 195            CASE WHEN rank_address = 0 THEN 100
 
 196                 WHEN rank_address = 11 THEN 5
 
 197                 ELSE rank_address END as rank_address,
 
 200       WHERE place_id = place.place_id
 
 202 --RAISE WARNING '%',location;
 
 203     -- mix in default names for countries
 
 204     IF location.rank_address = 4 and place.country_code is not NULL THEN
 
 206         SELECT coalesce(name, ''::hstore) as name FROM country_name
 
 207           WHERE country_code = place.country_code LIMIT 1
 
 209         place.name := country.name || place.name;
 
 213     IF location.rank_address < 4 THEN
 
 214       -- no country locations for ranks higher than country
 
 215       place.country_code := NULL::varchar(2);
 
 216     ELSEIF place.country_code IS NULL AND location.country_code IS NOT NULL THEN
 
 217       place.country_code := location.country_code;
 
 220     RETURN NEXT ROW(location.place_id, location.osm_type, location.osm_id,
 
 221                     location.name, location.class, location.type,
 
 223                     location.admin_level, true,
 
 224                     location.type not in ('postcode', 'postal_code'),
 
 225                     location.rank_address, 0)::addressline;
 
 227     current_rank_address := location.rank_address;
 
 230   -- --- Return records for address parts.
 
 233     SELECT placex.place_id, osm_type, osm_id, name, class, type,
 
 234            coalesce(extratags->'linked_place', extratags->'place') as place_type,
 
 235            admin_level, fromarea, isaddress,
 
 236            CASE WHEN rank_address = 11 THEN 5 ELSE rank_address END as rank_address,
 
 237            distance, country_code, postcode
 
 238       FROM place_addressline join placex on (address_place_id = placex.place_id)
 
 239       WHERE place_addressline.place_id IN (place.place_id, in_place_id)
 
 240             AND linked_place_id is null
 
 241             AND (placex.country_code IS NULL OR place.country_code IS NULL
 
 242                  OR placex.country_code = place.country_code)
 
 243       ORDER BY rank_address desc,
 
 244                (place_addressline.place_id = in_place_id) desc,
 
 245                (CASE WHEN coalesce((avals(name) && avals(place.address)), False) THEN 2
 
 246                      WHEN isaddress THEN 0
 
 248                           and place.centroid is not null
 
 249                           and ST_Contains(geometry, place.centroid) THEN 1
 
 251                fromarea desc, distance asc, rank_search desc
 
 253     -- RAISE WARNING '%',location;
 
 254     location_isaddress := location.rank_address != current_rank_address;
 
 256     IF place.country_code IS NULL AND location.country_code IS NOT NULL THEN
 
 257       place.country_code := location.country_code;
 
 259     IF location.type in ('postcode', 'postal_code')
 
 260        AND place.postcode is not null
 
 262       -- If the place had a postcode assigned, take this one only
 
 263       -- into consideration when it is an area and the place does not have
 
 264       -- a postcode itself.
 
 265       IF location.fromarea AND location_isaddress
 
 266          AND (place.address is null or not place.address ? 'postcode')
 
 268         place.postcode := null; -- remove the less exact postcode
 
 270         location_isaddress := false;
 
 273     RETURN NEXT ROW(location.place_id, location.osm_type, location.osm_id,
 
 274                     location.name, location.class, location.type,
 
 276                     location.admin_level, location.fromarea,
 
 278                     location.rank_address,
 
 279                     location.distance)::addressline;
 
 281     current_rank_address := location.rank_address;
 
 284   -- If no country was included yet, add the name information from country_name.
 
 285   IF current_rank_address > 4 THEN
 
 287       SELECT name || coalesce(derived_name, ''::hstore) as name FROM country_name
 
 288         WHERE country_code = place.country_code LIMIT 1
 
 290 --RAISE WARNING '% % %',current_rank_address,searchcountrycode,countryname;
 
 291       RETURN NEXT ROW(null, null, null, location.name, 'place', 'country', NULL,
 
 292                       null, true, true, 4, 0)::addressline;
 
 296   -- Finally add some artificial rows.
 
 297   IF place.country_code IS NOT NULL THEN
 
 298     location := ROW(null, null, null, hstore('ref', place.country_code),
 
 299                     'place', 'country_code', null, null, true, false, 4, 0)::addressline;
 
 300     RETURN NEXT location;
 
 303   IF place.name IS NOT NULL THEN
 
 304     location := ROW(in_place_id, null, null, place.name, place.class,
 
 305                     place.type, null, null, true, true, 29, 0)::addressline;
 
 306     RETURN NEXT location;
 
 309   IF place.housenumber IS NOT NULL THEN
 
 310     location := ROW(null, null, null, hstore('ref', place.housenumber),
 
 311                     'place', 'house_number', null, null, true, true, 28, 0)::addressline;
 
 312     RETURN NEXT location;
 
 315   IF place.address is not null and place.address ? '_unlisted_place' THEN
 
 316     RETURN NEXT ROW(null, null, null, hstore('name', place.address->'_unlisted_place'),
 
 317                     'place', 'locality', null, null, true, true, 25, 0)::addressline;
 
 320   IF place.postcode is not null THEN
 
 321     location := ROW(null, null, null, hstore('ref', place.postcode), 'place',
 
 322                     'postcode', null, null, false, true, 5, 0)::addressline;
 
 323     RETURN NEXT location;
 
 324   ELSEIF place.address is not null and place.address ? 'postcode'
 
 325          and not place.address->'postcode' SIMILAR TO '%(,|;)%' THEN
 
 326     location := ROW(null, null, null, hstore('ref', place.address->'postcode'), 'place',
 
 327                     'postcode', null, null, false, true, 5, 0)::addressline;
 
 328     RETURN NEXT location;
 
 334 LANGUAGE plpgsql STABLE;